第十章:内部类 《Thinking in java》学习笔记
简单的说,内部类就是将一个类的定义放到另一个类的定义内部。
内部类分为:成员内部类、局部内部类、静态内部类、匿名内部类。
a,成员内部类
作为外部类的一个成员存在,与外部类的属性、方法并列。
优点:
1、内部类作为外部类的成员,可以访问外部类的私有成员或属性。(即使声明为private,但是对于处于其内部的内部类还是可见的。)
2、可以内部类定义在外部类不可访问的属性。这样就在外部类中实现了比外部类private还要小的额访问权限。
注意:
1、内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两个类。
对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。编译完成后出现Outer.class 和 Outer$Inner.class 两个类
2、当Outer是一个private类时,外部类对于其外部访问是私有的,所以就无法建立外部类对象,进而也无法建立内部类对象。
b,局部内部类
在方法中第一的内部类称为局部内部类。
与局部变量类似,在局部内部类前不加修饰符public和private,其范围为定义它的代码块
注意:
1、在类外不可直接生产局部内部类(保证局部内部类对外是不可见的)。
2、要想使用局部内部类时需要生产对象,对象调用方法,在方法中才能调用局部内部类。
3、通过内部类和接口达到一个强制的弱耦合,用局部内部类来实现接口,并在方法中返回接口类型,使局部内部类不可见,屏蔽实现类的可见性。
c,静态内部类
静态内部类可以使用public,protected,private修饰
静态内部类中可以定义静态和非静态的成员
注意:
一个静态内部类不需要一个外部类的成员:只是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成
这实际上静态内部类成为了一个顶级类。
静态内部类不可用private来进行定义。
当类与接口(或者是接口与接口)发生方法命名冲突的时候,此时必须使用内部类来实现。用接口不能完全地实现多继承,用接口配合内部类才能实现真正的多继承。
d,匿名内部类
匿名内部类就是没有名字的内部类。
注意:
1、匿名内部类不能有构造函数
2、匿名内部类不能定义任何静态成员、方法和类
3、匿名内部类不能是public、protected、private、static
4、只能创建匿名内部类的一个实例
5、一个匿名内部类用其隐含实现一个接口或实现一个类。
6、因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效
说了一通概念,下面用例子来实现具体的细节;
通过一个小例子说明成员内部类的基本用法:
1 package unit10.innerclassers;
2
3 /**
4 * 内部类的展示举例
5 * @author coffee
6 */
7 public class Example1 {
8 private int value;
9
10 public void setValue(int num) {
11 value = num;
12 }
13
14 public int getValue() {
15 return value;
16 }
17
18 public class InnerclassOfExample1 {
19 private int value2;
20
21 public void setValue2(int value) {
22 this.value2 = value;
23 }
24
25 public int getValue2() {
26 return value;
27 }
28
29 public void setValue(int num) {
30 Example1.this.value = num;// 可以直接访问外围类的所有成员和方法
31 }
32
33 public int getValue() {
34 //或者用Example1.this.getValue();来直接访问外围类的所有方法
35 return Example1.this.value;
36 }
37 }
38
39 public static void main(String[] args) {
40 Example1 e = new Example1();
41 System.out.println("外围类自己设置自己的成员变量value为" + 10);
42 e.setValue(10);
43 System.out.println("设置后,外围类的成员变量value为" + e.getValue());
44 Example1.InnerclassOfExample1 i = e.new InnerclassOfExample1();// 实例化了一个内部类
45 System.out.println("内部类自己设置自己的成员变量value2为" + 20);
46 e.setValue(20);
47 System.out.println("设置后,内部类的成员变量value2为" + e.getValue());
48 i.getValue();
49
50 //通过内部类来操作外围类
51 System.out.println("内部类设置外围类的成员变量value为" + 26);
52 i.setValue(26);
53 System.out.println("设置后,内部类设置后外围类的成员变量value为" + i.getValue());
54 }
55 }
运行后的结果是:
外围类自己设置自己的成员变量value为10
设置后,外围类的成员变量value为10
内部类自己设置自己的成员变量value2为20
设置后,内部类的成员变量value2为20
内部类设置外围类的成员变量value为26
设置后,内部类设置后外围类的成员变量value为26
而在经过javac编译后,生成了Example1.class和Example1$InnerclassOfExample1.class这样的两个字节码文件;
通过这个例子,看出一般的内部类(即非静态的的内部类)的作用可以概括为:代码隐藏+元素访问权;内部类可以访问外围类的所有成员变量和方法,注意是所有,不管是不是private的,而这与c++嵌套类的设计不同,在c++中只是单纯的名字隐藏机制,与外围对象没有联系,也没有隐含的访问权
下面介绍下刚刚所提到的静态的内部类,通常java编程中,我们只能申明一个static的变量或者方法,那么静态内部类有哪些作用?我们进行下对比就清楚了
非静态的内部类只能声明非静态成员或方法,可以使用外围类的静态成员或变量;
而静态内部类则可拥有静态的成员和方法。,但静态内部类中只能引用其外部类的静态成员或变量。
因为静态内部类并不与外围类对象关联,其他的内部类的实例化之前都只能在与其外围类相关联的情况下才能被创建,但是有一个看起来像是特例:静态方法中的匿名内部类
先说下神马是匿名内部类吧,将下面这段代码加到上面的那个例子中即可
public Example1 zm(){
return new Example1(){//这就是匿名内部类,继承自Example1,也可以继承接口,使用方法相同
public int value;
//!public static int zm;//匿名内部类不能申明static成员和方法
public void setValue(int num){
value = Example1.a;//可以访问外围类的静态变量
value = Example1.this.value;//可以访问外围类的非静态变量
}
public int getValue(){
return value;
}
};//在匿名内部类末尾的分号,并不是用来标记此内部类结束(C++中是那样)。实际上,它是表达式的结束,只不过这个表达式正巧包含了内部类罢了。因此,这与别的地方使用的分号是一致的
}
看起来是一种很奇怪的方法,zm()方法返回一个对象,这个对象是Example1的子类的实例,如果把zm()改为static的方法后,就可以直接调用Example1.zm()然后返回一个Example1的子类的实例了,下面我比较下static zm()和zm()的区别:
不加static的方法中的匿名内部类:可以使用外围类的所有的成员变量及方法;
加static的方法中的匿名内部类:只可以使用外围类的静态成员和方法了;
注意:这里的外围类不是说所继承的类,例子中的代码只是碰巧继承了外围类而已。
写到这儿,想想内部类的基础知识也回忆的差不多了,至于应用的话,原文所说的在应用程序框架里面会经常使用,还可以利用内部类实现回调,其中比较重要的有
内部类可以实现多重继承,因为内部类可以继承多个非接口类型,即类和抽象类;
正常的情况下,接口中是不可以写任何代码的,但是可以再接口中实现静态内部类,然后写上公共的代码。
总结: 比起面向对象编程中的其他的概念来,接口和内部类算的上很复杂的了;像c++就没有这些,讲两者结合起来,可以解决c++中的用多重继承所能解决的问题,然而c++的多重继承却是很难使用的,相对而言java中的接口和内部类就容易理解多了,内部类的这些特性说出来是很直观的,但是就像多态机制一样,这些特性的使用应该是设计阶段应该考虑的问题,随着经验的累积,见多识广后,自己心中就会有杆称,权衡什么时候用接口,什么时候用内部类,或者两者都用。