Java编程思想--14类型信息

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

14.1 为什么需要RTTI

RTTI(Run-Time Type Identification):面向对象的基本目的是,让代码只操纵对基类的引用。如果在再派生出一个新类来拓展程序,那么原来的代码不会受到影响。但是有些时候在运行时需要确切知道某一对象具体类型,这时候就需要对对象的引用进行转换,使其变成具体类对象的引用。

例如,当使用容器存储对象时,需要进行上转型为Object,再取出时,但是上转型的过程中丢失了对象具体类型。再取出对象时需要将结果转型回具体的类。这就是RTTI的具体工作形式。

使用RTTI,可以查询对象的具体类型,从而挑出或者剔除特例。

14.2 Class对象

Class是一个特殊的对象,它包含了与类有关的信息。Class对象是用来创建类的所有的“常规对象”。Java使用Class对象来执行其RTTI,包括类型转换操作。除此之外,Class类还有大量其他RTTI方法。

类是程序的一部分,每个类都有一个Class对象。换言之,每当编写且编译一个新类,都会产生一个Class对象(保存在.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统。

类加载器子系统实际上可以包含一条加载器链,但只有一个原生加载器,它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java API类,它们通常是从本地加载的。在这条链中通常不需要添加额外的类加载器,但是如果有特殊要求(如以特殊方式下载的类),那么就需要挂接额外的类。

所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的使用时,就会加载这个类(间接证明了构造器是类的静态方法)。

Java使用的是动态加载的的方式,类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,则会根据引用的路径查找.class文件(先验证,后加载)。

Class对象

Class.forName(String s)

forName是Class的一个静态方法,该方法能够根据输入的字符串查找到要加载的类。并返回该类Class对象。拥有某个类的Class对象,就可以创建这个类的对象。如果输入字符串s非法,则会抛出ClassNotFoundException。

	try {
      c = Class.forName("chapter14.toys.FancyToy");
    } catch(ClassNotFoundException e) {
      System.out.println("Can't find FancyToy");
      System.exit(1);
    }

Class对象其他方法:

  • getName():产生全限定类名
  • getSimpleName():产生不包含包的类名
  • getCanonicalName():产生全限定类名
  • isInterface():是否为接口
  • getSuperClass():获取基类的Class对象
  • newInstance():实现虚拟构造器,创建该类的对象。允许不知道确切的类型,但是无路如何要创建自己
  • getInterfaces():获取当前类实现的接口列表
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("chapter14.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。这样更简单且更安全(在编译时就会受到检查),更高效(根除了对forName的调用)。类的字面常量还可以应用于接口数组,以及基本的数据类型。

字面类常量等价于
boolean.classBoolean.TYPE
char.classCharacter.TYPE
byte.classByte.TYPE
short.classCharacter.TYPE
int.classInteger.TYPE
long.classLong.TYPE
float.classFloat.TYPE
double.classDouble.TYPE
void.classVoid.TYPE

作者建议使用“.class”的形式,以保持与普通类的一致性。当使用“.class”来创建对象时,不会初始化该Class对象,为了使用类而做的准备包含三个步骤:

  • 加载。加载类的字节码,并从字节码中创建一个Class对象。
  • 链接。在静态域中为类中的字节码分配存储空间,必要的话,将解析这个类创建的对其他类的所有引用。
  • 初始化。如果该类具有超类,则对其初始化,执行静态初始化器与静态初始化块。

初始化被延迟到对静态方法或者非常数静态域进行首次引用时才执行。

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("chapter14.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
*///:~

初始化有效的实现了尽可能的“惰性”。从initiable引用的创建可以看到,仅适用.class语法来获取对类的引用不会发生初始化。但是为了产生Class引用,Class.forName()立即就进行了初始化。

14.2.2 泛化Class的引用

为了适用泛化Class引用时放松限制,Java允许使用通配符?表示任何事物。如果行进一步限制引用,可以使用extend关键字。

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,填充这个List的对象是使用newInstance()方法,通过该引用生成的:

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

public class FilledList<T> {
  //声明了一个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]
*///:~

注意这个类必须假设与它一同工作的任何类型都具有一个默认的构造器(无参构造器),并且如果不符合条件,将会产生一个异常。但该异常不会在编译时被检查到。

当将泛型应用于Class对象时,newInstance()将返回该对象的确切类型,而不仅仅只是在ToyTest.java中看到的基本的Object。

public class GenericToyTest {
    public static void main(String[] args) throws Exception {
      Class<FancyToy> ftClass = FancyToy.class;
      // Produces exact type:
      FancyToy fancyToy = ftClass.newInstance();
      Class<? super FancyToy> up = ftClass.getSuperclass();
      // This won't compile:
      // Class<Toy> up2 = ftClass.getSuperclass();
      // Only produces Object:
      Object obj = up.newInstance();
    }
  } ///:~

14.2.3 新的转型语法

Java SE5添加了用于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.
  }
} ///:~

14.3 类型转换前先做检查

使用 instanceof 判断对象是否属于某个特定类(或接口)的对象。

package chapter14;
interface Individual{
    public void move();
}

class Person implements Individual{
    public void move(){}
}

class Dog implements Individual{
    public void move(){}
}
public class InstanceOf {
    public static void main(String[] args) {
        Person p = new Person();
        Dog d = new Dog();

        System.out.println(p instanceof Individual);
        System.out.println(d instanceof Individual);
    }
}

14.3.1 使用字面类常量

		try {
            Person p1 = Person.class.newInstance() ;
            System.out.println(p instanceof Individual);
        } catch (Exception e) {
            //TODO: handle exception
        }

14.3.2 动态的instanceof

Class.isInstance方法提供了一种动态测试对象的途径。System.out.println(Person.class.isInstance(d));

14.4 注册工厂

14.6 instanceof与Class的等价性

instnceof与isInstance完全等价。equals与==完全等价。但是前者包括派生类,后者仅关心当前类的类型。

14.6 反射:运行时的类信息

RTTI:允许在运行时获取对象的类型信息,但是在编译时必须要知道所有要通过RTTI来处理的类。
反射:java.lang.reflect中提供的MethodConstructor配合Class对象的getMethods()getConstructors()方法,可以对未知对象的方法进行检查与调用。

package chapter14;

//: typeinfo/ShowMethods.java
// Using reflection to show all the methods of a class,
// even if the methods are defined in the base class.
// {Args: ShowMethods}
import java.lang.reflect.*;
import java.util.regex.*;

class ShowMethods {
  private static String usage =
    "usage:\n" +
    "ShowMethods qualified.class.name\n" +
    "To show all methods in class or:\n" +
    "ShowMethods qualified.class.name word\n" +
    "To search for methods involving 'word'";
  private static Pattern p = Pattern.compile("\\w+\\.|native |final ");
  public static void main(String[] args) {
    if(args.length < 1) {
      System.out.println(usage);
      System.exit(0);
    }
    int lines = 0;
    try {
      Class<?> c = Class.forName(args[0]);
      Method[] methods = c.getMethods();
      Constructor[] ctors = c.getConstructors();
      if(args.length == 1) {
        for(Method method : methods){
            // System.out.println(method.toString());
            System.out.println(
                p.matcher(method.toString()).replaceAll(""));
        }
          
        for(Constructor ctor : ctors)
          System.out.println(p.matcher(ctor.toString()).replaceAll(""));
        lines = methods.length + ctors.length;
      } else {
        for(Method method : methods)
          if(method.toString().indexOf(args[1]) != -1) {
            System.out.println(
              p.matcher(method.toString()).replaceAll(""));
            lines++;
          }
        for(Constructor ctor : ctors)
          if(ctor.toString().indexOf(args[1]) != -1) {
            System.out.println(p.matcher(
              ctor.toString()).replaceAll(""));
            lines++;
          }
      }
    } catch(ClassNotFoundException e) {
      System.out.println("No such class: " + e);
    }
  }
} /* Output:
public static void main(String[])
public native int hashCode()
public final native Class getClass()
public final void wait(long,int) throws InterruptedException
public final void wait() throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public final native void notify()
public final native void notifyAll()
public ShowMethods() //默认的构造器
*///:~ 

// java chapter14\ShowMethods.java chapter14.ShowMethods

14.7 动态代理

代理是基本的设计模式之一,代理的作用是插入新的对象来代替原有对象,以提供额外的或不同的操作。(如计算某个方法的被调用次数,或者某个方法的调用开销)

Java的动态代理可以动态的创建代理并且动态的处理对所代理方法的调用。java.lang.reflect

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);
    }
} /* Output: (95% match)	
doSomething
somethingElse bonobo
**** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null
doSomething
**** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@42e816
  bonobo
somethingElse bonobo
*///:~

