内部类

介绍

  内部类即没有被显示或隐式的声明为static的嵌套类,而static修饰的类即“静态内部类”也是嵌套类,但不算是内部类,其可以声明static成员。

  接口的成员类隐式的声明为静态的,所以不属于内部类。

  除了常量变量,内部类不能声明静态成员,但可以继承。

包括:

  • 非static成员类(声明直接被另一个类或接口的声明包围),
  • 局部类(不是类的成员且具有名字的嵌套类),
  • 匿名类(编译器自动从实例创建表达式中导出,不可为abstract和static隐式final的内部类)

成员内部类

  成员内部类可以无条件访问外部类的所有成员属性和成员方法,因为内部类在实例化时会保留外部类对象的引用。

  编译器会修改内部类的构造器,添加一个参数传递外部类的引用,这个是隐藏参数。

  Outer.this表示外部类引用,所以可以通过Outer.this.name来访问外部类属性,若在内部类中属性或方法和外部类同名则默认是内部类。

  外部类若想访问内部类的成员需要先创建内部类对象。

1
2
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

  内部类中不能有static静态方法,但可以有静态常量,不过限制要final。因为Java设计限制每个外部对象对应一个其内部实例,此内部实例的静态域必须是唯一的。

1
2
3
4
5
6
7
8
9
10
11
public class Outer {
public class Inner{
private int num;
private static int id;//编译Error
private final static int nextId1;//编译Error
private final static int nextId2 = 1;

public static void method(){ //编译Error
}
}
}

内部类编译过程

1
2
3
4
5
6
7
8
9
public class Outer {
static boolean access$0(Outer);
}

public class Outer$Inner {
public Outer$Inner(Outer);

final Outer this$0;
}

  编译器为了引用外部类,生成了附加的实例域this$0,在外部类添加静态方法access$0(),将返回外部类中的boolean属性。内部类中if(outer.ifTrue)即if(Outer.access$0(outer)),但这种静态设计就会导致同包内的其他类可以通过access$0访问到私有属性,不过需要修改类文件才能达到目的。


局部内部类

  定义在方法或作用域内的类,和成员内部类的区别就是访问权限只能在方法内或作用域内。所以就和局部变量一样无法用public,protected,private和static等修饰符。

  相较其他内部类,局部内部类除了访问外部类之外,还可以访问局部变量,不过条件是变量实际上应该是final的。

1
2
3
4
5
6
7
8
9
public void start(int outNum){
class Inner {
public void action(){
System.out.println("outNum: " + outNum);
}
}
Inner inner = new Inner();
inner.action();
}

  当创建实例对象时,outNum会传递给构造器并存储到val$outNum中,编译器会自动完成这部分工作,这种设计使内部类更加简单。但某些情况下比如实现一个计数器counter,既然每次计数都要更新counter那么就没法跳过final,因为Integer也是不可变的,解决办法是通过数目为1的整型数组来存储counter。

1
2
3
4
5
6
7
8
9
public class Outer$Inner {
public Outer$Inner(Outer,int);

public void action();

final int val$outNum;

final Outer this$0;
}

  若没有上述设计,在多线程执行内部类代码时这类并发更新会导致竞态条件


匿名内部类

  直接通过new关键字直接构造类体,只做一次实例化,所以类没有名称就叫做匿名内部类,通常用于构造接口的实现类。

1
2
3
<interface> iface = new <interface> {
...
}

  匿名类没有类名,所以也就没有构造器,参数会传递给超类构造器,所以实现接口时不能传参,实现抽象类时需要和其构造器保持一致。

  匿名内部类通常被用来实现监听器,不过用lambda表达式实现的方式会更好一些。


静态内部类

  static修饰的嵌套类,也就受限制于静态特性。静态内部类相较其他内部类并没有对外部类的引用,其他特性和内部类一致,为了表示这种区别,也把静态内部类叫嵌套类。

  相比内部类,静态内部类可以有静态域和静态方法,声明在接口内的内部类会自动变为static和public的类。


成员内部类如何无条件引用外部类成员?与静态内部类区别?

  成员内部类会单独编译为一个字节码文件,编译器会为成员内部类添加指向外部类对象的引用,即使内部类是无参构造器,编译仍默认加外部类对象的引用,所以也知道成员内部类会依赖于外部类对象。

  静态内部类区别于成员内部类,不依赖外部类对象,可以直接创建内部类对象,且不包含外部类引用的。


为什么局部内部类和匿名内部类只能访问局部final变量?

  如一个方法调用线程TheadA,方法内有局部变量a,那么方法执行完毕,a生命周期就结束了,但此时线程A还可能在执行,此时无法访问a怎么办?Java编译器会默认在局部或匿名内部类的常量池增加一个相同的值嵌入执行字节码,如此匿名内部类只是使用的副本,而非方法中的变量a,当然a有可能是不确定值的参数,编译时无法确定值,所以会通过构造器传参的方式来对副本初始化赋值。因此可以明白,如果局部变量改变的话会导致数据不一致,所以Java直接限制只能访问final局部变量。


内部类使用场景和优点

  1. 内部类可以独立继承接口,而不用管外部类如何,所以是Java解决多继承的方案
  2. 方便将关系紧密的类结合,内部类可任意访问外部类成员,并对外界隐藏
  3. 匿名内部类可以简单编写回调函数
  4. 方便编写事件驱动
  5. 方便编写线程代码

编程测试

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
public class Test{
public static void main(String[] args){
// 初始化Bean1
(1)
bean1.I++;
// 初始化Bean2
(2)
bean2.J++;
//初始化Bean3
(3)
bean3.k++;
}
class Bean1{
public int I = 0;
}

static class Bean2{
public int J = 0;
}
}

class Bean{
class Bean3{
public int k = 0;
}
}

  实例化:

1
2
3
4
5
6
7
8
1) Test test = new Test();
Test.Bean1 bean1 = test. new Bean1();

2) Test.Bean2 bean2 = new Test.Bean2();

3) Bean bean = new Bean();
Bean.Bean3 = bean.new Bean3();


参考博客和文章书籍等:

《Java核心技术 卷Ⅰ》

《Java语言规范》8.1 内部类 8.5 成员类 14.3 局部类 15.9.5 匿名类

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容