1.Class对象包含了与类有关的信息。
事实上,Class对象就是用来创建类的所有“常规”对象的。Java使用Class对象来执行RTTI,即使你正在执行的是类似转型这样的操作。Class类还拥有大量的使用RTTI的其他方式。
类是程序的一部分,每个类都有一个Class对象。换言之,每个编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机将使用被称为“类加载器”的子系统。
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分,原生类加载器加载的所谓的可信类,包括Java API类,他们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊的需求(例如以某种特殊的方式加载类,以支持Web服务器应用,或者在网络中下载类),那么你有一种方式可以挂接额外的类加载器。
所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字,因此,使用new操作符创建类的新对象也会被当作为对类的静态成员的引用。
因此,Java程序在他开始运行之前并非被完全加载,其余各个部分是在必须时才加载的。这一点与许多传统语言都不同,动态加载使能的行为,在诸如C++这样的静态加载语言中是很难得或者根本不可能复制的。
类加载器首先检查这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类型查找。Class文件(例如,某个附加类加载器可能会在数据库中查找字节码)。在这个累的字节码被加载时,他们就会接受验证,以确保其没有被破坏,并且不包括不良Java代码,一旦某个类的Class对象被载入内存,他就被用来创建这个类的所有对象。
Class.forName()这个方法是Class类的一个static成员。Class对象就和其他对象一样,我们可以获取并操作它的引用(这也是类加载器的工作)。forName()是取得Class对象的引用的一种方法。它是用一个包含目标类的文本名(注意拼写和大小写)的String做输入参数,返回的是一个Class对象的引用。如果Class.forName()找不到你要加载的类,他就会抛出异常ClassNotFoundException.这里我们只需要简单报告问题,但是在更严密的程序里,可能要在异常处理程序中解决这个问题。
无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的快捷途径,因为你不需要为了获得Class引用而持有该类型的对象。但是,如果你已经拥有了一个感兴趣的类型的对象,那就可以通过调用getClass()方法来获取Class引用了,这个方法属于根类Object的一部分,它将返回表示该对象的实际类型的Class引用。Class包含很多有用的方法,下面就是其中的一部分:
public interface HasBatteries {
}
public interface Shoots {
}
public interface Waterproof {
}
public class Toy {
public Toy(){};//默认构造器,如果不带默认构造器,则不能使用newInstance();创建对象。
Toy(int i){};
public static Toy getInstance(){
return new Toy();
}
public void print(){
System.out.println("你访问到我了!");
}
}
public class FancyToy extends Toy implements HasBatteries,Waterproof,Shoots{
FancyToy(){
super(1);
}
}
public class ToyTest {
static void printInfo(Class cc){
System.out.println("class name:"+cc.getName()+"is interface? ["+cc.isInterface()+"]");
System.out.println("simple name:"+cc.getSimpleName()+"canonical name "+cc.getCanonicalName()+"");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Class c=null;
try{
c=Class.forName("com.tkij.chapter14.Class.FancyToy");
}catch(ClassNotFoundException e){
System.out.println("cannot found fancytoy");
System.exit(1);
}
printInfo(c);
for(Class face:c.getInterfaces()){
printInfo(face);
}
Class up=c.getSuperclass();
Object obj=null;
try{
obj=up.newInstance();//使用newInstance()创建的类,必须带有默认的构造器,否则会报InstantiationException异常!
}catch(InstantiationException e){
System.out.println("canot instantiate");
}catch(IllegalAccessException e){
System.out.println("cannot access");
System.exit(1);
}
printInfo(obj.getClass());
}
}
FancyToy继承自Toy并实现了HasBatteries,Waterproof和Shoots接口。在main()中,用forname()方法在适当的try语句块中,创建了一个Class引用,并将其初始化为指向FancyToy Class.注意,在传递给forName()的字符串中,你必须使用全限定名(包括包名)。
printInfo()使用getName()来产生全限定的类名,并分别使用getSimpleName()和getCanonicalName()来产生不含包名的类名和全限定的类名,isInterface()方法如同起名,可以告诉你这个Class对象是否表示某个接口。因此,通过Class对象,你可以发现你想要了解的类型的所有的信息。
如果你有一个Class对象,还可以使用getSuperclass()方法查询其直接基类,这将返回你可以用来进一步查询的Class对象,因此,你可以在运行时发现一个对象完整的类继承结构。
Class的newInstance()方法是实现“虚拟构造器”的一种途径,虚拟构造器允许你声明:“我不知道你的确切类型,但是无论如何都要正确的创建你自己。”在前面的示例中,up仅仅只是一个Class引用,在编译期还不具备任何更近一步的类型信息。当你创建新实例时,会得到Object引用,但是这个引用指向的是Toy对象。当然,在你可以发送Object能够接受的消息之外的任何消息之前,你必须更多的了解它,并执行某种转型。另外,使用newInstance()来创建的类,必须带有默认的构造器,
2.泛化的Class引用
Class引用总是指向某个Class对象,他可以制造类的实例,并包含可作用于这些实例的所有方法代码。他还包含该类的静态成员,因此,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
但是,java SE5的设计者看准机会,将他的类型变得更具体了一些,而这是通过允许你对Class引用所指向的Class对象的类型所进行限定而实现的,这里用到了泛型语法。在下面的实例中,两种语法都是正确的:
public class GenericClassReferences {
public static void main(String[] args) {
Class intClass=int.class;
Class<Integer> genericIntClass=int.class;
genericIntClass=Integer.class;//same thing
intClass=double.class;
//genericIntClass=double.class;//Illegal
//Class<Number> genericNumberClass=int.class;//Illegal
}
}
普通的类引用不会产生警告信息,你可以看到,尽管泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class对象。通过这种泛型语法,可以让编译器强制执行额外的类型检查。
如果你希望稍微放松一些这种限制,应该怎么办呢?乍一看,好想你应该能够执行类似下面这样的操作:
Class<Number> genericNumberClass =int.class;
这看起来似乎是其作用的,因为Integer继承自Number.但是它无法工作。因为Integer Class对象不是Number Class对象的子类。
为了在使用泛化的Class引用时放松限制,我使用了通配符,它是Java泛型的一部分。通配符就是“?”,表示“任何事物”.因此,我们可以在上例的普通Class引用中添加通配符,并产生相同的结果:
public class WildcardClassReferences {
public static void main(String[] args) {
Class<?> intClass=int.class;
intClass=double.class;
}
}
在Java SE5,Class<?>优于平凡的Class,即便他们是等价的,并且平凡的Class如你所见,不会产生编译器警告信息。Class<?>的好处是它表示你并非是碰巧或者由于疏忽,而使用了一个非具体的类引用,你就是选择了非具体的版本。
为了创建一个Class引用,他被限定为某种类型,或该类型的任何子类型,你需要将通配符与extends关键字相结合,创建一个范围。因此,与仅仅声明Class<Number>不同,现在做如下声明:
public class BoundedClassReferences {
public static void main(String[] args) {
Class<? extends Number> bounded=int.class;
bounded=double.class;
bounded=Number.class;
//or anyting else derived from Number;
}
}
向Class引用添加泛型语法的原因仅仅是为了提供编译器检查,因为如果你操作有误,稍后立即就会发现这一点。在使用普通Class引用,你不会误入歧途,但是如果你确实犯了错误,那么知道运行时你才发现他,而这显然显得很不方便。
下面的示例使用了泛型类语法。它存储了一个类引用,稍稍又产生了一个List,填充这个List的对象是使用了newInstance()方法,通过该引用生成的:
public class CountedInteger {
private static long counter;
private final long id=counter++;
public String toString(){
return Long.toString(id);
}
}
public class FilledList<T> {
private Class<T> type;
public FilledList(Class<T> type){
this.type=type;
}
public List<T> create(int nElements){
List<T> result=new ArrayList<T>();
try{
for(int i=0;i<nElements;i++){
result.add(type.newInstance());
}
}catch(Exception e){
throw new RuntimeException(e);
}
return result;
}
public static void main(String[] args){
FilledList<CountedInteger> fl=new FilledList<CountedInteger>(CountedInteger.class);
System.out.println(fl.create(10));
}
}
注意,这个类必须假设与它一同工作的任何类型都具有一个默认的构造器(无参构造器),如果不符合该条件,你将得到一个异常。编译器对该程序不会产生任何警告信息。
当你将泛型语法用于Class对象时,会发生一件很有趣的事情:newInstance()将返回该对象的确切类型,而不仅仅只是在ToyTest.java中看到的基本Object.这在某种程度上有些受限:
public class GenericToyTest {
public static void main(String[] args) throws Exception{
Class<FancyToy> ftClass=FancyToy.class;
FancyToy fancyToy=ftClass.newInstance();
Class<? super FancyToy> up=ftClass.getSuperclass();
//Class<Toy> up2=ftClass.getSuperclass();//this won't compile
//only produces Object
Object obj=up.newInstance();
}
}
如果你手头的是超类,那么编译器将只允许你声明超类引用是“某个类,它是FancyToy超类”,就像在表达式Class<? Super FancyToy>中看到的,而不会接受Class<Toy>这样的声明。这看上去显得有些怪,因为getSuperClass()方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了-在本例子中就是Toy.class-而不仅仅只是“某个类,它是FancyToy超类”.不管怎么样,正是由于这种模糊性,up.newInstance()返回值不是精确类型,而只是Object.