You may not have thought about it, but every time you code a class, the compiled result is an instance of a class called (who’d a thunk?) Class. Likewise, each field definition is an instance of Field, each method is an instance of Method, and each constructor is an instance of Constructor.
These four classes, which are in package java.lang.reflect, form the foundation of reflection–which is basically Java about Java. Class, Field, Method, and Constructor each have their own fields and methods which let you examine and manipulate the attributes and behavior of classes, fields, methods, and constructors.
Reflection is a force multiplier. With it, you can write extremely generalized code to compare fields within instances, alter behavior of operations, parse input records, and format output records without directly addressing specific classes, methods, or fields.
The Class Class
The class Class (this is not a redundancy) tells you all you might want to know about a class or interface (like what fields and methods it contains), and lets you perform operations on it (like instantiation). If you’ve got an object, you can determine its class by calling its getClass() method.
Here are some of the more useful methods in the Class class.
Method | Function |
static Class<?> forName(String className) | Returns a Class object associated with the supplied name. This is especially useful for getting the class associated with an implementation of an interface. For example, you can include the interface implementation in your runtime classpath, pass the implementation’s class name as a runtime parameter, and use this method to get a corresponding Class object which you can then instantiate. |
<A extends Annotation> A getAnnotation(Class<A> annotationClass) | Returns the class’s Annotation of the specified type, if present. We’ll discuss annotations below. |
Annotation[] getAnnotations() | Returns an array of Annotations that are present on this class. |
ClassLoader getClassLoader() | Returns the ClassLoader associated with the class. The ClassLoader can retrieve items (like parameter files or images) on the runtime classpath. |
Constructor<T> getDeclaredConstructor (Class<?>… parameterTypes) | Returns a public Constructor object for this class that accepts the specified parameterTypes as arguments. An Exception is thrown if no such Constructor exists. The Constructor class has a method for creating an instance of the class. See the description of getInstance(), below, to see how. |
Constructor<?>[] getDeclaredConstructors() | Returns all the class’s public Constructors. |
Field getDeclaredField(String name) | Returns the Field object with the specified name. |
Field[] getDeclaredFields() | Returns all the class’s or interfaces declared fields. |
Method getDeclaredMethod (String name, Class<?>… parameterTypes) | Returns a Method object for the specified name that accepts the specified parameterTypes as arguments. |
Method[] getMethods() | Returns all the class’s or interface’s public methods. |
int getModifiers() | Returns the modifiers (like public or static) for the class or interface, coded as an integer. Java has methods for determining which modifiers are present. |
String getName() String getSimpleName() | Returns the class’s or interface’s name. The first form includes the package name. |
Package getPackage() | Returns the package for the class. |
URL getResource(String name) InputStream getResourceAsStream (String name) | Returns the specified resource (e.g., a text file, an image, etc.) as a URL or InputStream. The resource is located by the ClassLoader. |
Class<? super T> getSuperclass() | Returns the superclass, or null if there is none. This is important to know because getDeclaredFields() and getMethods() return only fields and methods declared in the current class and not those found in superclasses. If you want all the fields belonging to a subclass, you must work your way up the class hierarchy. |
boolean isAnnotation() | Returns true if this class is an Annotation. |
boolean isAssignableFrom(Class<?> cls) | Returns true if this class object is the same as, or is a superclass or superinterface of, cls. |
boolean isEnum() | Returns true if the class is an enum. |
boolean isInterface() | Returns true if the class is an interface. |
boolean isPrimitive() | Returns true if the class represents a primitive type. |
T newInstance() | Returns an instance of the class using a default constructor. Note: this method is deprecated as of Java 9. Instead, make a call like this: MyClass myInstance = MyClass.getDeclaredConstructor() .newInstance(); |
Annotations
Above, we mentioned Annotations. You may have noticed these in the code you’ve seen to far: annotations are the words preceded by @, like @SuppressWarnings and @Override.
An annotation comes in one of three flavors, called retention policies:
- SOURCE, discarded by the compiler (but used to control it)
- CLASS, retained in the compiled class file but not necessarily loaded at runtime
- RUNTIME, the most interesting because such annotations can be tested by your code.
And an annotation can be added to one or more of nearly anything (as declared in the annotation definition):
- Type declarations (classes, enums, interfaces)
- Fields
- Methods
- Parameters (i.e., method arguments)
- Constructors
- Packages
- etc.
Declaring a Runtime Annotation
So let’s define a annotation on a field as an example.
An Annotation is defined in its own .java file, just like a Class or an Interface. And it looks something like this:
enum SequenceType {ASCENDING, DESCENDING};
@Retention(RUNTIME)
@Target(ElementType.FIELD)
public @interface Key {
int order() default 0;
SequenceType sequence() default SequenceType.ASCENDING;
}
The @Retention annotation indicates that this annotation is available at runtime; i.e., your code can use it.
The @Target annotation indicates what kinds of element or elements can accept this annotation. In this example, this annotation is used only on Fields, but we could have declared it useable on other types of elements too: classes, methods, and so on.
Finally, the line that declares the annotation itself. @interface indicates that an annotation is being defined, and Key is its name.
Key has two parameters: an int named order with default value 0, and a SequenceType (our own enum) named sequence with default value ASCENDING. Notice that the parameter names are followed by “()” like a method with no arguments.
You don’t have to specify defaults for annotation parameters. If you don’t, the compiler will demand that the developer specify them when the annotation is used. And speaking of that, let’s try using our annotation.
public class Directory {
private String lastName;
private String firstName;
private String address;
@Key (order = 3)
private String city;
@Key (order = 1)
private String state;
@Key (order = 2, sequence = SequenceType.DESCENDING)
private String zip;
...
}
In our example (which is this lesson’s exercise–see below), we use the @Key annotation to indicate which fields in the Directory class are key fields on which instances can be sorted, in what order of priority, and whether a given field is sorted in ascending (the default) or descending order.
But beyond that, thanks to reflection we can write generalized code that can sort instances of any class just by adding @Key to relevant field declarations, without the sort code needing to know what the class looks like! This is part of the power of reflection.
The Field Class
The Field class lets us examine the attributes of a field, and retrieve and set the field’s value. Remember that you can get Field objects via methods of Class.
Here are some of the more useful methods of Field:
Method | Function |
Object get(Object obj) | Gets the value of the field in the instance obj. For example, if field is an integer field in MyClass, and myInstance is an instance of myClass, thenInteger myInt = (Integer) field.get(myInstance); returns the value of the field in myInstance. If the field is static, obj is ignored and can be null. |
<T extends Annotation> T getAnnotation (Class<T> annotationClass) | Returns the specified Annotation on the Field, if present. For example using our code above:Field field = Directory.class.getField("city"); |
boolean getBoolean(Object obj) byte getByte(Object obj) char getChar(Object obj) double getDouble(Object obj) float getFloat(Object obj) int getInt(Object obj) long getLong(Object obj) short getShort(Object obj) | Gets the value of the field within instance obj as boolean, byte, char, double, float, int, long, or short, respectively. If the field is static, obj is ignored and can be null. |
int getModifiers() | Returns the modifiers (like public or static) for the field, coded as an integer. Java has methods for determining which modifiers are present. |
String getName() | Returns the name of the field. |
Class<?> getType() | Returns a Class object that identifies the declared type of the field. |
void set(Object obj, Object value) | Sets the value of the field in instance obj to value. If the field is static, obj is ignored and can be null. |
void setAccessible(boolean flag) | Sets the accessibility of the field to true or false. For example, if a field is private, you must set its accessibility to true before you can get or set it. |
boolean canAccess(Object obj) | Returns true if the caller can access the field within obj. (This replaces the deprecated method isAccessible().) If the field is static, obj is ignored can can be null. |
void setBoolean(Object obj, boolean z) void setByte(Object obj, byte b) void setChar(Object obj, char c) void setDouble(Object obj, double d) void setFloat(Object obj, float f) void setInt(Object obj, int i) void setLong(Object obj, long l) void setShort(Object obj, short s) | Sets the field value in instance obj to the provided boolean, byte, char, double, float, int, long, or short value, respectively. If the field is static, obj is ignored and can be null. |
The Constructor Class
We noted above that once you have a Class object, you can get it to provide all its declared constructors, which are (yup!) objects of class Constructor. And as we also noted above, the Constructor class has a method for instantiating instances of the class to which a constructor belongs.
Here are the more useful methods of the Constructor class.
Method | Function |
<T extends Annotation> T getAnnotation (Class<T> annotationClass) | Returns the specified Annotation on the constructor, if present. |
int getModifiers() | Returns the modifiers (like public or static) for the constructor, coded as an integer. Java has methods for determining which modifiers are present. |
String getName() | Returns the constructor’s name. |
Annotation[][] getParameterAnnotations() | Returns the annotations on each argument to the constructor. |
int getParameterCount() | Returns the number of arguments to the constructor. |
Class<?>[] getParameterTypes() | Returns an array of the parameter types passed to the constructor. |
boolean isVarArgs() | Returns true if the constructor accepts a variable number of arguments. |
T newInstance(Object… initArgs) | Returns an instance of the class to which the constructor belongs. |
Of all the methods, newInstance() is hands down the most useful, as it allows you to create an instance of the class. For example:
Class<TheClass> clazz = TheClass.getClass();
Constructor const = clazz.getConstructor(Integer.class, String.class);
TheClass theInstance = const.newInstance(1, "ABC");
This code snippet creates an instance of TheClass using the constructor that accepts an Integer and a String as arguments.
The Method Class
The Class class gives you ways of getting Method objects that describe the methods defined in the class. Not surprisingly, the Method class is similar to the Constructor class, but with tweaks that apply to methods.
Method | Function |
<T extends Annotation> T getAnnotation (Class<T> annotationClass) | Returns the specified Annotation on the method, if present. |
int getModifiers() | Returns the modifiers (like public or static) for the method, coded as an integer. Java has methods for determining which modifiers are present. |
String getName() | Returns the method’s name. |
AnnotatedType getAnnotatedReturnType() | Returns an AnnotatedType object for the return type of the method. |
Annotation[][] getParameterAnnotations() | Returns the annotations on each argument to the method. |
int getParameterCount() | Returns the number of arguments to the method. |
Class<?>[] getParameterTypes | Returns an array of the parameter types passed to the method. |
Class<?> getReturnType() | Returns the type of return value. If the return type is “void”, the class returned is one with the name “void”. |
boolean isVarArgs() | Returns true if the method accepts a variable number of arguments. |
Object invoke(Object obj, Object… args) | Calls the method with the specified arguments (args) on the specified object (obj). If the method is static, obj is ignored and may be null. |
AnnotatedType and AnnotatedElement
Method getAnnotatedReturnType() of the Method class returns an AnnotatedType object. AnnotatedType is an interface which extends another interface: AnnotatedElement.
AnnotatedElement is implemented by all the classes in java.lang.reflect that can accept annotations: Class, Method, Field, etc. It has methods for acquiring the annotations attached to an object and determining the values of their parameters. AnnotatedType adds one more method: getType(), which returns the Type of the object the annotation is attached to.
And Type, in turn, is the superinterface common to all types in the Java language, such as objects, primitives, array types, and so on. Type has one method: getTypeName(), which returns a String holding the type name. Type is implemented by one class: Class. So you can call getTypeName() on any Class instance to see what its name is.
Modifier
You’ve probably noticed that each of the classes above as a getModifiers() method, which returns an int showing which modifiers are set on a field, constructor, class, interface, or method.
Java provides the Modifier class, which provide static methods to make it easy to test the value returned by getModifiers(). Each returns a boolean telling whether the modifiers reflect a given attribute. For example…
int mods = TheClass.class.getField("theField").getModifiers();
boolean isPublicField = Modifier.isPublic(mods);
… sets isPublicField to true if theField has the public attribute. Likewise, Modifier has these methods for testing other attributes:
- isAbstract()
- isFinal
- isInterface
- isPrivate
- isProtected
- isPublic
- isStatic
- isSynchronized
- isTransient
Exercise
Now let’s apply what you’ve learned. Download Lesson-15-Exercise-01 and import it into your workspace.
This project includes two text files with fields separated by commas. Corresponding to each of the files is a class: Directory or Payroll; these are subclasses of BaseRecord.
Your task is:
- Add code to the BaseRecord constructor to add Field objects to the List fieldList. The list should be ordered such that superclass fields appear in order of declaration followed by subclass fields.
- Add code to BaseRecord.parse() to populate the fields of the current class.
- Add code to BaseComparator.compareTo() to acquire the field values being compared.
- Add code to BaseComparator.createControlList() to build SortControl objects using the @Key annotations on fields of the class being sorted.
You’ll want to look at the SortControl and Key source files.
Notice that BaseRecord, Directory, and Payroll have no getters or setters. In this exercise, we don’t need them.
The output from running the exercise should look like this, with the lines in the order shown!
employeeNumber=5 lastName=Simpson firstName=Homer address=33 Spring Arbor Dr city=Springfield state=IL zip=60606
employeeNumber=2 lastName=Doe firstName=John address=123 Main St city=New York state=NY zip=10001
employeeNumber=3 lastName=Roe firstName=Richard address=123 Main St city=New Rochelle state=NY zip=10022
employeeNumber=4 lastName=Munster firstName=Lily address=1313 Mockingbird Lane city=Anytown state=WA zip=90909
employeeNumber=1 lastName=Munster firstName=Herman address=1313 Mockingbird Lane city=Anytown state=WA zip=90909
employeeNumber=3 hoursWorked=14.0 payRate=7.5
employeeNumber=2 hoursWorked=30.0 payRate=15.99
employeeNumber=1 hoursWorked=40.0 payRate=35.0
After you’re done, download a solution here.
What You Need to Know
- Reflection gives you the power to write general-purpose code that can create and process objects without knowing their specifics.
- The most prominent classes used by reflection are Class, Constructor, Field, and Method.
Up next: User Libraries in Eclipse