Java Nested Classes(內部類~第一篇英文技術文檔翻譯)

鄙人最近嘗試着翻譯了本身的第一篇英文技術文檔。
Java Nested Classes Reference From Oracle Documentationhtml

嵌套類-Nested Classes

在Java中咱們能夠在一個類的內部,再定義另一個類,其中裏面的那個類被稱爲嵌套類,示例以下。java

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}

術語:嵌套類有兩種類型:靜態和非靜態,當嵌套類被static修飾時,被稱爲靜態嵌套類(static nested classes),沒有被static修飾時的嵌套類被稱做內部類(inner classes)oracle

class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}

嵌套類是外部基類(即外部類)的成員,非靜態嵌套類(內部類)能夠獲取到外圍基類的其餘成員,其中也包括被聲明爲private的成員。靜態嵌套類則不能夠獲取基類的其餘成員。當作爲做爲外部類的成員,嵌套類能夠被定義爲private,public,protected或者package private。若是咱們須要在其餘外部類中使用內部類,則必定要將嵌套類聲明爲public或者 package private。ide

爲何使用嵌套類-Why Use Nested Classes?

使用嵌套類有如下幾個明顯的優點:this

  • 當僅會在一處用到某個類時,經過嵌套類能夠在邏輯上與基類(外部類)保持一種緊密的聯繫關係:當一個類只會在另外一個類中使用,那麼就能夠把這個類嵌入到另一個類中,可使得二者之間有着緊密的聯繫,嵌套類又稱之爲'輔助類'。
  • 經過合理的使用可使得整個包下的類定義更加的簡潔:更強的封裝性:A和B兩個類,B做爲A類的嵌套類,若是不將其中B類B類設置爲private的話,那麼B類就擁有訪問A類成員的權限。
  • 更好的可讀性和更高的可維護性:在編碼時內部的嵌套類老是須要和最外層類保持一種形式上的關聯關係。

靜態嵌套類-Static Nested Classes

靜態嵌套類不能直接引用外部基類的實例變量和實例方法,對於這樣的實例變量僅能夠經過對象引用來獲取。編碼

經過使用外圍基類名稱來獲取靜態嵌套類spa

OuterClass.StaticNestedClass

若是咱們想建立一個靜態嵌套類的對象,則可使用以下的方式翻譯

OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

內部類-Inner Classes

內部類能夠經過外部類實例,直接獲取基類對象的變量和方法,同理由於內部類是經過實例引用來和外部類創建關係的,因此在內部類中不能定義任何的靜態成員。只有當外部類實例對象被建立出來以後,才能夠實例化內部類。code

class OuterClass {
    ...
    class InnerClass {
        ...
    }
}

內部類實例只能存在於外部類實例中,而且能夠直接訪問其外部類實例的方法和字段。orm

在實例化內部類前,要先實例化外部類實例。能夠經過以下方式,經過外部對象實例來建立內部類對象。

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

內部類有兩種類型:局部類(local classes) 和 匿名類(anonymous classes).

局部類-Local Classes

局部類是一種被定義在代碼塊中的類,局部類一般時定義在方法體中。

如何聲明局部類:

能夠在任何一個方法之中定義一個局部類,如for循環中,或者在if子句中。

下面的LocalClassExample,是用來驗證兩個手機號,在這個類的validatePhoneNumber方法中,定義了一個名爲PhoneNumber的局部類。

public class LocalClassExample {
  
    static String regularExpression = "[^0-9]";
  
