Skip to content

内部类

  • 实例Outer.Inner in = new Outer().new Inner();
  • 必须有外部类的对象来创建内部类,因为内部类创建时会自动进行绑定

  • 内部类编译后会产生单独的class文件,如外部类$内部类.class

  • 内部类可以声明为public、private、protected、default

  • 内部类中, 访问外部类成员 : 直接访问, 包括私有(无论嵌套多少层都可以透明的访问,创建时也需要逐级.

  • 通常直接访问就可以,如果需要显示访问:格式Outer.this.

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

public class Sequence {
  private Object [] items;
  private int next = 0;
  public Sequence(int size) {
    items = new Object [size];
  }
  public void add(Object x) {
    if(next < items.length)
      items [next++] = x;
  }
    //提供序列化迭代器实现对外部类的访问
  private class SequenceSelector implements Selector {
    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();
    }
  }
}
- 这里的内部类是private的,其实例和类型对外部类之外都是不可见的,外部类不能直接用public方法返回一个private内部类的实例,但是可以返回内部类实现的public基类或接口(向上转型Selector)这种方式隐藏了内部类的具体实现,只暴露了接口或超类的公共方法。 - 将内部类设置为private或protected可以阻止外部直接访问,同时只提供向上转型的结果实现更好的封装 [[private内部类可以由外部类的public方法返回吗]]

内部类分类

[[内部类的三种类型]] - 局部内部类 - 在方法作用域中创建内部类

public class Parcel5 {
  public Destination destination(String s) {
      //在返回方法中定义一个内部类,这个内部类只能在该方法作用域中使用,他的定义不能在外部使用,但是可以向上转型后作为返回值返回
    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");
  }
}

  • 匿名内部类:定义一个类的同时对其进行实例化
  • 匿名类只能扩展一个类或实现一个接口
    public class Parcel7 {
      public Contents contents() {
        return new Contents() { // Insert class definition
          private int i = 11;
          @Override public int value() { return i; }
        }; // Semicolon required
      }
      public static void main(String [] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
      }
    }
    
  • 创建了一个继承自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);
      }
    }
    
    public class Wrapping {
      private int i;
      public Wrapping(int x) { i = x; }
      public int value() { return i; }
    }
    

  • 如果要在匿名类使用匿名类外的对象,要求参数用final 修饰,或者保证初始化后就不会再变化

    • 此规则的背后是闭包(Closure)的概念,在Java中,匿名类或lambda表达式可以捕获外部作用域中的变量,这些变量被称为捕获变量。为了保证变量在被捕获时的值在使用时依然有效并且不被意外修改,Java要求这些变量必须是final或“effectively final”。
  • 匿名函数的构造器

  • 可以使用初始化块{}

    public class Parcel10 {
      public Destination
          //被使用的对象要保证为 final
      destination(final String dest, final float price) {
        return new Destination() {
          private int cost;
          // 初始化块
          {
            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);
      }
    }
    

  • 静态内部类(嵌套类),内部类使用static修饰,

  • 不需要内部类对象和外部类对象之间的连接
  • 不需要通过外部类对象创建
  • 无法从内部访问非static的外部对象
  • 嵌套类内可以有static的数据及字段
  • Outer.Inner in = new Outer.Inner();
  • 可以定义在接口中,默认就是public static的
  • 静态内部类不是真的“静态”,只是不依赖于外部类
    • 也可以有非静态方法,并且可以创建多个实例
    • 静态内部来与外部顶层类是类似的,只是定义在一个类的内部

应用

  • 间接实现多继承
  • 每个内部类都有可以独立地继承自一个实现
  • 可以使用每个内部类去继承一个类,间接实现多继承

    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());
      }
    }
    

  • 闭包与回调 ^44cea9

    interface Incrementable {
      void increment();
    }
    
    //直接实现接口
    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(); }
    }
    
    //使用内部类实现接口,并给出方法用于获取内部接口(这里的内部接口会对外部进行访问,是一个闭包)
    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() {
          // 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();
      }
    }
    

  • 闭包:允许方法保留和使用定义在其外部作用域的变量,即使外部方法已经完成执行。Callee2的内部类Closure类似于一个闭包,因为它封装了Callee2的环境并提供了对其increment方法的访问。

  • 闭包让你可以在一个内层函数中访问到其外层函数的作用域。 [[什么是闭包]]
  • 回调:一个方法接受另一个方法作为参数,然后在适当的时候执行传递。Caller类接受一个实现了Incrementable接口的对象作为回调,并在go方法中调用该回调对象的increment方法。