可以将一个类的定义放在另一个类的定义内部,这就是内部类

内部类远不止如此,它就像是一个外围类;并能与之通信;而且你用内部类写出来的代码更加优雅清晰。

1. 创建内部类

将一个类定义在另一个类的内部,这就是内部类。内部类与组合是不同的概念。

如上我们创建了一个内部类,内部类与其它类的区别在于将类隐藏在了另一个类的内部,同时如contents方法所示,外部类的方法还可以返回一个指向内部类的引用,这也是很常见的一种用法。此外我们看到main()方法中创建的内部类对象是使用外部类的引用关联创建的,这一点在下一节中会说到。

public class Parcell {class Contents{private int i = 11;public int value(){return i;}}class Destination{private String label;public Destination(String whereto) {// TODO Auto-generated constructor stublabel = whereto;}String readLabel(){return label;}}public Contents contents(){return new Contents();}public Destination destination(String s){return new Destination(s);}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) {Parcell p1 = new Parcell();p1.ship("Inner Class");Parcell p2 = new Parcell();Contents c = p2.contents();Parcell.Destination d = p2.destination("Class Inner");}
}

2. 链接到外部类

上边的代码似乎只展示了内部类与其它类名字和组织结构的区别,内部类还有其它的用途。当我们创建了一个内部类对象,此对象就与制造它的外围对象之间有了一种关联,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。

所以内部类自动拥有对其外围类所有成员的访问权限,那么这是如何做到的呢?

当某个外围类的对象创建一个内部类的对象的时候,这个内部类对象必然会秘密捕获一个外围类对象的引用,也就是这个引用来选择外围类的成员。这里所有的细节都交给了编译器来处理。内部类的对象只能在其与外部类的对象相关联的时候才能被创建(在static方法中),构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错,不过绝大多数情况下这种都不需要我们操心。这里也就解释了上一节关于内部类初始化方式不同的原因。意思就是在static方法中,不能通过直接的new 构造函数的形式创建内部类,因为这种形式没有将内部类与外部类对象做关联,要先创建一个外部类的对象,然后使用该对象的引用创建内部类对象。当然如果内部类是静态的,那么就没有这种要求了。

3.使用.this与.new

如果我们需要在内部类生成外部类对象的引用,那么可以使用外部类名.this的形式,这里如果只使用this,则返回的是内部类对象的引用。

public class DoThis {void f(){System.out.println("This is outClass's method");}public class Inner{public DoThis outer(){return DoThis.this;//return this;}}public Inner inner(){return new Inner();}public static void main(String[] args) {DoThis dt = new DoThis();Inner in = dt.inner();in.outer().f();Inner out=dt.new Inner();out.outer().f();}
}

如前文说到,我们不能使用new直接创建内部类对象,我们需要使用外部类对象的引用创建,这里可以使用外部类对象的引用.new语法进行创建。

4. 内部类与向上转型

当将内部类向上转型为基类时,尤其是转型为接口时,内部类就有了用武之地。这是因为我们可以使内部类也就是接口的实现完全不可见也不可用,得到的只是基类或者接口的引用,从而更好的隐藏了实现的细节。我们先创建两个接口:

public interface Destination{String readLabel();
}
public interface Contents {int value();
}
class Parcell4{private class PContents implements Contents{private int i = 11;@Overridepublic int value() {// TODO Auto-generated method stubreturn i;}}protected class PDestination implements Destination{private String label;private PDestination(String whereto) {// TODO Auto-generated constructor stublabel = whereto;}@Overridepublic String readLabel() {// TODO Auto-generated method stubreturn label;}}public Destination destination(String s){return new PDestination(s);}public Contents contents(){return new PContents();}
}
public class TestParcell {public static void main(String[] args) {Parcell4 p = new Parcell4();Contents c = p.contents();Destination d = p.destination("Inner Class");System.out.println(d.readLabel());System.out.println(c.value());//因为PContents是private 所以不能被访问//Parcell4.PContents pc = p.new PContents();}
}

Parcell4中增加了一些新的东西,首先内部类PContents是private,除了Parcell4没有人能访问它,所以main函数最后一行编译不能通过。其次PDestination是protected的,所以除了该类本身和其子类还有同一个包中的类,其它类不能访问。因此客户端如果想访问这些实现,就受到了限制。不过我们可以看到,main函数的第二、第三行都实现了转型,也就是虽然不可见,但是不影响使用接口的实现。因此private的内部类提供了一种设计思路,通过这种方式完全阻止了依赖任何类型的编码,并且完全隐藏了实现的细节,并且由于不能访问任何新增加的、原本不属于公共接口的方法,因此接口的扩展就是没有价值的了。

5. 在方法和作用域内的内部类

有些时候我们可以将内部类创建在方法的作用域里或者是其它任何地方的作用域中,这么做有两个理由:

