Return generic type and upcast


#1

In java, we can declare return type with generic type, like this:

  public List<Object> get......();

And we can return instance of any subclass of Interface List.
But in scala, if we use trait or superclass in function declare statement, the result should be the type exactly declared .
So, can we declare return type using upcast like Java?


#2

Nothing’s stopping you from:

def getList():List[AnyRef]

or using:

def get():List[_] = ???

#3

As you example code, if I return a instance of ListBuffer[String], there will be a compiling error like this:
2019-04-15_162415


#4

ListBuffer[String] is not a subtype of ListBuffer[AnyRef]

with

val list = new ListBuffer[AnyRef] this would fix your problem.

You could also cast your buffer when your return it, i.e. list.asInstanceOf[ListBuffer[AnyRef]], but casting is generally less safe, so that shouldn’t be preferred here.

One could argue the main problem here is the signature of getListBuffer, and that it should never return ListBuffer[AnyRef], since there is very little you can do with that safely, but rather ListBuffer[String] (or maybe something like List[String], or List[A] where the A is a type parameter you should have on the class)


#5

All Java generics are invariant, because Java uses use-site variance, not declaration-site variance (like Scala does). Therefore you can write:
val listS: scala.List[AnyRef] = scala.List[String]()
but you cannot write:
val listJ: java.util.ArrayList[AnyRef] = new java.util.ArrayList[String]()
the compilation error is:

error: type mismatch;
 found   : util.this.ArrayList[String]
 required: util.this.ArrayList[scala.this.AnyRef]
Note: String <: scala.this.AnyRef, but Java-defined class ArrayList is invariant in type E.
You may wish to investigate a wildcard type such as `_ <: scala.this.AnyRef`. (SLS 3.2.10)
  val listJ: java.util.ArrayList[AnyRef] = new java.util.ArrayList[String]()

Indeed, doing what compiler suggest fixes compilation error. Following code compiles:
val listJ: java.util.ArrayList[_ <: AnyRef] = new java.util.ArrayList[String]()
and is equivalent to Java’s code:
java.util.ArrayList<? extends Object> listJ = new java.util.ArrayList<String>();
Note that with <Object> instead of <? extends Object> Java code also won’t compile and compiler will output this error:

error: incompatible types: ArrayList<String> cannot be converted to ArrayList<Object>
		java.util.ArrayList<Object> listJ = new java.util.ArrayList<String>();

In Scala some collections are covariant and some are invariant. As a rule of thumb:

  • immutable collections are covariant, so you can do: val listImm: collection.immutable.Seq[AnyRef] = collection.immutable.Seq[String]()
  • mutable collections are invariant, so you can’t do: val listMut: collection.mutable.Seq[AnyRef] = collection.mutable.Seq[String]()

More about variances: https://docs.scala-lang.org/tour/variances.html