type
status
date
slug
summary
tags
category
icon
password
Property
除了
int
等基本类型外,Java的其他类型全部都是class
(包括interface
),例如:String
Object
Runnable
Exception
- ...
class
(包括interface
)的本质是数据类型(Type
),无继承关系的数据类型无法赋值:而
class
是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class
类型时,将其加载进内存。每加载一种
class
,JVM就为其创建一个Class
类型的实例,并关联起来。注:这里的Class
类型是一个名叫Class
的class
:以
String
类为例,JVM加载String
类时,首先读取String.class
文件到内存,然后为String
类创建一个Class
实例并关联起来:这个
Class
实例是JVM内部创建的,如果查看JDK源码,可以发现Class
类的构造方法是private
,只有JVM能创建Class
实例,我们自己的Java程序是无法创建Class
实例的。所以,JVM持有的每个
Class
实例都指向一个数据类型(class
或interface
):一个
Class
实例包含了该class
的所有完整信息:由于JVM为每个加载的
class
创建了对应的Class
实例,并在实例中保存了该class
的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class
实例,就可以通过这个Class
实例获取到该实例对应的class
的所有信息。这种通过
Class
实例获取class
信息的方法称为反射(Reflection)如何获取一个
class
的Class
实例?有三个方法:- 直接通过一个
class
的静态变量class
获取
- 如果有一个实例变量,可以通过该实例变量提供的
getClass()
方法获取
- 如果知道一个
class
的完整类名,可以通过静态方法Class.forName()
获取
因为
Class
实例在JVM中是唯一的,所以上述方法获取的Class
实例是同一个实例,可以用==
比较两个Class
实例:Class
实例比较和instanceof
的差别:instanceof
不但匹配指定类型,还匹配指定类型的子类。而用==
判断class
实例可以精确地判断数据类型,但不能作子类型比较。通常情况下应该用
instanceof
判断数据类型,因为面向抽象编程的时候不关心具体的子类型。只有在需要精确判断一个类型是不是某个class
的时候,才使用==
判断class
实例。因为反射的目的是为了获得某个实例的信息,因此当拿到某个
Object
实例时,可以通过反射获取该Object
的class
信息:注意到数组(例如
String[]
)也是一种类,而且不同于String.class
,它的类名是[Ljava.lang.String;
。此外,JVM为每一种基本类型如int
也创建了Class
实例,通过int.class
访问。如果获取到了一个
Class
实例,就可以通过该Class
实例来创建对应类型的实例:上述代码相当于
new String()
。通过Class.newInstance()
可以创建类实例,它的局限是:只能调用public
的无参数构造方法。带参数的构造方法,或者非public
的构造方法都无法通过Class.newInstance()
被调用。动态加载
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载:
当执行
Main.java
时,由于用到了Main
,因此,JVM首先会把Main.class
加载到内存。然而,并不会加载Person.class
,除非程序执行到create()
方法,JVM发现需要加载Person
类时,才会首次加载Person.class
。如果没有执行create()
方法,那么Person.class
根本就不会被加载。这就是JVM动态加载
class
的特性。动态加载
class
的特性对于Java程序非常重要。利用JVM动态加载class
的特性,才能在运行期根据条件加载不同的实现类。例如,Commons Logging总是优先使用Log4j,只有当Log4j不存在时,才使用JDK的logging。利用JVM动态加载特性,大致的实现代码如下:这就是为什么只需要把Log4j的jar包放到classpath中,Commons Logging就会自动使用Log4j的原因。