Java编程思想__内部类

  • 可以将一个类的定义放在另一个类的定义内部,这就是内部类。
  •  内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。
  • 内部类看起来就像是一种代码隐藏机制: 将类置于其他类的内部。

 

创建内部类

  • 创建内部类的方式就如同你想的一样__把类的定义置于外围类的里面:
public class Parce1 {
    class Contents{
        private int i=11;
        public int value(){return i;}
    }

    class Destination {
        private String label;

        public Destination(String label) {
            this.label = label;
        }

        String readLabel(){return label;}
    }

    public void ship(String dest){
        Contents contents = new Contents();
        Destination destination = new Destination(dest);
        System.out.println(destination.readLabel());
    }

    public static void main(String[] args) {
        Parce1 parce1 = new Parce1();
        parce1.ship("Tasmania");
    }
}

//运行结果

Tasmania
  • 当我们在 ship() 方法里面使用内部类的时候,与使用普通类没什么不同。
  • 在这里,实际的区别只是内部类的名字是嵌套在 Parce1 里面的。
  • 更典型的情况是.外部类将有一个方法,该方法返回一个指向内部类的引用,就像在 to() 和contents() 方法中看到的那样:
public class Parcel2 {
    class Contents{
        private int i=11;
        public int value(){
            return i;
        }
    }
    class Destintion{
        private String label;

        public Destintion(String label) {
            this.label = label;
        }
        String readLabel(){return  label;}
    }

    public Destintion to(String dest){
        return new Destintion(dest);
    }

    public Contents contents(){
        return new Contents();
    }

    public void ship(String dest){
        Contents contents=new Contents();
        Destintion description=to(dest);
        System.out.println(description.readLabel());
    }

    public static void main(String[] args) {
        Parcel2 parcel2=new Parcel2();
        parcel2.ship("Tasmania");

        Parcel2 parcel21=new Parcel2();
        //defining references to inner classes 定义内部类引用
        Contents contents = parcel21.contents();
        Destintion borneo = parcel21.to("Borneo");
    }

}
//运行结果为
Tasmania
  1. 如果想从外部类的非静态方法之外的任意位置创建某个内部类对象,那么必须像在 main() 方法中那样,具体地指明这个对象的类型: OuterClassName.InnerClassName。

 

 

链接到外部类

  • 到目前为止,内部类似乎还只是一种名字隐藏组织代码的模式。
  • 这些是很有用, 但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。
  • 此外, 内部类还拥有其外围类的素有元素访问权。

 

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

class Sequence{
    private Object[] item;
    private int next=0;

    public Sequence(int size) {
        //创建一个执行大小的 数组
        item=new Object[size];
    }

    public void add(Object object){
        if (next < item.length){
            item[next++]=object;
        }
    }

    private class SequenceSelector implements Selector{
        private int i=0;

        @Override
        public boolean end() {
            return i == item.length;
        }

        @Override
        public Object current() {
            return item[i];
        }

        @Override
        public void next() {
            if (i<item.length){
                i++;
            }
        }
    }

    public Selector selector(){
        return new SequenceSelector();
    }

    public static void main(String[] args) {
        Sequence sequence = new Sequence(5);
        for (int i = 0; i < 5; i++) {
            //自动装箱机制 把int 自动转为 object
            sequence.add(i);
        }
        Selector selector = sequence.selector();

        while (!selector.end()){
            System.out.println(selector.current()+ "  ");
            selector.next();
        }
    }
}

//运行结果

0  
1  
2  
3  
4  
  1. Sequence 类只是一个固定大小的 Object的数组,以类的形式包装了起来。可以调用 add() 在序列未增加新的 Object。
  2. 要获取  Sequence 中每一个对象, 可以使用 Selector 接口。这是 迭代器 设计模式的一个例子,在后面我们将更多地学习它。
  3. Selector 允许你检查序列是否到末尾了( end() 方法) ,访问当前对象 ( current() 方法) ,以及移到序列中的下一个对象( next() 方法)。
  4. 因为 Selector 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且另的方法能以此接口为参数,来生成更加通用的代码。
  5. 所以 内部类自动拥有对其外围类所有成员的访问权。这是如何做到的呢? 当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向外围类对象的引用。然后在你访问此外部类的成员时,就是用哪个引用来选择外围类的成员。
  6. 幸运的是,编译器会帮你处理所有的细节,但你现在可以看到: 内部类的对象只能在与其外围类的对象相关联的情况下才能被创建( 就像你应该看到的, 在内部类是非 static 类时)。构建内部类对象时, 需要一个指向其外围类对象的引用, 如果编译器访问不到这个引用就会报错。 不过大多数情况下这都无需程序员操心。

 

 

