Scala学习笔记-详细记录学习中遇到的知识点
目录
- 输入
- print输出
- 变量
- 数据类型
- 标识符与关键字
- 运算符优先级
- if else
- for循环
- yield生成器
- break与continue
- 函数
- 局部函数
- 惰性lazy
- 异常
- 类与对象
- 构造器
- 构造器参数
- @BeanPropetry
- 前置条件
- 包
- 访问修饰符
- 继承
- 重写
- 抽象类
- 伴生对象
- apply方法
- 特质
- 提供特质隐藏类
- 动态混入
- 叠加特质
- 扩展类的特质
- 自身类型
- 内部类
- 类型投影
- 隐式转换
- 隐式值
- 隐式类
- 集合
- 定长数组Array
- 变长数组ArrayBuffer
- 多维数组
- 数组转换
- Scala与Java集合互换
- 元组Tuple
- 不可变列表List
- 可变列表ListBuffer
- 队列Queue
- 集合Map
- 集Set
- map 映射操作
- 扁平化映射flatMap
- 过滤filter
- 化简reduce
- 折叠fold
- 扫描scan
- 拉链zip
- 迭代器iterator
- 流Stream
- 视图view
- 并行集合par
- WordCount
- 模式匹配
- 对象匹配
- 样例类
- 密封类
- 偏函数PartialFunction
- 匿名函数
- 高阶函数
- 参数推断
- 部分应用函数
- 闭包
- 控制抽象
- 细节
官方文档 :https://www.scala-lang.org/api/current/
对应版本 :https://www.scala-lang.org/api/x.x.x/
学习链接 :https://www.bilibili.com/video/BV1jt411r7hU
Scala 是一门以 java 虚拟机(JVM )为运行环境并将面向对象 和函数式编程 的最佳特性结合在一起的多范式 (multi-paradigm)的静态类型编程语言 。
Scala 源代码(.scala )会被编译成 Java 字节码(.class ),然后运行于 JVM 之上,并可以调用现有的Java 类库,实现两种语言的无缝对接。(.scala文件可以直接通过scala命令编译运行得到结果)
package com.test
object ScalaTest {
def main(args: Array[String]): Unit = {
print("hello world")
}
}
由于Scala与Java类似,本博客只记录Scala特有的语法
输入
输入使用 StdIn 中对应的方法
import scala.io.StdIn
object ScalaTest {
def main(args: Array[String]): Unit = {
print("请输入一个数:")
var num: Int = StdIn.readInt()
print("请输入一个字符串:")
var string: String = StdIn.readLine()
print(num + " " + string)
}
}
print输出
- 字符串通过 + 号连接(类似 java)
- printf 用法 (类似 C 语言)字符串通过 % 传值
- 字符串通过 $ 引用(类似 PHP)
方式一
println("name=" + name + ",age=" + age)
方式二
printf("name=%s,age=%d\n", name, age)
方式三
println(s"name=$name,age= ${age + 1}")
变量
Scala 要求变量声明时必须初始化 。
package com.test
object ScalaTest {
def main(args: Array[String]): Unit = {
val name: String = "Tom"
var age: Int = 18
println("name=" + name + "age=" + age)
}
}
声明变量时,类型可以省略(编译器自动推导,即类型推导 )
类型确定后,就不能修改,因为 Scala 是强数据类型语言
val name = "Tom" //String
var age = 18 //Int
val salary = 1111.1 //Double
println(age.isInstanceOf[Int])
在声明/定义一个变量时,可以使用 var 或者 val 来修饰, var 修饰的变量可变,val 修饰的变量不可改(final )。因为 val 没有线程安全问题,因此效率高,Scala 的设计者推荐使用 val。val 和 var 的区别在于 val 只有一个只读方法,var 有读写方法
class Person{
val name:String = "Tom"
var age: Int = 18
def getInfo(): Unit ={
println("name=" + name+",age="+age)
}
}
public class Person {
private final String name = "Tom";
private int age = 18;
public String name() { return this.name; }
public int age() { return this.age; }
public void age_$eq(int x$1) { this.age = x$1; }
public void getInfo() {
Predef..MODULE$.println(10 + "name=" + name() + ",age=" + age());
}
}
var 修饰的对象引用 (地址)可以改变;val 修饰的则不可改变,但对象的状态(属性的值)却是可以改变的 (val array[Int],地址不可以改变,但是数组里面的元素可以改变)。
数据类型
Scala 与 Java 有着相同的数据类型,在 Scala 中数据类型都是对象 (Byte、Int、Long、Double都是对象,对象中有很多方法),也就是说 scala 没有 java 中的原生类型中的原生类型。

- Scala 数据类型分为两大类 AnyVal(值类型,运行时使用Java的基本类型) 和 AnyRef(引用类型), 注意:不管是 AnyVal 还是 AnyRef 都是对象。
- 在 Scala 中有一个根类型 Any ,它是所有类的父类
- Null 类型是 Scala 的特别类型,它只有一个值 null ,它是 bottom calss,它是所有 AnyRef 类型的子类。null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal:比如 Int, Float, Char, Boolean, Long, Double, Byte, Short)
- Nothing 类型也是 bottom class ,它是所有类的子类,在开发中通常可以将 Nothing 类型的值返回给任意变量或者函数, 可以使用其抛出异常
- Unit 类似于 Java 里的 void ,用作不返回任何结果。Unit 只有一个实例,() ,
- 在 Scala 中仍然遵守,低精度的值向高精度的值自动转换,即隐式转换(implicit conversion)
- 多种类型的数据混合运算时,系统首先自动将所有数据转换成精度最大的那种数据类型,然后再进行计算。byte,short,char 在计算时首先转换为 int 类型 。
var num:Short = 5
num = num - 2 //error, Int -> Short
num = (num - 2).toByte //correct
- 强制类型转换: 2.7.toInt(一切皆对象)强转只针对于最近的操作数有效,往往会使用小括号提升优先级
val num1: Int = 10 * 3.5.toInt + 6 * 1.5.toInt // 36
val num2: Int = (10 * 3.5 + 6 * 1.5).toInt // 44
- 整数默认是Int ,Long要在后面加 l 或 L。小数默认是Double ,Float要在后面加 f 或 F
- 字符类型 Char 是2个字节,Char 类型可以保存 Int 的常量值,但不能保存 Int 的变量值,需要强转
标识符与关键字
标识符规则:
- 首字符为字母,后续字符任意字母和数字,美元符号,可后接下划线 _
- 数字不可以开头
- 首字符为操作符(比如+ - * / ),后续字符也至少一个需要跟操作符。操作符(比如+ - * / )不能在标识符中间和最后。
var ++ = "string" //编译后为 $plus$plus
var +-*/ = "string"
var `true` = "string"
- 用反引号 `` 包括的任意字符串,即使是关键字也可以
Scala 有 39 个关键字:
package ,import ,class ,object ,trait ,extends ,with ,type ,forSome ,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
运算符优先级
- () []
- 单目运算符(!,~)(从右到左)
- 算术运算符
- 移位运算符
- 比较运算符
- 位运算符
- 逻辑运算符
- 赋值运算符(从右到左)
- 逗号
if else
Scala 的 if else 与Java 的类似
Scala 中任意表达式都是有返回值的 ,也就意味着 if else 表达式其实是有返回结果的,具体返回结果的值取决于满足条件的代码体的最后一行内容
val num: Int = 500
val result = {
if (num>50) 100 else "fifty"
}
print(result)
Scala不支持三元运算符,用 if else 代替
for循环
方式一、左闭右闭
for(i <- 1 to 5)
方式二、左闭右开
for(i <- 1 until 5)
方式三、使用Range,可设置步长,左闭右开
for (i <- Range(1,5,2))
方式四、左闭右闭,可设置步长
for (i <- 1.to(5, 2))
方式五、使用indices,底层与until相同,左闭右开
for (i <- array.indices)
方式六、forEach
list.foreach((x: Int) => println(x))
list.foreach(x => println(x))
list.foreach(println(_))
list.foreach(println)
方式七:类似Java的forEach
for (arg <- args)
arg是一个val,不能修改
循环守卫 ,即循环保护式(也称条件判断式)。保护式为 true 则进入循环体内部,为 false则跳过,类似于 continue。注意条件不需要括号。
for (i <- 1 to 100
if (i % 3) == 0
if (i % 10) == 0) {
print(i + " ") // 30 60 90
}
可以引入变量
for (i <- 1 to 8; j = 9 - i) {
println(i + " " + j)
}
嵌套循环 :多个 < - 之间要用分号隔开(因为圆括号和方括号里面不会分号推断 )
for (i <- 1 to 9; j <- 1 to 9) {
println(i + " " + j)
}
下面这个for循环有两个嵌套循环,外部循环遍历文件目录,获得文件名以 .txt 结尾的文件列表,内部循环获取每个文件里长度大于length的行。
object ScalaTest {
private def readFile(file: java.io.File) =
scala.io.Source.fromFile(file).getLines().toList
def grep(path: String, length: Int) = {
val list = new java.io.File(path)
for (file <- list.listFiles()
if file.getName.endsWith(".txt"); <---注意分号
line <- readFile(file)
if line.length > length)
println(file.getName + " " + line)
}
def main(args: Array[String]): Unit = {
grep("E:\ file", 10)
}
}
yield生成器
感觉类似python的生成器。使用 yield 关键字,将遍历过程中处理的结果返回到一个新 Vector 集合中(scala.collection.immutable.IndexedSeq)
// 获取2,4,6,8...20列表
val res = for(i <- 1 to 10) yield i * 2 // i可以是代码块
//生成满足1<=j<i<5的所有对偶(i, j)
for(i <- List.range(1, 5); j <- List.range(1, i)) yield (i, j)
res0: List[(Int, Int)] = List((2,1), (3,1), (3,2), (4,1), (4,2), (4,3))
List.range(1, 5).flatMap(i => List.range(1, i).map(j => (i, j)))
res1: List[(Int, Int)] = List((2,1), (3,1), (3,2), (4,1), (4,2), (4,3))
返回一个斐波拉契数列
def getFibonacci(n: Int): IndexedSeq[Int] = {
var one: Int = 1
var two: Int = 1
val Fibonacci = for (i <- 1 to n) yield {
if (i == 1) one
else if (i == 2) two
else {
two = one + two
one = two - one
two
}
}
Fibonacci
}
for 推导式有一个不成文的约定:当 for 推导式仅包含单一表达式时使用圆括号,当其包含多个表达式时使用大括号。当使用 {} 来换行写表达式时,分号就不用写了
for(i <- 1 to 10;j <- 1 to 10){}
可以写成
for{i <- 1 to 10
j <- 1 to 10}{}
while 与 do while 和Java的一样。While 语句本身没有值,即整个 While 语句的结果是 Unit 类型的()
break与continue
Scala 内置控制结构特地去掉了 break 和 continue,是为了更好的适应函数化编程,推荐使用函数式的风格解决 break 和 contine 的功能,而不是一个关键字。
breakable 在 util.control.Breaks._ 下,breakable 是一个高阶函数:可以接收函数的函数就是高阶函数。breakable源码如下:
- op: => Unit 表示接收的参数是一个没有输入,也没有返回值的函数(代码块)
- breakable 对 break() 抛出的异常做了处理,代码就继续执行
- 当传入的是代码块,要将() 改成{}
def breakable(op: => Unit) {
try {
op
} catch {
case ex: BreakControl =>
if (ex ne breakException) throw ex
}
}
import util.control.Breaks.{break, breakable}
var num: Int = 1
breakable(
while (num < 10) {
if (num == 5)
break()
num += 1
}
)
print(num) //5
可以使用 if else 或是 循环守卫实现 continue 的效果
for (i <- 1 to 10 if (i != 2 && i != 3)) {
println("i=" + i)
}
-----------------------
for (i <- 1 to 10) {
if (i != 2 && i != 3)
println("i=" + i)
}
函数

