初识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修饰的内部类)不需要指向外部类对象的引用。

内部类和向上转型

        内部类在进行向上转型,特别是转型为接口时有其独特的优势。因为内部类(即接口的实现)对外部而言是不可见、不可用的,这会方便隐藏实现:外部类只会获得一个指向基类或接口的引用。

        还是引用之前的例子,假设现在存在两个接口DestinationContents

        正如图中所示的,这两个接口可以让客户程序员进行使用。若客户程序员得到的是一个指向这些接口(指向基类同理)的引用,那么他们就无法从这个引用中得知其确切的类型:

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中,内部类PContentsprivate的,这表示只有Parcel_4有权对其进行访问。另外,PDestinationprotected的,这表示其的访问权限同样是受限的。

    不能向下转型为访问权限是private的内部类(若无继承关系,也无法向下转型为protected的内部类)。

        private内部类为类的设计者提供了一种方式,这种方式可以完全阻止任何与类型有关的编码依赖,并且可以完全隐藏实现细节。

在方法和作用域中的内部类

        内部类可以在一个方法或是任何一个作用域内创建。有两个理由支持这种做法:

  1. 像上述例子中展示的,需要实现某种接口,以便创建和返回一个引用
  2. 为解决一个复杂问题,在自己的解决方案中创建了一个类用于辅助,但不希望这个类被公开

局部内部类

        修改之前的例子,现在创建一个局部内部类。这种类是一个完整的类,它存在于一个方法的作用域中:

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的一部分因此,PDestinationdestination()外是无法访问的。另外,尽管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的,这就变成了嵌套类。这种类不同与普通的匿名类:

  1. 不需要一个外部类对象来创建嵌套类对象;
  2. 无法从嵌套类对象内部访问非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语法会寻找到正确的作用域。

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

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

相关文章

C++qt day8

1.用代码实现简单的图形化界面&#xff08;并将工程文件注释&#xff09; 头文件 #ifndef MYWIDGET_H #define MYWIDGET_H //防止头文件冲突#include <QWidget> //父类的头文件class MyWidget : public QWidget //自定义自己的界面类&#xff0c;公共继承…

性能测试 —— Jmeter事务控制器

事务&#xff1a; 性能测试中&#xff0c;事务指的是从端到端&#xff0c;一个完整的操作过程&#xff0c;比如一次登录、一次 筛选条件查询&#xff0c;一次支付等&#xff1b;技术上讲&#xff1a;事务就是由1个或多个请求组成的 事务控制器 事务控制器类似简单控制器&…

页面静态化、Freemarker入门

页面静态化介绍 页面的访问量比较大时&#xff0c;就会对数据库造成了很大的访问压力&#xff0c;并且数据库中的数据变化频率并不高。 那需要通过什么方法为数据库减压并提高系统运行性能呢&#xff1f;答案就是页面静态化。页面静态化其实就是将原来的动态网页(例如通过ajax…

ES6中新增加的Proxy对象及其使用方式

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ Proxy对象的基本概念Proxy对象的主要陷阱&#xff08;Traps&#xff09; ⭐ 使用Proxy对象⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来…

八、硬改之设备画像

系列文章目录 第一章 安卓aosp源码编译环境搭建 第二章 手机硬件参数介绍和校验算法 第三章 修改安卓aosp代码更改硬件参数 第四章 编译定制rom并刷机实现硬改(一) 第五章 编译定制rom并刷机实现硬改(二) 第六章 不root不magisk不xposed lsposed frida原生修改定位 第七章 安卓…

听GPT 讲Istio源代码--istioctl

在 Istio 项目的 istioctl 目录中&#xff0c;有一些子目录&#xff0c;每个目录都有不同的作用和功能。以下是这些子目录的详细介绍&#xff1a; /pkg: pkg 目录包含了 istioctl 工具的核心代码和库。这些代码和库提供了与 Istio 控制平面交互的功能&#xff0c;例如获取和修改…

Linux 安装 cuda

【存在问题】 有时候Ubuntu它自己会自动更新&#xff0c;使得之前能用得torch都用不了了。 输入nvidia-smi时&#xff0c;报错如下&#xff1a; NVIDIA-SMI has failed because it couldnt communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is…

PowerDesigner 逆向工程以及IDEA中UML插件

1、MySQL数据库连接&#xff08;JDBC方式&#xff09; 1.1 新建一个pdm&#xff0c;dbms选择mysql 1.2 Database - Connect 选择数据库连接 1.3 配置连接信息 数据库连接这里是通过一个配置文件来获取连接信息的&#xff0c;首次的话因为没有&#xff0c;所以我们需要选择…

