< Back
March 10, 2021

A Scala Introduction: A Comparison to Java

An introduction to the Scala language and its features, utilizing Java as a comparison
javascalasoftware development

Written by:

Pedro Correia, IT Consultant

As many people know, Java is one of the most popular programming languages on the market. However, Scala is not so widely known, even though its developers are some of the most well paid in the world.

In fact, Scala was built using the Java language and was created with the purpose of addressing Java’s issues. It originated at the Swiss Federal Institute of Technology in Lausanne, Switzerland, and was created by Martin Odersky.  I’ve written this article with the objective of introducing Scala while establishing comparisons with Java, showing why Scala is worthwhile to learn and use.

Scala’s Versatility

As most know, Java is a general-purpose object-oriented programming language with the great benefit of only needing to be written once and still being able to be run anywhere due to the Java Virtual Machine being able to compile code regardless of the underlying operating system.

The same is true for Scala, but it supports both object-oriented and functional paradigms of programming, which is a great strength. Scala does this by differentiating mutable variables and immutable variables, as the example below shows:

` ` `
var variable = “mutable variable” //mutable variable, can be altered

val value = “immutable variable” //immutable variable, constant value

def square(x: Int) = x * x //function

` ` `

Mutable variables are already well known. These can be defined using the var keyword and they represent values that can be altered at any point, just like the typical Java variables.

When it comes to immutable variables we use the val keyword. These are common in functional programming languages and they are unchangeable, similar to using Java’s final keyword.

The def keyword is used for functions. Above you can see a square function as an example.

This works similarly with Scala’s collections:

` ` `

//Scala lists are immutable and work similarly to Java’s LinkedLists

val list1: List[Int] = List(1,2,3)

val list2: List[Int] = List(4,5,6)

` ` `

As shown above, we initialized two different lists and they are both immutable. To add elements or update these lists, implies creating a brand new list since the ones defined above are unchangeable:

` ` `

scala> val list1new = 0 +: list1
val list1new: List[Int] = List(0, 1, 2, 3)

scala> val list2new = list2 :+ 7
val list2new: List[Int] = List(4, 5, 6, 7)

scala> val mergedList = list1 :: list2
val mergedList: List[Any] = List(List(1, 2, 3), 4, 5, 6)

scala> val mergedList = list1 ::: list2
val mergedList: List[Int] = List(1, 2, 3, 4, 5, 6)

` ` `

You can prepend, append, and concatenate lists but it also always implies creating a new list. If a list was initialized using the var keyword, however, you would be able to update the variable whenever needed:

` ` `

scala> var listVar = List(“Apple", “Pear", “Banana")
var listVar: List[String] = List(Apple, Pear, Banana)

scala> val listVal = List(“Orange", “Strawberry", “Pineapple")
val listVal: List[String] = List(Orange, Strawberry, Pineapple)

scala> listVar = listVar :+ “Mango"
// mutated listVar

scala> listVar
val res0: List[String] = List(Apple, Pear, Banana, Mango)

scala> listVal = listVal :+ “Mango"

               ^

       error: reassignment to val

` ` `

Some Java-like collections are still present in Scala, such as the Array collection. This is a mutable collection and you can add/remove elements from it just like in Java.

Scala’s collections are in the scala.collection package and you have both immutable and mutable collections that you can use in packages scala.collection.immutable and  scala.collection.mutable, respectively, giving you great flexibility on what type of collections you want to use in your project.

Compactibility and Readability

Scala is an incredibly powerful language due to many of its features. These features in tandem allow Scala to have a very compact and clean syntax in comparison to Java, which in turn increases productivity with the language. Here’s a piece of code initializing a list of integers in both Java and Scala to serve as a comparison:

` ` `

//Java

List list = new ArrayList();
list.add(“Apple”)
list.add(“Pear”)
list.add(“Banana”)

//Scala

val list = List(“Apple”, “Pear”, “Banana”)

` ` `

This is a simple example to show how Scala can be written much more cleanly. It’s important to mention that Java has ways to shorten the code but they usually involve using external libraries and tools. Here’s a simple class example:

` ` `

//Java


public class Person {
            private final String firstName; 
            private final String lastName;

            public Person(String firstName, String lastName) { 
                           this.firstName = firstName; 
                           this.lastName = lastName; 

            } 

            public String getFirstName() {  
                            return firstName; 

            } 

            public String getLastName() { 
                            return lastName; 

            } 

}

//Scala

class Person (val firstname: String, val lastName: String)

` ` `

