迭代器
迭代器(Iterator)是一种用于遍历集合中元素的工具。它提供了一种方法来访问集合中的元素,而不需要暴露集合的内部结构。在 Scala 中,你可以使用 iterator
方法来获取一个集合的迭代器。
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 方法来获取迭代器中的下一个元素 val nextElement = iterator.next() println(s"Next element: $nextElement") // 注意:上面的代码已经将迭代器移动到了第二个元素,因此下面的代码将从第二个元素开始执行 // 3. 使用 size 方法来获取迭代器中元素的个数 val size = iterator.size println(s"Size: $size") val size1 = iterator.size println(s"Size1: $size1") // 注意:上面的代码已经将迭代器移动到了末尾,因此下面的代码将不再有效 // 4. 使用 contains 方法来检查迭代器中是否包含某个元素 val containsElement = iterator.contains(2) println(s"Contains element: $containsElement") } } 输出: Has more elements: true Next element: 1 Size: 2 Size1: 0 Contains element: false
特别注意:迭代器是一次性的,所以在使用完毕后就不能再次使用。因此,在上面的代码中,我们在调用 next
方法后就不能再使用其他方法来访问迭代器中的元素了。所以 size1输出为0。
Tuple
把Tuple
从集合中抽出来讲述是因为Tuple
不属于集合。它是一种用来将多个值组合在一起的数据结构。一个Tuple
可以包含不同类型的元素,每个元素都有一个固定的位置。Scala 中的元组包含一系列类:Tuple2,Tuple3等,直到 Tuple22。
示例如下:
object Main { def main(args: Array[String]): Unit = { // 定义一个包含两个元素的Tuple val tuple1 = (1, "hello") println(tuple1) // 定义一个包含三个元素的Tuple val tuple2 = (1, "hello", true) println(tuple2) // 定义一个包含多个不同类型元素的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 方法来创建一个新的 Tuple,其中某些元素被替换为新值 //输出:(1,world) val newTuple = tuple.copy(_2 = "world") // 遍历元素 // 输出: 1 hello tuple.productIterator.foreach(println) // 转换为字符串 // 输出: (1,hello) val stringRepresentation = tuple.toString // 使用 Tuple.productArity 方法来获取 Tuple 中元素的个数 // 输出: 2 val arity = tuple.productArity // 使用 Tuple.productElement 方法来访问 Tuple 中的元素 // 输出: 1 val firstElement = tuple.productElement(0) } }
提取器对象
提取器对象是一个包含有 unapply
方法的单例对象。apply
方法就像一个构造器,接受参数然后创建一个实例对象,反之 unapply
方法接受一个实例对象然后返回最初创建它所用的参数。提取器常用在模式匹配和偏函数中。
下面是一个使用提取器对象(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 } } // 测试 val address = "john.doe@example.com" address match { case Email(user, domain) => println(s"User: $user, Domain: $domain") case _ => println("Invalid email address") }
在上述示例中,定义了一个名为Email
的提取器对象。提取器对象具有两个方法:apply
和unapply
。
apply
方法接收用户名和域名作为参数,并返回一个完整的电子邮件地址。在这个示例中,我们简单地将用户名和域名拼接成电子邮件地址的字符串。
unapply
方法接收一个电子邮件地址作为参数,并返回一个Option
类型的元组。在这个示例中,我们使用split
方法将电子邮件地址分割为用户名和域名两部分,并通过Some
将它们封装到一个Option
中返回。如果分割后的部分不是两部分,即电子邮件地址不符合预期的格式,我们返回None
。
在测试部分,我们创建了一个电子邮件地址字符串address
。然后,我们使用match
表达式将address
与提取器对象Email
进行匹配。如果匹配成功,我们提取出用户名和域名,并打印出对应的信息。如果匹配失败,即电子邮件地址无效,我们打印出相应的错误信息。
流程判断
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中的方法几乎没有区别。
for
object Main { def main(args: Array[String]): Unit = { println("----for循环----") for (i <- 1 to 5) { println(i) } } } 输出: ----for循环---- 1 2 3 4 5
for循环跟Java略微有点区别。其中i <- 1 to 5
是Scala中for循环的一种常见形式。它表示遍历一个序列,序列中的元素依次为1、2、3、4、5。
多重for循环简写
Scala中对于多重for循环可以进行简写,例如我们要用Java写多重for循环是下面这样:
public class Main { public static void main(String[] args) { // 多重for循环 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循环 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循环语法更加的精简。代码行数更少。
yield
在for循环的过程中我们可以使用 yield来对for循环的元素进行 操作收集:
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)
模式匹配(pattern matching)
在Scala语言中,没有switch
和case
关键字。相反,我们可以使用模式匹配(pattern matching)来实现类似于switch
语句的功能。它是Java中的switch
语句的升级版,同样可以用于替代一系列的 if/else 语句。下面是一个简单的例子,它展示了如何使用模式匹配来实现类似于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
在上面的例子中,定义了一个名为matchTest
的函数,它接受一个类型为Any
的参数x
。在函数体中,我们使用了一个模式匹配表达式来匹配参数x
的值。
在模式匹配表达式中,我们定义了四个case
子句。第一个case
子句匹配值为1的情况;第二个case
子句匹配值为"two"的情况;第三个case
子句匹配类型为Int
的情况;最后一个case
子句匹配所有其他情况。
样例类(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))
这段代码定义了一个抽象类 Notification
,以及两个扩展自 Notification
的样例类 Email
和 SMS
。然后定义了一个函数 showNotification
,它接受一个 Notification
类型的参数,并使用模式匹配来检查传入的通知是 Email
还是 SMS
,并相应地生成一条消息。
最后,我们创建了两个实例:一个 SMS
和一个 Email
,并使用 showNotification
函数来显示它们的消息。
模式守卫(Pattern guards)
为了让匹配更加具体,可以使用模式守卫,也就是在模式后面加上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" } // 测试 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
在上述示例中,我们定义了一个名为checkNumberType
的方法,它接收一个整数参数number
并返回一个描述数字类型的字符串。
通过使用模式守卫,我们可以对number
进行多个条件的匹配,并根据条件来返回相应的结果。在每个case
语句中,我们使用模式守卫来进一步过滤匹配的数字。
例如,case n if n > 0 && n % 2 == 0
表示当 number
大于 0 且为偶数时执行该分支。类似地,其他的 case
语句也使用了模式守卫来进行更精确的匹配。
在测试部分,我们调用了checkNumberType
方法并传入不同的整数进行测试。根据不同的输入,方法将返回相应的字符串描述数字类型。
仅匹配类型
当不同类型对象需要调用不同方法时,仅匹配类型的模式非常有用
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" } // 测试 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
在上述示例中,定义了一个名为processValue
的方法,它接收一个任意类型的参数value
,并返回一个描述值类型的字符串。
通过使用类型模式匹配,我们可以根据不同的值类型来执行相应的逻辑。在每个case
语句中,我们使用类型模式匹配来匹配特定类型的值。
例如,case str: String
表示当 value
的类型为 String
时执行该分支,并将其绑定到变量 str
。类似地,其他的 case
语句也使用了类型模式匹配来匹配不同的值类型。
在测试部分,我们调用了processValue
方法并传入不同类型的值进行测试。根据值的类型,方法将返回相应的描述字符串。
Scala的模式匹配是我觉得非常实用和灵活的一个功能,比Java的switch
语句更加强大和灵活。Scala的模式匹配可以匹配不同类型的值,包括数字、字符串、列表、元组等。而Java的switch
语句只能匹配整数、枚举和字符串类型的值。
密封类
特质(trait)和类(class)可以用sealed
标记为密封的,这意味着其所有子类都必须与之定义在相同文件中,从而保证所有子类型都是已知的。密封类限制了可扩展的子类类型,并在模式匹配中确保所有可能的类型都被处理,提高了代码的安全性和可靠性。
下面是一个使用密封类(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 } // 测试 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
在上述示例中,我们定义了一个密封类Shape
,它是一个抽象类,不能直接实例化。然后,我们通过扩展Shape
类创建了Circle
、Rectangle
和Square
这三个子类。
在calculateArea
方法中,我们使用模式匹配对传入的shape
进行匹配,并根据不同的Shape
子类执行相应的逻辑。在每个case
语句中,我们根据具体的形状类型提取相应的属性,并计算出面积。
在测试部分,我们创建了一个Circle
对象、一个Rectangle
对象和一个Square
对象,并分别调用calculateArea
方法计算它们的面积。
嵌套方法
当在Scala中定义一个方法时,我们可以选择将其嵌套在另一个方法内部。这样的嵌套方法只在外部方法的作用域内可见,而对于外部方法以外的代码是不可见的。这可以帮助我们组织和封装代码,提高代码的可读性和可维护性。
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 } // 调用外部方法 val price = calculateDiscountedPrice(100.0, 0.9) println(s"The final price is: $price")
在上述示例中,定义了一个外部方法calculateDiscountedPrice
,它接收原始价格originalPrice
和折扣百分比discountPercentage
作为参数,并返回最终价格。
在calculateDiscountedPrice
方法的内部,我们定义了两个嵌套方法:applyDiscount
和validateDiscount
。applyDiscount
方法用于计算折扣后的价格,它接收价格和折扣作为参数,并返回折扣后的价格。validateDiscount
方法用于验证折扣百分比是否超过最大折扣限制,并返回一个有效的折扣百分比。
在外部方法中,我们首先调用validateDiscount
方法来获取有效的折扣百分比,然后将其与原始价格一起传递给applyDiscount
方法,计算最终价格。最后,我们打印出最终价格。
正则表达式模型
正则表达式是用来找出数据中的指定模式(或缺少该模式)的字符串。.r
方法可使任意字符串变成一个正则表达式。
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 } // 测试 validateEmail("john.doe@example.com") // 输出: Valid email address: john.doe@example.com validateEmail("jane.doe@invalid") // 输出: Invalid email address: jane.doe@invalid }
在上述示例中,我们首先创建了一个名为emailPattern
的正则表达式对象,用于匹配电子邮件地址的模式。
然后,定义了一个名为validateEmail
的方法,它接收一个字符串类型的电子邮件地址作为参数,并使用正则表达式模式匹配来验证电子邮件地址的有效性。
在模式匹配的case
语句中,我们使用emailPattern
对传入的电子邮件地址进行匹配,并将匹配结果中的用户名、域名和扩展提取到相应的变量中。如果匹配成功,我们打印出验证通过的消息,并返回true
表示电子邮件地址有效。如果没有匹配成功,则打印出验证失败的消息,并返回false
表示电子邮件地址无效。
在测试部分,我们调用validateEmail
方法分别传入一个有效的电子邮件地址和一个无效的电子邮件地址进行测试。根据匹配结果,我们打印出相应的验证消息。
型变
在 Scala 中,协变(covariance)和逆变(contravariance)是用来描述类型参数在子类型关系中的行为的概念。协变和逆变是用来指定泛型类型参数的子类型关系的方式,以确保类型安全性。
协变
协变(Covariance): 协变表示类型参数在子类型关系中具有相同的方向。如果一个泛型类的类型参数是协变的,那么子类型的关系将保持不变,即父类型可以被替换为子类型。在 Scala 中,可以使用 +
符号来表示协变。
下面是一个使用协变的示例代码,使用 +
符号表示类型参数 A
是协变的:
class Animal class Dog extends Animal class Cage[+A] val dogCage: Cage[Dog] = new Cage[Dog] val animalCage: Cage[Animal] = dogCage
在上述示例中,我们定义了一个协变类 Cage[+A]
,它接受一个类型参数 A
,并使用 +
符号来表示 A
是协变的。我们创建了一个 dogCage
,它是一个 Cage[Dog]
类型的实例。然后,我们将 dogCage
赋值给一个类型为 Cage[Animal]
的变量 animalCage
,这是合法的,因为 Cage[+A]
的协变性允许我们将子类型的 Cage
赋值给父类型的 Cage
。
逆变
逆变(Contravariance): 逆变表示类型参数在子类型关系中具有相反的方向。如果一个泛型类的类型参数是逆变的,那么子类型的关系将反转,即父类型可以替换为子类型。在 Scala 中,可以使用 -
符号来表示逆变。
下面是一个使用逆变的示例代码,使用 -
符号表示类型参数 A
是逆变的:
class Animal class Dog extends Animal class Cage[-A] val animalCage: Cage[Animal] = new Cage[Animal] val dogCage: Cage[Dog] = animalCage
在上述示例中,定义了一个逆变类 Cage[-A]
,它接受一个类型参数 A
,并使用 -
符号来表示 A
是逆变的。我们创建了一个 animalCage
,它是一个 Cage[Animal]
类型的实例。然后,我们将 animalCage
赋值给一个类型为 Cage[Dog]
的变量 dogCage
,这是合法的,因为 Cage[-A]
的逆变性允许我们将父类型的 Cage
赋值给子类型的 Cage
。 通过协变和逆变,我们可以在 Scala 中实现更灵活的类型关系,并确保类型安全性。这在处理泛型集合或函数参数时特别有用。下面是一个更具体的示例:
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
在上述示例中,定义了一个抽象类 Animal
,以及它的两个子类 Dog
和 Cat
。Dog
和 Cat
类都实现了 name
方法。
然后,定义了一个协变类 Cage[+A]
,它接受一个类型参数 A
,并使用协变符号 +
表示 A
是协变的。Cage
类有一个名为 animal
的属性,它的类型是 A
,也就是动物的类型。我们定义了一个名为 showAnimal()
的方法,它打印出 animal
的名称。
接下来,定义了一个名为 printAnimalNames()
的函数,它接受一个类型为 Cage[Animal]
的参数,并打印出其中动物的名称。
我们创建了一个 Dog
类型的对象 dog
和一个 Cat
类型的对象 cat
。然后,我们分别创建了一个 Cage[Dog]
类型的 dogCage
和一个 Cage[Cat]
类型的 catCage
。
最后,我们分别调用 printAnimalNames()
函数,并传入 dogCage
和 catCage
。由于 Cage
类是协变的,所以可以将 Cage[Dog]
和 Cage[Cat]
赋值给 Cage[Animal]
类型的参数,而不会产生类型错误。