Thinking in Java:第十四章 类型信息

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

14.1 为什么需要RTTI

         在java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI的含义:在运行时,识别一个对象的类型。

14.2 Class对象

        Class对象就是用来创建类的所有的”常规“对象的。

         所有的类都是在第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。因此,诗意哦那个new操作符创建类的新对象也会被当作对类的静态成员的引用。

        java程序在它开始运行之前并非完全加载,其各个部分是在必需时才加载的。

//: typeinfo/SweetShop.java
// Examination of the way the class loader works.
import static net.mindview.util.Print.*;

class Candy {
  static { print("Loading Candy"); }
}

class Gum {
  static { print("Loading Gum"); }
}

class Cookie {
  static { print("Loading Cookie"); }
}

public class SweetShop {
  public static void main(String[] args) {	
    print("inside main");
    new Candy();
    print("After creating Candy");
    try {
      Class.forName("Gum");
    } catch(ClassNotFoundException e) {
      print("Couldn't find Gum");
    }
    print("After Class.forName(\"Gum\")");
    new Cookie();
    print("After creating Cookie");
  }
} /* Output:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
*///:~
Class类

Class类(在java.lang包中,Instances of the class Classrepresent classes and interfaces in a running Javaapplication):
        在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息;
       获取Class实例的三种方式:

  •  利用对象调用getClass()方法获取该对象的Class实例;
  • 使用Class类的静态方法forName(),用类的名字获取一个Class实例(staticClass forName(String className)  Returns the Classobject associated with the class or interface with the given stringname. );
  • 运用.class的方式来获取Class实例,对于基本数据类型的封装类,还可以采用.TYPE来获取相对应的基本数据类型的Class实例
       在newInstance()调用类中缺省的构造方法 ObjectnewInstance()(可在不知该类的名字的时候,常见这个类的实例) Creates a new instance of the class represented by this Classobject.
         在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象;

public class ClassTest {
     public static void main(String [] args)throws Exception{
         String str1="abc";
         Class cls1=str1.getClass();
         Class cls2=String.class;
         Class cls3=Class.forName("java.lang.String");
         System.out.println(cls1==cls2);
         System.out.println(cls1==cls3);
     }
 }
返回结果为:true,true.
解释:虚拟机只会产生一份字节码, 用这份字节码可以产生多个实例对象。

         Class的newInstance()方法是实现”虚拟构造器“的一种途径,虚拟构造器允许你申明:”我不知道你的确切类型,但是无论如何要正确地创建你自己“。这里必须要求类有一个默认的构造器。

/**  
 * 2012-2-6  
 * Administrator  
 */ 
/**  
 * @author: 梁焕月   
 * 文件名:TestClass.java   
 * 时间:2012-2-6上午10:01:52    
 */ 
public class TestClass {  
public  static void main(String[] args)  
{  
try {  
//测试Class.forName()  
Class testTypeForName=Class.forName("TestClassType");          
System.out.println("testForName---"+testTypeForName);  
//测试类名.class  
Class testTypeClass=TestClassType.class;  
System.out.println("testTypeClass---"+testTypeClass);  
//测试Object.getClass()  
TestClassType testGetClass= new TestClassType();  
System.out.println("testGetClass---"+testGetClass.getClass());  
} catch (ClassNotFoundException e) {  
// TODO Auto-generated catch block  
e.printStackTrace();  
}  
}  
}  
 class TestClassType{  
//构造函数  
public TestClassType(){  
System.out.println("----构造函数---");  
}  
//静态的参数初始化  
static{  
System.out.println("---静态的参数初始化---");  
}  
//非静态的参数初始化  
{  
System.out.println("----非静态的参数初始化---");  
}          
} 
测试的结果如下:

---静态的参数初始化---

testForName---class TestClassType

testTypeClass---class TestClassType

----非静态的参数初始化---

----构造函数---

testGetClass---class TestClassType

        根据结果可以发现,三种生成的Class对象一样的。并且三种生成Class对象只打印一次“静态的参数初始化”。
        我们知道,静态的方法属性初始化,是在加载类的时候初始化。而非静态方法属性初始化,是new类实例对象的时候加载。
         因此,这段程序说明,三种方式生成Class对象,其实只有一个Class对象。在生成Class对象的时候,首先判断内存中是否已经加载。所以,生成Class对象的过程其实是如此的:
        当我们编写一个新的java类时,JVM就会帮我们编译成class对象,存放在同名的.class文件中。在运行时,当需要生成这个类的对象,JVM就会检查此类是否已经装载内存中。若是没有装载,则把.class文件装入到内存中。若是装载,则根据class文件生成实例对象。

