反射(Reflect)
1、 类加载
类在内存中的生命周期:加载–>使用–>卸载
1.1 类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载。
类的加载又分为三个阶段:
(1)加载:load
就是指将类型的class字节码数据读入内存
(2)连接:link
①验证:校验合法性等
②准备:准备对应的内存(方法区),创建Class对象,为类变量赋默认值,为静态常量赋初始值。
③解析:把字节码中的符号引用替换为对应的直接地址引用
(3)初始化:initialize(类初始化)即执行类初始化方法,大多数情况下,类的加载就完成了类的初始化,有些情况下,会延迟类的初始化。
1.2 类初始化
1、哪些操作会导致类的初始化?
(1)运行主方法所在的类,要先完成类初始化,再执行main方法
(2)第一次使用某个类型就是在new它的对象,此时这个类没有初始化的话,先完成类初始化再做实例初始化
(3)调用某个类的静态成员(类变量和类方法),此时这个类没有初始化的话,先完成类初始化
(4)子类初始化时,发现它的父类还没有初始化的话,那么先初始化父类
(5)通过反射操作某个类时,如果这个类没有初始化,也会导致该类先初始化
class Father{
static{
System.out.println("main方法所在的类的父类(1)");//初始化子类时,会初始化父类
}
}
public class TestClinit1 extends Father{
static{
System.out.println("main方法所在的类(2)");//主方法所在的类会初始化
}
public static void main(String[] args) throws ClassNotFoundException {
new A();//第一次使用A就是创建它的对象,会初始化A类
B.test();//直接使用B类的静态成员会初始化B类
Class clazz = Class.forName("com.atguigu.test02.C");//通过反射操作C类,会初始化C类
}
}
class A{
static{
System.out.println("A类初始化");
}
}
class B{
static{
System.out.println("B类初始化");
}
public static void test(){
System.out.println("B类的静态方法");
}
}
class C{
static{
System.out.println("C类初始化");
}
}
2、哪些使用类的操作,但是不会导致类的初始化?
(1)使用某个类的静态的常量(static final)
(2)通过子类调用父类的静态变量,静态方法,只会导致父类初始化,不会导致子类初始化,即只有声明静态成员的类才会初始化
(3)用某个类型声明数组并创建数组对象时,不会导致这个类初始化
1.3 类加载器
我们在开发中可能会遇到java.lang.ClassNotFoundException或java.lang.NoClassDefError,想要更好的解决这类问题,或者在一些特殊的应用场景,比如需要支持类的动态加载或需要对编译后的字节码文件进行加密解密操作,那么就需要自定义类加载器,因此了解类加载器及其类加载机制就非常必要。
1、类加载器分为:
(1)引导类加载器(Bootstrap Classloader)又称为根类加载器
它负责加载jre/rt.jar核心库
它本身不是Java代码实现的,也不是ClassLoader的子类,获取它的对象时往往返回null
(2)扩展类加载器(Extension ClassLoader)
它负责加载jre/lib/ext扩展库
它是ClassLoader的子类
(3)应用程序类加载器(Application Classloader)
它负责加载项目的classpath路径下的类
它是ClassLoader的子类
(4)自定义类加载器
当你的程序需要加载“特定”目录下的类,可以自定义类加载器;
当你的程序的字节码文件需要加密时,那么往往会提供一个自定义类加载器对其进行解码
后面会见到的自定义类加载器:tomcat中
2、Java系统类加载器的双亲委托模式
简单描述:
下一级的类加载器,如果接到任务时,会先搜索是否加载过,如果没有,
会先把任务往上传,如果都没有加载过,一直到根加载器,如果根加载
器在它负责的路径下没有找到,会往回传,如果一路回传到最后一级都
没有找到,那么会报ClassNotFoundException或NoClassDefError,如
果在某一级找到了,就直接返回Class对象。
应用程序类加载器 把 扩展类加载器视为父加载器,
扩展类加载器 把 引导类加载器视为父加载器。
不是继承关系,是组合的方式实现的。
/**
*
* 演示类加载器获取
*/
public class ClassLoaderDemo {
public static void main(String[] args) {
ClassLoader classLoader1 = ClassLoaderDemo.class.getClassLoader();
System.out.println(classLoader1); //AppClassLoader
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2); //ExtClassLoader
ClassLoader classLoader3 = classLoader2.getParent();
System.out.println(classLoader3);//null
}
}
2、 java.lang.Class类
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
(1)java代码编译class文件,java里面提供类Class,代表字节码文件。
(2)通过Class获取字节码文件,操作类里面所有内容(构造、属性、方法等)
2.1 哪些类型可以获取Class对象
所有Java类型
用代码示例
//(1)基本数据类型和void
例如:int.class
void.class
//(2)类和接口
例如:String.class
Comparable.class
//(3)枚举
例如:ElementType.class
//(4)注解
例如:Override.class
//(5)数组
例如:int[].class
2.2 获取Class对象的四种方式
(1)类型名.class
要求编译期间已知类型
(2)对象.getClass()
获取对象的运行时类型
(3)Class.forName(类型全名称)
可以获取编译期间未知的类型
(4)ClassLoader的类加载器对象.loadClass(类型全名称)
可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
public class ClassDemo1 {
public static void main(String[] args) throws Exception {
// (1)类名.class
Class classDemo1Class = ClassDemo1.class;
System.out.println(classDemo1Class);
// (2)对象.getClass()
Class aClass = new ClassDemo1().getClass();
// (3)Class.forName(包类路径)
Class aClass1 = Class.forName("com.atguigu.refelect.ClassDemo1");
// (4)类加载方法loadClass
ClassLoader loader = aClass1.getClassLoader();
Class c4 = loader.loadClass("com.atguigu.refelect.ClassDemo1");
}
}
2.3 查看某个类的类加载器对象
//获取应用程序类加载器对象
//获取扩展类加载器对象
//获取根加载器对象
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
3、 反射的应用
3.1 获取类型的详细信息
可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)
示例代码获取常规信息
/**
* 反射方法演示1
*/
public class ClassDemo2 {
public static void main(String[] args) throws Exception {
//0 获取字节码文件
Class clazz = String.class;
//1 获取包
Package pack = clazz.getPackage();
System.out.println("包: "+pack.getName());
//2 获取修饰符
int mod = clazz.getModifiers();
System.out.println("修饰符: "+Modifier.toString(mod));
//3 获取包类路径
String name = clazz.getName();
System.out.println("包类路径: "+name);
//4 获取父类
Class superclass = clazz.getSuperclass();
System.out.println("父类: "+superclass);
//5、父接口们
Class[] interfaces = clazz.getInterfaces();
for (Class class1 : interfaces) {
System.out.println(class1);
}
//6 操作属性
Field field = clazz.getDeclaredField("value");
System.out.println("指定名称属性:"+field);
Field[] fields = clazz.getDeclaredFields();
for(Field f : fields) {
System.out.println("获取所有属性: "+f);
}
//7 获取构造
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor constructor : declaredConstructors) {
System.out.println("获取构造: "+constructor);
}
//8 获取方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods) {
System.out.println("获取方法: "+m);
}
}
}
3.2 创建任意引用类型的对象
两种方式:
1、直接通过Class对象来实例化(要求必须有无参构造)
2、通过获取构造器对象来进行实例化
/**
* 反射方法演示2
*/
public class ClassDemo3 {
public static void main(String[] args) throws Exception {
// new User(); 调用无参数构造
// new User("lucy",20); 调用有参数构造
//1 newInstance方法创建
Class<?> clazz1 = Class.forName("com.atguigu.refelect.User");
User user = (User)clazz1.newInstance();
System.out.println("第一种写法:"+user);
//2 获取有参数构造创建
//获取有参数构造
Constructor<?> constructor = clazz1.getDeclaredConstructor(String.class, int.class);
Object o = constructor.newInstance("lucy", 20);
System.out.println("第二种写法:"+o);
}
}
class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
3.3 操作任意类型的属性
(1)获取该类型的Class对象
Class clazz = Class.forName(“com.atguigu.bean.User”);
(2)获取属性对象
Field field = clazz.getDeclaredField(“username”);
(3)设置属性可访问
field.setAccessible(true);
(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象
Object obj = clazz.newInstance();
(4)设置属性值
field.set(obj,“chai”);
(5)获取属性值
Object value = field.get(obj);
如果操作静态变量,那么实例对象可以省略,用null表示
//反射操作属性
@Test
public void test01() throws Exception {
//获取Class
Class<?> clazz = Class.forName("com.atguigu.refelect.User");
//获取属性 age
Field field = clazz.getDeclaredField("age");
//设置操作属性可以进行访问-针对private
field.setAccessible(true);
//创建User实例化
Object stu = clazz.newInstance();
//设置属性值
field.set(stu,20); //setAge(20)
//输出值
Object value = field.get(stu); //getAge()
System.out.println("id = "+ value);
}
3.4 调用任意类型的方法
(1)获取该类型的Class对象
Class clazz = Class.forName(“com.atguigu.service.UserService”);
(2)获取方法对象
Method method = clazz.getDeclaredMethod(“login”,String.class,String.class);
(3)创建实例对象
Object obj = clazz.newInstance();
(4)调用方法
Object result = method.invoke(obj,“chai”,"123);
如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
如果方法是静态方法,实例对象也可以省略,用null代替
示例代码:
//反射操作方法
@Test
public void test02() throws Exception {
//获取Class
Class<?> clazz = Class.forName("com.atguigu.refelect.User");
//获取操作方法 setName
Method method = clazz.getDeclaredMethod("setName", String.class);
//让方法执行
User user = (User)clazz.newInstance();
method.invoke(user,"mary");
System.out.println(user.getName());
}