2.java反射

1,概述

​ JAVA 反射机制是在运行状态中,对于任意一个类,都能够找到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。

​ JDK中关于反射的相关类型都在 java.lang.reflect 包下,并不需要额外的第三⽅包来完成反射的⼯
作.

1.1 什么是反射

​ 反射就是可以在运行时加载类信息,并可以创建其对象访问其属性和方法。这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。

1.2 反射的功能

​ Java 反射机制主要提供以下功能:

​ 在运行时判断任意一个对象所属的类;

​ 在运行时构造任意一个类的对象;

​ 在运行时判断任意一个类所具有的成员变量和方法;

​ 在运行时调用任意一个对象的方法;

​ 生成动态代理。

2,Class对象

​ Class对象就是⼀个类被加载到jvm中后的运⾏时表现.⽽反射的⾸要任务就是得到Class对象.得到Class对象的⽅法如下

  • Class.forName(“类的全称”)
  • 类加载器获取
  • 类的class字段
  • 对象的getClass⽅法
2.1 Class.forName()

​ 这种⽅法适合只知道类的字符串表示,也就是全称的情况.如果类还没有加载它还会加载此类,如果已经加载了就会返回已加载的class对象给你,⽐如:

Class personCls = Class.forName("com.Person");
2.2 类的class字段

​ 这种⽅式是此类已经预先知道的情况下就⾮常合适,⽐如下⾯的代码

Class personCls = Person.class;
Class integerCls = Integer.class;
2.2.1 基本类型的class对象

​ 对于8个基本类型想获取其class对象信息除了可以⽤class字段的⽅式以外还可以利⽤其对应包装类型的TYPE字段来获取,⽐如:

Class intCls = int.class;
Class intCls = Integer.TYPE;
2.3 对象的getClass方法

​ 这种⽅式适合于已经有此类对象的情况下来获取类的class对象信息,⽐如下⾯的代码:

Person p = new Person();
Class personCls = p.getClass();
2.4 Void类对象

​ java⽅法的返回类型中有⼀个特殊的值就是void,其⽤java.lang.Void类来代表.所以获取此特殊的Class对象就⽤Void的class字段或TYPE字段来实现

Class<Void> clazz = Void.class;
Class<Void> clazz = Void.TYPE;
3,Class对象的基本操作

​ 有了Class对象之后,我们就可以利⽤它来获取各种各样的信息,主要可以获取的信息有如下⼀些:

  • 获取类的名字
  • 类的⽗类
  • 类的修饰符
  • 类实现的所有接⼝
  • 类的构造函数
  • 类的所有字段
  • 类的所有⽅法
3.1 获取类的名字

  • getSimpleName()
  • getName()
  • getCanonicalName()
  • getTypeName()

​ 类的名字分为简称和全称,比如下面的累其简称为Person,全称为com.Person

package com;
public class Person{}

​ 通过反射获取类的名称方法如下:

Class<T> clazz = ...;
String simpleName = clazz.getSimpleName();
String fullQualifiedName = clazz.getName();
3.2 获取类的修饰符

  • getModifiers()

​ 通过Class对象的 getModifiers ⽅法获取类的修饰符,此⽅法返回的是⼀个整数,然后依赖 Modifier 类的⼀系
列⽅法来分析 getModifiers ⽅法返回整数的含义.

int modifier = clazz.getModifiers();
boolean isPublic = Modifier.isPublic(modifier);
boolean isAbs = Modifier.isAbstract(modifier);
3.3 获取包的信息

  • getPackage()

​ 可以通过Class对象对的 getPackage() ⽅法获取类的包信息,此⽅法返回的是Package类型,通过此Package类型就可以得到包的名字,⽐如:

Package pkg = clazz.getPackage();
String pkgName = pkg.getName();
3.4 获取父类信息

  • getSuperclass()
  • getGenericSuperclass()
  • getAnnotatedSuperclass()

​ 获取⽗类信息主要是靠 getSuperClass() ⽅法实现,如果当前的Class对象代表的是Object类型,接⼝类型,void类型,基本类型,那么此⽅法返回null值