14.2.1 类字面常量

        类字面常量不仅可以用于普通的类,也可以应用于接口、数组以及基本的数据类型。

       有一点很有趣,但是用”.class“来创建对Class对象的引用时,不会自动的初始化该Class对象。为了是用类而做的准备工作实际包含三个步骤:

  1. 加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个Class对象。
  2. 链接。在链接阶段将验证类的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。
  3. 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化模块。
        初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行。
       如果一个static域不是final的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间)。

14.2.2 泛化的Class引用
  
        普通的类引用可以被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。
        通配符”?“表示"任何事物"。
//: typeinfo/WildcardClassReferences.java

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

//: typeinfo/BoundedClassReferences.java

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.
  }
} ///:~

         newInstance()将返回该对象的确切类型。
         getSuperClass()方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型。
        cast()方法接受参数对象,并将其转型为Class引用的类型。

14.3 类型转换前先做检查

        RTTI形式包括:
  •        传统的类型转换;
  • 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
  • 关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。
        如果不使用显式的类型转换,编译器就不允许你执行向下转型赋值,以告知编译器你拥有额外的信息,这些信息使你知道该类型是某种特定类型。
      
        instanceof有比较严格的限制:只可将其与命名类型进行比较,而不能与Class对象作比较。
//: typeinfo/PetCount3.java
// Using isInstance()
import typeinfo.pets.*;
import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*;

public class PetCount3 {
  static class PetCounter
  extends LinkedHashMap<Class<? extends Pet>,Integer> {
    public PetCounter() {
      super(MapData.map(LiteralPetCreator.allTypes, 0));
    }
    public void count(Pet pet) {
      // Class.isInstance() eliminates instanceofs:
      for(Map.Entry<Class<? extends Pet>,Integer> pair
          : entrySet())
        if(pair.getKey().isInstance(pet))
          put(pair.getKey(), pair.getValue() + 1);
    }	
    public String toString() {
      StringBuilder result = new StringBuilder("{");
      for(Map.Entry<Class<? extends Pet>,Integer> pair
          : entrySet()) {
        result.append(pair.getKey().getSimpleName());
        result.append("=");
        result.append(pair.getValue());
        result.append(", ");
      }
      result.delete(result.length()-2, result.length());
      result.append("}");
      return result.toString();
    }
  }	
  public static void main(String[] args) {
    PetCounter petCount = new PetCounter();
    for(Pet pet : Pets.createArray(20)) {
      printnb(pet.getClass().getSimpleName() + " ");
      petCount.count(pet);
    }
    print();
    print(petCount);
  }
} /* Output:
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric
{Pet=20, Dog=6, Cat=9, Rodent=5, Mutt=3, Pug=3, EgyptianMau=2, Manx=7, Cymric=5, Rat=2, Mouse=2, Hamster=1}
*///:~
        另外,可以使用isAssignableFrom()来执行运行时检查,以校验你传递的对象确实属于我们感兴趣的继承结构。

14.4 注册工厂

//: typeinfo/RegisteredFactories.java
// Registering Class Factories in the base class.
import typeinfo.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 {}

 14.5 instanceof与Class的等价性
 
         在查询类型信息时,以instanceof的形式(即以instanceof的形式或isInstance()的形式,它们产生相同的结果)与直接比较Class对象有一个很重要的差别。
         instanceof保持了类型的概念,它指的是”你是这个类吗,或者你是这类的派生类吗?“而如果用==比较实际的Class对象,就没有考虑继承——它或者是这个确切的类型,或者不是。

14.6 反射

        Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field,Method以及Constrctor类(每个类都实现了Member接口)。
       RTTI与反射之间真正的区别在于,对RTTI来说,编译器在编译时打开或检查.class文件。而对于反射来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

14.7 动态代理
//: typeinfo/SimpleDynamicProxy.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);
  }
} /* 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
*///:~
        通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器(你通常可以从已经被加载的对象中获取其类加载器,然后传递给它),一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHandler接口的一个实现。

