读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 }

 

 

 

  

posted @ 2015-11-26 07:50  有志竟成  阅读(187)  评论(0)    收藏  举报