使用.this 与 .new

  • 如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点this
  • 这样产生的引用自动地具有正确的类型, 这一点在编译器就被知晓并受到检查,因此没有任何运行时开销。
public class DotThis {
    void f(){
        System.out.println("DotThis f()");
    }

    class Inner{
        DotThis outer(){
            return DotThis.this;
        }
    }

    public Inner inner(){
        return new Inner();
    }

    public static void main(String[] args) {
        DotThis dotThis = new DotThis();
        DotThis.Inner inner = dotThis.inner();
        inner.outer().f();
    }
}
  1. 有时你可能想要告知某些其他对象,去创建其某个内部类的对象。
  2. 要实现此目的,你必须在 new 表达式中提供其他外部类对象的引用,这是需要使用.new 语法

 

    public static void main(String[] args) {
        DotThis dotThis = new DotThis();

        Inner inner1 = dotThis.new Inner();
        
       // DotThis.Inner inner = dotThis.inner();
        inner1.outer().f();
    }
  1. 要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字 DotNew,而是必须使用外部类的对象来创建该内部类对象,就像在上面的程序中所看到的那样。
  2. 这也解决了内部类名字作用域的问题,因此你不必声明(实际上你不能声明) doThis.new  DoThis.Inner() 。
  3. 在拥有外部类对象之前是不可能创建内部类对象的。这就是因为内部类对象会暗暗地连接到创建它的外部类对象上。
  4. 但是,如果你创建的是 嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
public class Parcel3 {

    class Contents{
        private int i=1;
        public int getVal(){
            return i;
        }
    }

    class Destination{
        private String label;

        public Destination(String label) {
            this.label = label;
        }
        String getLabel(){
            return label;
        }
    }

    public static void main(String[] args) {
        Parcel3 parcel3 = new Parcel3();
        Contents contents = parcel3.new Contents();
        Destination tasmania = parcel3.new Destination("Tasmania");
    }
}

 

 

内部类与向上转型

  • 当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。(从实现了某个接口的对象,得到了对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的。)
  • 这是因为此内部类__某个接口的实现__能够完全不可见,并且不可用。所得到地只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。
//我们可以创建前一个示例的接口 

interface Destination {
    String readLabel();
}

 interface Contents{
    int getVal();    
}
  1. 现在Contents 和 Destination 表示客户端程序员可用的接口。(记住,接口的所有成员自动被设置为 public的。)
  2. 当取得了一个指向基类或接口的引用时,甚至可能无法找出它确切的类型,如下
 interface Destination {
    String readLabel();
}

 interface Contents{
    int getVal();
}

class Parcel4{

     private class PContents implements Contents{
        private int i=11;

         @Override
         public int getVal() {
             return i;
         }
     }
     protected class PDestination implements Destination{

         private String label;

         public PDestination(String label) {
             this.label = label;
         }

         @Override
         public String readLabel() {
             return label;
         }
     }

     public Contents contents(){
         return new PContents();
     }
     public Destination destination(String context){
         return new PDestination(context);
     }

}


class TestParcel{
    public static void main(String[] args) {
        Parcel4 parcel4 = new Parcel4();
        Contents contents = parcel4.contents();
        Destination tasmania = parcel4.destination("Tasmania");
        String readLabel = tasmania.readLabel();
        System.out.println(readLabel);
    }
}
  1. Parcel4 增加了一些新东西: 内部类PContents 是private ,所以除了 Parcel4 ,没有人能访问它。 PDestination 是protected,所以只有Parcel4 及其子类,还有与 Parcel4 同一包中的类 (因为 protected 也给予了包访问权) 能访问 PDestination ,其他类都不能访问 PDestination。
  2. private 内部类给类的设计者提供了一种途径,通过这种方式完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度来看,由于不能访问任何新增加的,原本不属于公共接口的方法,所以扩展接口是没有价值的。这也给Java编译器提供了生成更高效代码的机会。

 

 

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

  • 到目前为止,我们所看到的只是内部类典型用途。通常,如果所读,写的代码包含了内部类,那么它们都是 平凡的 内部类,简单并且容易理解。然而,内部类的语法覆盖了大量其他的更加难以理解的技术。
  • 例如,可以在一个方法里面或者任意的作用域内定义内部类。有以下两个理由:
  1. 如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。
  2. 你要解决一个复杂的问题,想要创建一个类来辅佐你的解决方案,但是又不希望这个类是公共可用的。
