您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
Java反射使用总结
 
作者:一二三冲鸭
  1968  次浏览      15
 2020-10-14 
 
编辑推荐:
本文重点介绍了如何通过反射获取到某个类的方法、成员变量、构造函数等信息,同时也介绍动态代理的用法,这些都是反射的基础功能,反射的其他功能里就不一一介绍了。

本文来自于Vi的技术博客,由火龙果软件Anna编辑、推荐。

反射是java提供的一个重要功能,可以在运行时检查类、接口、方法和变量等信息,无需知道类的名字,方法名等。还可以在运行时实例化新对象,调用方法以及设置和获取变量值。

反射非常强大和有用,很多java框架中都有反射的影子,例如spring、mybatis等等,

JDBC利用反射将数据库的表字段映射到java对象的getter/setter方法。

Jackson, GSON, Boon等类库也是利用反射将JSON文件的属性映射到java对的象getter/setter方法。

可见,只要使用java,反射就无处不在。

Class对象

检查一个类之前,必须获取到java.lang.Class对象,java中的所有类型,包括long,int,数组等基本数据类型,都和Class对象有关系。

我们很多人去医院参加体检的时候,都做过B超检查,医生只需把一个探头在我们身上滑动就可以将我们体内的肝、胆、肾等器官反射到B超设备上显示。

Class类对象就相当于B超的探头,将一个类的方法、变量、接口、类名、类修饰符等信息告诉运行的程序。

Java提供了两种方式获取Class对象,一种是使用.class,另外一种是使用Class.forName()。

.class方式适用于在编译时已经知道具体的类。

Class alunbarClass = Alunbar.class;

Class.forName()方式适用于运行时动态获取Class对象,只需将类名作为forName方法的参数:

try{
Class alunbarClass1 = Class.forName("Alunbar");
}
catch(ClassNotFoundException e){
System.out.println("找不到Alunbar类");
}

这个方法会出现类找不到的情况,因此使用这个方法获取Class对象时,必须捕获ClassNotFoundException异常。

获取类名

package cn.alunbar;

public class Alunbar {
public static void main(String arts[]){
Class alunbarClass = Alunbar.class;
System.out.println (alunbarClass.getName());
System.out.println (alunbarClass.getSimpleName());
}
}

上面代码运行结果如下:

cn.alunbar.Alunbar
Alunbar

getName()方法获取的类名包含包信息。getSimpleName()方法只是获取类名,不包含包信息。

获取类修饰符

public class Alunbar {
public static void main(String arts[]){
Class alunbarClass = Alunbar.class;
System.out.println(alunbarClass.getModifiers());
System.out.println (Modifier.isPublic (alunbarClass.getModifiers()));

Class birdClass = Bird.class;
System.out.println(birdClass.getModifiers());
System.out.println (Modifier.isPublic (birdClass.getModifiers()));
}
private class Bird{
}
}

类修饰符有public、private等类型,getModifiers()可以获取一个类的修饰符,但是返回的结果是int,结合Modifier提供的方法,就可以确认修饰符的类型。

Modifier.isAbstract (int modifiers)
Modifier.isFinal (int modifiers)
Modifier.isInterface (int modifiers)
Modifier.isNative (int modifiers)
Modifier.isPrivate (int modifiers)
Modifier.isProtected (int modifiers)
Modifier.isPublic (int modifiers)
Modifier.isStatic (int modifiers)
Modifier.isStrict (int modifiers)
Modifier.isSynchronized (int modifiers)
Modifier.isTransient (int modifiers)
Modifier.isVolatile (int modifiers)

获取包信息

package cn.alunbar;

public class Alunbar {
public static void main(String arts[]){
Class birdClass = Bird.class;
System.out.println(birdClass.getPackage());
}
private class Bird{
}
}

getPackage()方法获取包信息。上面代码运行的结果:

package cn.alunbar

获取父类的Class对象

public class Alunbar {
public static void main(String arts[]){
Class birdClass = Bird.class;
Class superclass = birdClass.getSuperclass();
System.out.println(superclass.getSimpleName());
}
private class Bird extends Animal{
}
private class Animal{
}
}

上面代码运行的结果:

Animal

getSuperclass()方法返回的父类的Class对象。

获取接口信息

获取接口信息的方法:

Class[] interfaces = birdClass.getInterfaces();

一个类可以实现多个接口,所以getInterfaces()方法返回的是Class[]数组。 注意:getInterfaces()只返回指定类实现的接口,不会返父类实现的接口。

获取构造函数Constructor

