java面向对象--内部类

将一个类定义在另一个类里面,里面的那个类称为内部类,与属性、方法等一样视作外部类的成员。内部类提供了更好的封装,不允许同包中的其他类访问该内部类。

内部类作为外部类的成员,同样可以被4个访问限定符修饰。如果外部类需要访问非静态内部类的成员,必须创建非静态内部类对象来访问。

内部类成员可以直接访问外部类的数据。

如果存在一个非静态内部类对象,则一定存在一个被它寄存的外部类对象,也就是说在拥有外部类对象之前是不可能创建内部类对象的。但外部类对象存在时,非静态内部类对象不一定存在。

非静态内部类里不能有静态方法/静态属性/静态初始化块,也不能有嵌套类(static innerClass)。

在外部类的非静态方法内,可以直接通过new创建内部类对象,而且可以省略OuterClassName。在外部类的非静态方法之外引用内部类的对象时,需要具体指明这个对象的类型OuterClassName.InnerClassName varName;而且创建时,需要由外部类对象调用内部类的构造器OuterInstance.new InnerConstructor()。

非静态内部类对象里对外部类对象的引用:OuterClassName.this,this指非静态内部类对象本身。 

public class Outer {
   private String prop = "outerPro";
   public class Inner {
      private String prop = "innerPro";
      Inner() {
         System.out.println(prop);
         System.out.println(Outer.this.prop); // 内部类中对外部类对象的引用:OuterClassName.this
      }
   }

   public Inner getInner() {
      return new Inner(); // 外部类的非静态方法内可以直接new创建内部对象
   }

   public static void main(String[] args) {
      Outer outerObject = new Outer();
      Outer.Inner innerObject2 = outerObject.new Inner();// 外部类非静态方法中创建内部类必须通过instanceOfOuterClass.new创建
      // Inner innerObject = outerObject.getInner();
   }
} 

内部类自动拥有对其外部类所有成员的访问权,包括private。

interface Selector {
  boolean end();
  Object current();
  void next();
}

public class Sequence {
  private Object[] items;
  private int next = 0;
  public Sequence(int size) {
    o = new Object[size];
  }
  public void add(Object x) {
    if(next < o.length) {
      o[next] = x;
      next++;
    }
  }
  private class SequenceSelector implements Selector {
    int i = 0;
    public boolean end() {     //内部类可以直接访问外部类成员
      return i == items.length;
    }
    public Object current() {
      return items [i];
    }
    public void next() {
      if(i < items.length) i++;
    }
  }
  public Selector getSelector() {
    return new SequenceSelector ();
  }

public static void main(String[] args) {
    Sequence s = new Sequence(10);
    for(int i = 0; i < 10; i++)
        s.add(Integer.toString(i));
    Selector sl = s.getSelector();   
    while(!sl.end()) {
      System.out.println((String)sl.current());
      sl.next();
    }
  }
}
View Code

在方法和作用域内的内部类

作用:

创建不是公用的类来辅助解决复杂问题;创建并返回实现了某类型接口的对象的引用。

定义在方法中:

public class Parcel5 {
   public Destionation destionation(String str) {
      class PDestionation implements Destionation {  //方法里的内部类实现某个接口
         private String label;
         private PDestionation(String whereTo) {
            label = whereTo;
         }
         public String readLabel() {
            return label;
         }
      }
      return new PDestionation(str); //返回实现某个接口对象的引用
   }

   public static void main(String[] args) {
      Parcel5 parcel5 = new Parcel5();
      Destionation d = parcel5.destionation("chenssy"); //向上造型
   }
}
View Code

 

定义在作用域中:

public class Parcel6 {
   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("chenssy");
         String string = ts.getSlip();
      }
   }
   public void track() {
      internalTracking(true);
   }
 
   public static void main(String[] args) {
      Parcel6 parcel6 = new Parcel6();
      parcel6.track();
   }
}
View Code

匿名内部类

适合创建那种只需要使用一次的类。匿名内部类必须且只能继承一个父类或实现一个接口,语法如下,表示创建一个继承自某父类或实现某接口的匿名类对象。

  new 父类构造器(实参列表) | 接口() {

    //匿名内部类的实体部分

  };

创建匿名内部类时会立即创建一个该匿名类的实例,所以匿名内部类不能是抽象类。

匿名内部类因为没有类名,所以不能定义构造器,但可以定义实例初始化块进行初始化。

如果在匿名内部类里要使用一个在其外部定义的变量,编译器要求其参数引用是final的。

public class Parcel10 {
    public Destination destination(final String dest, final float price) {//参数引用必须是final的
        return new Destination() {
            private int cost;
            // 只能通过初始化块对匿名内部类初始化
            {
                cost = Math.round(price);
                if (cost > 100)
                    System.out.println("Over budget!");
            }
            private String label = dest; 
            public String readLabel() {
                return label;
            }
        };
    }
    public static void main(String[] args) {
        Parcel10 p = new Parcel10();
        Destination d = p.destination("Tasmania", 101.395F);
    }
}

工厂方法:改进接口中的工厂方法

interface Service {
    void method1();
    void method2();
}

