前言
java反射算是java学习过程中不可绕过的一关,在第一次学习的时候迷迷糊糊,这次再来学习一遍。
java 反射
反射允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。
反射的核心是JVM在运行时动态加载类或调用方法或访问属性。
class 类
我们正常类加载的方式是
- 导入包名--->通过new实例化--->取得实例化对象
而反射是 - 实例化对象--->getclass()方法--->得到包名
实现反射相关API
java.lang.Class 代表一个类
java.lang.reflect.Method 代表类的方法
java.lang.reflect.Field 代表类的成员属性
java.lang.reflect.Constructor 代表类的构造方法
列举一个demo
User.java
public class User extends Person{
private int id;
private String username;
private String password;
public int age;
public User(){}
public User(int id, String username, String password, int age) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
private void show(String username,String password) {
System.out.println("用户名:"+username+",密码:"+password);
}
public void study(String username) {
System.out.println(username+"正在学习~");
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
Person类
public class Person {
public String name;
private int age;
public Person(){}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this.name = name;
}
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;
}
private String show(String name) {
//System.out.println(name+"");
return name+"正在洗澡";
}
private static void teststatic(){
System.out.println("static method start");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
获取class类对象的四种方法
public void getclassTest(){
//1.获取class对象
User user1 = new User();
Class c1 = user1.getClass();
System.out.println("第一种"+c1);
//2.通过类的方式获取
Class c2 = User.class;
System.out.println("第二种"+c2);
//3.class.forName() 将字节码文件加载到内存
Class c3;
try {
c3 = Class.forName("com.atguigu.java.User");
System.out.println("第三种"+c3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//4. classLoader 类加载器
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class aClass = null;
try {
aClass = classLoader.loadClass("com.atguigu.java.User");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.print("第四种"+aClass);
}
获取成员属性
getFields() 获取所有public修饰的成员属性,包括父类
getDeclaredFields() 获得当前类的所有属性,包括private
public void test() throws ClassNotFoundException {
Class c = Class.forName("com.atguigu.java.User");
Field[] fields = c.getDeclaredFields();
//Field[] fields = c.getFields();
for (Field f : fields) {
System.out.println(f);
}
}
获取构造方法
getConstructors() 获得所有public修饰的构造方法,包括父类
getDeclaredConstructors() 获得当前类的所有构造方法,包括私有
getDeclaredConstructor() 获得当前类指定的构造方法
public void test() throws ClassNotFoundException {
Class clazz = Class.forName("com.atguigu.java.User");
Constructor[] cs = clazz.getDeclaredConstructors();
//Constructor[] cs = clazz.getConstructors();
for (Constructor con : cs) {
System.out.println(con);
}
}
public void test() throws ClassNotFoundException, NoSuchMethodException {
Class clazz = Class.forName("com.atguigu.java.User");
Constructor declaredConstructor = clazz.getDeclaredConstructor();
Constructor declaredConstructor1 = clazz.getDeclaredConstructor(int.class, String.class, String.class, int.class);
}
获取指定的构造方法,可以是无参构造方法,也可以是有参构造方法。
如果想要获取父类的构造方法呢?
public void test() throws ClassNotFoundException {
Class clazz = Class.forName("com.atguigu.java.User");
Class superclass = clazz.getSuperclass();
Constructor[] cs = superclass.getDeclaredConstructors();
for (Constructor con : cs) {
System.out.println(con);
}
}
获取成员方法
getMethods() 获取当前类和父类的所有公有的public方法,并以数组返回
getDeclaredMethods() 只查询当前类的所有定义的方法(包含private),并以数组返回
getDeclaredMethod() 获取某个方法,传入的第一个参数为方法名,第二个参数为方法参数
public void test() throws ClassNotFoundException {
Class c = Class.forName("com.atguigu.java.User");
Method[] method = c.getDeclaredMethods();
//Method[] method = c.getMethods();
for (Method meth : method) {
System.out.println(meth);
}
}
如果想要获取show()
方法,使用getDeclaredMethod()
方法,并传入方法名,以及形参。又因为该方法为private,所以设置访问权限。
注:setAccessible作用是启动和禁止访问安全检查的开关,参数为true表示反射的对象在使用时应该取消java语言访问检查,参数为false则表示反射的对象实施对java语言的访问检查。
public void test() throws ClassNotFoundException, NoSuchMethodException {
Class c = Class.forName("com.atguigu.java.User");
Method[] method = c.getDeclaredMethods();
Method show = c.getDeclaredMethod("show", String.class,String.class);
show.setAccessible(true);
}
反射调用属性
调用public age属性
public void test() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class c = Class.forName("com.atguigu.java.User");
Field agefield = c.getField("age");
User u = new User(1,"cseroad","123456",18);
System.out.println(agefield.getInt(u));
}
调用private username属性
public void test() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class c = Class.forName("javase.bibi.reflection.User");
Field usernamefield = c.getDeclaredField("username");
usernamefield.setAccessible(true);//忽略访问权限
User u = new User(1,"cseroad","123456",18);
System.out.println(usernamefield.get(u));
}
反射调用方法
调用public方法
public void test() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class c = Class.forName("com.atguigu.java.User");
Method study = c.getDeclaredMethod("study", String.class);
User u = new User(1,"vxeroad","123456",18);
study.invoke(u, "cseroad");
}
调用private方法
public void test() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class c = Class.forName("com.atguigu.java.User");
Method show = c.getDeclaredMethod("show", String.class, String.class);
show.setAccessible(true);
User u = new User(1,"vxeroad","123456",18);
show.invoke(u, "cseroad","1q2w3e4r");
}
反射创建对象
上面的例子我们看到还是用new的方式来创建的对象。也可以通过反射创建对象。
Class.newInstance() 只能够调用无参的构造方法,即默认的构造方法;要求构造方法必须是public类型的。
Constructor.newInstance() 可以根据传入的参数,调用任意的构造方法; 特定情况下可以调用私有的构造方法。
尝试反射创建对象并调用public study() 方法
public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 获得class对象
Class clazz = Class.forName("com.atguigu.java.User");
// 2. 获取无参构造器
Constructor declaredConstructor = clazz.getDeclaredConstructor();
// 3. Constructor.newInstance() 创建对象
Object cseroad = declaredConstructor.newInstance();
User user = (User) cseroad;
// 4. 获取方法
Method show = clazz.getDeclaredMethod("study", String.class);
show.invoke(user, "cseroad");
}
因为User类有两个构造方法,我们尝试用另一个有参构造器。
public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 获得class对象
Class clazz = Class.forName("com.atguigu.java.User");
// 2. 获取无参构造器
Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
// 3. Constructor.newInstance() 创建对象
Object cseroad = declaredConstructor.newInstance(1,"xx","xx",18);
User user = (User) cseroad;
// 4. 获取方法
Method show = clazz.getDeclaredMethod("study", String.class);
show.invoke(user,"cseroad");
}
为了区别实参和形参,在User类的study()方法中调整为
public void study(String username) {
System.out.println("我的年龄:"+age);
System.out.println(username+"正在学习~");
}
结论为:在newInstance创建对象的时候,对应有参构造器,传入形参。
再尝试反射创建对象并调用public show() 私有方法
public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 获得class对象
Class clazz = Class.forName("com.atguigu.java.User");
// 2. 获取无参构造器
Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
// 3. Constructor.newInstance() 创建对象
Object cseroad = declaredConstructor.newInstance(1,"xx","xx",18);
User user = (User) cseroad;
// 4. 获取方法
Method show = clazz.getDeclaredMethod("show", String.class,String.class);
show.setAccessible(true);
show.invoke(user,"cseroad","123456");
}
应用
我们趁热打铁,看看如何反射调用经常用到的Runtime类。
正常使用Runtime类执行系统命令为
Process p = Runtime.getRuntime().exec("calc");
而反射过程是加载类调用方法。
首先获得Runtime类
Class clazz = Class.forName("java.lang.Runtime");
然后看一下该类的构造器。
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor con : declaredConstructors){
System.out.print(con);
}
只有一个私有的无参构造器。
获取该构造器并设置访问权限。
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
有了构造器就可以创建对象。
Object o = declaredConstructor.newInstance();
然后获取指定的方法,之前也可以查看当前类的所有方法。
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method meth : declaredMethods){
System.out.println(meth);
}
可以看到exec()方法对应的都是public属性,且可以传入String类型,也可以传入String[]类型。
比如使用java.lang.String
那在getDeclaredMethod()方法后传入的第一个参数为exec()方法,第二个参数为String.class。
Method show = clazz.getDeclaredMethod("exec", String.class);
并使用invoke()方法执行exec()方法
show.invoke(o,"touch 1.txt");
所有以上代码为
public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 获得class对象
Class clazz = Class.forName("java.lang.Runtime");
// 2. 获取无参构造器
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
// 3. Constructor.newInstance() 创建对象
Object o = declaredConstructor.newInstance();
//Runtime o1 = (Runtime) o;
// 4. 获取方法
Method show = clazz.getDeclaredMethod("exec", String.class);
show.invoke(o,"touch 1.txt");
}
也可以传入String[] 类型
public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 获得class对象
Class clazz = Class.forName("java.lang.Runtime");
// 2. 获取无参构造器
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
// 3. Constructor.newInstance() 创建对象
Object o = declaredConstructor.newInstance();
//强制转换Runtime
//Runtime o1 = (Runtime) o;
// 4. 获取方法
String[] cmds = new String[]{"/bin/bash","-c","touch 2.txt"};
Method show = clazz.getDeclaredMethod("exec", String[].class);
// 强制转换为Object
show.invoke(o,(Object) cmds);
}
同样ProcessImpl类、ProcessBuilder类也可以反射调用执行系统命令
public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Class.forName("java.lang.ProcessBuilder");
Constructor declaredConstructor = clazz.getDeclaredConstructor(List.class);
ArrayList
总结
想比较第一次学习,对反射多了一些理解,并结合几个执行命令的类实践调用了反射。
参考资料
https://www.bilibili.com/video/BV1p4411P7V3
https://re0.top/2020/03/22/java-reflection/#Class%E7%B1%BB
https://m0nit0r.top/2020/06/07/java-deserialize-learn2/