目录

Java反射笔记

Java通过反射获给Java对象赋值为null

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
	 *
	 *java通过反射获给java对象赋值为null
	 * @param obj 对象
	 * @param propertyName 字段
	 * @return java.lang.Object
	 */
	public static Object setPropertyNull(Object obj, String propertyName) {
		//利用反射获取类属性
		Field[] field = obj.getClass().getDeclaredFields();
		for (int i = 0; i < field.length; i++) {
			Field f = field[i];
			f.setAccessible(true);
			//获取属性名
			String name = field[i].getName();
			name = name.substring(0, 1).toUpperCase() + name.substring(1);
			Method method = null;
			//获取属性类型
			String type = field[i].getGenericType().toString();
			try {
			//
				if (type.equals("class java.lang.String") && propertyName.equalsIgnoreCase(name)) {
					method = obj.getClass().getMethod("set" + name, String.class);
					method.invoke(obj, (Object)null);
				}
				if (type.equals("class java.lang.Integer") && propertyName.equalsIgnoreCase(name)) {
					method = obj.getClass().getMethod("set" + name, Integer.class);
					method.invoke(obj, (Object)null);
				}
				if (type.equals("int") && propertyName.equalsIgnoreCase(name)) {
					method = obj.getClass().getMethod("set" + name, int.class);
					method.invoke(obj, (Object)null);
				}
				if (type.equals("class java.lang.Long") && propertyName.equalsIgnoreCase(name)) {
					method = obj.getClass().getMethod("set" + name, Long.class);
					method.invoke(obj, (Object)null);
				}
				if (type.equals("class java.math.BigDecimal") && propertyName.equalsIgnoreCase(name)) {
					method = obj.getClass().getMethod("set" + name, BigDecimal.class);
					method.invoke(obj, (Object)null);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return obj;
	}```

使用PropertyDescriptor反射调用setter/getter方法

有时候我们只知道一个对象的字段,我们想通过反射的方式将此字段赋值,可直接写反射又太浪费时间,还需要自己手动拼接方法名,而java为我们提供了一个很方便的类(PropertyDescriptor)来操作这一过程。使用很简单,直接看代码:

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import com.pibgstar.demo.bean.User;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author pibigstar
 * @create 2018-12-03 18:36
 * @desc
 **/
public class TestPropertyDescriptor {

    public static void main(String[] args){
        TestPropertyDescriptor test = new TestPropertyDescriptor();
        User user = new User();
        user.setId("123");

        Object id = test.getAttribute("id", user);
        System.out.println("id:"+id.toString());

        test.setAttribute("name",user,"pibigstar");

        System.out.println(user);
    }

    /**
     * @Author:pibigstar
     * @Description: 根据字段获取属性值
     */
    private Object getAttribute(String filed,Object obj) {
        try {
            PropertyDescriptor pd = new PropertyDescriptor(filed,obj.getClass());
            // 获取getter方法
            Method method = pd.getReadMethod();
            Object result = method.invoke(obj);
            return result;
        } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
      return null;
    }

    /**
     * @Author:pibigstar
     * @Description: 设置属性字段值
     */
    private void setAttribute(String filed,Object obj,Object value) {
        try {
            PropertyDescriptor pd = new PropertyDescriptor(filed,obj.getClass());
            // 获取setter方法
            Method method = pd.getWriteMethod();
            method.invoke(obj, value);
        } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

运行结果

1
2
id:123
User{id='123', name='pibigstar'}

反射中Method类的invoke() 和getMethod()

就是调用类中的方法,最简单的用法是可以把方法参数化。

1
2
invoke(class, method)
Method Class.getMethod(String name, Class<?>... parameterTypes)的作用是获得对象所声明的公开方法

该方法的第一个参数name是要获得方法的名字,第二个参数parameterTypes是按声明顺序标识该方法形参类型。

1
2
3
4
person.getClass().getMethod("Speak", null);
//获得person对象的Speak方法,因为Speak方法没有形参,所以parameterTypes为null
person.getClass().getMethod("run", String.class);
//获得person对象的run方法,因为run方法的形参是String类型的,所以parameterTypes为String.class

如果对象内的方法的形参是int类型的,则parameterTypes是int.class

1
2
3
4
5
//getMethod第一个参数是方法名,第二个参数是该方法的参数类型,
//因为存在同方法名不同参数这种情况,所以只有同时指定方法名和参数类型才能唯一确定一个方法
Method method = XXX.getClass().getMethod(methodName,new Class[0]);
//第一个参数是具体调用该方法的对象
//第二个参数是执行该方法的具体参数    

如一个函数 int Test(int a, String str);

对应的getMethod方法:

  1. getMethod(“Test”,int.class,String.class);

  2. getMethod(“Test”,new Class[]{int.class,String.class});

1
2
3
4
5
6
7
8
//Method类的invoke(Object obj,Object args[])方法接收的参数必须为对象,返回值总是对象。
 //如果参数为基本类型数据,必须转换为相应的包装类型的对象。
    public Object invoke(Object obj,
                         Object... args)
            throws IllegalAccessException,
            IllegalArgumentException,
            InvocationTargetException
                

InvokeObj.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class InvokeObj {
    public void show() {
        System.out.println("无参show()方法");
    }

    public void show(String name) {
        System.out.println("show方法:" + name);
    }

    public String[] arrayShow(String[] arr) {
        return arr;
    }

    public String stringShow(String str) {
        return str;
    }

    public int intShow(int sum) {
        return sum;
    }
}

MethodInvokeTest.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodInvokeTest {
    public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {

        Class<InvokeObj> clazz = InvokeObj.class;
        Method[] methods = clazz.getMethods();
        //输出了Class类型的所有方法。
        System.out.println("以下输出InvokeObj类的方法");
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println();

        System.out.println("InvokeObj类的无参show()方法:");
        Method method1 = clazz.getMethod("show", null);
        //会执行无参show()方法
        Object obj = method1.invoke(new InvokeObj(), null);
        System.out.print("输出无参show()方法的返回值:" + obj);
        System.out.println();
        System.out.println();

        System.out.println("InvokeObj类的show()方法: ");
        Method method2 = clazz.getMethod("show", String.class);
        Object obj1 = method2.invoke(new InvokeObj(), "hello,world");
        // System.out.println("输出有参show()方法: ");
        System.out.println();

        System.out.println("InvokeObj类的arrayShow()方法: ");
        Method method3 = clazz.getMethod("arrayShow", String[].class);
        String[] strs = new String[]{"hello", "world", "!"};
        //数组类型的参数必须包含在new Object[]{}中,否则会报IllegalArgumentException  
        String[] strings = (String[]) method3.invoke(new InvokeObj(), new Object[]{strs});
        for (String str : strings) {
            System.out.println("arrayShow的数组元素:" + str);
        }
        System.out.println();

        System.out.println("InvokeObj类的StringShow()方法: ");
        Method method4 = clazz.getMethod("stringShow", String.class);
        String string = (String) method4.invoke(new InvokeObj(), "Thinking in java");
        System.out.println("stringShow()方法的返回值: " + string);
        System.out.println();

        System.out.println("InvokeObj类的intShow()方法: ");
        Method method5 = clazz.getMethod("intShow", int.class);
        int num = (Integer) method5.invoke(new InvokeObj(), 89);
        System.out.println("intShow()方法的返回值: " + num);
    }
}

./1.png

Java反射通过class获取父类泛型类型

在Java的反射技术中,通过Method和Field对象很容易就可以取出泛型类型的具体类型。但是普通类的泛型参数在编译期会被擦除,比如定义了一个List对象,在运行期我们可以拿到的信息就只剩下List,并不知道List中的元素类型是什么。这也说明了,为什么fun(List list)和 fun(List)会被编译器认为是相同的函数。

但是,对于获取父类的泛型类型,还是会有办法的。在Class类中,有这样一个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    /**
     * Returns the {@code Type} representing the direct superclass of
     * the entity (class, interface, primitive type or void) represented by
     * this {@code Class}.
     *
     * <p>If the superclass is a parameterized type, the {@code Type}
     * object returned must accurately reflect the actual type
     * parameters used in the source code. The parameterized type
     * representing the superclass is created if it had not been
     * created before. See the declaration of {@link
     * java.lang.reflect.ParameterizedType ParameterizedType} for the
     * semantics of the creation process for parameterized types.  If
     * this {@code Class} represents either the {@code Object}
     * class, an interface, a primitive type, or void, then null is
     * returned.  If this object represents an array class then the
     * {@code Class} object representing the {@code Object} class is
     * returned.
     *
     * @throws java.lang.reflect.GenericSignatureFormatError if the generic
     *     class signature does not conform to the format specified in
     *     <cite>The Java™ Virtual Machine Specification</cite>
     * @throws TypeNotPresentException if the generic superclass
     *     refers to a non-existent type declaration
     * @throws java.lang.reflect.MalformedParameterizedTypeException if the
     *     generic superclass refers to a parameterized type that cannot be
     *     instantiated  for any reason
     * @return the superclass of the class represented by this object
     * @since 1.5
     */
    public Type getGenericSuperclass() {
        if (getGenericSignature() != null) {
            // Historical irregularity:
            // Generic signature marks interfaces with superclass = Object
            // but this API returns null for interfaces
            if (isInterface())
                return null;
            return getGenericInfo().getSuperclass();
        } else
            return getSuperclass();
    }

此方法返回的是具有泛型信息的父类,如果没有则返回Object对象。具体什么样的类都返回什么样的结果,来看测试代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    public void printGenericType(){
        Class clazz1 = new ArrayList<Integer>().getClass();
        Class clazz2 = new String[0].getClass();
        Class clazz3 = String.class;
        Class clazz4 = Object.class;
        Class clazz5 = new IntList().getClass();
 
        System.out.println(clazz1.getGenericSuperclass());
        System.out.println(clazz2.getGenericSuperclass());
        System.out.println(clazz3.getGenericSuperclass());
        System.out.println(clazz4.getGenericSuperclass());
        System.out.println(clazz5.getGenericSuperclass());
    }

结果:

1
2
3
4
5
java.util.AbstractList<E>
class java.lang.Object
class java.lang.Object
null
java.util.ArrayList<java.lang.Integer>

上面的测试代码中分别打印出了ArrayList,数组,普通类,Object类和自定义的一个类通过getGenericSuperclass()方法获取到的结果。普通类以及数组的父类都是Object,所以打印出的结果是Object。Object没有父类,打印出的是null。普通的ArrayList打印出的结果是java.util.AbstractList,第五个class打印出了带有泛型信息的java.util.ArrayList<java.lang.Integer>。IntList的定义是这样的

1
2
public class IntList extends ArrayList<Integer> {
}

ArrayList的定义是这样的

1
2
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

​ 两处的不同在于继承父类的时候有没有知道父类泛型的具体类型,如果指定了,则可以通过上面的方法得到这个具体的类型。否则不能得到,这两者一定还存在 其他的区别联系,下面打印出获得的具体类型是什么

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    public void getGenericType(){
        Class clazz1 = new ArrayList<Integer>().getClass();
        Type type = clazz1.getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) type;
        System.out.println(parameterizedType);
        System.out.println(parameterizedType.getActualTypeArguments()[0].getClass());
 
        Class clazz2 = new IntList().getClass();
        Type type2 = clazz2.getGenericSuperclass();
        ParameterizedType parameterizedType2 = (ParameterizedType) type2;
        System.out.println(parameterizedType2);
        System.out.println(parameterizedType2.getActualTypeArguments()[0].getClass());
    }

结果

1
2
3
4
java.util.AbstractList<E>
class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
java.util.ArrayList<java.lang.Integer>
class java.lang.Class

​ 原来如此,如果在继承父类的时候没有指定父类的泛型类型,则这时候获取到的类型是TypeVariableImpl;而指定了父类的泛型类型则这时候就能获取到对应的Class

反射应用–通用的toString方法,泛型数组代码

反射应用 - toString方法/泛型数组代码

  我们常用的toString方法都是显式地调用其域,当需要时,要在类中重写toString方法(基本上一个类一个),而通用toString方法什么都不需要知道,直接调用即可。

  泛型数组代码不需要记,是java.util.Arrays类中copyOf(T[] original, int newLength) 方法实现底层中使用了反射机制,这里介绍了一下而已。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

public class ObjectAnalyzer{
	
	private ArrayList<Object> vistied =new ArrayList<>();
	
	public String toString(Object obj) {
		if (obj == null) return "null";   //对象为null,返回“null”
		if (vistied.contains(obj)) return "...";  //
		vistied.add(obj);
		
		Class<? extends Object> c1 = obj.getClass();
		if(c1 == String.class) return (String) obj; //对象类型为字符串,直接打印
		
		//对象类型为数组
		if(c1.isArray()) { 
			String r = c1.getComponentType() + "[]{";  //getComponentType返回数组类型
			for(int i=0;i<Array.getLength(obj);i++) {  //getLength返回数组长度
				if(i>0) r +=",";  //逗号分隔元素
				Object val =Array.get(obj, i); //返回数组中指定索引的值
				if(c1.getComponentType().isPrimitive()) //若数组中类型为基本类型,直接添加于字符串r中
					r += val;  
				else 
					r += toString(val); //递归,执行本方法中‘对象类型非数组’部分
				
			}
			return r+"}";
		}
		
		//对象类型为非数组
		String r = c1.getName(); //获取对象所属的类
		do {
			r += "[";
			Field[] fields = c1.getDeclaredFields(); //获取本类所有域
			AccessibleObject.setAccessible(fields, true); //开放域的访问限制
			for(Field f: fields) {
				if(!Modifier.isStatic(f.getModifiers())) {  //非静态域
					if(!r.endsWith("[")) r += ",";
					r += f.getName() + "=";
					try {   //处理异常
						Class<?> t = f.getType();  //获取域的类型
						Object val = f.get(obj);  //获取域值
						if(t.isPrimitive()) 
							r+= val; //若域类型为基本数据类型,直接加入域值
						else
							r += toString(val); //若域类型为对象,调用本方法
					}catch(Exception e){
						e.printStackTrace();
					}
				}
			}
			r += "]";
			c1 = c1.getSuperclass();  //开始获取超类的域
		}while(c1 != null);
		
		return r;
	}
	
	
	//泛型数组代码
	public static Object copyOf(Object a,int newLength) { //参数类型定义为Object,而不可以是Object[],是因为基本类型数组不可转换为Object[],但可以是Object
		Class<? extends Object> c1 = a.getClass();
		if(!c1.isArray()) return null;  //不是数组,返回null
		Class<?> componentType = c1.getComponentType();  //获取数组类型
		int length = Array.getLength(a); //获取数组长度
		Object newArray = Array.newInstance(componentType, newLength);
		System.arraycopy(a, 0, newArray, 0, Math.min(length,newLength));
		return newArray;
		
	}
}

调用

1
2
3
//调用toString方法
Employee em = new Employee(2,3,4,5);
System.out.println(new ObjectAnalyzer().toString(em));

java.lang.reflect.Array 相关API 表

返回类型 方法名和参数 解释
static Object get(Object array, int index) 返回存储在指定index上给定数组的内容
static xxxx getXxxx(Object array, int index) 若index索引处的类型为基本类型,可直接调用相关方法,那样得到的结果就不用强转了
static void set(Object array, int index, Object value) 将新值存储到给定位置上的给定数组中
static void setXxxx(Object array, int index, Object value) 若index索引处的类型为基本类型,可直接调用相关方法
static int getLength(Object array) 返回指定数组对象的长度
static Object newInstance(Class<?> componentType, int length) 返回具有给定类型,给定长度的新数组