class Parcel5{

     public Destination destination(String contents){
         class PDestination implements Destination{

             private String label;

             public PDestination(String label) {
                 this.label = label;
             }

             @Override
             public String readLabel() {
                 return label;
             }
         }
         return new PDestination(contents);
     }

}


class TestParcel{
    public static void main(String[] args) {
        Parcel5 parcel5 = new Parcel5();
        Destination tasmania = parcel5.destination("Tasmania");
        String readLabel = tasmania.readLabel();
        System.out.println(readLabel);
    }
}
  1. PDestination 是destination() 的一部分,而不是 Parcel5 的一部分。所以,在 destination() 之外不能访问 PDestination。
  2. 注意: 出现在 return 语句中的向上转型__返回的是 Destination 的引用,它是 PDestination 的基类。
// 下面的例子展示了如何在任意的作用域内嵌入一个内部类
public class Parcel6 {

    private void internalTracking(boolean flag){
        if (flag){
            class TrackingShip{
                    private String id;

                public TrackingShip(String id) {
                    this.id = id;
                }
                public String getId(){
                    return id;
                }
            }
            TrackingShip slip = new TrackingShip("slip");
            String id = slip.getId();
        }
    }
    public void track(){
        internalTracking(true);
    }

    public static void main(String[] args) {
        Parcel6 parcel6 = new Parcel6();
        parcel6.track();
    }
}
  1. TrackingShip 类被嵌入在 if 语句的作用域内, 这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。
  2. 然而,在定义 TrackingShip 的作用域之外,它是不可用的,除此之外,它与普通类是一样的。

 

 

匿名内部类

  • 下面例子看起来有点奇怪:
interface Contents1{
    int getVal();
}

public class Parcel7 {
    public Contents1 contents1(){
        return new Contents1() {
            private int i=11;
            @Override
            public int getVal() {
                return i;
            }
        };
    }

    public static void main(String[] args) {
        Parcel7 parcel7 = new Parcel7();
        Contents1 contents1 = parcel7.contents1();
        contents1.getVal();
    }
}
  1. contents1() 方法将返回值的生成与表示这个返回值的类的定义结合在一起! 另外,这个类是匿名的,它没有名字。
  2. 更糟糕的是 ,看起来似乎是你正要创建一个 Contents1对象。然是然后(在到达语句结束的分号之前) 你却说 等一等,我想在这里插入一个类的定义。
  3. 这种奇怪的语法指的是: 创建一个继承自 Contents1 的匿名类的对象。 通过 new 表达式返回的引用被自动向上转型为 Contents1 的引用。 
//上述匿名内部类的语法是下述形式的简化形式

class Parcel7b{
    
    class MyContents1 implements Contents1{

        private int i=11;
        
        @Override
        public int getVal() {
            return i;
        }
    }
    public  Contents1 contents1(){
        return new MyContents1();
    }

    public static void main(String[] args) {
        Parcel7b parcel7b = new Parcel7b();
        Contents1 contents1 = parcel7b.contents1();
    }
}
  1. 在这个匿名内部类中,使用了默认的构造器来生成 Contents1。
//如果你的基类需要一个有参构造器,应该怎么办呢?

class Wrapping{

    private int id;

    public Wrapping(int id) {
        this.id = id;
    }

    public int getVal(){
        return id;
    }
}

public class Parcel8 {
    public Wrapping wrapping(int x){
        return new Wrapping(x){
            @Override
            public int getVal() {
                return super.getVal()*20;
            }
        };
    }
    public static void main(String[] args) {
        Parcel8 parcel8 = new Parcel8();
        Wrapping wrapping = parcel8.wrapping(2);
        System.out.println(wrapping.getVal());
    }
}
  1. 只需要简单地传递合适的参数给基类的构造器即可,这里将 x 传进 new Wrapping(x)。 尽管Wrapping 只是一个具有具体实现的普通类,但它还是被其导出类当做公共接口来使用。
  2. 匿名内部类末尾的分号,并不是用来标记此内部类结束的。实际上,它标记的是表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。因此这与别的地方使用的分号是一致的。
