通常情况下,一个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. win10常用详细快捷键大全

    • 贴靠窗口:Win +左/右> Win +上/下>窗口可以变为1/4大小放置在屏幕4个角落• 切换窗口:Alt + Tab(不是新的,但任务切换界面改进)• 任务视图:Win + Tab ...

  2. 干货分享: 长达250页的Libvirt Qemu KVM的ppt,不实验无真相

    下载地址:Libvirt Qemu KVM 教程大全 http://files.cnblogs.com/popsuper1982/LibvirtQemuKVM.pptx 1. 概论 1.1 虚拟化的基 ...

  3. 从协议入手,剖析OAuth2.0(译 RFC 6749)

    1.介绍      https://tools.ietf.org/html/rfc6749  传统的client-server授权模型,客户端通过使用凭证(通常的用户名和明文密码)访问服务端受保护的资 ...

  4. [文章汇总]ASP.NET Core框架揭秘[最近更新:2018/10/31]

    之前一段时间都在个人公众号账号“大内老A”发布关于ASP.NET Core的系列文章,很多人留言希望能够同步到这里,所以在这里 对这些文章做一个汇总,以便于PC端阅读.如果说微软官方文档主要关于ASP ...

  5. 简述一下MVC和MVVM

    一. MVC 我们先来了解一下什么是MVC. MVC:分别所指Model.View.Controller. MVC为标准的设计模式,是官方推荐的权威的规范模式. 视图(View):用户交互界面. 控制 ...

  6. 原生JS实现下拉刷新

    <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8& ...

  7. [Swift]LeetCode473. 火柴拼正方形 | Matchsticks to Square

    Remember the story of Little Match Girl? By now, you know exactly what matchsticks the little match ...

  8. 【dotNet Core】Swagger下简单的给WebApi分组

    Startup.cs下ConfigureServices代码 这里主要在DocInclusionPredicate控制输出那些api. Startup.cs下Configure代码 给Controll ...

  9. WARNING: 'aclocal-1.14' is missing on your system.

    源码安装zabbix agent时进行到make install时报如下错误: WARNING: 'aclocal-1.14' is missing on your system. You shoul ...

  10. Qt之QComboBox定制

    说起下拉框,想必大家都比较熟悉,在我们注册一些网站的时候,会出现大量的地区数据供我们选择,这个时候出现的就是下拉框列表,再比如字体选择的时候也是使用的下拉框,如图1所示.下拉框到处可见,作为一个图形库 ...