Java Class对象详解
要怎样在java里来使用一个类,首先必须先把类的.class字节码文件加载进来,然后再进行连接对该类里的域分配内存,最后再调用构造器,如果该类有基类的话,会先去调用基类的构造器,总的来说,分为以下三个步骤。
1.根据环境变量找到并加载.class文件
2.为该类的非编译时常量分配内存
3.调用该类的构造器
java里的所有类都有一个Class对象,通过这个Class对象我们能够获取此类的各种信息。
当某个字节码文件被JVM加载的时候,Class对象就被创建。
Class类没有构造方法,是内部的一个defineClass方法来创建此对象的,此对象与被加载的字节码文件的类的类型相对应。
其实在java里包括基本数据类型(int short long byte float double boolean char),也包括了void
System.out.println(int.class.getName());
System.out.println(char.class.getName());
System.out.println(short.class.getName());
System.out.println(long.class.getName());
System.out.println(byte.class.getName());
System.out.println(float.class.getName());
System.out.println(double.class.getName());
System.out.println(boolean.class.getName());
System.out.println(void.class.getName());
都有与之对应的class对象,同类型的类型也共享一个class对象。也包括了数组,所有同类型同维度的数组也共享一个class对象。
public class Main {
public static void main(String[] args) {
System.out.println(char[].class.getName());//[C
System.out.println(char[][].class.getName());//[[C
}
}
class的forName方法
同时class里有一个static的方法forName,可以让我们显示的来把一个类的.class文件加载至JVM虚拟机。
static Class< ? > forName(String className)
public class Main {
public static void main(String[] args) throws Exception{
Class a = Class.forName("A");
}
}
class A{
void print(){
System.out.println("hello world");
}
}
该方法返回的是一个Class对象,这个Class对象也可以添加泛型。
这样的话我们就获得一个与A类型对应的Class对象。
但是这时编译器会强制的让我们抛出或者捕获这个异常,所以我们需要将它捕获或者抛出。
接下来我们还能重载一个A类的private的构造器(注:默认的构造器是隐式的static,我们重载之后就不再是static),但是我们仍然能获取它的class对象,因为调用构造器是在最后一步,而我们这里只是加载.class文件。
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class a = Class.forName("A");
}
}
class A{
private A(){
}
void print(){
System.out.println("hello world");
}
}
这段代码是毫无错误的
但是直到现在我们都还不能通过Class.forName来操作一个类,因为它只是简单的加载而已,但是没关系Class类还有一个newInstance方法,这个方法能帮助我们创建一个class类的实例,我们只需显示的转换一下类型即可操作A类.
public class Main {
public static void main(String[] args) throws Exception {
A a = (A)Class.forName("A").newInstance();
a.print();
}
}
class A{
void print(){
System.out.println("hello world");
}
}
这时,其实我们得到的就是一个A类型的实例了。
但是如果我们这时把A的构造方法声明为private呢?
public class Main {
public static void main(String[] args) throws Exception {
A a = (A)Class.forName("A").newInstance();
a.print();
}
}
class A{
private A(){
}
void print(){
System.out.println("hello world");
}
}
编译器是仍然不会报错的,但是如果我们执行这段代码就会发现会抛出一个异常,因为这是在运行时加载的,所以编译器是无法察觉的。这也是相当危险的,所以一般情况下我们都会遵守用new来创建对象。
既然class是运行时对象,那么对于final static 声明的域也是毫无作用的了,这么说的原因是用final static声明的域是不需要动态的来分配内存的,因为它是一个编译时常量。
到现在我们大概明白了class的含义和运用,那它和.class有什么联系呢。
其实每个类也有一个.class的常量,我们称为类字面常量,这个常量能够返回该类的class对象,也能通过newIstance创建实例来操作。
运用类字面常量的好处就在于不用去抛出或者捕获异常,所犯的错误在编译时就能查找出来。
public class Main {
public static void main(String[] args) throws Exception {
A a = (A)A.class.newInstance();
a.print();
}
}
class A{
void print(){
System.out.println("hello world");
}
}
封装类的TYPE
这里以boolean类型来说明这个问题
System.out.println(boolean.class == Boolean.TYPE);//true
所以我们得出基本类型的.class 和 封装类的TYPE是等价的。