java内部类初探

引言

内部类是定义在另一个类中的类,它曾经对于简洁地实现回调非常重要,不过今天lambda表达式在这方面可以做的更好。但内部类对于某些结构的构建还是很有用的。

普通内部类

常规的内部类有以下两个特点:

1.内部类可以对除了自己的外部类以外的其他类隐藏。

2.内部类方法可以访问其外部类的字段,包括私有字段(默认拥有一个对外部类的引用)。

public class InnerClassTest {public static void main(String[] args) {Outer outer = new Outer(1);Outer.Inner inner = outer.new Inner();inner.printA();}
}class Outer{private int a;public Outer(int a) {this.a = a;}class Inner{public void printA(){System.out.println(a);}}
}

上面的例子中,我们可以看到,内部类Inner的方法printA中可以直接访问其外部类的实例字段。

而在main函数中我们发现,在调用这个内部类的方法之前有两步操作:

1.创建一个外部类对象。

2.使用这个外部类对象new一个内部类对象。

这也就可以看出内部类的另外一个特性:

一个内部类对象的存在必须基于一个外部类对象,而外部类对象是可以独立存在的。

备注:内部类不能有static方法,看似内部类定义一个只能访问外部类静态字段和静态方法的方法是符合逻辑的,但java的设计者认为这只是徒增语言的复杂性,并没有选择这么做。 

私有的内部类

内部类与普通的类不同,它可以标记为private,这意味着只有它的外部类对象可以通过其方法去创建一个私有内部类。

public class InnerClassTest {public static void main(String[] args) {Outer outer = new Outer(1);outer.useInnerPrintA();}
}class Outer{private int a;public Outer(int a) {this.a = a;}public void useInnerPrintA(){Inner inner = new Inner();inner.printA();}private class Inner{public void printA(){System.out.println(a);}}
}

内部类的特殊语法规则

1.在内部类中调用外部类的字段和方法时,有一个更为完整的方法:OuterClass.this.parameter

当内部类和外部类中有重名变量时,这种方式也可以明确和区分两个同名的变量。

2. 可以使用一个完整版的构造器:outerObject.new InnerClass(construction parameters)

3.在内部类的作用域之外,还可以这样引用内部类成员:OuterClass.InnerClass.parameter

