Introduktion til Scala

1. Introduktion

I denne vejledning skal vi se på Scala - et af de primære sprog, der kører på Java Virtual Machine.

Vi begynder med de centrale sprogfunktioner såsom værdier, variabler, metoder og kontrolstrukturer. Derefter udforsker vi nogle avancerede funktioner såsom funktioner i højere ordre, curry, klasser, objekter og mønstermatchning.

For at få et overblik over JVM-sprogene, se vores hurtige guide til JVM-sprog

2. Opsætning af projekt

I denne vejledning bruger vi standard Scala-installationen fra //www.scala-lang.org/download/.

Lad os først tilføje scala-biblioteksafhængighed til vores pom.xml. Denne artefakt giver standardbiblioteket til sproget:

 org.scala-lang scala-bibliotek 2.12.7 

For det andet, lad os tilføje scala-maven-plugin til kompilering, test, kørsel og dokumentation af koden:

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

Maven har de nyeste artefakter til scala-lang og scala-maven-plugin.

Endelig bruger vi JUnit til enhedstest.

3. Grundlæggende funktioner

I dette afsnit undersøger vi de grundlæggende sprogfunktioner gennem eksempler. Vi bruger Scala-tolk til dette formål.

3.1. Tolk

Tolken er en interaktiv skal til skrivning af programmer og udtryk.

Lad os udskrive "hej verden" ved hjælp af det:

C: \> scala Velkommen til Scala 2.12.6 (Java HotSpot (TM) 64-bit Server VM, Java 1.8.0_92). Indtast udtryk til evaluering. Eller prøv: hjælp. scala> print ("Hello World!") Hello World! scala>

Ovenfor starter vi tolken ved at skrive 'scala' på kommandolinjen. Tolken starter og viser en velkomstmeddelelse efterfulgt af en prompt.

Derefter skriver vi vores udtryk ved denne prompt. Tolken læser udtrykket, evaluerer det og udskriver resultatet. Derefter sløjfer den og viser prompten igen.

Da det giver øjeblikkelig feedback, er tolken den nemmeste måde at komme i gang med sproget. Lad os derfor bruge det til at udforske de grundlæggende sprogfunktioner: udtryk og forskellige definitioner.

3.2. Udtryk

Enhver beregningspligtig erklæring er et udtryk.

Lad os skrive nogle udtryk og se deres resultater:

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 > hvis (11% 2 == 0) "lige" ellers "ulige" res4: String = ulige

Som vi kan se ovenfor, hvert udtryk har en værdi og en type.

Hvis et udtryk ikke har noget at returnere, returnerer det en værdi af typen Enhed. Denne type har kun en værdi: (). Det svarer til ugyldig nøgleord i Java.

3.3. Værdi Definition

Nøgleordet val bruges til at deklarere værdier.

Vi bruger det til at navngive resultatet af et udtryk:

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

Dette gør det muligt for os at genbruge resultatet flere gange.

Værdier er uforanderlige. Derfor kan vi ikke omfordele dem:

scala> pi = 3.1415: 12: fejl: omfordeling til val pi = 3.1415 ^

3.4. Variabel definition

Hvis vi har brug for at omfordele en værdi, erklærer vi den som en variabel i stedet.

Nøgleordet var bruges til at erklære variabler:

scala> var radius: Int = 3 radius: Int = 3

3.5. Metodedefinition

Vi definerer metoder ved hjælp af def nøgleord. Efter nøgleordet specificerer vi metodens navn, parameterdeklarationer, en separator (kolon) og returtype. Herefter specificerer vi en separator (=) efterfulgt af metodens krop.

I modsætning til Java bruger vi ikke Vend tilbage nøgleord for at returnere resultatet. En metode returnerer værdien af ​​det sidst evaluerede udtryk.

Lad os skrive en metode gns at beregne gennemsnittet af to tal:

scala> def gennemsnit (x: Dobbelt, y: Dobbelt): Dobbelt = {(x + y) / 2} gennemsnit: (x: Dobbelt, y: Dobbelt) Dobbelt

Lad os derefter påkalde denne metode:

scala> gennemsnit (10,20) res0: Dobbelt = 12,5 