Class<?> superClazz = clazz.getSuperclass();
String superClassName = superClazz.getSimpleName();
3.5 获取实现的接口

  • getInterfaces()
  • getGenericInterfaces()
  • getAnnotatedInterfaces()

​ 可以通过 getInterfaces ⽅法获取实现或继承的接⼝信息.如果当前的Class对象代表的是⼀个类,那么此⽅法得到是此类声明实现的所有接⼝信息,不包含其⽗类实现的接⼝信息.返回的数组中按声明的顺序排序.如果没有实现接⼝就返回⻓度为0的数组.

​ 如果当前的Class对象代表的是⼀个接⼝,那么此⽅法返回的是此接⼝extends的所有接⼝信息,返回的数组中按照声明的接⼝顺序排序.如果没有继承任何接⼝,返回的数组是⼀个⻓度为0的数组.

​ 如果当前的Class对象代表的是void或者基本类型,此⽅法返回⻓度为0的数组.

​ 如果当前的Class对象代表的是数组类型,那么返回的是 CloneableSerializable

Class[] personInterfaces = clazz.getInterfaces();
3.6 获取构造函数

  • getConstructors()
  • getConstructor()
  • getDeclaredConstructor()
  • getEnclosingConstructor()
  • getDeclaredConstructors()

​ 通过 getConstructors ⽅法可以获取类的所有构造函数.⽐如下⾯的⽅法:

Constructor<?>[] constructors = clazz.getConstructors();
3.7 获取字段

  • getField()
  • getFields()
  • getDeclaredFields()
  • getDeclaredField()

​ 字段分为类⾃⼰声明(Declare)的和从⽗类型继承过来的字段,想得到所有的字段(不区分是继承的还是⾃⼰声明的)就通过 getFields ⽅法,通过 getDeclaredFields() ⽅法可以获取此Class对象代表的类声明的所有字段,不包括继承过来的字段.⽐如下⾯的代码

Field[] fields = clazz.getDeclaredFields();

​ 上⾯的⽅法可以得到任意修饰符的字段,静态与实例字段可以得到,public,private等访问修饰符的字段也可以得到.如果想得到某⼀个具体名字的字段可以通过 getDeclaredFields(String name) ⽅法获取

Field f = clazz.getDeclaredFields("字段名");

如果Class对象代表的类型没有字段或者代表的是数组类型,基本数据类型或者void类型会返回⼀个⻓度为0的数组.

返回数组中的元素并没有进⾏排序,并且并没有特定的顺序,⽐如按照声明的顺序,所以你的代码不能依赖
反射中得到的字段的顺序来编写逻辑.

⽽ getFields ⽅法会返回⾃身和继承过来的所有 public 字段.并不包含其它修饰符的字段

3.8 获取方法

  • getMethod()
  • getMethods()
  • getEnclosingMethod()
  • getDeclaredMethods()
  • getDeclaredMethod()

​ ⽅法也分为类⾃⼰声明(Declare)的和从⽗类型继承过来的.可以通过 getDeclaredMethods() ⽅法获取此Class对象代表的类声明的所有⽅法,通 过 getMethods() ⽅法获取所有的⽅法.⽐如下⾯的代码

Method[] methods = clazz.getDeclaredMethods();

​ 如果想获得特定的⽅法,就需要传递⽅法名与参数类型,因为⽅法有重载.⽐如下⾯的代码表示取得只有⼀个String类型参数的⽅法doSth.

Method method = clazz.getDeclaredMethods("doSth",String.class)

​ getDeclaredMethods的第⼆个参数是可变⻓度的,因为⽅法的参数可以有多个.

​ 如果⼀个类只有静态代码块,那么getDeclaredMethods返回的数组中不包括这个静态代码块.

​ 如果Class对象代表的类型没有⽅法或者代表⼀个数组,基本类型,void类型,那么返回的是⻓度为0的数组.