14.8 空对象
       
//: typeinfo/Person.java
// A class with a Null Object.
import net.mindview.util.*;

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();
} ///:~
//: typeinfo/Position.java

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;
  }
} ///:~
 14.8.1 模拟对象与桩

          模拟对象与桩之间的差异在于程度不同。模拟对象往往是轻量级和自测试的,通常很多模拟对象被创建出来是为了处理各种不同的测试情况。桩只是返回桩数据,它通常是轻量级的,并且经常在测试间被复用。

14.9 接口与类型信息

          通过类型信息,接口的耦合性还是会传播出去——接口并非是对解耦的一种无懈可击的保障。

例子1、

//: typeinfo/InterfaceViolation.java
// Sneaking around an interface.
import typeinfo.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();
    }
  }
} /* Output:
B
*///:~
例子2,将实现使用包权限,这样 才客户端就无法使用向下转型:

//: typeinfo/HiddenImplementation.java
// Sneaking around package access.
import typeinfo.interfacea.*;
import typeinfo.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()
*///:~

       即使只发布编译后的代码也无法解决问题,因为使用 javap -private C命令,可以获得成员信息。其中-private标志表示所有的成员都因该显示。

class typeinfo.packageacess.C extends java.lang.Object implements typeinfo.interfaca.A{
      typeinfo.packageaccess.C();
      public void f();
      public void g();
      void u();
      protected void v();
      private void w();
}
例子3:私有内部类

//: typeinfo/InnerImplementation.java
// Private inner classes can't hide from reflection.
import typeinfo.interfacea.*;
import static net.mindview.util.Print.*;

class InnerA {
  private static class C implements A {
    public void f() { print("public C.f()"); }
    public void g() { print("public C.g()"); }
    void u() { print("package C.u()"); }
    protected void v() { print("protected C.v()"); }
    private void w() { print("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");
  }
} /* Output:
public C.f()
InnerA$C
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

例子4:匿名内部类

//: typeinfo/AnonymousImplementation.java
// Anonymous inner classes can't hide from reflection.
import typeinfo.interfacea.*;
import static net.mindview.util.Print.*;

class AnonymousA {
  public static A makeA() {
    return new A() {
      public void f() { print("public C.f()"); }
      public void g() { print("public C.g()"); }
      void u() { print("package C.u()"); }
      protected void v() { print("protected C.v()"); }
      private void w() { print("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()
*///:~
例子5:可以通过反射调用非公共访问权限的方法,也可以使用private域:

//: typeinfo/ModifyingPrivateFields.java
import java.lang.reflect.*;

class WithPrivateFinalField {
  private int i = 1;
  private final String s = "I'm totally safe";
  private String s2 = "Am I safe?";
  public String toString() {
    return "i = " + i + ", " + s + ", " + s2;
  }
}

public class ModifyingPrivateFields {
  public static void main(String[] args) throws Exception {
    WithPrivateFinalField pf = new WithPrivateFinalField();
    System.out.println(pf);
    Field f = pf.getClass().getDeclaredField("i");
    f.setAccessible(true);
    System.out.println("f.getInt(pf): " + f.getInt(pf));
    f.setInt(pf, 47);
    System.out.println(pf);
    f = pf.getClass().getDeclaredField("s");
    f.setAccessible(true);
    System.out.println("f.get(pf): " + f.get(pf));
    f.set(pf, "No, you're not!");
    System.out.println(pf);
    f = pf.getClass().getDeclaredField("s2");
    f.setAccessible(true);
    System.out.println("f.get(pf): " + f.get(pf));
    f.set(pf, "No, you're not!");
    System.out.println(pf);
  }
} /* Output:
i = 1, I'm totally safe, Am I safe?
f.getInt(pf): 1
i = 47, I'm totally safe, Am I safe?
f.get(pf): I'm totally safe
i = 47, I'm totally safe, Am I safe?
f.get(pf): Am I safe?
i = 47, I'm totally safe, No, you're not!
*///:~
         final域实际上是在遭遇修改时是安全的。运行时系统会在不抛异常的情况下接受任何修改尝试,但是实际上不会发生任何修改。

参考资料:

java中Class对象详解
java Class类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值