内部类详解————匿名内部类

内部类三连击:

《内部类详解————匿名内部类》

《内部类详解————局部内部类》

《内部类详解————静态嵌套类》

应用场景

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

形式

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

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

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

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

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

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

使用规则

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

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

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

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

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

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

使用案例:

/** 定义接口*/
public interface MyInterface {void doSomething();
}
public class TryUsingAnonymousClass {// 外部类成员方法public MyInterface useMyInterface() {final int number = 201855;// jdk1.8后可以省略finalfinal Object obj = new Object();// jdk1.8后可以省略finalMyInterface myInterface = new MyInterface() {// 匿名内部类@Overridepublic 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的):

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

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

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

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

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

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

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

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

常量池(博主注:此常量池为class文件常量池,非运行时常量池,两者最大的区别是后者具有动态性)
中主要存放两大类常量:字面量和符号引用。字面量比较接近于Java语言层的常量概念,如文本字符串、声明为final的常量值等。

匿名内部类被编译成了class文件,它将final定义的局部变量编译进了class文件的常量池中,因此,我们会看上面的代码:

public static void main(String[] args) {TryUsingAnonymousClass tc = new TryUsingAnonymousClass();MyInterface inter = tc.useMyInterface();inter.doSomething();}

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

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

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

综上,就是最近对匿名内部类的研究和讨论。结合了final关键字的用法和class文件常量池来多角度讨论匿名内部类的final常量问题。后期如果有什么新的理解还会继续更新。文中的错别字和排版不适感博主已经进行了纠错和修改,如果各位在阅读时发现了任何错误,都请在文末留言。

 

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

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

相关文章

内部类详解————局部内部类

内部类三连击: 《内部类详解————匿名内部类》 《内部类详解————局部内部类》 《内部类详解————静态嵌套类》 定义 在方法或某个作用域内的内部类,称为局部内部类。匿名内部类就是一种局部内部类。 实现方式 public class OutterType …

内部类详解————静态内部类

内部类三连击: 《内部类详解————匿名内部类》 《内部类详解————局部内部类》 《内部类详解————静态内部类》 定义 静态内部类,又叫静态嵌套类或嵌套类。是使用static关键字修饰的内部类。 静态内部类可以用 private 修饰,这…

jvm gc垃圾回收机制和参数说明amp;amp;Java JVM 垃圾回收(GC 在什么时候,对什么东西,做了什么事情)

jvm gc(垃圾回收机制) Java JVM 垃圾回收(GC 在什么时候,对什么东西,做了什么事情) 前言:(先大概了解一下整个过程)作者:知乎用户 链接:https:…

【Mathematical Model】Ransac线性回归Python代码

Ransac算法,也称为随机抽样一致性算法,是一种迭代方法,用于从一组包含噪声或异常值的数据中估计数学模型。Ransac算法特别适用于线性回归问题,因为它能够处理包含异常值的数据集,并能够估计出最佳的线性模型。 1 简介 …

异常解析————Parameter metadata not available for the given statement

引言 在将数据存入mysql数据库时抛出异常:Parameter metadata not available for the given statement。参数元数据对于给定的声明不可用。 SQL本身并没有错误: Autowiredprivate JdbcTemplate jdbc;public Integer saveScenicSequence(ScenicSequence…

Swagger使用————接口参数注解的使用缺陷

问题描述 在使用springboot开发web项目时,用到了swagger框架,来生成web api文档。但是其中有一项是举例说明参数的结构,如下图:但是,这个功能真的是非常方便,因为可以让前端开发人员第一时间得知参数的内部…

分布式事务最终一致性常用方案

分布式事务最终一致性常用方案目前的应用系统,不管是企业级应用还是互联网应用,最终数据的一致性是每个应用系统都要面临的问题,随着分布式的逐渐普及,数据一致性更加艰难,但是也很难有银弹的解决方案,也并…

数据列表的分页实现————分页敏捷开发

概要 分页功能是比较常见的基础功能,虽然比较简单,但是每次需要用到这个功能的时候还是需要现写一遍。为了实现更加宏观的业务复用,特将本人特别喜欢的简易分页逻辑在此记述,以备日后重用。 逻辑描述 一般的分页实现方式多是通…

Eclipse深度患者设置VSCode快捷键

VSCode设置Eclipse中常用的快捷键 将eclipse中一些基本的快捷键输入右侧用户快捷键设置中: // Place your key bindings in this file to overwrite the defaults [{ "key": "alt/", "command": "editor.action.triggerSugges…

NodeJS学习————关于let和const命令的使用理解

let的基本用法 在新的js规范ES6中,新增了let 命令,用来声明变量。用法类似于var,但不同的是所声明的变量,只在let 命令所在的代码块内有效。 { let a 10; var b 10; } //ReferenceError: a is not defined console.log(a …

forward和redirect的区别是什么?

forward和redirect是什么? 是servlet种的两种主要的跳转方式。forward又叫转发,redirect叫做重定向。 区别:(本地效应次数) 地址栏,数据共享,应用场景,效率,本质&…

MYSQL的索引类型:PRIMARY, INDEX,UNIQUE,FULLTEXT,SPAIAL 有什么区别?各适用于什么场合?

一、MySQL索引类型 MySql常见索引类型有:主键索引、唯一索引、普通索引、全文索引、组合索引 PRIMARY KEY(主键索引) ALTER TABLE table_name ADD PRIMARY KEY ( column ) UNIQUE(唯一索引) ALTER TABLE table_name ADD UNIQUE (colu…

Servlet入门总结

一、了解Servlet的概念Servlet定义:Servlet是基于Java技术的Web组件,由容器管理并产生动态的内容。Servlet引擎作为WEB服务器的扩展提供支持Servlet的功能。Servlet与客户端通过Servlet容器实现的请求/响应模型进行交互。 注意:Servlet不是从…

MySQL日期类型的处理总结

一、概述 MySQL中的日期类型包括以下5种: 类型大小 (字节)范围格式用途DATE31000-01-01/9999-12-31YYYY-MM-DD日期值TIME3-838:59:59/838:59:59HH:MM:SS时间值或持续时间YEAR11901/2155YYYY年份值DATETIME81000-01-01 00:00:00/9999-12-31 23:59:59YYYY-MM-DD HH:…

详解HTTP协议~~~

详解HTTP协议~~~HTTP 简介HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。。HTTP是一个基于TCP/IP通信协议来传递数据(…

Mybatis Plus————代码生成器

代码生成器 MyBatis Plus是MyBatis的扩展框架,而代码生成器是MP的核心功能之一,另外还有 “条件构造器”和“通用CRUD”等功能。 步骤演示 mp的代码生成器有两种方式自动生成代码,一种是通过main方法来执行程序,另一种是通过maven…

Spring MVC 流程图解析

Spring MVC 流程图解析Spring MVC工作流程图图一图二 SpringMVC工作流程描述DispatcherServlet,HandlerMapping,HandlerExecutionChain,HandlerAdapter,HttpMessageConveter,BindingResult,ModelAndView&am…

Java并发编程实战————可重入内置锁

引言 在《Java Concurrency in Practice》的加锁机制一节中作者提到: Java提供一种内置的锁机制来支持原子性:同步代码块。“重入”意味着获取锁的操作的粒度是“线程”,而不是调用。当某个线程请求一个由其他线程持有的锁时,发出…

java的守护进程与非守护进程

java的守护进程与非守护进程 最近重新研究Java基础知识,发现以前太多知识知识略略带过了,比较说Java的线程机制,在Java中有两类线程: User Thread(用户线程)、Daemon Thread(守护线程) ,(PS:以前忽略了&a…

双剑合璧————Spring Boot + Mybatis Plus

引言 最近在学习Mybatis Plus的使用,希望通过spring boot快速将mybatis plus整合进来。 对于springboot项目,mybatis plus团队也有自己的启动器 :mybatis-plus-boot-starter。这个依赖内部已经整合了mybatis-spring,也包括非快速…