​ 返回数组中的元素并没有进⾏排序,并且并没有特定的顺序,⽐如按照声明的顺序,所以你的代码不能依赖反射中得到的⽅法的顺序来编写逻辑.

​ ⽽getMethods⽅法会返回⾃身和继承过来的所有 public ⽅法.并不包含其它修饰符的⽅法.

3.9 实例化对象

  • newInstance()

​ 可以通过Class对象直接实例化⼀个类的对象出来.⽐如下⾯的代码

Class<Person> clazz = ...;
Person p = clazz.newInstance();

​ 这个⽅法必须要求类有⼀个 nullary constructor ,也就是⽆参的构造函数.默认构造就属于 nullary
constructor .如果没有这样的构造函数,那么调⽤ newInstance() ⽅法时会抛出异常.

​ 如果没有⽆参的构造函数,就不能这样实例化对象了,只能靠反射先获取Constructor对象,然后通过
Constructor来创建对象.

默认构造函数与⽆参构造函数这2个术语有⼀点点的区别.默认构造函数⽐较强调由编译器⽣成
(c++领域不强调编译器⽣成,⾃⼰写的也算默认构造函数)以及是public修饰符的,⽆参构造函数不强
调是public的,也不强调是编译器⽣成

https://en.wikipedia.org/wiki/Default_constructor

https://en.wikipedia.org/wiki/Nullary_constructor

3.10 isInstance()

  • isInstance()

​ Class对象的 isInstance ⽅法的作⽤等价于 instanceof 操作符.⽐如下⾯的代码会返回true

Person person = new Person();
Class<Person> clazz = Person.class;
System.out.println(clazz.isInstance(person));
4,构造函数

​ 由于⼀个类可以有多个构造函数,所以反射API提供了 getConstructors() ⽅法得到所有的构造函数,⽤
getConstructor(Class… parameterTypes) 来得到某⼀个具体的构造函数.⽐如下⾯的代码

Constructor<?>[] constructors = clazz.getConstructors();
Constructor<?> cons1 = clazz.getConstructor();
Constructor<?> cons2 = birdClass.getConstructor(String.class);

​ 如果没有对应参数类型的构造函数会抛出 NoSuchMethodException 异常.

​ 有了Constructor对象之后,我们就可以调⽤调⽤其 newInstance ⽅法来实例化对象,其作⽤等价于new⼀个
类的对象时指定调⽤这个构造函数.

cons1.newInstance();
cons2.newInsance("hello");
5,方法

​ 获得了Method对象后,我们可以获取⽅法的名字,可访问性以及调⽤⽅法等操作.⽐如下⾯的代码获取了⽅

法的名字,可访问性

Method m = ...;
boolean access = m.isAccessible();//获取可访问性
String name = m.getName();//得到⽅法名字
int parameterCount = m.getParameterCount();//得到⽅法参数个数
Parameter[] ps = m.getParameters();//获取所有参数信息
5.1 调用方法

​ 反射的⽅式调⽤⽅法主要是靠Method对象的 invoke(Object obj,Object…args) ⽅法来完成.其第⼀个参数是此⽅法所属类的对象,如果这个⽅法是个静态⽅法,这个参数可以传递null值,第⼆个参数就是⽅法需要的参数数据,是⼀个可变⻓度的参数.因为⽅法的参数个数可以有任意数量.

​ invoke⽅法的返回值就是反射⽅法调⽤后的返回值.

​ 假设⼀个类中有下⾯的⽅法:

public void doSth(String name) {
 System.out.println(name + " do sth");
}

​ 通过反射调⽤的⽅法的实现代码如下:

Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
Method m = clazz.getDeclaredMethod("doSth", String.class) ;
m.invoke(p,"cj");//p.doSth("cj")

​ 如果⽅法不能访问,仍然需要通过 setAccessible 来调整访问性.⽅法调⽤完毕之后再调回其访问性

Method m = clazz.getDeclaredMethod("doSth", String.class) ;
m.setAccessible(true);
m.invoke(p,"cj");
m.setAccessible(false);
5.2 方法参数