    public static void validatePhoneNumber(
        String phoneNumber1, String phoneNumber2) {
      
        final int numberLength = 10;
        
        // Valid in JDK 8 and later:
       
        // int numberLength = 10;
       
        class PhoneNumber {
            
            String formattedPhoneNumber = null;

            PhoneNumber(String phoneNumber){
                // numberLength = 7;
                String currentNumber = phoneNumber.replaceAll(
                  regularExpression, "");
                if (currentNumber.length() == numberLength)
                    formattedPhoneNumber = currentNumber;
                else
                    formattedPhoneNumber = null;
            }

            public String getNumber() {
                return formattedPhoneNumber;
            }
            
            // Valid in JDK 8 and later:

//            public void printOriginalNumbers() {
//                System.out.println("Original numbers are " + phoneNumber1 +
//                    " and " + phoneNumber2);
//            }
        }

        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
        
        // Valid in JDK 8 and later:

//        myNumber1.printOriginalNumbers();

        if (myNumber1.getNumber() == null) 
            System.out.println("First number is invalid");
        else
            System.out.println("First number is " + myNumber1.getNumber());
        if (myNumber2.getNumber() == null)
            System.out.println("Second number is invalid");
        else
            System.out.println("Second number is " + myNumber2.getNumber());

    }

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

經過刪除原有手機號中除0-9以外的字符後,檢查新的字符串中是否有十個數字,輸出結果以下:

First number is 1234567890
Second number is invalid

獲取外部類成員

局部類能夠獲取外部類的成員信息,在上一個例子中,PhoneNumber局部類的構造方法裏經過LocalClassExample.regularExpression,就拿到了外部類中的regularExpression成員。

另外,局部類中也能使用局部變量,可是在局部類中只能使用被final修飾後的變量,當一個局部類要使用定義在外部代碼塊中的局部變量或者參數時,他會俘獲(這個變量就是他的了)這個變量或者參數。

好比,PhoneNumber的構造方法中,可以/會,俘獲numberLength,由於這個變量在外圍塊中被聲明爲final,這樣的話numberLength 就成爲了一個被俘獲的變量了,有了主人。

可是在java 1.8版本中局部類可以使用定義在外部塊中的final或者effectively final的變量或者參數,若是一個變量或者參數的值在初始化後便不會被改變,則被稱爲effectively final。

好比在下面的代碼中,變量numberLength沒有被顯示的聲明爲final,在初始化後有在方法中又將numberLength的值修改成7:

PhoneNumber(String phoneNumber) {
    numberLength = 7;
    String currentNumber = phoneNumber.replaceAll(
        regularExpression, "");
    if (currentNumber.length() == numberLength)
        formattedPhoneNumber = currentNumber;
    else
        formattedPhoneNumber = null;
}

由於這個賦值語句numberLength = 7,變量numberLength 便再也不是 effectively final了,在這種情形下,內部類嘗試在if (currentNumber.length() == numberLength)這行代碼中獲取numberLength時,編譯器時會提示"local variables referenced from an inner class must be final or effectively final"

在java8中,若是在方法中聲明瞭局部類,那麼能夠在局部類中拿到方法的入參,就像下面的方法:

public void printOriginalNumbers() {
    System.out.println("Original numbers are " + phoneNumber1 +
        " and " + phoneNumber2);
}

局部類中的printOriginalNumbers方法獲取到了方法validatePhoneNumber中的phoneNumber1 和phoneNumber2兩個參數變量。

局部類與內部類的類似點

局部類像內部類同樣,兩者都不能定義和聲明靜態成員,在靜態方法validatePhoneNumber中定義的PhoneNumber局部類,只能引用外部類中的靜態成員。

若是將變量regularExpression定義爲非靜態,那麼在java編譯器編譯的時候會提示"non-static variable regularExpression cannot be referenced from a static context."錯誤信息。

由於要獲取外圍代碼塊中的實例成員,因此局部類不能時靜態的,因此在局部類中不能包含有靜態聲明。

不能在代碼塊中,嘗試定義或者聲明接口,由於接口本質上就是靜態的,好比下面的代碼是不能編譯成功的,由於在greetInEnglish方法內部包含有HelloThere接口:

public void greetInEnglish() {
    interface HelloThere {
       public void greet();
    }
    class EnglishHelloThere implements HelloThere {
        public void greet() {
            System.out.println("Hello " + name);
        }
    }
    HelloThere myGreeting = new EnglishHelloThere();
    myGreeting.greet();
}

固然在局部類中也不能聲明靜態方法,下面的代碼一樣,在編譯時會報"modifier 'static' is only allowed in constant variable declaration",由於EnglishGoodbye.sayGoodbye這個方法被聲明爲靜態方法了。

public void sayGoodbyeInEnglish() {
    class EnglishGoodbye {
        public static void sayGoodbye() {
            System.out.println("Bye bye");
        }
    }
    EnglishGoodbye.sayGoodbye();
}

局部類中只有變量時常量的時候,纔可能會出現有靜態成員變量的狀況,下面的代碼中有靜態成員但也能夠編譯經過,由於靜態變量EnglishGoodbye.farewell是常量。

public void sayGoodbyeInEnglish() {
    class EnglishGoodbye {
        public static final String farewell = "Bye bye";
        public void sayGoodbye() {
            System.out.println(farewell);
        }
    }
    EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye();
    myEnglishGoodbye.sayGoodbye();
}

匿名類-Anonymous Classes

匿名類可使你的代碼看上去更加的精簡,能夠在聲明一個匿名類的同時對它進行初始化,除了沒有類名之外,它跟局部類很像,對於只會使用一次的局部類的場景咱們能夠用匿名類來代替。

局部類就是一個類,而匿名類則更像是一個表達式,那麼咱們即可以在另外的表達式中使用匿名類。
下面的例子中 HelloWorldAnonymousClasses經過使用匿名類建立局部變量frenchGreeting 和spanishGreeting,經過使用局部類來建立和初始化englishGreeting。

 
public class HelloWorldAnonymousClasses {
  
    interface HelloWorld {
        public void greet();
        public void greetSomeone(String someone);
    }
  