Scala simplifies classes with its syntax and doesn’t require a constructor or getters like in Java. Scala also simplifies iterations. Here’s an example of a simple iteration over a list of strings and then converted to integers:

` ` `

//Java
List ints = new ArrayList();
for(String s: list) {
              ints.add(Integer.parseInt(s));

}

//Scala
val ints = list.map(s => s.toInt)

` ` `

Scala’s map function allows you to apply a function to every element of any object, in this case, this list has every element converted to an integer. It can also be used on some other wrapper object (an object that contains something) that isn’t necessarily a collection of multiple elements. These wrappers use the Monad concept, and there are a lot of functions that are associated with Monads. You can read more about Monads here.

Scala also allows something called Type Inference. This means that you don’t need to specify types when creating variables or in the return types of functions. You can notice this in the Scala code snippets above, where the list variables are created without specifying their type. Specifying code is nonetheless still possible:

` ` `

//variable initialization with inferred type
val list = List(1,2,3)

//variable initialization with specified type
val list: List[Int] = List(1,2,3)

//function with inferred return type
def square(x: Int) = x * x

//function with specified return type
def square(x: Int): Int = x * x

` ` `

Lastly, on the topic of compact code, Scala allows implicit variables. This allows you to have functions that take implicit arguments, meaning they are automatically passed through without specifying. When these functions are created, they automatically search for an implicit variable that matches the signature of the function argument. This is better explained with the example below:

` ` `

implicit val testVal = “Scala”
def testFunction()(implicit string: String) = {
              println(string)

}

` ` `

The function above automatically searches for an implicit value that is a String and automatically passes it to the function argument. Therefore, you can just call the function by writing testFunction().

All the functionality shown above is just some of the more relevant features that make Scala’s code more compact. Despite the fact that you are required to write less code and you can code more cleanly with Scala, it’s usually considered less readable than Java because of the shorter code and implicit details. This comes down to preference and some still think Scala is more readable regardless.

Lazy Evaluation

A really interesting and useful feature amongst functional programming languages is being able to declare lazy variables. This is usually used when we want variables to be evaluated at a later time. You create a lazy value with the “lazy” keyword:

` ` `
lazy val testVal = “This value is lazy”

` ` `

Variables by default are evaluated immediately, meaning that the value is calculated at the moment of its declaration. Lazy variables however are evaluated when the variable itself is called. It’s very useful when you have a variable that can’t be calculated at that moment, and you only want it to be resolved when you call it yourself.

Lazy evaluation is also present in collections, namely in the Stream collection. Streams are collections in Scala that, similarly to Lists, hold several elements. However, Lists and the values within them are evaluated when the List is declared, similarly to any other default variable. Streams however evaluate their elements when they are called:

` ` `

lazy val fibs: Stream[BigInt] = 
 BigInt(0) #::
 BigInt(1) #::
 fibs.zip(fibs.tail).map(n => {
 n._1 + n._2
        })

` ` `

Above you can see a Stream that stores the fibonacci sequence. You don’t need to understand this code straight away, but this code stores the number 0 and number 1 in the first two positions of the Stream, and the rest is calculated afterward.

Just like regular lazy variables, each element of the Stream is only calculated when called. This makes it so that you can have an infinitely long Stream, which isn’t possible with regular collections like Lists since you can’t evaluate infinite amounts of data.

Operator Overloading

In Java, operators are differentiated from objects/functions and always resolve in a similar way. However, Scala is what we call a pure object-oriented: everything is treated as an object. When it comes to operators, they are treated as a function and can be used in two different ways:

` ` `

//Using the usual operator syntax
val example1 = 2 + 2

//Using the function-like syntax
val example2 = 2.+(2)

` ` `

This goes back to the topic of Scala’s inherent syntax. The same code can be written in different styles depending on your preference, and the above example shows this. As mentioned before, it shows how Scala is structured with the absence of Java-like primitive types and operators. Additionally, since operators are functions, they can be changed and overloaded.

The example below shows an implementation of Rational numbers in Scala. Notice the + operation implementation:

` ` `

class Rational(n: Int, d: Int) {
 require(d != 0)

 private val g = gcd(n.abs, d.abs)
 val number = n / g
 val denom = d / g

 // auxiliary constructor
 def this(n: Int) = this(n,1)

 //greatest common denominator
 private def gcd(a: Int, b: Int): Int =
   if (b == 0) a else gcd(b, a % b)

 //sum with another rational
 def + (that: Rational): Rational = {
   new Rational(
     number * that.denom + that.number * denom,
     denom * that.denom
   }
 }

 //sum with an int
 def + (i: Int): Rational =
   new Rational(number + i * denom, denom)

 //toString function (used for printing the Rational)
 override def toString(): String = {
   return number + “/” + denom
 }
}

` ` `

