The class java.lang.Class
, and the reflection package java.lang.reflect
, provide a number of mechanisms for gathering information from the Java Virtual Machine. Known collectively as reflection, these facilities allow you to load classes on the fly, to find methods and fields in classes, to generate listings of them, and to invoke methods on dynamically loaded classes. There is even a mechanism to let you construct a class from scratch (well, actually, from an array of bytes) while your program is running. This is about as close as Java lets you get to the magic, secret internals of the Java machine.
The JVM itself is a large program, normally written in C and/or C++, that implements the Java Virtual Machine abstraction. You can get the source for OpenJDK and other JVMs via the Internet, which you could study for months. Here we concentrate on just a few aspects, and only from the point of view of a programmer using the JVM’s facilities, not how it works internally; that is an implementation detail that could vary from one vendor’s JVM to another.
I’ll start with loading an existing class dynamically, move on to listing the fields and methods of a class and invoking methods, and end by creating a class on the fly using a ClassLoader
. One of the more interesting aspects of Java, and one that accounts for both its flexibility (applets in days of your, servlets, web services, and other dynamic APIs) and was once part of its perceived speed problem, is the notion of dynamic loading. For example, even the simplest “Hello Java” program has to load the class file for your HelloJava
class, the class file for its parent (usually java.lang.Object
), the class for PrintStream
(because you used System.out
), the class for PrintStream
’s parent, and IOException
, and its parent, and so on. To see this in action, try something like:
java -verbose HelloJava | more
To take another example, when applets were popular, a browser would download an applet’s bytecode file over the Internet and run it on your desktop. How does it load the class file into the running JVM? We discuss this little bit of Java magic in Recipe 17.4. The chapter ends with replacement versions of the JDK tools javap, and a cross-reference tool that you can use to become a famous Java author by publishing your very own reference to the complete Java API.
You want to get a Class
object from a class name or instance.
If the type name is known at compile time, you can get the class instance using the compiler keyword .class
, which works on any type that is known at compile time, even the eight primitive types.
Otherwise, if you have an object (an instance of a class), you can call the java.lang.Object
method getClass()
, which returns the Class
object for the object’s class (now that was a mouthful!):
System
.
out
.
println
(
"Trying the ClassName.class keyword:"
);
System
.
out
.
println
(
"Object class: "
+
Object
.
class
);
System
.
out
.
println
(
"String class: "
+
String
.
class
);
System
.
out
.
println
(
"String[] class: "
+
String
[].
class
);
System
.
out
.
println
(
"Calendar class: "
+
Calendar
.
class
);
System
.
out
.
println
(
"Current class: "
+
ClassKeyword
.
class
);
System
.
out
.
println
(
"Class for int: "
+
int
.
class
);
System
.
out
.
println
();
System
.
out
.
println
(
"Trying the instance.getClass() method:"
);
System
.
out
.
println
(
"Sir Robin the Brave"
.
getClass
());
System
.
out
.
println
(
Calendar
.
getInstance
().
getClass
());
When we run it, we see:
C:javasrc eflect>java ClassKeyword Trying the ClassName.class keyword: Object class: class java.lang.Object String class: class java.lang.String String[] class: class [Ljava.lang.String; Calendar class: class java.util.Calendar Current class: class ClassKeyword Class for int: int Trying the instance.getClass( ) method: class java.lang.String class java.util.GregorianCalendar C:javasrc eflect>
Nothing fancy, but as you can see, you can get the Class
object for almost anything known at compile time, whether it’s part of a package or not.
You need to find arbitrary method or field names in arbitrary classes.
Use the reflection package java.lang.reflect
.
If you just wanted to find fields and methods in one particular class, you wouldn’t need this recipe; you could simply create an instance of the class using new
and refer to its fields and methods directly. But this allows you to find methods and fields in any class, even classes that have not yet been written! Given a class object created as in Recipe 17.1, you can obtain a list of constructors, a list of methods, or a list of fields. The method getMethods()
lists the methods available for a given class as an array of Method
objects. Similarly, getFields()
returns a list of Field
objects. Because constructor methods are treated specially by Java, there is also a getConstructors()
method, which returns an array of Constructor
objects. Even though Class
is in the package java.lang
, the Constructor
, Method
, and Field
objects it returns are in java.lang.
reflect
, so you need an import of this package. The ListMethods
class (see Example 17-1) shows how get a list of methods in a class whose name is known at runtime.
public
class
ListMethods
{
public
static
void
main
(
String
[]
argv
)
throws
ClassNotFoundException
{
if
(
argv
.
length
==
0
)
{
System
.
err
.
println
(
"Usage: ListMethods className"
);
return
;
}
Class
<?>
c
=
Class
.
forName
(
argv
[
0
]);
Constructor
<?>[]
cons
=
c
.
getConstructors
();
printList
(
"Constructors"
,
cons
);
Method
[]
meths
=
c
.
getMethods
();
printList
(
"Methods"
,
meths
);
}
static
void
printList
(
String
s
,
Object
[]
o
)
{
System
.
out
.
println
(
"*** "
+
s
+
" ***"
);
for
(
int
i
=
0
;
i
<
o
.
length
;
i
++)
System
.
out
.
println
(
o
[
i
].
toString
());
}
}
For example, you could run Example 17-1 on a class like java.lang.String
and get a fairly lengthy list of methods; I’ll only show part of the output so you can see what it looks like:
> java reflection.ListMethods java.lang.String *** Constructors *** public java.lang.String( ) public java.lang.String(java.lang.String) public java.lang.String(java.lang.StringBuffer) public java.lang.String(byte[]) // and many more... *** Methods *** public static java.lang.String java.lang.String.copyValueOf(char[]) public static java.lang.String java.lang.String.copyValueOf(char[],int,int) public static java.lang.String java.lang.String.valueOf(char) // and more valueOf( ) forms... public boolean java.lang.String.equals(java.lang.Object) public final native java.lang.Class java.lang.Object.getClass( ) // and more java.lang.Object methods... public char java.lang.String.charAt(int) public int java.lang.String.compareTo(java.lang.Object) public int java.lang.String.compareTo(java.lang.String)
You can see that this could be extended (almost literally) to write a BeanMethods
class that would list only the set/get methods defined in a JavaBean
(see Recipe 15.4).
Alternatively, you can find a particular method and invoke it, or find a particular field and refer to its value. Let’s start by finding a given field, because that’s the easiest. Example 17-2 is code that, given an Object
and the name of a field, finds the field (gets a Field
object) and then retrieves and prints the value of that Field
as an int
.
public
class
FindField
{
public
static
void
main
(
String
[]
unused
)
throws
NoSuchFieldException
,
IllegalAccessException
{
// Create instance of FindField
FindField
gf
=
new
FindField
();
// Create instance of target class (YearHolder defined below).
Object
o
=
new
YearHolder
();
// Use gf to extract a field from o.
System
.
out
.
println
(
"The value of 'currentYear' is: "
+
gf
.
intFieldValue
(
o
,
"currentYear"
));
}
int
intFieldValue
(
Object
o
,
String
name
)
throws
NoSuchFieldException
,
IllegalAccessException
{
Class
<?>
c
=
o
.
getClass
();
Field
fld
=
c
.
getField
(
name
);
int
value
=
fld
.
getInt
(
o
);
return
value
;
}
}
/** This is just a class that we want to get a field from */
class
YearHolder
{
/** Just a field that is used to show getting a field's value. */
public
int
currentYear
=
Calendar
.
getInstance
().
get
(
Calendar
.
YEAR
);
}
What if we need to find a method? The simplest way is to use the methods getMethod()
and invoke()
. But this is not altogether trivial. Suppose that somebody gives us a reference to an object. We don’t know its class but have been told that it should have this method:
public void work(String s) { }
We wish to invoke work()
. To find the method, we must make an array of Class
objects,
one per item in the parameter list. So, in this case, we make an array containing only a
reference to the class object for String
. Because we know the name of the class at compile
time, we’ll use the shorter invocation String.class
instead of Class.forName()
. This,
plus the name of the method as a string, gets us entry into the getMethod()
method of
the Class
object. If this succeeds, we have a Method
object. But guess what? In order
to invoke the method, we have to construct yet another array, this time an array of
Object
references actually containing the data to be passed to the invocation. We also,
of course, need an instance of the class in whose context the method is to be run. For
this demonstration class, we need to pass only a single string, because our array consists only
of the string. Example 17-3 is the code that finds the method and invokes
it.
/**
* Get a given method, and invoke it.
* @author Ian F. Darwin, http://www.darwinsys.com/
*/
public
class
GetAndInvokeMethod
{
/** This class is just here to give us something to work on,
* with a println() call that will prove we got into it.
*/
static
class
X
{
public
void
work
(
int
i
,
String
s
)
{
System
.
out
.
printf
(
"Called: i=%d, s=%s%n"
,
i
,
s
);
}
// The main code does not use this overload.
public
void
work
(
int
i
)
{
System
.
out
.
println
(
"Unexpected call!"
);
}
}
public
static
void
main
(
String
[]
argv
)
{
try
{
Class
<?>
clX
=
X
.
class
;
// or Class.forName("X");
// To find a method we need the array of matching Class types.
Class
<?>[]
argTypes
=
{
int
.
class
,
String
.
class
};
// Now find a Method object for the given method.
Method
worker
=
clX
.
getMethod
(
"work"
,
argTypes
);
// To INVOKE the method, we need the invocation
// arguments, as an Object array.
Object
[]
theData
=
{
42
,
"Chocolate Chips"
};
// The obvious last step: invoke the method.
// First arg is an instance, null if static method
worker
.
invoke
(
new
X
(),
theData
);
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"Invoke() failed: "
+
e
);
}
}
}
Not tiny, but it’s still not bad. In most programming languages, you couldn’t do that in the 40 lines it took us here.
A word of caution: when the arguments to a method are of a primitive type, such as int
, you do not pass Integer.class
into getMethod()
. Instead, you must use the class object representing the primitive type int
. The easiest way to find this class is in the Integer
class, as a public constant named TYPE
, so you’d pass Integer.TYPE
. The same is true for all the primitive types; for each, the corresponding wrapper class
has the primitive class
referred to as TYPE
.
Java also includes a mechanism called a MethodHandle
which was
intended both to simplify and to generalize use of Reflection to invoke methods;
we do not cover it here because in practice it has not shown
to be a significant improvement over using the Reflection API.
You want to access private fields and have heard you can do so using the Reflection API.
You bad kid, you! You’re not supposed to go after other classes’ private parts. But if you have to, and the SecurityManager allows you to use Reflection, you can.
There is occasionally a need to access private fields in other classes.
For example, I did so recently in writing a JUnit test case that needed to see all
the fields of a target class.
The secret is to call the Field
or Method
descriptor’s setAccessible()
method
passing the value true
before trying to get the value or invoke the method.
It really is that easy, as shown in Example 17-4.
class
X
{
@SuppressWarnings
(
"unused"
)
// Used surreptitiously below.
private
int
p
=
42
;
int
q
=
3
;
}
/**
* Demonstrate that it is, in fact, all too easy to access private members
* of an object using Reflection, using the default SecurityManager
*/
public
class
DefeatPrivacy
{
public
static
void
main
(
String
[]
args
)
throws
Exception
{
new
DefeatPrivacy
().
process
();
}
private
void
process
()
throws
Exception
{
X
x
=
new
X
();
System
.
out
.
println
(
x
);
// System.out.println(x.p); // Won't compile
System
.
out
.
println
(
x
.
q
);
Class
<?
extends
X
>
class1
=
x
.
getClass
();
Field
[]
flds
=
class1
.
getDeclaredFields
();
for
(
Field
f
:
flds
)
{
f
.
setAccessible
(
true
);
// bye-bye "private"
System
.
out
.
println
(
f
+
"=="
+
f
.
get
(
x
));
f
.
setAccessible
(
false
);
// reset to "correct" state
}
}
}
Use this with extreme care, because it can defeat some of the most cherished principles of Java programming.
You want to load classes dynamically, just like web servers load your servlets.
Use class.forName("ClassName");
and the class’s newInstance( )
method.
Suppose you are writing a Java application and want other developers to be able to extend your application by writing Java classes that run in the context of your application. In other words, these developers are, in essence, using Java as an extension language, in the same way that applets are an extension of a web browser. You would probably want to define a small set of methods that these extension programs would have and that you could call for such purposes as initialization, operation, and termination. The best way to do this is, of course, to publish a given, possibly abstract, class that provides those methods and get the developers to subclass from it. Sound familiar? It should. This is just how web browsers such as Netscape allow the deployment of applets.
We’ll leave the thornier issues of security and of loading a class file over a network socket for now, and assume that the user can install the classes into the application directory or into a directory that appears in CLASSPATH at the time the program is run. First, let’s define our class. We’ll call it Cooklet
(see Example 17-5) to avoid infringing on the overused word applet. And we’ll initially take the easiest path from ingredients to cookies before we complicate it.
/** A simple class, just to provide the list of methods that
* users need to provide to be usable in our application.
* Note that the class is abstract so you must subclass it,
* but the methods are non-abstract so you don't have to provide
* dummy versions if you don't need a particular functionality.
*/
public
abstract
class
Cooklet
{
/** The initialization method. The Cookie application will
* call you here (AFTER calling your no-argument constructor)
* to allow you to initialize your code
*/
public
void
initialize
(
)
{
}
/** The work method. The cookie application will call you
* here when it is time for you to start cooking.
*/
public
void
work
(
)
{
}
/** The termination method. The cookie application will call you
* here when it is time for you to stop cooking and shut down
* in an orderly fashion.
*/
public
void
terminate
(
)
{
}
}
Now, because we’ll be baking, err, making this available to other people, we’ll probably want to cook up a demonstration version too; see Example 17-6.
public
class
DemoCooklet
extends
Cooklet
{
public
void
work
()
{
System
.
out
.
println
(
"I am busy baking cookies."
);
}
public
void
terminate
()
{
System
.
out
.
println
(
"I am shutting down my ovens now."
);
}
}
But how does our application use it? Once we have the name of the user’s class, we need to create a Class
object for that class. This can be done easily using the static method Class.forName()
. Then we can create an instance of it using the Class
object’s newInstance()
method; this calls the class’s no-argument constructor. Then we simply cast the newly constructed object to our Cooklet
class, and we can call its methods! It actually takes longer to describe this code than to look at the code, so let’s do that now; see Example 17-7.
public
class
Cookies
{
public
static
void
main
(
String
[]
argv
)
{
System
.
out
.
println
(
"Cookies Application Version 0.0"
);
Cooklet
cooklet
=
null
;
String
cookletClassName
=
argv
[
0
];
try
{
Class
<
Cooklet
>
cookletClass
=
(
Class
<
Cooklet
>)
Class
.
forName
(
cookletClassName
);
cooklet
=
cookletClass
.
newInstance
();
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"Error "
+
cookletClassName
+
e
);
}
cooklet
.
initialize
();
cooklet
.
work
();
cooklet
.
terminate
();
}
}
And if we run it?
$ java Cookies DemoCooklet Cookies Application Version 0.0 I am busy baking cookies. I am shutting down my ovens now. $
Of course, this version has rather limited error handling. But you already know how to fix that. Your ClassLoader
can also place classes into a package by constructing a Package
object; you should do this if loading any medium-sized set of application classes.
You need to load a class from a nonstandard location and run its methods.
Examine the existing loaders such as java.net.URLClassLoader
. If none is suitable,
write and use your own ClassLoader
.
A ClassLoader
, of course, is a program that loads classes. One class loader is built into the Java Virtual Machine, but your application can create others as needed. Learning to write and run a working class loader and using it to load a class and run its methods is a nontrivial exercise. In fact, you rarely need to write a class loader, but knowing how is helpful in understanding how the JVM finds classes, creates objects, and calls methods.
ClassLoader
itself is abstract; you must subclass it, presumably providing a loadClass()
method that loads classes as you wish. It can load the bytes from a network connection, a local disk, RAM, a serial port, or anywhere else. Or you can construct the class file in memory yourself, if you have access to a compiler.
There is a general-purpose loader called java.net.URLClassLoader
that can be used if all you need
is to load classes via the Web protocol (or, more generally, from one or more URLs).
You must call the class loader’s loadClass()
method for any classes you wish to explicitly load from it.
Note that this method is called to load all classes required for classes you load (superclasses that
aren’t already loaded, for example).
However, the JVM still loads classes that you instantiate with the new
operator “normally” via CLASSPATH.
When writing a class loader, your loadClass()
method needs to get the class file into a byte array (typically by reading it), convert the array into a Class
object, and return the result.
What? That sounds a bit like “And Then a Miracle Occurs…” And it is. The miracle of class creation, however, happens down inside the JVM, where you don’t have access to it. Instead, your ClassLoader
has to call the protected defineClass()
method in your superclass (which is java.lang.ClassLoader
). This is illustrated in Figure 17-1, where a stream of bytes containing a hypothetical Chicken
class is converted into a ready-to-run Chicken
class in the JVM by calling the defineClass()
method.
To use your ClassLoader
subclass, you need to instantiate it and call its loadClass()
method with the name of the class you want to load. This gives you a Class
object for the named class; the Class
object in turn lets you construct instances, find and call methods, etc. Refer back to Recipe 17.2.
You’d rather construct a class dynamically by generating source code and compiling it.
Use the JavaCompiler
from javax.tools
.
There are many cases where you might need to generate code on the fly.
If you’re writing a framework, you might want to introspect on a model class to find its fields,
and generate accessors for them on the fly.
As we’ve seen in Recipe 17.2 above you could do this with the Field
class.
However, for a high-volume operation it may well be more efficient to generate direct access code.
The Java Compiler API has been around since Java 1.6 and is fairly easy to use for simple cases. The basic steps are:
Get the JavaCompiler object for your current Java Runtime. If it’s not available, either give up altogether, or fall back to using reflection.
Get a CompilerTask
(which is also a Callable
) to run the comilation, passing input and outputs.
Invoke the Callable
, either directly or by using an ExecutorService
.
Check the results. If true, invoke the class.
This is demonstrated in Example 17-8.
package
reflection
;
import
java.lang.reflect.Method
;
import
java.net.URI
;
import
java.util.List
;
import
java.util.concurrent.Callable
;
// tag::main[]
import
javax.tools.JavaCompiler
;
import
javax.tools.SimpleJavaFileObject
;
import
javax.tools.ToolProvider
;
/** Demo the Java Compiler API: Create a class, compile, load, and run it. * N.B. Will not run under Eclipse due to classpath settings; * best run it standalone using "java JavaCompiler.java" * @author Ian Darwin */
public
class
JavaCompilerDemo
{
private
final
static
String
PACKAGE
=
"reflection"
;
private
final
static
String
CLASS
=
"AnotherDemo"
;
private
static
boolean
verbose
;
public
static
void
main
(
String
[
]
args
)
throws
Exception
{
String
source
=
"package "
+
PACKAGE
+
"; "
+
"public class "
+
CLASS
+
" { "
+
" public static void main(String[] args) { "
+
" String message = (args.length > 0 ? args[0] : "Hi")"
+
"; "
+
" System.out.println(message + " from AnotherDemo"); "
+
" } } "
;
if
(
verbose
)
System
.
out
.
(
"Source to be compiled: "
+
source
)
;
JavaCompiler
compiler
=
ToolProvider
.
getSystemJavaCompiler
(
)
;
if
(
compiler
=
=
null
)
{
throw
new
IllegalStateException
(
"No default compiler, giving up."
)
;
}
Callable
<
Boolean
>
compilation
=
compiler
.
getTask
(
null
,
null
,
null
,
List
.
of
(
"-d"
,
"."
)
,
null
,
List
.
of
(
new
MySource
(
CLASS
,
source
)
)
)
;
boolean
result
=
compilation
.
call
(
)
;
if
(
result
)
{
System
.
out
.
println
(
"Compiled OK"
)
;
Class
<
?
>
c
=
Class
.
forName
(
PACKAGE
+
"."
+
CLASS
)
;
System
.
out
.
println
(
"Class = "
+
c
)
;
Method
m
=
c
.
getMethod
(
"main"
,
args
.
getClass
(
)
)
;
System
.
out
.
println
(
"Method descriptor = "
+
m
)
;
Object
[
]
passedArgs
=
{
args
}
;
m
.
invoke
(
null
,
passedArgs
)
;
}
else
{
System
.
out
.
println
(
"Compilation failed"
)
;
}
}
}
// end::main[]
class
MySource
extends
SimpleJavaFileObject
{
final
String
source
;
MySource
(
String
fileName
,
String
source
)
{
super
(
URI
.
create
(
"string:///"
+
fileName
.
replace
(
'.'
,
'/'
)
+
Kind
.
SOURCE
.
extension
)
,
Kind
.
SOURCE
)
;
this
.
source
=
source
;
}
@Override
public
CharSequence
getCharContent
(
boolean
ignoreEncodingErrors
)
{
return
source
;
}
}
The source code that we want to compile. In real life would probably be dynamically generated,
maybe using a StringBuffer
.
Get a reference to the default JavaCompiler object.
Ask the compiler to create a CompilerTask
to do the compilation.
CompilerTask
is also Callable
and we save it under that type.
The “-d .” are standard javac
arguments.
MySource
extends the compiler-provided API class SimpleJavaFileObject
to
give access to a file by creating a “file://” URL.
A Callable
can be put into an Executor (see Recipe 16.1);
we don’t need this capability but the Compiler API returns it. We invoke the Callable
directly.
Assuming the result
was true
indicating success, we load the class with Class.forName()
We have to find the main()
method in the generated class.
We re-use the String[].class
type from args, since all main
methods have the same argument.
Finally, we can invoke the main
method, re-using the incoming args
array
to pass any welcome message along.
Running this program with and without an argument shows that the argument passed
to the JavaCompilerDemo
is being passed correctly to the generated AnotherDemo
class.
$ java src/main/java/reflection/JavaCompilerDemo.java Compiled OK Class = class reflection.AnotherDemo Method descriptor = public static void reflection.AnotherDemo.main(java.lang.String[]) Hi from AnotherDemo $ java src/main/java/reflection/JavaCompilerDemo.java Welcome Compiled OK Class = class reflection.AnotherDemo Method descriptor = public static void reflection.AnotherDemo.main(java.lang.String[]) Welcome from AnotherDemo $
There is a lot to explore in the Compiler API, including the JavaFileManager
that lets you
control the placement of class files (other than by using -d
as we did here),
listeners to monitor compilation, control of output and error streams, and more.
Consult
the
javax.tools.JavaCompiler
documentation for details.
Slow performance?
Use a profiler, or, time individual methods using
System.currentTimeMillis()
before and after invoking the target method;
the difference is the time that method took.
Profiling tools—profilers—have a long history as one of the important tools in a programmer’s toolkit. A commercial profiling tool will help find bottlenecks in your program by showing both the number of times each method was called, and the amount of time in each.
Quite a bit of useful information can be obtained from a Java application by
use of the VisualVM
tool, which was part of the Oracle JDK up until Java 8.
With Java 9 this tool was open-sourced, and is now available from
the VisualVM project.
Another tool that is part of the JDK is Java Flight Recorder, which is now open-sourced and built into the JDK. Its data are meant to be analyzed by Java Mission Control. There are also third-party profilers that will give more detailed information; a web search will find current commercial offerings.
The simplest technique is to save the JVM’s accumulated time before and after dynamically loading a main program, and calculate the difference between those times. Code to do just this is presented in Example 17-11; for now, just remember that we have a way of timing a given Java class.
One way of measuring the efficiency of a particular operation is to run it many times in isolation. The overall time the program takes to run thus approximates the total time of many invocations of the same operation. Gross numbers like this can be compared if you want to know which of two ways of doing something is more efficient. Consider the case of string concatenation versus println()
. The code:
println("Time is " + n.toString( ) + " seconds");
will probably work by creating a StringBuilder
, appending the string "Time is "
, the
value of n
as a string, and " seconds"
, and finally converting the finished
StringBuilder
to a String
and passing that to println()
. Suppose you have a program
that does a lot of this, such as a Java servlet that creates a lot of HTML this way, and
you expect (or at least hope) your web site to be sufficiently busy so that doing this
efficiently will make a difference. There are two ways of thinking about this:
Theory A: This string concatenation is inefficient.
Theory B: String concatenation doesn’t matter; println()
is inefficient, too.
A proponent of Theory A might say that because println()
just puts stuff into a buffer,
it is very fast and that string concatenation is the expensive part.
How to decide between Theory A and Theory B? Assume you are willing to write a simple test program that tests both theories. Let’s just write a simple program both ways and time it. Example 17-9 is the timing program for Theory A.
public
class
StringPrintA
{
public
static
void
main
(
String
[]
argv
)
{
Object
o
=
"Hello World"
;
for
(
int
i
=
0
;
i
<
100000
;
i
++)
{
System
.
out
.
println
(
"<p><b>"
+
o
.
toString
()
+
"</b></p>"
);
}
}
}
StringPrintAA
(in the javasrc repo but not printed here) is the same but
explicitly uses a StringBuilder
for the string
concatenation. Example 17-10 is the tester for Theory B.
public
class
StringPrintB
{
public
static
void
main
(
String
[]
argv
)
{
Object
o
=
"Hello World"
;
for
(
int
i
=
0
;
i
<
100000
;
i
++)
{
System
.
out
.
(
"<p><b>"
);
System
.
out
.
(
o
.
toString
());
System
.
out
.
(
"</b></p>"
);
System
.
out
.
println
();
}
}
}
I ran StringPrintA
, StringPrintAA
, and StringPrintB
twice each on the same computer.
To eliminate JVM startup times, I ran them from a program called TimeNoArgs
, which
takes a class name and invokes its main()
method, using the Reflection API.
TimeNoArgs
and a shell script to run it, stringprinttimer.sh, are in the performance
folder of the javasrc source repository. Here are the results:
2004 Results |
StringPrintA |
17.23, 17.20 seconds |
StringPrintAA |
17.23, 17.23 seconds |
StringPrintB |
27.59, 27.60 seconds |
2014 Results |
StringPrintA |
|
0.714, 0.525 seconds |
StringPrintAA |
0.616, 0.561 seconds |
StringPrintB |
Although the times went down by a factor of roughly 20 over a decade,
the ratios remain remarkably consistent: StringPrintB
, which calls print()
and println()
multiple times, takes roughly twice as long.
Moral: Don’t guess. If it matters, time it.
Another moral: Multiple calls to System.out.print()
cost more than the same number of
calls to a StringBuilder
’s append()
method, by a factor of roughly 1.5 (or 150%).
Theory B wins; the extra println
calls appear to save a string concatenation but make
the program take substantially longer.
There are many other aspects of software performance. One that is fundamental to Java is garbage collection behavior. Sun/Oracle usually talk about this at JavaOne. See, for example, the JavaOne 2003 paper “Garbage Collection in the Java HotSpot Virtual Machine”. You should also see the JavaOne 2007 talk by the same GC Development team, “Garbage-Collection-Friendly Programming”, TS-2906. Unfortunately it seems to have gotten lost from Oracle’s website, but is still available online. JavaOne 2010 featured an updated presentation entitled “The Garbage Collection MythBusters.”
It’s pretty easy to build a simplified time
command in Java, given that you have System.currentTimeMillis()
to start with. Run my Time
program, and, on the command line, specify the name of the class to be timed, followed by the arguments (if any) that class needs for running. The program is shown in Example 17-11. The time that the class took is displayed. But remember that System.currentTimeMillis()
returns clock time, not necessarily CPU time. So you must run it on a machine that isn’t running a lot of background processes. And note also that I use dynamic loading (see Recipe 17.4) to let you put the Java class name on the command line.
public
class
Time
{
public
static
void
main
(
String
[]
argv
)
throws
Exception
{
// Instantiate target class, from argv[0]
Class
<?>
c
=
Class
.
forName
(
argv
[
0
]);
// Find its static main method (use our own argv as the signature).
Class
<?>[]
classes
=
{
argv
.
getClass
()
};
Method
main
=
c
.
getMethod
(
"main"
,
classes
);
// Make new argv array, dropping class name from front.
// (Normally Java doesn't get the class name, but in
// this case the user puts the name of the class to time
// as well as all its arguments...
String
nargv
[]
=
new
String
[
argv
.
length
-
1
];
System
.
arraycopy
(
argv
,
1
,
nargv
,
0
,
nargv
.
length
);
Object
[]
nargs
=
{
nargv
};
System
.
err
.
println
(
"Starting class "
+
c
);
// About to start timing run. Important to not do anything
// (even a println) that would be attributed to the program
// being timed, from here until we've gotten ending time.
// Get current (i.e., starting) time
long
t0
=
System
.
currentTimeMillis
();
// Run the main program
main
.
invoke
(
null
,
nargs
);
// Get ending time, and compute usage
long
t1
=
System
.
currentTimeMillis
();
long
runTime
=
t1
-
t0
;
System
.
err
.
println
(
"runTime="
+
Double
.
toString
(
runTime
/
1000
D
));
}
}
Of course, you can’t directly compare the results from the operating system time
command with results from running this program. There is a rather large, but fairly constant, initialization overhead—the JVM startup and the initialization of Object
and System.out
, for example—that is included in the former and excluded from the latter. One could even argue that my Time
program is more accurate because it excludes this constant overhead. But, as noted, it must be run on a single-user machine to yield repeatable results. And no fair running an editor in another window while waiting for your timed program to complete!
Java Performance: The Definitive Guide by Scott Oaks (O’Reilly) provides information on tuning Java performance.
You want to print all the information about a class, similar to the way javap does.
Get a Class
object, call its getFields()
and getMethods()
, and print the results.
The JDK includes a program called javap, the Java Printer. Sun’s JDK version normally prints the outline of a class file—a list of its methods and fields—but can also print out the Java bytecodes or machine instructions. The Kaffe package did not include a version of javap, so I wrote one and contributed it (see Example 17-12). The Kaffe folks have expanded it somewhat, but it still works basically the same. My version doesn’t print the bytecodes; it behaves rather like Sun’s behaves when you don’t give theirs any command-line options.
The getFields()
and getMethods()
methods return arrays of Field
and Method
, respectively; these are both in package java.lang.reflect
. I use a Modifiers
object to get details on the permissions and storage attributes of the fields and methods. In many Java implementations, you can bypass this and simply call toString()
in each Field
and Method
object (as I do here for Constructors
). Doing it this way gives me a bit more control over the formatting.
public
class
MyJavaP
{
/** Simple main program, construct self, process each class name
* found in argv.
*/
public
static
void
main
(
String
[]
argv
)
{
MyJavaP
pp
=
new
MyJavaP
();
if
(
argv
.
length
==
0
)
{
System
.
err
.
println
(
"Usage: MyJavaP className [...]"
);
System
.
exit
(
1
);
}
else
for
(
int
i
=
0
;
i
<
argv
.
length
;
i
++)
pp
.
doClass
(
argv
[
i
]);
}
/** Format the fields and methods of one class, given its name.
*/
protected
void
doClass
(
String
className
)
{
try
{
Class
<?
extends
Object
>
c
=
Class
.
forName
(
className
);
final
Annotation
[]
annotations
=
c
.
getAnnotations
();
for
(
Annotation
a
:
annotations
)
{
System
.
out
.
println
(
a
);
}
System
.
out
.
println
(
c
+
" {"
);
Field
fields
[]
=
c
.
getDeclaredFields
();
for
(
Field
f
:
fields
)
{
final
Annotation
[]
fldAnnotations
=
f
.
getAnnotations
();
for
(
Annotation
a
:
fldAnnotations
)
{
System
.
out
.
println
(
a
);
}
if
(!
Modifier
.
isPrivate
(
f
.
getModifiers
()))
System
.
out
.
println
(
" "
+
f
+
";"
);
}
Constructor
<?
extends
Object
>[]
constructors
=
c
.
getConstructors
();
for
(
Constructor
<?
extends
Object
>
con
:
constructors
)
{
System
.
out
.
println
(
" "
+
con
+
";"
);
}
Method
methods
[]
=
c
.
getDeclaredMethods
();
for
(
Method
m
:
methods
)
{
final
Annotation
[]
methodAnnotations
=
m
.
getAnnotations
();
for
(
Annotation
a
:
methodAnnotations
)
{
System
.
out
.
println
(
a
);
}
if
(!
Modifier
.
isPrivate
(
m
.
getModifiers
()))
{
System
.
out
.
println
(
" "
+
m
+
";"
);
}
}
System
.
out
.
println
(
"}"
);
}
catch
(
ClassNotFoundException
e
)
{
System
.
err
.
println
(
"Error: Class "
+
className
+
" not found!"
);
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"JavaP Error: "
+
e
);
}
}
}
You want to get a list of all the classes in a package.
You can’t, in the general case. There are some limited approaches, most involving “classpath scanning.”
There is no way to find out all the classes in a package, in part because, as we just
saw in Recipe 17.5,
you can add classes to a package at any time!
And, for better or for worse, the JVM and standard classes such as java.lang.Package
do not even allow you to enumerate the classes currently in a given package.
The nearest you can come is to look through the classpath.
And this will surely work only for local directories and JAR files;
if you have locally defined or network-loaded classes, this is not going to help.
In other words, it will find compiled classes, but not dynamically loaded ones.
There are several libraries that can automate this for you, and you’re welcome to use them.
The code to scan the classpath is fairly simple at heart, though, so classy developers with heart
will want to examine it.
Example 17-13 shows my ClassesInPackage
class with its one static method.
The code works but is rather short on error handling, and will crash on
non-existent packages and other failures.
The code goes through a few gyrations to get the classpath as an enumeration of URLs,
then looks at each element. “file:” URLs will contain the pathname of the file
containing the .class file, so we can just list it.
“jar:” URLs contain the filename as “file:/path_to_jar_file!package/name,” so we have to pull
this apart; the “package name” suffix is slightly redundant in this case because it’s the
package we asked the ClassLoader
to give us.
public
class
ClassesInPackage
{
/** This approach began as a contribution by Paul Kuit at
* http://stackoverflow.com/questions/1456930/, but his only
* handled single files in a directory in classpath, not in Jar files.
* N.B. Does NOT handle system classes!
* @param packageName
* @return
* @throws IOException
*/
public
static
String
[]
getPackageContent
(
String
packageName
)
throws
IOException
{
final
String
packageAsDirName
=
packageName
.
replace
(
"."
,
"/"
);
final
List
<
String
>
list
=
new
ArrayList
<>();
final
Enumeration
<
URL
>
urls
=
Thread
.
currentThread
().
getContextClassLoader
().
getResources
(
packageAsDirName
);
while
(
urls
.
hasMoreElements
())
{
URL
url
=
urls
.
nextElement
();
// System.out.println("URL = " + url);
String
file
=
url
.
getFile
();
switch
(
url
.
getProtocol
())
{
case
"file"
:
// This is the easy case: "file" is
// the full path to the classpath directory
File
dir
=
new
File
(
file
);
for
(
File
f
:
dir
.
listFiles
())
{
list
.
add
(
packageAsDirName
+
"/"
+
f
.
getName
());
}
break
;
case
"jar"
:
// This is the harder case; "file" is of the form
// "jar:/home/ian/bleah/darwinsys.jar!com/darwinsys/io"
// for some jar file that contains at least one class from
// the given package.
int
colon
=
file
.
indexOf
(
':'
);
int
bang
=
file
.
indexOf
(
'!'
);
String
jarFileName
=
file
.
substring
(
colon
+
1
,
bang
);
JarFile
jarFile
=
new
JarFile
(
jarFileName
);
Enumeration
<
JarEntry
>
entries
=
jarFile
.
entries
();
while
(
entries
.
hasMoreElements
())
{
JarEntry
e
=
entries
.
nextElement
();
String
jarEntryName
=
e
.
getName
();
if
(!
jarEntryName
.
endsWith
(
"/"
)
&&
jarEntryName
.
startsWith
(
packageAsDirName
))
{
list
.
add
(
jarEntryName
);
}
}
break
;
default
:
throw
new
IllegalStateException
(
"Dunno what to do with URL "
+
url
);
}
}
return
list
.
toArray
(
new
String
[]
{});
}
public
static
void
main
(
String
[]
args
)
throws
IOException
{
String
[]
names
=
getPackageContent
(
"com.darwinsys.io"
);
for
(
String
name
:
names
)
{
System
.
out
.
println
(
name
);
}
System
.
out
.
println
(
"Done"
);
}
}
Note that if you run this application in the “javasrc” project, it will list the
members of the demonstration package (com.darwinsys.io
) twice, because it will find them both in the build directory and in the JAR file. If this is an issue, change the List to a Set (see
Recipe 7.3).
You need to know how to use annotations in code or to define your own annotations.
Apply annotations in your code using @
AnnotationName before a class, method, field, etc.
Define annotations with @interface
at the same level as class
, interface
, etc.
Annotations are a way of adding additional information beyond what the source code conveys.
Annotations may be directed at the compiler or at runtime examination.
Their syntax was somewhat patterned after javadoc annotations
(such as @author
, @version
inside “doc comments”).
Annotations are what I call class-like things (so they have initial-cap names),
but are prefixed by @
sign where used (e.g., @Override
).
You can place them on classes, methods, fields, and a few other places; they must
appear immediately before what they annotate (ignoring space and comments).
A given annotation may only appear once in a given position (this is relaxed in Java 8 or 9).
As an example of the benefits of a compile-time annotation, consider the common error made when overriding: as shown in Example 17-14, a small error in the method signature can result it an overload when an override was intended.
public class MyClass { public boolean equals(MyClass object2) { // compare, return boolean } }
The code will compile just fine on any release of Java, but it is incorrect.
The standard contract of the equals()
method (see
Recipe 8.1) requires a method whose
solitary argument is of type java.lang.Object
. The preceding version creates an accidental
overload. Because the main use of equals()
(and its buddy method hashCode()
, see
Recipe 8.1) is in the
Collections classes (see Chapter 7),
this overloaded method will never get called, resulting both
in dead code and in incorrect operation of your class within +Set+s and +Map+s.
The solution is very simple: using the annotation java.lang.Override
, as in Example 17-15, informs the
compiler that the annotated method is required to be overriding a method inherited from
a supertype (such as a superclass or an interface). If not, the code will not compile.
public class MyClass { @Override public boolean equals(MyClass object2) { // compare, return boolean } }
This version of equals()
, while still incorrect, will be flagged as erroneous at compile time,
potentially avoiding a lot of debugging time.
This annotation, on your own classes, will help both at the time you write new code and
as you maintain your codebase; if a method is removed from a superclass, all the subclasses
that still attempt to override it and have the @Override
annotation, will cause
an error message, allowing you to remove a bunch of dead code.
The second major use of annotations is to provide metatdata at runtime.
For example, the Java Persistence API (JPA, see https://darwinsys.com/db_in_java)
uses its own set of annotations from
the package javax.persistence
to “mark up” entity classes to be loaded and/or
persisted. A JPA Entity Class might look like Example 17-16:
@Entity
public
class
Person
{
int
id
;
protected
String
firstName
;
protected
String
lastName
;
public
Person
()
{
// required by JPA; must code it since we need 2-arg form.
}
public
Person
(
String
firstName
,
String
lastName
)
{
this
.
firstName
=
firstName
;
this
.
lastName
=
lastName
;
}
@Id
@GeneratedValue
(
strategy
=
GenerationType
.
AUTO
,
generator
=
"my_poid_gen"
)
public
int
getId
()
{
return
id
;
}
public
void
setId
(
int
id
)
{
this
.
id
=
id
;
}
public
String
getFirstName
()
{
return
firstName
;
}
public
void
setFirstName
(
String
firstName
)
{
this
.
firstName
=
firstName
;
}
@Column
(
name
=
"surname"
)
public
String
getLastName
()
{
return
lastName
;
}
public
void
setLastName
(
String
lastName
)
{
this
.
lastName
=
lastName
;
}
@Override
public
String
toString
()
{
return
getFullName
();
}
@Transient
/* synthetic: cannot be used in JPA queries. */
public
String
getFullName
()
{
StringBuilder
sb
=
new
StringBuilder
();
if
(
firstName
!=
null
)
sb
.
append
(
firstName
).
append
(
' '
);
if
(
lastName
!=
null
)
sb
.
append
(
lastName
);
if
(
sb
.
length
()
==
0
)
sb
.
append
(
"NO NAME"
);
return
sb
.
toString
();
}
}
The @Entity
annotation at class level directs JPA to treat this as a data object to
be mapped into the database.
The @Id
informs JPA that this id
is the primary key property, and the @GeneratedValue
tells it how to assign the primary key values for newly created objects.
The @Column
annotation is only needed when the column name in the relational database
differs from the expected name based on the property; in this case, the SQL database
designer has used surname
, whereas the Java developer wants to use lastName
.
I said that annotations are class-like things, and so, you can define your own.
The syntax here is a bit funky; you use @interface
. It is rumored that the team
developing this feature was either told not to, or was afraid to, introduce a new
keyword into the language, due to the trouble that doing so had caused when the enum
keyword
was introduced in Java SE 1.4.
Or, maybe they just wanted to use a syntax that was more reminiscent of the annotation’s usage.
At any rate, Example 17-17 is a trivial example of a custom annotation.
package lang; public @interface MyToyAnnotation { }
Annotations are “class-like things” so they should be named the same way—that is, names that begin with a capital letter and, if public, stored in a source file of the same name (e.g, MyToyAnnotation.java).
Compile the Example 17-17 with javac
and you’ll see there’s a new MyToyAnnotation.class file.
In Example 17-18, we examine this with javap
, the standard JDK class inspection tool.
$ javap lang.MyToyAnnotation Compiled from "MyToyAnnotation.java" public interface lang.MyToyAnnotation extends java.lang.annotation.Annotation { } $
As it says, an Annotation
is represented in the class file format as just an interface that extends Annotation
(to answer the obvious question, you could write simple interfaces this way, but it would be a truly terrible idea). Let’s have a quick look at Annotation
itself:
$ javap java.lang.annotation.Annotation Compiled from "Annotation.java" public interface java.lang.annotation.Annotation { public abstract boolean equals(java.lang.Object); public abstract int hashCode(); public abstract java.lang.String toString(); public abstract java.lang.Class<? extends java.lang.annotation.Annotation> annotationType(); } $
Annotations can be made such that the compiler will only allow them in certain points in your code. Here is one that can only go on classes or interfaces:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { }
The @Target
specifies where the annotation can be used: ElementType.TYPE
makes it usable on
classes, interfaces, class-like things such as enums, even annotations!
To restrict it to use just on annotations, there is ElementType.ANNOTATION_TYPE
.
Other types include METHOD
, FIELD
, CONSTRUCTOR
, LOCAL_VARIABLE
, PACKAGE
, and PARAMETER
.
So, this annotation is itself annotated with two @ANNOTATION_TYPE
-targeted annotations.
Usage of annotations with an existing framework requires consulting their documentation. Using annotations for your own purpose at runtime requires use of the Reflection API, as shown in Example 17-21.
One more thing to note about annotations is that they may have attributes. These are defined as methods in the annotation source code, but used as attributes where the annotation is used. Example 17-21 is an annotated annotation with one such attribute:
/**
* A sample annotation for types (classes, interfaces);
* it will be available at run time.
*/
@Target
(
ElementType
.
TYPE
)
@Retention
(
RetentionPolicy
.
RUNTIME
)
public
@interface
AnnotationDemo
{
public
boolean
fancy
()
default
false
;
public
int
order
()
default
42
;
}
/** A simple example of using the annotation */
@AnnotationDemo
(
fancy
=
true
)
@Resource
(
name
=
"Dumbledore"
)
class
FancyClassJustToShowAnnotation
{
/** Print out the annotations attached to this class */
public
static
void
main
(
String
[]
args
)
{
Class
<?>
c
=
FancyClassJustToShowAnnotation
.
class
;
System
.
out
.
println
(
"Class "
+
c
.
getName
()
+
" has these annotations:"
);
for
(
Annotation
a
:
c
.
getAnnotations
())
{
if
(
a
instanceof
AnnotationDemo
)
{
AnnotationDemo
ad
=
(
AnnotationDemo
)
a
;
System
.
out
.
println
(
" "
+
a
+
" with fancy="
+
ad
.
fancy
()
+
" and order "
+
ad
.
order
());
}
else
{
System
.
out
.
println
(
" Somebody else's annotation: "
+
a
);
}
}
}
}
AnnotationDemo
has the meta-annotation @Target(ElementType.TYPE)
to
indicate that it can annotate user-defined types (such as classes). Other ElementType
choices include METHOD
, FIELD
, PARAMETER
and a few more. If more than one is
needed, use array initializer syntax.
AnnotationDemo
also has the @Retention(RetentionPolicy.RUNTIME)
annotation to request
that it be preserved until runtime. This is obviously required for any annotation that will be examined by a framework at runtime.
These two meta-annotations are common on user-defined annotations that will be examined at runtime.
The class FancyClassJustToShowAnnotation
shows using the AnnotationDemo
annotation,
along with a standard Java one (the @Resource
annotation).
Refer to Recipe 17.11 for a full example of using this mechanism.
You want to do plug-in-like things without using an explicit plug-in API.
Define an annotation for the purpose, and use it to mark the plug-in classes.
Suppose we want to model how the Java EE standard javax.annotations.Named
or
javax.faces.ManagedBean
annotations work; for each class
that is so annotated, convert the class name to an instance-like name (e.g, lowercase the first letter),
and do something special with it. You’d want to do something like the following:
Get the list of classes in the given package(s) (see Recipe 17.9).
Check if the class is annotated.
If so, save the name and Class
descriptor for later use.
This is implemented in Example 17-22.
/** Discover "plugins" or other add-in classes via Reflection using Annotations */
public
class
PluginsViaAnnotations
{
/**
* Find all classes in the given package which have the given
* class-level annotation class.
*/
public
static
List
<
Class
<?>>
findAnnotatedClasses
(
String
packageName
,
Class
<?
extends
Annotation
>
annotationClass
)
throws
Exception
{
List
<
Class
<?>>
ret
=
new
ArrayList
<>();
String
[]
clazzNames
=
ClassesInPackage
.
getPackageContent
(
packageName
);
for
(
String
clazzName
:
clazzNames
)
{
if
(!
clazzName
.
endsWith
(
".class"
))
{
continue
;
}
clazzName
=
clazzName
.
replace
(
'/'
,
'.'
).
replace
(
".class"
,
""
);
Class
<?>
c
=
null
;
try
{
c
=
Class
.
forName
(
clazzName
);
}
catch
(
ClassNotFoundException
ex
)
{
System
.
err
.
println
(
"Weird: class "
+
clazzName
+
" reported in package but gave CNFE: "
+
ex
);
continue
;
}
if
(
c
.
isAnnotationPresent
(
annotationClass
)
&&
!
ret
.
contains
(
c
))
ret
.
add
(
c
);
}
return
ret
;
}
We can take this one step further, and support particular method
annotations, similar to javax.annotations.PostCreate
, which is
meant to decorate a method that is to be called after an instance
of the bean has been instantiated by the framework. Our flow is
now something like this, and the code is shown in Example 17-23:
Get the list of classes in the given package(s) (again, see Recipe 17.9).
If you are using a class-level annotation, check if the class is annotated.
If this class is still of interest, get a list of its methods.
For each method, see if it contains a given method-specific annotation.
If so, add the class and method to a list of invocable methods.
/**
* Find all classes in the given package which have the given
* method-level annotation class on at least one method.
*/
public
static
List
<
Class
<?>>
findClassesWithAnnotatedMethods
(
String
packageName
,
Class
<?
extends
Annotation
>
methodAnnotationClass
)
throws
Exception
{
List
<
Class
<?>>
ret
=
new
ArrayList
<>();
String
[]
clazzNames
=
ClassesInPackage
.
getPackageContent
(
packageName
);
for
(
String
clazzName
:
clazzNames
)
{
if
(!
clazzName
.
endsWith
(
".class"
))
{
continue
;
}
clazzName
=
clazzName
.
replace
(
'/'
,
'.'
).
replace
(
".class"
,
""
);
Class
<?>
c
=
null
;
try
{
c
=
Class
.
forName
(
clazzName
);
// System.out.println("Loaded " + c);
}
catch
(
ClassNotFoundException
ex
)
{
System
.
err
.
println
(
"Weird: class "
+
clazzName
+
" reported in package but gave CNFE: "
+
ex
);
continue
;
}
for
(
Method
m
:
c
.
getDeclaredMethods
())
{
// System.out.printf("Class %s Method: %s ",
// c.getSimpleName(), m.getName());
if
(
m
.
isAnnotationPresent
(
methodAnnotationClass
)
&&
!
ret
.
contains
(
c
))
{
ret
.
add
(
c
);
}
}
}
return
ret
;
}
Recipe 17.10, and the rest of this chapter.
You’ve probably seen those other Java books that consist entirely of listings of the Java API for version thus-and-such of the JDK. I don’t suppose you thought the authors of these works sat down and typed the entire contents from scratch. As a programmer, you would have realized, I hope, that there must be a way to obtain that information from Java. But you might not have realized how easy it is! If you’ve read this chapter faithfully, you now know that there is one true way: make the computer do the walking. Example 17-24 is a program that puts most of the techniques together. This version generates a cross-reference listing, but by overriding the last few methods, you could easily convert it to print the information in any format you like, including an API Reference book. You’d need to deal with the details of this or that publishing software—FrameMaker, troff, TEX, or whatever—but that’s the easy part.
This program makes fuller use of the Reflection API than did MyJavaP
in Recipe 17.8. It also uses the java.util.zip
classes (see Recipe 10.15) to crack the JAR archive containing the class files of the API. Each class file found in the archive is loaded and listed; the listing part is similar to MyJavaP
.
public
class
CrossRef
extends
APIFormatter
{
/** Simple main program, construct self, process each .ZIP file
* found in CLASSPATH or in argv.
*/
public
static
void
main
(
String
[]
argv
)
throws
IOException
{
CrossRef
xref
=
new
CrossRef
();
xref
.
doArgs
(
argv
);
}
/**
* Print the fields and methods of one class.
*/
protected
void
doClass
(
Class
<?>
c
)
{
startClass
(
c
);
try
{
Field
[]
fields
=
c
.
getDeclaredFields
();
Arrays
.
sort
(
fields
,
new
Comparator
<
Field
>()
{
public
int
compare
(
Field
o1
,
Field
o2
)
{
return
o1
.
getName
().
compareTo
(
o2
.
getName
());
}
});
for
(
int
i
=
0
;
i
<
fields
.
length
;
i
++)
{
Field
field
=
(
Field
)
fields
[
i
];
if
(!
Modifier
.
isPrivate
(
field
.
getModifiers
()))
putField
(
field
,
c
);
// else System.err.println("private field ignored: " + field);
}
Method
methods
[]
=
c
.
getDeclaredMethods
();
Arrays
.
sort
(
methods
,
new
Comparator
<
Method
>()
{
public
int
compare
(
Method
o1
,
Method
o2
)
{
return
o1
.
getName
().
compareTo
(
o2
.
getName
());
}
});
for
(
int
i
=
0
;
i
<
methods
.
length
;
i
++)
{
if
(!
Modifier
.
isPrivate
(
methods
[
i
].
getModifiers
()))
putMethod
(
methods
[
i
],
c
);
// else System.err.println("pvt: " + methods[i]);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
endClass
();
}
/** put a Field's information to the standard output. */
protected
void
putField
(
Field
fld
,
Class
<?>
c
)
{
println
(
fld
.
getName
()
+
" field "
+
c
.
getName
()
+
" "
);
}
/** put a Method's information to the standard output. */
protected
void
putMethod
(
Method
method
,
Class
<?>
c
)
{
String
methName
=
method
.
getName
();
println
(
methName
+
" method "
+
c
.
getName
()
+
" "
);
}
/** Print the start of a class. Unused in this version,
* designed to be overridden */
protected
void
startClass
(
Class
<?>
c
)
{
}
/** Print the end of a class. Unused in this version,
* designed to be overridden */
protected
void
endClass
()
{
}
/** Convenience routine, short for System.out.println */
protected
final
void
println
(
String
s
)
{
System
.
out
.
println
(
s
);
}
}
You probably noticed the methods startClass()
and endClass()
, which are null. These methods are placeholders designed to make subclassing easy for when you need to write something at the start and end of each class. One example might be a fancy text formatting application in which you need to output a bold header at the beginning of each class. Another would be XML, where you’d want to write a tag like <class>
at the front of each class, and </class>
at the end. Example 17-25 is an XML-specific subclass that generates (limited) XML for each field and method.
public
class
CrossRefXML
extends
CrossRef
{
public
static
void
main
(
String
[]
argv
)
throws
IOException
{
CrossRef
xref
=
new
CrossRefXML
();
xref
.
doArgs
(
argv
);
}
/** Print the start of a class.
*/
protected
void
startClass
(
Class
<?>
c
)
{
println
(
"<class><classname>"
+
c
.
getName
()
+
"</classname>"
);
}
protected
void
putField
(
Field
fld
,
Class
<?>
c
)
{
println
(
"<field>"
+
fld
+
"</field>"
);
}
/** put a Method's information to the standard output.
* Marked protected so you can override it (hint, hint).
*/
protected
void
putMethod
(
Method
method
,
Class
<?>
c
)
{
println
(
"<method>"
+
method
+
"</method>"
);
}
/** Print the end of a class.
*/
protected
void
endClass
()
{
println
(
"</class>"
);
}
}
By the way, if you publish a book using either of these and get rich, “Remember, remember me!”
We have not investigated all the ins and outs of reflection or the ClassLoader
mechanism, but by now you should have a basic idea of how it works.
Perhaps the most important omissions are SecurityManager
and ProtectionDomain
. Only one SecurityManager
can be installed in a given instance of the JVM (e.g., to prevent malicious code from providing its own!). A browser running the old Java Applet API, for example, provides a SecurityManager
that is far more restrictive than the standard one. Writing such a SecurityManager
is left as an exercise for the reader—an important exercise for anyone planning to load classes over the Internet! (For more information about security managers and the Java Security APIs, see Java Security by Scott Oaks (O’Reilly). A ProtectionDomain
can be provided with a ClassLoader
to specify all the permissions needed for the class to run.
I’ve also left unexplored many topics in the JVM; see the (somewhat dated) O’Reilly books Java Virtual Machine and Java Language. You can also read the Sun/Oracle Java Language Specification and JVM Specification documents (both updated with new releases, available online), for a lifetime of reading enjoyment and edification!
The Apache Software Foundation maintains a vast array of useful software packages that are free to get and use. Source code is always available without charge from its website. Two packages you might want to investigate include the Commons BeanUtils and the Byte Code Engineering Library (BCEL). The Commons BeanUtils claims to provide easier-to-use wrappers around some of the Reflection API. BCEL is a third-party toolkit for building and manipulating “bytecode” class files. Written by Markus Dahm, BCEL has become part of the Apache Commons Project.
18.222.182.66