Java中内部类使用方面的总结

以下知识点通过阅读Thinking in Java总结,也是平常不怎么接触到的技巧.

内部非静态类的创建

需要创建一个这样的类,首先需要外部类的引用,然后用.new方法去创建这个外部类的内部类.原因也很简单——因为一个非静态的内部类需要访问外部类的话,需要持有这个外部类的引用,不然不知道访问哪个外部类,代码如下:

1
2
3
4
5
6
7
8
9
10
11

public class DotNew {
public class Inner {
}

public static void main(String[] args) {
DotNew dn = new DotNew();
//这样内部类就隐式的持有了这个dn的引用
DotNew.Inner dni = dn.new Inner();
}
}

内部类和向上转型

在定义一个接口后,我们可以在定义一个private的内部类,去实现这个接口,然后在外部类中去实现一个方法返回这个内部类,但是是以接口的形式去返回,这就能隐藏了这个类的细节.而private的访问权限也让外部不能直接转成这个内部类的形式,就完美的实现了对外暴露接口.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Parcel4 {

private class IDesination implements Destination {

@Override
public String readLabel() {
return "Destination";
}
}

public Destination getDestination() {
return new IDesination();
}

public static void main(String[] args) {
Parcel4 instance = new Parcel4();
//对外暴露了接口.
Destination destination = instance.getDestination();
System.out.println(destination.readLabel());
}
}

在匿名内部类中访问外部类不需要final的方法

因为匿名内部类没有名字,故我们不能为其创建一个构造器去初始化这个类,但是我们可以用代码段去初始化匿名内部类,这样就可以实现访问外部类不需要final这个关键字了.

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

abstract class Base {
public Base(int i) {
System.out.println("Base constructor, i = " + i);
}

public abstract void f();
}

public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{
//在这里可以访问外部类非final变量.
System.out.println("Inside instance initializer");
}

public void f() {
System.out.println("In anonymous f()");
}
};
}

public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
} /* Output:
Base constructor, i = 47
Inside instance initializer
In anonymous f()
*///:~

为什么内部类访问的外部变量需要使用final修饰

因为生命周期的原因。方法中的局部变量,方法结束后这个变量就要释放掉,final保证这个变量始终指向一个对象。
首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而跟随者被销毁。问题就来了,如果外部类的方法中的变量不定义final,那么当外部类方法执行完毕的时候,这个局部变量肯定也就被GC了,然而内部类的某个方法还没有执行完,这个时候他所引用的外部变量已经找不到了。如果定义为final,java会将这个变量复制一份作为成员变量内置于内部类中,这样的话,由于final所修饰的值始终无法改变,所以这个变量所指向的内存区域就不会变。
摘自为什么内部类访问的外部变量需要使用final修饰.

接口内部的类

正常情况下,不能在接口内部放置任何代码,但是静态内部类(下文简称嵌套类)却可以成为接口的一部分,因为接口中的任何成员变量都是publicstatic的,不违反接口的规则,甚至可以在内部实现其外部接口.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface ClassInInterface {
void howdy();

class Test implements ClassInInterface {
public void howdy() {
System.out.println("Howdy!");
}

public static void main(String[] args) {
new Test().howdy();
}
}

}

为什么需要内部类

TIJ中描述这最吸引人的原因是:

每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现.对于内部类都没有影响.

在某种需要下,我们需要某个类继承两个接口,这时候无论是一个类,还是内部类,都可以实现这种功能.但是如果我们需要实现继承两个抽象的类或者具体的类的时候,前者就不起作用了.这时候就需要我们使用内部类来实现多重继承的功能.一个简单的例子如下代码所示:

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
package com.crainax.tij.innerclasses;

/**
* Created by Crainax on 2016/4/1.
*/


class D {
}

abstract class E {
}

class Z extends D {
E makeE() {
return new E() {
};
}
}

public class MultiImplementation {

static void takeD(D d) {

}

static void takeE(E e) {

}

public static void main(String[] args) {
Z z = new Z();
z.makeE();
takeD(z);
//实现所需要的多重继承的功能.
takeE(z.makeE());
}

}

使用了内部类不但解决了”多重继承”的问题,还具有以下一些特性:

  1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立.
  2. 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类.
  3. 创建内部类对象的时刻并不依赖于外围类的创建.
  4. 内部类并没有令人迷惑的”is-a”关系,它就是一个独立的实体.

命令模式与内部类

Thinking In Java 对于这里的描述很详细,表现了Java在这里优雅的解决方案,我就在这里概括几点:

其中:

  1. 在事件驱动系统中,也就是在GUI问题中,用到了大量的内部类.
  2. 其中分有Event抽象类,定义了事件的时间戳与一些特有的属性,其中有抽象方法 action();还有Controller的类,能对Event起到一个遍历运行的作用.
  3. 一个控制框架的特定实现,比如控制温室的运作,有灯光、水、温度调节器的开关,还有响铃和重新启动系统,每种操作都集成在Controller类中,无疑用命令模式是最好解决的.
  4. 但是此时也有了一个问题,就是这个命令需要有action()的实现,又希望能访问到Controller的一些成员变量去控制这个行为.
  5. 这时内部类就派上用场了,每个命令都写在Controller中,用内部类继承Event类,就能实现这个功能了.此时再配合上一个队列,就能实现事件的不断触发与执行过程了.

内部类的继承

因为内部类隐式的持有外部类(下文称A)的引用,所以如果需要另外一个外部类(下文称B)去继承这个内部类的话,那么B也需要这个A的引用,不然不能经过编译,这时候可以在构造器中去说明对A引用的持有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  class WithInner {
class Inner {
}
}

public class InheritInner extends WithInner.Inner {
// ! InheritInner() {} // 不能经过编译
InheritInner(WithInner wi) {
wi.super();
}

public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}

当然还有一种情况,即B也有一个内部类,B继承于A,B的内部类继承于A的内部类,此时就不需要说明这个引用的持有.