Java without Java - Scala

Content:

  1. Introduction
  2. val vs var
  3. Built in types
  4. Operations
  5. Control structures
  6. Option
  7. Object Oriented Programming
  8. Generic Programming
  9. Functional Programming
  10. Scala and Java
  11. Runtime
  12. DSL features
  13. Why switch to Scala
  14. Scala or Kotlin
  15. Versions

Introduction:

Scala is a language developed by Martin Odersky 2001-2004.

Scala was designed to be multi-paradigm: Object Oriented, Generic and Functional.

Scala is used in backend systems (known users include Twitter, Morgan Stanley and Apache Spark) and web using the Play framework.

Scala can target:

  1. JVM
  2. JavaScript
  3. native via LLVM

At some point in time a CLR version also existed.

The entire article assume that the reader will be looking at using Scala for JVM platform.

The entire article assumes that the reader is proficient in the Java language and everything is explained by comparing it to Java.

Some C# syntax will also be shown to help developers that are more familar with C# than Java.

This is not a complete reference to Scala. This only covers some of the more important features. For more details see the Tour of Scala or get a Scala book.

My perspective will be very Java centric, because I know Java well. Someone from a Haskell background may very well have prioritized different aspects and explained things differently.

val vs var:

Variable declarations are different in Scala:

Scala Java equivalent Java 10+ equivalent C# equivalent
val v = 123 final int v = 123; final var v = 123; N/A
val v: Int = 123 final int v = 123; final int v = 123; N/A
var v = 123 int v = 123; var v = 123; var v = 123;
var v: Int = 123 int v = 123; int v = 123; int v = 123;

Example:

package jwj

object ValVsVar {
  def main(args: Array[String]): Unit = {
    val iv1 = 123
    //iv1 = 456
    var iv2 = 456
    println(s"$iv1 $iv2")
    iv2 = 789
    println(s"$iv1 $iv2")
    val iv3: Int = 123
    //iv3 = 456
    var iv4: Int = 456
    println(s"$iv3 $iv4")
    iv4 = 789
    println(s"$iv3 $iv4")
  }
}

Built in types:

Scala has the same basic types as Java.

Scala Java equivalent
Byte byte
Short short
Int int
Long long
Float float
Double double
Char char
Boolean boolean
String java.lang.String
Unit void or Void

Arrays are classes in Scala.

Scala Java equivalent
Array[MyClass] MyClass[]
Array[Short] short[]
Array[Int] int[]
Array[Long] long[]
Array[Float] float[]
Array[Double] double[]

Example:

package jwj

object Typ {
  def main(args: Array[String]): Unit = {
    val vb: Byte = 123
    println(s"$vb")
    val vs: Short = 123
    println(s"$vs")
    val vi: Int = 123
    println(s"$vi")
    val vl: Long = 123L
    println(s"$vl")
    val vf: Float = 123.456f
    println(s"$vf")
    val vd: Double = 123.456
    println(s"$vd")
    val vc: Char = 'A'
    println(s"$vc")
    val v: Boolean = true;
    println(s"$v")
    val vs1: String = "ABC"
    println(s"$vs1")
    val vs2: String = "\"ABC\""
    println(s"$vs2")
  }
}

Operations:

Scala has the normal arithmetic operators: +, -, *, / and %.

Scala has the normal comparison operators: ==, !=, <, <=, > and >=.

Note that == is same value and Scala has a separator operator eq for same identity.

Scala Java equivalent C# equivalent
a == b a.equals(b) a.Equals(b)
or:
a == b
a eq b a == b Object.ReferenceEquals(a, b)

Scala has the normal boolean operators: &&, || and !.

Scala does normal type conversion with (type).

Scala allows the definition of implicit type conversion via:

implicit def SomeName(input: InputType): OutputType = ...

Scala has the normal bitwise operators: <<, >>, >>>, &, |, ^ and ~.

Example:

package jwj

case class Funky(val v: Int)

object Ops {
  implicit def CToInt(o: Funky): Int = o.v
  def main(args: Array[String]): Unit = {
    val v1 = 123
    val v2 = ((4 * v1 + 5) / 6 - 7) % 8
    println(s"$v2")
    var bv: Byte = 123
    var iv: Int = 123
    //bv = iv
    bv = iv.toByte
    //iv = bv
    iv = bv.toInt
    val b1 = 0x0000001
    val b2 = b1 << 31
    val b3 = b1 ^ b2
    val b4 = ~b3
    println(s"$b4")
    val iv1 = 123
    val iv2 = 123
    println(s"${iv1 == iv2}")
    //println(s"${iv1 eq iv2}")
    val a = 'A'
    val b = 'B'
    val c = 'C'
    val sv1 = s"$a$b$c"
    val sv2 = s"$a$b$c"
    println(s"${sv1 == sv2}")
    println(s"${sv1 eq sv2}")
    val o = new Funky(123)
    println(o + 1)
  }
}

