java反射详解

1、 什么是反射?

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

本教程将深入介绍Java反射。解释Java反射的基础知识,包括如何使用数组,注解,泛型和动态代理。展示如何执行更具体的java反射,例如读取类的所有getter方法,或访问类的私有字段和方法。

2、 反射思维导图:

image

3、反射实例讲解

在获取类的信息之前,必须获取一个java.lang.Class对象。

两种方式获取:

 Class myObjectClass = MyObject.class
 Class class = Class.forName(className);

Class.forName里面的字符串必须提供完全限定的类名,也就是包含包名的类名,如cn.java.my.Test。如果运行时无法在类路径上找到类,则 该Class.forName()方法可能抛出一个ClassNotFoundException。

反过来,从一个Class对象获取它的类名,也有两种方式。

第一个getName()是获取完全限定的类名。

 Class aClass = MyObject.class
 String className = aClass.getName();

第二个getSimpleName()仅仅是获取类名,没有包的名称。

  Class aClass= MyObject.class
  String simpleClassName = aClass.getSimpleName();

我们也可以通过Class来获取类的限定符(如public、private等)。

 Class aClass = MyObject.class
  int modifiers = aClass.getModifiers();

所有限定符都被包装成了int,我们可以用 java.lang.reflect.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)

获取包的信息:

 Class aClass = MyObject.class
 Package package = aClass.getPackage();

获取超类:

Class superclass = aClass.getSuperclass();

超类类对象是一个像任何其他对象一样的Class,因此也可以继续对其进行类反射。

获取类对象实现的接口,一个类可以实现多个接口,所以用Class数组进行返回值接收。

 Class aClass = MyObject.class
 Class[] interfaces = aClass.getInterfaces();

访问类的方法:

 Method [] method = aClass.getMethods();

主要利用java.lang.reflect.Method这个类。该Method[]数组将为Method该类中声明的每个公共方法提供一个实例。

知道要访问的方法的精确参数类型,则可以这样做,而不是获取所有方法的数组。此示例返回名为“doSomething”的公共方法,在给定的类中采用String作为参数:

 Class aClass = MyObject.class
 Method method = aClass.getMethod(“doSomething”,new Class [] {String.class});

如果没有方法匹配给定的方法名和参数,在这种情况下String.class,一个NoSuchMethodException被抛出。

如果访问的方法不带参数,则传递null为参数类型数组,如下所示:

 Class aClass = MyObject.class
 Method method = aClass.getMethod(“doSomething”,null);

读取给定方法采用的参数,如下所示:

 Method method = aClass.getMethod(“doSomething”,null);
 Class[] parameterTypes = method.getParameterTypes();

访问如下方法的返回类型:

 Method method = aClass.getMethod(“doSomething”,null);
 Class returnType = method.getReturnType();

调用方法:

 Method method = MyObject.class.getMethod("doSomething", String.class);
 Object returnValue = method.invoke(null, "parameter-value1");

null参数是要调用方法的对象。如果方法是静态的,则提供null而不是对象实例。

  • Getter
    getter方法的名称以“get”开头,不需要参数参数,并返回一个值。
  • Setter
    一个setter方法的名称以“set”开头,并带有1个参数。

Setter可能会也可能不会返回值,不应该对setter的返回类型做出任何假设。

下面是一个判断get/set方法的例子。

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

访问类的构造函数:

 Constructor [] constructors = aClass.getConstructors();

Constructor[]数组将为Constructor类中声明的每个公共构造函数提供一个实例。

如果知道访问的构造函数的精确参数类型,则可以这样做,而不是获取所有构造函数的数组。此示例返回给定类的公共构造函数,该构造函数采用String作为参数:

 Class aClass = MyObject.class
 Constructor constructor =aClass.getConstructor(new Class[]{String.class});

如果没有构造函数在给定构造函数的参数相匹配,在这种情况下String.class,一个NoSuchMethodException被抛出。

读取给定构造函数采用的参数,如下所示:

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

Constructor.newInstance() 方法采用可选数量的参数,但必须在要调用的构造函数中为每个参数提供一个参数。在这种情况下,它是一个构造函数String

Constructor constructor = MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject)constructor.newInstance("constructor-arg1");

访问类的字段(成员变量),如下所示:

 Field [] method = aClass.getFields();

Field[]数组将为Field该类中声明的每个公共字段提供一个实例。

如果知道要访问的字段的名称,则可以像这样访问它:

 Class aClass = MyObject.class
 Field field = aClass.getField(“someField”);

使用该Field.getName()方法获取其字段名称 ,如下所示:

 Field field = aClass.getField(“someField”);
 String fieldName = field.getName();

Field.getType() 方法确定字段的字段类型(String,int等):

 Field field = aClass.getField(“someField”);
 Object fieldType = field.getType();

获得Field引用后,可以使用Field.get()和Field.set()方法获取并设置其值 ,如下所示:

 Class aClass = MyObject.class
 Field field = aClass.getField(“someField”);
 MyObject objectInstance = new MyObject();
 Object value = field.get(objectInstance);
 field.set(objetInstance,value);

访问私有字段