class Destination1{
    private String label;

    public String getLabel(){
        return label;
    }
}
public class Parcel9 {

    public Destination1 destination1(String content){ 这里书上说要final 但从jdk1.8开始可以不用final了
        return new Destination1(){

            private String lebel=content;

            @Override
            public String getLabel() {
                return lebel;
            }
        };
    }
    public static void main(String[] args) {
        Parcel9 parcel9 = new Parcel9();
        parcel9.destination1("Tasmania");
    }
}
  1. 从JDK1.8 开始 可以不用final 进行修饰参数。

 

 

在访工厂方法

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

interface ServiceFactory{
    Service getService();
}

class Implementation1 implements Service{

    private Implementation1() {
    }

    @Override
    public void method1() {
        System.out.println("Implementation1  method1");
    }

    @Override
    public void method2() {
        System.out.println("Implementation1  method2");
    }
    public static ServiceFactory serviceFactory=new ServiceFactory() {
        @Override
        public Service getService() {
            return new Implementation1();
        }
    };
}

class Impplementation2 implements Service{

    private Impplementation2() {
    }

    @Override
    public void method1() {
        System.out.println("Impplementation2  method1");
    }

    @Override
    public void method2() {
        System.out.println("Impplementation2  method2");
    }

    public static ServiceFactory serviceFactory=new ServiceFactory() {
        @Override
        public Service getService() {
            return new Impplementation2();
        }
    };
}

class Factoryes{

    static void services(ServiceFactory serviceFactory){
        Service service = serviceFactory.getService();
        service.method1();
        service.method2();
    }

    public static void main(String[] args) {
        services(Implementation1.serviceFactory);
        services(Impplementation2.serviceFactory);
    }
}

//运行结果为
Implementation1  method1
Implementation1  method2
Impplementation2  method1
Impplementation2  method2
  1. Implementation1 和 Impplementation2 的构造器都是 private的,并且没有任何必要去创建作为工厂的具名类。
  2. 另外,你经常只需要单一的工厂对象,因此在本例中它被创建为 Service实现中的一个 static 域。

 

 

嵌套类

  • 如果不需要内部类对象与其外围类对象之间有关系,那么可以将内部类声明为 static。这通常称为 嵌套类
  • 普通的内部类对象隐式地保存了一个引用,指向了它的外围类对象。然而,当内部类是 static 的时候,就不是这样了。
  • 嵌套内部类意味着:
  1. 要创建嵌套类的对象,并不需要其外围类的对象。
  2. 不能从嵌套;类的对象中访问非静态的外围类对象。
  • 嵌套类和普通类还有一个区别: 普通内部类的字段与方法,只能放在类饿的外部层次上,所以普通的内部类不能有 static 数据 和static字段,也不能包含嵌套类,但是嵌套类可以包含所有这些东西。
public class Parcel11 {

    private static class ParcelContents implements Contents{
        private int i=11;
        @Override
        public int getVal() {
            return i;
        }
    }

    protected static class ParcelDestination implements Destination{
        private String label;

        public ParcelDestination(String label) {
            this.label = label;
        }

        @Override
        public String readLabel() {
            return label;
        }
    }

    public static Contents contents(){
        return new ParcelContents();
    }

    public static Destination destination(String message){
        return new ParcelDestination(message);
    }

    public static void main(String[] args) {
        Contents contents = contents();
        Destination tasmania = destination("Tasmania");
    }
}
  1. 在 main() 中,没有任何 Parcel11 的对象时必须的,而是使用选取 static 成员的普通语法来调用方法__这些方法返回对 Contents 和 Destination 的引用。
  2. 在一个普通的(非 static)内部类中, 通过提个特殊的 this 引用可以链接到其外围类对象。嵌套类就没有这个特殊的this 引用,这使它类似于一个 static方法。

 

 

 

接口内部的类

  • 正常情况下,不能再接口内部放置任何代码,但嵌套类可以作为接口的一部分。
  • 你放到接口中的任何类都自动地是 public 和 static 的。因为 类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。
