Java的匿名内部类有哪些限制?
一、面试官视角:这道题想考察什么?
- 考察匿名内部类的概念和用法
- 考察语言规范以及语言的横向对比等
- 作为考察内存泄露的切入点
二、题目剖析:
1、匿名内部类的名字
匿名内部类没有名字,但在字节码中会定义为 包路径.OuterClass$1..N(外部类加$N,N是匿名内部类的顺序,如:$1表示类里面定义的第一个匿名内部类)
2、匿名内部类的继承结构
匿名内部类被new出来的时候,先天就会弄出来一个父类,同时new的时候也可以是一个接口,但不能new一个又继承了一个类又实现接口的匿名内部类,因为在Java中不支持或类型,Jdk10支持类型推导,但是也不支持又继承了一个类又实现接口的匿名内部类。
Java不支持,但Kotlin支持:
1 2 3
| val runnableFoo = object: Foo(), Runnable { override fun run() {} }
|
3、匿名内部类的构造方法
匿名内部类的构造方法是编译器定义的,非静态内部类会引用外部类的实例,静态内部类则不会引用外部类的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class OuterClass { public abstract class InnerClass { abstract void test(); } }
public class Clinet { public void run() { InnerClass innerClass = new OuterClass().new InnerClass() { ... } } }
public class Client$1 { public Clent$1(Clinet client, OuterClass outerClass) { ... } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class OuterClass { public interface InnerClass { void test(); } }
public class Client { public void run() { InnerClass innerClass = new OuterClass.InnerClass() { ... } } }
public class Client$1 { public Clinet$1(Clinet client) { ... } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class OuterClass { public interface InnerClass { void test(); } }
public class Client { public static void run() { InnerClass innerClass = new OuterClass.InnerClass() { ... } } }
public class Client$1 { public Clinet$1() { ... } }
|
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
| public class OuterCalss { public interface InnerClass { void test(); } }
public class Client { public static void run() { final Object object = new Object(); InnerClass innerClass = new OuterClass.InnerClass() { @Override void test() { System.out.println(object.toString()); } } } }
public class Client$1 { public Client$1(Object object) { ... } }
|
4、Lambda转换(SAM类型)
接口类且只有单一方法,可以使用Lambda转换;而抽象类就算有且只有单一方法,也不能使用Lambda转换。
1 2 3 4 5 6 7 8
| interface CallBack { void onSuccess() }
CallBack callBack = () -> { }
|
三、题目结论:
- 没有人类认知意义上的名字
- 只能继承一个父类或实现一个接口
- 父类是非静态的类型,则需父类外部实例来初始化
- 如果定义在非静态作用域内,会引用外部类实例
- 只能捕获外部作用域内的final变量
- 创建时只有单一方法的接口可以用Lambda转换