一.什么是反射
反射机制允许程序在执行期间借助于ReflectionAPI取得任何类的内部信息(成员变量成员方法构造器等),并能操作对象的属性及发那份,反射在设计模式和框架底层都会用到.
加载完类之后,在堆中就会产生一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息.这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称之为反射.
二.反射的使用
1.反射就是通过外部文件的配置,不修改源码的情况下来控制程序
2.反射入门,通过读取配置文件,来读取类和类方法:
package com.hspedu;
public class Cat {
private String name;
public void hi(){
System.out.println("hi"+name);
}
}
配置文件的配置:
classfullpath = com.hspedu.Cat
method = hi
package com.hspedu.reflection.question;
import com.hspedu.Cat;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
@SuppressWarnings({"all"})
public class ReflectionQuestion {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Properties properties = new Properties();
properties.load(new FileInputStream("src/re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String method = properties.get("method").toString();
System.out.println(method);
//传统的创建对象已经行不通,使用反射机制来解决
//1.加载类,返回class类型的对象cls
Class cls = Class.forName(classfullpath);
//2.通过cls得到你加载的类com.hspedu.Cat的实例
Object o = cls.newInstance();
System.out.println(o.getClass());
//3.通过cls得到加载的类的方法对象
//在反射中,可以把方法当做对象,万物皆对象
Method method1 = cls.getMethod(method);
method1.setAccessible(true);//取消方法对象使用时的访问检查,即进行反射优化
//4.通过method01调用方法,即通过方法对象调用方法
method1.invoke(o);//传统方法:对象.方法,反射机制 方法.invoke(对象)
}
}
4.反射机制原理示意图
5.反射相关的主要类
java.lang.class 代表一个类,class对象表示某个类加载后在堆中的对象
java.lang.reflection.method:代表类的方法,Method对象表示某个类的方法
java.lang.reflect.Filed:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造方法
6.反射机制的优缺点
优点:可以动态的创建和使用对象(也是框架底层的核心)
缺点;使用反射基本是解释执行,对执行速度有影响
三.反射调用优化
1.Method和Filed,Constructor对象都有setAccessible()方法,该方法的作用就是启动和禁用访问安全检查的开关
2.参数为true时表示反射的对象在使用时取消访问检查,为false时则表示在使用时执行访问检查
四.class类
1.class类也是类,也继承Object类
2.Class类对象不是new出来的,而是系统创建的
3.对于某个类的class对象,在内存中只有一份,因为类只加载一次
4.每个类的实例都会记得自己是由哪个class类实例所产生
5.通过Class对象可以完整的得到一个类的完整结构
6.class类对象是存放在堆的
7.类的字节码二进制数据,是存放在方法区的
8.class类的常用方法:
package com.hspedu;
import java.lang.reflect.Field;
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classAllPath = "com.hspedu.Car";
//1.获取到Class对应的Clas对象
Class aClass = Class.forName(classAllPath);
System.out.println(aClass);
//2.输出aClass
System.out.println(aClass);
System.out.println(aClass.getClass());//输出aClass的运行类型,即对象类型
//3.得到包名
System.out.println(aClass.getPackage().getName());
//4.得到全类名
System.out.println(aClass.getName());
//5.通过aClass创建对象实例
Car car = (Car)aClass.newInstance();
System.out.println(car);
//6.通过反射获取属性
Field brand = aClass.getField("brand");
System.out.println(brand.get(car));
//7.通过反射给属性赋值
brand.set(car,"奔驰");
System.out.println(brand.get(car));
//8.遍历所有的属性
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
}
}
五.获取class类的方法
1.已经知道一个类的全类名,且该类就在类路径下,可通过Class类的静态方法forName()获取
例如:Class cls1 = Class.forName(“java.lang.Cat”)
应用场景:多用于配置文件,读取类的全路径,加载类
2.若已经知道具体的类了,通过类的class获取,该方式安全可靠,程序性能最高
例如:Class cls2 = Cat.class
应用场景:多用于参数传递,比如通过反射得到对应的构造器对象
3.已知某个类的实例,调用该实例的getClass()方法获取Class对象(运行阶段)
实例:Class clazz = 对象.getClass();//运行类型
应用场景:通过创建好的对象,获取Class对象
4.通过类加载器来获取到类的Class对象
Car car1 = new Car();
//1.得到类加载器
ClassLoader classLoader = car1.getClass().getClassLoader();
//2.通过类加载器得到Class对象
Class cls4 = classLoader.loadClass(classAllPath);
5.基本数据类型获取类对象
Class cls = 基本数据类型.class
6.基本数据类型对应的包装类如integer,Stringbuffer,通过.type得到Class对象
Class cls = 包装类.TYPE
六.类加载
1.静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
2.动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性
类加载时机:
①.当创建对象时 ②.当子类被加载时 ③.调用类中的静态成员时 ④.通过反射(为动态加载编译阶段不会加载)
3.类加载的过程
①:加载阶段
jvm在该阶段的主要目的是将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至网络)转化为二进制节流加载到内存中,并生成一个代表该类的java.lang.class对象
②:连接
连接子阶段-验证:确保class文件的字节流包含的信息符合当前虚拟机的要求并且不会危害虚拟机自身的安全
包含文件格式验证(是否以魔数oxcafebabe开头),元数据验证,字节码验证和符号引用验证
可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
连接子阶段-准备:jvm会在该阶段对静态变量,分配内存并默认初始化并不会赋予值(对应数据类型的默认初始值如0,0L,null,false等).这些变量所使用的内存都将在方法区中进行分配
package com.hspedu;
public class Class03 {
//1.n1是实例属性,不是静态变量,因此在准备阶段是不会分配内存的
//2.n2是静态变量,分配内存n2是默认初始化0,不是20
//n3是static final 是常来那个,一旦赋值就不变 n3 = 30
public int n1 = 30;
public static int n2 = 20;
public static final int n3 = 30;
}
连接阶段-解析
虚拟机将常量池的符号引用替换为直接引用的过程
java虚拟机为每一个类都准备一张方法表,将其所有的方法列在其中,当需要调用一个类的方法时,只要知道这个方法在方法表的偏移量(通过解析操作,符号引用转换为直接引用类中的方法表中的位置),就可以直接调用该方法了。
③初始化:真正开始执行类中定义的java程序代码,此阶段是执行()方法的过程
该方法是由编译器按语句在源文件中出现的顺序,依次自动手机类中的所有静态变量的复制动作和静态代码块中的语句并进行合并
虚拟机会保证一个类的()方法在多线程环境中被正确的加锁同步,如果多个线程同时区初始化一个类,那么只会有一个线程去执行这个类的方法,其他线程都需要阻塞等待
七.通过反射获取类的结构信息
 throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1.获取到User类的Class对象
