内部类

使用内部类的原因

  1. 对同一个包中的其他类隐藏
  2. 访问定义这些方法的作用域的数据, 包括原本的私有数据
  • 内部类对象会有一个隐式引用, 指向实例化这个对象的外部类对象, 可以访问外部对象的全部状态
  • 但是Java中静态内部类没有这个指针, 所以Java静态内部类等于CPP中的嵌套类
  • 可以使用OuterClass.this表示外部类的引用, 比如 T.this.b
  • 可以使用outerObject.new InnerClass()编写内部类的构造器, 比如A listen = this.new B();
  • 在外部类的作用域之外, 可以使用OutClass.InnerClass引用
  • 内部类声明的所有静态字段都必须是final, 初始化为一个编译时常量
  • 内部类不能有static方法
  • 使用$javap -private ClassName可以将内部类文件转换为常规类文件

局部类

  • 可以在一个方法中声明局部类, 这个类不能用public或者private访问修饰符, 对外部完全隐藏, 除了这个方法外都不知道这个局部类的存在
  • 局部类不仅可以访问外部类的字段, 还可以访问局部变量, 不过这些局部变量都需要是只能赋值一次就不会改变的事实最终变量

匿名内部类

  • 如果只想要创建类的一个对象, 不需要为类指定名字
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public void start(int interval, boolean flag) {
    var listener = new ActionListener() {
    public void actionPerformed(ActionEvent event) {
    System.out.println("");
    if (flag) Toolkit.getDefaultToolkit().flag();
    }
    };
    }
    // 创建了一个新的对象listener, 这个类实现了ActionListener接口
    // 接口实现了actionPerformed方法

    // 可以是接口, 也可以是一个类
    // 因为匿名内部类没有名字, 所以没有构造器
  • 对比构造类对象和构造匿名内部类
    1
    2
    3
    4
    5
    6
    7
    8
    var a = new Person("1");
    var b = new Person("2") {...};
    // a 是类Person的一个对象
    // b 是匿名内部类的一个对象, 这个匿名内部类是Person的子类
    // 如果构造类的时候小括号后面跟上了大括号, 那么就是一个匿名内部类
    var c = new Object() {String name = "c";}
    System.out.println(c.name);
    // 如果声明c的类型为Object, c.name就无法编译了, 因为Object是不可指示的
  • 如果在一个方法中, 想要打印类名
    System.out.println(getClass())
  • 普通方法可以调用this, 但是静态方法没有this
  • 如果想要打印静态方法的类名, 可以使用匿名内部类
    System.out.println(new Object(){}.getClass().getEnclosingClass())
  • 这里的getEnclosingClass()是得到这个静态方法的外围类

静态内部类

  • 如果生成内部类只是为了隐藏这个类, 并不想生成这个内部类的引用, 可以使用static修饰
  • 比如要计算一个数组的最大值和最小值
    • 可以遍历数组两遍
    • 也可以定义一个类, 其中包含两个私有字段, 分别记录最大值和最小值
    • 但是这个类的类名可能会重复, 所以可以定义内部类隐藏类名
    • 可以将内部类声明为static的, 避免包含其他类的引用
    • 如果内部类是在一个静态方法中构造的, 则这个内部类必须要声明为静态内部类

代理

  • 代理可以在运行时创建一组给定接口的新类
  • 只有在编译时无法确定需要实现哪个接口的时候才需要使用代理
  • 代理类可以在运行时创建一个全新的类, 能够实现指定的接口
  • 一个代理类包含指定接口需要实现的方法, 以及Object类中的所有方法, 比如toString(), equals()
  • 必须提供一个调用处理器, 调用处理器是实现了InvocaitonHandler接口的类对象, 这个接口只有一个方法, Object invoke()
  • 只要调用代理方法, 就会调用这个invoke方法

创建代理对象

  • 需要使用Proxy类的newProxyInstance(), 包含三个参数

    1. 类加载器
    2. Class对象数组
    3. 一个调用处理器
  • 使用目的:

    1. 方法调用 路由到远程服务器
    2. 用户界面事件关联运行中的程序动作
    3. 调试跟踪方法调用
  • 定义一个TraceHandler包装器类存储一个包装的对象, invoke()打印所调用方法的名字和参数, 然后调用这个方法, 提供包装的对象作为隐式参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class TraceHandler implements InvocationHandler {
    private Object target;

    public new TraceHandler(Object t) {
    target = t;
    }

    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
    // print

    return m.invoke(target, args);
    }
    }

    Object val = ...;
    var handler = new TraceHandler(val);
    var interfaces = new Class[] {Comparable.class};
    Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] {Comparable.class}, handler);

特性

  1. 代理类是程序运行过程中动态创建的, 一旦创建就是常规的类
  2. 所有的代理类都扩展Proxy类, 一个代理类只有一个实例字段, 也就是调用处理器, 在Proxy超类中定义
    • 完成代理对象任务所需要的任何额外的数据都需要存储在调用处理器中
  3. 所有的代理类都需要覆盖Object类的toString(), equals(), hashCode(), 这些方法只是在调用处理器上调用invoke()
    • Object类中的其他方法, clone(), getClass()没有重新定义
  4. 没有定义代理类的名字, 虚拟机中的Proxy类会生成$Proxy开头的类名
  5. 一个特定的类加载器和一组接口, 只能有一个代理类, 同样可以使用Class proxyClass = Proxy.getProxyClass(null, interface)得到这个类
  6. 代理类一定是public, final的, 如果代理类实现的所有接口都是public的, 那么这个代理类就不属于任何包, 否则一定属于某一个包
  7. 可以通过调用Proxy.isProxyClass()检测一个特定的Class对象是否表示一个代理类
  • 调用一个目标代理的默认方法会触发调用处理器, 使用InvocationHandler接口的静态方法invokeDefault
    1
    2
    3
    4
    5
    6
    7
    InvocationHandler handler = (proxy, method, args) -> {
    if (method.isDefault()) {
    return InvocationHandler.invokeDefault(proxy, method, args);
    } else {
    return method.invoke(target, args);
    }
    }