4

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?

Eran
  • 387,369
  • 54
  • 702
  • 768
TrespassersW
  • 403
  • 7
  • 14

2 Answers2

4

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.

Community
  • 1
  • 1
Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • 3
    Yes. This. Plus one. Using raw types fouls up all the fancy type inference the compiler does for lambdas and method references. – Stuart Marks May 05 '15 at 22:42
  • Ah. Thank you. I've been converting code from C#, which allows you to define generic and non-generic classes with the same name. Since Java disallows that, I thought I could accomplish the same thing this way. But it looks like it isn't doing quite what I expected and is deprecated. Thank you for linking the other post. That was very informative. – TrespassersW May 07 '15 at 16:00
  • 1
    Also, I just wanted to add: I love that you offered an actual solution. While I agree that using deprecated code is a bad idea, and in this case I will find an alternate solution, it's still nice that you went beyond saying "Just don't do that" to finding a way to make it work. Thanks again. If I could Plus one your answer again, I would. – TrespassersW May 07 '15 at 16:08
0

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.

swingMan
  • 732
  • 1
  • 6
  • 17