Java的匿名内部类
相关文章:
什么是匿名内部类
在开始本文之前,我们先来看一下我们的老朋友PizzaStore
这一次是怎样出现的吧:
// Pizza.java
public interface Pizza {
public void getName();
}
// PizzaStore.java
public class PizzaStore {
public Pizza getCheesePizza() {
return new Pizza() {
private int size = 5;
public void getName() {System.out.println("Got a CheesePizza of size:\t" + size);}
};
}
public static void main(String[] args) {
PizzaStore store = new PizzaStore();
Pizza pizza = store.getCheesePizza();
pizza.getName();
}
}
看过这段代码,若不了解匿名内部类,肯定会觉得一头雾水,这里究竟是要实例化出一个对象,还是想要定义一个类?如果是想定义一个类,那么这个类似乎连名字我们都找不到。所以,最终看起来,似乎这段代码的作者是正想要创建一个实现Pizza
接口的类的对象,却一拍脑瓜想起,等一下,还没有这个类的定义,然后干脆在这里插入一个类的定义算了。
其实这种奇怪的语法便是匿名内部类,其具体含义是“创建一个继承自Pizza
接口的匿名类的对象”, 通过new
表达式返回的引用被自动向上转型为对于Contents
的引用。上述匿名内部类的语法是以下形式的简化形式:
public class PizzaStore {
private class CheesePizza implements Pizza {
private int size = 5;
public void getName() {System.out.println("Got a CheesePizza of size:\t" + size);}
}
public Pizza getCheesePizza() {
return new CheesePizza();
}
public static void main(String[] args) {
PizzaStore store = new PizzaStore();
Pizza pizza = store.getCheesePizza();
pizza.getName();
}
}
另外,还有一点需要我们注意的是在匿名内部类的定义的末尾是有一个分号的。在这里,这个分号并不是用来标记匿名内部类的定义的结束的,而是包含其之前new
的一个恰好包含了这个匿名内部类的整个语句的结束标志,所以,在这里,这个分号的使用方法实则与其它普通语句末尾的分号的使用没有何区别。
带参数的构造器的匿名内部类
在这个匿名内部类中,使用了默认的构造器来生成Pizza
,接下来,我们继续介绍,如果我们需要一个有参数的构造器时,应该怎样做。
首先,我们定义一个蔬菜披萨的类VegetablePizza
作为被继承的父类。
public class VegetablePizza {
private int size;
public VegetablePizza(int size) {this.size = size;}
public void getName() {System.out.println("Got a VegetablePizza");}
public int size() {return size;}
}
可以看到,在这个类中,我们定义了一个带有参数size
的构造器,接下来,我们要在我们的披萨商店中实现继承自该类的匿名内部类。
public class PizzaStore {
public VegetablePizza getVegetablePizza(int size) {
return new VegetablePizza(size) {
public void getName() {System.out.println("Inner: Got a VegetablePizza of size:\t" + size());}
};
}
public static void main(String[] args) {
PizzaStore store = new PizzaStore();
VegetablePizza pizza = store.getVegetablePizza(5);
pizza.getName();
}
}
程序输出:
Inner: Got a VegetablePizza of size: 5
我们在PizzaStore
的类中定义了一个继承自VegetablePizza
的匿名内部类,并在类内重写了getName
方法,并在main()
中向内传入了一个值为5
的参数size
,并从输出中我们可以看到,参数被准确地传入了进去,并通过我们在匿名内部类中重写的getName()
方法将其正确地输出了出来。
在这段程序中,尽管VegetablePizza
只是一个具有具体实现的普通类,但是它依然可以被其导出类当作公共“接口”来使用。
匿名内部类与外部对象的通信
当我们定义一个匿名内部类时,可以通过外部传入的对象来对其定义的字段进行初始化。如以下例子:
public class PizzaStore {
public Pizza getPizza(final int sizeInput, final String nameInput) {
return new Pizza() {
private int size;
private String name;
{
this.size = sizeInput;
this.name = nameInput;
System.out.println("Initialized a " + name + "!");
}
public void getName() {System.out.println("Inner: Got a " + name + " of size:\t"+ size);}
};
}
public static void main(String[] args) {
PizzaStore store = new PizzaStore();
Pizza pizza = store.getPizza(5, "CheesePizza");
pizza.getName();
}
}
输出:
Initialized a CheesePizza!
Inner: Got a CheesePizza of size: 5
在上面的这段程序中,我们定义了一个Pizza
返回类型的方法getPizza()
,在这个方法中定义了一个继承自Pizza
接口的匿名内部类。
但是,我们会发现在getPizza(final int sizeInput, final String nameInput)
的方法的定义中,声明了两个final
的参数,这是为什么呢?这是因为,如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数的引用必须是final
的,如果没有这样写的话,将会得到一个编译时的错误信息。
在这里,还有一点需要注意的是,再上一个例子中的getVegetablePizza(int size)
方法中的参数size
是没有final
的前缀的,这是因为,这个参数是被传到了匿名类的基类VegetablePizza
的构造器中,而并没有在匿名类的内部被直接使用,因此不需要加final
。
而且在这个例子中,使用了一个构造代码块来对类进行初始化。这是因为,若只是简单地给一个对象的一个字段进行赋值,那么直接在类的定义中加入这几个字段即可,但是若想实现类似于构造器的行为,这时应该怎么办呢?因为在匿名类中时不可能有显式的命名构造器的,因为,不要忘了,我们的这个匿名内部类甚至没有名字!但是通过实例初始化,在内部类中构造代码块,便可以打到类似于构造器的效果,就像上面的语句System.out.println("Initialized a " + name + "!");
一样,可以在对实例的初始化的时候被执行。
所以说,对于匿名内部类来说,实例初始化的实际效果就是构造器,当然它受到了限制,其中最明显的就是我们无法对于这样的初始化方法进行重载,所以每个匿名内部类都只能有一个构造器。
最后,从最开始我们就知道,匿名内部类是通过扩展已有的类或者接口来实现的,但是匿名内部类于正规的继承相比会有些受限,因为匿名内部类既可以扩展类,也可以扩展接口,但是无法同时扩展它们,而且若用来扩展接口,那么也仅仅能扩展一个接口。