Java编程思想之十四 类型信息

第十四章 类型信息

运行时类型信息使得你可以在程序运行时发现和使用类型信息

14.1 为什么需要RTTI

面向对象编程中基本的目的是:让代码只操作对基类的引用。
多态:

import java.util.*;

abstract class Shape {
  void draw() { System.out.println(this + ".draw()"); }
  abstract public String toString();
}

class Circle extends Shape {
  public String toString() { return "Circle"; }
}

class Square extends Shape {
  public String toString() { return "Square"; }
}

class Triangle extends Shape {
  public String toString() { return "Triangle"; }
}	

public class Shapes {
  public static void main(String[] args) {
    List<Shape> shapeList = Arrays.asList(
      new Circle(), new Square(), new Triangle()
    );
    for(Shape shape : shapeList)
      shape.draw();
  }
} /* Output:
Circle.draw()
Square.draw()
Triangle.draw()
*///:~

Rtti基本使用形式:所有类型转换都是在运行时进行正确性检查的。在运行时识别一个对象的类型。

14.2 Class对象

每当编写并且编译一个新类,就会产生一个Class对象。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为"类加载器"的子系统。
类加载器子系统实际上可以包含一条加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的所谓的可信类,它们通常是从本地盘加载的。
所有类都在第一个使用时,动态加载到JVM中。
Java程序在它开始运行之前并非被完全加载,其各个部分是在必须时才加载的。
类加载器首先检查这个类的class对象是否已经加载。如果没有加载,默认的类加载器就会根据类名查找.class文件。

//: typeinfo/toys/ToyTest.java
// Testing class Class.
package toys;

interface HasBatteries
{
}

interface Waterproof
{
}

interface Shoots
{
}

class Toy
{
    // Comment out the following default constructor
    // to see NoSuchMethodError from (*1*)
    Toy()
    {
    }

    Toy(int i)
    {
    }
}

class FancyToy extends Toy
        implements HasBatteries, Waterproof, Shoots
{
    FancyToy()
    {
        super(1);
    }
}

public class ToyTest
{
    static void printInfo(Class cc)
    {
        System.out.println("Class name: " + cc.getName() +
                " is interface? [" + cc.isInterface() + "]");
        System.out.println("Simple name: " + cc.getSimpleName());
        System.out.println("Canonical name : " + cc.getCanonicalName());
    }

    public static void main(String[] args)
    {
        Class c = null;
        try
        {
            c = Class.forName("toys.FancyToy");
        }
        catch (ClassNotFoundException e)
        {
            System.out.println("Can't find FancyToy");
            System.exit(1);
        }
        printInfo(c);
        for (Class face : c.getInterfaces())
            printInfo(face);
        Class up = c.getSuperclass();
        Object obj = null;
        try
        {
            // Requires default constructor:
            obj = up.newInstance();
        }
        catch (InstantiationException e)
        {
            System.out.println("Cannot instantiate");
            System.exit(1);
        }
        catch (IllegalAccessException e)
        {
            System.out.println("Cannot access");
            System.exit(1);
        }
        printInfo(obj.getClass());
    }
} /* Output:
Class name: typeinfo.toys.FancyToy is interface? [false]
Simple name: FancyToy
Canonical name : typeinfo.toys.FancyToy
Class name: typeinfo.toys.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name : typeinfo.toys.HasBatteries
Class name: typeinfo.toys.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name : typeinfo.toys.Waterproof
Class name: typeinfo.toys.Shoots is interface? [true]
Simple name: Shoots
Canonical name : typeinfo.toys.Shoots
Class name: typeinfo.toys.Toy is interface? [false]
Simple name: Toy
Canonical name : typeinfo.toys.Toy
*///:~

14.2.1 类字面常量

