Reflection

Type introspection is the ability of programming languages to determine the type (or its properties) of an arbitrary object at runtime. Java takes this concept one step further with reflection, allowing not only to determine the type at runtime, but also modifying it.

At its core, reflection builds on java.lang.Class, a generic class that is the definition of classes (which we already came across in the previous chapter). Note that most of the classes we will be using when dealing with reflection are in the package java.lang.reflection (package summary), and (almost) none of them have public constructors – the JVM will take care of the instantiation.

There are different ways to get to the class object of a Class:

// at compile time
Class<String> klass1 = String.class;

// or at runtime
Class<? extends String> klass2 = "Hello, World!".getClass();
Class<?> klass3 = Class.forName("java.lang.String");
Class<String> klass4 = (Class<String>) new String().getClass();

System.out.println(klass1.toString());  // java.lang.String
System.out.println(klass2.toString());  // ditto...
System.out.println(klass3.toString());
System.out.println(klass4.toString());

klass1.getName();  // java.lang.String
klass1.getSimpleName();  // String

Note that for Class<T> you can use the class name if known at compile time (or use an unchecked cast at runtime), or use the ? (wildcard) and appropriate bounds for any type.

So what can you do with a Class<?> object?

Basic Type Information

// all of these will return true
int.class.isPrimitive();
String[].class.isArray();
Comparable.class.isInterface();
(new Object() {})
	.getClass()
	.isAnonymousClass();          // also: local and member classes
Deprecated.class.isAnnotation();  // we will talk about those later

As you can see, even primitive type like int or float have class objects (which are, by the way, different from their wrapper types Integer.class etc.!).

Type Internals

// family affairs...
String.class.getSuperClass();  // Object.class

// constructors
String.class.getConstructors();  // returns Constructor<String>[]

// public methods
String.class.getMethod("charAt", int.class);
String.class.getMethods();  // returns Method[]

// public fields (attributes)
String.class.getField("CASE_INSENSITIVE_ORDER");  // Comparator<String>
String.class.getFields();  // returns Field[]

// public annotations (more on this later)
String.class.getAnnotation(Deprecated.class);  // null...
String.class.getAnnotationsByType(Deprecated.class);  // []
String.class.getAnnotations();  // returns Annotation[]

As you would expect, these methods may throw NoSuch{Method,Field}Exceptions and, more importantly, SecurityExceptions (more on security later).

Furthermore, you can distinguish between declared fields (methods, …), and “just” fields: .getFields() (and .getMethods() etc.) will return the public fields of an object, including those inherited by base classes. Use .getDeclaredFields() (and .getDeclaredMethods() etc.) to retrieve all fields declared in this particular class.

Object Instantiation

As you can see, you can also query for constructors of a class. This is the base for creating new instances based on class definitions:

Class<?> klass = "".getClass();
String magic = (String) klass.newInstance();  // unchecked cast...

Sticking with the example of strings, the documentation tells us that there exist non-default constructors. Consider for example the String(byte[] bytes) constructor:

Constructor<String> cons = (Constructor<String>) String.class
	.getConstructor(byte[].class);
String crazy = cons.newInstance(new byte[] {1, 2, 3, 4});

Note that this may trigger quite a few exceptions: InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, all of which make sense if you extrapolate their meaning from the name.

Messing with Objects

Modifying Fields (Attributes)

Remember Rumpelstiltskin? In short: A very ambitious father claimed his daughter could produce gold. Put under pressure by the king, she cuts a deal with an imp: it spins the gold and in return she would sacrifice her first child – unless she could guess the imp’s name!

Ok, here’s the mean imp and the poor daughter:

class Imp {
	private String name = "Rumpelstiltskin";
	boolean guess(String guess) {
		return guess.equals(name);
	}
}
class Daughter {
	public static void main(String... args) {
		Imp imp = new Imp();

		// to save your child...
		imp.guess(???);
	}
}

Since Imp.name is private, the imp feels safe (it’s dancing around the fire pit…). But can we help the daugher save her firstborn? Yes, we can! Using reflection, we will sneak through the imp’s “head” to find the string variable that encodes the name.

Imp imp = new Imp();
String oracle = null;
for (Field f : imp.getClass().getDeclaredFields()) {  // get all fields
	f.setAccessible(true);  // oops, you said `private`? :-)
	if (Modifier.isPrivate(f.getModifiers())  // looking for `private String`
			&& f.getType() == String.class) {
		oracle = (String) f.get(imp);  // heureka!
	}
}
imp.guess(oracle);  // true :-)

