Java进阶之反射

1 什么是反射

(1)反射(Reflection)是Java 程序开发语言的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。
(2)一句话总结:反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
(3)反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁,重点:是运行时而不是编译时。

2 反射的主要用途

当我们在使用IDE(如Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。
反射最重要的用途就是开发各种通用框架。很多框架(比如Spring)都是配置化的(比如通过XML文件配置JavaBean,Action之类的),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。例如:

  • 框架提供配置文件,用户可以配置,例如类名
  • 读取用户的配置文件
//一定要用完整的路径,不是硬编码,而是运算出来的
InputStream is = new FileInputStream("文件目录");
Properties properties = new Properties();
properties.load(is);
String value = properties.getProperty("key");
  • 通过反射加载对应的类,并且动态去使用

3 反射的基本运用

3.1 获得Class对象

(1)使用Class类的forName静态方法:

public static Class<?> forName(String className)
// 在JDBC开发中常用此方法加载数据库驱动
Class.forName(driver);

(2)直接获取某一个对象的class,比如:

Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;

(3)调用某个对象的getClass()方法,比如:

StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();

(4)判断是否为某个类的实例
一般地,我们用instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断是否为某个类的实例,它是一个Native方法:

public native boolean isInstance(Object obj);

3.2 创建对象

(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。

Class<?> c = String.class;
Object str = c.newInstance();
// 或者:
UserBean where;
Object item = where.getClass().newInstance();

(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。

//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例,constructor.newInstance();
Object obj = constructor.newInstance("23333");

3.3 获取某个Class对象的方法集合

(1)getDeclaredMethods()方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

Class<?> c = methodClass.class;
Object object = c.newInstance();

Method[] declaredMethods = c.getDeclaredMethods();

(2)getMethods()方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。

Method[] methods = c.getMethods();

(3)getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。

//获取methodClass类的add方法
Method method = c.getMethod("add", int.class, int.class);

3.4 获取构造方法

(1)得到所有的构造方法

Constructor<?>[] constructors = Class.forName("java.lang.String").getConstructors();

(2)得到指定参数的某一个构造方法

Constructor<?> constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);

3.5 通过反射获取带参和不带参构造方法

3.5.1 反射获得带参构造方法基础

(1)获取字节码文件对象

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

(2)获取带参构造器对象,public Constructor getConstructor(Class<?>… parameterTypes)

Constructor con = c.getConstructor(String.class, int.class, String.class);

(3)通过带参构造器对象创建对象,public T newInstance(Object… initargs)

Object obj = con.newInstance("林青霞", 27, "北京");
System.out.println(obj);
3.5.2 反射获得带参构造方法通用方法
public class Person {
    private int num;
   
    public Person() {
    }
    public Person(int num) {
        num = num;
    }
}
public class Son extends Person {
    private int num;
    private String str;
    
    public Son() {
        super();
    }
    public Son(int num, String str) {
        super(num);
        this.num = num;
        this.str = str;
    }
}
public <T extends Person> T createMethodVisitor(Class<T> clz, Class[] classParam, Object[] paramValues) {
        Person person = null;
        try {
            // 根据类路劲获取类类型
            Class c = Class.forName(clz.getName());
            // 调用构造方法,创建对象的Constructor对象,用他来获取构造方法的信息:即用其调用构造方法创建实例
            Constructor con;
            if (classParam != null && classParam.length > 0) {
                con = c.getConstructor(classParam);
            } else {
                con = c.getConstructor();
            }
            // 调用构造方法并创建实例
            if (paramValues != null && paramValues.length > 0) {
                person = (Person) con.newInstance(paramValues);
            } else {
                person = (Person) con.newInstance();
            }
        } catch (Exception e) {
            e.fillInStackTrace();
        }
        return (T) person;
    }
// 类的类型
Class[] params = {int.class, String.class};
// 类型值
Object[] values = {122, "测试"};
Log.i("createMethodVisitor1", "params = " + params[0] + " values = " + values[0]);
Object obj = createMethodVisitor(Son.class, params, values);
Log.i("createMethodVisitor2", "有参obj = " + obj);
Son son1 = createMethodVisitor(Son.class, null, null);
Log.i("createMethodVisitor3", "无参obj = " + son1);
// 结果
createMethodVisitor1: params = class java.lang.Integer values = 122
createMethodVisitor2: 有参obj = com.Son@2eb9c32
createMethodVisitor3: 无参obj = com.Son@894ed83
3.5.3 注意事项

(1)参数错误

// 错误
Class[] params = {Integer.class, String.class, Boolean.class};
// 正确
Class[] params = {int.class, String.class, boolean.class};

(2)构造函数
构造函数不能继承,只是调用而已。如果父类有无参构造函数,显式调用或者系统会默认调用父类无参构造函数super();如果父类没有无参构造函数,创建子类时不能编译。如下:删除了父类Person的无参构造函数:
在这里插入图片描述
创建有参构造函数后,系统就不再有默认无参构造函数;如果没有任何构造函数,系统会默认有一个无参构造函数。
学习链接:java中子类继承父类时是否继承构造函数

3.5.4 学习链接

通过反射获得带参构造方法并且使用

反射创建一个实例:构造方法带参和不带参

3.6 获取类的成员变量(字段)信息

(1)getFiled(): 访问公有的成员变量;
(2)getDeclaredField():所有已声明的成员变量。但不能得到其父类的成员变量;
(3)getFileds和getDeclaredFields():用法同上(参照Method)。

3.7 调用方法

当我们从类中获取了一个方法后,我们就可以用invoke()方法来调用这个方法。invoke方法的原型为:

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
public class test {
	Class<?> klass = methodClass.class;
	//创建methodClass的实例
	Object obj = klass.newInstance();
	//获取methodClass类的add方法
	Method method = klass.getMethod("add",int.class,int.class);
	//调用method对应的方法 => add(1,4)
	Object result = method.invoke(obj,1,4);
	System.out.println(result);
}

3.8 利用反射创建数组

数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference:

public static void testArray() throws ClassNotFoundException {
        Class<?> cls = Class.forName("java.lang.String");
        Object array = Array.newInstance(cls,25);
        // 往数组里添加内容
        Array.set(array,0,"hello");
        Array.set(array,1,"Java");
        Array.set(array,2,"fuck");
        Array.set(array,3,"Scala");
        Array.set(array,4,"Clojure");
        // 获取某一项的内容
        System.out.println(Array.get(array,3));
    }

其中的Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:

public static Object newInstance(Class<?> componentType, int length)
        throws NegativeArraySizeException {
        return newArray(componentType, length);
    }

而newArray()方法是一个Native方法,它在Hotspot JVM里的具体实现我们后边再研究:

private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;

4 实践

(1)基本使用参考:Java进阶之注解

(2)反射方式赋值

 /**
  * 获取查询结果
  */
 private List<T> getQueryResult(Cursor cursor, T where) {
	ArrayList items = new ArrayList();
    // 查询的对象
    Object item;
    while (cursor.moveToNext()) {
	    try {
	        item = where.getClass().newInstance();
            // 遍历映射集合(relationMap)的key:数据库表列名(colmunName)
            for (Object object : relationMap.entrySet()) {
	            Map.Entry entry = (Map.Entry) object;
                // 得到数据库表列名
                String colomunName = (String) entry.getKey();
                // 然后以列名拿到:列名在游标的位置
                Integer colmunIndex = cursor.getColumnIndex(colomunName);

                // 获取key对应的值:成员变量(Field对象)
                Field field = (Field) entry.getValue();
                Class type = field.getType();
                if (colmunIndex != -1) {
	                if (type == String.class) {
	                    // 反射方式赋值(native方法),相当于item.setValue(cursor.getString(colmunIndex));
                        field.set(item, cursor.getString(colmunIndex));
                    } else if (type == Double.class) {
                        field.set(item, cursor.getDouble(colmunIndex));
                    } else if (type == Integer.class) {
                        field.set(item, cursor.getInt(colmunIndex));
                    } else if (type == Long.class) {
                        field.set(item, cursor.getLong(colmunIndex));
                    } else if (type == byte[].class) {
                        field.set(item, cursor.getBlob(colmunIndex));
                    } else {
                        continue;
                    }
                }
            }
      items.add(item);
      } catch (InstantiationException e) {
	      e.printStackTrace();
      } catch (IllegalAccessException e) {
          e.printStackTrace();
      }
    }
	return items;
}

5 注意使用

由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

6 参考链接

深入解析Java反射(1) - 基础

深入解析Java反射(2) - invoke方法

JAVA面试-基础加强与巩固:反射、注解、泛型等

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值