Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensuring type safety on generic lambda expression

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;
    }
}
like image 213
Cyclonit Avatar asked May 15 '17 17:05

Cyclonit


People also ask

Can lambda expressions be generic?

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.

What are the two conditions required for using a lambda function?

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.

What is the type of lambda expression?

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.

Which of the following is true about lambda expression?

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.


1 Answers

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…

like image 98
Holger Avatar answered Sep 18 '22 10:09

Holger