1. 简介
在Java世界里,一切皆对象。从某种意义上来说,Java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别:Run-Time Type Identification),多态就是基于RTTI实现的。
每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, int等)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class
)。Class对象对应着java.lang.Class
类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。
Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的defineClass()
方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:
- 加载。这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法区的运行时数据接口,根据字节码在Java堆中生成一个代表这个类的
java.lang.Class
对象。 - 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。
- 初始化。到了此阶段,才真正开始执行类中定义的Java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。
**所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。**当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。
在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class
文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。
即:先在内存中创建这个类的Class对象,然后再用这个Class对象创建具体的实例对象。
2. 获得Class对象(类型类)
通过Class对象可以判断对象真正的类型。有三种方法可以获得Class对象:
Class.forName(String name, boolean initialize, ClassLoader loader)
类名.class
实例对象.getClass()
注意:第一种方法中,当使用仅有一个String参数的forName方法时,Class对象将默认调用当前类的类加载器作为加载器和并将第二参数为置为true。String参数为一个类或接口的完整路径名,那么此方法将试图定位、装载和连接该类,返回相应的Class对象。若不成功,则抛出ClassNotFoundException异常。
若将initialize
设定为 false,这样在加载类时并不会立即运行静态区块,而会在使用类建立对象时才运行静态区块。
我们一般所使用的对象都直接或间接继承自Object类。Object类中包含一个方法名叫getClass()
,利用这个方法就可以获得一个实例的Class对象(类型类)。因为一切皆是对象,类也不例外。例如,有如下一段代码:
A a = new A();
if (a.getClass() == A.class)
System.out.println("equal");
else
System.out.println("unequal");
// 打印结果为 equal
可以看到,对象a是A的一个实例,使用a.getClass()
返回的结果正是A的类型类,而通过类名.class
的方式获得的也是A的类型类,即二者等价,只是一个使用类的实例,一个使用类名。
特别注意的是,类型类是一一对应的,父类的类型类和子类的类型类是不同的,因此,假设A是B的子类,那么如下的代码将得到 “unequal”的输出:
A a = new A();
if(a.getClass()==B.class)
System.out.println("equal");
else
System.out.println("unequal");
// 结果为 unequal
因此,如果你知道一个实例,那么你可以通过实例的实例.getClass()
方法获得该对象的类型类,如果你知道一个类型,那么你可以使用类名.class
的方法获得该类型的类型类。
补充:
Class.forName().newInstance()
和 new
关键字创建对象实例的异同
在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字除了一个是方法,一个是关键字外,最主要的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。
那么为什么会有两种创建对象方式?这主要考虑到软件的可伸缩、可扩展和可重用等软件设计思想。
Java中工厂模式以及使用Java反射机制时经常使用newInstance()
方法来创建对象,例如:
class c = Class.forName("Example");
factory = (ExampleInterface)c.newInstance();
从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用newInstance()方法的时候,就必须保证:1、这个类已经加载;2、这个类已经连接了。而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载 java API的那个加载器。
现在可以看出,newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。 这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。
最后用最简单的描述来区分new关键字和newInstance()方法的区别:
newInstance()
: 弱类型。低效率。只能调用无参构造。new
: 强类型。相对高效。能调用任何public构造。
3. 获得类型的信息
getClassLoader()
:获取该类的类装载器。
getName():String
:获得该类的完全名称。
getConstructor(Class[])
:返回当前Class对象表示的类的指定的公有构造子对象。
getConstructors()
:返回当前Class对象表示的类的所有公有构造子对象数组。
getDeclaredConstructor(Class[])
:返回当前Class对象表示的类的指定已说明的一个构造子对象。
getDeclaredConstructors()
:返回当前Class对象表示的类的所有已说明的构造子对象数组。
getDeclaredField(String)
:返回当前Class对象表示的类或接口的指定已说明的一个域对象。
getDeclaredFields()
:返回当前Class对象表示的类或接口的所有已说明的域对象数组。
getDeclaredMethod(String,Class[])
:返回当前Class对象表示的类或接口的指定已说明的一个方法对象
getDeclaredMethods()
:返回Class对象表示的类或接口的所有已说明的方法数组。
getField(String)
:返回当前Class对象表示的类或接口的指定的公有成员域对象。
getFields()
:返回当前Class对象表示的类或接口的所有可访问的公有域对象数组。
getInterfaces()
:返回当前对象表示的类或接口实现的接口。
getMethod(String,Class[])
:返回当前Class对象表示的类或接口的指定的公有成员方法对象。
getMethods()
:返回当前Class对象表示的类或接口的所有公有成员方法对象数组,包括已声明的和从父类继承的方法。
newInstance()
:创建类的新实例。
getSuperClass():Class
:获得该类型的直接父类,如果该类型没有直接父类,那么返回null。
getInterfaces():Class[]
:获得该类型实现的所有接口。
isArray():boolean
:判断该类型是否是数组。
isEnum():boolean
:判断该类型是否是枚举类型。
isInterface():boolean
:判断该类型是否是接口。
isPrimitive():boolean
:判断该类型是否是基本类型,即是否是int,boolean,double等等。
isAssignableFrom(Class cls):boolean
:判断这个类型是否是类型cls的父(祖先)类或父(祖先)接口。
getComponentType()
:如果当前类表示一个数组,则返回表示该数组组件的Class对象,否则返回null。
更多阅读:Java API
关注微信公众号:【三朝猿老】 获得更多最新信息