Scala学习笔记
-
- 为什么学习Scala
- Scala 语言诞生小故事
- Scala 语言的特点
- 在 idea 中安装 scala 插件的步骤
- Scala语法的简单说明
- 函数的定义
- Scala 程序开发注意事项
- Scala 源码的查看的关联
- 代码规范
- 注释
- 变量和常量
- 标识符的命名规范
- 字符串输出
- 键盘输入
- Source读取数据
- 从URL或者其他源读取数据
- 读取二进制文件
- 往文件内写入数据
- 读写文件
- 数据类型
- 值类型转换
- 演示取余%操作
- 演示按位与&操作
- equals和eq和==的区别
- 运算符的本质
- 运算符优先级
- if-else
- for循环
- 循环中断
- 函数式编程
- 函数柯里化&闭包
- 递归和尾递归
- 控制抽象
- 传名参数
- 自定义While循环
- 惰性加载
- Scala包
- 包对象
- 导包说明
- 类和对象
- 访问权限
- 构造器
- 构造器参数
- 多态
- 抽象类
- 匿名子类
- 伴生类和伴生对象
- 单例设计模式
- 特质trait
- 叠加特质
- 钻石问题的特质叠加
- 自身类型
- 类型检查和转换
- 枚举类和应用类
- 集合
- 不可变列表List
- 可变列表
- 列表的常用操作
- 不可变Set集合
- 可变Set集合
- 不可变Map集合
- 可变Map
- Map基本操作
- 元组
- 迭代器(iterator)
- 集合常用函数
- 衍生集合
- 集合计算高级函数
- 合并Map
- 普通WordCount案例
- 复杂WordCount案例
- 队列
- 并行集合
- Option类型
- 模式匹配
- 序列化和反序列化
- 正则表达式
- 异常
- 隐式转换
- 泛型
基于黑马程序员深圳中心,尚硅谷做的笔记…
为什么学习Scala
- Spark新一代内存级大数据计算框架,是大数据的的主要内容.
- Spark就是使用Scala编写的.因此为了更好的学习Spark,需要掌握Scala语言.
- Scala 是一门多范式(编程的方式[
面向对象编程,函数式编程
])的编程语言
Scala 语言诞生小故事
- 创始人
马丁·奥德斯基
(Martin Odersky)是编译器及编程的狂热爱好者,长时间的编程之后,希望发明一 种语言,能够让写程序这样的基础工作变得高效,简单。所以当接触到 JAVA 语言后,对 JAVA 这门便携式,运行在网络,且存在垃圾回收的语言产生了极大的兴趣,所以决定将函数式编程语言的特点融合到 JAVA 中,由此发明 了两种语言(Pizza & Scala) - Pizza 和 Scala 极大地推动了 Java 编程语言的发展
- jdk5.0 的
泛型,增强for 循环, 自动类型转换
等,都是从 Pizza 引入的新特性。 - jdk8.0 的
类型推断,Lambda 表达式
就是从 scala 引入的特性。 - 且现在主流 JVM 的 javac 编译器就是马丁·奥德斯基编写出来的。Jdk5.0 和Jdk8.0 的编译器就是马丁·奥德 斯基写的,因此马丁·奥德斯基 一个人的战斗力抵得上一个 Java 开发团队。
Scala 语言的特点
Scala 是一门以 java 虚拟机(JVM)为运行环境并将面向对象和函数式编程
的最佳特性结合在一起 的静态类型编程语言。
- Scala 是一门多范式的编程语言,Scala 支持面向对象和函数式编程
- Scala 源代码(.scala)会被编译成 Java 字节码(.class),然后运行于 JVM 之上,并可以调用现有的 Java 类库, 实现两种语言的无缝对接。
- Scala 在设计时,马丁·奥德斯基 是参考了 Java 的设计思想,可以说 Scala 是源于 java,同时马丁·奥德 斯基 也加入了自己的思想,将函数式编程语言的特点融合到 JAVA 中
在 idea 中安装 scala 插件的步骤
方法1: file->settings->pulgins ->搜索Scala->install
方法2: scala 插件点击此链接去官网下载插件
或者是file->settings->pulgins ->搜索Scala->Plugin homepage->下载idea对应的版本
然后file->settings->pulgins ->搜索Scala->设置->install Plugin from Disk
,选择你安装的插件->OK->重启idea
1) main->(右击)New->Directory->目录名(如scala)
2) scala->Mark Directory as->Sources Root
3) 右击项目名->Add Framework Support->勾选Scala->OK
(在默认情况下,idea 不支持 scala,需要引入 scala 的相关的开发包.所以需要此步.如果第一次选择,没有看到 scala 的包,请点击 configure ,选择 scala 的主目录即可。)
此时就可以在scala下创建包或Scala Class了
Scala语法的简单说明
//1. object 是一个关键字,表示一个伴生对象
//2. 如果该文件只出现了一个 object HelloScala 就会在编译后产生两个.class 文件
//3. 第 1 个文件是 HelloScala.class 这个表示他的伴生类,但是空的.
//4. 第 2 个文件是 HelloScala$.class 对应的是 object HelloScala,但是本质是调用它对应的一个静态属性
object HelloScala {
// 1. def 表示一个方法或者一个函数
// 2. main 表示入口
// 3. args: Array[String] 表示形参,args 是形参名,Array[String]是形参类型,表示一个String类型的Array 数组
// 4. :Unit 表示返回值类型为 Unit ,等价于 java 的 void
// 5. = 表示 后面是函数体/方法体, 它还有返回值类型推导的作用
def main(args: Array[String]):Unit = {
// 表示的是输出,类似System.out.println("hello, scala")
// 在 scala 语句后不需要带像Java一样带;体现简洁
println("hello, scala")
}
}
为什么一个object HelloScala会产生两个.class文件
因为在Java中static不是面向对象的,希望在Scala中一切都是对象.所以把静态的内容和非静态的内容分开,
HelloScala类的非静态内容
(属性,方法)放在class HelloScala{ }
HelloScala类的静态内容
(属性,方法)放在object HelloScala{ }
函数的定义
def 函数名 ([参数名: 参数类型], …)[[: 返回值类型] =] { 语句… //完成某个功能 return 返回值 }
- 函数声明关键字为 def (definition)
- [参数名: 参数类型], …:表示函数的输入(就是参数列表), 可以没有。 如果有,多个参数使用逗号间隔
- 函数中的语句:表示为了实现某一功能代码块
- 函数可以有返回值,也可以没有
- 返回值形式 1: //
: 返回值类型 =
表示有返回值,并且指定了返回值的类型 - 返回值形式 2: //
=
, 表示返回值类型,使用类型推导 - 返回值形式 3: //
空的
,表示没有返回值,即使有 return 也不生效 - 如果没有 return ,默认以执行到最后一行的结果作为返回值
Scala 程序开发注意事项
- Scala 源文件以 “.scala" 为扩展名。
- Scala 程序的执行入口是 main()函数。
- Scala 语言严格区分大小写。
- Scala 方法由一条条语句构成,每个语句后不需要分号(Scala 语言会在每行后自动加分号),这也体现出 Scala 的简洁性。
- 如果在同一行有多条语句,除了最后一条语句不需要分号,其它语句需要分号(尽量一行就写一条语句)
Scala 源码的查看的关联
光标点击需要查看源码的地方(如类或方法等),然后按Ctrl+b
点击右上角的Attach Sources
代码规范
使用Ctrl+Alt+L
来进行格式化
运算符两边尽量各加一个空格.比如1 + 2 * 3
注释
//
单行注释
/*多行注释*/
/**文档注释*/
文档注释: 注释内容可以被工具 scaladoc 所解析,生成一套以网页文件形式体现该程序的说明文档
package com.hyj.chapter02
object DocumentCommen{
def main(args: Array[String]): Unit = { }
/**
* @param n1 传入一个整数 n1
* @param n2 传入一个整数 n2
* @return 返回一个整数值
*/
def sub(n1:Int,n2:Int): Int = {
return n1 - n2
}
}
生成文档注释的指令:(注:第一行)
文档:
变量和常量
变量相当于内存中一个数据存储空间的表示,通过变量名可以访问到变量(值).
scala 要求变量声明时必须要初始化.
var 变量名[:变量类型]=变量值
val 常量名[:常量类型]=常量值
声明变量时,类型可以省略,因为类型可以推断出来
能够使用val,就不要使用var,因为val是线程安全的,效率高
var 修饰的对象引用可以改变,val 修饰的则不可改变,但是对象的状态(值)却是可以改变的
标识符的命名规范
- 以字母或者下划线
_
开头,后接字母数字或下划线 - 以操作符开头,且只包含操作符
+ - * / # !等
- 用反引号包括的任意字符,即使是Scala关键字也可以
字符串输出
object InputDemo {
def main(args: Array[String]): Unit = {
val name:String ="张三"
var age:Int =20
var money:Double =1820.5626
//字符串,通过+来连接
println(name+"今年"+age+"岁了") //张三今年20岁了
//printf用法,通过%来传值
printf("%s今年得了%.2f元压岁钱
",name,money) //张三今年得了1820.56元压岁钱
//将字符串复制3次
println(name*3) //张三张三张三
//字符串模板(插值字符串):通过$获取变量值
println(s"name=${name} age=${age+1} money=${money}") //name=张三 age=21 money=1820.5626
//格式化模板字符串
println(f"${name}今年得了${money}%8.2f压岁钱") //张三今年得了 1820.56压岁钱
println(raw"${name}今年得了${money}%8.2f压岁钱") //张三今年得了1820.5626%8.2f压岁钱
//三引号表示字符串,保持多行字符串的原格式输出
var sql=
s"""
|select *
| from
|people
| where
|name=${name}
| and
| age<${age}
|""".stripMargin
println(sql)
}
}
键盘输入
import scala.io.StdIn
object InputDemo {
def main(args: Array[String]): Unit = {
print("请输入你的名字:>")
//接收用户从键盘输入的数据(字符串)
val name=StdIn.readLine()
print("请输入你的年龄:>")
val age=StdIn.readInt()
print("请输入你的工资:>")
val sal=StdIn.readDouble()
println("名字:"+name+" 年龄"+age+" 工资"+sal)
}
}
Source读取数据
在Scala语言的Source单例对象中, 提供了一些非常便捷的方法, 从而使开发者可以快速的从指定数据源(文本文件, URL地址等)中获取数据, 在使用Source单例对象之前, 需要先导包, 即import scala.io.Source
-
按行读取
我们可以以行为单位, 来读取数据源中的数据, 返回值是一个迭代器类型的对象
. 然后通过toArray, toList
方法, 将这些数据放到数组或者列表中即可.package testpackage
import scala.io.{BufferedSource, Source}
object TestDemo2 {
def main(args: Array[String]): Unit = {
//1.获取数据源文件对象
val source: BufferedSource = Source.fromFile(“src/main/resources/a.txt”)
//2.以行为单位读取数据
val lines: Iterator[String] = source.getLines()
//3.将读取到的数据封装到列表中
val list:List[String] = lines.toList
println(list) //List(好好学习 天天向上!, hello Hadoop Zookeeper, hello Flume Spark Flink, hello Sqoop HBase)
//4.关闭Source对象
source.close()
}
} -
按字符读取
Scala还提供了以字符为单位读取数据
这种方式, 这种用法类似于迭代器, 读取数据之后, 我们可以通过hasNext(),next()
方法, 灵活的获取数据.package testpackage
import scala.io.{BufferedSource, Source}
object TestDemo2 {
def main(args: Array[String]): Unit = {
//1.获取数据源文件对象
val source: BufferedSource = Source.fromFile(“src/main/resources/a.txt”)
/* //2.以字符为单位读取数据
val iter: BufferedIterator[Char] = source.buffered
while (iter.hasNext){
print(iter.next())
} */
//如果文件不是很大,我们可以直接把它读取到一个字符串中
val str: String = source.mkString
println(str)
//4.关闭Source对象
source.close()
}
} -
读取词法单元和数字
所谓的词法单元指的是以特定符号间隔开
的字符串, 如果数据源文件中的数据都是数字形式的字符串
, 我们可以很方便的从文件中直接获取这些数据, 例如:package testpackage
import scala.io.{BufferedSource, Source}
object TestDemo2 {
def main(args: Array[String]): Unit = {
//1.获取数据源文件对象
val source: BufferedSource = Source.fromFile(“src/main/resources/b.txt”)
//2.读取词法单元
// s表示空白字符(空格, ,
,
等) s+表示1个或多个空白字符
val arr: Array[String] = source.mkString.split(“\s+”)
//3.将字符串转换成对应的整数
val ints: Array[Int] = arr.map(_.toInt)
for (num <- ints) {
print(num + " ")
}
//4.关闭Source对象
source.close()
}
}
从URL或者其他源读取数据
Scala中提供了一种方式, 可以让我们直接从指定的URL路径, 或者其他源(例如: 特定的字符串)中直接读取数据。
-
从URL地址中读取数据
//1.获取数据源文件对象 val source: BufferedSource = Source.fromURL("https://www.csdn.net") //将数据封装到字符串中并打印 println(source.mkString) //3.关闭Source对象 source.close()
-
从其他源读取数据
val source: Source = Source.fromString("hello scala
hello kafka!!!")
val iter: Iterator[String] = source.getLines()
while (iter.hasNext){
println(iter.next())
}
读取二进制文件
Scala没有提供读取二进制文件的方法, 我们需要通过Java类库来实现.
-
需求
读取图片数据, 并将读取到的字节数打印到控制台上.package testpackage
import java.io.{File, FileInputStream}
object TestDemo2 {
def main(args: Array[String]): Unit = {
//创建File对象,关联数据源文件
val file: File = new File(“src/main/resources/img.png”)
//创建字节输入流,用来读取数据
val stream: FileInputStream = new FileInputStream(file)
//创建字节数组,用来存储读取到的数据(字节)
val bytes: Array[Byte] = new ArrayByte
//开始读取,将读取到的数据存储到字节数组中,并返回读取到的有效字节数
val i: Int = stream.read(bytes)
println(“读取到的有效字节数:”+i) //190294
println(“字节数组的长度:”+bytes.length) //190294
//关闭字节输入流
stream.close()
}
}
往文件内写入数据
package testpackage
import java.io.FileOutputStream
object TestDemo2 {
def main(args: Array[String]): Unit = {
//写入数据到文本文件
//创建字节输出流对象,关联目的地文件
val fileOutputStream: FileOutputStream = new FileOutputStream("./data/c.txt")
fileOutputStream.write("hello scala
".getBytes)
fileOutputStream.write("hello flink
".getBytes)
//关闭字节输出流
fileOutputStream.close()
}
}
读写文件
object InputDemo {
def main(args: Array[String]): Unit = {
//从文件中读取数据
Source.fromFile("src/main/resources/testfile").foreach(print)
//将数据写入文件
val writer = new PrintWriter(new File("src/main/resources/output.txt1"))
writer.write("helloscala
") //不具备换行功能
writer.write("spark")
writer.close()
}
}
数据类型
Java的基本数据类型不是真正意义上的对象,即使后面产生了基本类型的包装类,但是仍然存在基本数据类型,所以Java语言并不是真正的面向对象.
Scala 与 Java 有着相同的数据类型,在 Scala 中数据类型都是对象,也就是说 scala 没有 java 中的原生类型
在Scala中一切皆是对象,都是Any的子类
Scala数据类型分为两大类AnyVal(数值类型,可以理解为Java的基本数据类型)
和AnyRef(引用类型)
1)Any是所有类的的根类型,即所有类的父类(基类)
2)Null
类型只有一个实例null
,它是AnyRef的子类
3)Nothing类型
是所有类的子类,它可以作为没有正常返回值
的方法的返回类型,比如:
object InputDemo {
def main(args: Array[String]): Unit = {
def test(n: Int): Any = {
if (n == 1)
return n
else
throw new NullPointerException
}
print(test(1))
//表示fun1方法就是没有正常的返回值,专门用于返回异常
def fun1():Nothing={
throw new Exception("异常发生")
}
}
}
4)Unit
类型只有一个实例()
Byte 1字节
,Short 2字节
,Int 4字节
,Long 8字节
Float 4字节
,Double 8字节
Char 16位无符号Unicode字符
,String 字符串
Boolean 1字节
Unit 表示无值,和void等同,用作不反回任何结果的方法的结果类型
值类型转换
隐式转换
1)当Scala程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数据类型,这个就是自动类型转换(隐式转换)
2)有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型
,然后再进行计算.
3)(Byte,Short)和Char之间不会相互自动转换
4)Byte,Short,Char它们三者可以计算,在计算时首先转换为Int类型
5)自动提升原则:表达式结果的类型自动提升为操作数中最大的类型
6) 当我们把精度(容量)大 的数据类型赋值给精度(容量)小 的数据类型时,就会报错,反之就会进行自动类型 转换
强制类型转换
object DataTypeDemo {
def main(args: Array[String]): Unit = {
val l:Long =10
//将a强制转换为Int类型
println(l.toInt) //10
val d:Double=1.2345678
//将d强制转换为Byte类型
println(d.toByte) //1
//格式化输出,保留小数点2位,并进行四舍五入
println("d="+d.formatted("%.2f")) //d=1.23
var num : Int =10 * 3.5.toInt + 6 * 1.5.toInt
var num2 :Int=(10 * 3.5 + 6 * 1.5).toInt //44.0
println(num) //36
println(num2) //44
val a:Int =12
val c:Char = a.toChar //必须强转,否则报错
val c2:Char =12
//var char1: Char = 97 + 1 //报错,因为运算就会有类型,Int=>Char
var char2: Char = 98 //没有运算,编译器只判断范围有没有越界
}
}
演示取余%操作
当对一个数取模时,可以等价 a%b=a-a/b*b
,和java 的取模规则 一样
注意:Scala 中没有++、–操作符,需要通过+=、-=来实现同样的效果
object InputDemo {
def main(args: Array[String]): Unit = {
println(10 % 3) //10 - 10/3 * 3 = 10 - 3 * 3 =1
println(-10 % 3) // -10 - -10/3 *3 = -10 + 3 * 3 =-1
println(10 % -3) // 10 - 10/-3 * -3 =10 - -3 * -3 =10 - 9=1
println(-10 % -3) // -10 - -10/-3 * -3 = -10 - 3 * -3 = -10 + 9 =-1
}
}
演示按位与&操作
按位与&
,按位或|
,按位异或^
-
所有的运算都是以二进制补码进行的
-
二进制的最高位是符号位:0表示正数,1表示负数
-
正数的原码,反码,补码都一样
-
负数的反码=它的原码符号位不变,其它位按位取反
-
负数的补码=它的反码+1
-
0的反码,补码都是0
-
在计算机运算的时候,都是以
补码的方式来运算
的,但是返回结果时,其实会将补码转成原码
object InputDemo {
def main(args: Array[String]): Unit = {
val b: Int = 129
// 129的原码:00000000 00000000 00000000 10000001
//反码: 00000000 00000000 00000000 10000001
// 补码: 00000000 00000000 00000000 10000001
// 正数的原码反码补码一样
// 截取最后一个字节,Byte 得到补码10000001
// 10000000 反码
// 11111111 原码
print(b.toByte) //-127
val a: Int = 19 //10011
val c: Int = 45 //101101
// 10011
// 101101
// 000001
print(a & c)//1
}
}
equals和eq和==的区别
-
equals()比较的是值是否相等
-
eq()比较的是地址是否相等
-
==,如果比较的对象是null,调用的是eq()方法; 如果比较的对象不是null,调用的是equals()方法
object InputDemo {
def main(args: Array[String]): Unit = {
val str1:String=“scala”
val str2:String=new String(“scala”)
println(str1==str2) //true
println(str1.equals(str2)) //true
println(str1.eq(str2)) //false
}
}object TestDemo {
def main(args:Array[String]):Unit={
val str1:String=null
val str2:String=“scala”
val str3:String=null
println(str1str2) //false
println(str1str3) //true
}
}
运算符的本质
object InputDemo {
def main(args: Array[String]): Unit = {
val n1: Int = 25
val n2: Int = 2
//n1这个Int型的对象调用+方法,.点号可以省略,因为只有一个参数,所以()也可以省略
println(n1.+(n2))
println(n1+n2)
println(n1.*(n2))
println(n1*n2)
println(12.34.*(2))
println(7.5.toInt)
//.点号可以省略,用空格代替
println(7.5 toInt)
}
}
运算符优先级
if-else
scala 中,没有三目,使用 if – else 替代
if–else是有返回值的,具体返回 结果的值取决于满足条件的代码体的最后一行内容
object TestDemo {
def main(args:Array[String]):Unit={
val x:Int= 30;
if( x == 10 ){
println("x的值为 10")
}else if( x == 20 ){
println("x的值为 20")
}else if( x == 30 ){
println("x的值为 30")
}else{
println("无法判断x的值")
}
}
}
object ifElseDemo {
def main(args: Array[String]): Unit = {
print("请输入你的年龄:>")
var age:Int =StdIn.readInt()
val str:String =if(age>18) "成年" else "未成年" //else if
println(age+"->"+str)
}
}
for循环
object forDemo {
def main(args: Array[String]): Unit = {
//i将会从1-10循环,前后闭合,(包括1和10)
for(i <- 1 to 10){
print(i+" ")
}
//上面的本质是1(对象)调用to()方法
for (i <- 1.to(10)) {
print(i+" ")
}
for (i <- 1 to 10 by 3) { //步长为3
print(i + " ") //1 4 7 10
}
for (i <- 1 to 10 reverse) {
print(i + " ") //10 9 8 7 6 5 4 3 2 1
}
println()
//利用伴生对象创建一个集合
val list:List[String] =List("北京","广州","上海","深圳","杭州")
//遍历集合
for(item <- list){
print(item+" ")
}
println()
//i将会从1-9循环,前闭后开 [1,10)
for(i <- 1 until 10){
print(i+" ")
}
println()
//循环守卫即循环保护式,保护式为true则进入循环体内部,为false则跳过,类似于contince
for(i <- 1 to 10 if i!=2){
print(i+" ")
}
println()
//上面的代码和下面等价
for(i <- 1 to 10){
if(i!=2){
print(i+" ")
}
}
println()
val list2:List[String] =List("bj","sh","hz","sz","wh")
//集合进行遍历,使用循环守卫
for(item <- list2 if item.startsWith("s")){
print(item+" ")
}
println()
//引入变量
for(i <- 1 to 10;j=4-i){
print(j+" ")
}
println()
//上面的代码等价于↓
for(i <- 1 to 10){
val j=4-i
print(j+" ")
}
println()
//嵌套循环
for(i <- 1 to 3;j <- 1 to 3){
println("i="+i+" j="+j)
}
//上面的代码等价于↓
for(i <- 1 to 3){
for(j <- 1 to 3){
println("i="+i+" j="+j)
}
}
/* {}和()对于for表达式来说都可以
for推导式有一个不成文的约定,当for推导式仅包含单一表达式使用圆括号,当其包含多个表达式时使用大括号,
当使用{}来换行写表达式时,分号就不用写了*/
//方式一:
for{i <- 1 to 3 ;j <- 1 to 3}{
println("i="+i+" j="+j)
}
//方式二:
for{i <- 1 to 3
j <- 1 to 3}{
println("i="+i+" j="+j)
}
//循环返回值:将遍历过程中处理的结果返回到一个新的Vector集合中,使用yield关键字,yield可以写代码块
val res=for(i <- 1 to 5) yield i*2
println(res) //Vector(2, 4, 6, 8, 10)
val res2=for(i <- 1 to 10) yield {
if(i%2==0){
i
}
}
println(res2) //Vector((), 2, (), 4, (), 6, (), 8, (), 10)
//遍历1-10(不包括10),步长为3,Range是一个集合
for(i <- Range(1,10,3)){
print(i+" ")
}
}
}
循环中断
Scala内置控制结构去掉了break和contince.
import scala.util.control.Breaks.{break, breakable}
object breakDemo {
def main(args: Array[String]): Unit = {
//在for循环中仍然使用break()中断循环
breakable{ //表示接下来的代码是可中断的
for(i <- 1 to 10000){
if(i==20){
break()
}
print(i+" ")
}
}
}
}
函数式编程
函数和方法的区别
- 为完成某一功能的程序语句的集合称为函数
- 类中的函数称之为方法
- 函数没有重载和重写的概念,方法可以进行重载和重写
- Scala中函数可以嵌套定义
函数参数
-
可变参数
-
如果参数列表中存在多个参数,那么
可变参数一般放置在最后
-
参数默认值,一般将有默认值的参数放置在参数列表的后面
object InputDemo {
def main(args: Array[String]): Unit = {
def fun1(str: String*): Unit = {
println(str)
}
fun1(“aaa”, “bbb”) //WrappedArray(aaa, bbb) 此时返回一个集合
fun1() //List()
def fun2(str1: String, str2: String*): Unit = {
println(“str1=” + str1 + " str2=" + str2)
}
//str1=aaa str2=WrappedArray(bbb, ccc, ddd)
fun2(“aaa”, “bbb”, “ccc”, “ddd”)
//参数默认值
def fun3(str1: String = “hyj”): Unit = {
println(str1)
}fun3() //hyj fun3("huo") //huo def fun4(name: String, age: Int): Unit = { println("name=" + name + " age=" + age) } //带名传参 fun4(age = 20, name = "hyj") //name=hyj age=20
}
}
函数至简原则
-
return可以省略,Scala会使用函数体最后一行代码作为返回值
-
如果函数体只有一行代码,可以省略花括号
-
返回值类型如果能够推断出来,那么可以省略(
:
和返回值类型一起省略) -
如果有return,则不能省略返回值类型,必须指定
-
如果函数明确声明Uint,那么即使函数体中使用return关键字也不起作用
-
Scala如果期望是无返回值类型,可以省略
=
-
如果函数无参,但是声明了参数列表,那么调用时,小括号可加可不加
def fun1(): Unit = {
println(“hello”)
}
fun1()
fun1 -
如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def fun2: Unit = {
println(“world”)
}
fun2 -
如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
def fun3(name: String): Unit = {
println(name)
}
//匿名函数,lambda表达式
(name: String) => {
println(name)
}object InputDemo {
def main(args: Array[String]): Unit = {
val fun = (name: String) => {
println(name)
}
fun(“hello”)
//定义一个函数,以函数作为参数输入
def f(func: String => Unit): Unit = {
func(“scala”)
}
f((name: String) => {
println(name)
})
}
}
匿名函数
没有名字的函数就是匿名函数.(x:Int)=>{函数体}
传递匿名函数至简原则:
-
参数的类型可以省略,会根据形参进行的推导
-
类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过1的永远不能省略圆括号
-
匿名函数如果只有一行,则大括号也可以省略
-
如果参数只出现一次,则参数省略且后面参数可以用
_
代替object InputDemo {
def main(args: Array[String]): Unit = {
//定义一个函数,以函数作为参数输入
def f(func: String => Unit): Unit = {
func(“scala”)
}
f((name: String) => {
println(name)
})
//1) 参数的类型可以省略,会根据形参进行推导
f((name) => {
println(name)
})
//2) 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过1的永远不能省略圆括号
f(name => {
println(name)
})
//3) 匿名函数如果只有一行,则大括号也可以省略
f(name => println(name))
//4) 如果参数只出现一次,则参数省略且后面参数可以用_
代替
f(println(_))
//5)如果可以推断出,当前传入的println是一个函数体,而不是调用语句,可以直接省略下划线
f(println)
}
}
函数可以作为参数进行传递
object InputDemo {
def main(args: Array[String]): Unit = {
def f(fun: (Int, Int) => Int): Int = {
fun(1, 2)
}
println(f((a: Int, b: Int) => {a + b}))
println(f((a, b)=> a + b))
println(f( _ + _ ))
println(f(_ - _))
println(f(-_ + _))
}
}
函数可以作为值进行传递
object InputDemo {
def main(args: Array[String]): Unit = {
def fun(): Int = {
println("函数可以作为值进行传递")
666
}
//函数可以作为值进行传递
val a: Int = fun()
val b = fun
println(a + " " + b)
//在被调用函数fun后面加上_,相当于把函数fun当成一个整体传递给变量f
val f = fun _
f()
println(f) //函数引用
//注意:此时仅仅一个f是不可以的
//如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给变量
val f1: () => Int = fun
f1()
println(f1) //函数引用
}
}
函数可以作为函数的返回值返回
object InputDemo {
def main(args: Array[String]): Unit = {
def fun1(): Int => Unit = {
def fun2(a: Int): Unit = {
println("fun2被调用 " + a)
}
fun2 //将函数直接返回
}
fun1()(2)
}
}
object InputDemo {
def main(args: Array[String]): Unit = {
val arr: Array[Int] = Array(11, 22, 33, 44, 55, 66)
def arrayOperation(array: Array[Int], op: Int => Int): Array[Int] = {
for (elem <- array) yield op(elem)
}
def addTwo(elem: Int): Int = {
elem + 2
}
val newArray: Array[Int] = arrayOperation(arr, addTwo)
println(newArray.mkString(",")) //13,24,35,46,57,68
//传入匿名函数
val newArray2 = arrayOperation(arr, (elem: Int) => { elem * 2 })
val newArray3 = arrayOperation(arr, _ * 2 )
}
}
练习:定义一个函数fun,它接收一个Int类型的参数,返回一个函数(记作fun2).它返回的函数fun2,接收一个String类型的参数,同样返回一个函数(记作fun3).函数fun3接收一个Char类型的参数,返回一个Boolean的值.要求调用fun(0)(“”)(‘0’)得到返回值为false,否则为true.
object InputDemo {
def main(args: Array[String]): Unit = {
// def fun(i: Int): String => (Char => Boolean) = {
// def fun2(s: String): Char => Boolean = {
// def fun3(c: Char): Boolean = {
// if (i == 0 && s == "" && c == '0') false else true
// }
//
// fun3
// }
//
// fun2
// }
// println(fun(0)("")('0'))
//匿名函数(lambda表达式)简写
/* def fun(i: Int): String => (Char => Boolean) = {
s => c => if (i == 0 && s == "" && c == '0') false else true
}
println(fun(0)("")('0')) */
//柯里化
def fun(i: Int)(s: String)(c: Char): Boolean = {
if (i == 0 && s == "" && c == '0') false else true
}
println(fun(1)("hello")('0'))
}
}
函数柯里化&闭包
闭包
:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和它所处的环境,称为闭包.(调用函数时相当于创建了一个对象实例,把函数所依赖的环境和局部变量打包保存在这个对象实例里)函数柯里化
:柯里化(Currying)是指将原先接受多个参数的方法转换为多个只有一个参数的参数列表的过程。
递归和尾递归
尾递归的原理:当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。
object InputDemo {
def main(args: Array[String]): Unit = {
//递归实现计算阶乘
def fact(n: Int): Int = {
if (n == 0) return 0
fact(n - 1) * n
}
//尾递归实现
def tailFact(n: Int): Int = {
@tailrec
def loop(a: Int, currRes: Int): Int = {
if (a == 0) return currRes
loop(a - 1, currRes * a)
}
loop(n, 1)
}
println(tailFact(10))
}
}
控制抽象
控制抽象也是函数的一种, 它可以让我们更加灵活的使用函数.
假设函数A的参数列表需要接受一个函数B, 且函数B没有输入值也没有返回值
, 那么函数A就被称之为控制抽象函数
.
格式:
def 函数A (函数B: () => Unit) = {
//代码1
//代码2
//...
函数B()
}
object TestDemo2 {
def main(args: Array[String]): Unit = {
val myShop = (f1: () => Unit) => {
println("Welcome in!")
f1()
println("Thanks for coming!")
}
//2. 调用函数
myShop {
() =>{
println("我想买一个笔记版电脑")
println("我想买一个平板电脑")
println("我想买一个手机")
}
}
}
}
传名参数
() => Unit
是一个函数(参数列表为空).
=> Unit
传名参数,参数是一个返回值为Unit的代码块.
代码块是有返回值的,其最后一行作为返回值.
object InputDemo {
def main(args: Array[String]): Unit = {
def fun(): Int = {
println("fun调用")
666
}
//传名参数,传递的不是具体的值,而是代码块
def fun2(a: => Int):Unit={
println("a: "+a)
println("a: "+a)
}
fun2(11)
println("=========================")
fun2(fun())
println("=========================")
fun2({
println("这是一个代码块")
888
})
}
}
a: 11
a: 11
=========================
fun调用
a: 666
fun调用
a: 666
=========================
这是一个代码块
a: 888
这是一个代码块
a: 888
自定义While循环
object InputDemo {
def main(args: Array[String]): Unit = {
//用闭包实现一个While循环,将代码块作为参数传入,递归调用
def myWhile(condition: => Boolean): (=> Unit) => Unit = {
//内层函数需要递归调用,参数就是循环体
def doLoop(op: => Unit): Unit = {
if (condition) {
op
myWhile(condition)(op)
}
}
doLoop _
}
var n: Int = 10
myWhile({n >= 1})({
println(n)
n -= 1
})
// myWhile(n >= 1){
// println(n)
// n -= 1
// }
//2.用匿名函数实现
def myWhile2(condition: => Boolean): (=> Unit) => Unit = {
op => {
if (condition) {
op
myWhile2(condition)(op)
}
}
}
//3.用柯里化实现
def myWhile3(condition: => Boolean)(op: => Unit):Unit = {
if (condition) {
op
myWhile2(condition)(op)
}
}
}
}
惰性加载
当函数返回值被声明为lazy时,函数的执行将被推迟
,直到我们首次对此取值,该函数才会执行.这种函数我们称之为惰性函数.
object InputDemo {
def main(args: Array[String]): Unit = {
lazy val result = sum(66, 88)
println("======================")
println("result= " + result)
println("result= " + result)
def sum(n1: Int, n2: Int): Int = {
println("sum调用")
return n1 + n2
}
}
}
======================
sum调用
result= 154
result= 154
Scala包
命名规则:只能包含数字,字母,下划线,小圆点,但是不能用数字开头,也不要使用关键字
com.公司名.项目名.业务模块名
说明:Scala有两种包的管理风格,一种方式和Java的包管理风格相同,每个源文件一个包(包名和源文件所在路径不要求必须一致).包名用.
进行分割以表示包的层级关系,比如com.hyj.scala
.另一种风格,通过嵌套的风格表示层级关系.
package com{
package hyj{
package scala{
}
}
}
第二种风格有如下特点:
-
一个源文件中可以声明多个package
-
子包中的类可以直接访问父包中的内容,而无需导包
//用嵌套风格定义包
package com{import com.hyj.scala.Inner //需要导包
//在外层包中定义单例对象
object Outer{
var out:String=“out”
def main(args: Array[String]): Unit = {
println(Inner.in)
}
}
package hyj{
package scala{
//在内层包中定义单例对象
object Inner{
val in:String=“In”
def main(args: Array[String]): Unit = {
println(Outer.out) //out
Outer.out=“outer”
println(Outer.out) //outer
}
}
}
}
}//在同一文件中定义多个包
package aaa{
package bbb{import com.hyj.scala.Inner object Test_01{ def main(args: Array[String]): Unit = { println(Inner.in) } }
}
}
包对象
包对象:在Scala中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有class和object的共享变量,可以直接访问.
package ccc {
package ddd {
object Test_02 {
def main(args: Array[String]): Unit = {
println(name)
println(sum(10,20))
println(school)
}
}
}
// 定义一个包对象(必须和ddd包在同一层级上)
package object ddd {
val school: String = "bili"
}
}
// 定义一个包对象(必须和ccc包在同一层级上)
package object ccc {
val name: String = "scala"
def sum(a1: Int, a2: Int): Int = {
a1 + a2
}
}
定义(ava的包管理风格):
右击new Package Object
new一个包对象
package object com{
val shareValue="share"
def shareMethod(): Unit ={
println(s"我们在${shareValue}")
}
}
说明:若使用Java的包管理风格,则包对象一般定义在其对应包下的package.scala文件中,包对象与包名保持一致.
导包说明
- 和Java一样,可以在顶部使用import导入,在这个文件中的所有类都可以使用
- 局部导入:什么时候使用,就什么时候导入.在其作用范围内都可以使用
- 通配符导入:import java.util._
- 给类起名:import java.util.{ArrayList=>JL}
- 导入相同包的多个类:import java.util.{HashSet,ArrayList}
屏蔽类:import java.util.{ArrayList=>_,_}
- 导入包的绝对路径:
new _root_.java.util.HashMap
Scala中的三个默认导入分别是:
import java.lang._
import scala._
import scala.Predef._
类和对象
回顾Java中的类:
- 如果类是public的,则必须和文件名一致.
- 一般,一个.java有一个public类.
注意:Scala中没有public,一个.scala中可以写多个类.
Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
当将scala字段注解为@BeanProperty时,会自动生成get/set方法
object InputDemo {
def main(args: Array[String]): Unit = {
val student = new Student()
student.setName("tom")
student.age=20
// student.sex="女"
println("name= "+student.getName+" age="+student.age+" sex="+student.sex)
}
}
class Student {
@BeanProperty //此注解只能用在非private字段
var name: String = _ //""
var age: Int = _ //默认值为0
var sex: String = _ //""
}
访问权限
-
Scala中属性和方法的
默认权限为public
,但是Scala中无public关键字. -
private
为私有权限,只在类的内部和伴生对象
中使用. -
protected
为受保护权限,Scala中受保护权限比Java中更严格,同类,子类
可以访问,同包无法访问. -
private[包名]增加包访问权限
,包名下的其他类也可以使用. -
private[this]
只能在当前对象内部
访问package com.package1
class Person {
private var id: String = “202001888”
protected var name: String = “tom”
var sex: String = “女”
private[com] var age: Int = 20
def printInfo():Unit={
println(s"id= i d n a m e = id name= idname=name sex= s e x a g e = sex age= sexage=age")
}
}package com.package2
import com.package1.Person
class Student extends Person{
override def printInfo(): Unit = {
name=“scala”
sex=“女”
age=30
println(s"name= n a m e a g e = name age= nameage=age sex=$sex")
}
}import com.package2.Student
object InputDemo {
def main(args: Array[String]): Unit = {
val student = new Student()
student.printInfo()
}
}name=scala age=30 sex=女
构造器
-
和 Java 一样,Scala 构造对象也需要调用构造方法,并且可以有任意多个构造方法(即 scala 中构造器也支持重载)。
-
Scala 类的构造器包括: 主构造器(一个) 和 辅助构造器(多个)
Scala 构造器的基本语法:class 类名(形参列表){ // 主构造器
// 类体
def this(形参列表) { // 辅助构造器 }
def this(形参列表) { //辅助构造器可以有多个… }
} -
辅助构造器 函数的名称 this, 可以有多个,编译器通过不同参数(个数或类型来区分
-
辅助构造器不能直接创建对象,必须直接或间接调用主构造方法.
-
构造器调用其他另外的构造器,要求被调用的构造器必须提前说明.
-
主构造器会执行类定义中的所有语句(把类中写的语句放入到主构造器)
-
如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
-
如果想让主构造器变成私有的,可以在()之前加上 private,这样用户不能直接通过主构造器来构造对象了
object InputDemo {
def main(args: Array[String]): Unit = {
val student = new Student
println(““)
val student1 = new Student(“tom”)
println(””)
val student2 = new Student(“alice”, 38)
}
}class Student() {
//定义属性
var name: String = _
var age: Int = _
println(“1 主构造方法”)def this(name: String) { //辅助构造方法
this() // 直接调用主构造方法
println(“2 辅助构造方法”)
this.name = name
println(s"name: n a m e a g e : name age: nameage:age")
}def this(name: String, age: Int) { //辅助构造方法
this(name) // 间接调用主构造方法
println(“3 辅助构造方法”)
this.age=age
println(s"name: n a m e a g e : name age: nameage:age")
}
}
构造器参数
-
Scala 类的主构造器的形参
未用任何修饰符修饰
,那么这个参数是局部变量
。 -
如果参数使用
val /var
关键字声明,那么 Scala 会将参数作为类的成员属性
使用object InputDemo {
def main(args: Array[String]): Unit = {
val student = new Student
val student1 = new Student4(“tom”, 20)
println(s"${student1.name} ${student1.age}")
}
}class Student { //无参构造器
//定义属性
var name: String = _
var age: Int = _
}//上面等价于
class Student2(var name: String, var age: Int)
//主构造器参数无修饰 局部变量
class Student3(name: String,age: Int)class Student4(val name: String, val age: Int)
多态
在Java中:
public class Person {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.name);
student.hello();
System.out.println("========================");
Person1 student1 = new Student();
System.out.println(student1.name); //静态绑定属性
student1.hello(); //动态绑定方法
}
}
class Person1 {
String name = "person";
public void hello() {
System.out.println("hello person");
}
}
class Student extends Person1 {
String name = "student";
@Override
public void hello() {
System.out.println("hello student");
}
}
student
hello student
========================
person
hello student
在Scala中:
object TestDemo {
def main(args: Array[String]): Unit = {
val student: Student = new Student
println(student.name)
student.hello()
val student2: Person = new Student
println(student2.name) //在Scala中属性和方法都是动态绑定的
student2.hello()
}
}
class Person {
val name: String = "person"
def hello(): Unit = {
println("hello person")
}
}
class Student extends Person {
override val name: String = "student"
override def hello(): Unit = {
println("hello student")
}
}
student
hello student
student
hello student
抽象类
- 定义抽象类:
abstract class Person{}
通过abstract关键字标记抽象类 - 定义抽象属性:
var|val name:String
一个属性没有初始化就是抽象属性 - 定义抽象方法:
def hello():String
只声明而没有实现的方法就是抽象方法
继承和重写:
- 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类.
- 重写非抽象方法需要用override修饰,重写抽象方法则可以不加override
- 子类中调用父类的方法使用super关键字
- 子类对抽象属性进行实现,父类抽象属性可以用
var
修饰;子类对非抽象属性重写,父类非抽象属性只支持val
类型,而不支持var
.
因为var修饰的为可变变量,子类继承之后可以直接使用,没有必要重写
匿名子类
object TestDemo {
def main(args: Array[String]): Unit = {
val person:Person = new Person { //匿名子类
override var name: String = "tom"
override def hello(): Unit = println(s"hello $name")
}
println(person.name)
person.hello()
}
}
abstract class Person {
var name: String //抽象属性
def hello(): Unit //抽象方法
}
伴生类和伴生对象
object TestDemo {
def main(args: Array[String]): Unit = {
println("=========第1种方法===========")
val student = new Student("tom", 20) //利用伴生类创建对象
student.printInfo()
println("=========第2种方法===========")
val student1: Student = Student.newStudent("alice", 25) //利用伴生对象创建对象
student1.printInfo()
println("===========第3种方法==============")
val student2: Student = Student.apply("zhangsan", 13)
student2.printInfo()
// .apply可以省略不写
val student3: Student = Student("zhangsan", 13)
student2.printInfo()
}
}
class Student(val name: String, var age: Int) { //伴生类
def printInfo(): Unit = {
println(s"student: name=$name age=$age school=${Student.school}")
}
}
object Student { //单例对象(伴生对象)
val school: String = "B站大学"
def newStudent(name: String, age: Int): Student = new Student(name, age)
def apply(name: String, age: Int): Student = new Student(name, age)
}
单例设计模式
object TestDemo {
def main(args: Array[String]): Unit = {
val student1 = Student.getInstance()
student1.printInfo()
}
}
class Student private(val name: String, var age: Int) { //私有化构造器
def printInfo(): Unit = {
println(s"student: name=$name age=$age school=${Student.school}")
}
}
//饿汉式
object Student { //单例对象(伴生对象)
val school: String = "B站大学"
private val student: Student = new Student("alice", 10)
def getInstance(): Student = student
}
object Student { //单例对象(伴生对象)
val school: String = "B站大学"
private var student: Student = _
def getInstance(): Student = {
if (student == null) {
student = new Student("alice", 20)
}
student
}
}
特质trait
Scala语言中,采用特质trait(特征)来替代接口
的概念.也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明.
Scala中的trait中即可以有抽象属性和方法
,也可以有具体的属性和方法
.一个类可以混入多个特质
.这种感觉类似于Java中的抽象类.
Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充.
trait 特质名{
}
特质基本语法:一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素.所以在使用时,也采用了extends关键字
.如果有多个特质或存在父类,那么需要采用with关键字
连接.
- 没有父类
class 类名 extends 特质1 with 特质2 with 特质3...
- 有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3...
说明:
-
类和特质的关系:使用继承的关系
-
当一个类去继承特质时,第一个连接词是extends,后面是with.
-
如果一个类在同时继承特质和父类时,应当把父类写在extends后.
object TestDemo {
def main(args: Array[String]): Unit = {
val student = new Student
student.sayHello()
student.study()
student.play()
}
}//定义一个特质
trait Yong {
//声明抽象和非抽象属性
var age: Int
val name: String = “yong”//声明抽象和非抽象方法
def study(): Unitdef play(): Unit = {
println(“yong people is playing”)
}
}class Person {
val name: String = “person”
var age: Int = 20def sayHello(): Unit = {
println(s"${name}在跟你说hello")
}
}class Student extends Person with Yong {
//重写冲突的属性
override val name: String = “student”//实现抽象方法
def study(): Unit = {
println(s"student $name is studying")
}//重写父类方法
override def sayHello(): Unit = {
println(s"$name is say hello")
}
除了可以在类声明时继承特质以外,还可以在构建对象时混入特质
object TestDemo {
def main(args: Array[String]): Unit = {
//创建 OracleDB 实例,同时动态混入 Operate3 特质
val oracleDB = new OracleDB with Operate3 {
override def insert2(): Unit = {
println("insert2")
}
}
oracleDB.insert(100)
oracleDB.insert2()
val mySQL3 = new MySQL3 with Operate3 {
override def insert2(): Unit = {
println("============mySQL3 insert2 =========")
}
override def sayHi: Unit = {
println("mySQL3 sayHi")
}
}
mySQL3.insert2()
mySQL3.sayHi
//如果我们要去实例化一个 abstract 类,也可以,但是需要时候用匿名子类来构建
val mySQL = new MySQL3 {
override def sayHi: Unit = {
println("hi")
}
}
}
}
//特质
trait Operate3 {
def insert(id: Int): Unit = {
println("插入数据 = " + id)
}
def insert2(): Unit
}
//普通类
class OracleDB {}
//抽象类
abstract class MySQL3 {
def sayHi(): Unit
}
叠加特质
构建对象的同时如果混入多个特质,称之为叠加特质,那么特质声明顺序从左到右
,方法执行顺序从右到左
。
object TestDemo {
def main(args: Array[String]): Unit = {
val student = new Student
student.increase()
}
}
class Person{
def increase(): Unit={
println("Person increase")
}
}
trait Knowledge {
var amount: Int = 0
def increase(): Unit={
println("knowledge increase")
}
}
trait Talent{
def singing():Unit
def dancing():Unit
def increase(): Unit={
println("talent increase")
}
}
class Student extends Person with Knowledge with Talent{
override def dancing(): Unit = {
println("dancing")
}
override def singing(): Unit = println("singing")
override def increase(): Unit = super.increase() //重写冲突方法
}
talent increase
钻石问题的特质叠加
一个类(Sub)混入的两个trait(traitA,traitB)中具有相同的具体方法,且两个trait继承自相同的trait(traitC),及所谓的"钻石问题",解决这类冲突问题,Scala采用了特质叠加的策略.
所谓的特质叠加,就是将混入的多个trait中的冲突方法叠加起来.
如果想要调用具体特质的方法,可以指定:super[特质].xxx(…).其中的泛型必须是该特质的直接超类类型
object TestDemo {
def main(args: Array[String]): Unit = {
val ball = new MyFootBall
println(ball.describe()) //my ball is foot-red-ball
}
}
trait Ball {
def describe(): String = "ball"
}
trait ColorBall extends Ball {
var color: String = "red"
override def describe(): String = color + "-" + super.describe() //super.describe()调用的是Ball特质的describe()方法
}
trait CategoryBall extends Ball {
var category: String = "foot"
override def describe(): String = category + "-" + super.describe() //super.describe()调用的是ColorBall特质的describe()方法
// override def describe(): String = category + "-" + super[Ball].describe() 指定调用Ball特质的describe()方法
}
class MyFootBall extends ColorBall with CategoryBall {
override def describe(): String = "my ball is " + super.describe() //重写冲突的方法
}
自身类型
object TestDemo {
def main(args: Array[String]): Unit = {
val user = new RegisterUser("tom", "123456")
user.insert()
println(user.passwd+" "+user.name)
}
}
//用户类
class User(val name: String, val passwd: String)
//自身类型特质
trait UserDao {
// 明确告诉编译器,我就是 User,如果没有这句话,下面的name不能使用
_: User => //定义自身类型
def insert(): Unit = {
//既然我就是User,那么我就可以使用name
println(s"insert into database:$name")
}
}
class RegisterUser(name: String, passwd: String) extends User(name, passwd) with UserDao
/*insert into database:tom
123456 tom*/
类型检查和转换
obj.isInstanceOf[T]
判断obj是不是T类型
obj.asInstanceOf[T]
将obj强转成T类型
classOf
获取对象的类名
object TestDemo {
def main(args: Array[String]): Unit = {
val student = new Student("alice", 20)
student.study() //student study
student.sayHi() //hi from student alice
val person: Person = new Student("tom", 88)
person.sayHi() //hi from student tom
println(student.isInstanceOf[Person]) //true
println(person.isInstanceOf[Student]) //true
println(person.isInstanceOf[Person]) //true
if (person.isInstanceOf[Student]) {
val newStudent = person.asInstanceOf[Student]
newStudent.study() //student study
}
println(classOf[Student]) //class com.package2.Student
}
}
class Person(val name: String, val age: Int) {
def sayHi(): Unit = {
println("hi from person " + name)
}
}
class Student(name: String, age: Int) extends Person(name, age) {
override def sayHi(): Unit = {
println("hi from student " + name)
}
def study(): Unit = {
println("student study")
}
}
枚举类和应用类
枚举类:需要继承Enumeration
应用类:需要继承App
object TestDemo {
def main(args: Array[String]): Unit = {
println(Color.RED) //red
println(Color.BLUE) //blue
println(Color.BLACK) //BLACK
println(Color.GREEN) //green
}
}
//枚举类
object Color extends Enumeration{
val RED=Value(1,"red")
val YELLOW=Value(2,"yellow")
val BLUE=Value(3,"blue")
val BLACK=Value(4)
val GREEN=Value("green")
}
//应用类
object TestApp extends App{
println("hello scala") //hello scala
}
集合
- Scala的集合有3大类:
序列 Seq、集 Set、映射 Map
,所有的集合都扩展自 Iterable 特质,在 Scala 中集合 有可变(mutable)和不可变(immutable)两种类型. - 两个主要的包: 不可变集合:scala.collection.immutable 可变集合: scala.collection.mutable
- Scala
默认采用不可变集合
,对于几乎所有的集合类,Scala 都同时提供了可变(mutable)和不可变(immutable) 的版本 - 不可变集合:scala 不可变集合,就是这个集合本身不能动态变化。(类似 java 的数组,是不可以动态增 长的)
- 可变集合:可变集合,就是这个集合本身可以动态变化的。(比如:ArrayList , 是可以动态增长的)
不可变数组
(1)定义数组:
- 方式1:
val arr1 = new Array[Int](10)
如果希望存放任意数据类型,则指定Any
10表示数组的大小,确定后就不可以改变 - 方式2:
val aar2=Array(11,22,3,34,66,77)
调用伴生对象的apply()方法创建数组
(2)通过数组名.length
或者数组名.size
来获取数组的长度.
object TestDemo {
def main(args: Array[String]): Unit = {
val arr01 = new Array[Int](4) // //创建一个数组,存放Int类型的数据,大小为4
println(arr01.length) // 4
println("arr01(0)=" + arr01(0)) //访问指定下标的元素
for (i <- arr01) { //遍历数组
print(i+" ")
}
println()
arr01(3) = 10 //修改元素
println(arr01.mkString(" "))
}
}
遍历集合
object TestDemo {
def main(args: Array[String]): Unit = {
val arr = Array(11, 22, 33, 44, 55, 66, 77, 88)
for (i <- 0 until arr.length) {
println(arr(i))
}
//等价代码 因为 def indices: Range = 0 until length
for (i <- arr.indices) {
println(arr(i))
}
//迭代器
val iterator = arr.iterator
while (iterator.hasNext){
println(iterator.next())
}
//调用foreach()方法
// arr.foreach((elem: Int) =>println(elem))
arr.foreach(println)
}
}
object TestDemo {
def main(args: Array[String]): Unit = {
val arr = Array(22, 33, 44, 55, 66, 77, 88)
//在数组最后面添加一个元素
val newArr = arr.:+(99)
println(newArr.mkString(" "))
//在数组最前面添加一个元素
val newArr2 = arr.+:(11)
println(newArr2.mkString(" "))
//可以去掉.点号 用空格隔开 也可以去掉小括号
val newArr3 = arr :+ 99
println("newArr和newArr3等价: " + newArr3.mkString(" "))
val newArr4 = 11 +: arr //注意不能这样子写arr +: 11
println("newArr2和newArr4等价: " + newArr4.mkString(" "))
//一次性添加多个元素
val newArr5 = 1 +: 2 +: 3 +: arr :+ 4 :+ 5 :+ 6
println(newArr5.mkString(" ")) //1 2 3 22 33 44 55 66 77 88 4 5 6
}
}
可变数组
(1)定义可变数组:
- 创建空的ArrayBuffer可变数组
val/var 变量名 =new ArrayBuffer[元素类型]()
- 创建带有初始元素的ArrayBuffer可变数组
val/var 变量名 = ArrayBuffer(元素1,元素2,元素3....)
(2)
-
使用
+=
添加单个元素 -
使用
-=
删除单个元素 -
使用
++=
追加一个数组到可变数组中 -
使用
--=
移除可变数组中的指定多个元素object TestDemo {
def main(args: Array[String]): Unit = {
//创建可变数组
val arr:ArrayBuffer[Int] = new ArrayBufferInt
val arr2 =ArrayBuffer(22,33,44,55,66)
println(arr) //ArrayBuffer()
println(arr2) //ArrayBuffer(22, 33, 44, 55, 66)
//访问元素
println(arr2(3))
// 修改元素
arr2(2)=66
println(arr2) //ArrayBuffer(22, 33, 66, 55, 66)
}
}object TestDemo {
def main(args: Array[String]): Unit = {
//创建可变数组
val arr: ArrayBuffer[Int] = new ArrayBufferInt
val arr2 = ArrayBuffer(22, 33, 44, 55, 66)
//添加元素
//val newArr = arr2.:+(99)
val newArr = arr2 :+ 99
println(newArr) //ArrayBuffer(22, 33, 44, 55, 66, 99)
val newArr2 = arr
//在数组最后面添加一个元素
arr += 1
println(arr) //ArrayBuffer(1)
//往数组里添加元素,数组的引用不变
println(arr.eq(newArr2)) //true
println(newArr2) //ArrayBuffer(1)
newArr2 += 2
println(arr) //ArrayBuffer(1, 2)
//在数组最前面添加一个元素
3 +=: arr
println(arr) //ArrayBuffer(3, 1, 2)
//调用方法 在数组最前面添加一个元素
arr.prepend(0)
// 在数组最后面添加一个元素
arr.append(9)
//一次性添加多个元素
arr.append(10,12,13)
println(arr) //ArrayBuffer(0, 3, 1, 2, 9)
//在指定索引处插入(多个)元素
arr.insert(3,66,77,88)
println(arr) //ArrayBuffer(0, 3, 1, 66, 77, 88, 2, 9, 10, 12, 13)
//在指定索引处将arr2的所有元素插入
arr.insertAll(1,arr2)
println(arr)
// arr.prependAll(arr2)
// arr.appendAll(arr2)
//删除指定索引处的元素
arr.remove(6)
//从指定索引0处开始连续删除5个元素
arr.remove(0,5)
println(arr)
arr -= 66 //只删除第一次出现的 66
println(arr)
arr.clear() //清空数组
println(arr) //ArrayBuffer()
}
}object TestDemo {
def main(args: Array[String]): Unit = {
//创建可变数组
val arr: ArrayBuffer[Int] = ArrayBuffer(22, 33, 44, 55, 66)
//创建不可变数组
val arr1: Array[Int] = Array(1, 2, 3, 4, 5, 6)
//将可变数组转变为不可变数组
val newArr = arr.toArray
println(newArr.mkString(“,”))
//将不可变数组转变为可变数组
val newArr2: mutable.Buffer[Int] = arr1.toBuffer
println(newArr2)
}
}object TestDemo {
def main(args: Array[String]): Unit = {
//创建二维数组
val array: Array[Array[Int]] = Array.ofDim(2, 3)
//创建三维数组
val array1: Array[Array[Array[Int]]] = Array.ofDim(2, 3, 4)
//访问元素
array(0)(0) = 1
array(1)(1) = 1
//遍历数组
for {i <- array.indices
j <- array(i).indices} {
print(array(i)(j) + " ")
if (j == array(i).length - 1) {
println()
}
}
array.foreach(line => line.foreach(println))
//简写
array.foreach(_.foreach(println))
}
}
不可变列表List
(1)不可变列表指的是: 列表的元素、长度都是不可变的
。
(2)定义不可变列表:
- 格式一: 通过小括号直接初始化.
val/var 变量名 = List(元素1, 元素2, 元素3...)
- 格式二: 通过
Nil
创建一个空列表.
val/var 变量名 = Nil
- 格式三: 使用
::
方法实现.
val/var 变量名 = 元素1 :: 元素2 :: Nil
注意: 使用::
拼接方式来创建列表,必须在最后添加一个Nil
(3)
-
List 默认为不可变集合
-
List 数据有顺序,可重复
-
如果希望得到一个空列表,可以使用 Nil 对象
-
向列表中增加元素, 会返回新的列表/集合对象
object TestDemo {
def main(args: Array[String]): Unit = {
//创建一个List 注意:不能用List的构造方法创建List,因为List是个抽象类 只能用它的伴生对象创建List
val list = List(11, 22, 33)
println(list) //List(11, 22, 33)
// list(1)=66 注意:不能更改它的值
//添加元素
val newList1 = list.:+(44)
// val newList = list :+ 44
val newList2 = list.+:(0)
// val newList2 = 0 +: list
println(newList1) //List(11, 22, 33, 44)
println(newList2) //List(0, 11, 22, 33)// val newList3 = list.::(55) val newList3 = 55 :: list println(newList3) //List(55, 11, 22, 33) val list2 = Nil.::(1) println(list2) //List(1)
// 1) 符号::表示向集合中 新建集合添加元素。 2) 运算时,集合对象一定要放置在最右边, 3) 运算规则,从右向左
val list3 = 1 :: 2 :: 3 :: 4 :: Nil //第二种创建List的方法
println(list3) //List(1, 2, 3, 4)
//合并列表
val list4 = list ::: list3
println(list4) //List(11, 22, 33, 1, 2, 3, 4)
val list5 = list ++ list3
println(list5) //List(11, 22, 33, 1, 2, 3, 4)
}
}
可变列表
-
可变列表指的是
列表的元素、长度
都是可变的. -
定义可变列表
格式一: 创建空的可变列表.
val/var 变量名 = ListBuffer[数据类型]()
格式二: 通过小括号直接初始化.
val/var 变量名 = ListBuffer(元素1,元素2,元素3...)
object TestDemo {
def main(args: Array[String]): Unit = {
//创建可变列表
val list1: ListBuffer[Int] = new ListBufferInt
val list2: ListBuffer[Int] = ListBuffer(11, 22, 33)
println(list1) //ListBuffer()
println(list2) //ListBuffer(11, 22, 33)
//添加元素
list1.append(1, 2, 3)
list1.prepend(-1, 0)
//在指定索引1处添加(1个或多个)元素
list1.insert(1, 66, 77)
println(list1) //ListBuffer(-1, 66, 77, 0, 1, 2, 3)
11 +=: 22 +=: 33 +=: list1 += 44 += 55
println(list1) //ListBuffer(11, 22, 33, -1, 66, 77, 0, 1, 2, 3, 44, 55)
//从指定索引3处开始连续删除5个元素
list1.remove(3, 5)
println(list1) //ListBuffer(11, 22, 33, 2, 3, 44, 55)
//合并列表
val list3 = list1 ++ list2
println(list3) //ListBuffer(11, 22, 33, 2, 3, 44, 55, 11, 22, 33)
list1 ++= list2 //list1调用++=方法
println(list1) //ListBuffer(11, 22, 33, 2, 3, 44, 55, 11, 22, 33)
//清空列表
list1.clear()
list1.append(1, 2, 3, 4, 5, 6)
list1 ++=: list2 //list2调用++=:方法
println(list1) //ListBuffer(1, 2, 3, 4, 5, 6)
println(list2) //ListBuffer(1, 2, 3, 4, 5, 6, 11, 22, 33)
//修改元素
list1(0) = 0
list1.update(1, 66)
println(list1) //ListBuffer(0, 66, 3, 4, 5, 6)
//删除元素
list1.remove(1)
list1 -= 5
println(list1) //ListBuffer(0, 3, 4, 6)
}
}
列表的常用操作
-
拉链
:将两个列表,组合成一个元素为元组的列表
解释: 将列表List(“张三”, “李四”), List(23, 24)组合成列表List((张三,23), (李四,24)) -
拉开
:将一个包含元组的列表,拆解成包含两个列表的元组
解释: 将列表List((张三,23), (李四,24))拆解成元组(List(张三, 李四),List(23, 24))object TestDemo2 {
def main(args: Array[String]): Unit = {
//1. 定义列表names, 保存三个学生的姓名,分别为:张三、李四、王五
val names = List(“张三”, “李四”, “王五”)
// 2. 定义列表ages, 保存4个学生的年龄,分别为:23, 24, 25,30
val ages = List(23, 24, 25,30)
// 3. 使用zip将列表names和ages, 组合成一个元素为元组的列表list1.
val list1 = names.zip(ages)
// 4. 使用unzip将列表list1拆解成包含两个列表的元组tuple1
val tuple1 = list1.unzip
// 5. 打印结果
println("拉链: " + list1)
println("拉开: " + tuple1)
}
}
不可变Set集合
- Set(也叫: 集)代表没有重复元素的集合。特点是: 唯一, 无序
- 解释:
1). 唯一的意思是Set中的元素具有唯一性, 没有重复元素
2). 无序的意思是Set集中的元素, 添加顺序和取出顺序不一致 - 不可变集指的是
元素, 集的长度都不可变
. - 定义不可变集
格式一: 创建一个空的不可变集
val/var 变量名 = Set[类型]()
格式二: 给定元素来创建一个不可变集
val/var 变量名 = Set(元素1, 元素2, 元素3...)
不可变集的常见操作 :
-
获取集的大小(
size
) -
遍历集(和遍历数组一致)
-
添加一个元素,生成一个新的Set(
+
) -
拼接两个集,生成一个新的Set(
++
) -
拼接集和列表,生成一个新的Set(
++
) -
-
(减号)表示删除一个元素, 生成一个新的Set -
--
表示批量删除某个集中的元素, 从而生成一个新的Setobject TestDemo {
def main(args: Array[String]): Unit = {
//创建不可变Set
val set = Set(11, 22, 33, 44, 55)
println(set)
// 添加元素
// val set1 = set.+(20)
val set1 = set + 20
println(set1)
//合并Set
val set2 = Set(1, 2, 3, 4)
val set3 = set1 ++ set2
println(set3)
//删除元素
val set4 = set3 - 55
println(set4)
}
}
可变Set集合
可变集指的是元素, 集的长度
都可变, 它的创建方式和不可变集的创建方式一致,只不过需要先导入可变集类.
object TestDemo {
def main(args: Array[String]): Unit = {
//创建可变Set
val set = mutable.Set(1, 2, 3, 4, 2, 3)
println(set)
//添加元素
val set2 = set + 66
println(set2)
set += 11
println(set)
val b: Boolean = set.add(22)
println(set)
val b1: Boolean = set.add(22)
//true表示添加成功 false表示添加失败
println(b + " " + b1) //true false
//删除元素
set -= 1
println(set)
val bool: Boolean = set.remove(2)
val bool1: Boolean = set.remove(2)
//true表示删除成功 false表示删除失败
println(bool + " " + bool1)
//合并集合
val set3 = set ++ set2
println(set3)
set ++= set2
println(set)
}
}
不可变Map集合
映射
指的就是Map。它是由键值对(key, value)
组成的集合。特点是: 键具有唯一性
, 但是值可以重复
.
在Scala中,Map也分为不可变Map和可变Map
。
注意: 如果添加重复元素(即: 两组元素的键相同), 则会用新值覆盖旧值.
-
不可变Map指的是元素, 长度都不可变.
-
定义不可变Map:
方式一: 通过箭头
的方式实现.
val/var map = Map(键->值, 键->值, 键->值...)
// 推荐,可读性更好
方式二: 通过小括号的方式实现.
val/var map = Map((键, 值), (键, 值), (键, 值), (键, 值)...)
object TestDemo {
def main(args: Array[String]): Unit = {
//创建不可变Map
val map: Map[String, Int] = Map(“a” -> 1, “b” -> 2)
println(map)
println(map.getClass) //class scala.collection.immutable.MapKaTeX parse error: Expected '}', got 'EOF' at end of input: … println(s"key ----> ${map.get(key)}") //a ----> Some(1)等
}
//获取某一个key的value值
println(map.get(“b”).get) //2
println(map.get(“c”)) //None
println(map.getOrElse(“c”,“默认值”)) //默认值
println(map.getOrElse(“a”,“默认值”)) //1
println(map(“a”)) //1
}
}
可变Map
可变Map指的是元素, 长度都可变.
定义语法与不可变Map一致, 只不过需要先手动导包:import scala.collection.mutable.Map
object TestDemo {
def main(args: Array[String]): Unit = {
//创建可变Map
val map: mutable.Map[String, Int] = mutable.Map("a" -> 1, "b" -> 2)
println(map.getClass) //class scala.collection.mutable.HashMap
//添加元素
map.put("c", 3)
map.put("d", 4)
println(map)
map += (("e", 5)) //注意:双重括号
println(map)
//删除元素
map.remove("a")
map -= "c"
println(map)
//修改元素
map.update("e", 66)
println(map) //Map(e -> 66, b -> 2, d -> 4)
//合并Map
//创建不可变Map
val map1: Map[String, Int] = Map("a" -> 1, "b" -> 99)
map ++= map1 //如果map当中已经有对应的key了,就覆盖对应的value值
println(map) //Map(e -> 66, b -> 99, d -> 4, a -> 1)
}
}
Map基本操作
map(key)
: 根据键获取其对应的值, 键不存在返回None.map.keys
: 获取所有的键.map.values
: 获取所有的值.- 遍历map集合: 可以通过普通for实现.
getOrElse
: 根据键获取其对应的值, 如果键不存在, 则返回指定的默认值.+号: 增加键值对
, 并生成一个新的Map.
注意: 如果是可变Map, 则可以通过+=或者++=直接往该可变Map中添加键值对元素.
-号
: 根据键删除
其对应的键值对元素, 并生成一个新的Map.
注意: 如果是可变Map, 则可以通过-=或者--=直接从该可变Map中删除键值对元素.
元组
元组可以理解为一个容器,可以存放各种相同或不同类型的数据.说的简单点,就是将多个无关的数据封装成一个整体,称为元组.
元组中最大只能有22个元素
Map中的键值对其实就是元组
,只不过元组的元素个数为2,称之为对偶.
元组的长度和元素都是不可变的.
-
定义元组
(1) 格式一:通过小括号实现
val/var 元组 = (元素1, 元素2, 元素3....)
(2)格式二: 通过箭头来实现
val/var 元组 = 元素1->元素2
注意: 上述这种方式, 只适用于元组中只有两个元素的情况. -
访问元组中的元素
在Scala中, 可以通过元组名._编号
的形式来访问元组中的元素,_1表示访问第一个元素
,依次类推.也可以通过元组名.productIterator
的方式, 来获取该元组的迭代器, 从而实现遍历元组.object TestDemo {
def main(args: Array[String]): Unit = {
//创建元组
val tuple: (String, Int, Boolean, Char) = (“hello”, 66, true, ‘a’)
println(tuple)
//访问数据
println(tuple._1) //hello
println(tuple._2) //66
println(tuple._3) //true
println(tuple._4) //a
println(tuple.productElement(1)) //66 下标是从 0 开始计算
//遍历元组[通过迭代器来遍历]
for (elem <- tuple.productIterator) {
println(elem)
}
//嵌套元组
val mulTuple: (Int, String, Char, (String, Int)) = (66, “hello”, ‘a’, (“scala”, 88))
println(mulTuple._4._1) //scala
}
}
迭代器(iterator)
概述 :
Scala针对每一类集合都提供了一个迭代器(iterator), 用来迭代访问集合.
-
使用iterator方法可以从集合获取一个迭代器.
-
迭代器中有两个方法:
(1) hasNext方法: 查询容器中是否有下一个元素
(2) next方法: 返回迭代器的下一个元素,如果没有,抛出NoSuchElementException -
每一个迭代器都是有状态的.即: 迭代完后保留在最后一个元素的位置. 再次使用则抛出NoSuchElementException
-
可以使用while或者for来逐个获取元素.
object TestDemo2 {
def main(args: Array[String]): Unit = {
//1. 定义一个列表,包含以下元素:1,2,3,4,5val list1 = List(1, 2, 3, 4, 5) // 2. 使用while循环和迭代器,遍历打印该列表. // 2.1 根据列表获取其对应的迭代器对象. val it = list1.iterator // 2.2 判断迭代器中是否有下一个元素. while (it.hasNext) { // 2.3 如果有, 则获取下一个元素, 并打印. println(it.next) } // 迭代完后, 再次使用该迭代器获取元素, 则抛异常: NoSuchElementException // println(it.next)
}
}
集合常用函数
获取集合的长度length
获取集合的大小size
是否包含contains(数据)
求和sum
求乘积product
求最大值max
求最小值min
object TestDemo {
def main(args: Array[String]): Unit = {
val list = List(("a", 32), ("hello", 56), ("scala", 66), ("spack", 2), ("java", 23))
val list1 = List(12, 43, 65, 2, 12, 65, 32, 3)
//求最大值
println(list.max) //(spack,2)
// 求乘积
println(list1.product)
// 求和
println(list1.sum)
// 集合的长度
println(list1.length) //8
// 集合的大小
println(list1.size) //8
println(list.maxBy((tuple: (String, Int)) => tuple._2))
println(list.maxBy(_._2)) //(scala,66)
//排序
val sorted = list1.sorted
println(sorted) //从小到大
println(sorted.reverse) //从大到小
//传入隐式参数
println(list1.sorted(Ordering[Int])) //默认从小到大
println(list1.sorted(Ordering[Int].reverse)) //从大到小
println(list.sorted)
println(list.sortBy(_._2))
println(list.sortBy(_._2)(Ordering[Int].reverse))
println(list1.sortWith((a: Int, b: Int) => { a < b }))
println(list1.sortWith(_ < _)) //从小到大
println(list1.sortWith(_ > _)) //从大到小
}
}
衍生集合
object TestDemo {
def main(args: Array[String]): Unit = {
//创建不可变列表
val list = List(1, 3, 5, 7, 9, 11)
val list2 = List(1, 22, 7, 44, 3, 11, 8, 7)
//获取集合的头
println(list.head) //1
//获取集合的尾(不是头的就是尾)
println(list.tail) //List(3, 5, 7, 9, 11)
//获取集合最后一个元素
println(list.last) //11
//获取集合初始数据(不包含最后一个元素)
println(list.init) //List(1, 3, 5, 7, 9)
//反转
println(list.reverse) //List(11, 9, 7, 5, 3, 1)
//取前(后)n个元素
println(list.take(3)) //List(1, 3, 5)
println(list.takeRight(3)) //List(7, 9, 11)
//去掉前(后)n个元素
println(list.drop(2)) //List(5, 7, 9, 11)
println(list.dropRight(3)) //List(1, 3, 5)
//并集
val union = list.union(list2)
println(union) //List(1, 3, 5, 7, 9, 11, 1, 22, 7, 44, 3, 11, 8, 7)
println(list:::list2) //List(1, 3, 5, 7, 9, 11, 1, 22, 7, 44, 3, 11, 8, 7)
//交集
val inter = list.intersect(list2) //List(1, 3, 7, 11)
println(inter)
//差集
val diff1 = list.diff(list2)
println(diff1) //List(5, 9)
//拉链
val tuples:List[(Int,Int)] = list.zip(list2)
println(tuples) //List((1,1), (3,22), (5,7), (7,44), (9,3), (11,11))
//滑窗
for(elem <- list.sliding(3)){ //窗口的大小为3,默认步长为1
println(elem)
}
println("===================")
for(elem <- list.sliding(3,2)){ //窗口大小为3,步长为2
println(elem)
}
}
}
List(1, 3, 5)
List(3, 5, 7)
List(5, 7, 9)
List(7, 9, 11)
===================
List(1, 3, 5)
List(5, 7, 9)
List(9, 11)
集合计算高级函数
-
说明
(1)过滤
:遍历一个集合并从中获取满足指定条件的元素组成一个新的集合
(2)转化/映射(map)
:将集合中的每一个元素映射到某一个函数
(3)扁平化
(4)扁平化+映射
注:flatMap相当于先进行map操作,再进行flatten操作,集合中的每个元素的子元素映射到某个函数并返回新集合
(5)分组(group)
:按照指定的规则对集合的元素进行分组
(6)简化(规约)
(7)折叠
:fold 函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到 list 中的所有元素被遍历
所谓的聚合操作指的是将一个列表中的数据合并为一个
.package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
// 1.过滤 (选取偶数)
// val evenList = list.filter((elem: Int) => { elem % 2 == 0 })
val eventList = list.filter(_ % 2 == 0)
println(eventList) //List(2, 4, 6, 8)// 2. map (将集合中的每个元素乘2) //val list1 = list.map((elem: Int) => { elem * 2 }) val list1 = list.map(_ * 2) println(list1) // List(2, 4, 6, 8, 10, 12, 14, 16, 18) // 求平方 val list2 = list.map(x => x * x) println(list2) //List(1, 4, 9, 16, 25, 36, 49, 64, 81) // 3.扁平化 val list3: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) val list4 = list3(0) ::: list3(1) ::: list3(2) val list5 = list3(0) ++ list3(1) ++ list3(2) println(list4) //List(1, 2, 3, 4, 5, 6, 7, 8, 9) println(list5) //List(1, 2, 3, 4, 5, 6, 7, 8, 9) val flatList = list3.flatten println(flatList) //List(1, 2, 3, 4, 5, 6, 7, 8, 9) // 4.扁平化+映射 (将一组字符串进行分词,并保存成单词的列表) val strings: List[String] = List("hello scala", "hello spark", "hello flume", "hello kafka", "we study") val splitList: List[Array[String]] = strings.map(_.split(" ")) //分词 println(splitList.flatten) //List(hello, scala, hello, spark, hello, flume, hello, kafka, we, study) println("==============================") val flatmapList = strings.flatMap(_.split(" ")) println(flatmapList) //List(hello, scala, hello, spark, hello, flume, hello, kafka, we, study) // 5.分组 (分成奇偶两组) Map的key是组名,value是这一组中的所有元素 val groupMap: Map[Int, List[Int]] = list.groupBy(_ % 2) // 组名: 0 1 val groupMap2: Map[String, List[Int]] = list.groupBy(elem => { if (elem % 2 == 0) "偶数" else "奇数" }) //组名(两组): 偶数 奇数 println(groupMap) //Map(1 -> List(1, 3, 5, 7, 9), 0 -> List(2, 4, 6, 8)) println(groupMap2) //Map(奇数 -> List(1, 3, 5, 7, 9), 偶数 -> List(2, 4, 6, 8)) println("================================") //给定一组词汇,按照单词的首字母进行分组 val wordList: List[String] = List("java", "scala", "hadoop", "kafka", "flume", "hbase", "spark", "flink") val charToStrings: Map[Char, List[String]] = wordList.groupBy(_.charAt(0)) println(charToStrings) //Map(s -> List(scala, spark), j -> List(java), f -> List(flume, flink), h -> List(hadoop, hbase), k -> List(kafka))
}
}package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
//1. def reduceLeft(op: (B, A) => B): B
//2. reduceLeft 高阶函数,接收的函数 op 接收两个形参
//3. op 返回的结果,会作为下一次调用 op 时的第一个参数(左边),再次传入
//4. 当把整个 list 的集合都遍历后,计算结束(递归)val list = List(1, 2, 3, 4) // 规约 // list.reduce((a:Int,b:Int) => a + b) val sum: Int = list.reduce(_ + _) //从左往右 println(sum) //10 // 从左往右加 println(list.reduceLeft(_+_)) //10 // 从右往左加 println(list.reduceRight(_+_)) //10 val list1 = List(3, 4, 5, 8, 10) println(list1.reduce(_-_)) //-24 3-4-5-8-10=-24 println(list1.reduceLeft(_-_)) //-24 3-4-5-8-10=-24 println(list1.reduceRight(_-_)) //6 3-(4-(5-(8-10)))=6 /* 源码: def reduceRight[B >: A](op: (A, B) => B): B = if (isEmpty) throw new UnsupportedOperationException("Nil.reduceRight") else if (tail.isEmpty) head else op(head, tail.reduceRight(op)) */
}
}package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
//求最小值
val list = List(3, 4, 2, 7, 5, -1, 8, 90, 999)
//val minVal = list.reduce((n1: Int, n2: Int) => { if (n1 > n2) n2 else n1 })
val minVal = list.reduce((n1, n2) => if (n1 > n2) n2 else n1 )
println(“minVal=” + minVal) // -1}
}package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
val list=List(1,2,3,4,5,6,7,8,9)
val list2=List(3,4,5,8,10)
//初始聚合状态10
val a: Int = list.fold(10)(_ + )
println(a) //55 10+1+2+3+4+5+6+7+8+9=55
println(list.foldLeft(10)(-)) //-35 10-1-2-3-4-5-6-7-8-9
println(list.foldRight(10)(-)) //-5 (9-(8-(7-(6-(5-(4-(3-(2-(1-10)))))))))=-5
println(list2.foldRight(11)(-_)) //-5 (10-(8-(5-(4-(3-11)))))=-5
}
}
合并Map
package testpackage
import scala.collection.mutable
object TestDemo2 {
def main(args: Array[String]): Unit = {
val map1 = Map("a" -> 1, "b" -> 3, "c" -> 6)
val map2 = mutable.Map("a" -> 6, "b" -> 2, "c" -> 9, "d" -> 3) //必须是可变Map
println(map1 ++ map2) //Map(a -> 6, b -> 2, c -> 9, d -> 3)
// 遍历map1里面的每一个元素,去判断key是否在map2中,更新map2的值,所以map2必须是可变Map
//map2初始聚合状态 mergeMap当前聚合状态
/*注意:不能使用flod 因为
def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op)
map2和merageMap的类型都是Map,而kv是元组,类型不一致
*/
// def foldLeft[B](z: B)(op: (B, A) => B): B
val map3 = map1.foldLeft(map2)((mergeMap, kv) => {
val key: String = kv._1
val value = kv._2
mergeMap(key) = mergeMap.getOrElse(key, 0) + value
mergeMap
})
println(map3) //Map(b -> 5, d -> 3, a -> 7, c -> 15)
println(map2) //Map(b -> 5, d -> 3, a -> 7, c -> 15)
}
}
普通WordCount案例
-
需求:
单词计数:将集合中出现的相同的单词,进行计数,取计数排名前三的结果.package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
val stringList: List[String] = List(“hello java”, “hello scala”, “hello spark”, “hello hadoop zookeeper”, “hello scala flink”, “scala flink flume”)
// 1.对字符串进行切分,得到一个打散所有单词的列表
// val wordList1: List[Array[String]] = stringList.map(.split(" "))
// val wordList2: List[String] = wordList1.flatten
// println(wordList2)
val strings: List[String] = stringList.flatMap(.split(" "))
println(strings)
//2.相同的单词进行分组(注意:此时不能将其改成_,因为编译器容易出现混淆)
val groupMap: Map[String, List[String]] = strings.groupBy(word => word)
println(groupMap)
//3.对分组之后的List取长度,得到每个单词的个数
val countMap: Map[String, Int] = groupMap.map(kv => (kv._1, kv.2.length))
//4.将Map转换为List,并排序取前3
// val list: List[(String, Int)] = countMap.toList
// val sortList: List[(String, Int)] = list.sortWith(._2 > _._2)
// val result: List[(String, Int)] = sortList.take(3)
// println(result) //List((hello,5), (scala,3), (flink,2))val result: List[(String, Int)] = countMap.toList .sortWith(_._2 > _._2) .take(3) println(result)
}
}
复杂WordCount案例
package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
val stringList: List[(String, Int)] = List(
("hello java", 2), ("hello scala", 3),
("hello spark", 2), ("hello hadoop zookeeper", 4),
("hello scala flink", 2), ("scala flink flume", 1)
)
//思路1:直接展开为普通版本
val strings: List[String] = stringList.map(kv => {
(kv._1.trim + " ") * kv._2
})
println(strings) //List(hello java hello java , hello scala hello scala hello scala , hello spark hello spark , hello hadoop zookeeper hello hadoop zookeeper hello hadoop zookeeper hello hadoop zookeeper , hello scala flink hello scala flink , scala flink flume )
//接下来操作与普通版本一致
val result: List[(String, Int)] = strings
.flatMap(_.split(" ")) //空格分词
.groupBy(word => word) //按照单词分组
.map(kv => (kv._1, kv._2.size)) //统计出每个单词的个数
.toList
.sortBy(_._2)(Ordering[Int].reverse)
.take(3)
println(result) //List((hello,13), (scala,6), (hadoop,4))
println("=======================================================")
//思路2:直接基于预统计的结果进行转换
//1.将字符串打散为单词,并结合对应的个数包装成二元组
val tuples: List[(String, Int)] = stringList.flatMap(tuple => {
val strings1: Array[String] = tuple._1.split(" ")
strings1.map(word => (word, tuple._2))
})
println(tuples) //List((hello,2), (java,2), (hello,3), (scala,3), (hello,2), (spark,2), (hello,4), (hadoop,4), (zookeeper,4), (hello,2), (scala,2), (flink,2), (scala,1), (flink,1), (flume,1))
//2.对二元组按照单词进行分组
val preCountMap: Map[String, List[(String, Int)]] = tuples.groupBy(_._1)
println(preCountMap)
//3.叠加每个单词预统计的个数值 key不变,更新value
val countMap: Map[String, Int] = preCountMap.mapValues(tupList => tupList.map(_._2).sum)
println(countMap) //Map(java -> 2, flink -> 3, hadoop -> 4, spark -> 2, scala -> 6, zookeeper -> 4, flume -> 1, hello -> 13)
//4.转换成List,排序取前3
val countLIst: List[(String, Int)] = countMap
.toList
.sortWith(_._2 > _._2)
.take(3)
println(countLIst) //List((hello,13), (scala,6), (hadoop,4))
}
}
队列
-
说明
Scala也提供了队列Queue
的数据结构,队列的特点是先进先出
.进队和出队的方法分别为enqueue
和dequeue
-
队列是一个
有序列表
,在底层可以用数组或是链表来实现。 -
其输入和输出要遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出.
-
在 Scala 中,由设计者直接给我们提供队列类型使用。
-
在 scala 中, 有 scala.collection.mutable.Queue 和 scala.collection.immutable.Queue , 一般来说,我们在开发中通常使用可变集合中的队列.
package testpackage
import scala.collection.immutable.Queue
import scala.collection.mutableobject TestDemo2 {
def main(args: Array[String]): Unit = {
//创建一个可变队列
val que: mutable.Queue[String] = new mutable.QueueString
//enqueue 的操作,表示给队列尾部加入数据
que.enqueue(“a”,“b”,“c”,“d”)
println(que) //Queue(a, b, c, d)
//dequeue 的操作,是出队列(即将队列的头元素取出)
println(que.dequeue()) //a
println(que) //Queue(b, c, d)
println(que.dequeue()) //b
println(que) //Queue(c, d)
println(que.dequeue()) //c
val str: String = que.dequeue()
println(str) //d
println(que) //Queue()
println(“===========================================”)
//创建一个不可变队列
val que2: Queue[String] = Queue(“a”, “b”, “c”) //不能new 创建不可变队列
val newQue: Queue[String] = que2.enqueue(“d”)
println(que2) //Queue(a, b, c)
println(newQue) //Queue(a, b, c,d)
}
}
并行集合
-
说明
Scala为了充分使用多核CPU
,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算. -
主要用到的算法有: Divide and conquer : 分治算法,Scala 通过 splitters(分解器),combiners(组合器)等抽象层来实现,主要原理是将 计算工作
分解为很多的子任务
,分发
给一些处理器去完成,并将它们处理的结果合并
返回 .
Work stealin 算法(工作窃取算法),主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务 之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干
,这样达到尽早干完的目的package testpackage
import scala.collection.immutable
object TestDemo2 {
def main(args: Array[String]): Unit = {
//串行化执行
val result1: immutable.IndexedSeq[Long] = (0 to 100).map { x => Thread.currentThread.getId }.distinct
//并行执行
val result2 = (0 to 100).par.map { x => Thread.currentThread.getId }.distinct
println(result1) //Vector(1)
println(result2) //ParVector(12, 22, 18, 14, 17, 20, 19, 23, 21, 16, 13, 15)
}
}
Option类型
-
概述
实际开发中,在返回一些数据时,难免会遇到空指针异常(NullPointerException),遇到一次就处理一次相对来讲还是比较繁琐的,在Scala中,我们返回某些数据时,可以返回一个Option类型的对象来封装具体的值,从而实现有效的避免空指针异常. -
格式
在Scala中,Option类型表示可选值,这种类型的数据有两种形式:
(1)Some(x):表示实际的值
(2)None:表示没有值package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
// 普通实现
val result: Option[Int] = divide(10, 2)
println(result) //Some(5)
//通过模式匹配实现
result match {
case None => println(“除数不能为0”)
case Some(x) => println(s"商为$x") //商为5
}
//通过getOrElse()实现
println(result.getOrElse(“除数不能为0”)) //5
}// 定义一个两数相除的方法,使用Option类型来封装结果
def divide(a: Int, b: Int): Option[Int] = {
if (b == 0) { //除数为0
None
} else {
Some(a / b)
}}
}
注意:使用getOrElse()
方法时,当值为None时可以指定一个默认值.
模式匹配
基本介绍
- Scala 中的模式匹配类似于 Java 中的 switch 语法,但是更加强大。
- 模式匹配语法中,采用
match 关键字声明
,每个分支采用case 关键字
进行声明,当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果 所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句
应用案例
package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
val x: Int = 6
val y: String = x match {
case 1 => "one"
case 2 => "two"
case 3 => "three"
case _ => "other"
}
println(y) //other
println("=============================")
val a = 10
val b = 34
//用模式匹配实现简单的二元运算
def matchDualOp(op: Char): Int = op match {
case '+' => a + b
case '-' => a - b
case '*' => a * b
case '/' => a / b
case '%' => a % b
case _ => -1
}
println(matchDualOp('+')) //44
}
}
- 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句
- 如果所有 case 都不匹配,又没有写 case _ 分支,那么会
抛出 MatchError 异常
- 每个 case 中,
不用 break 语句
,自动中断 case - 可以在 match 中使用其它类型,而不仅仅是字符,可以是表达式
=>
等价于 java swtich 的:
- => 后面的代码块,直到下一个 case语句之前的代码, 是
作为一个整体执行
,可以使用{ } 括起来,也可以不括.
模式守卫
-
说明
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫.
所谓的守卫指的是在case语句中添加if条件判断
, 这样可以让我们的代码更简洁, 更优雅.package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
//求一个整数的绝对值
def abs(num:Int)=num match {
case i:Int if i>=0 => i //定义一个变量i来接收值
case j if j<0 => -j
case _ => “type illegal”
}
println(abs(10))
println(abs(-66))
}
}
模式匹配类型
匹配常量
-
说明
Scala中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等.package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
// 1.匹配常量
def describeConst(x: Any): String = x match {
case 1 => “Int one”
case “hello” => “String hello”
case true => “Boolean true”
case ‘+’ => “Char +”
//case _ => “Not”
case a => “Not” //也可以用一个变量来接收当前的值
}println(describeConst("hello")) println(describeConst(1)) println(describeConst(99))
}
}
匹配类型
-
说明
可以匹配对象的任意类型,这样做避免了使用 isInstanceOf 和 asInstanceOf 方法 -
类型匹配注意事项
(1) Map[String, Int] 和 Map[Int, String]从形式上不同,底层会进行类型擦除。List 也会进行类型擦除 .
(2) 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错.package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
//2.匹配类型
def describeType(x: Any): String = x match {
case i: Int => "Int " + i
case s: String => "String " + s
case list: List[String] => “List[String] " + list
case array: Array[Int] => “Array[Int] " + array.mkString(”,”)
case a => "Something else: " + a
}
println(describeType(100)) //Int 100
println(describeType(“hello”)) //String hello
//List存在泛型擦除(它只能判断到当前集合类型是List类型,里边的泛型是直接被擦掉的)
println(describeType(List(11,22,33,44,55))) //List[String] List(11, 22, 33, 44, 55)
println(describeType(List(“java”,“scala”))) //List[String] List(java, scala)
//Array不存在泛型擦除
println(describeType(Array(“hello”,“java”))) //Something else: [Ljava.lang.String;@75bd9247
}
}
匹配数组
-
Array(0) 匹配只有一个元素且为 0 的数组。
-
Array(x,y) 匹配有两个元素的数组,并将这两个元素赋值给 x 和 y。
当然可以依次类推 Array(x,y,z) 匹配数组有 3 个 元素的等等… -
Array(0,_*) 匹配数组以 0 开始
package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
//3.匹配数组
for (arr <- List(
Array(0),
Array(1, 0),
Array(4,5),
Array(0, 1, 0),
Array(1, 1, 0),
Array(2, 3, 7, 15),
Array(“hello”, 20, 30)
)) {
val result:String = arr match {
case Array(0) => “0”
case Array(1, 0) => “Array(1,0)”
case Array(x, y) => "Array: " + x + “,” + y //匹配两元素数组
case Array(0, _*) => “以0开头的数组”
case Array(x, 1, z) => “中间为1的三元素数组”
case _ => “something else”
}
println(result)
}
}
}
输出为:
0
Array(1,0)
Array: 4,5
以0开头的数组
中间为1的三元素数组
something else
something else
匹配列表
package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
// 4.匹配列表
for (list <- List(
List(0),
List(1, 0),
List(0, 0, 0),
List(1, 1, 0),
List(88),
List("hello")
)) {
val result: String = list match {
case List(0) => "0"
case List(x, y) => "List(x,y): " + x + "," + y
// case x :: y :: Nil => "List(x,y): " + x + "," + y // 匹配的是有两个元素的 List(x,y)
case List(0, _*) => "List(0,...)" //匹配的是以0开头后面有任意元素的List
case List(a) => "List(a): " + a
case _ => "something else"
}
println(result)
}
println("=============================================")
val list = List(1, 2, 3, 4, 6, 9, 7, 12)
list match {
case first :: second :: rest => println(s"first:$first,second:$second,rest:$rest")
case _ => println("something else")
}
val list2 = List(7, 12)
list2 match {
case first :: second :: rest => println(s"first:$first,second:$second,rest:$rest")
case _ => println("something else")
}
val list3 = List(12)
list3 match {
case first :: second :: rest => println(s"first:$first,second:$second,rest:$rest")
case _ => println("something else")
}
}
}
输出为:
0
List(x,y): 1,0
List(0,...)
something else
List(a): 88
List(a): hello
=============================================
first:1,second:2,rest:List(3, 4, 6, 9, 7, 12)
first:7,second:12,rest:List()
something else
list1 match {
case 0 :: Nil => println("匹配: 只有一个0元素的列表")
case 0 :: tail => println("匹配: 0开头, 后边元素无所谓(0到多个)的列表")
case x :: y :: Nil => println(s"匹配: 只有两个元素的列表, 元素为: ${x}, ${y}")
case _ => println("未匹配")
}
匹配元组
如果在case校验的时候, 变量没有被使用, 则可以用_
替代.
package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
// 5.匹配元组
for (tuple <- List(
(0, 1),
(0, 0),
(0, 1, 0),
(0, 1, 1),
(1, 23, 56),
("hello", true, 0.5)
)) {
val result: String = tuple match {
case (a, b) => "" + a + "," + b
case (0, _) => "(0,_)" // 表示匹配 0 开头的二元组
case (a, 1, _) => "(a,1,_) " + "a=" + a
case _ => "something else"
}
println(result)
}
println("===============================")
//在变量声明时匹配
val (x, y): (Int, String) = (10, "hello")
println(s"x:$x,y:$y") //x:10,y:hello
val List(first, second, _*) = List(23, 15, 9, 78)
println(s"first=$first,second=$second") //first=23,second=15
val fir :: sec :: rest = List(23, 15, 9, 78)
println(s"fir=$fir,sec=$sec,rest=$rest") //fir=23,sec=15,rest=List(9, 78)
println("==========================================")
// for推导式中进行模式匹配
val list: List[(String, Int)] = List(("a", 12), ("b", 45), ("c", 27))
//原本的遍历方式
for (elem <- list) {
println(elem._1 + " " + elem._2)
}
//将list的元素直接定义为元组,对变量赋值
for ((word, count) <- list) {
println(word + ":" + count)
}
//可以不考虑某个位置的变量,只遍历key或者value
for ((word, _) <- list) {
print(word+" ") //a b c
}
println()
//可以指定某个位置的值必须是多少
for (("a", count) <- list) {
println(count) //12
}
}
}
输出为:
0,1
0,0
(a,1,_) a=0
(a,1,_) a=0
something else
something else
===============================
x:10,y:hello
first=23,second=15
fir=23,sec=15,rest=List(9, 78)
==========================================
a 12
b 45
c 27
a:12
b:45
c:27
a b c
12
变量声明中的模式匹配
在定义变量时,可以使用模式匹配快速获取数据. 例如: 快速从数组,列表中获取数据
.
object TestDemo2 {
def main(args: Array[String]): Unit = {
//1. 生成包含0-10数字的数组,使用模式匹配分别获取第二个、第三个、第四个元素
//1.1 生成包含0-10数字的数组
val arr = (0 to 10).toArray
//1.2 使用模式匹配分别获取第二个、第三个、第四个元素
val Array(_, x, y, z, _*) = arr
//1.3 打印结果.
println(x, y, z)
println("-" * 15)
//2. 生成包含0-10数字的列表,使用模式匹配分别获取第一个、第二个元素
//2.1 生成包含0-10数字的列表,
val list = (0 to 10).toList
//2.2 使用模式匹配分别获取第一个、第二个元素
//思路一: List() 实现
val List(a, b, _*) = list
//思路二: ::, tail 实现.
val c :: d :: tail = list
//2.3 打印结果.
println(a, b)
println(c, d)
}
}
匹配for表达式
Scala中还可以使用模式匹配来匹配for表达式,从而实现快速获取指定数据, 让我们的代码看起来更简洁, 更优雅.
object TestDemo2 {
def main(args: Array[String]): Unit = {
//1. 定义变量记录学生的姓名和年龄.
val map1 = Map("张三" -> 23, "李四" -> 24, "王五" -> 23, "赵六" -> 26)
//2. 获取所有年龄为23的学生信息.
//2.1 格式一: 通过if语句实现
for((k,v) <- map1 if v == 23) println(s"${k} = ${v}")
//2.2 格式二: 通过固定值实现.
for((k, 23) <- map1) println(k + " = 23")
}
}
匹配对象
package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
val student =Student("alice", 20)
//针对对象实例的内容进行匹配
val result = student match {
case Student("alice", 18) => "alice,18"
case _ => "else"
}
println(result) //else
}
}
//定义类
class Student(val name: String, val age: Int)
//定义伴生对象
object Student {
def apply(name: String, age: Int): Student = new Student(name, age)
//必须实现一个unapply方法,用来对 对象属性进行拆解
def unapply(student: Student): Option[(String, Int)] = {
if (student == null) {
None
} else {
Some(student.name, student.age)
}
}
}
说明:
- val student = Student(“alice”, 20)该语句在执行时,实际调用的是Student伴生对象中的apply 方法,因此不用 new 关键字 就能构造出相应的对象。
- 当将 Student(“alice”, 18) 写在 case 后时 [case Student(“alice”, 18) => “alice,18”] ,会默认调用 unapply 方法 ( 对象提取器 ) ,student作为 unapply 方法的参数 , unapply 方法将student对象的 name 和 age 属性提取出来,与 Student(“alice”, 18) 中的属性值进行匹配。
- case 中对象的 unapply 方法 ( 对象提取器 )
返回 Some ,且所有属性均一致,才算匹配成功
,属性不一致,或返回 None ,则匹配失败
。 - 若只提取对象的一个属性,则提取器为
unapply (obj:Obj): Option[ T ]
若提取对象的多个属性,则提取器为unapply (obj:Obj): Option[ (T1,T2,T3…) ]
若提取对象的可变个属性,则提取器为unapplySeq (obj:Obj): Option[ Seq[T] ]
参考链接:【Scala】match——模式匹配总结
使用样例类:
package testpackage
object TestDemo2 {
def main(args: Array[String]): Unit = {
val student =Student("alice", 20)
//针对对象实例的内容进行匹配
val result = student match {
case Student("alice", 20) => "alice,20"
case _ => "else"
}
println(result) //alice,20
}
}
case class Student(name: String,age: Int)
- 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用方法,如
apply,unapply,toString,equals,hashCode和copy
.
样例类
-
概念
使用了case关键字的类定义就是样例类case class
,样例类是种特殊的类,实现了类构造参数的getter
方法(构造参数默认被声明为val),当构造参数被声明为var
类型的,它将帮你实现setter
和getter
方法. -
样例类默认帮你实现了
toString,equals,copy和hashCode
等方法. -
样例类
可以new
,也可以不用new
. -
格式
case class 样例类名([var/val] 成员变量名1:类型1,成员变量名2:类型2,成员变量名3:类型3){ }
(1)如果不写,则变量的默认修饰符是val,即:val是可以省略不写的
(2)如果要实现某个成员变量值可以被修改,则需手动添加var来修饰此变量.
样例类的默认方法:
当我们定义一个样例类后,编译器会自动帮我们生成一些方法,常用的如下:
-
apply()方法
可以让我们快速地使用类名来创建对象,省去了new这个关键字,如val p=Person()
-
toString()方法
可以让我们通过输出语句打印对象时,直接打印该对象的各个属性值.
如println(p)
打印的是对象p的各个属性值,而不是它的地址值. -
equals()方法
可以让我们直接使用==
来比较两个样例类对象的所有成员变量值是否相等.
例如p1==p2
比较的是两个对象的各个属性值是否相等,而不是比较地址值. -
hashCode()方法
用来获取对象的哈希值的.即:同一对象哈希值肯定相同,不同对象哈希值一般不同val p1=new Person(“张三”,20)
val p2=new Person(“张三”,20)
println(p1.hashCode()==p2.hashCode()) //true -
copy()方法
可以用来快速创建一个属性值相同的实例对象,还可以使用带名参数的形式给指定的成员变量赋值.val p1=new Person(“张三”,20)
val p2=p1.copy(age=23)
println(p1) //张三,20
println(p2) //张三,23 -
unapply()方法
对象提取器.一个类要想支持模式匹配,则必须要实现一个提取器。提取器指的就是unapply()方法.package testpackage
object TestDemo4 {
def main(args: Array[String]): Unit = {
val person1 = new Person(“张三”, 12)
val person2 = Person(“李四”, 20)
val person3 = Person(“王五”, 32)
val personList: List[Person] = List(person1, person2, person3)
personList.foreach(person => {
person match {
case Person(“张三”, 12) => println(“张三”)
case Person(“李四”, 32) => println(“李四”)
case Person(“王五”, 32) => println(“王五”)
case _ => println(“no match”)
}
})
}
}case class Person(name: String, age: Int)
结果为:
张三
no match
王五
偏函数中的模式匹配
偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查,例如该偏函数的输入类型是List[Int],而我们需要的是第一个元素是0的集合,这就是通过模式匹配实现的.
-
偏函数定义
val second: PartialFunction[List[Int]],Option[Int]]={
case x::y::_ =>Some(y) //case语句
}
偏函数名second
偏函数类型PartialFunction[List[Int]],Option[Int]]
参数类型List[Int]
返回值类型Option[Int]]
注:该偏函数的功能是返回输入的List集合的第二个元素
-
(1)定义:
所谓的偏函数是指被包在花括号内没有match的一组case语句, 偏函数是PartialFunction[A, B]类型的的一个实例对象, 其中A代表输入参数类型, B代表返回结果类型.
(2)格式:val 对象名 = { //这对大括号及其内部的一组case语句, 就组成了一个偏函数.
case 值1 => 表达式1
case 值2 => 表达式2
case 值3 => 表达式3
…
}object TestDemo2 {
def main(args: Array[String]): Unit = {
//1. 定义一个偏函数, 根据指定格式返回
val pf: PartialFunction[Int, String] = {
case 1 => “一”
case 2 => “二”
case 3 => “三”
case _ => “其他”
}
// 2. 调用方法
println(pf(1))
println(pf(2))
println(pf(3))
println(pf(4))
}
}
需求:结合map函数使用
-
定义一个列表,包含1-10的数字
-
请将1-3的数字都转换为[1-3]
-
请将4-8的数字都转换为[4-8]
-
将其他的数字转换为(8-*]
-
打印结果.
object TestDemo2 {
def main(args: Array[String]): Unit = {
//1. 定义一个列表,包含1-10的数字
val list1 = (1 to 10).toList
// 核心: 通过偏函数结合map使用, 来进行模式匹配
val list2 = list1.map {
// 2 请将1-3的数字都转换为[1-3]
case x if x >= 1 && x <= 3 => “[1-3]”
//3 请将4-8的数字都转换为[4-8]
case x if x >= 4 && x <= 8 => “[4-8]”
// 4 将其他的数字转换为(8-]
case _ => "(8-]"
}
// 5. 打印结果.
println(list2)
}
}package testpackage
object TestDemo4 {
def main(args: Array[String]): Unit = {
val list: List[(String, Int)] = List((“a”, 12), (“b”, 35), (“c”, 27), (“a”, 13))
//1.map转换,实现key不变,value*2
val newList: List[(String, Int)] = list.map(tuple => (tuple._1, tuple._2 * 2))
//2.用模式匹配对元组元素赋值,实现功能
val newList2: List[(String, Int)] = list.map(tuple => {
tuple match {
case (word, count) => (word, count * 2)
}
})
//3.省略lambda表达式的写法,进行简化 偏函数
val newList3: List[(String, Int)] = list.map {
case (word, count) => (word, count * 2)
}
//偏函数的应用,求绝对值
//对输入数据分为不同的情形:正,负,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
}
//偏函数(部分函数)orElse偏函数(部分函数)orElse偏函数=一个完整的函数
def abs(x:Int):Int=(positiveAbs orElse negativeAbs orElse zeroAbs) (x)
println(abs(-97)) //97
}
}
序列化和反序列化
在Scala中, 如果想将对象传输到其他虚拟机, 或者临时存储, 就可以通过序列化和反序列化
来实现了.
- 序列化: 把对象写到文件中的过程.
- 反序列化: 从文件中加载对象的过程.
注意: 一个类的对象要想实现序列化和反序列化操作, 则该类必须继承Serializable特质
.
需求:
-
定义样例类Person, 属性为姓名和年龄.
-
创建Person样例类的对象p.
-
通过序列化操作将对象p写入到项目下的data文件夹下的4.txt文本文件中.
-
通过反序列化操作从项目下的data文件夹下的4.txt文件中, 读取对象p.
package testpackage
import java.io.{FileInputStream, FileOutputStream, ObjectInputStream, ObjectOutputStream}
object TestDemo2 {
def main(args: Array[String]): Unit = {
//1.演示序列化操作,即将对象写入到文件中
//创建Person类型的对象p
val p: Person1 = Person1(“张三”, 20)
//创建序列化流,用来将对象写入到文件中
val oos: ObjectOutputStream = new ObjectOutputStream(new FileOutputStream(“./data/4.txt”))
//调用writeObject()方法,将对象写入到文件中
oos.writeObject§
//关闭序列化流
oos.close()//2.演示反序列化操作,即:从文件中直接读取对象 //创建反序列化流,关联数据源文件 val ois = new ObjectInputStream(new FileInputStream("./data/4.txt")) //调用readObject()方法,从数据源文件中读取指定的对象 //我们获取到的对象是AnyRef类型,所以需要转换成Person类型 val person: Person1 = ois.readObject().asInstanceOf[Person1] println(person.name + " " + person.age)
}
}//样例类
case class Person1(var name: String, var age: Int)
正则表达式
概述 :
所谓的正则表达式指的是正确的, 符合特定规则的式子
, 它是一门独立的语言, 并且能被兼容到绝大多数的编程语言中.
在scala中, 可以很方便地使用正则表达式来匹配数据。具体如下:
- Scala中提供了Regex类来定义正则表达式.
- 要构造一个Regex对象,直接使用String类的
r方法
即可. - 建议使用三个双引号来表示正则表达式,不然就得对正则中的反斜杠进行转义.
格式:
val 正则对象名 = """具体的正则表达式""".r
注意: 使用findAllMatchIn方法可以获取到所有正则匹配到的数据(字符串).
object TestDemo2 {
def main(args: Array[String]): Unit = {
//需求: 定义一个正则表达式,来匹配邮箱是否合法
// 1. 定义一个字符串, 表示邮箱
val email = "qq12344@163.com"
// 2. 定义一个正则表达式, 用来校验邮箱.
/*
. 表示任意字符
+ 数量词, 表示前边的字符出现至少1次, 至多无所谓.
@ 表示必须是@符号, 无特殊含义.
. 因为.在正则中有特殊的含义, 所以要转义一下, 使它变成普通的.
*/
//创建一个正则对象
val regex: Regex = """.+@.+..+""".r
//3. 打印结果
if (regex.findAllMatchIn(email).size != 0) {
// 合法邮箱
println(s"${email} 是一个合法的邮箱!")
} else {
println(s"${email} 是一个非法的邮箱!")
}
}
}
示例: 获取邮箱运营商
需求:
-
定义列表, 记录以下邮箱:
"38123845@qq.com", "a1da88123f@gmail.com", "zhansan@163.com", "123afadff.com"
-
使用正则表达式进行模式匹配,匹配出来邮箱运营商的名字。
例如:邮箱zhansan@163.com,需要将163(运营商的名字)匹配出来.
提示:1. 使用括号来匹配分组. 2. 打印匹配到的邮箱以及运营商.object TestDemo2 {
def main(args: Array[String]): Unit = {
//1. 定义列表, 记录邮箱.
val emlList = List(“38123845@qq.com”, “a1da88123f@gmail.com”, “zhansan@163.com”, “123afadff.com”)
// 2. 定义正则表达式.
val regex = “”“.+@(.+)…+”“”.r
// 3. 根据 模式匹配 匹配出所有合法的邮箱及其对应的运营商.
val result = emlList.map {
// email就是emlList这个列表中的每一个元素.
// company表示: 正则表达式中你用()括起来的内容, 也就是分组的数据.
// @regex()是固定写法
case email@regex(company) => email -> s"${company}" //元组
case email => email -> “未匹配”
}
// 4. 打印结果
println(result) //List((38123845@qq.com,qq), (a1da88123f@gmail.com,gmail), (zhansan@163.com,163), (123afadff.com,未匹配))
}
}
异常
(1) Java异常处理
package testpackage;
public class JavaException {
public static void main(String[] args) {
try {
int i = 0;
int b = 10;
int c = b / i;
// 执行代码时,会抛出 ArithmeticException 异常
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (Exception e) {
//catch时,需要将范围小的写到前面
e.printStackTrace();
} finally {
// 最终要执行的代码
System.out.println("java finally");
}
System.out.println("程序继续执行~~"); }
}
- java 语言按照
try—catch-catch...—finally
的方式来处理异常 - 不管有没有异常捕获,都会执行 finally, 因此通常可以在 finally 代码块中释放资源
- 可以有多个 catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。会提示 “Exception ‘java.lang.xxxxxx’ has already been caught”
(2)Scala异常处理
package testpackage
object ScalaException {
def main(args: Array[String]): Unit = {
try {
var res = 10 / 0
} catch {
case ex: ArithmeticException => {
println("算术异常=" + ex.getMessage)
}
case ex: Exception =>{
//对异常进行处理
println("异常信息=" + ex.getMessage)
}
} finally {
println("finaly 的代码...")
}
println("程序继续....")
}
}
输出为:
算术异常=/ by zero
finaly 的代码...
程序继续....
(3)Scala 异常处理小结
-
我们将可疑代码封装在 try 块中。 在 try 块之后使用了一个 catch 处理程序来捕获异常。如果发生任何异常, catch 处理程序将处理它,程序将不会异常终止。
-
Scala 的异常的工作机制和 Java 一样,但是
Scala 没有“checked(编译期)”异常
,即 Scala 没有编译异常
这 个概念,异常都是在运行的时候捕获处理。 -
用 throw 关键字,抛出一个异常对象。所有
异常都是 Throwable 的子类型
。throw 表达式是有类型的,就是Nothing
,因为 Nothing 是所有类型的子类型,所以 throw 表达式可以用在需要类型的地方.def main(args: Array[String]): Unit = {
val res: Nothing = test()
println(res)
}
def test(): Nothing = {
throw new Exception(“异常”)
} -
异常捕捉的机制与其他语言中一样,如果有异常发生,catch 子句是按次序捕捉的。因此,
在 catch 子句中, 越具体的异常越要靠前,越普遍的异常越靠后
,如果把越普遍的异常写在前,把具体的异常写在后,在 scala 中也 不会报错(不报错,但是不推荐),但这样是非常不好的编程风格。 -
finally 子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般
用于对象的清理工作
,这点 和 Java 一样。 -
Scala 提供了 throws 关键字来
声明异常
。可以使用方法定义声明异常。它向调用者函数提供了此方法可能 引发此异常的信息
。 它有助于调用函数处理并将该代码包含在 try-catch 块中,以避免程序异常终止。在 scala 中, 可以使用 throws 注释来声明异常def main(args: Array[String]): Unit = {
f11()
}
@throws(classOf[NumberFormatException])//等同于 NumberFormatException.class
def f11() = {
“abc”.toInt
}
隐式转换
当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译.
隐式函数
-
隐式转换可以在不需要改任何代码的情况下,扩展某个类的功能.
-
隐式转换函数是以
implicit 关键字
声明的带有单个参数
的函数。该函数是被自动调用的,将值从一种类型转换 为另一种类型
-
隐式转换函数的
函数名可以是任意的
,隐式转换与函数名称无关,只与函数签名(函数参数类型和返回值类型
)有关。 -
隐式函数可以有多个(即:隐式函数列表),但是需要保证在当前环境下,只有一个隐式函数能被识别
def main(args: Array[String]): Unit = {
//在当前环境中,不能存在满足条件的多个隐式函数
// implicit def fun1(d: Double) = d.toIntimplicit def fun2(d: Double) = d.toInt //(x) 在转换时,识别出有两个方法(fun1和fun2)可以被使用,就不能确定调用哪一个,所以出错 val num:Int=3.5 println(num) //3
}
package testpackage
object TestImplicitFunction {
//使用implicit关键字声明的函数称之为隐式函数
implicit def convert(arg: Int): MyRichInt = {
new MyRichInt(arg)
}def main(args: Array[String]): Unit = {
//val richInt = new MyRichInt(12)
// println(richInt.myMax(66)) //66
/* 当想调用对象功能(如:myMax())时,如果编译错误,那么编译器会尝试在当前作用域范围内查找能调用对应功能的转换规则,
这个调用过程是由编译器完成的,所以称之为隐式转换,也称之为自动转换.
*/
println(2.myMax(6))
}
}class MyRichInt(val self: Int) {
def myMax(i: Int): Int = {
if (self < i) i else self
}def myMin(i: Int): Int = {
if (self < i) self else i
}
}
隐式参数
-
隐式参数: 指的是用
implicit关键字修饰的变量
. -
普通方法或者函数中的参数可以通过implicit关键字声明为隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值.
-
同一个作用域中,相同类型的隐式值只能有一个
-
编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关.
-
隐式参数优先于默认参数
package testpackage
object TestImplicitValue {
//隐式变量(隐式值)
implicit val str: String = “hello scala”/*
def hello()(implicit arg:String=“very good”):Unit={
println(arg)
}
/
/ def hello(implicit arg:String=“very good”):Unit={
println(arg)
}*/
//简便写法
def hello(): Unit = {
//调用implicitly方法 想要拿到一个String类型的隐式参数
println(implicitly[String])
}def main(args: Array[String]): Unit = {
//因为上面省略了(),所以不能加()
hello
}
}
隐式类
-
在Scala2.10后提供了隐式类,可以使用implicit声明类,隐式类非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用.
-
隐式类所带的
构造参数有且只能有1个
-
隐式类必须被定义在"类"或"伴生对象"或"包对象"里,即隐式类不能是
顶级的
-
隐式类不能是 case class(样例类)
-
作用域内不能有与之相同名称的标识符
package testpackage
object TestImplicitClass {
implicit class MyRichInt(arg: Int) {
def myMax(i: Int): Int = {
if (arg < i) i else arg
}def myMin(i: Int): Int = { if (arg < i) arg else i } }
def main(args: Array[String]): Unit = {
println(1.myMax(3)) //3
}
}
隐式解析机制
即编译器是如何查找到缺失信息的,解析具有以下两种规则:
- 首先会在
当前代码作用域下查找隐式实体
(隐式方法、隐式类、隐式对象)。(一般是这种情况) - 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指
与该类 型相关联的全部伴生对象
以及该类型所在包的包对象
,一个隐式实体的类型 T 它的查找范围如下(第二种情况范围广且复杂在使用时,应当尽 量避免出现
):
a) 如果 T 被定义为 T with A with B with C,那么 A,B,C 都是 T 的部分,在 T 的隐式解析过程中,它们的伴生对象都会被搜索。
b) 如果 T 是参数化类型,那么类型参数和与类型参数相关联的部分都算作 T 的部分,比如 List[String]的隐式搜索 会搜索 List 的伴生对象和 String 的伴生对象。
c) 如果 T 是一个单例类型 p.T,即 T 是属于某个 p 对象内,那么这个 p 对象也会被搜索。
d) 如果 T 是个类型注入 S#T,那么 S 和 T 都会被搜索。
泛型
协变和逆变
-
语法
class MyList[+T]{ //协变 }
class MyList[-T]{ //逆变 }
class MyList[T] //不变
-
说明
协变:Son是Father的子类
,则MyList[Son]也作为MyList[Father]的子类
.
逆变:Son是Father的子类
,则MyList[Son]也作为MyList[Father]的父类
.
不变:不管Son和Father是什么关系,MyList[Son]与MyList[Father]都无父子关系
.package testpackage
object TestDemo4 {
def main(args: Array[String]): Unit = {
//1. 协变
val childList: MyCollection1[Parent] = new MyCollection1[Child]
//2. 逆变
val MyList: MyCollection2[Child] = new MyCollection2[Parent]
}
}//定义继承关系
class Parent {}class Child extends Parent {}
//定义带泛型的集合类型
class MyCollection1[+E] {}class MyCollection2[-E] {}
泛型上下限
-
语法
class PersonList[T<:Person]{ //泛型上限 }
class PersonList[T>:Person]{ //泛型下限 }
-
说明
泛型的上下限的作用是对传入的泛型进行限定.package testpackage
object TestDemo4 {
def main(args: Array[String]): Unit = {
test(new Child) //testpackage.Child
test[Child](new SubChild) //testpackage.SubChild
}//泛型通配符之上限
def test[A <: Child](a: A): Unit = {
println(a.getClass.getName)
}//泛型之通配符之下限
/* def test[A >: Child](a: A): Unit = {
println(a)
}*//* //泛型之通配符之下限 形式扩展
def test[A >: Child](a: A): Unit = {
println(a.getClass.getName)
}*/
}//定义继承关系
class Parent {}class Child extends Parent {}
class SubChild extends Child {}
上下文限定
-
语法:
def fun[A:B](a:A)=println(a)
等同于def fun[A](a:A)(implicit arg:B[A])=println(a)
-
说明:上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同, 使用上下文限定[A:Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过
implicitly[Ordering[A]]
获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生错误.implicit val x:Int=10 val y=implicitly[Int] val z=implicitly[Double] //报错 println(y) //10