Kihagyás

30 - Any, Anyref, AnyVal, Null és Nothing

Most, hogy kovariáns a listánk, végre meg tudjuk oldani azt, hogy ne így kelljen ezt:

1
case class Nil[T]() extends List[T]
Ezt azért hoztuk létre eredetileg az első generikus lista implementációnkban, mert kellett egy Nil[Int], egy Nil[Long] stb, valamilyen típusparaméter kellett a Listnek, amit extendeltünk és ha object, akkor ő maga nem lehet generikus.

De most, hogy kovariáns T-ben a listánk, mire is lenne szükségünk? Arra, hogy a Nil jó legyen List[Long]-nak is, List[String]-nek is, bárminek. Ez a kovariancia miatt akkor lenne lehetséges, ha lenne egy olyan X típus, ami minden más típusnak leszármazottja.

Nothing

Van ilyen típus! A neve pedig Nothing. Egy jó képet láthatunk a Scala osztályhierarchiájáról itt.

Mit látunk a képen?

  • Scalában mindennek van valamiféle típusa, és valahova ebbe a hierarchiába kerül be.
  • Konkrétan ami osztályokat mi implmentálunk, az automatikusan a Null és az AnyRef osztályok közé fog kerülni. Ha csak annyit írunk, hogy case class Point(x: Int, y: Int), akkor ennek a Point osztály közvetlen őse lesz az AnyRef osztály, és közvetlenül alá kerül a Null osztály. (A List és az Option nem bírnak különleges szereppel a többi ,,beépített'' Scala osztályhoz képest: ugyanígy Null és AnyRef közt van a Map, Set és a többiek is. Persze ha a Point osztály extendeli a Shape traitet mondjuk, akkor az lesz fölötte, és a Shape fölött lesz az AnyRef.)
  • A képen láthatjuk, hogy a Nothing osztály mindennek a leszármazottja lesz automatikusan.

Vajon akkor hogyan tudjuk objektumként deklarálni a Nil-t, ha azt akarjuk, hogy olyan lista legyen, ami bármilyen listának megfelel?

1
case object Nil extends List[Nothing]
Válasz mutatása

Ezzel már el is értük, hogy a Nil objektum az teljesen jó lesz pl. List[Int]-nek (hiszen Nothing <: Int), vagy List[String]-nek (hiszen Nothing <: String), vagy List[Pet]-nek, mindennek, mert a listánk kovariáns.

Felmerülhet persze a kérdés, hogy mégis milyen értékek lehetnek Nothing típusúak, nos, a névválasztás nem véletlen, semmilyenek, nincs olyan objektum, ami Nothing típusú lenne. Ez a típus elsősorban erre való, kovariáns generikus osztályoknak olyan objektumait definiálni, amik minden típusparaméter mellett megfelelnek az adott osztály példányainak.

Illetve, már találkoztunk Nothing típusú ,,függvénnyel'', a Predef osztályban deklarált ??? függvénnyel:

wut

Láthatjuk, hogy ez a függvény soha nem ad vissza értéket, csak kivételt dob, ezért valahol validnak hangzik, hogy a típusa akár Nothing is lehet: mindig igaz, hogy ha kiértékelődik valamire (amit nem fog), akkor az értéke Nothing típusú lesz. Mindenesetre ezzel elérjük azt, hogy bármilyen típusú még nem implementált függvényünk (vagy értékünk) van, akkor ideiglenesen fejlesztés közben a ??? függvényt írva oda fordulni fog a kódunk, mert a Nothing típusú ??? minden típusnak megfelel.

AnyRef, AnyVal, Any

Ahogy a képen is látszik, az AnyRef valójában egy alias a java.lang.Object-re, ez mondjuk össze is cseng azzal, hogy az osztályaink ez alá kerülnek be közvetlen, ha mást nem mondunk, Javában is ez történik. Minden, ami a JVM számára ,,Object'', ide fog kerülni.

