Scala语言
1 Scala语言的特点
Scala是一门以Java虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言(静态语言需要提前编译的如:Java、c、c++等,动态语言如:js)。
1. Scala是一门多范式的编程语言,Scala支持面向对象和函数式编程。(多范式,就是多种编程方法的意思。有面向过程、面向对象、泛型、函数式四种程序设计方法。)
2. Scala源代码(.scala)会被编译成Java字节码(.class),然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接。
3. Scala单作为一门语言来看,非常的简洁高效。
2 Scala程序
/**
* Scala完全面向对象,故Scala去掉了Java中非面向对象的元素,如static关键字,void类型。
* static:
* Scala无static关键字,由object实现类似静态方法的功能(类名.方法名)。
* class关键字和Java中的class关键字作用相同,用来定义一个类;
* void:
* 对于无返回值的函数,Scala定义其返回值类型为Unit类
* object:
* 关键字,声明一个单例对象(伴生对象),伴生对象主要为了解决Scala语言中缺失static关键字而程序又需要static关键字修饰的情况。
*/
object Test {
/**
* main方法名
* 小括号表示参数列表 参数声明格式为 参数名:参数类型
* Scala中没有public关键字,如果不声明访问权限,那么就是公共的。
* Scala中声明方法必须采用关键字def声明。
* Scala中方法实现赋值给方法声明,所以中间需要等号连接。
*
*/
def main(args: Array[String]): Unit = {
println("hello scala")
}
}
3 Scala的变量和常量
3.1 基本语法
var 变量名 [: 变量类型] = 初始值
val 常量名 [: 常量类型] = 初始值
//TODO 能用常量的地方不用变量。
3.2 示例
object Test {
def main(args: Array[String]): Unit = {
//TODO 声明变量时,类型可以省略,编译器自动推导,即类型推导。
var age = 18
age = 30
//TODO 类型确定后,就不能修改,说明 Scala 是强数据类型语言。
age = "tom"
//TODO 变量声明时,必须要有初始值。
var name //错误
//TODO 在声明一个变量时,可以使用 var 或者 val 来修饰,var 修饰的变量可改变,val 修饰的变量不可改。
var num1 = 10
num1 = 30 //正确
val num2 = 20
num2 = 100 //错误,因为 num2 是 val 修饰的,不可变。
//TODO var 修饰的对象引用可以改变,val 修饰的对象则不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)。
// p1 是 var 修饰的,p1 的属性可以变,而且 p1 本身也可以变
var p1 = new Person()
p1.name = "dalang"
p1 = null
// p2 是 val 修饰的,那么 p2 本身就不可变(即 p2 的内存地址不能变),但是p2 的属性是可以变,因为属性并没有用 val 修饰。
val p2 = new Person()
p2.name="jinlian" //正确
p2 = null // 错误的,因为 p2 是 val 修饰的
//TODO Scala声明变量时,使用下划线 _ :表示给变量初始值。
var name:String = _
//TODO 在Scala声明变量时,可以赋值表达式或代码块使用大括号括起来。
val xx = {
| println("xxxxx")
| "HELLO"
}
//TODO 在Scala变量声明中,可以加上lazy关键字,懒加载,当且仅当第一次使用这个值时才进行初始化。
lazy val sex = "man"
}
}
class Person{
var name : String = "jinlian"
}
4 字符串输出
object Test {
def main(args: Array[String]): Unit = {
// 普通字符串
val name :String = "xxx"
// 差值表达式
val name : String = "xxx"
val message :String= s"hello $name"
// 多行表达式,使用三引号。输入的内容,带有空格、\t 之类,导致每一行的开始位置不能整洁对齐。应用 Scala 的 stripMargin 方法,在 Scala 中 stripMargin 默认是“|”作为连接符。
是“|”作为连接符
val s =
"""
|select
| name,
| age
|from user
|where name="zhangsan"
""".stripMargin
}
}
5 数据类型
- Scala中一切数据都是对象,都是Any的子类。
- Scala中数据类型分为两大类:数值类型(AnyVal)、引用类型(AnyRef),不管是值类型还是引用类型都是对象。
- Scala数据类型仍然遵守,低精度的值类型向高精度值类型,自动转换(隐式转换)。
- Scala中的String是对Java中的String增强。
- Unit对应Java中的void,用于方法返回值的位置,表示方法没有返回值。Unit是一个数据类型,只有一个对象就是()。Void不是数据类型,只是一个关键字。
- Null是一个类型,只有一个对象就是null。它是所有引用类型(AnyRef)的子类。Null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)。
- Nothing是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数。
Java | Scala | 描述 |
---|---|---|
byte | Byte | 8 位有符号补码整数。数值区间为 -128 到 127 |
short | Short | 16 位有符号补码整数。数值区间为 -32768 到 32767 |
int | Int | 32 位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
long | Long | 64 位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 = 2 的(64-1)次方-1 |
double | Double | 64位8个字节 |
float | Float | 32位4个字节 |
boolean | Boolean | Boolean类型数据只允许取值 true 和 false,Boolean类型占 1 个字节。 |
char | Char | 16位2个字节 |
6 类型转换
6.1 数值类型自动转换
当 Scala 程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个就是自动类型转换(隐式转换)。
object Test {
def main(args: Array[String]): Unit = {
// 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
var n = 1 + 2.0
println(n) // n 就是 Double
// 把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
var n2 : Double= 1.0
var n3 : Int = n2 //错误,原因不能把高精度的数据直接赋值和低精度。
// (byte,short)和 char 之间不会相互自动转换。
var n4 : Byte = 1
var c1 : Char = n4 //错误
var n5 : Int = n4
// byte,short,char 他们三者可以计算,在计算时首先转换为 int类型。
var n6 : Byte = 1
var c2 : Char = 1
var n : Short = n6 + c2 //当 n6 + c2 结果类型就是 int
}
}
6.2 强制类型转换
将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出。
object Test {
def main(args: Array[String]): Unit = {
var num : Int = 2.7.toInt
}
}
6.3 数值类型和String类型间转换
object Test {
def main(args: Array[String]): Unit = {
// 基本类型转 String 类型(语法:将基本类型的值+"" 即可)
var str1 : String = true + ""
var str2 : String = 4.5 + ""
var str3 : String = 100 +""
// String 类型转基本数值类型(语法:调用相关 API)
var s1 : String = "12"
var n1 : Byte = s1.toByte
var n2 : Short = s1.toShort
var n3 : Int = s1.toInt
var n4 : Long = s1.toLong
// 在将 String 类型转成基本数值类型时,要确保 String 类型能够转成有效的数据,比如我们可以把"123",转成一个整数,但是不能把"hello"转成一个整数。
var n5:Int = "12.6".toInt // 会出现 NumberFormatException 异常。
}
}
7 运算符
object Test {
def main(args: Array[String]): Unit = {
// 在Scala中==相当于Java中的equals方法,比较的是两个字符串所包含的内容是否相同,而在Java中==比较的是两个字符串的内存地址是否相同。
var a: Int = 2
var b: Int = 1
println("a==b" + (a == b))
// 在 Scala 中没有 Java 中的++、--操作符,可以通过+=、-=来实现同样的效果。
// 在 Scala 中其实是没有运算符的,所有运算符都是方法。
// 标准的加法运算
val i:Int = 1.+(1)
// (1)当调用对象的方法时,.可以省略。
val j:Int = 1 + (1)
// (2)如果函数参数只有一个,或者没有参数,()可以省略。
val k:Int = 1 + 1
}
}
8 For循环控制
8.1 范围数据循环(To)
object Test {
// i 表示循环的变量,i 将会从 1-5 循环,前后闭合,会打印5次数据
def main(args: Array[String]): Unit = {
for(i <- 1 to 5){
println(i)
}
}
}
8.2 范围数据循环(Until)
object Test {
// i 表示循环的变量,i 将会从 1-4 循环,前闭后开,会打印4次数据
def main(args: Array[String]): Unit = {
for(i <- 1 until 5){
println(i)
}
}
}
8.3 循环守卫
object TestFor {
// 循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为 true 则进入循环体内部,为 false 则跳过,类似于 continue。
def main(args: Array[String]): Unit = {
for (i <- 1 to 5 if i != 3) {
println(i)
}
}
}
// 上面的代码类似于如下的代码
for (i <- 1 to 5 ){
if(i != 3){
println(i)
}
}
8.4 循环步长
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
// by 表示步长
8.5 循环返回值
object TestFor {
// 将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字。
def main(args: Array[String]): Unit = {
var res = for(i <-1 to 10) yield {
i * 2
}
println(res)
}
}
8.6 倒序打印
object TestFor {
// 倒序打印 10 到 1。
def main(args: Array[String]): Unit = {
for(i <- 1 to 10 reverse){
println(i)
}
}
}
8.7 循环中断
Scala 内置控制结构特地去掉了 break 和 continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。Scala中使用breakable控制结构来实现 break 和 continue 功能。
import scala.util.control.Breaks._
object Test{
def main(args:Array[String]): Unit = {
Breakable{
for (item <- 0 to 100){
if item>=50 break
println(item)
}
}
}
9 函数式编程
面向对象编程是通过分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题。而函数式编程是将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。Scala 语言是一个完全面向对象编程语言。万物皆对象。同时它也是一个完全函数式编程语言。万物皆函数。函数可以当做一个值进行传递。
9.1 函数的基本语法
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)函数定义
def f(arg: String): Unit = {
println(arg)
}
// (2)函数调用
f("hello world")
}
}
9.2 函数和方法的区别
- 为完成某一功能的程序语句的集合,称为函数。
- 类中的函数称之方法。
- 函数没有重载和重写的概念;方法可以进行重载和重写。
- Scala 中函数可以嵌套定义。
object TestFunction {
// 方法可以进行重载和重写,程序可以执行
def main(): Unit = {
}
def main(args: Array[String]): Unit = {
// 函数没有重载和重写的概念,程序报错
def test(): Unit ={
println("无参,无返回值")
}
def test(name:String):Unit={
println()
}
test()
//(3)Scala 中函数可以嵌套定义
def test2(): Unit ={
def test3(name:String):Unit={
println("函数可以嵌套定义")
}
}
}
}
9.3 函数的参数
- 参数默认值,一般将有默认值的参数放置在参数列表的后面。
- 如果参数列表中存在多个参数,那么可变参数一般放置在最后。
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)可变参数
def test( s : String* ): Unit = {
println(s)
}
// 有输入参数:输出 Array
test("Hello", "Scala")
// 无输入参数:输出 List()
test()
//======================================================
// (2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
def test2( name : String, s: String* ): Unit = {
println(name + "," + s)
}
test2("jinlian", "dalang")
//======================================================
// (3)参数默认值
def test3( name : String, age : Int = 30 ): Unit = {
println(s"$name, $age")
}
// 如果参数传递了值,那么会覆盖默认值
test3("jinlian", 20)
// 如果参数有默认值,在调用的时候,可以省略这个参数
test3("dalang")
// 一般情况下,将有默认值的参数放置在参数列表的后面
def test4( sex : String = "男", name : String ): Unit = {
println(s"$name, $sex")
}
// Scala 函数中参数传递是,从左到右
test4("wusong") //报错
//======================================================
//(4)带名参数
test4(name="ximenqing")
}
}
9.4 函数至简原则
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值。
- 如果函数体只有一行代码,可以省略花括号。
- 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)。
- 如果有 return,则不能省略返回值类型,必须指定。
- 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用。
- Scala 如果期望是无返回值类型,可以省略等号,将无返回值的函数称之为过程。
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加。
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略。
object TestFunction {
def main(args: Array[String]): Unit = {
// 函数标准写法
def f( s : String ): String = {
return s + " jinlian"
}
println(f("Hello"))
// ==========================================================
//(1) return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
def f1( s : String ): String = {
s + " jinlian"
}
println(f1("Hello"))
// ==========================================================
//(2)如果函数体只有一行代码,可以省略花括号
def f2(s:String):String = s + " jinlian"
// ==========================================================
//(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
def f3( s : String ) = s + " jinlian"
println(f3("Hello3"))
// ==========================================================
//(4)如果有 return,则不能省略返回值类型,必须指定。
def f4() :String = {
return "ximenqing4"
}
println(f4())
// ==========================================================
//(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
def f5(): Unit = {
return "dalang5"
}
println(f5())
// ==========================================================
//(6)Scala 如果期望是无返回值类型,可以省略等号,将无返回值的函数称之为过程。
def f6() {
"dalang6"
}
f6()
// ==========================================================
//(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def f7() = "dalang7"
f7()
f7
// ==========================================================
//(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def f8 = "dalang"
f8
}
9.5 匿名函数
9.5.1 介绍
没有名字的函数就是匿名函数。匿名函数主要使用在函数的参数为另一个函数。可以封装一个对数据的操作。
(x:Int)=>{函数体}
// x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑。
9.5.2 示例
// 匿名函数的简化有如下:
// 参数的类型可以省略,会根据形参进行自动的推导。
// 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
// 匿名函数如果只有一行,则大括号也可以省略。
// 如果参数只出现一次,则参数省略且后面参数可以用_代替。
object Test{
def main(args:Array[String]):Unit = {
// (1)定义一个函数:参数包含数据和逻辑函数
def operation(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}
// (2)定义逻辑函数
def op(ele: Int): Int = {
ele + 1
}
// (3)标准函数调用
val arr = operation(Array(1, 2, 3, 4), op)
println(arr.mkString(","))
// (4)采用匿名函数
val arr1 = operation(Array(1, 2, 3, 4), (ele: Int) => {ele + 1})
println(arr1.mkString(","))
//简化
// 参数的类型可以省略,会根据形参进行自动的推导;
val arr2 = operation(Array(1, 2, 3, 4), (ele) => {
ele + 1
})
println(arr2.mkString(","))
// 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
val arr3 = operation(Array(1, 2, 3, 4), ele => {
ele + 1
})
println(arr3.mkString(","))
// 匿名函数如果只有一行,则大括号也可以省略
val arr4 = operation(Array(1, 2, 3, 4), ele => ele + 1)
println(arr4.mkString(","))
//(4.4)如果参数只出现一次,则参数省略且后面参数可以用_代替
val arr5 = operation(Array(1, 2, 3, 4), _ + 1)
println(arr5.mkString(","))
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
def calculator(a: Int, b: Int, op: (Int, Int) => Int): Int= {op(a, b)}
// (1)标准版
println(calculator(2, 3, (x: Int, y: Int) => {x + y}))
// (2)如果只有一行,则大括号也可以省略
println(calculator(2, 3, (x: Int, y: Int) => x + y))
// (3)参数的类型可以省略,会根据形参进行自动的推导;
println(calculator(2, 3, (x , y) => x + y))
// (4)如果参数只出现一次,则参数省略且后面参数可以用_代替
println(calculator(2, 3, _ + _))
}
}
9.6 高阶函数
在Scala中,函数是一等公民,函数可以定义在任何位置上。函数可以作为值进行传递、函数可以作为参数进行传递甚至函数可以作为函数返回值返回。
object TestFunction {
def main(args: Array[String]): Unit = {
// 定义函数
def foo():Unit = {
println("foo...")
}
// 调用函数
foo()
}
}
9.6.1 函数作为值进行传递
object TestFunction {
def main(args: Array[String]): Unit = {
// 在上面定义的函数foo基础上,在被调用函数 foo 后面加上 _,相当于把函数 foo 当成一个整体,传递给变量 f1
val f1 = foo _
// 如果明确函数类型,那么不使用下划线也可以将函数作为整体传递给变量
val f2:() => Unit = foo
}
}
9.6.2 函数作为参数进行传递
object Test{
def main(args:Array[String]):Unit = {
def fun(f : (Int,Int)=>Int,a:Int,b:Int) :Int = {
f(a,b)
}
def add (a:Int,b:Int):Int = {
a + b
}
fun(add,2,3)
fun((a,b)=>a+b,2,3)
fun(_+_,3,4)
}
}
9.6.3 函数作为函数返回值返回
object Test{
def main(args:Array[String]):Unit = {
// 在这个示例中,f6函数作为f5的返回值
def f5(): Int => Int = {
def f6(a:Int):Int = {
a + 1
}
f6
}
f5()(2)
// 上面定义的f6函数可以按照如下步骤进行简化
def f5(): Int => Int = {
a => a + 1
}
def f5()(a:Int) = {
a+1
}
def f5()(a:Int)= a + 1
}
}
9.7 闭包
如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包。如下面的例子,函数func使用了函数fun的参数a。就是一个闭包。
object Test{
def main(args:Array[String]):Unit = {
def fun(a:Int):Int=>Int = {
def func(b:Int):Int = {
a + b
}
func
}
fun(5)(6)
}
}
9.8 函数柯里化
把一个参数列表的多个参数,变成多个参数列表。闭包可以通过简化得到。函数柯里化一定存在闭包。
object Test{
def main(args:Array[String]):Unit = {
def fun(a:Int):Int=>Int = b => a + b
// 上面的函数如果要省略返回值的话可以写成柯里化形式
def fun(a:Int)(b:Int) = a + b
fun(5)(6)
}
}
9.9 递归
一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用。
object Test{
def main(args:Array[String]):Unit = {
def func(a:Int): Int = {
if (a == 0) return 1
func(a - 1 ) * a
}
func(10)
}
}
9.9.1 尾递归
正常递归,每一次递归步骤,需要保存信息到堆栈里面,当递归步骤很多时,可能会导致堆栈溢出,尾递归就是为了解决上述问题,在尾递归中所有的计算都在递归之前调用,编译器可以利用这个属性避免堆栈错误,尾递归的调用可以使信息不插入堆栈,从而优化递归,使用@tailrec注解可使编译器测试写的方法是否为尾递归,如果不是尾递归的话,程序会报错。
object Test{
def main(args:Array[String]):Unit = {
def tailrec(a: Int): Int = {
@tailrec
def func(a: Int, b: Int): Int = {
if (a == 0) return b
func(a - 1, b * a)
}
func(a, 1)
}
println(tailrec(20))
}
}
9.10 控制抽象
9.10.1 值调用(call-by-value)
// 把计算后的值传递过去
object Test{
def main(args:Array[String]):Unit = {
def foo(a:Int):Unit = {
println(a)
}
foo(4)
}
}
9.10.2 名调用(call-by-name)
// 把代码块传递过去
object Test{
def main(args:Array[String]):Unit = {
def foo(a: => Unit): Unit = {
a
}
foo{println("名调用")}
}
}
9.10.3 示例
object Test{
def fun(condition: => Boolean): (=> Unit) => Unit = {
def loop(operation: => Unit): Unit = {
if (condition) {
operation
fun(condition)(operation)
}
}
loop
}
// 使用匿名函数简化
def fun2(condition: => Boolean): (=> Unit) => Unit = {
operation => {
if (condition) {
operation
fun2(condition)(operation)
}
}
}
//使用函数柯里化简化
def fun3(condition: => Boolean)(operation: => Unit): Unit = {
if (condition) {
operation
fun3(condition)(operation)
}
}
var n = 5
fun3(n >= 1) {
println(n)
n -= 1
}
}
9.11 惰性加载
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。lazy 不能修饰 var 类型的变量。
def main(args: Array[String]): Unit = {
lazy val res = sum(10, 30)
println("----------------")
println("res=" + res)
}
def sum(n1: Int, n2: Int): Int = {
println("sum 被执行。。。")
return n1 + n2
}
// 输出结果
// ----------------
// sum 被执行。。。
10 面向对象
10.1 包对象
在 Scala 中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有 class 和 object 的共享变量,可以被直接访问。
package object com{
val shareValue="share"
def shareMethod()={}
}
// 如上面定义的包对象可以使用在com包下的任何位置上。
10.2 访问权限
- Scala中属性和方法的默认访问权限为public,但Scala中没有public关键字。
- private为私有权限,只能在本类中或当前类的伴生对象中访问。
- private[this] 只能在本类中访问,伴生对象中也不能访问。
- protected为受保护权限,Scala中受保护权限比Java中更严格,可以在本类、子类、子类伴生对象中访问,同包无法访问。
- protected[this] 只能在本类、子类中访问(子类伴生对象中无法访问)
- private[包名]增加包访问权限,包名下的其他类也可以使用。
10.3 类和对象
10.3.1 定义类和属性
// 定义类
[private[包名]|protected] class 类名{
类体
// 属性是类的一个组成部分
// 在属性上面增加@BeanProperty注解的话,可以自动生成getter/setter方法,为了兼容一些需要使用到getter/setter方法的场景。
[private] var|val 属性名称 [:属性类型] = 属性值
}
// 可以把类的属性声明在类的后面。
// 这里的private关键字相当于把这个类的构造器私有化,这样只能通过这个类的伴生对象来创建。直接在类后面声明属性的时候修饰符val和var的作用相同。
class Test[private](val name:String,var age:Int){
}
// 在属性上增加@BeanProperty注解可以自动给属性生成getter/setter方法.
class Test{
@BeanProperty
var name:String = _
var age:Int = _
}
// 属性定义为私有属性,可以通过伴生对象进行调用
class Test[private](var name:String,var age:Int){
}
10.3.2 定义方法
def 方法名(参数:参数类型,参数:参数类型,...):返回值 = {}
// 在Scala中,函数的定义和方法的定义类似。
// 方法调用的四种方式
// 方式一:后缀调用法
// 例如: 1.+(10)
// 方式二:中缀调用法
// 例如:1 + 10
// 方式三:花括号调用法 方法只有一个参数,才能使用花括号调用法,通常参数的类型是函数(方法)
// 例如:
// 方式四:无括号调用法 如果方法没有参数,可以省略方法名后面的括号
// 例如:println
10.3.3 创建对象
- val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
- var 修饰对象,可以修改对象的引用和修改对象的属性值。
- 自动推导变量类型不能多态,所以多态需要显示声明。
class Person {
var name: String = "canglaoshi"
}
object Person {
def main(args: Array[String]): Unit = {
//val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
val person = new Person()
person.name = "bobo"
// person = new Person()// 错误的
println(person.name)
}
}
10.3.4 构造器
Scala 类的构造器包括:主构造器和辅助构造器。每个类可以有多个附属构造器,主构造器只有一个,主构造器可以有参,也可以无参,,
class 类名(形参列表) { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个...
}
}
- 辅助构造器,函数的名称 this,可以有多个,编译器通过参数的个数及类型来区分。附属构造器的第一行要么调用主构造器,要么调用其他附属构造器,但是最终必须调用主构造器。
- 主构造器只有一个。主构造器可以有参,也可以无参。
- 在 Scala 的类 class 中,除了属性定义和方法定义其他任何语句都属于主构造器所有。
- 构造器调用其他另外的构造器,要求被调用构造器必须提前声明。
- 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略。
- Scala 类的主构造器函数的形参包括三种类型:未用任何修饰、var 修饰、val 修饰。
- 未用任何修饰符修饰,这个参数就是一个局部变量。
- var 修饰参数,作为类的成员属性使用,可以修改。
- val 修饰参数,作为类只读属性使用,不能修改。
//(1)如果主构造器无参数,小括号可省略
//class Person (){
class Person {
var name: String = _
var age: Int = _
def this(age: Int) {
this()
this.age = age
println("辅助构造器")
}
def this(age: Int, name: String) {
this(age)
this.name = name
}
println("主构造器")
}
object Person {
def main(args: Array[String]): Unit = {
val person2 = new Person(18)
}
}
class Student(name: String, var age: Int, val sex: String) {
}
10.4 封装
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
Scala 中的 public 属性,底层实际为 private,并通过 get 方法和 set 方法对其进行操作。所以 Scala 并不推荐将属性设为 private,再为其设置public 的 get 和 set 方法的做法。但由于很多 Java 框架都利用反射调用 getXXX 和 setXXX 方法,有时候为了和这些框架兼容,也会为 Scala 的属性设置 getXXX 和 setXXX 方法(通过@BeanProperty 注解实现)。
10.5 继承
class 子类名 extends 父类名 { 类体 }
// 子类继承父类的属性和方法
// scala 是单继承
// 子类继承父类的属性和方法
// 继承的调用顺序:父类构造器->子类构造器
class Person(nameParam: String) {
var name = nameParam
var age: Int = _
def this(nameParam: String, ageParam: Int) {
this(nameParam)
this.age = ageParam
println("父类辅助构造器")
}
println("父类主构造器")
}
class Emp(nameParam: String, ageParam: Int) extends Person(nameParam, ageParam) {
var empNo: Int = _
def this(nameParam: String, ageParam: Int, empNoParam: Int) {
this(nameParam, ageParam)
this.empNo = empNoParam
println("子类的辅助构造器")
}
println("子类主构造器")
}
object Test {
def main(args: Array[String]): Unit = {
new Emp("z3", 11,1001)
}
}
10.6 多态
父类的引用指向子类的对象。Scala 中属性和方法都是动态绑定,而 Java 中只有方法为动态绑定,属性为静态绑定。
class Person {
val name: String = "person"
def hello(): Unit = {
println("hello person")
}
}
class Teacher extends Person {
override val name: String = "teacher"
override def hello(): Unit = {
println("hello teacher")
}
}
object Test {
def main(args: Array[String]): Unit = {
val teacher: Teacher = new Teacher()
println(teacher.name) // 调用Teacher类的name.
teacher.hello() //调用Teacher类的hello方法.
val teacher1:Person = new Teacher
println(teacher1.name) // 调用Teacher类的name.
teacher1.hello() //调用Teacher类的hello方法.
}
}
10.7 抽象类
10.7.1 抽象属性和抽象方法
abstract class Person{
val|var name:String // 一个属性没有初始化,就是抽象属性
def hello():String // 只声明而没有实现的方法,就是抽象方法
}
- 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类。
- 重写非抽象方法或者非抽象属性时必须用 override 修饰,重写抽象方法或抽象属性则可以不加 override。
- 子类中调用父类的方法使用 super 关键字。
- 抽象类中即可以有抽象属性和抽象方法,也可以存在非抽象属性和非抽象方法。
- 抽象属性可以用var或者val进行修饰,但是非抽象属性如果用var进行修饰的话,子类不能对这个属性进行重写,不过可以直接使用。
- Scala不支持使用super.父类成员属性,因为在Scala中,子类的实例化时会把父类对象中的成员变量(不含私有变量)复制到子类中。
10.7.2 匿名子类
和 Java 一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。
abstract class Person {
val name: String
def hello(): Unit
}
object Test {
def main(args: Array[String]): Unit = {
val person = new Person {
override val name: String = "teacher"
override def hello(): Unit = println("hello teacher")
}
}
}
10.8 单例对象(伴生对象)
Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象(伴生对象)。若单例对象名与类名一致,则称该单例对象为这个类的伴生对象,这个类为伴生对象的伴生类。伴生类和伴生对象的名称应该完全一致。这个类的所有“静态”内容都可以放置在它的伴生对象中声明。单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
// 伴生对象采用 object 关键字声明
object Person {
var country: String = "China"
}
// 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
class Person {
var name: String = "bobo"
}
object Test {
def main(args: Array[String]): Unit = {
// 伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
println(Person.country)
}
}
10.8.1 apply方法
伴生对象中可以自己定义apply方法,这个方法在底层调用的时候可以省略掉方法名,并且这个方法可以重载。这个方法主要为构建对象来使用的。
object Test {
def main(args: Array[String]): Unit = {
//通过伴生对象的 apply 方法,实现不使用 new 关键字创建对象。
val p1 = Person()
println("p1.name=" + p1.name)
val p2 = Person("bobo")
println("p2.name=" + p2.name)
}
}
//如果想让主构造器变成私有的,可以在()之前加上 private
class Person private(var name: String) {
}
object Person {
def apply(): Person = {
println("apply 空参被调用")
new Person("xx")
}
def apply(name: String): Person = {
println("apply 有参被调用")
new Person(name)
}
}
10.9 特质
Scala 语言中,采用特质 trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。Scala 引入 trait 特征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种补充。在每一个trait中都只存在一个主构造器,并且这个主构造器是无参的。
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了 extends 关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。类和特质之间是继承的关系。
trait 特质名 {
trait 主体
}
class 类名 extends 特质 1 with 特质 2 with 特质 3 …
class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3…
10.9.1 特质案例
trait PersonTrait {
// 特质可以同时拥有抽象方法和具体方法
// 声明属性
var name: String = _
// 抽象属性
var age: Int
// 声明方法
def eat(): Unit = {
println("eat")
}
// 抽象方法
def say(): Unit
}
trait SexTrait {
var sex: String
}
// 一个类可以实现/继承多个特质
// 所有的 Java 接口都可以当做 Scala 特质使用
class Teacher extends PersonTrait with java.io.Serializable {
override def say(): Unit = {
println("say")
}
override var age: Int = _
}
object TestTrait {
def main(args: Array[String]): Unit = {
val teacher = new Teacher
teacher.say()
teacher.eat()
//(4)动态混入:可灵活的扩展类的功能
val t2 = new Teacher with SexTrait {
override var sex: String = "男"
}
//调用混入 trait 的属性
println(t2.sex)
}
}
10.9.2 特质叠加
由于一个类可以混入(mixin)多个 trait,且 trait 中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。
冲突分为以下两种:
1 一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。
abstract class Study{
def study():Unit
}
trait English {
def describe():Unit = {
println("student study english")
}
}
trait Chinese{
def describe ():Unit = {
println("student study chinese ")
}
}
// 当某个类继承了两个具有相同方法的不同特质,那么这个类必须重写这两个特质中相同的方法,不然程序报错
class Student extends Study with English with Chinese{
override def study(): Unit = {
println("student study")
}
// 当重写之后如果调用父类的方法,会以继承的最后一个特质的内容来执行。
override def describe(): Unit = super.describe()
}
object Test{
def main(args:Array[String]):Unit = {
val student = new Student
student.study()
student.describe()
}
}
// 执行结果.........
// student study
// student study chinese
2 一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略。所谓的特质叠加,就是将混入的多个 trait 中的冲突方法叠加起来。
trait Ball {
def describe(): String = {
"ball"
}
}
trait Color extends Ball {
override def describe(): String = {
"blue-" + super.describe()
}
}
trait Category extends Ball {
override def describe(): String = {
"foot-" + super.describe()
}
}
class MyBall extends Category with Color {
override def describe(): String = {
"my ball is a " + super.describe()
}
}
object TestTrait {
def main(args: Array[String]): Unit = {
println(new MyBall().describe())
}
}
// 执行结果
// my ball is a blue-foot-ball
// 当一个类混入多个特质的时候,Scala 会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的 super.describe()调用的实际上是排好序后的下一个特质中的 describe()方法。,排序规则如下:
// MyBall -> Color -> Category -> Ball
上述示例中的 super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyBall 中的 super 指代 Color,Color 中的 super 指代 Category,Category 中的 super指代 Ball。 如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如:super[Category].describe()。 指定调用只能指定自身的父类,父类的父类不能指定。
10.9.3 特质自身类型
自身类型可实现依赖注入的功能。
class User(val name: String, val age: Int)
trait Dao {
def insert(user: User) = {
println("insert into database :" + user.name)
}
}
trait APP {
: Dao =>
def login(user: User): Unit = {
println("login :" + user.name)
insert(user)
}
}
object MyApp extends APP with Dao {
def main(args: Array[String]): Unit = {
login(new User("bobo", 11))
}
}
10.9.4 特质高级特性提前定义
在 Scala 中 trait 的构造器是无参的,如果要对 trait 中的参数进行初始化,需要提前定义。
trait MyTrait{
val name:String
}
class demo extends {override val name = "初始化值"} with MyTrait{}
10.9.5 特质和抽象类的区别
- 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
- 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)。
11 类型检查和转换
- obj.isInstanceOf[T]:判断 obj 是不是 T 类型。
- obj.asInstanceOf[T]:将 obj 类型强转成 T 类型。
- classOf和getClass: 获取对象的类名。
class Person{}
object Person {
def main(args: Array[String]): Unit = {
val person = new Person
// 判断对象是否为某个类型的实例
val bool: Boolean = person.isInstanceOf[Person]
if ( bool ) {
// 将对象转换为某个类型的实例
val p1: Person = person.asInstanceOf[Person]
println(p1)
println(p1.getClass)
}
// 获取类的信息
val pClass: Class[Person] = classOf[Person]
println(pClass)
}
}
12 枚举类和应用类
枚举类:需要继承 Enumeration
应用类:需要继承 App。应用类自带一个main方法,单例对象继承了App特质之后,可以直接运行。
object Test {
def main(args: Array[String]): Unit = {
println(Color.RED)
}
}
// 枚举类
object Color extends Enumeration {
val RED = Value(1, "red")
val YELLOW = Value(2, "yellow")
val BLUE = Value(3, "blue")
}
// 应用类
object Test20 extends App {
println("xxxxxxxxxxx");
}
13 集合
13.1 集合简介
- Scala 的集合有三大类:序列 Seq、集 Set、映射 Map,所有的集合都扩展自 Iterable特质。
- 对于几乎所有的集合类,Scala 都同时提供了可变和不可变的版本,分别位于以下两个包。
- 不可变集合:scala.collection.immutable 。
- 可变集合:scala.collection.mutable。
- Scala 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而不会对原对象进行修改。类似于 Java 中的 String 对象。
- 可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似于 Java 中StringBuilder 对象。
13.1.1 不可变集合
13.1.2 可变集合
13.2 数组
13.2.1 不可变数组
var 数组名:Array[元素类型] = new Array[元素类型](数组大小) // 声明一个空数组,
var 数组名:Array[元素类型] = Array(1,2,3,4,5) //声明数组的时候直接存值
object Test{
def main(args: Array[String]): Unit = {
val arr = new Array[Int](4)
println(arr01.mkString(","))
// 声明的数组没有存值,可以通过下面的方法存值
arr(3) = 10
arr.update(0,1)
// 查询遍历
println(arr.mkString(","))
arr.foreach(println)
// 增加元素,产生新的数组
val ints: Array[Int] = arr :+ 5
println(ints.mkString(","))
println(arr.sum)
println(arr.min)
println(arr.max)
println(arr.sorted.mkString(","))
}
}
13.2.2 可变数组
// 需要引入可变集合包,ArrayBuffer 是有序的集合
import scala.collection.mutable.ArrayBuffer
object Test{
def main(args: Array[String]): Unit = {
val arr = ArrayBuffer[Any](3, 2, 5) //初始化,并且存值
// 新增数组数据
arr.insert(3,8)
arr.append(10)
arr.+=(4)
// 修改数组数据
arr.update(2,9)
arr(2) = 10
// 查询遍历数据
println(arr)
arr.foreach(println)
println(arr.mkString(","))
println(arr.sum)
println(arr.max)
println(arr.min)
println(arr.sorted)
}
}
13.2.3 不可变数组与可变数组的转换
import scala.collection.mutable.ArrayBuffer
object Test{
def main(args: Array[String]): Unit = {
// ArrayBuffer => Array
val arrBuffer = ArrayBuffer[Int](1,2,3)
println(arrBuffer.toArray.getClass)
// Array => ArrayBuffer
val array = Array[Int](1,2,3)
println(array.toBuffer.getClass)
}
}
13.3 列表
13.3.1 不可变列表
List 默认为不可变集合。数据有顺序,可重复。
import scala.collection.immutable
object Test {
def main(args: Array[String]): Unit = {
// TODO 创建一个List
val lst1: List[Int] = List(1,2,3,4,3)
var lst2 = 1::2::3::4::Nil
var lst3 = List()
val lst4 = Nil
//TODO List增加数据 在列表头添加一个数字5组成一个新的列表
lst2 = 5::lst2
lst2 = lst2.+:(5)
//TODO 在列表头添加一个列表组成一个新的列表
lst2 = List(8,9):::lst2
// List(8, 9, 5, 5, 1, 2, 3, 4)
//TODO 查看列表第一个元素
println(lst2.head)
// 8
//TODO 查看列表除去第一个元素的其他元素
println(lst2.tail)
// List(9, 5, 5, 1, 2, 3, 4)
//TODO 检查列表是否为空
println(lst2.isEmpty)
// false
//TODO 列表升序排序
println(lst2.sorted)
// List(1, 2, 3, 4, 5, 5, 8, 9)
//TODO 列表降序排序
println(lst2.sorted.reverse)
// List(9, 8, 5, 5, 4, 3, 2, 1)
//TODO 从列表左边开始取第一个元素
println(lst2.take(1))
// List(8)
//TODO 从列表右边开始取第一个元素
println(lst2.takeRight(1))
// List(4)
//TODO 从列表左边开始删除第一个元素
println(lst2.drop(1))
// List(9, 5, 5, 1, 2, 3, 4)
//TODO 从列表右边开始删除第一个元素
println(lst2.dropRight(1))
// List(8, 9, 5, 5, 1, 2, 3)
// TODO 扁平化操作
val arr = List(List(12.98, 34.09, 45.12), List(1000.09, 23.49), List(123.87, 345.21))
println(arr.flatten)
// List(12.98, 34.09, 45.12, 1000.09, 23.49, 123.87, 345.21)
//TODO 拉链
val a = List(1,2,3,4)
val b = List("a","b","c","d")
val immutableSeq: immutable.Seq[(Int, String)] = a.zip(b)
println(immutableSeq)
// List((1,a), (2,b), (3,c), (4,d))
//TODO 拆分,
val m: immutable.Seq[(Int, String)] = List((1,"a"),(2,"b"),(3,"c"),(4,"d"))
println(m.unzip)
// (List(1, 2, 3, 4),List(a, b, c, d))
// TODO 全集
val c = List(1,2,3,4)
val d = List("a","b","c","d")
println(c.union(d))
// List(1, 2, 3, 4, a, b, c, d)
// TODO 交集
val e = List(1,2,3,4)
val f = List("a","b","c","d")
println(e.intersect(b))
// List()
// TODO 并集
val g = List(1,2,3,4)
val v = List("a","b","c","d")
println(g.diff(v))
// List(1, 2, 3, 4)
}
}
13.3.2 可变列表
import scala.collection.mutable.ListBuffer
object Test {
def main(args: Array[String]): Unit = {
//TODO 创建一个可变集合
val buffer = ListBuffer(1,2,3,4)
//TODO 向集合中添加数据
buffer.+=(5)
buffer.append(6)
buffer.insert(1,2)
//TODO 打印集合数据
buffer.foreach(println)
//TODO 修改数据
buffer(1) = 6
buffer.update(1,7)
//TODO 删除数据
buffer.-(5)
buffer.-=(5)
buffer.remove(5)
}
}
13.4 Set集合
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包。
13.4.1 不可变Set
Set 默认是不可变集合,数据无序,数据不可重复。
object Test {
def main(args: Array[String]): Unit = {
// TODO Set 默认是不可变集合,数据无序
val set = Set(1, 2, 3, 4, 5, 6, 8, 6)
}
}
13.4.2 可变mutable.Set
import scala.collection.mutable
object TestSet {
def main(args: Array[String]): Unit = {
//TODO 创建可变集合
val set = mutable.Set(1,2,3,4,5,6)
//TODO 集合添加元素
set += 8
//TODO 向集合中添加元素,返回一个新的 Set
val ints = set.+(9)
println(ints)
println("set2=" + set)
//TODO 删除数据
set-=(5)
//TODO 打印集合
set.foreach(println)
println(set.mkString(","))
}
}
13.5 Map
13.5.1 不可变Map
object TestMap {
def main(args: Array[String]): Unit = {
//TODO 创建不可变集合 Map
val map = Map( "a"->1, "b"->2, "c"->3 )
//TODO 访问数据
for (elem <- map.keys) {
// 使用 get 访问 map 集合的数据,会返回特殊类型 Option(选项):有值(Some),无值(None)
println(elem + "=" + map.get(elem).get)
}
//TODO 如果 key 不存在,返回 0
println(map.get("d").getOrElse(0))
println(map.getOrElse("d", 0))
//TODO 循环打印
map.foreach((kv)=>{println(kv)})
}
}
13.5.2 可变Map
import scala.collection.mutable
object Test {
def main(args: Array[String]): Unit = {
//TODO 创建可变集合
val map = mutable.Map( "a"->1, "b"->2, "c"->3 )
//TODO 向集合增加数据
map.+=("d"->4)
//TODO 将数值 4 添加到集合,并把集合中原值 1 返回
val maybeInt: Option[Int] = map.put("a", 4)
println(maybeInt.getOrElse(0))
//TODO 删除数据
map.-=("b", "c")
//TODO 修改数据
map.update("d",5)
map("d") = 5
//TODO 打印集合
map.foreach((kv)=>{println(kv)})
}
}
13.6 元组
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组。元组中最大只能有 22 个元素。
object TestTuple {
def main(args: Array[String]): Unit = {
//TODO 声明元组的方式:(元素 1,元素 2,元素 3)
val tuple: (Int, String, Boolean) = (40,"bobo",true)
//TODO 通过元素的顺序进行访问,调用方式:_顺序号
println(tuple._1)
println(tuple._2)
println(tuple._3)
//TODO 通过索引访问数据
println(tuple.productElement(0))
//TODO 通过迭代器访问数据
for (elem <- tuple.productIterator) {
println(elem)
}
}
}
13.7 集合常用函数
object Test {
def main(args: Array[String]): Unit = {
val arr = List(1,2,3,4,5,6)
//TODO foreach 循环遍历
arr.foreach(println)
//TODO map 映射
arr.map(x=> x * x)
//TODO flatMap:扁平化映射
val arr1 = List("1 2 3 4 5 6","7 8 9")
arr1.flatMap(_.split("\\s+")).foreach(println)
//TODO filter:过滤
arr.filter(_ % 2 ==0).foreach(println)
//TODO filterNot:和过滤相反
arr.filterNot(_ % 2 ==0).foreach(println)
//TODO exists :判断是否存在
arr.exists(_==9).booleanValue()
//TODO sortBy sortWith 排序
arr.sortBy(_).foreach(println) // 正序
arr.sortBy(-_).foreach(println) // 倒序
arr.sortWith(_>_).foreach(println)
//TODO groupBy 分组
arr.groupBy(_%2==0).foreach(println)
//TODO reduce:聚合计算
println(arr.reduce(_ + _))
//TODO fold:折叠 和reduce的区别是fold指定了初始化的值并且这是一个柯里化函数
println(arr.fold(0)(_ + _))
}
}
14 模式匹配
14.1 基本语法
模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行声明,当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有 case 都不匹配,那么会执行 case _分支,类似于 Java 中 default 语句。
object TestMatchCase {
def main(args: Array[String]): Unit = {
var a: Int = 10
var b: Int = 20
var operator: Char = 'd'
var result = operator match {
case '+' => a + b
case '-' => a - b
case '*' => a * b
case '/' => a / b
case _ => "illegal"
}
println(result)
}
}
- 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句,若此时没有 case _ 分支,那么会抛出 MatchError。
- 每个 case 中,不需要使用 break 语句,自动中断 case。
- match case 语句可以匹配任何类型,而不只是字面量。
- => 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括。
14.2 模式守卫
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。
object TestMatchGuard {
def main(args: Array[String]): Unit = {
def abs(x: Int) = x match {
case i: Int if i >= 0 => i
case j: Int if j < 0 => -j
case _ => "type illegal"
}
println(abs(-5))
}
}
14.3 模式匹配类型
14.3.1 匹配常量
Scala 中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等。
object TestMatchVal {
def main(args: Array[String]): Unit = {
println(describe(6))
}
def describe(x: Any) = x match {
case 5 => "Int five"
case "hello" => "String hello"
case true => "Boolean true"
case '+' => "Char +"
}
}
14.3.2 匹配类型
需要进行类型判断时,可以使用isInstanceOf[T]和asInstanceOf[T],也可使用模式匹配实现同样的功能。
object TestMatchClass {
def describe(x: Any) = x match {
case i: Int => "Int"
case s: String => "String hello"
case m: List[_] => "List"
case c: Array[Int] => "Array[Int]"
case someThing => "something else " + someThing
}
def main(args: Array[String]): Unit = {
//泛型擦除
println(describe(List(1, 2, 3, 4, 5)))
//数组例外,可保留泛型
println(describe(Array(1, 2, 3, 4, 5, 6)))
println(describe(Array("abc")))
}
}
14.3.3 匹配数组
Scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为 0 的数组。
object TestMatchArray {
def main(args: Array[String]): Unit = {
for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0),
Array(1, 1, 0), Array(1, 1, 0, 1), Array("hello", 90))) {
// 对一个数组集合进行遍历
val result = arr match {
case Array(0) => "0" //匹配 Array(0) 这个数组
case Array(x, y) => x + "," + y //匹配有两个元素的数组,然后将将元素值赋给对应的 x,y
case Array(0, _*) => "以 0 开头的数组" //匹配以 0 开头的数组
case _ => "something else"
}
println("result = " + result)
}
}
}
14.3.4 匹配列表
object TestMatchList {
def main(args: Array[String]): Unit = {
//list 是一个存放 List 集合的数组
//请思考,如果要匹配 List(88) 这样的只含有一个元素的列表,并原值返回.应该怎么写
for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1,
0, 0), List(88))) {
val result = list match {
case List(0) => "0" //匹配 List(0)
case List(x, y) => x + "," + y //匹配有两个元素的 List
case List(0, _*) => "0 ..."
case _ => "something else"
}
println(result)
}
}
}
// ===================================================
object TestMatchList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 2, 5, 6, 7)
list match {
case first :: second :: rest => println(first + "-" +
second + "-" + rest)
case _ => println("something else")
}
}
}
14.3.5 匹配元组
object TestMatchTuple {
def main(args: Array[String]): Unit = {
//对一个元组集合进行遍历
for (tuple <- Array((0, 1), (1, 0), (1, 1), (1, 0, 2))) {
val result = tuple match {
case (0, _) => "0 ..." //是第一个元素是 0 的元组
case (y, 0) => "" + y + "0" // 匹配后一个元素是 0 的元组
case (a, b) => "" + a + " " + b
case _ => "something else" //默认
}
println(result)
}
}
}
14.3.6 匹配对象
14.3.6.1 Option类型
Option类型为了减少程序中出现的空指针异常,是一个抽象类,有两个子类分别为Some和None,通过sealed关键字约束了抽象类的继承范围,Some是有值,None是无值。这两个类都是case class样例类。
//使用方式案例
Object OptionDemo{
def divide(x:Double,y:Double):Option[Double] = {
if(y!=0)
Some(x/y)
else
None
}
def main (args:Array[String]):Unit = {
divide(100,0) match {
case Some(value) = > println(value)
case None => println("无值")
}
}
}
14.3.6.2 提取器
Scala 提取器是一个带有unapply方法的对象。unapply方法算是apply方法的反向操作。unapply接受一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。
class Student private(val name: String, val age: Int)
object Student {
def apply(age: Int, name: String): Student = new Student(name, age)
// 若只提取对象的一个属性,则提取器为 unapply(obj:Obj):Option[T]
// 若提取对象的多个属性,则提取器为 unapply(obj:Obj):Option[(T1,T2,T3…)]
// 若提取对象的可变个属性,则提取器为 unapplySeq(obj:Obj):Option[Seq[T]]
def unapply(arg: Student): Option[(Int, String)] = {
if (arg != null) {
Some((arg.age, arg.name))
} else {
None
}
}
}
14.3.6.3 匹配对象
class User(val name: String, val age: Int)
object User {
def apply(name: String, age: Int): User = new User(name, age)
def unapply(user: User): Option[(String, Int)] = {
if (user == null)
None
else
Some(user.name, user.age)
}
}
object TestMatchUnapply {
def main(args: Array[String]): Unit = {
// 调用了单例对象的apply方法,不需要new关键字
val user: User = User("zhangsan", 11)
val result = user match {
// 默认调用unapply方法,user 作为 unapply 方法的参数,unapply 方法将 user 对象的 name 和 age 属性提取出来,与 User("zhangsan", 11)中的属性值进行匹配。case 中对象的 unapply 方法(提取器)返回 Some,且所有属性均一致,才算匹配成功,属性不一致,或返回 None,则匹配失败。
case User("zhangsan", 11) => "yes"
case _ => "no"
}
println(result)
}
}
14.3.7 匹配样例类
14.3.7.1 样例类
case class Person (name: String, age: Int)
- 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如 apply、unapply、toString、equals、hashCode 和 copy。
- 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。
- 构造器中的每一个参数都成为 val,除非它被显式地声明为 var(不建议这样做)。
14.3.7.2 匹配样例类
case class User(name: String, age: Int)
object TestMatchUnapply {
def main(args: Array[String]): Unit = {
val user: User = User("zhangsan", 11)
val result = user match {
case User("zhangsan", 11) => "yes"
case _ => "no"
}
println(result)
}
}
14.4 变量声明中的模式匹配
case class Person(name: String, age: Int)
object Test {
def main(args: Array[String]): Unit = {
val (x, y) = (1, 2)
println(s"x=$x,y=$y")
val Array(first, second, _*) = Array(1, 7, 2, 9)
println(s"first=$first,second=$second")
val one :: two :: other = List(0, 1, 2, 3)
println(s"$one $two $other")
val Person(name, age) = Person("zhangsan", 16)
println(s"name=$name,age=$age")
}
}
14.5 for表达式中的模式匹配
object Test {
def main(args: Array[String]): Unit = {
val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
for ((k, v) <- map) { //直接将 map 中的 k-v 遍历出来
println(k + " -> " + v) //3 个
}
println("----------------------")
//遍历 value=0 的 k-v ,如果 v 不是 0,过滤
for ((k, 0) <- map) {
println(k + " --> " + 0) // B->0
}
println("----------------------")
//if v == 0 是一个过滤的条件
for ((k, v) <- map if v >= 1) {
println(k + " ---> " + v) // A->1 和 c->33
}
}
}
14.6 偏函数中的模式匹配
14.6.1 偏函数定义
val second: PartialFunction[List[Int], Option[Int]] = {
case x :: y :: _ => Some(y)
}
// 该偏函数的功能是返回输入的 List 集合的第二个元素
// 偏函数就是被包在花括号中,没有match的一组case语句,偏函数是PartialFunction[A,B]的一个实例,A代表输入数值,B代表输出数值
14.6.2 示例
object Test {
def main(args: Array[String]): Unit = {
val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))
// 1. map转换,实现key不变,value2倍
val newList = list.map(tuple => (tuple._1, tuple._2 * 2))
// 2. 用模式匹配对元组元素赋值,实现功能
val newList2 = list.map(
tuple => {
tuple match {
case (word, count) => (word, count * 2)
}
}
)
// 3. 省略lambda表达式的写法,进行简化
val newList3 = list.map {
case (word, count) => (word, count * 2)
}
println(newList3)
// 偏函数的应用,求绝对值
// 对输入数据分为不同的情形:正、负、0
val positiveAbs: PartialFunction[Int, Int] = {
case x if x > 0 => x
}
val negativeAbs: PartialFunction[Int, Int] = {
case x if x < 0 => -x
}
val zeroAbs: PartialFunction[Int, Int] = {
case 0 => 0
}
def abs(x: Int): Int = (positiveAbs orElse negativeAbs orElse zeroAbs) (x)
println(abs(-67))
}
}
15 Scala异常处理
用 throw 关键字,抛出一个异常对象。所有异常都是 Throwable 的子类型。throw 表达式是有类型的,就是 Nothing,因为 Nothing 是所有类型的子类型,所以 throw 表达式可以用在需要类型的地方。
try{
}catch{
case ex:ArithmeticException => println("数学异常")
case e:Exception =>println(""+e.getMessage)
}finally{
}
在 Scala 中,可以使用 throws 注解来声明异常
def main(args: Array[String]): Unit = {
f11()
}
@throws(classOf[NumberFormatException])
def f11()={
"abc".toInt
}
16 隐式转换
当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译。隐式转换是 Scala 特有的功能,主要分为三种转换方式。
16.1 隐式函数
隐式转换可以在不需改任何代码的情况下,扩展某个类的功能。如下示例:
object Test{
def main(args:Array[String]):Unit = {
// 定义隐式函数,通过如下隐式函数的定义可以使Int类型的数据增加myMax和myMin方法
// 隐式转换函数必须使用implicit作为开头,并且函数一定要有返回值类型,隐式转换函数必须书写在object对象中
// 在 Scala 中定义的隐式函数有两种调用方式,一种方式是在当前程序的作用域内,一种就是手动调用通过import关键字进行导入。
implicit def convert(num: Int): MyRichInt = new MyRichInt(num)
println(12.myMax(15))
}
}
class MyRichInt(val self: Int) {
// 自定义比较大小的方法
def myMax(n: Int): Int = if ( n < self ) self else n
def myMin(n: Int): Int = if ( n < self ) n else self
}
16.2 隐式参数
普通方法或者函数中的参数可以通过 implicit 关键字声明为隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。
同一个作用域中,相同类型的隐式值只能有一个。
编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。
隐式参数优先于默认参数。
隐式参数参与的函数是一个柯里化函数,如下的sayHello方法,隐式参数提供的是整个最后一组柯里化的参数列表。implicit关键字是应用到整个参数列表而不是单个参数的。
object Test{
def main(args:Array[String]):Unit = {
// 同一个作用域中,相同类型的隐式值只能有一个。
// 编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。
implicit val str: String = "alice"
implicit val num: Int = 18
def sayHello()(implicit name: String): Unit = {
println("hello, " + name)
}
// 隐式参数优先于默认参数
def sayHi(implicit name: String = "atguigu"): Unit = {
println("hi, " + name)
}
sayHello
sayHi
// 简便写法
def hiAge = {
println("hi, " + implicitly[Int])
}
hiAge
}
}
// 如下的sayHello函数为一个柯里化函数,它的最后一个参数列表应该传递两个隐式参数,一个String类型,一个是Int类型
object Test {
def main(args: Array[String]): Unit = {
import Test2._
def sayHello = {
println(implicitly[String])
println(implicitly[Int])
}
sayHello
}
}
object Test2 {
implicit val str: String = "alice"
implicit val num: Int = 18
}
16.3 隐式类
- 其所带的构造参数有且只能有一个。
- 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶
级的。
object Test {
def main(args: Array[String]): Unit = {
println(12.myMin2(15))
}
// 隐式类其所带的构造参数有且只能有一个。
// 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。
implicit class MyRichInt2(val self: Int) {
def myMax2(n: Int): Int = if (n < self) self else n
def myMin2(n: Int): Int = if (n < self) n else self
}
}
16.4 隐式解析机制
- 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。
- 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象。
object Test {
def main(args: Array[String]): Unit = {
// 首先会在当前代码作用域下查找隐式实体
val teacher = new Teacher()
teacher.eat()
teacher.say()
}
}
trait PersonTrait {}
object PersonTrait {
// 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象。
implicit class Person5(user: Teacher) {
def say(): Unit = {
println("say...")
}
}
}
class Teacher extends PersonTrait {
def eat(): Unit = {
println("eat...")
}
}
17 泛型
默认泛型是非变格式的,泛型的子类和父类之间是没有任何关系的,不能进行泛型的父类引用指向子类。示例如下:
// [T]
class Temp[T]
object Test{
def main(args:Array[String]):Unit = {
val temp:Temp[String] = new Temp[String]
//当泛型中的内容之间存在父子关系的时候,编译报错。
val tempAny:Temp[Any] =temp
}
}
17.1 协变
泛型的子类和父类之间是可以进行多态关系的,父类引用可以指向子类的对象。如下的示例就是正确的。
// [+T]
class Temp[+T]
class Test {
def main(args:Array[String]):Unit = {
val temp:Temp[String] = new Temp[String]
val tempAny:Temp[Any] = temp
}
}
17.2 逆变
泛型的子类和父类之间是有关系的,泛型的子类引用可以指向泛型父类的对象。如下示例:
// [-T]
class Temp[-T]
class Test{
def main(args:Array[String]):Unit = {
val tempAny:Temp[Any] = new Temp[Any]
val temp:Temp[String] = tempAny
}
}
17.3 泛型上下限
在 Scala 中泛型是有上界下界的,限定必须从哪个类继承、或者必须是哪个类的父类。此时,就需要使用到上下界。
// 上界定义语法 使用<: 定义泛型,表示该泛型的参数必须要是该类型本身或者该类型的子类
[T <: 类型]
class Student [T<:Person](a:T,b:T){}
//下界定义语法 使用>: 定义泛型,表示该泛型的参数必须是该类型的父类或者类型本身
[T >: 类型]
class Student [T >: Person](a:T,b:T){}
class Parent {}
class Child extends Parent {}
class SubChild extends Child {}
object Test {
def main(args: Array[String]): Unit = {
test1[Child](new Child)
test1[Child](new SubChild)
test1[SubChild](new SubChild)
println("+++++++++++++++++++++++")
test2[Child](new Child)
test2[Parent](new Parent)
test2[Parent](new Child)
}
// 上限
def test1[A <: Child](a: A): Unit = {
println(a.getClass)
}
// 下限
def test2[A >: Child](a: A): Unit = {
println(a.getClass)
}
}
18 正则表达式
在 Scala 中使用Regex类来定义正则表达式,使用String类的.r方法来定义正则表达式。
object Test{
def main(args:Array[String]):Unit = {
val regex: Regex = """.+@.+\..+""".r
val email:String = "997843456@qq.com"
//获取第一个匹配的
val optionMatch : Option[Regex.Match] = regex.findFirstMatchIn(email)
if (optionMatch.isDefined) println("邮件合法") else println("邮件不合法")
//获取所有匹配的
regex.findAllMatchIn(email)
}
}