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<Integer> genericIntClass = int.class;
intClass = double.class;
//编译错误 Type mismatch: cannot convert from Class<Double> to Class<Integer>
//genericIntClass = double.class;
如上代码所示:通过使用泛型语法,可以让编译器强制执行额外的类型检查。
//编译报错
Class<Number> 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<? extends ***>
为了创建一个Class引用,它被限定为某种类型,或该类型的任何子类型,你需要将通配符与extends关键字相结合,创建一个范围。
Class<? extends Number> genericIntClass = int.class;
genericIntClass = double.class;
Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,可以在编译期间发现错误,而不是在运行期间。
Class<? Super ***>
public class GenericToyTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class<FancyToy> ftClass = FancyToy.class;
FancyToy fancyToy = ftClass.newInstance();
Class<? super FancyToy> up = ftClass.getSuperclass();
//Class<Toy> up2 = ftClass.getSuperclass();编译错误
Object obj = up.newInstance();//只接受Object
}
}
class Toy{}
class FancyToy extends Toy{}
- 编译器只允许声明超类引用是“某个类,它是FacyToy超类”
- 接受Class<? super FancyToy>,不接受Class
- getSuperClass()方法返回的是基类(不是接口)
- up.newInstance()的返回值不是精确类型,而只是Object
类型转换前先做检查
RTTI形式包括:
- 传统的类型转换,如(Shape),由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。
- 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需要的信息。
- 关键字instanceof,返回布尔值,告诉我们对象是不是某个特定类型的实例。
if(x instanceof Dog){
((Dog)x).bark();
}
编译器允许自用地做向上转型的赋值操作,而不需要任何显式操作;向下转型赋值,必须使用显式的类型转换。