java零基础入门-高级特性篇(十四) 类加载与反射 1
初学者有时候在做练习的时候,可能会碰到一个十分诡异的问题。今天老师讲到了String字符串,好开心,打开eclipse开始写代码,然后一顿操作,只见双手在键盘上飞驰,行云流水般写出如下代码,嘴角露出一丝微笑,String也不过如此嘛。
等等,咋报错了?这是个什么情况?哪里有错?为什么连个String类型都不给我用了?为什么!!!接下来轻则怀疑自己智商,重则自暴自弃,从此放弃走上码农之路。
其实大家仔细一看,哦~原来是类的名字有问题啊,怎么能定义一个叫String的类呢,这样写肯定报错呀。但请各位再仔细想一想,为什么就不能定义一个叫String的类呢?恐怕能说出其中原因的同学并不多。
类加载器与双亲委派模型
首先来回忆一下前面的知识。编写完java文件后,jvm是不能直接运行java文件的,首先要将java文件编译成class文件以后,jvm再把class文件加载到内存中,创建一个Class对象,这时候才可以使用这个类。加载这个动作,就是由类加载器完成的。在jvm中有三个内置类加载器,Bootstrap ClassLoader,Extension ClassLoader,Application ClassLoader。
Bootstrap ClassLoader:启动类加载器,用于加载java核心库jre/lib/rt.jar
Extension ClassLoader:扩展类加载器,用于加载java扩展库jre/ext/*.jar
Application ClassLoader:应用程序类加载器,自己写的代码都是使用这个加载器来加载的
先来看看这几个加载器的工作流程
这个类的加载过程有一个更加专业的名字,叫做双亲委派模型。这个模型名字看名字不好理解,来想象一下下面这个场景。小明一家人一起吃饭,看到大鸡腿就想吃,但是中华文明优良传统告诉他,得先问问爸爸,这个大鸡腿我能吃吗?然后爸爸又看看爷爷,问爷爷要吃吗?爷爷说还是给小明吃吧,这时候爸爸再告诉小明,你可以吃了。爸爸和爷爷是双亲,能不能吃要先看上面,如果恰好爷爷也想吃,小明也只能干瞪眼了。
再来看这个双亲委派模型,在加载类的时候,应用程序类加载器不会直接去加载它,它先要问它的父类加载器,它的父类加载器再去问爷类加载器,爷类加载器一看,不该我管,丢给父类,父类一看,也不是我管,最后丢回来给应用程序类加载器。
再来看看上面那个String的例子,为什么会报错。java本身有一个String的类型,String在哪?
注意String最上面的jar包,Bootstrap ClassLoader的管辖范围就是这个jar包,现在知道程序报错的原因了没?根据类的加载流程,我们希望得到的String是应该被启动类加载器加载的String,如果String的类路径是java.lang.String,就是我们想要的String。但是上个例子里面,我们自定义了一个String,它的类路径不是java.lang.String,所以他不会被启动类加载器加载,而是最后被应用程序类加载器加载,所以它并不是我们想要的那个String,不是那个可以用字符串赋值的类型String,这时候将一个字符串赋值给这个“String”,在编译的时候就会出错,因为我们自定义的String不具备这个接收字符串赋值的功能。
Class类型
上面说过,jvm把class文件加载到内存中时,会创建一个Class对象。这个Class对象是什么呢?类不是用class定义的么,怎么还有一个Class?别晕,现在就来详细说说Class是干什么的。
先从Class存在的意义来说。类是一类事物的描述,类包含了这类事物的信息,比如车这个类,包含了车的类型,用途,行为信息,可以在路上跑。鱼这个类,包含了鱼的种类,名称,行为信息,可以在水里游。
从上图可以看到,用属性和行为可以描述一类事物。那么“类”这个事物,是否也可以被描述呢?答案是肯定的,Class类就是用来描述类的信息的。Class也是一种类型,它专门用来描述类的特征。
车这个类可以用名称,类型,行为来描述。类这个类可以用属性,行为来描述,所有的类都可以有属性,都可以有行为。可以将Class类看做是普通类型的更高层抽象,用来描述普通类如何构成,包含内容等信息。
再从类的加载和对象创建来说。每写完一个类文件,首先会被编译成.class文件,然后在运行时,这个.class文件会被加载到jvm中,如果是第一次加载这个类,那么会同时生成这个类对应的Class对象。当使用new关键字创建类的对象的时候,会首先去这个类对应的Class对象获取该类的信息,然后创建对象,所以可以将Class对象看做是类的模板,同一个类的对象创建都使用这个模板。类创建的对象可以有很多,但是模板只有一份,也就是说每个类对应的Class对象只有一个。
java文件被编译加载后创建Class对象,当这个java文件的类需要创建对象的时候,也就是使用new关键字创建对象的时候,会去获取那个已经被创建好的Class对象中的信息。这里要注意一个重点,获取Class对象信息的时候是运行时,只有在运行时才能通过Class获取类的信息。
获取Class对象
既然Class对象包含这么多信息,那么在程序中如何获取Class对象?获取Class的方法有三种:
1.Class.forName("类名"); 通过类名字符串获取Class对象。
2.通过类的对象调用getClass() 获取该类型的Class对象
3.通过类型直接获取Class对象
先看第一种方法,通过类型的全限定名获取Class对象。通过这种方式可以直接使用一个字符串来获取Class对象,这种方式使用起来比较灵活,当需要对类型进行配置的时候,可以将对象的全限定名写入到配置文件中,这样就可以在不修改代码的情况下,通过修改配置的方式来改变创建对象的类型。需要注意的是,通过Class.forName()方法获取的对象是无法确定类型的,所以使用无限制泛型通配符。
第二种方法,是通过对象的实例的getClass()方法来获取Class对象,这种方式要先创建类型的对象,再根据创建的对象来获取Class对象。由于根据已知类型的对象获取的Class,所以这个Class对象的类型可以是User或其子类。
第三种方法,直接根据类型获取Class对象,这种方式最直接,连方法都不用调用,直接获取Class对象,速度最快效率最高。一般情况使用这种方法获取Class对象最为普遍。
最后要注意的一点是,同一个类只会有一个Class对象,上例中第二种和第三种方法获取到Class对象后,将获取到的对象与第一种方法获取到的Class对象进行地址比较,结果都是true。可以说明,不管用哪种方法获取Class对象,都只会获取到同一个并且是该类唯一一个Class对象。
现在已经获取到Class对象了,那么该如何使用它呢?前面说过,Class对象有类里面所有的信息,所以可以通过Class获取到类里面所有的构造器,方法,成员变量等等所有的信息。
Class对象可以获取到所有信息,上图只是一个普通的类,如果获取到ClassInfoDemo这个类对应的Class对象,就可以通过调用这个对象的方法获取如上图中包,成员变量,构造器,方法等信息。如果类还有其他信息比如注解,实现接口方法,内部类,外部类等等信息,都可以通过Class对象的对应方法获取,可见Class是一个功能非常强大的类。
讲了半天类的加载和Class对象,这些知识点有什么用?这都是为了接下来登场的知识点-反射打好基础,反射技术十分重要,在各大框架中被频繁的使用,所以在学习的过程中,了解反射的意义对后续框架的学习有很大的帮助。
本文为了方便理解,隐藏了部分类加载中的细节,如需深入了解,请自行查阅相关资料。