• 《OnJava》——11内部类


    内部类

    利用内部类,可以将逻辑上存在关联的类组织在一起,而且可以控制一个类在另一个类中的可见性。

    内部类和组合不同,内部类是一种代码隐藏机制:将代码放在其他类的内部。

    11.1 创建内部类

    创建内部类的方式就是把类定义放在一个包围它的类之中。

    /**
     * 创建内部类
     */
    public class Parcel1 {
      class Contents {
        private int i = 11;
        public int value() { return i; }
      }
      class Destination {
        private String label;
        Destination(String whereTo) {
          label = whereTo;
        }
        String readLabel() { return label; }
      }
      // 在Parcel1内,使用内部类看上去,就和使用任何其他类一样
      public void ship(String dest) {
        Contents c = new Contents();
        Destination d = new Destination(dest);
        System.out.println(d.readLabel());
      }
      public static void main(String[] args) {
        Parcel1 p = new Parcel1();
        p.ship("Tasmania");
      }
    }
    /* Output:
    Tasmania
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    返回一个指向内部类的引用

    public class Parcel2 {
      class Contents {
        private int i = 11;
        public int value() { return i; }
      }
      class Destination {
        private String label;
        Destination(String whereTo) {
          label = whereTo;
        }
        String readLabel() { return label; }
      }
      public Destination to(String s) {
        return new Destination(s);
      }
      public Contents contents() {
        return new Contents();
      }
      public void ship(String dest) {
        Contents c = contents();
        Destination d = to(dest);
        System.out.println(d.readLabel());
      }
      public static void main(String[] args) {
        Parcel2 p = new Parcel2();
        p.ship("Tasmania");
        Parcel2 q = new Parcel2();
        // Defining references to inner classes:
        //要在内部类的非静态方法之外的任何地方创建内部类对象
        //必须将对象的类型指定为OutClassName.InnerClassName
        //即,将实例指定到详细的类
        Parcel2.Contents c = q.contents();
        Parcel2.Destination d = q.to("Borneo");
        
        System.out.println("c = " + c.value());
        System.out.println("d = " + d.readLabel());
      }
    }
    /* Output:
    Tasmania
    c = 11
    d = Borneo
    */
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    11.2 到外部类的链接

    内部类看上去像是一种名称隐藏代码组织机制。

    当创建一个内部类时,这个内部类的对象中会隐含一个链接,指向用于创建该对象的外围对象。

    通过这个链接,无须任何特殊条件,内部类对象就可以访问外围对象的成员。

    内部类拥有对外围对象所有元素的访问权

    //保存一个对象序列
    
    //迭代器
    interface Selector {
      boolean end();
      Object current();
      void next();
    }
    //Sequence是以类的形式包装起来的定长Object数组。
    public class Sequence {
      private Object[] items;
      private int next = 0;
      public Sequence(int size) {
        items = new Object[size];
      }
      
      //使用add()方法向序列末尾增加一个新的Object(有空间的前提下)
      public void add(Object x) {
        if(next < items.length)
          items[next++] = x;
      }
      
      //实现“迭代器”接口,重写方法
      private class SequenceSelector implements Selector {
        //在SequenceSelector中使用到了外围items(private)
        //内部类可以访问外围对象的所有方法和字段,就好像拥有它们一样
        private int i = 0;
        @Override
        public boolean end() { return i == items.length; }
        @Override
        public Object current() { return items[i]; }
        @Override
        public void next() { if(i < items.length) i++; }
      }
      public Selector selector() {
        return new SequenceSelector();
      }
      public static void main(String[] args) {
        Sequence sequence = new Sequence(10);
        for(int i = 0; i < 10; i++)
          sequence.add(Integer.toString(i));
        Selector selector = sequence.selector();
        while(!selector.end()) {
          System.out.print(selector.current() + " ");
          selector.next();
        }
      }
    }
    /* Output:
    0 1 2 3 4 5 6 7 8 9
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    内部类可以自动访问外围类的所有成员。

    这是怎么做到的呢?

    对于负责创建内部类对象的特定外围类对象而言,内部类对象偷偷地获取了一个指向它的引用。(内部得外部的属性)

    该引用会被用于选择相应的成员

    内部类的对象只能与其外部类的对象关联创建(当内部类为非static时)

    11.3 使用 .this和.new

    若要生成外部类对象的引用,可以使用外部类的名字,后面加上句点和this。

    Inner.this这个就是外部类了

    public class DotThis {
      void f() { System.out.println("DotThis.f()"); }
      public class Inner {
        public DotThis outer() {
          return DotThis.this;
          // A plain "this" would be Inner's "this"
          //如果直接写this,引用的是Inner的this,当前this指向内部类,而不是外部类
        }
      }
      public Inner inner() { return new Inner(); }
      public static void main(String[] args) {
        DotThis dt = new DotThis();
        DotThis.Inner dti = dt.inner();
        dti.outer().f();
      }
    }
    /* Output:
    DotThis.f()
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用.new语法,在new表达式中提供指向其他外部类对象的引用:

    public class DotNew {
      public class Inner {}
      public static void main(String[] args) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner();
      }
    }
    //除非已经有了一个外部类的对象,否则创建内部类对象是不可能的
    //因为内部类的对象会暗中连接到用于创建它的外部类对象
    //如果你创建的是嵌套类(static修饰的内部类),就不需要指向外部类对象的引用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    11.4 内部类和向上转型

    当需要向上转型为基类,特别是接口时,内部类就更有吸引力了。(从实现某个接口的对象生成一个该接口类型的引用,其效果和向上转型为某个基类在本质上是一样的)

    这是因为,内部类(接口的实现)对外部而言可以时不可见的、不可用的,这便于隐藏实现

    外部获得的只是一个指向基类或接口的引用

    创建接口:接口会自动将其所有成员设置为public的。

    public interface Destination {
      String readLabel();
    }
    
    • 1
    • 2
    • 3
    public interface Contents {
      int value();
    }
    
    • 1
    • 2
    • 3

    当得到一个指向基类或接口的引用时,无法找到其确切的类型:

    class Parcel4 {
      private class PContents implements Contents {
        private int i = 11;
        @Override public int value() { return i; }
      }
      protected final class PDestination implements Destination {
        private String label;
        private PDestination(String whereTo) {
          label = whereTo;
        }
        @Override
        public String readLabel() { return label; }
      }
      public Destination destination(String s) {
        return new PDestination(s);
      }
      public Contents contents() {
        return new PContents();
      }
    }
    
    public class TestParcel {
      public static void main(String[] args) {
        Parcel4 p = new Parcel4();
        Contents c = p.contents();
        Destination d = p.destination("Tasmania");
        // Illegal -- can't access private class:
        //非法————不能访问private类
        //- Parcel4.PContents pc = p.new PContents();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在Parcel4中,内部类PContents时private的,所以只有Parcel4能访问它。

    普通类(非内部类)无法声明为private的或protected的,它们只能被给予public或包访问权限。

    PDestination是protected的,所以它只能被Parcel4、相同包中的类(因为protected也给予了包访问权限),以及Parcel4的子类访问。

    这意味着客户程序员对这些成员的了解是有限的,对它们的访问权限也是有限的。

    private内部类为类的设计者提供了一种方式,可以完全组织任何与类型相关的编码依赖,并且可以完全隐藏实现细节。

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

    内部类可以在一个方法内或者任何一个作用域内创建。

    1. 在方法中定义的类
    2. 在方法中的某个作用域内定义的类
    3. 实现某个接口的匿名类
    4. 这样的匿名类——它继承了拥有非默认构造器的类
    5. 执行字段初始化的匿名类
    6. 它通过实例初始化来执行构造(匿名内部类不可能有构造器)的匿名类。

    局部内部类:在一个方法的作用域内(而不是在另一个类的作用域内)创建一个完整的类。

    public class Parcel5 {
      
      public Destination destination(String s) {
        /**
         * 局部内部类 -- PDestination
         * PDestination在destination()外无法访问。
         */
        final class PDestination implements Destination {
          private String label;
          private PDestination(String whereTo) {
            label = whereTo;
          }
          @Override
          public String readLabel() { return label; }
        }
        return new PDestination(s);
      }
      public static void main(String[] args) {
        Parcel5 p = new Parcel5();
        Destination d = p.destination("Tasmania");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    PDestination是destination()的一部分,而不是parcel5的一部分。因此,PDestination在destination()外无法访问。

    在return语句中的向上转型意味着destination()中只传出了一个指向Destination接口的引用。

    PDestination类的名字被放在了destination()中这一事实,并不意味着一旦destination()方法返回,得到的PDestination就不是一个合法的对象了。

    在同一子目录下的每个类中,你都可以使用类标识符PDestination来命名内部类,而不会产生名字冲突。

    如何在任何作用域嵌入一个内部类:

    public class Parcel6 {
      private void internalTracking(boolean b) {
        if(b) {
          /**
           * 这个类被嵌入了if语句的作用域内
           * 在作用域之外是不可用的。
           */
          class TrackingSlip {
            private String id;
            TrackingSlip(String s) {
              id = s;
            }
            String getSlip() { return id; }
          }
          TrackingSlip ts = new TrackingSlip("slip");
          String s = ts.getSlip();
        }
        // Can't use it here! Out of scope:
        //- TrackingSlip ts = new TrackingSlip("x");
      }
      public void track() { internalTracking(true); }
      public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        p.track();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    11.6 匿名内部类

    /**
     * 返回匿名内部类的一个实例
     */
    public class Parcel7 {
      public Contents contents() {
        return new Contents() { // 插入类定义
          private int i = 11;
          @Override public int value() { return i; }
        }; // 分号是必须的
      }
      public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    contents()方法将返回值的创建和用于表示该返回值的类的定义结合了起来。此外,这个类没有名字——它是匿名的。

    看起来你正在创建一个Contents对象,但是在到达分号之前,你说:“等等,我想插入一个类定义。”

    “创建一个继承自Contents的匿名类的对象”,通过new表达式返回的引用会被自动地向上转型为一个Contents引用。

    匿名内部类的语法如下:

    public class Parcel7b {
      class MyContents implements Contents {
        private int i = 11;
        @Override public int value() { return i; }
      }
      public Contents contents() {
        return new MyContents();
      }
      public static void main(String[] args) {
        Parcel7b p = new Parcel7b();
        Contents c = p.contents();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这个匿名内部类中,Contents是用无参构造器创建的。

    如果基类需要带一个参数的构造器,应该这样:

    /**
     * 基类需要带一个参数的构造器
     */
    public class Parcel8 {
      public Wrapping wrapping(int x) {
        // Base constructor call:
        return new Wrapping(x) {          // [1]
          @Override public int value() {
            return super.value() * 47;
          }
        };                                // [2]
      }
      public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Wrapping w = p.wrapping(10);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    【1】将适当的参数传给基类构造器

    【2】匿名内部类末尾的分号并不是用来标记类体的结束。相反,它标记表达式的结束,而该表达式恰好包含了这个匿名类。因此,它和分号在其他地方的用法没什么不同。

    尽管,Wrapping是一个带有实现的普通类,但它也被用作其子类的公共“接口”:

    public class Wrapping {
      private int i;
      public Wrapping(int x) { i = x; }
      public int value() { return i; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    也可以在定义匿名类中的字段时执行初始化:

    public class Parcel9 {
      // Argument must be final or "effectively final"
      //要在匿名内部类中使用
      // to use within the anonymous inner class:
      // 参数必须是最终变量,或者"实际上的最终变量"
      public Destination destination(final String dest) {
        return new Destination() {
          private String label = dest;
          @Override
          public String readLabel() { return label; }
        };
      }
      public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.destination("Tasmania");
        System.out.println("d = " + d.readLabel());
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    如果你正在定义一个匿名类,而且一定要用到一个在该匿名类之外定义的对象,编译器要求参数引用用final修饰,或者是“实际上的最终变量”(也就是说,在初始化之后它永远不会改变,所以它可以被视为final)

    就像你在destination()的参数中看到的那样。这里不写final也没有任何问题,但把它写上当作提醒通常最好。

    如果只是要给一个字段赋值,这个实例中的方法就很好。

    但是如果必须执行某个蕾丝构造器的动作,该怎么办呢?因为匿名类没有名字,所以不可能有命名的构造器。

    借助实例初始化,我们可以在效果上为匿名内部类创建一个构造器:

    /**
     * Creating a constructor for an anonymous inner class
     * 为匿名内部类创建一个构造器
     */
    abstract class Base {
      Base(int i) {
        System.out.println("Base constructor, i = " + i);
      }
      public abstract void f();
    }
    
    public class AnonymousConstructor {
      public static Base getBase(int i) {
        return new Base(i) {
          { System.out.println("Inside instance initializer"); }
          @Override public void f() {
            System.out.println("In anonymous f()");
          }
        };
      }
      public static void main(String[] args) {
        Base base = getBase(47);
        base.f();
      }
    }
    /* Output:
    Base constructor, i = 47
    Inside instance initializer
    In anonymous f()
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    这里变量i并不是必须为最终变量。尽管i被传给了匿名类的基类构造器,但是在该匿名类内部,它并没有被直接使用到。

    对比一下,当参数在匿名类内部被用到:

    **
     * 使用"实例初始化",来执行匿名内部类的构造
     */
    public class Parcel10 {
      public Destination destination(final String dest, final float price) {
        return new Destination() {
          private int cost;
          //为每个对象执行实例初始化
          // Instance initialization for each object:
          {
            cost = Math.round(price);
            if(cost > 100)
              System.out.println("Over budget!");
          }
          private String label = dest;
          @Override
          public String readLabel() { return label; }
        };
      }
      public static void main(String[] args) {
        Parcel10 p = new Parcel10();
        Destination d = p.destination("Tasmania", 101.395F);
      }
    }
    /* Output:
    Over budget!
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    我们在实例初始化操作内可用看到一段代码,也就是if语句,它们不能作为字段初始化的一部分来执行。

    所以在效果上,实例初始化就是匿名内部类的构造器。不过它也有局限性——我们无法重载实例初始化部分,所以只能有一个这样的构造器。

    与普通的继承相比,匿名内部类有些局限性,因为它们要么是扩展一个类,要么是实现一个接口,但是两者不可兼得。

    而且就算要实现接口,也只能实现一个。

    11.7 嵌套类

    如果不需要内部类对象和外部类对象之间的连接,可以将内部类设置为static的。这就是嵌套类

    普通内部类对象中隐式地保留了一个引用,指向创建该对象的外部类对象。

    对于static的内部类(嵌套类)来说:

    1. 不需要一个外部类对象来创建嵌套类对象
    2. 无法从嵌套类对象内部访问非static的外部类对象

    嵌套类和普通内部类之间的不同:

    普通内部类的字段和方法,只能放在类的外部层次中,所以普通内部类中不能有static数据、static字段,也不能包含嵌套类

    但是嵌套类中可以包含:其他的静态元素

    /**
     * 嵌套类(static的内部类)
     */
    public class Parcel11 {
      private static class ParcelContents implements Contents {
        private int i = 11;
        @Override public int value() { return i; }
      }
      protected static final class ParcelDestination implements Destination {
        private String label;
        private ParcelDestination(String whereTo) {
          label = whereTo;
        }
        @Override
        public String readLabel() { return label; }
        // Nested classes can contain other static elements:
        //嵌套类可以包含其他静态元素
        public static void f() {}
        static int x = 10;
        static class AnotherLevel {//嵌套类
          public static void f() {}
          static int x = 10;
        }
      }
      public static Destination destination(String s) {
        return new ParcelDestination(s);
      }
      public static Contents contents() {
        return new ParcelContents();
      }
      public static void main(String[] args) {
        Contents c = contents();
        System.out.println("c.value() = " + c.value());
        Destination d = destination("Tasmania");
        System.out.println("d.readLabel() = " + d.readLabel());
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    这样在main()中并不需要Paecel11对象;

    静态方法可以直接调用,类名调用和对象调用

    非静态方法只能通过对象调用

    相反,我们使用选择static成员的普通语法来调用方法,这些方法指向Contents和Destination类型的引用。

    普通内部类(非static的)可以使用特殊的this(内部类.this)引用来创建指向外部类对象的连接。而嵌套类没有特殊的this引用,这使它和static方法类似。

    11.7.1 接口中的类

    嵌套类可以是接口的一部分。

    放到接口中的任何类都会自动成为public和static的。

    因为类是static的,所以被嵌套的类知识放在了这个接口的命名空间内。

    甚至可以在内部类内实现包围它的这个接口:

    public interface ClassInInterface {
      void howdy();
    
      /**
       * 在内部类实现这个接口,套娃呢在?
       */
      class Test implements ClassInInterface {
        @Override public void howdy() {
          System.out.println("Howdy!");
        }
        public static void main(String[] args) {
          new Test().howdy();
        }
      }
    }
    /* Output:
    Howdy!
    */
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    当你要创建某个接口的所有不同实现使用的公用代码时,将一个类嵌入这个接口中会非常方便。

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

    一个内部类被嵌套多少册并不重要。

    它可以透明地访问它的所有类的所有成员,如下面的代码所示:

    /**
     * 被嵌套的类可以访问各层外部类中的所有成员
     */
    class MNA {
      private void f() {}
      class A {
        private void g() {}
        public class B {
          public void h() {
            System.out.println("B::h()");
            g();
            {
              System.out.println("B::CodeBlock");
            }
            f();
          }
        }
      }
    }
    
    public class MultiNestingAccess {
      public static void main(String[] args) {
        MNA mna = new MNA();
        MNA.A mnaa = mna.new A();
        MNA.A.B mnaab = mnaa.new B();
        mnaab.h();
      }
    }
    /**
     * B::h()
     * B::CodeBlock
     * /
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    可以注意到,private方法g()和f()无须任何条件就可以调用。

    这个例子也演示了当你在一个不同的类中创建对象时,创建多层嵌套的内部类对象的基本语法。

    .new 语法会得到正确的作用域,所以不必在调用构造器时限定类的名字。

    11.8 为什么需要内部类

    到底为什么需要内部类呢?

    通常情况下,内部类继承自某个类或实现某个接口,内部类中的代码会操作用以创建该内部类对象的外部类对象。内部类提供了进入其外部类的某种窗口。

    引入内部类的主要原因:

    • 每个内部类都可以独立地继承自一个实现

    • 外部类是否已经继承了某个实现,对内部类并没有限制

    • 内部类完善了多重继承问题的解决方案

    • 内部类实际上支持我们继承多个非接口类型

    例如,在一个类内必须以某种形式实现两个接口。

    由于接口的灵活性,有两个选择:一个单独的类(同时实现两个接口)或一个内部类(分离实现两接口)

    interface A {}
    interface B {}
    
    //通过一个单独的类,同时实现两个接口
    class X implements A, B {}
    //通过内部类,分离实现两个接口
    class Y implements A {
      B makeB() {
        // Anonymous inner class:
        // 匿名内部类
        return new B() {};
      }
    }
    
    public 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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    如果使用的是抽象类或具体类,而不是接口的话,而且你的类必须以某种方式实现这两者,那就只能使用内部类来实现了:

    // 对于具体类或抽象类,内部类可以产生“多重实现继承”的效果
    class D {}
    abstract 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());
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    有了内部类,可以得到如下这些额外的功能:

    1. 内部类可以有多个实例,每个实例都有自己的状态信息,独立于外围类对象的信息。
    2. 一个外围类中可以有多个内部类,它们可以以不同方式实现同一接口,或者继承同一类。
    3. 内部类对象的创建时机不与外围类对象的创建捆绑到一起。
    4. 内部类不存在可能引起混淆的“is-a”关系;它是独立的实体。

    11.8.1 闭包与回调

    **闭包(closure)**是一个可调用的对象,它保留了自它被创建时所在的作用域的信息

    **回调(callback)**Java中的指针机制,通过回调可以给其他对象提供一段信息,以支持它在之后的某个时间点调用会原始的对象中

    // Very simple to just implement the interface:
    // 只实现这个接口非常简单
    class Callee1 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 mi) { mi.increment(); }
    }
    
    // If your class must implement increment() in
    // some other way, you must use an inner class:
    //如果我们的类必须以其他某种方式实现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 {
        //留个指回Callee2的钩子
        @Override public void increment() {
          // Specify outer-class method, otherwise
          // you'll get an infinite recursion:
          //调用外围类方法,否则会无限递归下去(跳出递归)
          Callee2.this.increment();
        }
      }
      Incrementable getCallbackReference() {
        return new Closure();
      }
    }
    
    class Caller {
      private Incrementable callbackReference;
      Caller(Incrementable cbh) {
        callbackReference = cbh;
      }
      void go() { callbackReference.increment(); }
    }
    
    public class Callbacks {
      public static void main(String[] args) {
        Callee1 c1 = new Callee1();
        Callee2 c2 = new Callee2();
        MyIncrement.f(c2);
        Caller caller1 = new Caller(c1);
        Caller caller2 =
          new Caller(c2.getCallbackReference());
        caller1.go();
        caller1.go();
        caller2.go();
        caller2.go();
      }
    }
    /* Output:
    Other operation
    1
    1
    2
    Other operation
    2
    Other operation
    3
    */
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    通过使用内部类来提供单独的实现,满足更多的需求。

    当创建内部类时,并没有增加或修改外围类的接口。

    参考《OnJava》Visit http://OnJava8.com for more book information.

  • 相关阅读:
    P1540 [NOIP2010 提高组] 机器翻译(模拟)
    Vue3 - 生命周期钩子函数(组合式 API)
    洛谷 P2183 [国家集训队]礼物)(扩展卢卡斯定理)
    tomcat下载安装及配置教程
    【OpenMV】AprilTag标记跟踪 NCC模板匹配 测距与测量物体大小
    专精特新企业认定条件
    【Python】中文字符写入json文件
    Jupyter 介绍
    count(*)查询性能很差?用这5招轻松优化
    2183440-36-8,APN-C3-PEG4-alkyne 性能稳定功能连接体
  • 原文地址:https://blog.csdn.net/weixin_45688141/article/details/125428803