Class userClass = Class.forName("User");
//2.通过public的无参构造器创建实例
Object o = userClass.newInstance();
//3,通过public的有参构造器创建实例,先得到对应构造器再去创建实例
/*
* 该构造器对象实际就是
*public User(String name){
this.name = name;
} */
Constructor constructor = userClass.getConstructor(String.class);
Object o1 = constructor.newInstance("888");
System.out.println("o1=" + o1);
//4.通过非public的有参构造器创建实例,getDeclaredConstructor可以返回任意一个构造器对象
Constructor declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
declaredConstructor.setAccessible(true);//暴力破解,使用反射访问私有构造器/方法/属性,即关闭安全检查
Object user1 = declaredConstructor.newInstance(100, "张三");
System.out.println(user1);
}
}
class User {
private int age = 23;
private String name = "韩顺平教育";
public User() {//无参构造器
}
public User(String name) {
this.name = name;
}
private User(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
九.通过反射访问类中的成员
静态属性在加载的时候可以跟对象无关联
import java.lang.reflect.Field;
public class ReflectAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Class<?> student = Class.forName("Students");
//2.使用反射创建对象
Object o = student.newInstance();
System.out.println(o.getClass());//得到对象的运行类型,也就是类对象
//3.使用反射得到age的属性对象
Field age= student.getField("age");
age.set(o,88);//给o对象的age重新赋值
System.out.println(o);
System.out.println(age.get(o));
//4.使用反射操作name属性
Field name = student.getDeclaredField("name");
name.setAccessible(true);
name.set(o,"王五");
System.out.println(o);
}
}
class Students{
public int age;
private static String name;
public Students(){
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static String getName() {
return name;
}
public static void setName(String name) {
Students.name = name;
}
@Override
public String toString() {
return "Students{" +
"age=" + age +"name = "+getName()+
'}';
}
}
十.通过反射访问方法
在反射中,如果方法有返回值,则返回的编译类型是object,运行类型和方法返回值类型一样
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectAccessMethod
{
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
//1.得到Boss类对应的Class对象
Class boss = Class.forName("Boss");
//2.创建对象
Object o = boss.newInstance();
//调用public的hi方法,得到方法的对象
Method hi = boss.getMethod("hi",String.class);
hi.invoke(o,"ooo");
//调用私有方法say
Method say = boss.getDeclaredMethod("say", int.class, String.class, char.class);
say.setAccessible(true);
System.out.println(say.invoke(o,1,"张三",'y'));
}
}
class Boss{
public int age;
private static String name;
public Boss(){}
private static String say(int n,String s,char c){
return n +" "+s+" "+c;
}
public void hi(String s){
System.out.println("hi"+s);
}
}