Helsingin yliopisto / tietojenkäsittelytieteen laitos / Ohjelmointitekniikka (Scala) / © Arto Wikla 2010

11 Scalan luokkahierarkia

Muutettu viimeksi 21.4.2010 / Sivu luotu 14.4.2010 / [oppikirjan esimerkit] / [Scala]

Sivun sisältöä:

Hierarkia

Luokkahierarkia

Any

Luokka Any on siis luokkahierarkian huipulla. Kaikki kielen luokat perivät sen. Luokka sisältää seitsemän metodia, mm:
  final def ==(that: Any): Boolean
  final def !=(that: Any): Boolean
  def equals(that: Any): Boolean
  def hashCode: Int
  def toString: String
Kuten näkyy, mikään luokka ei voi korvata yhtäsuuruus- ja erisuuruusoperaatiota (final), mutta equals-metodia sitä sitten korvataankin... Oletustoteutus on Javan tapaan oliosamuus.

Operaatiot == ja != kuitenkin tavalla tai toisella johtavat equals-vertailuun ja siten polymorfismin ansiosta niitä voidaan käyttää myös itse ohjelmoidun samuuden vertailuun. Ymmärtääkseni nuo kaksi operaatiota on ohjelmoitu siten, että ne kutsuvat equalsia. Mitenkäs muutenkaan?

Jo kurssin alussa kuultiin se ilosanoma, että merkkijonoja Scalassa voi (järkevästi) vertailla operaatioilla == ja !=.

Huom: Kun ohjelmoi omalle luokalleen equals-metodin, on syytä pitää huoli, että toteuttaa ekvivalenssirelaation ja myös että samaistettavien olioiden hashCode on sama, ks. API.

Metodi toString Javan tapaan antaa oletusarvoisen "teknisen" tulostusasun kaikkien luokkien ilmentymille. Tapana on korvata.

AnyVal

Scalaan sisäänrakennetut tyypit Byte, Short, Char, Int, Long, Float, Double, Boolean ja Unit on kerätty Anyn aliluokkaan AnyVal.

Lukuunottamatta viimeistä nuo vastaavat Javan alkeistyyppejä. Myös ohjelman suoritusaikana ne (useimmiten) voidaan toteuttaa alla lymyävän Javan tapaan.

Scalassa myös Javan alkeistyyppien vastineiden ajatellaan olevan olioita. Niitä ei kuitenkaan konstruoida tyyliin new Int(5). Ainoa tapa luoda tällaisia olioita on kirjoittaa ne ohjelmaan literaaleina: 345, 3.14, false. Teknisesti asia on hoideltu määrittelemällä nuo luokat abstrakteiksi.

Luokalla Unit on vain yksi ilmentymä: (). Se on kaikkien "arvoa palauttamattomien" "funktioiden" arvo...

Koska esimerkiksi siis Int on Anyn aliluokka, se perii Anyn kaluston:

println(4321.toString)            // 4321
println(4321 hashCode)            // 4321
println(4321 equals (4000+321))   // true

Matematiikasta poiketen "pienet kokonaisluvut" eivät ole "kokonaislukujen" osajoukko (aliluokka), "kokonaisluvut" eivät ole "reaalilukujen" osajoukko (aliluokka), jne. Itse asiassa tietokoneessa näiden tyyppien sisäiset esitykset ovatkin aidosti erilaisia. Tätä heijastellee myös Scalan valinta esittää numeeriset tyypit rinnakkaisina periytymishierarkiassa.

Javasta tutut "sallitut sijoitukset", implisiittiset tyyppimuunnokset, tiettyjen numeeristen tyyppien välillä ovat käytössä myös Scalassa. Kannattaa muuten huomata, että nämä todellakin aiheuttavat töitä myös ohjelman suoritusaikana.

AnyRef

Javan viittaustyyppien Scala-vastineet on koottu luokkaan AnyRef. Tämä luokka itse asiassa vastaa Javan Object-luokkaa. Jopa Javanimeä Object voi Scala-ohjelmassa käyttää AnyRefin synonyyminä.

Piirteet ("trait") tuovatkin sitten oman lisävärinsä asioihin. Kuvassa näkyi jo ScalaObject, jota käytetään joihinkin käännösteknisiin jippoihin...

Null ja Nothing

Kaikilla AnyRef-luokan aliluokilla on yhteinen aliluokka Null. Sillä on yksi ainoa ilmentymä, olio nimeltään null. Siksi mitä tahansa AnyRef-tyyppistä arvoa voidaan verrata arvoon null. Toisaalta AnyVal-oliot eivät ole vertailtavissa tähän arvoon. (Mielestäni Scala on hoitanut tämän asian Javaa tyylikkäämmin!)

Kaikkien Scalan luokkien yhteinen aliluokka on sitten se vihoviimeinen Nothing. Se on sikälikin nimensä mukainen, ettei siitä ole olemassa ainuttakaan ilmentymää.

Mihin sellaista voi käyttää? No sitä voi käyttää tyyppinä! Jos vaikkapa funktion tyyppi on Nothing, tuon funktion voi sijoittaa minne tahansa, minne jonkin arvon voi sijoittaa.

Luvussa 7 nähtiin "Scala-knoppologiaa": Scalassa (melkein) kaikella on arvo. Niin myös throw-ilmauksella:

  val n = readInt
  val half =
    if (n % 2 == 0)
      n / 2
    else
      throw new RuntimeException("n must be even")
Mitään arvoahan ei muuttujalle half aseteta, jos n on pariton. Vahvasti tyypitetyssä kielessä "arvoilla" pitää kuitenkin aina olla tyyppi. Teknisesti throw-lause on tyypiltään Nothing, joka on kaikkien Scalan tyyppien alityyppi. Tässä esimerkissä se tarkoittaa, että Nothing kelpaa esim. juuri Int-tyypin arvoksi!

Tulipa muuten kokeiltua:

  val n = readInt
  val half =
    if (n % 2 == 0)
      n / 2
    else
      println("n must be even")
  println(n)
Toimii! Lauseen println tyyppi on Unit ja halfin tyypiksi tulee tässä AnyVal. (Korjattu 21.4. virheellinen väite, että println olisi tyypiltään Nothing! Kiitokset opiskelija Onni Koskiselle!)

Vielä yksi järkevä esimerkki: Scalan standardikirjastossa on metodi

def error(message: String): Nothing =  
  throw new RuntimeException(message) 
jota voi käyttää tyyliin:
def divide(x: Int, y: Int): Int =
  if (y != 0) x / y
  else error("can't divide by zero")
Tässäkin Nothing-tyyppinen funktio kelpaa Int-arvoksi.

(Hmm. Nothingin tyylikkyydestä sitten voi olla ainakin kahta mieltä. En ole vielä omaa kantaani valinnut.)