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.