当前位置: 主页 > JAVA语言

java匿名内部类详解-匿名类 java

发布时间:2023-02-09 11:16   浏览次数:次   作者:佚名

883d116a2a2811e9524aac24e72844e1.png

Java是一种面向对象的编程语言,可以编写跨平台的应用软件。 Java技术具有出色的通用性、高效性、平台可移植性和安全性,被广泛应用于个人电脑、数据中心、游戏机、科学超级计算机、手机和互联网,拥有全球最大的专业开发者社区。

下面我给大家讲解一下Java基础内部类的一些知识点。 如果大家觉得有什么要补充的,可以在评论区讨论或者私聊我哦~

今天的详细讲解包括:内部类名词解析、内部类的类型、内部类的优势、内部类底层、内部类的应用场景、内部类常见面试题

1.内部类这个词的解析

在Java中,一个类的定义可以放在另一个类的定义里面,这就是内部类。 内部类本身是类的一个属性,定义方式与其他属性相同。

内部类的一个例子:

公共课外{

私人 int 半径 = 1;

公共静态整数计数 = 2;

公共外部(){

}

类内{

public void visitOuter() {

System.out.println("访问外层私有成员变量:" + radius);

System.out.println("访问外层静态变量:" + count);

}

}

} 复制代码

2. 内部类的类型

内部类可以分为成员内部类、局部内部类、匿名内部类和静态内部类四种。

db82a9e66ea7897c570e6633f4f6a5a9.png

1.成员内部类

在成员位置的类内部定义的非静态类是成员内部类。

公共课外{

私人静态 int 半径 = 1;

私人整数计数 = 2;

类内{

公共无效访问(){

System.out.println("访问外层静态变量:" + radius);

System.out.println("访问外层变量:" + count);

}

}

} 复制代码

成员内部类可以访问外部类的所有变量和方法,包括静态的和非静态的,私有的和公共的。 成员内部类依赖于外部类的实例,其创建方法是外部类的实例。 新建内部类(),如下:

外层外层 = 新外层层();

外。 内在=外在。 新的内部();

inner.visit(); 复制代码

2.局部内部类

方法中定义的内部类是局部内部类。

公共课外{

私人 int out_a = 1;

私人静态 int STATIC_b = 2;

public void testFunctionClass(){

int inner_c = 3;

类内{

私人无效乐趣(){

System.out.println(out_a);

System.out.println(STATIC_b);

System.out.println(inner_c);

}

}

内部内部=新内部();

内。 乐趣();

}

public static void testStaticFunctionClass(){

诠释 d = 3;

类内{

私人无效乐趣(){

// System.out.println(out_a); 编译错误,静态方法中定义的局部类不能访问外部类的实例变量

System.out.println(STATIC_b);

System.out.println(d);

}

}

内部内部=新内部();

内。 乐趣();

}

} 复制代码

实例方法中定义的局部类可以访问外部类的所有变量和方法,静态方法中定义的局部类只能访问外部类的静态变量和方法。 创建局部内部类的方法,在对应的方法new inner class()中,如下:

public static void testStaticFunctionClass(){

类内{

}

内部内部=新内部();

} 复制代码

3.匿名内部类

匿名内部类是没有名字的内部类,在日常开发中经常用到。

公共课外{

私人无效测试(最终诠释我){

新服务(){

公共无效方法(){

对于 (int j = 0; j < i; j++) {

System.out.println("匿名内部类");

}

}

}。方法();

}

}

//匿名内部类必须继承或实现一个已有的接口

接口服务{

无效方法();

} 复制代码

匿名内部类除了没有名字外,还有以下特点:

如何创建匿名内部类:

新类/接口{

//匿名内部类实现部分

} 复制代码

4.静态内部类

在类内部定义的静态类是静态内部类。

公共课外{

私人静态 int 半径 = 1;

静态类 StaticInner {

公共无效访问(){

System.out.println("访问外层静态变量:" + radius);

}

}

} 复制代码

静态内部类可以访问外部类的所有静态变量java匿名内部类详解,但不能访问外部类的非静态变量; 静态内部类的创建方法,新建外部类。 静态内部类(),如下:

Outer.StaticInner inner = new Outer.StaticInner();

inner.visit(); 复制代码

三、内部类的优点

为什么要使用内部类? 因为它有以下优点:

内部类对象可以访问创建它的外部类对象的内容,包括私有数据!

