Java开发笔记(五十四)内部类和嵌套类
通常情况下,一个Java代码文件只定义一个类,即使两个类是父类与子类的关系,也要把它们拆成两个代码文件分别定义。可是有些事物相互之间密切联系,又不同于父子类的继承关系,比如一棵树会开很多花朵,这些花儿作为树木的一份子,它们依附于树木,却不是树木的后代。花朵不但拥有独特的形态,包括花瓣、花蕊、花萼等,而且拥有完整的生命周期,从含苞欲放到盛开绽放再到凋谢枯萎。这样一来,倘若把花朵抽象为花朵类,那么花朵类将囊括花瓣、花蕊、花萼等成员属性,以及含苞、盛开、凋谢等成员方法。既然花朵类如此规整,完全可以定义为一个class,但是花朵类又依附于树木类,说明它不适合从树木类独立出来。
为了解决这种依附关系的表达问题,自然就得打破常规思维,其实Java支持类中有类,在一个类的内部再定义另外一个类,仿佛新类是已有类的成员一般。一个类的成员包括成员属性和成员方法,还包括刚才说的成员类,不过“成员类”的叫法不常见,大家约定俗成的叫法是“内部类”,与内部类相对应,外层的类也可称作“外部类”。仍旧以前述的树木类和花朵类为例,如今可在树木类的内部增加定义花儿类,就像下面代码那样:
//演示内部类的简单定义
public class Tree {
private String tree_name; public Tree(String tree_name) {
this.tree_name = tree_name;
} public void sprout() {
System.out.println(tree_name+"发芽啦");
// 外部类访问它的内部类,就像访问其它类一样,都要先创建类的实例,再访问它的成员
Flower flower = new Flower("花朵");
flower.bloom();
} // Flower类位于Tree类的内部,它是个内部类
public class Flower {
private String flower_name; public Flower(String flower_name) {
this.flower_name = flower_name;
} public void bloom() {
System.out.println(flower_name+"开花啦");
}
}
}
从以上代码可见,外部类里面访问内部类Flower,就像访问其它类一样,都要先创建类的实例,再访问它的成员。至于在外面别的地方访问这里的外部类Tree,自然也跟先前的用法没什么区别。可是如果别的地方也想调用内部类Flower,那就没这么容易了,因为直接通过new关键字是无法创建内部类实例的。只有先创建外部类的实例,才能基于该实例去new内部类的实例,内部实例的创建代码格式形如“外部类的实例名.new 内部类的名称(...)”。下面是外部调用内部类的具体代码例子:
// 先创建外部类的实例,再基于该实例去创建内部类的实例
TreeInner inner = new TreeInner("桃树");
// 创建一个内部类的实例,需要在new之前添加“外层类的实例名.”
TreeInner.Flower flower = inner.new Flower("桃花");
flower.bloom(); // 调用内部类实例的bloom方法
所谓好事多磨,引入内部类造成的麻烦不仅仅一个,还有另一个问题也挺棘手的。由于内部类是外部类的一个成员类,因此二者不可避免存在理论上的资源冲突。假设外部类与内部类同时拥有某个同名属性,比如它俩都定义了名叫tree_name的树木名称字段,那么在内部类里面,tree_name到底指的是内部类自身的同名属性,还是指外部类的同名属性呢?
从前面的类继承文章了解到,一旦遇到同名的父类属性、子类属性、输入参数,则编译器采取的是就近原则,例如在方法内部优先表示同名的输入参数,在子类内部优先表示同名的子类属性等等。同理,对于同名的内部类属性和外部类属性来说,tree_name在内部类里面优先表示内部类的同名属性。考虑到避免混淆的缘故,也可以在内部类里面使用“this.属性名”来表达内部类的自身属性。但如此一来,内部类又该怎样访问外部类的同名属性,确切地说,内部类Flower的定义代码应当如何调用外部类TreeInner的tree_name字段?显然这个问题足以让关键字this人格分裂,明明身在TreeInner里面,却代表不了TreeInner。为了拯救可怜的this,Java允许在this之前补充类名,从而限定此处的this究竟代表哪个类。譬如“TreeInner.this”表示的是外部类TreeInner自身,而“TreeInner.this.tree_name”则表示TreeInner的成员属性tree_name。于是在内部类里面终于能够区分内部类和外部类的同名属性了,详细的区分代码如下所示:
// 该方法访问内部类自身的tree_name字段
public void bloomInnerTree() {
// 内部类里面的this关键字指代内部类自身
System.out.println(this.tree_name+"的"+flower_name+"开花啦");
} // 该方法访问外部类TreeInner的tree_name字段
public void bloomOuterTree() {
// 要想在内部类里面访问外部类的成员,就必须在this之前添加“外部类的名称.”
System.out.println(TreeInner.this.tree_name+"的"+flower_name+"开花啦");
}
当然多数场合没有这种外部与内部属性命名冲突的情况,故而在this前面添加类名纯属多此一举,只有定义了内部类,并且内部类又要访问外部类成员的时候,才需要显式指定this的归属类名。
苦口婆心地啰嗦了许久,内部类的小脾气总算搞定了。不料一波三折,之前说到其它地方调用内部类的时候,必须先创建外部类的实例,然后才能创建并访问内部类的实例。这个流程实在繁琐,好比我想泡一杯茉莉花茶,难道非得到田里种一株茉莉才行?很明显这么搞费时又费力,理想的做法是:只要属于对茉莉花的人为加工,而非紧密依赖于茉莉植株的自然生长,那么这个茉莉花类理应削弱与茉莉类的耦合关系。为了把新的类与类关系同外部类与内部类区分开来,Java允许在内部类的定义代码前面添加关键字static,表示这是一种静态的内部类,它无需强制绑定外部类的实例即可正常使用。
静态内部类的正式称呼叫“嵌套类”,外层类于它而言仿佛一层外套,有套没套不会对嵌套类的功能运用产生实质性影响,套一套的目的仅仅表示二者比较熟悉而已。下面是把Flower类改写为嵌套类的代码定义例子,表面上只加了一个static:
//演示嵌套类的定义
public class TreeNest {
private String tree_name; public TreeNest(String tree_name) {
this.tree_name = tree_name;
} public void sprout() {
System.out.println(tree_name+"发芽啦");
} // Flower类虽然位于TreeNest类的里面,但是它被static修饰,故而与TreeNest类的关系比起一般的内部类要弱。
// 为了与一般的内部类区别开来,这里的Flower类被叫做嵌套类。
public static class Flower {
private String flower_name; public Flower(String flower_name) {
this.flower_name = flower_name;
} public void bloom() {
System.out.println(flower_name+"开花啦");
} public void bloomOuterTree() {
// 注意下面的写法是错误的,嵌套类不能直接访问外层类的成员
//System.out.println(TreeNest.this.tree_name+"的"+flower_name+"开花啦");
}
}
}
现在Flower类变成了嵌套类,别的地方访问它就会省点事,按照格式“new 外层类的名称.嵌套类的名称(...)”即可直接创建嵌套类的实例,不必画蛇添足先创建外层类的实例。完整的调用代码如下所示:
// 演示嵌套类的调用方法
private static void testNest() {
// 创建一个嵌套类的实例,格式为“new 外层类的名称.嵌套类的名称(...)”
TreeNest.Flower flower = new TreeNest.Flower("桃花");
flower.bloom();
}
正所谓有利必有弊,外部调用嵌套类倒是省事,嵌套类自身若要访问外层类就不能随心所欲了。原先花朵类作为内部类之时,通过前缀“外部类的名称.this”便可访问外部类的各项成员;现今花朵类摇身一变嵌套类,要访问外层的树木类不再容易了,对嵌套类而言,外层类犹如一个熟悉的陌生人,想跟它打招呼就像跟路人打招呼一样无甚区别,都得先创建对方的实例,然后才能通过实例访问它的每个成员。
迄今为止,这里已经介绍了好几种的类,它们相互之间的关系各异,通俗地说,子类与父类之间是继承关系,内部类与外部类之间是共存关系,嵌套类与外层类之间是同居关系。
更多Java技术文章参见《Java开发笔记(序)章节目录》
Java开发笔记(五十四)内部类和嵌套类的更多相关文章
- Java开发笔记(十四)几种运算符的优先级顺序
到目前为止,我们已经学习了Java语言的好几种运算符,包括算术运算符.赋值运算符.逻辑运算符.关系运算符等基础运算符,并且在书写赋值语句时都没添加圆括号,显然是默认了先完成算术.逻辑.关系等运算,最后 ...
- Java中的集合(十四) Map的实现类LinkedHashMap
Java中的集合(十四) Map的实现类LinkedHashMap 一.LinkedHashMap的简介 LinkedHashMap是Map接口的实现类,继承了HashMap,它通过重写父类相关的方法 ...
- “全栈2019”Java第六十七章:内部类、嵌套类详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- Java开发笔记(九十四)文件通道的性能优势
前面介绍了字节缓存的一堆概念,可能有的朋友还来不及消化,虽然文件通道的用法比起传统I/O有所简化,可是平白多了个操控繁琐的字节缓存,分明比较传统I/O更加复杂了.尽管字节缓存享有缓存方面的性能优势,但 ...
- 【Java学习笔记之三十四】超详解Java多线程基础
前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...
- Java基础笔记(十四)——封装
封装(好比ATM机) 将类的某些信息隐藏在类内部,不允许外部程序直接访问(隐藏对象的信息),通过该类提供的方法来实现对隐藏信息的操作和访问(留出访问的接口). 特点: 1.只能通过规定的方法访问数据. ...
- 树莓派开发笔记(十四):入手研华ADVANTECH工控树莓派UNO-220套件(三):使用研发自带系统测试rtc、gpio、232和485套件接口
前言 上一篇说明了必须要使用研华自带的8G卡的系统,通过沟通拿到了相关的系统,购买的时候会带8GB的卡,请自行备份一份镜像.本篇对uno-220套件的相关研华配套的额外接口做测试,篇幅较长,重点讲 ...
- Java开发学习(二十四)----SpringMVC设置请求映射路径
一.环境准备 创建一个Web的Maven项目 参考Java开发学习(二十三)----SpringMVC入门案例.工作流程解析及设置bean加载控制中环境准备 pom.xml添加Spring依赖 < ...
- Java开发笔记(十五)短路逻辑运算的优势
前面提到逻辑运算只能操作布尔变量,这其实是不严谨的,因为经过Java编程实现,会发现“&”.“|”.“^”这几个逻辑符号竟然可以对数字进行运算.譬如下面的代码就直接对数字分别开展了“与”.“或 ...
- 【Java学习笔记之十四】Java中this用法小节
用类名定义一个变量的时候,定义的只是一个引用,外面可以通过这个引用来访问这个类里面的属性和方法. 那们类里面是够也应该有一个引用来访问自己的属性和方法纳? 呵呵,JAVA提供了一个很好的东西,就是 t ...
随机推荐
- Visual Studio(VS)秘钥集合
Visual Studio 2019 Pro :NYWVH-HT4XC-R2WYW-9Y3CM-X4V3Y
- 循环结构for
教程:高能:语句结构都是由关键字开头,用冒号结束! 一:语句结构 for <variable> in <sequence>: <statements>else ...
- react基础篇 整理(一)
备注不知道为啥不能到出图片,详细知识自己百度一下就可以了,很简单的.画这个是为了更好的梳理知识,公司有个App项目,项目可控,所以尝试一下用React-native去做一下试试.
- 开发自定义ScriptableRenderPipeline,将DrawCall降低180倍
0x00 前言 大家都知道,Unity在2018版本中正式推出了Scriptable Render Pipeline.我们既可以通过Package Manager下载使用Unity预先创建好的Ligh ...
- 【安富莱原创开源应用第3期】花式玩转网络摄像头之VNC远程桌面版本,稳定运行2年不死机
说明: 1.前段时间开源了一个网络摄像头的TCP版本 https://www.cnblogs.com/armfly/p/9173167.html,这次再来一个远程VNC的版本.使用更方便,无需大家制作 ...
- FFmpeg 结构体学习(五): AVCodec 分析
在上文FFmpeg 结构体学习(四): AVFrame 分析我们学习了AVFrame结构体的相关内容.本文,我们将讲述一下AVCodec. AVCodec是存储编解码器信息的结构体.下面我们来分析一下 ...
- [Swift]LeetCode404. 左叶子之和 | Sum of Left Leaves
Find the sum of all left leaves in a given binary tree. Example: 3 / \ 9 20 / \ 15 7 There are two l ...
- [Swift]LeetCode518. 零钱兑换 II | Coin Change 2
You are given coins of different denominations and a total amount of money. Write a function to comp ...
- Docker for windows : 安装Redis
一.拉取Redis镜像 docker pull hub.c..com/library/redis: 二.创建并运行Redis docker run -d -it --name redis d4f259 ...
- Hibernate框架笔记04HQL_QBC查询详解_抓取策略优化机制
目录 1. Hibernate的查询方式 1.1 方式一:OID查询 1.2 方式二:对象导航查询 1.3 方式三:HQL方式 1.4 方式四:QBC查询 1.5 方式五:SQL查询 2. 环境搭建 ...