Scala語言入門:初學(xué)者的基礎(chǔ)語法指南
在計(jì)算機(jī)編程的世界里,Scala是一個(gè)不可或缺的語言。
作為一種在Java虛擬機(jī)(JVM)上運(yùn)行的靜態(tài)類型編程語言,Scala結(jié)合了面向?qū)ο蠛秃瘮?shù)式編程的特性,使它既有強(qiáng)大的表達(dá)力又具備優(yōu)秀的型態(tài)控制。
對(duì)于初學(xué)者來說,理解Scala的基本語法是掌握這門語言的關(guān)鍵步驟。本文將帶領(lǐng)大家逐步了解Scala的基礎(chǔ)知識(shí),無論你是編程新手還是想要擴(kuò)展技能集的專業(yè)開發(fā)者,都可以在這篇文章中找到有用的信息。
先分享Scala的官方網(wǎng)站:https://docs.scala-lang.org/。
大部分的學(xué)習(xí)資料都可以在這找到,語言支持切換中文,非常友好。
另外我們可以使用Scastie網(wǎng)站,在瀏覽器上直接運(yùn)行Scala代碼進(jìn)行調(diào)試:https://scastie.scala-lang.org/。
Scala & Java
Scala語言和Java語言有許多相似之處,但也有一些明顯的區(qū)別。
Scala語言來源于Java,它以Java虛擬機(jī)(JVM)為運(yùn)行環(huán)境,Scala源碼 (.scala)會(huì)編譯成.class文件。這意味著Scala程序可以與Java程序互操作,并且可以利用JVM的優(yōu)化和性能。
在語法上,Scala和Java有一些區(qū)別。
例如,在Scala中,一切皆為對(duì)象,而在Java中,基本類型、null、靜態(tài)方法等不是對(duì)象。在Scala中,成員變量/屬性必須顯示初始化,而在Java中可以不初始化。此外,在Scala中,異常處理采用Try-catch {case-case}-finally的方式,而在Java中采用Try-catch-catch-finally的方式。
Scala還有一些特有的概念,例如:惰性函數(shù)、伴生對(duì)象、特質(zhì)、偏函數(shù)等。這些概念都為Scala語言提供了更多的靈活性和表達(dá)能力。使得Scala語言非常適合用來開發(fā)大數(shù)據(jù)處理框架。此外,Scala語言的語法糖也非常甜,可以用更少的代碼量來實(shí)現(xiàn)相同的功能。
Scala安裝
先從安裝Scala說起,Scala的安裝也很簡單。
(1) 首先Idea安裝 Scala插件。
(2) 項(xiàng)目結(jié)構(gòu)里點(diǎn)擊全局庫,添加 Scala SDK進(jìn)行下載。
(3) 右鍵點(diǎn)擊添加到你要使用Scala的項(xiàng)目的項(xiàng)目庫,項(xiàng)目的庫里就會(huì)多出Scala的SDK。
到這就結(jié)束了,然后我們就可以在項(xiàng)目里使用Scala了。
新建一個(gè)Scala項(xiàng)目,運(yùn)行Hello Wrold試一下。
數(shù)據(jù)類型
Scala中的數(shù)據(jù)類型可以分為兩大類:值類型(AnyVal)和引用類型(AnyRef)。這兩種類型都是 Any 類型的子類。
值類型包括9種基本數(shù)據(jù)類型,分別是 Byte、Short、Int、Long、Float、Double、Char、Boolean 和 Unit。其中,前8種類型與Java中的基本數(shù)據(jù)類型相對(duì)應(yīng),而 Unit 類型表示無值,類似于Java中的 void。
引用類型包括所有非值類型的數(shù)據(jù)類型,例如字符串、數(shù)組、列表等。它們都是 AnyRef 類型的子類。
在Scala的數(shù)據(jù)類型層級(jí)結(jié)構(gòu)的底部,還有兩個(gè)特殊的數(shù)據(jù)類型:Nothing 和 Null。其中, Nothing 類型是所有類型的子類型,它沒有任何實(shí)例。而 Null 類型是所有引用類型的子類型,它只有一個(gè)實(shí)例:null。
語法
主方法是一個(gè)程序的入口點(diǎn)。JVM要求一個(gè)名為main的主方法,接受一個(gè)字符串?dāng)?shù)組的參數(shù)。你可以如下所示來定義一個(gè)主方法。
object Main {
def main(args: Array[String]): Unit =
println("Hello, Scala developer!")
}
在Scala 2中,也可以通過創(chuàng)建一個(gè)擴(kuò)展App類的對(duì)象來定義主程序。例如:
object Main extends App {
println("Hello, Scala developer!")
}
需要注意的是,這種方法在Scala 3中不再推薦使用。它們被新的@main方法取代了,這是在Scala 3中生成可以從命令行調(diào)用的程序的推薦方法。App目前仍以有限的形式存在,但它不支持命令行參數(shù),將來會(huì)被棄用。
1.val & var
在 Scala 中,val 和 var 都可以用來定義變量,但它們之間有一些重要的區(qū)別。
val 用于定義不可變變量,也就是說,一旦定義了一個(gè) val 變量,它的值就不能再被改變。例如:
val x = 1
// x = 2 // 這會(huì)報(bào)錯(cuò),因?yàn)椴荒芙o val 變量重新賦值
而 var 用于定義可變變量,它的值可以在定義后被改變。例如:
var y = 1
y = 2 // 這是合法的,因?yàn)?y 是一個(gè) var 變量
val和var的類型可以被推斷,或者你也可以顯式地聲明類型,例如:
val x: Int = 1 + 1
var x: Int = 1 + 1
在實(shí)際編程中,我們應(yīng)該盡量使用 val 來定義不可變變量,這樣可以提高代碼的可讀性和可維護(hù)性。只有在確實(shí)需要改變變量值的情況下,才應(yīng)該使用 var 來定義可變變量。
2.泛型
在Scala 中,使用方括號(hào) [] 來定義泛型類型。而在Java中是使用<>。
例如,下面這段代碼:
object Main extends App {
trait Animal {
def speak: String
}
class Dog extends Animal {
def speak = "Woof!"
}
class Cat extends Animal {
def speak = "Meow!"
}
class Parrot extends Animal {
def speak = "Squawk!"
}
class AnimalShelter[A <: Animal] {
private var animals: List[A] = Nil
def addAnimal(animal: A): Unit = {
animals = animal :: animals
}
def getAnimal: A = {
val animal = animals.head
animals = animals.tail
animal
}
}
val dogShelter = new AnimalShelter[Dog]
dogShelter.addAnimal(new Dog)
val dog: Dog = dogShelter.getAnimal
println(dog.speak)
val catShelter = new AnimalShelter[Cat]
catShelter.addAnimal(new Cat)
val cat: Cat = catShelter.getAnimal
println(cat.speak)
val parrotShelter = new AnimalShelter[Parrot]
parrotShelter.addAnimal(new Parrot)
val parrot: Parrot = parrotShelter.getAnimal
println(parrot.speak)
}
輸出:
Woof!
Meow!
Squawk!
這個(gè)示例中,我們定義了一個(gè) Animal 特質(zhì)和三個(gè)實(shí)現(xiàn)了該特質(zhì)的類:Dog,Cat 和 Parrot。然后我們定義了一個(gè) AnimalShelter 類,它使用了泛型類型參數(shù) A,并且限制了 A 必須是 Animal 的子類型。這樣我們就可以創(chuàng)建不同類型的動(dòng)物收容所,比如 dogShelter,catShelter 和 parrotShelter,并且在添加和獲取動(dòng)物時(shí)保證類型安全。
3.包導(dǎo)入
import 語句用于導(dǎo)入其他包中的成員(類,特質(zhì),函數(shù)等)。使用相同包的成員不需要 import 語句。導(dǎo)入語句可以有選擇性:
import users._ // 導(dǎo)入包 users 中的所有成員
import users.User // 導(dǎo)入類 User
import users.{User, UserPreferences} // 僅導(dǎo)入選擇的成員
import users.{UserPreferences => UPrefs} // 導(dǎo)入類并且設(shè)置別名
Scala 不同于 Java 的一點(diǎn)是 Scala 可以在任何地方使用導(dǎo)入:
def sqrtplus1(x: Int) = {
import scala.math.sqrt
sqrt(x) + 1.0
}
如果存在命名沖突并且你需要從項(xiàng)目的根目錄導(dǎo)入,請(qǐng)?jiān)诎Q前加上 root:
package accounts
import _root_.users._
注意:包 scala 和 java.lang 以及 object Predef 是默認(rèn)導(dǎo)入的。
4.包對(duì)象
在 Scala 中,包對(duì)象(Package Object)是一種特殊的對(duì)象,它與包同名,并且可以在包中定義一些公共的成員和方法,供包中的其他類和對(duì)象直接使用。包對(duì)象可以解決在包級(jí)別共享常量、類型別名、隱式轉(zhuǎn)換等問題。
在 Scala 中,可以使用 package 關(guān)鍵字定義一個(gè)包對(duì)象。包對(duì)象的文件名必須為 package.scala,并與包名一致。
下面是關(guān)于包對(duì)象的解釋和示例代碼:
// File: com/example/myapp/package.scala
package com.example
package object myapp {
val appName: String = "MyApp"
def printAppName(): Unit = {
println(appName)
}
}
在上述示例中,定義了一個(gè)包對(duì)象 myapp,位于包 com.example 下。在包對(duì)象中,我們定義了一個(gè)名為 appName 的常量和一個(gè)名為 printAppName 的方法。
這樣,我們就可以在包中的其他類和對(duì)象中直接使用 appName 和 printAppName,而無需導(dǎo)入或限定符。
下面是一個(gè)使用包對(duì)象的示例代碼:
package com.example.myapp
object Main {
def main(args: Array[String]): Unit = {
println(myapp.appName) // 直接訪問包對(duì)象中的常量
myapp.printAppName() // 直接調(diào)用包對(duì)象中的方法
}
}
在上述示例中,我們?cè)?nbsp;Main 對(duì)象中直接訪問了包對(duì)象 myapp 中的常量 appName 和方法 printAppName。由于包對(duì)象與包同名且位于同一包中,因此可以直接使用它們。
5.特質(zhì)
在Scala中,類是單繼承的,但是特質(zhì)(trait)可以多繼承。
這意味著,一個(gè)類只能繼承一個(gè)父類,但可以繼承多個(gè)特質(zhì)。這樣,從結(jié)果上看,就實(shí)現(xiàn)了多重繼承。
下面是一個(gè)例子:
trait A {
def printA() = println("A")
}
trait B {
def printB() = println("B")
}
class C extends A with B
object Main extends App {
val c = new C
c.printA()
c.printB()
}
輸出:
A
B
例子中,定義了兩個(gè)特質(zhì) A 和 B,它們分別有一個(gè)方法 printA 和 printB。然后我們定義了一個(gè)類 C,它繼承了特質(zhì) A 和 B。這樣,類 C 就可以使用特質(zhì) A 和 B 中定義的方法了。
特質(zhì)也可以有默認(rèn)的實(shí)現(xiàn):
trait Greeter {
def greet(name: String): Unit =
println("Hello, " + name + "!")
}
你可以使用extends關(guān)鍵字來繼承特質(zhì),使用override關(guān)鍵字來覆蓋默認(rèn)的實(shí)現(xiàn)。
class DefaultGreeter extends Greeter
class CustomizableGreeter(prefix: String, postfix: String) extends Greeter {
override def greet(name: String): Unit = {
println(prefix + name + postfix)
}
}
val greeter = new DefaultGreeter()
greeter.greet("Scala developer") // Hello, Scala developer!
val customGreeter = new CustomizableGreeter("How are you, ", "?")
customGreeter.greet("Scala developer") // How are you, Scala developer?
凡是需要特質(zhì)的地方,都可以由該特質(zhì)的子類型來替換。
import scala.collection.mutable.ArrayBuffer
trait Pet {
val name: String
}
class Cat(val name: String) extends Pet
class Dog(val name: String) extends Pet
val dog = new Dog("Harry")
val cat = new Cat("Sally")
val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name)) // Prints Harry Sally
在這里 trait Pet 有一個(gè)抽象字段 name ,name 由Cat和Dog的構(gòu)造函數(shù)中實(shí)現(xiàn)。最后一行,我們能調(diào)用pet.name的前提是它必須在特質(zhì)Pet的子類型中得到了實(shí)現(xiàn)。
6.運(yùn)算符
在 Scala 中,運(yùn)算符是用于執(zhí)行特定操作的符號(hào)或標(biāo)記。Scala 具有豐富的運(yùn)算符,并且允許用戶自定義運(yùn)算符,以及在自定義類中使用運(yùn)算符。下面是關(guān)于定義和使用運(yùn)算符的解釋和示例代碼:
在 Scala 中,可以使用 def 關(guān)鍵字定義自定義運(yùn)算符。自定義運(yùn)算符可以是任何由字母、數(shù)字或下劃線組成的標(biāo)識(shí)符,以及一些特殊字符,例如 +、-、* 等。要定義一個(gè)運(yùn)算符,可以在方法名前面加上一個(gè)操作符,然后在方法體中實(shí)現(xiàn)相應(yīng)的邏輯。
下面是一個(gè)示例代碼:
class Vector2D(val x: Double, val y: Double) {
def +(other: Vector2D): Vector2D = {
new Vector2D(x + other.x, y + other.y)
}
}
val v1 = new Vector2D(1.0, 2.0)
val v2 = new Vector2D(3.0, 4.0)
val sum = v1 + v2
println(sum.x) // 輸出:4.0
println(sum.y) // 輸出:6.0
在上述示例中,定義了一個(gè) Vector2D 類,表示二維向量。我們通過 val 關(guān)鍵字定義了 x 和 y 作為向量的坐標(biāo)。
然后,我們定義了一個(gè)自定義運(yùn)算符 +,它接受另一個(gè) Vector2D 對(duì)象作為參數(shù),并返回一個(gè)新的 Vector2D 對(duì)象。在方法體內(nèi),我們實(shí)現(xiàn)了向量的加法操作。
在主程序中,我們創(chuàng)建了兩個(gè) Vector2D 對(duì)象 v1 和 v2。然后,我們使用自定義的運(yùn)算符 + 來執(zhí)行向量的加法,并將結(jié)果賦值給 sum。
最后,我們打印出 sum 的 x 和 y 坐標(biāo),驗(yàn)證加法操作的結(jié)果。
我們可以像使用內(nèi)置運(yùn)算符一樣使用自定義運(yùn)算符。它們可以用于相應(yīng)類型的實(shí)例上,并按照定義的邏輯執(zhí)行操作。
下面是一個(gè)示例代碼:
val num1 = 10
val num2 = 5
val sum = num1 + num2
val difference = num1 - num2
val product = num1 * num2
println(sum) // 輸出:15
println(difference) // 輸出:5
println(product) // 輸出:50
在上述示例中,我們定義了兩個(gè)整數(shù)變量 num1 和 num2。然后,我們使用內(nèi)置的運(yùn)算符 +、- 和 * 來執(zhí)行加法、減法和乘法操作,并將結(jié)果分別賦值給 sum、difference 和 product。
7.傳名參數(shù)
傳名參數(shù)(Call-by-Name Parameters)是一種特殊的參數(shù)傳遞方式,它允許我們將表達(dá)式作為參數(shù)傳遞給函數(shù),并在需要時(shí)進(jìn)行求值。傳名參數(shù)使用 => 符號(hào)來定義,以表示傳遞的是一個(gè)表達(dá)式而不是具體的值。
傳名參數(shù)的特點(diǎn)是,在每次使用參數(shù)時(shí)都會(huì)重新求值表達(dá)式,而不是在調(diào)用函數(shù)時(shí)進(jìn)行求值。這樣可以延遲表達(dá)式的求值,只在需要時(shí)才進(jìn)行計(jì)算。傳名參數(shù)通常用于需要延遲計(jì)算、惰性求值或者需要按需執(zhí)行的場(chǎng)景。
下面是一個(gè)示例代碼:
def callByName(param: => Int): Unit = {
println("Inside callByName")
println("Param 1: " + param)
println("Param 2: " + param)
}
def randomNumber(): Int = {
println("Generating random number")
scala.util.Random.nextInt(100)
}
callByName(randomNumber())
輸出:
Inside callByName
Generating random number
Param 1: 53
Generating random number
Param 2: 87
在上述示例中,定義了一個(gè)名為 callByName 的函數(shù),它接受一個(gè)傳名參數(shù) param。在函數(shù)體內(nèi),我們打印出兩次參數(shù)的值。
另外,定義了一個(gè)名為 randomNumber 的函數(shù),它用于生成隨機(jī)數(shù)。在該函數(shù)內(nèi)部,我們打印出生成隨機(jī)數(shù)的消息,并使用 scala.util.Random.nextInt 方法生成一個(gè)介于 0 到 100 之間的隨機(jī)數(shù)。
在主程序中,我們調(diào)用 callByName 函數(shù),并將 randomNumber() 作為傳名參數(shù)傳遞進(jìn)去。
當(dāng)程序執(zhí)行時(shí),會(huì)先打印出 "Inside callByName" 的消息,然后兩次調(diào)用 param,即 randomNumber()。在每次調(diào)用時(shí),都會(huì)重新生成一個(gè)新的隨機(jī)數(shù),并打印出相應(yīng)的值。
這說明傳名參數(shù)在每次使用時(shí)都會(huì)重新求值表達(dá)式,而不是在調(diào)用函數(shù)時(shí)進(jìn)行求值。這樣可以實(shí)現(xiàn)按需執(zhí)行和延遲計(jì)算的效果。
8.implicit
implicit 關(guān)鍵字用于定義隱式轉(zhuǎn)換和隱式參數(shù)。它可以用來簡化代碼,讓編譯器自動(dòng)執(zhí)行一些操作。
下面是一些使用 implicit 關(guān)鍵字的示例:
(1) 隱式轉(zhuǎn)換:可以使用 implicit 關(guān)鍵字定義隱式轉(zhuǎn)換函數(shù),讓編譯器自動(dòng)將一種類型的值轉(zhuǎn)換為另一種類型的值。
implicit def intToString(x: Int): String = x.toString
val x: String = 1
println(x) // 輸出 "1"ndom number
Param 2: 87
在這個(gè)例子中,定義了一個(gè)隱式轉(zhuǎn)換函數(shù) intToString,它接受一個(gè) Int 類型的參數(shù),并返回它的字符串表示。由于這個(gè)函數(shù)被定義為 implicit,因此編譯器會(huì)在需要時(shí)自動(dòng)調(diào)用它。
在主程序中,我們將一個(gè) Int 類型的值賦值給一個(gè) String 類型的變量。由于類型不匹配,編譯器會(huì)嘗試尋找一個(gè)隱式轉(zhuǎn)換函數(shù)來將 Int 類型的值轉(zhuǎn)換為 String 類型的值。在這個(gè)例子中,編譯器找到了我們定義的 intToString 函數(shù),并自動(dòng)調(diào)用它將 1 轉(zhuǎn)換為 "1"。
(2) 隱式參數(shù):可以使用 implicit 關(guān)鍵字定義隱式參數(shù),讓編譯器自動(dòng)為方法提供參數(shù)值。
implicit val x: Int = 1
def foo(implicit x: Int): Unit = println(x)
foo // 輸出 1
在這個(gè)例子中,定義了一個(gè)隱式值 x 并賦值為 1。然后我們定義了一個(gè)方法 foo,它接受一個(gè)隱式參數(shù) x。
在主程序中,我們調(diào)用了方法 foo,但沒有顯式地傳入?yún)?shù)。由于方法 foo 接受一個(gè)隱式參數(shù),因此編譯器會(huì)嘗試尋找一個(gè)隱式值來作為參數(shù)傳入。在這個(gè)例子中,編譯器找到了我們定義的隱式值 x 并將其作為參數(shù)傳入方法 foo。
9.Object & Class
在Scala中,class 和 object 都可以用來定義類型,但它們之間有一些重要的區(qū)別。class 定義了一個(gè)類,它可以被實(shí)例化。每次使用 new 關(guān)鍵字創(chuàng)建一個(gè)類的實(shí)例時(shí),都會(huì)創(chuàng)建一個(gè)新的對(duì)象。
class MyClass(x: Int) {
def printX(): Unit = println(x)
}
val a = new MyClass(1)
val b = new MyClass(2)
a.printX() // 輸出 1
b.printX() // 輸出 2
構(gòu)造器可以通過提供一個(gè)默認(rèn)值來擁有可選參數(shù):
class Point(var x: Int = 0, var y: Int = 0)
val origin = new Point // x and y are both set to 0
val point1 = new Point(1)
println(point1.x) // prints 1
在這個(gè)版本的Point類中,x和y擁有默認(rèn)值0所以沒有必傳參數(shù)。然而,因?yàn)闃?gòu)造器是從左往右讀取參數(shù),所以如果僅僅要傳個(gè)y的值,你需要帶名傳參。
class Point(var x: Int = 0, var y: Int = 0)
val point2 = new Point(y=2)
println(point2.y) // prints 2
而 object 定義了一個(gè)單例對(duì)象。它不能被實(shí)例化,也不需要使用 new 關(guān)鍵字創(chuàng)建。在程序中,一個(gè) object 只有一個(gè)實(shí)例。此外,object 中定義的成員都是靜態(tài)的,這意味著它們可以在不創(chuàng)建實(shí)例的情況下直接訪問。而 class 中定義的成員只能在創(chuàng)建實(shí)例后訪問。
object MyObject {
val x = 1
def printX(): Unit = println(x)
}
MyObject.printX() // 輸出 1
另外,在Scala中,如果一個(gè) object 的名稱與一個(gè) class 的名稱相同,那么這個(gè) object 被稱為這個(gè) class 的伴生對(duì)象。伴生對(duì)象和類可以相互訪問彼此的私有成員:
class MyClass(x: Int) {
private val secret = 42
def printCompanionSecret(): Unit = println(MyClass.companionSecret)
}
object MyClass {
private val companionSecret = 24
def printSecret(c: MyClass): Unit = println(c.secret)
}
val a = new MyClass(1)
a.printCompanionSecret() // 輸出 24
MyClass.printSecret(a) // 輸出 42
在這個(gè)例子中,定義了一個(gè)類 MyClass 和它的伴生對(duì)象 MyClass。類 MyClass 中定義了一個(gè)私有成員變量 secret 和一個(gè)方法 printCompanionSecret,用于打印伴生對(duì)象中的私有成員變量 companionSecret。而伴生對(duì)象 MyClass 中定義了一個(gè)私有成員變量 companionSecret 和一個(gè)方法 printSecret,用于打印類 MyClass 的實(shí)例中的私有成員變量 secret。
在主程序中,創(chuàng)建了一個(gè)類 MyClass 的實(shí)例 a,并調(diào)用了它的 printCompanionSecret 方法。然后我們調(diào)用了伴生對(duì)象 MyClass 的 printSecret 方法,并將實(shí)例 a 作為參數(shù)傳入。
這就是Scala中類和伴生對(duì)象之間互相訪問私有成員的基本用法。
10.樣例類
樣例類(case class)是一種特殊的類,**常用于描述不可變的值對(duì)象(Value Object) **。
它們非常適合用于不可變的數(shù)據(jù)。定義一個(gè)樣例類非常簡單,只需在類定義前加上case關(guān)鍵字即可。例如,下面是一個(gè)簡單的樣例類定義:
case class Person(var name: String, var age: Int)
創(chuàng)建樣例類的實(shí)例時(shí),不需要使用new關(guān)鍵字,直接使用類名即可。例如,下面是一個(gè)創(chuàng)建樣例類實(shí)例并修改其成員變量的示例:
object Test01 {
case class Person(var name: String, var age: Int)
def main(args: Array[String]): Unit = {
val z = Person("張三", 20)
z.age = 23
println(s"z = $z")
}
}
11._(下劃線)
在Scala中,下劃線 _ 是一個(gè)特殊的符號(hào),它可以用在許多不同的地方,具有不同的含義。
- 作為通配符:下劃線可以用作通配符,表示匹配任意值。例如,在模式匹配中,可以使用下劃線來表示匹配任意值。
x match {
case 1 => "one"
case 2 => "two"
case _ => "other"
}
- 作為忽略符:下劃線也可以用來忽略不需要的值。例如,在解構(gòu)賦值時(shí),可以使用下劃線來忽略不需要的值。
val (x, _, z) = (1, 2, 3)
- 作為函數(shù)參數(shù)占位符:下劃線還可以用作函數(shù)參數(shù)的占位符,表示一個(gè)匿名函數(shù)的參數(shù)。例如,在調(diào)用高階函數(shù)時(shí),可以使用下劃線來簡化匿名函數(shù)的定義。
val list = List(1, 2, 3)
list.map(_ * 2)
- 將方法轉(zhuǎn)換為函數(shù):在方法名稱后加一個(gè)下劃線,會(huì)將其轉(zhuǎn)化為偏應(yīng)用函數(shù)(partially applied function),就能直接賦值了。
def add(x: Int, y: Int) = x + y
val f = add _
這只是下劃線在Scala中的一些常見用法。由于下劃線在不同的上下文中具有不同的含義,因此在使用時(shí)需要根據(jù)具體情況進(jìn)行判斷。
12.println
println 函數(shù)用于向標(biāo)準(zhǔn)輸出打印一行文本。它可以接受多種不同類型的參數(shù),并將它們轉(zhuǎn)換為字符串進(jìn)行輸出。
下面是一些常見的使用 println 函數(shù)進(jìn)行輸出的方式:
- 輸出字符串:直接將字符串作為參數(shù)傳入 println 函數(shù),它會(huì)將字符串原樣輸出。
println("Hello, world!")
- 輸出變量:將變量作為參數(shù)傳入 println 函數(shù),它會(huì)將變量的值轉(zhuǎn)換為字符串并輸出。
val x = 1
println(x)
- 輸出表達(dá)式:將表達(dá)式作為參數(shù)傳入 println 函數(shù),它會(huì)計(jì)算表達(dá)式的值并將其轉(zhuǎn)換為字符串輸出。
val x = 1
val y = 2
println(x + y)
- 使用字符串插值:可以使用字符串插值來格式化輸出。在字符串前加上 s 前綴,然后在字符串中使用 ${expression} 的形式來插入表達(dá)式的值。
val name = "Alice"
val age = 18
println(s"My name is $name and I am $age years old.")
這些是 println 函數(shù)的一些常見用法。你可以根據(jù)需要使用不同的方式來格式化輸出。
13.集合
在Scala中,集合有三大類:序列Seq、集Set、映射Map,所有的集合都擴(kuò)展自Iterable,所以Scala中的集合都可以使用 foreach方法。在Scala中集合有可變(mutable)和不可變(immutable)兩種類型。
14.List
如我們可以使用如下方式定義一個(gè)List,其他集合類型的定義方式也差不多。
object Main {
def main(args: Array[String]): Unit = {
// 定義一個(gè)空的字符串列表
var emptyList: List[String] = List()
// 定義一個(gè)具有數(shù)據(jù)的列表
var intList = List(1, 2, 3, 4, 5, 6)
// 定義空列表
var emptyList2 = Nil
// 使用::運(yùn)算符連接元素
var numList = 1 :: (2 :: (3 :: Nil))
println(emptyList)
println(intList)
println(emptyList2)
println(numList)
}
}
輸出:
List()
List(1, 2, 3, 4, 5, 6)
List()
List(1, 2, 3)
下面是一些List的常用方法:
val list = List(1, 2, 3, 4)
// 獲取列表的長度
val length = list.length
// 獲取列表的第一個(gè)元素
val first = list.head
// 獲取列表的最后一個(gè)元素
val last = list.last
// 獲取列表除第一個(gè)元素外剩余的元素
val tail = list.tail
// 獲取列表除最后一個(gè)元素外剩余的元素
val init = list.init
// 反轉(zhuǎn)列表
val reversed = list.reverse
// 在列表頭部添加元素
val newList1 = 0 +: list
// 在列表尾部添加元素
val newList2 = list :+ 5
// 連接兩個(gè)列表
val list1 = List(1, 2)
val list2 = List(3, 4)
val concatenatedList = list1 ++ list2
// 檢查列表是否為空
val isEmpty = list.isEmpty
// 檢查列表是否包含某個(gè)元素
val containsElement = list.contains(1)
// 過濾列表中的元素
val filteredList = list.filter(_ > 2)
// 映射列表中的元素
val mappedList = list.map(_ * 2)
// 折疊列表中的元素(從左到右)
val sum1 = list.foldLeft(0)(_ + _)
// 折疊列表中的元素(從右到左)
val sum2 = list.foldRight(0)(_ + _)
// 拉鏈操作
val names = List("Alice", "Bob", "Charlie")
val ages = List(25, 32, 29)
val zipped = names.zip(ages) // List(("Alice", 25), ("Bob", 32), ("Charlie", 29))
// 拉鏈操作后解壓縮
val (unzippedNames, unzippedAges) = zipped.unzip // (List("Alice", "Bob", "Charlie"), List(25, 32, 29))
更多方法不再贅述,網(wǎng)上很容易查閱到相關(guān)文章。
15.Map
object Main {
def main(args: Array[String]): Unit = {
// 定義一個(gè)空的映射
val emptyMap = Map()
// 定義一個(gè)具有數(shù)據(jù)的映射
val intMap = Map("key1" -> 1, "key2" -> 2)
// 使用元組定義一個(gè)映射
val tupleMap = Map(("key1", 1), ("key2", 2))
println(emptyMap)
println(intMap)
println(tupleMap)
}
}
輸出:
Map()
Map(key1 -> 1, key2 -> 2)
Map(key1 -> 1, key2 -> 2)
下面是map常用的一些方法:
val map = Map("key1" -> 1, "key2" -> 2)
// 獲取映射的大小
val size = map.size
// 獲取映射中的所有鍵
val keys = map.keys
// 獲取映射中的所有值
val values = map.values
// 檢查映射是否為空
val isEmpty = map.isEmpty
// 檢查映射是否包含某個(gè)鍵
val containsKey = map.contains("key1")
// 獲取映射中某個(gè)鍵對(duì)應(yīng)的值
val value = map("key1")
// 獲取映射中某個(gè)鍵對(duì)應(yīng)的值,如果不存在則返回默認(rèn)值
val valueOrDefault = map.getOrElse("key3", 0)
// 過濾映射中的元素
val filteredMap = map.filter { case (k, v) => v > 1 }
// 映射映射中的元素
val mappedMap = map.map { case (k, v) => (k, v * 2) }
// 遍歷映射中的元素
map.foreach { case (k, v) => println(s"key: $k, value: $v") }
這里的case關(guān)鍵字起到匹配的作用。
16.Range
Range屬于序列(Seq)這一類集合的子集。它表示一個(gè)整數(shù)序列,可以用來遍歷一個(gè)整數(shù)區(qū)間內(nèi)的所有整數(shù)。例如,1 to 5表示一個(gè)從1到5的整數(shù)序列,包括1和5。
Range常見于for循環(huán)中,如下可定義一個(gè)Range:
// 定義一個(gè)從1到5的整數(shù)序列,包括1和5
val range1 = 1 to 5
// 定義一個(gè)從1到5的整數(shù)序列,包括1但不包括5
val range2 = 1 until 5
// 定義一個(gè)從1到10的整數(shù)序列,步長為2
val range3 = 1 to 10 by 2
// 定義一個(gè)從10到1的整數(shù)序列,步長為-1
val range4 = 10 to 1 by -1
如果我們想把Range轉(zhuǎn)為List,我們可以這樣做:
val range = 1 to 5
val list = range.toList
Range繼承自Seq,因此它擁有Seq的所有常用方法,例如length、head、last、tail、init、reverse、isEmpty、contains、filter、map、foldLeft和foldRight等。它還擁有一些特殊的方法,例如:
val range = 1 to 10 by 2
// 獲取序列的起始值
val start = range.start
// 獲取序列的結(jié)束值
val end = range.end
// 獲取序列的步長
val step = range.step
// 獲取一個(gè)包括結(jié)束值的新序列
val inclusiveRange = range.inclusive
17.迭代器
迭代器(Iterator)是一種用于遍歷集合中元素的工具。它提供了一種方法來訪問集合中的元素,而不需要暴露集合的內(nèi)部結(jié)構(gòu)。在 Scala 中,你可以使用 iterator 方法來獲取一個(gè)集合的迭代器。
object Main {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3)
val iterator = list.iterator
// 1. 使用 hasNext 方法來檢查迭代器中是否還有元素
val hasMoreElements = iterator.hasNext
println(s"Has more elements: $hasMoreElements")
// 2. 使用 next 方法來獲取迭代器中的下一個(gè)元素
val nextElement = iterator.next()
println(s"Next element: $nextElement")
// 注意:上面的代碼已經(jīng)將迭代器移動(dòng)到了第二個(gè)元素,因此下面的代碼將從第二個(gè)元素開始執(zhí)行
// 3. 使用 size 方法來獲取迭代器中元素的個(gè)數(shù)
val size = iterator.size
println(s"Size: $size")
val size1 = iterator.size
println(s"Size1: $size1")
// 注意:上面的代碼已經(jīng)將迭代器移動(dòng)到了末尾,因此下面的代碼將不再有效
// 4. 使用 contains 方法來檢查迭代器中是否包含某個(gè)元素
val containsElement = iterator.contains(2)
println(s"Contains element: $containsElement")
}
}
輸出:
Has more elements: true
Next element: 1
Size: 2
Size1: 0
Contains element: false
特別注意:迭代器是一次性的,所以在使用完畢后就不能再次使用。因此,在上面的代碼中,我們?cè)谡{(diào)用 next 方法后就不能再使用其他方法來訪問迭代器中的元素了。所以 size1輸出為0。
18.Tuple
把Tuple從集合中抽出來講述是因?yàn)門uple不屬于集合。它是一種用來將多個(gè)值組合在一起的數(shù)據(jù)結(jié)構(gòu)。一個(gè)Tuple可以包含不同類型的元素,每個(gè)元素都有一個(gè)固定的位置。Scala 中的元組包含一系列類:Tuple2,Tuple3等,直到 Tuple22。
示例如下:
object Main {
def main(args: Array[String]): Unit = {
// 定義一個(gè)包含兩個(gè)元素的Tuple
val tuple1 = (1, "hello")
println(tuple1)
// 定義一個(gè)包含三個(gè)元素的Tuple
val tuple2 = (1, "hello", true)
println(tuple2)
// 定義一個(gè)包含多個(gè)不同類型元素的Tuple
val tuple3 = (1, "hello", true, 3.14)
println(tuple3)
// 訪問Tuple中的元素
val firstElement = tuple3._1
val secondElement = tuple3._2
println(s"first element: $firstElement, second element: $secondElement")
}
}
輸出:
(1,hello)
(1,hello,true)
(1,hello,true,3.14)
first element: 1, second element: hello
下面是一些Tuple的常用方法:
object Main {
def main(args: Array[String]): Unit = {
val tuple = (1, "hello")
// 交換二元組的元素
// 輸出:(hello,1)
val swapped = tuple.swap
// 使用 copy 方法來創(chuàng)建一個(gè)新的 Tuple,其中某些元素被替換為新值
//輸出:(1,world)
val newTuple = tuple.copy(_2 = "world")
// 遍歷元素
// 輸出:1 hello
tuple.productIterator.foreach(println)
// 轉(zhuǎn)換為字符串
// 輸出: (1,hello)
val stringRepresentation = tuple.toString
// 使用 Tuple.productArity 方法來獲取 Tuple 中元素的個(gè)數(shù)
// 輸出:2
val arity = tuple.productArity
// 使用 Tuple.productElement 方法來訪問 Tuple 中的元素
// 輸出:1
val firstElement = tuple.productElement(0)
}
}
19.提取器對(duì)象
提取器對(duì)象是一個(gè)包含有 unapply 方法的單例對(duì)象。apply 方法就像一個(gè)構(gòu)造器,接受參數(shù)然后創(chuàng)建一個(gè)實(shí)例對(duì)象,反之 unapply 方法接受一個(gè)實(shí)例對(duì)象然后返回最初創(chuàng)建它所用的參數(shù)。提取器常用在模式匹配和偏函數(shù)中。
下面是一個(gè)使用提取器對(duì)象(Extractor Object)的 Scala 代碼示例:
object Email {
def apply(user: String, domain: String): String = s"$user@$domain"
def unapply(email: String): Option[(String, String)] = {
val parts = email.split("@")
if (parts.length == 2) Some(parts(0), parts(1))
else None
}
}
// 測(cè)試
val address = "john.doe@example.com"
address match {
case Email(user, domain) => println(s"User: $user, Domain: $domain")
case _ => println("Invalid email address")
}
在上述示例中,定義了一個(gè)名為Email的提取器對(duì)象。提取器對(duì)象具有兩個(gè)方法:apply和unapply。
apply方法接收用戶名和域名作為參數(shù),并返回一個(gè)完整的電子郵件地址。在這個(gè)示例中,我們簡單地將用戶名和域名拼接成電子郵件地址的字符串。
unapply方法接收一個(gè)電子郵件地址作為參數(shù),并返回一個(gè)Option類型的元組。在這個(gè)示例中,我們使用split方法將電子郵件地址分割為用戶名和域名兩部分,并通過Some將它們封裝到一個(gè)Option中返回。如果分割后的部分不是兩部分,即電子郵件地址不符合預(yù)期的格式,我們返回None。
在測(cè)試部分,我們創(chuàng)建了一個(gè)電子郵件地址字符串a(chǎn)ddress。然后,我們使用match表達(dá)式將address與提取器對(duì)象Email進(jìn)行匹配。如果匹配成功,我們提取出用戶名和域名,并打印出對(duì)應(yīng)的信息。如果匹配失敗,即電子郵件地址無效,我們打印出相應(yīng)的錯(cuò)誤信息。
20.流程判斷
(1) while & if
object Main {
def main(args: Array[String]): Unit = {
println("----while----")
var i = 0
while (i < 5) {
println(i)
i += 1
}
println("----if----")
val x = 3
if (x > 0) {
println("x大于0")
} else {
println("x小于0")
}
}
}
輸出:
----while----
0
1
2
3
4
----if----
x大于0
Scala中的while和if跟Java中的方法幾乎沒有區(qū)別。
(2) for
object Main {
def main(args: Array[String]): Unit = {
println("----for循環(huán)----")
for (i <- 1 to 5) {
println(i)
}
}
}
輸出:
----for循環(huán)----
1
2
3
4
5
for循環(huán)跟Java略微有點(diǎn)區(qū)別。其中i <- 1 to 5是Scala中for循環(huán)的一種常見形式。它表示遍歷一個(gè)序列,序列中的元素依次為1、2、3、4、5。
(3) 多重for循環(huán)簡寫
Scala中對(duì)于多重for循環(huán)可以進(jìn)行簡寫,例如我們要用Java寫多重for循環(huán)是下面這樣:
public class Main {
public static void main(String[] args) {
// 多重for循環(huán)
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
System.out.println(i + " " + j);
}
}
}
}
而用Scala我們可以直接簡寫為下面這樣:
object Main {
def main(args: Array[String]): Unit = {
// 多重for循環(huán)
for (i <- 0 until 3; j <- 0 until 3) {
println(i + " " + j)
}
}
}
輸出:
0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2
可以看出scala的for循環(huán)語法更加的精簡。代碼行數(shù)更少。
(4) yield
在for循環(huán)的過程中我們可以使用 yield來對(duì)for循環(huán)的元素進(jìn)行 操作收集:
object Main {
def main(args: Array[String]): Unit = {
val numbers = for (i <- 1 to 5) yield i * 2
println(numbers)
}
}
輸出:
Vector(2, 4, 6, 8, 10)
21.模式匹配(pattern matching)
在Scala語言中,沒有switch和case關(guān)鍵字。相反,我們可以使用模式匹配(pattern matching)來實(shí)現(xiàn)類似于switch語句的功能。它是Java中的switch語句的升級(jí)版,同樣可以用于替代一系列的 if/else 語句。下面是一個(gè)簡單的例子,它展示了如何使用模式匹配來實(shí)現(xiàn)類似于switch語句的功能:
object Main {
def main(args: Array[String]): Unit = {
def matchTest(x: Any): String = x match {
case 1 => "one"
case "two" => "two"
case y: Int => "scala.Int"
case _ => "many"
}
println(matchTest(1))
println(matchTest("two"))
println(matchTest(3))
println(matchTest("test"))
}
}
輸出:
one
two
scala.Int
many
在上面的例子中,定義了一個(gè)名為matchTest的函數(shù),它接受一個(gè)類型為Any的參數(shù)x。在函數(shù)體中,我們使用了一個(gè)模式匹配表達(dá)式來匹配參數(shù)x的值。
在模式匹配表達(dá)式中,我們定義了四個(gè)case子句。第一個(gè)case子句匹配值為1的情況;第二個(gè)case子句匹配值為"two"的情況;第三個(gè)case子句匹配類型為Int的情況;最后一個(gè)case子句匹配所有其他情況。
22.樣例類(case classes)的匹配
樣例類非常適合用于模式匹配。
abstract class Notification
case class Email(sender: String, title: String, body: String) extends Notification
case class SMS(caller: String, message: String) extends Notification
def showNotification(notification: Notification): String = {
notification match {
case Email(sender, title, _) =>
s"You got an email from $sender with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
}
}
val someSms = SMS("12345", "Are you there?")
val someEmail = Email("John Doe", "Meeting", "Are we still meeting tomorrow?")
println(showNotification(someSms))
println(showNotification(someEmail))
這段代碼定義了一個(gè)抽象類 Notification,以及兩個(gè)擴(kuò)展自 Notification 的樣例類 Email 和 SMS。然后定義了一個(gè)函數(shù) showNotification,它接受一個(gè) Notification 類型的參數(shù),并使用模式匹配來檢查傳入的通知是 Email 還是 SMS,并相應(yīng)地生成一條消息。
最后,我們創(chuàng)建了兩個(gè)實(shí)例:一個(gè) SMS 和一個(gè) Email,并使用 showNotification 函數(shù)來顯示它們的消息。
23.模式守衛(wèi)(Pattern guards)
為了讓匹配更加具體,可以使用模式守衛(wèi),也就是在模式后面加上if <boolean expression>。
def checkNumberType(number: Int): String = number match {
case n if n > 0 && n % 2 == 0 => "Positive even number"
case n if n > 0 && n % 2 != 0 => "Positive odd number"
case n if n < 0 && n % 2 == 0 => "Negative even number"
case n if n < 0 && n % 2 != 0 => "Negative odd number"
case _ => "Zero"
}
// 測(cè)試
println(checkNumberType(10)) // 輸出: Positive even number
println(checkNumberType(15)) // 輸出: Positive odd number
println(checkNumberType(-4)) // 輸出: Negative even number
println(checkNumberType(-9)) // 輸出: Negative odd number
println(checkNumberType(0)) // 輸出: Zero
在上述示例中,我們定義了一個(gè)名為checkNumberType的方法,它接收一個(gè)整數(shù)參數(shù)number并返回一個(gè)描述數(shù)字類型的字符串。
通過使用模式守衛(wèi),我們可以對(duì)number進(jìn)行多個(gè)條件的匹配,并根據(jù)條件來返回相應(yīng)的結(jié)果。在每個(gè)case語句中,我們使用模式守衛(wèi)來進(jìn)一步過濾匹配的數(shù)字。
例如,case n if n > 0 && n % 2 == 0 表示當(dāng) number 大于 0 且為偶數(shù)時(shí)執(zhí)行該分支。類似地,其他的 case 語句也使用了模式守衛(wèi)來進(jìn)行更精確的匹配。
在測(cè)試部分,我們調(diào)用了checkNumberType方法并傳入不同的整數(shù)進(jìn)行測(cè)試。根據(jù)不同的輸入,方法將返回相應(yīng)的字符串描述數(shù)字類型。
24.僅匹配類型
當(dāng)不同類型對(duì)象需要調(diào)用不同方法時(shí),僅匹配類型的模式非常有用
def processValue(value: Any): String = value match {
case str: String => s"Received a String: $str"
case num: Int => s"Received an Int: $num"
case lst: List[_] => s"Received a List: $lst"
case _: Double => "Received a Double"
case _ => "Unknown value"
}
// 測(cè)試
println(processValue("Hello")) // 輸出: Received a String: Hello
println(processValue(10)) // 輸出: Received an Int: 10
println(processValue(List(1, 2, 3))) // 輸出: Received a List: List(1, 2, 3)
println(processValue(3.14)) // 輸出: Received a Double
println(processValue(true)) // 輸出: Unknown value
在上述示例中,定義了一個(gè)名為processValue的方法,它接收一個(gè)任意類型的參數(shù)value,并返回一個(gè)描述值類型的字符串。
通過使用類型模式匹配,我們可以根據(jù)不同的值類型來執(zhí)行相應(yīng)的邏輯。在每個(gè)case語句中,我們使用類型模式匹配來匹配特定類型的值。
例如,case str: String 表示當(dāng) value 的類型為 String 時(shí)執(zhí)行該分支,并將其綁定到變量 str。類似地,其他的 case 語句也使用了類型模式匹配來匹配不同的值類型。
在測(cè)試部分,我們調(diào)用了processValue方法并傳入不同類型的值進(jìn)行測(cè)試。根據(jù)值的類型,方法將返回相應(yīng)的描述字符串。
Scala的模式匹配是我覺得非常實(shí)用和靈活的一個(gè)功能,比Java的switch語句更加強(qiáng)大和靈活。Scala的模式匹配可以匹配不同類型的值,包括數(shù)字、字符串、列表、元組等。而Java的switch語句只能匹配整數(shù)、枚舉和字符串類型的值。
25.密封類
特質(zhì)(trait)和類(class)可以用sealed標(biāo)記為密封的,這意味著其所有子類都必須與之定義在相同文件中,從而保證所有子類型都是已知的。密封類限制了可擴(kuò)展的子類類型,并在模式匹配中確保所有可能的類型都被處理,提高了代碼的安全性和可靠性。
下面是一個(gè)使用密封類(sealed class)和模式匹配的 Scala 代碼示例:
sealed abstract class Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
case class Square(side: Double) extends Shape
def calculateArea(shape: Shape): Double = shape match {
case Circle(radius) => math.Pi * radius * radius
case Rectangle(width, height) => width * height
case Square(side) => side * side
}
// 測(cè)試
val circle = Circle(5.0)
val rectangle = Rectangle(3.0, 4.0)
val square = Square(2.5)
println(s"Area of circle: ${calculateArea(circle)}") // 輸出: Area of circle: 78.53981633974483
println(s"Area of rectangle: ${calculateArea(rectangle)}") // 輸出: Area of rectangle: 12.0
println(s"Area of square: ${calculateArea(square)}") // 輸出: Area of square: 6.25
在上述示例中,我們定義了一個(gè)密封類Shape,它是一個(gè)抽象類,不能直接實(shí)例化。然后,我們通過擴(kuò)展Shape類創(chuàng)建了Circle、Rectangle和Square這三個(gè)子類。
在calculateArea方法中,我們使用模式匹配對(duì)傳入的shape進(jìn)行匹配,并根據(jù)不同的Shape子類執(zhí)行相應(yīng)的邏輯。在每個(gè)case語句中,我們根據(jù)具體的形狀類型提取相應(yīng)的屬性,并計(jì)算出面積。
在測(cè)試部分,我們創(chuàng)建了一個(gè)Circle對(duì)象、一個(gè)Rectangle對(duì)象和一個(gè)Square對(duì)象,并分別調(diào)用calculateArea方法計(jì)算它們的面積。
26.嵌套方法
當(dāng)在Scala中定義一個(gè)方法時(shí),我們可以選擇將其嵌套在另一個(gè)方法內(nèi)部。這樣的嵌套方法只在外部方法的作用域內(nèi)可見,而對(duì)于外部方法以外的代碼是不可見的。這可以幫助我們組織和封裝代碼,提高代碼的可讀性和可維護(hù)性。
def calculateDiscountedPrice(originalPrice: Double, discountPercentage: Double): Double = {
def applyDiscount(price: Double, discount: Double): Double = {
val discountedPrice = price - (price * discount)
discountedPrice
}
def validateDiscount(discount: Double): Double = {
val maxDiscount = 0.8 // 最大折扣為80%
if (discount > maxDiscount) {
maxDiscount
} else {
discount
}
}
val validatedDiscount = validateDiscount(discountPercentage)
val finalPrice = applyDiscount(originalPrice, validatedDiscount)
finalPrice
}
// 調(diào)用外部方法
val price = calculateDiscountedPrice(100.0, 0.9)
println(s"The final price is: $price")
在上述示例中,定義了一個(gè)外部方法calculateDiscountedPrice,它接收原始價(jià)格originalPrice和折扣百分比discountPercentage作為參數(shù),并返回最終價(jià)格。
在calculateDiscountedPrice方法的內(nèi)部,我們定義了兩個(gè)嵌套方法:applyDiscount和validateDiscount。applyDiscount方法用于計(jì)算折扣后的價(jià)格,它接收價(jià)格和折扣作為參數(shù),并返回折扣后的價(jià)格。validateDiscount方法用于驗(yàn)證折扣百分比是否超過最大折扣限制,并返回一個(gè)有效的折扣百分比。
在外部方法中,我們首先調(diào)用validateDiscount方法來獲取有效的折扣百分比,然后將其與原始價(jià)格一起傳遞給applyDiscount方法,計(jì)算最終價(jià)格。最后,我們打印出最終價(jià)格。
27.正則表達(dá)式模型
正則表達(dá)式是用來找出數(shù)據(jù)中的指定模式(或缺少該模式)的字符串。.r方法可使任意字符串變成一個(gè)正則表達(dá)式。
object Main extends App {
val emailPattern = "([a-zA-Z0-9_.+-]+)@([a-zA-Z0-9-]+)\\.([a-zA-Z0-9-.]+)".r
def validateEmail(email: String): Boolean = email match {
case emailPattern(username, domain, extension) =>
println(s"Valid email address: $email")
true
case _ =>
println(s"Invalid email address: $email")
false
}
// 測(cè)試
validateEmail("john.doe@example.com") // 輸出: Valid email address: john.doe@example.com
validateEmail("jane.doe@invalid") // 輸出: Invalid email address: jane.doe@invalid
}
在上述示例中,我們首先創(chuàng)建了一個(gè)名為emailPattern的正則表達(dá)式對(duì)象,用于匹配電子郵件地址的模式。
然后,定義了一個(gè)名為validateEmail的方法,它接收一個(gè)字符串類型的電子郵件地址作為參數(shù),并使用正則表達(dá)式模式匹配來驗(yàn)證電子郵件地址的有效性。
在模式匹配的case語句中,我們使用emailPattern對(duì)傳入的電子郵件地址進(jìn)行匹配,并將匹配結(jié)果中的用戶名、域名和擴(kuò)展提取到相應(yīng)的變量中。如果匹配成功,我們打印出驗(yàn)證通過的消息,并返回true表示電子郵件地址有效。如果沒有匹配成功,則打印出驗(yàn)證失敗的消息,并返回false表示電子郵件地址無效。
在測(cè)試部分,我們調(diào)用validateEmail方法分別傳入一個(gè)有效的電子郵件地址和一個(gè)無效的電子郵件地址進(jìn)行測(cè)試。根據(jù)匹配結(jié)果,我們打印出相應(yīng)的驗(yàn)證消息。
28.型變
在 Scala 中,協(xié)變(covariance)和逆變(contravariance)是用來描述類型參數(shù)在子類型關(guān)系中的行為的概念。協(xié)變和逆變是用來指定泛型類型參數(shù)的子類型關(guān)系的方式,以確保類型安全性。
29.協(xié)變
協(xié)變(Covariance): 協(xié)變表示類型參數(shù)在子類型關(guān)系中具有相同的方向。如果一個(gè)泛型類的類型參數(shù)是協(xié)變的,那么子類型的關(guān)系將保持不變,即父類型可以被替換為子類型。在 Scala 中,可以使用 + 符號(hào)來表示協(xié)變。
下面是一個(gè)使用協(xié)變的示例代碼,使用 + 符號(hào)表示類型參數(shù) A 是協(xié)變的:
class Animal
class Dog extends Animal
class Cage[+A]
val dogCage: Cage[Dog] = new Cage[Dog]
val animalCage: Cage[Animal] = dogCage
在上述示例中,我們定義了一個(gè)協(xié)變類 Cage[+A],它接受一個(gè)類型參數(shù) A,并使用 + 符號(hào)來表示 A 是協(xié)變的。我們創(chuàng)建了一個(gè) dogCage,它是一個(gè) Cage[Dog] 類型的實(shí)例。然后,我們將 dogCage 賦值給一個(gè)類型為 Cage[Animal] 的變量 animalCage,這是合法的,因?yàn)?nbsp;Cage[+A] 的協(xié)變性允許我們將子類型的 Cage 賦值給父類型的 Cage。
30.逆變
逆變(Contravariance): 逆變表示類型參數(shù)在子類型關(guān)系中具有相反的方向。如果一個(gè)泛型類的類型參數(shù)是逆變的,那么子類型的關(guān)系將反轉(zhuǎn),即父類型可以替換為子類型。在 Scala 中,可以使用 - 符號(hào)來表示逆變。
下面是一個(gè)使用逆變的示例代碼,使用 - 符號(hào)表示類型參數(shù) A 是逆變的:
class Animal
class Dog extends Animal
class Cage[-A]
val animalCage: Cage[Animal] = new Cage[Animal]
val dogCage: Cage[Dog] = animalCage
在上述示例中,定義了一個(gè)逆變類 Cage[-A],它接受一個(gè)類型參數(shù) A,并使用 - 符號(hào)來表示 A 是逆變的。我們創(chuàng)建了一個(gè) animalCage,它是一個(gè) Cage[Animal] 類型的實(shí)例。然后,我們將 animalCage 賦值給一個(gè)類型為 Cage[Dog] 的變量 dogCage,這是合法的,因?yàn)?nbsp;Cage[-A] 的逆變性允許我們將父類型的 Cage 賦值給子類型的 Cage。通過協(xié)變和逆變,我們可以在 Scala 中實(shí)現(xiàn)更靈活的類型關(guān)系,并確保類型安全性。這在處理泛型集合或函數(shù)參數(shù)時(shí)特別有用。下面是一個(gè)更具體的示例:
abstract class Animal {
def name: String
}
class Dog(val name: String) extends Animal {
def bark(): Unit = println("Woof!")
}
class Cat(val name: String) extends Animal {
def meow(): Unit = println("Meow!")
}
class Cage[+A](val animal: A) {
def showAnimal(): Unit = println(animal.name)
}
def printAnimalNames(cage: Cage[Animal]): Unit = {
cage.showAnimal()
}
val dog: Dog = new Dog("Fido")
val cat: Cat = new Cat("Whiskers")
val dogCage: Cage[Dog] = new Cage[Dog](dog)
val catCage: Cage[Cat] = new Cage[Cat](cat)
printAnimalNames(dogCage) // 輸出:Fido
printAnimalNames(catCage) // 輸出:Whiskers
在上述示例中,定義了一個(gè)抽象類 Animal,以及它的兩個(gè)子類 Dog 和 Cat。Dog 和 Cat 類都實(shí)現(xiàn)了 name 方法。
然后,定義了一個(gè)協(xié)變類 Cage[+A],它接受一個(gè)類型參數(shù) A,并使用協(xié)變符號(hào) + 表示 A 是協(xié)變的。Cage 類有一個(gè)名為 animal 的屬性,它的類型是 A,也就是動(dòng)物的類型。我們定義了一個(gè)名為 showAnimal() 的方法,它打印出 animal 的名稱。
接下來,定義了一個(gè)名為 printAnimalNames() 的函數(shù),它接受一個(gè)類型為 Cage[Animal] 的參數(shù),并打印出其中動(dòng)物的名稱。
我們創(chuàng)建了一個(gè) Dog 類型的對(duì)象 dog 和一個(gè) Cat 類型的對(duì)象 cat。然后,我們分別創(chuàng)建了一個(gè) Cage[Dog] 類型的 dogCage 和一個(gè) Cage[Cat] 類型的 catCage。
最后,我們分別調(diào)用 printAnimalNames() 函數(shù),并傳入 dogCage 和 catCage。由于 Cage 類是協(xié)變的,所以可以將 Cage[Dog] 和 Cage[Cat] 賦值給 Cage[Animal] 類型的參數(shù),而不會(huì)產(chǎn)生類型錯(cuò)誤。
31.類型限界
在 Scala 中,類型上界(Upper Bounds)和類型下界(Lower Bounds)是用于限制泛型類型參數(shù)的范圍的概念。它們?cè)试S我們?cè)诜盒皖惢蚍盒秃瘮?shù)中指定類型參數(shù)必須滿足某種條件。下面是關(guān)于類型上界和類型下界的解釋和示例代碼:
32.類型上界
類型上界(Upper Bounds): 類型上界用于指定泛型類型參數(shù)必須是某個(gè)類型或其子類型。我們使用 <: 符號(hào)來定義類型上界。例如,A <: B 表示類型參數(shù) A 必須是類型 B 或其子類型。
下面是一個(gè)使用類型上界的示例代碼:
abstract class Animal {
def name: String
}
class Dog(val name: String) extends Animal {
def bark(): Unit = println("Woof!")
}
class Cage[A <: Animal](val animal: A) {
def showAnimal(): Unit = println(animal.name)
}
val dog: Dog = new Dog("Fido")
val cage: Cage[Animal] = new Cage[Dog](dog)
cage.showAnimal() // 輸出:Fido
在上述示例中,定義了一個(gè)抽象類 Animal,以及它的子類 Dog。Dog 類繼承自 Animal 類,并實(shí)現(xiàn)了 name 方法。
然后,定義了一個(gè)泛型類 Cage[A <: Animal],它接受一個(gè)類型參數(shù) A,并使用類型上界 A <: Animal 來確保 A 是 Animal 類型或其子類型。Cage 類有一個(gè)名為 animal 的屬性,它的類型是 A。我們定義了一個(gè)名為 showAnimal() 的方法,它打印出 animal 的名稱。
創(chuàng)建了一個(gè) Dog 類型的對(duì)象 dog。然后,我們創(chuàng)建了一個(gè) Cage[Animal] 類型的 cage,并將 dog 對(duì)象作為參數(shù)傳遞給它。
最后,調(diào)用 cage 的 showAnimal() 方法,它成功打印出了 Dog 對(duì)象的名稱。
33.類型下界
類型下界(Lower Bounds): 類型下界用于指定泛型類型參數(shù)必須是某個(gè)類型或其父類型。我們使用 > 符號(hào)來定義類型下界。例如,A >: B 表示類型參數(shù) A 必須是類型 B 或其父類型。
下面是一個(gè)使用類型下界的示例代碼:
class Animal {
def sound(): Unit = println("Animal sound")
}
class Dog extends Animal {
override def sound(): Unit = println("Dog barking")
}
class Cat extends Animal {
override def sound(): Unit = println("Cat meowing")
}
def makeSound[A >: Dog](animal: A): Unit = {
animal.sound()
}
val dog: Dog = new Dog
val cat: Cat = new Cat
makeSound(dog) // 輸出:Dog barking
makeSound(cat) // 輸出:Animal sound
在上述示例中,定義了一個(gè)基類 Animal,以及兩個(gè)子類 Dog 和 Cat。這些類都有一個(gè) sound() 方法,用于輸出不同的動(dòng)物聲音。
接下來,定義了一個(gè)泛型函數(shù) makeSound[A >: Dog](animal: A),其中類型參數(shù) A 的下界被定義為 Dog,即 A >: Dog。這意味著 A 必須是 Dog 類型或其父類型。
在 makeSound() 函數(shù)內(nèi)部,我們調(diào)用傳入的 animal 對(duì)象的 sound() 方法。
然后,創(chuàng)建了一個(gè) Dog 對(duì)象 dog 和一個(gè) Cat 對(duì)象 cat。
最后,分別調(diào)用 makeSound() 函數(shù),并將 dog 和 cat 作為參數(shù)傳遞進(jìn)去。由于類型下界被定義為 Dog,所以 dog 參數(shù)符合條件,而 cat 參數(shù)被隱式地向上轉(zhuǎn)型為 Animal,也滿足條件。因此,調(diào)用 makeSound() 函數(shù)時(shí),輸出了不同的聲音。
通過類型上界和類型下界,我們可以對(duì)泛型類型參數(shù)的范圍進(jìn)行限制,以確保類型的約束和類型安全性。這使得我們能夠編寫更靈活、可復(fù)用且類型安全的代碼。
34.內(nèi)部類
在 Scala 中,內(nèi)部類是一個(gè)定義在另一個(gè)類內(nèi)部的類。內(nèi)部類可以訪問外部類的成員,并具有更緊密的關(guān)聯(lián)性。下面是一個(gè)關(guān)于 Scala 中內(nèi)部類的解釋和示例代碼:
在 Scala 中,內(nèi)部類可以分為兩種類型:成員內(nèi)部類(Member Inner Class)和局部內(nèi)部類(Local Inner Class)。
成員內(nèi)部類:成員內(nèi)部類是定義在外部類的作用域內(nèi),并可以直接訪問外部類的成員(包括私有成員)。成員內(nèi)部類可以使用外部類的實(shí)例來創(chuàng)建和訪問。
下面是一個(gè)示例代碼:
class Outer {
private val outerField: Int = 10
class Inner {
def printOuterField(): Unit = {
println(s"Outer field value: $outerField")
}
}
}
val outer: Outer = new Outer
val inner: outer.Inner = new outer.Inner
inner.printOuterField() // 輸出:Outer field value: 10
在上述示例中,定義了一個(gè)外部類 Outer,它包含一個(gè)私有成員 outerField。內(nèi)部類 Inner 定義在 Outer 的作用域內(nèi),并可以訪問外部類的成員。
在主程序中,創(chuàng)建了外部類的實(shí)例 outer。然后,我們使用 outer.Inner 來創(chuàng)建內(nèi)部類的實(shí)例 inner。注意,我們需要使用外部類的實(shí)例來創(chuàng)建內(nèi)部類的實(shí)例。
最后,調(diào)用內(nèi)部類 inner 的 printOuterField() 方法,它成功訪問并打印了外部類的私有成員 outerField。
局部內(nèi)部類: 局部內(nèi)部類是定義在方法或代碼塊內(nèi)部的類。局部內(nèi)部類的作用域僅限于所在方法或代碼塊內(nèi)部,無法從外部訪問。
下面是一個(gè)示例代碼:
def outerMethod(): Unit = {
val outerField: Int = 10
class Inner {
def printOuterField(): Unit = {
println(s"Outer field value: $outerField")
}
}
val inner: Inner = new Inner
inner.printOuterField() // 輸出:Outer field value: 10
}
outerMethod()
在上述示例中,定義了一個(gè)外部方法 outerMethod。在方法內(nèi)部,我們定義了一個(gè)局部變量 outerField 和一個(gè)局部內(nèi)部類 Inner。
在方法內(nèi)部,創(chuàng)建了內(nèi)部類 Inner 的實(shí)例 inner。注意,內(nèi)部類的作用域僅限于方法內(nèi)部。
最后,調(diào)用內(nèi)部類 inner 的 printOuterField() 方法,它成功訪問并打印了外部變量 outerField。
通過使用內(nèi)部類,我們可以在 Scala 中實(shí)現(xiàn)更緊密的關(guān)聯(lián)性和封裝性,同時(shí)允許內(nèi)部類訪問外部類的成員。內(nèi)部類在某些場(chǎng)景下可以提供更清晰和組織良好的。
35.復(fù)合類型
在 Scala 中,復(fù)合類型(Compound Types)允許我們定義一個(gè)類型,它同時(shí)具有多個(gè)特質(zhì)(Traits)或類的特性。復(fù)合類型可以用于限制一個(gè)對(duì)象的類型,以便它同時(shí)具備多個(gè)特性。下面是關(guān)于復(fù)合類型的解釋和示例代碼:
復(fù)合類型使用 with 關(guān)鍵字將多個(gè)特質(zhì)或類組合在一起,形成一個(gè)新的類型。
下面是一個(gè)示例代碼:
trait Flyable {
def fly(): Unit
}
trait Swimmable {
def swim(): Unit
}
class Bird extends Flyable {
override def fly(): Unit = println("Flying...")
}
class Fish extends Swimmable {
override def swim(): Unit = println("Swimming...")
}
def action(obj: Flyable with Swimmable): Unit = {
obj.fly()
obj.swim()
}
val bird: Bird = new Bird
val fish: Fish = new Fish
action(bird) // 輸出:Flying...
action(fish) // 輸出:Swimming...
在上述示例中,定義了兩個(gè)特質(zhì) Flyable 和 Swimmable,分別表示可飛行和可游泳的特性。然后,我們定義了兩個(gè)類 Bird 和 Fish,分別實(shí)現(xiàn)了相應(yīng)的特質(zhì)。
接下來,定義了一個(gè)方法 action,它接受一個(gè)類型為 Flyable with Swimmable 的參數(shù)。這表示參數(shù)必須同時(shí)具備 Flyable 和 Swimmable 的特性。
在主程序中,創(chuàng)建了一個(gè) Bird 對(duì)象 bird 和一個(gè) Fish 對(duì)象 fish。
最后,分別調(diào)用 action 方法,并將 bird 和 fish 作為參數(shù)傳遞進(jìn)去。由于它們都同時(shí)具備 Flyable 和 Swimmable 的特性,所以可以成功調(diào)用 fly() 和 swim() 方法。
通過使用復(fù)合類型,可以在 Scala 中定義一個(gè)類型,它同時(shí)具備多個(gè)特質(zhì)或類的特性,從而實(shí)現(xiàn)更靈活和精確的類型約束。這有助于編寫更可靠和可復(fù)用的代碼。
36.多態(tài)方法
在 Scala 中,多態(tài)方法(Polymorphic Methods)允許我們定義可以接受多種類型參數(shù)的方法。這意味著同一個(gè)方法可以根據(jù)傳入?yún)?shù)的類型執(zhí)行不同的邏輯。下面是關(guān)于多態(tài)方法的解釋和示例代碼:
多態(tài)方法使用類型參數(shù)來定義方法的參數(shù)類型,并使用泛型來表示可以接受多種類型參數(shù)。在方法內(nèi)部,可以根據(jù)類型參數(shù)的實(shí)際類型執(zhí)行不同的邏輯。
下面是一個(gè)示例代碼:
def printType[T](value: T): Unit = {
value match {
case s: String => println("String: " + s)
case i: Int => println("Int: " + i)
case d: Double => println("Double: " + d)
case _ => println("Unknown type")
}
}
printType("Hello") // 輸出:String: Hello
printType(123) // 輸出:Int: 123
printType(3.14) // 輸出:Double: 3.14
printType(true) // 輸出:Unknown type
在上述示例中,定義了一個(gè)多態(tài)方法 printType,它接受一個(gè)類型參數(shù) T。根據(jù)傳入?yún)?shù)的類型,我們使用模式匹配來判斷其實(shí)際類型,并執(zhí)行相應(yīng)的邏輯。
在方法內(nèi)部,使用 match 表達(dá)式對(duì)傳入的參數(shù) value 進(jìn)行模式匹配。對(duì)于不同的類型,我們分別輸出相應(yīng)的類型信息。
在主程序中,多次調(diào)用 printType 方法,并傳入不同類型的參數(shù)。根據(jù)傳入的參數(shù)類型,方法會(huì)執(zhí)行相應(yīng)的邏輯并輸出對(duì)應(yīng)的類型信息。
37.函數(shù)
Scala中一個(gè)簡單的函數(shù)定義如下,我們可以在Scala中使用JDK的類:
import java.util.Date
object Main {
def main(args: Array[String]): Unit = {
printCurrentDate() // 輸出當(dāng)前日期和時(shí)間
}
def printCurrentDate(): Unit = {
val currentDate = new Date()
println(currentDate.toString)
}
}
38.函數(shù)默認(rèn)值
在 Scala 中,可以為函數(shù)參數(shù)指定默認(rèn)值。這樣,當(dāng)調(diào)用函數(shù)時(shí)如果沒有提供參數(shù)值,將使用默認(rèn)值。下面是一個(gè)簡單的示例:
object Main {
def main(args: Array[String]): Unit = {
greet() // 輸出 "Hello, World!"
greet("Alice") // 輸出 "Hello, Alice!"
}
def greet(name: String = "World"): Unit = {
println(s"Hello, $name!")
}
}
39.高階函數(shù)
高階函數(shù)是指使用其他函數(shù)作為參數(shù)、或者返回一個(gè)函數(shù)作為結(jié)果的函數(shù)。在Scala中函數(shù)是“一等公民”,所以允許定義高階函數(shù)。這里的術(shù)語可能有點(diǎn)讓人困惑,我們約定,使用函數(shù)值作為參數(shù),或者返回值為函數(shù)值的“函數(shù)”和“方法”,均稱之為“高階函數(shù)”。
def applyFuncToList(list: List[Int], f: Int => Int): List[Int] = {
list.map(f)
}
val numbers = List(1, 2, 3, 4)
val double = (x: Int) => x * 2
val doubledNumbers = applyFuncToList(numbers, double) // List(2, 4, 6, 8)
在這個(gè)例子中,applyFuncToList 函數(shù)接受一個(gè)整數(shù)列表和一個(gè)函數(shù) f,該函數(shù)將一個(gè)整數(shù)作為輸入并返回一個(gè)整數(shù)。然后,applyFuncToList 函數(shù)使用 map 方法將函數(shù) f 應(yīng)用于列表中的每個(gè)元素。在上面的代碼中,我們定義了一個(gè) double 函數(shù),它將輸入乘以2,并將其傳遞給 applyFuncToList 函數(shù)以對(duì)數(shù)字列表中的每個(gè)元素進(jìn)行加倍。
40.匿名函數(shù)
在 Scala 中,匿名函數(shù)是一種沒有名稱的函數(shù),可以用來創(chuàng)建簡潔的函數(shù)字面量。它們通常用于傳遞給高階函數(shù),或作為局部函數(shù)使用。
例如,下面是一個(gè)簡單的匿名函數(shù),它接受兩個(gè)整數(shù)參數(shù)并返回它們的和:
object Main {
def main(args: Array[String]): Unit = {
val add = (x: Int, y: Int) => x + y
println(add(1, 2)) //輸出:3
}
}
41.偏應(yīng)用函數(shù)
簡單來說,偏應(yīng)用函數(shù)就是一種只對(duì)輸入值的某個(gè)子集進(jìn)行處理的函數(shù)。它只會(huì)對(duì)符合特定條件的輸入值進(jìn)行處理,而對(duì)于不符合條件的輸入值則會(huì)拋出異常。
舉個(gè)例子:
object Main {
def main(args: Array[String]): Unit = {
println(divide.isDefinedAt(0)) // false
println(divideSafe.isDefinedAt(0)) // true
println(divide(1)) // 42
println(divideSafe(1)) // Some(42)
// println(divide(0)) // 拋出異常
println(divideSafe(0)) // None
}
val divide: PartialFunction[Int, Int] = {
case d: Int if d != 0 => 42 / d
}
val divideSafe: PartialFunction[Int, Option[Int]] = {
case d: Int if d != 0 => Some(42 / d)
case _ => None
}
}
這個(gè)例子中,divide 是一個(gè)偏應(yīng)用函數(shù),它只定義了對(duì)非零整數(shù)的除法運(yùn)算。如果我們嘗試用 divide 函數(shù)去除以零,它會(huì)拋出一個(gè)異常。其中isDefinedAt 是一個(gè)方法,它用于檢查偏應(yīng)用函數(shù)是否在給定的輸入值上定義。如果偏應(yīng)用函數(shù)在給定的輸入值上定義,那么 isDefinedAt 方法會(huì)返回 true,否則返回 false。
為了避免這種情況,我們可以使用 divideSafe 函數(shù),它返回一個(gè) Option 類型的結(jié)果。如果除數(shù)為零,它會(huì)返回 None 而不是拋出異常。
42.柯里化函數(shù)
柯里化(Currying)是一種將多參數(shù)函數(shù)轉(zhuǎn)換為一系列單參數(shù)函數(shù)的技術(shù)。我們可以使用柯里化來定義函數(shù),例如:
def add(a: Int)(b: Int): Int = a + b
這個(gè) add 函數(shù)接受兩個(gè)參數(shù) a 和 b,并返回它們的和。由于它是一個(gè)柯里化函數(shù),所以我們可以將它看作是一個(gè)接受單個(gè)參數(shù) a 的函數(shù),它返回一個(gè)接受單個(gè)參數(shù) b 的函數(shù)。
我們可以這樣調(diào)用這個(gè)函數(shù):
val result = add(1)(2) // 3
或者這樣:
val addOne = add(1) _
val result = addOne(2) // 3
在上面的例子中,我們首先調(diào)用 add 函數(shù)并傳入第一個(gè)參數(shù) 1,然后我們得到一個(gè)新的函數(shù) addOne,它接受一個(gè)參數(shù)并返回它與 1 的和。最后,我們調(diào)用 addOne 函數(shù)并傳入?yún)?shù) 2,得到結(jié)果 3。
柯里化函數(shù)可以幫助我們實(shí)現(xiàn)參數(shù)復(fù)用和延遲執(zhí)行等功能。
柯里化函數(shù)的好處之一是它可以讓我們給一個(gè)函數(shù)傳遞較少的參數(shù),得到一個(gè)已經(jīng)記住了某些固定參數(shù)的新函數(shù)。這樣,我們就可以在不同的地方使用這個(gè)新函數(shù),而不需要每次都傳遞相同的參數(shù)2。
此外,柯里化函數(shù)還可以幫助我們實(shí)現(xiàn)函數(shù)的延遲計(jì)算。當(dāng)我們傳遞部分參數(shù)時(shí),它會(huì)返回一個(gè)新的函數(shù),可以在新的函數(shù)中繼續(xù)傳遞后面的參數(shù)。這樣,我們就可以根據(jù)需要來決定何時(shí)執(zhí)行這個(gè)函數(shù)。
43.惰性函數(shù)
可以使用 lazy 關(guān)鍵字定義惰性函數(shù)。惰性函數(shù)的執(zhí)行會(huì)被推遲,直到我們首次對(duì)其取值時(shí)才會(huì)執(zhí)行。
下面是一個(gè)簡單的例子,展示了如何定義和使用惰性函數(shù):
def sum(x: Int, y: Int): Int = {
println("sum函數(shù)被執(zhí)行了...")
x + y
}
lazy val res: Int = sum(1, 2)
println("-----")
println(res)
在這個(gè)例子中,我們定義了一個(gè)函數(shù) sum,它接受兩個(gè)參數(shù)并返回它們的和。然后我們定義了一個(gè)惰性值 res 并將其賦值為 sum(1, 2)。
在主程序中,我們首先打印了一行分隔符。然后我們打印了變量 res 的值。由于 res 是一個(gè)惰性值,因此在打印它之前,函數(shù) sum 并沒有被執(zhí)行。只有當(dāng)我們首次對(duì) res 取值時(shí),函數(shù) sum 才會(huì)被執(zhí)行。
這就是Scala中惰性函數(shù)的基本用法。你可以使用 lazy 關(guān)鍵字定義惰性函數(shù),讓函數(shù)的執(zhí)行被推遲。
總結(jié)
在總結(jié)之處,我希望強(qiáng)調(diào)Scala的美學(xué)和實(shí)用性。它是一種同時(shí)支持函數(shù)式編程和面向?qū)ο缶幊痰恼Z言,Scala的語法設(shè)計(jì)使其對(duì)初學(xué)者非常友好,同時(shí)也為更深入地探索編程提供了空間。
學(xué)習(xí)Scala不僅能夠幫助你提高編程效率,還能開闊你的編程視野。當(dāng)你熟練掌握Scala后,你將發(fā)現(xiàn)一個(gè)全新的、充滿無限可能的編程世界正在向你敞開。今天,我們只是輕輕掀開了Scala的神秘面紗,未來等待你去挖掘的還有更多。
請(qǐng)繼續(xù)探索和嘗試,讓自己真正理解并掌握Scala的精髓。持續(xù)學(xué)習(xí),不斷思考,享受編程的樂趣。
最后,希望這篇文章能給你帶來收獲和思考。