Hvis en metode ikke tager nogen parametre, kan vi udelade parenteserne under definition og påkaldelse. Derudover kan vi udelade seler, hvis kroppen kun har et udtryk.

Lad os skrive en parameterløs metode coinToss som tilfældigt returnerer "Head" eller "Tail":

scala> def coinToss = if (Math.random> 0.5) "Head" ellers "Tail" coinToss: String

Lad os derefter påkalde denne metode:

scala> println (coinToss) Hale scala> println (coinToss) Hoved

4. Kontrolstrukturer

Kontrolstrukturer giver os mulighed for at ændre strømmen af ​​kontrol i et program. Vi har følgende kontrolstrukturer:

  • Hvis-ellers udtryk
  • Mens loop og gør mens loop
  • Til udtryk
  • Prøv udtryk
  • Match udtryk

I modsætning til Java har vi ikke Blive ved eller pause nøgleord. Vi har Vend tilbage nøgleord. Vi bør dog undgå at bruge det.

I stedet for kontakt erklæring, vi har mønstermatchning via matchudtryk. Derudover kan vi definere vores egne kontrolabstraktioner.

4.1. hvis ellers

Det hvis ellers udtryk ligner Java. Det andet del er valgfri. Vi kan rede flere hvis-ellers-udtryk.

Da det er et udtryk, returnerer det en værdi. Derfor bruger vi det svarende til den ternære operatør (? :) i Java. Faktisk sproget har ikke den ternære operatør.

Brug if-else, lad os skrive en metode til at beregne den største fælles divisor:

def gcd (x: Int, y: Int): Int = {hvis (y == 0) x ellers gcd (y, x% y)}

Lad os derefter skrive en enhedstest til denne metode:

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

4.2. Mens Loop

Mens løkken har en tilstand og en krop. Det evaluerer kroppen gentagne gange i en løkke, mens tilstanden er sand - tilstanden evalueres i begyndelsen af ​​hver iteration.

Da det ikke har noget nyttigt at vende tilbage, vender det tilbage Enhed.

Lad os bruge while-sløjfen til at skrive en metode til at beregne den største fælles divisor:

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

Lad os derefter kontrollere resultatet:

assertEquals (3, gcdIter (15, 27))

4.3. Gør Mens Loop

Do while-sløjfen svarer til while-sløjfen, bortset fra at looptilstanden evalueres i slutningen af ​​sløjfen.

Brug do-while-sløjfen til at skrive en metode til beregning af faktoria:

def factorial (a: Int): Int = {var result = 1 var i = 1 do {result * = i i = i + 1} while (i <= a) result}

Lad os derefter kontrollere resultatet:

assertEquals (720, factorial (6))

4.4. Til udtryk

For expression er meget mere alsidig end for loop i Java.

Det kan gentage sig over enkelte eller flere samlinger. Desuden kan den filtrere elementer ud samt producere nye samlinger.

Brug for til udtryk, lad os skrive en metode til at opsummere et interval af heltal:

def rangeSum (a: Int, b: Int) = {var sum = 0 for (i <- a til b) {sum + = i} sum}

Her, a til b er et generatorudtryk. Det genererer en række værdier fra -en til b.

i <- a til b er en generator. Det definerer jeg som val og tildeler den række af værdier produceret af generatoruttrykket.

Kroppen udføres for hver værdi i serien.

Lad os derefter kontrollere resultatet:

assertEquals (55, rangeSum (1, 10))

5. Funktioner

Scala er et funktionelt sprog. Funktioner er førsteklasses værdier her - vi kan bruge dem som enhver anden værditype.

I dette afsnit vil vi se på nogle avancerede koncepter relateret til funktioner - lokale funktioner, højere ordensfunktioner, anonyme funktioner og currying.

5.1. Lokale funktioner

Vi kan definere funktioner inde i funktioner. De kaldes indlejrede funktioner eller lokale funktioner. Svarende til de lokale variabler, de er kun synlige inden for den funktion, de er defineret i.

Lad os nu skrive en metode til beregning af strøm ved hjælp af en indlejret funktion:

def effekt (x: Int, y: Int): Int = {def powNested (i: Int, akkumulator: Int): Int = {hvis (i <= 0) akkumulator ellers powNested (i - 1, x * akkumulator)} powNested (y, 1)}