  1. 如前所示,实现了某个类型的接口,可以创建并返回接口的引用。
  2. 要解决一个复杂的问题,需要一个类来辅助解决,但是又不希望这个类是公开的。

在方法的作用域内部创建的内部类称为局部内部类:

6. 匿名内部类

public class Parcel7 {public Contents contents(){return new Contents(){private int i = 11;@Overridepublic int value() {return i;}};}public static void main(String[] args) {Parcel7 p7 = new Parcel7();Contents c = p7.contents();}
}

在此处,contents方法内部要返回一个Contents对象的时候,我们突然加了一个类的定义,这个类没有名字,它实现了Contents接口,也就是我们实际上创建了一个继承自Contents类的匿名类对象,于是这个return对象的引用就变成了一个来自向上转型的Contents引用。上述这个匿名内部类是下面这种形式的一种简化。

上述代码使用了默认的无参构造器,下边的示例展示当基类的构造器为有参数的构造器时,匿名内部类应当如何创建:

基类:

public class Wrapping {private int i;public Wrapping(int x){i=x;}public int value(){return i;}
}

匿名内部类:

public class Parcel8 {public Wrapping wrapping(int x){return new Wrapping(x){public int value(){return super.value() * 11;}};}
}

7. 嵌套类(静态内部类)

前边我们说的内部类,都是必须要有外部类关联的,也就是这些内部类有个隐式的引用,指向外部类。如果我们不需要这种关联,那么就可以将内部类显示的声明为static的,这种内部类称为嵌套类。嵌套类意味着:

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

嵌套类与普通的内部类还有一个区别,普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类,而嵌套类可以包含所有这些东西。

8. 为什么需要内部类

一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象,所以可以认为内部类提供了一种进入它外围类的窗口。如果我们只是需要一个对接口的引用,那么为何不使用外围类去实现那个接口呢?答案是如果这样能满足需求,那就需要这样做。内部类实现接口与外部类实现接口的区别在于后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以使用内部类最吸引人的原因:

每个内部类都能够独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的实现),对于内部类都没有影响

内部类有效的实现了“多重继承”,如果在一个类中要使用两个接口,那么使用单一类和内部类看起来没有什么区别(因为单一类可以直接实现多个接口,此处不写例子了),而如果这两个接口换成是抽象类或者是具体的类,那么由于Java不支持多重继承的原因,这里使用单一类显然不能解决问题了,而内部类恰好可以有效的解决这个问题,看似是个“多重继承”。

如果不是要解决类似上边的“多重继承”问题,那么可以不实用内部类,但是使用内部类还可以获得一些其它的特性。

  1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外部类的对象信息相互独立。
  2. 在单个外围类中,可以让多个内部类继承或实现多个基类。
  3. 创建内部类的时刻并不依赖于外围对象的创建。(个人不太理解这里,因为前边说了非嵌套类在static方法中使用时,需要关联一个外部对象的引用,不知道这里具体指的是什么。)
  4. 内部类并没有令人迷惑的is-a关系,它是一个独立的实体。

9. 内部类继承

class withInner{class Inner{     }
}
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);}
}

10 内部类可以被覆盖吗

如果一个类继承了另一个外部类,那么基类中的内部类会发生覆盖吗?

但是实际结果显然不是这样的,它还是走了正常的逻辑流程,说明子类并没有覆盖基类中内部类,这两个内部类彼此独立,在自己的命名空间中。当我们想进行类似“覆盖”内部类的功能时,可以明确的继承内部类,然后覆盖其方法。

11. 局部内部类

定义在方法体中内部类称为局部内部类

12. 内部类标识符

由于每个类都会产生一个.class文件,其中包含如何创建该类型的全部信息。(此信息产生一个“meta-class”,叫做class对象)所以内部类也一定会有个.class文件,它们有规范的命名规则,外围类加上“$”加上内部类的名字。

如果是匿名内部类,编译器会简单的产生一个数字作为标识符,如果内部类是嵌套在别的内部类里,那么就继续使用"$"符号。

Counter.class
LocalInnerclass$1.class
LocalInnerclass$LocalCounter.class
LocalInnerClass.class

13 总结

