6.1. Definiowanie cech
Scala umożliwia definiowanie tak zwanych cech, które są pod pewnymi względami podobne do zwykłych klas, ale mają też własności odróżniające je od nich. Cechy tworzymy używając słowa kluczowego trait. W pliku Traits1.scala zdefiniowane są: zwykła klasa Hi oraz cecha Hello.
Plik Traits1.scala: class Hi { def hi = "Hi!" } trait Hello { def hello = "Hello!" }
Próba utworzenia instancji w następujących przykładach ujawnia jedną z różnic między zwykłymi klasami, a cechami.
scala> val a = new Hi
a: Hi = Hi@a85c4
scala> a.hi
res0: String = Hi!
scala> val b = new Hello
<console>:10: error: trait Hello is abstract; cannot be instantiated
val b = new Hello
^
Nie jest możliwe utworzenie obiektu będącego instancją cechy Hello. Można natomiast utworzyć obiekt będący instancją klasy anonimowej, opartej o cechę Hello. Można to zrobić dodając do wyrażenia new Hello parę nawiasów klamrowych. To samo zresztą można zrobić w przypadku klasy Hi.
scala> val c = new Hi {}
c: Hi = $anon$1@bee915
scala> c.hi
res1: String = Hi!
scala> val d = new Hello {}
d: Hello = $anon$1@18bd2ba
scala> d.hello res2: String = Hello!
![]() | Wartość d ma tak zwany typ złożony. Typy złożone opisane są w punkcie 6.2. |
W pliku Traits2.scala znajduje się definicja klasy HiWithHello. Klasa ta rozszerza klasę Hi. Dodatkowo, w jej definicji umieszczone jest słowo kluczowe with, a po nim nazwa cechy Hello. Taki zapis oznacza, że klasa HiWithHello rozszerza klasę Hi oraz że dodatkowo ma „wmieszaną” cechę Hello.
Plik Traits2.scala: class HiWithHello extends Hi with Hello
Na instancji tej klasy można wywołać zarówno metodę hi, zdefiniowaną w klasie Hi, jak i metodę hello, zdefiniowaną w cesze Hello.
scala> val e = new HiWithHello e: HiWithHello = HiWithHello@16e2263 scala> e.hi + e.hello res3: String = Hi!Hello!
W przedstawionym przykładzie typ wartości e nie jest jawnie określony i kompilator samodzielnie wyciąga wniosek dotyczący jej typu. Można również jawnie zadeklarować typ takiej wartości i typem tym nie musi być HiWithHello. Można użyć jako typu klasy, z której ta klasa dziedziczy, jak również cechy wmieszanej w tę klasę. Jeszcze inne możliwości są opisane w punkcie 6.2. Obydwie poniższe definicje są prawidłowe.
scala> val f: Hi = new HiWithHello f: Hi = HiWithHello@944ca9 scala> val g: Hello = new HiWithHello g: Hello = HiWithHello@15b0487
Na obiektach f i g nie można wywołać niektórych metod dostępnych w klasie HiWithHello. To, które metody są dostępne, zależy od tego, jak zadeklarowany jest typ odpowiedniej instancji.
scala> f.hi
res4: String = Hi!
scala> g.hello
res5: String = Hello!
scala> f.hello
<console>:12: error: value hello is not a member of Hi
f.hello
^
scala> g.hi
<console>:12: error: value hi is not a member of Hello
g.hi
^
Zwykłe klasy nie mogą być wmieszane w inną klasę, a więc ich nazwa nie może być użyta po słowie kluczowym with.
Plik Traits3.scala: class HelloWithHi extends Hello with Hi
Definicja klasy HelloWithHi z pliku Traits3.scala jest błędna z tego powodu i nie kompiluje się.
$ scalac Traits3.scala
Traits3.scala:1: error: class Hi needs to be a trait to be mixed in
class HelloWithHi extends Hello with Hi
^
one error found
Klasa może dziedziczyć tylko z jednej klasy, ale może mieć wmieszaną więcej niż jedną cechę. W definicji klasy można podać więcej niż jedno słowo kluczowe with, z następującą po nim nazwą cechy. W pliku Traits4.scala jest zdefiniowana klasa HiWithHelloAndWelcome, która dziedziczy z klasy Hi oraz ma wmieszane cechy Hello i Welcome.
Plik Traits4.scala: trait Welcome { def welcome = "Welcome!" } class HiWithHelloAndWelcome extends Hi with Hello with Welcome
Na instancji tej klasy można wywoływać metody hi, hello oraz welcome.
scala> val k = new HiWithHelloAndWelcome k: HiWithHelloAndWelcome = HiWithHelloAndWelcome@199c6b5 scala> k.hi res0: String = Hi! scala> k.hello res1: String = Hello! scala> k.welcome res2: String = Welcome!
Definicja nowej klasy może pominąć jawne określenie po słowie kluczowym extends klasy, z której ta definiowana klasa ma dziedziczyć. W takim przypadku klasa dziedziczy z klasy AnyRef. Nie jest jednak możliwe użycie słowa kluczowego with do wmieszania cechy z jednoczesnym pominięciem słowa kluczowego extends. W przypadku, gdy nie chcemy określać jawnie klasy, z której nowa klasa będzie dziedziczyć, a chcemy podać jedną lub więcej cech, mających być wmieszanych do nowej klasy, należy nazwę pierwszej z tych cech poprzedzić słowem kluczowym extends, a dopiero kolejne poprzedzać słowem kluczowym with.
Plik Traits5.scala: class AnyRefWithHello extends Hello class AnyRefWithHelloWithWelcome extends Hello with Welcome
W ten sposób zdefiniowane są klasy AnyRefWithHello oraz AnyRefWithHelloWithWelcome z pliku Traits5.scala.
scala> val m = new AnyRefWithHello m: AnyRefWithHello = AnyRefWithHello@1e731e scala> val n = new AnyRefWithHelloWithWelcome n: AnyRefWithHelloWithWelcome = AnyRefWithHelloWithWelcome@1ff43be scala> m.hello res3: String = Hello! scala> n.hello res4: String = Hello! scala> n.welcome res5: String = Welcome!
Oprócz definiowania zwykłych klas, które mają wmieszane cechy, możemy również definiować nowe cechy, które rozszerzają jedną lub więcej cech. Na przykład cecha HelloWithWelcome, zdefiniowana w pliku Traits6.scala, rozszerza cechy Hello i Welcome.
Plik Traits6.scala: trait HelloWithWelcome extends Hello with Welcome
![]() | W dalszej części książki będę pisał o „dziedziczeniu” zarówno w przypadku składowych pochodzących ze zwykłych klas, jak i pochodzących z cech. |
![]() | Specyfikacja języka Scala opisuje cechy w punkcie 5.4. |
Plik Traits1.scala:
class Hi { def hi = "Hi!" }
trait Hello { def hello = "Hello!" }


