8
Understanding and Using Reflection Summary: Reflection is a powerful language feature. Java supports ‘structural-reflection’, which is safe to use. With reflection, it is possible to do two basic types of operations on code: inspection and manipulation. Reflection enables using ‘signature based polymorphism’ as an alternative to ‘interface based polymorphism’ in Java. Reflection also facilitates creation of flexible and adaptable frameworks and patterns, which is not usually possible with direct code. Though there are many clear advantages in using reflection, like any other language feature, reflection can be misused. This article covers a few specific problems and solutions to illustrate uses and misuses of reflection. In programming, reflection is the ability to dynamically create, use or manipulate classes and objects at runtime. For example, you can create an instance of a class, examine the class to find a specific method from that class and execute that method with the arguments you specify, all this dynamically at runtime using reflection. Reflection is not a new concept. Smalltalk – one of the early and important OO programming language – supported reflection in 1970s itself. Reflection is possible only when sophisticated runtime support is available in the language implementations. C++ follows static compilation model where the code is compiled to native code and executed; the runtime support is limited. So reflection is not possible in C++. However, Java and C# have sophisticated runtime mechanisms (JVM for Java and CLR for .NET), so reflection can be supported in these languages. One of the misconceptions about reflection is that it is difficult to learn or use. By learning a few APIs and basic concepts, even beginners can write simple code using reflection. However, like any other language feature, effective use of reflection takes effort and considerable experience. The reflection feature supported by languages like Java and C# is known as ‘structural reflection’ where only inspection and execution of the code is allowed. There is another form of reflection more sophisticated than ‘structural reflection’, known as ‘behavioral reflection’, which allows us to modify or manipulate the code at runtime [1]. For strongly-typed, commercially widely used languages like Java, ‘structural reflection’ suffices. Reflection is a powerful feature and it is very useful for creating dynamic or adaptable design and for writing development tools. Reflection is also often misused by the programmers. In this article, after a short example for showing a how we write reflection code in Java, we’ll see the advantages, disadvantages, uses and misuses of reflection with focus on disadvantages and misuses of reflection. Reflection Example: Class Dissection

Understanding And Using Reflection

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Understanding And Using Reflection

Understanding and Using Reflection

Summary: Reflection is a powerful language feature. Java supports ‘structural-reflection’, which is safe to use. With reflection, it is possible to do two basic types of operations on code: inspection and manipulation. Reflection enables using ‘signature based polymorphism’ as an alternative to ‘interface based polymorphism’ in Java. Reflection also facilitates creation of flexible and adaptable frameworks and patterns, which is not usually possible with direct code. Though there are many clear advantages in using reflection, like any other language feature, reflection can be misused. This article covers a few specific problems and solutions to illustrate uses and misuses of reflection.

In programming, reflection is the ability to dynamically create, use or manipulate classes and objects at runtime. For example, you can create an instance of a class, examine the class to find a specific method from that class and execute that method with the arguments you specify, all this dynamically at runtime using reflection. Reflection is not a new concept. Smalltalk – one of the early and important OO programming language – supported reflection in 1970s itself. Reflection is possible only when sophisticated runtime support is available in the language implementations. C++ follows static compilation model where the code is compiled to native code and executed; the runtime support is limited. So reflection is not possible in C++. However, Java and C# have sophisticated runtime mechanisms (JVM for Java and CLR for .NET), so reflection can be supported in these languages. One of the misconceptions about reflection is that it is difficult to learn or use. By learning a few APIs and basic concepts, even beginners can write simple code using reflection. However, like any other language feature, effective use of reflection takes effort and considerable experience. The reflection feature supported by languages like Java and C# is known as ‘structural reflection’ where only inspection and execution of the code is allowed. There is another form of reflection more sophisticated than ‘structural reflection’, known as ‘behavioral reflection’, which allows us to modify or manipulate the code at runtime [1]. For strongly-typed, commercially widely used languages like Java, ‘structural reflection’ suffices. Reflection is a powerful feature and it is very useful for creating dynamic or adaptable design and for writing development tools. Reflection is also often misused by the programmers. In this article, after a short example for showing a how we write reflection code in Java, we’ll see the advantages, disadvantages, uses and misuses of reflection with focus on disadvantages and misuses of reflection. Reflection Example: Class Dissection