public interface ClassInterface {

    void howdy();

    class Test implements ClassInterface{

        @Override
        public void howdy() {
            System.out.println("howdy()");
        }
        public static void main(String[] args) {
            new Test().howdy();
        }
    }
}
//运行结果为

howdy()
  1. 如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类回显得很方便。

 

 

从多层嵌套类中访问外部类的成员

  • 一个内部类嵌套多少层并不重要__它能透明地访问所有它嵌入的外围类的所有成员。
public class MNA {

    private void f(){}

    class A{
        private void g(){}

        public class B{
            void h(){
                g();
                f();
            }
        }
    }
}

class MultiNestingAccess{
    public static void main(String[] args) {
        MNA mna = new MNA();
        MNA.A a = mna.new A();
        MNA.A.B b = a.new B();
        b.h();
    }
}
  1. 可以看到 MNA.A.B 调用方法 g() 和 f() 不需要任何条件 (即使他们为 private )
  2. 这个例子同时展示了 如何从不同类里创建多层嵌套的内部类对象的基本语法。
  3. .new 语法能产生正确的作用域,所以不必在调用构造器时限定类名。

 

 

为什么需要内部类

  • 一般来说 内部类继承自某个类或实现了某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入外围类的窗口
  • 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
  • 为了看到更多的细节,让我们考虑这样一情形: 即必须在一个类中以某种方式实现两个接口。由于接口的灵活性,你有了两种选择: 使用单一类,或者使用内部类。
 interface A {}
 interface B {}
 
 class X implements A,B{
     
 }
 
 class Y implements A{
     B makeB(){
         return new B() {
             
         };
     }
 }
 
 class MultiInterfaces{
     static void takesA(A a){}
     static void takesB(B b){}

     public static void main(String[] args) {
         X x = new X();
         Y y = new Y();
         takesA(x);
         takesA(y);
         takesB(x);
         takesB(y.makeB());
     }
 }
  1. 当然,这里假设在两种方式下的代码结构都确实有逻辑意义。然而遇到问题的时候,通常问题本身就能给出某些指引,告诉你是应该使用单一类,还是使用内部类。
  2. 但如果没有任何其他限制,从实现的观点来看,前面的例子并没有什么区别,它们都能正常运作。
//如果拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承

public class D {}

abstract class E{}

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

        };
    }
}

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());
    }
}
  1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
  2. 在单个外围类中,可以让多个内部类以不同的方式实现同一接口,或继承同一个蕾。
  3. 创建内部类对象的时刻并不依赖于外围对象的创建。
  4. 内部类并没有令人迷惑的 is-a 关系,它就是一个独立的实体。

 

 

闭包与回调

  • 闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 private 成员。
  • Java通过内部类提供闭包的功能是优良的解决方案,它比指针更灵活,更安全。
interface Incrementable {
    void increment();
}

class Calleel implements Incrementable{
    private int i=0;

    @Override
    public void increment() {
        i++;
        System.out.println(i);
    }
}

class MyIncrement{
    public void increment(){
        System.out.println("Other operation");
    }
    static void f(MyIncrement myIncrement){
        myIncrement.increment();
    }
}

class Callee2 extends MyIncrement {
    private int i = 0;

    @Override
    public void increment() {
        super.increment();
        i++;
        System.out.println(i);
    }

    private class Closure implements Incrementable {

        @Override
        public void increment() {
            Callee2.this.increment();
        }
    }

    Incrementable getCallbackReference() {
        return new Closure();
    }
}

    class Caller{
        private Incrementable incrementable;

        public Caller(Incrementable incrementable) {
            this.incrementable = incrementable;
        }
        void go(){
            incrementable.increment();
        }
    }

    class Callbacks{
        public static void main(String[] args) {
            Calleel calleel=new Calleel();
            Callee2 callee2=new Callee2();
            MyIncrement.f(callee2);

            Caller caller1 = new Caller(calleel);
            Caller caller2 = new Caller(callee2.getCallbackReference());

            caller1.go();
            caller1.go();
            caller2.go();
            caller2.go();
        }
    }

//运行结果

