内部类(inner class)
内部类是定义在一个类中的类,可分为内部类、局部内部类、匿名内部类、静态内部类。
之前一直很纠结为什么需要使用内部类?主要有三个原因:
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
> 注释:回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调,就是回头调用的意思。主函数的事先干完,回头再调用传进来的那个函数
内部类 Example
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
public class InnerClassTest {
public static void main(String[] args) {
TalkingClock clock = new TalkingClock(1000,true);
clock.start();
JOptionPane.showMessageDialog(null,"确定退出?");
System.exit(0);//系统停止
}
}
class TalkingClock{
private int interval;
private boolean beep;
public TalkingClock(int interval, boolean beep) {
this.interval = interval;
this.beep = beep;
}
public void start(){
ActionListener listener = new TimePrinter();
// Timer为javax.swing包下的,不是java.util.Timer
Timer t = new Timer(interval,listener);
t.start();
}
// 内部类
public class TimePrinter implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("time is"+new Date());
if (beep)Toolkit.getDefaultToolkit().beep();// flag:1 这里引用了TalkingClock对象的域中的变量
// 这个类是所有实际类的抽象超类,抽象窗口工具包的实现的子类Toolkit类用于绑定各种组件
//*到特定的本地工具包实现。比如在windows上就是调用windows的本地工具包发声音
}
}
}
注释:flag:1 为了能让程序执行,内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。这个引用在内部类的定义中是不可见的。
通过javac -encoding utf-8 InnerClassTest.java编译后可以看见字节码文件(指定编码否则中文报错)
再使用反编译 javap -private TalkingClock$TimePrinter
并且上边的例子中
ActionListener listener = new TimePrinter();等价ActionListener listener = this.new TimePrinter();
if (beep)Toolkit.getDefaultToolkit().beep();等价if (TalkingClock1.this.beep) Toolkit.getDefaultToolkit().beep();
通常this限定词是多余的,不过,可以通过显示地命名将外围类引用设置为其他的对象。例如:如果对象TimePrinter是一个共有内部类,对于任意的语音时钟都可以构造一个TimePrinter:
TalkingClock jabberer = new TalkingClock(1000.true);
TalkingClock.TimePrinter listener = jabberer.new TimePrinter();
// 在外围类的作用域之外,可以这样引用内部类OuterClass.InnerClass
综上所述:内部类是一种编译器现象,与虚拟机无关。编译器会把内部类翻译成用$分隔外部类名与内部类名的常规类文件,而且虚拟机则对此一无所知。
局部内部类
局部内部类有一个优势,就是对外部世界可以完全隐藏起来,即使是TalkingClock类中的其他代码也不能访问它,除了start方法之外,没有任何方法知道TimePrinter类的存在。
局部内部类Example
public void start(){
// 局部内部类不能用public、private访问说明符进行声明,它的作用域只在这个局部类块中
class TimePrinter implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("time is"+new Date());
if (TalkingClock1.this.beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval,listener);
t.start();
}
注释:实际上局部类还有一个优点,就是它能访问局部变量,只是那些局部变量必须事实上为final,说明一旦赋值就不能再改变。
class TalkingClock{
// 这是Java SE8 的写法,再次之前需要用final限制如 public void start(int interval, final boolean beep)
public void start(int interval, boolean beep){
// 局部内部类不能用public、private访问说明符进行声明,它的作用域只在这个局部类块中
class TimePrinter implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("time is"+new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval,listener);
t.start();
}
}
同样编译后再反编译,可以看到进行了变量备份
匿名内部类
将局部内部类在深入一下,假如只创建这个类的一个对象,就不必命名了。
通用语法规则:
new SuperType(construction parameters){
inner class methods and data
}
匿名内部类Example
public void start(int interval, boolean beep){
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("time is"+new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval,listener);
t.start();
}
注释:习惯的做法是用匿名内部类实现事件监听和其他回调。如今最好还是用lambda表达式
public void start(int interval, boolean beep){
Timer t = new Timer(interval,event->{
System.out.println("time is"+new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
});
t.start();
}
静态内部类
有时候内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象,这就可以将内部类声明为static,以便取消产生的引用。
特例:静态内部类单例模式
public class SingleTon{
private SingleTon(){}
private static class SingleTonHoler{
private static SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstance(){
return SingleTonHoler.INSTANCE;
}
}
首先内部静态类和其成员必须是public或者protected的,public时可以直接从外部访问,protected则只能通过继承访问
如果其成员和方法都为静态的
可以直接访问,如
A.B.静态成员
A.B.静态方法
但是如果其成员和方法都不是静态的,则需要通过创建对象的方式访问
比如
public class A {
public static class B {
public String c = "";
public void D() {
}
}
}
要访问内部静态类B的成员c和方法D
则需要创建对象才行,如
A.B ab = new A.B();
String w = ab.c;
ab.D()
本文引用java核心技术 卷1,仅作学习积累