1. 程式人生 > >java面向對象基礎(二)

java面向對象基礎(二)

fun zoom 子類重寫 res 繼承關系 outer 關鍵字 sys ogr

權限修飾符

權限修飾符包括public、private、protected和不加任何修飾符的default,它們都可以修飾方法和變量。其中public和默認的default(不加任何修飾符)這兩個還可以修飾class。private和protected修飾類的情況只能在使用內部類時修飾,正常情況下不能使用這兩個修飾符修飾類。

(1).public:用public修飾的變量及方法,包內及包外的任何類(包括子類和普通類)均可以訪問;
(2).protected:用protected修飾的變量及方法,包內的任何類及包外那些繼承了該類的子類才能訪問,protected重點突出繼承;
(3).default

:沒有用public、protected及private中任何一種修飾,其訪問權限為default默認權限。默認訪問權限的類、類屬變量及方法,包內的任何類(包括繼承了此類的子類)都可以訪問它,而對於包外的任何類都不能訪問它(包括包外繼承了此類的子類)。default重點突出包;
(4)private: 用private修飾的變量及方法,只有本類可以訪問,而包內包外的任何類均不能訪問它。

就一句話:protected修飾符所修飾的變量和方法,只可以被子類訪問,而不管子類是不是和父類位於同一個包中。default修飾符所修飾的變量和方法,只可被同一個包中的其他類訪問,而不管其他類是不是該類的子類。protected屬於包修飾符,還是子類修飾符,而default屬於包修飾符。

從權限嚴格角度來說,private < default < protected < public

在考慮default修飾的權限時,它是包修飾符,其中沒有加入到包中的"裸體類"屬於同一個隱式的包中,因此可以互相訪問。

例如,前面的person和student的繼承關系中,將父類成員變量name加上private修飾符,於是下面的代碼將編譯出錯。因為new子類對象時,構造方法中賦值給this.name,而這個name是繼承自父類的,它是private的。因此對於子類來說,這個成員變量屬於能看到,不能引用、不能操作的擺設屬性。

class Person  {
    private String name;
    int age;

}

class Student extends Person {
    int studentID;

    Student(int id,String name,int age) {
        this.name = name;
        this.age = age;
        this.studentID = id;
    }

}

public class Inherit {
    public static void main(String[] args) {
        Student s1 = new Student(1,"Malongshuai",23);
    }
}

方法的重寫(overwrite/override)

父類定義的成員相對來說都比較粗糙,當子類繼承時,難免無法適當地描述子類。因此當子類對從父類繼承的方法不滿意時,可以重寫方法。

例如Person類能eat(),但girl類吃飯是淑女的吃,boy類吃飯是粗魯的吃。girl類很不滿意,因為父類的eat()只能描述吃,不能描述怎麽吃。於是girl類就重寫eat()方法,讓吃這個方法符合自身的淑女形象。

重寫方法必須和被重寫的方法具有相同的方法名稱、參數列表和返回類型。重寫的方法不能比被重寫的方法權限更嚴格。從方法訪問的角度來說,父類的方法都能被訪問,子類重寫後的方法卻不能被訪問,這顯然是不合理的,且即使這是能訪問父類方法,但重寫的意義就丟失了。

重寫方法時,最佳實踐方式是copy整個被重寫的方法的定義語句。因為即使重寫方法的名稱改變了,編譯也不會出錯。例如重寫eat()結果寫成了Eat(),編譯是不會有任何錯誤出現的,此時它沒有重寫,而是新定義了一個Eat()方法。

class Person  {
    String name;
    int age;

    void eat() { System.out.println("eating...");}
}

class Student extends Person {
    int studentID;

    Student(int id,String name,int age) {
        this.name = name;
        this.age = age;
        this.studentID = id;
    }

    void eat() { System.out.println("graceful eating");}  //重寫
    void study() {System.out.println("studing...");}
}

public class Inherit {
    public static void main(String[] args) {
        Student s1 = new Student(1,"Malongshuai",23);
        System.out.println(s1.studentID+","+s1.name+","+s1.age);
        s1.eat();  //調用重寫後的eat方法
    }
}

super關鍵字

this關鍵字指向對象自身,而super關鍵字則指向對象中的父對象。如下圖:

技術分享圖片

super既可以用來引用父對象的成員變量,也可以用來調用父對象的方法。例如下面的代碼:

class FatherClass {
    public int value;
    public void f(){
        value = 100;
        System.out.println("FatherClass.value="+value);
    }
}

class ChildClass extends FatherClass {
    public int value;
    public void f() {
        super.f();       //雖然f()要重寫,但父對象的f()函數還有一些用武之地來發揮余熱
        value = 200;
        System.out.println("ChildClass.value="+value);
        System.out.println(value);
        System.out.println(super.value);
    }
}

public class TestInherit {
    public static void main(String[] args) {
        ChildClass cc = new ChildClass();
        cc.f();
    }
}

new出子對象時,父類和子類中都有value屬性,它們都采用的初始化值0。當執行cc.f()時,調用子類的f()方法,該方法首先調用父對象的f()方法,父f()方法先將value賦值為100,這個value是父對象的屬性,然後回到子f()中賦值value為200,這個value是子對象自身的value,隨後輸出的兩個value都是子對象中的value屬性,最後的super.value是父對象中的value屬性。

