运行时类型信息(RTTI)使得你可以在程序运行时发现和使用类型信息。
Class对象
每一个类都有一个Class对象。所有的类都是在对其第一次使用时,动态加载到JVM中。此时,使用类加载器可以生成某个类的Class对象。(构造器也是类的静态方法。)
Class.forName("className")、classObj.getClass():这是取得Class对象引用的一种方法。
类字面量常量:另外一种方法生成Class对象的引用。eg,FancyToy.class。此方法更简单,更安全,因为在编译时会受到检查,因此不需要置于try语句块中,更高效。对于基本数据类型,还有一个标准字段TYPE。为了使用类而做的准备工作包含三个步骤:
1.加载:类加载器指向,查找字节码,创建Class对象。
2.链接:验证字节码,为静态域分配存储空间。若需要,解析这个类创建的其他类的引用。
3.初始化:若该类具有超类,对其初始化,执行静态初始化器和静态初始化块。
但需注意,使用字面量创建Class对象的引用时,不会自动地初始化该Class对象。初始化被延迟到对静态方法或者非常数静态域进行首次引用时才执行。
boolean.class | Boolean.TYPE |
---|---|
char.class | Character.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
泛化的Class引用:
Class intClass = int.class;
Class<Integer> gIntClass = int.class;
gIntClass = Integer.class; // Same thing
intClass = double.class;
// gIntClass = double.class //Illeagal
向上转型可以编译通过,但是无法工作。
Class<Number> gNClass = int.class;
可以使用通配符?。
Class<?> intClass = int.class;
intClass = double.class;
还可以使用范围通配符:
Class<? extends Number> bounded = int.class;
Class<? super Integer> s = Number.class;
使用泛型语法是为了提供编译器类型检查,使语言更加安全。
Class<SonClass> sClass = Son.class;
Son son = Son.newInstance();
Class<? super Son> fClass = sClass.getSuperclass();
// this won't compile
// Class<Father> fClass = sClass.getSuperclass();
// Only produces
Object obj = fClass.newInstance();
// 有点不懂
使用cast()转型:
Class<House> houseType = House.class;
House h = houseType.cast(b);
RTTI形式
1)传统的类型转换——“(Shape)”。报ClassCastException。
2)代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
3)instanceof,返回一个布尔值。class.isInstance(obj)提供了一种动态地测试对象的途径。使用此方法保持了类型的概念,表示某个类或者某个类的派生类。而使用eqauls()或者==比较Class对象时就没有考虑继承
if(x instanceof Dog){}
反射:运行时的类信息
RTTI的使用条件是编译时已知这个类。若在编译时无法获知这个对象所属的类,就需要使用反射来检查匿名对象的类的可用方法。当然,使用反射时JVM对于目标类的.class文件也是已知的。区别在于,对于RTTI来说,编译器在编译时打开和检查.class文件,而反射则是在运行时打开和检查.class文件。迷惑
类方法提取器:
class.getMethods():返回Method对象数组
class.getConstructors():返回Constructor对象数组。
动态代理
代理是为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象,是个中间人的角色。代理会提供和本体一样的方法,执行时没有区别。
//动态代理类
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{
print("do before method");
return method.invoke(proxied, args);
}
}
//执行动态代理
class SimpleDynamicProxy{
public static void consumer(Interface iface){
iface.doSomething();
iface.somethingElese("bonobo");
}
public static void main(String[] args){
RealObject real = new RealObject();
consumer(real);
//使用动态代理调用
Interface proxy = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{ Interface.class},
new DynamicProxyHandler(real)
);
consumer(proxy);
}
}
空对象
空对象是一种设计模式,有时候我们的代码中为避免 NullPointerException 会出现很多的对Null的判断语句,而这些语句一旦多起来,我们的代码就会变的惨不忍睹,因此我们引入了空对象模式(null object pattern)以此来使我们的代码变的更优雅一点。
首先创建一个标记接口:
public interface Null{}
这使得可以使用instanceof探测空对象。
class Person{
public final String name;
public final Strng address;
public Person(String name, String address){
this.name = name;
this.address = address;
}
//空对象
public static class NullPerson extends Person implements Null{
private NullPerson() {
super("None", "None");
public String toString(){ return "NullPerson";}
}
}
public static final Person NULL = new NullPerson();
}
现在,就可以使用Person.NULL这个单例对象作为该类的空对象。
接口与类型信息
反射可以调用所有的方法,包括private方法。首先使用class.getDeclaredMethod(methodName)获取所有声明的方法,然后method.setAccessible(true),然后method.invoke(obj)触发方法。不管是内部类还是匿名类,反射都是可以操作的。对于域来说,情况是一样的,只是final域是修改不了的。
网友评论