Reputation: 11
public interface JavaInterface<T> {
void sayHi(T[] keys);
}
public class JavaInterfaceImpl implements JavaInterface<String> {
@Override
public void sayHi(String[] array) {
}
}
class ScalaInterfaceImpl extends JavaInterface[String] {
override def sayHi(keys: Array[String]): Unit = {}
}
Here are the error messages.
class ScalaInterfaceImpl needs to be abstract, since method sayHi in trait JavaInterface of type (x$1: Array[String])Unit is not defined
(Note that Array[T with Object] does not match Array[String]: their type parameters differ)
class ScalaInterfaceImpl extends JavaInterface[String] {
and
method sayHi overrides nothing.
Note: the super classes of class ScalaInterfaceImpl contain the following, non final members named sayHi:
def sayHi(x$1: Array[String]): Unit
override def sayHi(keys: Array[String]): Unit = {}
trait
, things goes fine.trait ScalaInterface[T] {
def sayHi(keys: Array[T]): Unit
}
1
is not changeable (from a third-party lib), hence I'm blocked in 3
.update 1. I find it works with the following code
class ScalaInterfaceImpl extends JavaInterface[String] {
override def sayHi(keys: Array[String with Object]): Unit = {}
}
Can anyone help explain it?
Upvotes: 1
Views: 50
Reputation: 2638
Generic arrays are represented slightly different between the two languages. Even though they are not type erased, their enclosing generic trait and interface have their type parameter T
erased at compile-time.
First, consider the Scala example:
trait ScalaInterface[T] {
def sayHi(keys: Array[T]): Unit
}
After scalac
compiles it, this becomes:
public interface ScalaInterface {
void sayHi(Object var1);
}
Whereas the Java interface:
public interface JavaInterface<T> {
void sayHi(T[] keys);
}
After javac
compiles it, this becomes:
public interface JavaInterface {
void sayHi(Object[] var1);
}
The slightly different signature between the two looks harmless at first, but it actually makes the difference. Why Scala represents them differently is explained here:
What about genericity? In Java, you cannot write a
T[]
whereT
is a type parameter. How then is Scala’sArray[T]
represented? In fact a generic array likeArray[T]
could be at run-time any of Java’s eight primitive array typesbyte[]
,short[]
,char[]
,int[]
,long[]
,float[]
,double[]
,boolean[]
, or it could be an array of objects. The only common run-time type encompassing all of these types isAnyRef
(or, equivalentlyjava.lang.Object
), so that’s the type to which the Scala compiler mapsArray[T]
.
When you used JavaInterface<T>
, you actually placed the type Object
as the upper bound of type T
, at every location where T
is used in the interface, in this case in the sayHi
method. This happens because of type erasure.
On the other hand, the type T
in the generic trait ScalaInterface[T]
is not bounded to Object
; it's bounded to Any
because that's the base type in Scala. This means T
has different upper bounds between the two languages in the method sayHi
.
Therefore, even though JavaInterface<T>
and ScalaInterface[T]
look similar, their sayHi
method is not really equivalent. The scalac
compiler does some type inference under the hood, checks the location where type T
is used and concludes that the sayHi
method needs the following type constraint:
trait ScalaInterface[T] {
def sayHi(keys: Array[T with Object]): Unit
}
This is actually how your JavaInterface
is treated and why using String with Object
fixes the issue.
A question that arises is why Scala does not instead use Object
as the upper bound of the generic type:
trait ScalaInterface[T <: Object] {
def sayHi(keys: Array[T]): Unit
}
While this works for this particular use case, it's too constraining, because you would never be able to use subtypes of AnyVal
in type T
at all, so ScalaInterface[Int]
or ScalaInterface[Double]
would be impossible, just because the interface has a method that takes a generic array as argument.
If your interface did not have a method taking a generic array as argument it would not need this constrain at all. Or if your interface had another method that took a simple argument of type T
, that would constrained as well. Such considerations show the issue is not actually in the type argument T
, but in the method sayHi
, so this is where the issue is pinpointed and contained.
Upvotes: 1