鄙人最近尝试着翻译了自己的第一篇英文技术文档。
Java Nested Classes Reference From Oracle Documentation
嵌套类-Nested Classes
在Java中我们可以在一个类的内部,再定义另外一个类,其中里面的那个类被称为嵌套类,示例如下。
class OuterClass { ... class NestedClass { ... } }
术语:嵌套类有两种类型:静态和非静态,当嵌套类被static修饰时,被称为静态嵌套类(static nested classes),没有被static修饰时的嵌套类被称作内部类(inner classes)
class OuterClass { ... static class StaticNestedClass { ... } class InnerClass { ... } }
嵌套类是外部基类(即外部类)的成员,非静态嵌套类(内部类)可以获取到外围基类的其他成员,其中也包括被声明为private的成员。静态嵌套类则不可以获取基类的其他成员。当做为作为外部类的成员,嵌套类可以被定义为private,public,protected或者package private。如果我们需要在其他外部类中使用内部类,则一定要将嵌套类声明为public或者 package private。
为什么使用嵌套类-Why Use Nested Classes?
使用嵌套类有以下几个明显的优势:
- 当仅会在一处用到某个类时,通过嵌套类可以在逻辑上与基类(外部类)保持一种紧密的联系关系:当一个类只会在另一个类中使用,那么就可以把这个类嵌入到另外一个类中,可以使得两者之间有着紧密的联系,嵌套类又称之为'辅助类'。
- 通过合理的使用可以使得整个包下的类定义更加的简洁:更强的封装性:A和B两个类,B作为A类的嵌套类,如果不将其中B类B类设置为private的话,那么B类就拥有访问A类成员的权限。
- 更好的可读性和更高的可维护性:在编码时内部的嵌套类总是需要和最外层类保持一种形式上的关联关系。
静态嵌套类-Static Nested Classes
静态嵌套类不能直接引用外部基类的实例变量和实例方法,对于这样的实例变量仅可以通过对象引用来获取。
通过使用外围基类名称来获取静态嵌套类
OuterClass.StaticNestedClass
如果我们想创建一个静态嵌套类的对象,则可以使用如下的方式
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
内部类-Inner Classes
内部类可以通过外部类实例,直接获取基类对象的变量和方法,同理因为内部类是通过实例引用来和外部类建立关系的,所以在内部类中不能定义任何的静态成员。只有当外部类实例对象被创建出来之后,才可以实例化内部类。
class OuterClass { ... class InnerClass { ... } }
内部类实例只能存在于外部类实例中,并且可以直接访问其外部类实例的方法和字段。
在实例化内部类前,要先实例化外部类实例。可以通过如下方式,通过外部对象实例来创建内部类对象。
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。