Java还提供了一个方法来生成对Class对象的引用,即使用类字面常量
FancyToy.class;
类字面常量不仅可以应用于普通的类,也可以应用于接口、数组、基本数据类型。
对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本数据类型的Class对象。
当使用.class创建对象的引用时,不会自动初始化该class对象。
为了使用类而做的准备工作实际包含三个步骤:

  1. 加载:这是由类加载器执行的。该步骤将查找字节码,并从这些字节码中创建一个Class对象。
  2. 链接:在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。
  3. 初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
//: typeinfo/ClassInitialization.java
import java.util.*;

class Initable {
  static final int staticFinal = 47;//编译期常量,不需要进行初始化就可以读取
  static final int staticFinal2 =
    ClassInitialization.rand.nextInt(1000);
  static {
    System.out.println("Initializing Initable");
  }
}

class Initable2 {
  static int staticNonFinal = 147;
  static {
    System.out.println("Initializing Initable2");
  }
}

class Initable3 {
  static int staticNonFinal = 74;
  static {
    System.out.println("Initializing Initable3");
  }
}

public class ClassInitialization {
  public static Random rand = new Random(47);
  public static void main(String[] args) throws Exception {
    Class initable = Initable.class;
    System.out.println("After creating Initable ref");
    // Does not trigger initialization:
    System.out.println(Initable.staticFinal);
    // Does trigger initialization:
    System.out.println(Initable.staticFinal2);
    // Does trigger initialization:
    System.out.println(Initable2.staticNonFinal);
    Class initable3 = Class.forName("Initable3");
    System.out.println("After creating Initable3 ref");
    System.out.println(Initable3.staticNonFinal);
  }
} /* Output:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
*///:~

如果一个static域不是final的,那么在对它访问时,总是要求在它被读取前,先进行链接(分配存储空间)和初始化(初始化存储空间)

14.2.2 泛化的Class引用

Class引用总是指向某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还包含该类的静态成员,因此,Class引用表达的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
通过允许你对Class引用所指向的Class对象的类型进行限定,将它的类型变得更具体一些。

public class GenericClassReferences
{
    public static void main(String[] args)
    {
        Class intClass = int.class;
        Class<Integer> genericIntClass = int.class;//只能指向具体的类型
        genericIntClass = Integer.class; // Same thing
        intClass = double.class;
        //genericIntClass = double.class; // Illegal
    }
} ///:~

通配符:? 表示任何事物。

public class WildcardClassReferences {
  public static void main(String[] args) {
    Class<?> intClass = int.class;
    intClass = double.class;
  }
} ///:~

Class优于平凡得class,即便他们是等价得。Class的好处表达你知道你选择的是非具体的版本。
将通配符和extends关键字结合使用:

public class BoundedClassReferences {
  public static void main(String[] args) {
    Class<? extends Number> bounded = int.class;
    bounded = double.class;
    bounded = Number.class;
    // Or anything else derived from Number.
  }
} ///:~

下面的例子,它存储了一个引用,稍后又产生了一个List,填充这个List对象使用newInstance()方法:

import java.util.*;

class CountedInteger {
  private static long counter;
  private final long id = counter++;
  public String toString() { return Long.toString(id); }
}

public class FilledList<T> {
  private Class<T> type;
  public FilledList(Class<T> type) { this.type = type; }	
  public List<T> create(int nElements) {
    List<T> result = new ArrayList<T>();
    try {
      for(int i = 0; i < nElements; i++)
        result.add(type.newInstance());
    } catch(Exception e) {
      throw new RuntimeException(e);
    }
    return result;
  }
  public static void main(String[] args) {
    FilledList<CountedInteger> fl =
      new FilledList<CountedInteger>(CountedInteger.class);
    System.out.println(fl.create(15));
  }
} /* Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
*///:~

newInstance()将返回对象的确切类型:

public class GenericToy
{
    public static void main(String[] args) throws Exception
    {
        Class<B> b = B.class;
        System.out.println("a Name:" + b.getName());
        B b1 = b.newInstance();
        System.out.println(b);
        A a = (A)b.getSuperclass().newInstance();
        System.out.println("getSuperclass.Name:"+a);
        A a1 = b.newInstance();
        System.out.println(a1);

        Object o = b.getSuperclass();
        System.out.println(o);
    }
}
class A{
    static    {
        System.out.println("AAAA");
    }
    public String toString() {
        return "a";
    }
}
class B extends A{
    static    {
        System.out.println("BBBB");
    }
    public String toString()    {
        return "b";
    }
}
14.2.3 新的转型语法

