内部类是定义在另一个类中的类
使用内部类有三个主要原因:
内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
内部类可以对同一个包中的其他类隐藏起来
当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷
内部类的对象有一个隐私引用,它引用了实例化该内部对象 外围类对象。通过这个指针,可以访问外围类对象的全部状态
1.使用内部类访问对象状态:
例如:
public class TC{
private int interval;
private boolean beep;
public TC(int interval ,boolean beep){...}
public void start(){...}
public class TP implements ActionListener{
...
}
}
并不意味着每个TC都有一个TP实例域
public class TP implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println(....);
if(beep)...
}
}
TP中并没有定义beep的变量而是引用创建TP的TC对象的域
为了能够运行这个程序,内部类的对象总有一个隐式引用,它指向了创建它的外部类对象
所以可以这样写:
public void actionPerformed(ActionEvent event){
System.out.println(....);
if(outer.beep)...
}
外围类的引用在构造器中设置,编译器修改了所有的内部类的构造器,添加一个外围类引用的参数
因为TP类没有定义构造器,所以编译器为这个类生成了一个默认的构造器:
public TP(TK clock){
outer = clock;
}
当在start方法中创建了TP对象后,编译器就会将this引用传递给当前的语音时钟的构造器
ActionListener listener = new TP(this);
注意:只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性
2.内部类的特殊语法规则:
表示外围类引用:OuterClass.this
例如:
public void actionPerformed(ActionEvent event){
System.out.println(....);
if(TC.this.beep)...
}
反过来,可以采用下列语法格式更加明确地编写内部对象的构造器:
outerObject.new InnerClass(construction parameters)
ActionListener listener = this.new TP();
在外围类的作用域之外,可以这样引用内部类:
OuterClass.InnerClass
内部类中声明的所有静态域都必须是final
内部类不能有static方法,Java语言规范对这个限制没有做任何解释,也可以允许有,但是只能访问外围类的静态域和方法
3.内部类是否有用、必要和安全:
在Java 1.1中就增加了内部类,很多人认为这违背了Java要比C++更加简单的设计理念
内部类是一种编译器现象,与虚拟机无关。编译器将会把内部类翻译成用$分隔外部类名与内部类名的常规类文件,而虚拟机对此一无所知
例如,在TC类内部的TP类将被翻译成类文件TC$TP.class
可以通过javap -private ClassName查看:
public class TC$TP{
public TC$TP(TC);
public void actionPerformed(java.awt.event.ActionEvent);
final TC this$0;
}
编译器为了引用外围类,生成了一个附加的实例域this$0
那么,内部类如何管理那些额外 访问特权呢:
class TC{
private int interval;
private boolean beep;
public TC(int,boolean);
static boolean access$0(TC);
public void start();
}
注意编译器在外围类中添加静态方法acces$0。它将返回作为参数传递给他的对象域beep
内部类方法将调用那个方法,在TP类的actionPerformed方法中编写语句:
if(beep)就会提高下列调用的效率:if(TC.access$0(outer))
可以发现这里存在一个安全隐患,任何人可以调用access$0方法很容易地读到私有域beep
4.局部内部类:
这种情况相对于内部类只在外围类某个方法中创建这个类型的对象时使用了一次
所以可以这样写:
public void start(){
class TP implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("...");
if(beep)...
}
}
ActionListener listener = new TP();
Timer t = new Timer(1000,listener);
t.start();
}
局部类不能用public或private访问说明符进行声明,因为它的作用域被限定在声明这个局部类的块中
局部类的一个最明显的优势,即对外部世界可以完全地隐藏起来
5.由外部方法访问变量:
局部类还有一个优点,它们不仅能够访问包含它们 外部类,还可以访问局部变量,但是局部变量必须事实上为final
例如:
public void start(int interval,boolean beep){
class TP implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("...");
if(beep)...
}
}
ActionListener listener = new TP();
Timer t = new Timer(interval,listener);
t.start();
}
编译之后查看:
class TC$TP{
TC$TP(TC,boolean);
public void actionPerformed(java.awt.event.ActionEvent);
final boolean val$beep;
final TC this$0;
}
注意构造器的boolean参数和val$beep实例变量。创建一个对象时,beep会被传递到构造器,并存储在val$beep域中。编译器必须检测对局部变量的访问,为每一个变量建立相应的数据域,并将局部变量拷贝到构造器中,以便将这些数据域初始化为局部变量的副本
在Java SE 8之前,必须把从局部类访问的局部变量声明为final
6.匿名内部类:
如果只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类
public void start(int interval,boolean beep){
ActionListener listener = new ActionListener(){
public void actionPerformed(ActionEvent event){
System.out.println("...");
if(beep)...
}
}
Timer t = new Timer(interval,listener);
t.start();
}
创建一个实现ActionListener接口的类的新对象,需要实现的方法actionPerformed定义在括号{}内
因为匿名类没有类名,所以匿名类不能有构造器,取而代之的是,将构造器参数传递给超类构造器
相对于匿名类实现接口和实现超类的语法格式有所不同:
new SuperType(construction parameters){
inner class method and data
}
new InterfaceType(){
methods and data
}
有一种技巧称为“双括号初始化”,利用了内部类语法,例如构造一个数组;
ArrayList<String> friends = new ArrayList<>();
friends.add("Harry");
friends.add("Tony");
invite(friends);
如果不再需要这个数组列表,最好让它作为一个匿名列表,例如:
invite(new ArrayList<String>(){{add("Harry");add("Tony");}});
外层括号建立了ArrayList的一个匿名子类,内层括号则是一个对象构造块
7.静态内部类:
不需要内部类应用外围类对象时,可以将内部类声明为static,以便取消产生的引用
例如:
class ArrayAlg{
public static Pair minmax(double[] values){
...
return new Pair(min,max);
}
public static class Pair{
private double first;
private double second;
public Pair(double f,double s){
first = f;
second = s;
}
public double getFirst(){return first;}
public double getSecond(){return second;}
}
}
使用时:ArrayAlg.Pair p = ArrayAlg.minmax(d);
静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有的内部类完全一样
静态内部类可以有静态域和方法