​ ⽅法的参数可以通过Method对象来获取,主要有以下⼏个⽅法来取得参数相关的信息

  • getParameterCount(): 获取⽅法参数个数
  • getParameterTypes():获取⽅法的参数类型数组
  • getParameters():获取所有的参数信息
int paramCount = method.getParameterCount();
Class[] paramClz = method.getParameterTypes();
Parameter[] parameters = method.getParameters();
5.2.1 Parameter

反射时通过Parameter类型来代表⽅法的参数信息,通过此类型可以获取参数的名字,类型,在参数上修饰的注解,参数的修饰符等信息.

//获取⽅法的第⼀个参数
Parameter parameter = method.getParameters()[0];
String name = parameter.getName();
//得到参数的类型信息
Class[] paramClz = parameter.getParameterTypes();
int modifier = parameter.getModifiers();
//得到参数上声明的注解信息
Annotation[] annos = parameter.getDeclaredAnnotations();

​ 其中参数名默认是得不到的,得到是arg0,arg1这样的名字,需要给编译器提供额外的参数 -parameters ,以便编译的时候保留参数相关的调试信息,这样反射的时候就可以获取参数的名字信息

6,字段

我们反射得到了某个Field对象之后,就可以得到此Field的名字,类型以及给字段赋值等操作.⽐如下⾯的代码就得到了字段的名字与类型

Field field = ...;
Class<?> fieldClass = field,getType();
String name = field.getName();
int modifier = field.getMofiers();//得到修饰符
6.1 获取字段值

​ 获取字段主要是靠 get() 方法来完成,如果你确定字段的类型,可以调用对应类型的方法,比如调用 getInt 方法获取整数字段的值,这种以 get 开头获取字段值得方法主要针对基本类型,所以有8个这样的方法。

​ 获取字段值分为获取静态与实例字段的值。如果是静态字段,调用上述方法获取字段是传递 null ,如果是实例字段的话,必须传递此字段所属类的对象给方法。

​ 取静态字段的值:

Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
//sa 是类的一个public static字段
Field sa = clazz.getDeclaredField("sa");
System.out.print(sa.get(null));
System.out.print(sa.getInf(null));

​ 获取实例字段的值:

Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
Field a = clazz.getDeclaredField("a");
System.out.print(a.get(p));
System.out.print(a.getInt(p));

​ 上面获取字段的值得方式,必须去报字段的修饰符是可以访问的,否则会抛出 IllegalAccessException

​ 如果字段的修饰符限制了访问,可以通过修改其访问性来获取字段值获取调用对应的getter方法来获取字段的值,比如下面的代码就是修改修饰符的方式来获取字段的值

Field a = clazz.getDeclareField("a");
a.setAccessible(true);
System.out.print(a.get(p));
a.setAccessible(false);

​ 修改可访问性只是设定是否跳过java语言的访问控制检查,并不是修改了字段的修饰符

​ 如果一个不能访问的字段,比如私有字段有对应的getter方法,那么可以通过调用getter方法来获取字段的值,不过遗憾的是Field类型并没有提供获取此字段对应的getter的API

6.2 设置字段值

​ 设置值与获取值是类似的,主要是通过 set() 方法以及setDouble,setInt等方法来完成,比如下面的代码

Field a = clazz.getDeclaredField("a");
a.setAccessible(true);
a.set(p,600);
System.out.print(a.get(p));
a.setAccessible(false);
7,数组与反射

​ 数组是某个类型对象的一个合集,基于这个认识,数组的反射与普通的反射有一点点不同,比如下面的代码创建了一个长度为10的字符串数组

Class cls = Class.forName("java.lang.String");
Object arr = Array.newInstance(cls, 10);
Array.set(arr, 5, "this is a test");//设置第六个位置的字符串值
String s = (String)Array.get(arr, 5);
System.out.println(s);
String[] arr2 = (String[])arr;
System.out.println(arr2[5]);

​ 其中Array类是在 java.lang.reflection 包下面的一个类型

