1. 程式人生 > >【Java 程式設計】嵌入類,內部類,區域性類,匿名類

【Java 程式設計】嵌入類,內部類,區域性類,匿名類

文章目錄

在程式碼中使用嵌入類,能增強程式碼的封裝性和可讀性,讓程式碼更簡潔,有效。

1. 嵌入類,內部類 ,外部類

嵌入類

在 Java 中,允許在一個類中定義另一個類,稱之為嵌入類,其中 non-static 嵌入類又稱為內部類(inner class)。外部類也可以成為包裹類。

class OuterClass{
    // outer class
    static class
StaticNestedClass{ // static nested class } class NestedClass{ // inner class } }

Static nested class

  • 靜態嵌入類不可以訪問外部類的例項成員(instance variables or methods);
  • 通過外部類名來引用,並例項化;
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

Inner class 本質上就是外部類的例項成員,因此可以訪問 Outer Class 例項(instance)的所有成員,包括私有成員。

要建立 Inner class 的例項,必須先建立一個包裹類的例項,也正因如此,內部類不能定義靜態成員(不然,該怎麼訪問呢?):

OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

2. 變數的可見性

當具有相同名稱的變數或方法出現在包裹類和內部類時,內部類的宣告會覆蓋掉包裹類的宣告,這時將不能直接通過名稱來訪問外部類中被覆蓋掉的成員,要使用詳細的訪問路徑才行。

示例

public class ShadowTest
{ public int x = 0; class InnerClass { public int x = 1; void methodInInnerClass(int x) { System.out.println("x = " + x); // input parameter: x System.out.println("this.x = " + this.x); // innerClass.x System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); // shadowTest.x } } public static void main(String... args) { ShadowTest st = new ShadowTest(); ShadowTest.FirstLevel fl = st.new InnerClass(); // 內部類物件的例項化,注意用法 fl.methodInInnerClass(23); } }

特別注意!
在內部類呼叫外部類的例項變數,可以通過 OuterClass.this 進行訪問,類似於名稱空間。

3. 區域性類

區域性類(Local Classes)是定義在程式碼塊(Block)中的類,可以理解為區域性類(對應區域性變數)。

Java 中的 Block 是指由 { } 所包裹的程式碼:

class BlockDemo {
     public static void main(String[] args) {
          boolean condition = true;
          if (condition) { // begin block 1
               System.out.println("Condition is true.");
          } // end block one
          else { // begin block 2
               System.out.println("Condition is false.");
          } // end block 2
     }
}

在程式語言中,程式碼塊與變數作用域有很大的關係,但不同語言之間,有很大的差別。比如 JavaScript、Python 允許函式巢狀,可以產生巢狀的函式作用域——閉包;而 Java 中的方法是不能巢狀的,但是類和程式碼塊可以巢狀,併產生巢狀作用域。

JavaScript 中的 { } 並不產生獨立的作用域,與 c/c++/java 不同:

if (1){var xxx=111}
console.log(xxx) // 111

Java 則可以在 { } 中定義區域性類和區域性變數,比如在方法,迴圈語句,條件語句等的 { } 中:

public class OuterClass { 
    public static void method(String arg1, String arg2) {
        final int localVariable = 10;      
        class LocalClass {
            String localNum = null;
            LocalClass(String num){
                localNum = num;
            }

            public String getNumber() {
                return localNum;
            }
        }

        LocalClass myNumber1 = new LocalClass(arg1);
        LocalClass myNumber2 = new LocalClass(arg2);

        System.out.println("First number is " + myNumber1.getNumber());
        System.out.println("Second number is " + myNumber2.getNumber());
    }

    public static void main(String... args) {
        method("123-456-7890", "456-7890");
    }
}

區域性類只能訪問 final or effectively final 的外部變數,否則編譯器會報錯:local variables referenced from an inner class must be final or effectively final

4. 匿名類

匿名類(anonymous class)讓程式碼更簡潔,它沒有名稱,在宣告的同時例項化。當局部類只使用一次時,可以考慮使用匿名類。

public class HelloWorldAnonymousClasses {
    interface HelloWorld {
        public void greet();
    }
  
    public void sayHello() {        
        HelloWorld frenchGreeting = new HelloWorld() {
            public void greet() {
                greetSomeone("tout le monde");
            }
        };
        
        frenchGreeting.greet();
    }

    public static void main(String... args) {
        HelloWorldAnonymousClasses myApp = new HelloWorldAnonymousClasses();
        myApp.sayHello();
    }            
}

5. 避免內部類的序列化

內部類、區域性類和匿名類儘量避免進行序列化,因為編譯期在編譯這些特殊的結構時,會建立 synthetic constructs,包括 classses,methods,fields 等等,它們對應的位元組碼是由 Java 編譯器在編譯過程中生成的,不同的編譯器建立的 synthetic constructs 很可能是不同的,因此在反序列化時會有相容性問題。

比如,inner class 原始碼如下:

public class OuterClass {
    public class InnerClass { }
}

InnerClass 的建構函式是隱式宣告(implicitly declared)的,但是它的建構函式需要包含特定的引數,當 Java 編譯器在編譯 InnerClass 時,就會生成相應的建構函式,大致如下:

public class OuterClass {
    public class InnerClass {
        final OuterClass parent;
        InnerClass(final OuterClass this$0) {
            parent = this$0; 
        }
    }
}

Java 程式語言在語法上允許變數名中包含 $,但是按照慣例,編碼時儘量不要這樣用。

synthetic 指的是那些由編譯器生成,但在程式碼中既沒有隱式宣告,也沒有顯式宣告的結構,比如:

public class OuterClass {
    enum Colors {
        RED, WHITE;
    }
}

在編譯時,編譯器會生成類似於下面的程式碼:

final class Colors extends java.lang.Enum<Colors> { 
    public final static Colors RED = new Colors("RED", 0);
    public final static Colors BLUE = new Colors("WHITE", 1);
 
    private final static values = new Colors[]{ RED, BLUE };
 
    
    private Colors(String name, int ordinal) {  //  the formal parameters (name, ordinal) are synthetic.
        super(name, ordinal);
    }
 
    public static Colors[] values(){ // implicitly declared
        return values;
    }
 
    public static Colors valueOf(String name){ // implicitly declared
        return (Colors)java.lang.Enum.valueOf(Colors.class, name);
    }
}

synthetic constructs 跟編譯器的行為有關,不同編譯器間不保證相容。