Class引用的转型语法cast()方法:接受参入参数对象,转换为引用类型

class Building {}
class House extends Building {}

public class ClassCasts {
  public static void main(String[] args) {
    Building b = new House();
    Class<House> houseType = House.class;
    House h = houseType.cast(b);
    h = (House)b; // ... or just do this.
  }
} ///:~

Class.asSubclass():允许你将一个类对象转型为更加具体的类型。

14.3 类型转换前先做检查

我们已知的RTTI形式包括:

  1. 传统的类型转换
  2. 代表对象的类型的CLass对象。查询Class对象获取允许时所需要的信息。
  3. 关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。

instanceof只可将其与命名空间类型进行比较,而不能与Class对象作比较。

14.4 注册工厂

使用工厂方法设计模式,将对象的创建工作交给类自己去完成,工厂方法可以被多态的调用,从而为你创建恰当类型的对象。
使用工厂创建对象:

//: typeinfo/RegisteredFactories.java
// Registering Class Factories in the base class.
import factory.*;
import java.util.*;

class Part {
  public String toString() {
    return getClass().getSimpleName();
  }
  static List<Factory<? extends Part>> partFactories =
    new ArrayList<Factory<? extends Part>>();	
  static {
    // Collections.addAll() gives an "unchecked generic
    // array creation ... for varargs parameter" warning.
    partFactories.add(new FuelFilter.Factory());
    partFactories.add(new AirFilter.Factory());
    partFactories.add(new CabinAirFilter.Factory());
    partFactories.add(new OilFilter.Factory());
    partFactories.add(new FanBelt.Factory());
    partFactories.add(new PowerSteeringBelt.Factory());
    partFactories.add(new GeneratorBelt.Factory());
  }
  private static Random rand = new Random(47);
  public static Part createRandom() {
    int n = rand.nextInt(partFactories.size());
    return partFactories.get(n).create();
  }
}	

class Filter extends Part {}

class FuelFilter extends Filter {
  // Create a Class Factory for each specific type:
  public static class Factory
  implements factory.Factory<FuelFilter> {
    public FuelFilter create() { return new FuelFilter(); }
  }
}

class AirFilter extends Filter {
  public static class Factory
  implements factory.Factory<AirFilter> {
    public AirFilter create() { return new AirFilter(); }
  }
}	

class CabinAirFilter extends Filter {
  public static class Factory
  implements factory.Factory<CabinAirFilter> {
    public CabinAirFilter create() {
      return new CabinAirFilter();
    }
  }
}

class OilFilter extends Filter {
  public static class Factory
  implements factory.Factory<OilFilter> {
    public OilFilter create() { return new OilFilter(); }
  }
}	

class Belt extends Part {}

class FanBelt extends Belt {
  public static class Factory
  implements factory.Factory<FanBelt> {
    public FanBelt create() { return new FanBelt(); }
  }
}

class GeneratorBelt extends Belt {
  public static class Factory
  implements factory.Factory<GeneratorBelt> {
    public GeneratorBelt create() {
      return new GeneratorBelt();
    }
  }
}	

class PowerSteeringBelt extends Belt {
  public static class Factory
  implements factory.Factory<PowerSteeringBelt> {
    public PowerSteeringBelt create() {
      return new PowerSteeringBelt();
    }
  }
}	

public class RegisteredFactories {
  public static void main(String[] args) {
    for(int i = 0; i < 10; i++)
      System.out.println(Part.createRandom());
  }
} /* Output:
GeneratorBelt
CabinAirFilter
GeneratorBelt
AirFilter
PowerSteeringBelt
CabinAirFilter
FuelFilter
PowerSteeringBelt
PowerSteeringBelt
FuelFilter
*///:~