需要调用Class.getDeclaredField(String name) or Class.getDeclaredFields()方法。方法Class.getField(String name) 和Class.getFields()方法只返回公共字段,因此它们不起作用。下面是一个带有私有字段的类的简单示例,下面是通过反射访问该字段的代码:

 public class PrivateObject {
  private String privateString = null;
  public PrivateObject(String privateString) {
  this.privateString = privateString;
  }
 }
 PrivateObject privateObject = new PrivateObject("The Private Value");
 Field privateStringField = PrivateObject.class.
  getDeclaredField("privateString");
 privateStringField.setAccessible(true);
 String fieldValue = (String) privateStringField.get(privateObject);
 System.out.println("fieldValue = " + fieldValue);

访问私有方法

需要调用Class.getDeclaredMethod(String name, Class[] parameterTypes) or Class.getDeclaredMethods()方法。方法Class.getMethod(String name, Class[] parameterTypes) 和Class.getMethods()方法只返回公共方法,因此它们不起作用。下面是一个带有私有方法的类的简单示例,下面是通过反射访问该方法的代码:

 public class PrivateObject {
  private String privateString = null;
  public PrivateObject(String privateString) {
  this.privateString = privateString;
  } 
  private String getPrivateString(){
  return this.privateString;
  }
 }
 PrivateObject privateObject = new PrivateObject("The Private Value");
 Method privateStringMethod = PrivateObject.class.
  getDeclaredMethod("getPrivateString", null);
 privateStringMethod.setAccessible(true);
 String returnValue = (String)
  privateStringMethod.invoke(privateObject, null);
 System.out.println("returnValue = " + returnValue);

访问类的类注解,如下所示:

 Annotation [] annotations = aClass.getAnnotations();

注解MyAnnotation定义

 public @interface MyAnnotation {
  public String name();
  public String value();
 }

访问类注解的示例:

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

以下是带注解的方法示例:

 public class TheClass { 
  @MyAnnotation(name =“someName”,value =“Hello World”)
  public void doSomething(){} 
 }

访问方法注解,如下所示:

 Method method = ... //获取方法对象
 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 = ... //获取方法对象
 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());
}

为方法参数声明添加注解

public class TheClass {
 public static void doSomethingElse(
 @MyAnnotation(name="aName", value="aValue") String parameter){
 }
}

Method对象访问参数注解

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

以下是带注解的字段示例:

public class TheClass {
 @MyAnnotation(name =“someName”,value =“Hello World”)
 public String myField = null; 
}

访问字段注解:

Field field = ... //获取字段对象
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());
}

泛型反射:

示例类:

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

获得该getStringList() 方法的泛型返回类型

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

动态代理

通过Proxy.newProxyInstance()来创建动态代理,这个方法需要三个参数:

加载动态代理的类的类加载器。

需要实现的接口数组

实现InvocationHandler类一个handle类,如下例子:

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

这里有一个动态代理的例子。

public interface StartInterface {
 void sing();
 void play();
 void run();
 void collectMoney();
}
public class Star implements StartInterface{
 @Override
 public void sing() {
 System.out.println("star sing!");
 }

 @Override
 public void play() {
 System.out.println("start play!");
 }

 @Override
 public void run() {
 System.out.println("star run!");
 }

 @Override
 public void collectMoney() {
 System.out.println("star collectMoney!");
 }
}
public class StarHandle implements InvocationHandler {
 private StartInterface star;

 public StarHandle(StartInterface star) {
 this.star = star;
 }

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 if (method.getName().equals("sing")) {
 method.invoke(star, args);
 return null;
 }

 return null;
 }
}
public class DynamicTest {
 public static void main(String[] args) {
 StartInterface star = new Star();
 StarHandle starHandle = new StarHandle(star);
 StartInterface obj =(StartInterface) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
 new Class[]{StartInterface.class},starHandle);
 obj.sing();
 obj.play();
 }
}

最后输出:

star sing!

这里就是StartInterface代理到了Star类上,所以调用方法都是调用到了Star类的方法上,

3、 反射的应用场景

Java 反射可用于将JSON文件中的属性映射到Java对象中的getter / setter方法,如Jackson,GSON,Boon等 。或者,反射可用于将JDBC ResultSet 的列名映射到Java对象中的getter / setter方法。在spring创建单例bean的时候,也是利用反射来实现的。许多java框架的底层都用到了反射,反射构成了框架的基础。

本专栏定期发布java相关文章,欢迎关注!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,966评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,170评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,909评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,959评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,851评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,583评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,956评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,590评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,878评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,892评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,719评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,501评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,957评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,124评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,440评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,003评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,211评论 2 339

推荐阅读更多精彩内容

  • 调试从哪里来? 调试被称之为“黑客之瞳”,程序的对错通常都不是证明出来的,而是需要我们去观测验证的。调试是程序员的...
    不是流汗羊阅读 406评论 0 3
  • 微博,微信,简书,仿佛一天的功夫,满世界都被薛之谦刷屏。大家都在写着他。 当年我不温不火,你嫁给我,很多人不知...
    浅尘一笙阅读 363评论 6 13
  • 晕倒雀 贵妃拥有羞花貌, 西施长着沉鱼容。 如烟虽未落大雁, 落个麻雀是实情。[擦汗][擦汗][擦汗][糗大了][...
    往事如烟胖婆婆阅读 1,256评论 27 35
  • 昨天看到洛阳信息港有人发了一篇相亲日记,相亲故事很有看头,激发笔者产生灵感,写了一篇《百里挑一相亲日记》。今天一大...
    遇知音阅读 937评论 7 7