內部類別

  • 在一般類別中在定義一個類別,依位置可以分為:
    • 一般類部類別 (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 的區域變數

​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)

就是沒有宣告名稱的類別,直接以{}實作程式碼
適用於只會用到一次,之後便不需再使用的類別

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,2n).class
  • 匿名類別在實作interface時,也是用implements來實作的。
  • 同樣的在實作abstract class時,也會用extends來實作的。(你在反組譯匿名類別的class時會發現)
  • 這種寫法的出現,就是為了簡化成式的撰寫方式
  • 多行的問題,在sdk1.6_10被修正了,會在編譯時期拋出例外

靜態內部類別 (static innner class)

如同加上static 的變數一樣,只是它屬於某個類別,並不是類別中的實體。
本身並沒有this的參考

若外部類別有宣告static成員的話,因為也是被配置在Globe區,因此外部的靜態成員與靜態內部類別可以互相看見並存取。
而他也是唯一一個內部類別可以同時宣告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成員變數

覆寫 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]