你真的了解java中的Class对象吗?

Class对象

类型信息在运行时如何表示?

  • Class对象包含了与类有关的信息
  • Class对象就是用来创建所有“常规”对象的
  • java使用Class对象来执行其RTTI

类是程序的一部分,每个类都有一个Class对象。每当编写并编译了一个新类,就会产生一个Class对象(同名的.class文件中)。

为了生成这个类的对象,运行这个程序的JVM,会使用“类加载器”的子系统。

所有的类在对其第一次使用时,就会动态加载到JVM中。比如程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使构造器没有使用static关键词。因此,使用new操作符创建类的新对象也会被当作对类的静态成员的引用。

java程序在它开始运行之前并非完全加载,其各个部分是必需时才加载的。

类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。

一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

代码如下:

public class RTTI {
	public static void main(String[] args) {
		new Candy();
		try {
			Class.forName("com.test.Gum");
		} catch (Exception e) {
			System.out.println("loading gum fail");
		}
		new Cookie();
	}
}
class Candy{
	static{
		System.out.println("loading candy");
	}
}
class Gum{
	static{
		System.out.println("loading gum");
	}
}
class Cookie{
	static{
		System.out.println("loading cookie");
	}
}
output:
loading candy
loading gum
loading cookie

从代码可以总结出:

Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的

Class.forName(“com.test.Gum”);

forName()方法是Class类的一个static成员。

还可以通过getClass()方法来获取Class对象的引用。

类字面常量

java还可以通过类字面常量来生成Class对象的引用。例如:

Cookie.class

这样做简单、而且更安全,因为它在编译时就会受到检查,并且根除了对forName()方法的调用,更高效。

类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型

对于基本类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应基本数据类型的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创建对Class对象的引用时,不会自动地初始化该Class对象

泛化的Class引用

Class

Class intClass = int.class;
Class genericIntClass = int.class;
intClass = double.class;
//编译错误 Type mismatch: cannot convert from Class to Class
//genericIntClass = double.class;

如上代码所示:通过使用泛型语法,可以让编译器强制执行额外的类型检查。

//编译报错
Class genericIntClass = int.class;

Integer继承自Number,但是编译错误,因为Integer Class对象不是Numner Class对象的子类。

为了在使用泛化的Class引用时放松限制,可以使用通配符,它是java泛型的一部分。通配符“?”表示“任何事物”。如下代码所示:

Class genericIntClass = int.class;
genericIntClass = double.class;

在java SE5中,Class优于平凡的Class,即便他们是等价的,Class不会产生编译器警告信息。Class的好处是它表示你并非是碰巧或由于疏忽,而使用了一个非具体的类引用,选择了非具体的版本。

Class

为了创建一个Class引用,它被限定为某种类型,或该类型的任何子类型,你需要将通配符与extends关键字相结合,创建一个范围。

Class genericIntClass = int.class;
genericIntClass = double.class;

Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,可以在编译期间发现错误,而不是在运行期间

Class

public class GenericToyTest {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
		Class ftClass = FancyToy.class;
		FancyToy fancyToy = ftClass.newInstance();
		Class up = ftClass.getSuperclass();
		//Class up2 = ftClass.getSuperclass();编译错误
		Object obj = up.newInstance();//只接受Object
	}
}
class Toy{}
class FancyToy extends Toy{}
  • 编译器只允许声明超类引用是“某个类,它是FacyToy超类”
  • 接受Class,不接受Class
  • getSuperClass()方法返回的是基类(不是接口)
  • up.newInstance()的返回值不是精确类型,而只是Object

类型转换前先做检查

RTTI形式包括:

  • 传统的类型转换,如(Shape),由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。
  • 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需要的信息。
  • 关键字instanceof,返回布尔值,告诉我们对象是不是某个特定类型的实例。
if(x instanceof Dog){
    ((Dog)x).bark();
}

编译器允许自用地做向上转型的赋值操作,而不需要任何显式操作;向下转型赋值,必须使用显式的类型转换。

你可能感兴趣的:(java基础,java)