文章目录
一 内部类
- 内部类(inner class) 是定义在另一个类中的类。为什么需要使用内部类呢? 其主要原因有以下三点:
- 内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous) 内部类比较 便捷。
1.1 使用内部类访问对象状态
- 内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域.
- 内部类对象拥有一个对外围类对象的引用,这个引用在内部类的定义中是不可见的
如果有 一个 TimePrinter 类是一个常规类,它就需要通过 TalkingClock 类的公有方法访问 beep标志, 而使用内部类可以给予改进, 即不必提供仅用于访问其他类的访问器。
public class InnerClassTest {
public static void main(String[] args) {
TalkingClock clock = new TalkingClock(1000,true);
clock.start();
JOptionPane.showMessageDialog(null,"请点击!");
System.exit(0);
}
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
public class TalkingClock {
private int interval;
private boolean beep;
/** * 内部类 */
public class TimePrinter implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
public void start(){
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval,listener);
t.start();
}
public TalkingClock(int interval, boolean beep) {
this.interval = interval;
this.beep = beep;
}
public TalkingClock() {
}
}
1.2 内部类的特殊语法规则
表达式
OuterClass.this
表示外围类引用,例如,可以像下面这样编写 TimePrinter 内部类的 actionPerformed方法:
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();
}
反过来,可以采用下列语法格式更加明确地编写内部对象的构造器:
outerObject.new InnerClass{construction parameters)
public void start(){
ActionListener listener = this.new TimePrinter();
Timer t = new Timer(interval,listener);
t.start();
}
通常,this 限定词是多余的。不过,可以通过显式地命名 将外围类引用设置为其他的对象。例如, 如果 TimePrinter 是一个公有内部类,对于任意的语 音时钟都可以构造一个 TimePrinter:
TalkingClock clock = new TalkingClock(1000,true);
TalkingClock.TimePrinter printer = clock.new TimePrinter();
- 需要注意, 在外围类的作用域之外,可以这样引用内
OuterClass.InnerClass
内部类中声明的所有静态域都必须是 final。原因很简单。我们希望一个静态域只 有一个实例, 不过对于每个外部对象, 会分别有一个单独的内部类实例。如果这个域不 是 final, 它可能就不是唯一的。 内部类不能有 static 方法。Java 语言规范对这个限制没有做任何解释。也可以允许有 静态方法,但只能访问外围类的静态域和方法。显然,Java 设计者认为相对于这种复杂 性来说, 它带来的好处有些得不偿失。
1.3 局部内部类
如果仔细地阅读一下 TalkingClock 示例的代码就会发现, TimePrinter 这个类名字只在 start 方法中创建这个类型的对象时使用了一次。 当遇到这类情况时, 可以在一个方法中定义局部类。
public void start(){
class TimePrinter implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
局部类不能用 public 或 private访问说明符进行声明。它的作用域被限定在声明这个局部 类的块中。
局部类有一个优势, 即对外部世界可以完全地隐藏起来。 即使 TalkingClock 类中的其他 代码也不能访问它。除 start 方法之外, 没有任何方法知道 TimePrinter 类的存在。
与其他内部类相比较, 局部类还有一个优点。它们不仅能够访问包含它们的外部类, 还 可以访问局部变量。不过,那些局部变量必须事实上为 final。这说明,它们一旦赋值就绝不 会改变。这里, 将 TalkingClock 构造器的参数 interva丨和 beep 移至 start 方法中。
- javaSE8 可以不写final.
public class TalkingClock {
public void start(int interval,boolean beep){
class TimePrinter implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
public class InnerClassTest {
public static void main(String[] args) {
TalkingClock clock = new TalkingClock();
clock.start(1000,true);
JOptionPane.showMessageDialog(null, "请点击");
System.exit(0);
}
}
1.4 匿名内部类
将局部内部类的使用再深人一步。假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类(anonymous inner class)。
public class TalkingClock {
public void start(int interval,boolean beep){
/** * 匿名内部类 */
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval, listener);
t.start();
}
}
它的含义是:创建一个实现 ActionListener 接口的类的新 对象,需要实现的方法 actionPerformed定义在括号内。 通常的语法格式为:
new SuperType(constructionparameters)
{
inner class methodsanddata
}
SuperType 可以是 ActionListener 这样的接口, 于是内部类就要实现这个接口。 SuperType 也可以是一个类,于是内部类就要扩展它。
由于构造器的名字必须与类名相同, 而匿名类没有类名,所以,匿名类不能有构造器。 取而代之的是,将构造器参数传递给超类(superclass) 构造器。尤其是在内部类实现接口的 时候, 不能有任何构造参数。不仅如此,还要像下面这样提供一组括号:
new InterfaceType()
{
methods and data
}
多年来,Java 程序员习惯的做法是用匿名内部类实现事件监听器和其他回调。如今最好 还是使用 lambda 表达式。例如, 这一节前面给出的 start 方法用 lambda 表达式来写会简洁得 多,
public void start(int interval,boolean beep){
Timer t = new Timer(interval, event -> {
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
});
t.start();
}
1.5 静态内部类
- 什么时候使用静态内部类:
有时候, 使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用 外围类对象。为此,可以将内部类声明为 static, 以便取消产生的引用。
只有内部类可以声明为 static。静态内部类的对象除了没有对生成它的外围类对象 的引用特权外, 与其他所冇内部类完全一样.
- 实例 : 计算数组中最小值和最大值的问题。 只遍历数组一次,并能够同时计算出最小值和最 大值
public class ArrayAIg {
/** * 静态内部类 * 这个方法必须返冋两个数值, 为此, 可以定义一个包含两个值的类 Pair: */
public static class Pair{
private double first;
private double second;
public Pair(double first, double second) {
this.first = first;
this.second = second;
}
public double getFirst() {
return first;
}
public double getSecond() {
return second;
}
@Override
public String toString() {
return "Pair{" +
"first=" + first +
", second=" + second +
'}';
}
}
/** * 求一个数组中的最大和最小值 */
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);
}
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
public class StaticInnerClassTest {
public static void main(String[] args) {
double[] d = {234.3,24.5,32,1,3,2,42,2324,1,3};
// 通过ArrayAlg.Pair 访问
ArrayAIg.Pair dd = ArrayAIg.minmax(d);
System.out.println(dd.toString());
}
}
二 : 代理(跳过)
利用代理可以在运行时创建一个实现了一组给 定接1J 的新类 : 这种功能只有在编译时无法确定需要实现哪个接 U时才冇必要使用。对于应用程序设计人员来说, 遇到这种情况的机会很少。如果对这种高级技术不感兴趣,可以跳过 本节内容。然而,对于系统程序设计人员来说,代理带来的灵活性却十分重要。
2.1 何时使用代理?
假设有一个表示接口的 Class 对象(有可能只包含一个接口),它的确切类型在编译时无 法知道。这确实有些难度。要想构造一个实现这些接口的类,就需要使用 newlnstance 方法 或反射找出这个类的构造器。但是, 不能实例化一个接口,需要在程序处于运行状态时定义 一个新类。