This implementation lets you use the + operator the same way you use it in simple arithmetic operations, and lets you do something like this:

` ` `

val rational1 = new Rational(2,3)
val rational2 = new Rational(1,6)

rational1 + rational2 //this returns 5/6

` ` `

You can also overload any other operator. By default, you already have a lot of operator methods in Scala, like in integer operations and even things like String operations, where the + operator is used for concatenation. This is another useful feature that contributes to Scala’s clean and concise code that you can implement into your classes and even override already existing operator methods with your own implementations.

Functions as Variables

Another great feature that Scala has is being able to declare functions as variables. This allows for a lot of freedom with the types of functions and code we can write.

Functions in Scala can be assigned to a variable or simply be declared anonymously. These can be passed as arguments of other functions as well, opening up a lot of possibilities. In Java, this is simply not possible, since functions are treated as something completely different, whereas in Scala you have functions being treated like any other variable.

Here is an example of the same function written normally and then written anonymously:

` ` `

//regular function
def double(i: Int): Int = i * 2

//anonymous function
(i: Int) => i * 2

` ` `

Since functions are variables, these can be passed as function arguments. Functions that take one or more functions as arguments and/or returns a function, as a result, are called “higher-order functions.” You’ve seen before in this article the usage of the .map function, which is a good example of a higher-order function.

Concurrency Simplified

In Java, the use of Threads is very prominent when you want to use concurrency in your project. This option is also available in Scala, however, Scala has its own set of tools to achieve this.

One of these types of tools are Futures, which simply put are wrappers that hold a certain operation that will try to complete itself while the remainder of the program resumes, meaning it runs in a separate thread:

` ` `

//future example
val future = Future {
  someOperation()
}

` ` `

In Java, you are usually required to create a custom Thread class, make your own run() method, and then call .start() on your thread. And due to the nature of object-oriented programming, there’s definitely a risk involved in soft locking your program due to the side-effects of mutable data.

The equivalent in Scala would be the example above. Collecting the result of that operation is also very simple:

` ` `
//method 1: .onComplete

future.onComplete {
  case Success(user) => println(“It worked!”)
  case Failure(exception) => println(“error!”)
}

//method 2: .map

Future.map{ result =>
  doSomethingWithResult(result)
}

` ` `

Another great tool for concurrency in Scala are Actors (specifically from the Akka library). You can create a class that extends the Actor class and by calling the Actor reference and passing certain info, you can have an Actor instance run certain code on a separate thread. Actors are more of a complex concept to explain, but nonetheless, if you want to learn more about them you can go here.

Things Java Does Better

This article has highlighted several of Scala’s features that it has over Java, but Scala isn’t completely infallible. Here are a few of Java’s strengths that Scala doesn’t have:

  • Java doesn’t have the best performance compared to the more popular programming languages on the market, but it does have better performance than Scala.
  • Java’s popularity in itself is also a great thing, especially because you have a bigger community of developers available to help out, as well as a lot more 3rd party libraries.
  • Both Java and Scala have documentation available online, but Java’s goes into more detail on the language’s features and is overall better than Scala’s.
  • As mentioned previously, even though this is preferential, most people consider Java’s more verbose code to be more readable and easier to understand than Scala’s, even if it requires a lot more code.
  • Java is backward compatible while Scala isn’t, meaning that code written in a new version of the language will also be able to run in an older version.
  • Java is very beginner-friendly and easy to understand for someone that wants to learn the language, while Scala is more complex due to the functional paradigm and its features.

It’s Difficult… but It’s Worth It!

Scala has many useful functions and strengths over Java, but in turn, Scala has a higher learning curve. This has a lot to do with the fact that Scala is a functional language, which in general is less intuitive to learn than your typical object-oriented language.

Another aspect is Scala’s syntactic sugar, which while it makes the code shorter and clearer to read, can sometimes be harder to learn since a lot of aspects in the programming logic are made less clear since shortcuts are taken and a lot of information is implicit on the written code, as mentioned previously.

I had my first contact with Scala back when I joined Growin in their Scala Academy program. Since then I’ve come to love the language and its tools, even though learning the functional paradigm was difficult and new to me at first.

But despite the difficulty and the differences with Java, Scala has been gaining a lot of popularity recently and it’s a language I recommend due to a lot of its advantages over Java, making it a great choice for getting into functional programming.


×

Hello!

Click below to speak to one of our team members.

× How can we help?