技術分享圖片

雖然在圖中看上去super和this的地位是相同的,但實際上它們之間很不公平,不公平之處在於有引用變量(上圖中的cc)指向子對象,所以能夠使用"return this"代碼來返回一個子對象,但卻不能使用"return super"來返回子對象中的父對象,因為沒有引用變量指向父對象。

關於super調用的成員變量,需要區分清楚是子對象中的屬性還是父對象中的屬性。如果子對象和父對象中有同名屬性var,在沒有指定"this.var"和"super.var"時,僅模糊地指定var時將優先取子對象的屬性,如果子對象中沒有某屬性,則var表示的是父對象中的屬性。例如:

class Student extends Person {
    int studentID;
    int age = 33;

    Student(int id) {
        this.name = super.name + "x";
        this.age = age + 2;   //右邊的age是子對象的屬性,但如果將"int age = 33;"註釋,則age是父對象的屬性
        this.studentID = id;
    }
}

繼承時構造方法的重寫super()

子對象中總是包含父對象,這個父對象是怎麽來的?對象都是通過構造方法構造出來的,因此在new子類對象的時候,會調用對應的子類構造方法構造子對象,正是這個時候使用super()方法表示調用父類構造方法將父對象構造出來的。

在寫構造父對象的代碼時有以下幾個規則:

  1. 使用子類構造方法構造子對象時,必須要構造父對象。
  2. 子類可以在自己的構造方法中使用super(args)來調用父類的構造方法。同理,可以使用this(args)來調用本類其他的構造方法。
  3. super(args)必須寫在子類構造方法中的第一行,因為要先構造出父對象,再慢慢填補子對象自身。如果沒有顯式書寫super(args),則默認在第一行處調用父類無參數的構造方法,等價於super()。
  4. 如果子類構造方法中調用的super(args)在父類中不存在對應參數列表的構造方法,則編譯錯處。這包括沒有顯式指定super()時,且父類又重載了構造方法使得父類中沒有了無參數的構造方法時。
class Person {
    String name;
    int age;

    Person() {
        System.out.println("Person()");
    }

    Person(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person(arg1,arg2)");
    }

    Person(String name) {
        this.name = name;
        age = 20;
        System.out.println("Person(arg1)");
    } 
}

class Student extends Person {
    int studentID;

    Student(int id) {
        super("Malongshuai",23);   //第一行調用父類構造方法構造父對象,且是含有兩個參數的Person(arg1,arg2)
        this.name = super.name + "X"; //調用父對象中的name屬性
        this.age = age + 2;           //也是調用父對象中的屬性age
        this.studentID = id;
    }
}

public class TestSuper {
    public static void main(String[] args) {
        Student s1 = new Student(1);
        System.out.println(s1.studentID+", "+s1.name+", "+s1.age);
    }
}

如果將"super("Malongshuai",23);"修改為super("Malongshuai"),則表示調用父類的Person(arg1)構造方法。如果改為super(),則表示調用父類的Person()構造方法。如果省略不寫super,則等價於super()。

Object類

除了明確定義了從某個父類繼承的子類,java中的所有類都是從java.lang包中的Object類繼承來的。也就是說,Object類是所有類繼承的根,也就是它們的祖宗。一級繼承一級,最終的根總是Object類。

這個類裏提供了幾個方法,但基本上所有方法都建議重寫,因為它的級別太高,抽象化的太嚴重,它的提供的那些方法也就太大眾化。

toString()

在和對象做數據連接時,將自動調用該類的toString()方法。例如System.out.println("Hello" + Person)時,等價於System.out.println("Hello" + Person.toString())

例如:

public class TTString {
    public static void main(String [] args) {
        Person p = new Person();
        System.out.println(p);
        System.out.println(p.toString());
    }
}

class Person {}

編譯並運行,查看toString()的運行結果。

D:\myjava
λ javac TTString.java

D:\myjava
λ java TTString
Person@15db9742
Person@15db9742

toString()的結果是"類名@hex(hashcode)"。官方建議,任何子類都應該重寫該方法。例如:

public class TTString {
    public static void main(String [] args) {
        Person p = new Person();
        System.out.println(p);
    }
}

class Person {
    public String toString() {  //重寫toString()
        return "Hello World";
    }
}

對象的比較"=="和equals()

對象與對象之間是否有相等關系?一般可以認為,如果兩個對象的對象內容完全相同,將認為是相等的對象。

在進行對象比較時,"=="比較的是兩個對象的引用地址,因此兩個對象使用"=="比較時是絕對不會相等的。Object類中的equals(),基本等價於"==",因此也無法正確比較對象是否相等。所以官方手冊建議重寫equals()方法。在String類中已經重寫好了。

例如,使用String類中的equals()。

public class TTequals {
    public static void main(String [] args) {
        String s1 = new String("hello");
        String s2 = new String("hello");
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
    }
}

final關鍵字

final表示最終的意思,它可以修飾變量、方法和類。

  1. final變量的值不能被改變。
    • (1).final的成員變量不可改變。
    • (2).final的局部變量和形參不可改變。
  2. final的方法不能被重寫。
  3. final的類不能被繼承。

也就是說,要想讓變量只讀、方法不可改變或類的繼承到此結束,就用final進行修飾。

註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!

java面向對象基礎(二)