简介
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. * 说明: */@MyAnnotationpublic 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是否是注解类:falseStudent是否包含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
类,Retrofit
的Util
类等,来跟我学习一下把。
泛型
首先说说获取类的泛型获取,先介绍一个接口Type
,它是java中所有类型的通用接口,类型包括原始类型(class),参数化类型(ParameterizedType),数组类型(GenericArrayType),类型变量(TypeVariable)和基本数据类型。内部实现了一个方法getTypeName()
,用于返回类型参数信息,基于它向外扩展的接口有:
ParameterizedType
表示一个参数化类型,说简单点就是带有参数类型的类型,如Collection ,如果带有了类型,如Collection<String>,那么就是说String将Collection参数化GenericArrayType
表示一个参数化类型或类型变量的数组类型,简单说就是泛型A<T>[]或泛型数组T[]TypeVariable
所有类型变量的父接口,也就是我们定义抽象类中的那种K,E等泛型变量,可以泛指任何类WildcardType
表示一个通配符表达,例如?,? extends IntegerType
可以通过class
的getGenericInterfaces
或getGenericSuperclass()
方法来获取
这么说或许还有点抽象,ParameterizedType
与TypeVariable
的概念或许还拎不太清。下面通过示例来加深理解
先来说说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
类型。
接下来的GenericArrayType
和WildcardType
就比较好理解了,将以上示例中的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 T
,T
,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类,我们就可以获取到它的父类,接口,方法,字段,以及泛型,从而做一些解耦封装,提高类的解耦性。或者在不修改源码的情况下执行或修改源码中的方法,字段,从而达到理想中的效果。小伙伴们快去练习一下把。
作者:我是黄教主啊
链接:https://www.jianshu.com/p/3607832ed0ea