欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java中的神奇的技术—反射

程序员文章站 2022-06-01 20:23:03
...

前言

反射允许运行中的 Java 程序对自身进行检查,或者说“自审”或“自省”,并能直接操作程序的内部属性。这个技术允许程序员不通过new一个对象,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意方法和属性。

实现Java反射机制有Class类、Field类、Constructor类和Method类。

类名 属于哪个包 说明
Class类 java.lang 代表正在运行的Java应用程序中的一个
Filed类 java.lang.reflect 代表动态绑定的类或接口中的属性
Method类 java.lang.reflect 代表了动态绑定的类或接口中的普通方法
Constructor类 java.lang.reflect 代表动态绑定的类中的构造方法

一、获取Class实例

1、forName()方法

(本文的异常都抛出去)

        Class c1 = Class.forName("java.lang.Thread");
        System.out.println(c1.getName());
        String s = "java.lang.String";
        Class c2 = Class.forName(s);
        System.out.println(c2.getName());

输出结果:

java.lang.Thread
java.lang.String

2、getClass()方法

        Class c2 = "a".getClass();
        System.out.println(c2.getName());
        Integer a = 0;
        Class c3 = a.getClass();
        System.out.println(c3.getName());

输出结果:

java.lang.String
java.lang.Integer

3、.class属性

java语言中任何一种类型,包括基本数据类型,它都有.class属性
Class c = 任何类型.class;

        Class c4 = int.class;
        Class c5 = String.class;
        System.out.println(c4.getName());
        System.out.println(c5.getName());

输出结果:

int
java.lang.String

二、通过Class类的newInstance()方法来实例化对象

获取到Class,有何作用?用处是大大的!

我们首先定义一个测试类,这个类是在我项目的base包下。

package base;

public class Student {
    private String name;
    private  int age;
    public String id;
    public String sex;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
        System.out.println("这是Student类的无参构造方法!");
    }
}

然后在测试类中测试。

package test;

public class ReflectTest02 {
    public static void main(String[] args) throws Exception {
        // 通过反射机制,获取Class,通过Class来实例化对象
        Class c = Class.forName("base.Student");
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

来看看输出结果:

这是Student类的无参构造方法!
base.Student@7ef20235

咦?它把我们Student类中无参构造方法中的内容执行了!
这是因为:newInstance() 这个方法会调用Student这个类的无参数构造方法,完成对象的创建。
所以重点是:我们必须要保证无参构造方法的存在

三、Field类:获取和访问类中的属性

Student类仍是上面的那个。

public static void main(String[] args) throws Exception {

        // 通过反射机制,获取Class,通过Class来实例化对象
        Class c = Class.forName("base.Student");
        //Object obj = c.newInstance();
        //System.out.println(obj);
        System.out.println("类名: " + c.getName());
        System.out.println("简类名: " + c.getSimpleName());

        System.out.println("=====================================");
        // getFields()返回一个包含某些Field对象的数组,这些对象反映此Class对象所表示的类或接口的所有可访问公共字段。
        Field[] fields1 = c.getFields();
        System.out.println("Student类中的public修饰的字段: ");
        for (int i=0; i<fields1.length; i++) {
            System.out.println(fields1[i].getName());
        }

        System.out.println("=====================================");
        /* getDeclaredFields()返回Method对象的一个数组,这些对象反映此Class对象表示的类或接口声明的所有方法,包括公共、
            保护、默认(包)访问和私有方法,但不包括继承的方法。*/
        Field[] fields2 = c.getDeclaredFields();
        System.out.println("Student类中的所有的字段: ");
        for (int i=0; i<fields2.length; i++) {
            System.out.println(fields2[i].getName());
        }

        System.out.println("=====================================");
        for (Field field : fields2) {
            //获取属性的修饰符列表
            int i = field.getModifiers();
            String modifierString = Modifier.toString(i);
            //获取属性的类型
            Class fieldType = field.getType();
            String typeName = fieldType.getSimpleName();
            //获取属性的名字
            String filedName = field.getName();

            System.out.println(modifierString + " " + typeName + " " + filedName + ";");
        }
    }

输出结果:

Student类的静态代码块执行了!
类名: base.Student
简类名: Student
=====================================
Student类中的public修饰的字段: 
id
sex
=====================================
Student类中的所有的字段: 
name
age
id
sex
=====================================
private String name;
private int age;
public String id;
public String sex;

看到这里,是不是觉得很神奇?我们把Student类中变量及其修饰符都取到了!
再把上面的方法整合一下:

public static void main(String[] args) throws Exception {

        Class c = Class.forName("base.Student");
        StringBuilder s = new StringBuilder();
        s.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() + " {\n");

        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            //获取属性的修饰符列表
            int i = field.getModifiers();
            String modifierString = Modifier.toString(i);
            s.append("\t");
            s.append(modifierString);
            //获取属性的类型
            Class fieldType = field.getType();
            String typeName = fieldType.getSimpleName();
            s.append(" ");
            s.append(typeName);
            //获取属性的名字
            String filedName = field.getName();
            s.append(" ");
            s.append(filedName);
            s.append(";\n");
        }
        s.append("}");
        System.out.println(s);
    }

