Scala
概述
概述
- 体现了面向对象,函数式编程等多种不同的语言范式,且融合了不同语言新的特性
- scala语言是基于java语言开发的,所以大部分的java代码可以直接在scala中使用
- 代码可以不需要分号结尾: scala推荐一行代码中只有一个逻辑,那么分号可以省略
入门案例
1 |
|
变量/数据类型
变量
概念
够通过取值推断变量的类型,那么变量类型可以省略
如果使用多态,那么类型不能省略
1
var name3 = "Tom";
Java语法中变量在使用前进行初始化就可以,但Scala必须显示进行初始化操作
可变变量
1 |
|
不可变变量
1 |
|
标识符
概念
Scala中的标识符可以用于声明运算符
1
2
3
4
5val + = "zhangsan"
val @@ = "zhangsan"
val :: = "zhangsan"
// 颜文字
val :-> = "lisi"在编译时,将特殊符号编译为转换的变量名,这个变量名以
$
开头的一般情况下,标识符起名时不能使用
$
开头可以用反引号使用关键字
1
2println(`private`)
Thread.`yield`()
字符串
概念
- 在Scala中,字符串的类型实际上就是Java中的String类
拼接
方式1:字符串拼接,
+
1
println("name: " + name)
方式2:传值字符串,
%s
1
printf("name: %s\n", name)
方式3:插值字符串,
s"$”
1
2println(s"name: $name")
println(s"name: ${name.substring(0)}")
多行字符串
默认竖线代表顶格符,可通过
stripMargin('')
改变1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22val str1 =
"""
#Hello
#Scala
#""".stripMargin('#')
println(str1)
val sql =
"""
| select
| id
| from
| (
| select
| *
| from t_user
| where id = 1
| order by id desc
|
| ) a
| group by id
|
|""".stripMargin
输入输出
控制台
read
:控制台读取1
val str: String = StdIn.readLine();
文件
数据源
- 绝对路径:不可改变的路径
- 本地路径:file:///c:/test/test.txt
- 网络路径:http://www.xxx.com
- 相对路径:可以改变的路径,一定存在一个基准路径
- IDEA中基准路径为项目的根路径
Scala进行文件读写操作,用的都是Java中的I/O类
1
2
3
4
5
6
7
8
9
10val source: BufferedSource = Source.fromFile("data/world.txt")
val strings: Iterator[String] = source.getLines()
while (strings.hasNext) {
println(strings.next())
}
source.close()
val writer = new PrintWriter(new File("data/test.txt"))
writer.write("Hello World!")
writer.close()
网络
C/S模拟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39object SlaverObject {
def main(args: Array[String]): Unit = {
// 启动服务器
val server = new ServerSocket(9999)
println("server started")
// 接收客户端的连接请求,会阻塞
val client = server.accept()
println("connected")
// 接收信息
val in = new ObjectInputStream(client.getInputStream)
val user = in.readObject()
println(s"get message from client: $user")
// 关闭流
in.close()
client.close()
server.close()
}
}
object MasterObject {
def main(args: Array[String]): Unit = {
// 连接服务器
val client = new Socket("localhost", 9999)
// 发送数据
val out = new ObjectOutputStream(client.getOutputStream)
val user = new User("Tom", 23)
out.writeObject(user)
println(s"send message to server: $user")
// 关闭流
out.flush()
out.close()
client.close()
}
}
数据类型
概念
- Scala是完全面向对象的语言,所以不存在基本数据类型的概念,有的只是任意值对象类型
AnyVal
和任意引用对象类型AnyRef
继承关系
graph TD
Any --> AnyVal
Any --> AnyRef
AnyVal --> Double --> Nothing
AnyVal --> Float --> Nothing
AnyVal --> Long --> Nothing
AnyVal --> Int --> Nothing
Double -..-> Float -..-> Long -..-> Int
Int -..-> Short -..-> Byte
Int -..-> Char
AnyVal --> Short --> Nothing
AnyVal --> Byte --> Nothing
AnyVal --> Boolean --> Nothing
AnyVal --> Char --> Nothing
AnyVal --> StringOps --> Nothing
AnyVal --> Unit --> Nothing
AnyRef --> ScalaCollections --> Null
AnyRef --> AllJavaClasses --> Null
AnyRef --> OtherScalaClasses --> Null
Null --> Nothing
Any:通用类型
1
var a: Any = "123"
Nothing:用于统一方法的异常和正常的返回
1
2
3def test(): Nothing = {
throw new Exception()
}Unit:Unit是一个类型,这个类型只有一个对象,打印就是小括号
1
2val u: Unit = testUnit()
def testUnit(): Unit = {}(Scala collections):Scala集合类型
1
val list: AnyRef = List(1, 2, 3, 4)
Null:在scala中是一个类型,只有一个对象,就是null
1
2val n = null
val user: User = null(other Scala classes):Scala其他类型
1
val obj1: AnyRef = DataTypeDemo
(all Java classes):Java所有类型
1
val obj: AnyRef = new User()
隐式转换
编译器将类型进行了转换,所以可以编译通过,这个转换开发人员看不见,将这样的转换称之为隐式转换
常量的运算在编译期
1
val aChar: Char = 'a' + 1
变量的运算在运行时计算,类型转换错误会报
1
2
3// 进行加法运算时byte会提升成int,int不能转为char
val aByte: Byte = 'a'
val aChar: Char = aByte + 1所有的类型都可以转换为字符串
1
2val b1 : Byte = 20
println(b1.toString)字符串也可以转换为数值
1
2val s = "123"
val i1 = s.toInt
运算符
关系运算
==
Scala语法中双等号就是比较对象的内容,是非空
equals
操作,和equals
不一样1
2
3
4
5
6
7val s1 = new String("abc")
val s2 = new String("abc")
// s1 = null, s2 != null:false
// s1 = null, s2 = null:true
println(s1 == s2)
// s1 = null:报错
println(s1.equals(s2)eq
方法在编译后就是Java中的双等号,对引用类型来说就是地址的比较1
println(s1.eq(s2))
1 |
|
赋值运算
+=
++
运算有歧义,容易理解出现错误,所以scala中没有这样的语法,所以采用+=
的方式来代替
运算符本质
概念
在Scala中其实是没有运算符的,所有运算符都是方法(可以自定义方法)
1
2// abcabc
val a = "abc" * 2Scala是一个完全面向对象的语言,数字也是对象
- 方法在调用时,可以省略点
- 方法如果参数只有一个或没有,那么小括号可以省略
流程控制
分支控制
概念
没有三元运算符,使用if分支判断替代
表达式都有返回值,返回结果为表达中满足条件的最后一行代码的执行结果
1
2
3
4
5
6
7val res = if (age == 30) {
30
} else {
null
}
// 30
println(res)
循环控制
使用方法
方法1:
START to END by STEP
1
2
3
4
5
6
7
8
9// for (int i = 1; i <= 5; i++)
// 1 to 5 = 1.to(5)
// for (i: Int <- 1.to(5))
for (i <- 1 to 5) {
println(i)
}
// for (int i = 1; i <= 5; i + 2)
for (i <- 1 to 5 by 2) {
}方法2:
Range(START, (END + 1), STEP)
(不包含参数2)1
2
3val range = Range(1, 5, 2)
for (i <- Range(1, 5, 2)) {}
for (i <- Range(5, 1, -1)) {}方法3:
START until (END + 1) by STEP
- 与Range效果相同
循环守卫
- 满足条件才会进入循环体
1 |
|
循环嵌套
1 |
|
引入变量
1 |
|
返回值
返回的是集合类
yield:每一次的循环结果保存下来
1
2
3
4
5val res = for (i <- Range(1, 5)) yield {
i
}
// Vector(1,2,3,4)
println(res)
跳出循环
没有continue/break关键字,采用面向对象的方式来代替
采用抛出异常的方式来跳出循环,需使用
Breaks.breakable()
捕捉异常使其不抛出异常1
2
3
4
5
6
7
8Breaks.breakable(){
for (i <- Range(1, 5)) {
if (i == 3) {
Breaks.break()
}
}
}
println("there")
函数式编程
定义
方法
- 方法就是函数
- 功能的封装,强调功能的从属关系
- 类中声明的函数叫方法
- 可以重写重载
函数
- 功的封装,不强调功能的从属关系
- 在方法或函数中
- 函数作用域比较窄
- 编译后,函数会被编译为一个新的方法
private static final Unit test$1()
- 不可以重写重载
- 能嵌套
- 如果函数名称和方法名称相同,那么调用时默认为函数调用
声明方法
def 函数名(参数1:参数类型1, 参数2:参数类型2):函数返回值类型 = {函数体}
参数
可变参数
- 可变参数在使用时都是集合对象
- 可变参数和参数默认值是不能联合声明
- 参数类型后面增加星号
1 |
|
默认参数
- 底层就是编译为一个方法,不传参数时由编译器自动调用这个方法
- 函数的参数默认以val声明,意味着不能改
- 可变参数和参数默认值是不能联合声明
1 |
|
带名参数
- 传递参数时,增加参数的名称,用于改变传参的顺序
- 参数在传递时默认为顺序匹配,可以通过特殊的语法改变传值的顺序
1 |
|
至简原则
概念
函数体会将满足条件的最后一行的代码的执行结果作为函数的返回值
1
2
3def fun(): String = {
return "lisi"
}如果函数返回数据,那么可以推断出返回值类型的话,返回值类型可以省略
1
2
3def fun() = {
return "lisi"
}如果函数体的逻辑代码只有一行的,那么大括号可以省略
1
def fun() = "lisi"
如果函数的参数列表中没有声明任何的参数,那么参数列表可以省略
1
def fun = "lisi"
当函数省略参数列表的声明时,调用这个函数不能增加小括号
1
println(fun)
如果函数明确声明为Unit,那么函数体中的return关键字不会被返回
如果函数体中使用return返回结果,那么一定要声明返回值类型
1
2
3
4def fun(): Unit = {
// 不会报错
return "zhangsan"
}如果希望省略Unit,但同时又不希望函数体中的return起作用,那么可以将等号同时省略,称为过程函数
1
2
3def fun() {
return "zhangsan"
}关键字def和函数名也可以省略,称之为匿名函数,匿名函数不能独立使用
函数返回
作为值
函数也是对象也有对象类型
若将函数作为整体,而不是执行结果赋值给变量,那么需要采用特殊符号:下划线
1
2
3
4def fun(): Unit = {
println("this is function1")
}
val val = fun _函数独立使用时,参数声明没有个数限制
将函数作为对象给别人使用,那么函数的参数声明的个数最多为22个
调用方式1
1
2// 0为参数个数
val val1: Function0[Unit] = fun1调用方式2
1
2
3val val3: () => Unit = fun1
val val4: String => Unit = fun2
val val5: (String, String) => Unit = fun3
之所以使用下划线让函数作为对象使用,因为代码中没有明确变量的类型,所以需要通过取值类推断,如果变量声明的类型为函数类型,那么可以不使用下划线让函数作为对象
作为参数
匿名函数
如果函数体的逻辑代码只有一行,大括号可以省略,代码写在一行中
如果参数的类型可以推断出来,那么参数类型可以省略的
1
fun5((x, y) => x + y)
如果参数只有一个的话,参数列表的小括号可以省略
如果参数在使用时,按照顺序只使用了一次,那么可以使用下划线代替参数
1
fun5(_ + _)
使用匿名函数时,给定的参数直接放回,不能使用下划线代替,必须完整
作为返回值
- 一般应用于将内部的函数在外部使用,这种方式不推荐自己定义类型
1 |
|
抽象控制
- 参数类型不完整,那么在传递参数时,也是不完整:只有传递代码就可以,不需要完整的声明
- 可以采用控制抽象设计语法
1 |
|
闭包
- 概念
- 将当前的代码形成了一个闭合的环境,这个环境称之为闭包环境,简称为闭包
- 一个函数使用了外部的变量,把这个变量包含到了它的内部来使用,改变了这个变量的生命周期
- 版本区别
- Scala2.12版本前闭包功能采用的是匿名函数类型实现
- Scala2.12版本闭包功能采用的是更改函数声明实现
- 出现位置
- 内部函数在外部使用时会有闭包
- 将函数作为对象使用,会有闭包
- 所有的匿名函数都有闭包
1 |
|
柯里化
- 将无关的参数分离开
1 |
|
递归
- scala中要求递归函数必须明确声明返回值类型
- 函数内部调用自身
- 一定要有跳出递归的逻辑
- 递归函数在调用时传递的参数之间有关系
伪递归/尾递归
- Scala中尾递归不是真正的递归,是由编译器进行了优化形成了while循环
- Java中尾递归不会优化为while循环
- 尾递归也会出问题
1 |
|
惰性函数
- 函数结果没使用,那么这个函数就不会执行
1 |
|
面向对象编程
类
概念
- Scala中的源码可以声明多个类,而且可以声明多个公共类,名称可以和文件名不一样
包
- 可以让源码文件中多次使用package关键字
- 源码物理路径和包名没有关系
- 明确包的作用域,可以在package关键字的后面增加大括号
- 同一个源码中,子包中可以直接访问父包中的内容
- 可以将包当成对象来用,直接声明属性和方法
1 |
|
1 |
|
导入
Java中的
import
功能比较单一(导入其他包中的类、静态导入),但是不能省略马丁想赋予import更多的功能星号在Scala中有特殊用于,所以不能使用在
import
语法中,需要采用特殊符号:下划线1
import java.util._
import
关键字可以在任何地方使用1
2
3
4
5object ImportDemo {
def main(args: Array[String]): Unit = {
import java.util.Date
}
}可以在一行中导入同一个包中多个类
1
import java.util.{ArrayList, List, LinkedList}
导包
1
2import java.util
new util.ArrayList()屏蔽/隐藏类
1
2
3import java.util._
// java.sql.Data被隐藏
import java.sql.{Date => _, _}Scala中导入类的操作,是以相对路径(当前包路径)的方式导入的,如果想要使用绝对路径的方式,那么需要增加特殊操作:
_root_
1
println(new _root_.java.util.HashMap())
给类起别名
1
2import java.util.{HashMap => JavaHashMap}
println(new JavaHashMap())
属性
属性就是类中的变量,在编译时,编译器会将变量编译为类的(私有的)属性,同时提供了属性对应的
set/get
(不遵循bean规范)方法1
2// private String name = "Tom";
var name: String = "Tom"- 给类的属性赋值,等同于调用对象属性的
set
方法 - 访问类的属性时,等同于调用对象属性的
get
方法
- 给类的属性赋值,等同于调用对象属性的
val
声明的属性,在编译时,会给属性添加final
关键字,编译器不会提供属性的set
方法1
2// private final int age = 20;
val age: Int = 20使用
private
会使属性所对应的get/set
方法变为私有的1
private var national: String = "China"
变量必须显示地初始化,若希望类的属性和Java一样可以由系统进行初始化可以采用特殊符号:
_
1
var city: String = _
Scala中给属性提供的
set/get
方法不遵循Java Bean规范,使用@BeanProperty
注解会添加针对该属性的set/get
方法1
@BeanProperty var email: String = _
访问权限
- Java
private
:同类(default)
:同类,同包protected
:同类,同包,子类(调用者,父类信息在编译时加载到子类(单线继承))public
:任意
- Scala
private
:同类private[包名]
:包私有,同包protected
:受保护的,同类,子类,没有同包(default)
:什么都不写就是公共的。没有public关键字
方法
所谓的方法,其实就是类中声明的函数,属于类的一部分
常用方法:使用预先声明好的方法
java.lang
这个包中的类在Java使用时,不需要显示导入使用- Scala中也存在同样的操作
java.lang
scala
Predef
重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21object OverloadDemo {
def main(args: Array[String]): Unit = {
val aClass: ParentClass = new ChildClass
// this is parent
printClass(aClass)
}
def printClass(parentClass: ParentClass): Unit ={
println("this is parent")
}
def printClass(childClass: ChildClass): Unit = {
println("this is child")
}
class ParentClass {}
class ChildClass extends ParentClass {}
}重写
- 如何区分父类,子类中相同的方法,需要采用TODO 动态绑定机制
- 在调用对象的成员方法过程中,将方法和对象的实际内存进行绑定,然后调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class OverrideDemo {
public static void main(String[] args) {
ParentClass aClass = new ChildClass();
// 20
System.out.println(aClass.sum());
}
}
class ParentClass {
private int num = 10;
public int sum() {
return num + 10;
}
}
class ChildClass extends ParentClass {
private int num = 20;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class OverrideDemo {
public static void main(String[] args) {
ParentClass aClass = new ChildClass();
// 30
System.out.println(aClass.sum());
}
}
class ParentClass {
private int num = 10;
public int sum() {
return getNum() + 10;
}
public int getNum() {
return num;
}
}
class ChildClass extends ParentClass {
private int num = 20;
@Override
public int getNum() {
return num;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class OverrideDemo {
public static void main(String[] args) {
ParentClass aClass = new ChildClass();
// 20
// ParentClass.sum()
System.out.println(aClass.sum());
}
}
class ParentClass {
private static int num = 10;
public static int sum() {
return num + 10;
}
}
class ChildClass extends ParentClass {
private static int num = 20;
public static int sum() {
return num + 20;
}
}
对象
创建方法
- 反射
- new
- 反序列化
- clone
构造
Java中的构造方法
- 提供无参,公共的构造方法
- 构造方法可以重载的
- 构造方法可以互相调用
- 必须显示调用父类有参的构造方法
- 构造方法的名称应该和类型一致
Scala中的构造方法
- 提供无参,公共的构造方法
- Scala中构造方法的名称和类名不一致
Scala是一个完全面向对象的语言,又是一个完全面向函数的语言,所以类也是一个函数:声明一个类就等同于声明一个函数
类名的后面可以声明小括号,表示构造参数列表
- 如果提供了类的构造方法,那么JVM不会再给类提供无参的构造方法
- 之所以在类名后面提供构造方法,主要目的就是为了类的初始化
1
2
3
4class User() {
// 类的初始化
// 构造方法体和类的主题内容
}主构造方法
- 用于完成类的初始化操作
辅助构造方法
- 在类初始化完成后,做一些辅助功能
- 辅助构造方法的名字是this关键字,其他和普通方法一样
- 辅助构造方法执行前,应该首先调用主构造方法完成类的初始化
- 辅助构造方法可以重载,并可以互相调用,调用的辅助构造方法应该提前声明
- 在构造参数前使用var或val声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// this.name = name
class Child(/*主构造方法*/var childName: String) extends Parent(childName)/*父类的有参构造初始化*/ {
println("child load behind parent")
// 辅助构造方法
def this() = {
// 调用主构造方法
this("Tom")
println("this()")
}
// 辅助构造方法
def this(age: Int) = {
// 调用前面声明的辅助构造方法
this()
println(s"this($age)")
}
}
class Parent(var parentName: String) {
println("parent load first")
}私有构造:声明一个公共的,静态的,返回本类型的方法,用于获取对象
1
2
3
4
5
6
7class PrivateClass private() {}
object PrivateClass {
def getInstance(): PrivateClass = {
new PrivateClass
}
}
伴生类/对象
Scala中没有静态语法,但是可以直接使用Java中的静态操作
Scala采用了一种特殊的处理方式来代替静态语法:
object
object
关键字可以用于创建对象,对象的名字就是声明的名字
class
和object
的关系- 使用object关键字声明的类和对象有关系,这个对象等同于伴随着这个类创建时所产生的,所以将这个对象称之为伴生对象,这个类称之为伴生类
- 伴生对象就是一个对象,可以访问伴生类中的所有东西,包括私有
- 伴生对象其实就是马丁模拟静态语法所产生的
一般将静态语法操作的代码写在伴生对象中,将成员方法或属性写在伴生类中
伴生对象就是单例的,伴生对象只需要声明即可,无需构建,所以不需要构造参数列表
- 单例模式存在一个问题:创建的对象不会被回收,需要显示地回收(设置为null)
- 如果伴生对象中构建对象的方法名成为apply,编译器可自动识别,方法名可省略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 调用类的构造方法
val user = new User()
// 调用伴生对象apply方法
val singletonDemo1 = SingletonDemo.apply()
val singletonDemo2 = SingletonDemo()
// 伴生对象本体
val singletonDemo3 = SingletonDemo
class SingletonDemo private {}
object SingletonDemo {
def apply() = {
new SingletonDemo
}
}
抽象
- 抽象类
- 抽象类没有办法直接实例化,需要由子类继承后完成实例化操作
- 子类继承抽象类后,可以声明为抽象类,也可以将父类的抽象方法补充完整
- 抽象属性
- 底层实现时其实就是抽象方法
- 抽象属性:编译时不会在类中声明属性,而是会声明属性抽象的set/get方法
- 重写属性:编译时会在类中声明私有属性,同时提供属性公共的set/get方法
- 重写
- 抽象属性:补充完整
- 完整属性:需添加
override
关键字
- 底层实现时其实就是抽象方法
- 抽象方法
- Scala中不完整的方法就是抽象,所以无需增加
abstract
关键字 - 重写
- 抽象方法:补充完整
- 完整方法:需添加
override
关键字 - 推荐只要重写都添加
override
- Scala中不完整的方法就是抽象,所以无需增加
1 |
|
特质/特质
概念
- 将多个对象中相同的特征从对象中剥离出来形成独立的一个结构,称之为
trait
(特征 - 如果一个对象符合这个特征,那么可以将这个特征加入到这个对象,这个加入的过程,称之为混入(
extends
) - 可以将Trait理解为接口和抽象类的结合体(Scala没有接口)
- 继承方式
- 1特质:采用
extends
关键字 - 多特质:第一个特质采用
extends
,后续采用with
- 存在父类和特质:使用
extends
关键字继承父类,使用with
关键字来混入特征
- 1特质:采用
- 初始化顺序
- 父类的特质
- 父类
- 特质1
- 特质2
- 当前类
1 |
|
动态混入
1 |
|
1 |
|
动态叠加
Java中不能类的多继承 : 砖石问题
Scala采用了一种功能叠加的方式解决砖石问题
super
不是父特质的意思,是上一级(上一个)的意思(super
在编译时起作用)1
2
3
4
5// 初始化顺序: Operator→Log→Database
// 调用顺序
// 用户→[DataBase.opData()→[Log.opData()→[Operator.opData()[MySQL]]]]
// 向数据库中向日志文件中操作数据
new MySQL().opData()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20trait Operator {
def opData() = println("操作数据")
}
trait DataBase extends Operator {
override def opData(): Unit = {
print("向数据库中")
super.opData()
}
}
trait Log extends Operator {
override def opData(): Unit = {
print("向日志文件中")
super.opData()
}
}
class MySQL extends Log with DataBase {
}可以使用
spuer[特质]
指定某个特质1
2// 向数据库中操作数据
new MySQL().opData()1
2
3
4
5
6trait DataBase extends Operator {
override def opData(): Unit = {
print("向数据库中")
super[Operator].opData()
}
}
扩展
反射
- 字符串底层数组长度不可变,数组中的内容可变
- 字符串工具类没提供改变数组中内容值的方法,需使用反射修改
1 |
|
枚举
1 |
|
应用类
1 |
|
1 |
|
定义别名
1 |
|
集合
概念
- Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质
- 对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本,一般可以根据集合所在包名进行区分
- Scala默认提供的集合都是不可变
通用方法
基础操作
1 |
|
集合操作
1 |
|
运算
1 |
|
聚合
1 |
|
功能函数
1 |
|
数组
概念
- 严格意义上数组不是集合,Scala中给数组一个特定的类型:Array
- Scala默认提供的集合都是不可变
不可变
1 |
|
多维
1 |
|
可变
1 |
|
转换
1 |
|
WordCount案例
1 |
|
1 |
|
Seq
概念
- 数据有序,可以放重复数据
- 一般会从采用List
不可变
1 |
|
可变
1 |
|
转换
1 |
|
Set
1 |
|
Map
1 |
|
Tuple
1 |
|
并行
1 |
|
1 |
|
模式匹配
概念
- Scala中的模式匹配类似于Java中的
switch
语法(Scala没有switch
) - Scala从语法中补充了更多的功能,可按照指定的规则对数据或对象进行匹配
- 当数据满足某一个分支时,执行完毕后就直接跳出
case _
分支类似于default语言,分支匹配其实就是顺序匹配- 如果数据没有匹配任何规则,会发生错误
1 |
|
匹配规则
匹配常量
1 |
|
匹配类型
- 类型前增加变量名称,这个变量就是将数据转换成指定类型的变量
- 如果想要使用下划线代表的数据,可以给下划线起名来使用
- Scala中类型匹配时不考虑泛型,
Array
类除外
1 |
|
匹配数组
- 判断给定的数组的规则,对一个数组集合进行遍历
1 |
|
匹配列表
1 |
|
1 |
|
匹配元组
1 |
|
匹配对象
1 |
|
普通类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class User {
var name: String = _
var age: Int = _
}
object User {
// apply: Attribute => Object
def apply(name: String, age: Int) = {
val user = new User
user.name = name
user.age = age
user
}
// unapply: Object => Attribute
def unapply(user: User): Option[(String, Int)] = {
Option((user.name, user.age))
}
}样例类
- 如果在类的前面的增加
case
关键字,这个类专门用于模式匹配称之为样例类 - 在编译时,会自动生成/增加/重写了大量的方法
- 样例类会自动实现可序列化接口
- 样例类的构造参数直接能够作为属性使用,(不能修改,如果想要修改,需要将参数使用var声明)
- 样例类自动生成伴生对象,而且其中自动声明了
apply
/unapply
1
case class User(var name: String, age: Int)
- 如果在类的前面的增加
偏函数
全量函数:函数进行处理时必须对所有的数据进行处理
1
2
3
4
5
6
7
8
9
10val list2 = List(1, 2, 3, 4)
// map只支持全量函数操作
// 2 () 6 ()
val list3 = list2.map(
num => {
if (num % 2 != 0) {
num * 2
}
}
)偏函数: 函数进行处理时只对满足条件的数据进行处理
1 |
|
应用
1 |
|
异常
概念
- 异常分类
- 编译时异常:编译器为了程序的健壮性,提示开发者,代码是会有问题的,应该想办法针对于不同的问题来解决
- 运行时异常
- Scala中异常没有分类,无需显示抛出方法异常,没有
throws
关键字 - 如果Java程序调用Scala代码,Scala需要使用
@throws[Exception]
使Java明确异常
Java异常
1 |
|
Scala异常
1 |
|
隐式转换
概念
- 如果程序编译出错,编译器会尝试在整个的作用域中查找能够让程序编译通过的方式
- 如果找到,编译器会尝试二次编译,让之前编译出现错误的代码经过转换后能够编译通过
- 这个转换过程看不见但是存在,称为隐式转换
- 所谓的隐式转换其实就是类型的转换
隐式函数
- 同一作用域中不能有多个相同转换规则
1 |
|
1 |
|
隐式参数&变量
- 隐式参数不用传递,这个传递的过程由编译器完成
- 在同一个作用域中,如果相同的转换规则的多个数据,会发生错误
1 |
|
隐式类
- 所带的构造参数有且只能有一个
- 不能为顶级对象
1 |
|
作用范围
- 当前代码作用域
- 父类或伴生对象
- 特征或伴生对象
- 在其他地方声明(包对象)
- 直接导入
1 |
|
1 |
|
泛型
概念
- 泛型和类型的区别
- 类型对外部的数据做约束,泛型对内部的数据做约束
- 泛型和类型的层次不一样,不能作为整体来考虑
- 泛型在某些场合中,其实就是类型参数,用于向类中传递参数
- 泛型其实只在编译时有效, 将这个操作称之为“泛型擦除“
- 为了使用方便,可以定义泛型的边界
泛型转换
概念
- 将类型和泛型当成一个整体来使用
协变
- 将类型和泛型联合使用,类型相同时如果泛型存在父子类关系,那么联合的类型也就存在父子类关系
+T
1 |
|
逆变
- 类型相同,泛型之间存在父子关系,那么让联合后的类型存在子父关系
-T
1 |
|
泛型边界
上限
_ >: T
1 |
|
下限
_ <: T
1 |
|
上下文限定
1 |
|
正则表达式
- 模式匹配匹配的是规则:类型,常量,元组,集合,数组,对象,参数
- 正则表达式也是匹配规则:String
1 |
|