公共课外{

私人 int 半径 = 1;

受保护的无效测试(){

System.out.println("我是一个外部类方法");

}

类内{

公共无效访问(){

System.out.println("访问外部类变量" + radius);

测试();

}

}

} 复制代码

我们可以看到内部类Inner可以访问外部类Outer的私有变量radius或者方法test。

内部类不被同包的其他类看到,封装性好

当内部类修饰为private时,该类对外是隐藏的。 当一个内部类实现一个接口并进行向上改造时,接口的实现已经对外部隐藏起来,很好地体现了封装性。

//提供的接口

接口 IContent{

字符串 getContents();

}

公共课外{

//私有内部类屏蔽实现细节

私有类 PContents 实现 IContent{

@覆盖

公共字符串 getContents() {

System.out.println("获取内部类内容");

返回“内部类内容”;

}

}

//对外提供方法

公共 IContent getIContent() {

返回新的 PContents();

}

public static void main(String[] args) {

外层=新外层();

IContent a1 = 外部。 getIContent();

a1. 获取内容();

}

}

复制代码

我们可以发现Outer外部类对外提供getIContent方法,使用内部类实现细节,然后使用private修饰内部类进行屏蔽,充分体现了Java的封装性。

内部类有效的实现了“多重继承”,优化了java单继承的缺陷。

我们知道,在Java世界中,一个类只能有一个直接父类,即以单继承的方式存在。 但是内部类使“多重继承”成为可能:

抄袭Java编程思想,内部类实现“多重继承”的温情如下:

D类{}

抽象类 E{}

Z 类扩展 D {

E makeE(){ 返回新 E() {}; }

}

公共类多重实现{

static void 采用 D(D d) {}

static void 采用 E(E e) {}

public static void main(String[] args){

Zz = 新 Z();

取 D(z);

takesE(z.makeE());

}

} 复制代码

代码中出现了一个类D和一个抽象类E。 然后,用Z类继承D,内部类构造返回E。因此,当你想要D或E时,Z可以搞定,“多重继承”的特性就完美发挥了。

匿名内部类便于定义回调。

什么是回调? 假设有两个类A和B,A中调用了B的一个方法b,执行过程中b调用了A的方法c,那么c就调用了一个回调函数。

78a0e9a614b8ea987b601b98eba46ea0.png

当然回调函数也可以是函数,就是同步回调,最简单的回调方式。 回调的应用场景很多,比如android中的事件监听器。匿名内部类可以很方便的定义回调,看一个例子

//定义一个回调接口

公共接口回调{

无效执行();

}

公共课 TimeTools {

/**

* 通过定义CallBack接口的execute方法测试函数调用时长

* @param 回调

*/

公共无效测试时间(回调回调){

long beginTime = System.currentTimeMillis(); //记录开始时间

callBack.execute(); ///回调操作

long endTime = System.currentTimeMillis(); //记录结束时间

System.out.println("[使用时间]:" + (endTime - beginTime)); //打印使用时间

}

public static void main(String[] args) {

TimeTools 工具 = new TimeTools();

tool.testTime(新回调(){

//匿名内部类,定义execute方法

公共无效执行(){

TestTimeObject testTimeObject = new TestTimeObject();

测试时间对象。 测试方法();

}

});

}

}

复制代码

在调用testTime()测时间的时候,用匿名内部类实现一个方法execute(),在这个方法里做事情(执行目标函数),执行完返回testTime方法,很好实现测试持续时间的函数调用函数。 显然,匿名内部类使回调实现变得容易。

四、内部类底层

内部类标识符

每个内部类都会生成一个 .class 文件,其中包含有关如何创建该类型对象的所有信息。 内部类还必须生成一个 .class 文件来包含它们的类对象信息。 内部类文件的命名有严格的规定:外部类名+$+内部类名。

一个简单的例子:

公共课外{

类内{

}

} 复制代码

javac Outer.java编译后生成的class文件如下:

0e6942606adf0481a37f7743e3f0d51c.png

如果内部类是匿名的,编译器将简单地生成一个数字作为它的标识符。 如果内部类嵌套在其他内部类(静态内部类)中,只需在外部类标识符和“$”之后直接添加它们的名称即可。

为什么内部类可以访问外部类的成员,包括私有数据?

从上一节我们知道,内部类可以访问外部类的成员,包括私有数据。 那么它是如何做到的呢? 答案将在接下来揭晓。

看看这个简单的例子:

公共课外{

私人整数我= 0;

类内{

无效方法(){

System.out.println(i);

}

}

} 复制代码

