要点
- 匿名类的概念和用法
- 语言规范以及语言的横向对比等
- 内存泄漏的切入点
总结
- 没有人类认知意义上的名字
- 只能继承一个父类或实现一个接口
- 父类是非静态的类型,则需父类外部实例来初始化
- 如果定义在非静态作用域内,会引用外部类实例
- 只能捕获外部作用域内的final变量
- 创建时只有单一方法的接口可以用Lambda转换
a.匿名内部类的名字
表面上是没有引用名的,
但其实是有用于定位的“名字”,
**如上代码,
new Foo()在定义的时候,
重写了bar()这个方法,
如此一来new Foo(){...}
这里就是一个匿名内部类
了;
呐这个匿名内部类,实际上在字节码
中是会定义出来的,!!!
定义出来一个用于定位的“名字”,
这个“名字”可见上面代码的第二行,
“com.bennyhuo.iiv.ch1.
”即代码包名
,
“OuterClass$1
”即外部内名
$1
,1
代表这个匿名内部类
,
是前缀的外部类中,定义的第一个匿名内部类,
再创建第二个匿名内部类 就是$2
了;
所以匿名内部类
跟普通类
一样,是可以加载
出来的!!!
只不过参数格式
不一样,普通类
是“class 类名
”匿名内部类
是“class 包名.外部类名$num
”**
b.匿名内部类的继承结构
- **
匿名内部类
被创建的时候,
就默认 匿名内部类
是作为一个子类
去继承
其对应的父类
了:(接口亦同)**
- **但是下面这种类型,
既 继承某个父类 又 实现某个接口 的“匿名内部类” 的 这种类型,
在Java中是不被接受的,
因为这其实是一种“或类型”,
即Runnable或上Foo的结果,作为一种类,
这在Java中是不被接受的:**
**即使使用Java 10 的var关键字来定义,
也是不行的,
这种类型还是不能被接受:
([在处理 var时,编译器先是查看表达式右边部分,
也就是所谓的构造器,并将它作为变量的类型,然后将该类型写入字节码当中]( https://www.cnblogs.com/feichen-2018/p/8650216.html))**
**嗯,
可是如果实在是想实现一个
如既 继承某个父类 又 实现某个接口 的“匿名内部类”
这样的类型,
但要不想占用太多资源
,要求同匿名内部类
一样用完即销毁
,怎么办?
那别用匿名内部类
呗,
在方法体内部
实现即可,!!!
便可以在方法调用完毕后将其回收
,
也可以达到需求
:**
**另外,Kotlin是可以实现,既 继承某个父类 又 实现某个接口 的“匿名内部类” 的 这种类型的
:
(object类似于class与定义一个引用,
object与后面冒号之间不接名字表示匿名,
冒号后面要继承什么,实现什么,直接写出来就是了)**
c.匿名内部类的构造方法(关注:匿名内部类
对外部类
的引用
)
- **匿名内部类会有外部类的引用,
这个可能导致内存泄漏!**
- **
匿名内部类
的构造方法
是编译器
帮忙定义
的!!!
开发者没有权 定义匿名内部类
的构造方法
;**
**编译器 会 根据代码 为 匿名内部类
的构造方法
引入一些参数,
如下面图中例子,
(右上)有一个OuterClass
,里边有一个InnerClass
,
(左上)这时候在Client类中,
new出来一个匿名内部类
,
**
匿名内部类——父类非静态、所在方法(匿`类被new出来的位置所处的方法)非静态
**例子中这个new出来的匿名内部类
,
实际上它的父类就是InnerClass
,
而InnerClass本身是一个非静态的内部类
,!!!!!非静态的内部类本身就会引用外部类的实例!!!!!!
,
所以OuterClass()的实例也会在这里(左上第四行)new出来;
而下方的Client$1
就是上述所说的匿名内部类的类型
了,Client$1
的命名格式其实就是刚刚说的外部内名$匿名内部类序号
;
所以图中下方代码块的第二行,
即编译器
根据代码 为 匿名内部类
生成的构造方法
,
其第一个参数
,就是Client,即匿名内部类所在方法
对应的 外部作用域
(最外部类);
因为这里的匿名内部类所在的方法
是非静态方法
,
所以一定是被某个实例(最外部类实例)
引用
着的!!!!!
其第二个参数
,即匿名内部类的父类
,
这个父类
如果是某外部类
的非静态内部类
,
那把这个对应的外部类实例
传进来即可,
因为这个外部类实例
可以应用到其成员
(包括非静态内部类
);**
匿名内部类——父类静态、所在方法非静态
interface
跟静态内部类
的效果是差不多的,
就是静态
的,
也就是不会去引用其外部类的实例!!!!!
所以这时候匿名内部类
的构造方法
的参数
就只有一个所在方法的最外部类实例
了;
匿名内部类--父类静态、所在方法静态
**而,当匿名内部类所在的方法是静态的,
则其构造方法的参数中,
不存在所在方法的最外部类实例
了;**
**即,
匿名内部类
的构造方法
的参数个数
,
由其父类
以及其所在方法
是否静态
决定,
父类非静态
,则需传入父类相关实例
;
所在方法非静态
,则需传入所在方法的最外部类实例
;
反则,
哪个静态了,就不用传哪个;
00 01 10 11**
捕获 匿名内部类 所在方法内
(外部作用域) 的 局部变量
快照的情况
匿名内部类
重写父类方法
时,引用到的外部方法体内
的局部final变量
**通常,要求要被捕获的局部变量
需要是final
修饰的;
虽然说如果不final
的话,
对匿名内部类
的构造方法
也不是很有影响
,
因为传给匿名内部类构造方法
的这个局部变量实例
,
实际上只是捕获局部变量实例
的一个快照
,快照
即复制一份引用
,给匿名内部类
的构造方法
去使用,
但是,
如果这个局部变量实例
不是final
的,
那局部变量实例
被重新赋值
了,
外部的局部变量实例
跟传给匿名内部类构造方法
的局部变量实例
,就不一样了!
这个肯定是不行的吧,
所以,
Java要求,
这个地方(图中左边,第三行,也即被传给 匿' 构造方法
的这个 局部实例
)一定
要是final
的,
原因就是为了让这个局部变量实例
,在外部和在 匿’ 构造方法
中,
给保持一致
!**
匿名内部类的构造方法小结
- 是编译器生成的
参数列表包括
- 外部对象(定义在非静态域内)<如上的Client>
- 父类的外部对象(父类非静态)<如上的OuterClass>
- 父类的构造方法参数(父类有构造方法且参数列表不为空)
- 外部捕获的变量(方法体内有引用外部final变量)<如上的Object>
- **事实上是可以通过
反射
,去修改
匿名内部类
的构造方法
持有的外部引用
(参数列表
)的**
Lambda转换(SAM转换)
- SAM--single abstract method 单一方法类型
一个接口,只有一个抽象方法时,可以用Lambda表达式替换实现;
关注语言版本的变化
- 体现对技术的热情
- 体现好学的品质
- 显得专业