  1. 内部类是定义在一个类内部的类,这个类可以在方法中,也可以在方法外。内部类可以访问到其外部类的所有域。
  2. 内部类与外部类之间的关联关系是使用一个隐式的外部类引用,所以在创建内部类时,需要先创建一个外部类引用进行关联。这种形式出现在当内部类不是static修饰并且在static方法域中创建内部类对象引用的时候。
  3. 在内部类中,要使用外部类.this才可以返回外部类的对象引用,如果使用this只是返回了内部类的对象引用。当我们创建了一个外部类对象引用时,可以使用引用.new 内部类()的形式创建内部类对象。
  4. 内部类可以用来向上转型实现接口,这种方式有效的建立了接口与实现的隔离,可以使实现完全不可见,不可修改。
  5. 在方法和作用域里的类,有两个作用,一个是如前所示用来实现接口并返回。另一个原因是我想创建一个类辅助我解决问题,但是不想这个类可见。
  6. 使用return new xx(){} 在{}内部定义类的一些域可以创建一个实现或继承xx的匿名类,这个匿名类没有名字,也就没有构造函数。匿名类使用的外部方法引用需要被修饰为final。
  7. 如前所示,内部类的创建需要与外部类进行关联。如果我们不需要进行关联,那么可以将内部类修饰为static,这种称为嵌套类。嵌套类与外部类彼此独立。
  8. 内部类可以实现类似“多重继承”。
  9. 内部类与外部类的引用有关联,所以在继承内部类的时候需要显示的在构造函数中引用外部类的引用,以说明这种关联。
  10. 外部类被继承之后,内部类没有发生特别的变化,也就是它不会被覆盖,如果在子类中重新定义同名的内部类,这会被认为是第二个类,与之前的内部类彼此在不同的命名空间,没有关联。
  11. 局部内部类是定义在方法中的,作用与匿名类相同,但是有构造函数,可以进行构造函数重载。
  12. 所有的类都有标识符,内部类的标识符为外部类名字$内部类名字。

Java编程思想 第十章:内部类相关推荐

  1. Java编程思想 第二十章 注解

    文章目录 20.0 前言 20.1 基本语法 20.1.1 定义注解 20.1.2 元注解: 20.2 编写注解处理器 20.2.1 例一 20.2.2 例二 20.2.3 注解不支持继承 20.3 ...

  2. 【Java编程思想】读书笔记(二)第六章---第十章

    Java编程思想(第四版)学习笔记 第六章---第十章 第六章:访问权限控制 6.2Java访问权限修饰词 第七章:复用类 7.1 组合语法 7.2 继承语法(extends) 7.4.2名称屏蔽(重 ...

  3. 关于阅读java编程思想和effective java的一些看法

    个人认为,java编程思想并不适合当作新手入门书籍来看,它更多是像给已经使用过java的人群对于基础的一些查缺补漏,有点像一本大部头的工具书,目前该书已看至第十章 -- 内部类, 而effective ...

  4. Java编程思想 (1~10)

    [注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第一章 对象导论 1.万物皆对象 2.程序就是对象的集合 3.每个对象都是由其它对象所 ...

  5. 《JAVA编程思想》学习笔记:第21章(并发)

    目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章:多 ...

  6. 《Java编程思想》读书笔记一

    很早之前就买了<Java编程思想>这本书,初学时看这本书看的云里雾里的,实在费劲,就放在一边垫桌底了.感觉这本书是适合C/C++程序员转行到Java学习的一本书,并不适合零基础的初学者去看 ...

  7. 《Java编程思想》读书笔记(二)

    三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第一章到第十章的内容,这一次记录的是第 ...

  8. Java编程思想(第4版)(评注版)

    传世经典书丛  Java编程思想(第4版)(评注版)  (美)埃克尔(Eckel, B.)著 刘中兵评注 ISBN 978-7-121-13521-7 2011年6月出版 定    价:108.00元 ...

  9. 《Java编程思想》读书笔记

    前言:三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第十七章到第十八章的内容,这一次 ...

最新文章

  1. Python 炫技操作:海象运算符的三种用法
  2. 沈志康奖教金 计算机学院,天津大学机械工程学院
  3. windows下,linux下c++生成文件夹
  4. s2 安恒 漏洞验证工具_Struts2漏洞利用工具下载(更新2017-V1.8版增加S2-045/S2-046)
  5. 石油-美元金融体系的形成
  6. NVIDIA DLI 深度学习培训 | 北京站 即将开班
  7. mysql-connector-java-8.0.26-bin.jar 包含bin的jar下载
  8. python 存根_pyi文件是干吗的?(一文读懂Python的存根文件和类型检查)
  9. linux强制退出进程
  10. 物流软件全过程管理办法
  11. 【数据可视化】数据可视化分类
  12. 71外链论坛_免费发外链平台
  13. 前端三剑客:HTML
  14. 7-30 正常血压 (6 分)
  15. win7安装Scrapy时报错Failed building wheel for Twisted
  16. 跟着狂神老师配置Dubbo
  17. 免登陆免会员修改finalshell背景图(避坑版)
  18. Firefox火狐浏览器强制使用阅读模式(添加插件法)
  19. 尤雨溪和Vue的那些事
  20. Cosmic Cleaner(求两球相交体积)

热门文章

  1. 【转】解决父容器高度不跟随子元素扩大的问题
  2. leetcode 46 全排列
  3. 40.简述操作系统中调用过程?
  4. 危机时保路人还是保乘客?无人车伦理困境背后:谁来制定算法规则
  5. 宝马无人车体验:把司机彻底干掉,有必要吗?
  6. Fedora 30系统下,用g++编译opencv项目
  7. 国内C/C++刷题网站汇总
  8. tensorflow安装教程
  9. matlab verilog 接口,使用SystemVerilog简化FPGA中的接口
  10. java中rpn_java – RPNCalculator代码混淆