一个外部类Outer,一个外部类私有属性i,一个内部类Inner,一个内部类方法method。 内部类方法访问外部类属性 i。

先编译,javac Outer.java,生成.class文件,如下:

0e6942606adf0481a37f7743e3f0d51c.png

javap-类路径。 -v Outer$Inner,反编译Outer$Inner.class文件得到如下信息:

a20bde1f9762ea7320252c9e5334f1f8.png

我们可以看到这一行,这是一个指向外部类对象的指针:

final innerclass.Outer this$0;

虽然编译器在创建内部类的时候添加了对外部类的引用,但是这个引用是如何赋值的呢? 编译器会在内部类的构造函数中添加一个参数进行初始化。 参数的类型就是外部类的类型,如下:

innerclass.Outer$Inner(innerclass.Outer);复制代码

成员内部类中的Outter this&0指针指向外部类对象,所以在成员内部类中可以自由访问外部类的成员。

局部内部类和匿名内部类访问局部变量时,为什么变量必须是final? 它的内部原理是什么?

先看这段代码:

公共课外{

void outMethod(){

最终int = 10;

类内{

void innerMethod(){

System.out.println(a);

}

}

}

} 复制代码

反编译(Outer$1Inner)得到如下信息:

f31e2dbbcb5bf2c6bb140a123bd673cc.png

我们可以在内部类的innerMethod方法中看到如下指令:

3:bipush 10 复制代码

在上面的例子中,为什么要添加final? 因为生命周期不一致,局部变量直接入栈,非final局部变量在方法执行结束时销毁。 但是,局部内部类仍然有对局部变量的引用。 如果局部内部类要调用局部变量,就会出错。 加上final可以保证局部内部类使用的变量和外层的局部变量区别开来,解决了这个问题。

我们再看一段代码,其实就是把变量a移到传参方法中。

公共课外{

void outMethod(final int a){

类内{

void innerMethod(){

System.out.println(a);

}

}

}

} 复制代码

反编译得到:

c2d4c641830ccd219069072c56fcf71c.png

我们看到匿名内部类Outer$1Inner的构造函数包含两个参数,一个是对外部类对象的引用,一个是int变量。 很明显,这里是将变量innerMethod方法中的形参a以参数的形式传入,对匿名内部类中的副本(变量a的副本)进行赋值和初始化。

那么,新的问题又出现了,既然innerMethod方法中访问的变量a和outMethod方法中的变量a不是同一个变量,那么在innerMethod方法中修改a会怎样呢? 那样就会造成数据不一致的问题。

如何解决? 使用final修饰符,final修饰的引用类型变量不允许指向新的对象,解决了数据不一致的问题。 注意:在Java8中,局部内部类引用的局部变量默认加上final,所以不需要加final关键字。

5、内部类的应用场景。

我们一般在什么场景下使用内部类呢?

场景之一:一些多算法的场合

在一些算法比较多的场合,也可以使用内部类,比如:

阵列。 排序(empsjava匿名内部类详解,新比较器(){

公共 int 比较(对象 o1,对象 o2)

{

返回 ((Employee)o1).getServedYears()-((Employee)o2).getServedYears();

}

}); 复制代码

场景二:解决一些非面向对象的语句块。

如果语句块很多,包括if...else语句、case语句等,不便于维护和扩展,那么可以使用内部类+设计模式来解决。

场景三:适当使用内部类,使代码更加灵活和可扩展。

适当地使用内部类可以使你的代码更加灵活和可扩展。 比如JDK的lamda表达式,使用了很多内部类,代码就优雅多了。如下

// JDK8 Lambda表达式写法

new Thread(() -> System.out.println("Thread run()")).start();

场景 4:当一个类不再被除它的外部类之外的其他类使用时。

如果一个类不能被其他类使用; 或者由于某种原因,不能被其他类引用。 那么我们可以考虑将其实现为一个内部类。 数据库连接池就是这样一个典型的例子。

6.内部类常见面试题

最后再来看一道经典的内部课堂面试题。

公共课外{

私人年龄 = 12;

类内{

私人年龄 = 13;

公共无效打印(){

年龄 = 14;

System.out.println("局部变量:" + age);

System.out.println("内部类变量:" + this.age);

System.out.println("外部类变量:" + Outer.this.age);

}

}

public static void main(String[] args) {

Outer.Inner in = new Outer().new Inner();

在.print();

}

}

复制代码

运行结果:

9467f7a621e7c595bd5b2323b56e4514.png

想要建筑师资料,请私聊我