Class对象

来源:互联网 发布:淘宝违规申诉入口 编辑:程序博客网 时间:2024/05/16 17:17

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 stubClass 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 thingintClass=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 ObjectObject obj=up.newInstance();}}

如果你手头的是超类,那么编译器将只允许你声明超类引用是“某个类,它是FancyToy超类”,就像在表达式Class<? Super FancyToy>中看到的,而不会接受Class<Toy>这样的声明。这看上去显得有些怪,因为getSuperClass()方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了-在本例子中就是Toy.class-而不仅仅只是“某个类,它是FancyToy超类”.不管怎么样,正是由于这种模糊性,up.newInstance()返回值不是精确类型,而只是Object.








原创粉丝点击