亲宝软件园·资讯

展开

Scala 学习笔记

FLYMOOD 人气:0

https://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”方法

 

让“==”和“!=”比较自然相等性

加载全部内容

相关教程
猜你喜欢
用户评论