输出结果:

Student类的静态代码块执行了!
public class Student {
	private String name;
	private int age;
	public String id;
	public String sex;
}

就很厉害有没有?我们当然也可以获取sun公司的类中的变量及其修饰符。
就改变上面代码中一句,就可以获取String类的变量了!

Class c = Class.forName("java.lang.String");

输出结果:

public final class String {
	private final byte[] value;
	private final byte coder;
	private int hash;
	private boolean hashIsZero;
	private static final long serialVersionUID;
	static final boolean COMPACT_STRINGS;
	private static final ObjectStreamField[] serialPersistentFields;
	public static final Comparator CASE_INSENSITIVE_ORDER;
	static final byte LATIN1;
	static final byte UTF16;
}

通过反射机制访问一个java对象的属性

把人家类中的属性打印出来也没啥,重点是我们该如何通过反射机制去访问一个java对象的属性,怎么去给属性赋值,怎么取属性的值(通过set和get方法)。

public static void main(String[] args) throws Exception {
        //获取Student对象obj
        Class c = Class.forName("base.Student");
        Object obj = c.newInstance();
        
        //获取4个属性(根据属性的名称来获取Field),传入的是属性的名字
        Field nameFiled = c.getDeclaredField("name");
        Field ageField = c.getDeclaredField("age");
        Field idField = c.getDeclaredField("id");
        Field sexField = c.getDeclaredField("sex");
        //因为name和age是私有变量,需要设置它为可以访问的
        nameFiled.setAccessible(true);
        ageField.setAccessible(true);
        
        //通过set方法赋值,传入两个东西:obj对象和属性的值
        nameFiled.set(obj,"oos");
        ageField.set(obj,18);
        idField.set(obj,"20201111");
        sexField.set(obj,"male");
        
        //重写Student的toString方法,输出obj
        System.out.println(obj);
		System.out.println(nameFiled.get(obj));
        System.out.println(ageField.get(obj));
        System.out.println(idField.get(obj));
        System.out.println(sexField.get(obj));

    }

输出结果:

Student类的静态代码块执行了!
这是Student类的无参构造方法!
Student{name='oos', age=18, id='20201111', sex='male'}
oos
18
20201111
male

对比以往传统的给对象赋值方法,反射机制明显变得复杂了,但是也让代码变得更加有“操作性”。但是这里有一个致命的缺点,就是反射机制会打破封装,可以从外部访问私有变量,这是比较危险的。

四、Method类:获取和调用类中的方法

public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();
        //Class userServiceClass = Class.forName("com.bjpowernode.java.service.UserService");
        Class userServiceClass = Class.forName("java.util.Date");
        s.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");

        Method[] methods = userServiceClass.getDeclaredMethods();
        for(Method method : methods){
            //public boolean login(String name,String password){}
            s.append("\t");
            s.append(Modifier.toString(method.getModifiers()));
            s.append(" ");
            s.append(method.getReturnType().getSimpleName());
            s.append(" ");
            s.append(method.getName());
            s.append("(");
            // 参数列表
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType : parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            // 删除指定下标位置上的字符
            s.deleteCharAt(s.length() - 1);
            s.append("){}\n");
        }

        s.append("}");
        System.out.println(s);
    }

