java类的加载,反射机制
一、类的加载机制
java程序运行需要某个类时,如果该类还没有加载到内存中,系统会通过加载、连接、初始化三个步骤来对类进行初始化
1.类加载
Java本身一.class字节码的形式存在,他不是一个可执行文件,所以需要jvm将类加载到内存中
类的加载由类加载器完成,jvm本身包含一个类加载器,称为 根类加载器(Bootstrap ClassLoader) 和JVM一样,根类加载器使用本地代码实现的,它负责加载核心java类 (即有java.*开头的类)
另外,jvm还提供了两个类加载器,它们都是用java语言写的,有根类加载器加载,其中,
扩展类加载器(Extension ClassLoader) 负责加载扩展类,包括所有javax.*开头的类和存放在JRE的扩展目录下(JAVA_HOME/jre/lib/ext)中JAR的类包
系统类加载器(Application ClassLoader) 负责加载应用程序自身的类
此外,java API中还提供了了一个ClassLoader抽象类,开发者可以通过继承ClassLoader基类来创建自定义的类加载器。
重点
1.类的class文件读入内存后,就会创建一个java.lang.Class对象。也就是说,当java程序中使用任何类时,系统都会为之创建一个java.lang.Class对象。一旦某个类被载入jvm中,同一个类就不会再次被载入。
2.一个类加载后,对应的Class对象,可以通过该类的实例的getClass() 方法得到,Class对象有一个getClassLoader() 方法,可以的到该类所用到的类加载器
上代码
//演示得到一个类的加载器
public class GetClassLoaderDemo{
public static void main(String[] args){
GetClassLoaderDemo g=new GetClassLoaderDemo();
Class c=g.getClass();
ClassLoader loader=c.getClassLoader();
System.out.println(loder);
//得到这个类加载器的父类信息,也就是扩展类加载器
System.out.println(loader.getParent());
}
}
2.连接
当类加载后,系统就为之创建了一个对应的Class对象,接着就会进入连接阶段。连接阶段会负责把类的二进制数据合并到JRE中。类连接又可以分为如下三个阶段
1.验证:验证被加载的类是否有正确的内部结构,并和其他类协调一致
2.准备:负责为类的静态属性分配内存,并设置默认初始值
3.解析:将类的二进制数据中的符号引用替换成直接引用
3.初始化
随后,进行类的初始化阶段,jvm负责对类进行初始化,也就是对静态属性进行初始化。在java类中,对静态属性指定初始值的方式有两种:(1)声明静态属性时指定初始值
(2)使用静态初始化块为静态属性指定初始换值
jvm初始化一个类,一般包含下面几个步骤
- 假如这个类还没有被加载和连接,程序先加载并连接该类
- 假如该类的直接父类还没有被初始化,则初始化其直接父类
- 假如该类中有初始化语句,则系统依次执行这些初始化语句
二、反射
1.反射概念
所谓反射,是指在java中,可以在运行期载入、探知和使用编译器完全未知的类
换句话说,java程序可以装载一个运行期才能得到名称的类,获取其完成结构,并创建对象、或者对类的成员变量设定值、或者调用其方法。这种“看透”类的能力,被称为反省、内省或自省
在java API中的java.lang包和java.lang.reflect包中,提供了Class类、Field类、Method类、Constructor类、Array类等用于实现反射机制
2. 使用反射查看类信息
前面我讲到了在类加载的时候,系统会为该类生成一个对应的java.lang.Class对象。通过Class对象,我们可以访问JVM中的这个类。在java程序中获得Class对象的方法有三种
第一种方式:在编译期不知道类名,但是在运行期可以获得该类名称的时候,使用Class类的forName()静态方法就可以获得Class对象。 例如:
Class c=Class.forName("全限定类名");
第二种方式:在编译期知道类名的情况,可以调用该类的class属性来获得该类对象的Class对象。 例如:
Class c=类名.class;
第三种方式:如果一个类的实例对象已经得到,则调用该对象的getClass()方法返回该对象的Class对象。getClass()方法是java.lang.Object类的方法之一,所有对象都可以调用该方法。 例如:
Class c=对象名.getClass();
在Class类中,通过getFields(),getMethods(),getConstructors()方法,可以通过获得Class对象所含的类的 public 的属性,方法,构造器
而通过getDeclaredFields(),getDeclaredMethods(),getDeclaredConstructors()方法,可以获得Class所包含类的所有属性,方法,构造器信息。分别返回Field,Method和Constructor类。
3.使用反射创建并操作对象
通过Class对象获取某个类的属性,方法和构造器后,程序就可以通过Constructor对象调用相应的构造器创建对象,通过Field对象访问对象并修改对象的属性。通过Method对象来执行相应的方法。此外,Class类还有一个newInstance()方法用于创建此Class对象所表示的类的一个新实例。
java中,创建对象有四种方式,分别是:new 、反射、对象克隆、对象反序列化。
这里说一下通过反射创建对象的两种方式
- 使用Class对象的newInstance()方法来创建该Class对象对应类的实例。这种方式要求Class对象对应的类有默认构造器,执行newInstance()方法时,实际上是调用默认构造器来创建实例。用这种方法创建对象比较常见。
- 先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应的类的实例。通过这种方式可以选择使用某个类的指定构造器来创建对象。
4.使用反射调用方法
- 通过Class.forName()或者 类名.class 或者 对象名.getClass()任意一种方法,获得Class对象
- 通过Class对象的getMethod()方法获取要调用的方法的Method对象
- 调用获取的Method对象的invoke()方法,来调用对应类的方法
5.访问属性值
通过Class对象的getFields()方法或者getField()方法可以获取一个类的全部属性或指定属性。这两个方法的返回值是一个Field对象数组或一个对象。Filed对象提供了如下两种方法来访问属性:
- getXXX(Object obj):获取obj对象的Field的属性值。这里XXX对应八种基本数据类型,如果是引用类型,则取消get后面的XXX。
- setXXX(Object obj ,XXX val):将obj对象的Field的属性值设置为val。这里XXX对应八种基本数据类型,如果是引用类型,则取消set后面的XXX。
6.动态创建和访问数组
使用Array类的静态方法newInstance(),可以创建Class对象对应类的数组。
Array类的静态方法set(),设置元素
Array类的静态方法et(),得到元素
public class ArrayDemo{
public static void main(String [] args) throws Exception{
Class c=Class.forName("java.lang.String");
//创建长度为5的字符串数字
Object array=Array.newInstance(c,5);
//设置索引位置为2的元素为 李四
Array.set(array,2,"李四");
//得到索引为2的元素
String s=(String)Array.get(array,5);
System.out.println(s);
}
}