Java反射的使用姿势一览

反射的学习使用

日常的学习工作中,可能用到反射的地方不太多,但看看一些优秀框架的源码,会发现基本上都离不开反射的使用;因此本篇博文将专注下如何使用

本片博文布局如下:

  1. 反射是什么,有什么用,可以做什么

  2. 如何使用反射

  3. 实例:

    • 利用反射方式,获取一个类的所有成员变量的name及值
    • 通过反射方式,修改对象的私有成员变量
    • 会通过写一个BeanUtils实现对象的成员变量值拷贝来覆盖上面两个场景

I. 反射定义

指程序可以访问、检测和修改它本身状态或行为的一种能力

直接说定义的话,可能并不能非常清晰的解释说明,结合作用进行描述

反射可以干什么?

1在运行时构造任意一个类的对象。 2在运行时判断任意一个对象所属的类。 3在运行时判断任意一个类所具有的成员变量和方法。 4在运行时调用任意一个对象的方法

有了上面四点,基本上你想干嘛就可以干嘛,比如我现在就有下面这个类

1public class RefectTest extends MyRefect implements IRefect { 2 3 private static String s1 = "hello"; 4 5 private static int s2 = 100; 6 7 private int s3 = 200; 8 9 private boolean ans; 10 11 protected RefectTest next; 12 13 public RefectTest() { 14 } 15 16 public RefectTest(int s3, boolean ans, RefectTest next) { 17 this.s3 = s3; 18 this.ans = ans; 19 this.next = next; 20 } 21 22 public RefectTest next() { 23 return next; 24 } 25 26 private int count(int a, int b) { 27 return a + b; 28 } 29}

现在我有了clz,其赋值语句为 Class clz = RefectTest.class, 那么我可以干啥?

  1. 创建一个 RefectTest 对象

    1// 若有默认构造方法 2RefectTest instance = clz.newIntance(); 3 4// 若需要传参数 5Constructor con = clz.getConstructor(int.class, boolean.class, RefectTest.class); 6RefectTest instance2 = con.newInstance(10, true, new RefectTest());
  2. 判断父类是否是 MyRefect

    1// 判断MyRefect是否为clz的父类 2boolean ans = MyRefect.class.isAssignableFrom(clz);
  3. 获取所有的成员变量

    1// 获取所有的成员变量(包括私有的) 2Field[] fields = clz.getDeclaredFields();
  4. 获取所有的方法

    1// 获取所有的成员方法(包括私有方法) 2Method[] methods = clz.getDeclaredMethods();

上面给出了可以干些什么,并给了对应的简单示例,引入了几个新的类Constructor, Field, Method, 下面将详细解释这三个类是什么,怎么用

II. 反射的使用

努力结合实际的应用场景,给出每种利用反射的实现对应需求的使用姿势,有些场景可能并不是特别贴切,欢迎提出给合适的场景以此进行替换

1. 通过反射创建对象

这是个比较常见的场景,我在使用了自定义注解时,通常会这么晚

应用场景:

我定义了一个校验器的注解ValDot,注解中有个校验规则class对象,如下

