初识Java 9-1 内部类
目录
创建内部类
到外部类的链接
使用.this和.new
内部类和向上转型
在方法和作用域中的内部类
匿名内部类
嵌套类
接口中的类
从多嵌套的内部类中访问外部人员
本笔记参考自: 《On Java 中文版》
定义在另一个类中的类称为内部类。利用内部类,将逻辑上存在关联的类组织在一起,并且可以控制一个类在另一个类中的可见性。
创建内部类
创建内部类的方式就是把类定义在一个包围它的类中。
public class Parcel_1 {class Contents {private int i = 1;public int value() {return i;}}class Destination {private String label;Destination(String whereTo) {label = whereTo;}String readLabel() {return label;}}// 内部类的使用看起来和使用其他类没有区别public void ship(String dest) {Contents c = new Contents();Destination d = new Destination(dest);System.out.println(d.readLabel());}public static void main(String[] args) {Parcel_1 p = new Parcel_1();p.ship("一串字符串");}
}
程序执行,输出:一串字符串 。
在上述程序中,ship()方法进行了内部类对象的创建,这与使用普通类并无什么区别。除了这种使用内部类的方式外,在外部类中设置一个方法,用来返回一个指向内部类的引用,这种形式也很常见:
public class Parcel_2 {class Contents {private int i = 1;public int value() {return i;}}class Destination {private String label;Destination(String whereTo) {label = whereTo;}String readLabel() {return label;}}public Destination toDest(String s) {return new Destination(s);}public Contents toCon() {return new Contents();}public void ship(String dest) {Contents c = toCon();Destination d = toDest(dest);System.out.println(d.readLabel());}public static void main(String[] args) {Parcel_2 p_1 = new Parcel_2();p_1.ship("第二串字符串");Parcel_2 p_2 = new Parcel_2();// 定义指向内部类的引用Parcel_2.Contents c = p_2.toCon();Parcel_2.Destination d = p_2.toDest("这是一个输入");}
}
在外部类的非静态方法之外的任何地方创建内部类的对象,其对象类型的指定需要遵循以下格式:
OuterClassName.InnerClassName
到外部类的链接
对于一个负责创建内部类对象的特定外围类对象而言,内部类对象会获得一个隐藏的指向外围类的引用。
当创建一个内部类时,这个内部类的对象中会隐含一个链接,这个链接用于创建该对象的外围对象。通过这一链接,无需任何条件就可以直接访问外围对象的成员。除此之外,内部类还拥有对外围对象所有元素的访问权。
interface Selector {boolean end();Object current();void next();
}public class Sequence {private Object[] items;private int next = 0;public Sequence(int size) {items = new Object[size];}public void add(Object x) {if (next < items.length)items[next++] = x;}private class SequenceSelector implements Selector {private int i = 0;@Overridepublic boolean end() {return i == items.length;}@Overridepublic Object current() {return items[i];}@Overridepublic void next() {if (i < items.length)i++;}}public Selector selector() {return new SequenceSelector();}public static void main(String[] args) {Sequence sequence = new Sequence(10);for (int i = 0; i < 10; i++)sequence.add(Integer.toString(i));Selector selector = sequence.selector();while (!selector.end()) {System.out.print(selector.current() + " ");selector.next();}System.out.println();}
}
程序执行的结果是:
通过sequence中的每一个对象,可以使用Selector接口。这就是一个迭代器设计模式的例子。因为Selector是一个接口,其他类可以使用自己的方式去实现这一接口,而其他方法可以通过Selector这个接口去创建更加通用的代码。
注意,上述程序中,private字段items并不是内部类SequenceSelector的一部分,但是内部类的end()、current()和next()方法都使用到了该引用。这就是因为内部类可以访问外围对象的所有方法和字段。
使用.this和.new
要在内部类中生成外部类对象的引用,可以使用 外部类的名字+.this :
public class DotThis {void f() {System.out.println("这是外部类DoThis的f()");}public class Inner {public DotThis outer() {return DotThis.this;// 若直接使用this,得到的是一个Inner类的引用}}public Inner inner() {return new Inner();}public static void main(String[] args) {DotThis dt = new DotThis();DotThis.Inner dti = dt.inner();dti.outer().f();}
}
程序执行,输出:这是外部类DoThis的f() 。
若要创建内部类的对象,我们还需要使用其外部类的对象。此时会使用到.new语法:
public class DotNew {public class Inner {}public static void main(String[] args) {DotNew dn = new DotNew();DotNew.Inner dni = dn.new Inner();}
}
通过这种方式,解决了内部类的名字作用域问题。也因此,不需要使用 dn.new DotNew.Inner() 这种更加冗余的方式(不过这种方式也确实不被允许使用)。
.new的使用例(部分代码重复多次,因此这次放入图片):
内部类的对象会隐式地连接到用于创建它的外部类对象。
在之后会出现,嵌套类(static修饰的内部类)不需要指向外部类对象的引用。
内部类和向上转型
内部类在进行向上转型,特别是转型为接口时有其独特的优势。因为内部类(即接口的实现)对外部而言是不可见、不可用的,这会方便隐藏实现:外部类只会获得一个指向基类或接口的引用。
还是引用之前的例子,假设现在存在两个接口Destination和Contents:
正如图中所示的,这两个接口可以让客户程序员进行使用。若客户程序员得到的是一个指向这些接口(指向基类同理)的引用,那么他们就无法从这个引用中得知其确切的类型:
class Parcel_4 {private class PContents implements Contents { // 访问权限为private,无法从外部直接访问private int i = 1;@Overridepublic int value() {return i;}}protected final class PDestination implements Destination {private String label;private PDestination(String whereTo) {label = whereTo;}@Overridepublic String readLabel() {return label;}}public Destination destination(String s) {return new PDestination(s);}public Contents contents() {return new PContents();}
}public class TestParcel {public static void main(String[] args) {Parcel_4 p = new Parcel_4();Contents c = p.contents();Destination d = p.destination("这是第四串字符串");// 注意:不能访问private类// Parcel_4.PContents pc = p.new PContents();}
}
在Parcel_4中,内部类PContents是private的,这表示只有Parcel_4有权对其进行访问。另外,PDestination是protected的,这表示其的访问权限同样是受限的。
不能向下转型为访问权限是private的内部类(若无继承关系,也无法向下转型为protected的内部类)。
private内部类为类的设计者提供了一种方式,这种方式可以完全阻止任何与类型有关的编码依赖,并且可以完全隐藏实现细节。
在方法和作用域中的内部类
内部类可以在一个方法或是任何一个作用域内创建。有两个理由支持这种做法:
- 像上述例子中展示的,需要实现某种接口,以便创建和返回一个引用;
- 为解决一个复杂问题,在自己的解决方案中创建了一个类用于辅助,但不希望这个类被公开。
局部内部类
修改之前的例子,现在创建一个局部内部类。这种类是一个完整的类,它存在于一个方法的作用域中:
public class Parcel_5 {public Destination destination(String s) {final class PDestination implements Destination {private String label;private PDestination(String whereTo) {label = whereTo;}@Overridepublic String readLabel() {return label;}}return new PDestination(s);}public static void main(String[] args) {Parcel_5 p = new Parcel_5();Destination d = p.destination("这也是一个字符串");}
}
在上述程序中,PDestination类是destination()方法的一部分,而不是Parcel_5的一部分。因此,PDestination在destination()外是无法访问的。另外,尽管PDestination类在是destination()进行了定义,但即使destination()方法已经返回,PDestination的对象依旧会是合法的。
在同一子目录下的每一个类中,都可以使用类标识符PDestination来命名内部类,这不会产生命名冲突。
接下来的例子会展示如何如何将内部类嵌入到一个条件判断的作用域中:
public class Parcel_6 {private void internalTracking(Boolean b) {if (b) {class TrackingSlip {private String id;TrackingSlip(String s) {id = s;}String getSlip() {return id;}}TrackingSlip ts = new TrackingSlip("可以使用");String s = ts.getSlip();}// 超出if的作用域,无法使用内部类// TrackingSlip ts = new TrackingSlip("不能使用");}public void track() {internalTracking(true);}public static void main(String[] args) {Parcel_6 p = new Parcel_6();p.track();}
}
上述程序中,虽然内部类被布置到了if语句中,但这并不表示这个内部类的创建是有条件的,它会与其他代码一起被编译。
匿名内部类
一个内部类可以是匿名的:这种类通常会与方法返回值的创建结合在一起,在值被返回之前插入一个类的定义。
public class Parcel_7 {// Contents是之前声明的接口,它的方法未被定义public Contents contents() {return new Contents() { // 在进行返回时,插入类的定义private int i = 1;@Overridepublic int value() {return i;}}; // 必要的分号}public static void main(String[] args) {Parcel_7 p = new Parcel_7();Contents c = p.contents();}
}
这段代码看起来是在准备创建一个Contents的对象,但返回值却被插入了一个类的定义:
return new Contents() { // ...
};
这种语法的意思是“创建一个继承自Contents的匿名类的对象”。在这里,通过new表达式返回的引用会被自动向上转型为一个Contents引用。上述的匿名内部类的语法是以下代码的缩写:
---
另外,上面展示的匿名内部类中,Contents是用无参构造器创建的。若基类需要的是一个带有参数的构造器,那么:
首先,这个匿名内部类的基类构造器需要带有参数:
public class Wrapping { // 基类Wrappingprivate int i;public Wrapping(int x) { // 含参构造器i = x;}public int value() {return i;}
}
尽管Wrapping只是一个带有实现的普通类,但它也是其子类的公共“接口”。
然后是匿名内部类的创建:
public class Parcel_8 {public Wrapping Wrapping(int x) {return new Wrapping(x) { // 需要将合适的参数传递给基类构造器@Overridepublic int value() {return super.value() * 12;}}; // 这个分号标记表达式的结束,但它刚好包含了这个匿名类}public static void main(String[] args) {Parcel_8 p = new Parcel_8();Wrapping w = p.Wrapping(10);}
}
上述程序中,return语句末尾的分号标记着表达式的结束,但它并不会标记类体的结束。
---
若正在构建一个匿名类,并且这个匿名类一定需要使用这个匿名类外部定义的对象,此时,编译器会要求被使用的参数引用使用final修饰,或是“实际上的最终变量”(这种变量在初始化后不再改变,因此被视为final)。
public class Parcel_9 {public Destination destination(final String dest) {return new Destination() {private String label = dest;@Overridepublic String readLabel() {return label;}};}public static void main(String[] args) {Parcel_9 p = new Parcel_9();Destination d = p.destination("这里是Parcel_9");}
}
在上述程序中,方法destination()的参数可以不用加上final,但通常会把final写上作为提示。
---
由于匿名类没有名字,所以也不可能有命名的构造器。但如果我们必须对匿名类执行某个类似于构造器的动作,这应该怎么办?借助实例初始化,就可以在效果上为匿名内部类创建一个构造器:
abstract class Base {Base(int i) {System.out.println("这是Base的构造器,i = " + i);}public abstract void f();
}public class AnonymousConstructor {public static Base getBase(int i) {return new Base(i) {{ // 进行实例初始化System.out.println("内部类的实例初始化");}@Overridepublic void f() {System.out.println("匿名类的f()");}};}public static void main(String[] args) {Base base = getBase(10);base.f();}
}
程序执行的结果是:
在这里,传入匿名类的变量i并不一定需要是最终变量,尽管i被传入匿名类的基类构造器,但匿名类内部没有直接使用到它。而下方的程序中,由于匿名类使用了参数,所以被使用的参数必须是最终变量:
public class Parcel_10 {public Destination destination(final String dest, final float price) {return new Destination() {private int cost;{// 为每个对象执行实例初始化cost = Math.round(price);if (cost > 100)System.out.println("太贵了吧!");}private String label = dest;@Overridepublic String readLabel() {return label;}};}public static void main(String[] args) {Parcel_10 p = new Parcel_10();Destination d = p.destination("买什么好呢?", 120);}
}
实例初始化操作中包含了一段if语句,这段if语句不能作为字段初始化的一部分来执行。在效果上,实例初始化部分就是匿名内部类的构造器。但因为我们无法重载实例初始化的部分,所以只能有一个这样的构造器。
与普通的继承相比,匿名构造器只能扩展一个类,或是实现一个接口,且二者不能兼得。
嵌套类
将内部类设置为static的,这就变成了嵌套类。这种类不同与普通的匿名类:
- 不需要一个外部类对象来创建嵌套类对象;
- 无法从嵌套类对象内部访问非static的外部类对象。
除此之外,嵌套类内部还能存放其他嵌套类,或者static数据及static字段。这些是普通内部类无法做到的:
public class Parcel_11 {// 嵌套类:带有static的内部类private static class ParceContents implements Contents {private int i = 1;@Overridepublic int value() {return i;}}protected static final class ParceDestination implements Destination {private String label;private ParceDestination(String whereTo) {label = whereTo;}@Overridepublic String readLabel() {return label;}// 嵌套类可以包含其他静态元素public static void f() {}static int x = 10;static class AnotherLevel {public static void f() {}static int x = 10;}}public static Destination destination(String s) {return new ParceDestination(s);}public static Contents contents() {return new ParceContents();}public static void main(String[] args) {Contents c = contents();Destination d = destination("不知道写什么,随便写点");}
}
普通内部类(即非static的)可以使用特殊的this引用创建向外部类对象的连接。而嵌套类没有特殊的this引用,这使得它和static方法类似。
接口中的类
嵌套类可以是接口的一部分,因为类是static的,所以被嵌套的类只是被放到了这个接口的命名空间里。甚至于,可以在嵌套类中实现包围它的接口:
public interface ClassInterface {void howdy();class Test implements ClassInterface {@Overridepublic void howdy() {System.out.println("在嵌套类内部实现了外围接口的方法");}}public static void main(String[] args) {new Test().howdy();}
}
程序执行的结果是:
当需要创建一个接口的所有不同实现的公用代码时,一个嵌套在接口中的类会很有用。
有时,为了测试一个独立的类,会用到一个单独的main()。这种main()就可以被放入到嵌套类中,在交付产品时将其删去即可。
从多嵌套的内部类中访问外部人员
一个类被嵌套了多少层都不重要,因为它可以透明地访问包含它的所有类的所有成员:
class MNA {private void f() {}class A {private void g() {}public class B {void h() {g();f();}}}
}public class MultiNestingAcess {public static void main(String[] args) {MNA mna = new MNA();MNA.A mnaa = mna.new A();MNA.A.B mnaab = mnaa.new B();mnaab.h();}
}
不需要在调用构造器时限定类的名字,因为.new语法会寻找到正确的作用域。
相关文章:

初识Java 9-1 内部类
目录 创建内部类 到外部类的链接 使用.this和.new 内部类和向上转型 在方法和作用域中的内部类 匿名内部类 嵌套类 接口中的类 从多嵌套的内部类中访问外部人员 本笔记参考自: 《On Java 中文版》 定义在另一个类中的类称为内部类。利用内部类,…...
合宙Air724UG LuatOS-Air LVGL API控件-屏幕横屏竖屏切换(Rotation)
屏幕横屏竖屏切换(Rotation) lvgl.disp_set_rotation(nil, lvgl.DISP_ROT_angle) 屏幕横屏竖屏切换显示,core版本号要>3202参数 参数类型释义取值nil无意义nilangle显示角度0,90,270,360 返回值nil 例子 lvgl.init()- -初始化 lvgl.disp_set_rotation(nil,…...
在Unity中,Instantiate函数用于在场景中创建一个新的游戏对象实例
在Unity中,Instantiate函数用于在场景中创建一个新的游戏对象实例。它的语法如下所示: public static Object Instantiate(Object original, Vector3 position, Quaternion rotation); original:要实例化的原始游戏对象。position࿱…...

解决 tesserocr报错 Failed to init API, possibly an invalid tessdata path : ./
问题描述 我们在初次使用tesserocr库的时候,可能会报以下错误: RuntimeError: Failed to init API, possibly an invalid tessdata path: ./ 这是因为我们在使用 conda 创建的环境中找不到"tessdata"这个文件夹。 解决办法 这时候把Tessera…...

使用Python CV2融合人脸到新图片--优化版
优化说明 上一版本人脸跟奥特曼图片合并后边界感很严重,于是查找资料发现CV2还有一个泊松函数很适合融合图像。具体代码如下: import numpy as np import cv2usrFilePath "newpic22.jpg" atmFilePath "atm2.jpg" src cv2.imrea…...
Python分享之对象的属性
Python一切皆对象(object),每个对象都可能有多个属性(attribute)。Python的属性有一套统一的管理方案。 属性的__dict__系统 对象的属性可能来自于其类定义,叫做类属性(class attribute)。类属性可能来自类定义自身,也可能根据类定义继承来的…...
编程参考 - std::exchange和std::swap的区别
这两个功能是C standard library中的Standard template library中的一部分。容易混淆,我们来看下它们的区别。 exchange: 这个函数是一个返回原先值的set函数。 std::exchange is a setter returning the old value. int z std::exchange(x, y); Af…...
Sentinel整合RestTemplate
resttemplate开启sentinel保护配置resttemplate.sentinel.enabledtrue配置sentinel-dashboard地址spring.cloud.sentinel.transport.dashboardlocalhost:8858\ spring.cloud.sentinel.transport.dashboard.port8739 实例化RestTemplate并加入SentinelRestTemplate注解Configura…...
微前端学习(下)
一、课程目标 qiankun 整体运行流程微前端实现方案二、课程大纲 qiankun 整体流程微前端方案实现DIY微前端核心能力1、微前端方案实现 基于 iframe 完全隔离的方案、使用纯的Web Components构建应用EMP基于webpack module federationqiankun、icestark 自己实现JS以及样式隔离2…...
Android Splash实现
1、创建Activity package com.wsy.knowledge.ui.splashimport android.animation.Animator import android.animation.AnimatorListenerAdapter import android.annotation.SuppressLint import android.os.Build import android.os.Looper import android.util.Log import an…...

FPGA projet : VGA
在vga屏幕上显示 : 野火科技 相比于上个工程,只需要修改 vga_pix 模块即可。 注意存储器类型变量的定义:reg 【宽度】<名称>【深度】 赋值 always (poseedge vga_clk)begin 为每一行赋值,不可位赋…...

JDK8 升级至JDK19
优质博文IT-BLOG-CN 目前部分项目使用JDK8,部分项目使用JDK19因此,环境变量中还是保持JDK8,只需要下载JDK19免安装版本,通过配置IDEA就可以完成本地开发。 一、IDEA 环境设置 【1】通过快捷键CTRL SHIFT ALT S或者File->P…...

Python3.10 IDLE更换主题
前言 自定义主题网上有很多,3.10IDLE的UI有一些新的东西,直接扣过来会有些地方覆盖不到,需要自己测试着添几行配置,以下做个记录。 配置文件路径 Python安装目录下的Lib\idlelib\config-highlight.def。如果是默认安装…...

C# OpenVino Yolov8 Pose 姿态识别
效果 项目 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp;namespace OpenVino_Yolov8_Demo {public…...

北邮22级信通院数电:Verilog-FPGA(1)实验一“跑通第一个例程” 过程中遇到的常见问题与解决方案汇总(持续更新中)
北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章,请访问专栏: 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 目录 问题一:Verilog代码没有跑通 报…...

CSS - 鼠标移入整行高亮显示,适用于会员套餐各参数对比页面(display: table,div 转表格形式)
效果图 可根据基础示例和进阶示例,复制进行改造样式。 如下图所示,本文提供 2 个示例。 基础示例 找个 HTML 页面,一键复制运行。 <body><h1 style"text-align: center;">基础示例</h1><section class"…...

无涯教程-JavaScript - ATAN2函数
描述 The ATAN2 function returns the arctangent, or inverse tangent, of the specified x- and ycoordinates, in radians, between -π/2 and π/2. 语法 ATAN2 (x_num, y_num)争论 Argument描述Required/OptionalX_numThe x-coordinate of the point.RequiredY_numThe…...

Tomcat 下部署 jFinal
1、检查web.xml 配置,在 tomcat 下部署需要检查 web.xml 是否存在,并且要确保配置正确,配置格式如下。 <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns:xsi"http://www.w3.org/2001/XMLSchema-i…...

【Spatial-Temporal Action Localization(二)】论文阅读2017年
文章目录 1. ActionVLAD: Learning spatio-temporal aggregation for action classification [code](https://github.com/rohitgirdhar/ActionVLAD/)[](https://github.com/rohitgirdhar/ActionVLAD/)摘要和结论引言:针对痛点和贡献相关工作模型框架思考不足之处 2.…...

二维码智慧门牌管理系统:数据现势性,满足应用需求的根本保证
文章目录 前言一、项目背景二、数据的现势性三、系统的优势四、应用前景 前言 在当今信息化社会,数据的重要性日益凸显,尤其是数据的现势性,它决定着服务的质量和满足应用需求的能力。近日,一个创新的二维码智慧门牌管理系统项目…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...