Or alternatively, we could brainwash the imp.

Imp imp = new Imp();
for (Field f : imp.getClass().getDeclaredFields()) {
	f.setAccessible(true);
	if (Modifier.isPrivate(f.getModifiers()) 
		&& f.getType() == String.class) {
		f.set(imp, "Pinocchio");  // oops :-)
	}
}
imp.guess("Pinocchio");  // true :-)

The Field class allows us to retrieve and modify both the modifiers and the values of fields (given an instance).

Calling Functions

Similar to accessing and modifying fields, you can enumerate and invoke methods. Sticking with the imp above, what if the imp’s name were well-known, but nobody knew how to ask for a guess?

class WeirdImp {
	static final String name = "Rumpelstiltskin";
	private boolean saewlkhasdfwds(String slaskdjh) {
		return name.equals(slaskdjh);
	}
}

This time, the name is well known, but the guessing function is hidden. Again, reflection to the rescue.

WeirdImp weirdo = new WeirdImp();
for (Method m : weirdo.getClass().getDeclaredMethods()) {
	m.setAccessible(true);
	if (m.getReturnType() == boolean.class  // ...returns boolean?
			&& m.getParameterCount() == 1   // ...has one arg?
			&& m.getParameterTypes()[0] == String.class) {  // which is String?
		System.out.println(m.invoke(weirdo, WeirdImp.name));
	}
}

Basic Java Beans

Reflection can be used to facilitate an architecture where code is dynamically loaded at runtime. This is often called a plugin mechanism, and Java Beans have been around for quite a long time.

Consider this simple example: We want to have a game loader that can load arbitrary text-based games which are provided as a third party .jar file.

package reflection;
public interface TextBasedGame {
	void run(InputStream in, PrintStream out) throws IOException;
}

A simple parrot (echoing) game could be:

package reflection.games;
public class Parrot implements TextBasedGame {
	@Override
	public void run(InputStream in, PrintStream out) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(in));
		out.println("Welcome to parrot. Please say something");

		String line;
		while ((line = reader.readLine()) != null && line.length() > 0) {
			out.println("You said: " + line);
		}
		out.println("Bye!");
	}
}

These games all implement the TextBasedGame interface, and their .class files can be packaged into a jar. Later, if you know the location of the jar file, you can load classes by-name:

package reflection;
public class TextGameLoader {
	public static void main(String... args) throws 
		IOException, ClassNotFoundException, IllegalAccessException, 
		InstantiationException {

		// load classes from jar file
		URL url = new URL("jar:file:/Users/riko493/git/hsro-inf-prg3/example/games.jar!/");
		URLClassLoader cl = URLClassLoader.newInstance(new URL[] {url});

		// you can play "Addition" or "Parrot"
		final String choice = "Parrot";

		TextBasedGame g = (TextBasedGame) cl.loadClass("reflection.games." + choice)
				.newInstance();
		g.run(System.in, System.out);
	}
}

Check out this games.jar, which provides the games reflection.games.Addition and reflection.games.Parrot.

Security

The previous sections showed clearly how powerful the tools of reflection are. Naturally, security is a concern: what if someone loads your jars, enumerates all classes, and then tries to steal passwords from a user?

This has indeed been done, and is the reason why Java was historically considered insecure or even unsafe to use. However, newer versions of Java have a sophisticated system of permissions and security settings that can limit or prevent reflection (and other critical functionality).

Two things that do not work, at least out of the box:

  • While you can do a forced write on final fields, this typically does not affect the code at runtime, since the values are already bound at compiletime.
  • It is impossible to swap out methods since class definitions are read-only and read-once. If you wanted to facilitate that, you would have to write your own class loader.

Object Comparison

One last word regarding reflection and object comparison. As you know, we distinguish two types of equality: reference and content-based equality.

class K {
	K(String s) {
		this.s = s;
	}
}

int a = 1;
int b = 1;

a == b;  // true: for primitive types, the values are compared

K k1 = new K("Hans");
K k2 = new K("Hans");

k1 == k2;       // false: they point to different memory
k1.equals(k2);  // ???

If you don’t overwrite the .equals method in your class, the default version Object.equals will check for reference equality. If you overwrite it, you need to make sure to

  • test for null
  • test for reference equality (same memory?)
  • test for same type
  • call super.equals(), if it was overwritten there
  • compare all attributes

Consider this implementation of equals:

