通用FunctionalInterface和类型擦除

时间:2015-01-19 19:31:24

标签: java generics lambda java-8

以下编译错误意味着什么?

FooSetter类型中的方法apply(Sample.Foo,capture#5-of?)不适用于参数(Sample.Foo,  捕获#6-of?)

它出现在下面的代码中(在我用注释表示的底部),它试图将一个bean的setter映射到另一个bean的getter而不进行强制转换(是的,我知道有一些实用程序可以执行此操作)但这是一个例子)。我使用以下两个功能接口

@FunctionalInterface
public interface FooSetter<V> {
    void apply(Foo foo, V value);
} 


@FunctionalInterface
public interface BarGetter<T> {
   T apply(Bar from); 
}


public class Sample {

public static class Bar{
    public String description;
    public Integer num;

    public String getDecription(){
        return description;
    }
    public void setDescription(String desc){
        this.description = desc;
    }
    public Integer getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
}

public static class Foo{

    private String name;
    private Integer age;

    public Foo setName(String name){
        this.name = name;
        return this;
    }

    public Foo setAge(int age){
        this.age = age;
        return this;
    }

    @Override
    public String toString(){
        return "Name" + name + "  Age " + age;
    }
}

public static void main(String[] args) throws Exception  {

    HashMap<String, BarGetter<?>> barGetters= Maps.newHashMap();
    barGetters.put("description", (BarGetter<String>)(Bar b) -> b.getDecription());
    barGetters.put("num", (BarGetter<Integer>)(Bar b) -> b.getNum());

    HashMap<String, FooSetter<?>> fooSetters= Maps.newHashMap();
    fooSetters.put("name",   (Foo f, String name) -> f.setName(name));
    fooSetters.put("age",   (Foo f, Integer age) -> f.setAge(age));

    HashMap<String,String> fooFromBar = Maps.newHashMap();
                 // FOO      BAR
    fooFromBar.put("name", "description");
    fooFromBar.put("age", "num");

    Bar bar  = new Bar();
    bar.setDescription("bar desc");
    bar.setNum(5);

    Foo newFoo = new Foo();
    for (String key : fooFromBar.keySet()) {
       //COMPILATION ERROR  
       fooSetters.get(key).apply(newFoo, barGetters.get( fooFromBar.get(key) ).apply(bar) );
    }
 }
}

在类型擦除方面,我有没有看到这里的东西?

2 个答案:

答案 0 :(得分:2)

fooSetters.get(key)

返回类型FooSetter<?>的值,即。一个FooSetter,它不知道它设置的类型。因此,其apply方法具有以下(推断)定义

void apply(Foo foo, ? value);

您不能将此方法用于任何事物(对于其第二个参数),但null(可用于任何参考类型),因为您不知道它是什么类型。< / p>

barGetters.get( fooFromBar.get(key) )

返回BarGetter<?>类型的值,即BarGetter,它不知道它得到了什么。因此,apply方法显示为

? apply(Bar from);

?可能与fooSetter.apply所期望的完全不同。

因此,类型系统不允许它,并且会出现编译错误。

答案 1 :(得分:1)

如果你有一个不同类型的泛型元素的集合,你需要一个帮助器class,它包装了实际的元素并用运行时检查来保护它的访问权限,它取代了不起作用的编译时检查在这种情况下。

E.g。

final class FooSetterHolder<T> {
    final Class<T> guard;
    final FooSetter<T> setter;

    FooSetterHolder(Class<T> guard, FooSetter<T> setter) {
        this.guard = guard;
        this.setter = setter;
    }
    <U> FooSetterHolder<U> cast(Class<U> expectedType) {
        if(guard != expectedType) throw new ClassCastException(
            "cannot cast FooSetterHolder<"+guard.getName()
           +"> to FooSetterHolder<"+expectedType.getName()+'>');

        // now that we have checked the type
        @SuppressWarnings("unchecked") FooSetterHolder<U> h=(FooSetterHolder)this;

        return h;
    }
}

您可以通过以下方式使用此助手类:

HashMap<String, FooSetterHolder<?>> fooSetters= new HashMap<>();
fooSetters.put("name", new FooSetterHolder<>(String.class, Foo::setName));
fooSetters.put("age", new FooSetterHolder<>(Integer.class, Foo::setAge));

FooSetter<String> nameSetter=fooSetters.get("name").cast(String.class).setter;
FooSetter<Integer> ageSetter=fooSetters.get("age").cast(Integer.class).setter;

cast方法提供了一个操作,它在内部使用不可避免的未经检查的操作,但是对预期类型和实际类型执行运行时比较,以便使用它的代码是类型安全的(假设没有其他未经检查的操作)

因此编译器不会拒绝以下代码

FooSetter<Integer> wrong=fooSetters.get("name").cast(Integer.class).setter;

但不会创建堆污染,而是抛出ClassCastException,消息“cannot cast FooSetterHolder<java.lang.String> to FooSetterHolder<java.lang.Integer>”非常类似于普通的非泛型类型转换。