第10章 内部类
10.1 创建内部类
此标题如若修改为:定义内部类并创建,我觉得更合适一些。
定义内部类的方法:把类的定义置于外围类的里面
创建内部类的方法:
OuterClassName.InnerClassName innerClassInstance = outerClassInstance.new InnerClassName(Para list);
Sample:
1 class OuterClass { 2 public class Inner1 { 3 public Inner1(String name) { 4 this.name = name; 5 } 6 private String name; 7 } 8 9 protected class Inner2 { 10 } 11 12 class Inner3 { 13 } 14 15 private class Inner4 { 16 } 17 } 18 19 public class Test { 20 public static void main(String[] args) { 21 OuterClass outer = new OuterClass(); 22 OuterClass.Inner1 inner1 = outer.new Inner1("Inner1"); 23 OuterClass.Inner2 inner2 = outer.new Inner2(); 24 OuterClass.Inner3 inner3 = outer.new Inner3(); 25 // Of course you can NOT create a inner class not accessible. 26 //OuterClass.Inner4 inner4 = outer.new Inner4(); 27 } 28 }
10.2 链接到外部类
Java内部类的最显著的特点,这也是与C++的嵌套类的最显著的不同 -- 用书上的原话来说:”当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问外围对象的所有成员,而不需要任何特殊条件。“。内部类自动拥有对其外围类所有从成员的访问权限,包括private。
书上用了一个Iterator模式的例子。
1 interface Selector { 2 boolean end(); 3 Object current(); 4 void next(); 5 } 6 7 public class Sequence { 8 private Object[] items; 9 private int next = 0; 10 public Sequence(int size) { items = new Object[size];} 11 public void add(Object x) { 12 if (next < items.length) 13 items[next++] = x; 14 } 15 16 private class SequenceSelector implements Selector { 17 private int i = 0; 18 public boolean end() { return i == items.length; } 19 public Object current() { return items[i]; } 20 public void next() { if(i < items.length) i++; } 21 } 22 public Selector selector() { return new SequenceSelector(); } 23 24 public static void main(String[] args) { 25 Sequence sequence = new Sequence(10); 26 for(int i = 0; i < 10; ++i) 27 sequence.add(Integer.toString(i)); 28 Selector selector = sequence.selector(); 29 while(!selector.end()){ 30 System.out.println(selector.current() + ""); 31 selector.next(); 32 } 33 } 34 }
10.3 使用.this与.new
在Inner类内部如何使用外部类的对象引用呢?OuterClassName.this,也就是.this用法。目前有点无法理解.this的用途,因为对于内部类来说,它总是可以无条件的直接访问外部类的所有成员(参见上一节);对于使用内部类的客户端来说,在创建内部类的时候因为必须用外部类的对象+.new的方式生成一个内部类的instance,所以客户端已经有了外部类对象的句柄了。
答案来了,代码来自http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
1 public class ShadowTest { 2 3 public int x = 0; 4 5 class FirstLevel { 6 7 public int x = 1; 8 9 void methodInFirstLevel(int x) { 10 System.out.println("x = " + x); 11 System.out.println("this.x = " + this.x); 12 System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); 13 } 14 } 15 16 public static void main(String... args) { 17 ShadowTest st = new ShadowTest(); 18 ShadowTest.FirstLevel fl = st.new FirstLevel(); 19 fl.methodInFirstLevel(23); 20 } 21 }/* 22 x = 23 23 this.x = 1 24 ShadowTest.this.x = 0 25 */
10.4 内部类与向上转型
定义一个外部public interface(或者是抽象类,或者是某个基类),在某个类内部定义一个内部类实现该接口(抽象类,或者基类的方法),通过设置该内部类的访问控制权限(比如设置为私有,包权限,或者protected权限),使该接口(抽象类,或者基类)的实现对外不可见。从而达到了隐藏细节的目的。
例子可以参见10.2的代码。
10.5 在方法和作用域内的内部类
也就是在C++中所说的Local Class,既然说到了C++,就先来回忆一下的Local Class。
C++中Local Class的定义:一个定义与函数(方法)中的Class。它有如下的使用规则:
- 声明于Local Class定义所在Function之前的全局变量可通过域作用符(::)访问。
- Local Class定义所在Function之中声明的static变量也可被访问。
- Local Class定义所在Function之中声明的局部变量不可访问。
- Local Class不可以有static data member。
- Local Class的所有member function必须被定义在Class内部。
- Local Class定义所在的Function(有一个专业术语叫enclosing function)不可以访问Local Class中定义的私有成员。
来看一段C++的代码:
1 #include <iostream> 2 int y; 3 void g(); 4 int main() 5 { 6 g(); 7 return 0; 8 } 9 10 void g() 11 { 12 static int x = 4; 13 class local{ 14 public: 15 void put( int n) { 16 ::y=n; 17 y = x; //You could not refer y by using this.y which will result an compile error. 18 } 19 int getGlobalY() {return ::y;} 20 int getLocalY() {return y;} 21 private: 22 int y; 23 } ab; 24 ab.put(20); 25 ::std::cout << "The value assigned to global y is::" << ab.getGlobalY() << ::std::endl; 26 ::std::cout << "The value assigned to local y is::" << ab.getLocalY() << ::std::endl; 27 // you can NOT access private member of local class 28 //::std::cout << "The value assigned to local y is::" << ab.y; 29 }
10.6 匿名内部类
一个典型的匿名内部类的用法(去实现一个接口)
1 import static java.lang.System.out; 2 interface IActionListener { 3 void clicked(); 4 } 5 6 public class Button{ 7 public IActionListener getActionListener(){ 8 return new IActionListener() { 9 public void clicked(){ 10 out.println("Button(" + name + ").clicked() in anonymouse inner class"); 11 } 12 }; 13 } 14 Button(String name){ 15 this.name = name; 16 } 17 private String name = ""; 18 public static void main(String args[]){ 19 new Button("Open File").getActionListener().clicked(); 20 } 21 }/* 22 clicked() in anonymouse inner class 23 */
一个稍微迷惑人的用法是去继承另外一个类(而不是一个接口),匿名内部类因为没有名字,所以无法定义它的构造函数,但我们可以把传递给Base类参数。另外可以匿名内部类可以有自己的初始化块(但内部类不可以有静态域)。另外内部类的某个方法如果需要传递参数,那么参数必须声明为final的。
1 import static java.lang.System.out; 2 class ActionListener { 3 ActionListener(String name){ 4 System.out.println("ActionListern constructor of Widget " + name); 5 widgetName = name; 6 } 7 void clicked(final String surfix){}; 8 private String widgetName = ""; 9 } 10 11 public class Button{ 12 public ActionListener getActionListener(){ 13 return new ActionListener("Button") { 14 /*error: Illegal static declaration in inner class 15 static private String surfix = "1"; 16 static { 17 surfix = "1111"; 18 } 19 */ 20 private String prefix = "Button"; 21 { 22 prefix = "Button1"; 23 } 24 public void clicked(final String surfix){ 25 out.println("Button(" + name + ").clicked() in anonymouse inner class"); 26 out.println(prefix + surfix); 27 } 28 class Test{ /*Of course, inner class can defined in local class*/ 29 } 30 }; 31 } 32 Button(String name){ 33 this.name = name; 34 } 35 private String name = ""; 36 public static void main(String args[]){ 37 new Button("Open File").getActionListener().clicked(".btn"); 38 } 39 }/* 40 ActionListern constructor of Widget Button 41 Button(Open File).clicked() in anonymouse inner class 42 Button1.btn 43 */
10.7 嵌套类
前面所设计到的内部类看上去与C++的InnerClass有很大的不同,最大的区别在于,C++的InnerClass更像一种Namespace的嵌套,InnerClass与Enclosing Class的实例之间没有任何关系,但Java的内部类因为由Enclosing Class的Object.new创建,自然拥有了对Enclosing Object的一切访问权限。
那么在Java中有没有与C++的InnerClass同等概念的InnerClass呢?有,那就是定义为static的嵌套类。
接口的内部类自动是public static;多层嵌套内部类访问外部类的成员是透明的。
10.8 为什么需要内部类
多了一种实现多重继承的选择。比如有两个Interface A和B,要在同一个类中实现这两个Interface,既可以选择多重继承,也可以选择单继承+内部类的方式,下面就来看一看这两种方式的实现方法。
1 import static java.lang.System.out; 2 interface A {} 3 interface B { 4 void f(); 5 } 6 7 class X implements A, B { 8 public void f() {out.println("B.f() in X");} 9 } 10 11 class Y implements A { 12 B getB() { 13 return new B() { 14 public void f() {out.println("B.f() in Y");} 15 }; 16 } 17 } 18 19 public class Test { 20 static void takesA(A a){} 21 static void takesB(B b){b.f();} 22 public static void main(String args[]) { 23 X x = new X(); 24 Y y = new Y(); 25 takesA(x); 26 takesA(y); 27 takesB(x); 28 takesB(y.getB()); 29 } 30 }/* Output: 31 B.f() in X 32 B.f() in Y 33 */
显然class Y的方式具有更大的灵活性,原因有若干个:
- class Y不一定非得和interface B保持一种is-a的关系。
- 如果上面的例子中A与B不是interface而都是abstract class或者都是class那么用class X的方式就做不到了,而使用内部类的class Y的方式则不受限制。
- 与上一条中的原因相同,如果需要实现的两个接口中含有同样签名的方法,那么用简单的继承(implements)的方式显然无法办到,这时候用内部类是一个比较好的解决方法。
- 使用内部类可以实现同一接口在同一个对象上展现不同的行为,关于这一点我们来看一下如下的代码片段。
1 import static java.lang.System.out; 2 interface B { 3 void f(); 4 } 5 class Y { 6 B getB1() { 7 return new B() { 8 public void f() {out.println("B1.f() in Y");} 9 }; 10 } 11 B getB2() { 12 return new B() { 13 public void f() {out.println("B2.f() in Y");} 14 }; 15 } 16 } 17 public class Test { 18 static void takesB(B b){b.f();} 19 public static void main(String args[]) { 20 Y y = new Y(); 21 takesB(y.getB1()); 22 takesB(y.getB2()); 23 } 24 }/* Output: 25 B1.f() in Y 26 B2.f() in Y 27 */
一个Class Y就可以实现对同一个Interface B的f()方法产生两种完全不同的行为,这为多态增添了更多的意义。