Control structures:

Scala has very powerful control structures. Especially the for loop is very flexible.

Scala also has a yield that is more powerful than C# yield.

Scala Java equivalent C# equivalent
if(a) {
  foo()
} else {
  bar()
}
if(a) {
    foo();
} else {
    bar();
}
if(a)
{
    foo();
}
else
{
    bar();
}
res = if(a) b else c
res = a ? b : c;
res = a ? b : c;
x match {
  case 1 => foo()
  case 2 => bar()
  case _ => throw new Exception("Ooops")
}
switch(x) {
    case 1:
        foo();
        break;
    case 2:
        bar();
        break;
    default:
        throw new Exception("Ooops");
}
switch(x)
{
    case 1:
        foo();
        break;
    case 2:
        bar();
        break;
    default:
        throw new Exception("Ooops");
}
o match {
  case v if(1 <= v && v <= 10) => println("in 1..10")
  case v: Int => println("Int not in 1..10")
  case _ => println("not Int")
}
if(o instanceof Integer) {
   int io = (Integer)o;
   if(1 <= io && io <= 10) {
       System.out.println("in 1..10");
   } else {
       System.out.println("Int not in 1..10");
   }
} else {
   System.out.println("not Int");
}
if(o is int)
{
   int io = (int)o;
   if(1 <= io && io <= 10)
   {
       Console.WriteLine("in 1..10");
   }
   else
   {
       Console.WriteLine("Int not in 1..10");
   }
}
else
{
   Console.WriteLine("not Int");
}
res = o match {
  case vi: Int => "Int"
  case vs: String => "String"
  case _ => "Other type"
}
if(o instanceof Integer) {
    res = "Int";
} else if(o instanceof String) {
    res = "String";
} else {
    res = "Other type";
}
if(o is int)
{
    res = "Int";
}
else if(o is string)
{
    res = "String";
}
else
{
    res = "Other type";
}
for(i <- ia) {
  ...
}
for(int i : ia) {
    ...
}
foreach(int i in ia)
{
    ...
}
for(i <- 1 to 10) {
  ...
}
for(int i = 0; i <= 10; i++) {
    ...
}
for(int i = 0; i <= 10; i++)
{
    ...
}
for(i <- 1 to 10 if i != 7) {
  ...
}
for(int i = 0; i <= 10; i++) {
    if(i != 7) {
        ...
    }
}
for(int i = 0; i <= 10; i++)
{
    if(i != 7)
    {
        ...
    }
}
for(i <- 1 to 10; j <- 1 to 5) {
  ...
}
for(int i = 1; i <= 10; i++) {
    for(int j = 1; j <= 5; j++) {
        ...
    }
}
for(int i = 0; i <= 10; i++)
{
    for(int j = 1; j < 5; j++)
    {
        ...
    }
}
while(c) {
    ...
}
while(c) {
    ...
}
while(c)
{
  ...
}
do {
    ...
} while(c)
do {
    ...
} while(c)
do
{
    ...
} while(c)
val somename = some_loop yield somevalue
N/A
IEnumerable<Type> Name()
{
    some_loop
    {
        yield return somevalue;
    }
}

Scala supports both default values for arguments and call by argument name.

Scala Java equivalent C# equivalent
def m(v: Int = 0): Unit = {
    ...
}
void m() {
    m(0);
}
void m(int v) {
    ...
}
void m(int v = 0)
{
    ...
}
m(v = 1)
N/A
m(v: 1);

Example:

package jwj

object Ctl {
  def m(a: Int = 123, b: Int = 456): Unit = {
    println(s"$a $b")
  }
  def main(args: Array[String]): Unit = {
    val v = 7
    val s1 = if(v == 0) "zero" else "not zero"
    println(s1)
    val s2 = v match {
      case 0 => "zero"
      case _ => "not zero"
    }
    println(s2)
    val v2 = (v, 0, 1)
    v2 match {
      case (a,b,c) => println(s"$a $b $c")
      case _ => println("no match")
    }
    val a = Array(1, 2, 3)
    for(i <- a) {
        println(i)
    }
    for(i <- 1 to 3) {
        println(i)
    }
    for(i <- 3 to 1 by -1) {
        println(i)
    }
    for(i <- a if i != 2) {
        println(i)
    }
    for(i <- a; j <- a) {
      println(s"$i * $j = ${i*j}");
    }
    val aa = for(i <- a) yield i * i;
    for(i <- aa) {
      println(i);
    }
    m(321, 654)
    m(321)
    m(b = 654)
    m()
  }
}