    public void sayHello() {
        
        class EnglishGreeting implements HelloWorld {
            String name = "world";
            public void greet() {
                greetSomeone("world");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hello " + name);
            }
        }
      
        HelloWorld englishGreeting = new EnglishGreeting();
        
        HelloWorld frenchGreeting = new HelloWorld() {
            String name = "tout le monde";
            public void greet() {
                greetSomeone("tout le monde");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Salut " + name);
            }
        };
        
        HelloWorld spanishGreeting = new HelloWorld() {
            String name = "mundo";
            public void greet() {
                greetSomeone("mundo");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hola, " + name);
            }
        };
        englishGreeting.greet();
        frenchGreeting.greetSomeone("Fred");
        spanishGreeting.greet();
    }

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

如何使用和定義一個匿名類

咱們能夠經過frenchGreeting的建立過程來一探匿名類的組成。

HelloWorld frenchGreeting = new HelloWorld() {
    String name = "tout le monde";
    public void greet() {
        greetSomeone("tout le monde");
    }
    public void greetSomeone(String someone) {
        name = someone;
        System.out.println("Salut " + name);
    }
};

匿名類的組成部分

  • new 操做符
  • 要實現的接口名,或者要繼承的父類的名稱,在此例中匿名類實現了HelloWorld接口。
  • 括號,跟通常初始化一個類實例別無二致,須要填入構造方法中的構造參數,注:用匿名類實現接口時,沒有構造方法,那麼括號中不須要填參數便可。
  • 類主體,即匿名類的實現。

由於匿名類被當作表達式同樣被使用,如在定義frenchGreeting對象時,匿名類的所有定義都是該表達式的一部分, 這也解釋了爲何匿名類定義的最後要以;結尾,由於表達式以分號;結尾。

訪問外部類的局部變量、聲明和使用匿名類成員

像局部類同樣,匿名類一樣也能夠俘獲變量,對於外部區域的局部變量擁有同樣的訪問特性。

  • 匿名類能夠訪問外部其封閉類的成員
  • 匿名類沒法訪問那些不是final或者effectively final的局部變量
  • 匿名類中的聲明的類型變量,會覆蓋掉外部區域中的同名的變量

對於匿名類中的成員,匿名類具備跟局部類相同的限制

  • 不能在匿名類中聲明靜態代碼塊,或者再定義內部成員接口
  • 匿名類中僅當變量爲常量時,才能夠出現靜態成員

小結,在匿名類中能夠聲明以下內容

  • 列表項目
  • 字段
  • 額外的方法(即便不實現任何父類的方法)
  • 實例代碼塊
  • 局部類

可是,不能夠在匿名類中聲明構造方法

匿名類的一個實例

匿名類在java GUI中使用的較爲頻繁

import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
 
public class HelloWorld extends Application {
    public static void main(String[] args) {
        launch(args);
    }
    
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction(new EventHandler<ActionEvent>() {
 
            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });
        
        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

變量覆蓋問題-Shadowing

在內部類或者方法定義中聲明的變量類型跟外圍區域有相同的名稱,那麼內部的聲明會覆蓋掉外部區域中的聲明,不能直接經過變量名拿到外部區域中定義的變量,以下所示:

public class ShadowTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
        }
    }

    public static void main(String... args) {
        ShadowTest st = new ShadowTest();
        ShadowTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

輸出以下

x = 23
this.x = 1
ShadowTest.this.x = 0

示例代碼中定義了三個名爲x的變量,ShadowTest中的成員變量,內部類FirstLevel中成員變量,以及方法methodInFirstLevel中的參數。
方法methodInFirstLevel中的x會覆蓋掉內部類FirstLevel中的x。由於當你在方法methodInFirstLevel中使用變量x時,實際上使用的的是方法參數的值。
若是想引用內部類FirstLevel中的x,須要使用this關鍵字,來表明引用的時內部類中方法外圍的x。

System.out.println("this.x = " + this.x);

若是向引用最外面的基類變量x,則須要指明外部類的類名  

System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);

序列化問題-Serialization

咱們強烈不建議對內部類、局部類及匿名類,實現序列化。
當Java編譯器編譯內部類的構造方法時,會生成synthetic constructs。即一些在源碼中不曾出現過的類、方法、字段和其餘的構造方法也會被編譯出來。
synthetic constructs方式,能夠在不改變JVM的前提下,只經過java編譯器就能夠實現java的新特性。然而,不一樣的編譯器實現synthetic constructs的方式有所不一樣,這也就意味着,對於一樣的.java源碼,不一樣的編譯器會編譯出來不一樣的.class文件。
所以,對於一個內部類序列化後,使用不一樣的JRE進行反序列化的話,可能會存在兼容性的問題。

原文出處:https://www.cnblogs.com/lingyejun/p/10085629.html

相關文章
相關標籤/搜索