Introduksjon til Scala

1. Introduksjon

I denne opplæringen skal vi se på Scala - et av de primære språkene som kjører på Java Virtual Machine.

Vi begynner med kjernespråkfunksjonene som verdier, variabler, metoder og kontrollstrukturer. Deretter vil vi utforske noen avanserte funksjoner som høyere ordensfunksjoner, karri, klasser, objekter og mønstermatching.

For å få en oversikt over JVM-språkene, sjekk ut vår hurtigguide til JVM-språk

2. Prosjektoppsett

I denne veiledningen bruker vi standard Scala-installasjon fra //www.scala-lang.org/download/.

For det første, la oss legge til avhengighet av scala-bibliotek i vår pom.xml. Denne gjenstanden gir standardbiblioteket for språket:

 org.scala-lang scala-library 2.12.7 

For det andre, la oss legge til scala-maven-plugin for å kompilere, teste, kjøre og dokumentere koden:

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

Maven har de nyeste gjenstandene for scala-lang og scala-maven-plugin.

Til slutt bruker vi JUnit til enhetstesting.

3. Grunnleggende funksjoner

I denne delen vil vi undersøke de grunnleggende språkfunksjonene gjennom eksempler. Vi bruker Scala-tolken til dette formålet.

3.1. Tolk

Tolken er et interaktivt skall for å skrive programmer og uttrykk.

La oss skrive ut "hei verden" ved å bruke den:

C: \> scala Velkommen til Scala 2.12.6 (Java HotSpot (TM) 64-biters server VM, Java 1.8.0_92). Skriv inn uttrykk for evaluering. Eller prøv: hjelp. scala> print ("Hello World!") Hello World! scala>

Ovenfor starter vi tolken med å skrive ‘scala’ på kommandolinjen. Tolken starter og viser en velkomstmelding etterfulgt av en melding.

Deretter skriver vi inn uttrykket vårt ved denne ledeteksten. Tolken leser uttrykket, evaluerer det og skriver ut resultatet. Deretter løkker den og viser meldingen igjen.

Siden det gir umiddelbar tilbakemelding, er tolk den enkleste måten å komme i gang med språket. La oss derfor bruke den til å utforske de grunnleggende språkfunksjonene: uttrykk og forskjellige definisjoner.

3.2. Uttrykkene

Enhver beregningsberetning er et uttrykk.

La oss skrive noen uttrykk og se resultatene:

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) "jevn" ellers "odd" res4: String = odd

Som vi kan se ovenfor, hvert uttrykk har en verdi og en type.

Hvis et uttrykk ikke har noe å returnere, returnerer det en verdi av typen Enhet. Denne typen har bare en verdi: (). Det ligner på tomrom nøkkelord i Java.

3.3. Verdidefinisjon

Nøkkelordet val brukes til å deklarere verdier.

Vi bruker den til å navngi resultatet av et uttrykk:

scala> val pi: Double = 3.14 pi: Double = 3.14 scala> print (pi) 3.14 

Ved å gjøre det kan vi bruke resultatet flere ganger.

Verdier er uforanderlige. Derfor kan vi ikke overføre dem til:

scala> pi = 3.1415: 12: feil: omplassering til val pi = 3.1415 ^

3.4. Variabel definisjon

Hvis vi trenger å tildele en verdi på nytt, erklærer vi den som en variabel i stedet.

Nøkkelordet var brukes til å erklære variabler:

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

3.5. Metodedefinisjon

Vi definerer metoder ved hjelp av def nøkkelord. Etter nøkkelordet spesifiserer vi metodens navn, parameterdeklarasjoner, en skilletegn (kolon) og returtype. Etter dette spesifiserer vi en skilletegn (=) etterfulgt av metodens kropp.

I motsetning til Java bruker vi ikke komme tilbake nøkkelord for å returnere resultatet. En metode returnerer verdien av det siste uttrykket som ble evaluert.

La oss skrive en metode gj.sn. for å beregne gjennomsnittet av to tall:

scala> def avg (x: Double, y: Double): Double = {(x + y) / 2} avg: (x: Double, y: Double) Double

La oss påkalle denne metoden:

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

Hvis en metode ikke tar noen parametere, kan vi utelate parentesene under definisjon og påkalling. I tillegg kan vi utelate selene hvis kroppen bare har ett uttrykk.

La oss skrive en parameterløs metode myntkast som tilfeldig returnerer "Head" eller "Tail":

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

La oss deretter påkalle denne metoden:

scala> println (coinToss) Hale scala> println (coinToss) Hode

4. Kontrollstrukturer

Kontrollstrukturer lar oss endre strømmen av kontroll i et program. Vi har følgende kontrollstrukturer:

  • Hvis annet uttrykk
  • Mens loop og gjør mens loop
  • For uttrykk
  • Prøv uttrykk
  • Match uttrykk

I motsetning til Java har vi ikke det Fortsette eller gå i stykker søkeord. Vi har komme tilbake nøkkelord. Vi bør imidlertid unngå å bruke den.

I stedet for bytte om uttalelse, vi har mønstermatching via kamputtrykk. I tillegg kan vi definere våre egne kontrollabstraksjoner.

4.1. hvis-annet

De hvis-annet uttrykk ligner Java. De ellers del er valgfri. Vi kan hekke flere uttrykk hvis annet.

Siden det er et uttrykk, returnerer det en verdi. Derfor bruker vi det på samme måte som den ternære operatøren (? :) i Java. Faktisk språket har ikke den ternære operatøren.

Bruk if-else, la oss skrive en metode for å beregne den største felles divisoren:

def gcd (x: Int, y: Int): Int = {if (y == 0) x else gcd (y, x% y)}

La oss deretter skrive en enhetstest for denne metoden:

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

4.2. Mens Loop

Mens løkken har en tilstand og en kropp. Det evaluerer kroppen gjentatte ganger i en løkke mens tilstanden er sann - tilstanden blir evaluert i begynnelsen av hver iterasjon.

Siden den ikke har noe nyttig å returnere, kommer den tilbake Enhet.

La oss bruke while-løkken til å skrive en metode for å beregne den største felles divisoren:

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}

La oss deretter bekrefte resultatet:

assertEquals (3, gcdIter (15, 27))

4.3. Gjør Mens Loop

Gjør mens sløyfen ligner på mens sløyfen bortsett fra at sløyfetilstanden blir evaluert på slutten av sløyfen.

La oss bruke do-while-sløyfen og skrive en metode for å beregne fakultet:

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

La oss deretter bekrefte resultatet:

assertEquals (720, factorial (6))

4.4. For uttrykk

For expression er mye mer allsidig enn for loop i Java.

Den kan gjentas over enkelt- eller flere samlinger. Videre kan den filtrere ut elementer og produsere nye samlinger.

Ved hjelp av for uttrykk, la oss skrive en metode for å oppsummere et utvalg av heltall:

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

Her, a til b er et generatoruttrykk. Det genererer en serie verdier fra en til b.

i <- a til b er en generator. Det definerer Jeg som val og tildeler den serien av verdier produsert av generatoruttrykket.

Kroppen utføres for hver verdi i serien.

La oss deretter bekrefte resultatet:

assertEquals (55, rangeSum (1, 10))

5. Funksjoner

Scala er et funksjonelt språk. Funksjoner er førsteklasses verdier her - vi kan bruke dem som alle andre verdityper.

I denne delen ser vi på noen avanserte konsepter relatert til funksjoner - lokale funksjoner, høyere ordensfunksjoner, anonyme funksjoner og karri.

5.1. Lokale funksjoner

Vi kan definere funksjoner i funksjoner. De blir referert til som nestede funksjoner eller lokale funksjoner. I likhet med de lokale variablene, de er bare synlige innenfor funksjonen de er definert i.

La oss nå skrive en metode for å beregne strøm ved hjelp av en nestet funksjon:

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