Option:

Scala has null, but value types cannot be null and usage of null in Scala is generally discouraged. To handle no values Scala has an Option class.

Option works great with pattern matching.

Follow Scala convention and use Option instead of null.

Example:

package jwj

object Option {
  def opttxt[T](x: Option[T]): String = x match {
    case Some(x2) => x2.toString()
    case None => "N/A"
  }
  def main(args: Array[String]): Unit = {
    //val iv1: Int = null
    val iv2: Option[Int] = None
    println(s"${opttxt(iv2)}")
    val iv3: Option[Int] = Some(123)
    println(s"${opttxt(iv3)}")
  }
}

Object Oriented Programming:

Scala has a somewhat different approach to OOP than Java.

Major differences:

Scala does not have named interfaces. Instead an adhoc anonymous minimal interface can be declared for arguments.

Scala traits are pieces of implementation functionality that can be added to any class.

A Scala case class is an immutable data class with convenient property accessor (val and var), property mutator (var), == operator, toString method and copy method. Note that case classes are instantiated without new.

Scala have a non-Java-compatible convention using underscore that result in a C# syntax.

Enums in Scala extend the Enunmeration class.

Scala Java equivalent C# equivalent
class X {
  ...
  def m(Iv: Int, Sv: String): Unit = ...
  ...
}
...
def m2(o: { def m(Iv: Int, Sv: String): Unit }): Unit = {
  ...
  o.m(123, "ABC")
  ...
}
...
m2(new X())
interface I {
    void m(iv : int, sv : String);
}
class X implements I {
    ...
    void m(iv : int, sv : String) {
        ...
    }
    ...
}
...
void m2(o : I) {
    ...
    o.m(123, "ABC");
    ...
}
...
m2(new X());
interface I
{
    void m(iv : int, sv : string);
}
class X : I
{
    ...
    void m(iv : int, sv : String)
    {
        ...
    }
    ...
}
...
void m2(o : I)
{
    ...
    o.m(123, "ABC");
    ...
}
...
m2(new X());
trait T {
  def m1() = ...
}
abstract class X {
  def m1()
  def m2() = ...
}
class Y extends X with T {
}
N/A (interfaces with default methods in Java 8+ can be misused to achieve the same) N/A
case class X(val a: Int, val b: Int)
class X {
    private int a;
    private int b;
    public X(int a, int b) {
        this.a = a;
        this.b = b;
    }
    public int getA() {
        return a;
    }
    public int getB() {
        return b;
    }
    @Override
    public boolean equals(Object o) {
        // check for equality based on a and b
    }
    @Override
    public int hashCode() {
        // calculate hash code based on a and b
    }
    @Override
    public String toString() {
        // construct String based on class name, a and b
    }
    // copy method is not easily implementable in Java
}

Java 14+:
record X (int a, int b) { }
class X
{
    public int A { get; set; }
    public int B { get; set; }
    public X(int a, int b)
    {
        A = a;
        B = b;
    }
    public override bool Equals(Object o)
    {
        // check for equality based on a and b
    }
    public override int HashCode() {
        // calculate hash code based on a and b
    }
    public override string ToString()
    {
        // construct String based on class name, a and b
    }
    // copy method is not easily implementable in C#
}
class X {
  var v: Int
  ...
}
...
var o = new X()
o.v = o.v + 1
class X {
    public int v;
    ...
}
...
X o = new X();
o.v = o.v + 1;
class X
{
    public int V;
    ...
}
...
X o = new X();
o.V = o.V + 1;
class X {
  var _v: Int
  def v = _v
  def v_ = (value: Int): Unit = _v = value;
  ...
}
...
var o = new X()
o.v = o.v + 1 // note that the trailing underscore is not there (it is magic)
class X {
    private int v;
    public int getV() {
        return v;
    }
    public void setV(v : int) {
        this.v = v;
    }
    ...
}
...
X o = new X();
o.setV(o.getV() + 1);
class X
{
    public int V { get; set; };
    ...
}
...
X o = new X();
o.V = o.V + 1;
object E extends Enumeration { val A, B, C }
enum E { A, B, C }
enum E { A, B, C }

Example:

package jwj

trait T {
  def test():Unit = println("Test")
}

abstract class P(iv: Int) {
  def dump(): Unit = {
    println(s"$iv")
  }
  def test(): Unit
}

class C(iv: Int, sv: String) extends P(iv) with T {
  override def dump(): Unit = {
    super.dump()
    println(s"$sv")
  }  
}

