Java编程笔记7:内部类
图源:PHP中文网
所谓的内部类,其实就是定义在类中的类。这和Java编程笔记6:接口 - 魔芋红茶’s blog (icexmoon.xyz)中提到的接口嵌套的方式有点相似。不过内部类比接口嵌套更常见,也更有用。
内部类
定义内部类并不困难:
package ch7.inner_class;
import java.util.Random;
import util.Fmt;
class OulterClass {
protected class InnerClass {
private int num;
public InnerClass(int num) {
this.num = num;
}
@Override
public String toString() {
return Fmt.sprintf("InnerClass(%d)", num);
}
}
public InnerClass getInnerClassInstance() {
Random random = new Random();
return new InnerClass(random.nextInt(100));
}
}
public class Main {
public static void main(String[] args) {
OulterClass oc = new OulterClass();
OulterClass.InnerClass ic = oc.getInnerClassInstance();
System.out.println(ic);
// InnerClass(84)
}
}
需要注意的是,这里获取内部类实例的方式是通过“外部类”OulterClass
的getInnerClassInstance
方法,而非直接在main
函数中通过new OulterClass.InnerClass()
创建,是因为后者其实是无法执行的,具体原因后边会解释。
同时可以看到,内部类可以使用protected
声明,并不局限于public
和包访问权限,事实上你甚至可以将内部类声明为private
,并且在某些情况下这样做还很有用,这点在后面会同样举例说明。
在内部类所属的外部类之外,要使用内部类就需要用OlterClass.InnerClass
这样的方式,或者也可以用import
语句进行导入,这点和嵌套接口的使用方式是一致的。
链接到外部类
之所以说内部类很有用,是因为内部类会“隐含”一个外部类实例的引用,利用这个引用我们可以直接访问外部类的属性和方法。
在Java编程笔记6:接口 - 魔芋红茶’s blog (icexmoon.xyz)中【完全解耦】小节中,我举过一个用适配器模式让NumberGenerator
实现Printable
的例子,当然这很棒,很具扩展性。但是使用设计模式往往会带来另一个后果——类的数量会大大增加。在那个例子中并不明显,但是随着你用适配器模式给NumberGenerator
添加一个又一个适配器类,类数量膨胀是可以预期的,而且如果按照Java项目一般性的一个类文件只包含一个类定义的做法,很快你就能看到一个目录下包含了一堆xx.java
文件。
实际上在Java中更好的做法是用内部类来实现适配器模式:
package ch7.decouple4;
import java.util.Random;
public class NumberGenerator {
private static Random random = new Random();
public Printable getPrinter(int printTimes) {
return new NGPrintAdapter(printTimes);
}
public int getNumber() {
return random.nextInt(100);
}
private class NGPrintAdapter implements Printable {
private int printTimes;
public NGPrintAdapter(int printTimes) {
if (printTimes < 0) {
throw new Error();
}
this.printTimes = printTimes;
}
@Override
public void print() {
for (int i = 0; i < printTimes; i++) {
System.out.print(getNumber() + " ");
}
System.out.println();
}
}
}
在这个改写后的例子中,适配器类NGPrintAdapter
是NumberGenerator
的内部类,这样做的好处在于:
- 不需要额外使用单独的代码文件存放适配器类。
- 明确了适配器类和适配目标类的从属关系。
- 适配器类作为内部类,可以直接调用适配目标对象的属性和方法,在这个示例中,适配器类的
print
方法中直接调用了外部类NumberGenerator
实例的getNumber()
方法。 - 可以用
private
修饰适配器类,让适配器类的实现对外部隐藏。客户端代码仅仅需要的是一个实现了Printable
的类型,并不关心具体是怎么实现的。
最后看一下测试代码,几乎没有怎么修改:
public class Main {
public static void main(String[] args) {
Printable p1 = new NumberSequence(new int[] { 1, 2, 3 });
Printable p2 = new CharSequence(new char[] { 'a', 'b', 'c' });
p1.print();
p2.print();
NumberGenerator ng = new NumberGenerator();
Printable p3 = ng.getPrinter(6);
p3.print();
// 1 2 3
// a b c
// 40 26 82 31 78 6
}
}
this和new
某些时候你可能希望在内部类中直接获取外部类实例的引用,可以使用this
关键字实现:
package ch7.inner_class2;
import java.util.Random;
import util.Fmt;
class OulterClass {
protected class InnerClass {
...
public OulterClass getOulterClassInstance() {
return OulterClass.this;
}
}
...
}
public class Main {
public static void main(String[] args) {
OulterClass oc = new OulterClass();
OulterClass.InnerClass ic = oc.getInnerClassInstance();
System.out.println(ic);
// InnerClass(84)
OulterClass oc2 = ic.getOulterClassInstance();
System.out.println(oc == oc2);
// true
}
}
或者内部类的属性名或方法名与外部类冲突时,也可以用this
关键字显式调用以作区分:
package ch7.inner_class3;
import java.util.Random;
import util.Fmt;
class OulterClass {
protected class InnerClass {
private int num;
public InnerClass(int num) {
this.num = num;
System.out.println("print inner:" + this.toString());
System.out.println("print outler:" + OulterClass.this.toString());
// print inner:InnerClass(66)
// print outler:OulterClass()
}
@Override
public String toString() {
return Fmt.sprintf("InnerClass(%d)", num);
}
...
}
...
@Override
public String toString() {
return "OulterClass()";
}
}
...
前面这些例子都说明了内部类与外部类的紧密关系——内部类实例包含了对外部类实例的引用。这也是为什么一开始说的,无法通过普通的new OulterClass.InnerClass()
方式来创建内部类,因为你没有给它指定所需要的外部类实例。
换句话说,只要我们指定一个外部类实例,就可以创建一个持有其引用的对应的内部类:
...
public class Main {
public static void main(String[] args) {
OulterClass oc = new OulterClass();
OulterClass.InnerClass ic = oc.new InnerClass(10);
System.out.println(ic);
// InnerClass(10)
}
}
oc.new InnerClass(10)
这样的方式的确看起来很怪异,但它所表达的的确是用一个外部类实例oc
创建一个内部类InnerClass
的实例。
此外,oc.new
之后的InnerClass
没有添加外部类名称,事实上你也不能那么做,这是因为oc
作为外部类的实例,已经很明确了,不需要额外指定外部类名。
在方法和作用域内的内部类
事实上,除了可以在类中定义类以外,还可以在方法和作用域中定义类,这样的类被称作局部内部类:
package ch7.func_inner;
public class Main {
public static void main(String[] args) {
class LocalInnerClass{
@Override
public String toString() {
return this.getClass().getName();
}
}
LocalInnerClass lic = new LocalInnerClass();
System.out.println(lic);
// ch7.func_inner.Main$1LocalInnerClass
}
}
上面的示例中,LocalInnerClass
的作用域被局限在main
函数中,也就是说其它地方的代码都无法使用它。但这并不意味着main
函数结束后这个类定义就会被销毁,事实上只是外部代码无法访问而已,如果main
函数被再次调用,就可以重新使用这个类定义。
类似的,你可以在任何类型的作用域中定义内部类,比如条件语句:
package ch7.func_inner2;
import java.util.Random;
import util.Fmt;
public class Main {
public static void main(String[] args) {
Random random = new Random();
int a = random.nextInt(100);
int b = random.nextInt(100);
if (a > b) {
class LocalInnerClass {
@Override
public String toString() {
return this.getClass().getName();
}
}
LocalInnerClass lic = new LocalInnerClass();
System.out.println(lic);
}
Fmt.printf("a:%d,b:%d\n", a, b);
// ch7.func_inner2.Main$1LocalInnerClass
// a:35,b:5
}
}
匿名内部类
在Java中,匿名内部类是最常见和使用的一种内部类。
所谓的匿名类,就是“没有名字的类”,而匿名内部类,就是没有名字的内部类。
继续前边NumberGenerator
的例子,如果用匿名内部类改写:
package ch7.nonname;
import java.util.Random;
public class NumberGenerator {
private static Random random = new Random();
public Printable getPrinter(int printTimes) {
if (printTimes <= 0) {
return null;
}
return new Printable() {
@Override
public void print() {
for (int i = 0; i < printTimes; i++) {
System.out.print(getNumber() + " ");
}
System.out.println();
}
};
}
public int getNumber() {
return random.nextInt(100);
}
}
这里的new Printable(){...}
语法就是创建一个实现了Printable
的匿名类,并将其实例化。
匿名类往往需要从外部获取信息,比如上面的示例中,匿名类就需要获取一个打印次数(printTimes
),该信息由getPrinter
方法的printTimes
参数保存,比较神奇的是我们不需要任何方式,将其“传递”给匿名类,只需要直接在匿名类中像使用自身属性那样使用即可。
其实如果上边的代码在JavaSE8之前的版本中编译,是无法通过编译的。这是因为Java中的匿名类,其实和PHP、Python、Go等支持函数式编程的语言中的函数一样,都是用闭包来实现的。而闭包所使用的外部数据,实际上是不能改变的(或者直接由外部传入),在Java中,这体现为——匿名类使用的外部数据都必须被定义为final
。也就是说在JavaSE8之前,我们上面的例子要写成:
package ch7.nonname2;
import java.util.Random;
public class NumberGenerator {
private static Random random = new Random();
public Printable getPrinter(final int printTimes) {
...
return new Printable() {
for (int i = 0; i < printTimes; i++) {
...
}
...
};
}
...
}
否则就无法通过编译。
大概是很多人被这种规定坑?在JavaSE8中添加了一种新特性,叫做effectively final。具体来说就是,如果一个变量在定义后没有发生过改变,那么该变量就是effectively final的。
对于这种effectively final变量,Java可以进行优化——在需要的时候给其自动添加上final
声明。
现在再来看上边的例子,printTimes
作为getPrinter
的参数,实际上除了在匿名类中使用之外,没有任何其他使用,更别说修改了,所以必然是effectively final的。所以在因为匿名类的使用而需要我们给其添加final
声明的时候,Java编译器可以“自动”帮我们完成。
所以也就不存在JavaSE8之前我们必须手动添加上final
声明的那种限制了。
但是,了解这种新特性的原理后很容易就明白这样产生的另一个问题,如果匿名类使用的外部变量被修改过,不是effectively final的,那是不是编译器就没办法帮我们自动完成添加final
声明的工作?答案是Yes。
package ch7.nonname3;
import java.util.Random;
public class NumberGenerator {
private static Random random = new Random();
public Printable getPrinter(int printTimes) {
if (printTimes <= 0) {
return null;
}
printTimes = random.nextInt(10);
return new Printable() {
@Override
public void print() {
// Local variable printTimes defined in an enclosing scope must be final or effectively final
for (int i = 0; i < printTimes; i++) {
System.out.print(getNumber() + " ");
}
System.out.println();
}
};
}
public int getNumber() {
return random.nextInt(100);
}
}
上面的代码是无法通过编译的,报错信息很明确——privateTimes
变量应当是final
或者effectively final
类型。
要解决这种问题也很容易,定义一个匿名类专用的final
类型就是了:
package ch7.nonname4;
import java.util.Random;
public class NumberGenerator {
private static Random random = new Random();
public Printable getPrinter(int printTimes) {
...
printTimes = random.nextInt(10);
final int newTimes = printTimes;
return new Printable() {
@Override
public void print() {
for (int i = 0; i < newTimes; i++) {
System.out.print(getNumber() + " ");
}
System.out.println();
}
};
}
...
}
大多数情况下匿名类都用于实现某个接口,但实际上同样可以继承自某个类:
package ch7.nonname5;
import util.Fmt;
abstract class Tank{
protected String name;
public Tank(String name){
this.name = name;
}
abstract public void fire();
abstract public void move();
}
class TankFactory{
public static Tank buildTank(String name){
return new Tank(name) {
@Override
public void fire() {
Fmt.printf("Tank(%s) is firing.\n", this.name);
}
@Override
public void move() {
Fmt.printf("Tank(%s) is moving.\n", this.name);
}
};
}
}
public class Main {
public static void main(String[] args) {
Tank tank = TankFactory.buildTank("99");
tank.move();
tank.fire();
// Tank(99) is moving.
// Tank(99) is firing.
}
}
Tank
是一个抽象基类,TankFactory
的buildTank
方法可以直接返回一个继承自Tank
的匿名类生成的实例。需要注意的是Tank
有一个构造函数,必须接收一个String
参数,所以匿名类也需要用new Tank(name){...}
的方式构建,其中name
就是传递给基类Tank
构造函数的参数。
因为匿名类是没有名字的,自然我们就没法在其中定义构造函数,所以也只能通过上面这种方式给基类构造函数传递参数。
虽然基类构造函数传参的问题可以通过上面的方式解决,但是构造函数承担的初始化工作要怎么办?
不知道还你还记不记得Java编程笔记2:初始化和清理 - 魔芋红茶’s blog (icexmoon.xyz)中提到的初始化块,或许初始化块对于普通类来说可有可无,但是对于匿名类来说,其是可以执行复杂初始化语句的唯一方式:
package ch7.nonname6;
import util.Fmt;
...
class TankFactory {
private static int num = 1;
public static Tank buildTank(String name) {
final int newNum = num;
num++;
return new Tank(name) {
private int num;
{
this.num = newNum;
Fmt.printf("Tank(name:%s,num:%d) is build.\n", this.name, this.num);
}
...
};
}
}
public class Main {
public static void main(String[] args) {
Tank t1 = TankFactory.buildTank("99");
Tank t2 = TankFactory.buildTank("M1A1");
// Tank(name:99,num:1) is build.
// Tank(name:M1A1,num:2) is build.
}
}
虽然匿名类的确很有用,但匿名类有很多限制:
- 引用的外部变量必须是
final
或者effectively final
的。 - 只能实现一个接口或者继承自一个基类,且不能同时实现。
- 不能包含静态属性和方法。
再谈工厂模式
实际我们前面提到的工厂模式很适合使用匿名类来实现:
package ch7.factory3;
public class HeavyTank implements Tank {
public static Factory factory = new Factory() {
@Override
public Tank buildTank() {
HeavyTank tank = new HeavyTank();
tank.buildSites();
tank.buildBarbette();
tank.buildWeaponSystem();
tank.ready();
return tank;
}
};
...
}
package ch7.factory3;
public class LightTank implements Tank {
public static Factory factory = new Factory() {
@Override
public Tank buildTank() {
LightTank tank = new LightTank();
tank.buildSites();
tank.buildBarbette();
tank.buildWeaponSystem();
tank.ready();
return tank;
}
};
...
}
package ch7.factory3;
public class Main {
public static void main(String[] args) {
Factory factory = HeavyTank.factory;
Tank tank1 = factory.buildTank();
factory = LightTank.factory;
Tank tank2 = factory.buildTank();
// Heavy Tank sites is build.
// Heavy Tank barbette is build.
// Heavy Tank weapon system is build.
// Heavy Tank build work is all over.
// Light Tank sites is build.
// Light Tank barbette is build.
// Light Tank weapon system is build.
// Light Tank build work is all over.
}
}
这里使用匿名类创建的实例作为Tank
子类对应的工厂。
为什么需要内部类
虽然前面已经说了使用内部类会带来的一些好处,但似乎没有一个案例是必须使用内部类的,所以必须说明为什么一定需要内部类。
在Java编程笔记6:接口 - 魔芋红茶’s blog (icexmoon.xyz)中我们说过,Java是不支持多继承的,但一个类可以通过继承一个类和实现多个接口的方式实现某种程度上的多继承。某种程度上讲,这是一种妥协——相对于真正实现多继承的语言(如Python)来说。
事实上并不是一定无法在Java中实现多继承,通过内部类可以“曲线救国”:
package ch7.why;
class BaseA{}
class BaseB{}
class MyClass extends BaseA{
public BaseB getBaseB(){
return new BaseB();
}
}
public class Main {
public static void main(String[] args) {
MyClass mc = new MyClass();
BaseB bb = mc.getBaseB();
BaseA ba = mc;
}
}
就像上面演示的那样,通过使用内部类,可以在“事实上”实现让MyClass
类同时具备两个基类的用途。这类问题也只能用内部类解决。
除此之外,在Java编程笔记6:接口 - 魔芋红茶’s blog (icexmoon.xyz)中提到的继承多个接口时可能产生的“方法冲突问题”,同样可以用内部类来解决:
package ch7.why2;
interface InterA {
void test();
}
interface InterB {
int test();
}
class MyClass implements InterA {
@Override
public void test() {
System.out.println("test() is called.");
}
public InterB getInterB() {
return new InterB() {
@Override
public int test() {
return 0;
}
};
}
}
public class Main {
public static void main(String[] args) {
MyClass mc = new MyClass();
InterB ib = mc.getInterB();
InterA ia = mc;
}
}
多层嵌套
虽然并不常见,但内部类可以多层嵌套,比如:
package ch7.why3;
class A {
private void funcA() {
System.out.println("funcA() is called.");
}
class B {
private void funcB() {
System.out.println("funcB() is called.");
}
class C {
public void funcC(){
funcA();
funcB();
}
}
}
}
public class Main {
public static void main(String[] args) {
A a = new A();
A.B b = a.new B();
A.B.C c = b.new C();
c.funcC();
}
}
无论嵌套层次有多深入,嘴里侧的内部类都可以访问外部所有类的所有成员,包括private
成员。
嵌套类
可以定义一种“静态内部类”,其行为就是普通的定义在类中的类,除了使用的时候要通过外部类名之外,和普通的类没有任何区别:
package ch7.static1;
class OulterCls{
static class InnerCls{
private String privateAttr = "private_attr";
protected String protectedAttr = "protected_attr";
public String publicAttr = "public_attr";
static String staticAttr = "static_attr";
public static void staticFunc(){
System.out.println("staticFunc() is called.");
}
public void normalFunc(){
System.out.println("normalFunc() is called.");
}
}
}
public class Main {
public static void main(String[] args) {
OulterCls.InnerCls innerCls = new OulterCls.InnerCls();
innerCls.staticFunc();
innerCls.normalFunc();
// staticFunc() is called.
// normalFunc() is called.
}
}
上面的例子中,静态内部类InnerCls
具备所有普通类的特性,就像在Java编程笔记6:接口 - 魔芋红茶’s blog (icexmoon.xyz)中介绍的嵌套接口那样,除了使用时需要使用外部类名以外,和普通类没有任何区别。所以这种内部类也称作“嵌套类”。
显然,嵌套类并没有前面介绍的普通内部类那种与外部类实例的关联关系,自然也无法使用外部类实例的引用。但好处是不需要外部类实例就可以单独创建。
继承内部类
大多数情况下都不会涉及内部类的继承,但如果你的确需要这么做,依然是可以实现的:
package ch7.extends1;
class OulterCls{
class InnerCls{}
}
class InnerChild extends OulterCls.InnerCls{
public InnerChild(OulterCls oc) {
oc.super();
}
}
public class Main {
public static void main(String[] args) {
OulterCls oc = new OulterCls();
InnerChild ic = new InnerChild(oc);
}
}
这里InnerChild
作为一个直接继承OulterCls
的内部类InnerCls
的类,必须实现一个包含OulterCls
参数的构造函数,原因是内部类InnerCls
必须包含一个外部类OulterCls
实例的引用。
上面这些不难理解,但为什么需要在构造函数中调用oc.super()
这样的诡异写法?
在理想情况中,我们可能希望使用super(oc)
这样的写法,因为InnerChild
的父类是InnerCls
,如果用super
调用其构造函数,并传入一个OulterCls
实例,自然就建立了内部类和外部类实例的联系。但是内部类InnerChild
没有这样的构造函数,也不能有,因为这种联系的建立是编译器隐式建立的,而非用构造器显式创建。所以Java就提供了一个在效果上和super(oc)
作用等价的替代方式:oc.super()
。
如果是在同一个类中继承内部类,就不存在上面的问题:
package ch7.extends2;
class OulterCls {
class InnerCls {
}
class InnerChild extends InnerCls {
}
}
public class Main {
public static void main(String[] args) {
OulterCls oc = new OulterCls();
OulterCls.InnerChild ic = oc.new InnerChild();
}
}
定义和使用都和普通内部类没有什么区别,只不过多了一个基类。
这点实际上是我的突发奇想,《Thinking in Java》中并没有相关描述,但实际测试中发现是可行的。
覆盖内部类
普通方法可以在继承的时候进行覆盖(重写),内部类(定义)是否可以同样被覆盖?
package ch7.override1;
class ParentOulter {
public class Inner {
@Override
public String toString() {
return "ParentOulter.Inner";
}
}
public Inner getInnerInstance() {
return new Inner();
}
}
class ChildOulter extends ParentOulter {
public class Inner {
@Override
public String toString() {
return "ChildOulter.Inner";
}
}
}
public class Main {
public static void main(String[] args) {
ChildOulter co = new ChildOulter();
System.out.println(co.getInnerInstance());
// ParentOulter.Inner
}
}
在上面的示例中,ChildOulter
继承了ParentOulter
,并且重新定义了内部类Inner
,如果内部类也会像普通方法那样被覆盖,且会存在类似的“多态机制”的话,那么调用ChildOulter
实例的getInnerInstance
方法应当会返回一个ChildOulter.Inner
实例,但结果显然并不是。所以内部类是不能被覆盖的,每个外部类都拥有独立的内部类。
但我们可以用另一种方式让外部类进行继承的同时让内部类建立“某种联系”:
package ch7.override2;
class ParentOulter {
public class Inner {
public Inner() {
System.out.println("ParentOulter.Inner is build.");
}
@Override
public String toString() {
return "ParentOulter.Inner";
}
}
public Inner getInnerInstance() {
return new Inner();
}
}
class ChildOulter extends ParentOulter {
public class Inner extends ParentOulter.Inner {
public Inner() {
super();
System.out.println("ChildOulter.Inner is build.");
}
@Override
public String toString() {
return "ChildOulter.Inner";
}
}
@Override
public ParentOulter.Inner getInnerInstance() {
return new Inner();
}
}
public class Main {
public static void main(String[] args) {
ChildOulter co = new ChildOulter();
System.out.println(co.getInnerInstance());
// ParentOulter.Inner is build.
// ChildOulter.Inner is build.
// ChildOulter.Inner
}
}
在外部类存在继承关系的同时,我们让内部类同样建立了继承关系。这种方式相当微妙,有点像之前在Java编程笔记5:多态 - 魔芋红茶’s blog (icexmoon.xyz)中介绍的“返回值协变”。
局部内部类VS匿名内部类
局部内部类和匿名内部类都可以在作用域和函数中使用,不过存在略微差别:
package ch7.local;
import java.util.Random;
interface Printable {
void print();
}
public class Main {
private static Printable getPrinter1() {
return new Printable() {
private int num;
{
num = random.nextInt(100);
}
private static Random random = new Random();
@Override
public void print() {
System.out.println("non-name class's num:" + num);
}
};
}
private static Printable getPrinter2() {
class NumberPrinter implements Printable {
private int num;
private static Random random = new Random();
public NumberPrinter() {
num = random.nextInt(100);
}
@Override
public void print() {
System.out.println("local inner class's num:" + num);
}
}
return new NumberPrinter();
}
public static void main(String[] args) {
Printable p1 = getPrinter1();
Printable p2 = getPrinter2();
p1.print();
p2.print();
// non-name class's num:87
// local inner class's num:93
}
}
主要的区别在局部内部类可以拥有构造函数,甚至可以重载构造函数,而匿名类则不行。此外局部内部类还可以进行多继承,匿名类同样不行。但是匿名类在写法上更简单。
总而言之,在绝大多数仅需要一个“简单实现”(比如只需要返回一个实现了某某接口的对象)时,只需要使用匿名类就可以了,这样编码效率更高。但如果功能复杂,匿名类无法满足,就可以尝试用局部内部类来进行替代,后者拥有完整的类功能。
内部类标识符
在执行Java程序时,Java编译器会将源码编译为.class
结尾的字节码文件,然后再执行字节码。
在这个过程中,每个字节码文件包含一个类定义,如果一个.java
文件中包含三个类定义,就会产生三个.class
文件,内部类也是如此。
package ch7.flag;
import java.util.Random;
interface Printable {
void print();
}
public class Main {
private static Printable getPrinter1() {
return new Printable() {
...
@Override
public void print() {
System.out.println("non-name class's num:" + num);
System.out.println(getClass().getName());
}
};
}
private static Printable getPrinter2() {
class NumberPrinter implements Printable {
...
@Override
public void print() {
System.out.println("local inner class's num:" + num);
System.out.println(getClass().getName());
}
}
return new NumberPrinter();
}
public static void main(String[] args) {
Printable p1 = getPrinter1();
Printable p2 = getPrinter2();
p1.print();
p2.print();
// non-name class's num:38
// ch7.flag.Main$1
// local inner class's num:90
// ch7.flag.Main$1NumberPrinter
}
}
这里修改了之前的示例,让其输出内部类的类名,可以发现内部类的类名都是以<OulterClsName>$<number><InnerClsName>
的方式命名的,其中$
作为区分内部类和外部类名称的特殊符号。对于匿名内部类,是没有类名的,直接在$
后使用一个编译器分配的数字表示。
事实上类名会直接作为生成的字节码文件名,比如上边的源码生成的字节码文件中就包含:Main$1.class
和Main$1NumberPrinter.class
。
我个人觉得之所以Java会有这么复杂的内部类设计,很大程度上是因为JavaSE8之前的函数式编程语法的缺失导致的,不得不使用这种艰涩的方式来提供类似的支持,在Python和Go中就很少看到类似的用法,因为直接传递函数对象会让代码变得更为简洁。
谢谢阅读。