# 参考: Thinking in Java 4th
# Inner Class
Inner Class
内部类的定义: 在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
1. 内部类的种类
1.1 成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:


package mytest; public class InnerTest1 { String name = "OUT class name"; private String privatepara = "this is private out param"; static String staticpara = "this is static out param"; public InnerTest1(){ System.out.println("out Class1 is instanced"); } public String getname(){ return this.name; } public String getinnername(){ return this.new Inner1().name; } public class Inner1{ String name = "INNER class1 name"; public Inner1(){ System.out.println("Inner Class1 is instanced"); } public String getname(){ return this.name; } public void getoutpara(){ System.out.println(privatepara); System.out.println(staticpara); } public String getoutname(){ return InnerTest1.this.name; } public String callgetname(){ return InnerTest1.this.getname(); } } }
<Test Code>


package mytest; public class Test1 { public static void main(String[] args) { InnerTest1 test1 = new InnerTest1(); //成员内部类是依附外部类而存在的.如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。 InnerTest1.Inner1 testinner1 = test1.new Inner1(); System.out.println(test1.getname()); //在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问. System.out.println(test1.getinnername()); //成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员) testinner1.getoutpara(); /*当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。 * 如果要访问外部类的同名成员,需要以下面的形式进行访问: * 1. 外部类.this.成员变量 * 2. 外部类.this.成员方法 */ System.out.println(testinner1.getname()); System.out.println(testinner1.getoutname()); System.out.println(testinner1.callgetname()); } }
<Note>
1. 成员内部类是依附外部类而存在的.如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。
2. 在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问.
3. 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)
4. 当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。
如果要访问外部类的同名成员,需要以下面的形式进行访问:
1. 外部类.this.成员变量
2. 外部类.this.成员方法
5. 内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。
如果成员内部类用private修饰,则只能在外部类的内部访问;
如果用public修饰,则任何地方都能访问;
如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;
如果是默认访问权限,则只能在同一个包下访问。普通类只能被public和包访问两种权限修饰。
Q: 我们现在知道一个内部类可以访问封装类的成员。这是如何实现的呢?
A: 内部类必须拥有对封装类的特定对象的一个引用,而封装类的作用就是创建这个内部类。随后,当我们引用封装类的一个成员时,就利用那个(隐藏)的引用来选择那个成员。
1.2 方法和作用域中的内部类
若我们在一个方法甚至一个任意的作用域内创建内部类。有两方面的原因促使我们这样做:
(1) 我们准备实现某种形式的接口,使自己能创建和返回一个句柄。 // 内部类实现抽象接口
(2) 要解决一个复杂的问题,并希望创建一个类,用来辅助自己的程序方案。同时不愿意把它公开。
------ from Thinking in Java 4th
1.2.1 局部内部类


package mytest; interface Destination { String readLabel(); } public class InnerTest2 { //一个方法的作用域中创建一个完整的类 public Destination dest(String s) { class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } return new PDestination(s); } // 在任意作用域内嵌套一个内部类 private void internalTracking(boolean b) { if(b) { class TrackingSlip { private String id; TrackingSlip(String s) { id = s; } String getSlip() { return id; } } TrackingSlip ts = new TrackingSlip("slip"); String s = ts.getSlip(); System.out.println(s); } // Can't use it here! Out of scope: //! TrackingSlip ts = new TrackingSlip("x"); } public static void main(String[] args) { InnerTest2 p = new InnerTest2(); Destination d = p.dest("Tanzania"); System.out.println(d.readLabel()); // Console output $->Tanzania p.internalTracking(true); // Console output $->slip } }
<Note>
1. PDestination 类属于 dest()的一部分,而不是 InnerTest2 的一部分(注意可为相同目录内每个类内部的一个内部类使用类标识符 PDestination,这样做不会发生命名的冲突)。因此,PDestination 不可从 dest()的外部访问。请注意在返回语句中发生的上溯造型——除了指向基础类 Destination 的一个句柄之外,没有任何东西超出 dest()的边界之外。当然,不能由于类 PDestination 的名字置于 dest()内部,就认为在dest()返回之后 PDestination 不是一个有效的对象。
2. TrackingSlip 类嵌套于一个 if 语句的作用域内。这并不意味着类是有条件创建的——它会随同其他所有东西得到编译。然而,在定义它的那个作用域之外,它是不可使用的。
3. 若试图定义一个局部内部类,并想使用在局部内部类外部定义的一个对象,则编译器要求外部对象为 final属性。<具体见1.2.2>
1.2.2 匿名内部类-----属于局部内部类
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
};
上面的代码要表达的意思是 :创建从 Contents 衍生出来的匿名类的一个对象”。由 new 表达式返回的句柄会自动上溯造型成一个 Contents 句柄。匿名内部类的语法其实要表达的是:
class MyContents extends Contents {
private int i = 11;
public int value() { return i; }
}
return new MyContents();


package mytest; interface Contents { strvalue value(); int count(); String tostr(); } class strvalue{ String str; } public class InnerTest3 { public Contents cont(final strvalue name,final int count,final String str) { return new Contents() { private String j = str;//assign value public strvalue value() { name.str = name.str+" world";return name; } public int count() { //count++; final data can not be change return count; } public String tostr(){ //str = new String("hello world"); // The final local variable str cannot be assigned, //since it is defined in an enclosing type return str; } }; // Semicolon required in this case } public static void main(String[] args) { InnerTest3 p = new InnerTest3(); strvalue str = new strvalue(); str.str ="hello"; int count = 1; String k = new String("teststring"); System.out.println("input str >> "+str); System.out.println("input str >> "+str.str); System.out.println("input count >> "+count); Contents c = p.cont(str,count,k); System.out.println("Content.values() >> "+c.value().str); System.out.println("Content.count() >> "+c.count()); System.out.println("str >> "+str); System.out.println("str >> "+str.str); System.out.println("count >> "+count); } } // OutPut: // input str >> mytest.strvalue@2a139a55 // input str >> hello // input count >> 1 // Content.values() >> hello world // Content.count() >> 1 // str >> mytest.strvalue@2a139a55 // str >> hello world // count >> 1
<Note>
1. 若想对匿名内部类的一个对象进行某种形式的初始化,此时会出现什么情况呢?由于它是匿名的,没有名字赋给构建器,所以我们不能拥有一个构建器。然而,我们可在定义自己的字段时进行初始化。
2. 若试图定义一个匿名内部类,并想使用在匿名内部类外部定义的一个对象,则编译器要求外部对象为 final属性。这正是我们将 cont()的自变量设为 final 的原因。如果忘记这样做,就会得到一条编译期出错提示。
3. cont()方法中的参数name 是final的,但由于它是对象,所以只要其索引地址不变,内部的object类型属性的值是可以变化的。
1.3 Static 内部类
通常可以认为内部类的对象默认持有创建它的那个封装类的一个对象的句柄。然而,假如我们说一个内部类是 static 的,这种说法却是不成立的。static 内部类意味着:
(1) 为创建一个 static 内部类的对象,我们不需要一个外部类对象。
(2) 不能从 static 内部类的一个对象中访问一个外部类对象。所以Static 内部类不能使用外部类的非static成员变量或者方法。


package mytest; public class InnerTest{ protected static class PDestination { private static String version = "1.1"; private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } public static PDestination dest(String s) { return new PDestination(s); } public static void main(String[] args) { PDestination d = dest("Tanzania"); System.out.println(PDestination.version); System.out.println(d.readLabel()); } }
<Note>
1. 创建静态内部类对象的一般形式为: 外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()。
2. 创建成员内部类对象的一般形式为: 外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()。
3. 局部内部类和匿名内部类由于作用域原因不能在外部类外面创建实例对象。
2. 从内部类继承
由于内部类构建器必须同封装类对象的一个句柄联系到一起(Static静态内部类除外),所以从一个内部类(成员内部类)继承的时候:
1)成员内部类的引用方式必须为 Outter.Inner.
2)构造器中必须有指向外部类对象的引用,并通过这个引用调用super()。
<Code>


class WithInner { class Inner {} } public class InheritInner extends WithInner.Inner { //! InheritInner() {} // 不能通过编译的,一定要加上形参(外部类对象) InheritInner(WithInner wi) { wi.super(); //必须有这句调用 } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } }
3. 内部类的继承
若衍生类与基础类均有同名的成员内部类且衍生类的内部类明确继承基类的内部类,则完成继承语法。
<Code>


class Egg2 { protected class Yolk { public Yolk() { System.out.println("Egg2.Yolk()"); } public void f() { System.out.println("Egg2.Yolk.f()"); } } private Yolk y = new Yolk(); public Egg2() { System.out.println("New Egg2()"); } public void insertYolk(Yolk yy) { System.out.println("insert begin..."); y = yy; } public void g() { y.f(); } } public class BigEgg2 extends Egg2 { public class Yolk extends Egg2.Yolk { public Yolk() { System.out.println("BigEgg2.Yolk()"); } public void f() { System.out.println("BigEgg2.Yolk.f()"); } } public BigEgg2() { insertYolk(new Yolk()); } public static void main(String[] args) { Egg2 e2 = new BigEgg2(); e2.g(); } }
<OutPut>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
insert begin...
BigEgg2.Yolk.f()
若仅仅是相同名字的内部类,没有明确继承则不算继承.


private Yolk y; protected class Yolk{ public Yolk(){ System.out.println("Egg.Yolk"); } public void f() { System.out.println("Egg.Yolk.f()"); } } public Egg(){ System.out.println("new Egg"); y = new Yolk(); } public void g() { y.f(); } } public class BigEgg extends Egg{ public class Yolk{ public Yolk(){ System.out.println("BigEgg.Yolk"); } public void f() { System.out.println("BigEgg.Yolk.f()"); } } public static void main(String[] args){ new BigEgg().g();; } }
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
new Egg
Egg.Yolk
Egg.Yolk.f()