文章目录
在代码中使用嵌入类,能增强代码的封装性和可读性,让代码更简洁,有效。
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 跟编译器的行为有关,不同编译器间不保证兼容。