获取构造函数的方法:

Class birdClass = Bird.class;
Constructor[] constructors = birdClass.getConstructors();

一个类会有多个构造函数,getConstructors()返回的是Constructor[]数组,包含了所有声明的用public修饰的构造函数。

如果你已经知道了某个构造的参数,可以通过下面的方法获取到回应的构造函数对象:

public class Alunbar {
public static void main(String arts[]){
Class birdClass = Bird.class;
try{
Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
}catch(NoSuchMethodException e){
}
}
private class Bird {
public Bird(){
}
public Bird(String eat){
}
}
}

上面获取构造函数的方式有2点需要注意:

1、只能获取到public修饰的构造函数。

2、需要捕获NoSuchMethodException异常。

获取构造函数的参数

获取到构造函数的对象之后,可以通过getParameterTypes()获取到构造函数的参数。

Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
Class[] parameterTypes = constructors.getParameterTypes();

初始化对象

通过反射获取到构造器之后,通过newInstance()方法就可以生成类对象。

public class Alunbar {
public static void main(String arts[]){
Class birdClass = Bird.class;
try{
Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
Bird bird = (Bird)constructors.newInstance ("eat tea");
}catch(Exception e){
System.out.println("没有对应的构造函数");
}
}
class Bird {
public Bird(){
}
protected Bird(String eat){
}
}
}

newinstance()方法接受可选数量的参数,必须为所调用的构造函数提供准确的参数。如果构造函数要求String的参数,在调用newinstance()方法是,必须提供String类型的参数。

获取Methods方法信息

下面代码是通过反射可以获取到该类的声明的成员方法信息:

Method[] metchods = birdClass.getMethods();
Method[] metchods1 = birdClass.getDeclaredMethods();
Method eatMetchod = birdClass.getMethod ("eat", new Class[]{int.class});
Method eatMetchod1 = birdClass.getDeclaredMethod ("eat", new Class[]{int.class});

无参的getMethods()获取到所有public修饰的方法,返回的是Method[]数组。 无参的getDeclaredMethods()方法到的是所有的成员方法,和修饰符无关。 对于有参的getMethods()方法,必须提供要获取的方法名以及方法名的参数。如果要获取的方法没有参数,则用null替代:

Method eatMetchod = birdClass.getMethod("eat", null);

无参的getMethods()和getDeclaredMethods()都只能获取到类声明的成员方法,不能获取到继承父类的方法。

获取成员方法参数

Class birdClass = Bird.class;
Class[] parameterTypes = eatMetchod1.getParameterTypes();

获取成员方法返回类型

Class birdClass = Bird.class;
Class returnType = eatMetchod1.getReturnType();

invoke()方法

java反射提供invoke()方法,在运行时根据业务需要调用相应的方法,这种情况在运行时非常常见,只要通过反射获取到方法名之后,就可以调用对应的方法:

Class birdClass = Bird.class;
Constructor constructors1 = birdClass.getConstructor();
Method eatMetchod = birdClass.getMethod ("eat", new Class[]{int.class});
System.out.println (eatMetchod.invoke (constructors1.newInstance(), 2));

invoke方法有两个参数,第一个参数是要调用方法的对象,上面的代码中就是Bird的对象,第二个参数是调用方法要传入的参数。如果有多个参数,则用数组。

如果调用的是static方法,invoke()方法第一个参数就用null代替:

public class Alunbar {
public static void main(String arts[]){
try{
Class birdClass = Bird.class;
Constructor constructors1 = birdClass.getConstructor();
Method eatMetchod = birdClass.getMethod ("eat", new Class[]{int.class});
System.out.println (eatMetchod.invoke(null, 2));
}catch(Exception e){
e.printStackTrace();
System.out.println("没有对应的构造函数");
}
}
}
class Bird{
public static int eat(int eat){
return eat;
}
public Bird(){
}
public Bird(String eat){
}
private void talk(){}
}
class Animal{
public void run(){
}
}

使用反射可以在运行时检查和调用类声明的成员方法,可以用来检测某个类是否有getter和setter方法。getter和setter是java bean必须有的方法。 getter和setter方法有下面的一些规律: getter方法以get为前缀,无参,有返回值 setter方法以set为前缀,有一个参数,返回值可有可无, 下面的代码提供了检测一个类是否有getter和setter方法:

public static void printGettersSetters(Class aClass){
Method[] methods = aClass.getMethods();
for(Method method : methods){
if(isGetter(method)) System.out.println ("getter: " + method);
if(isSetter(method)) System.out.println ("setter: " + method);
}
}
public static boolean isGetter (Method method){
if(!method.getName().startsWith("get")) return false;
if(method.getParameterTypes( ).length != 0) return false;
if(void.class.equals (method.getReturnType()) return false;
return true;
}
public static boolean isSetter (Method method){
if(!method.getName().startsWith ("set")) return false;
if(method.getParameterTypes( ).length != 1) return false;
return true;
}

获取成员变量

通过反射可以在运行时获取到类的所有成员变量,还可以给成员变量赋值和获取成员变量的值。

Class birdClass = Bird.class;
Field[] fields1 = birdClass.getFields();
Field[] fields2 = birdClass.getDeclaredFields();
Field fields3 = birdClass.getField("age");
Field fields4 = birdClass.getDeclaredField("age");

getFields()方法获取所有public修饰的成员变量,getField()方法需要传入变量名,并且变量必须是public修饰符修饰。

getDeclaredFields方法获取所有生命的成员变量,不管是public还是private。

获取成员变量类型

Field fields4 = birdClass.getDeclaredField("age");
Object fieldType = fields4.getType();

成员变量赋值和取值

一旦获取到成员变量的Field引用,就可以获取通过get()方法获取变量值,通过set()方法给变量赋值:

Class birdClass = Bird.class;
Field fields3 = birdClass.getField("age");
Bird bird = new Bird();
Object value = fields3.get(bird);
fields3.set(bird, value);

访问私有变量

有很多文章讨论禁止通过反射访问一个对象的私有变量,但是到目前为止所有的jdk还是允许通过反射访问私有变量。

使用 Class.getDeclaredField (String name)或者Class.getDeclaredFields()才能获取到私有变量。

package field;
import java.lang.reflect.Field;
public class PrivateField {
protected String name;
public PrivateField(String name){
this.name = name;
}
}
public class PrivateFieldTest {
public static void main (String args[])throws Exception{
Class privateFieldClass = PrivateField.class;
Field privateName = privateFieldClass.getDeclaredField ("name");
privateName.setAccessible(false);
PrivateField privateField = new PrivateField ("Alunbar");
String privateFieldValue = (String) privateName.get (privateField);
System.out.println ("私有变量值:" + privateFieldValue);
}
}

上面的代码有点需要注意:必须调用setAccessible(true)方法,这是针对私有变量而言,public和protected等都不需要。这个方法是允许通过反射访问类的私有变量。

访问私有方法

和私有变量一样,私有方法也是不允许其他的类随意调用的,但是通过反射可以饶过这一限制。 使用Class.getDeclaredMethod(String name, Class[] parameterTypes)或者Class.getDeclaredMethods()方法获取到私有方法。

public class PrivateMethod {
private String accesPrivateMethod(){
return "成功访问私有方法";
}
}
public class PrivateMethodTest {
public static void main (String args[])throws Exception{
Class privateMethodClass = PrivateMethod.class;
Method privateStringMethod = privateMethodClass.getDeclaredMethod ("accesPrivateMethod", null);
privateStringMethod.setAccessible(true);
String returnValue = (String) privateStringMethod.invoke(new PrivateMethod(), null);
System.out.println ("returnValue = " + returnValue);
}
}

和访问私有变量一样,也要调用setAccessible(true)方法,允许通过反射访问类的私有方法。

访问类注解信息

通过反射可以在运行时获取到类、方法、变量和参数的注解信息。

访问类的所有注解信息:

Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

访问类特定的注解信息:

Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}

访问方法注解信息:

Method method = ... //obtain method object
Annotation[] annotations = method.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

访问特定方法注解信息:

Method method = ... // obtain method object
Annotation annotation = method.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}

访问参数注解信息:

Method method = ... //obtain method object
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();
int i=0;
for(Annotation[] annotations : parameterAnnotations){
Class parameterType = parameterTypes[i++];
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("param: " + parameterType.getName());
System.out.println("name : " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
}

 

Method.getParameterAnnotations()方法返回的是一个二维的Annotation数组,其中包含每个方法参数的注解数组。

访问类所有变量注解信息:

Field field = ... //obtain field object
Annotation[] annotations = field.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

访问类某个特定变量的注解信息:

Field field = ... // obtain method object
Annotation annotation = field.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}

获取泛型信息

很多人认为java类在编译的时候会把泛型信息给擦除掉,所以在运行时是无法获取到泛型信息的。其实在某些情况下,还是可以通过反射在运行时获取到泛型信息的。

获取到java.lang.reflect.Method对象,就有可能获取到某个方法的泛型返回信息。

