介绍
有一个有意思的小实验,有一盆水,在盆中放上镜子,放到阳光下,镜子反射到墙面的光变成了彩虹。通过反射,可以知道光是由七种颜色组成的。
java中的反射又叫内省或者自省,简单来说就是知道自己身上有什么东西。我们知道一个类有自己的继承体系,由构造器、方法和字段组成,jdk1.5之后加入了注解。所有这些东西都能通过反射拿到。
Java中跟反射有关的类有:Class、Constructor、Method、Filed。下面介绍这四个类中经常使用的方法。
一、Class对象
Class对象是由java虚拟机创建的,当我们的应用程序需要某个类的时候 ,java虚拟机会加载该类,并创建对用的Class对象。Java中的对象、接口、枚举、注解、基本类型及其数组类型,都会有对应的Class对象。
创建Class对象的方式:
- 类名.class方式,例如:Object.class;
- Class.forName()方法,例如:Class.forName(“com.xxx.User”);
- 对象通过继承自Object的getClass()方法,例如:user.getClass(),每个实例都保存了指向当前类的Class实例的指针
Class常用的方法
1、java.lang.Class#isAssignableFrom(Class)
方法名称直译:是可赋值的从(某个Class对象),表示的是当前Class对象所代表的的类或接口要么是指定的参数的Class对象所代表的类或者接口的子类,要么是相同的类型。作用是通过此方法判断后,可以进行安全的强制类型转换。
//老虎类
public class Tiger {
}
//用法,此方法可以接受所有的Class对象
@SuppressWarnings("unchecked")
public void process(Class<?> clazz) {
//进行类型判断
if (clazz.isAssignableFrom(Tiger.class)) {
processTigerClass((Class<Tiger>) clazz);
}
//......
}
//此方法处理Tiger及其子类的Class对象
public void processTigerClass(Class<? extends Tiger> clazz) {
//.......
}
2、java.lang.Class#asSubclass(Class)
方法名称直译:作为子类,意思把当前的Class实例,转成指定的参数Class实例的子类。主要的作用就是窄化类型,传递给方法参数。
例如:
//老虎类
public class Tiger {
}
//用法,此方法可以接受所有的Class对象
//@SuppressWarnings("unchecked")
public void process(Class<?> clazz) {
if (clazz.isAssignableFrom(Tiger.class)) {
processTigerClass(clazz.asSubclass(Tiger.class));//进行类型转换
//当然我们也可以不使用上述方式,参见下方,只不过会产生编译警告,未受检的类型转换
//在此方法上添加@SuppressWarnings("unchecked")即可
//processTigerClass((Class<Tiger>) clazz);
}
//......
}
//此方法处理Tiger及其子类的Class对象
public void processTigerClass(Class<? extends Tiger> clazz) {
//.......
}
3、java.lang.Class#cast(Object)
方法名直译:转换,把指定的对象转成当前Class对象所代表的类或着接口
例如:
//老虎类
public class Tiger {
}
//处理所有的对象
public void processObject(Object object) {
//获取老虎的Class对象
Class<Tiger> clazz = Tiger.class;
//进行类型判断
if (clazz.isAssignableFrom(object.getClass())) {
processTiger(clazz.cast(object));
}
//平时开发中用的是另一种形式,更简单,参见下方
/*
if (object instanceof Tiger){
processTiger((Tiger) object);
}
*/
//......
}
//只处理老虎对象
public void processTiger(Tiger tiger) {
//......
4、java.lang.Class#forName(String)
方法名直译:对于(什么)的名字,加载由字符串所指定全限定名类的Class对象。还有一个重载的方法,java.lang.Class#forName(String, boolean,ClassLoader)
解释下重载方法的参数:
- 类的全限定名
- 是否初始化,是否执行static代码块以及静态变量的赋值
- 类加载器,指定当前对象由那个类加载器加载
//user对象
public class User {
//由public static final 修饰的字段称为编译时期常量,在使用时不会触发类的加载
public static final String name= "zs";
static {
System.out.println("user");
}
private static int a = getA();
private static int getA() {
System.out.println("getA");
return 0;
}
}
//测试
public void testUser() throws Exception {
//加载User类时,不会触发静态代码块执行以及静态字段的赋值
Class<?> clazz = Class.forName("com.xxx.User", false, Thread.currentThread().getContextClassLoader());
//另外由public static final 修饰的字段称为编译时期常量,在使用时不会触发类的加载
System.out.println(User.name);
}
5、java.lang.Class#getAnnotation(Class)
方法名直译:获取(指定类型)注解
@MyAnnotation
public class Order{}
//假设获取到了Order的Class对象
public void process(Class<?> clazz) {
//获取类上的指定注解
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println(annotation.name());
}
还有几个跟注解有关的方法
- java.lang.Class#getAnnotations(),获取当前Class对象上全部的注解,当然只能获取到生命周期是RUNTIME的注解
- java.lang.Class#getAnnotationsByType(Class),获取指定注解类型的全部注解,指定的注解类型是可重复的
- java.lang.Class#isAnnotationPresent(Class),判断Class对象上是否出现指定的注解
6、java.lang.Class#getCanonicalName()
方法名直译:获取标准名称
用法参见代码:
//定义一个Category类以及内部类Helper
public class Category {
public static class Helper{}
}
public void testCanonicalName(){
//获取Category的标准名称
System.out.println(Category.class.getCanonicalName());//com.xxx.pojo.Category
//获取Category数组的标准名称
System.out.println(Category[].class.getCanonicalName());//com.xxx.pojo.Category[]
//获取Category内部类的Helper标准名称
System.out.println(Category.Helper.class.getCanonicalName());//com.xxx.pojo.Category.Helper
//获取Category内部类的Helper数组标准名称
System.out.println(Category.Helper[].class.getCanonicalName());//com.xxx.pojo.Category.Helper[]
//定义一个方法的内部类
class A{}
//获取A的标准名称
System.out.println(A.class.getCanonicalName());//null
//获取A的匿名类的标准名称
System.out.println(new A(){}.getClass().getCanonicalName());//null
}
7、java.lang.Class#getClasses()
方法名直译:获取类,获取当前Class对象所代表的类或接口的内部public的类或者接口
java.lang.Class#getDeclaredClasses()方法获取全部的内部类或者接口
//Category
public class Category {
public static class Helper{}
public class Other{}
public interface Another{}
}
//测试
public void testInnerClass(){
for (Class<?> clazz : Category.class.getClasses()) {
System.out.println("clazz = " + clazz);
}
}
//输出结果
/*
clazz = interface com.xxx.pojo.Category$Another
clazz = class com.xxx.pojo.Category$Other
clazz = class com.xxx.pojo.Category$Helper
*/
8、java.lang.Class#getComponentType()
方法名直译:获取组件的类型,获取数组组件类型,如果不是数组返回null
public void testComponentType() {
Class<?> clazz = Category[].class;
System.out.println(clazz.getComponentType());//class com.xxx.pojo.Category
}
9、获取构造器
第一、获取public修饰的构造器
- java.lang.Class#getConstructor(Class<?> …) 获取指定参数类型的public修饰的构造器
- java.lang.Class#getConstructors() 获取全部的public修饰的构造器
第二、获取任意修饰符的构造器 - java.lang.Class#getDeclaredConstructor(Class<?> …) 获取指定参数类型的构造器
- java.lang.Class#getDeclaredConstructors() 获取全部的构造器
//Category
public class Category {
private String name;
private int age;
public Category(){}
public Category(String name){
this.name = name;
}
private Category(int age){
this.age = age;
}
}
public void testConstructor() throws Exception {
Class<?> clazz = Category.class;
//public修饰的无参构造器
Constructor<?> nonArgPublic = clazz.getConstructor();
//public修饰的带有String参数的构造器
Constructor<?> argPublic = clazz.getConstructor(String.class);
//public修饰的全部构造器
Constructor<?>[] allPublic = clazz.getConstructors();
//private修饰的带有int参数的构造器
Constructor<?> argPrivate = clazz.getDeclaredConstructor(int.class);
//获取全部构造器
Constructor<?>[] all = clazz.getDeclaredConstructors();
}
10、获取方法或者字段
参见第九条,只是在传递参数时需要多指定一个方法名或字段名参数
11、java.lang.Class#getDeclaringClass()
方法名直译:获取声明的类,内部类的Class对象获取外部类的Class对象
//Category
public class Category {
public static class Helper{}
public class Other{}
public interface Another{}
}
public void testDeclaringClass(){
Class<?> clazz = Category.Helper.class;
System.out.println(clazz.getDeclaringClass());//class com.xxx.pojo.Category
}
12、java.lang.Class#getEnumConstants()
方法名直译:获取枚举常量,如果不是枚举类则返回null
与枚举相关的还有一个方法: java.lang.Class#isEnum()判断当前类是否是枚举类
public enum Color {
RED,
ORANGE,
YELLOW,
GREEN,
CYAN,
BLUE,
PURPLE
}
public void testEnum(){
Class<Color> clazz = Color.class;
for (Color enumConstant : clazz.getEnumConstants()) {
System.out.println("enumConstant = " + enumConstant);
}
}
//输出
/*
enumConstant = RED
enumConstant = ORANGE
enumConstant = YELLOW
enumConstant = GREEN
enumConstant = CYAN
enumConstant = BLUE
enumConstant = PURPLE
*/
13、java.lang.Class#getGenericInterfaces()
方法名直译:获取泛型接口,可以用于泛型解析
还有一个方法与之类似:java.lang.Class#getGenericSuperclass()用于获取泛型超类
//泛型接口
public interface Creature<T> {
}
//实现类
public class Tiger<T> implements Creature<T> {
}
//测试
public void testGenericInterfaces() {
Class<?> clazz = Tiger.class;
for (Type genericInterface : clazz.getGenericInterfaces()) {
//class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
System.out.println(genericInterface.getClass());
//genericInterface = com.xxx.interfaces.Creature<T>
System.out.println("genericInterface = " + genericInterface);
}
}
14、java.lang.Class#getInterfaces()
获取所有接口
与之类似的还有java.lang.Class#getSuperclass()获取超类
15、java.lang.Class#getModifiers()
有一个辅助类,可以判断修饰符类型:java.lang.reflect.Modifier类,有很多以静态方法用于判断修饰符类型,具体参见Modifier类,比较简单就不一一列举了。
16、java.lang.Class#getSimpleName()
方法名直译:获取简单的名称
public void testSimpleName(){
Class<?> clazz = User.class;
System.out.println(clazz.getSimpleName());//User
}
17、几个用于判断的方法
1、java.lang.Class#isArray() 判断此Class对象是否是数组类型
public void testIsArray(){
Class<?> userClass = User.class;
Class<?> userArrayClass = User[].class;
System.out.println(userClass.isArray());//false
System.out.println(userArrayClass.isArray());//true
}
2、java.lang.Class#isInstance(Object)
方法名直译:(指定对象)是(当前类)实例
public void testIsInstance(Class<?> clazz) {
User user = new User();
if (clazz.isInstance(user)) {
System.out.println(true);
}
}
3、java.lang.Class#isInterface() 判断当前Class对象是否代表接口
4、java.lang.Class#isMemberClass() 判断当前Class对象所代表的类或者接口是否是另外一个接口或类的成员(即内部类或接口)
5、java.lang.Class#isPrimitive()判断是否是原生类型,int.class等等
18、java.lang.Class#newInstance()
使用默认构造器创建对象。
//Class对象需要有无参构造器
//不建议直接使用,除非有约定,提供无参构造器
public void testNewInstance(Class<?> clazz) throws Exception {
Object object = clazz.newInstance();
}
Class对象的方法就介绍这么多,应该是包括了基本常用的方法
Constructor、Method、Field
这三个类是很类似的,其中的方法就不一一介绍,能知道Class中常用的方法,自然就可以理解这三个类中很多的方法,下面主要介绍些Class对象中没有的方法
二、Method
先介绍Method,因为构造器是特殊的方法,与Method有很多共同之处,带有generic关键字的都是跟泛型有关就不解释了,
可参考:https://blog.youkuaiyun.com/weixin_45341408/article/details/109804201
1、java.lang.reflect.Method#getDeclaredAnnotations()获取声明在方法上的全部注解
2、java.lang.reflect.Method#getDefaultValue()返回注解中的默认值,不是注解类型则返回null
3、java.lang.reflect.Method#getExceptionTypes()获取方法声明时抛出的异常类型(跟在throws后面的)
4、java.lang.reflect.Method#getParameterAnnotations()获取方法参数上的注解类型
返回值是一个二维数组,第一维对应的方法参数的位置,第二维就是方法参数所对应的全部注解。
5、java.lang.reflect.Method#getParameterCount()获取方法参数个数
6、java.lang.reflect.Method#getParameterTypes()获取方法参数类型
7、java.lang.reflect.Method#getReturnType()获取方法的返回值类型
8、java.lang.reflect.Method#getTypeParameters()获取方法上声明的泛型
例如:public <T,E> void say(){} 则返回值是T和E的TypeVariable对象
9、java.lang.reflect.Method#isDefault()是否是默认方法,jdk1.8可以在接口中声明默认方法
public interface Animal {
default void say(){}
}
10、java.lang.reflect.Method#isVarArgs()方法参数是否是可变参数
11、java.lang.reflect.Method#invoke(Object,Object…)执行方法
第一个参数是目标对象(是静态方法时可以为null),第二个参数是方法参数
12、java.lang.reflect.AccessibleObject#setAccessible(boolean)这个是父类中的方法
当Method是不是public是,调用此方法指定参数为true,既可以进行方法的反射调用
13、java.lang.reflect.Method#isBridge()是否是桥接方法
桥接方法需要解释下:
如何才会出现桥接方法?
重写(实现)父类(父接口)的方法时,返回值是父类或父接口返回值的子类型,也就是协变返回类型
有以下两种情况:
1、泛型,泛型会被擦除为Object类型,所以重写泛型方法,当我们指定泛型类型是Object子类时,就会出现桥接方法
2、方法的重写,指定返回值类型为父类或父接口返回值的子类型
//带有泛型的接口
public interface Creature<T> {
//第一种情况
public T get();
//第二种情况
public Object getObject();
}
//实现类
public class Sparrow implements Creature<String> {
//第一种情况
@Override
public String get() {
return "OK";
}
//第二种情况
@Override
public User getObject() {
return new User();
}
}
//下面是通过javap反编译的Sparrow的字节码文件
public class Sparrow implements Creature<java.lang.String> {
public Sparrow();
public java.lang.String get();
public User getObject();
//桥接方法
public java.lang.Object getObject();
//桥接方法
public java.lang.Object get();
}
//测试
public void testBridge(){
for (Method method : Sparrow.class.getDeclaredMethods()) {
System.out.print("method name : " +method.getName());
System.out.println(" isBridge : "+method.isBridge());
}
}
//输出是
/*
method name : get isBridge : true
method name : get isBridge : false
method name : getObject isBridge : true
method name : getObject isBridge : false
*/
看到反编译的代码很奇怪,在java规范中是不允许出现这种情况的,因为方法的重载是不考虑返回值的。因为桥接方法是由编译器生成的,是安全的,而且桥接方法会调用的都是对应的方法,例如:public java.lang.Object getObject()桥接方法会调用public User getObject()方法来执行方法体。
桥接方法之所以出现,是为了能够有协变返回类型,用来支持泛型的出现。
三、Constructor
介绍下Method类中没有的方法,这就是构造器和普通方法的区别,能够创建对象:
java.lang.reflect.Constructor#newInstance(Object …),根据传入的参数创建对应的实例
与获取Constructor是指定的参数类型要一致。
四、Filed
Filed中有很多getXXX(Object)和setXXX(Object,XXX)这个非常简单不介绍了,还有一些和Constructor和Method对象一样的方法也不介绍了。
java.lang.reflect.Field#isEnumConstant() 判断字段是否是枚举常量
总结
只是介绍了Java中反射用到的对象及常用法法,非常的基础,没有涉及业务场景,反射实在是非常强大,对于反射只有一句话,学好它,java的各种框架就自动学会了一半。