14.5 instanceof与Class的等价性

在查询类型信息时,以instanceof的形式与直接比较Class对象有一个很重要的差别,下面看看这些差别:

//: typeinfo/FamilyVsExactType.java
// The difference between instanceof and class

class Base {}
class Derived extends Base {}	

public class FamilyVsExactType {
  static void test(Object x) {
    System.out.println("Testing x of type " + x.getClass());
    System.out.println("x instanceof Base " + (x instanceof Base));
    System.out.println("x instanceof Derived "+ (x instanceof Derived));
    System.out.println("Base.isInstance(x) "+ Base.class.isInstance(x));
    System.out.println("Derived.isInstance(x) " +
      Derived.class.isInstance(x));
    System.out.println("x.getClass() == Base.class " +
      (x.getClass() == Base.class));
    System.out.println("x.getClass() == Derived.class " +
      (x.getClass() == Derived.class));
    System.out.println("x.getClass().equals(Base.class)) "+
      (x.getClass().equals(Base.class)));
    System.out.println("x.getClass().equals(Derived.class)) " +
      (x.getClass().equals(Derived.class)));
  }
  public static void main(String[] args) {
    test(new Base());
    test(new Derived());
  }	
} /* Output:
Testing x of type class typeinfo.Base
x instanceof Base true
x instanceof Derived false
Base.isInstance(x) true
Derived.isInstance(x) false
x.getClass() == Base.class true
x.getClass() == Derived.class false
x.getClass().equals(Base.class)) true
x.getClass().equals(Derived.class)) false
Testing x of type class typeinfo.Derived
x instanceof Base true
x instanceof Derived true
Base.isInstance(x) true
Derived.isInstance(x) true
x.getClass() == Base.class false
x.getClass() == Derived.class true
x.getClass().equals(Base.class)) false
x.getClass().equals(Derived.class)) true
*///:~

14.6 反射:运行时的类信息

在编译时,编译器必须知道所要通过RTTI来处理的类。
反射提供了一种机制——用来检查可用的方法,并返回方法名。
人们想要在运行时获取类的信息的另一个动机,希望在跨网络的平台上创建和运行对象的能力。这被称为远程方法调用(RMI),它允许将一个Java程序分布到多台机器上。
Class类与java.lang.reflect类库一起对反射概念进行了支持。这些类型的对象都是又JVM在运行时创建的,用以表示未知类里对应的成员。
当通过反射与一个未知类型的对象打交道时,JVM只是简单的检查这个对象,看它属于哪个特定的类。在用它做其他事情之前必须先加载那个类的Class对象。因此,那个类的.Class文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络获取。所以RTTI和反射之间真正的区别只在于du:对RTTI来说,编译器在编译时打开和检查.class文件,对于反射来说:.class在编译时是不可获取的,所有在运行时打开和检查.class文件。

14.6.1 类方法提取器

反射机制提供了一种方法,使我们能够编写可以自动展示完整接口的简单工具:


14.7 动态代理

代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替实际对象的对象。

interface Interface {
  void doSomething();
  void somethingElse(String arg);
}
class RealObject implements Interface {
  public void doSomething() { System.out.println("doSomething"); }
  public void somethingElse(String arg) {
    System.out.println("somethingElse " + arg);
  }
}	

class SimpleProxy implements Interface {
  private Interface proxied;
  public SimpleProxy(Interface proxied) {
    this.proxied = proxied;
  }
  public void doSomething() {
    System.out.println("SimpleProxy doSomething");
    proxied.doSomething();
  }
  public void somethingElse(String arg) {
    System.out.println("SimpleProxy somethingElse " + arg);
    proxied.somethingElse(arg);
  }
}	

class SimpleProxyDemo {
  public static void consumer(Interface iface) {
    iface.doSomething();
    iface.somethingElse("bonobo");
  }
  public static void main(String[] args) {
    consumer(new RealObject());
    consumer(new SimpleProxy(new RealObject()));
  }
}