输出结果:

public class Date {
	public boolean equals(Object){}
	public String toString){}
	public int hashCode){}
	public Object clone){}
	public volatile int compareTo(Object){}
	public int compareTo(Date){}
	public static Date from(Instant){}
	private void readObject(ObjectInputStream){}
	private void writeObject(ObjectOutputStream){}
	private final Date normalize(Date){}
	private final Date normalize){}
	public boolean before(Date){}
	public boolean after(Date){}
	public static long parse(String){}
	public long getTime){}
	public int getYear){}
	public int getSeconds){}
	public Instant toInstant){}
	public static long UTC(int,int,int,int,int,int){}
	public void setTime(long){}
	public int getMonth){}
	static final long getMillisOf(Date){}
	public void setDate(int){}
	private final Date getCalendarDate){}
	public void setHours(int){}
	public int getHours){}
	public int getMinutes){}
	public void setMinutes(int){}
	public void setSeconds(int){}
	public void setMonth(int){}
	public void setYear(int){}
	private static final BaseCalendar getCalendarSystem(int){}
	private static final BaseCalendar getCalendarSystem(long){}
	private static final BaseCalendar getCalendarSystem(Date){}
	private final long getTimeImpl){}
	private static final StringBuilder convertToAbbr(StringBuilder,String){}
	private static final synchronized BaseCalendar getJulianCalendar){}
	public int getDate){}
	public int getDay){}
	public String toLocaleString){}
	public String toGMTString){}
	public int getTimezoneOffset){}
}

java.util.Date包下的方法都打印出来了。

通过反射机制调用一个对象的方法(重点!!)
定义一个新的类ComputeTest,里面有个compute()方法,可以返回一个包含两数加减乘除的Map集合,我们通过反射机制去调用这个方法。

public class ComputeTest {
    public Map<String,Double> compute(double a, double b) {
        Map<String,Double> map = new HashMap<>();
        map.put("加: ",a + b);
        map.put("减: ",a - b);
        map.put("乘: ",a * b);
        if (b == 0) {
            System.out.println("分母不能为0!");
            return map;
        }
        map.put("除: ",a / b);
        return map;
    }
}

以下是测试代码:

public static void main(String[] args) throws Exception {

        // 创建对象
        Class c = Class.forName("base.ComputeTest");
        Object obj = c.newInstance();

        // 获取Method
        Method computeMethod = c.getDeclaredMethod("compute", double.class, double.class);

        Map<String,Double> map = new HashMap<>();
        //invoke方法调用obj里的compute方法
        map = (Map<String, Double>) computeMethod.invoke(obj,1,4);

        // 遍历输出map集合
        for (String s : map.keySet()) {
            System.out.println(s + ": " + map.get(s));
        }
    }

输出结果:

加: : 5.0
减: : -3.0
除: : 0.25
乘: : 4.0

五、Constructor类:获取类中的构造方法和调用构造方法实例化对象

这是Student类:

public class Student {
    private String name;
    private  int age;
    public String id;
    public String sex;
    
    public Student() {
        System.out.println("这是Student类的无参构造方法!");
    }

    public Student(String name, int age, String id, String sex) {
        this.name = name;
        this.age = age;
        this.id = id;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id='" + id + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}
public static void main(String[] args) throws Exception {
        // 调用无参数构造方法
        Class c = Class.forName("base.Student");
        Object obj1 = c.newInstance();
        System.out.println(obj1);
        
        // 调用有参数的构造方法
        Constructor conStudent = c.getConstructor(String.class,int.class,String.class,String.class);
        Object obj2 = conStudent.newInstance("oos",18,"20201111","male");
        System.out.println(obj2);
    }

输出结果:

这是Student类的无参构造方法!
Student{name='null', age=0, id='null', sex='null'}
Student{name='oos', age=18, id='20201111', sex='male'}

参考资料

[1]https://www.jianshu.com/p/9be58ee20dee
[2]https://www.bilibili.com/video/BV1Rx411876f (动力节点杜老师)