case class Data(val iv: Int, val sv: String)

object OO1 {
  def m(z: { def test(): Unit }): Unit = z.test()
  def main(args: Array[String]): Unit = {
    val o = new C(123, "ABC")
    o.dump()
    o.test()
    m(o)
    val d1 = Data(123, "ABC")
    val d2 = Data(123, "ABC")
    println(s"$d1 $d2 ${d1==d2}")
    val d3 = d1.copy()
    val d4 = d1.copy(iv = 456);
    println(s"$d3 $d4 ${d3==d4}")
  }
}

Scala does not have static instead it has objects and companion objects.

An object is an all static class or a singleton.

A companion object represent what in Java would be static part of a class. In scala a companion object is simply an object with the same name as the class - the convention is to import all membes of the companion object.

Scala Java equivalent C# equivalent
object X {
  var v = 0
  def m(): Unit = { ... }
}
...
tmp = X.v
X.m()
class X {
    private static int v;
    public static int getV() {
        return v;
    }
    public static void setV(int v) {
        X.v = v;
    }
    public void m() { ... }
}
...
tmp = X.getV();
X.m();
static class X
{
    public static int V { get; set; }
    static void M() { ... }
}
...
tmp = X.V;
X.M();
object X {
  def mo(): Unit = { ... }
}
class X {
  def m(): Unit = { ... }
}
...
o = X()
o.m()
X.mo()
class X {
    static void mo() { ... }
    void m() { ... }
}
...
o = new X();
o.m();
X.mo();
class X
{
    static void Mo() { ... }
    void M() { ... }
}
...
o = new X();
o.M();
X.Mo();

Example:

package jwj

object O {
    def m(): Unit = {
        println("O.m")
    }
}

object SC {
  var compf: Int = 0
}

class SC {
  import SC._
  compf = compf + 1
  var instf: Int = compf
  override def toString(): String = {
     return s"$instf of $compf" 
  }
}

object OO2 {
  def main(args: Array[String]): Unit = {
    O.m()
    val o1 = new SC()
    println(o1)
    val o2 = new SC()
    println(o1)
    println(o2)
  }
}

Scala supports operator overload because operators are just methods in Scala. To get indexeers it is necessary to use apply and update methods (magic names).

Scala supports extension methods via implicit classes.

Scala Java equivalent C# equivalent
class X {
  ...
  def +(o: X) = ...
  def -(o: X) = ...
  def *(o: X) = ...
  def /(o: X) = ...
  def apply(ix: Int): E = ...
  def update(ix: Int, value: E): Unit = { ... }
}
N/A
class X
{
    ...
    static X operator+(X a, X b) { ... }
    static X operator-(X a, X b) { ... }
    static X operator*(X a, X b) { ... }
    static X operator/(X a, X b) { ... }
    E this[int] 
    {
        get { return ...; }
        set { ... value ...; }
    }
}
object Extensions {
  implicit class SomeTypeExtension(me: SomeType) {
    def newmethod(arg: ArgTyp): RetType = {
      // do whatever with me (SomeType) and arg (ArgTyp)
    }
  }
}
N/A
static class Extensions
{
    RetType newmethod(this SomeType me, ArgTyp arg)
    {
        // do whatever with me (SomeType) and arg (ArgTyp)
    }
}

Example:

package jwj

class MyInt(val v: Int) {
  def +(o: MyInt) = new MyInt(v + o.v)
  def -(o: MyInt) = new MyInt(v - o.v)
  def *(o: MyInt) = new MyInt(v * o.v)
  def /(o: MyInt) = new MyInt(v / o.v)
  override def toString(): String = return s"$v"
}

object MyInt {
  implicit def Int2MyInt(v: Int): MyInt = new MyInt(v)
  implicit def MyInt2Int(v: MyInt): Int = v.v
}

class TenInt {
    val a = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    def apply(ix: Int): Int = a(ix)
    def update(ix: Int, v: Int) { a(ix) = v }
}

object Helper {
  implicit class IntExtension(me: Int) {
    def plusPowerOf2(n: Int): Int = me + 1 << n
  }
}

object OO3 {
  def main(args: Array[String]): Unit = {
    val i1: MyInt = 1
    val i2: MyInt = 2
    val i3: MyInt = 3
    val i7: Int = i3 * i2 + i1
    println(i7)
    val ten = new TenInt()
    println(ten(7))
    ten(7) = 17
    println(ten(7))
    import jwj.Helper._
    println(1.plusPowerOf2(0))
    println(1.plusPowerOf2(1))
    println(1.plusPowerOf2(2))
    println(1.plusPowerOf2(3))
  }
}

