Published 2024-08-17.
Time to read: 10 minutes.
Scala’s type hierarchy is more comprehensive than Java’s. This lecture also discusses type conversion, type widening of returned values from expressions, object equality, the Scala REPL’s :type
command and provides detailed commentary on many important Scala types.
Scala’s type hierarchy is more comprehensive than Java’s.
This lecture also discusses type conversion,
type widening of returned values from expressions, object equality,
the Scala REPL’s :type
command and
provides detailed commentary on many important Scala types.
The sample code for this lecture can be found in
courseNotes/
.
Scala Inheritance Hierarchy
The Scala inheritance hierarchy is shown in the following diagram (no changes at this level of abstraction were required for 100% accuracy with Scala 2.13).

Notice that:
- All Scala values are objects, including numbers and strings.
-
All objects are subclasses of
Any
. - There are two main classifications of Scala types:
-
Immutable value types, such as the primitive types
Byte
,Short
,Char
,Int
,Long
,Float
,Double
,Boolean
, andUnit
.-
The base class of all value types is
AnyVal
. -
Byte
s can be viewed as / converted toShort
s,Short
s andChar
s can be viewed as / converted toInt
s,Int
s can be viewed as / converted toLong
s,Long
s can be viewed as / converted toFloat
s, andFloat
s can be viewed as / converted toDouble
s. Unit
can also be written as()
.
-
The base class of all value types is
-
Reference types, such as Scala and Java objects.
The base class of all reference types is
AnyRef
, which is equivalent tojava.lang.Object
.- All non-value objects (aka reference types) are subclasses of
AnyRef
. -
Reference types are created using the
new
keyword, unless the class extendsAnyVal
, which means the class is a value type. As we will learn shortly, companion objects often use the default methodapply
to hide the use of thenew
keyword, but it is there nonetheless. - All objects that you can define are subclasses of
AnyRef
, including all types ofFunction
s. -
Null
is a subtype of all reference types; it’s only instance isnull
.
- All non-value objects (aka reference types) are subclasses of
-
Immutable value types, such as the primitive types
- Since
Null
is not a subtype of value types,null
cannot be assigned to a variable defined as a value type. -
Nothing
is a subtype of all types. In other words,Nothing
is a subtype of everything – very Zen-like!
mostly consists of expressions
Type Widening
An expression returns a value. Unlike Java, which has many types of statements but few expressions, most Scala code is comprised of expressions.
Type widening happens when an expression does not return a consistent type.
Lets examine how type widening affects if
and match
expressions now.
if-then-else
An if
-then
-else
expression can return one type from the then
clause and another type from the else
clause.
In this example, the variable called rightNow
contains a Long
with the current system time in milliseconds.
The method called x
tests the time to see if it is an even number, or if today is a Tuesday, and concludes with a catch-all clause.
The return values are highlighted in orange, and their types are different:
Boolean
, String
and Long
.
Referring to the type hierarchy diagram we can see that the common supertype of all possible return types is Any
.
scala> import java.util.Date import java.util.Date
scala> def x: Any = { | val rightNow: Long = System.currentTimeMillis | if (rightNow % 2 == 0) true else | if (new Date(rightNow).toString.contains("Tue ")) | "Today is a Tuesday" else rightNow | } x: Any
scala> x res0: Any = true
scala> x res1: Any = 1565282539351
Running the above in the REPL shows that x
returns type Any
due to type widening.
Of course, the type returned by x
each time it is invoked might actually be
Boolean
, Long
or String
.
if-then
Similar to if
-then
-else
, if
-then
is an expression that returns a value, but the returned value is not useful and if used will introduce a bug.
This is because an if
expression without an else
clause is always subject to
type widening to type AnyVal
.
Scala 3 fixed this problem by disallowing the assignment of an if
statement without an else
clause:
$ scala -explain Welcome to Scala 3.4.2 (11.0.23, Java OpenJDK 64-Bit Server VM). Type in expressions for evaluation. Or try :help.
scala> def useless(x: Int, y: Int) = if (x>y) x 1 warning found -- [E190] Potential Issue Warning: --------------------------------------------- 1 |def useless(x: Int, y: Int) = if (x>y) x | ^ | Discarded non-Unit value of type Int. You may want to use `()`. |----------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | As this expression is not of type Unit, it is desugared into `{ x; () }`. | Here the `x` expression is a pure statement that can be discarded. | Therefore the expression is effectively equivalent to `()`. ----------------------------------------------------------------------------- def useless(x: Int, y: Int): Unit
The Scala 2 compiler computes the return type as AnyVal
,
which is an obscure clue that the computation might return Unit
if the non-existant else
clause is triggered:
scala> def useless(x: Int, y: Int) = if (x>y) x useless: (x: Int, y: Int)AnyVal
scala> useless(13, 14) res2: AnyVal = ()
scala> useless(132, 14) res3: AnyVal = 132
The nonexistent else
clause has value
Unit
,
which means “no value”.
Unit
is often written as ().
The type of the missing else
clause is added by the Scala compiler when it computes the value of the entire if
-then
expression.
Referring to the class hierarchy diagram we can see why the Scala compiler realizes that type AnyVal
is the most specific type that can represent all possible returned values from this if
/then
expression.
Always include an else
clause when an if
/then
expression is intended to return a value from a block of code.
match
We will explore match
expressions in more detail in the
Pattern Matching
lecture later in this course, but for this lecture let’s consider match
as just being a multi-way branch.
This multiway branch expression either returns an Int
, a Float
or a Double
.
The common supertype is Double
.
scala> def wat(x: Any): Double = x match { case int: Int => int case string: String => string.toFloat case _ => 1.23 } wat: (x: Any)Double
scala> wat(1) res4: Double = 1.0
scala> wat("1.23") res5: Double = 1.2300000190734863
Nothing and Throwable
Nothing
is Scala’s bottom type; all other types are subtypes of Nothing
.
Type Nothing
can therefore be widened to any other type.
Throwable
is a Java class which in Scala can be paired with the throw
keyword to throw an exception;
unhandled exceptions halt the currently running program.
The JVM defines two subclasses of Throwable
:
Error
and
Exception
.
If this information is news to you please read the Throwable
Javadoc.
Another good source of information is the chapter on Exceptions in the JVM Specification.
The sys.error
method prints a message to standard error and exits the program by throwing an Exception
,
specifically a RuntimeException
.
If you invoke sys.error
in the REPL the message is displayed and any computation that had been underway is halted, however the REPL does not exit. Here it is in action:
scala> sys.error("Oopsie!") java.lang.RuntimeException: Oopsie! at scala.sys.package$.error(package.scala:29) ... 28 additional lines of the stacktrace not shown
Because sys.error
is declared as returning type Nothing
you can invoke sys.error
in an assignment to any type, and the result type will be determined by the other, non-Nothing
type.
For example, the then
clause of this if
statement has type Int
, and the else
clause has type Nothing
;
this means the returned type of the entire if
expression is Int
.
scala> val meaning: Int = if (true) 42 else sys.error("Oopsie!") meaning: Int = 42
Because sys.error
ends the program it never returns a value,
so it should seem natural to be able to add in a call to sys.error
anywhere in your program without affecting return types.
Similarly, in Scala the throw
keyword is defined to have type Nothing
.
Again this means that return types of expressions are unaffected by exceptions that are thrown inside those expressions.
As we will learn in the
Try and try/catch/finally
lecture later in this course, in Scala each of the try
and case
clauses contribute to the computed return type.
In this next code example an Exception
will be thrown if there is a problem converting string
to an Int
.
Because that exception is merely returned by the case
clause without being handled, and is just rethrown,
the try
statement in this code example does not do anything useful.
The point of this code example is to show that because of type widening the type returned by the try
statement is Int
,
and the type returned by the case
clause is Nothing
,
so the returned type for the entire try
/ catch
expression is Int
.
scala> def try1(string: String): Int = | try { | string.toInt | } catch { | case e: Exception => throw e | } try1: (string: String)Int
scala> try1("123") res6: Int = 123
The try2
method builds on the try1
code example.
try2
returns Double
because both catch cases return Double
,
so the Int
computed by the try
clause is widened to Double
.
The first catch case handles NumberFormatException
s by returning the constant 42.0,
while the second catch case attempts to convert string
to a Double
. If that fails then an uncaught exception will be thrown.
scala> def try2(string: String): Double = | try { | string.toInt | } catch { | case _: NumberFormatException => | 42.0 | | case _: Exception => | string.toDouble | } try2: (string: String)Double
scala> try2("12.3") res7: Double = 12.3
scala> try2("asdf") res8: Double = 42.0
Exercise – Type Conversion and Type Widening
Taking the try2
example one step further, here is try3
.
Please explain why try3
returns Double
.
def try3(string: String): Double = try { string.toInt } catch { case _: Exception => try { string.toDouble } catch { case e: Exception => throw e } }
Solution
As the title of this excercise suggests, two things are going on in this code example: type conversion and type widening.
- The type of the try clause is declared to be
Int
. - The type of the nested try clause is
Double
. - The type of the innermost catch case is
Nothing
.
Int
s are converted to Double
s.
The common supertype of Double
and Nothing
is Double
, so the return type of try3
is Double
.
Declaring a Method that Returns Nothing
Methods that return Nothing
are only useful for terminating a program or throwing an exception.
Here is the Scala source code for
sys.exit
:
def exit(status: Int): Nothing = { java.lang.System.exit(status) throw new Throwable() }
The method calls java.lang.System.exit
, which never returns.
The expression that follows throws a new Throwable
,
but even though that is never executed its return type establishes the type of the exit
method because that is the last statement.
The run time libraries for Perl, PHP and Python
all define a method called die
. Here is an example of how to define and use a similar method for Scala,
also called die
, that returns Nothing
.
Notice that the variable called freedom
has type Int
,
and that the program ends if the else
clause executes.
def die(message: String): Nothing = { Console.err.println(message) Console.err.flush() sys.exit(1) } val freedom: Int = if (false) 99 else die("Help! I’m trapped in a computer!")
You can run the above code by typing:
$ sbt "runMain NothingDoing" Help! I’m trapped in a computer!
Exception: sbt.TrapExitSecurityException thrown from the UncaughtExceptionHandler in thread "run-main-0"
Object Equality
In Scala, all types except Array
s use value equality as defined by the type’s
equals
method, and manifested by ==
and !=
operators,
not reference equality like Java or C#.
Remember that the abstract class Any
defines the equals
, ==
and !=
methods, and those methods are often overriden by subtypes.
def equals(arg0: Any): Boolean final def ==(arg0: Any): Boolean final def !=(arg0: Any): Boolean