在Scala中,函数的创建不用依赖于类或者对象
def 函数名 ([参数名: 参数类型], ...)[[: 返回值类型] =] {
语句
return 返回值
}
函数可以有返回值,也可以没有
返回值形式 1: : 返回值类型 =
返回值形式 2: = 表示返回值类型不确定,使用类型推导完成
返回值形式 3: 表示没有返回值,return 不生效
在声明形参时,可以直接赋初始值(默认值)。调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值。
如果函数存在多个参数,每一个参数都可以设定默认值,传递参数默认按照声明顺序[从左到右],也可以使用带名参数
def mysqlCon(add:String = "localhost",port : Int = 3306,
user: String = "root", pwd : String = "root"): Unit = {
println("add=" + add)
println("port=" + port)
println("user=" + user)
println("pwd=" + pwd)
}
从左到右覆盖,顺序不能改变
mysqlCon("127.0.0.1", 7777)
使用带名参数
mysqlCon(user = "tom", pwd = "123")
Scala 函数支持可变参数 ,args 是集合,通过 for 循环可以访问到各个值。args的类型是Array。
支持 0 到多个参数
def sum(args :Int*) : Int = {}
支持 1 到多个参数
def sum(n1: Int, args: Int*) : Int = {}
遍历可变形参
for (item <- args) {}
如果想传入数组,要在参数后面加上 : _ *
def echo(arg: String, args: String*) = args.foreach(println)
def main(args: Array[String]): Unit = {
val array = Array("1", "2", "3")
echo("", array: _*)
}
函数注意事项:
- 函数的形参列表可以是多个, 如果函数没有形参,调用时可以不带()
- Scala 中的函数可以根据函数体最后一行代码自行推断函数返回值类型,所以return 关键字可以省略
def getSum(n1: Int, n2: Int): Int = {
n1 + n2
}
- 因为 Scala 可以自行推断,所以在省略 return 关键字的场合,返回值类型也可以省略
def getSum(n1: Int, n2: Int) = {
n1 + n2
}
- 如果代码只有一行,花括号也可以省略
def getSum(n1: Int, n2: Int) = n1 + n2
- 如果函数明确使用 return 关键字,那么函数返回就不能使用自行推断了 。这时要明确写成 : 返回类型 = ,如果返回值处什么都不写,即使有 return 返回值为()
- 如果函数明确声明无返回值(声明 Unit),那么函数体中即使使用 return 关键字也不会有返回值
- 如果明确函数无返回值或不确定返回值类型,那么返回值类型可以省略(或声明为 Any)
- Scala 语法中任何的语法结构都可以嵌套其他语法结构(灵活),即:函数中可以再声明/定义函数,类中可以再声明类 ,方法中可以再声明/定义方法
- 递归函数未执行之前是无法推断出来结果类型,使用递归时必须有明确的返回值类型
- 函数的任何参数都是 val ,而不是 var
def f(a: Int) = a += 1 // 不能编译
局部函数
局部函数定义在函数内部,只对该函数可见,不能从外部访问 。局部函数不需要用 private 修饰(这个修饰符只能在成员上使用)
def processFile(filename: String, width: Int) = {
def processLine(line: String) = {
if (line.length > width)
println(filename + ": " + line)
}
val source = scala.io.Source.fromFile(filename)
for (line <- source.getLines())
processLine(line)
}
局部函数可以访问包含它们的函数的参数,像 processLine 可以直接访问 processFile 的参数 filename 和 width。
惰性lazy
惰性计算(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来了一些好处。首先,您可以将耗时的计算推迟到绝对需要的时候。其次,您可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。Java 并没有为惰性提供原生支持,Scala 提供了。
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数,在 Java 的某些框架代码中称之为懒加载(延迟加载)。
object LazyTest {
def main(args: Array[String]): Unit = {
lazy val res = sum(10, 20)
println("res=" + res) //在使用res时才执行sum()
}
def sum(n1: Int, n2: Int): Int = {
return n1 + n2
}
}
- lazy 不能修饰 var 类型的变量
- 加了 lazy 会导致函数的执行被推迟,如果声明一个变量时声明了 lazy,那么变量值的分配也会推迟
异常
Scala 提供 try 和 catch 块来处理异常。try 块用于包含可能出错的代码。catch 块用于处理 try 块中发生的异常。可以根据需要在程序中有任意数量的 try…catch 块。
在 Scala 中只有一个 catch,在 catch 中有多个 case, 每个 case 可以匹配一种异常,= > 关键符号,表示后面是对该异常的处理代码块。
def main(args: Array[String]): Unit = {
try {
var num = 10 / 0
} catch {
case ex: ArithmeticException => println("捕获除数为0的算术异常")
case ex: Exception => println("捕获异常")
} finally {
println("处理异常成功")
}
}
运行结果:
捕获除数为0的算术异常
处理异常成功
跟Scala的大多数控制结构一样,try-catch-finally最终会返回一个值。例如下面这个例子,如果没有抛出异常,整个表达式的结果就是try子句的结果;如果URL的格式有问题并被捕获异常,则返回一个默认的URL;如果没有抛出异常也没有捕获异常,整个表达式就没有结果。
import java.net.{MalformedURLException, URL}
def getURL(path: String) =
try {
new URL(path)
} catch {
case ex: MalformedURLException =>
new URL("http://www.scala-lang.org")
}
- Scala 的异常的工作机制和 Java 一样,但是 Scala 没有“checked(编译期)”异常,即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理。
- 用 throw 关键字,抛出一个异常对象。所有异常都是 Throwable 的子类型。throw 表达式是有返回值的,是 Nothing ,因为 Nothing 是所有类型的子类型,所以 throw 表达式可以用在需要类型的地方
def test(): Nothing = {
throw new ArithmeticException("算术异常")
}
- 如果有异常发生,catch 子句是按次序捕捉 的。因此,在 catch子句中,越具体的异常越要靠前,越普遍的异常越靠后 ,如果把越普遍的异常写在前,把具体的异常写在后,在 scala 中也不会报错,但这样是非常不好的编程风格。
- finally 子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作或释放资源,这点和 Java 一样。
- 在 Scala 中,可以使用 throws 注释来声明异常
def main(args: Array[String]): Unit = {
func()
}
//等同于 NumberFormatException.class
@throws(classOf[NumberFormatException])
def func() = {
"abc".toInt
}
类与对象
先写一个Person类
object ScalaTest {
def main(args: Array[String]): Unit = {
val person = new Person
person.name = "John"
person.age = 18
person.getInfo()
}
}
class Person{
var name:String = _ //默认值,null
var age: Int = _ //默认值,0
def getInfo(): Unit ={
println("name=" + name+",age="+age)
}
}
反编译如下:
public class Person {
private String name;
private int age;
public String name() { return this.name; }
public void name_$eq(String x$1) { this.name = x$1; }
public int age() { return this.age; }
public void age_$eq(int x$1) { this.age = x$1; }
public void getInfo() {
Predef..MODULE$.println(10 + "name=" + name() + ",age=" + age());
}
}
- 类中声明了name和age,底层默认是 private ,同时生成对应的 xxx() (类似getter)和 xxx_$eq() (类似setter)的方法
- 类中定义的方法底层默认为 public
- 一个 Scala 源文件可以包含多个类.,而且默认都是 public
- 定义属性时,必须显式的初始化,可以使用符号 _ (下划线),让系统分配默认值
- 如果赋值为 null,则一定要加类型。因为不加类型, 那么该属性的类型就是 Null 类型.
- 变量声明语法:[访问修饰符] var 属性名称 [:类型] = 属性值
- 创建对象语法:val | var 对象名 [:类型] = new 类型()(创建对象的类型可以自动推断,所以类型可以省略,多态时必须加类型)
- 如果我们不希望改变对象的引用(即:内存地址),应该声明为 val 性质的,否则声明为 var
构建一个有理数类,实现有理数加法和乘法。
class Rational(a: Int, b: Int) {
require(b != 0)
private val g = gcd(a.abs, b.abs)
val numer = a / g
val denom = b / g
def this(a: Int) = this(a, 1)
def +(that: Rational): Rational = new Rational(numer * that.denom + that.numer * denom, denom * that.denom)
def *(that: Rational): Rational = new Rational((numer * that.numer, denom * that.denom))
override def toString = a + "/" + b
private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
}
构造器
Scala 类的构造器包括:主构造器 和 辅助构造器 。主构造器只有一个,辅构造器可以有多个,且辅构造器名字为 this
object ScalaTest {
def main(args: Array[String]): Unit = {
val person = new Person("John", 18)
person.getInfo()
}
}
class Person() {
var name: String = _
var age: Int = _
age += 10
def this(name: String) {
this()
this.name = name
}
def this(age: Int) {
this("Tom") //间接调用主构造器,this(name: String)第一行调用了主构造器
this.age = age
}
def this(name: String, age: Int) {
this
this.name = name
this.age = age
}
def getInfo(): Unit = {
println("name=" + name + ",age=" + age)
}
println(age)
}
反编译如下:
public class Person{
private String name;
private int age;
public String name() { return this.name; }
public void name_$eq(String x$1) { this.name = x$1; }
public int age() { return this.age; }
public void age_$eq(int x$1) { this.age = x$1; }
public void getInfo() {
Predef..MODULE$.println(10 + "name=" + name() + ",age=" + age());
}
public Person() {
age_$eq(age() + 10);
Predef..MODULE$.println(BoxesRunTime.boxToInteger(age()));
}
public Person(String name) {
this();
name_$eq(name);
}
public Person(int age) {
this("Tom");
age_$eq(age);
}
public Person(String name, int age) {
this();
name_$eq(name);
age_$eq(age);
}
}
- 主构造器内的语句,如 age += 10,println(age) 最终会放到主构造器内,并最终执行主构造器中所有语句,
- 辅构造器第一行必须显式调用主构造器 (直接或间接),原因是保持与父类的关系,主构造器会有super。即辅构造器最终都一定要调用主构造器,执行主构造器的逻辑。主构造器是类的单一入口。
- 构造器作用是完成对新对象的初始化,构造器没有返回值
- 主构造器的声明直接放置于类名之后,如 class Person(name: String) {}
- 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
- 辅助构造器名称为 this ,多个辅助构造器通过不同参数列表进行区分
- 辅助构造器有先后顺序 ,先定义的可以被后面的辅助构造器调用,先定义的不能调用后定义的辅助构造器
- 如果想让主构造器变成私有的,可以在()之前加上 private ,这样用户只能通过辅助构造器来构造对象了
class A private {}
- 对象创建的流程:
- 加载类的信息(属性信息,方法信息)
- 在内存中(堆)开辟空间
- 使用父类的构造器(主和辅助)进行初始化
- 使用主构造器对属性进行初始化
- 使用辅助构造器对属性进行初始化
- 将开辟的对象的地址赋给对象引用
构造器参数
- Scala 类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量 。
- 如果参数使用 val 关键字声明,那么 Scala 会将参数作为类的私有的只读属性 使用
- 如果参数使用 var 关键字声明,那么那么 Scala 会将参数作为类的私有的,但是可读写属性 使用,并会提供属性对应的 xxx()[类似 getter]/xxx_$eq()[类似 setter]方法
//形参没有任何修饰符,newName是一个局部变量
class Person(newName: String) {
var name = newName
}
//使用val,newName是一个私有的只读属性
class Person1(val newName: String) {
var name = newName
}
//使用var,newName是一个私有的可读写属性
class Person2(var newName: String) {
var name = newName
}
object ScalaTest {
def main(args: Array[String]): Unit = {
val person = new Person("John")
println(person.newName) ---> Error
val person1 = new Person1("John")
println(person1.newName)
person1.newName = "Tom" ---> Error
val person2 = new Person2("John")
println(person2.newName)
person2.newName = "Tom"
println(person2.newName)
}
}
Person的反编译结果
public class Person {
private String name;
public String name() { return this.name; }
public void name_$eq(String x$1) { this.name = x$1; }
public Person(String newName) { this.name = newName; }
}
Person1的反编译结果,newName是一个final修饰的常量
public class Person1 {
private final String newName;
private String name;
public String newName() { return this.newName; }
public String name() { return this.name; }
public void name_$eq(String x$1) { this.name = x$1; }
public Person1(String newName) { this.name = newName; }
}
Person2的反编译结果,newName是一个变量
public class Person2 {
private String newName;
private String name = newName();
public String newName() { return this.newName; }
public void newName_$eq(String x$1) { this.newName = x$1; }
public String name() { return this.name; }
public void name_$eq(String x$1) { this.name = x$1; }
public Person2(String newName) {}
}
类参数前面可以添加private、protected、override等修饰符。
class Cat {
val dangerous = false
}
class Tiger(
override val dangerous: Boolean,
private var age: Int
) extends Cat
等价于
class Tiger(param1: Boolean, param2: Int) extends Cat {
override val dangerous: Boolean = param1
private var age: Int = param2
}
@BeanPropetry
JavaBeans 规范定义了 Java 的属性配置 getXxx() 和 setXxx() 的方法。许多 Java 工具(框架)都依赖这个习惯。为了 Java 的互操作性。将 Scala 字段加 @BeanProperty 时,这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性。
@BeanPropetry生成的 get/set 方法对原来底层的 xxx(),xxx_$eq() 方法没有冲突,二者可以共存
import scala.beans.BeanProperty
class Person() {
@BeanProperty var name: String = _
}
object ScalaTest {
def main(args: Array[String]): Unit = {
val person = new Person()
person.name = "John"
println(person.name) //John
person.setName("Tom")
println(person.getName) //Tom
}
}
前置条件
前置条件是对传入的方法或构造方法的值的约束,如果不满足则不能创建对象。实现这个可以使用 require 方法(这个方法定义在Predef中),如果满足条件则正常执行,否则抛出 java.lang.IllegalArgumentException 来阻止对象的创建。
例如,创建一个除法类,主构造器传入两个参数,如果分母为0则不能创建对象。
class Divide (a: Double, b: Double) {
require(b != 0, "分母不能为0")
println("a / b = " + (a / b))
}
object ScalaTest {
def main(args: Array[String]): Unit = {
new Divide(1, 0)
}
}
Exception in thread “main” java.lang.IllegalArgumentException: requirement failed: 分母不能为0
包
Scala的包除了可以像Java的写法外,还支持下面的写法。Sacla 支持在一个文件中同时创建多个包,以及给各个包创建类,trait 和 object。
package com { //包com
package test { //包test
import scala.beans.BeanProperty
class Person() {
@BeanProperty var name: String = _
}
object ScalaTest {
def main(args: Array[String]): Unit = {
val person = new Person()
person.name = "John"
println(person.name)
person.setName("Tom")
println(person.getName)
}
}
}
package study{
import scala.beans.BeanProperty
class Person() {
@BeanProperty var name: String = _
}
}
}
注意事项:
- Scala 中包名和源码所在的系统文件目录结构可以不一致,但是编译后的字节码文件路径和包名会保持一致(这个工作由编译器完成)
- 可以在同一个文件中,将类(class / object)、trait 创建在不同的包中。Scala 中子包中直接访问父包中的内容, 大括号体现作用域,子父包中如果有同名的类,则采用就近原则 。
- 父包要访问子包的内容时,需要 import 对应的类。
- package object scala 表示创建一个包对象 scala,它是 scala 这个包对应的包对象。每一个包都可以有一个包对象,包对象的名字需要和子包一样。在包对象中可以定义变量、方法,然后可以在对应的包中使用。在底层这个包对象会生成两个类 package.class 和 package$.class (每个包只有一个包对象,不同包生成的两个类名字都一样)
包的引入
- 在 Scala 中,import 语句可以出现在任何地方,并不仅限于文件顶部,但要注意作用域 。好处是:在需要时在引入包,缩小 import 包的作用范围,提高效率
- 如果想要导入包中所有的类,可以通过通配符 _ (下划线),比如 scala.io._
- 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)
import scala.collection.mutable.{HashMap, HashSet}
- 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分
//将 java.util.HashMap 重命名为 JavaHashMap
import java.util.{ HashMap=>JavaHashMap, List}
import scala.collection.mutable.HashMap
var map = new HashMap() // 此时的 HashMap 指向的是 scala 中的 HashMap
var map1 = new JavaHashMap(); // 此时使用的 java 中 hashMap 的别名
- 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉
//引入 java.util 包的所有类,但是忽略 HahsMap 类
import java.util.{ HashMap=>_, _}
//此时的 HashMap 指向的是 scala 中的 HashMap
//而且 idea 工具的提示也不会显示 java.util 的 HashMap了
var map = new HashMap()
访问修饰符
- 当属性访问权限为默认时(没有修饰符),从底层看属性是 private 的,但是因为提供了 xxx_$eq()(setter)/ xxx()(getter)方法,因此从使用效果看是任何地方都可以访问)
- 当方法访问权限为默认时,默认为 public 访问权限
- private 为私有权限,使用private时,只在类的内部和伴生对象中可用,其它地方不可以访问
- protected 为受保护权限,scala 中受保护权限比 Java 中更严格,只能子类 访问(在子类中),同包无法访问
- 在 Scala 中没有 public 关键字 ,即不能用 public 显式的修饰属性和方法。
- 使用包访问权限
class Person() {
//增加包访问权限后,private依然起作用,此时不仅可以在同类使用,
//还可以在test包下其他类使用
private[test] var name: String = _
//此时private依然起作用,但可以在com包下所有子类使用
private[com] var age: Int = _
}
- 当声明属性 var 时,本身就自动提供了对应 setter/getter 方法,如果属性声明为 private 的,那么自动生成的 setter/getter 方法也是 private 的,如果属性省略访问权限修饰符,那么自动生成的 setter/getter 方法是 public 的
继承
在Scala中,所有类继承自AnyRef。
继承可以解决代码复用 ,当多个类存在相同的属性(变量)和方法时, 可以从这些类中抽象出父类。在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 语句来声明继承父类即可。
子类继承父类所有的非私有成员。父类只能访问自己的属性和方法,不能访问子类的。
Scala重写一个非抽象方法需要用 override 修饰符,调用超类的方法使用 super 关键字
要测试某个对象是否属于某个给定的类,可以用 isInstanceOf 方法。用 asInstanceOf 方法将引用转换为子类的引用。classOf 获取对象的类名。
package com.test {
object ScalaTest {
def main(args: Array[String]): Unit = {
val student = new Student
val employee = new Employee
test(student)
test(employee)
}
// 父类引用
def test(p: Person): Unit = {
if (p.isInstanceOf[Student])
p.asInstanceOf[Student].StudnetMethod
else if (p.isInstanceOf[Employee])
p.asInstanceOf[Employee].EmployeeMethod
else
p.PersonMethod
}
}
class Person {
def getName(): String = "Person"
def PersonMethod: Unit = {}
}
class Student() extends Person() {
override def getName(): String = "Student"
def StudnetMethod: Unit = {}
}
class Employee extends Person {
override def getName(): String = "Employee"
def EmployeeMethod: Unit = {}
}
}
- 每个辅助构造器都必须先调用主构造器(直接或间接)
- 只有主构造器可以调用父类的构造器 ,辅助构造器不能直接调用父类的构造器。在 Scala 的构造器中,不能调用 super(params)
在继承时可以直接将参数传给父类,调用父类的构造器
object ScalaTest {
def main(args: Array[String]): Unit = {
val label = new Label(10, 20)
label.printXY()
}
}
class Point(xPoint: Int, yPoint: Int) {
def printXY() = println("x=" + xPoint + ",y=" + yPoint)
}
class Label(xPoint: Int, yPoint: Int) extends Point(xPoint, yPoint) {}
重写
在 Scala 中,子类可以重写字段(Java不能,Java是隐藏父类字段,子类创建新的字段),重写了父类的成员必须使用 override 修饰(一是避免重写的方法出错,编译可以检查出来;二是如果父类升级添加了一个方法,子类也有一个同签名的方法,但没有用override修饰,那么编译会会出错,否则父类的方法会覆盖子类的方法)。
动态绑定的机制 :
- 如果调用的是方法,则 JVM 会将该方法和对象的内存地址绑定,var A = new B,那么方法与B对象的内存地址绑定(编译看左边,运行看右边)
- 如果调用的是属性,则没有动态绑定,在哪里调用就返回对应值
object ScalaTest {
def main(args: Array[String]): Unit = {
val obj1: A = new B
val obj2: B = new B
println(obj1.num + " " + obj2.num)
obj1.printNum
obj2.printNum
}
}
class A {
val num: Int = 10
def printNum: Unit = println("A")
}
class B extends A {
override val num: Int = 20
override def printNum: Unit = println("B")
}
运行结果:20 20 B B
重写字段的注意事项:
- def 只能重写另一个 def(方法只能重写另一个方法)
- val 只能重写另一个 val 属性(不能重写var,底层方法不一致,val只有xxx()方法) 或 重写不带参数的 def(底层val会生成一个xxx()的类似getter的方法,该方法会覆盖父类同名方法)。def 不能重写val(def 可以返回可变的值)
object ScalaTest {
def main(args: Array[String]): Unit = {
val obj1: A = new B
val obj2: B = new B
println(obj1.name()) //父类只能调父类的
println(obj2.name) //子类只能调子类的
}
}
class A {
def name(): String = "A"
}
class B extends A {
val name: String = "B"
}
运行结果:B B
- var 只能重写另一个抽象 的 var 属性。如果是覆写一个父类的抽象var属性,那么 override 关键字可省略,原因是抽象的属性在底层不会生成对应的属性声明 ,而是只生成两个对应的抽象方法(num num_$eq),在子类中去重写父类的抽象属性,本质是实现 抽象方法
abstract class A {
var num : Int
}
class B extends A {
override var num: Int = 20
}
抽象类
当一个类含有抽象属性或方法时,则该类需要标记为 abstract 。一个属性没有初始化,那么这个属性就是抽象属性,对于抽象的属性,在底层不会生成对应的属性声明,而是生成两个对应的abstract抽象方法(name name_$eq)。
如果是覆写一个父类的抽象属性,那么 override 关键字可省略 [原因:父类的抽象属性生成的是抽象方法,子类重写相当于实现抽象方法]。
抽象类中可以有抽象 / 非抽象属性,也可以有抽象 / 非抽象方法
abstract class AbstractClass {
var num: Int
val name: String = "Tom"
def func()
def info(): Unit = {
println("name=" + name)
}
}
class A extends AbstractClass {
override var num: Int = 20
override def func(): Unit = println("hello")
}
反编译结果如下,num没有声明属性,只生成两个抽象方法:
public abstract class AbstractClass{
private final String name = "Tom";
public abstract int num();
public abstract void num_$eq(int paramInt);
public abstract void func();
public String name() { return this.name; }
public void info() {
Predef..MODULE$.println(5 + "name=" + name());
}
}
public class A extends AbstractClass{
private int num = 20;
public int num() { return this.num; }
public void num_$eq(int x$1) { this.num = x$1; }
public void func() { Predef..MODULE$.println("hello"); }
}
默认情况下,一个抽象类是不能实例化的,但是实例化时,可以动态的实现了抽象类的所有抽象方法(匿名子类),override 可以加也可以不加
def main(args: Array[String]): Unit = {
var abstractClass = new AbstractClass {
override var num: Int = 20
override def func(): Unit = println("hello")
}
}
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为 abstract 类
- 抽象方法和抽象属性不能使用 private、final 来修饰
- 子类重写抽象方法不需要 override ,相当于实现抽象方法,不算重写,写上也不会错
- 抽象类中的抽象属性、方法可以加abstract,也可以不加
伴生对象
Scala 语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在 Scala 中没有静态的概念)。但是为了能够和 Java 语言交互(因为 Java 中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象 。这个类的所有静态 (static )内容都可以放置在它的伴生对象中声明和调用,对于伴生对象的内容,我们可以直接通过类名服务属性方法。
所以main方法只能在伴生对象(object)使用,不能在伴生类(class)使用
伴生类 :将非静态的内容写到该类中,编译后底层生成 xxx 类;伴生对象 :将静态的内容写入到该对象(类),编译后底层生成 xxx$ 类。伴生对象的名称应该和伴生类名一致
类和它的伴生对象可以互相访问对方的私有成员。
object ScalaTest {
def main(args: Array[String]): Unit = {
val person = new Person()
person.hello()
person.helloworld()
}
}
class Person() extends Trait {
override def hello(): Unit = {
println("hello")
}
}
trait Trait {
def hello()
def helloworld(): Unit ={
println("hello world")
}
}
从底层原理看,伴生对象实现静态特性是依赖于 public static final MODULE$ 实现的
apply方法
在伴生对象 中定义 apply 方法,可以实现: 类名(参数) 方式来创建对象实例
object ScalaTest {
def main(args: Array[String]): Unit = {
var point = Point(10, 20)
point.printXY()
var point1 = new Point(10,20)
point1.printXY()
}
}
class Point(xPoint: Int, yPoint: Int) {
def printXY() = println("x=" + xPoint + ",y=" + yPoint)
}
object Point {
def apply(xPoint: Int, yPoint: Int): Point = new Point(xPoint, yPoint)
}
apply方法可以赋默认值
object Point {
def apply(xPoint: Int = 0, yPoint: Int = 0): Point = new Point(xPoint, yPoint)
}
特质
Scala 语言中,采用特质 trait (特征)来代替接口的概念。多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。可以理解 trait 等价于(interface + abstract class)
trait 特质名 {
trait 体
}
没有父类
class 类名 extends 特质1 with 特质2 with 特质3
有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3
在 Scala 中,Java 中的接口可以当做特质使用
//trait Serializable extends Any with java.io.Serializable
object T1 extends Serializable {}
特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。
object ScalaTest {
def main(args: Array[String]): Unit = {
val mysql = new MySQL
mysql.info()
mysql.func()
}
}
trait database {
val name: String
val tag: String = "database"
def func()
def info(): Unit = {
println("tag=" + tag + ",name=" + name)
}
}
class MySQL extends database {
def insert() = println("insert")
override val name: String = "MySQL"
override def func(): Unit = println(name)
}
反编译结果如下:trait底层是一个接口,抽象属性和非抽象属性的方法的实现在实现接口的类中实现。
public abstract interface database {
public abstract void com$test$database$_setter_$tag_$eq(String paramString);
public abstract String name();
public abstract String tag();
public abstract void func();
public void info() {
Predef..MODULE$.println(10 + "tag=" + tag() + ",name=" + name());
}
public static void $init$(database $this) {
$this.com$test$database$_setter_$tag_$eq("database");
}
}
public class MySQL implements database {
private final String name;
private final String tag;
public void info() { database.info$(this); }
public String tag() { return this.tag; }
public void com$test$database$_setter_$tag_$eq(String x$1) { this.tag = x$1; }
public void insert() {
Predef..MODULE$.println("insert");
}
public String name() {
return this.name;
}
public void func() { Predef..MODULE$.println(name()); }
public MySQL() {
database.$init$(this);
this.name = "MySQL";
}
}
特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为类自己的字段。
Scala提供一个 App特质 ,其作用是不用写Main方法,直接在类中写执行语句,其效果与在Main方法一样。
object ScalaTest extends App {
for (line <- Source.fromFile("E:\ test\ brand.txt").getLines())
println(line)
}
Scala提供了排序的特质,Ordered[T] 和 Ordering[T] ,对应Java中的 Comparable 和 Comparator
提供特质隐藏类
这是一种设计模式吧,隐藏类本身,提供反映类的公有接口的特质。
trait A[T] {
def num: T
}
object A {
def apply[T](x: T): A[T] = new B[T](x)
private class B[T](private val x: T) extends A[T] {
def num = x
}
}
object ScalaTest {
def main(args: Array[String]): Unit = {
val a: A[Int] = A(1)
println(a.num)
}
}
动态混入
动态混入是 Scala 特有的方式,可在不修改类声明/定义的情况下扩展类的功能,可以在不影响原有的继承关系的基础上扩展其它功能 (当前类使用动态混入特质,其子类不会获得该特质的属性和方法),耦合性低 。
语法:new 对象 with 特质
object ScalaTest {
def main(args: Array[String]): Unit = {
val mysql = new MySQL with database {
override val name: String = "MySQL"
override def func(): Unit = println(name)
override def create(): Unit = println("create")
}
mysql.info()
mysql.func()
mysql.create()
val yourSQL = new YourSQL
yourSQL.delete
yourSQL.insert()
// yourSQL.info() error
// yourSQL.func() error
}
}
trait database {
val name: String
val tag: String = "database"
def func()
def info(): Unit = {
println("tag=" + tag + ",name=" + name)
}
}
abstract class MySQL {
def create()
def insert() = println("insert")
}
class YourSQL extends MySQL {
override def create(): Unit = println("create")
def delete = println("delete")
}
从上面可以看到,MySQL这个抽象类动态混入了特质database并实现了抽象方法。YourSQL继承了MySQL,但YourSQL并没有获取到特质database中的 info() 和 func()。所以动态混入可以在不影响原有的继承关系的基础上扩展其它功能 。如果想子类也获得特质的属性和方法,那么可以用传统的方式(class 类名 extends 父类 with 特质1 with 特质2 with 特质3 )
富接口 :即该特质中既有抽象方法,又有非抽象方法,就如上面的 database 特质
叠加特质
构建对象的同时如果混入多个特质,称之为叠加特质 ,那么特质声明顺序从左到右,方法执行顺序从右到左 。
object ScalaTest {
def main(args: Array[String]): Unit = {
val e = new E with C with D
println("===========")
e.info("Nobody")
}
}
trait A {
println("A")
def info(name:String)
}
trait B extends A {
println("B")
override def info(name: String): Unit = {
println("这里是B")
println(name)
}
}
trait C extends B {
println("C")
override def info(name: String): Unit = {
println("这里是C")
super.info(name)
}
}
trait D extends B {
println("D")
override def info(name: String): Unit = {
println("这里是D")
super.info(name)
}
}
class E {
println("E的构造器")
}
运行结果:
E的构造器
A B C D这里是D
这里是C
这里是B
Nobody
声明顺序:首先运行E的构造器,创建E的对象;然后继续特质C,因为C有父特质B,B有父特质A,所以是ABC,然后从左往右到D,因为父特质已经走过了,所以不用再走一次
执行顺序:可以从运行结果看出,特质中如果调用 super,并不是调用父特质的方法,而是向左边继续查找特质,如果找不到,才会去调用父特质 。所以D左边的是C,C左边没有特质,调用父特质B。
如果想要调用具体特质的方法,可以指定:super[特质].xxx(…) ,其中的泛型必须是该特质的直接超类类型 ,比如D方法里有 super[B].xxx(…),这个B不能是其它,也不能是A(不是直接父类)。
trait D extends B {
print("D")
override def info(name: String): Unit = {
println("这里是D")
super[B].info(name)
}
}
运行结果:
这里是D
这里是B
Nobody
注意叠加特质与传统的继承特质的声明顺序不一样,F对象是最后才创建的
class F extends E with C with D{
println("\nF的构造器")
}
val f = new F
运行结果:
E的构造器
A B C D
F的构造器
扩展类的特质
特质可以继承类,以用来拓展该特质的一些功能。所有混入该特质的类,会自动成为那个特质所继承的超类的子类 。
特质 LoggedException 继承了 Exception 类,LoggedException 可以扩展 或者修改 Exception 的功能,而且 LoggedException 成为 Exception 的子类。
trait LoggedException extends Exception {
def log(): Unit = {
println(getMessage()) // 方法来自于 Exception 类
}
override def printStackTrace(): Unit = println("Exception")
}
如果混入该特质的类,已经继承了另一个类(A 类),则要求 A 类是特质超类的子类,否则就会出现了多继承现象,发生错误。
ConsoleException 继承了 IndexOutOfBoundsException,IndexOutOfBoundsException 是 Exception 的子类,所以没有问题。如果 ConsoleException 继承的不是 Exception 的子类,那么就会报错。
class ConsoleException extends IndexOutOfBoundsException with LoggedException{
override def getMessage = "错误消息!"
}
如果特质继承的是抽象类,那么重写的方法要用 abstract 和 override 同时修饰。
自身类型
自身类型 :主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
trait LoggedException {
this:Exception=>
def log(): Unit = {
println(getMessage())
}
}
LoggedException 是自身类型特质,它要求混入该特质的类也是 Exception 子类 。this: Exception明确告诉编译器,我就是 Exception,可以调用其中的方法。
class Console extends LoggedException //报错
Illegal inheritance, self-type Console does not conform to Exception
class Console extends RuntimeException with LoggedException //OK
内部类
成员内部类的创建:val outerClass: OuterClass = new OuterClass,val innerClass = new outerClass.InnerClass
静态内部类的创建:val staticInnerClass = new StaticOuterClass.StaticInnerClass
成员内部类访问外部类的属性:外部类名.this.属性名
静态内部类访问外部类的属性:外部类名.属性名
object ScalaTest {
def main(args: Array[String]): Unit = {
val outerClass: OuterClass = new OuterClass
val innerClass = new outerClass.InnerClass
val staticInnerClass = new StaticOuterClass.StaticInnerClass
innerClass.info
staticInnerClass.info
}
}
class OuterClass {
val name = "OuterClass"
private var num = 10
class InnerClass {
def info = println("name=" + OuterClass.this.name + ",num=" + num)
}
}
object StaticOuterClass {
val name = "StaticOuterClass"
private var num = 20
class StaticInnerClass {
def info = println("name=" + StaticOuterClass.name + ",num=" + num)
}
}
内部类如果想要访问外部类的属性,也可以通过外部类别名 访问。访问方式:外部类别名.属性名
class OuterClass {
MyOuterClass =>
val name = "OuterClass"
private var num = 10
class InnerClass {
def info = println("name=" + MyOuterClass.name + ",num=" + num)
}
}
object StaticOuterClass {
MyStaticOuterClass =>
val name = "StaticOuterClass"
private var num = 20
class StaticInnerClass {
def info = println("name=" + MyStaticOuterClass.name + ",num=" + num)
}
}
注意:属性必须放外部类别名后面
类型投影
在Scala中,内部类依赖于外部类,innerClass1 依赖于 outerClass1,info()方法的参数只能是outerClass1.InnerClass 类型,如果传入 innerClass2 会报错
object ScalaTest {
def main(args: Array[String]): Unit = {
val outerClass1: OuterClass = new OuterClass
val innerClass1 = new outerClass1.InnerClass
val outerClass2: OuterClass = new OuterClass
val innerClass2 = new outerClass2.InnerClass
innerClass1.info(innerClass1) //OK
innerClass2.info(innerClass2) //OK
innerClass1.info(innerClass2) //Error
}
}
class OuterClass {
class InnerClass {
def info(innerClass: InnerClass) = println(innerClass)
}
}
可以使用类型投影 解决,在方法声明上,如果使用 外部类#内部类 的方式,表示忽略内部类的对象关系,只考虑类型。innerClass1.info(innerClass2)可以正常运行。
class OuterClass {
class InnerClass {
def info(innerClass: OuterClass#InnerClass) = println(innerClass)
}
}
隐式转换
隐式转换函数是以 implicit 关键字声明的带有单个参数 的函数。这种函数将会自动调用,将值从一种类型转换为另一种类型。隐式函数应当在作用域内 才能生效。
def main(args: Array[String]): Unit = {
implicit def doubleToInt(d: Double): Int = d.toInt
传入一个Double类型的参数,返回一个Int类型的值,val num: Int = doubleToInt(3.5)
val num: Int = 3.5
println(num)
}
编译器首先会照原样编译,但会遇到一个类型错误(Double不能转Int),编译器会寻找一个隐式转换,找到 doubleToInt。
- 隐式转换函数的函数名可以是任意 的,大部分情况下隐式转换与函数名称无关,只与函数签名 (函数参数类型和返回值类型)有关。最好还是见名知意。
- 隐式函数可以有多个(即:隐式函数列表),但是需要保证在当前环境下,只有一个隐式函数能被识别。
隐式转换可以丰富类库功能 ,下面的mySQL对象可以调用不是MySQL类的方法
object ScalaTest {
implicit def addDelete(mySQL: MySQL): Database = new Database
def main(args: Array[String]): Unit = {
val mySQL = new MySQL
mySQL.insert()
mySQL.delete()
}
}
class Database {
def delete() = println("delete")
}
class MySQL {
def insert() = println("insert")
}
反编译如下,底层构建了一个方法返回Database对象:
public Database addDelete(MySQL mySQL) {
return new Database();
}
public void main(String[] args) {
MySQL mySQL = new MySQL();
mySQL.insert();
addDelete(mySQL).delete();
}
隐式转换可以模拟新的语法,比如Map支持 - > 的语法,其中就用到了隐式转换。Map要接收Tuple2的元组。1.->(“one”) = (1, “one”)
Map(1 -> "one", 2 -> "two")
Map((1, "one"), (2, "two"))
implicit final class ArrowAssoc[A](private val self: A) extends AnyVal {
@inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
def →[B](y: B): Tuple2[A, B] = ->(y)
}
隐式转换的时机:
- 当方法中的参数的类型与目标类型不一致时, 或者是赋值时。
- 当对象调用所在类中不存在的方法或成员时,编译器会自动将对象进行隐式转换(根据类型)
隐式值
隐式值也叫隐式变量,将某个形参变量标记为 implicit ,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数。
object ScalaTest {
implicit val name: String = "Tom"
def func1(implicit name: String = "Jerry") {
print(name + " ")
}
def main(args: Array[String]): Unit = {
func1("Mark")
func1
func1()
}
}
运行结果:Mark Tom Jerry
- 当在程序中同时有隐式值、默认值、传入参数,优先级为:传入参数 > 隐式值 > 默认值
- 隐式值只能传一个参数,如果func1传两个隐式值则报错,因为隐式转换只能有一个参数
- 函数如果不写括号则优先查看是否有隐式值,如果没有则使用默认值,如果没有默认值则报错。函数如果写了括号,则直接查看是否有默认值。
- 当一个隐式参数匹配不到隐式值,仍然会使用默认值
- 隐式参数是应用到整个参数列表,例如下面两个都是隐式参数
def func1(implicit name: String = "Jerry", age: Int = 18)
隐式类
隐式类可以使用 implicit 声明类,隐式类同样可以扩展类的功能,比前面使用隐式转换丰富类库功能更加的方便,在集合中隐式类会发挥重要的作用。
隐式类使用有如下几个特点:
- 隐式类的构造参数有且只能有一个
- 隐式类必须被定义在其它(非自身 ) “类 ” 或 “伴生对象 ” 或 “包对象 ” 里,即隐式类不能是顶级的(top-levelobjects)。例如很多隐式转换都放在Predef,然后一起引入。
- 隐式类不能是 case class
- 作用域内不能有与之相同名称的标识符
object ScalaTest {
class MySQL {
def insert() = println("insert")
}
class Database {}
implicit class DB(mySQL: MySQL) {
def delete() = println("delete")
def update() = println("update")
}
def main(args: Array[String]): Unit = {
val mySQL = new MySQL
mySQL.insert()
mySQL.delete()
mySQL.update()
}
}
工作原理如下,由于MySQL这个类没有delete方法,编译器会查找一个从MySQL到某个有这个方法的类型的隐式转换。它找到DB有一个delete方法,插入对这个转换的调用。反编译如下,底层构建了一个Database类
public static class Database{
public void delete() { Predef..MODULE$.println("delete"); }
public void update() { Predef..MODULE$.println("update"); }
public Database(ScalaTest.MySQL mySQL){}
}
...
public ScalaTest.Database Database(ScalaTest.MySQL mySQL) {
return new ScalaTest.Database(mySQL);
}
public void main(String[] args) {
ScalaTest.MySQL mySQL = new ScalaTest.MySQL();
mySQL.insert();
Database(mySQL).delete();
Database(mySQL).update();
}
集合
Scala 同时支持不可变集合 和可变集合 ,不可变集合可以安全的并发访问。Scala 默认采用不可变集合 ,对于几乎所有的集合类,Scala 都同时提供了 可变(mutable) 和 不可变(immutable) 的版本。
不可变集合:scala.collection.immutable 。集合本身不能动态变化,内容可以改变(类似Java的数组)。

可变集合: scala.collection.mutable 。集合本身可以动态变化的。(类似Java的ArrayList)。

Scala 的集合有三大类:序列 Seq (有序,Linear Seq)、集 Set 、映射 Map (key->value),所有的集合都扩展自 Iterable 特质 。
.IndexSeq 和 LinearSeq 的区别:IndexSeq 是通过索引来查找和定位,因此速度快,比如 String 就是一个索引集合,通过索引即可定位;LineaSeq 是线型的,有头尾的概念,这种数据结构一般是通过遍历来查找。
定长数组Array
定长数组等同于 Java 中的数组,创建定长数组方式如下:
方式一:[]是泛型,()是长度,没有赋值,Int初始值为0
val array = new Array[Int](10)
方式二:在定义数组时,直接赋值,使用 apply 方法创建数组对象
数组的类型由编译器推导,Any表示任何类型
val array = Array(1,2,3)
访问修改方式如下:
方式一:下标访问(注意与Java不同)
println(array(0)) //底层就是调用.apply(0)
array(1) = 10 //底层调用的就是.update(1, 10)
方式二:使用方法
println(array.apply(1))
array.update(2, 5)
遍历方式如下:
方式一:类似forEach
for(i <- array)
print(i + " ")
方式二:传统的下标遍历
indices方法做了一个包装:def indices: Range = 0 until length
for (i <- array.indices)
print(array(i) + " ")
变长数组ArrayBuffer
ArrayBuffer 是变长数组,类似 java 的 ArrayList
创建变长数组,需要提前导入ArrayBuffer类:import scala.collection.mutable.ArrayBuffer
创建方式如下:
导包
import scala.collection.mutable.ArrayBuffer
方式一:定义一个长度为0的整型变长数组
val array = new ArrayBuffer[Int]()
方式二:定义一个变长数组并赋初始值
val array = ArrayBuffer[String]("hadoop", "storm", "spark")
访问修改方式 和 遍历方式和定长数组一样
添加 / 删除方式如下:
添加:
方式一:append 支持可变参数
array.append("hive","kafka")
方式二:+= 添加单个元素
array += "hive"
方式三:++= 添加多个元素,后接一个数组
array ++= Array("hive", "kafka")
方式四:insert 指定位置插入单个数据
array.insert(0, "hive")
方式五:insertAll 指定位置插入多个数据
array.insertAll(1, Array("hive", "kafka"))
删除:
方式一:remove 指定位置的数据
array.remove(0)
方式二:-= 删除其中一个数据
array -= "hadoop"
方式三:--= 删除多个数据
array --= Array("hadoop", "storm")
多维数组
val array = Array.ofDim[Int](3, 4)
println(arr(1)(1))
arr(1)(1) = 100
数组转换
定长数组与变长数组的转换
定长数组转可变数组
immutableArray.toBuffer
可变数组转定长数组
mutableArray.toArray
注意:toBuffer 和 toArray 均返回一个新是数组,原数组并未发生改变。
Scala与Java集合互换
需要用到 scala.collection.JavaConverters (Deprecated: (Since version 2.13.0) Use scala.jdk.CollectionConverters instead)
下面的转换可以通过 asScala 和 asJava 实现
scala.collection.Iterable <=> java.lang.Iterable
scala.collection.Iterator <=> java.util.Iterator
scala.collection.mutable.Buffer <=> java.util.List
scala.collection.mutable.Set <=> java.util.Set
scala.collection.mutable.Map <=> java.util.Map
scala.collection.concurrent.Map <=> java.util.concurrent.ConcurrentMap
通过 asScala 和特殊命名的扩展方法支持以下转换为Java集合
scala.collection.Iterable <=> java.util.Collection (via asJavaCollection)
scala.collection.Iterator <=> java.util.Enumeration (via asJavaEnumeration)
scala.collection.mutable.Map <=> java.util.Dictionary (via asJavaDictionary)
以下单向转换是通过 asJava 提供的
scala.collection.Seq => java.util.List
scala.collection.mutable.Seq => java.util.List
scala.collection.Set => java.util.Set
scala.collection.Map => java.util.Map
以下单向转换是通过 asScala 提供的
java.util.Properties => scala.collection.mutable.Map
Scala 数组与 Java 的 List 的互转
import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import java.util.List
val arrayBuffer: ArrayBuffer[Int] = ArrayBuffer[Int](1,2,3)
println(arrayBuffer)
val javaList: List[Int] = arrayBuffer.asJava
println(javaList)
val newBuffer: mutable.Buffer[Int] = javaList.asScala
println(arrayBuffer.eq(newBuffer))
运行结果:
ArrayBuffer(1, 2, 3)
[1, 2, 3]
true
从结果可以看出,arrayBuffer和newBuffer是相同的。在所有情况下,从源类型到目标类型再转换回来都将返回原始源对象。
元组Tuple
元组可以存放各种相同或不同类型的数据,可以将多个无关的数据封装为一个整体。元组中最大只能有 22 个元素 (Tuple1…Tuple22)。
元组的创建如下,元组可以放任何不相关的类型的数据,而且可以放入元组或集合。元组一旦创建则不可以修改 ,如果元组内有可变集合,则集合内容可变。
tuple 是一个五元组,类型是Tuple5
val tuple = (1, '2', (4, '5'), null, true)
final case class Tuple5[+T1, +T2, +T3, +T4, +T5](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5)
extends Product5[T1, T2, T3, T4, T5] {
override def toString() = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + ")"
}
元组的访问方式如下,因为列表的apply方法只能返回一种类型,所以不能用 “tuple(0)” 的方式访问元组元素。
方式一:采用顺序号
println(tuple._3)
方式二:通过索引(productElement)访问
println(tuple.productElement(0))
元组的遍历方式如下,元组的遍历需要使用到迭代器
for (item <- tuple.productIterator)
println(item)
如果元组只有一个元素,需要 (1,) ,加一个逗号。
不可变列表List
Scala 中的 List 和 Java 的 List 不一样,在 Java 中 List 是一个接口,真正存放数据是 ArrayList。而 Scala 的 List 可以直接存放数据,是一个 object。默认情况下 Scala 的 List 是不可变 的,List 属于序列 Seq。List的结构是链表 。
List 在包对象 package object scala 做了 val List = scala.collection.immutable.List,所以可以直接使用 List创建列表。
List可以放任何数据,用Any类型。
Scala 的 List 的类型是协变 的:对每组类型 S 和 T,如果 S 是 T 的子类型,那么 List[S] 是 List[T] 的子类型。例如,List[String] 是 List[Object] 的子类型。
创建一个Int类型的集合
val list: immutable.Seq[Int] = List(1,2,3)
创建一个空集合
val nil: immutable.Seq[Nothing] = Nil
运行结果:
List(1, 2, 3)
List()
List的访问方式如下:
list(1)
list.apply(0)
list.take(1) //获取第一个元素
for(item <- list1)
println(item)
向列表中增加元素, 会返回新的列表/集合对象,原列表不变 。
List的追加方式一如下,(记住冒号:是靠着集合的)
val list1 = List(1,2,3)
:+ 表示在列表的最后面增加数据
val list2 = list1 :+ 4
println("list2=" + list2)
+: 表示在列表的最前面增加数据
val list3 = 4 +: list1
println("list3=" + list3)
println("list1=" + list1)
运行结果:
list2=List(1, 2, 3, 4)
list3=List(4, 1, 2, 3)
list1=List(1, 2, 3)
List的追加方式二如下。运算时,集合对象一定要放置在最右边,一定要集合结尾。运算规则,从右向左 。
val list1 = List(1,2,3)
:: 表示向集合中添加元素
val list2 = 4::5::list1::Nil
println("list2=" + list2)
::: 表示将一个集合中的每一个元素加入
到另一个集合中去,两边都必须是集合
val list3 = 4::5::list1:::Nil
println("list3=" + list3)
println("list1=" + list1)
运行结果:
list2=List(4, 5, List(1, 2, 3))
list3=List(4, 5, 1, 2, 3)
list1=List(1, 2, 3)
为什么不在列表末尾追加元素?
List中的 :+ 很少被使用,因为往列表末尾追加元素的操作所需的时间随着列表的大小线性增加,而使用 :: 在列表前边追加只需要常量时间。
如果想通过追加元素高效构建列表,可以依次在头部添加再调用reverse 。也可以用可变列表 ListBuffer ,之后再调用 toList 。
| 方法 | 描述 |
|---|---|
| val list = “Will” :: “fill” :: “until” :: Nil | 创建一个新的List[String] |
| list.count(s=>s.length == 4) | 对list中长度为4的字符串元素进行统计(返回2) |
| list.exists(s=>s==“until") | 判断list中是否有字符串元素的值为“until"(返回true) |
| list.filter(s=>s.length==4) | 按顺序返回列表list中所有长度为4的元素列表(返回List(“Will”, “fill") ) |
| list.foreach(s=>println(s) ) | 对列表list中的每个字符串执行print(打印“will fill until") |
| list.foreach(print) | 跟上一条一样,但更精简(同样打印"will fill until") |
| list.drop(2) | 返回去掉了list的头两个元素的列表(返回List(“until”) ) |
| list.dropRight(2) | 返回去掉了list的后两个元素的列表(返回List(“Will”) ) |
| list.forall(s=>s.ends With(“l”)) | 表示列表list中是否所有元素都以字母“l"结尾(返回true) |
| list.map(s=>s+“y") | 返回一个对列表list所有字符串元素末尾添加"y”的新字符串的列表(返回List(“willy", “filly”, “untily") ) |
| list.mkString("[", ",“, “]”) | 用列表list的所有元素组合成的字符串(返回“[will,fill, until]") |
| list.head | 返回列表list的首个元素(返回"will") |
| list.last(耗时) | 返回列表list的最后一个元素(返回“until") |
| list.tail | 返回列表中除第一个元素外的所有元素 |
| list.init(耗时) | 返回列表list除最后一个元素之外的其他元素组成的列表(返回List(“will”,“fill") ) |
| list.isEmpty | 表示列表list是否是空列表(返回false) |
| list.length(耗时) | 返回列表list的元素个数(返回3) |
| list.filterNot(s=>s.length==4) | 按顺序返回列表list中所有长度不为4的元素列表(返回List(“until”) ) |
| list.reverse()(耗时) | 翻转列表 |
| list.flatten() | 将一个列表里的列表扁平化 |
| list.indices | 返回列表所有下标的列表 |
| list.partition | 分组,返回一个包含两组的列表(一组true一组false) |
| list.splitAt | 将列表从指定的下标切开,返回两个列表组成的列表 |
| list.find | 返回满足条件的第一个元素 |
插入排序:
def insert(x: Int, list: List[Int]): List[Int] =
if (list.isEmpty || x <= list.head) x :: list
else list.head :: insert(x, list.tail)
def sort(list: List[Int]): List[Int] =
if (list.isEmpty) Nil
else insert(list.head, sort(list.tail))
换一种方法
def insert(x: Int, xs: List[Int]): List[Int] =
xs match {
case List() => List(x)
case y :: ys => if (x <= y) x :: xs else y :: insert(x, ys)
}
def sort(xs: List[Int]): List[Int] =
xs match {
case List() => List()
case x :: xsl => insert(x, sort(xsl))
}
归并排序:
def sort(op: (Int, Int) => Boolean, list: List[Int]): List[Int] = {
def merge(left: List[Int], right: List[Int]): List[Int] = {
(left, right) match {
case (_, Nil) => left
case (Nil, _) => right
case (x :: xs, y :: ys) =>
if (op(x, y)) x :: merge(xs, right)
else y :: merge(left, ys)
}
}
val mid = list.length / 2
if (mid == 0) list
else {
val (left, right) = list.splitAt(mid)
merge(sort(op, left), sort(op, right))
}
}
可变列表ListBuffer
ListBuffer与Java的 LinkedList 类似。ListBuffer的创建与访问如下
val list1 = ListBuffer(1,2,3)
println(list1(0))
ListBuffer的添加与删除如下
:+ 与 +: 的用法和 List 一样,添加元素并返回新集合
val list1 = ListBuffer(1,2,3)
list1.append(4)
val list2 = list1+=5
println("list2=" + list2)
val list3 = list1++=Array(6,7)
println("list3=" + list3)
println("list1=" + list1)
list1.remove(0)
println("list1=" + list1)
运行结果:
list2=ListBuffer(1, 2, 3, 4, 5)
list3=ListBuffer(1, 2, 3, 4, 5, 6, 7)
list1=ListBuffer(1, 2, 3, 4, 5, 6, 7)
list1=ListBuffer(2, 3, 4, 5, 6, 7)
队列Queue
在Scala中,有可变和不可变队列,一般在开发中通常使用可变集合中的队列。
val queue = new mutable.Queue[Int]()
queue += 1
queue ++= Array(2, 3)
queue ++= List(4, 5)
enqueue入队列
queue.enqueue(6, 7)
println("queue=" + queue)
dequeue出队列
println("queue.dequeue=" + queue.dequeue + ",queue.head=" + queue.head + ",queue.last=" + queue.last)
println("queue=" + queue)
返回除了第一个以外剩余的元素,可以级联使用
val tail = queue.tail
println("queue.tail=" + tail)
println("queue=" + queue)
运行结果:
queue=Queue(1, 2, 3, 4, 5, 6, 7)
queue.dequeue=1,queue.head=2,queue.last=7
queue=Queue(2, 3, 4, 5, 6, 7)
queue.tail=Queue(3, 4, 5, 6, 7)
queue=Queue(2, 3, 4, 5, 6, 7)
集合Map
Java 中的 HashMap 是无序 的,key 不能重复 ,它存储的内容是键值对(key-value)映射。
Scala 中的 Map 和 Java 类似,也是一个散列表 ,它存储的内容也是键值对(key-value)映射,Scala 中不可变的 Map 是有序的,可变的 Map 是无序的 。每一个键值对 Tuple2 类型。
构造不可变映射如下,Scala默认是不可变Map
val map = Map("name" -> "Tom", "city" -> "北京")
构造可变映射Map如下,Map()工厂方法返回一个HashMap。
方式一:
val map = mutable.Map("name" -> "Tom", "city" -> "北京")
方式二:创建空的映射
val map = new scala.collection.mutable.HashMap[String, Int]
val map: Map[Int, String] = Map.empty
方式三:对偶元组
val map = mutable.Map(("name", "Tom"), ("city", "北京"))
映射Map获取值方式如下
方式一:如果 key 存在,则返回对应的值
如果 key 不存在,则抛出异常[java.util.NoSuchElementException]
map2("name")---->"Tom"
map2("age")---->Exception in thread "main" java.util.NoSuchElementException: key not found: age
方式二:使用 contains 方法检查是否存在 key
if (map.contains("Alice"))
println("key存在,值=" + map("Alice"))
else
println("key不存在:)")
方式三:使用 map.get(key).get 取值
map.get(key)返回一个 Option 对象,要么是 Some,要么是 None
如果 map.get(key).get key 存在,返回 key 对应的值
否则,抛出异常java.util.NoSuchElementException: None.get
println(map.get("Jack").get)
方式四:使用 map.getOrElse() 取值
def getOrElse[V1 >: V](key: K, default: => V1)
如果 key 存在,返回 key 对应的值
如果 key 不存在,返回默认值
println(map.getOrElse("age", "Nothing"))---->Nothing
方式五:使用 map.getOrElseUpdate() 取值
def getOrElseUpdate(key: K, op: => V)
如果 key 存在,返回 key 对应的值
如果 key 不存在,则添加该 key 和 value 进入Map
println(map.getOrElseUpdate("name","Mark"))---->Tom
println(map)---->Map(city -> 北京, name -> Tom)
println(map.getOrElseUpdate("gender","male"))---->male
println(map)---->Map(city -> 北京, name -> Tom, gender -> male)
方式流:使用 apply
如果 key 存在,则返回对应的值
如果 key 不存在,则抛出异常[java.util.NoSuchElementException]
println(map.apply("name"))
对映射Map的修改、添加和删除
修改 map 的元素
如果 key 存在,则修改对应的值;key 不存在,则添加
map("country")="China"
添加 map 元素
如果 key 存在,则更新;如果不存在,则添加
map += (("gender","male"),("country","China"))
map += ("gender"->"male","country"->"China")
map ++= map1
map ++ List("1"->"1","2"->"2")
删除 map 元素
如果 key 存在,则删除;如果不存在,也不会报错
map -= "name"
map -= ("name","number")
Map的遍历方式如下
遍历 key-value
for((k, v) <- map) println(k + " " + v)
遍历所有 key
for(k <- map.keys) println(k)
遍历所有 value
for(v <- map.values) println(v)
取出元组
for(tuple <- map) println(tuple._1 + " " + tuple._2)
集Set
集Set是不重复 元素的集合,是无序 的,默认是以哈希表 实现。
默认情况下,Scala使用的是不可变集合 Set,如果使用可变集合,需要引用scala.collection.mutable.Set ,其工厂方法返回一个HashSet。
Set的操作如下
val set = Set(1, 2, 3) 不可变
val set2 = mutable.Set(1,2,"hello") 可变
val set3 = Set.empty[Int] 空集
set2.add(4) 方式 1
set2+= 6 方式 2
set2.+=(5) 方式 3
set ++ List(1,2,3)
set2 -= 2 操作符形式
set2.remove("abc") 方法形式
for(x <- set2) println(x)
map 映射操作
map() :接收一个函数作为参数,将元素转换成其它形式或提取信息。该函数会被应用到每个元素,并将其映射成一个新的元素。
要求将 List(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回。
传统做法
object ScalaTest {
def main(args: Array[String]): Unit = {
var list = List(3, 5, 7)
var newList = List[Int]()
for (item <- list)
newList = newList :+ item
println(newList)
}
}
使用map操作
object ScalaTest {
def func(n: Int): Int = n
def main(args: Array[String]): Unit = {
var list = List(3, 5, 7)
val newList = list.map(func)
println(newList)
}
}
map 方法是一个高阶函数 (接收函数的函数)。依次遍历集合list,将每个元素传递给func函数,func返回新的值并放入一个新的集合。下面的 func 是一个高阶函数:
object ScalaTest {
def sum(a: Int, b: Int): Int = a + b
def func(f: (Int, Int) => Int, a: Int, b: Int): Int = f(a, b)
def main(args: Array[String]): Unit = {
var result: Int = func(sum, 4, 5)
println(result) // 9
}
}
说明:sum() 的一个普通函数,func() 是一个高阶函数。f 是传入的函数名,是一个指向函数的指针,调用 f 相当于调用函数,(Int, Int) => Int 函数接收两个 Int 类型的参数并返回一个 Int 类型的结果,a 和 b 是传入的参数。func 将调用 f 函数,最终返回一个 Int 类型的结果。
下面模拟实现map的机制:
object ScalaTest {
def sum(a: Int): Int = a * a
def main(args: Array[String]): Unit = {
val myList = new MyList(1, 2, 3, 4, 5)
val newList: ArrayBuffer[Int] = myList.map(sum)
println(newList)
}
}
class MyList(args: Int*) {
def map(f: Int => Int): ArrayBuffer[Int] = {
var newList = ArrayBuffer[Int]()
for (item <- args)
newList += f(item)
newList
}
}
扁平化映射flatMap
flatMap :是将集合中的每个元素映射到某个函数,之后打散每个元素并返回新的集合(只打散一层 )。
def upper(s: String): String = {
val string = s.toUpperCase
println(string)
string
}
def main(args: Array[String]): Unit = {
val list = List("Alice", "Bob", "Nick")
val newList = list.flatMap(upper)
println(newList)
}
运行结果:
ALICE
BOB
NICK
List(A, L, I, C, E, B, O, B, N, I, C, K)
下面的func只调用了3次,对应list里面的3个元素,可以看到flatMap是对集合的每个元素整体操作,之后再将每个元素打散放到一个集合内并返回。
def multiple(a: Int): Int = a * a
def func(list: List[Int]): List[Int] = {
val newList = list.map(multiple)
println(newList)
newList
}
def main(args: Array[String]): Unit = {
val list = List(List(1, 2), List(3, 4), List(5))
val newList = list.flatMap(func)
println(newList)
}
运行结果:
List(1, 4)
List(9, 16)
List(25)
List(1, 4, 9, 16, 25)
def main(args: Array[String]): Unit = {
val lines = List("Java Python SQL Assembly Java PHP",
"PHP Java PHP Assembly SQL Python",
"Python Java PHP SQL PHP SQL")
val result = lines.flatMap(_.split(" "))
println(result)
}
运行结果:
List(Java, Python, SQL, Assembly, Java, PHP, PHP, Java, PHP, Assembly, SQL, Python, Python, Java, PHP, SQL, PHP, SQL)
有一个Person类,表示这个人的姓名,性别,子女。现在要求列表中所有母亲和孩子的对偶。
case class Person(name: String, isMale: Boolean, children: Person*)
val lara = Person("lara", false)
val bob = Person("bob", true)
val julie = Person("julie", false, lara, bob)
val persons = List(lara, bob, julie)
先过滤母亲,然后得到一个母亲列表。由于每遍历一个孩子就会产生一个列表,所以要用flatMap打散。flatMap遍历每个母亲,同时map遍历每个母亲的每个孩子,得到元组
persons.filter(!_.isMale)
.flatMap(p => p.children.map(c => (p.name, c.name)))
.foreach(println)
(julie,lara)
(julie,bob)
过滤filter
filter :将符合要求的数据(筛选)放置到新的集合中,filter需要传入一个返回值为Boolean 的函数。
下面将字符串长度大于3的过滤出来:
def len(s: String): Boolean = s.length > 3
def main(args: Array[String]): Unit = {
val list = List("Alice", "Bob", "Nick")
val newList = list.filter(len)
println(newList)
}
运行结果:
List(Alice, Nick)
下面将偶数过滤出来:
def func(n: Int): Boolean = n % 2 == 0
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5)
val newList = list.filter(func)
println(newList)
}
运行结果:
List(2, 4)
化简reduce
需求:计算 List(1, 2, 3, 4 ,5) 的和
def sum(a: Int, b: Int): Int = a + b
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5)
val newList = list.reduce(sum)
println(newList)
}
运行结果:15
步骤 1:(1 + 2)
步骤 2:(1 + 2) + 3
步骤 3:((1 + 2) + 3) + 4
步骤 4:(((1 + 2) + 3) + 4) + 5 = 15
reduce 底层是调用 reduceLeft 的,reduceLeft(f) 接收的函数需要的形式为 op: (B, A) = > B): B
reduceleft(f) 的运行规则:从左边开始执行,将得到的结果返回给第一个参数,然后继续和下一个元素运行,将得到的结果继续返回给第一个参数…
def minus(a: Int, b:Int): Int = a - b
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5)
val newList = list.reduceLeft(minus)
println(newList)
}
运行结果:-13 (((1-2) - 3) - 4) - 5 = -13
def minus(a: Int, b: Int): Int = a - b
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5)
val newList = list.reduceRight(minus)
println(newList)
}
运行结果:3 1 - (2 - (3 -(4 - 5))) = 3
折叠fold
折叠和化简的运行机制几乎一样,reduceLeft 就是调用的 foldLeftB,并且是默认从集合的 head 元素开始操作
def minus(a: Int, b: Int): Int = a - b
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
val newList = list.foldLeft(0)(minus)
println(newList)
}
0作为第一个参数与1运算,((((0-1) - 2) - 3)) - 4 = -10
def minus(a: Int, b: Int): Int = a - b
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
val newList = list.foldRight(0)(minus)
println(newList)
}
1- (2 -(3- (4 - 0))) = -2
foldLeft 和 foldRight 缩写方法分别是 /: 和 *:* (deprecated)
var i = (2 /: list) (minus) // =等价=> list.foldLeft(2)(minus)
var i = (list :\ 2) (minus) // list.foldRight(2)(minus)
利用fold翻转列表
val list: List[Int] = list.foldLeft(List[Int]())((ys, y) => y :: ys)
扫描scan
对某个集合的所有元素做 fold 操作,但是会把产生的所有中间结果放置于一个集合中保存
def minus(a: Int, b: Int): Int = a - b
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
val newList = list.scanLeft(0)(minus)
println(newList)
}
运行结果:List(0, -1, -3, -6, -10)
def minus(a: Int, b: Int): Int = a - b
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
val newList = list.scanRight(0)(minus)
println(newList)
}
运行结果:List(-2, 3, -1, 4, 0)
拉链zip
拉链可以将两个集合进行对偶元组合并
def main(args: Array[String]): Unit = {
val a = List(1, 3)
val b = List(2, 4)
val list = a.zip(b)
println(list)
}
运行结果:List((1,2), (3,4))
如果两个集合个数不对应,会造成数据丢失
def main(args: Array[String]): Unit = {
val a = List(1, 3, 5, 6)
val b = List(2, 4)
val list = a.zip(b)
println(list)
}
运行结果:List((1,2), (3,4))
迭代器iterator
通过 iterator 方法从集合获得一个迭代器,通过 while 循环和 for 表达式对集合进行遍历
方式一:
val iterator = List(1, 3, 5, 6).iterator
while (iterator.hasNext){
println(iterator.next())
}
方式二:
val iterator = List(1, 3, 5, 6).iterator
for (item <- iterator){
println(item)
}
流Stream
Stream 是一个集合,可以用于存放无穷多个元素,但是这无穷个元素并不会一次性生产出来,而是需要用到多大的区间,就会动态的生产,末尾元素遵循 lazy 规则(即:要使用结果才进行计算的)
创建的集合的第一个元素是 n , 后续元素生成的规则是 n * 2,后续元素生成的规则是可以自定义的
def func(n: BigInt): Stream[BigInt] = n #:: func(n * 2)
def main(args: Array[String]): Unit = {
val stream: Stream[BigInt] = func(1)
println(stream)
//取出第一个元素
println("head=" + stream.head)
println(stream.tail)
// 当对流执行 tail 操作时,就会生成一个新的数据
println(stream)
}
视图view
view 方法产出一个总是被懒执行 的集合,view 不会缓存数据,每次都要重新计算。只有在使用时才会计算。
def multiple(a: Int): Int = a * a
def main(args: Array[String]): Unit = {
var list = Range(1,11)
var newList = list.view.map(multiple)
println(newList)
for (item <- newList) {
print(item + " ")
}
}
运行结果:
SeqViewM(…)
1 4 9 16 25 36 49 64 81 100
可以看到一开始没有执行,当遍历的时候才执行。
并行集合par
Scala 为了充分使用多核 CPU,提供了并行集合,用于多核环境的并行计算。用法很简单,只需要在之前的集合操作加上 par 就可以了。
def main(args: Array[String]): Unit = {
def myPrint(n: Int) = print(n + " ")
(1 to 5).foreach(myPrint)
println()
(1 to 5).par.foreach(myPrint)
}
运行结果:
1 2 3 4 5
3 5 4 2 1
def main(args: Array[String]): Unit = {
val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}.distinct
val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}.distinct
println(result1) //非并行
println(result2) //并行
}
运行结果:
Vector(main)
ParVector(scala-execution-context-global-11, scala-execution-context-global-13,
scala-execution-context-global-12, scala-execution-context-global-14)
distinct 可以去重
WordCount
使用映射集合操作,统计 list 中各个单词出现的次数,并按次数大小由大到小排序
做法一:
def splitLine(map: mutable.Map[String, Int], str: String): mutable.Map[String, Int] = {
val strings: Array[String] = str.split(' ')
for (string <- strings) {
map += (string -> (map.getOrElse(string, 0) + 1))
}
map
}
def main(args: Array[String]): Unit = {
val lines = List("Java Python SQL Assembly Java PHP",
"PHP Java PHP Assembly SQL Python",
"Python Java PHP SQL PHP SQL")
val map: mutable.Map[String, Int] = mutable.Map()
lines.foldLeft(map)(splitLine)
val result: List[(String, Int)] = map.toList.sortBy(_._2)
println(result)
}
运行结果:
List((Assembly,2), (Python,3), (SQL,4), (Java,4), (PHP,5))
做法二:
def main(args: Array[String]): Unit = {
val lines = List("Java Python SQL Assembly Java PHP",
"PHP Java PHP Assembly SQL Python",
"Python Java PHP SQL PHP SQL")
val map: mutable.Map[String, Int] = mutable.Map()
val result = lines.flatMap(_.split(" "))
.foldLeft(map)((map: mutable.Map[String, Int], string: String) => {
map += (string -> (map.getOrElse(string, 0) + 1)) //匿名函数
})
.toList.sortWith(_._2 < _._2)
println(result)
}
运行结果:
List((Assembly,2), (Python,3), (SQL,4), (Java,4), (PHP,5))
做法三:
一般形式:
def main(args: Array[String]): Unit = {
val lines = List("Java Python SQL Assembly Java PHP",
"PHP Java PHP Assembly SQL Python",
"Python Java PHP SQL PHP SQL")
val result = lines.flatMap((s: String) => s.split(" ")) //分割字符串
.map((s: String) => (s, 1)) //将每一个字符串转为一个元组(s,1)
.groupBy((x: (String, Int)) => x._1) //根据元组的第一个元素分组
.map((x: (String, List[(String, Int)])) => (x._1, x._2.size)) //统计各分组大小
.toList.sortBy((x: (String, Int)) => x._2) //按照元组第二个元素排序
println(result)
}
简写形式:
def main(args: Array[String]): Unit = {
val lines = List("Java Python SQL Assembly Java PHP",
"PHP Java PHP Assembly SQL Python",
"Python Java PHP SQL PHP SQL")
val result = lines.flatMap(_.split(" "))
.map((_, 1))
.groupBy(_._1)
.map(x => (x._1, x._2.size))
.toList.sortBy(_._2)
println(result)
}
运行结果:
List((Assembly,2), (Python,3), (SQL,4), (Java,4), (PHP,5))
模式匹配
Scala 中的模式匹配类似于 Java 中的 switch 语法。在模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行匹配。
当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码;如果匹配不成功,继续执行下一个分支进行判断;如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句。
def main(args: Array[String]): Unit = {
val operator = Array('+', '-', '*', '/', 520, 10)
val (x, y) = (20, 10)
var result: Any = 0
for (o <- operator) {
o match {
case '+' => result = x + y
case '-' => result = x - y
case '*' => result = x * y
case '/' => result = x / y
case 100 => result = "one hundred"
case _ if (o.isInstanceOf[Int] && o > 500) => result = o
case _ => {
print("\nNot an operator.Return ")
result = "Nothing"
}
}
print(result + " ")
}
}
运行结果:
30 10 200 2 520
Not an operator.Return Nothing
注意事项:
- 匹配的顺序是从上到下 ,匹配到一个就执行 = > 后的代码
- = > 后面的代码块不需要写 break ,执行完会自动的退出 match
- 如果想要匹配某个范围的数据,可以在模式匹配中增加条件守卫,此时 case _ 的下划线指的是忽略值o。守卫可不加 ()
- 如果所有 case 都不匹配,那么会执行 case _ 分支,如果没有 case _ 分支,那么会抛出异常 MatchError
- = > 后可以用 {} 写代码块
- 可以在 match 中使用任意类型
def main(args: Array[String]): Unit = {
val operator = Array('+', '-', '*', '/')
val (x, y) = (20, 10)
for (o <- operator) {
val result = o match {
case '+' => x + y
case '-' => x - y
case '*' => x * y
case '/' => x / y
case _ => -1
}
print(result + " ")
}
}
运行结果:
30 10 200 2
- 如果在 case 关键字后跟变量名 ,那么 match 前表达式的值会赋给这个变量,而且是无条件匹配 ,下面除了 ‘+’ 其它均返回原字符
def main(args: Array[String]): Unit = {
val operator = Array('+', '-', '*', '/')
val (x, y) = (20, 10)
for (o <- operator) {
val result:Any = o match {
case '+' => x + y
case variable => variable
case '-' => x - y
case '*' => x * y
case '/' => x / y
case _ => -1
}
print(result + " ")
}
}
运行结果:
30 - * /
- Scala要求模式都是线性的:同一个模式变量在模式中只能出现一次。
case class Calculate(a: Int, op: String, b: Int)
object ScalaTest {
def Add(cal: Calculate) = cal match {
case Calculate(x, "+", x) => Calculate(x, "*", 2)
case _ => cal
}
}
error: x is already defined as value x
使用模式守卫
def Add(cal: Calculate) = cal match {
case Calculate(x, "+", y) if x == y => Calculate(x, "*", 2)
case _ => cal
}
模式匹配可以匹配对象的任意类型 ,这样做避免了使用 isInstanceOf 和 asInstanceOf 方法
def main(args: Array[String]): Unit = {
val list = List(1, Array("哈"), Map(3 -> "2"), Map("4" -> 5), BigInt(6))
for (item <- list) {
val result = item match {
case a: Map[Int, String] => "Map[Int, String]"
case b: Map[String, Int] => "Map[String, Int]"
case c: Array[Int] => "Array[Int]"
case d: Array[String] => "Array[String]"
case e: BigInt => "BigInt"
case f: Int => "Int"
case _ => "Nothing"
}
println(result)
}
}
运行结果:
Int
Array[String] <----注意这里
Map[Int, String] <----注意这里
Map[Int, String] <----注意这里
BigInt
注意,Scala采用了类型擦除 ,运行时不会保留类型参数的信息,所以运行时无法判断出泛型的类型。例如上面的两个Map,由于类型擦除,只能知道它是一个Map,但不知道是什么类型的Map,所以只能匹配Map变量a。
Map(3 -> "2"), Map("4" -> 5)
case a: Map[Int, String] => "Map[Int, String]"
case b: Map[String, Int] => "Map[String, Int]"
擦除规则唯一例外 的是数组,Java和Scala都对数组做了处理,数组的元素类型是跟数组要一起保存的。
Array("哈")
case c: Array[Int] => "Array[Int]"
case d: Array[String] => "Array[String]"
如果使用类型匹配,case 后面的变量不会无条件匹配,只会先赋值,然后判断类型。
使用类型匹配时,如果 case _ 出现在 match 中间,则表示隐藏变量名,不使用变量(=> 后面不使用变量_)
def main(args: Array[String]): Unit = {
val list = List(1, Array(2, 3), Map("4" -> 5), BigInt(6))
for (item <- list) {
val result = item match {
case a: Int => a
case _: Map[String, Int] => "Map[String, Int]"
case c: BigInt => "BigInt"
case _ => "Nothing"
}
print(result + "\t")
}
}
运行结果:
1 Nothing Map[String, Int] BigInt
模式匹配可以匹配数组、元组、列表 ,用法和正则表达式类似
下面是匹配数组 ,如果想要表示多个值需要用 _ *,下划线表示不接收变量
val list = List(Array(0), Array(10, 20), Array(1, 2, 3, 4, 5), List(1, 2, 3, 4))
for (item <- list) {
item match {
case List(_*) => println("类型为List的数组")
case Array(0) => println("只有一个元素且为0的数组")
case Array(1, _, 3, _*) => println("第一个为1,第三个为3的数组")
case Array(x, y) => printf("包含两个元素%d,%d的数组\n", x, y)
case _ => println("Nothing")
}
}
运行结果:
只有一个元素且为0的数组
包含两个元素10,20的数组
第一个为1,第三个为3的数组
类型为List的数组
下面是匹配列表 ,如果想要表示一个或多个值需要用 _ (不需要*)或者变量,
val list = List(List(0), List(10, 20), List(3, 2, 1), List(1, 2, 3, 4, 5))
for (item <- list) {
item match {
case 0 :: Nil => println("只有一个元素且为0的列表")
case 3 :: everything => println("3开头的列表,后面是" + everything)
case 1 :: _ :: 3 :: _ => println("第一个为1,第三个为3的列表")
case x :: y :: Nil => printf("包含两个元素%d,%d的列表\n", x, y)
case _ => println("Nothing")
}
}
运行结果:
只有一个元素且为0的列表
包含两个元素10,20的列表
3开头的列表,后面是List(2, 1)
第一个为1,第三个为3的列表
下面是匹配元组
val list = List(Tuple1(0), (10, 20), (3, 2, 1), (1, 2, 3, 4, 5))
for (item <- list) {
item match {
case Tuple1(0) => println("只有一个元素且为0的元组")
case (3, _, _) => println("3开头的三元组")
case (x, 20) => printf("第一个为%d,第二个为20的元组\n", x)
case (1, _, 3, _, _) => println("第一个为1,第三个为3的五元组")
case _ => println("Nothing")
}
}
运行结果:
只有一个元素且为0的元组
第一个为10,第二个为20的元组
3开头的三元组
第一个为1,第三个为3的五元组
模式匹配可以用在变量声明和for循环
val (x, y, z) = (1, 2, "hello")
println("x=" + x + ", y=" + y + ", z=" + z)
val Array(first, second, _*) = Array(1, 2, 3, 4)
println("first=" + first + ", second=" + second)
运行结果:
x=1, y=2, z=hello
first=1, second=2
val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
for ((k, 0) <- map) {
println(k + " --> " + 0)
}
运行结果:B --> 0
对象匹配
case 中对象的 unapply 方法(对象提取器)返回 Some 则为匹配成功;返回 None 则为匹配失败
object ScalaTest {
def main(args: Array[String]): Unit = {
val num: Double = 36 // Square(6.0)
num match {
case Square(n) => println("匹配成功,n=" + n)
case _ => println("匹配失败")
}
}
}
object Square {
def unapply(d: Double): Option[Double] = {
println("unapply,d=" + d)
Some(math.sqrt(d))
}
def apply(d: Double): Double = d * d
}
运行结果:
unapply,d=36.0
匹配成功,n=6.0
当匹配到 case Square(n),调用 Square 的 unapply(d: Double),此时 d = 36.0。如果对象提取器 unapply(d: Double) 返回的是 Some(6),则表示匹配成功,同时将 6 赋给 Square(n) 的 n,此时 n = 6.0.
object ScalaTest {
def main(args: Array[String]): Unit = {
val num = "A,B,C"
num match {
case Names(a, b, c) => printf("匹配成功,a=%s,b=%s,c=%s", a, b, c)
case _ => println("匹配失败")
}
}
}
object Names {
def unapplySeq(s: String): Option[Seq[String]] = {
if (s.contains(",")) Some(s.split(","))
else None
}
}
运行结果:
匹配成功,a=A,b=B,c=C
当 case 后面的对象提取器方法的参数为多个,则会默认调用 unapplySeq() 方法。注意,unapplySeq返回的值的个数需要和 Names(a, b, c) 要一样
样例类
样例类是为模式匹配而优化的类,用case进行声明。
//样例类
abstract class Amount
case class Dollar(value: Double) extends Amount {
def show = println(value)
}
case class Currency(value: Double, unit: String) extends Amount
case object NoAmount extends Amount
object ScalaTest {
def main(args: Array[String]): Unit = {
val dollar = Dollar(100)
val currency = Currency(200, "¥")
val money = currency.copy(unit = "$")
val array = Array(dollar, currency, money, NoAmount)
for (arg <- array)
arg match {
case Dollar(x) => println("Dollar(" + x + ")")
case Currency(v, u) => println("Currency(" + v + ", " + u + ")")
case NoAmount => println("NoAmount")
case _ => println("Nothing")
}
}
}
- 样例类可以像其他类添加字段和方法。
- 样例类的伴生对象提供了apply方法,可以不用new创建对象
- 样例类自动生成 toString,equals,hashCode,copy方法
- 样例类的每个构造方法参数都隐式成为这个类的字段,效果和参数声明之前带上val一样。
final case class ::[T](head: T, tail: List[T]) extends List[T]{ override def isEmpty: Boolean = false }
等价于
final case class ::[T](val head: T, val tail: List[T]) extends List[T]{ override def isEmpty: Boolean = false }
密封类
通过在样例类的超类前面添加 sealed 修饰符,当进行样例类模式匹配时,如果缺失模式,编译器会发出警告。
密封类除了在同一个文件中定义的子类外,不能添加子类。
sealed abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object NoAmount extends Amount
// warning: match may not be exhaustive.
def describe(amount: Amount) =
amount match {
case Dollar(x) => println("Dollar(" + x + ")")
case NoAmount => println("NoAmount")
}
可以加一个注解压制模式分支的覆盖完整性检测。
def describe(amount: Amount) =
(amount: @unchecked) match {
case Dollar(x) => println("Dollar(" + x + ")")
case NoAmount => println("NoAmount")
}
偏函数PartialFunction
偏函数PartialFunction 是个特质,可以同时实现 filter 和 map 的功能。
将集合 val list = List(1, 2, 3, 4, “abc”) list 中的所有数字+1,并返回一个新的集合
object ScalaTest {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, "hello")
val result = list.collect(func)
println(result)
}
val func: PartialFunction[Any, Int] = new PartialFunction[Any, Int] {
override def isDefinedAt(x: Any): Boolean = x.isInstanceOf[Int]
override def apply(v: Any): Int = v.asInstanceOf[Int] + 1
}
}
运行结果:
List(2, 3, 4, 5)
PartialFunction[Any,Int] 表示偏函数接收的参数类型是 Any,返回类型是 Int。[Any, Int]是泛型 ,第一个表示传入参数类型,第二个表示返回参数类型。
isDefinedAt(x: Any) 如果返回 true,就会去调用 apply 构建对象实例;如果是 false,则过滤
apply 构造器对传入的值 + 1,并返回新的集合
collect 方法使用偏函数
偏函数有两种简写方式,每一个case就是一个偏函数。偏函数就是对特定的值进行操作。
方式一:在 PartialFunction 内使用 case
object ScalaTest {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, "hello")
val result = list.collect(func)
println(result)
}
def func: PartialFunction[Any, Any] = {
case n: Int => n + 1
case s: String => s.toUpperCase()
}
}
方式二:直接在 collect 写 case,注意花括号
object ScalaTest {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, "hello")
val result = list.collect{
case n: Int => n + 1
case s: String => s.toUpperCase()
}
println(result)
}
}
匿名函数
没有名字的函数就是匿名函数。可以将匿名函数赋给一个变量,然后这个变量指向函数,可以直接用这个变量调用函数。
val triple = (x: Double) => {
println("x=" + x)
3 * x
}
println("triple" + triple(3)) // 9.0
匿名函数不需要写 def 函数名;不需要写返回类型,使用类型推导;= 变成 =>;如果有多行,则使用{}
高阶函数
能够接受函数作为参数的函数,叫做高阶函数,可使应用程序更加健壮。
object ScalaTest {
def sum(d: Double): Double = d + d
def mod(d: Double): Int = d.toInt % 2
def func(f: Double => Double, f2: Double => Int, n1: Double) = f(f2(n1))
def main(args: Array[String]): Unit = {
val res = func(sum, mod, 5.0)
println("res=" + res) // 2.0
}
}
object ScalaTest {
def minus(x: Int) = {
y: Int => x - y //匿名函数,闭包
}
def main(args: Array[String]): Unit = {
val func = minus(3)
println(func(1)) // 2
println(func(9)) // -6
println(minus(4)(9)) // -5
}
}
minus 是高阶函数,因为它返回匿名函数 (y: Int) => x - y,返回的匿名函数可以使用变量接收。
minus 同时还是一个闭包 ,因为该函数引用到到函数外的 x,那么该函数和 x 整体形成一个闭包。val func = minus(3) 就是一个闭包,此时多次调用 func 时(多次调用闭包),使用的是同一个 x,x 不变。所以闭包的好处是可以将参数先存起来,后面可以使用。
下面的函数 makeSuffix(suffix: String) 可以接收一个文件后缀名(比如.jpg), 并返回一个闭包。调用闭包,可以传入一个文件名。如果该文件名没有指定的后缀(比如.jpg),则返回文件名.jpg;如果已经有.jpg 后缀,则返回原文件名。
object ScalaTest {
def main(args: Array[String]): Unit = {
val f = makeSuffix(".jpg")
println(f("dog.jpg")) // dog.jpg
println(f("cat")) // cat.jpg
}
def makeSuffix(suffix: String) = {
//返回一个匿名函数,回使用到 suffix
(filename: String) => {
if (filename.endsWith(suffix)) {
filename
} else {
filename + suffix
}
}
}
}
使用高阶函数减少代码重复 。例如下面有3个结构一样的函数。
private val fileList = new java.io.File(".").listFiles()
def fileEnding(query: String): Array[File] =
for (file <- fileList if file.getName.endsWith(query)) yield file
def fileContaining(query: String): Array[File] =
for (file <- fileList if file.getName.contains(query)) yield file
def fileMatching(query: String): Array[File] =
for (file <- fileList if file.getName.matches(query)) yield file
将其简化为如下
private val fileList = new java.io.File(".").listFiles()
def fileMatch(query: String, matcher: (String, String) => Boolean): Array[File] =
for (file <- fileList if matcher(file.getName, query)) yield file
def fileEnding(query: String): Array[File] =
fileMatch(query, _.endsWith(_))
def fileContaining(query: String): Array[File] =
fileMatch(query, _.contains(_))
def fileMatching(query: String): Array[File] =
fileMatch(query, _.matches(_))
解释一下 fileEnding 方法里的匿名函数 .endsWith() 的含义:
(fileName: String, query: String) => fileName.endsWith(query)
(fileName, query) => fileName.endsWith(query)
_.endsWith(_)
fileMatch 是外部函数的内部函数,可以使用闭包,直接使用query
private val fileList = new java.io.File(".").listFiles()
def fileMatch(matcher: String => Boolean): Array[File] =
for (file <- fileList if matcher(file.getName)) yield file
def fileEnding(query: String): Array[File] =
fileMatch(_.endsWith(query))
def fileContaining(query: String): Array[File] =
fileMatch(_.endsWith(query))
def fileMatching(query: String): Array[File] =
fileMatch(_.endsWith(query))
参数推断
参数推断可以省去类型信息,因为参数类型是可以推断出来的
规则如下:
- 参数类型是可以推断时,可以省略参数类型
- 当传入的函数只有一个参数时,可以省略括号
- 如果变量只在 = > 右边出现一次,可以省略左边的变量,右边的变量用 _ 来代替
- 如果只有一个变量且没有任何需要改变的地方,则可以只写方法名,如 list.foreach(println)
val list: List[Int] = List(1, 2, 3, 4)
map内的是一个匿名函数,遍历列表元素并加1
println(list.map((x: Int) => x + 1))
可以推出参数类型是Int,可以省略
println(list.map((x) => x + 1))
函数只有一个参数,可以省略括号
println(list.map(x => x + 1))
x 只在 => 右边出现一次,可以用 _ 来代替
println(list.map(_ + 1))
val list = List(1, 2, 3, 4)
println(list.reduce((x: Int, y: Int) => x + y)) // 10
println(list.reduce((x, y) => x + y))
println(list.reduce(_ + _))
val f: (Int, Int) => Int = (_: Int) + (_: Int)
println(f(5, 10)) // 15
val f: (Int, Int) => Int = _ + _
println(f(5, 10))
部分应用函数
部分应用函数不需要给出所有的参数,给出部分或者完全不给,用下划线 “_” 代替。特定情况下,下划线可以省略:list.foreach(println)
scala> def sum(a: Int, b: Int): Int = a + b
sum: (a: Int, b: Int)Int
scala> val a = sum
<console>:12: error: missing argument list for method sum
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `sum _` or `sum(_,_)` instead of `sum`.
val a = sum
scala> val a = sum _ // 完全不给
a: (Int, Int) => Int = $Lambda$1019/424832797@626c569b
scala> a(1, 2)
res0: Int = 3
scala> val b = sum(5, _: Int) // 给出部分
b: Int => Int = $Lambda$1032/1075758996@d76099a
scala> b(3)
res1: Int = 8
如果部分应用函数没有参数,调用时要加上圆括号()。
def func(): Unit = println("hello,world!")
val f = func _
f() // 打印"hello,world!"
闭包
闭包就是即使外部函数返回之后,内部函数总是可以访问其所在的外部函数中声明的参数和变量。闭包可以避免使用全局变量,防止全局变量污染,局部变量会常驻在内存中,所以会造成内存泄漏。
每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。
个人理解:闭包是一个外部函数中的内部函数,可以使用同一地址中外部函数中(第一次创建的外部函数)对应变量最终的值。闭包接收一个参数,返回一个保存了这个参数的函数。
def sum(a: Int): Int => Int = (b: Int) => a + b
def main(args: Array[String]): Unit = {
val closure1: Int => Int = sum(1)
println(closure1(3))
println(closure1(4))
val closure2: Int => Int = sum(20)
println(closure2(30))
println(closure2(40))
}
closure1 和 closure2 是两个不同的闭包。sum函数的内部函数用到了sum的参数。sum传入一个值返回一个函数引用。
柯里化,就是闭包,写法不一样而已
def sum(x: Int)(y: Int) = x + y
控制抽象
控制抽象是一个函数,满足如下条件:
- 传入的参数是函数
- 抽象控制函数的参数是一个没有输入值也没有返回值的函数,() => Unit
def func(f: () => Unit) = f()
def main(args: Array[String]): Unit = {
func {
() =>
println("func是一个控制抽象函数")
println("抽象控制的参数是一个匿名函数")
}
}
运行结果:
func是一个控制抽象函数
抽象控制的参数是一个匿名函数
func是一个控制抽象函数,没有输入值也没有返回值。f 是一个函数引用,要加括号。
抽象控制可以简写,func 里面的是匿名函数
def func(f: => Unit) = f
def main(args: Array[String]): Unit = {
func {
println("func是一个控制抽象函数")
println("抽象控制的参数是一个匿名函数")
}
}
breakable 也是一个抽象控制函数,它采用了简写形式
def breakable(op: => Unit) {
try {
op
} catch {
case ex: BreakControl =>
if (ex ne breakException) throw ex
}
}
def main(args: Array[String]): Unit = {
import util.control.Breaks._
var n = 0
breakable {
while (n <= 10) {
print(n + " ")
n += 1
if (n == 5) {
break()
}
}
}
}
下面使用抽象控制实现类似 while 的 loop 函数,递归调用。
object ScalaTest {
def loop(condition: => Boolean)(block: => Unit): Unit = {
if (condition) {
block
loop(condition)(block) //递归
}
}
def main(args: Array[String]): Unit = {
var n = 5
loop(n > 0) {
n -= 1
print(n + " ")
}
}
}
细节
- 默认值 _ 不能在方法内使用,否则会报 Local variables must be initialiazed
- 在 Scala 中,如果一个函数(方法)没有形参,则可以省略 ()
object ScalaTest {
def func(): Unit ={ print("hello") }
def main(args: Array[String]): Unit = {
func
}
}
- 在 Scala 中没有 ++ 和 – , 而使用 +=1 和 -= 1
- Scala不支持三元运算符,用 if else 代替
val num = 50
val result = if (num > 10) num else 0
println(result) //50
- 在 Scala 中,可以把一个函数直接赋给一个变量,但是不执行函数,语法:函数名 _
object ScalaTest {
def func(): Unit = println("hello,world!")
def main(args: Array[String]): Unit = {
var f1 = func _ //不执行
func() //执行
}
}
- A 操作符 B 等同于 A.操作符(B) ,A 操作符= B 等同于 A = A 操作符 B
- 在 Scala 中,函数也是有类型,比如 plus 就是
plus(_) 的下划线代表从集合中遍历出来的一个元素
object ScalaTest {
def plus(x: Int) = 3 + x
def main(args: Array[String]): Unit = {
val result = List(1, 2, 3).map(plus(_))
println(result.mkString(",")) //4,5,6
}
}
-
Scala的数组的访问方式是将下标写在圆括号里,如array(0),而不是像Java使用方括号
-
类似 +、-、*、/ 这样的字符串可以用作方法名,所以 1 + 2 实际上是调用了Int对象1上,名为 + 的方法,将2作为参数传入。也可以写成 1.+(2)。Scala允许操作符表示法 ,所有的方法都可以使用操作符表示法,例如判断字符串string是否包含子串可以写成:string.contains(“Love”) == > string contains "Love"

-
Scala会隐式引入 java.lang 和 scala包 的成员,以及名为 Predef 的伴生对象的所有成员。
-
Java有4个命名空间:字段、方法、类型、包。Scala只有2个命名空间:值(字段、方法、包、伴生对象),类型(类、特质名)
-
Scala的 == 是判断内容是否相同,用法与 equals 一样。判断地址是否相同用 eq 。
-
注意Scala匿名函数的参数问题,下面的错误原因是因为Scala将匿名函数的两个参数key和value看成了一个tuple参数,导致类型不匹配。解决方法是使用模式匹配。
// 错误,报错类型不匹配
obj.map((key:String, value:(Int, Int))=> (key, value._1/ value._2.toDouble))
// 解决方法:模式匹配
obj.map{ case(k, v) => (k, v._1 / v._2.toDouble) }