Page 2: Understanding And Using Reflection

We’ll see small sample program to understand how we can use reflection in Java. Java’s java.lang.Class encapsulates the class that is the basic execution unit of the Java language. This class deals with Java class representation as a whole, and the classes in the java.lang.reflect package deal with the parts of this class, such as methods and fields.Let us consider an example that dissects a class using reflection (with the output of the program shown in comments):

class Reflect {public static Constructor [] constructors;public static Method [] methods;public static Field [] fields; public static Class thisClass; static {

try {thisClass = Class.forName("Reflect");// dissect and see this class itself!

}catch(ClassNotFoundException cnfe) {

System.out.println("class doesn't exist " + cnfe);System.exit(-1);

}constructors = thisClass.getConstructors();methods = thisClass.getMethods();fields = thisClass.getFields();

}

public static void main(String []args) {System.out.println("This class is "+ thisClass);for(int i = 0; i < constructors.length; i++)

System.out.println("Ctor "+ i + " " + constructors[i]);for(int i = 0; i < methods.length; i++)

System.out.println("Method " + i + " " + methods[i]);for(int i = 0; i < fields.length; i++)

System.out.println("Field " + i + " " + fields[i]);}

} // output: // This class is class Reflect// Method 0 public static void // Reflect.main(java.lang.String[])// Method 1 public native int java.lang.Object.hashCode()// Method 2 public final native java.lang.Class

Page 3: Understanding And Using Reflection

// java.lang.Object.getClass()// Method 3 public final void // java.lang.Object.wait(long,int) throws // java.lang.InterruptedException// Method 4 public final void java.lang.Object.wait()// throws java.lang.InterruptedException// Method 5 public final native void java.lang.Object.wait(long) // throws java.lang.InterruptedException// Method 6 public boolean // java.lang.Object.equals(java.lang.Object)// Method 7 public java.lang.String java.lang.Object.toString()// Method 8 public final native void java.lang.Object.notify()// Method 9 public final native void // java.lang.Object.notifyAll()// Field 0 public static java.lang.reflect.Constructor[] // Reflect.constructors// Field 1 public static java.lang.reflect.Method[] // Reflect.methods// Field 2 public static java.lang.reflect.Field[] // Reflect.fields// Field 3 public static java.lang.Class Reflect.thisClass This code is designed to peek into its own .class file and see what constructors, methods and fields are available inside. The whole code can be written within the main method itself, but this slightly contrived example has fields and a constructor for illustration. Since all the classes inherit from Object the methods of that class are also shown in the output.

Advantages of Reflection

1) Signature-Based Polymorphism Java and C# programmers are familiar with polymorphism based on interface inheritance. Reflection provides an alternative where we can invoke methods having same signature, from different classes not have a common interface (which is required in interface based polymorphism). For example, we can implement dynamic observer pattern or command pattern (dynamic design patterns) which invoke a method based on same signature from classes not related to each other by interface inheritance.

2) Inspecting and Manipulating Classes Writing direct code is based on depending on specific class details (like interface, method or field names) known at compile-time. If we need to write code that depends on class details known only at runtime, then reflection comes handy.

Page 4: Understanding And Using Reflection

3) Creating Adaptable and Flexible SolutionsBecause of its dynamic nature, reflection is useful for creating adaptable and flexible software that is often not possible with direct code. Theoretically, it is well known that reflection can help add more dynamism and flexibility to frameworks and design patterns. For example, it is possible to write code for factory design pattern by loading the classes using reflection instead of writing fixed code depending on specific static types with if else statements inside the factory method.

4) Writing Tools that Require Implementation Details Many of the developer tools need access to internal or implementation details of the code. For example, an intelligent text editor can query a class using reflection and get details about its implementation, which can be very helpful. When user types in an object name and a ‘.’ to access a member, the text editor can pop with a list box with list of accessible members (obtained using reflection) that the programmer can select from. Since reflection programmatic access to the implementation details of the class, it is very useful for writing program development tools.

Problems with ReflectionThough there are various benefits in using reflection for adaptable and flexible software, reflection has many problems.

