前言
本文分为以下几节
- 内部类和局部类
- 匿名内部类
- lambda表达式
在Java程序语言中,内部类、局部类、匿名内部类统称为嵌套类。何为嵌套类?--在类里面定义的类称为嵌套类。
内部类
内部类和局部类的划分是根据类定义的位置决定的,参见下面的例子:
public class MyArrayList {
private final static int SIZE = 10;
private int[] arrayOfInts = new int[SIZE];
public MyArrayList() {
for (int i = 0; i < SIZE; i++) {
arrayOfInts[i] = i;
}
}
public Iterator iterator(){
return new MyIterator();
}
public class MyIterator implements java.util.Iterator<Integer> {
private int nextIndex = 0;
public boolean hasNext() {
return (nextIndex <= SIZE - 1);
}
public Integer next() {
Integer retValue = Integer.valueOf(arrayOfInts[nextIndex]);
nextIndex += 1;
return retValue;
}
}
public static void main(String s[]) {
MyArrayList list = new MyArrayList();
Iterator it = list.iterator();
Iterator _it = list.new MyIterator();
while (it.hasNext()){
System.out.print(it.next()+" ");
}
System.out.println();
while (_it.hasNext()){
System.out.print(_it.next()+" ");
}
}
}
程序输出:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
在这个例子中我们定义了一个MyArrayList类,称为外部类。同时在这个类的Body中定义了一个MyIterator 类。MyIterator被称为内部类。
内部类具有一个特性,它可以很方便的访问外部类的字段和方法。并且对于外部世界来说具有更好的封装性。
为何我们在使用内部类的时候要依赖外部类呢?例如
MyArrayList list = new MyArrayList();
Iterator _it = list.new MyIterator();
原因很简单,看下面的图,虽然MyIterator是内部类,但是编译后便变成了一个单独的class文件。由于内部类中可能会操作到外部类的字段和方法,所以我们在new 内部类的时候必须要保证存在一个外部类的实例,并把这个实例隐式的传递给内部类(通过上面的语句编译器可以帮助我们完成传递)。

内部类也可以用static修饰,如果用static修饰那么它将只能访问外部类的static字段和方法,你可以通过Iterator _it =new MyArrayList.MyIterator();来实例化静态内部类。也可以通过MyArrayList.MyIterator.method()来直接调用静态内部类的静态方法。
局部类
再看一个例子:
public class HelloWorld {
static String regularExpression = "[^0-9]";
public static void validatePhoneNumber(
String phoneNumber1, String phoneNumber2) {
final int numberLength = 10;
//局部类 start
class PhoneNumber {
String formattedPhoneNumber;
PhoneNumber(String phoneNumber) {
// numberLength = 7;
// phoneNumber1 = "aaa";
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
public String getNumber() {
return formattedPhoneNumber;
}
public void printOriginalNumbers() {
System.out.println("Original numbers are " + phoneNumber1 +
" and " + phoneNumber2);
}
}
//局部类 end
PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
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());
//输出入参
myNumber1.printOriginalNumbers();
}
public static void main(String... args) {
validatePhoneNumber("123-456-7890", "456-7890");
}
}
这个例子中在validatePhoneNumber()方法中定义了一个类PhoneNumber ,并依靠这个类对传入的参数(号码)进行了操作。我们将PhoneNumber 称为局部类。
可以看到局部类是定义在方法里的(准确的说是定义在块里),它不仅仅可以访问外部类字段,还可以访问方法的参数以及局部变量。
注意
- 局部类只能访问参数和局部变量,并不能为其赋值!!!!也就是说如果有语句
// numberLength = 7;
// phoneNumber1 = "aaa";
这样的语句编译器是会报错的- 局部类不能和内部类一样用static修饰,它是能否访问实例变量或静态变量完全由方法是否用static修饰决定。
为何不能为局部变量和参数赋值?
一开始我也百思不得其解,最后终于找到了答案。看下面的图,局部类同样也被编译成了两个单独的class文件。通常情况下我们认为让局部类访问局部变量和参数也没有什么问题,但是考虑一种特殊但是真实存在的情况,如果我们的局部类是个实现了Runable接口的类会怎么样呢?在方法里调用了内部类的run方法,有可能我们的方法执行完了线程类还在运行。这时候方法的栈帧已经销毁了,线程类无法操作局部变量和参数。所以局部变量和参数可以认为是复制了一份,传入局部类,局部类无法改变栈帧内的任何数据。
匿名内部类
上面已经介绍了内部类和局部类,个人认为它们是具有很多共性的,仅仅是定义的位置有些不同(一个定义在外部类的Body中,一个定义在外部类的块里)。而匿名内部类和它们是有着本质区别的,匿名内部类是存在于表达式中的,与见下面的例子:
public class HelloWorld {
public static void main(String[] args) {
Button btn = new Button();
btn.setOnAction(new Listener() {
@Override
public void onClick() {
System.out.println("Hello World!");
}
});
}
}
上面的例子只是为了简单的阐述匿名内部类的使用场景,我们在btn.setOnAction()中new 了一个匿名内部类。从语句最后的;后就可以看出,它是存在于表达式中的。Listener是接口名,类的定义和实例化在同一瞬间完成。与Class A extend Listener这种类定义方式有着本质的不同。局部类和内部类仅仅是在定义类的位置上有所不同,而匿名内部类却连定义类的方式都焕然一新。
匿名内部类可以使我们的代码更加简短,并且减少了类的数量。广泛应用于类似按钮的回调函数中。因为每个按钮的回调内容都不相同,不可能为每个按钮的回调都定义一个类,这会使得类库十分庞大。所以匿名内部类就成功的解决了这一问题。
匿名内部类根据它存在的表达式的位置不同,也可以使用外部类的字段和方法中的局部变量。使用的规则和上述的内部类与局部类一致。
lambda表达式
尽管匿名内部类极大大简化了代码,但是还有升级的余地。难免有些人会觉得:我连个new字都不想写。见下面的例子:
public class HelloWorld {
public static void main(String[] args) {
Button btn = new Button();
btn.setOnAction(()->System.out.println("hello world"));
}
}
上面的例子是lambda表达式的实际运用,它比匿名内部类更简洁。适用于接口内只有一个方法的情形。
lambda表达式由3个部分组成:
参数列表:()
箭头:->
方法体:{}
()中包含形参,->是一个固定存在的标志符,{}中为语句,当{}中只有一条语句时{}可省略,效果等同于
{
return System.out.println("hello world");
}
参数的类型由编译时自动推断