interface ServiceFactory {
    Service getService();
}

class Implementation1 implements Service {
    private Implementation1() {
    }
    public void method1() {
        System.out.println("Implementation1 method1");
    }
    public void method2() {
        System.out.println("Implementation1 method2");
    }
    public static ServiceFactory factory = new ServiceFactory() {
        public Service getService() {
            return new Implementation1();
        }
    };
}

class Implementation2 implements Service {
    private Implementation2() {
    }
    public void method1() {
        System.out.println("Implementation2 method1");
    }
    public void method2() {
        System.out.println("Implementation2 method2");
    }
    public static ServiceFactory factory = new ServiceFactory() {//通过匿名内部类生成一个factory对象
        public Service getService() {
            return new Implementation2();
        }
    };
}

public class Factories {
    public static void serviceConsumer(ServiceFactory fact) {
        Service s = fact.getService();
        s.method1();
        s.method2();
    }

    public static void main(String[] args) {
        serviceConsumer(Implementation1.factory);
        // Implementations are completely interchangeable:
        serviceConsumer(Implementation2.factory);
    }
}
View Code

静态内部类:被static修饰的内部类。

static关键字把类的成员变成类相关,不再是实例相关。外部类不能被static限定,因为外部类上一级是包不是类。

静态内部类的对象不是寄存在外部类对象中,而是寄存在外部类的类本身中。静态内部类对象只有外部类的类引用,没有外部类对象的引用。也就是创建静态内部类的对象时,不需要外部类对象,直接通过外部类调用内部类的构造器new OuterClass.InnerContructor()。

因为没有外部类对象的引用,所以不能从静态内部类对象的方法中访问非静态的外部类对象的实例属性,类似于static方法。

普通内部类因为需要有外部类对象,会和编译器加载静态资源的顺序冲突,所以不能有static数据,不能包含静态内部类。但是静态内部类可以包含静态成员,也可以包含非静态成员。外部类不能直接访问静态内部类的成员,需要使用静态内部类的类名访问内部类的类成员,静态内部类的对象访问内部类的实例成员。

接口中的内部类被public和static修饰,只能是静态内部类。 

为什么需要内部类?

内部类使得多重继承的解决方案更完美。如果继承的是多个类(抽象或具体)而不是接口,则只能使用内部类实现多重继承。

class D {
}
class E {
}
class Z extends D {
   E makeE() {
      return new E() {
      };
   }
}

public class MultiImplementation {
   static void takesD(D d) {
   }
   static void takesE(E e) {
   }
   public static void main(String[] args) {
      Z z = new Z();
      takesD(z);
      takesE(z.makeE());
   }
} 

闭包和回调

闭包(Closure)是一种能被调用的对象,它保存了创建它的作用域的信息。Java并无闭包规范,而是通过“接口+内部类”来实现闭包的功能。因为对于非静态内部类而言,它不仅记录了其外部类的详细信息,还保留了一个创建非静态内部类对象的引用,通过这个引用可以访问外部类的所有成员,所以可以把非静态内部类对象当成面向对象领域的闭包。

某个方法一旦获得了内部类对象的引用后,就可以在需要时调用外部类实例的方法。

例如Teachable接口和Programmer父类,都有work方法,但程序员和教师的work功能不一样,如何实现一个类同时具备不同功能的work()方法:

interface Teachable {
   void work();
}

class Programmer {
   private String name;
   public Programmer(String name) {
      this.name = name;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }

   public void work() {
      System.out.println(name + "正在敲代码...");
   }
}

 
class ComputerTeacher extends Programmer {
   public ComputerTeacher(String name) {
      super(name);
   }
   //程序员工作使用父类Programmer实现的work()方法
   //教学工作仍定义在ComputerTeacher中,但是与Teachable接口没有关系,而且是private的,本类之外不能访问
   private void teach() {
      System.out.println(getName() + "正在讲课...");
   }
   //非静态内部类实现Teachable的work方法,作用仅仅是向客户类提供一个调用外部类方法的途径
   private class Closure implements Teachable {//实现了Teachable接口,可以当作Teachable使用
      public void work() {
         teach();
      }
   }
   // 返回一个非静态内部类的引用,允许外部类通过该引用来回调外部类的方法
   public Teachable getCallbackReference() {
      return new Closure();
   }
} 

public class TestClosure {
   public static void main(String[] args) {
      ComputerTeacher ct = new ComputerTeacher("程序猿");
      // 直接调用ComputerTeacher从Programmer类继承下来的work方法
      ct.work();
      // 表明上看是调用的Closure的work方法,实际上是通过work方法调用ComputerTeacher的teach方法
      ct.getCallbackReference().work();
   }
}

内部类标识符

每个类都会生成一个.class文件,包含了如何创建这个类型的对象有关的所有信息(此信息产生了一个名为Class对象的“meta-class”)。内部类也如此,内部类class文件命名规则:

  OuterClassName1&OuterClassName2&InnerClassName.class

如果内部类是匿名的,那么编译器会简单地生成数字,把它们作为内部类标识符使用。

posted @ 2017-04-10 21:28  开发之路  阅读(335)  评论(0编辑  收藏  举报