1. Scala介绍
Scala是Scalable Language 的简写,是一门多范式的编程语言,最早由联邦理工学院洛桑(EPFL)的Martin Odersky于2001年开始设计。
1.2 Scala的特点
- 面向对象:每个值都是对象。对象的数据类型以及行为由类和特质描述。类抽象机制的扩展有两种途径:一种途径是子类继承,另一种途径是灵活的混入机制。
- 函数式编程:其函数也能当成值来使用。
- 静态类型。
- 扩展性。
- 并发性。
1.3 Scala的基本语法
- 区分大小写:Scala是大小写敏感的,这意味着标识Hello 和 hello在Scala中会有不同的含义。
- 类名:于所有的类名的第一个字母要大写。如果需要使用几个单词来构成一个类的名称,每个单词的第一个字母要大写。如:class MyFirstScalaClass。
- 方法名称:所有的方法名称的第一个字母用小写。如果若干单词被用于构成方法的名称,则每个单词的第一个字母应大写。示例:def myMethodName()。
- 程序文件名:程序文件的名称应该与对象名称完全匹配。保存文件时,应该保存它使用的对象名称(记住Scala是区分大小写),并追加".scala"为文件扩展名。
2. Scala的变量和数据类型
2.1 Scala中变量的定义
Scala有两种变量的定义格式:
var 变量名 [:数据类型] = 初始值 var i : Int = 8
val 变量名 [:数据类型] = 初始值 val j : Int = 10
- 声明变量时,类型可以省略,编译器自动推导,即类型推导
- 类型确定后,就不能修改,说明Scala是强类型语言
- 变量声明时,必须要有初始值
- 在声明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可以改变,val修饰的变量不可改变。
2.2 Scala中标识符的命名规则
- 以字母或者下划线为开头,后边可以接字母、数字和下划线;
- 以操作符开头,且只包含操作符(+ - * / ! 等);
- 用反引号‘’包含的任一字符串,即使是Scala中的关键字也可以(39个)。
Scala中的关键字如下:
abstract | case | catch | class |
def | do | else | extends |
false | final | finally | for |
forSome | if | implicit | import |
lazy | match | new | null |
object | override | package | private |
protected | return | sealed | super |
this | throw | trait | try |
true | type | val | var |
while | with | yield | |
- | : | = | => |
<- | <: | <% | >: |
# | @ |
2.3 Scala中的字符串
创建字符串:
var greeting = "Hello World!";
或
var greeting:String = "Hello World!";
1)字符串可以通过+号连接
val name: String = "alice"
val age: Int = 18
println(age + "岁的" + name + "在学校")
2)*号用于将一个字符串赋值多次并拼接
println(name * 3)
3)printf用法:字符串,通过%拼接
printf("%d岁的%s在学校",age,name)
4)字符串模板(插值字符串),通过$获取变量值
println(S"${age}岁的${name}在学校")
2.4 Scala的键盘输入
编程中,使用键盘输入语句来获取用户输入的数据。
//输入信息
println("请输入姓名")
val name: String = StdIn.readLine()
println("请输入年龄")
val age: Int = StdIn.readInt()
//输出信息
println(S"欢迎${age}岁的${name}")
2.5 Scala的文件读取与写入
1) 从文件中读取数据
Source.fromFile("test.txt").foreach(print)
//从test.txt文件中读取数据并打印出来
2)将数据写入文件
val writer = new PrintWriter(new File("test.txt"))
writer.write("测试输入")
writer.close()
//结果将“测试输入”四个字写入到test.txt文件中
2.6 Scala的数据类型
Scala中的所有数据类型都是对象:
数据类型 | 描述 |
---|---|
Byte | 8位有符号补码整数。数值区间为 -128 到 127 |
Short | 16位有符号补码整数。数值区间为 -32768 到 32767 |
Int | 32位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
Long | 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 |
Float | 32 位, IEEE 754 标准的单精度浮点数 |
Double | 64 位 IEEE 754 标准的双精度浮点数 |
Char | 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF |
String | 字符序列 |
Boolean | true或false |
Unit | 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null | null 或空引用 |
Nothing | Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。 |
Any | Any是所有其他类的超类 |
AnyRef | AnyRef类是Scala里所有引用类(reference class)的基类 |
Scala中的常见转义字符
转义字符 | Unicode | 描述 |
---|---|---|
\b | \u0008 | 退格(BS) ,将当前位置移到前一列 |
\t | \u0009 | 水平制表(HT) (跳到下一个TAB位置) |
\n | \u000a | 换行(LF) ,将当前位置移到下一行开头 |
\f | \u000c | 换页(FF),将当前位置移到下页开头 |
\r | \u000d | 回车(CR) ,将当前位置移到本行开头 |
\" | \u0022 | 代表一个双引号(")字符 |
\' | \u0027 | 代表一个单引号(')字符 |
\\ | \u005c | 代表一个反斜线字符 '\' |
此外“\”还能用于输出一些特殊字符。
1) 整型字面量:Int类型如果要表示Long,可以在数字后边添加L或小写l作为后缀。
634L
2)浮点型字面量:如果一个浮点数后面由F或f,则表示这是一个Float类型,否则就是一个Double类型。
1e30F
3)布尔型字面量:只有true和flase两种。
4)符号字面量:可以被写成: '<标识符> ,这里 <标识符> 可以是任何字母或数字的标识(注意:不能以数字开头)。这种字面量被映射成预定义类scala.Symbol的实例。如: 符号字面量 'x 是表达式 scala.Symbol("x") 的简写。
5)字符字面量:字符变量使用单引号‘’来定义。
‘a’
6)字符串字面量:字符串字面量使用双引号“”来定义。
“Hello World”
7)多行字符串表示方法:可以使用三个双引号来表示分隔符。
“““测
试
输
出”””
8)Null值:a.空值是 scala.Null 类型。b.Scala.Null和scala.Nothing是用统一的方式处理Scala面向对象类型系统的某些"边界情况"的特殊类型。c.Null类是null引用对象的类型,它是每个引用类(继承自AnyRef的类)的子类。Null不兼容值类型。
2.7 Scala的数据类型转换
2.7.1 Scala的自动数据类型转换
当Scala程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个属于自动类型转换(隐式转换)。数据类型按精度大小排序为:
- 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换为精度大的数据类型,然后再进行计算。
- 把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
- (byte,short)和char之间不会相互自动转换。
- byte,short和char之间可以计算,在计算时首先转换为int类型。
2.7.3 Scala的强制类型转换
属于自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转换函数,但可能导致精度降低或溢出,需格外注意。
var num : Int = 2.7.toInt
- 将数据由高精度转换为低精度,就需要使用到强制转换。
- 强制转换符号只针对最近的操作符有效,往往会使用小括号提升优先级。
3. Scala运算符
3.1 算术运算符
运算符 | 描述 | 实例 |
---|---|---|
+ | 加号 | A + B 运算结果为 30 |
- | 减号 | A - B 运算结果为 -10 |
* | 乘号 | A * B 运算结果为 200 |
/ | 除号 | B / A 运算结果为 2 |
% | 取余 | B % A 运算结果为 0 |
3.2 关系运算符
运算符 | 描述 | 实例 |
---|---|---|
== | 等于 | (A == B) 运算结果为 false |
!= | 不等于 | (A != B) 运算结果为 true |
> | 大于 | (A > B) 运算结果为 false |
< | 小于 | (A < B) 运算结果为 true |
>= | 大于等于 | (A >= B) 运算结果为 false |
<= | 小于等于 | (A <= B) 运算结果为 true |
3.3 逻辑运算符
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑与 | (A && B) 运算结果为 false |
|| | 逻辑或 | (A || B) 运算结果为 true |
! | 逻辑非 | !(A && B) 运算结果为 true |
3.4 位运算符
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与运算符 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 |
| | 按位或运算符 | (a | b) 输出结果 61 ,二进制解释: 0011 1101 |
^ | 按位异或运算符 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 |
~ | 按位取反运算符 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。 |
<< | 左移动运算符 | a << 2 输出结果 240 ,二进制解释: 1111 0000 |
>> | 右移动运算符 | a >> 2 输出结果 15 ,二进制解释: 0000 1111 |
>>> | 无符号右移 | A >>>2 输出结果 15, 二进制解释: 0000 1111 |
3.5 赋值运算符
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算,指定右边操作数赋值给左边的操作数。 | C = A + B 将 A + B 的运算结果赋值给 C |
+= | 相加后再赋值,将左右两边的操作数相加后再赋值给左边的操作数。 | C += A 相当于 C = C + A |
-= | 相减后再赋值,将左右两边的操作数相减后再赋值给左边的操作数。 | C -= A 相当于 C = C - A |
*= | 相乘后再赋值,将左右两边的操作数相乘后再赋值给左边的操作数。 | C *= A 相当于 C = C * A |
/= | 相除后再赋值,将左右两边的操作数相除后再赋值给左边的操作数。 | C /= A 相当于 C = C / A |
%= | 求余后再赋值,将左右两边的操作数求余后再赋值给左边的操作数。 | C %= A is equivalent to C = C % A |
<<= | 按位左移后再赋值 | C <<= 2 相当于 C = C << 2 |
>>= | 按位右移后再赋值 | C >>= 2 相当于 C = C >> 2 |
&= | 按位与运算后赋值 | C &= 2 相当于 C = C & 2 |
^= | 按位异或运算符后再赋值 | C ^= 2 相当于 C = C ^ 2 |
|= | 按位或运算后再赋值 | C |= 2 相当于 C = C | 2 |
3.6 运算符优先级
类别 | 运算符 | 关联性 |
---|---|---|
1 | () [] | 左到右 |
2 | ! ~ | 右到左 |
3 | * / % | 左到右 |
4 | + - | 左到右 |
5 | >> >>> << | 左到右 |
6 | > >= < <= | 左到右 |
7 | == != | 左到右 |
8 | & | 左到右 |
9 | ^ | 左到右 |
10 | | | 左到右 |
11 | && | 左到右 |
12 | || | 左到右 |
13 | = += -= *= /= %= >>= <<= &= ^= |= | 右到左 |
14 | , | 左到右 |
4. Scala的流程控制
4.1 IF...ELSE语句
跟其他大部分语言一样,Scala可以通过IF...ELSE语句来控制一条或多条语句的执行:
其基本语法也完全相同
if(布尔表达式 1){
// 如果布尔表达式 1 为 true 则执行该语句块
}else if(布尔表达式 2){
// 如果布尔表达式 2 为 true 则执行该语句块
}else if(布尔表达式 3){
// 如果布尔表达式 3 为 true 则执行该语句块
}else {
// 如果以上条件都为 false 执行该语句块
}
4.2 For循环
4.2.1 范围遍历
Scala中for循环用于实现范围遍历的语法为:
for( var x <- Range ){
statement(s);
}
其中,Range 可以是一个数字区间表示 i to j ,或者 i until j。左箭头 <- 用于为变量 x 赋值。
1)to语句:表示闭区间,前后闭合
var a = 0;
// for 循环
for( a <- 1 to 10){
println( "Value of a: " + a );
}
以上代码会输出a的范围为从1到9。
2)until:左闭右开
var a = 0;
// for 循环
for( a <- 1 until 10){
println( "Value of a: " + a );
}
输出范围实际为从1到9。
4.2.2 集合遍历
通过先前创建的集合,可以使用for循环对其进行遍历
var a = 0;
val numList = List(1,2,3,4,5,6);
// for 循环
for( a <- numList ){
println( "Value of a: " + a );
}
以上代码将输出1到6的数字。
4.2.3 循环守卫
可以使用一个或多个 if 语句来过滤一些元素。
var a = 0;
val numList = List(1,2,3,4,5,6,7,8,9,10);
// for 循环
for( a <- numList
if a != 3; if a < 8 ){
println( "Value of a: " + a );
}
以上代码的输出结果为1,2,4,5,6,7
4.2.4 循环步长
使用by可以设置循环步长
for (i <- 1 to 10 by 2){
println("i=" + i)
}
4.2.5 循环嵌套
可以使用分号 (;) 来设置多个区间,它将迭代给定区间所有的可能值。
var a = 0;
var b = 0;
// for 循环
for( a <- 1 to 3; b <- 1 to 3){
println( "Value of a: " + a );
println( "Value of b: " + b );
}
以上代码的执行结果为:
Value of a: 1
Value of b: 1
Value of a: 1
Value of b: 2
Value of a: 1
Value of b: 3
Value of a: 2
Value of b: 1
Value of a: 2
Value of b: 2
Value of a: 2
Value of b: 3
Value of a: 3
Value of b: 1
Value of a: 3
Value of b: 2
Value of a: 3
Value of b: 3
4.2.6 循环的返回值
可以使用yield将for循环的返回值作为一个变量存储。同时将遍历过程中的结果返回到一个新的Vector集合中。
var a = 0;
val numList = List(1,2,3,4,5,6,7,8,9,10);
// for 循环
var retVal = for{ a <- numList
if a != 3; if a < 8
}yield a
// 输出返回值
for( a <- retVal){
println( "Value of a: " + a );
}
其输出结果为
value of a: 1
value of a: 2
value of a: 4
value of a: 5
value of a: 6
value of a: 7
4.3 While循环
判断条件可以是任意的表达式,为任意非零值时都为 true。当条件为 true 时执行循环。 当条件为 false 时,退出循环,程序流将继续执行紧接着循环的下一条语句。
while(condition)
{
statement(s);
}
4.4 do...while循环
do...while 循环在循环的尾部检查它的条件,并确保循环至少执行一次。
do {
statement(s);
} while( condition );
4.5 跳出循环
Breaks.breakable(
for(i <- 0 until 5){
if(i==3)
Breaks.break()
println(i)
}
)
5. 函数式编程
Scala是一种纯粹的面向对象的函数式编程语言。面向过程指的是将要执行的任务按步骤分好后再混合执行,类似于一份蛋炒饭,而面向对象则是盖浇饭,各自完成各自的任务后简单组装即可。相比之下,面向过程编程的性能会更好,但现代软件语言讲究可维护性,则面向对象的编程方式更受欢迎。
面向过程和面向对象都属于命令式编程,每一行编程语句都对应于硬件的执行过程。函数式编程则类似于数学中的y=f(x),每一条执行语句都有返回值,通过n多个函数的求解,即可得到最终的结果。相比之下,命令式编程性能可能更高,函数式编程则更好理解。
5.1 函数的基础
5.1.1 函数与方法
- 为完成某一功能的程序语句的集合,称为函数。
- 类中的函数称之为方法。
5.1.2 函数的声明
5.1.3 不同类型函数的定义
1)无参,无返回值
def f1():Unit = {
println("1. 无参,无返回值")
}
2)无参,有返回值
def f2(): Int = {
println("2,无参,有返回值")
return 12
}
3)有参,无返回值
def f3(name: String): Unit = {
println("3.有参,无返回值" + name)
}
4)有参,有返回值
5)多参,无返回值
6)多参,有返回值
5.1.4 函数的参数
1)可变参数,如果参数列表中还存在其他参数,那么可变参数需放置在最后
Scala允许所致命函数的最后一个参数是可重复的,即不需要指定函数参数的格式,就可以向函数传入可变长度参数列表。通过在参数的类型之后放一个星号来设置可变参数(可重复的参数)。
object Test {
def main(args: Array[String]) {
printStrings("Runoob", "Scala", "Python");
}
def printStrings( args:String* ) = {
var i : Int = 0;
for( arg <- args ){
println("Arg value[" + i + "] = " + arg );
i = i + 1;
}
}
}
以上代码的输出结果为:
Arg value[0] = Runoob
Arg value[1] = Scala
Arg value[2] = Python
2)参数默认值,一般将有默认值的参数放置在参数列表的后面
Scala可以为函数指定默认参数值,使用了默认参数,在之后调用函数的过程中可以不需要传递参数,此时便会使用它的默认参数值,如果传递了参数,则会使用新传递的参数。
object Test {
def main(args: Array[String]) {
println( "返回值 : " + addInt() );
}
def addInt( a:Int=5, b:Int=7 ) : Int = {
var sum:Int = 0
sum = a + b
return sum
}
}
以上代码的输出结果为:
返回值 : 12
3)指定函数参数名
通过指定函数参数名,即可不按照顺序像函数传递参数。
object Test {
def main(args: Array[String]) {
printInt(b=5, a=7);
}
def printInt( a:Int, b:Int ) = {
println("Value of a : " + a );
println("Value of b : " + b );
}
}
以上代码的运行结果如下:
Value of a : 7
Value of b : 5
5.1.5 函数至简原则
- return可以省略,Scala会使用函数体的最后一行代码作为返回值;
- 如果函数体只有一行代码,可以省略花括号;
- 返回值类型如果能够推断出来,那么可以省略(:和返回类型一起省略);
- 如果有return,则不能省略返回值类型,必须指定;
- 如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用;
- Scala如果期望是无返回类型,可以省略等号;
- 如果函数无参,但是声明了参数列表,那么调用时,小括号可加可不加;
- 如果函数没有参数列表,那么声明小括号可以省略,调用时小括号必须省略;
- 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略(匿名函数,lambda表达式)。
5.2 匿名函数
5.2.1 匿名函数实例
没有名字的函数就是匿名函数,其一大特点是在于其可以作为参数再次进行传递。
(x:Int)=>{函数体}
其中,x表示输入参数的名字,Int表示输入参数的类型,函数体表示具体的代码逻辑。
实例:
val fun = (name: String) => {println(name)}
fun("aaa")
def f(func: String => Unit): Unit = {
func("aaa")
}
f(fun)
f( (name: String) => {println(name)} )
以上代码的输出结果为:
aaa
aaa
aaa
5.2.2 匿名函数的至简原则
1)参数的类型可以省略,会根据形参进行自动的推导
f( (name) => {
println(name)
} )
2)类型省略之后,发现只有一个参数,则圆括号也可以省略,其他情况:没有参数和参数超过1的情况不能省略圆括号
f( name => {
println(name)
} )
3)如果匿名函数只有一行,则大括号也可以省略
f( name => println(name) )
4)如果参数只出现一次,则参数省略且后面参数可以用_代替
f( println(_) )
5)如果可以推断出,当前传入的println是一个函数体,而不是调用语句,可以直接省略下划线
f( println )
5.3 高级函数应用
5.3.1 高阶函数用法
1)函数可以作为值进行传递
val f1: Int=>Int = f
val f2 = f _
2)函数可以作为参数进行传递
def dualEval(op: (Int, Int)=>Int, a: Int, b: Int): Int = {
op(a,b)
}
def add(a: Int, b: Int): Int = {
a+b
}
dualEval(add, 12 35)
以上代码的运行结果:
47
3)函数可以作为函数返回值返回
def f5(): Int=>Unit = {
def f6(a: Int): Unit = {
println("f6调用 " + a)
}
f6 //将函数直接返回
}
println(f5()(25))
以上代码的执行结果为
f6调用 25
5.3.2 函数的闭包
如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称之为闭包。
def addByA(a: Int): Int=>Int = {
def addB(b: Int): Int = {
a+b
}
addB
}
println(addByA(35)(24))
val addByFour = addByA(4)
val addByFive = addByA(5)
println(addByFour(13))
println(addByFive(25))
以上代码的输出结果
59
17
30
5.3.3 函数柯里化
把一个参数列表的多个参数,变成多个参数列表。
def add(x: Int,y: Int) = x+y
def add(x: Int)(y: Int) = x+y
上述两种表述方式是等效的,且下边属于上边函数的柯里化,是闭包的一种表现形式。该表示形式的意思是:接收一个x为参数,返回一个匿名函数,该匿名函数的定义是:接收一个Int型参数y,函数体为x+y。
object Test {
def main(args: Array[String]) {
val str1:String = "Hello, "
val str2:String = "Scala!"
println( "str1 + str2 = " + strcat(str1)(str2) )
}
def strcat(s1: String)(s2: String) = {
s1 + s2
}
}
上述代码的运行结果为:
str1 + str2 = Hello, Scala!
5.3.3 递归函数
- 方法必须调用自身
- 方法必须要有跳出的逻辑
- 方法调用自身时,传递的参数应该有规律
- Scala中的递归必须声明函数返回类型
object Test {
def main(args: Array[String]) {
for (i <- 1 to 10)
println(i + " 的阶乘为: = " + factorial(i) )
}
def factorial(n: Int): Int = {
if (n <= 1)
1
else
n * factorial(n - 1)
}
}
以上代码的运行结果为:
1 的阶乘为: = 1
2 的阶乘为: = 2
3 的阶乘为: = 6
4 的阶乘为: = 24
5 的阶乘为: = 120
6 的阶乘为: = 720
7 的阶乘为: = 5040
8 的阶乘为: = 40320
9 的阶乘为: = 362880
10 的阶乘为: = 3628800
5.3.4 控制抽象
Scala的解释器在解析函数参数时有两种方式:
1)传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部;
def f0(a: Int): Unit = {
println("a" + a)
println("a" + a)
}
2)传名参数(call-by-name):将未计算的参数表达式直接应用到函数内部,每次使用传名调用时都会计算一次表达式的值。
object Test {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("获取时间,单位为纳秒")
System.nanoTime
}
def delayed( t: => Long ) = {
println("在 delayed 方法内")
println("参数: " + t)
t
}
}
以上代码的运行结果为:
在 delayed 方法内
获取时间,单位为纳秒
参数: 241550840475831
获取时间,单位为纳秒
5.3.5 惰性加载
当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取执,该函数才会执行。称这种函数为惰性函数。
lazy val result: Int = sum(13,47)
println("1.函数调用")
println("2.result = " + result)
def sum(a: Int, b: Int): Int = {
println("3. sum调用")
a + b
}
以上代码的执行结果为:
1. 函数调用
3. sum调用
2. result = 60
6. 面向对象
6.1 Scala包
1)基本语法:package 包名
2)包的三大作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
6.1.1 包的命名
1)命名规则:只能包含数字、字母、下划线、小圆点,但不能用数字开头,也不要使用关键字。
2)命名规范
一般是小写字母+小圆点
6.1.2 包的说明
Scala有两种包的管理风格:
1)和Java相同,每个源文件一个包(包名和源文件所在路径不要求必须一直),包名用“.”进行分割表示层级关系;
2)通过嵌套的风格表示层级关系,如下:
package p1{
package p2{
package p3{
}
}
}
第二种风格有一下特点:
- 一个源文件中可以声明多个package
- 子包中的类可以直接访问父包中的内容,而无需导包
6.1.3 包对象
在Scala中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有class和object的共享变量,可以被直接访问。
package object com{
val shareValue = "share"
def shareMethond() = {}
}
6.1.4 导包说明
- 和Java一样,可以在顶部使用import导入,在这个文件中的所有类都可以使用
- 局部导入:什么时候使用,什么时候导入,在其作用范围内都可以使用
- 通配符导入:import java.util._
- 给类起名:import java.util.{ArrayList=>JL}\
- 导入相同包的多个类:import java.util.{HashSet, ArrayList}
- 屏蔽类:imort java.util.{ArrayList = >_,_}
- 导入包的绝对路径: new _root_.java.util.HashMap
import com.atguigu.Fruit | 引入 com.atguigu 包下 Fruit (class 和 object) |
import com.atguigu._ | 引入 com.atguigu 下的所有成员 |
import com.atguigu.Fruit._ | 引入 Fruit(object)的所有成员 |
import com.atguigu. {Fruit,Vegetable} | 引入com.atguigu下的Fruit和Vegetable |
import com.atguigu. {Fruit=>Shuiguo} | 引入com.atguigu包下的Fruit并更名为Shuiguo |
import com.atguigu. {Fruit=>Shuiguo,_} | 引入com.atguigu包下的所有成员,并将Fruit 更名为Shuiguo |
import com.atguigu. {Fruit=>_,_} | 引入 com.atguigu 包下屏蔽 Fruit 类 |
new_root_java.util.HashMap | 引入的Java的绝对路径 |
Scala中默认已经导入的三个包:
- import java.lang._
- import acala._
- import scala.Predef._
6.2 类和对象
类:可以看作是一个模板
对象:用于表示具体的事务
6.2.1 类的定义
类是抽象的,不占用内存,是用于创建对象的蓝图,是一个定义包括在特定类型的对象中的方法和变量的软件模板。其定义模板为:
[修饰符] class 类名{
类体
}
此外,Scala中的类不声明为public,其默认即为public,一个Scala源文件中可以有多个类。
class Point(xc: Int, yc: Int) {
var x: Int = xc
var y: Int = yc
def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
println ("x 的坐标点: " + x);
println ("y 的坐标点: " + y);
}
}
以上实例中定义了两个变量x和y,一个没有返回值的方法move,xc和yc称为类参数,在整个类中都可以访问。
6.2.2 对象的定义
对象是具体的,占用存储空间。可以使用new来实例化类,并访问类中的方法和变量。
class Point(xc: Int, yc: Int) {
var x: Int = xc
var y: Int = yc
def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
println ("x 的坐标点: " + x);
println ("y 的坐标点: " + y);
}
}
object Test {
def main(args: Array[String]) {
val pt = new Point(10, 20);
// 移到一个新的位置
pt.move(10, 10);
}
}
上述代码的运行结果为:
x 的坐标点: 20
y 的坐标点: 30
6.2.3 访问修饰符
Scala中的访问权限分别为public、private和protected。
- Scala中实际上没有public关键字,如果未指定任何修饰符,则默认为public,这样的成员在任何地方都可以被访问。
- private为私有权限,只有在类的内部和伴生对象中可用
- protected为受保护权限,同类,子类中可以访问,同包无法访问
- private[包名]:增加包访问权限,包名下的其他类也可以使用
6.2.4 构建方法
def 方法名(参数列表)[:返回值类型]={
方法体
}
6.2.5 创建对象
val | var 对象名 [:类型] = new 类型()
6.2.6 构造器
Scala中的构造器包括:主构造器和辅助构造器
class 类名(形参列表){ //主构造器
//类体
def this(形参列表){ //辅助构造器
}
def this(形参列表){ //辅助构造器可以有多个...
}
}
- 辅助构造器函数名称必须为this,可以有多个,编译器通过参数的个数及类型来区分
- 辅助构造方法不能直接构建对象,必须直接或间接调用主构造方法
- 构造器调用其他另外的构造器,要求被调用构造器必须提前声明
6.2.7 构造器函数
Scala类的主构造器函数的形参包括三种类型:未使用任何修饰、var修饰、val修饰
- 未使用任何修饰符修饰,这个参数就是一个局部变量
- var修饰参数,作为类的成员属性使用,可以修改
- val修饰参数,作为类只读属性使用,不能修改
6.3 封装、继承和多态
6.3.1 封装
封装就是把抽象出来的数据和对数据的操作封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(成员方法),才能对数据进行操作。
Scala中的public属性,底层实际上为private,并通过get方法和set方法对其进行操作,所以Scala并不推荐将属性设置为private,再为其设置public的get和set方法的做法。
6.3.2 继承
基本语法
class 子类名 extends 父类名 {类体}
- 子类继承父类的属性和方法
- Scala是单继承
import java.io._
class Point(val xc: Int, val yc: Int) {
var x: Int = xc
var y: Int = yc
def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
println ("x 的坐标点 : " + x);
println ("y 的坐标点 : " + y);
}
}
class Location(override val xc: Int, override val yc: Int,
val zc :Int) extends Point(xc, yc){
var z: Int = zc
def move(dx: Int, dy: Int, dz: Int) {
x = x + dx
y = y + dy
z = z + dz
println ("x 的坐标点 : " + x);
println ("y 的坐标点 : " + y);
println ("z 的坐标点 : " + z);
}
}
object Test {
def main(args: Array[String]) {
val loc = new Location(10, 20, 15);
// 移到一个新的位置
loc.move(10, 10, 5);
}
}
以上代码的输出结果:
x 的坐标点 : 20
y 的坐标点 : 30
z 的坐标点 : 20
- 重写一个非抽象方法必须使用override修饰符。
- 只有主构造函数才可以往基类的构造函数里写参数。
- 在子类中重写超类的抽象方法时,你不需要使用override关键字。
6.3.3 多态
父类指针指向子类对象。
6.4 抽象类
6.4.1 抽象属性和抽象方法
1)定义抽象类:
abstract class Person{} //通过abstract关键字标记抽象类
2)定义抽象属性:
val|var name:String //一个属性没有初始化,就是抽象属性
3)定义抽象方法
def hello() : String //只声明而没有实现的方法,就是抽象方法
- 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类;
- 重写非抽象方法需要使用override修饰,重写抽象方法则可以不加override;
- 子类中调用父类的方法使用super关键字;
- 子类对抽象属性进行实现,父类抽象属性可以使用var修饰;子类对非抽象属性进行重写,父类非抽象属性只支持val类型,而不支持var。
6.4.2 匿名子类
可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。
6.5 单例对象(伴生对象)
Scala是完全面向对象的语言,没有静态操作,同时为了能够与Java语言交互,就产生了一种特殊的对象,即单利对象。
object Person{
val country:String="China"
}
若单利对象名与类名一直,则称该单例对象为这个类的伴生对象,这个类的所有“静态”内容都可以防止在它的伴生对象中声明。
- 单例对象采用object关键字声明
- 单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致
- 单例对象中的属性和方法都可以通过伴生对象名直接调用访问
6.6 特质(Trait)
Scala语言中,采用特质来代替接口的概念,也就是说,多个类具有相同的特质时,就可以将这个特质独立出来,采用关键字trait声明。
Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入多个特质。类似于Java中的抽象类。
Scala引入trait特质,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。
6.6.1 特质的声明
trait 特质名{
trait 主体
}
6.6.2 特质的基本语法
一个类具有某种特质,就意味着这个类满足了这个特质的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。
1)基本语法
没有父类:class 类名 extends 特质1 with 特质2 with 特质3 ...
有父类: class 类名 extends 父类 with 特质1 with 特质2 with 特质3 ...
6.6.3 特质的叠加
由于一个类可以混入多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表和返回值均相同),则必然会出现继承冲突问题。冲突分为以下两种:
1)一个类混入的两个trait中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类中重写冲突方法。
2)一个类混入的两个trait中具有相同的具体方法,且两个trait继承自相同的trait,即所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加策略。
trait Ball{
def describe(): String = {
"ball"
}
}
trait Color extends Ball {
override def describe(): String = {
"blue-" + super.decribe()
}
}
trait Category extends Ball {
override def describe(): String = {
"foot-" + super.decribe()
}
}
class MyBall extends Category with Color {
override def describe(): String = {
"My ball is a " + super.describe()
}
}
object TestTrait {
def main (args: Array[String]): Unit = {
println(new MyBall().describe())
}
}
上述代码的运行结果为:
my ball is a blue-foot-ball
当一个类中混入多个特质时,scala会对所有的特质及其父特质按照一定的顺序进行排序,排序规则如下:
- 案例中的super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,MyClass中的super指代Color,Color中的super呆滞Category,Category中的super代指Ball;
- 如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如:super[Category].describe()。
6.6.4 特质和抽象类的区别
- 优先使用特质,一个类扩展多个特质时很方便的,但却只能扩展一个抽象类。
- 如果需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。
7. 集合
7.1 集合简介
1)Scala的集合有三大类:序列Seq、集Set和映射Map,所有的集合都扩展自Iterable特质;
2)对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本,分别位于一下两个包:
不可变集合:scala.collection.immutable
可变集合:scala.collection.mutable
3)Scala不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新的对象,而不会对原始对象进行修改。类似于java中的String对象
4)可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似于java中的StringBuilder对象
7.1.1 不可变集合集成图
7.1.2 可变集合集成图
7.2 数组
7.2.1 不可变数组
1)第一种方式定义数组
val arr1 = new Array[Int](10)
其中,new是关键字,[Int]是指定存放的数据类型,如果希望存放任意数据类型,则指定Any,(10)表示数组大小,确定之后不可以变化
2)第二种方式定义数组
val arr2 = Array(1,2)
这种方式在定义数组时,直接赋初值。
3)访问和修改元素:
println(arr(0))
arr(0) = 12
4)遍历数组
for(i <- 0 until arr.length){
println(arr(i))
}
5)添加元素
val newArr = arr.:+(69) //往后加
val newArr = arr.+:(69) //往前加
7.2.2 可变数组
1)创建可变数组
val arr = ArrayBuffer[Any](3,2,5)
其中,ArrayBuffer需要引入scala.collection.mutable.ArrayBuffer
2)添加元素
arr += 17 //在后边添加
17 +=: arr //在前边添加
更推荐:
arr.append(36)
arr.prepend(11,58)
3)删除元素
arr.remove(3) //删除3位置的元素
arr.remove(0,11) //从0开始删除11个数
7.2.3 可变数组与不可变数组的转换
arr.toBuffer //不可变数组转可变数组,本身没有变化
arr.toArray //可变数组转不可变数组,本身没有变化
7.2.4 多维数组
多维数组一个数组中的值可以是另一个数组,另一个数组的值也可以是一个数组。矩阵与表格是我们常见的二维数组。
val myMatrix = Array.ofDim[Int](3, 3)
实例:
import Array._
object Test {
def main(args: Array[String]) {
val myMatrix = Array.ofDim[Int](3, 3)
// 创建矩阵
for (i <- 0 to 2) {
for ( j <- 0 to 2) {
myMatrix(i)(j) = j;
}
}
// 打印二维阵列
for (i <- 0 to 2) {
for ( j <- 0 to 2) {
print(" " + myMatrix(i)(j));
}
println();
}
}
}
以上代码的输出结果为:
0 1 2
0 1 2
0 1 2
7.3 列表List
7.3.1 不可变List
1)List默认为不可变集合
2)创建一个List(数据有顺序,可重复)
val list1 = List(23, 65, 87)
3)访问和遍历List
println(list(1)) //这里为了方便使用,对列表进行了优化使得可以访问特定元素
list1.foreach(println)
4)添加元素
val list2 = 10 +: list1 //前边添加数据
val list3 = list1 :+ 23 //后边添加数据
或者
val list6 = 32 :: Nil
val list7 = 17 :: 28 :: 29 :: 16 :: Nil
其中,Nil表示空集合
5)合并列表
val list9 = list6 ::: list7
val list9 = list6 ++ list7
7.3.2 可变列表(ListBuffer)
1)创建可变列表
val list1 : ListBuffer[Int] = new ListBuffer[Int]()
val list2 = ListBuffer (12,53,75)
2)添加元素
list1.append(15,62)
list2.prepend(1,19)
list1.insert(1,20,21)
3)合并数组
val list3 = list1 ++ list2
4)修改元素
list2(30) = 30
5)删除元素
list2.remove(2) //删除指定位置
list2 -= 25 //删除指定数据
7.4 Set集合
Scala Set(集合)是没有重复的对象集合,所有的元素都是唯一的。默认情况下,Scala使用的是不变集合,如果你想使用可变集合,需要引用scala.collection.mutable.Set包
7.4.1 不可变集合
Set默认为不可变集合,数据无序,数据不可重复。
1)创建set
val set1 = Set(13,23,55,12,23)
2)添加元素
val set2 = set1.+(20)
val set2 = set1 + 20
3)合并Set
val set3 = Set(1,2,3)
val set4 = set2 ++ set3
4)删除元素
val set5 = set4 - 2
7.4.2 可变集合
1)创建set
val set1 = mutable.Set(13,23,53,12)
2)添加元素
val set2 = set1 + 1
set1 += 1
set1.add(1)
3)删除元素
val set2 = set1 - 2
val set2 -= 2
set1.remove(2)
4)合并两个set
val set3 = mutable.Set(1,2,3)
val set4 = set1 ++ set2
set1 ++= set3
7.5 Map集合
Scala中的Map是一个散列表,它存储的内容是键值对(key-value)映射
7.5.1 不可变Map
1)创建Map
val map1: Map[String, Int] = Map("a" -> 12, "b" -> 25, "hello" -> 3)
2)遍历元素
map1.foreach(println)
3)取Map中所有的key或value
for (key <- map1.keys){
println(s"$key ---> ${map1.get(key)}")
}
4)访问某一key的value
map1.get("a").get
map1.getOrElse("c", 0) //如果没有找到c的话则返回0
map1("a")
7.5.2 可变Map
1)可变Map
val map1: Map[String, Int] = mutable.Map("a" -> 12, "b" -> 25, "hello" -> 3)
2)添加元素
map1.put("c", 9)
map1 += (("e", 7))
3)删除元素
map1.remove("c")
map1 -= "e"
4)修改元素
map1.update("c", 2)
5)合并Map
val map2: Map[String, Int] = mutable.Map("p" -> 12, "b" -> 5, "ohello" -> 3)
map1 ++ map2 //map1的值添加到map2并进行覆盖
7.6 元组
元组可以理解为一个容器,里边可以存放各种相同或不同类型的数据。简单来说,就是将多个无关的数据封装为一个整体,称之为元组。元组中最大只能有22个元素。
1)创建元组
val tuple: (String, Int, Char, Boolean) = ("hello", 100, 'a', true)
2)访问元组
println(tuple._1)
println(tuple._2)
println(tuple._3)
println(tuple._4)
3)遍历元组
for(elem <- tuple.productIterator) println(elem)
4)嵌套元组
val mulTuple = (12, 0.3, "hello", (23, "kk"), 26)
println(mulTuple._4._2)
7.7 集合常用函数
7.7.1 基本属性和常用操作
val list = List(1, 3, 5, 7, 9)
1)获取集合长度
println(list.length)
2)获取集合大小
println(set.size)
3)循环遍历
for (elem <- list)
println(elem)
4)迭代器
for (elem <- list.iterator) println(elem)
5)生成字符串
println(list)
6)是否包含
println(list.contains())
7.7.2 衍生集合
val list1: List[Int] = List(1, 2, 4, 5, 8)
val list2: List[Int] = List(2, 6, 9, 12, 33)
1)获取集合的头
println (list1.head)
2)获取集合的尾
println (list1.tail)
3)集合最后一个数据
println (list1.last)
4)集合初始数据(不包含最后一个)
println(list1.init)
5)反转
println (list1.reverse)
6)取前(后)n个数据
println (list1. take (3))
println (list1. takeRight (3) )
7)去掉前(后)n个元素
println (list1.drop (3))
println (list1.dropRight (3) )
8)并集
println (list1.union (list2))
println (list1 ::: list2)
9)交集
println (list1.intersect (list2))
10)差集
println (list1.diff (list2))
11)拉链(如果两个集合元素个数不相等,那么会将同等数量的数据进行拉链,多余的数据省略不用)
println (list1.zip(list2))
12)滑窗
list1.sliding(2, 5) .foreach(println)
7.7.3 集合计算简单函数
val list: List[Int] = List(1, 5, -3, 4, 2, -7, 6)
1)求和
println(list.sum)
2)求乘积
println (list.product)
3)求最大值
println(list.max)
4)求最小值
println (list.min)
5)排序
//按元素大小排序
println(list.sortBy(x => x))
//按元素绝对值大小排序
println(list.sortBy(x => x.abs))
//按元素大小升序排序
println(list.sortWith((×, y) => x < y))
//按元素大小降序排序
println(list. sortWith((×, y) => x > y))
7.7.4 集合计算高级函数
val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val nestedList: List[List[Int]] = List(List (1, 2, 3), List(4,5, 6), List(7, 8, 9))
val wordList: List[String]= List("hello world", "helloatguigu", "hello scala")
1)过滤
println(list.filter(x => x % 2 == 20))
2)转化/映射
println(list.map(x => x + 1))
3)扁平化
println (nestedList.flatten)
4)扁平化+映射
println(wordList.flatMap(x => x.split(" ")))
5)分组
println(list.groupBy(x => x % 2))
7.8 队列(Queue)
队列的特点就是先进先出,进队和出队的方法分别为enquenue和dequeue。
1)创建一个可变队列
val queue new mutable.Queue[String]()
2)入队
queue.enqueue("a", "b")
3)出队
queue.dequeue() //有返回值
7.9 并行集合
Scala为了充分使用多核CPU,提供了并行集合,用于多核环境的并行计算。
//串行
val result1 = (0to100).map(
x=> Thread.currentThread.getId
)
//并行
val result2 = (0to100).par.map(
x => Thread.currentThread.getId
)
8 模式匹配
8.1 基本语法
模式匹配语法中,采用match关键字声明,每个分支采用case关键字声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,则会执行对应的逻辑代码,如果匹配失败继续执行下一个分支进行判断。如果所有case1都不匹配,那么会执行case_分支。
object Test {
def main(args: Array[String]) {
println(matchTest("two"))
println(matchTest("test"))
println(matchTest(1))
println(matchTest(6))
}
def matchTest(x: Any): Any = x match {
case 1 => "one"
case "two" => 2
case y: Int => "scala.Int"
case _ => "many"
}
}
上述代码的执行为:
2
many
one
scala.Int
可以使用模式匹配实现简单的二元运算
def matchDualOp(op:Char): Int = op match {
case '+' => a+b
case '-' => a-b
case '*' => a*b
case '/' => a/b
case '%' => a%b
case _ => "非法运算符"
}
- 如果所有case都不匹配,那么会执行case_分支,类似于default语句,若此时没有case_分支,那么会抛出MatchError;
- 每个case中,不需要使用break语句,自动中断case;
- match case语句可以匹配任何;类型,而不只是字面量;
- => 后面的代码块,直到下一个case语句之前的代码都是作为一个整体执行,可以使用{}括起来,也可以不括。
8.2 模式守卫
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。
def abs(num: Int): Int = {
num match {
case i if i >=0 => i
case i if i <0 => -i
}
}
8.3 模式匹配常量
8.3.1 匹配常量
Scala中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字和布尔值等。
def describeConst(x: Any): String = x match {
case 1 => "one"
case "hello" => "string"
case true => "Boolean"
case _ => ""
}
8.3.2 匹配类型
def describeType(x: Any): String = x match {
case i: Int => "Int"
case s: String => "String"
case list: List[String] => "List" + list
case array: Array[Int] => "Array[Int]"
case a => "else" + a //可以获取到当前值
}
8.3.3 匹配数组
val result = 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_ => "else"
}
8.3.4 匹配列表
val result = list match{
case List(0) => "0"
case List(1, 0) => "List(1,0)"
case List(x, y) => "List:" + x + y //匹配两元素数组
case List(0, _*) => "以0开头的数组"
case List(x, 1, z) => "中间为1的三元素数组"
case_ => "else"
}
8.3.5 匹配元组
val result = tuple match {
case (a,b) => "" + a + b
case (0,_) => "(0,_)"
case (a,1,_) => "(a,1,_)"
case_ => "else"
}
8.3.6 匹配对象及样例类
使用了case关键字的类定义就是样例类(case class),样例类是种特殊的类,经过优化以用于模式匹配。
object Test {
def main(args: Array[String]) {
val alice = new Person("Alice", 25)
val bob = new Person("Bob", 32)
val charlie = new Person("Charlie", 32)
for (person <- List(alice, bob, charlie)) {
person match {
case Person("Alice", 25) => println("Hi Alice!")
case Person("Bob", 32) => println("Hi Bob!")
case Person(name, age) =>
println("Age: " + age + " year, name: " + name + "?")
}
}
}
// 样例类
case class Person(name: String, age: Int)
}
以上代码的输出结果:
Hi Alice!
Hi Bob!
Age: 32 year, name: Charlie?
在声明样例类时,发生了一下过程:
- 构造器的每一个参数都称为val,除非显式的被声明为var,但并不推荐这么做;
- 在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
- 提供unapply方法使模式匹配可以工作;
- 生成toString、equats、hashCode和copy方法,除非显式给出这些方法的定义。
9 异常
1)抛出异常
throw new IllegalArgumentException
2)捕获异常和finally语句
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
object Test {
def main(args: Array[String]) {
try {
val f = new FileReader("input.txt")
} catch {
case ex: FileNotFoundException => {
println("Missing file exception")
}
case ex: IOException => {
println("IO Exception")
}
} finally { //finally语句总是会执行
println("Exiting finally...")
}
}
}
10 隐式转换
当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译。
10.1 隐式函数
隐式函数在不需要改任何代码的情况下,扩展某个类的功能。
10.2 隐式参数
普通方法或函数中的参数可以通过implicit关键字声明为隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。
- 同一个作用域,相同类型的隐式值只能有一个
- 编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。
- 隐式参数优先于默认参数
10.3 隐式类
可以使用implicit声明类,可以扩展类的功能。
- 其所带的构造参数有且只能有一个;
- 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。
10.4 隐式解析机制
- 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)
- 如果第一条规则查找失败,会继续在隐式参数的类型作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象。
11 泛型
11.1 协变和逆变
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]的“无父子关系”
11.2 泛型上下限
Class PersonList[T<:Person]{ //泛型上限
}
Class PersonList[T>:Person]{ //泛型下限
}
用于对传入的泛型进行限定
11.3 上下限限定
def f[A:B](a:A) = println(a) //等同于def f[A](a:A)(implict arg:B[A])=println(a)
上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A:Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过 implicitly[Ordering[A]]获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误。
12 总结
本文章为本人在学习Scala过程中的体会和笔记,一方面方便后边自己的查询,另一方面也希望方便一些大家。另外本人先前对Java和Scala完全不了解,所以文中可能会有诸多不足之处,望体谅,如果有错误也欢迎大家指出。谢谢!!