通常情况下,一个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开发笔记(五十四)内部类和嵌套类的更多相关文章

  1. Java开发笔记(十四)几种运算符的优先级顺序

    到目前为止,我们已经学习了Java语言的好几种运算符,包括算术运算符.赋值运算符.逻辑运算符.关系运算符等基础运算符,并且在书写赋值语句时都没添加圆括号,显然是默认了先完成算术.逻辑.关系等运算,最后 ...

  2. Java中的集合(十四) Map的实现类LinkedHashMap

    Java中的集合(十四) Map的实现类LinkedHashMap 一.LinkedHashMap的简介 LinkedHashMap是Map接口的实现类,继承了HashMap,它通过重写父类相关的方法 ...

  3. “全栈2019”Java第六十七章:内部类、嵌套类详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. Java开发笔记(九十四)文件通道的性能优势

    前面介绍了字节缓存的一堆概念,可能有的朋友还来不及消化,虽然文件通道的用法比起传统I/O有所简化,可是平白多了个操控繁琐的字节缓存,分明比较传统I/O更加复杂了.尽管字节缓存享有缓存方面的性能优势,但 ...

  5. 【Java学习笔记之三十四】超详解Java多线程基础

    前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...

  6. Java基础笔记(十四)——封装

    封装(好比ATM机) 将类的某些信息隐藏在类内部,不允许外部程序直接访问(隐藏对象的信息),通过该类提供的方法来实现对隐藏信息的操作和访问(留出访问的接口). 特点: 1.只能通过规定的方法访问数据. ...

  7. 树莓派开发笔记(十四):入手研华ADVANTECH工控树莓派UNO-220套件(三):使用研发自带系统测试rtc、gpio、232和485套件接口

    前言   上一篇说明了必须要使用研华自带的8G卡的系统,通过沟通拿到了相关的系统,购买的时候会带8GB的卡,请自行备份一份镜像.本篇对uno-220套件的相关研华配套的额外接口做测试,篇幅较长,重点讲 ...

  8. Java开发学习(二十四)----SpringMVC设置请求映射路径

    一.环境准备 创建一个Web的Maven项目 参考Java开发学习(二十三)----SpringMVC入门案例.工作流程解析及设置bean加载控制中环境准备 pom.xml添加Spring依赖 < ...

  9. Java开发笔记(十五)短路逻辑运算的优势

    前面提到逻辑运算只能操作布尔变量,这其实是不严谨的,因为经过Java编程实现,会发现“&”.“|”.“^”这几个逻辑符号竟然可以对数字进行运算.譬如下面的代码就直接对数字分别开展了“与”.“或 ...

  10. 【Java学习笔记之十四】Java中this用法小节

    用类名定义一个变量的时候,定义的只是一个引用,外面可以通过这个引用来访问这个类里面的属性和方法. 那们类里面是够也应该有一个引用来访问自己的属性和方法纳? 呵呵,JAVA提供了一个很好的东西,就是 t ...

随机推荐

  1. Bandwagon的配置记录(二) —— ftp文件传输

    SSH登录服务器 登录的方法在Bandwagon的配置记录(一) —— kexue上网 配置前的准备 1.新建一个目录(  /home/ftp  ),以后可以把文件放在这里,这里相当于是个中转站 cd ...

  2. koa2学习(一)

    前期准备: node环境 npm包管理工具 安装Koa npm install --save koa 第一个程序 创建index.js const Koa = require('koa'); cons ...

  3. js-day05-JSON-jQuery初体验

    JSON数据格式 JSON(JavaScript Object Notation)一种简单的数据格式,比xml更轻巧.易于人阅读和编写,同时也易于机器解析和生成(网络传输速度快)JSON是JavaSc ...

  4. charles-web端开发者实用功能点

    ##网速模拟功能 throttle功能对于前端来说非常实用,可以看页面在低网速下的表现,从而找出优化的点. 在线上环境通常有些因为网速慢导致的bug,在本机无法重现,那时候就很抓瞎,如果嫌远程麻烦,推 ...

  5. Android APK 瘦身 - JOOX Music项目实战

    导语 JOOX Music是腾讯海外布局的一个音乐产品,2014年发布以来已经成为5个国家和地区排名第一的音乐App.东南亚是JOOX Music的主要发行地区,由于JOOX Music所面对的市场存 ...

  6. Touch事件在移动端web开发中的详解

    一.pc端事件回顾 HTML事件.DOM0事件.DOM2事件 事件对象. 如果上述概念不清楚,请先去了解. 二.移动端事件简介 2.1 pc端事件在移动端的问题 ​ 移动设备主要特点是不配备鼠标,键盘 ...

  7. python爬虫学习视频资料免费送,用起来非常666

    当我们浏览网页的时候,经常会看到像下面这些好看的图片,你是否想把这些图片保存下载下来. 我们最常规的做法就是通过鼠标右键,选择另存为.但有些图片点击鼠标右键的时候并没有另存为选项,或者你可以通过截图工 ...

  8. [Swift]LeetCode732. 我的日程安排表 III | My Calendar III

    Implement a MyCalendarThree class to store your events. A new event can always be added. Your class ...

  9. [Swift]LeetCode756. 金字塔转换矩阵 | Pyramid Transition Matrix

    We are stacking blocks to form a pyramid. Each block has a color which is a one letter string, like ...

  10. [Swift]LeetCode840. 矩阵中的幻方 | Magic Squares In Grid

    A 3 x 3 magic square is a 3 x 3 grid filled with distinct numbers from 1 to 9 such that each row, co ...