匿名内部类属于局部内部类吗_内部类详解————匿名内部类

1fdb9569a019c9946bea503007d3a3dd.png

应用场景

由于匿名内部类不利于代码的重用,因此,一般在确定此内部类只会使用一次时,才会使用匿名内部类。

形式

public class OutterClass { public Runnable task() { return new Runnable() { @Override public void run() { System.out.println("匿名内部类..."); } }; }}

这种实现方式是不是很眼熟呢?

 // 初始化线程实例 Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("匿名内部类..."); } });

我们为线程创建一个Runnable子类实例的方式,就是一种匿名内部类的写法。我们通过这种没有名字的类,实现了将实现类(下称子类)实例创建与子类定义结合在一起的优雅格式,这也就是所谓的“使用类的定义直接创建实例”。

上面的代码是实现了Runnable接口,并重写了其中的run()方法,当然我们可以自己定义一个类(非接口)然后通过这种匿名内部类的方式来隐式的继承,并重写基类中的方法。

不论是继承父类,还是实现接口,实际上拿到的是父类或接口的引用。这个父类引用实际指向的是一个由匿名内部类定义的类的实例。因此,这个被继承的父类(或接口)必须是事先存在的。否则,编译器会提示你创建这个类。

使用规则

经过查阅资料和实操得出的匿名内部类的几条规则:

规则一:匿名内部类中的方法都是通过父类引用访问的,所以,如果定义了一个在父类中没有的方法,那么这个方法是不能被这个父类引用调用到的。(可以仅仅作为匿名内部类中方法之间的代码共享)。

规则二:匿名内部类既可以继承父类,也可以实现接口,但是不能两者兼备。而且如果实现接口也只能实现一个接口。

规则三:匿名内部类中不可能有构造器。但可通过实例初始化块 来达到构造器的效果,但是也不能重载实例初始化方法(即仅有一个这样的“构造器”)。

规则四:在匿名内部类中如果希望使用一个其外部定义的对象,那么编译器会要求其参数引用是final的。

关于第四条规则,这里牵涉了一个重要的且比较复杂的问题。

使用案例:

/** 定义接口*/public interface MyInterface { void doSomething();}
public class TryUsingAnonymousClass { // 外部类成员方法 public MyInterface useMyInterface() { final int number = 201855;// jdk1.8后可以省略final final Object obj = new Object();// jdk1.8后可以省略final  MyInterface myInterface = new MyInterface() { // 匿名内部类 @Override public void doSomething() { System.out.println("匿名内部类中使用基本数据类型:" + number); System.out.println("匿名内部类中使用引用数据类型:" + obj); } }; return myInterface; }  public static void main(String[] args) { TryUsingAnonymousClass tc = new TryUsingAnonymousClass(); MyInterface inter = tc.useMyInterface(); inter.doSomething(); }}

输出:

匿名内部类中使用基本数据类型:201855匿名内部类中使用引用数据类型:java.lang.Object@15db9742

我们通过匿名内部类的方式实现了接口MyInterface,并使用了外部类的成员方法useMyInterface() 中定义的两个局部变量:

int number = 201855;Object obj = new Object();

(在jdk1.8之后,新增了effectively final功能,开发者可以不必显式地使用final关键字来修饰局部内部类或匿名内部类中用到的局部变量,由系统默认添加。)

因此我们在匿名内部类中用到的局部变量必须为常量(对于基本类型,其值恒定不变;对于引用类型,其引用,即指向的地址恒定不变)。

如果强行改值,则会报错(这是在1.8程序上未使用final定义number时的尝试,系统果然默认此值为final的):

456f03fb1a06538bf7793d9bf74e0e62.png

不得不引出的局部变量与匿名内部类实例生命周期问题

我们知道成员方法中的局部变量是在运行期进行定义和初始化的,而局部内部类(包括匿名内部类)虽然是在方法中定义的,但是它却依然会在编译期实现从java文件到class文件的转化,即编译成class文件。

编译期在前,运行期在后。而我们却要在编译期使用运行期定义的变量!

怎么办?我们脑海中浮现了两个在编译期便能取得常量的相关关键字:static final 但显然,static无法定义局部变量。

那final能为我们的程序带来什么?

翻阅《Java编程思想》中对final关键字的剖析(第四版,140页):

一个永不改变的编译时常量。

《深入理解Java虚拟机:JVM高级特性与最佳实践》中(第二版,168页)对于Class文件常量池也做出了相关解释:

常量池(博主注:此常量池为class文件常量池,非运行时常量池,两者最大的区别是后者具有动态性)

中主要存放两大类常量:字面量和符号引用。字面量比较接近于Java语言层的常量概念,如文本字符串、声明为final的常量值等。

int型局部变量number和Object类型obj在方法useMyInterface()执行完毕之后即结束了生命周期,但是在下面通过调用inter对象的doSomething()方法依然可以有效的输出这两个值,说明这两个常量并没有受到外部类方法执行完毕而导致局部变量生命周期结束的问题,实际上number和obj已经存在于匿名内部类对应的class文件中的常量池中。

虽然final修饰的常量解决了在编译期拿到运行期的变量的问题,但是final带来的副作用是,这个值无法改变。

对于需要改变局部变量值的情况,我们可以通过在匿名内部类中使用赋值的方式(学名:引用拷贝 =.0)来“接管”局部变量的值,然后我们就可以随意更改这个值了。

综上,就是最近对匿名内部类的研究和讨论。结合了final关键字的用法和class文件常量池来多角度讨论匿名内部类的final常量问题。

---欢迎关注【Java圣斗士】,我是你们的小可爱(✪ω✪) Morty---

---专注IT职场经验、IT技术分享的灵魂写手---

---每天带你领略IT的魅力---

---期待与您陪伴!---

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值