2017-05-15 68 views
6

這是Java8 retrieving lambda setter from class的後續操作。確保通用lambda表達式的類型安全性

我試圖讓一個getter方法對某個領域

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); 
} 

這是基本的方法:

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

這並不編譯,由於類型不匹配:

IGetter<TestEntity, Long> getter = accessorFactory.getGetter(TestEntity.class, "name", String.class); 

但是這樣編譯:

Field field = TestEntity.class.getDeclaredField("name"); 
IGetter<TestEntity, Long> getter = accessorFactory.getGetter(TestEntity.class, field); 

,令我驚訝的是,這並不使用它上面檢索到的吸氣工作:

TestEntity testEntity = new TestEntity(1L, "Test"); 
System.out.println(getter.get(testEntity)); 

但是,一旦我這樣做:

Long value = getter.get(testEntity); 

我得到以下異常:

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) 

有什麼方法可以趕上這個更早?

的TestEntity類:

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

回答

7

的問題是,你的方法

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); 
} 

實際上並沒有執行檢查。你基本上是鑄造一個Class實例來鍵入Class這沒有效果。通用類型Class<?>更改爲Class<R>是一個純粹的編譯時事情,這就是爲什麼在這一點上應該得到一個「未經檢查」的警告,至少如果啓用了所有警告。

在運行時做一個真正的檢驗的唯一方法,是讓來自呼叫者的預期類型:

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); 
} 

當然,這使得該方法接受Field有點毫無意義。實際的getGetter將已經執行查找,要求獲取者的返回類型完全匹配,並且不要求該字段的類型和獲取者的返回類型必須匹配。實際上,它會對內部細節產生不必要的依賴。

爲什麼不簡單註釋獲取者而不是字段...