`
cywhoyi
  • 浏览: 421258 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Reflection API

 
阅读更多

If you have ever asked yourself questions like these:
– “How do I invoke a method, having only it’s name in a String?”
- “How do I list all the attributes in a class dynamically?”
- “How do I write a method that resets the state of any given object to default values?”

Then you have probably already heared of Java’s Reflection API, and in case you haven’t, this is a good opportunity to see what it’s all about, and what it can be used for. This feature is really powerful, but as always, has to be used with some good judgement.

The advantage it brings to the table is the ability to analyse information regarding a class, as it’s attributes, methods, annotations, or even the state of an instance, all of that in runtime. The dynamism obtained by this sounds really useful, doesn’t it?

In this tutorial I intent to show some basic usage of the Reflection API. What can be done, what shouldn’t be done, advantages and drawbacks.

So… Shall we? 

The starting point, the Class<T> class

One of the most important things when working with Reflection is knowing where to start, knowing what class gives us access to all that information. And the answer to that is: java.lang.Class<T>

Let’s assume we have the following class:

1 package com.pkg;
2  
3 public class MyClass{
4  
5    private int number;
6    private String text;
7  
8 }

We can obtain the Class‘s reference in 3 different ways. Directly from the class, from it’s name, or from an instance:

1 Class<?> clazz1 = MyClass.class;
2 Class<?> clazz2 = Class.forName("com.pkg.MyClass");
3  
4 MyClass instance = new MyClass();
5 Class<?> clazz3 = instance.getClass();

Tip: AHere we see an important detail. Normally name the identifiers of an instance of Class something like clazz or clz, it may seem weird, but that’s only because class is already a reserved word in the Java language.

From the class’ reference we can navigate through everything, find out what are it’s members, annotations, even it’s package or it’s ClassLoader, but we’re gonna get into all of that in more detail later, so for now let’s focus on the methods that give us information regarding class itself:

int getModifiers() Returns the modifiers of the class or interface inside an int, to find out exactly what modifiers are applied, we should use the static methods provieded by the Modifier class
boolean isArray() Determines if the class represents an Array
boolean isEnum() Determines if the class was declared as an enum in the sourcecode
booleanisInstance(Object obj) Returns true if the informed object can be assigned to an object of the type represented by this Class
boolean isInterface() Determines if the class represents an interface
boolean isPrimitive() Determines if the class represents a primitive type
T newInstance() Creates a new instance of this class
Class<? super T> getSuperclass() Returns the reference of the superclass, in case it is invoked in the Object class, it returns null

You can see a complete definition of these methods and many others directly in the class documentation.

TipTo use successfully the methods in the Modifier class, it is necessary to have some basic knowledge on bitwise operations.Being the most common in this case, the AND operation.

The attributes, the Field class

The fields represent the attributes of a class, it’s that simple. It is through this class that we can obtain information about them, but before that, how do we get a reference to a Field?

Inside the Class class we have a few different methods that can return us the class’ fields, as a spoiler I already say that there are equivalent methods for methods and constructors, but let’s focus on the attributes first.

We have a few “important to remember” things in relation to how to find out the members of a class, I’ll present the methods first, and then elaborate more on these details.

Field getField(String name) Returns the Field that reflects to the public attribute of the class with the given name.
Field[] getFields() Returns the array of Fields that reflect to the public attributes of the class.
Field getDeclaredField(String name) Returns the Field that reflects to the declared attribute of the class with the given name.
Field[] getDeclaredFields() Returns the array of Fields that reflect to the declared attributes of the class.

Alright, seems like we have 2 groups of very similar methods, and we do. The differences between them are subtle, and not many know them.

The getField and getFields methods return only the public attributes of the class. It’s somehow cleaner, because we don’t always want (and many times shouldn’t) meddle with internal attributes. The advantage here is that it still searches for attributes in the superclass,going up the hierarchy, being convenient for us, but again, the attributes in the superclasses must be public to be found.

Now the getDeclaredField and getDeclaredFields methods are not so clean, for them any declared attribute is valid, may it be private, protected or whatever. So, contrary to popular belief, accessing private members via Reflection is not that complex. And as another difference they do not search for attributes on the superclasses, so they ignore inheritance hierarchy completely.

Let’s exemplify trying to access the attribute inside a class, showing the use of the aforementioned methods, and others that the Fieldclass provides. Using as example the MyClass class we defined earlier, let’s assume we want to know the value contained in the text attribute of some instance.

1 MyClass instance = new MyClass();
2 instance.setText("Reflective Extraction");
3 Class<?> clazz = instance.getClass();
4 Field field = clazz.getDeclaredField("text"); // Accessing private attribute
5 Object value = field.get(instance);
6 System.out.println(value);

Tip: The usage of the get method may be confusing at first, but it’s rather simple. Having a Field in your hands, you send the target instance from which you want to extract the value as an argument. It is necessary to have in mind that the Field is related to the class, and not to the instance, therefore if the attribute is not static, a concrete instance is required so that a value can be extracted. In case we’re talking about a static attribute, you can pass any instance of any type, or even null as an argument.

Okay, we accessed our attribute using the getDeclaredField method, passing the field’s name, and retrieving it’s current value using theget method. All done, right?

Wrong. When we run our code we see that an exception has been thrown, more specifically an IllegalAccessException, it’s not just because we have the field’s reference, that we can manipulate it to our will, but there’s a way around that as well.

The Field‘s superclass is the AccessibleObject class which is also the superclass of Method and Constructor, so this explanation is valid for any of the 3.

The AccessibleObject class defines 2 main methods: setAccessible(boolean) and isAccessible(), that basically define if an attribute is accessible or not. In our case, private attributes are never accessible, unless you’re inside the same class, in that case you can access them without any problems. So all we have to do is configure our text as accessible.

1 MyClass instance = new MyClass();
2 instance.setText("Reflective Extraction");
3 Class<?> clazz = instance.getClass();
4 Field field = clazz.getDeclaredField("text"); // Accessing private attribute
5 field.setAccessible(true); // Setting as accessible
6 Object value = field.get(instance);
7 System.out.println(value);

And there we go, we can print our value without hassle 

TipThe AccessibleObject class has a convenience static method, also named setAccessible, but it receives an array of AccessibleObject and the boolean flag. This method can be used to set multiple Fields, Methods or Constructors as accessible all in one go. In case you want to set all the attributes in a class as accessible, you can just do this:

1 MyClass instance = new MyClass();
2 Class<?> clazz = instance.getClass();
3 Field[] fields = clazz.getDeclaredFields();
4 AccessibleObject.setAccessible(fields, true);

And in the same way we extract the value from an attribute, we can assign one too. We do that using the set method. When we invoke it we need to inform the instance we want to modify, and the value we want to set to the corresponding attribute.

1 MyClass instance = new MyClass();
2 Class<?> clazz = instance.getClass();
3 Field field = clazz.getDeclaredField("text");
4 field.setAccessible(true);
5 field.set(instance, "Reflective attribution");
6 System.out.println(instance.getText());

Actions, the Method class

Before we get started on the actual methods, I want to highlight, as I said before, that it is very similar to the Field and Constructorclasses, so these 3 may be obtained in the same way from our Class reference, which means that if we have a getDeclaredField method we also have a getDeclaredMethod and a getDeclaredConstructor (the same is true for the other methods). So they work exactly the same way and for that reason explaining them again would be pointless and time wasting.

So let’s get started, how can we invoke methods using Reflection?

A method is not like an attribute, so only it’s name isn’t enough, because we can have many different methods with the same name, which is called overloading. So to get a specific method we need to inform it’s name, and the list of arguments it receives. Let’s assume our class has the methods described below:

01 public void print(String text){
02     System.out.println(text);
03 }
04  
05 public void printHelloWorld(){
06     System.out.println("Hello World");
07 }
08  
09 public int sum(int[] values){
10     int sum = 0;
11     for(int n : values){
12         sum += n;
13     }
14     return sum;
15 }

To get the reference to these methods, in the order they were declared in the example, we could just do this:

1 MyClass instance = new MyClass();
2 Class<?> clazz = instance.getClass();
3 Method print = clazz.getMethod("print", String.class);
4 Method printHelloWorld = clazz.getMethod("printHelloWorld");
5 Method sum = clazz.getMethod("sum"int[].class);

And to invoke them, we use the invoke method (coincidence?  )

The method’s signature is this:

1 Object invoke(Object obj, Object... args)

Which means, we need to inform the instance (target) on which the method will be invoked as well as the arguments that we need to pass to it, if any. Besides that it returns an Object, that will be the return of the method, whatever it may be. In case the method returns nothing (void), a call to invoke will return null.

For our 3 method references above the invocations would be like this:

1 print.invoke(instance, "I'm Mr. Reflection by now");
2 printHelloWorld.invoke(instance);
3 int sumValue = (int) sum.invoke(instance, new int[]{1410});
4 System.out.println(sumValue);

Tip: Again if we’re working with static methods, it’s not necessary to pass a valid instance, we can just do this:

1 staticMethod.invoke(null);

What about generic arguments?

Well, in that case we need to understand a little more about the language before we can invoke them. It’s necessary to know that generic types only exist in compilation time, and as Reflection runs in runtime, the generic types no longer exist, all of this because of a feature called Type Erasure, that was created to maintain backwards compatibility with previous Java versions.

Most likely, a method that receives a generic argument will receive Object, but there’s also another possiblity. If you declare a generic type like this: <T extends CharSequence>, it is possible that the method in runtime will receive a CharSequence, since it’s the most specific type possible, still remaining generic, so for this method:

1 public  print(T sequence){
2     System.out.println(sequence)
3 }

A Reflection invocation could look like this:

1 Method print = clazz.getMethod(print, CharSequence.class);
2 print.invoke(instance);

Creating instances the Constructor<T> class

As we all know, a constructor is not a method, even though it’s usage is very similar. It’s like we’re invoking a method, but with the newkeyword behind it, and also we only invoke it when we want to create a new instance of the class. This resemblance in usage causes a resemblance in it’s manipulation regarding Reflection. It so happens that instead of using invoke, we’re going to use the newInstancemethod, with a few differences.

We are creating an instance, so there’s no instance to associate to the constructor, therefore we don’t pass any as argument. But as we do with our  Methods, passamos somente a lista de argumentos.

The Class<T> has a newInstance method too, but it would only be useful if our class had an accessible constructor without arguments, in case there isn’t any, we need a direct reference to our Constructor<T>. Let’s define a few constructors in our example class:

01 public class MyClass{
02  
03     private String text;
04     private int number;
05  
06     public MyClass(){
07  
08     }
09  
10     public MyClass(String text){
11         this.text = text;
12     }
13  
14     public MyClass(String text, int number){
15         this.text = text;
16         this.number = number;
17     }
18  
19 }

Now let’s get each one of them using Reflection:

1 Class clazz = MyClass.class;
2 Constructor c1 = clazz.getConstructor();
3 Constructor c2 = clazz.getConstructor(String.class);
4 Constructor c3 = clazz.getConstructor(String.classint.class);

Now let’s create 3 instances, one from each constructor reference:

1 MyClass instance1 = c1.newInstance();
2 MyClass instance2 = c2.newInstance("text");
3 MyClass instance3 = c3.newInstance("other text"1);

Getting this far we can see that we can manipulate a class almost any way we want, listing it’s attributes, getting references to methods, altering it’s state, reading information, but we’re still missing one thing, that in my opinion, is too cool not to mention 

Metadata, the AnnotatedElement interface

When we need to check information related to annotations, we use the methods provided by the AnnotatedElement interface. FYI: the first class in the hierarchy to implement these methods is AccessibleObject, so all it’s subclasses will have access to them.

With annotations we can define information about our classes, it’s attributes and/or methods, without actually writing any execution code. The annotation will be processed in another time, in a way making development easier. So let’s get to it and write ourselves a small example:

We create 1 annotation named @NotNull, and whenever an attribute is annotated with it, it cannot hold the value null, ’nuff said. Let’s get to the code::

1 public class MyClass{
2  
3     @NotNull
4     private String text;
5  
6 }

Okay, so we defined this characteristic about our text attribute, but how are we going to effectively validate this rule? With our Validatorclass, like this:

01 public class Validator{
02  
03     public static void validate(Object target) throws IllegalArgumentException, IllegalAccessException{
04         Class<?> clazz = target.getClass();
05         Field[] fields = clazz.getDeclaredFields();
06  
07         for (Field field : fields){
08             validateField(field, target);
09         }
10     }
11  
12     private static void validateField(Field field, Object target) throws IllegalArgumentException, IllegalAccessException{
13         field.setAccessible(true);
14         Object value = field.get(target);
15         if (field.isAnnotationPresent(NotNull.class)){
16             if (value == null)
17                 throw new IllegalArgumentException("This attribute cannot be null");
18         }
19     }
20 }

So when we validate our objects, we can use this annotation to work for us, and our validator will check it, keeping the code in a single place, making it that much more maintainable. In some point of our code we’ll have a call like this:

1 Validator.validate(myClassInstance);

And we’re done. This technique is widely used when developing frameworks, and you can usually just check the documentation to see what each annotation would do, and use them accordingly.

Drawbacks, why shouldn’t I use Reflection?

I believe it was made clear that using reflection brings many benefits and conveniences for us in some situations, but as we all know, when all we have is a hammer, any problem looks like a nail, so don’t get all excited thinking about how to solve everything with Reflection, because it has it’s disadvantages:

  • Performance overhead: When using reflection, the JVM needs to do a lot of work to get all the information we need, do the dynamic invocations and all, so this carries a certain cost in processing time.
  • Runtime safety: In order to run Reflection code it is necessary to have a certain level of clearence inside the virtual machine, and you may not have that all the time, so keep that in mind when thinking about using it.
  • Model safety: Our attributes have different visibilities for a reason, right? And reflection can completely ignore them, doing whatever it wants, so it may cause some alerts in your encapsulation.

The basic rule is: Only use Reflection when there’s no easier alternative. If you can do something without reflection, you probably should. You should analyse on a case by case basis.

More information can be obtained in the Oracle tutorial.

I know there are other features when talking about Reflection, such as Proxies, but they are much more advanced and complex. I may write about them in a future post, but it’s out of the scope of this one, as it is only meant to be used as an introduction to the subject, or a refresher for those who already knew this, so bringing up advanced topics would do more harm than good.

分享到:
评论

相关推荐

    理解JavaScript中的Proxy 与 Reflection API

    JavaScript中的Proxy和Reflection API是两种强大的工具,它们允许开发者对对象的行为进行深度定制,从而实现更灵活和精细的控制。Proxy作为一个代理,可以拦截并控制对目标对象的访问,而Reflection API则提供了一组...

    reflectionAPI:Firefox ReflectionAPI

    git clone git@github.com:WhisperingChaos/reflectionAPI.git . git clone [binaryjail-restify]( )到'desktop / binaryjail' git clone git@github.com:binaryjail/binaryjail.git 当前在“桌面”中时,运行./...

    php-reflection:基于php-parserPHP文件的Nodejs Reflection API

    基于PHP文件的Nodejs Reflection API 安装 npm install php-reflection --save 用法 var reflection = require ( 'php-reflection' ) ; var workspace = new reflection . Repository ( '/home/devbox/my-project' ...

    JavaReflectionTuto:Java Reflection API入门教程

    在本教程中,我们将深入探讨Java Reflection API的基本概念、使用场景以及如何有效地利用它来增强你的Java应用。 1. **基础知识** - **类的加载与初始化**:反射API首先涉及到类的加载过程。Java虚拟机(JVM)在运行...

    ReflectionApiStudy:此回购旨在研究Java的Reflection Api

    ReflectionApiStudy 此存储库旨在研究Java的Reflection Api。 回购的工作方式: -每个想要将其研究保存在此存储库中的人都将创建一个带有名称/用户名的文件夹,并将所有研究保存在该文件夹中,例如:root -willmenn ...

    java-android-orm-db-example:该项目显示了使用某些设计模式和Java Reflection API的自定义ORM实现

    本项目“java-android-orm-db-example”是针对Android平台的一个自定义ORM实现,它巧妙地结合了设计模式和Java Reflection API,为开发者提供了更便捷的数据持久化方案。 首先,让我们深入了解一下ORM的基本概念。...

    Laravel开发-reflection

    在Laravel框架中,Reflection是一个重要的概念,它主要涉及到PHP的反射API(Reflection API)。反射是一种在运行时分析类、接口、方法等元数据的技术,它允许程序在执行过程中检查自身的行为和结构。在Laravel中,...

    reflection模拟JavaBean.rar

    在Java编程语言中,Reflection API是一个强大的工具,它允许程序在运行时检查和操作类、接口、字段和方法的信息。这个“reflection模拟JavaBean.rar”压缩包可能包含了一个示例项目,展示了如何使用反射机制来操作...

    PHP面向对象程序设计之类与反射API详解共5页.pdf

    在PHP中,反射API(Reflection API)是另一个关键工具,它允许我们在运行时检查类、接口、函数、方法、常量等元数据。反射API提供了一种动态获取和操作PHP代码结构的方式,这在编写元编程或复杂组件时非常有用。 类...

    JAVA_API_中文版帮助文档

    6. **反射**:Reflection API允许在运行时检查类的信息,如类名、方法、字段等,甚至可以动态创建对象和调用方法。 7. **泛型**:自Java 5引入,泛型提供了类型安全的集合,避免了强制类型转换,提高了代码的可读性...

    java_Api

    8. **Java Reflection API**:允许程序在运行时检查类、接口、字段和方法的信息,甚至修改它们的行为。 9. **Java安全管理器(Security Manager)**:提供了一种机制,用于限制代码在特定环境下的执行权限,增强了...

    JDK_API_1_6_zh_CN.CHM

    Reflection API允许程序在运行时动态地获取类的信息,并能创建对象、调用方法、访问字段,增强了程序的灵活性。 12. **注解(Annotation)** JDK 1.5引入的注解是一种元数据,它提供了在代码中添加信息的方式,...

    在Java中使用反射API的一个实例

    在Java编程语言中,反射API(Reflection API)是一种强大的工具,允许程序在运行时检查和操作其他类的信息,包括类的结构、字段、方法以及构造器。反射API使得程序员可以在未知类名或方法名的情况下,动态地创建对象...

    JMI规范API,java元数据

    JMI规范API是建立在Java反射API(Reflection API)基础之上,提供了一种更加系统化和模块化的访问元数据的方法。Java反射API允许我们在运行时检查类、接口、字段和方法的详细信息,但JMI API则更专注于元数据的查询...

    Java_Reflection_Programming.rar_Agent_java programming_反射

    首先,Reflection API是Java标准库中的一个关键部分,位于java.lang.reflect包下。它提供了一系列的类和接口,如Class、Constructor、Field和Method,用于在运行时访问和操作类的信息。通过Class对象,我们可以获取...

    mirror,Java反射API上的简单DSL层.zip

    Java反射API(Reflection API)是Java语言提供的一种强大工具,它允许程序在运行时检查类、接口、字段和方法的信息,甚至能够动态调用方法和修改对象状态。这个强大的特性使得Java开发者能够在不修改源代码的情况下...

Global site tag (gtag.js) - Google Analytics