In Scala, I need to override the following, given, Java classes and methods:
public abstract class AbstractJava<T> {
protected abstract T test(Class<? extends T> clazz);
}
public class ConcreteJava extends AbstractJava<Object> {
@Override
protected Object test(Class<?> clazz) {
return null;
}
}
// Scala
class ConcreteScala extends ConcreteJava {
protected override def test(clazz: Class[_ <: AnyRef]): AnyRef =
super.test(clazz)
}
I'm getting the compilation error:
error: ambiguous reference to overloaded definition,
both method test in class ConcreteJava of type
(clazz: java.lang.Class[_])java.lang.Object
and method test in class AbstractJava of type
(clazz: java.lang.Class[_ <: java.lang.Object])java.lang.Object
match argument types (Class[_$1]) and expected result type AnyRef
super.test(clazz)
I wouldn't expect the Scala compiler to refer to an abstract
method on a super
call. Also, I'd expect it to refer to the direct super class first.
How can I make the Scala class compile?
Thanks!
Edit:
When leaving off the super.test(clazz)
call, there'll be the error message:
error: name clash between defined and inherited member:
method test:(clazz: Class[_ <: AnyRef])AnyRef and
method test:(clazz: java.lang.Class[_])java.lang.Object in class ConcreteJava
have same type after erasure: (clazz: java.lang.Class)java.lang.Object
protected override def test(clazz: Class[_ <: AnyRef]): AnyRef = null
Well, of course these are the same types (or variants) ...! - So there's something wrong with Scala/Java inheritance ...
Thanks to michid, there's a preliminary solution:
class ConcreteScala3 {
this: ConcreteJava =>
protected override def test(clazz: Class[_ <: AnyRef]): AnyRef = {
this.foo() // method of ConcreteJava
null
}
}
although we can't make super
calls from here.
Responses are still most welcome.
There are some limitations when overriding Java methods with raw types. See the corresponding Scala ticket. Specifically Martin Odersky's comment: "[...] The only thing one can do in these situations is implement a subclass in Java that implements the method. [...]"
However, I pointed out in a blog post earlier that there seems to be a solution for certain cases. The trick is to explicitly declare the self type of the overriding Scala class using an existential type for the raw type on the Java side.
With this technique I got the following working:
public abstract class AbstractJava<T> {
protected abstract T test(Class<T> clazz);
}
public class ConcreteJava extends AbstractJava<Object> {
@Override
protected Object test(Class<Object> clazz) {
return null;
}
}
class ConcreteScala extends ConcreteJava {
this: AbstractJava[AnyRef] =>
protected override def test(clazz: Class[AnyRef]): AnyRef = {
super.test(clazz)
}
}
The question about the same issue was raised again in 2017.
I think that this is certainly a bug and I created an issue SI-10155.
You can apply the following workaround.
Create additional Java class that by overriding test()
"renames" it to renameTest()
and also provides ability to call super ConcreteJava.test()
through concreteTest()
method.
public abstract class RenameJava extends ConcreteJava {
public Object concreteTest(Class<?> c) {
return super.test(c);
}
abstract protected Object renameTest(Class<?> c);
@Override
protected Object test(Class<?> c) {
return renameTest(c);
}
}
Now in ConcreteScala
class you can override renameTest()
and you're still able to call super ConcreteJava.test()
method using concreteTest()
method.
class ConcreteScala extends RenameJava {
override protected def renameTest(c: Class[_]) = {
// custom logic
concreteTest(c)
}
}
来源:https://stackoverflow.com/questions/6440176/scala-overriding-generic-java-methods-ii