1) Exposes implementation details. Use of reflection essentially exposes much of implementation details – such as the methods, fields, accessibility and other metadata – of the class used. Access to low-level detail through reflection is a necessity for implementing low-level programming tools. But, when using reflection for solving higher-level problems (such as providing dynamic implementations of design patterns), accessing, using and depending on implementation details of classes is not required. One of the major objectives of object oriented programming is information hiding, where only the relevant higher level details of the class that the programmer needs to know are exposed to the users, and low-level implementation details are hidden. Reflection exposes such low-level implementation details of the class to the program.

2) Performance

The code that is reflective is slow that the direct code that performs the same functionality. Reflection can be order of times (10 to 30 times) slower than the direct code. Reflection performance also depends on the kind of operations are done in the code (is it creating objects, accessing members, making method calls, etc). If the main logic uses reflection or if the hot-spot function uses reflection,

Page 5: Understanding And Using Reflection

then we can feel the effect of reflection on application performance. If the reflection code is used sparingly in the application or if the methods using reflection code are rarely invoked, then reflection might not affect application performance.

3) Code Complexity

The reflective code is more complex and harder to understand the direct code that performs the same functionality. There are three main problems that affect the readability and maintainability of the code using reflection.

1) Obscures Logic

The use of reflection APIs obscures the underlying logic. In direct code for achieving the same functionality, the logic will be straight-forward to understand. However, since all the basic operations in reflection should be done using reflection APIs, the logic gets lost in the maze of API calls.

For example, creating an object and invoking a method in that is small and trivial in direct code. For achieving the same using reflection, the class has to be loaded, an object has to be created by programmatically figuring out the correct constructor and pass the necessary arguments, get the meta-data of the class and get the Method object from that class, and then invoke that method on the object by passing necessary arguments. All this requires multiple lines of code and a few exception handlers to ensure that the code doesn’t crash in case if something goes wrong in this process.

2) Missing Static Checks

The compiler statically checks the program when code is compiled. For example, the compiler ensures that only valid methods are called based on the static type of the object (to ensure type-safety). However, for equivalent reflective code, the compiler only checks of the reflection APIs are statically correct or not. All the checks involved in actually performing the operation using reflection APIs should be done by the programmer to ensure that the program is type-safe and that we are not violating any semantic rules.

For example, if we attempt to invoke bar() method on an object of Object type created using reflection, a runtime exception will be thrown that the method is not found in Object class. Similarly, if you try to access a private member from outside the class, an exception indicating access violation will be thrown at runtime. These two problems would have been caught at compile-time itself in direct code.

Consider another example where you’re invoking a method of a specific class using reflection. If the signature of the method changes, then all the code that depends

Page 6: Understanding And Using Reflection

on that method will fail to compile. However, the code that invokes the method using reflection will not fail at compile time, but it will fail only at runtime.

3) More Exception Handlers

The static checks done by a compiler in static code have to be done by the programmers using exception handling code. So, programmers have to provide more exception handlers in reflective code than the direct code. Providing more exception handling code increases implementation/testing effort and complicates application logic, so there is more possibility of making mistakes in code.

Because of these reasons, it is more difficult to maintain or debug programs using reflection compared to the direct code that does not use reflection.

4) Security

Programmers often cite security as a reason for not using reflection in their code. It is true that there are some security concerns in using reflection, particularly in component and internet programming where the code might be from un-trusted sources. However, in most cases, the security concerns are misguided. The reflection code is still executed under the same watchful eyes of the Java security manager, like any other code. So, just using reflection in the code doesn’t affect the safety of the code or make the code unsafe.

Having stated that, reflection does provide some limited ways to circumvent many of the checks that are usually statically checked. For example, in Java, private members are accessible only to the class members. If we attempt to access a private member from code outside the class, it will result in an exception. But by setting the suppressAccessChecks method (provided in java.lang.reflect.ReflectPermission class), access checks can be switched off for that class. So, you can access all members from outside the class using reflection, which has potential for un-secure access to internal data of a class or an object.

For more details on security and performance problems with reflection, see [2].

Use and Misuse of Reflection

Like any other language feature, reflection can be misused. There are two broad categories in which reflection is often misused. In low-level programming (code that operates at .class file levels), reflection should not be used when better alternatives are available. Similarly, when it is possible to provide a solution with high-level language features, that should be preferred over reflective solution.

