类的加载
理论知识及例子来自李刚的疯狂java讲义,在此感谢李老师!!!
类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的类加载器我们称之为系统类加载器。除此之外我们还可以通过几成ClassLoader基类来自定义类加载器
从本地文件加载class文件
从jar包中加载class文件,就像jdbc:oracle:OracleDriver似的
通过网络读取加载class文件
把一个java文件动态编译并加载
类被加载之后会在系统中为之生成一个Class对象,接着将会进入连接阶段
连接阶段会把类的二进制文件加载到jre中,连接阶段又可以分为以下三步:
验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致
准备:为类的静态属性分配内存并初始化
解析:将类中二进制数据中的符号引用替换成直接引用
类的初始化阶段,虚拟机负责对类进行初始化,主要是对静态属性初始化,静态属性初始化有两种方式:(1)声明静态属性时直接初始化;(2)使用静态初始化块初始化;
publicclassStaticParameter {
/**
*静态的final属性只能在两个位置初始化
* 1)声明时直接初始化
* 2)使用静态初始化块初始化
*/
publicstaticfinalinta;
static{
a= 1;
}
}
静态int属性不初始化则默认为0,String对象为null
publicclassStaticParameter {
/**
*静态的final属性只能在两个位置初始化
* 1)声明时直接初始化
* 2)使用静态初始化块初始化
*/
publicstaticfinalinta;
publicstaticintb;
publicstaticStringstr;
static{
a= 1;
}
publicstaticvoidmain(String[] args) {
System.out.println(b);//o
System.out.println(str);//null
}
}
JVM初始化一个类的步骤
假如这个类还没被加载和链接,那么程序先加载连接该类
假如该类的直接父类还没有初始化,则先初始化其直接父类
假如类中有初始化块,则按照前后顺序依次初始化
类初始化的时机
当java程序通过下面6种方式使用某个类或者某个接口时,系统会初始化该类或者接口。
创建类的实例(使用new关键字、使用类的反射、使用反序列化创建实例)
调用这个类的静态方法
访问它的静态属性或者为其静态属性初始化
使用反射方式强制创建某个类或某个接口对应的Class对象,如Class.forName(“Person”),如果系统还未初始化Person类则此操作会导致Person类被初始化
初始化该类的子类
直接使用java.exe来运行该类
需要注意的是:对于final类型的静态属性,如果在编译时就可以得到属性值(初始化),则可认为该属性可以被当成编译时常量,。当程序使用编译时常量,不会导致该类的初始化。
publicclassFinalStaticLoader {
publicstaticvoidmain(String[] args) {
System.out.println(Test.str);
//输出结果为adfggg,可以看出Test类并没有被加载
}
}
classTest{
static{
System.out.println("类被初始化加载");
}
publicstaticfinalStringstr="adfggg";
}
如果一个类的静态final属性在运行时才能够确定属性值,则调用此属性就会初始化此类。
publicclassFinalStaticLoader {
publicstaticvoidmain(String[] args) {
System.out.println(Test.time);
//输出结果为1386331930159,可以看出Test类被加载了
}
}
classTest{
static{
System.out.println("类被初始化加载");
}
publicstaticfinallongtime= System.currentTimeMillis();
}
类加载器
JVM启动时会形成三个类加载器组成的初始类加载器层次结构
Bootstrap ClassLoader:根类加载器
Extension ClassLoader:扩展类加载器
System ClassLoader:系统类加载器
根类加载器负责java的核心类加载,并不是ClassLoader的子类,而是由JVM自身实现的。
扩展类加载器负责jre的扩展目录(jdk1.7.0_21\jre\lib\ext)中jar的类包,通过这个功能我们可以为java核心类扩展核心类以外的其他功能(将我们自己制作的jar包放在该目录下)。
系统类加载器负载JVM启动时加载来自命中java中的-classpath或者java.class.path属性的,或CLASSPATH环境变量所指定的jar包或类路径。如果没有特别指定则用户自定义的类加载器你都是用系统类加载器作为父类。
创建并使用自己的类加载器:
JVM中除了根加载器之外的所有类加载器都是ClassLoader的子类实例,ClassLoader包含了大量的protected方法——这些方法都可以被子类重写。
URLClassLoader类:
java为ClassLoader提供了一个URLClassLoader实现类,该类也是系统类加载器和扩展类加载器的父类(继承关系,而不是父类加载器),我们可以直接使用URLClassLoader来加载类,常用构造方法为:
URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls指定的系列路径查询并加载类。
URL[] urls = {newURL("file:mysql.jar")};
URLClassLoader myClassLoader =newURLClassLoader(urls);
Driver driver= (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance();
URL可以以file:http:ftp:作为前缀表示从文件。网络。ftp加载。
通过反射获取类的信息
java多态机制使得java程序许多对象在运行时出现两种类型,编译时类型和运行时类型。假如从外部传来一个对象供我们使用,对象的编译类型为Object我们如何获知它的运行时类型呢?
如果我们明确知道对象的编译、运行时类型则可以使用instanceof进行判断后强制类型转换
如果我们不知道对象的编译时类型,程序只能靠运行时获取它的信息,这时则必须使用反射
使用反射机制获取类信息步骤:获得Class对象
每个类被加载之后,系统会为孩子生成一个Class对象,通过该Class对象则可以访问JVM中的这个类。java中获取Class对象有如下三种方式
使用Class。forName(String str)方式获取,参数为类的全限定名(必须添加完整包名)
调用某个类的class属性,比如Person.class会返回Person类对应的Class对象
调用某个对象的getClass()方法,这个方法位于java.lang.Object下因此每个对象都可调用,该方法返回该对象所属类对应的Class对象
对于第一种和第二种方法都是直接使用类来获取该类对应的Class对象,第二种方法更为常用;因为他可以在类编译期间就判定该类的Class对象是否存在,且无需调用方法更加效率。
从Class对象中获取信息
通过Class对象可以获取大量的Method Constructor,Field等属性,这些对象分别代表该类所包括的方法,构造器,和属性,我们可以通过这些对象来执行实际的功能:例如调用方法、创建实例。
extends U>
( clazz)强制转换该 Class对象,以表示指定的class对象所表示的类的一个子类。
(obj)将一个对象强制转换成此 Class对象所表示的类或接口。
boolean
()如果要在调用此方法时将要初始化该类,则返回将分配给该类的断言状态。
static>
(className)返回与带有给定字符串名的类或接口相关联的 Class对象。
static>
(name, boolean initialize,loader)使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class对象。
A
( annotationClass)如果存在该元素的指定类型的注释,则返回这些注释,否则返回null。
()返回Java Language Specification中所定义的底层类的规范化名称。
>[]
()返回一个包含某些 Class对象的数组,这些对象表示属于此 Class对象所表示的类的成员的所有公共类和接口。
()返回表示数组组件类型的 Class。
(>... parameterTypes)返回一个 Constructor对象,它反映此 Class对象所表示的类的指定公共构造方法。
()返回一个包含某些 Constructor对象的数组,这些对象反映此 Class对象所表示的类的所有公共构造方法。
>[]
()返回 Class对象的一个数组,这些对象反映声明为此 Class对象所表示的类的成员的所有类和接口。
(>... parameterTypes)返回一个 Constructor对象,该对象反映此 Class对象所表示的类或接口的指定构造方法。
()返回 Constructor对象的一个数组,这些对象反映此 Class对象表示的类声明的所有构造方法。
(name)返回一个 Field对象,该对象反映此 Class对象所表示的类或接口的指定已声明字段。
()返回 Field对象的一个数组,这些对象反映此 Class对象所表示的类或接口所声明的所有字段。
(name,>... parameterTypes)返回一个 Method对象,该对象反映此 Class对象所表示的类或接口的指定已声明方法。
()返回 Method对象的一个数组,这些对象反映此 Class对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
()如果此 Class对象所表示的类或接口是另一个类的成员,则返回的 Class对象表示该对象的声明类。
()如果该 Class对象表示构造方法中的一个本地或匿名类,则返回对象,它表示底层类的立即封闭构造方法。
()如果此 Class对象表示某一方法中的一个本地或匿名类,则返回对象,它表示底层类的立即封闭方法。
[]
()如果此Class对象不表示枚举类型,则返回枚举类的元素或null。
(name)返回一个 Field对象,它反映此 Class对象所表示的类或接口的指定公共成员字段。
()返回一个包含某些 Field对象的数组,这些对象反映此 Class对象所表示的类或接口的所有可访问公共字段。
()返回表示某些接口的 Type,这些接口由此对象所表示的类或接口直接实现。
()返回表示此 Class所表示的实体(类、接口、基本类型或void)的直接超类的 Type。
>[]
()确定此对象所表示的类或接口实现的接口。
(name,>... parameterTypes)返回一个 Method对象,它反映此 Class对象所表示的类或接口的指定公共成员方法。
()返回一个包含某些 Method对象的数组,这些对象反映此 Class对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member方法。
int
()返回此类或接口以整数编码的Java语言修饰符。
()以 String的形式返回此 Class对象所表示的实体(类、接口、数组类、基本类型或void)名称。
super>
()返回表示此 Class所表示的实体(类、接口、基本类型或void)的超类的 Class。
()按声明顺序返回 TypeVariable对象的一个数组,这些对象表示用此 GenericDeclaration对象所表示的常规声明来声明的类型变量。
boolean
()如果此 Class对象表示一个注释类型则返回true。
boolean
( extends> annotationClass)如果指定类型的注释存在于此元素上,则返回true,否则返回false。
boolean
()当且仅当底层类是匿名类时返回 true。
boolean
()判定此 Class对象是否表示一个数组类。
boolean
(> cls)判定此 Class对象所表示的类或接口与指定的 Class参数所表示的类或接口是否相同,或是否是其超类或超接口。
boolean
()当且仅当该类声明为源代码中的枚举时返回true。
boolean
(obj)判定指定的 Object是否与此 Class所表示的对象赋值兼容。
boolean
()判定指定的 Class对象是否表示一个接口类型。
boolean
()当且仅当底层类是本地类时返回 true。
boolean
()当且仅当底层类是成员类时返回 true。
boolean
()判定指定的 Class对象是否表示一个基本类型。
boolean
()如果此类是复合类,则返回 true,否则 false。
()创建此 Class对象所表示的类的一个新实例。
测试
publicclassPerson {
publicinti= 1;
//私有构造器
privatePerson(){}
//公有带参数的构造器
publicPerson(String name){
System.out.println("有参数的公有构造器");
}
//无参普通方法
publicvoidinfo(){
System.out.println("无参普通方法");
}
//有参的普通方法
publicvoidinfo(String str){
System.out.println("有参的普通方法");
}
//内部类
classInner{}
//通过反射机制获取类的信息
publicstaticvoidmain(String[] args)throwsException{
//获取Person类的Class对象
Class c = Person.class;
System.out.println("该Class对象对应的包名为:"+c.getPackage());
System.out.println("父类为:"+c.getSuperclass());
//获取该Class对象对应的全部构造器
Constructor>[] ctor = c.getDeclaredConstructors();
System.out.println("打印Person类的全部构造器");
for(Constructor cs:ctor){
System.out.println(cs);
}
//获取该Class对象对应的全部public构造方法
Constructor[] pubCtor = c.getConstructors();
System.out.println("Person类的public构造方法");
for(Constructor pubc:pubCtor){
System.out.println(pubc);
}
//获取Class对象对应的全部public方法,父类对应的方法也会被取出
Method[] m = c.getMethods();
System.out.println("全部public方法");
for(Method m1:m){
System.out.println(m1);
}
//获取内部类
Class[] inner = c.getDeclaredClasses();
System.out.println("打印内部类");
for(Class cIn:inner){
System.out.println(cIn);
}
}
}
使用反射生成并操作对象
Clss对象可以获取该类里包含的方法(Method),属性(Field)和构造器(Constructor),程序可以使用Method对象来执行对应的方法,通过Constructor对象调用对应的构造器你创造对象。
1、创建对象
使用Class的newInstance()方法创建一个该Class对应类的一个实例,此方法需要调用该类的默认构造器,因此对应得类需要有默认构造器才可以
使用Clss对象获取对应的Constructor对象,再调用Constructor对象的newInstance()方法创建实例,此种方法可以使用指定构造器创建实例。
通过第一种方式比较常见,java很多框架中都使用配置文件创建java对象,从配置文件读取的时字符串类名,需要通过该字符串创建对应的类就需要使用反射的机制。
测试程序:创建一个对象池,对象池根据name-value对创建对象
publicclassObjectPoolFactory {
//定义一个对象池,前面是对象名,后面是实际对象
privateMapobjectPool=newHashMap();
//定义一个创建对象的方法,该方法只要传入一个字符串类名,就可以根据该类名创建一个对象
privateObject createObject(String className)throwsClassNotFoundException, InstantiationException, IllegalAccessException{
//根据字符串获取对应的Cladd对象
Class> c = Class.forName(className);
//使用默认构造器创建实例
returnc.newInstance();
}
//读取properties配置文件创建实例并初始化对象池
publicvoidinitPoll(String fileName)throwsIOException, ClassNotFoundException, InstantiationException, IllegalAccessException{
FileInputStream fis =null;
fis =newFileInputStream(fileName);
Properties pro =newProperties();
pro.load(fis);
for(String name:pro.stringPropertyNames()){
//每取出一个键值对则根据属性值创建一个对象,调用createObjecy()方法生成对象放入对象池
objectPool.put(name, createObject(pro.getProperty(name)));
}
fis.close();
}
//从对象池中获取指定name的对象
publicObject getObject(String name){
returnobjectPool.get(name);
}
publicstaticvoidmain(String[] args)throwsIOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
ObjectPoolFactory opf =newObjectPoolFactory();
opf.initPoll("obj.properties");
System.out.println(opf.getObject("a"));
}
}
如果不想使用默认构造器创造实例,则需要使用Constructor对象对应的构造器
获取该类的Class对象
利用Class的getConstructor()方法获取指定构造器
调用特定的Constructor的newInstance()方法创建对象
对已知类的创建一般不适用反射机制,毕竟效率要低一些。但是当程序需要动态创建某个类的对象时会考虑使用反射,SPRING框架使用较多的反射机制。
调用方法
当获取某个类对应的Class对象之后则可以使用getMethods()或者getMethod()方法获取全部或者指定的方法;每个Method对象对应一个方法,获取Method之后就可以使用Method的invoke方法调用指定方法
Object invok(Object obj,Object para):obj为主调,后面的为参数
下面对对象池的功能进行加强,,程序允许在配置文件中增加对象的属性值,对象池工厂会读取对象的属性值并调用对象的setter方法为对象初始化属性。
publicclassExtendedObjectPollFactory {
//创建一个对象池,key为对象名,value为对象类型
privateMapobjPool=newHashMap();
//读取配置文件初始化Properties对象
privatePropertiespro=newProperties();
publicvoidinitPro(String fileName){
FileInputStream fis =null;
try{
fis =newFileInputStream(fileName);
pro.load(fis);
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally{
if(fis !=null){
try{
fis.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
//定义创建对象的方法,传入一个字符串类名则创建一个对象
publicObject createObj(String name)throwsClassNotFoundException, InstantiationException, IllegalAccessException{
//使用字符串类名获取该类对应的Class对象
Class c = Class.forName(name);
//使用newInstance方法获取一个实例
returnc.newInstance();
}
//根据配置文件初始化对象池
publicvoidinitPoll()throwsClassNotFoundException, InstantiationException, IllegalAccessException{
//初始化对象池
for(String name:pro.stringPropertyNames()){
//取出一对键值对(key-value),如果键值对中不包含%则根据value创建一个实例(value为一个类名)并放入对象池
if(!name.contains("%")){
objPool.put(name, createObj(pro.getProperty(name)));
}
}
}
//为对象池中的对象属性初始化
publicvoidinitProperty()throwsSecurityException, NoSuchMethodException,
IllegalArgumentException, IllegalAccessException, InvocationTargetException{
for(String name:pro.stringPropertyNames()){
//如果名字中包含%则认为是设置的属性
if(name.contains("%")){
String[] objAndPro = name.split("%");
//取出需要设置属性的目标对象
Object target = getObject(objAndPro[0]);
//该属性对应的setter方法为set+”属性首字母大写“+属性其余字母小写
String mtdName ="set"+objAndPro[1].substring(0,1).toUpperCase()+objAndPro[1].substring(1);
//通过target对象的getClass方法获取他对应的Class对象
Class c = target.getClass();
//获取该属性对应的setter方法
Method mtd = c.getMethod(mtdName, String.class);
//通过Method的invoke方法执行setter方法
mtd.invoke(target,pro.getProperty(name));
}
}
}
//从对象池中根绝对象名取出对象的方法
publicObject getObject(String name){
returnobjPool.get(name);
}
publicstaticvoidmain(String[] args) {
ExtendedObjectPollFactory opf =newExtendedObjectPollFactory();
opf.initPro("cong.properties");
try{
opf.initPoll();
opf.initProperty();
System.out.println(opf.getObject("a"));
}catch(ClassNotFoundException e) {
e.printStackTrace();
}catch(InstantiationException e) {
e.printStackTrace();
}catch(IllegalAccessException e) {
e.printStackTrace();
}catch(SecurityException e) {
e.printStackTrace();
}catch(IllegalArgumentException e) {
e.printStackTrace();
}catch(NoSuchMethodException e) {
e.printStackTrace();
}catch(InvocationTargetException e) {
e.printStackTrace();
}
}
}
配置文件
a=javax.swing.JFrame
b=javax.swing.JLabel
a%title=TestTitle
a%itle表示a的title属性,其值为Test Title;spring框架就是将对象以及属性值都放置在配置文件中进行管理使得系统得到良好的解耦
此外如果需要访问类中的private属性或者private方法则需要有调用此类方法的权限,如果程序需要调用其私有方法或者属性可以使用Method的setAccessible(boolean flag)方法预先取消java的语言访问权限检查(setAccessible()方法时Method父类的方法)
访问属性值
通过Class的getField(或者getFields()方法可以获得Class的全部属性或者指定属性
getXxx(Object obj):获取指定对象的属性值,此处的Xxx对应8中基本类型,如果该属性时引用类型则取消Xxx;
setXxx(Object obj,Xxx val):将obj对象的该Field属性值设置为val,Xxx对应8中基本类型,如果为引用类型则取消Xxx。
使用这两个方法可以随意访问指定对象的所有属性,包括私有属性
publicclassFieldTest {
publicstaticvoidmain(String[] args) {
Student s =newStudent();
//获取Student类对应的的Class对象
Class sClass = s.getClass();
try{
/**
* getField()方法只能获取public属性
* getDeclaredField()方法则可以获得所有属性
*/
//获取Student中名称为name的属性
Field nameField = sClass.getDeclaredField("name");
//取消访问权限检查
nameField.setAccessible(true);
//调用set方法为nameField属性设置值
nameField.set(s,"pursuit");
//获取名为age的属性
Field ageField = sClass.getDeclaredField("age");
//取消访问权限检查
ageField.setAccessible(true);
ageField.set(s, 21);
System.out.println(s);
//输出结果为Student [name=pursuit, age=21]
}catch(SecurityException e) {
e.printStackTrace();
}catch(NoSuchFieldException e) {
e.printStackTrace();
}catch(IllegalArgumentException e) {
e.printStackTrace();
}catch(IllegalAccessException e) {
e.printStackTrace();
}
}
}
classStudent{
privateStringname;
privateintage;
publicString toString() {
return"Student [name="+name+", age="+age+"]";
}
}
操作数组:
java.lang.reflact报下提供了一个Array类,该类可以代表所有类型的数组。
程序可以使用Array类来动态创建数组并操作数组
static Object newInstance(Class,length):创建一个指定元素类型指定维度与长度的数组
static void setXXX(Object arry,index val):将数组中指定索引处的元素值设置为val,如果次数组元素是引用类型则使用set(Objec obt,index Object val)
publicclassArrayTest {
publicstaticvoidmain(String[] args) {
//创建一个长度为10的元素类型为String的数组
Object ary = Array.newInstance(String.class, 10);
Array.set(ary, 0,"反射数组");
Array.set(ary,1,"sfafa");
//取出元素的值
Object ary1 = Array.get(ary, 0);
Object ary2 = Array.get(ary, 1);
System.out.println(ary1);//反射数组
System.out.println(ary2);//sfafa
}
}