JDK学习之反射的使用姿势一览

文章目录
  1. 反射的学习使用
  2. I. 反射定义
  3. II. 反射的使用
    1. 1. 通过反射创建对象
    2. 2. 判断class的继承关系
      1. 判断是否为基础数据类型
      2. 判断是否为另一个类的子类,另一个接口的实现类
      3. 结合泛型时,获取泛型的实际类型
    3. 3. 获取成员变量
    4. 4. 获取方法
  4. III. 实例DEMO
  5. IV. 小结
  6. V. 其他
    1. 声明
    2. 扫描关注,java分享

反射的学习使用

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

本片博文布局如下:

  1. 反射是什么,有什么用,可以做什么
  2. 如何使用反射
  3. 实例:

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

I. 反射定义

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

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

反射可以干什么?

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

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

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
public class RefectTest extends MyRefect implements IRefect {

private static String s1 = "hello";

private static int s2 = 100;

private int s3 = 200;

private boolean ans;

protected RefectTest next;

public RefectTest() {
}

public RefectTest(int s3, boolean ans, RefectTest next) {
this.s3 = s3;
this.ans = ans;
this.next = next;
}

public RefectTest next() {
return next;
}

private int count(int a, int b) {
return a + b;
}
}

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

  1. 创建一个 RefectTest 对象

    1
    2
    3
    4
    5
    6
    // 若有默认构造方法
    RefectTest instance = clz.newIntance();

    // 若需要传参数
    Constructor con = clz.getConstructor(int.class, boolean.class, RefectTest.class);
    RefectTest instance2 = con.newInstance(10, true, new RefectTest());
  2. 判断父类是否是 MyRefect

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

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

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

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

II. 反射的使用

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

1. 通过反射创建对象

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

应用场景:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface ICheckRule {
boolean check(Object ... obj);
}

public class DefaultCheckRule implements ICheckRule {
@Override
public boolean check(Object... obj) {
return false;
}
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckDot {
// 校验规则
Class<? extends ICheckRule> check() default DefaultCheckRule.class;
}

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

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

if(rule.check(joinPoint.getArgs())) {
throw new IllegalStateException("check argument error!");
}
}
}

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

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


Constructor构造器类

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ConTest {

private int a,b;

public ConTest(int a, int b) {
this.a = a;
this.b = b;
}

@Override
public String toString() {
return "ConTest{" + "a=" + a + ", b=" + b + '}';
}

public static void main(String[] args) throws Exception {
Class clz = ConTest.class;
// 获取对应的构造器(注意参数类型)
Constructor constructor = clz.getConstructor(int.class, int.class);
// 创建实例(注意参数要匹配)
ConTest test = (ConTest) constructor.newInstance(10, 20);
System.out.println(test.toString());
}
}

输出

1
ConTest{a=10, b=20}

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

1
2
3
4
5
6
7
8
9
10
11
// 根据参数类型获取匹配的构造器
Constructor getConstructor(Class[] params)

// 获取所有的
Constructor[] getConstructors()

// 相比较前面的,这里可以获取私有方法
Constructor getDeclaredConstructor(Class[] params)

// 可以获取私有方法
Constructor[] getDeclaredConstructors()

2. 判断class的继承关系

判断是否为基础数据类型

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

因此返回true的类型有:

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

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

附带一句,是没有null.class这种用法的

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ExtendTest {

interface ITest {}

abstract class ATest {
abstract public void print();
}

class TestClz extends ATest implements ITest {
@Override
public void print() {
System.out.println("TestClz");
}
}


public static void main(String[] args) {
Class clz = TestClz.class;

System.out.println(ATest.class.isAssignableFrom(clz));
System.out.println(ITest.class.isAssignableFrom(clz));
}
}

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

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

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

1
2
3
4
5
6
7
8
9
class A<T, ID> {
}

class B extends A<String, Integer> {
}

public static void main(String[] args) {
System.out.println(B.class.getGenericSuperclass());
}

换成泛型接口呢 ?

1
2
3
4
5
6
7
8
9
10
11
12
13
interface A<T, ID> {  
}

class B implements A<String, Integer> {
}

public static void main(String[] args) {
ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericInterfaces()[0];
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}

3. 获取成员变量

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

1
2
3
4
5
6
7
8
9
10
11
// 获取指定的公共成员变量
Field getField(String name)

// 获得所有公共字段
Field[] getFields()

// 获取指定声明的成员变量(包括prive)
Field getDeclaredField(String name)

// 获取所有声明的成员变量
Field[] getDeclaredFields()

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

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

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

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

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

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

4. 获取方法

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

1
2
3
4
5
6
7
8
9
10
11
// 根据方法名,参数类型获取公共方法
Method getMethod(String name, Class[] params)

// 获取所有的公共方法
Method[] getMethods()

// 根据方法名,参数类型,获取声明的方法(包括私有)
Method getDeclaredMethod(String name, Class[] params)

// 获取所有声明的方法
Method[] getDeclaredMethods()

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

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

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

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

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

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

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

III. 实例DEMO

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

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

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
public class BeanUtils {
public static void copy(Object source, Object dest) throws Exception {
Class destClz = dest.getClass();

// 获取目标的所有成员
Field[] destFields = destClz.getDeclaredFields();
Object value;
for (Field field : destFields) { // 遍历所有的成员,并赋值
// 获取value值
value = getVal(field.getName(), source);

field.setAccessible(true);
field.set(dest, value);
}
}


private static Object getVal(String name, Object obj) throws Exception {
try {
// 优先获取obj中同名的成员变量
Field field = obj.getClass().getField(name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException e) {
// 表示没有同名的变量
}

// 获取对应的 getXxx() 或者 isXxx() 方法
name = name.substring(0, 1).toUpperCase() + name.substring(1);
String methodName = "get" + name;
String methodName2 = "is" + name;
Method[] methods = obj.getClass().getMethods();
for (Method method : methods) {
// 只获取无参的方法
if (method.getParameterCount() > 0) {
continue;
}

if (method.getName().equals(methodName)
|| method.getName().equals(methodName2)) {
return method.invoke(obj);
}
}

// 没有匹配到,这里返回null实际上是不合适的
// 因为如果原属性为基本数据类型,赋值null为报错
throw new Exception();
}
}

IV. 小结

反射的四种用途

  1. 创建一个 RefectTest 对象

    1
    2
    3
    4
    5
    6
    // 若有默认构造方法
    RefectTest instance = clz.newIntance();

    // 若需要传参数
    Constructor con = clz.getConstructor(int.class, boolean.class, RefectTest.class);
    RefectTest instance2 = con.newInstance(10, true, new RefectTest());
  2. 判断父类是否是 MyRefect

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

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

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

使用注意事项

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

V. 其他

声明

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

扫描关注,java分享

QrCode

# Java

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×