了解Java反射

Java Reflection可以在运行时查看类,接口,字段和方法,而无需在编译时知道类,方法等的名称。 还可以使用反射来实例化新对象,调用方法和get/set字段值。

反射是Java的一项强大的功能,但不应该被随意使用,它有如下一些缺点:

  • 性能不佳 : 由于java反射动态地解析类型,因此它涉及扫描类路径以查找要加载的类的处理,从而导致性能降低。
  • 安全限制 : Reflection需要运行时权限,这些权限可能不适用于在安全管理器下运行的系统。 由于安全管理器,这可能导致应用程序在运行时失败。
  • 安全问题 : 使用Reflection我们能访问被禁止访问的代码,比如能够访问类的private字段并修改它的值,这可能带来严重的安全威胁,并导致程序运行异常。
  • 高维护成本 : Reflection的代码难以理解和调试,问题代码在编译时难以被发现,因此代码的维护成本更高。

1. 类反射

使用反射可以在运行时查看类,查看类通常是使用反射的第一步,通过反射我们能获取的类的信息包括但不限于:

  • 类名
  • 类修饰符 (比如 public,private,synchronized 等)
  • 包信息
  • 父类
  • 实现的接口
  • 构造方法
  • 方法
  • 字段
  • 注解
1.1 Class 对象

在查看任意一个类之前,你需要获取其 java.lang.Class 对象,Java中所有类型包括原始数据类型都有一个相关的Class对象。如果你知道类的名称,可以如下获取其Class对象:

Class myObjectClass = MyObject.class

如果在编译时不知道类的名称,但是能在运行时获取其名称的字符串,也可以这样来获取其Class对象

String className = "com.xiaoqiang.app.MyObject";
Class myObjClass = Class.forName(className);

需要注意的是名称字符串必须是类的完整名称,即包含类所在包的包名。Class.forName()方法还可能抛出 ClassNotFoundException 异常。

1.2 类名称
Class myClass = ...
String className = myClass.getName();    //包含包名
String simpleCName = myClass.getSimpleName();   //只包含类名
1.3 修饰符
Class myClass = ... 
int modifiers = myClass.getModifiers();

getModifiers方法将类的所有修饰符都打包进了一个整数中,可以通过以下的方法判断修饰符的类型:

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)
1.4 包信息
Class myClass = ... 
Package package = myClass.getPackage();
1.5 父类
Class superClass = myClass.getSuperClass();
1.6 实现的接口
Class[] interfaces = myClass.getInterfaces();
1.7 构造方法
Constructor[] pubConstructors = myClass.getConstructors();  //获取所有public构造方法
Constructor[] constructors = myClass.getDeclaredConstructors();  //获取所有构造方法
1.8 方法
Method[] pubMethods = myClass.getMethods();  //获取所有public方法
Method[] methods = myClass.getDeclaredMethods();  //获取所有方法
1.9 字段
Field[] pubFields = myClass.getFields();  //获取所有public字段
Field[] fields = myClass.getDeclaredFields();  //获取所有字段
1.10 注解
Annotation[] annotations = myClass.getAnnotations();

2. 构造方法(Constructor)

使用反射可以在运行时查看类的构造方法并且初始化对象,这可以通过 java.lang.reflect.Constructor 类来实现。

2.1 获取Constructor对象

获取类的所有构造方法已经在 1.7 中介绍过了。
也可以通过传入参数类型,获取指定构造方法,如下:

Constructor constructor = myClass.getConstructor(String.class, int.class);

如果该类没有该指定参数的构造方法,将会抛出 NoSuchMethodException 异常。

2.2 构造参数

有了Constructor对象之后可以获取构造方法的参数类型

Class[] parameterTypes = myConstructor.getParameterTypes();
2.3 使用Constructor来实例化对象
Constructor constructor = Gas95.class.getConstructor(String.class, int.class);
Gas95 gas95 = (Gas95) constructor.newInstance("中石化91#", 22);

3. 字段(Field)

使用反射可以在运行时查看类的字段(成员变量)并且进行get/set操作,这可以通过 java.lang.reflect.Field 类来实现。

3.1 获取字段对象

获取类的所有字段已经在 1.9 中介绍过。
还可以通过字段名获取指定字段:

Field field = Gas95.class.getField("name");  //只能获取public字段
Field field = Gas95.class.getDeclaredField("name");  //无修饰符限制

此方法可能会导致 NoSuchFieldException 异常。

3.2 字段名

一旦获得了Field对象,就可以通过其getName方法获取字段名称,如下:

String fieldName = field.getName();
3.3 字段类型

通过Field.getType()方法可以获得字段的类型:

Class type = field.getType();
3.4 get/set 字段值

get值:

Gas95 gas95 = new Gas95("中石化", "95#");
Field field = Gas95.class.getDeclaredField("gasType");
String gasType = (String) field.get(gas95);

获取到的 gasType 就是 95#

set值:

field.set(gas95, "98#");

set之后,gas95 实例的 gasType 字段值为 98#

如果字段是静态(static)的,那么get/set的时候不需要传入类的实例,用 null 替代即可。
对于 private 的Field,如果想调用其get/set方法,需要先调用方法使之可访问,如下:

field.setAccessible(true);

4. 方法(Method)

使用反射可以在运行时查看并调用类的方法,这可以通过 java.lang.reflect.Method 类来实现。

4.1 获取Method对象

获取Method对象的方法在 1.8 中已经介绍过。
还可以通过指定的方法名和参数类型来获取指定的方法,如下:

Method method = Gas95.class.getMethod("printGasInfo", boolean.class);

其中 printGasInfo 方法需要传入一个 boolean 类型的参数

4.2 方法的参数和返回值类型

获取方法的所有参数的类型:

Class[] parameterTypes = method.getParameterTypes();

获取方法的返回值的类型:

Class returnType = method.getReturnType();

如果方法没有返回值,那么类型是 void

4.3 通过Method对象调用方法
Method method = Gas95.class.getMethod("printGasInfo", boolean.class);
method.invoke(new Gas95("Shell", "98#"), true);

方法 Method.invoke(Object target, Object ... parameters) 第一个参数为类的实例,后面的参数为方法需要的参数。如果方法是静态(static)的,第一个参数可以传入 null


5. 注解(Annotation)

使用反射可以在运行时访问Java类中的注解

5.1 类的注解

1.10 中已经介绍过

5.2 方法的注解
Method method = ...
Annotation[] annotations = method.getAnnotations();

当然还能指定注解的类型

Annotation annotation = method.getAnnotation(TheAnnotation.class);
5.3 方法参数的注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();

获取该方法所有参数的所有注解,返回一个二维数组。

5.4 字段的注解
Annotation[] annotations = field.getAnnotations();

获取指定类型的注解

Annotation annotation = field.getAnnotation(TheAnnotation.class);

6. 访问非public方法、字段

以上的各种 getMethod()、getMethods()、getField()等方法,都只能访问对应修饰符为 public 的方法/字段。如果想访问使用了其它修饰符的方法/字段,需要调用对应的 getDeclaredXXX() 方法。

在获取到非public修饰的字段/方法之后,还不能直接对它进行调用,需要先调用
Method.setAccessible(true) / Field.setAccessible(true),将它们设置成可访问,然后再调用。


以上是反射常见的用法,如果想进一步了解,请参考
Java Reflection Tutorial

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值