Programming  /  Scala September 01, 2010

Learning Scala part eight – Scala’s type hierarchy and object equality

In the previous part of the Learning Scala series we learned about Traits. In this part we’ll move on with a quick look at Scala’s type hierarchy and a few interesting classes in it. We’ll also look at how Scala deals with equality comparisons between objects.

Scala’s type hierarchy

Scala’s type hierarchy is illustrated in the below diagram. The diagram is basically a simplified version of a more detailed diagram of Scala’s type hierarchy found on page three in An Overview of the Scala Programming Language. The namespace for all of the types in the diagram is scala, so for instance the ScalaObject class’ full path is scala.ScalaObject.

A class diagram illustrating Scala's type hierarchy

 

As we can see the base type in the Scala type system is the Any type. It defines some final methods such as ==, !=  and isInstanceOf[T]. It also defines a few methods such as equals, hashCode and toString that are intended to be overridden by subtypes. It is subclassed by the AnyVal and AnyRef types.

AnyVal is the base class for all value classes and the Unit type which we’ll discuss shortly. As for the value classes (Boolean, Long etc) these correspond to the primitive types of the runtime platform (the JVM or the CLR).

AnyRef is the parent of all reference classes, both Scala types and the Java types. It is equivalent to the runtime’s object class (java.lang.Object or System.Object).

All of AnyVal’s subtypes are immutable value instances that can’t be instantiated while all subtypes of AnyRef are created using the new keyword.

The Null type is a subtype of all reference types and there exists a single instance of it, null. As the Null class isn’t a subtype of the value classes a variable of such a type can’t be assigned null as a value.

The Nothing type is on the other hand a subtype of all types, including the value types. However, there exists no instances of Nothing so we can’t assign that as a value to variables. It is however useful for type parameters. I hope to explore the use of Nothing further later on in this series.

Unit

While the other nine subtypes of AnyVal are familiar value types the Unit type stands out. Unit is similar to void in Java or C# and is used primarily as a return value for functions. However, as opposed to Java’s or C#’s void Unit is a first class citizen in Scala in the sense that there is actually a value of type Unit. What this means is that the below code is valid. Had it on the other hand been written in Java or C# and the method would have had void as return type it wouldn’t have been as it is actually returning a value.

class MyClass {
    def someMethod(): Unit = {
        return 1
    }
}

Also, let’s say that we have a method that takes a function as a parameter. If that function is specified to not return anything in C# we would not be able to pass the method a function that actually has a return value, even though logically that’s perfectly fine as our method could just ignore whatever the function returns. The fact that a return type of type Unit is just like any other return type solves this issue as we can easily pass a function that returns (for instance) a string in place of a function that returns Unit. 

If this peaks your interest (or you’re totally confused and require a better explanation) Matthew Podwysocki has an interesting blog post over at CodeBetter.com about the limitations of void in C# contrasted with F# which has a Unit type similar to Scala’s.

Equality of Objects

In Scala the == and != methods (or operators if you will, but in Scala they are actually methods) check for value equality as opposed to Java or C# where they check for reference equality. Both the == and the != methods are defined as final in the Any type. They both use the equals method which is also defined, but not as final, in Any. In other words, for the == and != methods to work as expected for our custom types we need to override the equals method to ensure that it compares the relevant values.

For instance, let’s say that we have a Person class with a single field named name. Then two instances of that class with the same name should equal each other when the equals method or the == method is used, but as default they wont.

class Person(val name: String)

var person1 = new Person("John")
var person2 = new Person("John")

person1 == person2 //Should be true but is false

We can easily fix this by overriding the equals method.

class Person(val name: String) {
    override def equals(that: Any) : Boolean = {
        that.isInstanceOf[Person] 
        && (this.hashCode() == that.asInstanceOf [Person].hashCode());
    }
    
    override def hashCode = name.hashCode
}

var person1 = new Person("John")
var person2 = new Person("John")

person1 == person2 //True

Note that we also override the hashCode method. This is partially to be able to use it for the equals method but it is also a recommendation to do so when overriding the equals method as two objects that equal each other should also hash to the same value.

As an alternative to overriding the equals method (and others like toString ang hashCode) we can also create something called case classes for which these will be automatically implemented for us. We’ll look at case classes later on in the series.

Checking for reference equality

To check for reference equality, that is to get the same effect as using the == operator in C# or Java, the AnyRef type (which all reference types inherit from) has a method named eq that does just that. It also has a method named ne that does just the opposite.

Recap and a look at what’s next

In this post we’ve looked at Scala’s type hierarchy as well as at some special types, among them the Unit type which serves the same purpose as the void keyword in C# and Java. We’ve also seen that the equals method and the == and != “operators” compare objects by value in Scala instead of by reference as in Java and C#.

In the next part we’ll take a look at why we could feel comfortable creating public fields in classes like we did in the fourth part of the series when we look at the Uniform Access Principle.

About this post and the Learning Scala series

This post is a part of a series of posts in which I describe my experiences while trying to learn Scala. I try to do it in the form of a tutorial as I find doing so is an excellent way of consolidating knowledge and hopefully I can also help others. However, keep in mind that I’m in no way an expert on Scala. This is just a way to document what I’ve learned so far and there might be some things that I’ve misunderstood. If you’ve found such a thing and would like to help me correct that misunderstanding, or if you would like to leave any other kind of feedback don’t hesitate to leave a comment!

You can find links to all posts in this series at the bottom of the first post.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

More about Scala