JavaSE笔记——内部类

news2024/12/23 19:40:35

文章目录

  • 前言
  • 一、创建内部类
  • 二、链接外部类
  • 三、使用.this 和.new
  • 四、内部类与向上转型
  • 五、内部类方法和作用域
  • 六、匿名内部类
  • 七、嵌套类
    • 1. 接口内部的类
    • 2. 从多层嵌套类中访问外部类的成员
  • 八、继承内部类
  • 九、局部内部类
  • 总结


前言

一个定义在另一个类中的类,叫作内部类。内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信。


一、创建内部类

创建内部类的方式就如同你想的一样,把类的定义置于外围类的里面:

public class Parcel1 {
    class Contents {
        private int i = 11;

        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) {
        Parcel1 p = new Parcel1();
        p.ship("Tasmania");
    }
}

在外部类方法中可以直接指明类型 InnerClassName,在其他类中需要指明OuterClassName.InnerClassName

二、链接外部类

到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用,但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。

public class External {
    private String name;

    public External(String name) {
        this.name = name;
    }

    public void printName(){
        System.out.println(name);
    }

    public class Internal{

        public void get(){
            System.out.println(name);
            printName();
        }

    }

    public Internal internal(){
        return new Internal();
    }

    public static void main(String[] args) {
        External external = new External("张三");
        Internal internal = external.internal();
        internal.get();
    }
}

在这里插入图片描述
所以内部类自动拥有对其外围类所有成员的访问权,当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(内部类是非static 类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。

三、使用.this 和.new

如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和 this。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。

public class DotThis {
    void f() {
        System.out.println("DotThis.f()");
    }

    public class Inner {
        public DotThis outer() {
            return DotThis.this;
        }
    }

    public Inner inner() {
        return new Inner();
    }

    public static void main(String[] args) {
        DotThis dt = new DotThis();
        DotThis.Inner dti = dt.inner();
        dti.outer().f();
    }
}

在这里插入图片描述
有时你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在 new 表达式中提供对其他外部类对象的引用,这是需要使用 .new 语法,就像下面这样:

public class DotNew {
    public class Inner {
    }

    public static void main(String[] args) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner();
    }
}

四、内部类与向上转型

当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。这是因为此内部类,某个接口的实现,能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。

public interface Destination {
    String readLabel();
}

public interface Contents {
    int value();
}

public class Parcel4 {
    private class PContents implements Contents {
        private int i = 11;

        @Override
        public int value() {
            return i;
        }
    }

    protected final class PDestination implements Destination {
        private String label;

        private PDestination(String whereTo) {
            label = whereTo;
        }

        @Override
        public String readLabel() {
            return label;
        }
    }

    public Destination destination(String s) {
        return new PDestination(s);
    }

    public Contents contents() {
        return new PContents();
    }

    public static void main(String[] args) {
        Parcel4 p = new Parcel4();
        Contents c = p.contents();
        Destination d = p.destination("Tasmania");
    }
}

在 Parcel4 中,内部类 PContents 是 private,所以除了 Parcel4,没有人能访问它。普通(非内部)类的访问权限不能被设为 private 或者 protected;他们只能设置为 public 或 package 访问权限。

PDestination 是 protected,所以只有 Parcel4 及其子类、还有与 Parcel4 同一个包中的类(因为protected 也给予了包访问权)能访问 PDestination,其他类都不能访问 PDestination,这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。实际上,甚至不能向下转型成 private 内部类(或 protected 内部类,除非是继承自它的子类),因为不能访问其名字,就像在 TestParcel 类中看到的那样。private 内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度来看,由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没有价值的。这也给 Java 编译器提供了生成高效代码的机会。

五、内部类方法和作用域

通常,如果所读、写的代码包含了内部类,那么它们都是 “平凡的” 内部类,简单并且容易理解。然而,内部类的语法覆盖了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作
用域内定义内部类。这么做有两个理由:

  1. 如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。
  2. 想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。

在后面的例子中,先前的代码将被修改,以用来实现:

  1. 一个定义在方法中的类。
  2. 一个定义在作用域内的类,此作用域在方法的内部。
  3. 一个实现了接口的匿名类。
  4. 一个匿名类,它扩展了没有默认构造器的类。
  5. 一个匿名类,它执行字段初始化。
  6. 一个匿名类,它通过实例初始化实现构造(匿名内部类不可能有构造器)。

在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类。这被称作局部内部类:

public class Parcel5 {
    public Destination destination(String s) {
        final class PDestination implements Destination {
            private String lable;

            public PDestination(String lable) {
                this.lable = lable;
            }

            @Override
            public String readLabel() {
                return lable;
            }
        }
        return new PDestination(s);
    }

    public static void main(String[] args) {
        Parcel5 parcel5 = new Parcel5();
        parcel5.destination("张");
    }
}

在任意的作用域内嵌入一个内部类:

 */
