In this article we present several examples of advanced features in the Scala programming language.
Scala behaves differently to Java when overriding class members (see [1], 5.1.3). For instance a Scala programmer may override not only the value but also the type of a class member. We illustrate those differences with three small code examples.
Given the two Java class declarations
class Foo {}
class Bar extends Foo {}
we consider the following class hierarchy
abstract class A {
Foo foo; // [1]
}
class A1 extends A {
int foo; // [2]
A1() { foo = 1; }
}
class A2 extends A {
A2() { foo = new Bar(); }
}
abstract class B extends A {
int foo; // [3]
}
class B1 extends B {
A21() { foo = 2; }
}
where the abstract field foo of type Foo
in base class A (marked as [1]) is hidden by a
member with the same name declared in A1 and B
(marke as [2] resp. as [3]), two subclasses of
A.
That's legal Java code! We give here a few test cases together with their output:
class Main {
public static void main(String[] args) {
new Main();
}
Main() {
A a = new A1();
System.out.println("a.foo="+a.foo); // null
a = new A2();
System.out.println("a.foo="+a.foo); // Bar@..
B b = new B1();
System.out.println("b.foo="+b.foo); // 2
a = b;
System.out.println("a.foo="+a.foo); // null
}
}
The Scala compiler rejects the code below which implements the same class hierarchy
class Foo
class Bar extends Foo
abstract class A {
val foo: Foo // [1]
}
class A1 extends A {
val foo: Int = 1 // [2]
}
class A2 extends A {
val foo: Bar = new Bar()
}
abstract class B extends A {
val foo: Int // [3]
}
class B1 extends B {
val foo = 2
}
and prints out the following error messages:
Main.scala:9: error: overriding value foo in class A of type Foo;
value foo has incompatible type
val foo: Int = 1
^
Main.scala:15: error: overriding value foo in class A of type Foo;
value foo has incompatible type
val foo: Int
^
Main.scala:18: error: overriding value foo in class A of type Foo;
value foo has incompatible type
val foo = 2
^
three errors found
Let's now revisit a slightly modified version of our class hierarchy:
abstract class A {
Foo foo; // [1]
}
class A1 extends A {
A1() { foo = new Foo(); }
}
class A2 extends A {
A2() { foo = new Bar(); }
}
abstract class B extends A {
}
class B1 extends B {
B1() { foo = new Foo(); }
}
Again we interest us in the abstract field foo
of type Foo declared in base class A
(marked as [1]).
When declaring a subclass of A a Java programmer has to
initialize foo with some value whose type is a subtype
of Foo but he can't restrict that choice by specifying
a more specific type for foo, for instance in A2.
class Main {
public static void main(String[] args) {
new Main();
}
Main() {
A a = new A1();
System.out.println("a.foo="+a.foo); // Foo@..
a = new A2();
System.out.println("a.foo="+a.foo); // Bar@..
B b = new B1();
System.out.println("b.foo="+b.foo); // Foo@..
a = b;
System.out.println("a.foo="+a.foo); // Foo@..
}
}
In contrary a Scala programmer may override the type of foo
in a subclass of A (marked as [1] and
[2]):
abstract class A {
val foo: Foo
}
class A1 extends A {
val foo: Foo = new Foo()
}
class A2 extends A {
val foo: Bar = new Bar() // [1]
}
abstract class B extends A {
val foo: Bar // [2]
}
class B1 extends B {
//val foo = new Foo() // error: value foo has incompatible type
val foo = new Bar()
}
foo now must be initialized with a value whose type is
a subtype of Bar in every subclass of B.
The result for test case 4 differs from the output for the Java example.
object Main extends App {
var a: A = new A1()
println("a.foo="+a.foo) // Foo@..
a = new A2()
println("a.foo="+a.foo) // Bar@..
var b: B = new B1()
println("b.foo="+b.foo) // Bar@..
a = b
println("a.foo="+a.foo) // Bar@..
}
And in Scala we can go one step further: we may define an abstract
type in base clase A (marked as [1]) and
then specify the concrete type of foo (and typically of
more class members) in a subclass of A:
abstract class A {
type T <: Foo // [1]
val foo: T
}
class A1 extends A {
type T = Foo
val foo: T = new Foo()
}
class A2 extends A {
type T = Bar
val foo: T = new Bar()
}
abstract class B extends A {
type T = Bar
}
class B1 extends B {
//val foo = new Foo() // error: value foo has incompatible type
val foo = new Bar()
}
We get the same results for the test cases from example 2.