Scala at Light Speed, Part 2: Object Orientation
Scala at Light Speed, Part 2: Object Orientation
Check out this post to learn more about object-orientation in Scala.
Join the DZone community and get the full member experience.Join For Free
This article series is for busy programmers who want to learn Scala fast, in 2 hours or less. The articles are the written version of Rock the JVM's Scala at Light Speed mini-course, which you can find for free on YouTube or on the Rock the JVM website in video form.
You may also like: Scala at Light Speed, Part 1: The Essentials
This is the second article of the series, which will focus on Scala as an object-oriented language. You can watch it in video form here or in the embedded video below.
In the previous part, we learned:
- How to set up a Scala project in IntelliJ IDEA
- Values, expressions, and types
- Functions and recursion
- The Unit type
Nothing too fancy. Scala has the same notion of a class as you likely saw in other languages.
Fields are defined with
val, methods with
def, and they work as we discussed in Part 1. Fields and methods are accessed with the dot operator much like other languages.
Scala has single class inheritance (much like Java), as shown below:
Classes in Scala can take arguments — those are the constructor arguments. When you define a class, you also define its constructor signature. In this case, the Dog constructor takes a single String argument.
Beware that constructor argument are not fields — the code below will trigger an error.
In order to save a constructor argument as a field, put a
val before the argument name:
Notice that nothing is mutable (we have the notion of a variable in Scala, but it's heavily discouraged). In general, we work with immutable data structures: any intended modification to an existing instance should return a new (modified) instance.
Inheritance, Traits, and Method Notations
Scala has the same subtype polymorphism you may have seen in other statically typed languages:
Meaning, we declared a value to be an Animal, but at runtime, it acts like a Dog. A derived class can, of course, override their superclass methods.
Scala also has the notion of abstract classes, where you may leave fields/methods without implementation:
As the comment above mentions, all fields/methods are by default public unless we restrict their visibility with the
protected modifiers. They work exactly as in Java:
private restricts access to the class only,
protected restricts access to the class and all derived classes.
We also have the notion of an "interface," a type with everything being abstract — this is a
(Although, we can also implement fields/methods in traits; long story)
Scala has single-class inheritance and multi-trait inheritance:
First, notice that Crocodile derives from Animal and two other traits.
Second, notice that the Philosopher has a method called
?!. Scala is very permissive with method naming, where method names can start with non-alphanumeric characters. Names like
~> are all valid, and they're used in practice. Examples include
:: in working with lists,
! in working with Akka actors and
~> in working with Akka Streams, to name a few.
Third, watch below.
The expressions in lines 3 and 4 above are called infix notation, which applies to methods with one argument. Instead of the
instance.method(argument) call structure, we can use
instance method argument. This makes Scala extremely expressive because we can make expressions look like natural language (a croc eats a dog, line 3) or like math operations (line 4).
Which brings us to another point. Even plain math operators like 1 + 2 are actually method calls:
1 + 2 is identical to
1.+(2): we apply the number 1's plus method on argument 2.
Finally, on inheritance, Scala has the notion of an anonymous class, much like Java:
What that essentially tells the compiler (pseudo-Scala, but not off by much):
Objects and Companions
Scala has singletons as first-class elements with the keyword
Once you define an
object, you've defined both its type and the single instance of that type, and you can now use it as any other value.
You can also put a singleton — we're going to call them objects from now on — and a class (or a trait) with the same name in the same file: those are called companions. They can access each other's private fields.
Notice how we access a field or a method without referring to an instance of Animal, but to the Animal singleton (which are always different things). That's analogous to "static" fields/methods you see in other languages.
Because all the code we write is part of either an instance of a class or a singleton, many people consider Scala even more object-oriented than Java!
The Apply Method
Methods in Scala named
apply are special.
Apply methods can be called in two ways on a singleton or class instance:
One is the regular method call; the other is new. Apply allows instances to be "invoked" like functions. This will prove important in the next part when we talk about functional programming. You can add apply methods to any class, with any kind of arguments. Another example:
The Scala collections library uses this technique for many collections.
One trick: we can use apply in companion objects as factory methods:
In this way, we can construct class instances without
new. In the upcoming Scala 3, that will actually be done by default by the compiler.
In practice, we often need to work with lightweight data structures. We need them:
- To have sensible equals/hashCodes so that they can be included in collections easily
- To be serializable as we send them over the wire
- To be easy to build
- To be easy to deconstruct* (we'll show how later when we talk about pattern matching)
So that we don't have to write similar code every time, Scala has the notion of a case class for the above needs.
When you define a case class, the compiler automatically generates:
- Deep equals and hashCode
- Serializing format
- Acompanion object with an apply factory method as shown 30 seconds ago
- Sensible deconstruction format* (talk later in pattern matching)
- And more: copy methods, product iterators, curried constructor functions (no need to care about these for now)
Being based on the JVM, Scala has the same notion of exceptions like Java. The below code should serve as an example. This is similar to other languages that support exceptions, e.g. Java, C#, C++ (don't get me started with exceptions in C++).
For Java developers: Scala doesn't have the notion of a checked exception, i.e. the type of which you need to declare or that you must catch.
A Bit of Generics
In Scala, we can attach type arguments to our classes/traits to reuse the code we write on multiple types. You've surely seen this in other statically-typed languages.
In this case, we are using the generic type T to make the list mechanism applicable to ints, strings, persons, or other objects. The Scala standard library collections are generic. Examples:
The compiler still runs type inference and type checks so that we always operate on the correct types.
In the next article, we'll talk about Scala as a functional programming language.
Opinions expressed by DZone contributors are their own.