Programming in Scala as a "Better" Java
Silvestre Pitti
Posted on September 29, 2024
Brief introduction to the Scala language for Java programmers
Scala is a programming language for the JVM born in 2004 that is both object oriented and functional. Since it runs in the JVM it can call any Java method and be called by any Java method. So, moving from Java to Scala is practically pain free, all you have to do is learn the new syntax. You have all the Java libraries at your disposal plus all the improvements Scala has to offer. Even though Scala is both OOP and Functional it doesn't force you to use either of the two paradigms and so it can just be used as a simple OOP replacement for Java with great benefits to speed of coding and readability. In this article I will not get into the details but just show you enough to make you curious and try it. If you want to dive deeper here is a link for you: https://www.scala-lang.org
Here are a few reasons why I think you should start coding in
Scala instead of Java.
Boilerplate / Verbosity
Here is a short list of features of Scala that reduce the verbosity of the code compared to Java
- A toString method is included automatically in every class. Most of the time it's human readable.
- Automatic get/set methods in every class
- The fields of a class are declared at the top of the class declaration as if they were parameters to the class
- The method println() instead of System.out.println().
- No need for return, the last expression in a block is its return value
- No need to specify public
- No need for break; in switch/match statements
- In some cases parentheses are optional
- If the function is only one line you don't need to put braces around it
Case Classes
Case classes are just another way to do away with boilerplate code. Most of the time a case class is just a one liner whereas to write the same functionality in Java would take at least 50 lines for a simple class with 2 fields.
case class Person(name: String, address: String)
public class Person {
private final String name;
private final String address;
public Person(String name, String address) {
this.name = name;
this.address = address;
}
@Override
public int hashCode() {
return Objects.hash(name, address);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof Person)) {
return false;
} else {
Person other = (Person) obj;
return Objects.equals(name, other.name)
&& Objects.equals(address, other.address);
}
}
@Override
public String toString() {
return "Person [name=" + name + ", address=" + address + "]";
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public void setName(String name) {
this.name = name;
}
public void setAddress(String address) {
this.address = address;
}
}
Yes, the lastest versions of Java have added Records but the Record syntax doesn't respect the standard Java get/set nomenclature. The real beauty of case classes is their use together with sealed traits in the match expression.
sealed trait LivingBeing
case class Amoeba extends LivingBeing
case class JellyFish extends LivingBeing
case class Human extends LivingBeing
val being: LivingBeing = Amoeba
being match {
case Amoeba => println("Hi, I'm an Amoeba")
case JellyFish => println("Hi, I'm a Jelly Fish")
case Human => println("Hi, I'm Human")
// case _ => not needed because the compiler knows that
// all the cases are covered
}
;
Scala doesn't need semicolons, except for multi instructions on a single line (and I avoid them whenever I can). I think that the semicolon is the stupidest character in the programming world but still it's everywhere. If the creators of Algol could have seen the future I don't think they would have introduced it. It distracts you from the flow of "idea to code". You've just written a long block of code just to see the compiler complain that there'a an error that doesn't make sense, and then 10 minutes later you realize it was the missing semicolon from the line before it.
If you like the Python indentation, Scala has that too. In the latest versions of Scala you can substitute the braces with indentation. Personally I prefer braces, but "Infinite Diversity in Infinite Combinations" is the way.
Match Expressions
The match expression in Scala is the corresponding instruction to the switch statement in Java. It serves the same purpose but it's much more powerful. The Java switch started out as a copy of the C switch, fortunately the latest Java versions have started to copy a few features from Scala. With the match expression you can choose between the usual integers, but also String and any other case class. It also lets you use the fields in the case class as parameters in the case clause (the part after the =>). And match expressions are a perfect tool when working with lists and all the collection classes.
// simple match on integers
val j = Random.nextInt(5)
j match {
case 1 => println("uno")
case 2 => println("dos")
case 3 => println("tres")
case _ => println("I can only count to 3 in spanish")
}
// match on String
val breakfast = "milk"
breakfast match {
case "milk" => println("Very light breakfast")
case "milk and cookies" => println("Light breakfast")
case "bacon and eggs" => println("Harty breakfast")
case _ => println("Did you have breakfast?")
}
// match on a case class
sealed trait LivingBeing
case class Amoeba extends LivingBeing
case class JellyFish extends LivingBeing
case class Human extends LivingBeing
val being: LivingBeing = Amoeba
being match {
case Amoeba => println("Hi, I'm an Amoeba")
case JellyFish => println("Hi, I'm a Jelly Fish")
case Human => println("Hi, I'm Human")
// case _ => not needed because the compiler knows that
// all the cases are covered
}
// previous examples forgetting a case
val being: LivingBeing = Amoeba
being match {
case Amoeba => println("Hi, I'm an Amoeba")
case JellyFish => println("Hi, I'm a Jelly Fish")
}
// Produces the following compiler warning:
// match may not be exhaustive.
// It would fail on pattern case: Human
// more detailed example
sealed trait LivingBeing
case class Amoeba(size: Int) extends LivingBeing
case class JellyFish(poisonous: Boolean) extends LivingBeing
case class Human(name: String) extends LivingBeing
val being: LivingBeing = JellyFish(true)
being match {
case Amoeba(n) => println(s"Hi, I'm an Amoeba and I am $n millimeters long")
case JellyFish(p) => println(s"Hi, I'm a Jelly Fish " +
s"and I am ${if(p) "" else "not"} poisonous")
case Human(n) => println(s"Hi, I'm Human and my name is $n")
}
// match on lists
val l = List(1, 2, 3, 4, 5)
l match {
case List(_, _, 3, _, _) =>
println("List of 5 elements with 3 as the third element")
case head::tail =>
println(s"List with $head as the first element, " +
s"and $tail as the rest of the list")
case _ => println("Anything")
}
String Interpolation
In Scala there is a much simpler way to substitute variable values inside strings. There are 3 string interpolators, the "s", "f" and "raw" interpolators. The s interpolator just substitutes any variables preceded by a $ or any expression inside ${} with its value. The f interpolator does the same as the s but you can add formatting using the printf style formats. The raw interpolator does the same as the s but it doesn't perform escaping of literals. Here are a few examples.
// using the s interpolator
// substitute simple variables using $
val head = 15
val tail = List(25, 12, 32)
println(s"List with $head as the first element, and $tail as the rest of the list")
// substitute any expression using ${}
val p = true
println(s"Hi, I'm a Jelly Fish and I am ${if(p) "" else "not"} poisonous")
// substitute values and formats using the f interpolator
val fl = 12.45
val st = 13
println(f"the fixed width left justified value is $st%-5s;")
// prints -> the fixed width left justified value is 13 ;
println(f"the floating point value is $fl%2.2f")
// prints -> the floating point value is 12,45
val & var
In Scala it is discouraged to use mutable variables. It is a tenet of FP but also by using immutable variables the code is easier to reason about and less error prone. In async code immutable variables avoid deadlocks and race conditions. The val keyword indicates an immutable variable while var indicates a mutable one. If you instantiate a val and then try to reassign it, the compiler will complain. This tells you that you either should have used a mutable variable or there is something wrong in your code.
Everything is an Object
Java is an object oriented language so why does it have primitive non object types? Scala has solved the problem by making the Java primitives full fledged objects. Also, the object hierarchy is slightly modified to allow consistency and compatability with FP principles.
Everything is an Expression
In Scala, everything is an expression. This means that everything returns a value. You can assign the result of a for, if or match expression to a variable. This way you can avoid the use of mutable variables and repeated assignments inside the corresponding statements.
// for statement
// even though it is declared with val the ListBuffer is a mutable collection
val lb: ListBuffer[Int] = new ListBuffer
for(i <- 0 to 10) {
lb.append(i)
}
// for expression
val y = for(i <- 0 to 10) yield i
// match statement with mutable variable and multiple assignments
var x = "some initial value"
val r = 10
r match {
case 5 => x = "5"
case 10 => x = "10"
case _ => x = "0"
}
// match expression
val r = 10
val x = r match {
case 5 => "5"
case 10 => "10"
case _ => "0"
}
// if statement with mutable variable and multiple assignments
var ok = "some initial value"
val n = 15
if(n < 30) ok = x
else ok = "30+"
// if expression
val n = 15
val ok = if(n < 30) x else "30+"
Type Inference
Scala, just like Java, is a strongly typed language and yet in Scala you can write code like you were writing in Python. Most of the time you don't need to specify the type of a variable in Scala because the compiler infers the type from the context. The only places you need to specify the types are function parameters and the return types of recursive functions. It can be very useful, with an IDE that shows the inferred type, to check on a calculation by checking if the inferred type is what you expected.
Collection Classes
The image below, taken from
https://docs.scala-lang.org/scala3/book/collections-classes.html,
shows the great variety of collections to choose from in Scala. There is also an analogous tree of collection classes for the mutable classes that are implemented in the same way but allow you to add, remove or reorder elements.
null
You can write in Scala using null, but I think you should make an effort to avoid it. There are classes like Option or Either that solve the problem of null and make your life as a programmer much easier. The Option class encodes the idea of the existence of a value or not. You can return a value from a function as a Some(x)
(where x is the value you need to return from the function) or None
if the value the function should return does not exist. By annotating that the function returns an Option of some type the function signature automatically expresses the fact that the function may not return a value. It is basically what null does in Java but you don't need to have error handling in your code and you can't forget to manage the absence of a value because the compiler will tell you, say goodbye to NPEs.
Try it, you won't regret it
In summary you can just start using Scala in place of Java and then little by little start coding in the functional style and see if you feel at ease with it or not.
Posted on September 29, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.