Java的动态代理可以动态创建并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。

import java.lang.reflect.*;

class DynamicProxyHandler implements InvocationHandler {
  private Object proxied;
  public DynamicProxyHandler(Object proxied) {
    this.proxied = proxied;
  }
  public Object invoke(Object proxy, Method method, Object[] args)
  throws Throwable {
    System.out.println("**** proxy: " + proxy.getClass() +
      ", method: " + method + ", args: " + args);
    if(args != null)
      for(Object arg : args)
        System.out.println("  " + arg);
    return method.invoke(proxied, args);
  }
}	

class SimpleDynamicProxy {
  public static void consumer(Interface iface) {
    iface.doSomething();
    iface.somethingElse("bonobo");
  }
  public static void main(String[] args) {
    RealObject real = new RealObject();
    consumer(real);
    // Insert a proxy and call again:
    Interface proxy = (Interface)Proxy.newProxyInstance(
      Interface.class.getClassLoader(),
      new Class[]{ Interface.class },
      new DynamicProxyHandler(real));
    consumer(proxy);
  }
} 

动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个实际对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。

14.8 空对象

引入空对象的思想是很有用的,它可以接收传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在的任何真实对象的值。通过这种方式,你可以假设所有的对象都是有效的,而不必浪费时间去检查null。
即使空对象可以响应实际对象可以响应的所有消息,任需要测试是否为空:

public interface Operation {
  String description();
  void command();
} 
class Person {
  public final String first;
  public final String last;
  public final String address;
  // etc.
  public Person(String first, String last, String address){
    this.first = first;
    this.last = last;
    this.address = address;
  }	
  public String toString() {
    return "Person: " + first + " " + last + " " + address;
  }
  public static class NullPerson
  extends Person implements Null {
    private NullPerson() { super("None", "None", "None"); }
    public String toString() { return "NullPerson"; }
  }
  public static final Person NULL = new NullPerson();
} 
class Position {
  private String title;
  private Person person;
  public Position(String jobTitle, Person employee) {
    title = jobTitle;
    person = employee;
    if(person == null)
      person = Person.NULL;
  }
  public Position(String jobTitle) {
    title = jobTitle;
    person = Person.NULL;
  }	
  public String getTitle() { return title; }
  public void setTitle(String newTitle) {
    title = newTitle;
  }
  public Person getPerson() { return person; }
  public void setPerson(Person newPerson) {
    person = newPerson;
    if(person == null)
      person = Person.NULL;
  }
  public String toString() {
    return "Position: " + title + " " + person;
  }
} 
//: typeinfo/Staff.java
import java.util.*;

public class Staff extends ArrayList<Position> {
  public void add(String title, Person person) {
    add(new Position(title, person));
  }
  public void add(String... titles) {
    for(String title : titles)
      add(new Position(title));
  }
  public Staff(String... titles) { add(titles); }
  public boolean positionAvailable(String title) {
    for(Position position : this)
      if(position.getTitle().equals(title) &&
         position.getPerson() == Person.NULL)
        return true;
    return false;
  }	
  public void fillPosition(String title, Person hire) {
    for(Position position : this)
      if(position.getTitle().equals(title) &&
         position.getPerson() == Person.NULL) {
        position.setPerson(hire);
        return;
      }
    throw new RuntimeException(
      "Position " + title + " not available");
  }	
  public static void main(String[] args) {
    Staff staff = new Staff("President", "CTO",
      "Marketing Manager", "Product Manager",
      "Project Lead", "Software Engineer",
      "Software Engineer", "Software Engineer",
      "Software Engineer", "Test Engineer",
      "Technical Writer");
    staff.fillPosition("President",
      new Person("Me", "Last", "The Top, Lonely At"));
    staff.fillPosition("Project Lead",
      new Person("Janet", "Planner", "The Burbs"));
    if(staff.positionAvailable("Software Engineer"))
      staff.fillPosition("Software Engineer",
        new Person("Bob", "Coder", "Bright Light City"));
    System.out.println(staff);
  }
} /* Output:	
[Position: President Person: Me Last The Top, Lonely At, Position: CTO NullPerson, Position: Marketing Manager NullPerson, Position: Product Manager NullPerson, Position: Project Lead Person: Janet Planner The Burbs, Position: Software Engineer Person: Bob Coder Bright Light City, Position: Software Engineer NullPerson, Position: Software Engineer NullPerson, Position: Software Engineer NullPerson, Position: Test Engineer NullPerson, Position: Technical Writer NullPerson]
*///:~

