类加载和反射
本文阐述java中的类加载和反射两个底层原理。类
类加载
类加载的概念
类加载将.java编译后的.class文件的二进制数据,加载到内存的过程就叫做类加载。
类的生命周期
类的生命周期分为5个部分:加载、连接、初始化、使用、卸载。
1.加载
加载即查找并加载类的二进制数据,分为如下3步:
(1) 根据一个类的权限定名(包名+类名)来获取其定义的二进制字节流。
(2)将类中的所有静态的数据的存储结构都转换到方法区中去,作为程序运行时的数据结构,需要注意的是方法区也是堆。(放置的更多的是与类相关的数据)
与类相关的数据(类的代码、静态域/静态属性、静态初始化块、静态方法、常量池。
(3)2. 将类中的所有静态的数据的存储结构都转换到方法区中去,作为程序运行时的数据结构,需要注意的是方法区也是堆。(放置的更多的是与类相关的数据)
与类相关的数据(类的代码、静态域/静态属性、静态初始化块、静态方法、常量池。
2.连接
(1)验证
验证的作用,确保加载类的正确性
文件格式验证、元数据验证、字节码验证、符号引用验证
(2)准备
为类的静态属性分配内存,并初始化为默认值(基本数据类型为0,引用数据类型为null)。
(3)解析
将类的二进制数据中的符号引用,全部替换为直接引用。
3.初始化
初始化就是对静态属性进行初始化
(1)声明静态属性时指定初始值;
(2)使用静态初始化块为静态属性指定初始值
为类的静态变量赋予正确的初始值。
- 假如这个类还没有被加载和连接,程序先加载并连接该类。
- 假如该类的直接父类还没有被初始化,则先初始化其直接父类。
- 假如该类中有初始化语句,则系统依次执行这些初始化语句。
当Java程序首次通过下面六种方式来使用某个类或者接口时,系统就会初始化该类或者接口:
1.创建类的实例。
2. 调用某个类的静态方法。
3.访问某个类或接口的静态属性,或者为静态属性赋值。
4.使用反射方式强制创建某个类或接口对应的java.lang.Class对象。
5.初始化某个类的子类。
6. 直接使用java.exe命令运行某个主类。
以下三种不会初始化:
- 使用final 修饰的常量
- 通过数组定义引用
- 通过子类引用父类的静态变量,也不会初始化
使用标准javaSE版本的类加载器,加载的类,一旦被加载拥有缓存效果,但并不会是说永远不销毁,它的销毁时间有垃圾回收器决定。
4.使用
使用是正常使用类,如实例化对象、调用方法等。
5.卸载
卸载的情况一般有如下几种:
- 执行了System,exit()
- 程序正常执行结束
- 在执行过程中遇到了异常或错误而异常终止
例:
该例中会将类的信息先加载在方法区中,本例中有两个类MainEnter、和A两个类,会在方法区中加载类的代码、静态变量、静态方法、常量池和类的类名。会在堆中产生相应的java.lang.Class对象,并分配相应的存储空间,初始化等工作。在执行左边的代码时,main方法以及A()的构造方法,会压入方法栈中,main方法在最底层。
类加载器中类的来源
- 本地文件系统加载的.class文件
- 从jar文件中加载.class文件,这种也是比较常见
- 通过网络加载class文件
- 动态加载一个Java源文件,先编译,后加载
类加载器的类型
类加载器的类型主要有如下的类型:
类的加载由类加载器完成。JVM本身包含了一个类加载器,称为根类加载器(Bootstrap ClassLoader)。和JVM一样,根类加载器是用本地代码实现的,它负责加载核心Java类(即所有java.开头的类)。
1.启动类加载器:核心类库 java.,Bootstrap ClassLoader
2.扩展类加载器:扩展类库 javax.* Extension classLoader
3,.应用类加载器:Application ClassLoader
自定义类加载器也是应用程序类加载的一种。
JVM类加载器
•全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
•父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
•缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
不同的类加载器,加载相同的类时,产生class对象不一定相同。
自定义类加载器:
public class InitClassLoader extends ClassLoader {
// "C:/eclipse-workspace/javaoo20/bin"
private String rootDir;
public InitClassLoader(String rootDir) {
// TODO Auto-generated constructor stub
this.rootDir = rootDir;
}
/**
* 根据类的全限定名查找类
*/
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
// 从类的缓存区中,去找到类是否已经被加载过了,如果加载过了,直接返回Class对象
Class<?> c = findLoadedClass(name);
if (c != null) {
return c;
} else {
ClassLoader parent = this.getParent();// 根据继承关系,找到父加载类
c = parent.loadClass(name);
if (c != null) {
return c;
} else {// 爹不加载,自己加载
byte[] datas = getClassData(name);
if (datas == null) {
throw new ClassNotFoundException(name);
} else {
c = defineClass(name, datas, 0, datas.length);// 将字节数组,转换成Class实例
}
}
}
return c;
}
/**
* 该方法读取.class文件,返回内部的字节码内容
*
* @param name
* @return
*/
private byte[] getClassData(String name) {
// TODO Auto-generated method stub
// com.gezhi.javaoo20.bean.Person
String filePath = rootDir + "/" + name.replace(".", "/") + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(filePath);
out = new ByteArrayOutputStream();
byte[] datas = new byte[1024];
// 输入流中,当读到-1时,表示读完了
int lenth = 0;
while ((lenth = in.read(datas)) != -1) {
out.write(datas, 0, lenth);
}
return out.toByteArray();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
} finally {
try {
in.close();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//主方法:
public class MainEnter02 {
public static void main(String[] args) {
// TODO Auto-generated method stub
InitClassLoader icl = new InitClassLoader("C:/eclipse-workspace/javaoo20/bin");
InitClassLoader icl02 = new InitClassLoader("C:/eclipse-workspace/javaoo20/bin");
Class<?> c = null;
Class<?> c1 = null;
Class<?> c2 = null;
try {
c = icl.findClass("com.gezhi.javaoo20.bean.Person");
System.out.println(c);
System.out.println(c.hashCode());
c1 = icl.findClass("com.gezhi.javaoo20.bean.Person");
c2 = icl02.findClass("com.gezhi.javaoo20.bean.Person");
System.out.println(c1.hashCode());
System.out.println(c == c1);
System.out.println(c2.hashCode());
System.out.println(c == c2);
//类加载器
System.out.println(c.getClassLoader());
System.out.println(c.getClassLoader().getParent());
System.out.println(c.getClassLoader().getParent().getParent());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
反射的概念
反射机制:+也就是说,在Java中,可以在程序运行时动态加载、探索和使用在编译时完全不确定的类。
换句话说:Java运行时动态加载一个只知道“完全限定类名”类,并得到完整的数据结构(运行时数据),也可以指的是完整的数据结构,我们必须创建类的实例,以及访问一个类的属性,可以访问类的方法,我们把这种能力,也被称为“内省”和“自律”、“自省”.
在Java程序中获得Class对象的方式有三种:
1. 如果类的实例已经存在,我们可以使用以下方法:
Person p = new Person();
Class c=对象名.getClass();
2. 如果编译器在编译时已经知道类,我们也可以使用类名.class获得class对象:
class c= 类名.class;
3.使用反射中Class.formName(),知道类全名
Class c=Class.forName(“权限定名”)
对象实际上代表现实生活中的一些数据,或者是数据的表示,当然Class的对象,代表方法区中的“类的数据结构”,类的相关结构信息,也被存储在Class对象中。
Class对象,不像其他的类的对象,直接存储数据,更像镜子,可以间接的访问“方法栈”。
通过Class对象,探究类的相关信息
Class c=Class.forName(name)
探究一个类中的属性
c.GetField()//获得所有的公有的属性
c.getDeclaredField()//获得所有的属性,包括私有属性
c.getField(name)//通过名字获得属性,公有的中查找
c.getDeclaredField(name)//通过名字获得属性,公有的中查找
探究一个类中的方法
Method[] methods=c.getMthods()
For(Method m:methods){
getMethods()获得公共的方法,包括继承至父类的方法
getName()方法名
getParameterType()参数列表
getReturnType()返回类型
getDeclaredMethods()获得所有方法
}
Method method=c.getMethod(“方法名”,参数1,参数2,参数3)
本方法用于通过方法名和参数列表取出公共的方法(参数可变)
Method[] method=c.getDeclaredMethod() //得到私有方法
c.getName()//得到类的全路径
c.getSuperclass().getName()//得到类的超类的全路径
c.getSimpleName()//得到类的类名
探究一个类的构造器
Constructor[] cons=c.getConstructors();//获取所有公共的的构造器
c.getDeclaredConstructors();//获取私有的无参构造
Constructor<?>未知的构造器类型
c.getDeclaredConstructors(String.class,)//有参构造
使用构造,产生类的对象
类加载
Constructor<?> constructor =c.getConstructor();
Object obj=constructor.newInstance();//利用构造器产生对象
Constructor<?> constructor=c.getDeclaredConstructor();//私有无参构造
Constructor.setAcceable(true)//安全管理,放开私有权限
Object obj=constructor.newInstance(“张三”,“”);//有参构造
使用Class对象,操作类的属性
Field field=c.getField(“gender”);
Field.get(obj)//可以得到某个属性的值
Field.set(obj,””);//设置某个值
使用Class对象,操作类的方法
Method method=c.getMethod(“方法名”,“参数(String.class)”);//获得方法
Method.invoke(obj,””)//执行某个对象的某个方法,并传入参数
Method.setAccessible(true)//设置权限
注解
JDK1.5以后提供的,一种和类,接口,数组地位平等的一种新的特性
注解可以应用在以下的这些元素中:包、类、属性、方法、形参列表
它的作用主要有3个:
-
跟comment(注释)一样,对元素进行注释说明
-
跟其他的第三方达成一定的共识,或者达成一定的“约定”如:@override注释就是我们代码和编译器达成一个共识,由此注解的元素,必须是一个重新实现的方法
-
第三个作用,注解的内容可以在程序中,被访问到,比如:完成ORM映射
注解的格式:@注解的名称(参数名)
注解的声明:Public @interface 注解名{
}
常见的注解:JDK的内置注解、自定义注解
JDK的内置注解:@override(重写)、@Deprecated(过时、不推荐、废弃)、@SuppressWarning(”unused”)
Deprecation 使用了不赞成使用的类或方法时
Unchecked 未检查
Fallthrough switch case未写break
Finally finally子句未能完成
All 所有的警告抑制
元注解
Target注解可能出现的地方
反射案例:
通过反射加载学生类,新建实例并设置属性信息,通过setAge改变年龄。
package com.gezhi.practise.fourth.entity;
/**
* 学生类
* @author Administrator
*
*/
public class Student {
/**
* 学生名字
*/
private String studentName;
/**
* 学生年龄
*/
private int age;
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public int getAge() {
return age;
}
public void setAge(int age) throws AgeErrorException {
if(age>=1 && age<=100) {
this.age = age;
}else {
System.out.println("年龄在1-100之间");
}
}
}
使用反射:
package com.geizhi.practise.first.main;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class MainStudent {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
/**Class<?>一个未知的类型
* 加载Student类,通过权限定名即com.gezhi.practise.fourth.entity.Student,该类的全路径
*/
Class <?> Student=Class.forName("com.gezhi.practise.fourth.entity.Student");
/**
* 根据加载的类,新建一个实例对象
*/
Object student=Student.newInstance();
/**
* Field属性类,import java.lang.reflect.Field;导包时导入reflect相关的
* 获得Student中的studentName属性
*/
Field field01=Student.getDeclaredField("studentName");
/**
* 获得Student中的age属性
*/
Field field02=Student.getDeclaredField("age");
/**
* 由于是private设置权限,不安全检查
*/
field01.setAccessible(true);
field02.setAccessible(true);
/**
* 给Student设置值
* 第一个参数是实例对象
* 第二个参数是设置的值
*/
field01.set(student, "李建平");
field02.set(student, 18);
/**Method方法类
* 获得Student类中的setAge方法
* 导包时,导入:import java.lang.reflect.Method
* 通过getMethod方法获得
* 第1个参数:方法名
* 第2个参数:方法参数列表,参数类型而不是变量,如为基本数据类型则:int.class、double.class、String.class
*/
Method method=Student.getMethod("setAge", int.class);
/**
* invoke运行方法
* 方法名.invoke
* 参数1:实例对象
* 参数2:方法参数值
*/
method.invoke(student, 25);
System.out.println(field01.get(student));
System.out.println(field02.get(student));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
执行结果: