玩转Class之Class的各种骚操作(反射封装必看)

简介

Class类表示正在运行的Java应用程序中的类和接口,枚举和基本数据类型,我们可以从中获取到类的一切相关信息,包括字段,方法,名称,父类,接口等

常用方法介绍

名称获取

此类方法用于获取类的名称信息
getName()方法

  • 如果是一个实体类,则会返回完整包名路径名称,
    例如位于com.hj.testclass包下的student类,则会返回com.hj.testclass.student
  • 如果是一个数组类型,则返回内部嵌套深度的一个或多个"["字符,后面拼接上基本数据类型的二进制名称,二进制名称表如下:
Element Type Encoding
boolean Z
byte B
char C
class or interface Lclassname
double D
float F
int I
long J
short S

示例:

(new long[1][2][3]).getClass().getName()
输出:
[[[J
  • 如果是基本数据类型,则会返回数据类型的关键字
byte.class.getName()
输出:
byte

getSimpleName()方法
返回源代码中给出的基础类的简单名称。 如果基础类是匿名的,则返回一个空字符串。

Student student = new Student();
Class mClass = student.getClass();
log("getSimpleName:"+mClass.getSimpleName());
输出:
Student

getPackage()方法
返回包名信息

注解相关

可获取此类是否是注解,是否包含某注解并获取到其对象,获取全部注解

方法 作用
getAnnotation(Class<A> annotationClass) 获取传入的注解对象,如果不存在,则返回null
getAnnotations() 返回此类上的所有注解
getAnnotationsByType(Class<A> annotationClass) 返回传入的注解对象数组,与getAnnotation()的区别是检测传入的注解是否是重复元素
isAnnotation() 判断这个类是否是一个注解类
isAnnotationPresent(Class<? extends Annotation> annotationClass) 是否包含传入的注解类,效果与getAnnotation()!=null相同

举个栗子:
新建一个注解对象:

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

新建一个Student并加入@MyAnnotation注解:

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */
@MyAnnotation
public class Student{

    public static void main(String[] args) {
        Student student = new Student();
        Class studentClass = student.getClass();
        boolean isAnnotation = studentClass.isAnnotation();
        log(studentClass.getSimpleName()+"是否是注解类:"+isAnnotation);
        boolean isContainAnnotation = studentClass.isAnnotationPresent(MyAnnotation.class);
        log(studentClass.getSimpleName()+"是否包含MyAnnotation注解类:"+isContainAnnotation);
        Annotation annotation = studentClass.getAnnotation(MyAnnotation.class);
        Annotation[] annotations = studentClass.getAnnotations();
        if (annotation != null) {
            log("获取指定的MyAnnotation类:"+annotation.toString());
        }
        if (annotations.length > 0){
            log("获取注解集合中的第一个元素:"+annotations[0].toString());
        }
    }

    private static void log(String value) {
        System.out.print(value);
    }
}

打印:

Student是否是注解类:false
Student是否包含MyAnnotation注解类:true
获取指定的MyAnnotation类:@jie.com.imageoptimize.mclass.MyAnnotation()
获取注解集合中的第一个元素:@jie.com.imageoptimize.mclass.MyAnnotation()

构造方法相关

在介绍构造方法之前,先介绍一个类Constructor,它的作用是提供一个类的单个构造方法的信息访问,如果一个类有两个构造方法,那么这个类就会对应有两个Constructor类,它可以使用newInstance方法来进行类的构造方法实现并进行扩展,但如果发生缩小转换则会抛出IllegalArgumentException异常,比如这个类有两个构造参数却只传入一个,就会抛异常。
获取Constructor信息的方法有:

方法 作用
getConstructors() 返回这个类的公共构造函数的 Constructor对象的数组
getConstructor(Class<?>..parameterTypes) 传入一个指定的参数类型来获取特定的公共构造方法类
getDeclaredConstructors() 与getConstructors的区别是返回所有的构造方法数组,不限于public protected private
getDeclaredConstructor(Class<?>..parameterTypes) 与getConstructor的区别是会返回所有类型的构造方法

接下来介绍一下Constructor类的newInstance方法,这个方法说白了就是执行构造函数的,你传入一个当前构造函数的类型的值进去,那么就会执行这个类的构造方法。
下面举一个反射调用构造方法的栗子:
新建一个Student类,分别添加两个私有构造方法,一个公有构造方法,并添加两个参数,在构造方法处执行打印逻辑,代码如下:

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */

public class Student {

    private String name;
    private int age;

    private Student() {

    }

    public Student(String name) {
        this.name = name;
        log("公有构造方法执行了,打印传入的名称为:"+name);
    }

    private Student(int age){
        this.age = age;
        log("私有构造方法执行了,打印传入的年龄为:"+age);
    }

    public static void main(String[] args) {
        Student student = new Student();
        Class mClass = student.getClass();
        Constructor[] constructors = mClass.getConstructors();
        log("获取" + mClass.getSimpleName() + "的公共构造方法数量为:" + constructors.length);
        try {
            Constructor constructor = mClass.getConstructor(String.class);

            constructor.newInstance("张三");

        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }


        Constructor[] declaredConstructors = mClass.getDeclaredConstructors();
        log("获取" + mClass.getSimpleName() + "的所有构造方法数量为:" + declaredConstructors.length);
        try {
            Constructor constructor = mClass.getDeclaredConstructor(int.class);

            constructor.newInstance(18);

        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private static void log(String value) {
        System.out.print(value);
    }
}

打印结果如下:

获取Student的公共构造方法数量为:1
公有构造方法执行了,打印传入的名称为:张三
获取Student的所有构造方法数量为:3
私有构造方法执行了,打印传入的年龄为:18

怎么样,反射调用类的构造方法技能get到了吗

字段相关

此类型方法是用的最多的一种,希望小伙伴们可以熟练掌握
在介绍字段方法之前,先介绍一个类Field,它用于保存,修改字段的信息,甚至可以修改字段的访问权限,常用的方法如下:

方法 作用
getName() 获取字段名称
get() 传入需要获取的值的类的对象,获取该字段的值,返回object类型,使用的时候需要做类型判断
getBoolean(),getInt()... 获取指定类型的字段值
set(),setBoolean()... 将指定的类的指定值设置为新值
isAccessible() 判断此字段是否有访问权限
setAccessible() 设置字段的权限,为true代表可以访问

接下来再来看看如何使用Class来获取字段信息:

方法 作用
getFields() 获取所有的公共字段
getField() 传入字段的名称,返回公共字段对象
getDeclaredFields() 获取所有字段,返回一个Field数组
getDeclaredField 传入字段的名称,返回字段对象,无访问限制

下面举个获取字段名称和内容的例子,并分享一个常用的套路写法,一般的都可以按照这个套路来写:

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */

public class Student {

    public String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {
        Student student = new Student("张三", 18);
        setAllComponentsName(student);
    }
   
    private static void setAllComponentsName(Object f) {
        Field[] fields = f.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 对于每个属性,获取属性名
            String varName = field.getName();
            try {
                // 获取原来的访问控制权限
                boolean accessFlag = field.isAccessible();
                // 修改访问控制权限
                field.setAccessible(true);
                // 获取在对象f中属性fields[i]对应的对象中的变量
                Object o = field.get(f);
                if (!"".equals(varName)) {
                    //这里可以处理相关逻辑
                    if (o != null) {
                        if (o instanceof String) {
                            String value = (String) o;
                            log("String类型字段:" + value);
                        } else if (o instanceof Integer) {
                            int value = (int) o;
                            log("int类型字段:" + value);
                        }
                    }
                }
                // 恢复访问控制权限
                field.setAccessible(accessFlag);
            } catch (IllegalArgumentException | IllegalAccessException ex) {
                ex.printStackTrace();
            }
        }
    }

    private static void log(String value) {
        System.out.print(value);
    }
}

打印结果:

String类型字段:张三
int类型字段:18

一般都是先将权限修改为true,再去读取字段内容,随后进行逻辑处理,最后记得将权限改回来.

方法相关

此类型方法也是用的比较多的一种,是反射调用方法的关键
class中关于方法的封装都是给Method类来操作的,它可以获取,修改,执行类的方法,核心方法就是invoke(),作用是执行方法,第一个参数是需要执行的方法的类对象,随后是需要执行的方法参数值。
获取Method对象有四个方法:

方法 作用
getMethods() 获取类的所有公共方法
getMethod() 获取指定公共方法,传入一个方法的名称与参数的类型
getDeclaredMethods() 获取类的所有方法
getDeclaredMethod() 获取类的指定方法,传入参数同getMethod()

举个例子,将本来为张三的名称修改为李四:

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */

public class Student {

    public String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void setName(String name){
        this.name = name;
        log("设置的名称为:"+name);
    }

    public static void main(String[] args) {
        Student student = new Student("张三", 18);
        Class mClass = student.getClass();
        try {
            Method method = mClass.getDeclaredMethod("setName",String.class);
            method.invoke(student,"李四");
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }


    private static void log(String value) {
        System.out.print(value);
    }
}

打印结果:

设置的名称为:李四

在项目中使用,可以参考上面的Field类,其实都是差不多的,一通百通。

抽象,继承,接口,泛型相关

此类方法虽然不常用到,但在某些情况下能发挥意想不到的作用,在许多框架的源码中也能看见它们的身影,如Gson$Gson$Types类,RetrofitUtil类等,来跟我学习一下把。

泛型
首先说说获取类的泛型获取,先介绍一个接口Type,它是java中所有类型的通用接口,类型包括原始类型(class),参数化类型(ParameterizedType),数组类型(GenericArrayType),类型变量(TypeVariable)和基本数据类型。内部实现了一个方法getTypeName(),用于返回类型参数信息,基于它向外扩展的接口有:

  • ParameterizedType
    表示一个参数化类型,说简单点就是带有参数类型的类型,如Collection ,如果带有了类型,如Collection<String>,那么就是说String将Collection参数化
  • GenericArrayType
    表示一个参数化类型或类型变量的数组类型,简单说就是泛型A<T>[]或泛型数组T[]
  • TypeVariable
    所有类型变量的父接口,也就是我们定义抽象类中的那种K,E等泛型变量,可以泛指任何类
  • WildcardType
    表示一个通配符表达,例如?,? extends Integer
    Type可以通过classgetGenericInterfacesgetGenericSuperclass()方法来获取
    这么说或许还有点抽象,ParameterizedTypeTypeVariable的概念或许还拎不太清。下面通过示例来加深理解
    先来说说ParameterizedType
    举个例子,先声明一个带泛型的超类Person
/**
 * Created by hj on 2019/1/10.
 * 说明:
 */

public class Person<T> {

    public T feature;

}

随后定义一个实体类Feature

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */
public class Feature {

}

最后申明一个Student并继承Person,将Feature作为T传入:

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */

public class Student extends Person<Feature> {

    public static void main(String[] args) {
        Student student = new Student();
        Class mClass = student.getClass();
        Type type = mClass.getGenericSuperclass();
        //这里获取的type为Person<Feature>,所以属于ParameterizedType
        if (type instanceof ParameterizedType) {
            //父类的泛型可能有多个,这里只写了一个,所以取第一个就行了,这里是取Person<Feature>中的Feature
            Class tClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0];
            log(tClass.getSimpleName());
        }
    }


    private static void log(String value) {
        System.out.print(value);
    }
}

因为Class类也实现了Type接口,所以是可以强制转化的
打印结果如下:

Feature

可以看到在此示例中,只要是Person<>中传入的参数是一个参数类型,不管是实体类String都行,只要是参数而没有泛指意义,那么它就属于ParameterizedType类型
接下来将代码修改一下,将Feature改为T,修改获取逻辑:

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */

public class Student<T> extends Person<T> {

    public static void main(String[] args) {
        Student student = new Student();
        Class mClass = student.getClass();
        Type type = mClass.getGenericSuperclass();
       //此时这里的type为Person<T>,而不是T,所以依旧属于ParameterizedType
        if (type instanceof ParameterizedType) {
           //这里是从Person<T>中获取T
            Type childType = ((ParameterizedType)type).getActualTypeArguments()[0];
            if (childType instanceof TypeVariable){
                log(((TypeVariable) childType).getName());
            }
        }
    }


    private static void log(String value) {
        System.out.print(value);
    }
}

输出名称为

T

不知道小伙伴们有没有理解了,当获取的类型是泛指的,如T等,那么就是TypeVariable类型,当类型是参数类型的,例如由子类传入的,具体化的,那么就是ParameterizedType类型。
接下来的GenericArrayTypeWildcardType就比较好理解了,将以上示例中的Person<T>改为Person<T[]>,将结果输出,代码如下:

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */

public class Student<T> extends Person<T[]> {

    public static void main(String[] args) {
        Student student = new Student();
        Class mClass = student.getClass();
        Type type = mClass.getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            //这里获取到的类型为:T[],是一个数组类型,所以是GenericArrayType
            Type childType = ((ParameterizedType)type).getActualTypeArguments()[0];
            if (childType instanceof GenericArrayType){
                log(childType.toString());
            }
        }
    }


    private static void log(String value) {
        System.out.print(value);
    }
}

输出名称为:

T[]

WildcardType就不演示了,将T改为带的类型就可以了,如果封装类型实在太复杂,教大家一个技巧,打个断点,啥类型都帮你显示出来了。

断点识别参数类型.png

一看就知道是ParameterizedType类型了,哈哈哈,溜不溜。

这里给小伙伴一个作业:
List<? extends T>[]中的List<? extends T>,? extends TT ,List<? extends T>[]分别代表什么类型呢?

继承
获取父级类使用Class.getSuperclass()方法即可获取父类的class

接口
获取接口也比较简单,一个接口实际上也是一个Class类,通过getInterfaces()获取到一个Class数组,而接口里声明的方法可以通过获取Method的方式来获取,
举个栗子,先创建一个MyInterface接口

/**
 * Created by hj on 2019/1/11.
 * 说明:
 */
public interface MyInterface {
    void getName();
}

Student类里实现并获取:

/**
 * Created by hj on 2019/1/10.
 * 说明:
 */

public class Student implements MyInterface{

    public static void main(String[] args) {
        Student student = new Student();
        Class mClass = student.getClass();
        Class interfaces = mClass.getInterfaces()[0];
        try {
            Method methods = interfaces.getMethod("getName",null);
            log(methods.getName());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    private static void log(String value) {
        System.out.print(value);
    }

    @Override
    public void getName() {

    }
}

打印出方法名为:

getName

总结

只要拿到Class类,我们就可以获取到它的父类,接口,方法,字段,以及泛型,从而做一些解耦封装,提高类的解耦性。或者在不修改源码的情况下执行或修改源码中的方法,字段,从而达到理想中的效果。小伙伴们快去练习一下把。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容