Lad os derefter kontrollere resultatet:

assertEquals (8, power (2, 3))

5.2. Funktioner med højere ordre

Da funktioner er værdier, kan vi videregive dem som parametre til en anden funktion. Vi kan også få en funktion til at returnere en anden funktion.

Vi henviser til funktioner, der fungerer på funktioner som højere ordensfunktioner. De sætter os i stand til at arbejde på et mere abstrakt niveau. Ved hjælp af dem kan vi reducere duplikering af kode ved at skrive generaliserede algoritmer.

Lad os nu skrive en højere ordensfunktion for at udføre et kort og reducere operationen over en række heltal:

def mapReduce (r: (Int, Int) => Int, i: Int, m: Int => Int, a: Int, b: Int) = {def iter (a: Int, resultat: Int): Int = { hvis (a> b) {resultat} andet {iter (a + 1, r (m (a), resultat))}} iter (a, i)}

Her, r og m er parametre for Fungere type. Ved at videregive forskellige funktioner kan vi løse en række problemer, såsom summen af ​​firkanter eller terninger og det faktuelle.

Lad os derefter bruge denne funktion til at skrive en anden funktion sumSquares der summerer kvadraterne for heltal:

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

Ovenfor kan vi se det højere ordensfunktioner har tendens til at skabe mange små funktioner til engangsbrug. Vi kan undgå at navngive dem ved hjælp af anonyme funktioner.

5.3. Anonyme funktioner

En anonym funktion er et udtryk, der evalueres til en funktion. Det svarer til lambda-udtrykket i Java.

Lad os omskrive det forrige eksempel ved hjælp af anonyme funktioner:

@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))}

I dette eksempel mapReduce modtager to anonyme funktioner: (x, y) => x + y og x => x * x.

Scala kan udlede parametertyperne fra konteksten. Derfor udelader vi typen af ​​parametre i disse funktioner.

Dette resulterer i en mere kortfattet kode sammenlignet med det foregående eksempel.

5.4. Currying-funktioner

En curried-funktion tager flere argumentlister, f.eks def f (x: Int) (y: Int). Det anvendes ved at sende flere argumentlister, som i f (5) (6).

Det evalueres som en påkaldelse af en funktionskæde. Disse mellemfunktioner tager et enkelt argument og returnerer en funktion.

Vi kan også delvist specificere argumentlister, såsom f (5).

Lad os nu forstå dette med et eksempel:

@Test def whenSumModCalledWith6And10_then10 = {// a curried function def sum (f: Int => Int) (a: Int, b: Int): Int = if (a> b) 0 else f (a) + sum (f) (a + 1, b) // en anden curried-funktion def mod (n: Int) (x: Int) = x% n // anvendelse af en curried-funktion assertEquals (1, mod (5) (6)) // partial anvendelse af curried-funktion // efterfølgende understregning kræves for at // gøre funktionstype eksplicit val sumMod5 = sum (mod (5)) _ assertEquals (10, sumMod5 (6, 10))}

Over, sum og mod hver tager to argumentlister.

Vi passerer de to argumenter lister som mod (5) (6). Dette evalueres som to funktionsopkald. Først, mod (5) evalueres, som returnerer en funktion. Dette til gengæld påberåbes med argument 6. Vi får 1 som resultat.

Det er muligt delvist at anvende parametrene som i mod (5). Vi får en funktion som et resultat.

Tilsvarende i udtrykket sum (mod (5)) _, vi sender kun det første argument til sum fungere. Derfor, sumMod5 er en funktion.

Understreget bruges som pladsholder til ikke anvendte argumenter. Da compileren ikke kan udlede, at der forventes en funktionstype, bruger vi den efterfølgende understregning til at gøre funktionsreturtypen eksplicit.

5.5. Parametre ved navn

En funktion kan anvende parametre på to forskellige måder - efter værdi og efter navn - den evaluerer kun byværdiargumenter en gang på indkaldelsestidspunktet. I modsætning hertil evaluerer det argumenter ved navn ved hver gang de henvises. Hvis argumentet ved navn ikke bruges, evalueres det ikke.

Scala bruger standardværdiparametre. Forud for parametertypen med pil (=>) skifter den til parameter ved navn.

Lad os nu bruge det til at implementere while-løkken:

def whileLoop (condition: => Boolean) (body: => Unit): Unit = if (condition) {body whileLoop (condition) (body)}

For at ovenstående funktion skal fungere korrekt, begge parametre tilstand og legeme skal evalueres hver gang de henvises. Derfor definerer vi dem som parametre ved navn.

6. Klassedefinition

Vi definerer en klasse med klasse nøgleord efterfulgt af navnet på klassen.

Efter navnet, Vi kan specificere primære konstruktorparametre. Dette tilføjer automatisk medlemmer med samme navn til klassen.

I klassekroppen definerer vi medlemmerne - værdier, variabler, metoder osv. De er offentlige som standard, medmindre de er ændret af privat eller beskyttet adgangsmodifikatorer.

Vi er nødt til at bruge tilsidesætte nøgleord for at tilsidesætte en metode fra superklassen.

Lad os definere en klassemedarbejder:

klasse Medarbejder (val navn: String, var løn: Int, årligIncrement: Int = 20) {def inkrementSalary (): Enhed = {løn + = årligIncrement} tilsidesætter def tilString = s "Medarbejder (navn = $ navn, løn = $ løn ) "}

Her specificerer vi tre konstruktorparametre - navn, lønog årligIncrement.

Da vi erklærer navn og løn med val og var nøgleord, de tilsvarende medlemmer er offentlige. På den anden side bruger vi ikke val eller var nøgleord til årligIncrement parameter. Derfor er det tilsvarende medlem privat. Da vi angiver en standardværdi for denne parameter, kan vi udelade den, mens vi kalder konstruktøren.

Ud over felterne definerer vi metoden inkrementSalary. Denne metode er offentlig.

Lad os derefter skrive en enhedstest til denne klasse:

@Test def whenSalaryIncremented_thenCorrectSalary = {val medarbejder = ny medarbejder ("John Doe", 1000) medarbejder.incrementSalary () assertEquals (1020, medarbejder.salar)}

6.1. Abstrakt klasse

Vi bruger nøgleordet abstrakt at gøre en klasse abstrakt. Det svarer til det i Java. Det kan have alle de medlemmer, som en almindelig klasse kan have.

Desuden kan den indeholde abstrakte medlemmer. Disse er medlemmer med retfærdig erklæring og ingen definition, med deres definition er angivet i underklassen.

På samme måde som Java kan vi ikke oprette en forekomst af en abstrakt klasse.

Lad os nu illustrere den abstrakte klasse med et eksempel.

Lad os først oprette en abstrakt klasse IntSet for at repræsentere sæt af heltal:

abstrakt klasse IntSet {// tilføj et element til sættet def inkl (x: Int): IntSet // om et element hører til sættet def indeholder (x: Int): Boolsk}

Lad os derefter oprette en konkret underklasse EmptyIntSet for at repræsentere det tomme sæt:

klasse EmptyIntSet udvider IntSet {def indeholder (x: Int) = falsk def inkl (x: Int) = ny NonEmptyIntSet (x, dette)}

Så en anden underklasse NonEmptyIntSet repræsenterer de ikke-tomme sæt:

klasse NonEmptyIntSet (val head: Int, val tail: IntSet) udvider IntSet {def indeholder (x: Int) = head == x || (hale indeholder x) def inkl (x: Int) = hvis (dette indeholder x) {dette} ellers {new NonEmptyIntSet (x, dette)}}

Lad os endelig skrive en enhedstest til NonEmptySet:

@Test def givenSetOf1To10_whenContains11Called_thenFalse = {// Opsæt et sæt, der indeholder heltal 1 til 10. val set1To10 = Range (1, 10) .foldLeft (new EmptyIntSet (): IntSet) {(x, y) => x incl y} assertFalse (set1To10 indeholder 11)}

6.2. Træk

Egenskaber svarer til Java-grænseflader med følgende forskelle:

  • stand til at strække sig fra en klasse
  • kan få adgang til superklassemedlemmer
  • kan have initialiseringserklæringer

Vi definerer dem, som vi definerer klasser, men bruger egenskab nøgleord. Desuden kan de have de samme medlemmer som abstrakte klasser undtagen konstruktorparametre. Desuden er de beregnet til at blive føjet til en anden klasse som en mixin.

Lad os nu illustrere træk ved hjælp af et eksempel.

Lad os først definere et træk UpperCasePrinter for at sikre toString metode returnerer en værdi i store bogstaver:

træk UpperCasePrinter {tilsidesætte def toString = super.toString toUpperCase}

Lad os derefter teste dette træk ved at føje det til et Medarbejder klasse:

@Test def givenEmployeeWithTrait_whenToStringCalled_thenUpper = {val medarbejder = ny medarbejder ("John Doe", 10) med UpperCasePrinter assertEquals ("MEDARBEJDER (NAVN = JOHN DOE, LØN = 10)", medarbejder.toString)}

Klasser, objekter og træk kan højst arve en klasse, men et hvilket som helst antal træk.

7. Objektdefinition

Objekter er forekomster af en klasse. Som vi har set i tidligere eksempler, opretter vi objekter fra en klasse ved hjælp af ny nøgleord.

Men hvis en klasse kun kan have en forekomst, er vi nødt til at forhindre oprettelsen af ​​flere forekomster. I Java bruger vi Singleton-mønsteret til at opnå dette.

I sådanne tilfælde har vi en kortfattet syntaks kaldet objektdefinition - svarende til klassedefinitionen med en forskel. I stedet for at bruge klasse nøgleord bruger vi objekt nøgleord. Dette definerer en klasse og skaber dovent sin eneste instans.

Vi bruger objektdefinitioner til at implementere hjælpemetoder og singletoner.

Lad os definere en Hjælpeprogrammer objekt:

objekt Utils {def gennemsnit (x: dobbelt, y: dobbelt) = (x + y) / 2}

Her definerer vi klassen Hjælpeprogrammer og også skabe sin eneste forekomst.

Vi henviser til denne eneste instans ved hjælp af dens navnHjælpeprogrammer. Denne forekomst oprettes første gang den er adgang.

Vi kan ikke oprette en anden forekomst af hjælpeprogrammer ved hjælp af ny nøgleord.

Lad os nu skrive en enhedstest til Hjælpeprogrammer objekt:

assertEquals (15.0, Hjælpeprogrammer gennemsnit (10, 20), 1e-5)

7.1. Companion Object og Companion Class

Hvis en klasse og en objektdefinition har samme navn, kalder vi dem som henholdsvis ledsagerklasse og ledsagende objekt. Vi skal definere begge i samme fil. Ledsagere kan få adgang til private medlemmer fra deres ledsagerklasse og omvendt.

I modsætning til Java, vi har ikke statiske medlemmer. I stedet bruger vi ledsagende objekter til at implementere statiske medlemmer.

8. Mønstertilpasning

Mønstertilpasning matcher et udtryk med en række af alternativer. Hver af disse begynder med nøgleordet sag. Dette efterfølges af et mønster, separatorpil (=>) og et antal udtryk. Udtrykket evalueres, hvis mønsteret matcher.

Vi kan bygge mønstre ud fra:

  • case klasse konstruktører
  • variabelt mønster
  • wildcard mønsteret _
  • bogstaver
  • konstante identifikatorer

Sagsklasser gør det nemt at foretage mønstermatchning på objekter. Vi tilføjer sag nøgleord, mens du definerer en klasse for at gøre det til en sagsklasse.

Således er mønstermatchning meget kraftigere end switch-sætningen i Java. Af denne grund er det en meget anvendt sprogfunktion.

Lad os nu skrive Fibonacci-metoden ved hjælp af mønstermatchning:

def retracement (n: Int): Int = n match 1 => 1 tilfælde x hvis x> 1 => Fibonacci (x-1) + retracement (x-2) 

Lad os derefter skrive en enhedstest til denne metode:

assertEquals (13, retracement (6))

9. Konklusion

I denne vejledning introducerede vi Scala-sproget og nogle af dets nøglefunktioner. Som vi har set, giver det fremragende support til tvingende, funktionel og objektorienteret programmering.

Som sædvanlig kan den fulde kildekode findes på GitHub.


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