前言

反射允许运行中的 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()方法

java当中的任何一个类都是要装载在虚拟机上才能够运行,因此Class.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'}

参考资料

https://www.jianshu.com/p/9be58ee20dee