Other operation
1
1
2
Other operation
2
Other operation
3
  1. 展示了外围类实现一个接口与内部类实现此接口之间区别。就代码而言 Calleel 是简单的解决方式。Callee2继承自 MyIncrement 后者已经有了一个不同的 increment() 方法,并且与 Incrementable接口期望的 increment() 方法完全不相同。
  2. 回调的价值在于它的灵活性__可以在运行时动态地决定需要调用什么方法。

 

内部类与控制框架

  • 应用程序框架 就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,已解决你特定问题。
  • 模板方法包含算法的基本结构,并会调用一个或多个可覆盖的方法,以完成算法的动作。设计模式总是将变化的事物与保持不变的事物分开,在这个设计模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物。
  • 控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。主要用来响应事件的系统被称作 事件驱动系统
public abstract class Event {
    private Long eventTime;

    protected final long delayTime;


    public Event(long delayTime) {
        this.delayTime = delayTime;
        start();
    }

    void start(){
        //System.nanoTime()  :返回的是纳秒,nanoTime而返回的可能是任意时间,甚至可能是负数……
        eventTime = System.nanoTime()+delayTime;
    }

    boolean ready(){
        return System.nanoTime() >= eventTime;
    }

     abstract void action();
    
}
  1. 当希望运行运行 Event 并随后调用 start() 时,那么构造器就会捕获(从对象创建的时刻开始的)时间, 此时间是这样得来的: start() 获取当前时间,然后加上一个延迟时间,这样生成触发事件的时间。
  2. start() 是一个独立的方法,而没有包含在构造器内,因为这样就可以在事件运行以后重新启动计时器,也就是能够重复使用 Event 对象。例如,如果想要重复一个事件,只需简单地在 action() 中调用 start() 方法。
  3. ready() 告诉你何时可以运行 action() 方法。当然,可以在导出类中覆盖 ready() 方法,使得 Event 能够基于时间以外的其他因素而触发。
class Controller{
    private List<Event> eventList=new ArrayList<Event>();
    public void addEvent(Event event){
        eventList.add(event);
    }
    void run(){
        while (eventList.size() > 0){
            for (Event event:new ArrayList<Event>(eventList)) {
                if (event.ready()){
                    System.out.println(event);
                    event.action();
                    eventList.remove(event);
                }
            }
        }
    }
}
  1. run() 方法循环遍历 eventList ,寻找就绪的(ready()),要运行的 Event对象。对找到的每一个就绪(ready())事件,使用对象的 toString() 打印其信息,调用其action() 方法,然后从队列中移除 Event。
  2. 使变化的事物与不变的事物相互分离__就是各种不同的 Event 对象所具有的不同行为,而你通过创建不同的 Event子类来表现不同的行为。
  • 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必须的各种不同的action()。
  • 内部类能够很容易地访问外围类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码将变得令人讨厌,以至于你肯定会选择别的方法。

 

 

内部类的继承

  • 因为内部类的构造器必须连接到指向其他外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。

  • 问题在于,那个指向外围类对象的 秘密的 引用必须被初始化,而在导出类中不再存在可连接的默认对象。

  • 要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联:

 class WithInner {
    class Inner{

    }
}
class InheritInner extends WithInner.Inner {
   // public InheritInner() {} Won't compile  不会编译
    
    public InheritInner(WithInner withInner) {
        withInner.super();
    }
    public static void main(String[] args) {
        WithInner inner = new WithInner();
        InheritInner inheritInner = new InheritInner(inner);
    }
}
  1. 可以看到, InheritInner 只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法
enclosingClassReference.super();
  1. 这样才提供了必要的引用,然后程序才能编译通过。

 

 

内部类可以被覆盖吗

  • 如果创建了一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么呢?
  • 也就是说,内部类可以被覆盖吗? 这看起来似乎是个很有用的思想,但是覆盖内部类就好像它是外围类的一个方法,其实并不起什么作用。
 class Egg {

    private Yolk yolk;

    protected class Yolk{
        public Yolk(){
            System.out.println("Egg Yolk");
        }
    }

    public Egg() {
        System.out.println("new Egg");
        yolk=new Yolk();
    }
}
    class BigEgg extends Egg{
        public class Yolk {
            public Yolk() {
                System.out.println("BigEgg Yolk");
            }
        }
        public static void main(String[] args) {
            new BigEgg();
        }
    }

//运行结果

