Scala 学习笔记
FLYMOOD 人气:0https://blog.csdn.net/qq_34291505/articlehttps://img.qb5200.com/download-x/details/86744581
Scala : 函数式编程
用“var”修饰的变量,可以重新赋予新的值,并且把原值抛弃。在后续重新赋值时,就不用再写“var”了。而用“val”修饰的变量,则禁止被重新赋值
在首次定义变量时,就必须赋予具体的值来初始化
Scala提倡定义val类型的变量,因为它是函数式编程,而函数式编程的思想之一就是传入函数的参数不应该被改变
第三章 Scala的基本类型
Scala的基本类型
Byte 8-bit有符号整数,补码表示,范围是 到
Short 16-bit有符号整数,补码表示,范围是 到
Int 32-bit有符号整数,补码表示,范围是 到
Long 64-bit有符号整数,补码表示,范围是 到
Char 16-bit字符,Unicode编码,范围是 0 到
String 字符串
Float 32-bit单精度浮点数,符合IEEE 754标准
Double 64-bit双精度浮点数,符合IEEE 754标准
Boolean 布尔值,其值为true或者false
在定义变量时,应该指明变量的类型,如果要显式声明变量的类型,或者无法推断时,则只需在变量名后面加上一个冒号“ : ”
scala> val x: Int = 123
x: Int = 123
scala> val y: String = "123"
y: String = 123
scala> val z: Double = 1.2
z: Double = 1.2
整数字面量
默认情况下推断为Int类型
浮点数字面量
类型默认是Double类型
注意,Double类型的字面量不能赋给Float类型的变量。虽然Float允许扩展成Double类型,但是会发生精度损失
val b = -3.2f
字符与字符串字面量
以单引号' '包起来的一个字符,采用Unicode编码
字符串就是用双引号" "包起来的字符序列,长度任意,允许掺杂转义字符。此外,也可以用前后各三个双引号""" """包起来,这样字符串里也能出现双引号,而且转义字符不会被解读(类似于Python r)
scala> val d = '\\'
d: Char = \
scala> val b = """So long \u0041 String \\\'\"!"""
b: String = So long A String \\\'\"!
字符串插值
表达式可以被嵌入在字符串字面量中并被求值,
第一种形式是s插值器,即在字符串的双引号前加一个s,从美元符号开始到首个非标识符字符(字母、数字、下划线和操作符的组合称为标识符,以及反引号对` `包起来的字符串)的部分会被当作表达式,如果有非标识符字符,就必须放在花括号里,且左花括号要紧跟美元符号
第二种形式是raw插值器,它与s插值器类似,只不过不识别转义字符
第三种形式是f插值器,允许给内嵌的表达式加上printf风格的指令,指令放在表达式之后并以百分号开始
scala> raw"\\\\"
res2: String = \\\\
scala> printf(f"${math.Pi}%.5f")
3.14159
第四章 Scala基础——函数及其几种形式
Scala的函数定义以“def”开头。
接着是用圆括号“( )”包起来的参数列表。在参数列表里,多个参数用逗号隔开,并且每个参数名后面要紧跟一个冒号以及显式声明的参数类型,因为编译器在编译期间无法推断出入参类型。写完参数列表后,应该紧跟一个冒号,再添加函数返回结果的类型。最后,再写一个等号“=”,等号后面是用花括号“{ }”包起来的函数体。
用“def”开始函数定义 | 函数名 | | 参数及参数类型 | | | 函数返回结果的类型 | | | | 等号 | | | | | def max(x: Int, y: Int): Int = { if(x > y) x else | y | } | | 花括号里定义函数体
建议一行只写一条完整的语句,句末分号省略,让编译器自动推断
函数的返回结果
编译器会自动为函数体里的最后一个表达式加上“return”,将其作为返回结果。建议不要显式声明“return”,这会引发warning
返回结果有一个特殊的类型——Unit,表示没有值返回。
等号与函数体
当函数的返回类型没有显式声明时,那么这个等号可以省略,但是返回类型一定会被推断成Unit类型,不管有没有值返回
函数的返回类型显式声明时,则无论如何都不能省略等号。建议写代码时不要省略等号
------------- 有等号省返回类型就会推断类型。
------------- 没有等号一定是Unit
无参函数
如果一个函数没有参数,那么可以写一个空括号作参数列表,也可以不写。如果有空括号,那么调用时可以写也可以不写空括号
方法
方法其实就是定义在class、object、trait里面的函数
嵌套函数
局部函数可以直接使用外层函数的参数,也可以直接使用外层函数的内部变量
函数字面量
可以把一个函数当参数传递给另一个函数,也可以让一个函数返回一个函数,亦可以把函数赋给一个变量,又或者像定义一个值那样在函数里定义别的函数(即前述的嵌套函数)
函数字面量是一种匿名函数的形式,它可以存储在变量里、成为函数参数或者当作函数返回值,其定义形式为:
(参数1: 参数1类型, 参数2: 参数2类型, ...) => { 函数体 }
通常,函数字面量会赋给一个变量,这样就能通过“变量名(参数)”的形式来使用函数字面量,在参数类型可以被推断的情况下,可以省略类型,并且参数只有一个时,圆括号也可以省略。
函数字面量的形式可以更精简,即只保留函数体,并用下划线“_”作为占位符来代替参数。在参数类型不明确时,需要在下划线后面显式声明其类型。多个占位符代表多个参数
scala> val f = (_: Int) + (_: Int) f: (Int, Int) => Int = $$Lambda$1072/1534177037@fb42c1c scala> f(1, 2) res0: Int = 3
scala> val add = (x: Int) => { (y: Int) => x + y } add: Int => (Int => Int) = $$Lambda$1192/1767705308@55456711 scala> add(1)(10) res0: Int = 11 x=1,y=10 scala> def aFunc(f: Int => Int) = f(1) + 1 aFunc: (f: Int => Int)Int scala> aFunc(x => x + 1) res1: Int = 3
部分应用函数
有一个函数定义为“def max(...) ...”,若想要把这个函数存储在某个变量里,不能直接写成“val x = max”的形式,而必须像函数调用那样,给出一部分参数,故而称作部分应用函数(如果参数全给了,就成了函数调用)
scala> def sum(x: Int, y: Int, z: Int) = x + y + z sum: (x: Int, y: Int, z: Int)Int scala> val a = sum(1, 2, 3) a: Int = 6 scala> val b = sum(1, _: Int, 3) b: Int => Int = $$Lambda$1204/1037479646@5b0bfe86 scala> b(2) res0: Int = 6 scala> val c = sum _ c: (Int, Int, Int) => Int = $$Lambda$1208/1853277442@5e4c26a1 scala> c(1, 2, 3) res1: Int = 6 ————————————————
一个参数都不给的部分应用函数,只需要在函数名后面给一个下划线即可,注意函数名和下划线之间必须有空格
如果部分应用函数一个参数都没有给出,比如例子中的c,那么在需要该函数作入参的地方,下划线也可以省略
scala> def needSum(f: (Int, Int, Int) => Int) = f(1, 2, 3)
needSum: (f: (Int, Int, Int) => Int)Int
scala> needSum(sum)
res3: Int = 6
闭包
一个函数除了可以使用它的参数外,还能使用定义在函数以外的其他变量. 使用自由变量的函数称为闭包。
闭包捕获的自由变量,后续若新建同名的自由变量来覆盖前面的定义,新自由变量与已创建的闭包无关
如果闭包捕获的自由变量本身是一个可变对象(例如var类型变量),那么闭包会随之改变
函数的特殊调用形式
具名参数
调用时显式声明参数名并给其赋值
默认参数值
调用函数时缺省了这个参数,那么就会使用定义时给的默认值
重复参数
函数的最后一个参数标记为重复参数,其形式为在最后一个参数的类型后面加上星号“*”, (---- python 中的可变长度参数)
类型为“T*”的参数的实际类型是“Array[T]”,即若干个T类型对象构成的数组。但不可将arrary 作为参数传入,而应该一个一个传入。除非用“变量名: _*”的形式告诉编译器把数组元素一个一个地传入
def addMany(msg: String, num: Int*) =
addMany("sum = ", 1, 2, 3)
addMany("sum = ", Array(1, 2, 3): _*)
柯里化
Scala有一个独特的语法——柯里化,也就是一个函数可以有任意个参数列表
def addCurry(x: Int)(y: Int)(z: Int) = x + y + z
传名参数
无参函数,那么通常的类型表示法是“() => 函数的返回类型”
传名参数的类型表示法是“=> 函数的返回类型”
调用该函数时,传递进去的函数字面量则可以只写“函数体”
// 传名参数的用法,注意因为去掉了空括号,所以调用predicate时不能有括号 def byNameAssert(predicate: => Boolean) = if(assertionEnabled && !predicate) throw new AssertionError // 传名参数版本的调用,看上去更自然 byNameAssert(5 > 3)
def xxx(f: T => U, ...) ...”或 “def xxx(...): T => U”的代码,要理解前者表示需要传入一个函数作为参数,后者表示函数返回的对象是一个函数
第五章 Scala基础——类和对象
一个类就是一个类型,不同的类就是不同的类型
val或var类型的变量,它们被称为“字段”;还可以定义“def”函数,它们被称为“方法”;
字段也叫“实例变量”,因为每个被构造出来的对象都有其自己的字段
用new构造出来的对象可以赋给变量
val类型的变量只能与初始化时的对象绑定,不能再被赋予新的对象。一旦对象与变量绑定了,便可以通过“变量名.成员”的方式来多次访问对象的成员
Scala的类成员默认都是公有的,没有“public”这个关键字。如果不想某个成员被外部访问,则可以在前面加上关键字“private”来修饰
类的构造方法
主构造方法
Scala则不需要显式定义构造方法 ,而是把类内部非字段、非方法的代码都当作“主构造方法”。
类名后面可以定义若干个参数列表,用于接收参数,这些参数将在构造对象时用于初始化字段并传递给主构造方法使用
scala> class Students(n: String) { | val name = n | println("A student named " + n + " has been registered.") | } defined class Students scala> val stu = new Students("Tom")
辅助构造方法
辅助构造方法都是以“def this(......)”来开头的
而且第一步行为必须是调用该类的另一个构造方法,即第一条语句必须是“this(......)”——要么是主构造方法,要么是之前的另一个辅助构造方法。这种规则的结果就是任何构造方法最终都会调用该类的主构造方法,使得主构造方法成为类的单一入口。
scala> class Students(n: String) { | val name = n | def this() = this("None") | println("A student named " + n + " has been registered.") | } defined class Students scala> val stu = new Students
无析构函数
私有主构造方法
在类名与类的参数列表之间加上关键字“private”,那么主构造方法就是私有的,只能被内部定义访问,外部代码构造对象时就不能通过主构造方法进行
class Students private (n: String, m: Int)
val stu = new Students("Bill", 90) ---------------- error
val stu = new Students("Bill') ----------------correct
重写toString方法
类的toString方法,这个方法返回一个字符串,并在构造完一个对象时被自动调用,返回结果交给解释器打印.这个方法是继承来的,要重写它必须在前面加上关键字“override”
scala> class Students(n: String) {
| val name = n
| override def toString = "A student named " + n + "."
| }
defined class Students
————————————————
方法重载
重载是一个类里有多个不同版本的同名方法,重写是子类覆盖定义了超类的某个方法
方法虽然同名,但是它们是不同的,因为函数真正的特征标是它的参数,而不是函数名或返回类型
类参数
在类参数前加上val或var来修饰,这样就会在类的内部会生成一个与参数同名的公有字段
除此之外,还可以加上关键字private、protected或override来表明字段的权限
scala> class Students(val name: String, var score: Int) { | def exam(s: Int) = score = s | override def toString = name + "'s score is " + score + "." | } defined class Students
单例对象与伴生对象
除了用new可以构造一个对象,也可以用“object”开头定义一个对象。它类似于类的定义,只不过不能像类那样有参数,也没有构造方法
object定义的对象只能有这一个,故而得名“单例对象”。
如果某个单例对象和某个类同名,那么单例对象称为这个类的“伴生对象”,同样,类称为这个单例对象的“伴生类”。伴生类和伴生对象必须在同一个文件里,而且两者可以互访对方所有成员
Scala的做法是把类内所有的静态变量从类里移除,转而集中定义在伴生对象里,让静态变量属于伴生对象这个独一无二的对象。
单例对象里面可以定义字段和方法。Scala允许在类里定义别的类和单例对象,所以单例对象也可以包含别的类和单例对象的定义
单例对象除了用作伴生对象,通常也可以用于打包某方面功能的函数系列成为一个工具集,或者包含主函数成为程序的入口
每个单例对象有自己独特的类型,即object.type
工厂对象与工厂方法
义一个方法专门用来构造某一个类的对象,那么这种方法就称为“工厂方法”。包含这些工厂方法集合的单例对象,也就叫“工厂对象”
通常,工厂方法会定义在伴生对象里。
使用工厂方法的好处是可以不用直接使用new来实例化对象,改用方法调用,而且方法名可以是任意的,这样对外隐藏了类的实现细节
// students.scala class Students(val name: String, var score: Int) { def exam(s: Int) = score = s override def toString = name + "'s score is " + score + "." } object Students { def registerStu(name: String, score: Int) = new Students(name, score) }
apply方法
apply,如果定义了这个方法,那么既可以显式调用——“对象.apply(参数)” ,也可以隐式调用——“对象(参数)”。隐式调用时,编译器会自动插入缺失的“.apply”。
通常,在伴生对象里定义名为apply的工厂方法,就能通过“伴生对象名(参数)”来构造一个对象
也常常在类里定义一个与类相关的、具有特定行为的apply方法,让使用者可以隐式调用,进而隐藏相应的实现细节
// students2.scala class Students2(val name: String, var score: Int) { def apply(s: Int) = score = s def display() = println("Current score is " + score + ".") override def toString = name + "'s score is " + score + "." } object Students2 { def apply(name: String, score: Int) = new Students2(name, score) }
scala> val stu2 = Students2("Jack", 60) stu2: Students2 = Jack's score is 60. scala> stu2(80) scala> stu2.display Current score is 80.
主函数
主函数是Scala程序唯一的入口
要提供这样的入口,则必须在某个单例对象里定义一个名为“main”的函数,而且该函数只有一个参数,类型为字符串数组Array[String],函数的返回类型是Unit
// students2.scala class Students2(val name: String, var score: Int) { def apply(s: Int) = score = s def display() = println("Current score is " + score + ".") override def toString = name + "'s score is " + score + "." } object Students2 { def apply(name: String, score: Int) = new Students2(name, score) } // main.scala object Start { def main(args: Array[String]) = { try { val score = args(1).toInt val s = Students2(args(0), score) println(s.toString) } catch { case ex: ArrayIndexOutOfBoundsException => println("Arguments are deficient!") case ex: NumberFormatException => println("Second argument must be a Int!") } } }
使用命令“scalac students2.scala main.scala”将两个文件编译后,就能用命令“scala Start 参数1 参数2”来运行程序
第六章 Scala基础——操作符即方法
Scala并不存在操作符的概念,这些所谓的操作符,例如算术运算的加减乘除,逻辑运算的与或非,比较运算的大于小于等等,其实都是定义在“class Int”、“class Double”等类里的成员方法
表达式“1 + 2”的真正形式应该是“1.+(2)”
Scala里任何类定义的成员方法都是操作符,而且方法调用都能写成操作符的形式:去掉句点符号,并且方法参数只有一个时可以省略圆括号。
前缀操作符
前缀操作符只有“+”、“-”、“!”和“~”四个,相对应的方法名分别是“unary_+”、“unary_-”、“unary_!”和“unary_~”
中缀操作符
两个操作数中的一个是调用该方法的对象,一个是传入该方法的参数,参数那一边没有数量限制,只是多个参数需要放在圆括号里。Scala规定,以冒号“ : ”结尾的操作符,其右操作数是调用该方法的对象,其余操作符都是把左操作数当调用该方法的对象
后缀操作符
对象的相等性
让“==”和“!=”比较自然相等性
为了比较引用相等性,Scala提供了“eq”和“ne”方法
让“==”和“!=”比较自然相等性
加载全部内容