什么是MQ消息队列及四大主流MQ的优缺点(个人网站复习搬运)

什么是&#xff2d;&#xff31;消息队列及四大主流&#xff2d;&#xff31;的优缺点 小程序要上一个限时活动模块&#xff0c;需要有延时队列&#xff0c;从网上了解到用RabbitMQ可以解决&#xff0c;就了解了下 MQ 并以此做记录。 一、为什么要用 MQ 核心就是解耦、异步和…

Zabbix监控部署项目

为什么选择Zabbix Zabbix 是一个基于 WEB 界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。zabbix 能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。 面试常问 你用过哪些监控软件 zabbix …

Python用正则化Lasso、岭回归预测房价、随机森林交叉验证鸢尾花数据可视化2案例|数据分享...

全文链接&#xff1a;https://tecdat.cn/?p33632 机器学习模型的表现不佳通常是由于过度拟合或欠拟合引起的&#xff0c;我们将重点关注客户经常遇到的过拟合情况&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 相关视频 过度拟合是指学习的假设在训练数据上…

MySQL数据库upsert使用

本文翻译自&#xff1a;MySQL UPSERT - javatpoint&#xff0c;并附带自己的一些理解和使用经验. MySQL UPSERT UPSERT是数据库管理系统管理数据库的基本功能之一&#xff0c;它允许数据库操作语言在表中插入一条新的数据或更新已有的数据。UPSERT是一个原子操作&#xff0c;…

git 远程名称 远程分支 介绍

原文&#xff1a; 开发者社区> 越前君> 细读 Git | 让你弄懂 origin、HEAD、FETCH_HEAD 相关内容 读书笔记&#xff1a;担心大佬文章搬家&#xff0c;故整理此学习笔记 远程名称&#xff08;Remote Name&#xff09; Origin 1、 origin 只是远程仓库的一个名称&#xff…

浅谈C++|类的继承篇

引子&#xff1a; 继承是面向对象三大特性之一、有些类与类之间存在特殊的关系&#xff0c;例如下图中: 我们发现&#xff0c;定义这些类时&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。 这个时候我们就可以考虑利用继承的技术&#xff0c;减少…

【Selenium】webdriver.ChromeOptions()官方文档参数

Google官方Chrome文档&#xff0c;在此记录一下 Chrome Flags for Tooling Many tools maintain a list of runtime flags for Chrome to configure the environment. This file is an attempt to document all chrome flags that are relevant to tools, automation, benchm…

竞赛 基于机器视觉的行人口罩佩戴检测

简介 2020新冠爆发以来&#xff0c;疫情牵动着全国人民的心&#xff0c;一线医护工作者在最前线抗击疫情的同时&#xff0c;我们也可以看到很多科技行业和人工智能领域的从业者&#xff0c;也在贡献着他们的力量。近些天来&#xff0c;旷视、商汤、海康、百度都多家科技公司研…

红外检漏技术

SF6气体绝缘设备发生泄漏后会造成运行开关闭锁、 内部绝缘击穿&#xff0c; 泄漏到空气中会造成环境污染&#xff0c; 并严重危害现场人员安全。 再加之SF6气体成本高&#xff0c; 频繁补气&#xff0c; 使维护成本增加&#xff0c; 造成经济损失。 红外检漏是依据SF6气体对红外…

EasyUI combobox 实现搜索(模糊匹配)功能

很简单的一个下拉框搜索模糊匹配功能&#xff0c;在此记录&#xff1a; 1&#xff1a;页面实现&#xff1a; <select class"easyui-combobox" name"combobox" id"combobox" style"width:135px;height:25px;" headerValue"请选…

LeetCode142.环形链表-II

这道题和上一道题几乎没有任何区别啊&#xff0c;为什么还是中等难度&#xff0c;我用上一道题的解法一分钟就写出来了&#xff0c;只不过返回的不是true和false而是节点&#xff0c;以下是我的代码&#xff1a; public class Solution {public ListNode detectCycle(ListNode…

推荐一款负载均衡器,助你轻松管理多个 Socks5 代理

推荐一款负载均衡器&#xff0c;助你轻松管理多个 Socks5 代理。 推荐一个 GitHub 开源项目 mingcheng/socks5lb&#xff0c;该项目在 GitHub 有超过 400 Star&#xff0c;用一句话介绍该项目就是&#xff1a;“A simple socks5 proxy load balance and transparent proxy”&a…