Scala also provide itws own BigInt and BigDecimal types with operators:

package jwj

import java.math.{BigInteger => JavaBigInteger}
import java.math.{BigDecimal => JavaBigDecimal}

import scala.math.BigInt
import scala.math.BigDecimal

object Big {
  def main(args: Array[String]): Unit = {
    val i1 = new BigInt(new JavaBigInteger("1"))
    val i2 = new BigInt(new JavaBigInteger("2"))
    val i3 = new BigInt(new JavaBigInteger("3"))
    val i = i3 * i2 + i1
    println(i.toString() + " " + i.getClass().getName())
    val x1 = new BigDecimal(new JavaBigDecimal("1.1"))
    val x2 = new BigDecimal(new JavaBigDecimal("2.2"))
    val x3 = new BigDecimal(new JavaBigDecimal("3.3"))
    val x = x1 / x2 + x3
    println(x.toString() + " " + x.getClass().getName())
  }
}

Generic Programming:

Generic programming in Scala is similar to generic programming in Java - the syntax is just sligtly different.

Scala Java equivalent C# equivalent
class C[T] {
  ...
}
class C<T> {
    ...
}
class C<T>
{
    ...
}
class C[T <: MyClass] {
    ...
}
class C<T extends MyClass> {
    ...
}
class C<T> where T : MyClass
{
    ...
}
class I[-T] {
    // methods that take a T as input
}
...
def m(o: I[T]): Unit = {
    ...
}
...
m(instanceOfClassImplementingIforTorSuperClass)
interface I<T> {
    // methods that take a T as input
}
...
void m(I<? super T> o) {
    ...
}
...
m(instanceOfClassImplementingIforTorSuperClass);
interface I<in T>
{
    // methods that take a T as input
}
...
void m(I<T> o)
{
    ...
}
...
m(instanceOfClassImplementingIforTorSuperClass);
class I[+ T] {
    // methods that produce a T as output
}
...
def m(o: I[T]): Unit = {
    ...
}
...
m(instanceOfClassImplementingIforTorSubClass)
interface I<T> {
    // methods that produce a T as output
}
...
void m(I<? extends T> o) {
    ...
}
...
m(instanceOfClassImplementingIforTorSubClass);
interface I<out T>
{
    // methods that produce a T as output
}
...
void m(I<T> o)
{
    ...
}
...
m(instanceOfClassImplementingIforTorSubClass);

Example:

package jwj

class G1[T](val o: T) {
  override def toString: String = return s"$o"  
}

class GP {
  override def toString() = "GP"
}

class GC extends GP() {
  override def toString() = "GC"
}

class GGC extends GC() {
  override def toString() = "GGC"
}

class G2U[T <: GC](val o: T)

class G2L[T >: GC](val o: T)

class G3I[-T] {
  def mi(o: T) = { }
}

class G3O[+T] {
  def mo(): Option[T] = null
}

object Gen {
  def f3i[T](o: G3I[T]): Unit = { }
  def f3o[T](o: G3O[T]): Unit = { }
  def main(args: Array[String]): Unit = {
    val o1int = new G1[Int](123)
    println(o1int.o)
    val o1infint = new G1(123)
    println(o1infint.o)
    val o1string = new G1[String]("ABC")
    println(o1string.o)
    val o1infstring = new G1("ABC")
    println(o1infstring.o)
    //val dummy = new G2U(123)
    //val dummy = new G2U("ABC")
    //val dummy = new G2L(123)
    //val dummy = new G2L("ABC")
    //val o2pu = new G2U(new GP())
    //println(o2pu.o)
    val o2cu = new G2U(new GC())
    println(o2cu.o)
    val o2gcu = new G2U(new GGC())
    println(o2gcu.o)
    val o2pl = new G2L(new GP())
    println(o2pl.o)
    val o2cl = new G2L(new GC())
    println(o2cl.o)
    val o2gcl = new G2L(new GGC()) // for some reason accepted
    println(o2gcl.o)
    f3i[GP](new G3I[GP]())
    //f3i[GP](new G3I[GC]())
    f3i[GC](new G3I[GP]())
    f3i[GC](new G3I[GC]())
    f3o[GP](new G3O[GP]())
    f3o[GP](new G3O[GC]())
    //f3o[GC](new G3O[GP]())
    f3o[GC](new G3O[GC]())
  }
}

Functional Programming:

Scala supports functions as first class objects and lambda's.

