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.