Java内部类

内部类

  内部类是定义在另一个类中的类,使用内部类的主要原因:

  1. 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据

  2. 内部类可以对同一个包中的其他类隐藏起来

  3. 当想要定义一个回调函数而且不想编写大量代码时,使用匿名内部类比较便捷

  内部类示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package innerClass;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
/**
* This program demonstrates the use of inner classes.
* @version 1.11 2015-05-12
* @author Cay Horstmann
*/
public class InnerClassTest
{
public static void main(String[] args)
{
TalkingClock clock = new TalkingClock(1000, true);
clock.start();
// keep program running until user selects "Ok"
JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}
/**
* A clock that prints the time in regular intervals.
*/
class TalkingClock
{
private int interval;
private boolean beep;
/**
* Constructs a talking clock
* @param interval the interval between messages (in milliseconds)
* @param beep true if the clock should beep
*/
public TalkingClock(int interval, boolean beep)
{
this.interval = interval;
this.beep = beep;
}
/**
* Starts the clock.
*/
public void start()
{
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
public class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep(); // 这里引用了外层的实例域
}
}
}

  这里内部类访问了外层类的实例域,实际上内部类的对象总有一个隐式引用,它指向了创建它的外部类对象,当然上述代码中TimPrinter类可以设置成private,因为只有TalkingClock类的方法创建了TimePrinter。当然我们还可以在外部类的外部新创建一个内部类的实例

1
2
TalkingClock j = new TalkingClock(1000, true);
TalkingClock.TimePrinter tt = j.new TimePrinter(); // 注意此事内部类中外部对象的引用即为j

  内部类是一种编译器行为,与jvm无关,编译器会把内部类翻译成用$分隔外部类名和内部类名的常规类文件,而jvm对此一无所知,例如之前内部类TimePrinter类将被翻译成TalkingClock$TimePrinter.class,如果利用反射打印内部类的变量方法信息:

1
2
3
4
5
public class TalkingClock$TimePrinter {
public TalkingClock$TimePrinter(TalkingClock);
public void actionPerformed(java.awt.event.ActionEvent);
final TalkingClock this$0;
}

  编译器为了引用外围类,声称了附加实例域this$0,编译器为内部类生成了默认的构造器:

1
2
3
public TimePrinter(TalkingClock clock) {
this$0 = clock;
}

  这里将外围对象的引用为this$0,因此内部类的actionPerformed方法。

1
2
3
4
5
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
if (this$0.beep) Toolkit.getDefaultToolkit().beep();
}

  既然内部类被翻译成古怪的常规类,但是jvm对此一无所知,那么内部类又如何获得额外的访问权限呢(比如获得外部类的私有域),我们再次利用反射查看外部类TalkingClock:

1
2
3
4
5
6
7
8
class TalkingClock
{
private int interval;
private boolean beep;
public TalkingClock(int, boolean);
static boolean access$0(TalkingClock);
public void start();
}

  可以看到可以通过调用access$0方法获得beep的值,但是这样确实存在安全隐患

局部内部类

  上例中TimePrinter累只在start方法中创建这个类型对象时使用了一次,因此可以定义一个局部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void start()
{
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
Date now = new Date();
System.out.println("At the tone, the time is " + now);
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}

  下面这个例子是局部内部类访问局部变量,注意访问的局部变量必须是final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class TalkingClock
{
/**
* Starts the clock.
* @param interval the interval between messages (in milliseconds)
* @param beep true if the clock should beep
*/
public void start(int interval, final boolean beep)
{
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
Date now = new Date();
System.out.println("At the tone, the time is " + now);
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
}

匿名内部类

  有时只是为了创建这个类的一个对象,就没有必要命名了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TalkingClock
{
/**
* Starts the clock.
* @param interval the interval between messages (in milliseconds)
* @param beep true if the clock should beep
*/
public void start(int interval, final boolean beep)
{
ActionListener listener = new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
Date now = new Date();
System.out.println("At the tone, the time is " + now);
if (beep) Toolkit.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval, listener);
t.start();
}
}

  语法格式:

1
2
3
new SuperType(construction parameters) {
inner class methods and data
}

  当然这里SuperType可以是接口,于是内部类就要实现这个接口

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package staticInnerClass;
/**
* This program demonstrates the use of static inner classes.
* @version 1.02 2015-05-12
* @author Cay Horstmann
*/
public class StaticInnerClassTest
{
public static void main(String[] args)
{
double[] d = new double[20];
for (int i = 0; i < d.length; i++)
d[i] = 100 * Math.random();
ArrayAlg.Pair p = ArrayAlg.minmax(d);
System.out.println("min = " + p.getFirst());
System.out.println("max = " + p.getSecond());
}
}
class ArrayAlg
{
/**
* A pair of floating-point numbers
*/
public static class Pair
{
private double first;
private double second;
/**
* Constructs a pair from two floating-point numbers
* @param f the first number
* @param s the second number
*/
public Pair(double f, double s)
{
first = f;
second = s;
}
/**
* Returns the first number of the pair
* @return the first number
*/
public double getFirst()
{
return first;
}
/**
* Returns the second number of the pair
* @return the second number
*/
public double getSecond()
{
return second;
}
}
/**
* Computes both the minimum and the maximum of an array
* @param values an array of floating-point numbers
* @return a pair whose first element is the minimum and whose second element
* is the maximum
*/
public static Pair minmax(double[] values)
{
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
for (double v : values)
{
if (min > v) min = v;
if (max < v) max = v;
}
return new Pair(min, max);
}
}

  本例中内部类Pair没有引用其外部类,所以这里设置为static,注意只有内部class才可能声明为static,静态内部类不能引用其外部类对象,而且这里内部类必须设置成static,因为在一个静态的minmax方法中new了Pair对象,非静态的内部类要“借助”与一个外部类对象。