今天整理一下内部类,其中包含了内部类的特殊形式,对比普通类有什么区别和作用,内部类和外围类之间的联系,内部类的扩展和被扩展,内部类编译后文件的标识符。文章中有任何错误或遗漏请告知博主~
前置概念
嵌套类型:将一个类innerClass
或者接口innerInterface
的定义放在另一个类outterClass
或者接口outterInterface
定义的内部,那么类innerClass
就是内部类、outterClass
则是外围类,类似的接口innerInterface
为嵌套接口,接口outterInterface
为外围接口。
从这里可以看出嵌套类型之间的区别取决于内部类型是一个类还是一个接口,以及它的外围类型是一个类还是一个接口。内部类型可以是静态的,也可以不是:前者允许简单的类型结构,后者定义了它和包围类对象之间的特别关系。
例如:
class OutterClass{
class InnerClass{
private String name;
}
interface InnerInterface{
String getName();
}
static class InnerClass2{
String name;
}
}
interface OutterInterface{
class InnerClass{
private String name;
String getName(){
return name;
}
}
interface InnerInterface{
void getName();
}
}
在这篇文章中,从最常见的类中嵌套类开始理解整个嵌套类型~
内部类
知道了内部类的概念之后,可以根据类的各种形式得到一些特殊的内部类,其中包括,静态内部类,局部内部类,匿名内部类。在对整个内部类应该有个大体的认识后,在这里抛出疑问,为什么需要内部类,仅仅是因为定义在另一个类的内部么?
其实问题在《Thinking in Java》中有早有说明:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类来说都没有影响。这句话说明了它可以提供一个功能,即,使用内部类后,外围类可以得到类似于多继承的能力。但不仅于此,使用内部类还可以获得其他一些特性:
1.内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围类的对象信息相互独立。
2.在单个外围类中,可以用多个内部类以不同的形式实现同一个接口或继承同一个类。
3.内部类属于外围类的一个轻量级可选组件。
4.内部类提供了更好的封装效果,只能依靠外围类来访问。
注:《Thinking in Java》中写的几条特性里面只选了前面两条,并去掉其中第三条换了第四条,如果有不同理解可以一起讨论。
大体上,内部类的作用已经被明确了,但是我们并不清楚定义内部类是如何和外围类进行联系的,下面可以看一下内部类和外围类的联系机制。
内部类与外围类的联系
尽管我们知道内部类的作用和带来的特性,但是并不清楚它是如何做到这些的,例如它能提供类似于多继承的机制,但是内部类和外围类又有什么关系?如果无法将内部类和外围类联系在一起,那么内部类仅仅只是写在类定义里面的一个普通类,无法带来它特殊的作用。如下代码:
/**
***内部类与外围类的联系
**/
public class InnerClassDemo {
public static void main(String[] arg0){
OutterClass outter = new OutterClass();
OutterClass.InnerClass inner = outter.new InnerClass();
System.out.println("--inner.getName = "+inner.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
class InnerClass{
public String getName(){
return name;
}
}
}
输出:
--inner.getName = OutterClassName
可以看到,内部类本身并没有定义name
字段,它的方法是得到name
字段的值,输出的name
字段的值是外围类的name
字段的值,那么外围类和内部类之间必然是有关联的,这种关联就是内部类特性和作用的关键。在上面的例子中,内部类自然拥有了外围类成员的访问权限,这是如何做到的呢?其实在代码中可以看到,内部类InnerClass
实例inner
的创建是通过其外部类实例outter.new
出来的,那么内部类对象是有机会获取外围类对象的引用的。其实事实上也确实如此,在访问外围类成员的字段时,就是通过捕获的外围类对象引用(非static的内部类)这个引用是限定-this的引用(这个引用强调了一个概念:内部类对象是和包围类对象紧密绑定在一起的,这也是同一个外围类对象实现的一部分),通过这个引用可以访问它的包围类的所有成员,包括私有的。包围类对象也可以访问它内部类对象的私有成员,但只能先声明字段才能访问。
知道了内部类和外部类的联系后,就可以灵活使用内部类语法规则来完成我们的设计,下面来看内部类的使用。
内部类的使用
创建
因为内部类提供了更好的封装性,我们只能通过它的外围类来访问它,那么怎么在外围类外部创建内部类对象呢?在没有使用内部类的时候,我们创建一个类型的实例时,通常选择使用new
关键字,但是在内部类这里,会发现new
关键字只有在外围类内部才起作用,而在外围类之外是无法new
出来的,其实这也是和内部类与外部类的联系有关,内部类既然要自然拥有访问外围类实例的权限,自然要与外围类实例联系在一起,所以需要如上例所示通过外围类实例使用new
创建:OutterClass.InnerClass inner = outter.new InnerClass();
当然这种特别的new
的方式不是唯一解,我们也可以选择另一种创建方式:
/**
*** .this创建
**/
public class innerClassDemo {
public static void main(String[] arg0){
OutterClass outter = new OutterClass();
OutterClass.InnerClass inner = outter.getInnerClass();
System.out.println("--inner.getName = "+inner.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
public InnerClass getInnerClass(){
return new InnerClass();
}
class InnerClass{
public OutterClass getOutterClass(){
return OutterClass.this;
}
public String getName(){
return name;
}
}
}
输出:
--inner.getName = OutterClassName
在这个例子中,我们直接通过方法返回一个内部类,在这个内部类中使用.this来绑定它的外围类对象,输出的结果也是正确的。
总结一下,有两种方式在外部创建一个类的内部类,一种是.this
,另一种是.new
继承
在之前,我们的内部类一直是一个基类。很多时候,我们使用的内部类,需要继承一个已经存在的类,这个类中存在了一些基本的实现。既然是继承那么内部类的继承也是有选择性的,它可以继承一个普通类,也可以继承一个其他类的内部类。在继承普通类时,就像一般的类,但是如果它要继承另外一个内部类,如下:
/**
***内部类继承内部类
**/
public class innerClassDemo {
public static void main(String[] arg0){
OutterClass outter = new OutterClass();
OutterClass.InnerClass inner = outter.getInnerClass();
System.out.println("--inner.getName = "+inner.getName());
}
}
class OutterClass extends OutterClass2{
private String name = "OutterClassName";
public InnerClass getInnerClass(){
return new InnerClass();
}
class InnerClass extends InnerClass2{
public OutterClass getOutterClass(){
return OutterClass.this;
}
public String getName(){
return name;
}
}
}
class OutterClass2{
private String name = "OutterClassName2";
public InnerClass2 getInnerClass(){
return new InnerClass2();
}
class InnerClass2{
public OutterClass2 getOutterClass(){
return OutterClass2.this;
}
public String getName(){
return name;
}
}
}
输出:
--inner.getName = OutterClassName
在这里可以看到InnerClass
继承了InnerClass2
,程序也是正确的运行的,当我把InnerClass
类的外围类OutterClass
的继承关系去掉,就会提示错误,这说明,如果一个内部类要继承另外一个内部类,那么需要它的外围类也继承它要继承的内部类的外围类,即InnerClass
要继承InnerClass2
,则OutterClass
要继承OutterClass2
。
把内部类继承内部类说完之后,再看一下,外部类继承包围类的一个内部类,例如:
/**
***外部类继承内部类
**/
public class innerClassDemo {
public static void main(String[] arg0){
Unrelate unrelate = new Unrelate(new OutterClass());
System.out.println("unrelateName = "+unrelate.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
public InnerClass getInnerClass(){
return new InnerClass();
}
class InnerClass{
public OutterClass getOutterClass(){
return OutterClass.this;
}
public String getName(){
return name;
}
}
}
class Unrelate extends OutterClass.InnerClass{
Unrelate(OutterClass outter){
outter.super();
}
}
输出:
unrelateName = OutterClassName
在这里,如果直接class Unrelate extends InnerClass
会报错,因为它的超类InnerClass
是一个内部类,需要关联一个外围类,所以正确的写法是class Unrelate extends OutterClass.InnerClass
,尽管Unrelate
类不是一个内部类,也不是一个外围类,但是还是需要给它传入一个外围类对象绑定。而Unrelate
对象的使用和其他普通类并没有什么不同。
作用字段,继承和隐藏
在这里整体的分析一下内部类的作用情况。
首先,内部类获取了它外围类的引用,所以外围类的所有字段和方法都是可以使用的,术语叫作 在作用字段内。但是,内部类自己也存在的字段和方法,如果内部类的字段和方法和外围类的字段方法名一样,会不会造成冲突?代码如下:
/**
***内部类作用字段
**/
public class innerClassDemo {
public static void main(String[] arg0){
OutterClass outter = new OutterClass();
OutterClass.InnerClass inner = outter.new InnerClass();
System.out.println("--getName = "+inner.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
public InnerClass getInnerClass(){
return new InnerClass();
}
public String getName(){
System.out.println("--OutterClass-getName-");
return name;
}
class InnerClass{
private String name = "InnerClassName";
public OutterClass getOutterClass(){
return OutterClass.this;
}
public String getName(){
System.out.println("--InnerClass-getName-");
return name;
}
}
}
输出:
--InnerClass-getName-
--getName = InnerClassName
这里可以看到,输出的结果和之前在InnerClass
中没有name
字段时不一样,inner
使用了它自己的字段name
,而属于外围类的字段name
和方法getName()
被隐藏了,其实隐藏外围类的情况共有两种:
1.内部类有自己的字段和方法。
2.内部类的父类有字段和方法。
在这两种情况下,任意的简单名用法,都会直接引用内部类成员。因为会出现隐藏的问题,所以在内部类内使用外围类的字段和方法的时候,建议使用.this
来限定,例如:OutterClass.this.name
。
把一般的内部类说完之后,我们看一下几种特别的内部类:
静态内部类
定义:把一个静态类定义在另一个类定义中,就是静态内部类(嵌套类),和普通的内部类相比,定义时使用了static
关键字。
还记得之前,普通的内部类对象会在内部捕获并保存它外围类对象的引用,但是,静态内部类不是这样的。静态内部类本身就是外围类的一个成员,而不是一个独立的对象存在的。因为没有保存它外围类对象,所以静态内部类的创建不依赖于外围类的引用,也没有自然获取的外围类各种字段方法的权限。 例如:
/**
***静态内部类
**/
public class innerClassDemo {
public static void main(String[] arg0){
OutterClass.InnerClass2 inner = new OutterClass.InnerClass2();
System.out.println("--getName = "+inner.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
public String getName(){
System.out.println("--OutterClass-getName-");
return name;
}
static class InnerClass2{
private String name = "InnerClassName2";
public String getName(){
System.out.println("--InnerClass2-getName-");
return name;
}
}
}
为InnerClass
类添加static
关键字,使得它变成一个静态内部类,那么使用OutterClass.this.name
的时候会提示错误,说明无法访问外围类的非静态字段
局部内部类
定义:定义在外围类的内部代码块中,它不时外围类的一部分,但是能访问当前代码块内的变量和所有的外围类的成员,作用的范围类似于局部变量只在当前代码块内部,因为外部没有任何引用它的路径。
局部内部类可以访问定义的该类作用字段中的所有变量,包括代码块中的局部变量,外围类的所有成员和局部内部类的所有字段,但是代码块中的局部变量或方法参数只能声明成final才能被访问。这是多线程的问题访问保证安全性的措施,而且这样的好处是值是确定的。在局部内部类中,也会存在普通内部类存在的隐藏字段方法等问题,如果隐藏的是代码块的局部变量,那么久没有办法来访问这个被隐藏的变量了。
匿名内部类
定义:扩展了某个类或实现了某个接口的的匿名类。
匿名内部类没有显示声明的构造器,但是可以使用初始代码块来初始化。虽然匿名类使用起来很简单,但是会降低代码的可读性。
接口中的嵌套
虽然可以使用,但是目前来说,接口作为一个行为协议,尽量不要在内部书写除协议本身以外的东西。
接口中嵌套类或者接口,本质上和类中嵌套类一样,只是把一个与接口联系紧密的类型关联到这个接口的内部。例如:
interface OutterInterface{
class InnerClass{}
interface InnerInterface{}
InnerClass getInnerClass();
InnerInterface getInnerInterface();
}
这样,内部的接口或者内部的类就和外围接口紧密的绑定在一起。注:任何在接口中定义的类都是public
和static
的
类中嵌套接口
其实这个是很少很少用,目前来说,我还没有见过,但是说明一下。它们在类中起到的作用仅仅只是组织相关类型的机制。由于接口没有实现,所以它不能是非静态的,默认的static
关键字省略。
内部类标识符
一般来说,我们的Demo.java
文件编译后文件名为Demo.class
文件,但是如果java文件内部包含了内部类,那么文件会将内部类分出一个class文件,内部类的文件名为外围类名$内部类名
,例如Outer
实体中还有Inner
类,那么编译出来后,会存在Outer.class
文件和Outer$Inner.class
文件。那么如果是个匿名内部类呢,它本身就没有名字,这种情况下,编译器会简单的产生一个数字作为它的标识符。例如Outer$1.class
。
写到这里已经把内部类给写完了,虽然后面部分只用文字描述了一下,还有一些东西没有写在上面,具体的可以再去看看研究研究,比如匿名内部类和局部内部类的区别等。
本文参考《Thinking in Java》第10章内部类,《Java程序设计语言》第5章 嵌套类和接口