Scala Java equivalent C# equivalent
(Type1, Type2) => ReturnType
@FunctionalInterface
interface SomeInterface {
    ReturnType someMethod(Type1 arg1, Type2 arg);
}
delegate ReturnType name(Type1 arg1, Type2 arg2)
(arg1: Type1, arg2: Type2) => ...
(Type1 arg1, Type2 arg2) -> ...
(Type1 arg1, Type2 arg2) => ...
(arg1, arg2) => ...
(arg1, arg2) -> ...
(arg1, arg2) => ...

Example:

package jwj

object FP {
  val eps = 0.0000000001
  def diff(f: (Double) => Double, x: Double): Double = {
      return (f(x + eps) - f(x)) / eps
  }
  def newton_solve(f: (Double) => Double): Double = {
      var x = 1.0
      var xold = 0.0
      do {
          xold = x
          x = x - f(x) / diff(f, x)
      } while(x/xold < (1 - eps) || x/xold > (1 + eps))
      return x
  }
  def main(args: Array[String]): Unit = {
    val sqrt_func = (x: Double) => x * x - 2
    val sqrt2 = newton_solve(sqrt_func)
    println(s"$sqrt2 ${sqrt2  * sqrt2}")
    val curt3 = newton_solve((x: Double) => x * x * x - 3 )
    println(s"$curt3 ${curt3 * curt3 * curt3}")
    val fortg_func = (x: Double, a: Double) => x * x * x * x - a
    val fort4_func = (x: Double) => fortg_func(x, 4.0) 
    val fort4 = newton_solve(fort4_func)
    println(s"$fort4 ${fort4 * fort4 * fort4 * fort4}")
  }
}

Scala has some advanced Functional Programming features.

Scala allows to specify delay of actual execution until needed with simple syntax.

lazy val name = somefunctioncall

will delay the functioncall until the value is actually used.

def name(arg: => typ) = ...

will delay any functioncall as argument to this function until the argument is actually used.

Scala supports currying and partially applied functions.

def name(arg1: typ1)(arg2: typ2) = ...

make it possible to do the trivial:

name(val1)(val2)

but also to do the partially applied function:

f = name(arg1)_
f(arg2)

Example:

package jwj

object FPX {
  def f(v: Int): Int = {
    println(s"v = $v")
    return v + 1
  }
  def normal(v: Int): Unit = {
    val v1 = f(1) // f called with 1
    println("normal")
    val v2 = v
    println(v1 + v2)
  }
  def delayed(v: => Int): Unit = {
    lazy val v1 = f(1)
    println("delayed")
    val v2 = v // f called with 2
    println(v1 + v2) // f called with 1
  }
  def add0(v1: Int, v2: Int) = println(s"$v1 + $v2 = ${v1 + v2}")
  def add1(v1: Int): (Int => Unit) = (v2: Int) => println(s"$v1 + $v2 = ${v1 + v2}")
  def add2(v1: Int)(v2: Int): Unit = println(s"$v1 + $v2 = ${v1 + v2}")
  def main(args: Array[String]): Unit = {
    normal(f(2)) // f called with 2
    delayed(f(2))
    add0(1, 2)
    add0(123, 1)
    add0(123, 2)
    add0(123, 3)
    add1(1)(2)
    val f1 = add1(123)
    f1(1)
    f1(2)
    f1(3)
    add2(1)(2)
    val f2 = add2(123)_ // note underscore
    f1(1)
    f1(2)
    f1(3)
  }
}

Scala and Java:

Scala and Java classes are interoperable. But conventions vary including convention for properties accessors and mutators.

Scala to Java is easy as Scala can just use traditional Java getter and setter methods:

Java to Scala is more tricky as the Scala getter and setter by default look like:

To fix that the @BeanProperty annotation can be put on the property in the Scala class. Then it wil follow Java convention.

Example:

package jwj;

public class JavaClass {
    private int v;
    public int getV() {
        return v;
    }
    public void setV(int v) {
        this.v = v;
    }
    public void m() {
        System.out.printf("v=%d\n", v);
    }
    public void callback() {
        ScalaClass osc = new ScalaClass(0);
        osc.setV(123);
        osc.m();
    }
}
package jwj

import scala.beans.BeanProperty

class ScalaClass(@BeanProperty var v: Int) {
  def m() = { println(s"v=$v") }
}

object Scala {
  def main(args: Array[String]): Unit = {
    val oj = new JavaClass()
    oj.setV(123)
    oj.m()
    oj.callback()
  }
}

Runtime:

Scala for JVM platform uses the Java runtime, but Scala has added a lot of extra Scala specific classes.

This is an overview of some of the more important ones.

Package scala:

Any
Base class of all types
AnyVal
Base class of all value types
AnyRef
Base class of all reference types
Array<T>
Class for arrays
Option[T]
Optional value
Some[T]
Some value
None
No value
Tuple2[T1,T2]
Pair of values
Tuple3[T1,T2,T3]
Triplet of values
Tuple4[T1,T2,T3,T4]
Quadrouplet of values

