内部类(Inner Class)为定义在类或方法内部的类,其的特点,用途及使用原因如下——
- 内部类中的方法可以访问该类定义所在作用域(也就是它的外部类的作用域)的成员,包括私有成员。
- 内部类可以对包内其它类不可见(普通的 class 的修饰符只能是 public 和默认,其在同一个包内都是可见的)。
- 内部类可利用于 lambda 表达式,匿名实现类等特性,可以实现闭包等功能。
内部类还可分为内部类和静态内部类,其在语义上和功能上都是有差异的。
我们接触到的内部类最典型的就是 Map 的 Entry,lombok 的 builder。
一般定义的内部类,其在语义上来说是和实例对应的,不过这种对应关系不是 1 对 1,是 n 对 1,因为一个实例可以创建无数个对应其的内部类实例。
类对内部类的实例化和对普通对象的代码是一样的,但是编译器在背后会做不同的工作——内部类实例化时,当前类实例的指针也会被隐式地赋予给该实例,这就使内部类的实例能够访问外部类的属性。一个示例代码见下——
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class InnerTest { private int outerData = 100; private class Inner { public void getData() { System.out.println(++outerData); } } public void doSomething() { Inner inner = new Inner(); inner.getData(); } public static void main(String[] args) { InnerTest tst = new InnerTest(); tst.doSomething(); tst.doSomething(); tst.doSomething(); } }
|
一个有趣的事实是,不能在其它类中直接创建 Inner 实例,不能在 static 方法中直接创建 Inner 实例——没有和它绑定的外部类对象,自然不能创建!但是可以先创建外部对象,再创建其内部对象,示例见下。
| public class InnerTest { private int outerData = 100; private class Inner { public void getData() { System.out.println(++outerData); } } public static void main(String[] args) { InnerTest tst = new InnerTest(); InnerTest.Inner inner = tst.new Inner(); inner.getData(); inner.getData(); inner.getData(); } }
|
使用 static 定义的内部类并非意味着这个类是单例的(Java 没有类似 Kotlin 那样的 object 修饰符)——其在语义上是和类对应的,在使用上的限制仅限于其访问修饰符了。但是也可以采用取巧的方法——在构造器中将它的外部类实例作为自己的一个指针,这样就也能够获取和普通的内部类一样的效果了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class InnerTest { private int outerData = 100; private static class Inner { final InnerTest outer; Inner(InnerTest outer) { this.outer = outer; } void getData() { System.out.println(++outer.outerData); } } public void doSomething() { Inner inner = new Inner(this); inner.getData(); } public static void main(String[] args) { InnerTest tst = new InnerTest(); tst.doSomething(); tst.doSomething(); tst.doSomething(); } }
|
静态内部类(也叫嵌套类)和常规内部类的差别在于——
- 常规内部类保存对外围类对象的引用,静态内部类则无。
- 静态内部类可以有 static 的成员。
- 常规内部类需要先生成外围对象,静态内部类可以直接被生成(实际上这也让静态内部类也被称为嵌套类——只是将这个类的定义隐藏在另一个类里罢了)。
内部类可以定义在方法中,这会导致其作用域仅限于该方法作用域中。
| public static void main(String[] args) { int a = 1; class Tmp { void fun() { System.out.println(a); System.out.println("fun!"); } } Tmp tmp = new Tmp(); tmp.fun(); }
|
至于匿名内部类,lambda 表达式,函数式接口等概念已经无需再提,这里再提一点——关于{{}}
语法(DBI),这其实是建立了一个匿名子类,同时通过初始化块来进行操作。如在使用 SpringMVC 构造返回结果时,可以这样写——
| @GetMapping("/hello") Map<String, Object> hello() { return new HashMap<String, Object>(){{ put("hello", "world"); put("answer", 42); }}; }
|
这形式其实就是构造了一个 HashMap 的匿名子类,定义一个新的初始化块并进行一些操作(这里的执行顺序是父类初始化块->父类构造器->子类初始化块)。这种形式的缺点之一是,最后生成的对象的 class 并非是 HashMap,在某些时候会出现问题,比如在 equals 方法里,比较对象类型是否相同时;比如使用 gson 进行序列化时,如此构造的对象将返回 null(这可让我吃了些苦头!)。