简体   繁体   中英

Irregularities in the Scala class hierarchy

Scala superimposes a very elegant class hierarchy over Java's type system, balooning out from Any at the top, into AnyRef and AnyVal to cover Java's Objects and primitives respectively, then finally converging, collapsing the reference types onto Null and all types onto Nothing. As I understand it, Nothing is a subtype of everything; Null a subtype of all subtypes of AnyRef/java.lang.Object. [ see http://www.scala-lang.org/node/128 ]

However, there seem to be a few irregularities, a few places where it does not work to simply think of all Scala types as elements of a seamless type hierarchy. I find this irksome, and want to understand the places where I might be surprised.

So far, I know of a few irregularities:

1) Although Null is a subtype of AnyRef, calling null.isInstanceOf[AnyRef] (or other subtypes of AnyRef) returns false. I suspect this was chosen to be consistent with the behavior of Java's instanceof operator.

2) Everything is covariant to Nothing, regardless of variance annotations. If I have a method that returns a type T that is not marked covariant, I can override that method to return type Nothing. [NOTE: this claim is mistaken, see answers and comments below!]

3) I can't apply isInstanceOf to the type AnyVal [ See Why can AnyVal not be used in an isInstanceOf check? and How to test a value on being AnyVal? ]

4) It is illegal to ask whether something isInstanceOf[Null], which is a perfectly coherent thing to ask (although not particularly necessary, since "myVar == null" would give the same answer)

Are there other examples of irregularities or special cases in Scala's type hierarchy? I feel like these are worth learning and understanding to avoid unwelcome surprises.

1) "A string is a subtype of AnyRef".isInstanceOf[AnyRef] returns true . This is true for other subtypes of AnyRef as well, except for Null . The only irregularity there is done to be consistent with Java, as you said.

2) If B is a subtype of A , that is B <: A , then you can always override a method:

def foo: A = ...

to:

override def foo: B = ...

This is called refining the return type, and is always allowed. Since Nothing is a subtype of every other type ( Nothing <: A for all A ), you can always refine your return type to Nothing (eg by throwing an exception in the body of the method). This is a pretty regular property. The return type covariance is not directly related to variance annotations on type parameters .

3) The other questions cover this nicely.

4) This is because the Null type does not exist in the Java runtime. I guess if you wanted to emulate this, you could create your own instanceOf method - you would first have to check if the argument is null , otherwise, do the normal isInstanceOf check.

There are other irregularities, yes. See for example: If an Int can't be null, what does null.asInstanceOf[Int] mean?

Arrays are another example, where you may pay the uniformity of generic arrays with boxing/unboxing or instanceof checks at runtime. The new Array[Any] is translated into an object array - storing an integer into the array will result in boxing it. Whenever you use an Array[T] , where T has no upper bound, the array will be patterned matched against the correct runtime array type every time you index an element.

To better understand how you might be surprised it's useful to think in terms of how these constructs are translated to the JVM where there is a notion of primitive and reference types, boxing/unboxing and different array classes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM