今天发现友盟错误反馈一条信息如下:

从信息中可以确定是TencentUtil类中某个匿名内部类出了问题,但是因为此类中匿名内部类过多,具体定位是哪一个倒是有点不确定了,所以抽时间研究了下匿名内部类相关知识。

匿名内部类属于内部类的其中一种,从内部类讲起,内部类类型共有如下:

1.成员内部类

public class A {

String s;

class B{

}

}

B就是成员内部类,实例化B需要先实例化A对象(B b = new A().new B();),B会持有A对象的引用,所以鉴于这点,引出java中的内存泄漏问题。

2.局部内部类

public class A {

public C getB(){

class B extends C{

String s = "B";

}

return new B();

}

}

class C{

String s;

}

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

3.匿名内部类

匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。

例如点击事件

view.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

}

});

再例如子线程的runnable对象

4.静态内部类

这个把前面的成员内部类拿过来,前面加个static就行了

public class A {

String s;

static class B{

}

}

然后实例化的时候就不需要先实例化A,(B b = new A.B();),B也不会再持有A的对象引用,所以将内部类改为static能解决内存泄漏这个说法原因是在这。

看完上面 回到本文的出发点上来,内部类命名规则。

这么记吧,所有内部类会在编译的时候产生相对应的class文件,非匿名内部类类名规则为 OutClass$InnerClass (外部类类名与内部类类名中间用$连接) 匿名内部类类名则为OutClass$数字(OutClass$1,OutClass$2,OutClass$3)

public class A {

C c = new C(){

String s="i am c";

@Override

public void demo() {

}

};

C c2 = new C(){

String s="i am c2";

@Override

public void demo() {

}

};

}

interface C{

public void demo();

}

Thread对象是本类中第六个匿名内部类,runnable对象是这个thread对象中的第一个匿名内部类,OK TencentUtil$6$1.run代表的意思就是这个runnalbe中的run方法了。

===============================分割线===========================

最后说一个相关的问题。我们在用局部内部类和匿名内部类时,都要求局部变量为final,这是为啥呢?

想必这个问题也曾经困扰过很多人,在讨论这个问题之前,先看下面这段代码:

public class Test {

public static void main(String[] args) {

}

public void test(final int b) {

final int a = 10;

new Thread(){

public void run() {

System.out.println(a);

System.out.println(b);

};

}.start();

}

}

这段代码会被编译成两个class文件:Test.class和Testx.class(x为正整数)。

根据上图可知,test方法中的匿名内部类的名字被起为 Test$1。

上段代码中,如果把变量a和b前面的任一个final去掉,这段代码都编译不过。我们先考虑这样一个问题:

当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制 的手段来解决这个问题。将这段代码的字节码反编译可以得到下面的内容:

我们看到在run方法中有一条指令:

bipush 10

这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

下面再看一个例子:

public class Test {

public static void main(String[] args) {

}

public void test(final int a) {

new Thread(){

public void run() {

System.out.println(a);

};

}.start();

}

}

反编译得到:

我们看到匿名内部类Test$1的构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参a以参数的形式传进来对匿名内部类中的拷贝(变量a的拷贝)进行赋值初始化。

也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。

根据class名 赋值_匿名内部类 类名规则相关推荐

  1. C语言高级编程:数组名赋值给指针变量的用法解析

    代码: #include<stdio.h>typedef struct test {int a;int b;int c; } TEST;TEST arr[]={{1,2,3},{4,5,6 ...

  2. 数据库中表名、字段名、字符串大小写处理规则

    测试数据: CREATE TABLE `test` (`name` varchar(30)); insert into test values('abc'); insert into test val ...

  3. python变量无需创建赋值_阿博的Python之路-变量

    之前阿博分享了Python的运算符,在它的后面开始学习变量相关的知识.在这里要给小伙伴们分享Python的变量. 注意:小编的编码环境是Python 3. 什么是变量 Python变量是记录事物变化状 ...

  4. 当这个类被修饰public的话,为什么源文件名必须要与类名相同

    一个java源文件为什么只能有一个public类呢?当这个类被修饰public的话,为什么源文件名必须要与类名相同呢? Java编程思想中的一段话: 当编写一个java源代码文件时,此文件通常被称为编 ...

  5. C++ 不能通过给字符数组名赋值的方法给字符数组赋值

    已知有数组定义:char a[3][4]; 形如:"a[0] = "AAA";"的赋值语句是错误的,因为在C++中,无法通过给字符数组名赋值来实现给字符数组每一 ...

  6. java 反射set方法赋值_反射 根据属性名获得属性set方法并为set方法赋值

    /** * * @param key 属性名 * @param value 属性值 * @param o 要封装的对象 */ public static void setObject(String k ...

  7. Java标识符(类名、变量名、方法名、接口名、包名)命名的一般规则

    Java标识符的概念以及命名约定 标识符是那些可以起自定义的符号名都叫标识符,在Java中例如:类名.变量名.方法名.接口名.包名等等. 标识符的命名规则(不按这些规则命名会导致程序错误,编译不通过) ...

  8. java内部类赋值_详解 Java 内部类

    内部类在 Java 里面算是非常常见的一个功能了,在日常开发中我们肯定多多少少都用过,这里总结一下关于 Java 中内部类的相关知识点和一些使用内部类时需要注意的点. 从种类上说,内部类可以分为四类: ...

  9. mysql 动态传入表名 存储过程_面试再问MySQL存储过程和触发器就把这篇文章给他...

    Mysql存储过程及触发器trigger 存储过程 一.一个简单的存储过程 1,一个简单的存储过程 delimiter $$create procedure testa()begin Select * ...

最新文章

  1. Axure 8 window 下载,破解
  2. Emptyproject分析
  3. 检查JavaScript中变量是数字还是字符串
  4. k-means算法的理解与实现
  5. esp32摄像显示时间_物联网平台开发难学吗?掌握ESP32帮你1分钟入门
  6. 基于插件架构的简单的Winform框架(下)
  7. 搭建centos在线yum源镜像服务器,搭建CentOS在线yum源镜像服务器(上)
  8. 关于Linux学习的热身知识六
  9. [转]Kaldi语音识别
  10. JSP内置对象之application
  11. Qt 统计文件夹的文件总大小
  12. 西门子em235模块的功能_与其研究人工智能不如研究可编程控制器之德国西门子PLCS7200...
  13. Json文件格式化方法
  14. 什么句子可以暗示自己恋爱了?
  15. 尚硅谷云原生实战视频教程发布
  16. 分割网络对结构光图像进行分割
  17. 【PHP】`异客塞尔`世界 与 神奇的字符串++
  18. UNCTF2022 wp Re ezzzzre
  19. __del__()方法
  20. python 点击屏幕

热门文章

  1. gentoo doc web site
  2. 入门系列之在Ubuntu上安装Drone持续集成环境
  3. 通过ssl调用远程WebService
  4. [POJ2184] Cow Exhibition
  5. Firefox 有 6 成用户仍使用 Add-On 扩展
  6. FreeBSD下安装配置Hadoop集群(性能调优)
  7. 【学习笔记】python - pyecharts
  8. 解读MD07中可供货天数的计算
  9. 维护工厂的装运点确认
  10. 迪拜的经济支柱是什么?