public class Parcel6 {
    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("slip");
            String s = ts.getSlip();
        }
    }

    public void track() {
        internalTracking(true);
    }

    public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        p.track();
    }
}

六、匿名内部类

public class Parcel7 {
    public Contents contents() {
        return new Contents() {
            private int i = 11;

            @Override
            public int value() {
                return i;
            }
        };
    }

    public static void main(String[] args) {
        Parcel7 parcel7 = new Parcel7();
        Contents contents = parcel7.contents();
    }
}

contents() 方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外,这个类是匿名的,它没有名字。

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是 final 的(也就是说,它在初始化后不会改变,所以可以被当作final)。

public abstract class Base {

    Base(int i) {
        System.out.println("Base constructor, i = " + i);
    }
    public abstract void f();
}

public class AnonymousConstructor {
    public static Base getBase(int i) {
        return new Base(i) {
            {
                System.out.println("Inside instance initializer");
            }

            @Override
            public void f() {
                System.out.println("In anonymous f()");
            }
        };
    }

    public static void main(String[] args) {
        Base base = getBase(47);
        base.f();
    }
}

在这里插入图片描述

七、嵌套类

如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static,这通常称为嵌套类。想要理解 static 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是 static 的时,就不是这样了。嵌套类意味着:

  1. 要创建嵌套类的对象,并不需要其外围类的对象。
  2. 不能从嵌套类的对象中访问非静态的外围类对象。

嵌套类与普通的内部类还有一个区别。普通的内部类不能有 static 数据和 static 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西:

public class Parcel11 {
    private static class ParcelContents implements Contents {
        private static int i = 11;

        @Override
        public int value() {
            return i;
        }
    }

    public static void main(String[] args) {
        ParcelContents parcelContents = new ParcelContents();
    }
}

1. 接口内部的类

嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 public 和 static 的。因为类是 static 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口,就像下面这样:

public interface ClassInInterface {

    void howdy();

    class Test implements ClassInInterface {
        @Override
        public void howdy() {
            System.out.println("Howdy!");
        }

        public static void main(String[] args) {
            new Test().howdy();
        }
    }

}

在这里插入图片描述

2. 从多层嵌套类中访问外部类的成员

一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员,如下所示:

public class MNA {
    private void f() {
    }

    class A {
        private void g() {
        }

        public class B {
            void h() {
                g();
                f();
            }
        }
    }
}

八、继承内部类

因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外围类对象的 “秘密的” 引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联:

public class InheritInner extends WithInner.Inner{

    InheritInner(WithInner wi) {
        wi.super();
    }

    public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

可以看到,InheritInner 只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法:enclosingClassReference.super(); 这样才提供了必要的引用,然后程序才能编译通过。

九、局部内部类

前面提到过,可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及此外围类的所有成员。

public class LocalInnerClass {
    private int count = 0;