public class InnerClassTest {public static void main(String[] args) {Outer outer = new Outer(1);// 使用格式 outerObject.new InnerClass(construction parameters)Outer.Inner inner = outer.new Inner();inner.printA();// 使用格式 OuterClass.InnerClass.parameter 来引用内部类System.out.println(Outer.Inner.a);}
}class Outer{private int a;public Outer(int a) {this.a = a;}public void useInnerPrintA(){// 用如下方式改写可以增加明确性Inner inner = this.new Inner();inner.printA();}class Inner{static int a = 2;public void printA(){// 如果有重名变量,该名称会指向内部类变量System.out.println(a);// 使用 OuterClass.this.parameter 格式来指代外部对象System.out.println(Outer.this.a);}}
}

局部内部类

当内部类只被某个方法使用了一次时,可以在这个方法内定义这个内部类,这样的内部类就称为局部内部类。

就像它的名字一样,局部内部类的作用域固定在这个方法内,因此不必也不能为其添加访问修饰符,如public、private。

class Outer{private int a;public Outer(int a) {this.a = a;}public void useInnerPrintA(){class Inner{    //局部内部类public void printA(){System.out.println(a);}}Inner inner = new Inner();inner.printA();}}

局部内部类可以像一般的内部类一样访问它外层的字段,这里不仅包括外部类的字段,也包括它所在的方法的局部变量,但需要注意,它访问的局部变量必须是事实最终变量才可以,也就在事实上是不会改变的变量。

class Outer{private int a;public Outer(int a) {this.a = a;}public void useInnerPrintA(int b){// 此处的b和c都是局部字段// 若局部内部类引用了它们,则不可被更改int c = 3;class Inner{public void printA(){System.out.println(a);System.out.println(b);System.out.println(c);a++;    // 合法b++;    // 不合法c++;    // 不合法}}Inner inner = new Inner();inner.printA();}}

局部内部类和lambda表达式在这方面是一样的,笔者认为它们是近亲,它们在离开了其所在的方法体之后,该方法体所使用的栈内存被释放,但局部内部类和lambda表达式内部所使用过的这些局部字段会连同它们一起,被持久的保留在堆内存中,这些保留在堆内存中的它们可能会在此后任意的时刻被调用和访问。

上述案例中的变量a并不限制为事实最终变量,原因很明显,因为它本来就是外部类对象的实例字段,并不会因为方法体的结束而消亡,与上述过程不符。

public class TimerTest
{  public static void main(String[] args){  int start = 10;// 此处的lambda表达式引用了外部字段start,不得被修改var timer1 = new Timer(1000, e -> System.out.println(start--));    //不合法}
}

如果不是事实最终变量,那么在多线程环境下就会产生不可预测性和严重的安全性问题,所以java的设计者为了安全考虑,要求这些外部的局部字段必须是事实最终变量

匿名内部类

你可能以前了解过匿名内部类,它往往是用于临时创建某个实现了特定接口的对象。但事实上,它既可以用于一个必须要实现某个或者某些方法的接口类、抽象类,也可以用于一个普通类。

基于类的方法重写

当你仅仅想要创建某个类的一个对象,并且希望它有特殊的动作时,你可以使用匿名内部类去完成这个动作,但特殊的动作一般仅限于对已有的方法进行重写才有意义。

你甚至可以为这个特殊的对象添加只属于它的字段和方法,但你根据stu这个变量去调用它们的时候却是做不到的,因为编译器只会将stu这个变量当做是一个普通的Student对象。(具体请见下方代码)

public class InnerClassTest {public static void main(String[] args) {Student stu = new Student(18) {// 无意义的操作(可以通过编译,但不能使用)String name = "Bob";// 无意义的操作(可以通过编译,但不能使用)public void print() {   System.out.println("haha");}@Overridepublic String toString() {return "Student age:" + getAge();}};System.out.println(stu);//以下两句无法通过编译System.out.println(stu.name);stu.print();}
}class Student{private int age;public Student(int age) {this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

可以看到,在上述代码中,stu所指向的对象拥有仅属于它自己的toString方法;而新定义的print方法是不可用的,编译器仅将它当做是一个普通的Student对象,不能识别到这个新定义的方法,因此也就不能使用它。

基于接口的方法实现

在上面的情况之外,也是更通常的情况下,你的匿名内部类会建立在一个接口类的实现上,那么就要提供一个接口的空构造器,注意这里的小括号是不能缺省的:

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;public class InnerClassTest {public static void main(String[] args) {// 使用new构造了一个接口的对象,此处小括号不能省略Timer timer = new Timer(1000, new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {System.out.println("每隔一秒执行一次");}});timer.start();while (true){}}
}

上面这个例子你不需要了解它的全部细节,我们仅用它来展示一个基于接口实现的匿名内部类,你只需要大概的知道是什么意思就好了。

要求外部变量是事实最终变量

匿名内部类不仅跟lambda表达式是近亲,甚至几乎就是一个东西,所以跟上面所述的内容一样,它所引用的外部变量也要求是事实最终变量。

public class Example {public void doSomething() {int x = 10;// 匿名内部类中引用 x,x 必须是事实上最终变量Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println(x);}};x = 20; // 这里修改 x 的值,将导致编译错误}
}

双括号初始化

尽管匿名类它本身不能持有构造器,因为它只是用于创建另外一个类的特殊对象,但却可以为其提供一个用于对象初始化的代码块,基础牢固的同学应该对类的静态代码块和普通代码块有所了解,添加普通代码块可以让这个类的对象初始化时执行一些语句:

    List<String> list = new ArrayList<>() {{add("Tom"); add("Bob");}// 重写方法};

你可以用这种奇特的方式去初始化这个对象,可简写为以下形式:

    List<String> list = new ArrayList<>() {{add("Tom"); add("Bob");}};

但事实上并不建议大家用这种方式初始化一个对象,因为对不了解这种罕见语法的人而言,这种写法的可读性很差,虽然你可以写注释,但你绝对有更清晰的方式去做这件事。

静态内部类

内部类可以声明为static,静态的内部类相当于是一个嵌套的类,它跟普通类只有一个区别,就是它隐藏在了另外一个类的内部,它最大的好处就是避免声明一个跟其他类重名的类。

就跟上面说的一样,静态内部类跟写在外面的普通类并无差别,它既没有对其外部类的引用,还与常规内部类不同,可以有自己的静态字段和方法,就跟普通类完全一样

声明和构造一个静态内部类也想当简单,格式如下:

OuterClass.InnerClass obj = new OuterClass.InnerClass();

下面给到一个《java 核心技术》一书中给到的静态内部类的使用例子,在这个例子中,我们创建了一个静态内部类Pair,用于存放一组数据,这个类名通常容易跟其他类重名,使用静态内部类就避免了这个情况:

/*** This program demonstrates the use of static inner classes.* @version 1.02 2015-05-12* @author Cay Horstmann*/
public class StaticInnerClassTest
{public static void main(String[] args){var values = new double[20];for (int i = 0; i < values.length; i++)values[i] = 100 * Math.random();ArrayAlg.Pair p = ArrayAlg.minmax(values);System.out.println("min = " + p.getFirst());System.out.println("max = " + p.getSecond());}
}class ArrayAlg
{/*** A pair of floating-point numbers*/public static class Pair{private double first;private double second;/*** Constructs a pair from two floating-point numbers* @param f the first number* @param s the second number*/public Pair(double f, double s){first = f;second = s;}/*** Returns the first number of the pair* @return the first number*/public double getFirst(){return first;}/*** Returns the second number of the pair* @return the second number*/public double getSecond(){return second;}}/*** Computes both the minimum and the maximum of an array* @param values an array of floating-point numbers* @return a pair whose first element is the minimum and whose second element* is the maximum*/public static Pair minmax(double[] values){double min = Double.POSITIVE_INFINITY;double max = Double.NEGATIVE_INFINITY;for (double v : values){if (min > v) min = v;if (max < v) max = v;}return new Pair(min, max);}
}

下面的两条默认行为不一定是必须的,但既然有默认设定,说明正常情况下一般这样更有意义:

在接口中声明的内部类自动是public和static的。

类中声明的接口(包括记录类、枚举类)都自动是static的。

后记

这里可以提到一个词:闭包。

闭包是指一个连同了外部自由变量的代码块,java的内部类和lambda表达式就具备这样的特点,有兴趣的读者可以自行查阅其他资料了解闭包的概念,如果有人跟你炫耀他所写的语言中有闭包,那么你可以自信的告诉他,java语言也有闭包。

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

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

相关文章

医疗器械维修工程师必须重视的方面

彩虹医疗器械维修技能培训开班报名中 长期班低至五折&#xff0c; 打破常规培训模式轻松愉快技术学习&#xff01; 两个多月时间&#xff0c;提升自我&#xff01; 点击进入 彩虹实训基地 理论实践结合教学 小班授课 立即咨询 1 工程师须重视 在医疗行业中&#xff0c;…

青少年编程学习 等级考试 信奥赛NOI/蓝桥杯/NOC/GESP等比赛资料合集

一、博主愚见 在当今信息技术高速发展的时代&#xff0c;编程已经成为了一种必备的技能。随着社会对于科技人才的需求不断增加&#xff0c;青少年编程学习正逐渐成为一种趋势。为了更好地帮助青少年学习编程&#xff0c;提升他们的技能和素质&#xff0c;博主结合自身多年从事青…

MacOS下VMware Fusion配置静态IP

前言 在虚拟机安装系统后&#xff0c;默认是通过DHCP动态分配的IP&#xff0c;这会导致每次重启虚拟机ip都可能会改变&#xff0c;使用起来会有很多不便。 配置静态IP 查看主机网关地址 cat /Library/Preferences/VMware\ Fusion/vmnet8/nat.conf 查看主机DNS&#xff0c;m…

宿主机无法连接docker里的redis问题解决(生产环境慎用)

宿主机无法连接docker里的redis问题解决&#xff08;生产环境慎用&#xff09; 问题描述解决方案 问题描述 1.连接超时 2.连接能连上但马上断开并报错 3.提示保护模式什么的 (error) DENIED Redis is running in protected mode because protected mode is enabled链接redis …

OpenCV压缩保存图像

imwrite 1.JPG格式图片是自带压缩的 IMWRITE_JPEG_QUALITY For JPEG, it can be a quality from 0 to 100 (the higher is the better). Default value is 95. IMWRITE_JPEG_PROGRESSIVE Python: cv.IMWRITE_JPEG_PROGRESSIVE Enable JPEG features, 0 or 1, default is…

总结MYSQL中VHARCHAR和TEXT

前几天在设计表结构时&#xff0c;针对表中的一个字段使用text还是使用varchar是受到了开发同学的挑战。本篇文章对text和varchar的区别做个总结。 VHARCHAR和TEXT对比 char(n)varchar(n)中括号中n代表字符的个数&#xff0c;并不代表字节个数&#xff0c;所以当使用了中文的…

笔记本分屏怎么操作?3个方法提高工作效率!

“有朋友知道笔记本怎么才能实现分屏吗&#xff1f;我在工作时&#xff0c;经常需要来回切换屏幕&#xff0c;效率真的太低了&#xff0c;有什么方法可以实现两个屏幕同时使用吗&#xff1f;” 在现代生活中&#xff0c;多任务处理已成为常态&#xff0c;而笔记本分屏技术为用户…

电脑监控软件丨功能详情丨特点分析

电脑监控软件的出现&#xff0c;是在信息技术的飞速发展以及计算机使用的普及的背景下产生的。随着计算机在企业、学校以及家庭等各个场所的广泛使用&#xff0c;管理和保护计算机数据安全的问题变得越来越重要。因此&#xff0c;电脑监控软件应运而生&#xff0c;旨在帮助用户…

DaoWiki(基于Django)开发笔记 20231114-阿里云mysql外部访问

文章目录 创建mysql用户&#xff0c;用户远程访问配置阿里云安全策略下载安装mysql workbench 创建mysql用户&#xff0c;用户远程访问 创建用户 CREATE USER dao_wiki% IDENTIFIED BY password;授权访问dao_wiki数据库 GRANT ALL PRIVILEGES ON dao_wiki.* TO dao_wiki%; F…

浅谈掌动智能验收测试主要服务内容

所谓验收测试是对软件的功能性、性能效率、兼容性、易用性、可靠性、信息安全性、维护性、可移植性进行测试&#xff0c;对产品说明、用户文档集进行审阅&#xff0c;为科研项目、信息工程项目等进行第三方验收评测&#xff0c;交付验收测试报告。本文将介绍掌动智能验收测试主…

Rust 中的引用与借用

目录 1、引用与借用 1.1 可变引用 1.2 悬垂引用 1.3 引用的规则 2、slice 类型 2.1 字符串字面量其实就是一个slice 2.2 总结 1、引用与借用 在之前我们将String 类型的值返回给调用函数&#xff0c;这样会导致这个String会被移动到函数中&#xff0c;这样在原来的作用域…

Python数据结构: 列表(List)详解

在Python中&#xff0c;列表&#xff08;List&#xff09;是一种有序、可变的数据类型&#xff0c;被广泛用于存储和处理多个元素。列表是一种容器&#xff0c;可以包含任意数据类型的元素&#xff0c;包括数字、字符串、列表、字典等。本文将深入讨论列表的各个方面&#xff0…

TCP连接出现大量CLOSE_WAIT不回收的问题排查

背景 日常运维过程中&#xff0c;收到“应用A”突然挂起没有处理请求的告警&#xff0c;然后触发“存活检查”不通过&#xff0c;自动重启了。 问题 为什么“应用A”突然挂起&#xff1f; 分析 排查过程很长&#xff0c;走了很多弯路&#xff0c;这里只列出本案例有效行动…

K8S知识点(八)

&#xff08;1&#xff09;实战入门-Label 通过标签实现Pod的区分&#xff0c;说白了就是一种标签选择机制 可以使用命令是否加了标签&#xff1a; 打标签&#xff1a; 更新标签&#xff1a; 筛选标签&#xff1a; 修改配置文件&#xff0c;重新创建一个pod 筛选&#xff1…

vue项目中在scss代码中使用data中的变量

尽管在日常开发中&#xff0c;这类情况实际上很少出现。 VUE2: 在HTML中使用时&#xff0c;请确保将cssVars绑定在需要使用CSS变量的元素或该元素的上层元素上。 <template><div :style"cssVars"><div class"test"/></div><…

Java集合框架

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

jwt工作原理及组成结构

JWT&#xff08;JSON Web Token&#xff09;是一种用于身份验证和授权的开放标准&#xff08;RFC 7519&#xff09;&#xff0c;它是一种安全的、轻量级的身份验证方式。 JWT由三部分组成&#xff1a;头部&#xff08;Header&#xff09;、载荷&#xff08;Payload&#xff09…

代理模式-静态动态代理-jdk动态代理-cglib动态代理

代理模式 静态代理 动态代理&#xff1a;jdk动态代理 cglib动态代理 注意 &#xff1a;下面的代码截图 要配合文字去看 我对代码的每一步都做了解释 所以需要配合图片观看提取吗1111https://pan.baidu.com/s/1OxQSwbQ--t5Zvmwzjh1T0A?pwd1111 这里直接把项目文件 及代码 …

Qt生产者消费者使用QWaitCondition

继承 QThread 重写 void run() Q_DECL_OVERRIDE; 调用start()开启线程 使用 QMutex mutex; QWaitCondition newdataAvailable; 将互斥量锁住 QMutexLocker locker(&mutex); m_stoptrue; 生产者唤醒所有线程表示创建了资源&#xff1a; newdataAvailable.wa…

MySQL(15):存储过程与函数

存储过程概述 含义&#xff1a; 存储过程的英文是 Stored Procedure 。它的思想很简单&#xff0c;就是一组经过 预先编译 的 SQL 语句的封装。 执行过程&#xff1a; 存储过程预先存储在 MySQL 服务器上&#xff0c;需要执行的时候&#xff0c;客户端只需要向服务器端发出调用…