This is somewhat of a follow up on Java8 retrieving lambda setter from class.
I'm trying to get a getter method for a given field
public <T, R> IGetter<T, R> getGetter(Class<T> clazz, Field field) {
Class<R> fieldType = null;
try {
fieldType = (Class<R>) field.getType();
} catch(ClassCastException e) {
error("Attempted to create a mistyped getter for the field " + field + "!");
}
return getGetter(clazz, field.getName(), fieldType);
}
This is the underlying method:
public <T, R> IGetter<T, R> getGetter(Class<T> clazz, String fieldName, Class<R> fieldType) {
MethodHandles.Lookup caller = null;
MethodHandle target = null;
MethodType func = null;
try {
caller = MethodHandles.lookup();
MethodType getter = MethodType.methodType(fieldType);
target = caller.findVirtual(clazz, computeGetterName(fieldName), getter);
func = target.type();
} catch (NoSuchMethodException e) {
error("Could not locate a properly named getter \"" + computeGetterName(fieldName) + "\"!");
} catch (IllegalAccessException e) {
error("Could not access \"" + computeGetterName(fieldName) + "\"!");
}
CallSite site = null;
try {
site = LambdaMetafactory.metafactory(
caller,
"get",
MethodType.methodType(IGetter.class),
func.generic(),
target,
func
);
} catch (LambdaConversionException e) {
error("Could not convert the getter \"" + computeGetterName(fieldName) + "\" into a lambda expression!");
}
MethodHandle factory = site.getTarget();
IGetter<T, R> r = null;
try {
r = (IGetter<T, R>) factory.invoke();
} catch (Throwable throwable) {
error("Casting the factory of \"" + computeGetterName(fieldName) + "\" failed!");
}
return r;
}
This does not compile, due to a type mismatch:
IGetter<TestEntity, Long> getter = accessorFactory.getGetter(TestEntity.class, "name", String.class);
This however does compile:
Field field = TestEntity.class.getDeclaredField("name");
IGetter<TestEntity, Long> getter = accessorFactory.getGetter(TestEntity.class, field);
And, to my surprise, this does work using the getter which was retrieved above:
TestEntity testEntity = new TestEntity(1L, "Test");
System.out.println(getter.get(testEntity));
However, once I do this:
Long value = getter.get(testEntity);
I get the following exception:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
at de.cyclonit.exercise.Main.main(Main.java:26)
Is there some way to catch this earlier on?
The TestEntity class:
public class TestEntity {
private Long id;
private String name;
public TestEntity(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
}
A lambda expression can't specify type parameters, so it's not generic. However, a functional interface associated with lambda expression is generic. In this case, the target type of lambda expression has determined by the type of argument(s) specified when a functional interface reference is declared.
In order to match a lambda to a single method interface, also called a "functional interface", several conditions need to be met: The functional interface has to have exactly one unimplemented method, and that method (naturally) has to be abstract.
Lambda Expressions are anonymous functions. These functions do not need a name or a class to be used. Lambda expressions are added in Java 8. Lambda expressions basically express instances of functional interfaces An interface with a single abstract method is called a functional interface.
Q 5 - Which of the following is correct about Java 8 lambda expression? A - Lambda expressions are used primarily to define inline implementation of a functional interface.
The problem is that your method
public <T, R> IGetter<T, R> getGetter(Class<T> clazz, Field field) {
Class<R> fieldType = null;
try {
fieldType = (Class<R>) field.getType();
} catch(ClassCastException e) {
error("Attempted to create a mistyped getter for the field " + field + "!");
}
return getGetter(clazz, field.getName(), fieldType);
}
is not actually performing a check. You are basically casting a Class
instance to type Class
which has no effect. The change of the generic type Class<?>
to Class<R>
is a pure compile-time thing, which is why you should get an “unchecked” warning at this point, at least, if all warnings are enabled.
The only way to do a real check at runtime, is to get the expected type from the caller:
public <T, R> IGetter<T, R> getGetter(Class<T> clazz, Class<R> fieldType, Field field) {
if(fieldType != field.getType()) {
error("Attempted to create a mistyped getter for the field " + field + "!");
}
return getGetter(clazz, field.getName(), fieldType);
}
Of course, it makes the method accepting a Field
a bit pointless. The actual getGetter
will already perform a lookup requiring an exact match of the getter’s return type and you gain nothing from requiring that the field’s type and the getter’s return type have to match. Actually, it creates an unnecessary dependency to internal details.
Why not simply annotate the getters instead of the fields…
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With