文档章节

Java反射的使用姿势一览

小灰灰Blog
 小灰灰Blog
发布于 2017/11/14 22:35
字数 2558
阅读 761
收藏 53
点赞 1
评论 0

反射的学习使用

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

本片博文布局如下:

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

  2. 如何使用反射

  3. 实例:

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

I. 反射定义

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

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

反射可以干什么?

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

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

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 对象

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

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

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

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

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

II. 反射的使用

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

1. 通过反射创建对象

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

应用场景:

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

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;
}

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

@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 对象,然后传入需要的构造参数, 测试如下

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());
    }
}

输出

ConTest{a=10, b=20}

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

// 根据参数类型获取匹配的构造器
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

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


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

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

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

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));
    }
}

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

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

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

class A<T, ID> {
}

class B extends A<String, Integer> {
}

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

换成泛型接口呢 ?

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() 来获取所有声明的变量,这个应用场景会和下面的获取方法并执行联合一起说明

// 获取指定的公共成员变量
Field getField(String name)
 
// 获得所有公共字段 
Field[] getFields()

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

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

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

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

    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. 获取方法

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

// 根据方法名,参数类型获取公共方法
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. 执行方法 有了这个就可以做很多事情了,实例中给出说明

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

III. 实例DEMO

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

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

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 对象

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

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

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

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

使用注意事项

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

V. 其他

声明

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

扫描关注,java分享

QrCode

© 著作权归作者所有

共有 人打赏支持
小灰灰Blog
粉丝 160
博文 150
码字总数 258693
作品 0
武汉
程序员
为什么我墙裂建议大家使用枚举来实现单例。

关于单例模式,我的博客中有很多文章介绍过。作为23种设计模式中最为常用的设计模式,单例模式并没有想象的那么简单。因为在设计单例的时候要考虑很多问题,比如线程安全问题、序列化对单例的...

⋅ 06/10 ⋅ 0

Java基础之反射(非常重要)

反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)) 一、反射的概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道...

Java-老刘 ⋅ 05/15 ⋅ 0

Java 8 停止维护,Java 9 难产,IDEA 2018 发布,还有……

祝大家五一劳动节快乐,工作顺利! 又到了总结上个月干货的时候了,这个月我们带来了各种Java技术干货,各种送书抽奖福利,各种面试题分享,各种最新动态资讯等。 - 5.1重磅活动 区块链免费送...

Java技术栈 ⋅ 04/30 ⋅ 0

Java反射改变Android属性

Java反射改变Android属性 在某些情况下,Android体系里面的某些对象没有对外提供针对某个属性或者类,方法公开的get或者set方法,但是项目需要对这些需要修改和调整。就需要使用Java的反射机...

zhangphil ⋅ 04/28 ⋅ 0

作为一个java程序员这些技能你都知道吗?

一、Java特点 1、 面向对象 尽管受到其前辈的影响,但Java没被设计成兼容其他语言源代码的程序。这允许Java开发组自由地从零开始。这样做的一个结果是,Java语言可以更直接、更易用、更实际的...

java高级架构牛人 ⋅ 05/23 ⋅ 0

有一到五年开发经验的JAVA程序员需要掌握的知识与技能!

JAVA是一种平台,也是一种程序设计语言,如何学好程序设计不仅仅适用于JAVA,对C++等其他程序设计语言也一样管用。有编程高手认为,JAVA也好C也好没什么分别,拿来就用。为什么他们能达到如此...

java高级架构牛人 ⋅ 06/02 ⋅ 0

Java反序列化漏洞的原理分析

  *本文原创作者:Moonlightos,本文属FreeBuf原创奖励计划,未经许可禁止转载   世界上有三件事最难:      把别人的钱装进自己的口袋里   把自己的想法装进别人的脑袋里   让自...

FreeBuf ⋅ 05/04 ⋅ 0

Java反射实践:从反射中理解class

写在前面 今天在需求评审的时候,遇到了挺有意思的要求。需求是什么样子就不说了。总之完成这个需求需要一个调用系统api的操作。然而这个api因为并不稳定的原因。被谷歌hide掉了。 这个时候我...

