匿名内部类 - ( 零基础学java )

Java-匿名内部类

我们先分析匿名内部类的结构,然后逐一解释,最后以下罗列的问题都会在下面的内容中一一得到解答 :

匿名内部类到底是什么?

我们为什么要学习匿名内部类 ?

匿名内部类都有怎样的作用 ?

匿名内部类应用的场景又有哪些 ?

匿名内部类是否有缺陷?

让我们带着这些问题来学习Java中的匿名内部类吧 !

结构决定性质

结构

  • 匿名内部类基本语法 :
new 父类构造器(参数) / 实现接口() {// 类的主体部分
};
  • 解释匿名内部类语法中所有的概念:

    • new 父类构造器(参数) 表示匿名内部类是某个类的子类实例。

    • 实现接口() 表示匿名内部类是某个接口的实现实例。

    • { ... } 内部是匿名内部类的主体部分,包含类的字段、方法等定义。


性质

匿名内部类(Anonymous Inner Class)是一种在声明和创建对象的同时定义类的方式,它没有显式的类名。通过 匿名内部类 看这几个字的字面意思我们都知道这是个没有名字的类,即 非具名类 . 以下是匿名内部类的具备的一些性质 :

  1. 可以实现接口或继承类: 匿名内部类可以实现接口或继承某个类,从而提供具体的实现。
  2. 没有显式的类名: 匿名内部类没有显式的类名,因为它是一种临时的、一次性的实现。
  3. 一次性使用: 通常用于临时的、一次性的场景,不需要复用。因为匿名内部类没有类名,所以无法在其他地方重复使用。
  4. 可以访问外部类的成员: 匿名内部类可以访问外部类的成员,包括成员变量和方法。对于外部类的局部变量,有一些规则,比如必须是final或者事实上是final的。
  5. 可以包含字段和方法: 在匿名内部类的主体部分,可以包含字段(成员变量)和方法的定义。
  6. 不可以包含静态成员: 匿名内部类不能包含静态成员,包括静态方法和静态变量。

我们来一个一个的解释,同时我也会拿具体的代码演示.

可以实现接口或继承类

  • 匿名内部类可以用来实现接口或者继承类,这也是匿名内部类最常用的一个用法,我们在实际开发中,如果需要实现(重写)某个接口(类)的方法,而且这个方法只是临时使用,不需要复用,而且会出现很多这样的场景,我们肯定是不想一一都去额外的封装一个类去实现接口或者继承类然后再创建类的实例,取调用我们需要的方法,这个时候我们就 匿名内部类 就排上用场啦,我们可以通过匿名内部类去临时创建一个接口的实例(接口不能实例化,这里只是形象比喻一下)或者创建一个临时的子类重写了父类方法的实例. 总结一下 : 当匿名内部类需要实现接口或者继承类时,它可以直接在创建对象的地方定义类的主体,而不需要显式地声明一个具名的类。
