读Java编程思想随笔の内部类
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。然而必须要了解,内部类与组合是完全不同的两种概念。
创建一个内部类
1 public class Parcel1 { 2 class Contents{ 3 private int i = 11; 4 public int value(){ 5 return i; 6 } 7 } 8 class Destination{ 9 private String label; 10 Destination(String whereTo){ 11 label = whereTo; 12 } 13 String readLabel(){return label;} 14 } 15 public void ship(String dest){ 16 Contents c = new Contents(); 17 Destination d = new Destination(dest); 18 System.out.println(d.readLabel()); 19 } 20 public static void main(String[] args) { 21 Parcel1 p = new Parcel1(); 22 p.ship("Tasmania"); 23 } 24 }
链接到外部类
到目前为止,内部类似乎还是一种名字隐藏和组织代码的模式。这些很有用,但还不是最引人注目的,它还有其他用途。当生成一个内部类的对象时,此对象与制造它的外围对象就有了某种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类所有元素的访问权。
1 interface Selector{ 2 boolean end(); 3 Object current(); 4 void next(); 5 } 6 public class Sequence { 7 private Object [] items; 8 private int next = 0; 9 public Sequence(int size){ 10 items = new Object[size]; 11 } 12 public void add(Object o){ 13 if (next<items.length) 14 items[next++] = o; 15 } 16 private class SequenceSelector implements Selector{ 17 private int i = 0; 18 public boolean end(){ 19 return i == items.length; 20 } 21 public Object current(){ 22 return items[i]; 23 } 24 public void next(){ 25 if (i<items.length) 26 i++; 27 } 28 } 29 public Selector selector(){ 30 return new SequenceSelector(); 31 } 32 33 public static void main(String[] args) { 34 Sequence sequence = new Sequence(10); 35 for (int i = 0; i< 10;i++){ 36 sequence.add(Integer.toString(i)); 37 } 38 Selector selector = sequence.selector(); 39 while (!selector.end()){ 40 System.out.println(selector.current()+" "); 41 selector.next(); 42 } 43 } 44 }
所以内部类自动拥有其外围类所有成员的访问权。这是如何做到的?当某个外围类对象创建一个内部类对象时,此内部类对象必定会秘密的捕获那个指向外围类的对象引用。然后,在你访问外围类成员时,就是用那个引用来选择外围类成员。幸运的是,编译器会帮你处理所有细节,但你现在可以看到:内部类的对象只能在与其外围类对象关联的情况下才能被创建。构建内部类时,需要一个指向外围类对象的引用,如果编译器访问不到这个引用就会报错。
使用.this与.new
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this。这样产生的引用自动的具有正确的类型,这一点在编译器就被知晓并接受检查,因此没有任何运行时开销。
1 public class DoThis { 2 void f(){ 3 System.out.println("DoThis.f()"); 4 } 5 public class Inner{ 6 public DoThis outer(){ 7 return DoThis.this; 8 } 9 } 10 public Inner inner(){ 11 return new Inner(); 12 } 13 14 public static void main(String[] args) { 15 DoThis dt = new DoThis(); 16 DoThis.Inner dti = dt.inner(); 17 dti.outer(); 18 } 19 }
有时你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在new表达式中提供对其他外部类对象的引用,这是需要使用.new()语法,就像下面这样:
1 public class DotNew { 2 public class Inner{} 3 public static void main(String[] args) { 4 DotNew dn = new DotNew(); 5 DotNew.Inner dni = dn.new Inner(); 6 } 7 }
要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字DotNew,而是必须使用外部类的对象来创建该内部类的对象,就像在上面的程序中看到的那样。这也解决了内部类名字作用域的问题。因此你不必声明dn.new DotNew.Inner()。
在拥有其外部类对象之前是不可能创建其内部类对象的。这是因为内部类会暗暗的连接到创建它的外部类对象上的。但是,如果你创建的嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
1 public class Parcel3 { 2 class Content{ 3 private int i =11; 4 public int value(){ 5 return i; 6 } 7 } 8 class Destination{ 9 private String label; 10 Destination(String whereTo){ 11 label = whereTo; 12 } 13 String readLabel(){ 14 return label; 15 } 16 } 17 public static void main(String[] args) { 18 Parcel3 p = new Parcel3(); 19 Parcel3.Content c = p.new Content(); 20 Parcel3.Destination d = p.new Destination("Tasmania"); 21 } 22 }
当将某个内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的)这是因为此内部类----某个接口的实现----能够完全不可见,并且不可用。所以,得到的是指向基类或接口的引用,所以能够很方便的隐藏其实现细节。
1 public interface Contents { 2 int value(); 3 }
1 public interface Destination { 2 String readLabel(); 3 }
1 public class TestParcel { 2 public static void main(String[] args) { 3 Parcel4 p = new Parcel4(); 4 Contents c = p.contents(); 5 Destination d = p.destination("Tasmania"); 6 7 } 8 } 9 class Parcel4{ 10 private class PContents implements Contents{ 11 private int i = 11; 12 public int value(){ 13 return i; 14 } 15 } 16 protected class PDestination implements Destination{ 17 private String label; 18 private PDestination(String whereTo){ 19 label = whereTo; 20 } 21 public String readLabel(){ 22 return label; 23 } 24 } 25 public Destination destination(String s){ 26 return new PDestination(s); 27 } 28 public Contents contents(){ 29 return new PContents(); 30 } 31 }
private内部类给类的设计者提供了一种途径,通过这种方式可以完全地阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度来看,由于不能访问任何新增加的,原本不属于公共接口的方法,所以扩展接口是没有价值的。这也是给Java编译器提供了生成更高效代码的机会。
在方法和作用域内的内部内
在方法的作用域内创建一个完整的类
1 public class Parcel5 { 2 public Destination destination(String s){ 3 class PDestionation implements Destination{ 4 private String label; 5 private PDestionation(String whereTo){ 6 label = whereTo; 7 } 8 public String readLabel(){return label;} 9 } 10 return new PDestionation(s); 11 } 12 13 public static void main(String[] args) { 14 Parcel5 p = new Parcel5(); 15 Destination d = p.destination("Tasmania"); 16 } 17 18 }
值得注意的是,在上述示例中,destination()中定义了内部类PDestination,并不意味着一旦dest()方法执行完毕PDestination就不可用了。
在任意的作用域内嵌套一个内部类
1 public class Parcel6 { 2 private void internalTracking(boolean b){ 3 if (b){ 4 class TrackingSlip{ 5 private String id; 6 TrackingSlip(String s){ 7 this.id = s; 8 } 9 String getSlip(){ 10 return id; 11 } 12 } 13 TrackingSlip ts = new TrackingSlip(""); 14 ts.getSlip(); 15 } 16 //在定义TrackingSlip之外会编译错误 17 //TrackingSlip ts = new TrackingSlip(""); 18 //ts.getSlip(); 19 } 20 public void track(){ 21 internalTracking(true); 22 } 23 public static void main(String[] args) { 24 Parcel6 p = new Parcel6(); 25 p.track(); 26 } 27 }
匿名内部类
1 public class Parcel7 { 2 public Contents contents(){ 3 return new Contents(){ 4 private int i = 11; 5 public int value(){return i;} 6 };//semicolon requeried in this case 7 } 8 public static void main(String[] args) { 9 Parcel7 p = new Parcel7(); 10 Contents c = p.contents(); 11 } 12 }
创建一个继承自Contents的 匿名类的对象。通过new表达式返回的引用被自动向上转型为对Contents的引用。
上述匿名内部类的语法是下述形式的简化形式
1 public class Parcel7b { 2 class MyContents implements Contents{ 3 private int i = 11; 4 public int value(){ 5 return i; 6 } 7 } 8 public Contents contents(){return new MyContents();} 9 10 public static void main(String[] args) { 11 Parcel7b p = new Parcel7b(); 12 p.contents(); 13 } 14 }
在这个匿名内部类中,使用了默认构造器来生成Contents。下面代码展示的是,如果你的基类需要一个有参数的构造器,怎么办?
1 public class Parcel8 { 2 public Wrapping wrapping(int x){ 3 return new Wrapping(x){//pass constructor argument 4 public int value(){ 5 return super.value()*47; 6 } 7 }; 8 } 9 }
如果定义了一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用时final的,就像你在destination()参数中看到的那样。如果你忘记了,将会得到一个编译时错误消息。
1 public class Parcel9 { 2 //argument must be final to use inside 3 //anonymous inner class; 4 public Destination destination(final String dest){ 5 return new Destination(){ 6 private String label = dest; 7 public String readLabel(){return label;} 8 }; 9 } 10 11 public static void main(String[] args) { 12 Parcel9 p = new Parcel9(); 13 Destination d = p.destination("Tasmania"); 14 } 15 }
如果只是简单的给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似构造器的行为,该怎么办?在匿名类中不可能有匿名构造器,但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果。
1 public class AnonymouseConstructor { 2 public static Base getBase(int i){ 3 return new Base(i){ 4 { 5 System.out.println("inside instance initlizer"); 6 } 7 public void f(){ 8 System.out.println("in Anonymouse f()"); 9 } 10 }; 11 } 12 13 public static void main(String[] args) { 14 Base base = getBase(47); 15 base.f(); 16 } 17 } 18 abstract class Base{ 19 public Base(int i){ 20 System.out.println("Base Constructor.i="+i); 21 } 22 public abstract void f(); 23 }
在实例初始化操作内部,可以看到有一段代码,它们不能作为字段初始化动作的一部分来执行。所以对于匿名类而言,实例初始化的实际效果就是构造器。当然,它受到了限制----你不能重载实例初始化方法,所以你仅有一个这样的构造器。
匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而如果是实现接口,也只能实现一个接口。
1 public class Parcel10 { 2 //argument must be final to use inside 3 //anonymous inner class; 4 public Destination destination(final String dest,final float price){ 5 return new Destination(){ 6 private int cost; 7 { 8 cost = Math.round(price); 9 if (cost>100){ 10 System.out.println("over budget"); 11 } 12 } 13 private String label = dest; 14 public String readLabel(){return label;} 15 }; 16 } 17 18 public static void main(String[] args) { 19 Parcel10 p = new Parcel10(); 20 Destination d = p.destination("Tasmania",101.395F); 21 } 22 }
嵌套类
如果不需要内部类对象与其外围类对象有联系,那么可以将内部类声明为static。这通常称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式的保存了一个引用,指向创建它的外围类对象。然而内部类时static时,就不是这样了。嵌套类意味着:
1、要创建嵌套类的对象,并不需要其外围类对象;
2、不能从嵌套类的对象中访问非静态的外围类对象
嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能包含static数据和static字段,也不能有嵌套类。但是嵌套类可以包含有这些东西。
1 public class Parcel11 { 2 private static class ParcelContents implements Contents{ 3 private int i = 11; 4 @Override 5 public int value() { 6 return i; 7 } 8 } 9 protected static class ParcelDestination implements Destination{ 10 private String label; 11 private ParcelDestination(String whereTo){ 12 this.label = whereTo; 13 } 14 @Override 15 public String readLabel() { 16 return label; 17 } 18 public static void f(){ 19 20 } 21 static int x = 10; 22 static class AnotherLevel{ 23 public static void f(){ 24 25 } 26 static int x = 10; 27 } 28 } 29 public static Destination destination(String s){ 30 return new ParcelDestination(s); 31 } 32 public static Contents contents(){ 33 return new ParcelContents(); 34 } 35 36 public static void main(String[] args) { 37 Contents pc = contents(); 38 Destination pd = destination(""); 39 } 40 }
接口内部的类
正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分,你放在接口中任何类都自动转为public和static,因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则,你甚至可以在内部类中实现其外围的接口。
1 public interface ClassInInterface { 2 void showhy(); 3 class Test implements ClassInInterface{ 4 @Override 5 public void showhy() { 6 System.out.println("showhy"); 7 } 8 public static void main(String[] args) { 9 new Test().showhy(); 10 } 11 } 12 }
如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便。
从多层嵌套类中访问外部类的成员
一个内部类被嵌套多少层并不重要,它能透明的访问所有它所嵌入的外围类的所有成员。
1 public class MultiNestingAccess { 2 public static void main(String[] args) { 3 MNA mna = new MNA(); 4 MNA.A mnaa = mna.new A(); 5 MNA.A.B mnaab = mnaa.new B(); 6 mnaab.h(); 7 } 8 } 9 class MNA{ 10 private void f(){} 11 class A{ 12 private void g(){} 13 public class B{ 14 void h(){ 15 g(); 16 f(); 17 } 18 } 19 } 20 }
为什么需要内部类
一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。
内部类必须回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:"如果这能满足需求,那就应该这么做"。那么内部类实现这个接口与外围类实现这个接口有什么区别?答案是:后者不能总能享用接口所带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因的是:
每个内部类都能独立地继承自一个实现,所以无论外围类是否已经继承了某个实现,对于内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计和编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了多重继承。也就是说,内部类允许继承多个非接口类型。
为了看到更多的细节,让我们考虑这样一种情形:即必须在一个类中以某种方式实现两个接口。由于接口的灵活性,你有两种选择:使用单一类,或者使用内部类:
1 public class MultiInterface { 2 static void takeA(A a){} 3 static void takeB(B b){} 4 public static void main(String[] args) { 5 X x = new X(); 6 Y y = new Y(); 7 takeA(x); 8 takeA(y); 9 takeB(x); 10 takeB(y.makeB()); 11 12 } 13 } 14 interface A{} 15 interface B{} 16 class X implements A,B{} 17 class Y implements A{ 18 B makeB(){ 19 return new B(){}; 20 } 21 }
如果没有任何其他限制,从实现的观点来看,上面的示例并没有什么区别,它们都能正常运作。
如果拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承。
1 public class MultiImplemention { 2 static void tasksD(D d){} 3 static void tasksE(E e){} 4 public static void main(String[] args) { 5 Z z = new Z(); 6 tasksD(z); 7 tasksE(z.makeE()); 8 } 9 } 10 class D{} 11 abstract class E{} 12 class Z extends D{ 13 E makeE(){return new E(){};} 14 }