Note that TupleN classes are rarely used explicit but simply created with the tuple syntax of (val1,...,valn).

See also basic types here.

Packages scala.collection, scala.collection.immutable and scala.collection.mutable:

Scala has its own set of collections. They are very similar to Java's collection but provide better support for immutable collections.

The hirarchys generally are:

scala.collection.Something[E]
    scala.collection.immutable.Something[E]
    scala.collection.mutable.Something[E]

And:

Traversable
    Iterable
        Seq
            LinearSeq
                List
                Stack
                Queue
        Map
            HashMap
            SortedMap
                TreeMap
        Set
            HashSet
            SortedSet
                TreeSet

Example:

package jwj

import scala.collection.mutable

object Col {
  def main(args: Array[String]): Unit = {
    val a = Array(1, 2, 3)
    println(a.indices)
    val lst1 = List(1, 2, 3)
    println(lst1)
    println(lst1.indices)
    //lst1 += 4
    //lst1(1) = 0
    val lst2 = mutable.MutableList(1, 2, 3)
    println(lst2)
    println(lst2.indices)
    lst2 += 4
    lst2(1) = 0
    println(lst2)
    val map1 = Map(1 -> "A", 2 -> "BB", 3 -> "CCC")
    println(map1)
    println(map1.keys)
    println(map1.values)
    //map1 += 4 -> "DDDD"
    //map1(2) = "X"
    val map2 = mutable.Map(1 -> "A", 2 -> "BB", 3 -> "CCC")
    println(map2)
    println(map2.keys)
    println(map2.values)
    map2 += 4 -> "DDDD"
    map2(2) = "X"
    println(map2)
    val set1 = Set(1, 2, 3)
    println(set1)
    //set1 += 4
    val set2 = mutable.Set(1, 2, 3)
    println(set2)
    set2 += 4
    println(set2)
    val z = (123, 456.789, "ABC")
    println(z)
    println(s"${z._1} ${z._2} ${z._3}")
  }
}

Besides the usual methods for collections that Java has had since forever then Scala also got a number of methods for lambda usage.

filter
list/array to list/array with subset of elements
map
list/array to list/array or map with converted elements
fold
list/array to single value

Example:

package jwj

object ColLambda {
  def main(args: Array[String]): Unit = {
    val lst = List(1, 2, 3, 3)
    println(lst)
    println(lst.sum)
    println(lst.count(v => true))
    println(lst.exists( v => v > 2 ))
    println(lst.exists( v => v > 3 ))
    println(lst.forall( v => v > 0 ))
    println(lst.forall( v => v > 1 ))
    println(lst.distinct)
    println(lst.filter( v => v > 1))
    println(lst.fold(0)((acc, v) => acc + v ))
    lst.foreach( v => println(s" $v") )
    println(lst.map( v => v + 1 ))
    println(lst.map( v => s"Item: $v" ))
    println(lst.map( v => (v, s"Item: $v")).toMap)
  }
}

DSL features:

Scala got some features for enabling writing DSL (Domain Specific Language) in Scala.

Scala supports infix notation.

The statement:

a b c

is the same as:

a.b(c)

And note that all operators are just methods in Scala, so the reverse logic applies to operators!

Scala also allows for import of all members of a class or object.

And when combined with lambda's and Scala's very powerful functional programming features that can produce some new language syntax.

Exaample:

package jwj

class CI {
    def moveto(n: Int): Unit = {
        println(s"moveto $n")
    }
}

class CW {
    var a = 0
    var b = ""
}

object DSL {
  def main(args: Array[String]): Unit = {
    val o = new CI()
    o.moveto(3)
    o moveto 3
    val v1 = 3 * 2 + 1
    val v2 = 3.*(2).+(1);
    println(s"$v1 $v2")
    val w = new CW()
    import w._
    a = 123
    b = "abc"
    println(s"$a $b")
  }
}

Why switch to Scala:

I believe that for some projects it will be beneficial to switch from Java to Scala.

Benefits of Scala compared to Java (in order of significance):

  1. Save a lot of code in complex business logic
  2. Save a lot of method code for data classes
  3. More readable code via operator overload
  4. Simple function variables

Overall I would expect Scala code to have 25-50% fewer lines of code than the same Java code.

Drawbacks of Scala compared to Java (in order of significance):

Scala or Kotlin:

Scala and Kotlin share a lot of syntax. So from a syntax perspective they are very similar.

See here for description of Kotlin.

There are some differences though.

Kotlin got:

