- 1、项目创建和环境配置
- 2、Hello world
- 3、变量和数据类型
- 4、运算符
- 5、流程控制
- 6、函数式编程
- 7、 面向对象
- 8 集合
- 9 模式匹配
- 10 异常处理
- 11 隐式转换
- 12 泛型
1、项目创建和环境配置
-
new project
-
确定项目所属组及名称,以及存放目录
-
创建scala目录
-
将scala目录设置为Sources Root
-
添加scala支持
2、Hello world
package chapter01
/*
* object : 关键字,申明一个单例对象(伴生对象)
*/
object HelloWorld {
/*
main 方法:从外部可以直接调用执行的方法
语法:
def 方法名称(参数名称:参数类型):返回值类型={方法体}
*/
def main(s: Array[String]): Unit = {
println("hello world!")
System.out.println("hello world! This is from java.")
}
}
3、变量和数据类型
3.1 注释
scala的注释,同java
-
单行注释
//
-
多行注释
/* */
-
文档注释
/** * */
3.3 代码规范
- 使用tab键实现缩进,默认整体向右边移动,用shift+tab整体向左移动
- 使用ctrl+alt+L来实现格式化代码,让代码美观
- 运算符两边习惯性各加一个空格
- 一行最长不超过80个字符
3.4 变量和常量
-
基本语法
var 变量名[:变量类型]=初始值 var i:int = 1 //变量的设置方式 val 变量名[:变量类型]=初始值 val i:int = 1 //常量的设置方式
-
注意事项
- 变量声明时,类型可以省略,编译器自动推导,即类型推导
- 类型确定后,就不能修改,说明scala是强类型语言
- 变量声明时,必须要有初始值
- 在申明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可改变,val修饰的变量不可改
- var修饰的对象引用可以改变,val修饰的对象则不可改变,但对象的状态(值)却是可以改变的
3.5 标识符
Scala 对各种变量、方法、函数等命名时使用的字符序列称为标识符
- 命名规则
- 以字母或者下划线开头,后接字母、数字、下划线
- 以操作符开头的话,那且只能包含操作符(+-*/#!等)
- 用反引号`...`包括的任意字符串,即便是Scala关键字(共39个)
- package, import , class , object , trait, extends, with , type , for
- private, protected, abstract, sealed, final, implicit, lazy, override
- try, catch, finally, throw
- if, else, match, case, do, while, for, return, yield
- def, val, var
- this, super
- new
- true, false, null
3.6 字符串
-
基本语法
-
字符串,通过+号连接
object Test { def main(args:Array[String]):Unit={ val num:Int = 10 var name:String = "N" print("这儿有"+num+"个"+name) } }
-
printf用法:字符串,通过%传值
object Test { def main(args:Array[String]):Unit={ val num:Int = 10 var name:String = "N" printf("这儿有%d个%s",num,name) } }
-
字符串模板(插值字符串):通过$获取变量值
object Test { def main(args: Array[String]): Unit = { val num: Int = 10 var name: String = "N" println(s"这儿有${num}个${name}") val num1: Double = 2.1234 println(f"这儿有${num1}%2.2f个${name}") // 格式化模板字符串 println(raw"这儿有${num1}%2.2f个${name}") // 所有字符串全部原样输出 println( s""" |select * |from boy t |where t.age < ${num1} |and t.name=\'${name}\' |""".stripMargin) //通过三引号,能够忽略换行 } }
-
3.7 键盘输入及文件读取
-
键盘输入基本语法
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble() ......
import scala.io.StdIn object Test { def main(args: Array[String]): Unit = { print("姓名:") val name = StdIn.readLine() print("年齡:") val age = StdIn.readLine() println(s"${name}的年齡是:${age}!") } }
-
文件读取基本语法
import java.io.{File, PrintWriter} import scala.io.Source object Test { def main(args: Array[String]): Unit = { // 从文件中读取数据 Source.fromFile("src/main/resources/test.txt").foreach(print) // 将数据写入文件 // scala可以使用java的语法 val writer = new PrintWriter(new File("src/main/resources/output.txt")) writer.write("hello world") writer.close() } }
3.8 数据类型
-
图例
-
说明
- java的数据类型为:基本类型(char、byte、short、int、long、float、double、boolean),引用类型(对象类型,包括各种基本类型的包装类(Character、Byte、Short、Integer、Long、Float、Double、Boolean)),这说明:java中基本类型和引用类型没有共同的祖先,即java不是完全的面向对象
- scala中一切数据都是对象,都是Any的子类
- Scala中的数据类型:数值类型(AnyVal)、引用类型(AnyRef),但是不管是数值类型还是引用类型都是对象
- Scala数据类型仍然遵守:低精度的值类型向高精度值类型转换,即自动转换(隐式转换)
- Scala中的StringOps是对java中的String增强
- Unit:对应了java中的void,用于方法返回值的位置,表示方法没有返回值。Unit是一个数据类型,只有一个对象:()。需要说明的是,Void不是数据类型,只是一个关键字
- Null是一个类型,只有一个对象就是null,它是所有引用类型(AnyRef)的子类
- Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,这样可以把抛出的返回值,返回给任何的变量或者函数
3.9 整数类型
-
Byte、Short、Int、Long
数据类型 描述 Byte [1] 8 位有符号补码整数。数值区间为 -128 到 127 Short [2] 16 位有符号补码整数。数值区间为 -32768 到 32767 Int [4] 32 位有符号补码整数。数值区间为 -2147483648 到 2147483647 Long [8] 64 位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 = 2 的(64-1)次方-1 -
各进度数值相加,在不溢出的情况下,可以使用强制转换
3.10 浮点类型
- Float、Double
- Scala 的浮点型常量默认为 Double 型,声明 Float 型常量,须后加‘f’或‘F’。
3.11 字符类型
- Char
- 字符常量是用单引号 ' ' 括起来的单个字符
3.12 布尔类型
- Boolean
- 布尔类型也叫 Boolean 类型,Booolean 类型数据只允许取值 true 和 false
- boolean 类型占 1 个字节
3.13 空类型
-
Unit类型、Null类型、Nothing类型
数据类型 描述 Unit 表示无值,和其他语言中 void 等同。用作不返回任何结果的方法的结果 类型。Unit 只有一个实例值,写成()。 Null null , Null 类型只有一个实例值 null Nothing Nothing 类型在 Scala 的类层级最低端;它是任何其他类型的子类型。 当一个函数,我们确定没有正常的返回值,可以用 Nothing 来指定返回类 型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数 或者变量(兼容性)
3.14 类型转换
-
自动类型转换
当 Scala 程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这 个就是自动类型转换(隐式转换)。
- 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成 精度大的那种数据类型,然后再进行 计算
- 把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动 类型转换
- (byte,short)和 char 之间不会相互自动转换
- byte,short,char 他们三者可以计算,在计算时首先转换为 int 类型
-
强制类型转换
自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上 强制转函数,但可能造成精度降低或溢出
- 使用方式为:obj.toInt
- 将数据由高精度转换为低精度,就需要使用到强制转换
- 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
- 基本类型转 String 类型(语法:将基本类型的值+"" 即可)
- String 类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)
object Test { def main(args: Array[String]): Unit = { val n1:Int = (2.5+2.9).toInt print(n1) } }
4、运算符
5、流程控制
5.1 if-else if-else
5.2 For
object Test {
def main(args: Array[String]): Unit = {
for (i <- 0 to 9){ // 范围遍历 ,前后闭合的范围
//类似于 for(i <- 0.to(9))
println(i+". hello world!")
}
}
}
object Test {
def main(args: Array[String]): Unit = {
for (i <- 0 until 9){ //不包含边界的范围遍历 ,前闭合后开的范围
//类似于 for(i <- 0.to(9))
println(i+". hello world!")
}
}
}
object Test {
def main(args: Array[String]): Unit = {
for (i <- 0 to 9 if i != 5){
/* 循环守卫
循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为 true 则进入循环体内部,为 false 则跳过,类似于 continue。
* */
println(i+". hello world!")
}
}
}
object Test {
def main(args: Array[String]): Unit = {
for (i <- 0 to 9 by 2){
/* 循环步长
* */
println(i+". hello world!")
}
for(i <- 9 to 1 by -1){
println(i)
}
for(i <- 1 to 9 reverse){
println(i)
}
}
}
object Test {
def main(args: Array[String]): Unit = {
for(i <- 1 to 3; j <- 1 to 5){
/* 嵌套for循环
**/
println("i="+i+";"+"j="+j)
}
}
}
object Test {
def main(args: Array[String]): Unit = {
/* 循环返回值
说明:将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字。
* */
val result: immutable.IndexedSeq[Int] = for (i <- 1 to 10) yield {
i
}
print(result)
}
}
5.3 while/do...while
5.4 循环中断
-
java中的写法
public class Test { public static void main(String[] args){ // 通过抛出异常的方式,实现break的效果 try { for(int i=0; i<10; i++){ if(i==3){ //break; throw new RuntimeException(); } System.out.println(i); } } catch (RuntimeException e) { //e.printStackTrace(); } System.out.println("这是循环外面的代码"); } }
-
scala中的写法
import scala.util.control.Breaks object Test { def main(args: Array[String]): Unit = { // 通过抛出异常的方式,实现break的效果 try { for (i <- 1 to 10) { if (i == 5) { throw new RuntimeException() } println(i) } } catch { case e: Exception => // 什么都不做,只是退出循环 } println("这是在循环外") // 使用scala中的Breaks类的break方方,实现异常的抛出和捕获 Breaks.breakable( for (i: Int <- 0 to 10) { if (i == 5) { Breaks.break() } println(i) } ) println("这是在循环外") } }
6、函数式编程
- Scala语言是一个完全面向对象编程语言,体现在万物皆对象,对象的本质是:对数据和行为的一个封装
- Scala语言是一个完全函数式编程语言,体现在万物皆函数,函数的本质是:函数可以当作一个值进行传递
6.1 函数基础
6.1.1 函数基本语法
-
基本语法
6.1.2 函数和方法的区别
- 核心概念
- 为完成某一个功能的程序语句的集合,称为函数
- 类中的函数称为方法
- 说明
- Scala语言可以在任何的语法结构中声明任何的语法
- 函数没有重载和重写的概念;方法可以进行重载和重写
- Scala中函数可以嵌套定义
6.1.3 函数的定义
6.1.4 函数参数
- 可变参数
- 如果参数列表中存在多个参数,那么可变参数一般放置在最后
- 参数默认值,一般将有默认值的参数放置在参数列表的后面
- 带名参数
6.1.5 函数至简原则
-
return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
object Test { def main(args:Array[String]):Unit={ def f(s:String):String = { //return s //等同于如下 s } println(f("hello")) } }
-
如果函数体只有一行代码,可以省略花括号
object Test { def main(args:Array[String]):Unit={ def f(s:String):String = s //省略了花括号{} println(f("hello")) } }
-
返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
object Test { def main(args:Array[String]):Unit={ def f(s:String) = s // :和返回值类型一起省略 println(f("hello")) } }
-
如果有 return,则不能省略返回值类型,必须指定
object Test { def main(args:Array[String]):Unit={ def f(s:String):String = { // 必须指定返回值类型 return s } println(f("hello")) } }
-
如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
-
Scala 如果期望是无返回值类型,可以省略等号
object Test { def main(args:Array[String]):Unit={ // 将无返回值的函数称为过程 def f(s:String){ // 省略等号 println(S) } f("hello") } }
-
如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
-
如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
-
如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
object Test { def main(args:Array[String]):Unit={ def f = (s:String)=>{println(s)} //省略了def后的表达式赋值给f def fc(f:String => Unit):Unit = { // 此中的String => Unit 整体表示为一个函数的声明,其中String作为参数类型,Unit作为返回值类型 f("hello") } fc(f) // 等同于 fc((s:String)=>{println(s)}) } }
6.2 函数高级
6.2.1 匿名函数
-
语法
没有名字的函数就是匿名函数。
(x:Int) => { 函数体 }
- x表示输入参数
- Int表示输入参数类型
- 函数体表示具体代码逻辑
-
传递匿名函数至简原则(当传递的函数有一个参数)
-
参数的类型可以省略,会根据形参进行自动的推导
object Test { def main(args: Array[String]): Unit = { def fc(f: String => Unit): Unit = { f("hello") } fc((s)=>{println(s)}) } }
-
类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参 数超过 1 的永远不能省略圆括号
object Test { def main(args: Array[String]): Unit = { def fc(f: String => Unit): Unit = { f("hello") } fc(s=>{println(s)}) } }
-
匿名函数如果只有一行,则大括号也可以省略
object Test { def main(args: Array[String]): Unit = { def fc(f: String => Unit): Unit = { f("hello") } // 匿名函数如果只有一行,则大括号也可以省略 fc(s=>println(s)) } }
-
如果参数只出现一次,则参数省略且后面参数可以用_代替
object Test { def main(args: Array[String]): Unit = { def fc(f: String => Unit): Unit = { f("hello") } //如果参数只出现一次,则参数省略且后面参数可以用_代替 fc(println(_)) } }
-
-
传递2个参数
object Test { def main(args: Array[String]): Unit = { def fc(f: (Int, Int) => Int): Int = { f(1, 2) } val a = (x:Int,y:Int) => x+y val b = (x:Int,y:Int) => x-y println(fc(a)) println(fc(b)) } }
6.2.2 高阶函数
-
函数作为值进行传递
object Test { def main(args: Array[String]): Unit = { def f(s: String): String = { s } // 函数本体作为值传递 val fc = f _ // 在被调用函数f后面加上 _ ,相当于把函数f当成一个整体,传递给常量fc val fa: String => String = f // 如果明确变量类型,不使用下划线也可以将函数作为整体传递给变量 println(fc) // chapter05.Test$$$Lambda$1/189568618@1fbc7afb println(fc("hello fc")) // hello fc println(fa) // chapter05.Test$$$Lambda$2/495053715@45c8e616 println(fa("hello fa")) // hello fa } }
object Test { def main(args: Array[String]): Unit = { // 定义二元计算函数 def f(op: (Int, Int) => Int, a: Int, b: Int): Int = { op(a, b) } def fc(a: Int, b: Int): Int = { return a + b } // 普通的函数作为参数,进行传递 println(f(fc, 1, 2)) } }
object Test { def main(args: Array[String]): Unit = { // 将函数作为返回值 def f(): String => Unit = { def fc(a: String): Unit = { println(a + " fc") } fc } println(f()("hello")) } }
-
函数传值的应用
object Test { def main(args: Array[String]): Unit = { def f(array:Array[Int],op:Int => Int):Array[Int]={ for(elem <- array) yield op(elem) } def fc(a:Int):Int={ a*2 } // val newArray = f(Array(1,2,3,4),fc:Int=>Int) val newArray = f(Array(1,2,3,4),fc _) // 等同于上面的写法 println(newArray.mkString(" ")) // 传入匿名函数 println(f(Array(1,2,3,4),_*2).mkString(",")) } }
6.2.3 函数柯里化&闭包
-
定义
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和它所处的环境,称为闭包
函数柯里化:把一个参数列表的多个参数,变成多个参数列表
6.2.3 递归
-
定义
一个函数/方法在函数/方法体内又调用了本身,称为递归调用
object Test { def main(args: Array[String]): Unit = { // 常规方式完成递归 def f(n: Int): Int = { if (n == 0) return 1 return f(n - 1) * n } println(f(5)) // 尾递归 def fc(n: Int): Int = { def fcc(n: Int, curResult: Int): Int = { if (n == 0) return curResult fcc(n - 1, curResult * n) } fcc(n, 1) } println(fc(5)) } }
6.2.4 控制抽象
-
值调用:把计算后的值传递过去
object Test { def main(args: Array[String]): Unit = { // 传值参数,传递一个值 def f(a:Int):Unit={ println(" a= "+a) } f(1) } }
-
名调用:把代码传递过去
object Test { def main(args: Array[String]): Unit = { // 传名参数 def f(a: => Int): Unit = { // =>Int,表示代码块,需要返回的值为Int类型 println("获取返回值 a= " + a) } f(1) println("*"*20) f({ println("这是要执行的代码快") return 2 }) } }
6.2.5 惰性加载
-
定义
当函数返回值被申明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会被执行,这种函数称之为惰性函数
object Test { def main(args: Array[String]): Unit = { lazy val result: Int = sum(1, 2) println("执行步骤1 ") println("执行步骤2:result=" + result) } def sum(a: Int, b: Int): Int = { println("执行步骤3") a + b } }
7、 面向对象
7.1 scala包
-
基本语法
package 包名
-
scala包的作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
-
scala的命名
-
命名规则
只能包含数字、字母、下划线、小圆点,但不能用数字开头,也不允许使用关键字
-
命令规范
一般是小写字母+小圆点
com.公司名.项目名.业务模块名
-
-
包说明
scala有两种包管理风格:
-
和java相同,每个源文件一个包(包名和源文件所在路径不要求一致,属于逻辑上的关系),包名用"."进行分隔以表示包的层级关系
-
通过嵌套的风格表示层级关系
package com{ package nuochengze{ package scala{ } } }
- 特点一:一个源文件中可以声明多个package
- 特点二:子包中的类可以直接访问父包中的内容,而无需导包;但父包中的类不能直接访问子包中的内容,需要导包
-
-
包对象
在scala中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有class和object的共享变量,可以被直接访问
-
定义
package object com{ val shareValue = "shate" def shareMethod()={} }
-
说明
- 当使用java的包管理风格时,包对象一般定义在其对应包下,包对象与包名保持一致
- 如采用嵌套方式管理包,则包对象可与包定义在同一文件中,但是要保证包对象 与包声明在同一作用域中
-
-
导包说明
-
在顶部使用 import 导入,在这个文件中的所有类都可以使用
-
在顶部使用 import 导入,在这个文件中的所有类都可以使用
-
通配符导入:import java.util._
-
给类起名:import java.util.{ArrayList=>JL}
-
导入相同包的多个类:import java.util.{HashSet, ArrayList}
-
屏蔽类:import java.util.{ArrayList =>_,_}
-
Scala 中的三个默认导入分别是
import java.lang._ import scala._ import scala.Predef._
-
7.2 类和对象
7.2.1 类定义
-
基本语法
[修饰符] class 类名{ 类体 }
- scala语法中,类并不申明为public,所有这些类都具有公共可见性(即默认就是public)
- 一个scala源文件可以包含多个类
7.2.2 属性
-
定义
属性是类的一个组成部分
-
基本语法
[修饰符] var|val 属性名称 [:类型] = 属性值
- 使用Bean属性(@BeanPropetry),可以自动生成规范的set/get方法
7.3 封装
-
定义
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
7.3.1 访问权限
- Scala 中属性和方法的默认访问权限为 public,但 Scala 中无 public 关键字。
- private 为私有权限,只在类的内部和伴生对象中可用。
- protected 为受保护权限,Scala 中受保护权限比 Java 中更严格,同类、子类可以 访问,同包无法访问。
- private[包名]增加包访问权限,包名下的其他类也可以使用
7.3.2 方法
-
语法
def 方法名(参数列表) [:返回值类型] = { 方法体 }
7.3.3 对象的创建
-
语法
val|var 对象名[:类型] = new 类型()
- val修饰对象,不能改变对象的引用(内存地址),可以改变对象属性的值
- var修饰对象,可以修改对象的引用和修改对象的属性值
7.3.4 构造器
-
说明
scala构造对象需要调用构造方法,并且可以有任意多个构造方法
scala类的构造器包括:主构造器和辅助构造器
-
语法
class 类名(形参列表){ // 主构造器,即为对象本身 // 类体 def this(形参列表){ // 辅助构造器,可以有多个 } }
- 辅助构造器,函数的名称this,可以有多个,编译器通过参数的个数及类型来区分
- 辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法
- 构造器调用其他另外的构造器,要求被调用的构造器必须提前的声明
7.4 继承和多态
-
继承语法
class 子类名 extends 父类名 {类体}
- 子类继承父类的属性和方法
- scala是单继承
- 父类主构造器 -> 子类主构造器
- 主构造器 -> 辅助构造器
-
多态
一个接口的多种实现方式
scala中属性和方法都是动态绑定,而java中只有方法为动态绑定
7.5 抽象类
-
语法
- 定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类
- 定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
- 定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法
-
继承&重写
- 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
- 重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override
- 子类中调用父类的方法使用 super 关键字
- 子类对抽象属性进行实现,父类抽象属性可以用 var 修饰;
- 子类对非抽象属性重写,父类非抽象属性只支持 val 类型,而不支持 var。(var 修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写)
-
匿名子类
可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类
7.6 伴生对象(单例对象)
scala是完全面向对象的语言,为了能够和java语言交互(java中有静态概念,scala中没有静态概念),产生了一种特殊的对象来模拟类对象,该对象称为单例对象。当单例对象与类名一致时,称该单例对象为这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明。
-
单例对象语法
object Person{ ... }
- 单例对象采用object关键字声明
- 单例对象对应的类称为伴生类,伴生对象的名称应该和伴生类名一致
- 单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问
-
其他
- 如果想让主构造器变成私有的,可以在()之前加上private
7.7 特质
-
说明
- scala语言中,采用特质trait(特征)来代替接口的概念,即,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明
- scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质
-
特质基本语法
-
说明
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,在使用时采用extends关键字,如果有多个特质或存在父类,需要采用with关键字连接
-
基本语法
-
没有父类
class 类名 extends 特质1 with 特质2 with 特质3...
-
有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3...
-
-
其他说明
-
类和特质的关系:使用继承的关系
-
当一个类去继承特质时,第一个连接词是extends,后面是with
-
如果一个类在同时继承特质和父类时,应该把父类写在extends后
-
一个类可以混入(mixin)多个特质
-
所有的java接口都可以当作scala特质使用
-
动态混入:创建对象时混入trait,而无需使类混入该trait;如果混入的trait中有未实现的方法,则需要实现
object Test { def main(args: Array[String]): Unit = { val f = new testClass() with testTrait{ override var value1: String = "这是重写的特质中的属性" override def f1(): Unit = { println("这是重写的特质中的方法") } } f.f1() println(f.value1) } trait testTrait{ // 定义抽象属性和非抽象属性 var value1:String var value2:String= _ // 定义抽象方法和非抽象方法 def f1():Unit def f2():Unit={ println("f2——:特质中的非抽象方法") } } class testClass(){ println("testClass——:测试类") } }
-
-
-
特质叠加
-
当由一个类混入多个trait时,如果混入的特质具有相同的方法(方法名,参数列表,返回值均相同),则会出现继承冲突问题
-
倒三角问题
一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且 两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法
-
菱形问题
一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且 两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala 采用了特质叠加的策略
// 所谓的特质叠加,就是将混入的多个 trait 中的冲突方法叠加起来 object Test { def main(args: Array[String]): Unit = { println(new traitSub().f()) } trait traitC { def f(): String = { "traitC" } } trait traitA extends traitC { override def f(): String = { "traitA-" + super.f() } } trait traitB extends traitC { override def f(): String = "traitB-" + super.f() } class traitSub extends traitA with traitB { override def f(): String = "Test value: " + super.f() } }
-
-
特质叠加执行顺序
当一个类混入多个特质的时候,scala 会对所有的特质及其父特质按照一定的顺序进行 排序,而此案例中的 super.f()调用的实际上是排好序后的下一个特质中的f() 方法
-
如果想要调用某个指定的混入特质中的方法,可以增加约束:super[]
例如:super[traitA].f()
-
-
特质自身类型
-
说明
自身类型可实现依赖注入的功能
class A(val x:String){ } trait B{ def f(a:A):Unit = { println(s"B--->>>println ${a.x}") } } trait C{ _: B => // 自身类型可实现依赖注入的功能 def fc(a:A):Unit={ println(s"C--->>>println ${a.x}") f(a) } } object Test extends C with B{ def main(args: Array[String]): Unit = { fc(new A("hello")) } }
-
-
特质总结
- 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类
- 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数, 而特质不行(无参构造)
7.8 类型检测和转换
- 说明
- obj.isInstanceOf[T]:判断 obj 是不是 T 类型
- obj.asInstanceOf[T]:将 obj 强转成 T 类型
- obj.asInstanceOf[T]:将 obj 强转成 T 类型
7.9 Type定义新类型
-
说明
使用type关键字可以定义新的数据的数据类型名称,本质上就是类型的一个别名
object Test { def main(args: Array[String]): Unit = { type S=String var v:S="abc" def test():S="xyz" } }
8 集合
- Scala的集合共有三大类:序列Seq、集Set、映射Map,这些集合都扩展自Iterable
- 对于几乎所有的集合类,Scala都同时提供了可变和不可变两种版本,所属的包为以下两种:
- 不可变集合:scala.collection.immutable
- 可变集合:scala.collection.mutable
- scala不可变集合指该集合对象不可修改,每次修改就会返回一个新对象,其不会对原对象进行修改,类比于java中的String对象
- scala可变集合指可以直接对原对象进行修改,而不会返回新的对象,类比于java中的StringBuilder对象
-
不可变集合继承图
- IndexedSeq通过索引来查找和定位
- LinearSeq是线型的,有头部和尾部的概念
-
可变集合继承图
8.1 数组
8.1.1 不可变数组
-
第一种方式定义数组
val arr = new Array[Int](10)
- new 是关键字
- [Int] 指定可以存放的数据类型,如果希望存放任意数据类型,可以指定Any
- (10) 表示数组的大小,确定后就不可以变化
object Test { def main(args: Array[String]): Unit = { val arr = new Array[Int](10) for( a <- 0 until arr.length){ arr(a) = a } println(arr.mkString(" ")) } }
-
第二种方式定义数组
val arr = Array(1,2,3)
- 在定义数组时,直接调用伴生对象赋初始值
- 本质上使用apply方法创建的数组对象
object Test { def main(args: Array[String]): Unit = { val arr = new Array[Int](10) for( a <- 0 until arr.length){ arr(a) = a } // 1.用下标遍历数组 for( a <- arr.indices){ // def indices: Range = 0 until length print(arr(a)+" ") } println() // 2.用增强for循环遍历 for(a <- arr){ print(a+" ") } println() // 3.用迭代器遍历 val iter = arr.iterator while(iter.hasNext){ print(iter.next+" ") } println() // 4.调用foreach方法,遍历 arr.foreach((x:Int) => print(x+" ")) println() // 5.以特定的连接条件,输出整体的元素 println(arr.mkString(",")) println("*"*20) // 6 往数组后面添加元素(其实是新生成一个数组替代) val newArr = arr.:+(66) println("arr: "+ arr.mkString(" ")) println("newArr: "+ newArr.mkString(" ")) // 7 往数组前面添加元素 println("往数组前面添加元素"+"*"*20) val newArr2 = arr.+:(77) println("arr: "+ arr.mkString(" ")) println("newArr2: "+ newArr2.mkString(" ")) val newArr3 = newArr2 :+ 66 // 简写,运算符的本质是方法 println("newArr3: "+ newArr3.mkString(" ")) val newArr4 = 55 +: 66 +: newArr2 //简写,运算符的本质是方法 println("newArr4: "+ newArr4.mkString(" ")) } }
8.1.2 可变数组
-
定义变长数组
var arr = ArrayBuffer[Any](1,2,3)
- [Any] 表示存放任意数据类型
- (1,2,3) 表示初始化的三个元素
- ArrayBuffer需要引入scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer object Test { def main(args: Array[String]): Unit = { // 1.创建数组 val arr1 = ArrayBuffer[Int]() var arr2 = ArrayBuffer(1,2,3) println(arr1) println(arr2) // 2.往可变数组尾部添加元素 arr1.+=(1) arr1 += 2 arr1.append(3) println(arr1) // 3.往可变数组头部添加元素 arr1.+=:(0) -1 +=: arr1 arr1.prepend(-2) println(arr1) // 4.在任意位置添加数组 arr1.insert(0,6,6) println(arr1) // 5.insertAll,prependAll,appendAll,在任意、最前面、最后面添加集合 // 6 删除元素 arr1.remove(1,3) // 按位置和数量删除 println(arr1) arr1 -= 3 // 按元素删除 println(arr1) } }
8.1.3 不可变数组与可变数组的转换
-
说明
- arr.toBuffer 不可变数组转换为可变数组
- arr.toArray 可变数组转换为不可变数组
- 无论是arr.toBuffer还是arr.toArray,都将返回结果,但其调用者本身不会发生变化
import scala.collection.mutable.ArrayBuffer object Test { def main(args: Array[String]): Unit = { // 从可变数组转换为不可变数组 var arr1 = ArrayBuffer(1,2,3) val newArr1 = arr1.toArray println(arr1) println(newArr1) // 从不可遍数组传唤为可变数组 val arr2 = Array(1,2,3) var newArr2 = arr2.toBuffer println(arr2) println(newArr2) } }
8.1.4 多维数组
-
多维数组定义
val arrDim2 = Array.ofDim[Int](3,3)
- Array.ofDim[Int] 声明数组类型
- (3,3) 表示3行3列
object Test { def main(args: Array[String]): Unit = { val arrDim2 = Array.ofDim[Int](3,3) arrDim2(1)(2) = 2 arrDim2(2)(2) = 1 for(i <- 0 until arrDim2.length;j <- 0 until arrDim2(i).length){ println(arrDim2(i)(j)) } println("*"*20) for(i <- arrDim2.indices;j <- arrDim2(i).indices){ print(arrDim2(i)(j) + "\t") if(j == arrDim2(i).size-1){println()} } println("*"*20) arrDim2.foreach(_.foreach(println)) } }
8.2 列表List
8.2.1 不可变List
object Test {
def main(args: Array[String]): Unit = {
// 1.创建一个List
val list1 = List(1,2,3,4) // List通过伴生对象创建
println(list1)
// 2.访问和遍历数组
println(list1(1))
list1.foreach(println)
println("*"*20)
// 3.添加元素
val list2 = list1.+:(10) // 在头部添加元素
val list3 = 10 +: list1
println(list2)
println(list3)
println("*"*20)
val list4 = list1.:+(11) // 在尾部添加元素
val list5 = list1 :+ 11
println(list4)
println(list5)
val list6 = list1.::(51) // 通过 :: 方法在头部添加元素
println(list6)
val list7 = Nil.::(66) // 通过Nil快速创建列表
println(list7)
val list8 = 1 :: 2 :: 3 :: Nil // 由右到左创建列表
println(list8)
// 3 合并两个列表
println("*"*20)
val list9 = list4 :: list5
println(list4)
println(list5)
println(list9)
println("*"*20)
val list10 = list4 ::: list5 // 通过 ::: 方法在头部添加元素,且将列表扁平化处理,得到一个新的列表
val list11 = list4 ++ list5 // ::: 等同于 ++
println(list10)
println(list11)
}
}
8.2.2 可变List
import scala.collection.mutable.ListBuffer
object Test {
def main(args: Array[String]): Unit = {
val list1 = new ListBuffer[Int]()
val list2 = ListBuffer(1,2,3)
println(list1)
println(list2)
}
}
8.3 集合
-
说明
默认情况下,scala使用的是不可变集合,想使用可变集合,需要引用scala.collection.mutable.Set包
8.3.1 不可变Set
object Test {
def main(args: Array[String]): Unit = {
// 1.创建set
val set1 = Set(1, 2, 3, 4, 4, 5, 2, 3)
println(set1)
// 2.添加元素
val set2 = set1.+(20) // 集合在内是无序的,且唯一
val set3 = set1 + 20
println(set2)
println(set3)
// 3.合并集合
val set4 = Set(1,2,4,4,5,6,7,8)
val set5 = set3 ++ set4
println(set5)
// 4 删除元素
val set6 = set5 - 1
println(set6)
}
}
8.3.2 可变Set
import scala.collection.mutable
object Test {
def main(args: Array[String]): Unit = {
// 1.创建set
val set1 = mutable.Set(1, 2, 3, 4, 4, 5, 2, 3)
println(set1)
println("*"*20)
// 2.添加元素
val set2 = set1 + 6
println(set1)
println(set2)
println("*"*20)
set1 += 33
println(set1)
println("*"*20)
set1.add(44)
println(set1)
// 3 删除元素
println("*"*20)
set1 -= 2
println(set1)
println("*"*20)
set1.remove(4)
println(set1)
// 4.合并集合
println("*"*20)
val set3 = mutable.Set(12,2,3,9)
println(set1)
println(set3)
val set4 = set1 ++ set3
println(set1)
println(set3)
println(set4)
println("*"*20)
println(set1)
println(set3)
set3 ++= set1 // set1集合不便,set3集合值改变
println(set1)
println(set3)
}
}
8.4 Map集合
-
说明
scala中的Map和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射
8.4.1 不可变Map
object Test {
def main(args: Array[String]): Unit = {
// 1.创建Map
val map1 = Map("a" -> 12, "b" -> 22,"c" -> 11)
println(map1)
// 2.遍历元素
map1.foreach(println)
println("*"*20)
map1.foreach((kv:(String,Int)) => println(kv)) // 等效于 map1.foreach(println)
// 3 取map中所有的key 或者value
println("*"*20)
for(key <- map1.keys){
println(s"${key}--->>>${map1.get(key)}")
}
// 4.访问某一个key的value值
println("*"*20)
println(map1("a"))
println(map1.get("a").get)
println(map1.get("d"))
println(map1.getOrElse("d",0))
// 防止无值导致的Some(x)=None,而无get方法,从而造成异常
}}
8.4.2 可变Map
import scala.collection.mutable
object Test {
def main(args: Array[String]): Unit = {
// 1.创建Map
val map1 = mutable.Map("a" -> 12, "b" -> 22,"c" -> 11)
println(map1)
// 2.添加元素
map1.put("d",10)
map1.put("e",222)
map1.+=(("f",666)) // 作用同 map1.put("d",10)
println(map1)
// 3.删除元素
println("*"*20)
map1.remove("c")
println(map1.getOrElse("c",0))
println(map1)
map1.-=("d")
println(map1)
// 4.修改元素
println("*"*20)
map1.update("m",222) // 当map中无对应值时,表现为插入操作
println(map1)
map1.update("a",999) // 当map中有对应值时,表现为更新操作
println(map1)
// 5.合并Map
val map2 = Map("x" -> 12, "y" -> 22,"z" -> 11)
map1 ++= map2
println(map1)
}
}
8.5 元组
-
定义
元组可以理解为一个容器,可以存放各种相同或不同类型的数据。即元组,将多个无关的数据封装为一个整体
-
说明
- 元组中最大只能有22个元素
- 声明元组的方式 :(元素1,元素2,元素3,...)
- Map中的键值对其实就是元组,只不过元组的元素个数为2,称之为对偶
import scala.collection.mutable object Test { def main(args: Array[String]): Unit = { // 1.创建元组 val tuple1: (String, Int, Char, Boolean) = ("hello", 100, 'a', true) println(tuple1) // 2.访问数据 println(tuple1._1) // 通过._num的方式访问 println(tuple1.productElement(0)) // 通过productElement方式访问,索引以0开头 // 3.遍历元组 println("*" * 20) for (elem <- tuple1.productIterator) { println(elem) } // 4.嵌套元组 println("*" * 20) val multuple = (10, 22, (12, "a"), "hello") println(multuple._3._1) // 5. 对偶 println("*" * 20) val map = Map("a"->1, "b"->2, "c"->3) val map1 = Map(("a",1), ("b",2), ("c",3)) map.foreach(tuple=>{println(tuple._1 + "=" + tuple._2)}) } }
8.6 集合常用函数
8.6.1 通用属性和操作
object Test {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
val set = Set(1,2,3)
// 1.获取线性集合长度
println(list.length)
println(list.size)
// 2.获取非线性集合长度
println(set.size)
// 3.遍历
list.foreach(print)
println()
set.foreach(print)
println()
for(elem <- list){
print(elem)
}
println()
for(elem <- set){
print(elem)
}
println()
// 4.迭代器
for(elem <- list.iterator){
print(elem)
}
println("*"*20)
// 5.生成字符串
println(list)
println(set)
println(list.mkString(","))
println("*"*20)
// 6.是否包含某个元素
println(list.contains(23))
println(set.contains(2))
}
}
8.6.2 衍生集合操作
object Test {
def main(args: Array[String]): Unit = {
val list1 = List(1, 2, 3, 4)
val list2 = List(3,4,243,53)
// 1.获取集合的头
println(list1.head)
// 2.获取集合的尾(这里的尾,指的是除了头的部分)
println(list1.tail) // List(2, 3, 4)
// 3.获取集合的最后一个数据
println(list1.last)
// 4.获取集合初始数据(不包含最后一个数据的剩余部分)
println(list1.init)
// 5 反转集合
println(list1.reverse)
// 6 取前(后)n个元素
println(list1.take(3)) // 取前3个元素
println(list1.takeRight(2)) // 取后2个元素
// 7.去掉前(后)n个元素
println(list1.drop(2)) // 去掉前2个元素
println(list1.dropRight(3)) // 去掉后3个元素
println("*"*20)
// 8.并集
val union_ = list1.union(list2)
println("union_: "+ union_)
println(list1:::list2) // union等于将集合加在一起
println(list1 ++ list2)
println(Set(1,2,3,4).union(Set(3,4,5))) // 如果是set做并集操作,会去重
// 9.交集
println("交集"+"*"*20)
println(list1.intersect(list2))
// 10.差集
println("差集"+"*"*20)
println(list1.diff(list2))
println(list2.diff(list1))
// 11.拉链
println("拉链"+"*"*20)
println(list1.zip(list2)) // list1中的元素占_v1
println(list2.zip(list1)) // list2中的元素占_v1
// 12.滑窗
println("滑窗"+"*"*20)
for(elem <- list1.sliding(3)){ // 确定滑窗的范围
println(elem)
}
println("-"*20)
for(elem <- list1.sliding(3,2)){ // 确定滑窗的范围+滑动的距离
println(elem)
}
println("-"*20)
for(elem <- list1.sliding(3,3)){ // 确定滑窗的范围等于滑动的距离时,又称为滚动窗口
println(elem)
}
}
}
8.6.3 集合计算简单函数
object Test {
def main(args: Array[String]): Unit = {
val list1 = List(13, 6, 3, 4)
val list2 = List(("a",22),("b",2),("c",4),("d",7),("e",9))
// 1.求和
println(list1.sum)
// 2.求乘积
println(list1.product)
// 3.求最大值
println(list1.max)
// 3.1 maxBy
println("maxBy"+"*"*20)
println(list2.max) // 默认按照._v1进行取值
println(list2.maxBy((tuple:(String,Int)) => tuple._2 )) // 通过传递匿名函数,按._v2进行取值
println(list2.maxBy(_._2)) // 简写
// 4.求最小值
println("minBy"+"*"*20)
println(list1.min)
println(list2.min)
println(list2.minBy(_._2)) // 简写
// 5.排序
// 5.1 sorted 从小到大排序
println("sorted"+"*"*20)
println(list1.sorted)
// 5.2 sorted.reverse 从大到小排序
println("sorted.reverse"+"*"*20)
println(list1.sorted.reverse)
// 5.3 sorted.implicit 传入隐式参数
println("sorted.implicit"+"*"*20)
println(list1.sorted(Ordering[Int].reverse))
// 5.4 sorted.implicit 传入隐式参数
println("sorted.implicit"+"*"*20)
println(list1.sorted(Ordering[Int].reverse))
// 5.5 sortBy
println("sortBy"+"*"*20)
println(list2.sortBy(_._2))
println(list2.sortBy(_._2)(Ordering[Int].reverse)) //逆序排列
// 5.6 sortWith
println("sortWith"+"*"*20)
println(list1.sortWith((a:Int,b:Int) => {a<b} )) // 从小到大排列
println(list1.sortWith(_<_)) // 从小到大排列 简化写法
println(list1.sortWith(_>_)) // 从大到小排列 简化写法
}
}
8.6.4 集合计算高级函数
8.6.4.1 集合转换操作(Map类)
object Test {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
// 1.过滤:遍历一个集合,并从中获取满足制定条件的元素组成一个新的集合
println("filter" + "*" * 20)
println(list.filter((elem: Int) => {
elem % 2 == 0
})) // 获取列表中的偶数
println(list.filter(_ % 2 == 1)) // 获取奇数
// 2.map: 转换/映射(map) 将集合中的每一个元素映射到某一个函数
println("map" + "*" * 20)
println(list.map((elem: Int) => {
elem * 2
})) // 集合中的元素值*2
println(list.map(_ * 2)) // 集合中的元素值*2 简化
// 3.扁平化 flatten
println("扁平化" + "*" * 20)
val nestedList = List(List(1, 2, 43), List(2, 2, 4), List(2, 1), List(2, 4, 6, 8, 3))
println(nestedList)
println(nestedList(0) ::: nestedList(1) ::: nestedList(2) ::: nestedList(3)) // 扩展性较差
println(nestedList.flatten)
// 4.扁平映射
println("扁平映射" + "*" * 20)
val list2: List[String] = List("hello world", "hello java", "hello scala")
println(list2)
val splitList2: List[Array[String]] = list2.map(elem => elem.split(" ")) //分词
println(splitList2.flatten) // 打散
println(list2.flatMap(elem => elem.split(" "))) //flatMap结合了以上的分词和打散
// 5.分组
println("分组" + "*" * 20)
println(list.groupBy(_ % 2))
/* groupBy对列表list进行分组,其中groupBy(f)内函数f的返回值作为map的key,
待归类的词作为map的value
// Map(1 -> List(1, 3, 5, 7, 9), 0 -> List(2, 4, 6, 8))
*/
println(list.groupBy((data:Int)=>{if(data%2==0) "偶数" else "奇数"}))
}
}
8.6.4.2 集合转换操作(Reduce)
object Test {
def main(args: Array[String]): Unit = {
val list = List(1,2,3,4)
// 1.reduce 简化(归约):通过指定的逻辑将集合中的数据进行聚合,从而减少数据,获取最终的结果
println("归约"+"*"*20)
println(list.reduce(_+_)) // 功能同sum
println(list.reduceLeft(_+_)) // reduce的底层是reduceLeft
println(list.reduceRight(_+_))
// 2.reduceLeft和reduceRight的区别
val list2 = List(1,2,3,4)
println(list2.reduceLeft(_-_)) // 结果为-8
println(list2.reduceRight(_-_)) // 结果为-2 ,源码为递归操作:(1-(2-(3-4)))
}
}
8.6.4.3 集合转换操作(Fold)
object Test {
def main(args: Array[String]): Unit = {
// 1.Fold
val list = List(1,2,3,4)
println(list.fold(10)(_+_)) // 执行步骤:10 +1+2+3+4
println(list.foldLeft(10)(_-_)) //fold的底层是foldLeft 执行步骤:10 -1-2-3-4
println(list.foldRight(10)(_-_)) // 执行步骤:1-(2-(3-(4-10)))
/*
fold方法使用了函数柯里化,存在两个参数列表
第一个参数列表为: 零值(初始值)
第二个参数列表为: 简化规则
* */
}
}
8.6.5 案例
-
案例1:Map的简单累加
import scala.collection.mutable object Test { def main(args: Array[String]): Unit = { val map1 = mutable.Map("a" ->1 ,"b" -> 2, "c" -> 3) val map2 = mutable.Map("a" ->4 ,"b" -> 5, "c" -> 6,"d" -> 2) val map3 = map2.foldLeft(map1){ (map,kv) =>{ val k = kv._1 val v = kv._2 map(k) = map.getOrElse(k,0)+v map } } println(map3) } }
-
案例2:普通的worldCount案例
object Test { def main(args: Array[String]): Unit = { val stringList = List( "hello", "hello world", "hello scala", "hello spark from scala", "hello flink from scala" ) // 1.对字符串进行切分,得到一个打散所有单词的列表 val wordList: List[String] = stringList.map(_.split(" ")).flatten println(wordList) // 2.相同的单词进行分组 val wordGroup: Map[String, List[String]] = wordList.groupBy((word) => { word }) println(wordGroup) // 3.对分组之后的List取长度,得到每个单词的个数 val wordCount: Map[String, Int] = wordGroup.map((kv) => { (kv._1, kv._2.length) }) println(wordGroup) // 4.将map转换为list,并排序(用sortBy排序)取前3 println(wordCount.toList) println("用sortBy排序"+"*"*20) val wordCountSortedBy: List[(String, Int)] = wordCount.toList.sortBy((tuple: (String, Int)) => { tuple._2 })(Ordering[Int].reverse) println(wordCountSortedBy) println(wordCountSortedBy.take(3)) // 5.将map转换为list,并排序(用sortWith排序)取前3 println("用sortWith排序"+"*"*20) val wordCountSortedWith: List[(String, Int)] = wordCount.toList.sortWith(_._2 > _._2) println(wordCountSortedWith) println(wordCountSortedWith.take(3)) } }
-
案例3:复杂的wordCount案例
object Test { def main(args: Array[String]): Unit = { val tupleList: List[(String, Int)] = List( ("hello", 3), ("hello world", 2), ("hello scala", 4), ("hello spark from scala", 1), ("hello flink from scala", 5) ) // 思路1:直接展开 println("思路1:直接展开" + "*" * 20) val newStringList: List[String] = tupleList.map((kv) => { (kv._1.trim + " ") * kv._2 }) println(newStringList) val result1: List[(String, Int)] = newStringList.flatMap( _.split(" ") ).groupBy((word) => { word }).map((kv) => { (kv._1, kv._2.length) }).toList.sortBy( (tuple) => { tuple._2 } )(Ordering[Int].reverse).take(3) println(result1) // 思路2:直接基于预统计的结果进行转换 println("思路2:直接基于预统计的结果进行转换" + "*" * 20) println( tupleList .flatMap((tuple) => { val strings = tuple._1.split(" ") strings.map((k) => { (k, tuple._2) }) }).groupBy((tuple) => { tuple._1 }) .map((tupleList) => { val v2Sum: Int = tupleList._2.map((tuple) => { tuple._2 }).sum (tupleList._1, v2Sum) }) .toList .sortBy((tuple) => { tuple._2 })(Ordering[Int].reverse) .take(3) ) } }
8.7 队列
-
说明
scala提供了队列(Queue)的数据结构,其特点是先进先出。
进队和出队的方式分别为enqueue和dequeue
-
可变队列
import scala.collection.mutable object Test { def main(args: Array[String]): Unit = { val mutableQueue = new mutable.Queue[String]() // 1.进入队列 mutableQueue.enqueue("a","b","c") println(mutableQueue) // 2.出队列 println("result: "+ mutableQueue.dequeue()) // 返回值为出队列的值 println(mutableQueue) } }
-
不可变队列
import scala.collection.immutable object Test { def main(args: Array[String]): Unit = { val immutableQueue = immutable.Queue[String]("a","b","c") // 不可变队列使用伴生对象创建值 // 1.进入队列 println("进入队列"+"*"*20) println(immutableQueue) val newImmutableQueue = immutableQueue.enqueue("d") // 其本身的值不会改变,需要用新变量取接受 println(immutableQueue) println(newImmutableQueue) // 2.出队列 println("出队列"+"*"*20) val result = immutableQueue.dequeue //返回值类型 (String, Queue[String]) println(immutableQueue) // 其本身的值不会改变 println(result) } }
-
8.8 并行集合
-
说明
scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的计算
import scala.collection.immutable import scala.collection.parallel.immutable.ParSeq object Test { def main(args: Array[String]): Unit = { val result1: immutable.IndexedSeq[Long] = (1 to 10).map( (x) => { Thread.currentThread.getId } ) println(result1) // Vector(1, 1, 1, 1, 1, 1, 1, 1, 1, 1) val result2: ParSeq[Long] = (1 to 10).par.map( (x) => { Thread.currentThread.getId } ) println(result2) // ParVector(10, 10, 12, 14, 14, 11, 11, 13, 13, 13) } }
9 模式匹配
-
定义
op match{ case a => a case b => b case _ => "Nothing" }
- 采用match关键字声明,每个分支采用case关键字申明
- 当需要匹配时,会从第一个case分支开始,逐次匹配;如果所有case都不匹配,执行case _分支,其类似于java中default语句,当没有case _分支时,会抛出MatchError
-
=>
后面的代码块,直到下一个case语句之前的代码都是作为一个整体执行,可以用{}括起来,也可以省略
object Test { def main(args: Array[String]): Unit = { val a = A("aaa",111) val result = a match{ case A("aaa", 111) => a.str+" "+a.value case _ => "Nothing" } println(result) } }
9.1 模式守卫
-
说明
如果想要表达式匹配某个范围的数据,就需要在模式匹配中增加条件守卫
object Test { def main(args: Array[String]): Unit = { val a = 1; a match { case a if a>=0 => println("a大于0") case a if a<0 => println("a小于0") case _ => println("Nothing") } } }
9.2 模式匹配类型
9.2.1 匹配常量
- Scala 中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等
9.2.2 匹配类型
-
Scala 中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等
object Test { def main(args: Array[String]): Unit = { def des(x:Any) : Unit = { x match{ case x: Int => println("Int " + x) case x: String => println("String " + x) case x: List[Int] => println("List " + x) case x: Array[Int] => println("Array " + x.mkString(",")) case x => println("Nothing "+ x) } } des(List(1,2,3,4)) des(List("hello","world")) // 针对列表类型,虽然定义了具体的泛型,但是存在类型擦除,即并不关心是Int,还是String,都会按默认类型输出 des(Array("hello","world")) des(Array(1,2,3)) //数组例外,可保留泛型 // 输出结果 // List List(1, 2, 3, 4) // List List(hello, world) // Nothing [Ljava.lang.String;@7dc36524 // Array 1,2,3 } }
9.2.3 匹配数组、列表、元组
-
scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素 为 0 的数组
object Test { def main(args: Array[String]): Unit = { for (arr <- Array( Array(0), Array(1, 0), Array(0, 0, 1), Array(2, 1, 2), Array("hello", 90) )) { arr match { case Array(0) => println("匹配 Array(0) 这个数组") case Array(x, y) => println("匹配两个元素的数组 " +x+" " + y) // 匹配有两个元素的数组,然后将将元素值赋给对应的 x,y case Array(0, _*) => println("匹配以 0 开头数组") case _ => println("Something else") } } /* 匹配 Array(0) 这个数组 匹配两个元素的数组 1 0 匹配以 0 开头数组 Something else 匹配两个元素的数组 hello 90 * */ } }
9.2.4 匹配对象及样例类
-
匹配对象
object Test { def main(args: Array[String]): Unit = { val a:A = A("h",1) a match{ case A("h",1) => println("Yes") case _ => println("No") } } } class A(val v1:String,val v2:Int) object A { def apply(v1: String, v2: Int): A = { new A(v1, v2) } def unapply(a:A):Option[(String,Int)] = { if(a == null){ None }else{ Some(a.v1,a.v2) } } }
-
val a:A = A("h",1)
这条语句在执行时,实际调用的A伴生对象中的apply方法,因此不用new关键字也能构造出相对应的对象 -
当
A("h",1)
在case后时,会默认调用unapply方法(对象提取器),unapply方法将A对象的属性提取出来 -
若只提取对象的一个属性,则提取器为 unapply(obj:Obj):Option[T]
若提取对象的多个属性,则提取器为 unapply(obj:Obj):Option[(T1,T2,T3…)]
若提取对象的多个属性,则提取器为 unapply(obj:Obj):Option[(T1,T2,T3…)]
-
-
样例类
-
语法
case class A(val v1:String)
- 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中 自动提供了一些常用的方法,如 apply、unapply、toString、equals、hashCode 和 copy
- 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例 类可以直接使用模式匹配,而无需自己实现 unapply 方法
- 构造器中的每一个参数都成为 val,除非它被显式地声明为 var(不建议这样做)
object Test { def main(args: Array[String]): Unit = { val a:A = A("h",1) a match{ case A("h",1) => println("Yes") case _ => println("No") } } } case class A(val v1:String,val v2:Int)
-
10 异常处理
-
语法
object Test{ def main(args:Array[String]):Unit={ try{ var n = 10/0 }catch{ case ex:ArithmeticException => {println("发生算术异常")} case ex:Exception => {println("发生了异常")} }finally{ println("终处理") } } }
-
Scala没有编译异常的概念,异常都是在运行的时候捕获处理
-
按编程贵方,需要把越具体的异常写在前面
-
finally 子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作
-
用throw关键字,抛出异常
def test():Nothing ={ throw new Exception("异常") }
- 所有的异常都是Throwable的子类型
- throw表达式是有类型的,即Nothing
- Nothing是所有类型的子类型,故throw表达式可以用在需要类型的地方
-
在scala中,可以使用throws注解来声明异常
object Test{ def main(args:Array[String]):Unit={ f() } @throws(classOf[NumberFormatException]) def f() = { "abc".toInt } }
-
11 隐式转换
-
说明
当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译
11.1 隐式函数
-
说明
隐式转换可以在不需要修改任何代码的情况下,扩展某个类的功能
-
隐式函数
class MyRichInt(val arg:Int){ def valueMax(i:Int):Int = { if(arg<i) i else arg } } object Test{ def main(args:Array[String]):Unit={ implicit def convert(arg:Int):MyRichInt = { new MyRichInt(arg) } /* 当想调用对象功能时,如果编译错误,那么编译器会尝试在当前作用域范围内查找能调用对应功能的转换规则,这个调用过程是由编译器完成的,所以称之为隐式转换。也称之为自动转换 */ println(2.valueMax(15)) } }
- 使用Implicit关键字声明的函数称为隐式函数
-
隐式类
- 使用implict声明隐式类
- 隐式类所带的构造参数有且只能有一个
- 隐式类必须定义在"类"或"伴生对象"或"包对象"里,即隐式类不能是*的
object Test{ implicit class MyRichInt(val arg:Int){ def valueMax(i:Int):Int = { if(arg<i) i else arg } } def main(args:Array[String]):Unit={ println(2.valueMax(15)) } }
11.2 隐式参数
-
说明
普通方法或者函数中的参数可以通过implicit关键字声明为隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值
object Test{ def main(args:Array[String]):Unit={ implicit val name:String = "world" implicit val i:Int = 10 def sayHello()(implicit arg:String):Unit ={ println("hello "+arg) } def sayInt()(implicit arg:Int):Unit={ println("hi "+arg) } // sayHello的简单写法 def sayHello_():Unit={ println("hello "+ implicitly[String]) } sayHello() sayHello_() sayInt() } }
- 同一个作用域中,相同类型的隐式值只能有一个
- 编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关
- 隐式参数优先于默认值
- 使用的implicitly,并不是一个申明,底层是一个带泛型的函数
11.3 隐式解析机制
- 说明
- 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一 般是这种情况)
- 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。 类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象
12 泛型
12.1 协变和逆变
-
语法
class MyList[+T]{//协变} class MyList[-T]{//逆变} class MyList[T]{ //不变}
- 协变:当Son 是 Father 的子类时,MyList在类型声明时,其实并入父子类关系,当为协变泛型时,MyList[Son] 也作为 MyList[Father]的“子类”
- 逆变:当Son 是 Father 的子类时,MyList在类型声明时,其实并入父子类关系,当为逆变泛型时,MyList[Son] 作为 MyList[Father]的“父类”
- 不变:当Son 是 Father 的子类时,MyList在类型声明时,其实并入父子类关系,当为不变泛型时,MyList[Father]与 MyList[Son]“无父子关系”
12.2 泛型上下限
-
语法
def f[A : B](a: A) = println(a) //等同于 def f[A](a:A)(implicit arg:B[A])=println(a)
-
说明
上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过 implicitly[Ordering[A]] 获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误