泛型方法返回类型

下面的类中定义了一个返回值中有泛型的方法:

public class MyClass {
protected List<String> stringList = ...;
public List<String> getStringList(){
return this.stringList;
}
}

下面的代码使用反射检测getStringList()方法返回的是List<String>而不是List

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;
System.out.println("typeArgClass = " + typeArgClass);
}
}

上面这段代码会打印:typeArgClass = java.lang.String

泛型方法参数类型

下面的类定义了一个有泛型参数的方法setStringList():

public class MyClass {
protected List<String> stringList = ...;
public void setStringList (List<String> list){
this.stringList = list;
}
}

Method类提供了getGenericParameterTypes()方法获取方法的泛型参数。

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

上面的代码会打印出parameterArgType = java.lang.String

泛型变量类型

通过反射也可以获取到类的成员泛型变量信息——静态变量或实例变量。下面的类定义了一个泛型变量:

public class MyClass {
public List<String> stringList = ...;
}

通过反射的Filed对象获取到泛型变量的类型信息:

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

Field对象提供了getGenericType()方法获取到泛型变量。 上面的代码会打印出:fieldArgClass = java.lang.String

动态代理

使用反射可以在运行时创建接口的动态实现,java.lang.reflect.Proxy类提供了创建动态实现的功能。我们把运行时创建接口的动态实现称为动态代理。

动态代理可以用于许多不同的目的,例如数据库连接和事务管理、用于单元测试的动态模拟对象以及其他类似aop的方法拦截等。

创建代理

调用java.lang.reflect.Proxy类的newProxyInstance()方法就可以常见动态代理,newProxyInstance()方法有三个参数:

1、用于“加载”动态代理类的类加载器。

2、要实现的接口数组。

3、将代理上的所有方法调用转发到InvocationHandler的对象。 代码如下:

InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);

运行上面代码后,proxy变量包含了MyInterface接口的动态实现。

对代理的所有调用都将由到实现了InvocationHandler接口的handler 对象来处理。

InvocationHandler

如上面说的一样,必须将InvocationHandler的实现传递给Proxy.newProxyInstance()方法。对动态代理的所有方法调用都转发到实现接口的InvocationHandler对象。 InvocationHandler代码:

public interface InvocationHandler{
Object invoke (Object proxy, Method method, Object[] args)
throws Throwable;
}

实现InvocationHandler接口的类:

public class MyInvocationHandler implements InvocationHandler{
public Object invoke (Object proxy, Method method, Object[] args)
throws Throwable {
//do something "dynamic"
}
}

下面详细介绍传递给invoke方法的三个参数。

Object proxy参数,实现接口的动态代理对象。通常不需要这个对象。

Method method参数,表示在动态代理实现的接口上调用的方法。通过Method对象,可以获取到方法名,参数类型,返回类型等信息。

Object[] args参数,包含调用接口中实现的方法时传递给代理的参数值。注意:如果接口中的参数是int、long等基本数据时,这里的args必须使用Integer, Long等包装类型。

上面代码中会生成一个MyInterface接口的对象proxy,通过proxy对象调用的方法都会由MyInvocationHandler类的invoke方法处理。

动态代理使用场景:

1、数据库连接和事务管理。例如Spring框架有一个事务代理,可以启动和提交/回滚事务

2、用于单元测试的动态模拟对象

3、类似AOP的方法拦截。

本文就介绍到这里,反射的其他功能里就不一一介绍了。

 
   
1968 次浏览       15
相关文章

Java微服务新生代之Nacos
深入理解Java中的容器
Java容器详解
Java代码质量检查工具及使用案例
相关文档

Java性能优化
Spring框架
SSM框架简单简绍
从零开始学java编程经典
相关课程

高性能Java编程与系统性能优化
JavaEE架构、 设计模式及性能调优
Java编程基础到应用开发
JAVA虚拟机原理剖析
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]
 
最新文章
Java虚拟机架构
JVM——Java虚拟机架构
Java容器详解
Java进阶--深入理解ArrayList实现原理
Java并发容器,底层原理深入分析
最新课程
java编程基础到应用开发
JavaEE架构、 设计模式及性能调优
高性能Java编程与系统性能优化
SpringBoot&Cloud、JavaSSM框架
Spring Boot 培训
更多...   
成功案例
国内知名银行 Spring+SpringBoot+Cloud+MVC
北京 Java编程基础与网页开发基础
北京 Struts+Spring
华夏基金 ActiveMQ 原理
某民航公 Java基础编程到应用开发
更多...