Java vs Scala — Differences we know, Reasons we don’t

Ritika
5 min readNov 14, 2022

--

These four keywords pop up everywhere when we look out for differences between Java and Scala but we hardly come across the reasons for their existence.

I have come across several services in my journey till now as a Software Engineer and most of them were either written in Java or Scala or even a mix of both ( yes, Scala source code can be compiled to Java bytecode to run on a JVM :) ) so I had to know where they differ and more importantly, how they differ from one another. Here’s the list of four most starking differences between Java and Scala:

OPERATOR OVERLOADING: This is one of the first listed differences between Java and Scala. “Scala supports operator overloading and Java doesn’t.” But what if I told you that Scala has no operators actually. Well yes, the symbols that we take for granted to be operators (e.g. +) are actually methods in Scala. But don’t we use statements like 2 + 3 almost everywhere? We do… and that’s how the illusion emerges.

Behind the scenes:

Scala supports a punctuation free syntax of invoking methods of Arity-1 (one argument) called the infix notation, so 2 + 3 is nothing but 2 .+ (3). Thus, we can use operator overloading to redefine the behaviour of these symbols and use it as per our use case like in case of calculating sum for complex numbers.

@Test
def testOperator() : Unit = {
val a = 2
val b = 3
val c = a + b
System.out.println(c)
}

@Test
def testOperatorAsMethod() : Unit = {
val a = 2
val b = 3
val c = a .+ (b)
System.out.println(c)
}

LAZY EVALUATION: Lazy evaluation, also termed as a call-by-need evaluation strategy refers to a case where an expression evaluation can be postponed till its first use. Scala is a mixed type language which means it allows both strict (evaluate now by default) and lazy (evaluate on first use by explicit mention) evaluations.

@Test
def testStrictEvaluation() : Unit = {
val isHeavyComputationExecuted = testHeavyComputationFunction()
if(false && isHeavyComputationExecuted) {
System.out.println("Hello from If")
} else {
System.out.println("Hello from Else")
}
}


/* Output:
Heavy Computation Executing
Hello from Else
*/


@Test
def testLazyEvaluation() : Unit = {
lazy val isHeavyComputationExecuted = testHeavyComputationFunction()
if(false && isHeavyComputationExecuted) {
System.out.println("Hello from If")
} else {
System.out.println("Hello from Else")
}
}

/* Output:
Hello from Else
*/

This is very useful in cases where we want to optimise on the overall computations and avoid redundant evaluations. Spark jobs use it at its core all the time.

SLOWER COMPILATION: Another difference usually listed in Scala vs Java is that code compilation in Scala takes more time than in Java. Java uses a code compiler javac for the process and Scala uses scalac for the same. The following reasons contribute towards this lack of speed and are given by Dr. Martin Odersky himself, the creator of Scala -

  1. Scalac has lots of classes to be loaded and jit-compiled
  2. The Scalac compilation speed is 500 to 1000 lines/sec which is 1/10th of that of Javac. This is due to the rich features that it provides. (With great power comes great preparation 😛) Type Inference, type checking, transformation from Java to Scala, bytecode generation and several other tasks result in this increased code compilation time.

ACTOR MODEL FOR CONCURRENCY: This is the age of concurrent and distributed systems, Actor model supports that by providing a higher level of abstraction to achieve the same. You must be thinking that Java also provides concurrency, isn’t it? Yes it does..Both Scala and Java help in achieving concurrency but it is the underlying mechanism which differs here. Having to deal with threads, locks and race conditions in code is a nightmare and solving them or testing them becomes very difficult, Akka (Scala Actor Model framework) changes that for good.

So what is this Actor Model after all?

Consider an actor as an object whose task is to receive messages and take actions against them. These actions could be computations which the actor itself performs or delegations where it passes on the task to the required actor etc. Each actor also doesn’t need to mandatorily act upon all the messages received and can discard them as well.

package com.scalaproject.actormodel

import akka.actor.Actor

class TestActor extends Actor {

def receive = {
case value: String => performTask(value)
case _ => println("Sorry, I don't recognize this message, Discarding!")
}
}

and how do these actors communicate?

We can use ActorRef to communicate with an Actor in Akka. The communication can be of two types; one where you just tell something (associated symbol !) and the other where you ask something to which a reply is expected (associated symbol ?).

Let’s consider a very small example of counting the number of error logs in a log file. The child actor is to take a log string and check if it is error log or not and the parent parses the log file then delegates each log line to the child actor and keep the final count.

import akka.actor.Actor

case class IsErrorLog(string: String)
case class JustProcessedErrorLog(boolean: Boolean)

class ChildActor extends Actor {
def receive = {
case IsErrorLog(string) => {
val result = string.contains("ERROR")
sender ! JustProcessedErrorLog(result)
}
case _ => println("Unrecognized message!!")
}
}
import akka.actor.Actor
import akka.actor.Props
import akka.actor.ActorRef

case class StartScanningLogFile()

class ParentActor(fileName: String) extends Actor {

private var errorCount = 0
private var totalLines = 0
private var linesProcessed = 0
private var fileSender: Option[ActorRef] = None

def receive = {
case StartScanningLogFile() => {
fileSender = Some(sender) // save reference to process invoker
import scala.io.Source._
fromFile(fileName).getLines.foreach { line =>
context.actorOf(Props[ChildActor]) ! IsErrorLog(line)
totalLines += 1
}
}

case JustProcessedErrorLog(isErrorLog: Boolean)
=> {
if(isErrorLog) {
errorCount += 1
}
linesProcessed += 1
if (linesProcessed == totalLines) {
fileSender.map(_ ! errorCount) // provide result to process invoker
}
}
case _ => println("Unrecognized message!!")
}
}

That’s how these actors just work upon the message not knowing its source and executing their own task when the time comes ( the correct message is received ).

Of course there are other differences you will comes across both of these but these are the ones that I find need a bit of context for better understanding and utilisation. Hope you all find this helpful! Good day :)

--

--

Ritika
0 Followers

Software Engineer by the day, Writer by the night and A Dreamer all along! @insta: ifiwasastory