new Egg
Egg Yolk
  1. 默认的构造器是编译器自动生成的,这里是调用基类的默认构造器。
  2. 你可能认为创建的是BigEgg的对象,那么所使用的应该是 覆盖后 的Yolk 版本,但从输出中可以看到实际情况并不是这样的。
  3. 当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的:
public class Egg2 {
    protected class Yolk{
        public Yolk() {
            System.out.println("Egg2 Yolk");
        }
        void  f(){
            System.out.println("Egg2 Yolk f()");
        }
    }
    private Yolk yolk=new Yolk();

    public Egg2() {
        System.out.println("Egg2 Constructor");
    }
    void insertYolk(Yolk yolk){
        this.yolk=yolk;
    }

    void g(){
        yolk.f();
    }
}
class BigEgg2 extends Egg2{

    public class Yolk extends Egg2.Yolk{
        public Yolk() {
            System.out.println("BigEgg2 Yolk");
        }
        @Override
        void f() {
            System.out.println("BigEgg2 Yolk f()");
        }
    }

    public BigEgg2() {
        insertYolk(new Yolk());
    }

    public static void main(String[] args) {
        new BigEgg2().g();
    }
}

//运行结果

Egg2 Yolk
Egg2 Constructor
Egg2 Yolk
BigEgg2 Yolk
BigEgg2 Yolk f()
  1. 现在 BigEgg2.Yolk 通过 extends Egg2.Yolk 明确地继承了此内部类,并且覆盖了其中的方法。insertYolk() 方法允许 BigEgg2 将它自己的 Yolk 对象向上转型为 Egg2 中的引用 this.yolk。
  2. 所以当 g() 调用 yolk.f();时,覆盖后的新版的 f() 被执行。第二次调用 Egg2.Yolk(), 结果是 BigEgg2.Yolk 的构造器调用了其基类的构造器。可以看到 在调用 g() 的时候,新版的 f() 被调用了。

 

 

 

局部内部类

  • 可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块的常量,以及此外围类的所有成员。
 interface Counter {
    int next();
}

 class LocalInnerClass{
     private int count = 0;
     Counter getCounter(String name){
         class LocalCounter implements Counter{

             public LocalCounter() {
                 System.out.println("LocalCounter constructor");
             }

             @Override
             public int next() {
                 System.out.println(name);
                 return count++;
             }
         }
         return new LocalCounter();
     }
     Counter getCounter2(String name){
         return new Counter() {
             {
                 System.out.println("Counter()");
             }

             @Override
             public int next() {
                 System.out.println(name);
                 return count++;
             }
         };
     }

    public static void main(String[] args) {
        LocalInnerClass lic = new LocalInnerClass();
        Counter inner = lic.getCounter("local inner");
        Counter counter2 = lic.getCounter2("Anonymous inner");
        for (int i = 0; i < 5; i++) {
            System.out.println(inner.next());
        }

        for (int i = 0; i < 5; i++) {
            System.out.println(counter2.next());
        }
    }
}

//运行结果为

LocalCounter constructor
Counter()
local inner
0
local inner
1
local inner
2
local inner
3
local inner
4
Anonymous inner
5
Anonymous inner
6
Anonymous inner
7
Anonymous inner
8
Anonymous inner
9
  1. Counter 返回的是序列中的下一个值。我们分别使用局部内部类和匿名内部类实现了这个功能,它们具有相同的行为和能力。
  2. 既然局部内部类的名字在方法外是不可见的,那为什么我们仍然使用局部内部类而不是匿名内部类呢?
  3. 唯一的理由是: 我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化
  4. 所以使用局部内部类而不使用匿名内部类的另一个理由就是,需要不止一个该内部类的对象

 

 

内部类标识符

  • 由于每个类都会产生一个 .class 文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个 meta-class,叫做Class对象), 你可能猜到了, 内部类也必须生成一个 .class文件以包含它们的Class 对象信息。这些类文件命名有严格的规则: 外围类的名字,加上 $ 再加上内部类名字
Callbacks.class
Callee2$1.class
Callee2$Closure.class
  1. 如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与 $ 的后面。
  2. 虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况。因为这是Java的标准命名方式,所以产生的文件自动都是平台无关的(注意: 为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) 。

 