可以通过静态方法java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)创建动态代理。第一个参数是代理的类的类加载器,第二个参数是需要代理类实现的接口列表,第三个参数是调用处理程序。

14.8 空对象

在使用代理时,可以定义空对象(单例模式),使其能够完成代理的所需的动作,进而避免频繁的检查null

14.9 接口与类型信息

interface关键字允许程序员隔离构件,进而降低耦合性,但是这种耦合性还是会传播出去。

package chapter14.interfacea;
public interface A {
    void f();
}
package chapter14.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();
        System.out.println(a.getClass().getName());
        if(a instanceof B){
            B b = (B) a;
            b.g();
        }
    }
}

在该例中,用户依然能通过类型检查与向下转型来操作B。为了实现隐藏,可以将类的访问权限设置成包的访问权限。但是这一设置仍会被反射机制打断。通过反射,仍旧可以到达并调用所有方法,甚至是private方法(内部类、匿名类、域)。通过知道方法名,然后在其Method对象上调用setAccessible(true)方法。

虽然可以通过发布编译后的代码来阻止这种情况,但是并不能解决问题。使用javap可以突破这一限制javap -private C.class

package chapter14.interfacea;

class C implements A{
    public void f(){
        System.out.println("public C.f()");
    }
    public void g(){
        System.out.println("public C.g()");
    }
    public void u(){
        System.out.println("public 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();
    }
}
package chapter14;

import java.lang.reflect.Method;

import chapter14.interfacea.*;

public class HiddenImplementation {
    public static void main(String[] args) {
        A a = HiddenC.makeA();
        a.f();
        System.out.print(a.getClass().getName());
        // if(a instanceof C){
        // C c = (C) a;
        // }
        try {
            callHiddenMethod(a, "g");
            callHiddenMethod(a, "u");
            callHiddenMethod(a, "v");
            callHiddenMethod(a, "w");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    static void callHiddenMethod(Object a, String methodName) throws Exception{
        Method g = a.getClass().getDeclaredMethod(methodName);
        g.setAccessible(true);
        g.invoke(a);
    }
}

对于字段,声明为final的基本数据类型与String不会被反射修改,而Integer对象却会被修改。

package chapter14;

import java.lang.reflect.Field;

class WithPrivateFinalField{
    private int i = 1;
    private final String s = "I'm totolly safe!";
    private Integer j = 2;
    private final Integer k = 3;
    private String s2 = "I'm safe?";
    public String toString(){
        return "i=" + i + ", j=" + j + ", k=" + k + ", s1="+ s +", s2=" + s2;
    }
}

public class ModifyingPrivateFields {
    public static void main(String[] args) {
        WithPrivateFinalField w = new WithPrivateFinalField();
        System.out.println(w.toString());
        try {
            // public int i
            Field fi = w.getClass().getDeclaredField("i");
            fi.setAccessible(true);
            fi.setInt(w, -1);
            // peivate Integer j
            Field fj = w.getClass().getDeclaredField("j");
            fj.setAccessible(true);
            fj.set(w, Integer.valueOf(-1));
            // // private final Integer k
            Field fk = w.getClass().getDeclaredField("k");
            fk.setAccessible(true);
            fk.set(w, Integer.valueOf(-1));
            

            // // private final String s
            Field fs = w.getClass().getDeclaredField("s");
            fs.setAccessible(true);
            fs.set(w, "No! you are not!");
            
            // // private String s2
            Field fs2 = w.getClass().getDeclaredField("s2");
            fs2.setAccessible(true);
            fs2.set(w, "No! you are not!");
            System.out.println(w.toString());
        } catch (Exception e) {
            //TODO: handle exception
            e.printStackTrace();
        }
    }
}

14.10 总结

  1. RTTI允许通过匿名基类的对象的引用来发现类型信息。
  2. RTTI有时能解决效率问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值