使用命令模式:

//: typeinfo/Robot.java
import java.util.*;
public interface Robot {
  String name();
  String model();
  List<Operation> operations();
  class Test {
    public static void test(Robot r) {
      if(r instanceof Null)
        System.out.println("[Null Robot]");
      System.out.println("Robot name: " + r.name());
      System.out.println("Robot model: " + r.model());
      for(Operation operation : r.operations()) {
        System.out.println(operation.description());
        operation.command();
      }
    }
  }
} ///:~
//: typeinfo/SnowRemovalRobot.java
import java.util.*;

public class SnowRemovalRobot implements Robot {
  private String name;
  public SnowRemovalRobot(String name) {this.name = name;}
  public String name() { return name; }
  public String model() { return "SnowBot Series 11"; }
  public List<Operation> operations() {
    return Arrays.asList(
      new Operation() {
        public String description() {
          return name + " can shovel snow";
        }
        public void command() {
          System.out.println(name + " shoveling snow");
        }
      },	
      new Operation() {
        public String description() {
          return name + " can chip ice";
        }
        public void command() {
          System.out.println(name + " chipping ice");
        }
      },
      new Operation() {
        public String description() {
          return name + " can clear the roof";
        }
        public void command() {
          System.out.println(name + " clearing roof");
        }
      }
    );
  }	
  public static void main(String[] args) {
    Robot.Test.test(new SnowRemovalRobot("Slusher"));
  }
} /* Output:
Robot name: Slusher
Robot model: SnowBot Series 11
Slusher can shovel snow
Slusher shoveling snow
Slusher can chip ice
Slusher chipping ice
Slusher can clear the roof
Slusher clearing roof
*///:~
//: typeinfo/NullRobot.java
// Using a dynamic proxy to create a Null Object.
import java.lang.reflect.*;
import java.util.*;
class NullRobotProxyHandler implements InvocationHandler {
  private String nullName;
  private Robot proxied = new NRobot();
  NullRobotProxyHandler(Class<? extends Robot> type) {
    nullName = type.getSimpleName() + " NullRobot";
  }
  private class NRobot implements Null, Robot {
    public String name() { return nullName; }
    public String model() { return nullName; }
    public List<Operation> operations() {
      return Collections.emptyList();
    }
  }	
  public Object
  invoke(Object proxy, Method method, Object[] args)
  throws Throwable {
    return method.invoke(proxied, args);
  }
}
public class NullRobot {
  public static Robot
  newNullRobot(Class<? extends Robot> type) {
    return (Robot)Proxy.newProxyInstance(
      NullRobot.class.getClassLoader(),
      new Class[]{ Null.class, Robot.class },
      new NullRobotProxyHandler(type));
  }	
  public static void main(String[] args) {
    Robot[] bots = {
      new SnowRemovalRobot("SnowBee"),
      newNullRobot(SnowRemovalRobot.class)
    };
    for(Robot bot : bots)
      Robot.Test.test(bot);
  }
} /* Output:
Robot name: SnowBee
Robot model: SnowBot Series 11
SnowBee can shovel snow
SnowBee shoveling snow
SnowBee can chip ice
SnowBee chipping ice
SnowBee can clear the roof
SnowBee clearing roof
[Null Robot]
Robot name: SnowRemovalRobot NullRobot
Robot model: SnowRemovalRobot NullRobot
*///:~

14.8.1 模拟对象与桩

