TrespassersW
TrespassersW

Reputation: 413

Generic FunctionalInterface and Method Reference messed up by Type Erasure

I have the following generic FunctionalInterface:

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

And this ArrayList descendant:

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

Now, I write this code using method references and type erasure:

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 baffles me. Why would the compiler be fine with me assigning this::doFoo to a FooInterface variable, and calling List.add() in the first part of the code, only to reject calling the same add() method from the class that descends from ArrayList?

Seems like something funky is going on with type erasure in my descendant class, but what? Is this a bug? Have I done something not supported?

Upvotes: 4

Views: 260

Answers (2)

Radiodef
Radiodef

Reputation: 37845

FooList (without a type argument) is called a raw type. 4.8. Raw Types says this:

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

This means that a raw FooList is just a raw ArrayList and the method add accepts Object.

Since Object is not a functional interface, it cannot be the target of a lambda. This would not work either:

Object f = this::doFoo;

The full compiler error more or less corroborates all this:

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

One way to "fix" it is by doing something tricky like the following:

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

Really the solution here is to not use raw types but since you mention 'erasure' it would seem you are aware of this to some extent. There's no reason to use raw types.

Upvotes: 4

swingMan
swingMan

Reputation: 752

You need to parameterize FooList. If you change

FooList list2 = new FooList();

to

FooList<FooInterface> list2 = new FooList();

it will get rid of the compiler error.

Upvotes: 0

Related Questions