La oss deretter bekrefte resultatet:

assertEquals (8, power (2, 3))

5.2. Funksjoner med høyere ordre

Siden funksjoner er verdier, kan vi overføre dem som parametere til en annen funksjon. Vi kan også få en funksjon til å returnere en annen funksjon.

Vi refererer til funksjoner som fungerer på funksjoner som høyere ordensfunksjoner. De gjør det mulig for oss å jobbe på et mer abstrakt nivå. Ved å bruke dem kan vi redusere duplisering av kode ved å skrive generaliserte algoritmer.

La oss nå skrive en høyere ordensfunksjon for å utføre et kart og redusere operasjonen over en rekke heltall:

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} annet {iter (a + 1, r (m (a), resultat))}} iter (a, i)}

Her, r og m er parametere for Funksjon type. Ved å passere forskjellige funksjoner, kan vi løse en rekke problemer, for eksempel summen av firkanter eller terninger, og det faktiske.

La oss deretter bruke denne funksjonen til å skrive en annen funksjon sumSquares som summerer kvadratene av heltall:

@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, kvadrat, a, b) assertEquals (385, sumSquares (1, 10))}

Ovenfor kan vi se det høyere ordensfunksjoner har en tendens til å skape mange små engangsfunksjoner. Vi kan unngå å navngi dem ved å bruke anonyme funksjoner.

5.3. Anonyme funksjoner

En anonym funksjon er et uttrykk som evalueres til en funksjon. Det ligner på lambdauttrykket i Java.

La oss omskrive det forrige eksemplet ved hjelp av anonyme funksjoner:

@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 eksemplet, kart reduksjon mottar to anonyme funksjoner: (x, y) => x + y og x => x * x.

Scala kan utlede parametertypene fra konteksten. Derfor utelater vi typen parametere i disse funksjonene.

Dette resulterer i en mer kortfattet kode sammenlignet med forrige eksempel.

5.4. Currying Funksjoner

En curried-funksjon tar flere argumentlister, for eksempel def f (x: Int) (y: Int). Den brukes ved å sende flere argumentlister, som i f (5) (6).

Det blir evaluert som en påkallelse av en funksjonskjede. Disse mellomfunksjonene tar et enkelt argument og returnerer en funksjon.

Vi kan også delvis spesifisere argumentlister, for eksempel f (5).

La oss nå 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 annen curried-funksjon def mod (n: Int) (x: Int) = x% n // anvendelse av en curried-funksjon assertEquals (1, mod (5) (6)) // partial anvendelse av curried-funksjon // slepende understrek er nødvendig for // å gjøre funksjonstypen eksplisitt val sumMod5 = sum (mod (5)) _ assertEquals (10, sumMod5 (6, 10))}

Ovenfor, sum og mod tar hver to argumentlister.

Vi passerer de to argumentlistene som mod (5) (6). Dette blir evaluert som to funksjonsanrop. Først, mod (5) blir evaluert, som returnerer en funksjon. Dette blir igjen påberopt med argument 6. Vi får 1 som resultat.

Det er mulig å delvis bruke parametrene som i mod (5). Vi får en funksjon som et resultat.

Tilsvarende i uttrykket sum (mod (5)) _, vi gir bare det første argumentet til sum funksjon. Derfor, sumMod5 er en funksjon.

Understreken brukes som plassholder for ubrukte argumenter. Siden kompilatoren ikke kan utlede at det forventes en funksjonstype, bruker vi den etterfølgende understrekingen for å gjøre funksjonstypen tilbake eksplisitt.

5.5. By-Name Parameters

En funksjon kan bruke parametere på to forskjellige måter - etter verdi og etter navn - den evaluerer byverdiargumenter bare en gang på påkallingstidspunktet. I motsetning til det evaluerer den navnene på argumenter når de blir henvist. Hvis navnet på argumentet ikke brukes, blir det ikke evaluert.

Scala bruker standardverdiparametere som standard. Hvis parametertypen er foran med pil (=>), bytter den til parameter ved navn.

