Scala进阶
一、模式匹配
1.1结构和概念
match case的语法如下:变量 match { case 值 => 代码 }。如果值为下划线,则代表了不满⾜以上所有情况下的默认情况如何处理。此外,match case中,只要⼀个case分⽀满⾜并处理了,就不会继续判断下⼀个case分⽀了。(与Java不同,java的switch case需要⽤break阻⽌)
(1)使⽤|分割多个选项(表示“或”)
(2)可以给模式添加守卫
/**
* 与switch-case不同的地方
* 1. 模式匹配中,没有穿透性
* 2. 在case后面,可以使用|连接多个值,表示“或”的意思
* 3. 在模式匹配的最后,使用_来表示上述都不成立的情况
* 4. 可以给匹配项添加if守卫
*/
object _01_matchTest1 {
// 模式匹配的基础应用
def main(args: Array[String]): Unit = {
val oper = StdIn.readLine("请输入一个操作符")
val x = 10
val y = 20
// 值匹配
oper match {
case "+" => println(x + y)
case "-" => println(x - y)
case "*" | "x" => println(x * y)
case "/" => println(x / y)
case _ if oper.length > 1 => println("操作符的位数不对啊")
case _ => println("其他的操作符")
}
}
}
1.2类型匹配
Scala的模式匹配⼀个强⼤之处就在于,可以直接匹配类型,⽽不是值!!!这点是java的switch case绝对做不到的。
注意: 当你在匹配类型的时候,必须给出⼀个变量名,否则你将会拿对象本身来进⾏匹配
(1)如果只想要进行类型的匹配,不想要这个变量或者对该变量进行向下转型,那么可以使用_下划线进行占位
(2)Float 匹配类型为Class的Float对象
object _02_matchTest2 {
def main(args: Array[String]): Unit = {
// 类型匹配: 比switch-case更强大
val array: Array[Any] = Array(123, "hello", 'a', 3.14f, true)
val element = array(Random.nextInt(array.length))
// 逻辑:
// 如果element实际的类型,和case之后的类型匹配上,就会将值给前面的变量进行赋值,并且执行=>之后的逻辑
// 如果指向进行类型的匹配,不想向下转型,给匹配变量赋值,可以使用_表示占位
element match {
case x: Int => println(s"匹配到了一个整数 $x")
case x: String => println(s"匹配到了一个字符串 $x")
case _: Char => println("匹配到了一个字符")
case _: Double => println("匹配到了一个浮点型")
case Float => println("匹配到了一个Float类型") // 类型为 Class的Float对象
case _ => println(s"出现了其他的类型 $element")
}
}
}
1.3数组匹配
对Array进⾏模式匹配,分别可以匹配带有指定元素的数组、带有指定个数元素的数组、以某元素打头的数组
1.4列表匹配
1.5元组匹配
object _03_matchTest3 {
def main(args: Array[String]): Unit = {
// 数组匹配
val array = Array(0, 1, 2)
array match {
case Array(0) => println("逐个元素的匹配,这里表示数组中只有0")
case Array(x, y) => println(s"匹配两个元素的数组,将值分别给x和y赋值 x = $x, y = $y")
case Array(0, _*) => println("匹配任意的以0开头的数组")
case _ => println("没有包含0的数组")
}
// 列表匹配
val list = List(0, 1, 2)
list match {
case 0 :: Nil => println("逐个元素的匹配,这里表示列表中只有0")
case x :: y :: Nil => println(s"匹配两个元素的列表,将值分别给x和y赋值 x = $x, y = $y")
case 0 :: tail => println("匹配任意的以0开头的列表")
case _ => println("匹配到了其他的列表")
}
// 元组匹配
var tuple = (3.14, 100)
tuple match {
case (3.14, 100) => println("具体的值匹配")
case (x, y) => println(s"匹配到两个元素的元组, x = $x, y = $y")
case (x, _) => println(s"匹配到连个元素的元组, x = $x, y的值我不在乎")
case (3.14, y) => println(s"匹配到两个元素的元组, x是3.14, y = $y")
case (x, 100) =>
}
}
}
1.6样例类匹配
实质是样例类的拆分模式
这里的类的匹配,匹配成功后,可以将属性给指定的变量进行赋值(这一切都依赖于unapply()方法,这里为了方便直接写的是case class,因为case class内部提供了unapply方法)
向下转型可以转换为模式匹配,看IDEA提示
/**
* 这里的类的匹配, 匹配成功后, 可以将属性给指定的变量进行赋值
* 这一切,都依赖一个方法 unapply
*/
object _04_matchTest4 {
def main(args: Array[String]): Unit = {
val student = Student("xiaobai", 10, "male")
student match {
case Student(name, age, gender) => println(s"$name, $age, $gender")
case Student(name, age, _) => println(s"$name, $age")
}
}
def showAnimalProperties(animal: Animal): Unit = {
animal match {
case Dog(name, age) =>
println(s"匹配到了一个Dog对象, name = $name, age = $age")
case Cat(name, age, _) =>
println(s"匹配到了一个Cat对象, name = $name, age = $age")
case Rabbit(name, _, hobby) =>
println(s"匹配到了一个Rabbit对象, name = $name, hobby = $hobby")
}
}
}
case class Student(name: String, age: Int, gender: String)
abstract class Animal
case class Dog(name: String, age: Int) extends Animal
case class Cat(name: String, age: Int, furColor: String) extends Animal
case class Rabbit(name: String, age: Int, hobby: String) extends Animal
1.7Option类型
在Scala中Option类型样例类⽤来表示可能存在或也可能不存在的值(Option的⼦类有Some和None)。
Some包装了某个值,None表示没有值。
Option表示可能存在,也可能不存在值
对于Option类型的值,不要直接使用get()方法获取值,这是因为很可能拿到一个none值,我们可以使用模式匹配拿出来(另外一种方法是:map.getOrElse()方法)
object _05_matchTest5 {
def main(args: Array[String]): Unit = {
// Option表示可能存在,也可能不存在的值
// 有两个子类 Some, None
val map = Map("u1s1" -> "有一说一")
map.get("u1s1") match {
case Some(x) => println(x)
case None => println("没有值")
}
val value = map.get("u1s1") match {
case Some(x) => x
case None => "defaultValue"
}
println(value)
}
}
二、高阶函数
(1)传入参数为函数
(2)传入参数为匿名函数
(3)传⼊参数为⽅法(隐式转换⽅法到函数)
2.1高阶函数初体验
object _01_functionTest1 {
def main(args: Array[String]): Unit = {
val calculate: (Int, Int) => Int = _ + _
// 1. 直接使用
println(calculate(10, 20))
// 2. 存入一个变量
val cal2 = calculate
println(cal2(10, 20))
// 3. 作为参数
// 传入三个参数,其中第一个参数是函数,返回一个Int型的值
val calculator: ((Int, Int) => Int, Int, Int) => Int = _(_, _)
println(calculator(calculate, 10, 20))
// 相当于_*_(10,20),前面的是一个函数,后面括号内传入参数
println(calculator(_-_, 10, 20))
// 4. 作为返回值
val calculator2: Boolean => ((Int, Int) => Int) = x => {
if (x) {
(a: Int, b: Int) => a + b
} else {
(a: Int, b: Int) => a - b
}
}
calculator2(true)(10, 20)
}
}
2.2闭包
object _02_functionTest2 {
def main(args: Array[String]): Unit = {
val x = 10
// 闭包1: 使用到了外部的一个局部变量,返回值依赖这个局部变量
val calculator1: () => Int = () => x * 2
//
val calculator2: Int => Int = _ + x
val test1: () => Unit = test()
test1()
}
def test(): () => Unit = {
val a = 10
() => println(a)
}
}
2.3柯里化
/**
* 函数的柯里化 curring
*/
object _03_functionTest3 {
def main(args: Array[String]): Unit = {
calculateFakeCurring(10)(20)
println(calculateCurring(10)(20))
calculateCurring(10)_
}
// 非柯里化的写法
def calculate(x: Int, y: Int): Int = x + y
// 非柯里化的写法
def calculateFakeCurring(x: Int): Int => Int = {
(y: Int) => x + y
}
// 柯里化的写法
def calculateCurring(x: Int)(y: Int): Int = x + y
}
2.4偏函数
/**
* 偏函数
*/
object _04_functionTest4 {
def main(args: Array[String]): Unit = {
val res0: Int = test.apply("abc")
println(res0)
println(test.apply("one"))
println(test.apply("two"))
println(test.isDefinedAt("four")) // 判断是否定义了与指定的case相匹配的计算逻辑
// 偏函数,在调用apply的时候,是可以省略不写的
println(test("one"))
}
// 1. 定义一个偏函数
def test: PartialFunction[String, Int] = {
case "one" => 1
case "two" => 2
case "three" => 3
case _ => -1
}
}
2.5 高阶函数和模式匹配的结合
object _05_functionTest5 {
// 高阶函数和模式匹配的结合
def main(args: Array[String]): Unit = {
showNum(1)
showNum("123")
showNum(3.14)
val shower: Any => Unit = {
case t: Int => println(s"Int => $t")
case t: String => println(s"String => $t")
case _ => println("other")
}
shower(1)
shower("123")
shower(3.14)
val array: Array[Any] = Array(1, 2, 3, 4, "hello")
// 需求: 对这个数组中,所有的数字+1,其他类型的不变
val res0: Array[Any] = array.map({
case a: Int => a + 1
case x: Any => x
})
println(res0.mkString(", "))
}
def showNum(number: Any): Unit = {
number match {
case x: Int => println(s"Int => $x")
case x: String => println(s"String => $x")
case _ => println("other")
}
}
}
三、隐式转换和隐式函数
3.1隐式类
使用关键字implicit修饰的类就是隐式类,是对某一个已经存在的类的增强,是为了给某一个可扩展功能的
在定义隐式类的时候,需要在主构造器中,定义出需要增强的类类型的参数
隐式类定义在object中
注意事项:
1.隐式转换类书写的位置要求:只能定义在class、trait、object中
2.隐式类中的主构造器只能有一个非隐式参数
3.隐式类不能是case class
4.使用的位置:如果时候用到的隐式转换类不在当前的上下文定义,需要导入包
object _01_implictTest1 {
def main(args: Array[String]): Unit = {
val a: String = "hello"
a.printLength()
1.compare(2)
1.compareTo(2)
}
implicit class StringExtension(source: String) {
def printLength(): Unit = println(source.length)
}
}
/**
* 隐式类:
* 使用关键字 implicit 修饰的类就是隐式类, 是对某一个已经存在的类的增强, 是为了给某一个类拓展功能的
* 在定义隐式类的时候,需要在主构造器中,定义出需要增强的类类型的参数
*
* 注意事项:
* 1. 隐式类,只能定义在class、trait、object中
* 2. 隐式类的主构造器,只能有一个非隐式参数
* 3. 隐式类不能是case class
* 4. 使用的时候,如果使用到的隐式转换类不在当前的上下文定义,需要导包
* 导入指定的隐式转换类
* import day05._03_implict._02_implictTest2.StringExtension
* 导入指定类中所有的隐式转换类
* import day05._03_implict._02_implictTest2._
*/
object _02_implictTest2 {
implicit class StringExtension(source: String) {
def printLength(): Unit = println(source.length)
def concat(other: String*): String = source + ", " + other.mkString(", ")
}
implicit class IntExtension(source: Int)
implicit class FloatExtension(source: Float)
}
object Test2 {
def main(args: Array[String]): Unit = {
// 1. 定义一个字符串
val str = "hello"
// 2. 使用隐式类中增强的方法
// 如果隐式类不在当前的类中定义,使用的时候,需要导入
// 导入指定的隐式转换类
// import day05._03_implict._02_implictTest2.StringExtension
// 导入指定类中所有的隐式转换类
import day05._03_implict._02_implictTest2._
str.printLength()
println(str.concat("world", "你好", "师姐"))
}
}
3.2隐式方法
使用关键字implict修饰方法,就是隐式方法
一般情况下,是实现一个类型到另一个类型的隐式转换
字符串类型的数字不能转换成Int类型,这是因为没有从String到Int的隐式转换方法
那么我们可以自定义一个方法
当需要从String转型到Int的时候,会从当前上下文中查找是否有指定的转型的方法
1.在一个上下文中,不能同时出现两个相同的类型转换方法
2.当隐式转换方法不在需要使用的方法中(方法中包含这个隐式方法)时,我们这个时候需要导入这个类
/**
* 隐式方法: 使用关键字 implicit 修饰的方法,就是隐式方法
* 一般情况下,是实现一个类型到另外一个类型的隐式转换
*
* 1. 在一个上下文中,不能同时出现两个相同的类型转换方法
*/
object _03_implicitTest3 {
def main(args: Array[String]): Unit = {
// 当需要从String转型为Int的时候,会从当前上下文中查找是否有指定的转型的方法
// 定义了一个隐式转换的方法,可以由String转型为Int
val num1: Int = "1234"
val num2: Int = false
val num3: Boolean = 1
println(num1)
println(num2)
println(num3)
}
implicit def string2int(str: String): Int = Integer.parseInt(str)
implicit def boolean2int(bl: Boolean): Int = if (bl) 1 else 0
implicit def int2boolean(value: Int): Boolean = value != 0
}
3.3隐式参数
1.使用implict修饰的参数,就是隐式参数
2.隐式参数在调用的时候,可以给值,也可以不给值
3.使用隐式参数前提条件:在调用隐式参数的时候,如果不给隐式参数赋值,那么会从当前上下文中查找隐式变量
4.不能出现多个相同类型的隐式变量,否则在使用的时候会因为不确定使用哪一个而出错。但是可以有多不同类型的隐式变量
/**
* 隐式参数
*/
object _04_implicitTest4 {
def main(args: Array[String]): Unit = {
// 前提条件:
// 当前的上下文中,需要有一个隐式变量
implicit val a: Int = 10
// 不能出现多个隐式变量,否则在使用的时候会因为不确定使用哪一个而出错
// implicit val b: Int = 100
implicit val b: String = "123"
// 在调用隐式参数的方法的时候,如果不给隐式参数赋值,那么会从当前上下文中查找隐式变量
// 直接作为这个方法的参数
println(calculate(10))
println(calculate(10)(20))
}
// 使用implicit修饰的参数,就是隐式参数
// 隐式参数,在调用的时候,可以给值,也可以不给值
def calculate(x: Int)(implicit y: Int): Int = x + y
}
四、泛型
[B <: A] upper bounds 上限或上界:B类型的上界是A类型,也就是B类型的⽗类是A类型
[B >: A] lower bounds 下限或下界:B类型的下界是A类型,也就是B类型的⼦类是A类型
[B <% A] view bounds 视界:(过时了)表示B类型要转换为A类型,需要⼀个隐式转换函数
[B : A] context bounds 上下⽂界定(⽤到了隐式参数的语法糖):需要⼀个隐式转换的值
[-A]:逆变,作为参数类型。是指实现的参数类型是接⼝定义的参数类型的⽗类
[+B]:协变,作为返回类型。是指返回类型是接⼝定义返回类型的⼦类
object _01_genericTest1 {
def main(args: Array[String]): Unit = {
// val value: PetContainer[Dog] = new PetContainer[Dog](new Dog)
// val res0: PetContainer[Pet] = value
// new PetContainer[Dog].print(new Dog())
val values: PetContainer2[Hashiqi] = new PetContainer2[Dog]()
}
}
class Pet
class Dog extends Pet
class Hashiqi extends Dog
// 泛型上界 [A <: Pet] : 表示泛型A的类型,可以是Pet或者Pet的子类型
// 泛型下界 [A >: Dog] : 表示泛型A的类型,可以是Dog或者Dog的父类型
class PetContainer[+A]() {
// 泛型定义为协变时,在成员中不能直接使用这个泛型
// def printPetInfo(pet: A): Unit = println(pet)
// 如果需要在成员方法中使用,成员方法也需要添加泛型
def printPetInto[B >: A](pet: B): Unit = println(pet)
}
class PetContainer2[-A]() {
// 泛型定义为逆变时,在成员中可以直接使用
def printPetInfo(pet: A): Unit = println(pet)
}