Java 内部类

类中类

基本事实

  • 内部类的定位是只为外围类提供服务,不应该暴露给外界,如果暴露也是以接口形式暴露公共行为,不会暴露内部具体实现
  • 内部类只是一个声明,但是同样遵守类成员访问规则,如果内部类被private修饰,那么只能被外围类自己使用
  • 不管是什么样的类编译后都是独立的class文件,内部类编译后产出class文件,采用外围类$内部类的命名方式,匿名时使用数字作为名字

非静态内部类


普通的内部类包含一个隐藏的父类引用,因为在同一类内,内部类可以父类访问所有成员变量(包括private),使用外围类.this.成员进行访问
坏处是如果内部类对象不回收,那么父类对象也不会被回收

应用

内部类可以使用新接口,作为转换器,返回对象的另一种形态

  • Map返回keySet
HashMap.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
//...
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}

final class KeySet extends AbstractSet<K> {
//...
}
//...
}
  • List返回Iterator
ArrayList.java
1
2
3
4
5
6
7
8
9
10
11
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//...
public Iterator<E> iterator() {
return new Itr();
}

private class Itr implements Iterator<E> {
//...
}
//...
}

静态内部类


如果内部类不准备访问父类成员,可以用static定义为静态内部类,不会和父类对象绑定,即静态内类可以脱离外围成员存在

应用

表示内部结构

  • Map内部KV结构,不需要引用Map本身
HashMap.java
1
2
3
4
5
6
7
8
9
10
11
12
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
//...
}

transient Node<K,V>[] table;
//...
}

匿名内部类


通常是一次性临时使用,没必要给个名字固定下来
现场提供了一套接口实现,并产生一个实例
只能进行已有方法实现,不能新增新方法,因为没有名字,只能用原接口进行引用,即使定义了新方法外界也根本无法调用
如果是非静态环境下,同样隐藏包含一个外围类的引用

应用

临时使用

  • 创建函数对象,比如Comparator
  • 创建过程对象,比如Runnable
  • 静态工厂中,定制实现
  • 作为枚举常量,比如有Season接口,定义四个季节的匿名实例

局部内部类


可以如同声明局部变量一样存在,比如方法体内,遵从作用域并且无需修饰符,比较少使用

访问机制


内部类可以访问外部类的所有成员

  • 对于可见成员,内部类通过隐式持有的外部类.this引用来访问
  • 对于不可见成员,外部类内会生成静态的access方法

如果内部类在方法里,还需要访问外围方法局部变量

  • 内部类构造时复制一份传入构造方法

因为内部类拿到的是副本,修改无意义不会影响外部,容易出错
因此编译器要求局部变量需要加上final,明示不可修改
如果事实上没有修改,可以不加

Sample
1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
int i = 0;
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(i); //正常
i = 1; //报错 Local variable i defined in an enclosing scope must be final or effectively final
}
};
}
}