The use of reflection is warranted if there are no other alternative solutions available to solve the problem. For example, in many programming tools, the specific

Page 7: Understanding And Using Reflection

class names or details of the classes will not be known at compile-time itself. In those cases, it is necessary to use reflection.

There are other cases where there are alternatives available, but not attractive. For example, say we are implementing a Java code browser tool which shows relationship between classes and shows members in the classes for quick reference. A requirement for that tool is that it should work at the bytecode level by reading from the Java .class files. There are two main ways to implement the functionality to analyze the code from the .class files. One is to decompile it and read the class and member details and then use that information to analyze the classes. Another way is to use reflection and infer the class details. We don’t need the sophistication or the implementation complexity of a decompiler, for the simple work of showing class member details. So, using reflection is a better alternative for writing the tool.

Depending on the situation in which reflection is used, reflection can be a valid use or misuse. Consider the example of signature-based polymorphism using reflection. Consider that we are developing a GUI software. It has subsystems from various sources like third-party, open-source and in-house. Consider that these classes have a common method draw() which draws the specific image on the display window. Since these subsystems are from different sources, they do not inherit from a common interface class. So, how can you write code to invoke draw method for different classes?

One possible solution is to write code to make use of the dynamic type of the object. Check for specific class types in nested if-else statements and provide casts to downcast the object to specific types. Then we can apply the draw method on the specific types. You can easily spot the problem with this approach: switching based on specific dynamic types is a strict no-no in object oriented programming as the code becomes rigid and depends on specific types.

Reflection comes handy in this situation: we can inspect the classes at runtime to see if it has draw() method in it. If so, we can invoke the method with the given object to draw the image on the display window. In practice this solution works because the only requirement is that the method with specific signature should be available in the class and it can be resolved and invoked using reflection. Since the code is from different sources, compared to other solutions, this solution using reflection is attractive. This example illustrates the proper use of reflection.

Now suppose that we have the same problem to solve but all the sub-systems are developed in-house. Now, we have the option to modify the classes that have draw method to implement from a common interface, say, Drawable. Now we can write code to cast the objects back to Drawable type and call draw method from that. This solution works fine and solves the problem. Instead of this solution, if we use reflection, then it is incorrect. Since there are many problems associated with

Page 8: Understanding And Using Reflection

using reflection, it is better to avoid reflection in this case and use interface-based polymorphism instead which is a direct solution.

Joshua Block, in his book [3], presents the discussion on using interfaces vs. reflection concisely as ‘Prefer interfaces to reflection’.

Reflection can be very useful when it comes to implementing tools or technologies that involve the implementation details. For example, tools such as code debuggers, dis-assemblers, class browsers, profilers, code obfuscators and technologies such as JavaBeans, JDBC drivers etc make use of reflection facility to completely access the internal details of the classes to provide dynamically changeable behavior. It is acceptable to use reflection in such tools and technologies; but reflection is not for performing usual tasks that can be done with higher level programming features in application code.

Reflection is also a convenient feature that it becomes tempting for a programmer to use. As noted in the disadvantages of reflection section in this article, reflection can negatively impact readability and maintainability of the program. So, overuse of reflection is discouraged, and it is better to limit the use of reflection only when it is required and no other better alternatives are available.

Bill Wegner, in his book[4], puts this guideline concisely as, ‘Don't overuse reflection’. Egbert Althammer, based on his research on reflection[1], gives one of his guidelines as: ‘Use reflection only if there are no adequate alternatives’.

References:

[1] Egbert Althammer, "Reflection Patterns in the Context of Object and Component Technology", PhD thesis, Universität Konstanz, 2001. URL: http://www.ub.uni-konstanz.de/kops/volltexte/2002/803/

[2] Dennis Sosnoski, ‘Java programming dynamics, Part 2: Introducing reflection’, developerWorks, 2003. URL: http://www.ibm.com/developerworks/library/j-dyn0603/

[3] Joshua Bloch, Effective Java Programming Language Guide, Addison-Wesley Professional, 2001.

[4] Bill Wagner, "C#:50 Specific Ways to Improve Your C#", Addison-Wesley Professional, 2004.

All rights reserved. Copyright Jan 2004