通用功能接口和方法参考被类型擦除弄乱了

Generic FunctionalInterface and Method Reference messed up by Type Erasure

我有以下通用功能接口:

@FunctionalInterface
public interface FooInterface<T> {
    void bar(T arg);
}

而这个 ArrayList 后代:

public class FooList<T> extends ArrayList<FooInterface<T>> {
    public void doFoo(T arg) {
        for(Iterator<FooInterface<T>> i = iterator(); i.hasNext(); ) {
            i.next().bar(arg);
        }
    }
}

现在,我使用方法引用和类型擦除编写这段代码:

protected void doFoo(Object arg) { }

private void doStuff() {                                         
    FooInterface f = this::doFoo; 

    List<FooInterface> list = new ArrayList<>();
    list.add(f2);                 
    list.add(this::doFoo);        

    FooList list2 = new FooList();
    list2.add(f2);                
    list2.add(this::doFoo);    // <-- Compiler chokes here, complaining that this is not a FunctionalInterface
}                                        

这让我很困惑。为什么编译器会同意我将 this::doFoo 分配给 FooInterface 变量,并在代码的第一部分调用 List.add(),只是拒绝从 [= 调用相同的 add() 方法25=] 是 ArrayList 的后代?

我的后代 class 中的类型擦除似乎发生了一些奇怪的事情,但是什么?这是一个错误吗?我做了一些不受支持的事情吗?

FooList(没有类型参数)被称为原始类型。 4.8. Raw Types 是这样说的:

The superclasses (respectively, superinterfaces) of a raw type are the erasures of the superclasses (superinterfaces) of any of the parameterizations of the generic type.

这意味着原始 FooList 只是原始 ArrayList 并且方法 add 接受 Object.

因为 Object 不是函数式接口,所以它不能成为 lambda 的目标。这也行不通:

Object f = this::doFoo;

完整的编译器错误或多或少证实了这一切:

error: no suitable method found for add(this::doFoo)
    list2.add(this::doFoo);    // <-- Compiler chokes here, complaining that this is not a FunctionalInterface
         ^
    method <strong>Collection.add(Object)</strong> is not applicable
      (argument mismatch; <strong>Object is not a functional interface</strong>)

实现 "fix" 的一种方法是执行如下操作:

public class FooList<T> extends ArrayList<FooInterface<T>> {
    @Override
    public boolean add(FooInterface<T> e) {
        return super.add(e);
    }
    ...
}

这里的真正解决方案是 not use raw types,但既然你提到了 'erasure',那么你似乎在某种程度上意识到了这一点。没有理由使用原始类型。

您需要参数化 FooList。如果你改变

FooList list2 = new FooList();

FooList<FooInterface> list2 = new FooList();

它将消除编译器错误。