1public interface ICheckRule { 2 boolean check(Object ... obj); 3} 4 5public class DefaultCheckRule implements ICheckRule { 6 @Override 7 public boolean check(Object... obj) { 8 return false; 9 } 10} 11 12@Retention(RetentionPolicy.RUNTIME) 13@Target(ElementType.METHOD) 14public @interface CheckDot { 15 // 校验规则 16 Class<? extends ICheckRule> check() default DefaultCheckRule.class; 17}

上面定义了注解和校验条件,接着进入整体,在切面中,需要获取

1@Aspect 2@Component 3public class CheckAspect { 4 @Before("@annotation(checkDot)") 5 public void process(JoinPoint joinPoint, CheckDot checkDot) 6 throws IllegalAccessException, InstantiationException { 7 // 注意,这里获取注解上的校验规则类,并获取实例 8 ICheckRule rule = checkDot.check().newInstance(); 9 10 if(rule.check(joinPoint.getArgs())) { 11 throw new IllegalStateException("check argument error!"); 12 } 13 } 14}

上面是一个较好的利用反射获取实例的应用场景,想一想,如果不用反射,这个校验规则怎么传进来呢,这个时候就没那么方便了(当然也不是不可以,最简单的就是拿一个Holder持有类名到类对象的映射关系,然后在注解中传类名,也可以达到上面的效果)

还有一种场景可能就比较蛋疼了,如果一个类没有默认构造方法,通过反射就没法直接用class.newInstanace()


Constructor构造器类

根据Class优先获取到 Constructor 对象,然后传入需要的构造参数, 测试如下

1public class ConTest { 2 3 private int a,b; 4 5 public ConTest(int a, int b) { 6 this.a = a; 7 this.b = b; 8 } 9 10 @Override 11 public String toString() { 12 return "ConTest{" + "a=" + a + ", b=" + b + '}'; 13 } 14 15 public static void main(String[] args) throws Exception { 16 Class clz = ConTest.class; 17 // 获取对应的构造器(注意参数类型) 18 Constructor constructor = clz.getConstructor(int.class, int.class); 19 // 创建实例(注意参数要匹配) 20 ConTest test = (ConTest) constructor.newInstance(10, 20); 21 System.out.println(test.toString()); 22 } 23}

输出

ConTest{a=10, b=20}

一般常用下面四种方式获取

1// 根据参数类型获取匹配的构造器 2Constructor getConstructor(Class[] params) 3 4// 获取所有的 5Constructor[] getConstructors() 6 7// 相比较前面的,这里可以获取私有方法 8Constructor getDeclaredConstructor(Class[] params) 9 10// 可以获取私有方法 11Constructor[] getDeclaredConstructors()

2. 判断class的继承关系

判断是否为基础数据类型

基本类型较为特殊,所以JDK很人性化的给封装了一个方法,Class#isPrimitive

因此返回true的类型有:

  • int
  • long
  • short
  • byte
  • char
  • boolean

封装后的类型,返回的依然是false

<font color="red">附带一句,是没有null.class这种用法的</font>


判断是否为另一个类的子类,另一个接口的实现类

通常我们利用 instanceof 关键字来判断继承关系,但是这个是针对对象来的,现在给一个class,要怎么玩?

看下面,主要就是 Class#isAssignableFrom() 的功劳了

1public class ExtendTest { 2 3 interface ITest {} 4 5 abstract class ATest { 6 abstract public void print(); 7 } 8 9 class TestClz extends ATest implements ITest { 10 @Override 11 public void print() { 12 System.out.println("TestClz"); 13 } 14 } 15 16 17 public static void main(String[] args) { 18 Class clz = TestClz.class; 19 20 System.out.println(ATest.class.isAssignableFrom(clz)); 21 System.out.println(ITest.class.isAssignableFrom(clz)); 22 } 23}

需要注意一点,父类作为调用方,子类作为参数

结合泛型时,获取泛型的实际类型

泛型,又是一个有意思的功能,这里不多说,继承一个泛型基类,然后问题是如何通过反射获得泛型签名中的类型,一般会在继承或实现泛型接口时会用到它。

1class A<T, ID> { 2} 3 4class B extends A<String, Integer> { 5} 6 7public static void main(String[] args) { 8 System.out.println(B.class.getGenericSuperclass()); 9}

换成泛型接口呢 ?

1interface A<T, ID> { 2} 3 4class B implements A<String, Integer> { 5} 6 7public static void main(String[] args) { 8 ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericInterfaces()[0]; 9 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); 10 for (Type actualTypeArgument : actualTypeArguments) { 11 System.out.println(actualTypeArgument); 12 } 13}

3. 获取成员变量

获取成员变量,主要是根据 B.class.getDeclaredFields() 来获取所有声明的变量,这个应用场景会和下面的获取方法并执行联合一起说明

1// 获取指定的公共成员变量 2Field getField(String name) 3 4// 获得所有公共字段 5Field[] getFields() 6 7// 获取指定声明的成员变量(包括prive) 8Field getDeclaredField(String name) 9 10// 获取所有声明的成员变量 11Field[] getDeclaredFields()

这个主要返回 Field对象,现在有了Field,可以做些啥?

  1. 判断成员的修饰 Field#getModifiers()

    1int modify = field.getModifiers(); 2// 是否是静态变量 3boolean ans = Modifier.isStatic(modifier); 4// 是否是公共变量 5boolean ans = Modifier.isPublic(modifier); 6// 是否不可变 7boolean ans = Modifier.isFinal(modifier); 8// ...
  2. 获取成员的变量名 : field#getName()

  3. 获取成员对应的value: field#get(instance)

    • 对于静态成员,instance可以为null
    • 对于非静态成员,instance必须为一个实例对象
  4. 获取注解: field#getAnnotations()

    • 这个就厉害了,hibernate的校验框架,在成员变量上加一个注解Max,就可以设置参数的最大值,其实就是通过反射获取到注解,然后进行相应的逻辑

4. 获取方法

获取方法,同上面的差不多,也有四种方式

1// 根据方法名,参数类型获取公共方法 2Method getMethod(String name, Class[] params) 3 4// 获取所有的公共方法 5Method[] getMethods() 6 7// 根据方法名,参数类型,获取声明的方法(包括私有) 8Method getDeclaredMethod(String name, Class[] params) 9 10// 获取所有声明的方法 11Method[] getDeclaredMethods()

返回了一个Method类,那么这个东西又有一些什么功能?

  1. 获取方法名 Method#getName()

  2. 获取方法所在的类 : Method#getDeclaringClass()

  3. 获取方法返回类型 : Method#getReturnType()

  4. 获取方法上的注解 : Method#getAnnotations()

  5. 执行方法 有了这个就可以做很多事情了,实例中给出说明

    1// 设置方法可访问(即私有方法也可以被调用) 2method.setAccessible(true); 3// instance为实例对象, args为传入参数 4method.invoke(instance, args)

III. 实例DEMO

通过反射的方式,实现一个 BeanUtils,实现Bean的拷贝

当一个Bean有较多的成员变量时,如果我们采用最原始的setXXX()来一次赋值的时候,一是实现比较繁琐,其次就是当Bean的字段发生变动之后,也需要同步的修改,那么我们借助反射的方式,实现一个优雅的 BeanUtils 工具类

1public class BeanUtils { 2 public static void copy(Object source, Object dest) throws Exception { 3 Class destClz = dest.getClass(); 4 5 // 获取目标的所有成员 6 Field[] destFields = destClz.getDeclaredFields(); 7 Object value; 8 for (Field field : destFields) { // 遍历所有的成员,并赋值 9 // 获取value值 10 value = getVal(field.getName(), source); 11 12 field.setAccessible(true); 13 field.set(dest, value); 14 } 15 } 16 17 18 private static Object getVal(String name, Object obj) throws Exception { 19 try { 20 // 优先获取obj中同名的成员变量 21 Field field = obj.getClass().getField(name); 22 field.setAccessible(true); 23 return field.get(obj); 24 } catch (NoSuchFieldException e) { 25 // 表示没有同名的变量 26 } 27 28 // 获取对应的 getXxx() 或者 isXxx() 方法 29 name = name.substring(0, 1).toUpperCase() + name.substring(1); 30 String methodName = "get" + name; 31 String methodName2 = "is" + name; 32 Method[] methods = obj.getClass().getMethods(); 33 for (Method method : methods) { 34 // 只获取无参的方法 35 if (method.getParameterCount() > 0) { 36 continue; 37 } 38 39 if (method.getName().equals(methodName) 40 || method.getName().equals(methodName2)) { 41 return method.invoke(obj); 42 } 43 } 44 45 // 没有匹配到,这里返回null实际上是不合适的 46 // 因为如果原属性为基本数据类型,赋值null为报错 47 throw new Exception(); 48 } 49}

IV. 小结

反射的四种用途

  1. 创建一个 RefectTest 对象

    1// 若有默认构造方法 2RefectTest instance = clz.newIntance(); 3 4// 若需要传参数 5Constructor con = clz.getConstructor(int.class, boolean.class, RefectTest.class); 6RefectTest instance2 = con.newInstance(10, true, new RefectTest());
  2. 判断父类是否是 MyRefect

    1// 判断MyRefect是否为clz的父类 2boolean ans = MyRefect.class.isAssignableFrom(clz);
  3. 获取所有的成员变量

    1// 获取所有的成员变量(包括私有的) 2Field[] fields = clz.getDeclaredFields();
  4. 获取所有的方法

    1// 获取所有的成员方法(包括私有方法) 2Method[] methods = clz.getDeclaredMethods(); 3 4

使用注意事项

  1. 操作私有变量,私有方法时,先设置field.setAccessible(true);确保可访问
  2. 反射会带来额外的性能开销
  3. 可以用 Class#isAssignableFrom() 来判断类继承关系
  4. 可以用 Class#isPrimitive()判断是否为基本数据类型
  5. 可以用 Class#getGenericSuperclass() 获取泛型类型

V. 其他

声明

尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见解不全,如有问题,欢迎批评指正

扫描关注,java分享

QrCode

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java面试(反射)05

1.什么是反射JAVA反射机制是在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类信息以及动态调用对象内容就称为java语言的反射机制。2.反射的作用在运行时判断任意一个对象所属的

Java基础与提高干货系列——Java反射机制

前言今天介绍下Java的反射机制,以前我们获取一个类的实例都是使用new一个实例出来。那样太low了,今天跟我一起来学习学习一种更加高大上的方式来实现。正文Java反射机制定义Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以

Java基础与提高干货系列 -- Java反射机制

前言今天介绍下Java的反射机制,以前我们获取一个类的实例都是使用new一个实例出来。那样太low了,今天跟我一起来学习学习一种更加高大上的方式来实现。正文Java反射机制定义Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性