package src.demo;//定义一个接口
interface  Chef{void cook();
}class Sever{public void shangCai(){System.out.println("服务员上菜");}
}public class Demo01 {public static void main(String[] args) {//使用匿名内部类来实现接口Chef chef1 = new Chef() {@Overridepublic void cook() {System.out.println("炒蛋炒饭");}};//调用我们使用匿名内部类实现接口实例中的抽象方法chef1.cook();//使用匿名内部类重写父类方法Sever sever = new Sever() {@Overridepublic void shangCai() {System.out.println("服务员上菜->蛋炒饭");}};//调用我们使用匿名内部类重写父类的方法sever.shangCai();}
}

通过上述的代码,我演示了两种使用匿名内部类的情况:

  1. 实现接口: 通过匿名内部类实现了 Chef 接口,提供了 cook 方法的具体实现。

    Chef chef1 = new Chef() {@Overridepublic void cook() {System.out.println("炒蛋炒饭");}
    };
    

    这部分代码演示了匿名内部类用于实现接口的情况,通过创建一个实现了 Chef 接口的匿名内部类的实例,重写了接口中的抽象方法 cook

  2. 重写父类方法: 通过匿名内部类重写了 Sever 类的 shangCai 方法。

    Sever sever = new Sever() {@Overridepublic void shangCai() {System.out.println("服务员上菜->蛋炒饭");}
    };
    

    这部分代码演示了匿名内部类用于重写父类方法的情况,通过创建一个继承自 Sever 类的匿名内部类的实例,重写了 Sever 类的方法 shangCai

通过这两种使用情况,你展示了匿名内部类在实现接口和重写父类方法时的便利性。匿名内部类可以直接在创建对象的地方提供类的定义,避免了显式地声明一个具名的类,尤其适用于一次性的实现或重写。

没有显式的类名

没有显式的类名,这意味这我们使用匿名内部类创建出来的实例是无法通过类名去访问的,如果我们需要实现函数回调的话,我们就可以使用父类引用或者接口引用去接收. 所以,我们可以知道 **匿名内部类是必须基于已存在的类或者接口,因为它的实质是在这个基础上创建一个新的匿名子类或者实现一个匿名实例,**这样做的优势就是我们省略了显式的创建一个具名子类的步骤,这对于我们一些简单的或者一次性的任务是非常方便的,因我们我们可以在需要的地方直接实现类的功能,而无需为此专门定义一个新的类,这使得代码可以更为紧凑和直观.

一次性使用

匿名内部类具备这个性质的理由就是 : 因为匿名内部类没有类名,所以无法在其他地方重复使用。

可以访问外部类的成员

实际需求中如果我们需要使用匿名内部类访问外部类的一些成员(包括属性和方法) , 那么其中是否有什么限制或者规则吗 ? 有的 ! 我们后续通过代码测试也可以知道,在这里我们先提前说明有怎样的规则 :

匿名内部类可以访问外部类的成员,包括成员变量和方法。对于外部类的局部变量,有一些规则,比如必须是final或者事实上是final.

(1)外部类的成员变量:

匿名内部类中可以访问外部类的成员变量,并且可以进行修改。这是因为外部类的成员变量在匿名内部类中有完整的作用域,而且匿名内部类实际上是外部类的一个扩展,可以直接访问外部类的成员。(需要注意的是 : 在匿名内部类的方法中,可以访问和修改外部类的成员变量。这些修改在匿名内部类的方法中生效,但不会影响外部类实例之外的其他代码。这是因为匿名内部类实际上是一个新的类,其代码块被包含在外部类的方法内部,而成员变量的修改是在匿名内部类的方法中执行的。)

实例代码如下 :

package src.main;public class Example {private int memberVar = 10;public void modifyMemberVar() {// 匿名内部类Runnable r1 = new Runnable() {@Overridepublic synchronized  void run() {// 访问外部类的成员变量System.out.println("Before modification: " + memberVar);// 修改外部类的成员变量memberVar = 20;System.out.println("After modification: " + memberVar);}};Runnable r2 = new Runnable() {@Overridepublic synchronized  void run() {// 访问外部类的成员变量System.out.println("Before modification: " + memberVar);// 修改外部类的成员变量memberVar = 20;System.out.println("After modification: " + memberVar);}};// 使用匿名内部类的实例new Thread(r1).start();new Thread(r2).start();}public static void main(String[] args) {Example example = new Example();example.modifyMemberVar();}
}
  1. 代码中,匿名内部类实现了Runnable接口,其中的run方法中访问并修改了外部类Example的成员变量memberVar
  2. 匿名内部类的作用域包括了它所在的方法,也就是modifyMemberVar方法。在这个方法中,您创建了两个不同的匿名内部类的实例(r1r2),每个实例都有自己的run方法。因此,每个实例的run方法中的对memberVar的修改是在各自匿名内部类的作用域内完成的。
  3. modifyMemberVar方法中创建了两个线程,每个线程都启动了一个匿名内部类实例。这意味着两个线程可以并发地执行各自匿名内部类的run方法,但彼此之间的修改不会相互干扰。

(2)外部类的局部变量:

匿名内部类可以访问外部类的成员变量和方法,但如果要访问外部方法中的局部变量,这个局部变量必须是 final 或者是 effectively final。这是因为匿名内部类的实例可能会在方法执行完毕之后仍然存在,而且对局部变量的引用是在匿名内部类中存储的。如果局部变量不是 final 或者 effectively final,那么在方法执行完毕后,外部局部变量的生命周期结束,但匿名内部类的实例可能仍然存在,这时如果访问这个局部变量就会出现问题。

这也就是我们说的 变量捕获机制,确切地说是对局部变量的捕获机制。

变量捕获机制的关键是,编译器会生成一个匿名内部类的构造函数,并将外部的局部变量传递给这个构造函数,以确保匿名内部类在之后仍然能够访问这些变量。这种捕获方式确保了在匿名内部类中对外部局部变量的访问是安全的。

示例代码如下:

public class Example {public void someMethod() {final int localVar = 42;//局部变量
//局部变量必须是 final 或者是 effectively finalRunnable r = new Runnable() {@Overridepublic void run() {//localVal = 1; 报错,无法修改System.out.println(localVar);//在匿名内部类中访问外部类的局部变量}};// 使用匿名内部类的实例new Thread(r).start();}
}
  • 在外部类修改局部变量的值(报错)
image-20231211084543439
  • 在匿名内部类中修改局部变量的值(报错)
image-20231211084615734
  • 不修改外部类局部变量的值,而是显式的让其被final修饰
image-20231211084851143
  • 不修改外部类的局部变量的值,而是隐式的意味着事实上是final,也就是局部变量一旦被定义就不再被修改,那么在事实上就可以认为这个局部变量是一个final修饰的了,而且就是修改了,在匿名类中访问这样的被修改的外部类的局部变量是会报错的!!! 因为变量捕获机制的存在 !!!
image-20231211085216762

在上述代码中,localVar 是一个局部变量,因为匿名内部类 Runnable 中引用了这个局部变量,所以必须声明为 final

(3) 所以,总结一下:

  • 外部类的成员变量可以在匿名内部类中被访问和修改。
  • 外部类的局部变量必须要求是 final 的,或者是事实上是 final 的,才能在匿名内部类中被访问。(变量捕获机制)

可以包含字段和方法

贴代码

interface Greeting {void greet();
}public class AnonymousInnerClassExample {public static void main(String[] args) {// 使用匿名内部类实现接口并包含字段和方法Greeting greeting = new Greeting() {//字段private String message = "Hello from anonymous inner class";@Overridepublic void greet() {System.out.println(message);sayGoodbye(); // 调用匿名内部类中定义的方法}//方法private void sayGoodbye() {System.out.println("Goodbye from anonymous inner class");}};// 调用实例方法greeting.greet();}
}

不可以包含静态成员

这是我的理解 :

匿名内部类的本质是一个实例,这个实例可以用来重写父类中的实例方法,也可以用来实现接口中的抽象方法,也可以用来实现抽象类中的抽象方法,但是不可以声明静态成员变量或者定义静态方法,虽然可以重写父类的静态方法,但是重写的静态方法是通过实例调用的,这违背了java面向对象编程的理念.所以匿名内部类是一个非具名的实例,之所以叫做类是因为匿名内部类既完成了拓展原有类或者接口的目的又完成了实例的创建.

( 匿名内部类是一个实例,而不是一个类。静态成员是属于类的,而匿名内部类没有类名,无法定义属于自己的静态成员。)

贴代码 :

package src.main;public class AnonymousInnerClassExample {public static void main(String[] args) {// 尝试在匿名内部类中定义静态成员(编译错误)Runnable myRunnable = new Runnable() {// 尝试定义静态成员变量(编译错误)// static int staticVariable = 20;@Overridepublic void run() {// 尝试定义静态方法(编译错误)// staticMethod();System.out.println("Inside Runnable");}};// 调用匿名内部类中的 run 方法myRunnable.run();}// 尝试在外部类中定义静态方法(正常)public static void staticMethod() {System.out.println("Static method");}
}

分析 : 在这个例子中,我们尝试在匿名内部类中定义静态成员变量和静态方法,但这会导致编译错误。匿名内部类无法包含静态成员,因为它本身是一个实例,而静态成员是属于类的,必须在类级别上声明。


  • 通过分析匿名内部类的结构和性质,我们已经解决了文章开头我们提出的疑问了,现在还剩下最后一个疑问,那就是 : 匿名内部类是否有缺陷? 如果有缺陷是否有更好的替代方案 ? (答案是有缺陷,而且有更好的替代方案)

    匿名内部类是一种便捷但有局限性的编程方式。虽然它在短期、轻量级的情况下很方便,但它的一次性使用、可读性较差、不适合复杂继承关系、对外部变量的访问限制、不能包含静态成员以及可能导致生命周期延长等缺陷,使得在一些复杂或长期维护的场景中,更倾向于使用具名类或Lambda表达式等更灵活的替代方式。


使用场景

在以下的场景中,匿名内部类的简洁语法和临时性的特性使得它成为一种方便的编码方式。然而,需要在使用时权衡其便利性和一些限制,选择适合当前情况的实现方式。

  1. 事件处理

    在 GUI 编程中,匿名内部类常用于处理用户界面上的事件,比如按钮的点击事件。这简化了代码,避免了为每个事件都创建一个独立的类。

    button.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// 处理按钮点击事件的逻辑}
    });
    
  2. 线程创建

    匿名内部类可以用于创建简单的线程对象,尤其在某个地方需要一次性的线程执行逻辑时。

    new Thread(new Runnable() {@Overridepublic void run() {// 线程执行的逻辑}
    }).start();
    
  3. 实现接口或抽象类

    当只需要实现某个接口或继承某个抽象类的单一实例时,匿名内部类可以提供简洁的语法。

    SomeInterface instance = new SomeInterface() {@Overridepublic void someMethod() {// 实现接口的逻辑}
    };
    
  4. 测试和调试

    在单元测试或调试时,有时需要临时性地实现某个接口或者继承某个类,匿名内部类能够方便地提供这种快速的实现。

    TestingTool.runTest(new TestInterface() {@Overridepublic void runTest() {// 测试逻辑}
    });
    
  5. 简化工厂方法

    在工厂方法中,如果只需要创建一个实例,匿名内部类可以用于快速创建。

    Factory.createInstance(new SomeInterface() {@Overridepublic void someMethod() {// 实现接口的逻辑}
    });
    
  6. 回调函数

    在某些设计模式或异步编程中,匿名内部类可以作为回调函数,用于定义异步操作完成后的回调逻辑。

    asyncOperation.doAsync(new Callback() {@Overridepublic void onComplete() {// 异步操作完成后的回调逻辑}
    });
    

通过上述的分析, 相信大家应该对Java中的匿名内部类有了很深刻的理解了,如果文章中有什么地方写的不对,或者理解有误,欢迎大家在评论区留言, 教学相长 ~

如果觉得这篇文章有帮助到您,您的三连是对我最大的支持 !

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/213365.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java (JDK 21) 调用 OpenCV (4.8.0)

Java 调用 OpenCV 一.OpenCV 下载和安装二.创建 Java Maven 项目三.其他测试 一.OpenCV 下载和安装 Open CV 官网 可以下载编译好的包,也可以下载源码自行编译 双击安装 opencv-4.8.0-windows.exe 默认为当前目录 安装即解压缩 根据系统位数选择 将 x64 目录下 op…

开往渤海的列车:沧港铁路如何扮演产业带城市生态共赢的关键先生

新时代构建新格局,新格局呼唤新作为。在交通强国战略背景下,铁路运输企业需要如何彰显“铁担当”? 逢山开路、遇水架桥,身处重要地理区位,沧州沧港铁路有限公司(以下简称“沧港铁路”)不断抢抓…

并查集带压缩路径的find

目录 原因: 优化: 原因: 当路径比较特殊,如图: 非常深,最底层进行find时,循环找根(或者递归找),消耗就比较大。 我们可以进行优化。 优化: &…

【C++】C++异常语法、使用、规范、异常安全及异常的优缺点

1. C异常概念 异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。 throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。catch: 在您想要处理…

给你的Python程序添点Emoji魔法:使用Emoji模块增添趣味和个性!

当你想给你的Python程序增添一些趣味和个性时,Emoji模块是一个很有用的工具。Emoji模块允许你在Python中使用各种表情符号,从笑脸到动物,甚至是食物和天气等。在本篇博客中,我们将介绍如何在Python中使用Emoji模块,并展…

【小白专用】使用PHP创建和操作MySQL数据库,数据表

php数据库操作 php连接mysql数据库 <?php $hostlocalhost; // 数据库主机名 $username"root"; // 数据库用户名 $password"al6"; // 数据库密码 $dbname"mysql"; // 数据库名 $connIDmysqli_connect($host,$username,$password,$dbn…

MQTT服务质量-QoS

QoS是消息发送方和接收方之间的协议&#xff0c;定义了指定消息发送保证等级。本文将深入探究MQTT中不同的QoS等级。 QoS是什么 MQTT提供三个QoS等级&#xff1a; 最多一次&#xff08;QoS 0&#xff09;至少一次&#xff08;QoS 1&#xff09;确切一次&#xff08;QoS 2&am…

科技提升安全,基于YOLOv5系列模型【n/s/m/l/x】开发构建商超扶梯场景下行人安全行为姿态检测识别系统

在商超等人流量较为密集的场景下经常会报道出现一些行人在扶梯上摔倒、受伤等问题&#xff0c;随着AI技术的快速发展与不断普及&#xff0c;越来越多的商超、地铁等场景开始加装专用的安全检测预警系统&#xff0c;核心工作原理即使AI模型与摄像头图像视频流的实时计算&#xf…

使用alpine镜像部署go应用时踩的坑

使用alpine镜像部署go应用时踩的坑 关于交叉编译 实际上我在ubuntu的交叉编译出来的exe并不能在alpine上运行&#xff0c;这边采取拉镜像编译复制出来的做法&#xff0c;部署再用干净的alpine 拉取golang:alpine踩坑 在Dockerhub上可以找到&#xff1a; 然而拉取的alpine中…

在普通的项目中创建web的功能

新增web功能: 1.创建一个新项目&#xff0c;不勾选模板&#xff1a;2.添加web功能&#xff1a; 1.创建一个新项目&#xff0c;不勾选模板&#xff1a; 发现普通项目没有webapp文件夹&#xff0c;即没有web的功能。 2.添加web功能&#xff1a; Add framework support:添加一些…

luceda ipkiss教程 45:在版图上加LOGO

**在设计版图时往往需要加上公司或者学校的LOGO,只需要LOGO的图片&#xff0c;通过代码就可以将LOGO加到版图上&#xff0c;比如&#xff1a; ** 通过代码可以得到版图上的LOGO: ! 代码如下&#xff1a; from si_fab import all as pdk from ipkiss3 import all as i3i3.TECH…

国际验证码有哪些具体的应用场景?

用户注册 在许多网站和应用程序中&#xff0c;用户注册是必要的第一步。通过使用验证码接口&#xff0c;可以防止恶意机器人或自动化程序大规模注册账号&#xff0c;从而保护网站或应用程序的安全性和可靠性。 密码重置 当用户忘记密码或需要重置密码时&#xff0c;验证码可…

MyBatis逆向工程

正向工程&#xff1a;先创建Java实体类&#xff0c;由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。逆向工程&#xff1a;先创建数据库表&#xff0c;由框架负责根据数据库表&#xff0c;反向生成如下资源&#xff1a; Java实体类Mapper接口Mapper映射文件 1…

docker的基本管理和概念

docker是什么&#xff1f; docker是开源的应用容器引擎。基于go语言开发的。运行在Linux系统中的开源的轻量级的“虚拟机”。 docker的容器技术可以在一台主机上轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器 docker的宿主机是linux系统。集装箱可以理解为相互…

CAN总线协议编程实例

1. can.h #ifndef __CAN_H #define __CAN_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/ /* CAN 引脚 定义 */#define CAN_RX_GPIO_PORT GPIOA #define CAN_RX_GPI…

R2RNet: Low-light Image Enhancement viaReal-low to Real-normal Network

本研究针对在弱光条件下拍摄的图像可能严重降低图像质量的问题进行了探索。解决一系列低光图像的退化可以有效提高图像的视觉质量和高级视觉任务的性能。在本研究中&#xff0c;我们提出了一种新颖的基于Retinex的真实低光到真实正常光网络&#xff08;R2RNet&#xff09;&…

Vue自定义指令插槽作用域插槽具名插槽

Vue自定义指令&插槽&作用域插槽&具名插槽 一、学习目标 1.自定义指令 基本语法&#xff08;全局、局部注册&#xff09;指令的值v-loading的指令封装 2.插槽 默认插槽具名插槽作用域插槽 3.综合案例&#xff1a;商品列表 MyTag组件封装MyTable组件封装 4.路…

小红书笔记投流全攻略,打造爆款内容

在小红书平台上&#xff0c;信息流投放和搜索广告是两种主要的广告形式。信息流投放主要通过用户刷作品时展示你的笔记&#xff0c;而搜索广告则是用户搜索相关关键词时展示出的内容。今天就和大家分享下小红书笔记投流全攻略&#xff0c;打造爆款内容&#xff01; 一、什么样你…

探秘ipa文件签名工具在线签名工具:工作原理和代码表示原理

随着iOS应用程序的兴起&#xff0c;ipa文件的安全性变得越来越重要。为了确保应用程序来源的可信度和完整性&#xff0c;开发者需要对其应用进行签名&#xff0c;并使用正确的证书来验证其身份。在这篇文章中&#xff0c;我们将探索一个名为在线签名工具的ipa文件签名工具&…

【JavaWeb笔记】单选框,结合Servlet

各个部分的作用 jsp部分 form action"..."&#xff1a;表单标签&#xff0c;供用户提交数据。内部的submit点击之后相当于是点action的URL input type"radio"&#xff1a;输入类型为单选框。把name设置为一样的&#xff0c;这样效果上就是单选&#xff…