Scala got:

But the biggest difference is probably in philosophy. It seeems to me that:

Scala certainly has some serious traction in the market. But it is my clear impression that Kotlin is growing much faster in usage and either already has or soon will surpass it in usage.

But that does not make Scala a bad language. It just make it much less likely that it will become a mainstream language.

My expectation is that Scala will stay a niche language. But a niche language for the best of the best programmers!

Versions:

Scala release history:

Version Release date
1.0 January 20th 2004
2.0 March 12th 2006
... ...
2.11 May 21st 2014
2.12 November 3rd 2016
2.13 June 7th 2019
3.0 May 14th 2021
3.1 October 18th 2021

The article above covers Scala version 2.x (for x=11/12/13). A few selected new features in newer versions are covered below.

Version 3.0:

Scala 3.0 is a huge upgrade. It is almost like a new language.

Indentation can be used instead of {} to indicatre blocks similar to Python:

class C1 {
  def m(): Int = 123
}

class C2:
  def m(): Int = 456

object Ind30 {
  def main(args: Array[String]): Unit = {
    println((new C1()).m())
    println((new C2()).m())
    println("start")
    val b = false
    if(b) {
      println("1")
      println("2")
    }
    if(b)
      println("3")
      println("4")
    println("finish")
  }
}

Extension methods can be implemented via extension keyword instead of the implicit class workaround:

extension (me: Int) {
  def plusPowerOf2(n: Int): Int = me + (1 << n)
}

object Ext30 {
  def main(args: Array[String]): Unit = {
    println(1.plusPowerOf2(0))
    println(1.plusPowerOf2(1))
    println(1.plusPowerOf2(2))
    println(1.plusPowerOf2(3))
  }
}

Intersection types and union types has been added similar to TypeScript/JavaScript. An intersection type is a type that requires the actual type to extend all the specified types. A union type is a type where the actual type can be any of the specified types.

Intersection type:

trait X {
  def hi(): Unit = {
    println("Hi")
  }
}

trait Y {
  def hello(): Unit = {
    println("Hello")
  }
}

class A {
}

class AA extends A with X with Y {
}

class B {
}

class BB extends B with X with Y {
}

def f(o: X&Y): Unit = {
  o.hi()
  o.hello()
}

object Ext30 {
  def main(args: Array[String]): Unit = {
    f(new AA())
    f(new BB())
    f(new A() with X with Y)
    f(new B() with X with Y)
  }
}

Union type:

case class X(val a: Int)

case class Y(val b: Int)

def f(o: X|Y): Unit = {
  // the Java way
  if(o.isInstanceOf[X]) {
    println("a = " + o.asInstanceOf[X].a)
  }
  if(o.isInstanceOf[Y]) {
    println("b = " + o.asInstanceOf[Y].b)
  }
  // the Scala way
  println(o match
            case X(a) => "a = " + a
            case Y(b) => "b = " + b
          )
}

object Ext30 {
  def main(args: Array[String]): Unit = {
    f(new X(123))
    f(new Y(456))
  }
}

Union type may not look like something that will be widely used, but there is one essential use case.

Scala 3 supports non-nullable reference types similar to C# 8.0+. And union type used to specify nullable!

Scala 2.x
Scala 3.x without enabled
Scala 3.x with enabled Java C# 1.0 - 7.0
C# 8.0+ without enabled
C# 8.0+ with enabled
non-nullable N/A Xxx N/A N/A Xxx
nullable Xxx Xxx|Null Xxx Xxx Xxx?

Code snippet:

object En30 {
  def main(args: Array[String]): Unit = {
    val s1: String = "XXX"
    println(s1)
    //val s2: String = null
    //println(s2)
    val s3 : String|Null = "XXX"
    println(s3)
    val s4 : String|Null = null
    println(s4)
    //s1 = s3
    import scala.language.unsafeNulls
    //s1 = s3
  }
}

To enable the new feature compile with:

scala -Yexplicit-nulls -Ysafe-init ...

The do while loop has been removed.

Version 3.1:

As an experiemental feature safe exception handling similar to Java checked exceptions has been added:

import scala.annotation.experimental
import language.experimental.saferExceptions

@experimental
class MyException extends Exception

@experimental
def f():Unit throws MyException = {
  throw new MyException()
}

@experimental
object SE31 {
  def main(args: Array[String]): Unit = {
    try {
      f()
    } catch {
      case mex: MyException => println("Got it!")
    }
  }
}

Article history:

Version Date Description
1.0 June 15th 2019 Initial version
1.1 July 8th 2021 Add small Java 10+ and Java 14+ notes
1.2 March 6th 2022 Add version history

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj