Ensuring type safety on generic lambda expression

Cyclonit :

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;
    }
}
Holger :

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…

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=467392&siteId=1