Interface implementation with method argument superclasses
Asked Answered
L

3

6

As a practical example of the general question in the subject, I'd like to implement the containsAll method in the Set interface with

public boolean containsAll(Iterable<?> c) { /* ... */ }

I figure this should be allowed, since Collection is Iterable meaning such a containsAll would cover the interface requirement. Likewise, more generally being able to implement interfaces with argument superclasses seems like it should work.

However, Eclipse says no way (haven't tried just javac straight-up) - can someone explain the reason for that? I'm sure there's something in the spec which makes it the way it is, but I'd like to understand the motivation for requirement as well. Or am I missing something like Iterable<?> not being a superclass of Collection<?>?

As a side question - given I'm declaring two methods would the method with the Iterable signature always be preferred on calls with a Collection argument?

Eclipse Error:

If I remove the method with the Collection signature, just leaving the Iterable one (see after error), I get the following:

The type BitPowerSet must implement the inherited abstract method Set<Long>.containsAll(Collection<?>)

The exact implementation being:

@Override public boolean containsAll(Collection<?> c) {
  for (Object o : c) if (!contains(o)) return false;
  return true;
}
public boolean containsAll(Iterable<?> c) {
  for (Object o : c) if (!contains(o)) return false;
  return true;
}
Lownecked answered 16/10, 2010 at 16:6 Comment(5)
Could you post an error Eclipse is giving you? Works for me in IDEA.Rowdyism
@Nikita: edited in. soooo...it could just be an Eclipse thing?Lownecked
This one is a terminology nightmare. I run away from such challenges.Predilection
It seems, Peter's answer is correct, when you implement abstract method you have to follow exact signature. I've probably set up my example inaccurately, have to check it again.Rowdyism
Same error in NetBeans 6.8 for me.Secund
S
2

My guess as to why java has this restriction is, say you have:

class A {
    void foo(String s) { ...  }
}

class B extends A {
    // Note generalized type
    @Override void foo(Object s) { ...  }
}

Now if you have class C extends B and it wants to override foo, it's not clear what argument it should take.

Say for example C extended A directly at first, overriding void foo(String s), and then it was changed to extend B. In this case C's existing override of foo would become invalid because B's foo should be able to handle all Objects, not just Strings.

Soilure answered 16/10, 2010 at 16:34 Comment(3)
ah, that seems to be a sensible explanation - widening the interface would demand that sub classes maintain the widened interface. Still, that seems like it should be allowed - typically, subclasses aren't allowed to narrow interfaces.Lownecked
It also probably makes the virtual table's design clearer to assert that all methods in it have the exact signature. Or maybe they simply figured that they could make it work if they specced it out enough, but didn't see a compelling need and left it out.Soilure
I'm missing something--is that example supposed to work? On the @Override annotation I get "Method does not override method from its superclass".Foamflower
M
5

Since the interface you are implementing declares the (abstract) method containsAll(Collection<?>), you must implement it with this exact signature. Java does not allow you to implement/override a method with a wider parameter type than the original. This is why you get the error you show when you comment out your method with the Collection signature.

You don't show the other error you claim to get when the method is not commented out, but I guess it might have to do something with ambiguous method overloading.

Mcnully answered 16/10, 2010 at 16:21 Comment(3)
There is no error when the method isn't commented out. Only when the one with the Collection signature is.Lownecked
Also, any insight as to why this is the case? Is it along the lines of @oksayt's answer?Lownecked
@Carl, you mean why Java is designed so? Could be.Recipient
S
2

My guess as to why java has this restriction is, say you have:

class A {
    void foo(String s) { ...  }
}

class B extends A {
    // Note generalized type
    @Override void foo(Object s) { ...  }
}

Now if you have class C extends B and it wants to override foo, it's not clear what argument it should take.

Say for example C extended A directly at first, overriding void foo(String s), and then it was changed to extend B. In this case C's existing override of foo would become invalid because B's foo should be able to handle all Objects, not just Strings.

Soilure answered 16/10, 2010 at 16:34 Comment(3)
ah, that seems to be a sensible explanation - widening the interface would demand that sub classes maintain the widened interface. Still, that seems like it should be allowed - typically, subclasses aren't allowed to narrow interfaces.Lownecked
It also probably makes the virtual table's design clearer to assert that all methods in it have the exact signature. Or maybe they simply figured that they could make it work if they specced it out enough, but didn't see a compelling need and left it out.Soilure
I'm missing something--is that example supposed to work? On the @Override annotation I get "Method does not override method from its superclass".Foamflower
S
0

The argument types are part of the method signature so the jvm needs a method with exact the same signature to find overrides. A containsAll( Iterable) will have a different signature than containsAll(Collection).

If I remember right the compiler has to use some workarounds to make generics work in spite of this limitation.

To your second question, the compiler would prefer the Collection argument since it is a subtype of Iterable, this makes the Collection method more specific than the Iterable one.

Secund answered 16/10, 2010 at 16:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.