总结:

  • 比起面向对象编程中其他的概念来,接口和内部类更深奥复杂,比如C++就没有这些。将两者结合起来,同样能够解决C++中的用多重继承所能解决的问题。
  • 然而多种继承在  C++中被证明是难以相当难以使用的,相比较而言,Java接口和内部类就容易理解多了。

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Java编程思想》是一本经典的Java编程入门书籍,是每位Java程序员必读的宝典。该书由Bruce Eckel编写,通过清晰、简洁的语言和丰富的示例代码,系统地介绍了Java编程的基本概念、语法和最佳实践。 本书分为两部分,第一部分主要介绍了Java编程的基础知识,包括面向对象的理念、类与对象、继承和多态、接口与内部类、异常处理以及Java集合等内容。这些基础知识是学习Java编程的必备基础,通过深入浅出的方式,读者可以逐步掌握Java的核心概念和编程技巧。 第二部分则更加深入和高级,讨论了Java编程的一些高级主题,例如并发编程、图形用户界面、网络编程和数据库访问等。这些内容对于有一定Java开发经验的程序员来说非常有价值,可以帮助他们进一步提升自己的编程能力。 《Java编程思想》具有很强的实践性和指导性,书中的示例代码通俗易懂,附有详细的解释和说明,读者可以通过实际操作来巩固所学知识。此外,本书还对一些常见的编程问题进行了分析和解答,对读者更好地理解和掌握Java编程提供了帮助。 总之,《Java编程思想》是一本非常经典的Java入门书籍,它广泛被广大Java程序员所推崇和使用。无论是初学者还是有经验的 Java开发人员,都能从这本书中获取到丰富的知识和编程经验,提升自己的编程技能。 ### 回答2: 《Java编程思想》是一本经典的Java编程指南和教程,由美国计算机科学家Bruce Eckel所著。本书全面而深入地介绍了Java编程的基本理念和原则,适合初学者和有一定基础的程序员阅读。 这本书从面向对象的观点出发,系统地讲解了Java语言的核心概念和特性,如类、对象、继承、封装、多态等。同时,书中还涉及了Java关键字、基本数据类型、运算符、流程控制语句等基础知识的讲解。 除了基础的语法知识,本书还着重介绍了Java程序设计的思想和方法。作者通过丰富的示例和具体的案例分析,展示了面向对象编程的优势和实践,如封装、继承、多态的应用,以及设计模式、异常处理等高级编程技巧。 《Java编程思想》的另一个亮点是其对Java标准库的深入讲解。书中详细介绍了Java中常用的类和接口,包括集合框架、IO操作、线程、网络编程等。这对于初学者来说十分有用,可以帮助读者更好地理解和运用Java的核心API。 总的说来,这本书系统而全面地介绍了Java编程的基本概念、语法特性和设计思想,适合初学者和有一定基础的程序员阅读。通过阅读本书,读者可以掌握Java编程的基础知识,并培养面向对象的编程思维。同时,本书也适合作为Java程序员的参考手册,帮助解决实际开发中遇到的问题。 ### 回答3: 《Java编程思想》是美国计算机科学家Bruce Eckel所著的一本经典教材,对Java编程语言进行了深入浅出的讲解。这本书全面系统地介绍了Java的基本概念、语法规则以及面向对象编程思想和技巧,适合初学者和有一定基础的开发者阅读。 《Java编程思想》以通俗易懂的方式,循序渐进地讲解了Java的核心概念,包括类、对象、继承、多态等。通过大量的示例代码和实际案例,读者可以深入理解面向对象的编程思想,掌握Java的核心特性和用法。 这本书的特点之一是注重实践,通过大量的实例帮助读者巩固所学知识,并引导读者自己进行实践和探索。书中涵盖了众多经典的Java编程问题和解决方案,深入讲解了Java的各个方面,包括异常处理、并发编程、IO操作、网络编程等。 《Java编程思想》还介绍了一些高级主题,如泛型、反射、注解等,帮助读者掌握更高级的Java编程技巧和知识点。此外,书中还介绍了一些常用的Java开发工具和框架,如Eclipse、Maven等,让读者了解如何高效地进行Java开发。 总之,《Java编程思想》是一本权威且深入浅出的Java编程教材,适合初学者入门和有经验的开发者进一步提升编程水平。这本书重在培养读者全面的编程思维和解决问题的能力,是Java开发者必备的一本经典之作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值