6.6. Parametry

Kolejna różnica między klasami oraz cechami związana jest z parametrami klasy. W pliku TraitParams1.scala znajduje się definicja klasy Hi, zawierającej parametr.

Plik TraitParams1.scala:
class Hi(name: String) {
  def hi = "Hi "+name+"!"
}

Cechy nie mogą mieć parametrów.

Plik TraitParams2.scala:
trait Hello(name: String) {
  def hello = "Hello "+name+"!"
}

Definicja cechy Hello z pliku TraitParams2.scala jest niepoprawna i nie kompiluje się.

$ scalac TraitParams2.scala
TraitParams2.scala:1: error: traits or objects may not have parameters
trait Hello(name: String) {
           ^
one error found

W pliku TraitParams3.scala znajduje się definicja cechy Hello3, która kompiluje się bez błędów. Zamiast parametru, jej definicja zawiera abstrakcyjną składową name.

Plik TraitParams3.scala:
trait Hello3 {
  val name: String
  def hello = println("Hello "+name+"!")
}

Polecenie z wiersza tworzy obiekt klasy anonimowej, rozszerzającej cechę Hello3. Abstrakcyjna składowa name cechy Hello3 zostaje w tym poleceniu nadpisana i uzyskuje wartość.

scala> val john = new Hello3 { val name = "John" } 
john: Hello3 = $anon$1@12b107
scala> john.hello
Hello John!

Zdefiniowane w pliku TraitParams4.scala klasy Hello4a i Hello4b, które rozszerzają cechę Hello3, deklarują parametr o nazwie name, który nadpisuje abstrakcyjną składową name cechy Hello3. W przypadku nadpisywania składowej abstrakcyjnej można, ale nie trzeba, użyć modyfikatora override. W definicji klasy Hello4a ten modyfikator jest pominięty, natomiast znajduje się w definicji klasy Hello4b.

Plik TraitParams4.scala:
class Hello4a(val name: String) extends Hello3
class Hello4b(override val name: String) extends Hello3

Poniższe polecenia wykorzystują te klasy.

scala> new Hello4a("Peter").hello
Hello Peter!

scala> new Hello4b("Paul").hello
Hello Paul!

W pliku TraitParams5.scala znajduje się definicja cechy Hello5, w której — w odróżnieniu od cechy Hello3 — metoda println z metody hello nie odwołuje się bezpośrednio do wartości name, ale do wartości greeting, której definicja wykorzystuje wartość name.

Plik TraitParams5.scala:
trait Hello5 {
  val name: String
  val greeting = "Hello "+name+"!" 
  def hello = println(greeting)
}
class Hello5Mary extends Hello5 { val name = "Mary" }

Klasa Hello5Mary rozszerza cechę Hello5.

scala> (new Hello5Mary).hello
Hello null!

Metoda hello wywoływana na instancji tej klasy wyświetla null, zamiast imienia. Dzieje się tak dlatego, że instrukcja z wiersza jest wywoływana przed konstruktorem Hello5Mary. Wobec tego, w momencie definiowania wartości greeting, a więc w momencie wykonywania tej instrukcji, name ma początkową wartość null. Docelową wartość uzyskuje dopiero w wyniku wykonania kodu konstruktora klasy Hello5Mary.

Ten problem można rozwiązać za pomocą tak zwanej wczesnej definicji. Wczesne definicje umieszcza się w nawiasach klamrowych po słowie extends. Wczesne definicje są wykonywane przed wywołaniem konstruktora nadtypu. Plik TraitParams5a.scala zawiera definicję klasy Hello5Ann, w której jest użyta wczesna definicja zawierająca definicję wartości name.

Plik TraitParams5a.scala:
class Hello5Ann extends { val name = "Ann" } with Hello5

Ponieważ wczesne definicje są wykonywane przed kodem konstruktora nadtypu, więc w momencie wykonywania instrukcji z wiersza name zawiera już odpowiednią wartość.

scala> (new Hello5Ann).hello
Hello Ann!

W pliku TraitParams5b.scala znajduje się inny przykład. Klasa Hello5Name jest zdefiniowana wewnątrz klasy Hello5Factory z wykorzystaniem wczesnej definicji. We wczesnej definicji występuje odwołanie do parametru klasy Hello5Factory. To odwołanie zawiera przedrostek this. Takie odwołanie jest prawidłowe, gdyż this w tym przypadku dotyczy klasy Hello5Factory. Blok kodu zawierający wczesne definicje jest ewaluowany tak, jakby był lokalnym blokiem, zawierającym definicje wartości.

Plik TraitParams5b.scala:
class Hello5Factory(paramName: String) {
  class Hello5Name extends { val name = this.paramName } with Hello5
  def create = new Hello5Name
}

Poniższe wyrażenie ilustruje działanie kodu pliku TraitParams5b.scala.

scala> new Hello5Factory("Peter").create.hello
Hello Peter!

Próba przesunięcia bloku definiującego wartość name na koniec definicji klasy Hello5Name, tak jak to jest zrobione w pliku TraitParams5c.scala, skutkuje błędem kompilacji.

Plik TraitParams5c.scala:
class Hello5Factory(paramName: String) {
  class Hello5Name extends Hello5 { val name = this.paramName }
  def create = new Hello5Name
}

Błąd wynika z tego, że w takim przypadku przedrostek this odnosi się do definiowanej klasy, czyli klasy Hello5Name, a w tej klasie nie ma składowej o nazwie paramName.

$ scalac TraitParams5c.scala
TraitParams5c.scala:2: error: value paramName is not a member of Hello5Factory.this.Hello5Name
  class Hello5Name extends Hello5 { val name = this.paramName }
                                                    ^
one error found

Specyfikacja języka Scala opisuje wczesne definicje w punkcie 5.1.6.

Język programowania Scala Wydanie 2. Copyright © Grzegorz Balcerek 2016

Licencja Creative Commons

Ten utwór jest dostępny na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowe.

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.