运行时类型信息可以让我们在程序运行时发现和使用类型的信息
- Java中在运行时识别对象和类的信息主要有两种方式:
- 传统的RTTI,它假定我们在编译时已经知道了所有的类型
- 反射机制,它允许我们在运行时发现和使用类型信息
RTTI
在使用基本的向上转型时,就会使用到RTTI提供的类型转换,例如:
1 2 3 4 5 6 7 8
| class Shape{} class Circle extends Shape{} class Square extends Shape{} public class RTTI{ public static void main(String[] args){ List<Shape> list = Arrays.asList(new Circle(), new Square()); } }
|
在这里,Circle对象和Square对象会向上转型为Shape对象,而这同时也会丢失Shape对象的具体类型。在Java中,所有的类型转换都是在运行时进行正确性检查的,而RTTI的含义就是:在运行时,识别一个对象的类型。
在编码时,就是使用容器和泛型来强制确保这个转换;在运行时,使用RTTI来确保这个转换
但是,RTTI的类型转换并不彻底:它只是将Object转型为Shape,而不是转型为Circle、Square。而后面Shape会执行什么代码就是多态机制的事情了。
Class对象
要理解RTTI在Java中的工作原理,就必须知道类型信息在运行时是如何表示的,而Class对象就是用来在运行时表示类型信息的,即是用来储存类型信息的。
在Java中,每编写并编译了一个新类,都会产生一个Class对象,而这些Class对象就是通过编译完后所生成的对应的.class文件来创建的,它们是一一对应的,让后JVM就可以使用Class对象来创建我们编写类的对象,在这个过程中将使用被称为”类加载器“的子系统,而将class文件加载到虚拟机的内存,这个过程称为类加载。
创建一个类对象的过程:
- 编写一个新类;
- 编译生成.class文件;
- 加载:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象;
- 链接:验证类中的字节码,为静态域分配空间,解析这个类创建的对其他类的所有引用;
- 初始化:如果该类有超类,则对其初始化,执行静态初始化器和静态代码块。
forName方法
- Class.forName(“类的包路径”)可以获取指定类的Class对象,使用该对象就可以获取类中包含信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.muchlab.classinfo;
class Candy{ static { System.out.println("Loading Candy"); } } class Gum{ static { System.out.println("Loading Gum"); } } class Cookie{ static { System.out.println("Loading Cookie"); } }
public class SweetShop { public static void main(String[] args) { System.out.println("inside main"); new Candy(); System.out.println("After creating Candy");
try { final Class gum = Class.forName("com.muchlab.classinfo.Gum");
} catch (ClassNotFoundException e) { System.out.println("Couldn't find Gum"); } System.out.println("After Class.forName('Gum')"); final Cookie cookie = new Cookie(); final Class<? extends Cookie> aClass = cookie.getClass(); System.out.println(aClass.getSimpleName());
System.out.println("After creating Cookie"); } }
|
getxxx
- xx.getClass:通过对象来获取该对象类型的Class对象
- getName:通过Class对象来获取全限定类名
- getSimpleName:通过Class对象来获取不含包名类名
- getCanonicalName:通过Class对象来获取全限定类名
- getInterfaces:通过Class对象来获取类实现的接口列表
- getSuperclass:通过Class对象来获取父类
- newInstance:通过Class对象来创建具体类的对象,使用这个方法需要注意的一点是,Class对象所指向的类必须要有一个无参构造器,如不符合条件将会获得一个异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package com.muchlab.classinfo;
import java.lang.reflect.Field;
interface HasBatteries{} interface Waterproof{} interface Shoots{}
class Toy{ public String name; private int number; 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("com.muchlab.classinfo.FancyToy"); } catch (ClassNotFoundException e) { System.out.println("Can't find FancyToy"); System.exit(1); } printInfo(c); for (Class anInterface : c.getInterfaces()) { printInfo(anInterface); } try { final Field field = c.getField("name"); System.out.println(field.getName()); } catch (NoSuchFieldException e) { e.printStackTrace(); } final Class superclass = c.getSuperclass(); Object o = null; try { o = superclass.newInstance(); } catch (InstantiationException e) { System.out.println("Can't instantiate"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("Can't access"); System.exit(1); } printInfo(o.getClass()); final Class<FancyToy> fancyToyClass = FancyToy.class; System.out.println(int.class==Integer.TYPE); } }
|
类字面常量
- 如果使用forName方法来获取相关类的Class对象,不仅要指定类的全限定类名,而且在编译时不会受到检查,所以可以使用
类名.class
的方式来获取类的Class对象
- 当使用
.class
方式来创建Class对象的引用时,不会自动地初始化该Class对象,初始化将会被延迟到对静态方法获知非常熟静态域进行首次引用时才执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package com.muchlab.classinfo;
import java.util.Random;
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 ClassNotFoundException { Class initable = Initable.class; System.out.println("==========================="); System.out.println(Initable.staticFinal); System.out.println("==========================="); System.out.println(Initable.staticFinal2); System.out.println("==========================="); System.out.println(Initable2.staticNonFinal); final Class<?> initable3 = Class.forName("com.muchlab.classinfo.Initable3"); System.out.println(Initable3.staticNonFinal); } }
|
.class和.TYPE
对于基本数据类型的包装类,有一个标准字段TYPE,TYPE字段是一个引用,指向对应的基本数据类型的Class对象,即基本类型.class
就等价于包装类.TYPE
基本类型.class |
包装类.TYPE |
boolean.class |
Boolean.TYPE |
char.class |
Character.TYPE |
int.class |
Integer.TYPE |
…….. |
…….. |
泛化的Class引用
Class<? extends xx>
- 使用泛化的Class引用主要是对Class引用所指向的对象的确切类型进行限制,如下面代码所示:对Class引用所指向的对象限制为Integer
1 2 3 4 5 6 7 8
| package com.muchlab.classinfo;
public class GenericClassReferences { public static void main(String[] args) { Class<Integer> cc = int.class; System.out.println(cc.getName()); } }
|
- 但有时我们会觉得这种限制太过严格了。为解决这个问题,我们可以放松一些限制,这时我们就会想使用Integer的父类Number来进行泛化。
1
| Class<Number> cc = int.class;
|
- 但这行代码其实会通不过编译,原因是Integer的Class对象并不是Number的Class对象的子类,这时就可以使用通配符来解决这个继承的问题。在Class泛型中的指向类的继承关系是这样表示的:
Class<? extends Number> cc = int.class;
它代表了继承于Number的某个类的Class对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.muchlab.classinfo;
public class WildcardClassReferences { public static void main(String[] args) {
Class<? extends Number> intclass = int.class; intclass = double.class; } }
|
Class<?>
- 在编写Class对象时,
Class<?>
是要优于Class的,即便它们是等价的,Class<?>
的好处就是它表示你并非是由于疏忽而忘记写泛型的类型,而是使用了一个非具体的类引用。
Class<? super xxx>
- Class<? super xxx>代表的是Class中的泛型为xxx的超类,这里写成Class<超类>并不能起到作用。
1 2 3 4 5 6 7 8 9 10
| package com.muchlab.classinfo;
public class GenericToyTest { public static void main(String[] args) throws IllegalAccessException, InstantiationException { Class<FancyToy> cc = FancyToy.class; Class<? super FancyToy> superClass = cc.getSuperclass(); System.out.println(superClass.getName()); } }
|
反射,运行时的类型信息
- 如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个限制:这个类型在编译时必须已知,这样RTTI才能识别它,而反射则没有这个限制。
- Class类于java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用于表示未知类对应的成员。这样就可以使用Constructor来创建对象,用get()和set()来读取和修改Field对象相关联的字段,用invoke()来调用与Method相关联的方法。
- getxxx:
- getConstructors():获取构造器
- getFields():获取成员变量
- getMethods():获取成员方法
JDK的动态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package com.muchlab.reflect;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
class DynamicProxyHandler implements InvocationHandler{ private Object proxied;
public DynamicProxyHandler(Object proxied) { this.proxied = proxied; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("=============================Before Invoke=================================="); System.out.println("**** proxy: "+proxy.getClass()+ ", method: "+method+", arg: " + args); if (args!=null) for (Object arg : args) { System.out.println(" " + arg); }
Object ret = method.invoke(proxied, args); System.out.println("=============================After Invoke=================================="); return ret; } }
public class SimpleDynamicProxy { public static void consumer(Interface iface){ iface.doSomething(); iface.somethingElse("bonobo"); }
public static void main(String[] args) { RealObj realObj = new RealObj(); consumer(realObj);
Interface proxy = (Interface) Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(realObj)); consumer(proxy); } }
|
参考资料:Java编程思想