Sie sind auf Seite 1von 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
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
// 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.
3) Creating Adaptable and Flexible Solutions
Because 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 Reflection


Though 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,
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
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
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
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

Das könnte Ihnen auch gefallen