--- title: Java CH5 tags: Java, OCA, OCP, SE8, InnerClass Date: 2019/11/18 2135 --- ## 內部類別 - 在一般類別中在定義一個類別,依位置可以分為: - 一般類部類別 (regular inner class) - 方法類別 (method class) - 匿名類別 (anonymous class) - 靜態內部類別 (static inner class) ### 一般類部類別 (regular inner class) - 和我們一般建立的類別一模一樣 - 一個外部類別可以有很多個內部類別,同樣的一個內部類別也可以有很多個次內部類別 - 語法: - ``` class OuterClass { class InnerClass { class SubInnerClass { ... } } } ``` - 編譯後的檔名 - 以上面為例,會產生三個檔案,以$區隔 - ```OuterClass.class``` (class OuterClass) - ```OuterClass$InnerClass.class``` (class InnerClass) - ```OuterClass$InnerClass$SubInnerClass.class``` (class SubInnerClass) - 外部類別存取內部類別 - ``` class OuterClass { class InnerClass { void mA(){ System.out.println("OuterClass.InnerClass.mA()"); } } } class test { public static void main(String args[]) { OuterClass oc = new OuterClass(); OuterClass.InnerClass ic = oc.new InnerClass(); ic.mA(); OuterClass.InnerClass ocic = new OutClass().new InnerClass(); ocic.mA(); } } ``` - 對 OuterClass 而言,可以直接寫成 InnerClass ic = new InnerClass(); (因為他本來就位於 OuterClass 內。 - 對其他的外部類別來說,InnerClass 是 OuterClass的內部類別,所以需宣告成 OuterClass.InnerClass #### 一般內部類別的成員存取關係 ``` class Outer { private static int sx=9; private int x = 7; class Inner { private int x=77; public void foo() { int x=777; System.out.println("local x = " + x); //x=777 System.out.println("Inner's x = " + this.x); //Inner's x = this.x = 77 System.out.println("Outer's x = " + Outer.this.x); //Outer's x = Outer.this.x = 7 System.out.println("Outer's sx = " + Outer.sx); //Outer's sx = 9 } } } public class test { public static void main(String[] args) { new Outer().new Inner().foo(); } } ``` Q: 為甚麼Inner可以參照到Outer.this.x?? A: 因為執行時期,Outer會提供自己的this參照給Inner所用。另外Outer.sx也可以是因為Outer.this.sx (nonstaic call static) ### 方法內部類別 (method class) - DEF:在方法中宣告的類別 - EX: ``` public class test { public static void main(String[] args) { new test().see(); } void see() { class Inner { void foo() { System.out.println("Method Inner Class"); } } Inner i=new Inner(); i.foo(); } } ``` - 建立的方法內部類別物件的程式碼**必須寫在方法類別定義之後** - 編譯之後,方法內部類別的檔名是 **test$1Inner.class**,其中**1**是流水號 #### 方法區域變數 > 不能直接存取方法內部非 Final 的區域變數 [color=#FF0FFF] > ``` public class test { public static void main(String[] args) { new test().see(); } void see() { final int fx=7; int x = 77; class Inner { void foo() { System.out.println("Local x = " + x); //error,要嘛加上final,不然刪除 System.out.println("Final fx = " + sx); } } Inner i=new Inner(); i.foo(); } } ``` ### 匿名類別 (anonymous class) > 就是沒有宣告名稱的類別,直接以{...}實作程式碼 > 適用於只會用到一次,之後便不需再使用的類別[color=#45ed40] > ``` interface Pet { String attr = "cute"; void skill(); void move(); } class test { public static void main(Stringp[] args) { Pet p = new Pet() { public void skill() { System.out.println(attr); } public void move() { System.out.println("run"); } }; //重要,記得加上分號 p.skill(); p.move(); } } ``` #### 匿名類別實體檔名 - 外部類別$流水號(1,2...n).class - 匿名類別在實作interface時,也是用implements來實作的。 - 同樣的在實作abstract class時,也會用extends來實作的。(你在反組譯匿名類別的class時會發現) - 這種寫法的出現,就是為了簡化成式的撰寫方式 - 多行的問題,在sdk1.6_10被修正了,會在編譯時期拋出例外 ### 靜態內部類別 (static innner class) > 如同加上static 的變數一樣,只是它屬於某個類別,並不是類別中的實體。[color=#45ed40] > 本身並沒有this的參考 > > 若外部類別有宣告static成員的話,因為也是被配置在Globe區,因此**外部的靜態成員與靜態內部類別**可以互相看見並存取。[color=#ff0000] > 而他也是**唯一一個內部類別**可以同時宣告static和non-static的內部類別。 > **註:一般內部類別若宣告了static成員就等於是靜態內部類別** ``` class Outer { static class Inner { public void mA() { out.println("static Inner class A."); } public static void mB() { out.println("static Inner class B."); } } } public class Test { public static void main(String[] args) { Outer.Inner oi = new Outer.Inner(); oi.mA(); oi.mB(); Outer,Inner.mB(); } } ``` #### 靜態內部類別與成員之間的關係 ``` class Outer { private int x=7; static private int sx = 9; static class Inner { private int x=77; static private int sx=99; public void mA() { out.println(sx); //99 out.println(Outer.sx); //9 out.println(x); //77 out.println(this.x); //77 out.println(Outer.this.x); //error, can't reference Outer nonstatic x //if want access Outer x, should rewrite in "new Outer().x" } public void mB() { out.println(sx); //99 out.println(Outer.sx); //9 } } } public class Test { public static void main(String[] args) { Outer.Inner oi = new Outer().Inner; oi.mA(); } } ``` ## Keyword: final - 用final宣告的類別、方法、屬性,不能以任何方式修改(只能被初始化一次) - 類別不可再被繼承 - 方法不能再被複寫 - 屬性給予定值後不可更動 - 若沒在宣告同時初始化的話,則稱為:[blank final variable] - 只能在建構子裡使用此方法 ## 物件型別轉換 - 種類 - 隱含式轉換 (Casts Up) - 範圍:大(子類別) 轉 小(父類別) - EX:多型 - 強制型轉換 (Downward Casts) - 範圍:小(父類別) 轉 大(子類別) - EX:FatherClass f = (FatherClass)new ChildrenClass(); - 在轉換時會有風險,應先用**instanceof**來判斷是否可以強制轉型 - **instanceof運算子** - 使用時機:判斷是否可以強制轉型 - 回傳值:true or false - 語法:```被轉物件變數 instanceof 欲轉類別名稱``` ## Initializer實作區塊 - 實作區塊:指的是{...} - 若單獨放在class類就是**Initializer實作區塊** - 種類 - instance initializer - static initializer ### initializer - EX:用來初始化instance物件變數 - 編譯時,會將initializer中的程式碼**複製到所有建構子中** - 假設建構子中原本就有實作的話,**則會將initializer的程式碼複製到原先程式碼的前面** ``` public class Test { String id, name; int birth; Test() {} Test(int i){} { //this is an initializer id = "A123456789"; name = "Joe"; birth = 19530423; } void getInfo() { System.out.printf("Name: %s, id: %s, birth: %d\n", name, id, birth); } public static void main(Stringp[] args) { new Test().getInfo(); //Name: Joe, id: A123456789, birth: 19530423 new Test(3).getInfo(); //Name: Joe, id: A123456789, birth: 19530423 } } ``` ### static initializer - **就是針對static成員的初始化** - 宣告在類別中,而不是在方法裡 - 用來指派或初始static變數 - **於類別載入時執行,且只執行一次** ``` static { ...... } ``` > 注意:static initialzier中,不可以存取或初始化instance的物件變數 > 但在instance initializer中,卻可以存取或初始畫static成員變數 [color=#ff0fff] > ## 覆寫 equals() 和 hashCode() ### equal() - 目的 - 讓程式設計者可以很直覺地用來做兩物件之間的比較 - **Object類別中原本就有提供 equals()** - 比的是兩個物件的**記憶體參照位置** - 與實際物件本身的數型沒有任何關係 (所以不管怎麼比較都不會是相同的,因為參照值本來就不同,除非引用同個物件) - 因此我們必須依照實際的情況,來自定什麼條件(屬性)相等,來實作(覆寫)原本的equals() ``` class Ball { String color; int num; Ball(String c, int n) { color = c; num = n; } } public class Test { public static void main(String[] args) { Ball b1 = new("red", 1); Ball b2 = new("red", 1); System.out.println(b1 == b2); //false System.out.println(b1.equals(b2)); //false,因為Ball繼承Object,使用Object::equals() } } ``` #### 覆寫equals()的基本原則 - 使用 **==** 判斷,也許要比較兩者是否來自同個物件實體 - 比較兩者是否屬於同個類別,用 **getClass()** - 用該類別的必要的成員變數來判斷,利用if-else - 將Ball類別加上我們覆寫的equals() ``` public boolean equals(Object o) { if(this == o) return true; //是否來自同個物件實體? if(o != null && getClass() == o.getClass()) { //是否視同個類別? if(o instanceof Ball) { //是否類別叫做Ball? Ball ball = (Ball)o; if(color.equals(ball.color) && num.equals(ball.num)) //屬性是否相等? return true; } } return false; } ``` #### **在覆寫equals()時,一律覆寫hashcode()** - hashcode (雜湊碼),可以想成是物件的編號 - 用來作物件定址 - 在同個應用程式執行期間,對同個物件呼叫hashcode(),其回傳值必須一樣 - 若兩物件被equals()定義為相等的話,其hashcode()也必須相等。 - 反之,兩物件不相等,**其hashcode()可以不必產生相同的解果(相同也可以)** - 對不同的物件產生不同的hashcode,有可能提升hashtable的效率 - 詳細參考[Effective Java Programming Language Guide,條款8]