8,泛型的反射

​ 经常会说在编译的时候会擦除所有的泛型信息,这样在运⾏时你就不能得到 任何 的泛型信息,这并不完全
正确.在某些情况下,在运⾏时是可以得到⼀些泛型相关的信息的.

​ 当你通过反射检查 java.util.List 这样的泛型接⼝时,你是不能得到任何的泛型信息的,因为这个类型不知道
会参数化为哪个具体的类型.

​ 但⼀个对参数化的泛型实例的引⽤是有办法得到⼀些泛型信息的,⽐如下⾯的代码中,如果mylist是⼀个类
的字段,此字段引⽤的是⼀个实例化的泛型类型,通过mylist字段是可以得到类型实参String的.

List<String> mylist = new ArrayList<String>();

​ 总⽽⾔之,只有通过实参化的泛型类型的引⽤来得到泛型信息,⽽不能通过泛型本身得到泛型信息,这些引
⽤主要是如下⼏种情况:

字段

⽅法的返回值

⽅法的参数

下⾯通过具体的代码来演示这三种情况,所有的代码都是在下⾯类的基础上进⾏的

public class MyClass {
     protected List<String> stringList ;

     public List<String> getStringList(){
         return this.stringList;
 	}
     public void setStringList(List<String> list){
         this.stringList = list;
     }
}
8.1 方法的泛型返回类型

getStringList ⽅法的返回类型是⼀个以String作为List泛型的实参的类型,想得到实参String就可以通过下
⾯的⽅式来获得

Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
 ParameterizedType type = (ParameterizedType) returnType;
 Type[] typeArguments = type.getActualTypeArguments();
 for(Type typeArgument : typeArguments){
 Class typeArgClass = (Class) typeArgument;
 //输出java.lang.String
 System.out.println("typeArgClass = " + typeArgClass);
 }
}
8.2 方法的泛型参数类型

setStringList⽅法的参数是⼀个实参为String的List泛型类型,想通过反射得到实参String的信息的代码如
下:

method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
     if(genericParameterType instanceof ParameterizedType){
		ParameterizedType aType = (ParameterizedType) genericParameterType;
         Type[] parameterArgTypes = aType.getActualTypeArguments();
         for(Type parameterArgType : parameterArgTypes){
             Class parameterArgClass = (Class) parameterArgType;
             System.out.println("parameterArgClass = " + parameterArgClass);
         }
     }
}
8.3 字段的泛型类型

​ 字段 stringList 声明的是⼀个实参为String的List类型,通过反射得到实参String的⽅法如下:

Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
    ParameterizedType aType = (ParameterizedType) genericFieldType;
    Type[] fieldArgTypes = aType.getActualTypeArguments();
     for(Type fieldArgType : fieldArgTypes){
          Class fieldArgClass = (Class) fieldArgType;
          System.out.println("fieldArgClass = " + fieldArgClass);
     }
}
6,综合案例

​ 反射通常⽤在bean之间的拷⻉,从Map中拷⻉数据到bean中,以及把数据库中读取的记录转换为⼀个bean
等等场景中.


​ 从 Map 生成 Bean

public <T> T getBean(Map<String,Object> source,Class<T> clazz) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
        T t = clazz.newInstance();
        for(Map.Entry<String,Object> entry : source.entrySet()){
            String key = entry.getKey();
            Object value = entry.getValue();
            Field field = clazz.getField(key);
            field.setAccessible(true);
            field.set(t,value);
            field.setAccessible(false);
        }
        return t;
    }

​ 调用

Map<String,Object> map = new HashMap<>();
map.put("stuId",100);
map.put("stuName","syl");
map.put("stuAge","2001-10-21");
map.put("stuSex","男");
StudentEntity bean = new Demo1().getBean(map, StudentEntity.class);
System.out.println(bean);
参考资料

https://www.oracle.com/technetwork/articles/java/javareflection-1536171.html

https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html

http://tutorials.jenkov.com/java-reflection/generics.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值