Using generics and reflection to write generic code in Java
Generics and reflection are two language features that people often find hard to understand and often avoid them at first sight. However, these are also two of the most powerful features in an object oriented programming languages, for example, Java. In this post, I will briefly introduce these features and then we will use them to write generic code. Generic code is code that you can use to satisfy same type of requirements at different places in your project, using different parameters. This is done to avoid code duplication.
Generics
A generic type is a generic class or interface that is parameterised over types.
I know, that doesn’t make sense. Let us understand using a very basic example that you, as a Java user, use on a day to day basis. Let us say you have a class Customer in your project. Now, you want to have a list of objects of type Customer. This is how you would define that list.
List<Customer> customers = new ArrayList<>();
Notice, List<Customer>. Here, List is a generic interface that is parameterised over types. And, the type we are using in this case is Customer. Since, List is a generic interface, it can have any type(Employee, Department, Order etc…). Whatever type we will provide between <>, the methods of the List interface will operate over that type. The code to add an item in the list will be same, but it will work for any type that is passed between <>. And that is what being generic means.
Reflection
It is a feature that allows a user to write code, that can inspect and manipulate objects and classes at runtime. All while the program is running.
What this means is that, reflection can allow you to inspect a java object at runtime and access all variables/methods associated with it and even instantiate it and then use it. This is a very powerful tool. Of course, with great power comes great responsibility. So, you must be very careful when it comes to using this.
Let us understand using a very basic example. Let us say that there are two classes having the same method.
class TargetClass {
public String someMethod(int arg1, String arg2) {
return arg2 + "==" + arg1;
}
}
class AnotherTargetClass {
public String someMethod(int arg1, String arg2) {
return arg2 + "--" + arg1;
}
}
Now, you have a method somewhere, having a logic that requires you to access the someMethod on an object that is passed to it. This can be somewhere in a common library. You do not know whether the object will be of type TargetClass or AnotherTargetClass, but you will know that the class of the object will have someMethod. This is how you will write that method, making use of reflection.
public String someOtherMethod(Object obj) {
// 1. Get the Class object for the obj class
Class<?> cls = obj.getClass();
// 2. Get a reference to the desired method using the method name
Method method = cls.getMethod("someMethod", int.class, String.class);
// 3. Call the method using the invoke() method, passing in the target object and arguments
return (String) method.invoke(obj, 123, "Hello");
}
- You get the class of the parameter obj.
- You use the class to get the method you want to invoke, in this case someMethod having two parameters, int and String.
- You invoke the method on the obj with required parameters.
We used reflection to write generic code. In our case, our method can take any parameter having someMethod and work with it to generate the desired result. You might think that we could have used polymorphism/inheritance here — which you could have, but there are some requirements where you cannot. For example, legacy code where you might be forbidden to do such changes.
Using Generics and Reflection together
Let us say we have some java classes which can have two different id fields present on them.
class SomeClass {
private String id;
private String referenceId;
// getters, setters and contructors
// .....
}
class AnotherClass {
private String id;
private String referenceId;
// getters, setters and contructors
// .....
}
At runtime, we don’t know which field will come as populated in a REST request. This is how we can use generics and reflection to write code to get us the name of the field which is coming as populated — preferring id field in case both are populated and throws an exception if none are populated.
// T is the type parameter of the obj
public <T> String getNonNullFieldName(T obj, String fieldNameOne, String fieldNameTwo) {
// 1. Get the Class object for the obj class
Class<?> cls = obj.getClass();
// 2. Get the names of the getter methods of the two fields
String fieldNameOneGetMethodName = "get" + fieldNameOne.substring(0, 1)
.toUpperCase()
+ idField.substring(1);
String fieldNameTwoGetMethodName = "get" + fieldNameTwo.substring(0, 1)
.toUpperCase()
+ clientReferenceIdField.substring(1);
// 3. Get a reference to the getter method of fieldNameOne and fieldNameTwo using the method names
Method fieldNameOneGetMethod = cls.getMethod(fieldNameOneGetMethodName);
Method fieldNameTwoGetMethod = cls.getMethod(fieldNameTwoGetMethodName);
// 4. Call the methods using the invoke() method, passing in the target object
String resultOne = (String) fieldNameOneGetMethod.invoke(obj);
String resultTwo = (String) fieldNameTwoGetMethod.invoke(obj);
// 5. Check which field has value
if (resultOne != null) {
return fieldNameOne;
} else if (resultTwo != null) {
return fieldNameTwo;
} else {
throw new NullPointerException("Either " + fieldNameOne + " or "
+ fieldNameTwo + " should be present");
}
}
The code here assumes that obj will always have the two fields with respective getter methods. If that is not certain, then other safety checks should be put in place to avoid run time exceptions.
This is how this method will be called:
SomeClass someObject = new SomeObject("some-id", null);
String nonNullField = getNonNullFieldName(someObject, "id", "referenceId");
// nonNullField will have a value as "id"
AnotherClass someOtherObject = new AnotherClass(null, "some-reference-id");
nonNullField = getNonNullFieldName(someOtherObject, "id", "referenceId");
// nonNullField will have a value as "referenceId"
You can see that the type parameter of the method is changing in two calls. First, it is SomeClass and then it is AnotherClass. This is possible only because of generics. Compiler doesn’t complain about type safety because we till it that it can accept any parameter there.
And Reflection makes this method generic enough to get the non null field among any two fields in an object which has a getter method for those fields.
I hope this was useful to you!