Bevezetés a Scalába

1. Bemutatkozás

Ebben az oktatóanyagban megvizsgáljuk a Scalát - az egyik elsődleges nyelvet, amely a Java virtuális gépen fut.

Kezdjük az alapvető nyelvi jellemzőkkel, például értékekkel, változókkal, módszerekkel és vezérlő struktúrákkal. Ezután megvizsgálunk néhány olyan speciális funkciót, mint például a magasabb rendű funkciók, a curry, az osztályok, az objektumok és a mintaillesztés.

A JVM-nyelvek áttekintéséhez olvassa el a JVM-nyelvek gyors útmutatóját

2. Projekt beállítása

Ebben az oktatóanyagban a Scala szokásos telepítését fogjuk használni: //www.scala-lang.org/download/.

Először adjuk hozzá a scala-library függőséget a pom.xml fájlhoz. Ez a mű biztosítja a nyelv szabványos könyvtárát:

 org.scala-lang scala-library 2.12.7 

Másodszor adjuk hozzá a scala-maven-plugint a kód fordításához, teszteléséhez, futtatásához és dokumentálásához:

 net.alchim31.maven scala-maven-plugin 3.3.2 compile testCompile 

A Maven rendelkezik a scala-lang és a scala-maven-plugin legújabb műtermékeivel.

Végül a JUnit-et fogjuk használni az egység teszteléséhez.

3. Alapvető jellemzők

Ebben a részben példákon keresztül megvizsgáljuk az alapvető nyelvi jellemzőket. Erre a célra a Scala tolmácsot fogjuk használni.

3.1. Tolmács

A tolmács egy interaktív héj a programok és kifejezések írására.

Nyomtassuk ki a „hello world” -t:

C: \> scala Üdvözöljük a Scala 2.12.6 verzióban (Java HotSpot (TM) 64 bites kiszolgáló virtuális gép, Java 1.8.0_92). Írja be a kifejezéseket az értékeléshez. Vagy próbáld meg: segítség. scala> print ("Hello World!") Hello World! scala>

Fentebb a tolmácsot úgy indítjuk, hogy a parancssorba beírjuk a „scala” szót. A tolmács elindul, és üdvözlő üzenetet jelenít meg, amelyet egy prompt követ.

Ezután írja be a kifejezést erre a kérdésre. A tolmács elolvassa a kifejezést, kiértékeli és kinyomtatja az eredményt. Ezután hurokba lép, és újra megjeleníti a felszólítást.

Mivel azonnali visszajelzést ad, a tolmács a legegyszerűbb módszer a nyelv használatának megkezdésére. Ezért használjuk fel az alapvető nyelvi jellemzők feltárását: kifejezéseket és különféle definíciókat.

3.2. Kifejezések

Bármely kiszámítható állítás kifejezés.

Írjunk néhány kifejezést, és nézzük meg az eredményeiket:

scala> 123 + 321 res0: Int = 444 scala> 7 * 6 res1: Int = 42 scala> "Hello", + "World" res2: String = Hello, World scala> "zipZAP" * 3 res3: String = zipZAPzipZAPzipZAP scala > if (11% 2 == 0) "páros" else "páratlan" res4: Karakterlánc = páratlan

Mint fent láthatjuk, minden kifejezésnek van értéke és típusa.

Ha egy kifejezésnek nincs mit visszatérnie, akkor egy típusú értéket ad vissza Mértékegység. Ennek a típusnak csak egy értéke van: (). Hasonló a üres kulcsszó Java-ban.

3.3. Értékdefiníció

A kulcsszó val értékek deklarálására szolgál.

Egy kifejezés eredményének megnevezésére használjuk:

scala> val pi: Dupla = 3,14 pi: Dupla = 3,14 scala> print (pi) 3,14 

Ez lehetővé teszi számunkra az eredmény többszörös újrafelhasználását.

Az értékek változhatatlanok. Ezért nem rendelhetjük át őket:

scala> pi = 3.1415: 12: hiba: átrendelés val pi = 3.1415 ^ -re