    Counter getCounter(final String name) {
        class LocalCounter implements Counter {
            LocalCounter() {
                System.out.println("LocalCounter()");
            }

            @Override
            public int next() {
                System.out.print(name); // Access local final
                return count++;
            }
        }
        return new LocalCounter();
    }
}

总结

比起面向对象编程中其他的概念来,接口和内部类更深奥复杂,比如 C++ 就没有这些。将两者结合起来,同样能够解决 C++ 中的用多重继承所能解决的问题。虽然这些特性本身是相当直观的,但是就像多态机制一样,这些特性的使用应该是设计阶段考虑的问题。

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

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

相关文章

AUTO-CUT安装。

auto cut 是李沐老师分享的视频剪辑工具wisper。 这我非常需要啊。 所以看看怎么安装。 先下载客户端。和ffmpeg 链接:https://pan.baidu.com/s/1EZomUtV8Y_es8crR6-r3GQ 提取码:lgky 解压安装autocut客户端。 还需要ffmpeg和环境。 环境配置是自动…

事业编上岸浙大mpa的个人经验总结

先来介绍下我的个人情况吧,我是2022级浙大MPA项目的一名新生,也是一名中学老师,定居在杭州,毕业于湖州师范学院,在学校主要是负责教研这一块的工作,因为工作需要的原因,在综合了解了几个项目后&…

测试工具Hercules

下载地址:Hercules SETUP utility | HW-group.com Hercules SETUP实用程序是有用的串行端口终端(RS-485或RS-232终端)、UDP/IP终端和TCP/IP客户端服务器终端。它只为硬件组内部使用而创建,但今天它在一个实用程序中包含了许多功能…

手把手刷算法项目fucking-algorithm,干翻算法

今天给大家分享一个开源项目,在 GitHub 排行榜上今天特别火,都爬到了日排行榜的第二名。 大家想知道,面试互联网大厂,必面的是什么吗?当然是算法。作为程序员,互联网大厂的面试,算法是最重要的…

用javascript分类刷leetcode10.递归分治(图文视频讲解)

递归三要素 递归函数以及参数递归终止条件递归单层搜索逻辑 递归伪代码模版: function recursion(level, param1, param2, ...) {//递归终止条件if (level > MAX_LEVEL) {// output resultreturn;}//处理当前层process_data(level, data, ...);//进入下一层re…

10_缓存-2_二级缓存

二级缓存是以namespace为标记的缓存,可以是由一个SqlSessionFactory创建的SqlSession之间共享缓存数据。默认并不开启。下面的代码中创建了两个SqlSession,执行相同的SQL语句,尝试让第二个SqlSession使用第一个SqlSession查询后缓存的数据。要…

CTF Misc(1)图片隐写基础以及原理,覆盖了大部分题型

前言 在ctf比赛中,misc方向是必考的一个方向,其中,图片隐写也是最常见的题目类型,在本篇文章中,将教授以下内容 1.各种图片文件的头数据以及判断是什么类型的图片 2.png图片隐写 3.jpg图片隐写 4.gif图片隐写 5.bmp图…

Android---RecyclerView实现吸顶效果

目录 一、ItemDecoration 二、实现RecyclerView吸顶效果 1、实现一个简单的RecyclerView。 2、通过ItemDecoration画分割线 3、画出每个分组的组名 4、实现吸顶效果 完整demo 一、ItemDecoration ItemDecoration 允许应用给具体的 View 添加具体的图画或者 layout 的偏移…

论文投稿指南——中文核心期刊推荐(物理学)

【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…

[附源码]计算机毕业设计Python的高校课程知识库(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等…

如何做好源代码防泄密

​ 一、前言 • 各类嵌入式研发及平台软件研发行业,都有自己的核心数据以及核心文档,用户数据等敏感信息,这些信息数据有以下共性: –属于核心机密资料,万一泄密会给造成恶劣影响 –核心数据类型多,有…

验证码是自动化的天敌?看看大神是怎么解决的

01 验证码 1、什么是验证码: 指一种随机生成的信息(数字、字母、汉字、图片、算术题)等为了防止恶意的请求行为,增加应用的安全性 自动化过程中也是需要进行注册或者登陆的操作,所以需要处理验证 2、验证码处理方式…

《第一堂棒球课》:MLB棒球创造营·棒球名人堂

铃木一朗,1973年10月22日出生于西春日井郡丰山町(日本),日本职业棒球运动员,效力于美国职棒大联盟西雅图水手队。 1991年被欧力士蓝浪以第四指名选中,1994年以片假名(Ichiro)在一军…

记录一次并发问题的解决

并发问题的产生背景 该问题是在生产运行的过程中出现的。这个运行的项目是一个拉取第三方数据的一个服务,该服务会在拉取到数据之后直接将该数据直接插入到本地库,其中插入本地库的操作是调用的一个静态方法,静态方法对数据进行了多次数据处…

携程Apollo配置中心架构介绍

俗话说”麻雀虽小,五脏俱全“,有人说想看开源源码却不知道什么好,事实上,那些流行多年,广受好评的开源工程都是很值得一读的。今天我们介绍Apollo配置中心的基本情况,之所以介绍这个,主要是因为…

在线地图持续进化,BAT技术“鲜”发制人

配图来自Canva可画 眼下,在线地图正在成为智能穿戴、物流运输、旅游度假等诸多领域的“基础设施”,尤其是自动驾驶、车路协同等汽车细分赛道越来越重视在线地图的导入。 得益于此,在线地图市场持续走向火热。华经产业研究院数据显示&#x…

linux基础学习-ssh基础

ssh基础 通过SSH客户端我们可以连接到运行了SSH服务器的远程机器上 SSH客户端是一种使用Secure Shell(SSH)协议连接到远程计算机的软件程序目前较为可靠,专门为远程登录会话和其他网络服务提供安全性的协议 利用SSH协议可以有效防止远程管理过程中的信息泄露提供SS…

行业级开源无人机目标追踪,高空助力抓贼!

活久见!成都一高楼惊险无人机抓小偷视频中危险动作,请勿模仿! 本次实验中我们使用的是Prometheus 600(P600)行业级无人机研发平台(此平台适用于无人机行业应用开发与室外环境下的无人机算法验证&#xff0…

White Rose设计与架构的想法分享

在七牛云校园黑客马拉松中,一款设计优秀、逻辑清晰的白板作品脱颖而出,获得第二名的好成绩,这就是来自郑州大学Since团队的White Rose白板,以下是他们的设计和架构分享。 一、前言 White Rose是参加七牛云hackathon比赛的作品&am…

【】Fate单机部署及代码调试全流程ongoing

这里写自定义目录标题Fate单机部署及代码调试全流程一、安装Linux系统或者虚拟机-Linux系统1、先装虚拟机2、在虚拟机上安装Ubuntu系统二、FATE单机部署并PyCharm如何连接远程服务器的docker容器进行运行和调试代码【整体未成功,可跳过】三、Ubuntu系统上安装anacon…