5.10. Klasy wewnątrz klas
Klasy można definiować w ciele innych klas. Przykładem takiej definicji jest klasa Hi2, zdefiniowana wewnątrz klasy Hi1, w pliku Enclosing1.scala.
Plik Enclosing1.scala: class Hi1 { def greet = println("Hi1") class Hi2 { def greet = println("Hi2") } }
Utworzenie instancji klasy Hi1 nie stanowi problemu.
scala> val hi1 = new Hi1 hi1: Hi1 = Hi1@39680d scala> hi1.greet Hi1
Poniższe wyrażenia pokazują jak można utworzyć instancję klasy Hi2.
scala> val hi2 = new hi1.Hi2 hi2: hi1.Hi2 = Hi1$Hi2@76f6a0 scala> hi2.greet Hi2
Typem utworzonej instancji pokazanym przez konsolę jest hi1.Hi2. Jeśli utworzymy inną instancję klasy Hi1, a następnie inną instancję zawartej w niej klasy Hi2, to obie instancje klasy Hi2 nie będą tego samego typu. Pokazują to następujące wyrażenia.
scala> val hi1b = new Hi1 hi1b: Hi1 = Hi1@44b638
scala> var hi2b = new hi1b.Hi2 hi2b: hi1b.Hi2 = Hi1$Hi2@191b517 scala> hi2b = hi2 <console>:14: error: type mismatch; found : hi1.Hi2 required: hi1b.Hi2 hi2b = hi2 ^
Nie udało się przypisać wartości hi2 do zmiennej hi2b, gdyż ta wartość ma typ hi1b.Hi2, a typem zmiennej jest hi1.Hi2. W nazwach obu typów znajdują się nazwy instancji klasy Hi1. W obu przypadkach są to wartości niezmienne, zdefiniowane przy pomocy słowa kluczowego val. Gdyby były to zmienne, zdefiniowane przy pomocy var, to instancji klasy Hi2 nie udałoby się w ogóle utworzyć. Taką sytuację pokazuje kolejny przykład.
scala> var hi1c = new Hi1 hi1c: Hi1 = Hi1@12a7d73 scala> val hi2c = new hi1c.Hi2 <console>:11: error: stable identifier required, but hi1c found. val hi2c = new hi1c.Hi2 ^
Kompilator odrzucił definicję, oczekując tak zwanego stabilnego identyfikatora, a zmienne nimi nie są.
Specyfikacja języka Scala opisuje stabilne modyfikatory w punkcie 3.1. |
Kolejny przykład pokazuje klasy podobne do poprzednich, ale zawierające dodatkowe metody o nazwie talk.
Plik Enclosing2.scala: class Hello1 { def greet = println("Hello1") def talk = { greet; this.greet } class Hello2 { def greet = println("Hello2") def talk = { this.greet; Hello1.this.greet } } }
Do składowych klasy można się odwoływać poprzedzając nazwę składowej przedrostkiem this. Metoda talk z klasy Hello1 wywołuje dwukrotnie metodę greet z tej klasy, przy czym w pierwszym wywołaniu robi to przy pomocy samej nazwy metody, a w drugim za pomocą nazwy poprzedzonej przedrostkiem this. Metoda talk z klasy Hello2 wywołuje metodę greet z klas Hello2 i Hello1. Pierwsze wywołanie, z użyciem przedrostka this, wywołuje metodę greet z klasy Hello2. W celu wywołania metody z klasy Hello1 użyty jest inny przedostek — przedrostek this jest dodatkowo poprzedzony nazwą klasy. Poniższe wyrażenia pokazują rezultaty wywołania obu metod talk.
scala> val hello1 = new Hello1 hello1: Hello1 = Hello1@1815ffc scala> val hello2 = new hello1.Hello2 hello2: hello1.Hello2 = Hello1$Hello2@1d29a59
scala> hello1.talk Hello1 Hello1 scala> hello2.talk Hello2 Hello1
Definicje klas znajdujących się w pliku Enclosing3.scala ilustrują jeszcze inny sposób odwoływania się do składowych klas. Zamiast przedrostka this można użyć przedrostka o nazwie zdefiniowanej samodzielnie. Nazwę takiego przedrostka deklaruje się poprzez umieszczenie na początku ciała klasy (po otwierającym nawiasie klamrowym) parametru, po którym umieszcza się podwójną strzałkę w prawo (znaki => lub znak ⇒). Nazwa tego parametru staje się aliasem przedrostka this. Poprzez taki alias można się odwoływać do instancji klasy.
Plik Enclosing3.scala: class Welcome1 { welcome1 => def greet = println("Welcome1") class Welcome2 { welcome2 ⇒ def greet = println("Welcome2") def talk = { welcome1.greet welcome2.greet } } }
Poniższy przykład pokazuje rezultat wywołania metody talk.
scala> val w1 = new Welcome1 w1: Welcome1 = Welcome1@1bc0606 scala> val w2 = new w1.Welcome2 w2: w1.Welcome2 = Welcome1$Welcome2@1b25825 scala> w2.talk Welcome1 Welcome2