空对象的逻辑变体是模拟对象和桩。模拟对象和桩都只是假扮可以传递实际信息的存活对象,而不是像空对象那样可以称为null的一种更加智能化的替代品。

14.9 接口与类型信息

interface关键字的一种重要目标就是允许程序员隔离构建,进而降低耦合性。通过类型信息,这种耦合性还是会传播出去——接口并非是对解耦的一种无懈可击的保障。

package interfacea;

public interface A {
  void f();
} 
import interfacea.*;

class B implements A {
  public void f() {}
  public void g() {}
}

public class InterfaceViolation {
  public static void main(String[] args) {
    A a = new B();
    a.f();
    // a.g(); // Compile error
    System.out.println(a.getClass().getName());
    if(a instanceof B) {
      B b = (B)a;
      b.g();
    }
  }
}

最简单的方式是对实现使用包访问权限,这样在包外部的客户端就不能看见它了。

package packageaccess;
import interfacea.*;
class C implements A {
  public void f() { System.out.println("public C.f()"); }
  public void g() { System.out.println("public C.g()"); }
  void u() { System.out.println("package C.u()"); }
  protected void v() { System.out.println("protected C.v()"); }
  private void w() { System.out.println("private C.w()"); }
}

public class HiddenC {
  public static A makeA() { return new C(); }
}

这里makeA返回C类型,但在包在并不能使用到C。但是反射却仍旧可以调用:

import interfacea.*;
import packageaccess.*;
import java.lang.reflect.*;

public class HiddenImplementation {
  public static void main(String[] args) throws Exception {
    A a = HiddenC.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Compile error: cannot find symbol 'C':
    /* if(a instanceof C) {
      C c = (C)a;
      c.g();
    } */
    // Oops! Reflection still allows us to call g():
    callHiddenMethod(a, "g");
    // And even methods that are less accessible!
    callHiddenMethod(a, "u");
    callHiddenMethod(a, "v");
    callHiddenMethod(a, "w");
  }
  static void callHiddenMethod(Object a, String methodName)
  throws Exception {
    Method g = a.getClass().getDeclaredMethod(methodName);
    g.setAccessible(true);
    g.invoke(a);
  }
} /* Output:
public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

即使是内部类,反思仍旧可以调用到:

import interfacea.*;
class InnerA {
  private static class C implements A {
    public void f() { System.out.println("public C.f()"); }
    public void g() { System.out.println("public C.g()"); }
    void u() { System.out.println("package C.u()"); }
    protected void v() { System.out.println("protected C.v()"); }
    private void w() { System.out.println("private C.w()"); }
  }
  public static A makeA() { return new C(); }
}	

public class InnerImplementation {
  public static void main(String[] args) throws Exception {
    A a = InnerA.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Reflection still gets into the private class:
    HiddenImplementation.callHiddenMethod(a, "g");
    HiddenImplementation.callHiddenMethod(a, "u");
    HiddenImplementation.callHiddenMethod(a, "v");
    HiddenImplementation.callHiddenMethod(a, "w");
  }
}

匿名类也一样:

import interfacea.*;
class AnonymousA {
  public static A makeA() {
    return new A() {
      public void f() { System.out.println("public C.f()"); }
      public void g() { System.out.println("public C.g()"); }
      void u() { System.out.println("package C.u()"); }
      protected void v() { System.out.println("protected C.v()"); }
      private void w() { System.out.println("private C.w()"); }
    };
  }
}
public class AnonymousImplementation {
  public static void main(String[] args) throws Exception {
    A a = AnonymousA.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Reflection still gets into the anonymous class:
    HiddenImplementation.callHiddenMethod(a, "g");
    HiddenImplementation.callHiddenMethod(a, "u");
    HiddenImplementation.callHiddenMethod(a, "v");
    HiddenImplementation.callHiddenMethod(a, "w");
  }
} /* Output:
public C.f()
AnonymousA$1
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

posted on 2019-08-01 10:26  Mr.Tan&  阅读(196)  评论(0编辑  收藏  举报

导航