javalambda

How can I obtain the return type of a lambda method in Java?


I'm trying to get the class of the generic type <T> in my Java code. I am using Alibaba's FastJSON for JSON processing. Below are the key parts of my code:

Caller:

public Features findFeatureById(BigDecimal id) {
    return mixedCacheService.get(FEATURE, id.toString(), new MixedCacheService.Func<>(() -> featuresDao.selectById(id)));
}

Callee:

public <T> T get(@NotNull CacheBaseConstants cacheBaseConstants, @NotNull String key, @NotNull Supplier<T> getter) {
    String value = get(cacheBaseConstants, key);
    if ("".equals(value)) {
        return null;
    }
    // How can I get T.class here?
    return Optional.ofNullable(value)
            .map(o -> JSON.parseObject(value, T.class))
            .orElseGet(() -> put(cacheBaseConstants, key, getter));
}

I attempted to extend com.alibaba.fastjson.TypeReference:

public static class Func<T> extends TypeReference<T> {
    private Supplier<T> supplier;

    public Func(Supplier<T> supplier) {
        super();
        this.supplier = supplier;
    }

    public T get() {
        return supplier.get();
    }
}

I also tried using getter.supplier.getClass().getGenericSuperclass(), but it returned Object.


prescription

import cn.hutool.core.lang.TypeReference;

import java.util.function.Supplier;

public abstract class SupplierProxy<T> extends TypeReference<T> {
    private final Supplier<T> supplier;

    public SupplierProxy(Supplier<T> supplier) {
        super();
        this.supplier = supplier;
    }

    public T get() {
        return supplier.get();
    }

}

Unit Test

   @Test
    public void get() {
        Features actual = new Features();
        actual.setName("123");
        assertEquals(actual,mixedCacheService.get(FEATURE_BY_KEY, "1", new SupplierProxy<Features>(() -> actual) {
        }));
        assertEquals( actual,mixedCacheService.get(FEATURE_BY_KEY, "1", new SupplierProxy<Features>(() -> actual) {
        }));
    }

Thank you all for your replies, they have inspired me


Solution

  • You can't. That's what 'erasure' means. Generics are a figment of the compiler's imagination. If at compile time it is not possible to know, then it is not possible to know.

    Generics is preserved in signatures. In other words, given:

    class Foo<K, T extends List<K>> {}
    

    Then at runtime you can still get the generics out, but what you can get out, is what is there at compile time. In other words, you can get literally:

    What you cannot get, is given, say:

    Foo<String, ArrayList<String>> x = new Foo<>();
    System.out.println(deriveStatsFrom(x));
    

    that deriveStatsFrom cannot possibly obtain String or ArrayList<String> here. It's not a matter of 'it is difficult'; no, it is a matter of that is impossible - the compiler removes all this stuff therefore at runtime it is simply not possible to determine this.

    You mention com.alibaba.fastjson.TypeReference.

    This class was explicitly designed to get around this.

    To use it, you must write your type refs like this:

    TypeReference<List<String>> stringListRef = new TypeReference<List<String>>() {};
    

    Note the weird {} at the end. This is mandatory. This creates a tiny little class, classes do carry their signature's generics. In other words, the above is lots of syntax sugar for:

    class SomeIrrelevantName extends TypeReference<List<String>> {}
    
    TypeReference<List<String>> stringListRef = new SomeIrrelevantName();
    

    and therefore, stringListRef.getClass() lets you get at it.

    That's what the API wants you to do. In your method you'll have to add an argument of type TypeReference<T>; this method that you pasted cannot know so you can't work around it. You really have to keep telling yourself this as a mantra: If at compile time you do not know then you DO NOT KNOW. Whatever knows it at compile time has to make this thing and pass it around.