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

11 Scalan luokkahierarkia

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

Sivun sisältöä:

Hierarkia

Luokkahierarkia

Any

Luokka Any on siis luokkahierarkian huipulla. Kaikki muut luokat perivät sen. Luokka sisältää mm. metodit:
  final def == (that: Any): Boolean
  final def != (that: Any): Boolean
  def equals(that: Any): Boolean
  def ##: Int
  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. Nuo kaksi operaatiota on ohjelmoitu siten, että ne kutsuvat equals-metodia.

Jo kurssin alussa kuultiin se ilosanoma, että esimerkiksi merkkijonojen samuuta 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 tulostusasu käyttökelpoisemmalla.

AnyVal

Scalaan sisäänrakennetut tyypit Byte, Short, Char, Int, Long, Float, Double, Boolean ja Unit on kerätty abstraktin Any-luokan abstraktiin 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 ja "finaaleiksi".

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ä.

Piirretyypit ("trait") tuovatkin sitten oman lisävärinsä asioihin. Kuvassa näkyy jo yksi tällainen, 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.

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.

Kuten on opittu, Scalassa (melkein) kaikella on arvo. Niin myös throw-ilmauksella:

  val n = scala.io.StdIn.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-tyyppiseksi arvoksi!

[Tulipa muuten kokeiltua, vaikka ei ihan tähän liitykään:

  val n = scala.io.StdIn.readInt
  val half =
    if (n % 2 == 0)
      n / 2
    else
      println("n must be even")
  println(n)
  println(half)
Toimii! Lauseen println tyyppi on Unit ja halfin tyypiksi tulee tässä AnyVal.

Kokeillaan parillisella ja parittomalla:
Syötetään 44:

44
22
Syötetään 33:
n must be even
33
()
Tämä syrjähyppy ei kuulu kurssiin.]

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 sys.error("Nollalla ei saa jakaa!")

val a = divide(1,0)
Seuraus:
java.lang.RuntimeException: nollalla ei saa jakaa
	at scala.sys.package$.error(package.scala:27)
	at Main$$anon$1.divide(tmp.sca:4)
...
jne...
Tässäkin Nothing-tyyppinen funktio kelpaa Int-arvoksi.

(Hmm... Tuon Nothing-luokan tyylikkyydestä voi olla ainakin kahta eri mieltä...)