#back-end/Java/reflect
反射
反射的概述
专业的解释(了解一下):
是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意属性和方法;
这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
通俗的理解:(掌握)
学习反射到底学什么?
反射都是从class字节码文件中获取的内容。
- 如何获取class字节码文件的对象
- 利用反射如何获取构造方法(创建对象)
- 利用反射如何获取成员变量(赋值,获取值)
- 利用反射如何获取成员方法(运行)
获取字节码文件对象的三种方式
- Class这个类里面的静态方法forName(“全类名”)(最常用)
- 通过class属性获取
- 通过对象获取字节码文件对象
代码示例:
Class clazz1 = Class.forName("com.itheima.reflectdemo.Student");
Class clazz2 = Student.class;
System.out.println(clazz1 == clazz2);
Student s = new Student(); Class clazz3 = s.getClass(); System.out.println(clazz1 == clazz2); System.out.println(clazz2 == clazz3);
|
字节码文件和字节码文件对象
java文件:就是我们自己编写的java代码。
字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)
字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。
这个对象里面至少包含了:构造方法,成员变量,成员方法。
而我们的反射获取的是什么?字节码文件对象,这个对象在内存中是唯一的。
获取构造方法
规则:
get表示获取
Declared表示私有
最后的s表示所有,复数形式
如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用
方法名 |
说明 |
Constructor<?>[] getConstructors() |
获得所有的构造(只能public修饰) |
Constructor<?>[] getDeclaredConstructors() |
获得所有的构造(包含private修饰) |
Constructor getConstructor(Class<?>… parameterTypes) |
获取指定构造(只能public修饰) |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) |
获取指定构造(包含private修饰) |
代码示例:
public class ReflectDemo2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { Class clazz = Class.forName("com.itheima.reflectdemo.Student");
Constructor[] constructors1 = clazz.getConstructors(); for (Constructor constructor : constructors1) { System.out.println(constructor); }
System.out.println("=======================");
Constructor[] constructors2 = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors2) { System.out.println(constructor); } System.out.println("=======================");
Constructor con1 = clazz.getConstructor(); System.out.println(con1);
Constructor con2 = clazz.getConstructor(String.class,int.class); System.out.println(con2);
System.out.println("======================="); Constructor con3 = clazz.getDeclaredConstructor(); System.out.println(con3);
Constructor con4 = clazz.getDeclaredConstructor(String.class); System.out.println(con4); } }
|
获取构造方法并创建对象
涉及到的方法:newInstance
代码示例:
public class Student { private String name;
private int age;
public Student() {
}
public Student(String name) { this.name = name; }
private Student(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; }
public String toString() { return "Student{name = " + name + ", age = " + age + "}"; } }
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
Constructor con = clazz.getConstructor();
Student stu = (Student) con.newInstance(); System.out.println(stu);
System.out.println("=============================================");
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
Constructor con = clazz.getDeclaredConstructor(String.class, int.class);
con.setAccessible(true);
Student stu = (Student) con.newInstance("zhangsan", 23); System.out.println(stu);
|
获取成员变量
规则:
get表示获取
Declared表示私有
最后的s表示所有,复数形式
如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用
方法名:
方法名 |
说明 |
Field[] getFields() |
返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() |
返回所有成员变量对象的数组,存在就能拿到 |
Field getField(String name) |
返回单个成员变量对象(只能拿public的) |
Field getDeclaredField(String name) |
返回单个成员变量对象,存在就能拿到 |
代码示例:
public class ReflectDemo4 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class clazz = Class.forName("com.itheima.reflectdemo.Student");
Field[] fields1 = clazz.getFields(); for (Field field : fields1) { System.out.println(field); }
System.out.println("===============================");
Field[] fields2 = clazz.getDeclaredFields(); for (Field field : fields2) { System.out.println(field); }
System.out.println("===============================");
Field field4 = clazz.getField("gender"); System.out.println(field4);
System.out.println("==============================="); Field field5 = clazz.getDeclaredField("name"); System.out.println(field5);
} }
public class Student { private String name;
private int age;
public String gender;
public String address;
public Student() { }
public Student(String name, int age, String address) { this.name = name; this.age = age; this.address = address; }
public Student(String name, int age, String gender, String address) { this.name = name; this.age = age; this.gender = gender; this.address = address; }
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; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String toString() { return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}"; } }
|
获取成员变量并获取值和修改值
方法 |
说明 |
void set(Object obj, Object value) |
赋值 |
Object get(Object obj) |
获取值 |
代码示例:
public class ReflectDemo5 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Student s = new Student("zhangsan",23,"广州"); Student ss = new Student("lisi",24,"北京");
Class clazz = Class.forName("com.itheima.reflectdemo.Student");
Field field = clazz.getDeclaredField("name"); field.setAccessible(true);
field.set(s,"wangwu");
String result = (String)field.get(s);
System.out.println(result);
System.out.println(s); System.out.println(ss);
} }
public class Student { private String name; private int age; public String gender; public String address;
public Student() { }
public Student(String name, int age, String address) { this.name = name; this.age = age; this.address = address; }
public Student(String name, int age, String gender, String address) { this.name = name; this.age = age; this.gender = gender; this.address = address; }
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; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String toString() { return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}"; } }
|
获取成员方法
规则:
get表示获取
Declared表示私有
最后的s表示所有,复数形式
如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用
方法名 |
说明 |
Method[] getMethods() |
返回所有成员方法对象的数组(只能拿public的) |
Method[] getDeclaredMethods() |
返回所有成员方法对象的数组,存在就能拿到 |
Method getMethod(String name, Class<?>… parameterTypes) |
返回单个成员方法对象(只能拿public的) |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) |
返回单个成员方法对象,存在就能拿到 |
代码示例:
public class ReflectDemo6 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { Class<?> clazz = Class.forName("com.itheima.reflectdemo.Student");
Method[] methods1 = clazz.getMethods(); for (Method method : methods1) { System.out.println(method); }
System.out.println("==========================="); Method[] methods2 = clazz.getDeclaredMethods(); for (Method method : methods2) { System.out.println(method); }
System.out.println("==========================="); Method method3 = clazz.getMethod("sleep"); System.out.println(method3);
Method method4 = clazz.getMethod("eat",String.class); System.out.println(method4);
Method method5 = clazz.getDeclaredMethod("playGame"); System.out.println(method5); } }
|
获取成员方法并运行
方法
Object invoke(Object obj, Object… args) :运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
代码示例:
package com.itheima.a02reflectdemo1;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class ReflectDemo6 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student"); Student s = new Student(); Method eatMethod = clazz.getMethod("eat",String.class); String result = (String) eatMethod.invoke(s, "重庆小面"); System.out.println(result);
} }
public class Student { private String name; private int age; public String gender; public String address;
public Student() {
}
public Student(String name) { this.name = name; }
private Student(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; }
public String toString() { return "Student{name = " + name + ", age = " + age + "}"; }
private void study(){ System.out.println("学生在学习"); }
private void sleep(){ System.out.println("学生在睡觉"); }
public String eat(String something){ System.out.println("学生在吃" + something); return "学生已经吃完了,非常happy"; } }
|
面试题
你觉得反射好不好?好,有两个方向
第一个方向:无视修饰符访问类中的内容。但是这种操作在开发中一般不用,都是框架底层来用的。
第二个方向:反射可以跟配置文件结合起来使用,动态的创建对象,动态的调用方法。
练习泛型擦除(掌握概念,了解代码)
理解:(掌握)
集合中的泛型只在java文件中存在,当编译成class文件之后,就没有泛型了。
代码示例:(了解)
package com.itheima.reflectdemo;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList;
public class ReflectDemo8 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { ArrayList<Integer> list = new ArrayList<>(); list.add(123);
Class clazz = list.getClass();
Method method = clazz.getMethod("add", Object.class);
method.invoke(list,"aaa");
System.out.println(list); } }
|
练习:修改字符串的内容(掌握概念,了解代码)
在这个练习中,我需要你掌握的是字符串不能修改的真正原因。
字符串,在底层是一个byte类型的字节数组,名字叫做value
private final byte[] value;
|
真正不能被修改的原因:final和private
final修饰value表示value记录的地址值不能修改。
private修饰value而且没有对外提供getvalue和setvalue的方法。所以,在外界不能获取或修改value记录的地址值。
如果要强行修改可以用反射:
代码示例:(了解)
String s = "abc"; String ss = "abc";
Class clazz = s.getClass();
Field field = clazz.getDeclaredField("value");
field.setAccessible(true);
byte[] bytes = (byte[]) field.get(s); bytes[0] = 100;
System.out.println(s); System.out.println(ss);
|
练习,反射和配置文件结合动态获取的练习(重点)
需求: 利用反射根据文件中的不同类名和方法名,创建不同的对象并调用方法。
分析:
①通过Properties加载配置文件
②得到类名和方法名
③通过类名反射得到Class对象
④通过Class对象创建一个对象
⑤通过Class对象得到方法
⑥调用方法
代码示例:
public class ReflectDemo9 { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Properties prop = new Properties(); FileInputStream fis = new FileInputStream("day14-code\\prop.properties"); prop.load(fis); fis.close(); System.out.println(prop);
String classname = prop.get("classname") + ""; String methodname = prop.get("methodname") + "";
Class clazz = Class.forName(classname);
Constructor con = clazz.getDeclaredConstructor(); con.setAccessible(true); Object o = con.newInstance(); System.out.println(o);
Method method = clazz.getDeclaredMethod(methodname); method.setAccessible(true);
method.invoke(o);
} }
配置文件中的信息: classname=com.itheima.a02reflectdemo1.Student methodname=sleep
|
利用发射保存对象中的信息(重点)
public class MyReflectDemo { public static void main(String[] args) throws IllegalAccessException, IOException {
Student s = new Student("小A",23,'女',167.5,"睡觉"); Teacher t = new Teacher("播妞",10000); saveObject(s); }
public static void saveObject(Object obj) throws IllegalAccessException, IOException { Class clazz = obj.getClass(); BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\\a.txt")); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); String name = field.getName(); Object value = field.get(obj); bw.write(name + "=" + value); bw.newLine(); }
bw.close();
} }
|
public class Student { private String name; private int age; private char gender; private double height; private String hobby;
public Student() { }
public Student(String name, int age, char gender, double height, String hobby) { this.name = name; this.age = age; this.gender = gender; this.height = height; this.hobby = hobby; }
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; }
public char getGender() { return gender; }
public void setGender(char gender) { this.gender = gender; }
public double getHeight() { return height; }
public void setHeight(double height) { this.height = height; }
public String getHobby() { return hobby; }
public void setHobby(String hobby) { this.hobby = hobby; }
public String toString() { return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}"; } }
|
public class Teacher { private String name; private double salary;
public Teacher() { }
public Teacher(String name, double salary) { this.name = name; this.salary = salary; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
public String toString() { return "Teacher{name = " + name + ", salary = " + salary + "}"; } }
|
动态代理
好处
无侵入式的给方法增强功能
动态代理三要素
切记一点:代理可以增强或者拦截的方法都在接口中,接口需要写在newProxyInstance的第二个参数里。
代码实现
public class Test { public static void main(String[] args) {
BigStar bigStar = new BigStar("鸡哥"); Star proxy = ProxyUtil.createProxy(bigStar);
String result = proxy.sing("只因你太美"); System.out.println(result); } }
|
public class ProxyUtil {
public static Star createProxy(BigStar bigStar){
Star star = (Star) Proxy.newProxyInstance( ProxyUtil.class.getClassLoader(), new Class[]{Star.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("sing".equals(method.getName())){ System.out.println("准备话筒,收钱"); }else if("dance".equals(method.getName())){ System.out.println("准备场地,收钱"); } return method.invoke(bigStar,args); } } ); return star; } }
|
public interface Star { public abstract String sing(String name); public abstract void dance(); }
|
public class BigStar implements Star { private String name;
public BigStar() { }
public BigStar(String name) { this.name = name; }
@Override public String sing(String name){ System.out.println(this.name + "正在唱" + name); return "谢谢"; }
@Override public void dance(){ System.out.println(this.name + "正在跳舞"); }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String toString() { return "BigStar{name = " + name + "}"; } }
|
额外扩展
动态代理,还可以拦截方法
比如:
在这个故事中,经济人作为代理,如果别人让邀请大明星去唱歌,打篮球,经纪人就增强功能。
但是如果别人让大明星去扫厕所,经纪人就要拦截,不会去调用大明星的方法。
public class ProxyUtil { public static Star createProxy(BigStar bigStar){ public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) Star star = (Star) Proxy.newProxyInstance( ProxyUtil.class.getClassLoader(), new Class[]{Star.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("cleanWC".equals(method.getName())){ System.out.println("拦截,不调用大明星的方法"); return null; } return method.invoke(bigStar,args); } } ); return star; } }
|
动态代理的练习
对add方法进行增强,对remove方法进行拦截,对其他方法不拦截也不增强
public class MyProxyDemo1 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>();
List proxyList = (List) Proxy.newProxyInstance( MyProxyDemo1.class.getClassLoader(), new Class[]{List.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("add")) { long start = System.currentTimeMillis(); method.invoke(list, args); long end = System.currentTimeMillis(); System.out.println("耗时时间:" + (end - start)); return true; }else if(method.getName().equals("remove") && args[0] instanceof Integer){ System.out.println("拦截了按照索引删除的方法"); return null; }else if(method.getName().equals("remove")){ System.out.println("拦截了按照对象删除的方法"); return false; }else{ method.invoke(list,args); return null; } } } );
proxyList.add("aaa"); proxyList.add("bbb"); proxyList.add("ccc"); proxyList.add("ddd");
proxyList.remove(0); proxyList.remove("aaa");
System.out.println(list); } }
|