La oss nå bruke den til å implementere mens loop:

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

For at ovennevnte funksjon skal fungere riktig, begge parametere tilstand og kropp bør evalueres hver gang de blir henvist. Derfor definerer vi dem som parameternavn ved navn.

6. Klassedefinisjon

Vi definerer en klasse med klasse nøkkelord etterfulgt av navnet på klassen.

Etter navnet, vi kan spesifisere primære konstruktorparametere. Hvis du gjør det, legges medlemmene med samme navn automatisk til klassen.

I klassens kropp definerer vi medlemmene - verdier, variabler, metoder osv. De er offentlige som standard, med mindre de er endret av privat eller beskyttet tilgangsmodifikatorer.

Vi må bruke overstyring nøkkelord for å overstyre en metode fra superklassen.

La oss definere en klassemedarbeider:

klasse Ansatt (val navn: String, var lønn: Int, årligInkrement: Int = 20) {def inkrementSalary (): Enhet = {lønn + = årligIncrement} overstyr def tilString = s "Ansatt (navn = $ navn, lønn = $ lønn ) "}

Her spesifiserer vi tre konstruktorparametere - Navn, lønn, og årligIncrement.

Siden vi erklærer Navn og lønn med val og var søkeord, de tilsvarende medlemmene er offentlige. På den annen side bruker vi ikke val eller var nøkkelord for årligIncrement parameter. Derfor er det tilsvarende medlemmet privat. Når vi spesifiserer en standardverdi for denne parameteren, kan vi utelate den mens vi ringer til konstruktøren.

I tillegg til feltene definerer vi metoden inkrementSalary. Denne metoden er offentlig.

Deretter la oss skrive en enhetstest for denne klassen:

@Test def whenSalaryIncremented_thenCorrectSalary = {val medarbeider = ny ansatt ("John Doe", 1000) medarbeider.incrementSalary () assertEquals (1020, ansatt.lønn)}

6.1. Abstrakt klasse

Vi bruker nøkkelordet abstrakt å lage en klasse abstrakt. Det ligner på Java. Den kan ha alle medlemmene som en vanlig klasse kan ha.

Videre kan den inneholde abstrakte medlemmer. Dette er medlemmer med rettferdig erklæring og ingen definisjon, med definisjonen deres er gitt i underklassen.

På samme måte som Java kan vi ikke opprette en forekomst av en abstrakt klasse.

La oss nå illustrere den abstrakte klassen med et eksempel.

La oss først lage en abstrakt klasse IntSet å representere settet med heltall:

abstrakt klasse IntSet {// legg til et element i settet def inkl (x: Int): IntSet // om et element tilhører settet def inneholder (x: Int): Boolsk}

La oss deretter lage en konkret underklasse EmptyIntSet å representere det tomme settet:

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

Deretter en annen underklasse NonEmptyIntSet representerer de ikke-tomme settene:

klasse NonEmptyIntSet (val head: Int, val tail: IntSet) utvider IntSet {def inneholder (x: Int) = head == x || (halen inneholder x) def inkl (x: Int) = hvis (dette inneholder x) {dette} ellers {new NonEmptyIntSet (x, dette)}}

Til slutt, la oss skrive en enhetstest for NonEmptySet:

@Test def givenSetOf1To10_whenContains11Called_thenFalse = {// Sett opp et sett som inneholder heltall 1 til 10. val set1To10 = Range (1, 10) .foldLeft (new EmptyIntSet (): IntSet) {(x, y) => x incl y} assertFalse (set1To10 inneholder 11)}

6.2. Egenskaper

Egenskaper tilsvarer Java-grensesnitt med følgende forskjeller:

  • i stand til å strekke seg fra en klasse
  • har tilgang til superklassemedlemmer
  • kan ha initialiseringsuttalelser

Vi definerer dem slik vi definerer klasser, men bruker trekk nøkkelord. Dessuten kan de ha de samme medlemmene som abstrakte klasser, bortsett fra konstruktørparametere. Videre er de ment å bli lagt til noen annen klasse som en mixin.

La oss illustrere egenskaper ved hjelp av et eksempel.

La oss først definere et trekk UpperCasePrinter for å sikre toString metoden returnerer en verdi i store bokstaver:

trekk UpperCasePrinter {overstyre def toString = super.toString toUpperCase}

La oss så teste denne egenskapen ved å legge den til en Ansatt klasse:

@Test def givenEmployeeWithTrait_whenToStringCalled_thenUpper = {val medarbeider = ny ansatt ("John Doe", 10) med UpperCasePrinter assertEquals ("MEDARBEIDER (NAVN = JOHN DOE, LØNN = 10)", ansatt.toString)}

Klasser, objekter og egenskaper kan maksimalt arve en klasse, men et hvilket som helst antall egenskaper.

7. Objektdefinisjon

Objekter er forekomster av en klasse. Som vi har sett i tidligere eksempler, lager vi objekter fra en klasse ved hjelp av ny nøkkelord.

Imidlertid, hvis en klasse bare kan ha en forekomst, må vi forhindre at det opprettes flere forekomster. I Java bruker vi Singleton-mønsteret for å oppnå dette.

For slike tilfeller har vi en kortfattet syntaks kalt objektdefinisjon - lik klassedefinisjonen med en forskjell. I stedet for å bruke klasse nøkkelord, bruker vi gjenstand nøkkelord. Å gjøre dette definerer en klasse og skaper lat sitt eneste tilfelle.

Vi bruker objektdefinisjoner for å implementere verktøymetoder og singletoner.

La oss definere en Verktøy gjenstand:

objektverktøy {def gjennomsnitt (x: Dobbelt, y: Dobbelt) = (x + y) / 2}

Her definerer vi klassen Verktøy og oppretter også sin eneste forekomst.

Vi refererer til denne eneste forekomsten ved å bruke navnetVerktøy. Denne forekomsten opprettes første gang den er tilgjengelig.

Vi kan ikke opprette en annen forekomst av verktøy som bruker ny nøkkelord.

La oss nå skrive en enhetstest for Verktøy gjenstand:

assertEquals (15.0, Verktøy gjennomsnitt (10, 20), 1e-5)

7.1. Ledsagerobjekt og ledsagerklasse

Hvis en klasse og en objektdefinisjon har samme navn, kaller vi dem som henholdsvis ledsagerklasse og ledsagerobjekt. Vi må definere begge i samme fil. Ledsagerobjekter har tilgang til private medlemmer fra ledsagerklassen og omvendt.

I motsetning til Java, vi har ikke statiske medlemmer. I stedet bruker vi ledsagende objekter til å implementere statiske medlemmer.

8. Mønstermatching

Mønster matching samsvarer med et uttrykk til en sekvens av alternativer. Hver av disse begynner med nøkkelordet sak. Dette følges av et mønster, skillepil (=>) og et antall uttrykk. Uttrykket evalueres hvis mønsteret stemmer overens.

Vi kan bygge mønstre fra:

  • saksklassekonstruktører
  • variabelt mønster
  • jokertegnmønsteret _
  • bokstavelige
  • konstante identifikatorer

Saksklasser gjør det enkelt å gjøre mønstermatching på objekter. Vi legger til sak nøkkelord mens du definerer en klasse for å gjøre det til en saksklasse.

Dermed er mønstermatching mye kraftigere enn bryteruttalelsen i Java. Av denne grunn er det en mye brukt språkfunksjon.

La oss nå skrive Fibonacci-metoden ved hjelp av mønstermatching:

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

Deretter la oss skrive en enhetstest for denne metoden:

assertEquals (13, retracement (6))

9. Konklusjon

I denne opplæringen introduserte vi Scala-språket og noen av dets viktigste funksjoner. Som vi har sett, gir det utmerket støtte for tvingende, funksjonell og objektorientert programmering.

Som vanlig finner du full kildekode på GitHub.


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