public boolean equals(Object o) {
	if (o == null) return false;
	if (o == this) return true;   // same memory

	// version A
	if (!this.getClass().equals(o.getClass()))
		return false;
	// version B
	if (this.getClass() != o.getClass())
		return false;
	// version C
	if (!(o instanceof K))
		return false;

	if (!super.equals(o))
		return false;

	// now compare attributes
	return this.s.equals(((K) o).s);

}

Here’s the question: Which of the versions A, B or C are correct ways to test if the types of the two objects match?

A) is correct, since we compare both runtime classes with .equals.

B) is correct, since the class objects are shared among all instances (and parametrized generics; recall type erasure).

C) is however incorrect: the instanceof operator would also return true if there is a match on an interface of derived class.

Annotations

Equipped with the tools to introspect classes both at compile and run time, we can now dive into annotations.

Annotations are meta-information, they can be attached to classes, methods or fields, and can be read by the compiler or using reflection at runtime. They are denoted using the @ character, for example @Override.

Defining Annotations

Annotations are similar to interfaces: both as in syntax and as in a method or field can have multiple annotations.

public @interface Fixed {
    String author() ;
    String date() ;
    String bugsFixed() default "" ;
}

This defines the annotation @Fixed(...) with three arguments; the last one is optional and defaults to the empty string.

@Fixed(author="riko493", date="2017-11-15")
void method() { ... }

In general, there are marker annotations (e.g. @Deprecated) without arguments, value annotations (e.g. @SuppressWarnings("...")) that take exactly one value, and more sophisticated annotations (e.g. @Fixed(...) above).

public @interface SomeValue {
	String value();
}
@SomeValue("meh") void method() { ... }

Meta-Annotations

Even annotations can be annotated. Meta annotations define where and how annotations can be used.

  • @Target({ElementType.FIELD, ElementType.METHOD}): Use this to limit your custom annotation to fields or methods.
  • @Retention(RetentionPolicy.{RUNTIME,CLASS,SOURCE}): This controls if the annotation is available at runtime, in the class file, or only in the source code.
  • @Inherited: Use this to make an annotation to be passed on to deriving classes.

Method Annotations

Here are a few popular method annotations, some of those you may have come across already:

class K {
	@Override
	public boolean equals(Object o) {
		// ...
	}
	@Deprecated
	public void useSomethingElseNow() {
		// ...
	}
	@SuppressWarnings("unchecked")
	public void nastyCasts() {

	}
}

What are they good for?

  • @Override is used to signal the intent of overwriting; results in compile error if its actually no overwrite (e.g. @Override public boolean equals(K k))
  • @Deprecated marks a method not to be used anymore; it might be removed in the future.
  • @SuppressWarnings(...) turns off certain compiler warnings

Type Annotations

Here are a few type annotations (source):

  • @NonNull: The compiler can determine cases where a code path might receive a null value, without ever having to debug a NullPointerException.
  • @ReadOnly: The compiler will flag any attempt to change the object.
  • @Regex: Provides compile-time verification that a String intended to be used as a regular expression is a properly formatted regular expression.
  • @Tainted and @Untainted: Identity types of data that should not be used together, such as remote user input being used in system commands, or sensitive information in log streams.

Examples of Annotations

JUnit5

The new JUnit5 test drivers inspect test classes for certain annotations.

class MyTest {
	BufferedReader reader;

	@BeforeAll
	void setUp() {
		reader = new BufferedReader();  // ...
	}

	@Test
	void testSomeClass() {
		// ...
	}
}

Most of the time, you will get around with @BeforeAll, @AfterAll and @Test; see this complete list of annotations.

Gson by Google

Gson by Google helps with de/serializing objects (see today’s assignment).

It allows you to map between JSON and Java objects:

class Klass {
	private int value1 = 1;
	private String value2 = "abc";
	@SerializedName("odd-name") private String oddName = "1337";
	private transient int value3 = 3;  // will be excluded
	Klass() {
		// default constructor (required)
	}
}

// Serialization
Klass obj = new Klass();
Gson gson = new Gson();
String json = gson.toJson(obj);  
// ==> json is {"value1":1,"value2":"abc","odd-name": "1337"}

// Deserialization
Klass obj2 = gson.fromJson(json, Klass.class);
// ==> obj2 is just like obj

Others

  • Hibernate: sophisticated object storage
  • Butterknife: GUI bindings for Android ridiculously simplified
  • Retrofit: consume REST interfaces without any pain
  • Lombok: avoid writing boilerplate code such as setter, toStrings etc.

Historic remark: The APT was removed in Java 8, see http://openjdk.java.net/jeps/117