Almost all Scala types use value equality. For example, String
s are compared using values and not references:
scala> val s1 = "hi" s1: String = hi
scala> val s2 = s1 s2: String = hi
scala> assert(s1==s2) scala> assert(s1=="hi")
Scala handles Array
equality differently than for all other Scala types,
using reference equality and not value equality.
This means that two arrays with the same data do not compare equal.
For example:
scala> val a1 = Array(1, 2) a1: Array[Int] = Array(1, 2)
scala> val a2 = a1 a2: Array[Int] = Array(1, 2)
scala> assert(a1==a2)
scala> assert(a1==Array(1, 2)) java.lang.AssertionError: assertion failed at scala.Predef$.assert(Predef.scala:151) ... 33 additional lines of output elided
The Rest of the Equality Contract

As is the case with Java,
if you define equals
you must also define
hashCode
.
Let’s say we define a Dog
class and a Hog
class to each have a property called name
,
and we want to be able to compare Dog
instances with other Dog
instances using the Dog.name
property.
We also want to be able to compare Hog
instances with other Hog
instances via the Hog.name
property.
However, we need to disallow the comparison of Dog
instances with Hog
instances.
If regular Scala classes are used, we would need to use isInstanceOf
to validate whether the comparison makes sense or not, like this:
class Dog(val name: String) { override def equals(that: Any): Boolean = canEqual(that) && hashCode==that.hashCode override def hashCode = name.hashCode def canEqual(that: Any) : Boolean = that.isInstanceOf[Dog] } class Hog(val name: String) { override def equals(that: Any): Boolean = canEqual(that) && hashCode==that.hashCode override def hashCode = name.hashCode def canEqual(that: Any) : Boolean = that.isInstanceOf[Hog] } val dog = new Dog("Fido") val hog = new Hog("Porky") println(s"Should a dog be compared to a hog? ${dog.canEqual(hog)}") println(s"Comparing ${dog.name} with ${hog.name} gives: ${dog==hog}")
Output is:
Should a dog be compared to a hog? false Comparing Fido with Porky gives: false
The Strict Equality Operator Prevents Bugs

