Class对象
1.每当编写并编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机将使用被称为“类加载器”的子系统。
2.所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,因此使用new操作符创建类的新对象也会被当作对类的静态成员的引用。
3.Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。
4.Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。
5.无论如何,只要你想在运行时使用类型信息,就必需首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径,因为你不需要为了获得Class引用而持有该类型的对象。如果已经拥有了一个类型的对象,那就可以通过调用getClass()方法来获取Class引用。
6.getName()来产生全限定的类名;getSimpleName()来产生不含包名的类名;getCanonicalName()来产生全限定的类名;isInterface()方法可以告诉你这个Class对象是否表示一个接口;getInterfaces()方法返回的是Class对象,它们表示在Class对象中所包含的接口;getSuperclass()方法可用来查询基类,将返回用来进一步查询的Class对象;newInstance()可用来创建Class对象所对应的类,但必须带有默认构造器。
一.类字面常量
1.Java还可以使用类字面常量——“.class”的形式来生成对Class对象的引用。
2.类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。
3.与通过Class中的方法创建对Class对象的引用不同的是,通过“.class”的方法创建不会自动地初始化该Class对象,初始化被延迟到了对静态方法(包括构造器)或者非常数静态域进行首次引用的时候才会执行。
4.使用类需要包括的三个步骤:
(1).加载,这是由类加载器执行的。该步骤将查找字节码,并从这些字节码中创建一个Class对象。
(2).链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个创建的对其他类的引用。
(3).初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
5.如果一个static final值是“编译期常量”,那么这个值不需要对类进行初始化就可以被读取。如果一个static域不是final的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间)。
class Initable {
static final int staticFinal = 47;//static final的编译期常量,使用时不用初始化
static final int staticFinal2 = ClassInitaialization.rand.nextINt(1000);//虽然是static final的,但是不是编译期常量,使用时需要初始化
static int staticFinal3 = 47;//不是final的,使用时需要进行初始化
}
二.泛化的Class引用
1.Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
2.使用泛型语法,实现对Class引用所指向的Class对象的类型进行限定。
public class GenericClassReference {
public static void main(String...args) {
Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
genericIntClass = Integer.class; // Same thing
intClass = double.class; // Success
//genericIntClass = double.class; // Illegal
}
}
为了在使用泛化的Class引用时放松限制,可以使用通配符。
Class<?> intClass = int.class;
intClass = double.class;
3.当将泛型语法用于Class对象的时候:newInstance()将返回该对象的确切类型,而不仅仅只是基本的Object。
三.新的转型语法
1.JavaSE5中添加了用于Class引用的转型语法,即cast()方法:
Building b = new House();
Class<House> houseType = House.class;
House h = houseType.cast(b); // 与 h = (House) b; 效果相同
类型转换前先做检查
1.使用关键字instanceof,它返回一个布尔值,告诉我们对象是不是某个特定类型的实例:
if (x instanceof Dog)
((Dog) x).bark();
进行向下转型前,如果没有其他信息告诉你对象是什么类型,那么使用instanceof是非常重要的,否则会得到一个ClassCastException异常。
2.对instanceof有比较严格的限制:只可将其与命名类型进行比较,而不能与Class对象作比较。如果程序中编写了许多的instanceof表达式,就说明你的设计可能存在瑕疵。
一.动态的instanceof
1.isInstance()方法不用与命名类型进行比较,而可以直接与Class对象进行比较
Pet pet = new Dog();
if (pet.isInstance(Dog.class)))
((Dog) pet).bark();
2.使用isAssignableFrom()来执行运行时检查,以校验你传递的对象确实属于我们感兴趣的继承结构。
二.instanceof与Class的等价性
1.在查询类型信息的时候,以instanceof或isInstance()的形式与直接比较Class对象有一个很重要的区别。instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”而如果用“==”比较实际的Class对象,就没有考虑继承——它或者是这个确切的类型,或者不是。
反射:运行时的类信息
1.Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包括了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的用以表示未知类里对应的成员。
2.RTTI和反射之间真正的区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
类方法提取器
1.Class的getMethods()和getConstructors()方法分别返回Method对象的数组和Constructor对象的数组。
2.Class.forName()生成的结果在编译时是不可知的,因此所有的方法特征签名信息都是在执行时被提取出来的。反射机制提供了足够的支持,使得能够创建一个编译时完全未知的对象,并调用此对象的方法。
接口与类型信息
1.通过使用反射,即使实现包的访问权限,仍旧可以到达并调用所有方法,甚至是private方法。如果知道方法名,就可以在其Method对象上调用setAccessible(true),然后通过invoke()方法来使用该方法。
2.final域实际上在遭遇修改时是安全的。运行时系统会在不抛异常的情况下接受任何修改尝试,但实际上不会发生任何修改。
网友评论