3.4. Változó definíció

Ha át kell rendelnünk egy értéket, akkor azt változóként deklaráljuk.

A kulcsszó var a változók deklarálására szolgál:

scala> var sugár: Int = 3 sugár: Int = 3

3.5. A módszer meghatározása

A módszereket a def kulcsszó. A kulcsszó után megadjuk a metódus nevét, a paraméterdeklarációkat, az elválasztót (kettőspontot) és a visszatérés típusát. Ezek után megadunk egy elválasztót (=), amelyet a method törzs követ.

A Java-val ellentétben nem a Visszatérés kulcsszó az eredmény visszaadásához. Egy módszer az utolsó kiértékelt kifejezés értékét adja vissza.

Írjunk egy módszert Átl két szám átlagának kiszámításához:

scala> def átl. (x: dupla, y: dupla): dupla = {(x + y) / 2} átl.: (x: dupla, y: dupla) dupla

Akkor hívjuk meg ezt a módszert:

scala> átlag (10,20) res0: Dupla = 12,5 

Ha egy módszer nem vesz fel semmilyen paramétert, akkor a meghatározás és a meghívás során a zárójeleket elhagyhatjuk. Ezenkívül elhagyhatjuk a zárójeleket, ha a testnek csak egy kifejezése van.

Írjunk paraméter nélküli metódust pénzfeldobás amely véletlenszerűen adja vissza a „fej” vagy „farok” kifejezést:

scala> def coinToss = if (Math.random> 0.5) "Fej" egyéb "Farok" érmeToss: Karakterlánc

Ezután hívjuk meg ezt a módszert:

scala> println (coinToss) farok scala> println (coinToss) fej

4. Vezérlő szerkezetek

A vezérlő struktúrák lehetővé teszik számunkra, hogy megváltoztassuk a vezérlés áramlását egy programban. A következő ellenőrzési struktúrákkal rendelkezünk:

  • Ha-más kifejezés
  • Míg hurok és csinál, míg hurok
  • Kifejezésre
  • Próbálja ki a kifejezést
  • Match kifejezés

A Java-val ellentétben nálunk nincs folytatni vagy szünet kulcsszavak. Megvan a Visszatérés kulcsszó. Kerülnünk kell azonban a használatát.

A helyett kapcsoló állítással, megvan a Mintaillesztés egyezés kifejezéssel. Ezen felül meghatározhatjuk saját kontroll absztrakcióinkat.

4.1. ha más

A ha más kifejezés hasonló a Java-hoz. A más rész opcionális. Beágyazhatunk több if-else kifejezést.

Mivel kifejezés, ezért értéket ad vissza. Ezért hasonlóan használjuk, mint a Java háromfázisú operátorát (? :). Valójában a nyelv nem rendelkezik hármas operátorral.

Az if-else használatával írjunk egy módszert a legnagyobb közös osztó kiszámításához:

def gcd (x: Int, y: Int): Int = {if (y == 0) x egyéb gcd (y, x% y)}

Ezután írjunk egységtesztet ehhez a módszerhez:

@Test def whenGcdCalledWith15and27_then3 = {assertEquals (3, gcd (15, 27))}}

4.2. Míg a hurok

A while ciklusnak van állapota és teste. Többször is ciklusban értékeli a testet, miközben a feltétel igaz - a feltételt minden egyes iteráció elején értékeljük.

Mivel nincs semmi hasznos visszatérni, visszatér Mértékegység.

Használjuk a while ciklust arra, hogy írjunk egy módszert a legnagyobb közös osztó kiszámításához:

def gcdIter (x: Int, y: Int): Int = {var a = x var b = y, míg (b> 0) {a = a% b val t = a a = b b = t} a}

Ezután ellenőrizzük az eredményt:

assertEquals (3, gcdIter (15, 27))

4.3. Csináld, amíg hurok

A do while ciklus hasonló a while ciklushoz, azzal a különbséggel, hogy a ciklus állapotát a hurok végén értékelik.

A do-while ciklus segítségével írjunk egy módszert a faktoriál kiszámítására:

def faktoriális (a: Int): Int = {var eredmény = 1 var i = 1 do {eredmény * = i i = i + 1}, míg (i <= a) eredmény}

Ezután ellenőrizzük az eredményt:

assertEquals (720, faktoriális (6))

4.4. Kifejezésért

A for kifejezés sokkal sokoldalúbb, mint a Java for for loop.

Iterálhat egyetlen vagy több gyűjtemény felett. Sőt, kiszűrheti az elemeket, valamint új kollekciókat állíthat elő.

A kifejezés használatával írjunk egy módszert egész számtartomány összegzésére:

def tartománySum (a: Int, b: Int) = {var sum = 0 (i <- a - b) {összeg + = i} összeg}

Itt, a-tól b-ig egy generátor kifejezés. Értékek sorozatát generálja a nak nek b.

i <- a-tól b-ig generátor. Meghatározza én mint val és hozzárendeli a generátor kifejezés által létrehozott értéksorozatot.

A törzs a sorozat minden értékére végrehajtásra kerül.

Ezután ellenőrizzük az eredményt:

assertEquals (55, rangeSum (1, 10))

5. Funkciók

A Scala funkcionális nyelv. A függvények itt első osztályú értékek - használhatjuk őket, mint bármely más értéktípust.

Ebben a szakaszban a funkciókkal kapcsolatos fejlett fogalmakat vizsgáljuk meg - helyi függvények, magasabb rendű függvények, névtelen függvények és curry.

5.1. Helyi funkciók

Megadhatunk függvényeket a függvényeken belül. Beágyazott funkcióknak vagy helyi függvényeknek nevezzük őket. Hasonló a helyi változókhoz, csak a meghatározott funkción belül láthatók.

Most írjunk egy módszert a teljesítmény kiszámításához egy beágyazott függvény segítségével:

def teljesítmény (x: Int, y: Int): Int = {def powNested (i: Int, akkumulátor: Int): Int = {if (i <= 0) akkumulátor else powNested (i - 1, x * akkumulátor)} powNested (y, 1)}

Ezután ellenőrizzük az eredményt:

assertEquals (8, teljesítmény (2, 3))

5.2. Magasabb rendű funkciók

Mivel a függvények értékek, átadhatjuk őket paraméterként egy másik függvénynek. Lehet, hogy egy függvény egy másik függvényt ad vissza.

A függvényeken működő funkciókat magasabb rendű függvényeknek nevezzük. Lehetővé teszik, hogy elvontabb szinten dolgozzunk. Használatukkal általánosított algoritmusok megírásával csökkenthetjük a kódduplikációt.

Most írjunk egy magasabb rendű függvényt a térkép végrehajtására és a műveletek csökkentésére egész számok tartományában:

def mapReduce (r: (Int, Int) => Int, i: Int, m: Int => Int, a: Int, b: Int) = {def iter (a: Int, eredmény: Int): Int = { if (a> b) {eredmény} else {iter (a + 1, r (m (a), eredmény))}} iter (a, i)}

Itt, r és m paraméterei Funkció típus. Különböző függvények átadásával számos problémát megoldhatunk, például négyzetek vagy kockák összegét és a faktoriált.

Ezután használjuk ezt a függvényt egy másik függvény megírásához sumSquares ez összegezi az egész számok négyzetét:

@Test def whenCalledWithSumAndSquare_thenCorrectValue = {def négyzet (x: Int) = x * x def összeg (x: Int, y: Int) = x + y def sumSquares (a: Int, b: Int) = mapReduce (összeg, 0, négyzet, a, b) assertEquals (385, sumSquares (1, 10))}

Fent ezt láthatjuk a magasabb rendű függvények általában sok kicsi, egyszer használatos funkciót hoznak létre. Anonim függvények használatával elkerülhetjük a megnevezésüket.

5.3. Névtelen funkciók

Az anonim függvény egy kifejezés, amely értéket ad egy függvénynek. Hasonló a Java-ban a lambda kifejezéshez.

Írjuk át az előző példát névtelen függvények használatával:

@Test def whenCalledWithAnonymousFunctions_thenCorrectValue = {def sumSquares (a: Int, b: Int) = mapReduce ((x, y) => x + y, 0, x => x * x, a, b) assertEquals (385, sumSquares ( 1, 10))}

Ebben a példában mapReduce két névtelen funkciót kap: (x, y) => x + y és x => x * x.

A Scala a kontextusból következtethet a paramétertípusokra. Ezért ezekben a függvényekben kihagyjuk a paraméterek típusát.

Ez az előző példához képest tömörebb kódot eredményez.

5.4. Curry funkciók

A curried függvény több argumentumlistát vesz igénybe, például def f (x: Int) (y: Int). Több argumentumlista átadásával alkalmazzák, mint az f (5) (6).

A funkció lánc meghívásaként értékelik. Ezek a köztes függvények egyetlen argumentumot vesznek fel, és egy függvényt adnak vissza.

Részben megadhatunk argumentumlistákat is, mint pl f (5).

Most értsük meg ezt egy példával:

@Test def whenSumModCalledWith6And10_then10 = {// egy curried függvény def összeg (f: Int => Int) (a: Int, b: Int): Int = ha (a> b) 0 más (a + 1, b) // egy másik curried függvény def mod (n: Int) (x: Int) = x% n // curried függvény alkalmazása assertEquals (1, mod (5) (6)) // részleges A curried függvény alkalmazása // a visszahúzás aláhúzás szükséges ahhoz, hogy a függvénytípus explicit legyen val sumMod5 = sum (mod (5)) _ assertEquals (10, sumMod5 (6, 10))}

Felett, összeg és mod mindegyik vegyen két argumentumlistát.

A két argumentumlistát hasonlóan adjuk át mod (5) (6). Ezt két funkcióhívásként értékelik. Első, mod (5) kiértékelődik, amely egy függvényt ad vissza. Erre viszont érveléssel hivatkoznak 6. Ennek eredményeként 1-et kapunk.

Lehetséges a paraméterek részleges alkalmazása, mint a mod (5). Ennek eredményeként kapunk egy függvényt.

Hasonlóan a kifejezésben is összeg (mod (5)) _, csak az első érvet adjuk át összeg funkció. Ebből kifolyólag, sumMod5 egy függvény.

Az aláhúzás helyettesítőként szolgál az alkalmazatlan argumentumokban. Mivel a fordító nem következtethet arra, hogy függvénytípus várható, ezért a záró aláhúzást használjuk a függvény visszatérési típusának explicitvé tételéhez.

5.5. Névparaméterek

Egy függvény kétféle módon - érték és név szerint - alkalmazhatja a paramétereket, a híváskor csak egyszer értékeli a mellékérték argumentumokat. Ezzel szemben a név szerinti érveket értékeli, valahányszor hivatkoznak rájuk. Ha a melléknév argumentumot nem használják, akkor azt nem értékelik ki.

A Scala alapértelmezés szerint by-value paramétereket használ. Ha a paramétertípust nyíl (=>) előzi meg, akkor a név szerinti paraméterre vált.

Most használjuk a while ciklus megvalósításához:

def whileLoop (feltétel: => logikai érték) (body: => Unit): unit = if (feltétel) {body whileLoop (feltétel) (body)}

A fenti funkció megfelelő működéséhez mindkét paraméter feltétel és test minden alkalommal, amikor hivatkoznak rá, értékelni kell. Ezért névnév paraméterként definiáljuk őket.

6. Osztálymeghatározás

Osztályt határozunk meg a osztály kulcsszó, amelyet az osztály neve követ.

A név után, megadhatjuk az elsődleges konstruktor paramétereit. Ez automatikusan hozzáadja az azonos nevű tagokat az osztályhoz.

Az osztálytörzsben meghatározzuk a tagokat - értékeket, változókat, módszereket stb. Alapértelmezés szerint nyilvánosak, hacsak a magán vagy védett hozzáférés módosítók.

Használnunk kell a felülírja kulcsszó felülírhat egy módszert a szuperosztályból.

Határozzunk meg egy osztály alkalmazottat:

osztály Munkavállaló (név: String, változó fizetés: Int, évesInkrementum: Int = 20) {def incrementSalary (): Egység = {fizetés + = éves beszámítás} felülírja a def toString = s "Munkavállaló (név = $ név, fizetés = $ fizetés) ) "}

Itt három konstruktor paramétert adunk meg - név, fizetés, és évesInkrement.

Mivel kijelentjük név és fizetés val vel val és var kulcsszavak, a megfelelő tagok nyilvánosak. Másrészt nem használjuk val vagy var kulcsszó a évesInkrement paraméter. Ezért a levelező tag privát. Mivel ehhez a paraméternek alapértelmezett értéket adunk meg, elhagyhatjuk azt a konstruktor hívása közben.

A mezők mellett meghatározzuk a módszert inkrementSalary. Ez a módszer nyilvános.

Ezután írjunk egység tesztet ehhez az osztályhoz:

@Test def whenSalaryIncremented_thenCorrectSalary = {val alkalmazott = új alkalmazott ("John Doe", 1000) alkalmazott.incrementSalary () assertEquals (1020, alkalmazott.alga)}

6.1. Absztrakt osztály

A kulcsszót használjuk absztrakt hogy egy osztály elvont legyen. Hasonló a Java-hoz. Lehet minden tagja, aki egy rendes osztálynak lehet.

Ezenkívül tartalmazhat elvont tagokat. Ezek tagok igazságos nyilatkozattal és definíció nélkül, definíciójuk az alosztályban található.

A Java-hoz hasonlóan nem hozhatunk létre egy absztrakt osztály példányát.

Most illusztráljuk az absztrakt osztályt egy példával.

Először hozzunk létre egy absztrakt osztályt IntSet az egészek halmazának ábrázolásához:

IntSet absztrakt osztály {// adjon hozzá egy elemet a def halmazhoz, beleértve (x: Int): IntSet //, hogy egy elem tartozik-e a def halmazba, tartalmazza-e (x: Int): Boolean}

Ezután hozzunk létre egy konkrét alosztályt EmptyIntSet az üres halmaz ábrázolásához:

class EmptyIntSet kiterjeszti az IntSet-t {def tartalmazza (x: Int) = false def incl (x: Int) = new NonEmptyIntSet (x, this)}

Aztán egy újabb alosztály NonEmptyIntSet ábrázolják a nem üres halmazokat:

class NonEmptyIntSet (val fej: Int, val far: IntSet) kiterjeszti az IntSet {def tartalmazza (x: Int) = fej == x || (a farok x-et tartalmaz) def incl (x: Int) = if (ez x-et tartalmaz) {this} else {new NonEmptyIntSet (x, this)}}

Végül írjunk egy egység tesztet NonEmptySet:

@Test def megadottSetOf1To10_whenContains11Called_thenFalse = {// Állítson be egy 1 és 10 közötti egész számokat tartalmazó készletet. Val set1To10 = Tartomány (1, 10) .foldLeft (új EmptyIntSet (): IntSet) {(x, y) => x incl y} assertFalse (a set1To10 11-et tartalmaz)}

6.2. Vonások

A tulajdonságok a Java interfészeknek felelnek meg a következő különbségekkel:

  • osztálytól képes kiterjeszteni
  • hozzáférhet a szuperosztály tagjaihoz
  • lehetnek inicializáló utasítások

Az osztályokat definiáljuk, de az jellemvonás kulcsszó. Ezenkívül ugyanazok a tagok lehetnek, mint az absztrakt osztályok, kivéve a konstruktor paramétereit. Ezenkívül keverékként valamilyen más osztályba való felvételre szánják őket.

Most illusztráljuk a tulajdonságokat egy példával.

Először határozzunk meg egy tulajdonságot UpperCasePrinter hogy biztosítsák a Sztring A metódus nagybetűvel tér vissza:

vonás UpperCasePrinter {felülírja a def toString = super.toString toUpperCase parancsot}

Ezután teszteljük ezt a tulajdonságot hozzáadva egy Munkavállaló osztály:

@Test def megadottEmployeeWithTrait_whenToStringCalled_thenUpper = {val alkalmazott = új alkalmazott ("John Doe", 10), UpperCasePrinter assertEquals ("MUNKAVÁLLALÓ (Név = JOHN DOE, FÉLFEL = 10)", alkalmazott.toString)}

Az osztályok, objektumok és tulajdonságok legfeljebb egy osztályt örökölhetnek, de tetszőleges számú tulajdonságot.

7. Az objektum meghatározása

Az objektumok egy osztály példányai. Amint azt a korábbi példákban láthattuk, egy osztályból hozunk létre objektumokat a új kulcsszó.

Ha azonban egy osztálynak csak egy példánya lehet, akkor meg kell akadályoznunk több példány létrehozását. A Java-ban a Singleton mintát használjuk ennek elérésére.

Ilyen esetekben van egy tömör szintaxisunk, az úgynevezett objektumdefiníció - hasonlóan az osztálydefinícióhoz, egy különbséggel. Ahelyett, hogy a osztály kulcsszót használjuk tárgy kulcsszó. Ez meghatározza az osztályt, és lustán létrehozza az egyetlen példányát.

Az objektumdefiníciókat hasznossági módszerek és szingulettek végrehajtására használjuk.

Határozzuk meg a Hasznosságok tárgy:

object Utils {def átlag (x: dupla, y: dupla) = (x + y) / 2}

Itt definiáljuk az osztályt Hasznosságok és létrehozza egyetlen példányát is.

Erre az egyetlen példányra a nevét használva hivatkozunkHasznosságok. Ezt a példányt először hozzák létre.

Nem hozhatunk létre újabb segédprogramot a új kulcsszó.

Most írjunk egység tesztet a Hasznosságok tárgy:

assertEquals (15,0, Haszn. átlagos (10, 20), 1e-5)

7.1. Társobjektum és társosztály

Ha egy osztálynak és egy objektumdefiníciónak ugyanaz a neve, akkor társosztálynak, illetve társobjektumnak hívjuk őket. Meg kell határoznunk mindkettőt ugyanabban a fájlban. A kísérőobjektumok hozzáférhetnek a privát tagokhoz a társaiktól és fordítva.

A Java-val ellentétben nincsenek statikus tagjaink. Ehelyett társ objektumokat használunk a statikus tagok megvalósításához.

8. Mintaillesztés

A mintaillesztés egy kifejezést illeszt egy alternatívák sorozatához. Ezek mindegyike a kulcsszóval kezdődik ügy. Ezt követi a minta, az elválasztó nyíl (=>) és számos kifejezés. A kifejezés kiértékelésre kerül, ha a minta egyezik.

Mintákat készíthetünk:

  • esetosztály konstruktorok
  • változó minta
  • a helyettesítőminta _
  • literálok
  • állandó azonosítók

Az esetkategóriák megkönnyítik az objektumok mintaillesztését. Hozzátesszük ügy kulcsszó, miközben meghatároz egy osztályt, hogy eset-osztály legyen.

Így a Mintaillesztés sokkal erősebb, mint a Java kapcsoló utasítás. Emiatt széles körben használt nyelvi jellemző.

Most írjuk a Fibonacci metódust a mintaillesztés segítségével:

def fibonacci (n: Int): Int = n egyezés 1 => 1 eset x, ha x> 1 => fibonacci (x-1) + fibonacci (x-2) 

Ezután írjunk egységtesztet ehhez a módszerhez:

assertEquals (13, fibonacci (6))

9. Következtetés

Ebben az oktatóanyagban bemutattuk a Scala nyelvet és néhány legfontosabb jellemzőjét. Mint láttuk, kiváló támogatást nyújt az imperatív, funkcionális és objektum-orientált programozáshoz.

Szokás szerint a teljes forráskód megtalálható a GitHubon.


$config[zx-auto] not found$config[zx-overlay] not found