A source of bugs that I personally encounter more often than I care to admit is when comparing a type with an Option
of that type.
Continuing our example above:
scala> val maybeDog: Option[Dog] = Some(dog) maybeDog: Option[Dog] = Some(Dog@2168ae)
scala> dog == maybeDog // returns false, but the comparison should not be made! res4: Boolean = false
Here is one possible way of dealing with the problem.
We enhance our Dog
class with an strict equality operator ===
.
This performs a runtime check.
object MaybeDog2 extends App { class Dog2(val name: String) { override def equals(that: Any): Boolean = canEqual(that) && hashCode==that.hashCode override def hashCode = name.hashCode def canEqual(that: Any) : Boolean = that.isInstanceOf[Dog2] def ===(that: Any): Boolean = if (canEqual(that)) this==that else { println(s"ERROR: ${getClass.getName} should not be compared to a ${that.getClass.getName}") false } } val dog2 = new Dog2("Fido2") val maybeDog2 = Some(dog2) println(s"Comparing dog2 with maybeDog2: ${dog2===maybeDog2}") }
When we use ===
to compare a Dog2
with Option[Dog2]
we get a warning that the comparison is invalid.
This definitely indicates that we have a bug in our code:
ERROR: MaybeDog2$Dog2 should not be compared to a scala.Some Comparing dog2 with maybeDog2: false
In the Case Classes
lecture we will explore how Scala’s case classes automatically define canEqual
, which informs us if two objects can logically be compared.
This causes the check to be made at compile time instead of at runtime.
REPL’s :type
The Scala REPL has a :type
command that displays the type of an expression.
Here are examples of how it can be used:
scala> :type Array(1, 2) Array[Int]
scala> :type die("asdf") Nothing
scala> :type dog Dog
scala> :type hog Hog
The :type
command requires an expression to be evaluated.
Merely providing a method name is insufficient:
scala> :type wat ^ error: missing argument list for method wat Unapplied methods are only converted to functions when a function type is expected. You can make this conversion explicit by writing `wat _` or `wat(_)` instead of `wat`. scala> :type wat("x") Double
:type
only analyses instances of types, not types themselves.
Asking for the type of built-in classes such as Array
, Int
and PartialFunction
just returns a tautalogy:
scala> :type Array Array.type
scala> :type Int Int.type
scala> :type PartialFunction PartialFunction.type
User-defined types must be instantiated before :type
will attempt to analyse their instance’s type:
scala> :type Dog ^ error: not found: value Dog
scala> :type new Dog Dog
The -v
option provides more verbose output, including the internal type hierarchy for the computed value of the expression.
Sometimes this might be insightful, but since this is just a dump of sbt’s undocumented internal data structures
the output may not be as helpful as you might wish.
scala> :type Some("a") Some[String]
scala> :type -v Some("a") // Type signature Some[String] // Internal Type structure TypeRef( TypeSymbol( final case class Some[+A] extends Option[A] with Product with Serializable ) args = List( TypeRef( TypeSymbol( final class String extends Serializable with Comparable[String] with CharSequence ) ) ) )
Specially Handled Types
This section is inspired by Sébastien Doeraene’s answer to this Stack Overflow posting. Sébastien is the author of Scala.js. I provide links to types that are discussed in this course or the follow-on course, Intermediate Scala.
Special for the Type System
The following types are crucial to Scala’s type system. They have an influence on how type checking itself is performed. All these types are mentioned in the Scala Language Specification (or at least, they should be).
-
Any
,AnyRef
,AnyVal
,Null
,Nothing
: the five types that sit at the top and bottom of Scala’s type system. -
scala.FunctionN
, the canonical type given of anonymous functions. We will discuss this topic in the Functions are First Class and More Fun With Functions lectures. -
scala.PartialFunction
; we will discuss this topic in the Partial Functions lecture of the Intermediate Scala course. -
Unit
; discussed in the Learning Scala Using The REPL lecture. -
All types with literal notation:
Int
,Long
,Float
,Double
,Char
,Boolean
,String
,Symbol
(discussed in the Implicit Classes lecture of the Intermediate Scala course),java.lang.Class
. - All numeric primitive types and
Char
s. -
Option
(discussed in the Option, Some and None lecture) and tuples (discussed in the Scala Tuples, Pattern Matching and Unapply lectures). -
java.lang.Throwable
; discussed in the Try and try/catch/finally lecture. scala.Dynamic
.scala.Singleton
; discussed in the Self Types vs Inheritance lecture.- Most of
scala.reflect.*
, espciallyClassTag
andTypeTag.
- Most of the annotations in
scala.annotation.*
(e.g.,unchecked
). scala.language.*
; discussed in the Scala Imports and Packages lecture.
Known to the compiler as the desugaring of some language features
The following types are not crucial to the type system. They do not have an influence on type checking. However, the Scala language does feature a number of constructs which desugar into expressions of those types. These types are described in the Scala Language Specification.
-
scala.collection.Seq
,Nil
andList
(discussed in the Immutable Collections lecture of the Intermediate Scala course).WrappedArray
, which is used for varargs parameters (discussed in the Classes Part 2 lecture).TupleN
types; discussed in the Scala Tuples lectureProduct
andSerializable
(for case classes, discussed in the Classes Part 1 and Classes Part 2 lectures).MatchError
; discussed in the Pattern Matching lecture.scala.xml.*
scala.DelayedInit
; discussed in the More Fun With Functions lecture.
Known to the implementation of the language (Supplemental)
This information is outside the scope of this course, and is therefore marked supplemental.
The previous categories are handled by early (front-end) phases of the compiler, and are therefore shared by Scala/JVM, Scala.js and Scala Native. This category is typically known of the compiler back-end, and so potentially have different treatments. Note that both Scala.js and Scala Native do try to mimic the semantics of Scala/JVM to a reasonable degree.
Those types might not be mentioned in the Scala Language Specification, at least not all of them.
Here are those where the back-ends agree (re Scala Native, to the best of my knowledge):
-
All primitive types:
Boolean
,Char
,Byte
,Short
,Int
,Long
,Float
,Double
,Unit
. -
scala.Array
; discussed in the Mutable Collections lecture of the Intermediate Scala course. -
Cloneable
(currently not supported in Scala Native, see #334) String
andStringBuilder
(mostly for string concatenation)Object
, for virtually all its methods
And here are those where they disagree:
- Boxed versions of primitive types (such as
java.lang.Integer
) Serializable
java.rmi.Remote
andjava.rmi.RemoteException
- Some the annotations in
scala.annotation.*
(e.g.,strictfp
) - Some stuff in
java.lang.reflect.*
, used by Scala/JVM to implement structural types
Also, although not types per se, but a long list of primitive methods are also handled specifically by the back-ends.
Platform-specific types (Supplemental)
This information is outside the scope of this course, and is therefore marked supplemental.
In addition to the types mentioned above, which are available on all platforms, non-JVM platforms add their own special types for interoperability purposes.
Scala.js-specific types
See JSDefinitions.scala.
-
js.Any
: conceptually a third subtype ofAny
, besidesAnyVal
andAnyRef
. They have JavaScript semantics instead of Scala semantics. -
String
and the boxed versions of all primitive types (heavily rewritten--so-called "hijacked"--by the compiler). -
js.ThisFunctionN
: theirapply
methods behave differently than that of other JavaScript types (the first actual argument becomes thethisArgument
of the called function). js.UndefOr
andjs.|
(they behave as JS types even though they do not extendjs.Any
).js.Object
(new js.Object()
is special-cased as an empty JS object literal{}
).js.JavaScriptException
(behaves very specially inthrow
andcatch
).js.WrappedArray
(used by the desugaring of varargs).js.ConstructorTag
(similar toClassTag
).- The annotation
js.native
, and all annotations injs.annotation.*
Plus, a dozen more primitive methods.
Scala Native-specific types
See NirDefinitions.scala.
- Unsigned integers:
UByte
,UShort
,UInt
andULong
. Ptr
, the pointer type.FunctionPtrN
, the function pointer types.- The annotations in
native.*
- A number of extra primitive methods in
scala.scalanative.runtime
.
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.