本想学习一下Java中比较重要的几个关键字,然后就看到了static这个关键字,在查找资料的过程中看到了static nested class,然后就又查找了嵌套类的资料,然后源源不断、入坑不出一百年,索性就学习总结一下吧。
在这还是说个小插曲,遇到问题往往都比较喜欢去百度。但是百度的一般都是很多人的博客,如果能找到大牛的博客看还比较好,很多的博客只是图有其表,没有实质内容,很浪费时间。建议可以查找官方技术文档,还有一个推荐的网站:stackoverflow.com,是一个很好的论坛网站,技术知识介绍的比较简单易懂。当然无论是官方文档还是网站都是英文的,需要耐心和基础,学计算机,这应该是必经之路吧,坚持。
可以参考官方教程 :
Java Tutorial - Nested Classes
下面是翻译(英文水平有限,大家就嗑着瓜子喝着茶,凑合看吧!):
Java语言允许在一个类里定义另一个类,这样的一个类就叫做嵌套类,可以定义成:
class OuterClass{
...
class NestedClass{
...
}
}
术语:嵌套类可以分为两类:静态和非静态。声明成静态的嵌套类叫做静态嵌套类(static nested classes)。非静态嵌套类叫做内部类(inner classes)。
为什么要用嵌套类?
使用嵌套类的几个原因如下:
- 这是一种只用在一个位置的类的逻辑分组方式:如果一个类只对另一个类有用,那么将这个类嵌套在另一个类里可以使他们保持在一起。嵌套这样的“帮助类”可以使他们所在的包更有效率。
- 增加封装性:考虑两个顶层类,A和B,B需要获取到A中声明为private的成员。通过将类B藏在A里面的方式,类A的成员可以声明为private,并且类B也可以获取到这些成员。除此之外,类B也和外界隔绝。
- 可以产生易读性更高、可维护性更强的代码:嵌套小规模的类可以使这些代码离被用到的地方更近。
静态嵌套类(Static Nested Classes)
对于类方法和类变量,静态嵌套类可以从外部类中获取。(也就是说,静态嵌套类可以获取到外部类outer class中的类方法和类变量。)跟静态类方法一样,静态嵌套类不能直接获取定义在附属类(enclosing class)实例方法和变量:必须通过对象引用获取。
注意:一个静态嵌套类与外部类(和其他类)的实例成员的交互就和其他顶层类一样。事实上,一个静态嵌套类在行为上就是一个顶层类,只不过这个顶层类为了打包的方便而嵌套在其他顶层类中。
静态嵌套类可以通过附属类(enclosing class)名得到:
OuterClass.StaticNestedClass
例如,创建静态嵌套类的对象,可以使用语法:
OuterClass.StaticNestedClass nestedObject =
new OuterClass.StaticNestedClass();
内部类(Inner Classes)
对于实例方法和实例变量,内部类可以从附属类(enclosing class)的实例中获取,并且可以直接获取到对象的方法和字段。因为内部类是和实例进行联系,所以
在内部类中不能自定义任何静态成员。
一个内部类的实例对象存在于外部类的实例中。考虑下面类:
class OuterClass {
...
class InnerClass {
...
}
}
InnerClass的实例只存在于OuterClass类的实例中,并且可以直接获取到附属类(enclosing class)的方法和字段。在实例化一个内部类之前,必须先实例化外部类。然后,在外部类对象中创建内部类对象:
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
有两种特殊内部类:局部内部类(local classes)和匿名内部类(anonymous classes)。
覆盖(Shadowing)
假如在一个特定的范围(scope)(比如内部类和方法定义)内,一个类型(变量或者参数名)的声明与附属范围(enclosing scope)内的其他声明名字相同,那么这个声明将覆盖附属范围的声明。你不用单独使用名字来引用被覆盖声明。下面的例子证明这一点:
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
局部内部类(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除外的其他字符,然后检查手机号码是否包括10个数字。这个例子的打印结果:
First number is 1234567890
Second number is invalid
获取附属类(enclosing class)成员
一个局部类可以获取它的附属类的成员。在上一个例子中,PhoneNumber构造器获取到LocalClassExample.regularExpression。
除此之外,一个局部类可以获取局部变量。但是局部类只能获取到声明为final类型的局部变量。当一个局部类可以获取局部变量或者参数时,它可以捕获这个变量或者参数。比如,PhoneNumber构造器可以获取局部变量numberLength,因为它被声明为final,numberLength是一个可捕获的变量。
但是,从Java SE 8开始,局部类可以获取附属块中的final以及effectively final的局部变量和参数。effectively final是指一个变量或参数的值在初始化后从未发生改变。例如,假定numberLength没有被声明为final,在构造器中加上这样一句:
PhoneNumber(String phoneNumber) {
numberLength = 7;
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
由于这样一句,numberLength就不再是effectively final。结果,Java编译器就会在内部类PhoneNumber试图获取numberLength变量的地方产生这样一条错误信息,“
local variables referenced from an inner class must be final or effectively final”:
if (currentNumber.length() == numberLength)
从Java SE 8开始,声明在方法中的局部类可以获取该方法的参数。例如,可以在局部类PhoneNumber中定义这样一个方法:
public void printOriginalNumbers() {
System.out.println("Original numbers are " + phoneNumber1 +
" and " + phoneNumber2);
}
这个方法可以获取到方法validatePhoneNumber的参数phoneNumber1和phoneNumber2。
局部类和内部类相似
局部类和内部类一样都不能定义任何静态成员。定义在静态方法中的局部类只能引用附属类(enclosing class)的静态成员。例如,如果不把regularExPression定义成静态成员变量,Java编译器将产生这样一个错误“
non-static variable
regularExpression
cannot be referenced from a static context”。
局部类是非静态的,因为他们可以获取到附属块的实例成员。因此,他们不能包含大多数静态声明。
不能在一个块中声明一个接口,因为接口就是静态的。例如下面代码段不能通过编译,因为接口HelloThere定义在了greetInEnglish方法体中。
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();
}
不能在局部类中声明一个静态
initializers(不知道怎么翻译)和成员接口。下面代码将不会编译通过,因为
EnglishGoodbye.sayGoodbye被声明为静态的。Java编译器将产生这样一个错误信息,“
modifier 'static' is only allowed in constant variable declaration”。
public void sayGoodbyeInEnglish() {
class EnglishGoodbye {
public static void sayGoodbye() {
System.out.println("Bye bye");
}
}
EnglishGoodbye.sayGoodbye();
}
局部类中可以有常量形式的静态成员。(一个常量是原始类型或者String类型的变量被声明成final,在编译过程中初始化成常量表达式。编译时期常量表达式是一个在编译时生成的字符或者算术表达式。)下面代码段编译通过是因为
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的局部变量
- 和嵌套类一样,匿名类中类型声明会覆盖附属范围内有相同名字的声明
匿名类和内部类也有相同的限制:
- 不能声明静态initializers 和成员接口
- 可以存在static final声明的常量
匿名类中可以声明:
- 字段(Fields)
- 其他方法(即使没有从supertype中实现任何方法)
- instance initializers
- 局部类
但是,
不能再匿名类中声明构造器。
其实在后面还有最新Java se 8新加的lambda表达式,在这就不写了,有兴趣可以学习一下
更多记录:
- 非静态方法和类可以使用静态成员,静态方法和类不能使用非静态成员。个人猜想:
- 产生这个的原因是静态成员在类加载时只会产生一个,而非静态成员是实例化类时产生的,每个实例对象都会有各自独立的非静态成员。静态成员在类加载的时候就已经产生,而非静态成员必须使用new动态生成。静态成员可以单独使用,如果调用非静态成员,有可能这个非静态成员还没创建,这样就会造成错误。所以静态成员不能调用非静态成员。
- 而反过来就不会有这个问题,因为静态成员一定是在非静态成员创建之间就已经存在。
- 内部类不能定义静态成员,而静态嵌套类既可以定义静态成员,也可以定义非静态成员。
- 内部类是依赖于外部类的,从创建方式上就可以看出,内部类创建实例对象时必须先创建外部类的实例对象,也就是说内部类是外部类实例的一部分,必须依赖外部类实例。因此内部类中必然是不能定义静态成员的,因为静态成员不依赖实例,而依赖类。
- 上面已经介绍了静态嵌套类在行为上就和普通的类没有什么区别,之所以定义为嵌套类是因为突出嵌套类和外部类逻辑上的关系。所以,静态成员和非静态成员都可以在静态嵌套类中定义。至于为什么静态嵌套类中只能使用外部类中静态成员?因为静态嵌套类就与其他外部类一样,只能直接获取到静态成员,而其他成员必须通过实例获取。总的来说,不要以为静态嵌套类在某个类中就一定有什么联系,静态嵌套类完全可以当成在其他地方定义的类来使用。
- 在静态方法中定义的局部类只能调用外部类的静态成员和final局部变量或参数。道理和静态方法相同。
- 局部类中可以有static final声明的变量。因为在编译时期,static final声明的变量会被转化为常量,放在常量池中,这时的变量就不能当静态变量来看待了。
ps:文中的enclosing这个词不清楚怎么翻译,字典翻译是围绕,大概的意思就是包含的意思,比如外部类围绕在内部类外,也就是说外部类包含内部类。文中用 附属 可能不太准确,理解意思即可。
pps:本人也是新手上路,欢迎各位大神批评指正。