Better Faster Lighter Java [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Better Faster Lighter Java [Electronic resources] - نسخه متنی

Justin Gehtland; Bruce A. Tate

نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
افزودن یادداشت
افزودن یادداشت جدید








4.4 Reflection


The most accessible way to build a



service that depends on model data is
runtime reflection. I
don't know why Java developers never have embraced
runtime reflection the way that other languages have. It tends to be
used by tool developers and framework providers but not general
application programmers. You don't have to use
reflection everywhere, nor should you try. Instead, apply a little
reflection where it can have a tremendous impact. Here are some
things to keep in mind:

General needs versus specific needs


When you need to access an object's features in a
general way, use reflection. For example, if you're
moving the color field from an object to a buffer for transport, and
you don't ever use the field as a color, consider
reflection for the task, in order to reduce coupling. If
you're reading the color and setting other objects
to the same color, direct property accesses might be best.


Delaying decisions


If you don't know the name of a method or class
until runtime, consider reflection. If you already know the name,
there's no reason to delay the decisiona
simple method call is a better choice. For example, through
configuration, you can frequently decouple code and delay decisions
until runtime. Reflection gives you a tool to help this happen.



Bear in mind that although the performance of reflection has improved
in recent years, postponing binding decisions until runtime has a
performance cost. Jumping to a specifically named method or property
is much faster than going through the reflection API by a factor of
two or more. Reflection offers great power, but be judicious.


4.4.1 The Reflection API


The Java reflection API lets you

access
all of the elements that make up a class at runtime without requiring
source code or any other advanced knowledge of the classes. Using
reflection, you can:






Access a class's definition


When you declare a class, you specify a

class name and a set of
modifiers (like synchronized, static, and public).


Get all field definitions in a class


You can get the names, types, and modifiers for all of the fields in
a class.


Get all of the method definitions in a class


You can get the names, return types, parameters and types, and
modifiers for all of the methods in a class.


Get the parent class


Of course, since you can get a


superclass, you can get all
of the indirect methods and fields as well.


Access an instance's fields


You can read or write directly
to the
fields or utilize any getters and setters the instance might expose.


Call an instance's methods


Using reflection, you can also call methods on an instance.



In short, you can learn anything that you need to know about a class
and directly manipulate an instance of that class. If you want to
build a service that's independent of the structure
of an object, that's a powerful combination of
tools. You can inspect any method or field. You can load a class,
call a method, or get the data from a field without knowing anything
about the class in advance.

Further, reflection works well with


a passive model because through
reflection, the model already has all of the information that a
potential service might need. That service can accept the whole model
(or one class from the model) as input and use reflection to extract
the data to do something useful, such as serialize the data to XML
(like Castor), save it to a database (like Hibernate), or even wire
together behaviors and properties (like Spring).

When you use the reflection framework, you must import the reflection
libraries:

import java.lang.reflect.*;

The java.lang.reflection package



contains
everything you need, including four major classes:
Class, Constructor,
Field, and Method. These
classes let you deal with each major element of a Java class.
Java's runtime architecture makes it possible.

If you're like most Java developers, you probably
deal much more with an instance of an object rather than the class
object, but the class object is an integral part of
Java's runtime architecture. It contains the basic
DNA for a class. It's used to create classes and
serves as an attachment point for static methods and members. It
keeps track of its parent, to manage inheritance.
You've probably used classes in these ways, but
it's also the tool that enables reflection. Figure 4-5 shows a class instance at runtime. Each object
has an associated class, which is the DNA for a class that determines
its type. The class is the central entry point for the Java
reflection API. Using it, you can access the fields, methods, and
constructors for any class. You can also invoke methods and access
the data for an individual instance of the class. The rectangles in
grey represent the Java reflection framework.



Figure 4-5. All Java objects have an associated class


4.4.2 Accessing a Class


The class is the entry point


for reflection. Once you have
the class, you can call specific methods to get the associated
methods, constructors, and fields. Using the class, you can get a
single method or field by name, or get an array of all of the
supported fields or methods.

You can get the class in several ways. Sometimes, you
don't have an instance. For example, if
you're working on a factory, you might have access
to only the name of a class. In that case, load the class like so:

Class c = Class.forName(aString);

Other times, you might have nothing more than an object. In that
case, you'd get the class from the instance, like
this:

Class cls = obj.getClass( );

It's actually not quite that simple. But for now,
you just need to understand that Java supports more than one class
loader (though the architecture for doing so changed for Version 1.2
and beyond). Chapter 6 fills in the details
about class loading.


4.4.3 Accessing Fields


You're probably ready to


see a method
that's a little more concrete.
Let's use reflection to build a transparent service.
Assume that you need a service to emit XML for a given object.
Further, you want the model to remain transparent to the service, so
you won't need to change any model code to support
new objects. Given a target object, you're going to
attack the problem using reflection:

Get the class for the target object.

Get the declared fields from the class.

Get the value for each field.

If the object is primitive, emit the appropriate XML.


I'll show you the code all together, and then
we'll go through it in pieces.
Here's the entire method to process an object:

  public static void doObject(Object obj) throws Exception {
[1] Class cls = obj.getClass( );
emitXMLHeader(cls);
[2] Field[] fields = cls.getDeclaredFields( );
for (int i=0; i < fields.length; i++) {
Field field = fields[i];
[3] field.setAccessible(true);
[4] Object subObj = field.get(obj);
[5] if (!Modifier.isStatic(field.getModifiers( ))) {
if ((field.getType( ).isPrimitive( )) ||
((field.getType( ).getName( ) == "java.lang.String"))) {
[6] emitXML(field.getName( ), subObj);
} else {
[7] doObject(subObj);
}
}
}
emitXMLFooter(cls);
}

That's it. Let's see how the
individual pieces work.

[1] First, you've got to get the class
object, given an instance. You need the class to emit the XML tags
that bracket an entire object. You'll also get
individual fields from the class. Since you're
getting the class from a known instance, you use this method.[2] Next, the class must declare the fields. The
Class object lets you use several methods to
access its fields. At times, you'll want to get a
specific field, given the name of a property. You can do so with
getField(String name),
a method on class. Sometimes, you want to get only the declared
fields for a class. Do this with getDeclaredFields(
)
, which returns an array of fields. Other times, you also
want to get inherited fields. You can do this with
getFields( ), which returns an array of all fields
declared in a class and its parents. In this case,
you'll get the declared fields, get back an array,
and iterate over the array. [3]Often, your service will need to access fields with a
narrow scope, such as private fields. You wouldn't
want to always package such a service with your model, so the
Field class lets you step outside of the scoping
rules for convenience. In this example, you'll want
to make sure that the field is accessible, even if
it's a private field. To do so, simply call
setAccessible(true) on the field. [4]Access the field's value by calling the
get( ) method on your field object, to get the
value for the individual field. [5]Look at the modifiers to see whether the field is
primitive or static. If it's primitive, emit the
XML. If it's static, you'll want to
skip it, because it's attached to the class instead
of your object. The reflection API encodes all of the modifiers
within an integer, so they'll take up less space. In
order to read them, use a helper class called
Modifier to check if a modifier applies to your
class. You can access any modifier on a class, field, method, or
constructor in this way. [6]If it's primitive, emit the appropriate
XML and complete the XML for the class. [7]If it's not a primitive, call the method
doObject again, this time with the field value.

The bulk of the work is done within the doObject
method, as it should be. The code to emit the XML is surprisingly
simple. Here are the methods to emit XML for the overall class, and
for a field:

public static void emitXML(String name, Object value) {
System.out.println("<" + name + ">");
System.out.println(value.toString( ));
System.out.println("</" + name + ">");
}
public static void emitXMLHeader(Class cls) {
System.out.println("<"+cls.getName( )+">");
}
public static void emitXMLFooter(Class cls) {
System.out.println("</"+cls.getName( )+">");
}

The nuts and bolts are all there for an XML emitter.
You've probably noticed that I cheated and handled
only the simplest case. In fact, you'll need to
handle at least four types of fields for a general-purpose emitter:










Primitives


With reflection, you


deal with everything as
an object. Since primitives are not objects, reflection wraps them in
type wrappers. For example, to wrap the int 37 in a wrapper,
you'd say Integer
intWrapper =
new Integer(37). To get the
value from a wrapper, call a method on the Integer
class, like intWrapper.intValue( ).


Objects


If a field value is not an


array or primitive,
it's an object. You can deal with other classes
recursively. Get the class from the object and iterate through its
fields.


Arrays


Reflection uses a special


class to wrap arrays called
Array. This class lets you access the type of the
array and also provides access to each individual element of the
instance.


Special classes


Generally, you're going


to want to treat some classes
differently than others. For example, you may want special treatment
for strings or collections.



We've only handled the first two types of fields,
plus strings, but you can see how reflection works.
You've supported a surprising number of classes
without needing to alter model code at all. I must note that the
emitter we've constructed here, though generic and
useful, is not a full implementation. For a truly generalized
emitter, our class would have to be able to handle circular
references between classes, optional omission of referenced classes,
logically transient fields, and some kind of optional
name-substitution mapping pattern. Regardless, the point is no less
salient: reflection can provide an enormous amount of power without
any tight coupling.

You've seen how many transparent services use
reflection: they simply access a list of properties, recursively if
needed, and do the appropriate service. The types of services are
unlimited:

Hibernate, a persistence framework discussed in Chapter 7, looks at the value of your model before
and after you change it, and then generates SQL based on your
mappings to save the changes to a database.

Spring, a lightweight container discussed in Chapter 8, populates fields in your objects based on
a configuration file to wire your target objects to services.

XML emitters like Castor scan an object's fields
recursively to emit XML.

Distributed messaging services can use reflection to scan an
object's fields so that they can store compound
objects without depending on a memory address.


So far, I've only told you how to deal with data.
Fortunately, the reflection API also makes it easy to deal with
behavior.


4.4.4 Accessing Methods and Constructors


You can use reflection to




examine and execute methods. You can
access methods through java.lang.reflection.Method
and constructors through
java.lang.reflection.Constructor.
I'll describe the way that methods work;
you'll find constructors work the same way.

As with fields, you can use Class to access
methods in two ways: getMethods( ) returns an
array with all supported methods, and getDeclaredMethods(
)
returns only the declared methods for a class. You
t

hen can access the parameter types, modifiers,
and return value from the Method class.

Here's an example that prints all of the declared
methods in a class. It also prints out the types of each parameter,
and the return value:

  public static void printMethods(Object obj) throws Exception {
Class cls = obj.getClass( );
[1] Method[] methods = cls.getDeclaredMethods( );
for (int i=0; i < methods.length; i++) {
Method method = methods[i];
[2] System.out.println("Method name:" + method.getName( ));
[3] Class parmTypes[] = method.getParameterTypes( );
for (int j = 0; j < parmTypes.length; j++) {
System.out.print(" Parameter " + (j+1) + " type:");
System.out.println(parmTypes[j]);
}
System.out.println(" Returns: "+method.getReturnType( )+"\n");
}
}

Here's what the annotations indicate:


[1] As with the field example, you'll use
the class object to return all of the declared methods for the class.

[2] Once you have a method, you have access to its name and
type.

[3] You can also access each of the parameters. This example
simply iterates through them to print their types.

As you can see, inspecting methods works a whole lot like inspecting
fields. All that remains is to invoke a method.


4.4.4.1 Invoking a method


Often, you'll want to


invoke a method or constructor
without knowing all of the details until runtime, such as configuring
an object from a file. To do so, you'll need several
things:

The name of the method


Remember, that's not enough to identify a method in
Java.


The types of parameters


You'll also need an array with the parameter types,
because two methods with different signatures can share the same
name.


The parameter values


You'll need to build an array of parameters. If a
parameter is an object, you'll place it in the array
directly.

If a parameter is a primitive or array, you'll need
to wrap it first. For example, call new
Integer(59
) to wrap a primitive integer. To wrap an array,
you wrap it in an instance of Array. For example, to wrap an array of
five Integers, a single parameter would look like
wrappedArray below:

int a[]={1,2,3,4,5);
Object wrappedArray = Array.newInstance(Integer.TYPE, a);


The return type


The invocation returns an object or nothing at all.
You'll need to cast it to the appropriate type.



Here's the code to invoke a method called sum on
class Adder that takes two int
parameters and returns an Integer:

// target object is called "target"
Class c = Class.forName("Adder");
Class parameterTypes[] = new Class[2];
parameterTypes[0] = Integer.TYPE;
parameterTypes[1] = Integer.TYPE;
Method m = c.getMethod("sum", parameterTypes);
Object parms[] = new Object[2];
parms[0] = new Integer(1);
parms[1] = new Integer(1);
Integer returnValue = (Integer)m.invoke(target, parms);

That's really the bulk of working with reflection.
Compared to a simple method invocation or a simple field access, it
does not look simple. When you consider the overall impact, though,
the effort makes a huge difference.


/ 111