Ezzel párhuzamosan van az AnyVal osztály, ami alatt főképp azok a típusok kaptak helyet, melyek nem is objektumra fordulnak, hanem a legtöbb esetben a Java egy-egy elemi típusára: a Scalás Intből a JVM számára int lesz (nem Integer), a Double-ből double stb. Ezeket a típusokat value type-nak is hívják, szemben az objektumok reference type-jával, innen az osztályok neve: ha pl egy metódust írunk, ami kap egy valamilyen típusú paramétert, akkor ha ez egy reference type, úgy a címe, a referenciája kerül átadásra az objektumnak (és ha lenne mutátor metódusa, akkor az eredeti objektumot is megváltoztathatnánk vele a függvényben), ha pedig value type, akkor egy másolat kerül átadásra a függvénynek. (C-ben a structok value typeok, Javában ilyen nincs.)

Persze ahogy ,,legalsó'', úgy ,,legfelső'' osztályra is szükség van (pl. ha egy List[Pet] elé beszúrunk egy Int-et, annak is lehessen típusa), kell valaki az AnyVal és az AnyRef közé is, ez az Any osztály, aminek tényleg minden leszármazottja. (Azért sok esetben ha a fordító az Any típust következteti ki valaminek, kezdhetünk gyanakodni, hogy valami nem stimmel.)

Unit, Null

Az AnyVal osztály alatt találjuk még a Java primitív típusain felül a Unitot, amiről már tudjuk, hogy egyetlen értéke a (): neki az elsődleges funkciója az, hogy alapvetően a mellékhatás miatt kiértékelt kifejezéseknek is legyen valamiféle típusuk, Javás környezetben leginkább void lesz belőle.

Egy osztály van még, melyről nem volt szó, a Null: ennek az egyetlen példánya (hasonlóan a Unithoz, ez is egy olyan osztály, melynek csak egy lehetséges értéke van) a null. Idiomatikus Scalában nem használunk nullt, ahol lehet egy objektumreferencia akár null is, azt Optionnal illik megoldani, nem pedig folyamatos null checkekkel, de mivel használhatunk Scalából Java kódot és viszont, Javában pedig a null egy teljesen bevett szokás, ezért arra, hogy Java oldalról érkezik egy null, fel kell készülnünk. A nullnak viszont van egy olyan tulajdonsága, hogy bármilyen objektum referencia helyett használható, ezért került be ez az osztály az összes AnyRef alatti osztály alá: így a null lehet String is, vagy Integer, esetleg Pet is. (Plusz, ha Java oldalra az az igény, hogy adjunk pl. Option[String] helyett Stringet vissza, azzal, hogy lehessen null is hibajelzésként, akkor deklarálhatjuk a metódusunkat String kimenetűnek, és kiértékelődhet nullra is, le fog fordulni, mert a null megfelel Stringnek is.

Az if-else elmaradó else ága

Mi lehet a típusa az if ( 3 < 4 ) "kék" kifejezésnek?

`Any`. Általában a Scala az `if( b ) E1 else E2` kifejezés típusát úgy határozza meg, hogy megkeresi `E1` és `E2` típusainak a legszűkebb közös típusát. Így például egy `if( 3 < 4 ) Dog("Freya") else Cat("Morcos")` kifejezés típusa `Pet` lesz, mert ez az a legspecifikusabb típus, aminek a `Dog` is és a `Cat` is leszármazottja (note: a fordító **nem értékeli ki** a `3<4` kifejezést és nem jön rá, hogy ez mindenképp `Dog` lesz!) Azt meg már láttuk korábban, hogy ha elmarad az else, akkor a fordító oda generál egy `else ()` ágat. Aminek a típusa `Unit`. Tehát a `String` és a `Unit` típusok legszűkebb közös őstípusát keresi meg a fordító, ami az `Any`. Válasz mutatása

Utolsó frissítés: 2020-12-25 19:16:06