MDove ⋅ 04/11 ⋅ 0

Jenkins 教程(一)实现自动化打包及邮件通知

个人不喜欢装腔作势一堆专业术语放上去,让大多数人看不懂来提升逼格(所谓的专家),所以我简单的介绍jenkins是干啥的。本文使用jenkins,就是让它把git仓库里的东西取出来,然后在jenkins容器...

FantJ ⋅ 05/26 ⋅ 0

为什么我墙裂建议大家使用枚举来实现单例

我们知道,单例模式,一般有七种写法,那么这七种写法中,最好的是哪一种呢?为什么呢?本文就来抽丝剥茧一下。 哪种写单例的方式最好 在StakcOverflow中,有一个关于What is an efficient ...

冷_6986 ⋅ 06/13 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vim基础-编辑模式-命令模式

编辑模式:可以编辑修改文件。编辑模式下 按“esc”键返回一般模式。 按一次“Insert”键 (一般在键盘回格键右边)作用和“i”一样表示“插入”。按两次“Insert”键表示“替换”,作用为:...

ZHENG-JY ⋅ 9分钟前 ⋅ 0

MaxCompute读取分析OSS非结构化数据的实践经验总结

摘要: 本文背景 很多行业的信息系统中,例如金融行业的信息系统,相当多的数据交互工作是通过传统的文本文件进行交互的。此外,很多系统的业务日志和系统日志由于各种原因并没有进入ELK之类...

阿里云云栖社区 ⋅ 13分钟前 ⋅ 0

Linux操作系统有何优势?Linux学习

  当今世界流行的操作系统有3大类,Linux、Mac OS和Windows操作系统,Linux操作系统因其开源、免费、跨平台、良好的界面等特性,深受广大程序员们的青睐!   Linux操作系统被广泛的应用于...

老男孩Linux培训 ⋅ 15分钟前 ⋅ 0

Spring Cloud Spring Boot mybatis分布式微服务云架构 开发Web应用

静态资源访问 在我们开发Web应用的时候,需要引用大量的js、css、图片等静态资源。 默认配置 Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则: /static /pub...

itcloud ⋅ 19分钟前 ⋅ 0

6月19日任务 设置更改root密码、连接mysql、mysql常用命令

13.1 设置更改root密码 1. /usr/local/mysql/bin/mysql -uroot 设置环境变量 : export PATH=$PATH:/usr/local/mysql/bin/ 永久生效: vim /etc/profile 加入 export PATH=$PATH:/usr/local/m......

吕湘颖 ⋅ 21分钟前 ⋅ 0

MaxCompute读取分析OSS非结构化数据的实践经验总结

摘要: 本文背景 很多行业的信息系统中,例如金融行业的信息系统,相当多的数据交互工作是通过传统的文本文件进行交互的。此外,很多系统的业务日志和系统日志由于各种原因并没有进入ELK之类...

猫耳m ⋅ 22分钟前 ⋅ 0

Spring MVC controller,return重定向redirect:

@RequestMapping(value="/save",method=RequestMethod.POST)public String doSave(Course course) {log.debug("Info of Course");log.debug(ReflectionToStringBuilder.toStr......

颖伙虫 ⋅ 29分钟前 ⋅ 0

JavaSE——线程介绍

声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。 线程: 介绍:管线程叫多任务处理,首先你得知道...

凯哥学堂 ⋅ 33分钟前 ⋅ 0

ORM——使用spring jpa data实现逻辑删除

前言 在业务中是忌讳物理删除数据的,数据的这个对于一个IT公司可以说是最核心的资产,如果删除直接就物理删除,无疑是对核心资产的不重视,可能扯的比较远,本文最主要是想通过spring jpa ...

alexzhu592 ⋅ 39分钟前 ⋅ 0

CDN caching

Incapsula应用感知CDN使用智能分析和频率分析来动态缓存内容,并最大限度地提高效率。确保可直接从RAM获取最常访问的资源,而不依赖于较慢的访问机制。 1、 静态内容缓存 Incapsula缓存静态内...

上树的熊 ⋅ 42分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部