Scala常用知识点总结

前述:Scala文件会被编译为Java的class字节码文件,而且最终运行在JVM之上,所以有些操作会受限于JVM。

使用注意点:

1、List(1, 2, 3, "44")

语句正确执行,但返回得到是List[Any],即根据Int和String,将类型转为了Any。所以,List仍需要全部原始的类型一致。

2、scala语法级别的惰性执行lazy,只能对val起作用:一旦val常量被lazy修饰,则只有在变量真正使用到时才会执行,而且仅仅执行一次,。避免重复计算。【val常量可以是是函数返回值】

3、map()和flapMap()方法的区别:flatMap将集合的集合扁平化为集合,对Option[Option[T]]同样有效。

4、scala的构造函数以及构造器参数位置:

定义:

主构造器:class关键字后面存在字段时候,那些字段就是主构造器的参数,整个class内容(class中定义的字段也是)都是主构造器的内容,也就是说,new一个object时候,class中的可执行语句会自动执行。

辅助(从)构造器:显式使用this关键字作为函数名称的函数称为从构造器,从构造器中必须先调用已存在的一个构造器(不论是主构造器,还是其他的从构造器)。

作用:主构造器和各个从构造器的参数列表不同,根据个性化需求new对象。

注意点:

主构造器(class类名后面的参数列表)中的参数:(1)没有任何var或val修饰时,主构造器参数不会自动生成getter或者setter方法;(2)var修饰的参数,编译器会自动生成getter和setter方法;(3)val修饰的参数,编译器自动生成getter方法;(4)使用priavte修饰时候,编译器不会为参数生成getter和setter方法。

私有的主构造器:在class+类名称,加上private关键字,让主构造器变为私有,只有在从构造器中才可以调用,而在class外部无法调用。

5、通配符为下划线_,scala中的trait类似Java中的抽象类,其中的方法可以是抽象,也可以是具体的。

区分:Unit、Null、Nothing、Any、Nil、None

Unit 一般标识函数的返回类型,说明函数没有返回值。
Null

所有AnyRef的子类,空类型,拥有唯一的一个实例null。

null Null的实例。
Nothing

scala所有类型(值类型AnyVal和引用类型AnyRef)的子类。

用途:(1)说明类型参数,比如Nil或者List()创建的空列表。(2)说明函数的异常返回类型。

Any scala所有的值类型AnyVal和引用类型AnyRef的父类【scala类型系统的超类】。用途:scala在类型推断时候,比如Int和String同时存在List中,会直接推断出Any作为T。
Nil 表示空列表List(),扩展自List[Nothing],是所有List[T]的子类。
None 表示不存在值,作为Option[T]的子类出现(另一个子类是Some[T])。
   

Nothing is - together with scala.Null - at the bottom of Scala's type hierarchy.

Nothing is a subtype of every other type (including scala.Null); there exist no instances of this type. Although type Nothing is uninhabited, it is nevertheless useful in several ways. For instance, the Scala library defines a value scala.collection.immutable.Nil of type List[Nothing]. Because lists are covariant in Scala, this makes scala.collection.immutable.Nil an instance of List[T], for any element of type T.

Another usage for Nothing is the return type for methods which never return normally. One example is method error in scala.sys, which always throws an exception.

---

Scala中的集合和Iterator

1、集合分为可变mutable和不可变集合immutable,其中常用的是Array和ArrayBuffer。

注意:在使用Array、List等collection时候,需要将entire数据集全部load到内存中,而使用Stream可以使用流形式逐步加载数据到内存。【节省内存空间,但遍历整个数据集的时间相同】

2、scala中的String其实是Java自带类型,为不可变类型,可变类型为StringBuffer。

3、Option[T]是一个包含类型T的可变数值的容器,拥有2个子类,即存在值时为Some[T],否则为None。需要注意,既然Option是容器,那么Option也具有Array、List等的性质,比如map等方法。

注意:(1)对Some[T]调用get()得到类型为T的值,但对None调用会报错,所以一般使用getOrElse()方法,为None时返回缺省值。(2)Option容器的元素个数为0或者为1,当只需要Option副作用时候,可以使用for、map等遍历操作,因为Option[T]为None时候,for等遍历操作会直接跳过。

4、Iterator迭代器源码位于scale的collection集合包中,但本质上不属于集合collection,准确的说,Iterator是一种遍历colletion的方法,具有next和hasNext两个方法;此外既然是迭代器,那么只能访问一次,一旦访问结束,迭代器的整个状态会被销毁,不复存在。

(来源:https://netvl.github.io/scala-guidelines/libraries/collections.html

区分:方法、函数、闭包

方法:使用def定义的语句块,一般在class或者object中定义def方法。

函数:使用val定义、拥有输入参数和操作流程的语句块。一般是将匿名函数赋值给val变量的形式。

【在scala中,函数即是对象,都是scala.FunctionN(1-22)的实例】

Every function value is an instance of some class that extends one of several FunctionN traits in package scala, such as Function0 for functions with no parameters, Function1 for functions with one parameter, and so on. Each FunctionN trait has an apply method used to invoke the function.

闭包:当方法的返回类型是一个函数,函数使用到方法的参数(方法参数相对于函数是外部变量),这时的函数称为“闭包”。主要用于柯里化操作,也是柯里化赖以实现的前提。

方法和函数之间互转:

(方法1)方法名称+ 空格 + 下划线形式,转换

(方法2)将方法强制赋值给具有指定函数类型的val变量。

高阶函数:以函数作为参数的方法。比如map、foreach都可以以println作为参数。

各种特殊符号

(1)scala的泛型上边界(<:)和下边界(>:):

注意点:上边界要求各个类必须是指定类型的子类。而下边界则非必须,当类别是指定类的子类,则会将其提升到指定类别。

def say_apple_down[T <: Apple](things: Seq[T]): Unit = things.foreach(_.say())

def say_apple_up[T >: Apple](things: Seq[T]): Seq[T] = things

(2)协变和逆变

协变(+T):先定义父类Container对象,然后可以定义子类的Container对象。

逆变(-T):先定义子类Container对象,然后可以定义父类的Container对象。

(3)“_*”列表展开符号

用途:被调用方法是可变长度的参数,调用者是一个序列,这时需要“序列:_*”,将序列展开。

(4)“*”用法:

1、在匿名函数中,如果参数只需要使用一次,则充当匿名函数的参数名称。

2、在match case中充当其他case类型的占位。

3、在方法返回值中,充当其他变量的占位符。

4、涉及柯里化时,用于将方法转为函数。

implicit关键字用法

解答:

(1)作用:隐私的调用函数,不需要硬编码显式调用。

(2)scala程序在编译过程中,发现存在类型无法转换或者方法不存在时,若存在implicit修饰的函数或者类,则通过implicit修改的函数自动对类型进行转换,或者通过implicit修改的类,调用类中函数。

(3)使用implicit时候,注意不能存在二义性的implicit函数或者类,否则汇报“ambiguous”。

implicit def DoubleToString(d: Double): String = d.toString

def printStringVal(s: String): Unit ={
    println(s"done: $s")
}
printStringVal(100.0)

伴生类和伴生对象

解答:

(1)作用:在实例化类时候,使用函数调用的形式(通过定义apply函数)进行实例化。

(2)伴生类和伴生对象(也就是单例对象)需要放于同一个.scala文件中,类名和object名必须相同。伴生对象中的方法相当于static方法,需要在伴生对象中才能调用。伴生类和伴生对象可以调用对方的private属性。

(3)apply函数:定义在伴生对象中,在apply函数中对伴生类进行new操作。也就是实际调用的是apply方法,在apply方法中实例化一个伴生类对象。实现函数式的创建对象。

一个更好的解释:https://stackoverflow.com/questions/9737352/what-is-the-apply-function-in-scala/9738862#9738862

问题:为什么说List的apply()操作体现了“工厂方法”的思想?

解答:所谓工厂方法,也就是将对象的具体创建过程封装起来,对外透明。在List中,这种透明的创建过程是渣apply函数中进行。

object List extends SeqFactory[List] {   
/** $genericCanBuildFromInfo */   
    implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, List[A]] =     ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]   
    override def apply[A](xs: A*): List[A] = xs.toList 
}

def toList: List[A] = to[List]
def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A @uV]]): Col[A @uV] = {
  val b = cbf()
  b ++= seq
  b.result()
}

Array的apply创建方式:

/** Creates an array of `Int` objects */
// Subject to a compiler optimization in Cleanup, see above.
def apply(x: Int, xs: Int*): Array[Int] = {
  val array = new Array[Int](xs.length + 1)
  array(0) = x
  var i = 1
  for (x <- xs.iterator) { array(i) = x; i += 1 }
  array
}

注意:当object中的属性被修饰成private[this],则伴生类也无法直接访问此属性。在scala中,private[this]是最高级别的私有属性,只有当前object内部可以访问。

(4)unapply方法

作用:和apply相反,apply创建对象(打包),而unapply则将对象解包。一般用于match case匹配中,apply根据多个参数创建一个class对象,而unapply将一个class对象分解为多个参数的Some类型变量,进而提取各个参数值。

(5)case class会自动创建伴生对象。

偏函数PartialFunction

解答:

(1)目的:一个偏函数只针对部分输入运算;可以将一个完整的运算过程拆分为多个独立的偏函数,通过“组合”的方式实现运算的完整性。

(2)在定义函数名称时,在后面显式注明返回偏函数类型(第1个参数表示输入参数类型,第2个参数表示输出参数类型)。

scala.PartialFunction[Int, String]

(3)scala官方说明如下:

3.1 case使用

(1)match case:值匹配、类型匹配、case if 

(2)PartialFucntion定义中【注意case的用法】

case x if some_condation(x) => some_operation(x)

进一步参考资料:

https://blog.csdn.net/bluishglc/article/details/50995939

https://www.cnblogs.com/aademeng/articles/7410312.html

Scala partial functions (without a PhD)

https://blog.bruchez.name/2011/10/scala-partial-functions-without-phd.html

柯里化

解答:

(1)定义:将原本具有多个参数的方法,转为仅仅包含一个参数的方法,以及以剩余参数作为参数的函数。

(2)用于:延迟计算和执行、固定参数值。

def sum(x:Int)(y:Int) = x+y

如上代码所示,sum方法使用了柯里化技术,实际使用方法是sum(10)(20)。

示例:

scala的源码:corresponds,第一个参数是That,第2个参数p中的第2个参数的类型和That一致。【直接通过类型推断得到】
def corresponds[B](that : scala.collection.GenSeq[B])(p : scala.Function2[A, B, scala.Boolean]) : scala.Boolean = { /* compiled code */ }

scala的尾递归

解答:

(1)目的:解决正常递归(首递归)时栈溢出问题。

(2)正常的递归是先函数递归,再计算值。而尾递归先计算值,再函数递归;尾递归在最后一次递归调用时候直接返回最终结果。

(3)当函数是标准(严格)形式的尾递归,scala编译器会自动优化,否则不会自动优化。可以使用@scala.annotation.tailrec强制对scala函数注释,scala编译器会强制检查当前函数是否满足尾递归性质,否则会抛出异常。

(4)尾递归实现核心:寻找累加器(可能是实际加法操作,也可以是其他的聚集操作,只是一种叫法),在递归的同时,使用累加器不断的“聚集”过程中的临时值。

(5)正常情况下,递归存在有边界条件、递归前进段和递归返回段:边界条件不满足时,递归前进;当边界条件满足时,递归返回。

示例比如1+2+...+n-1+n或者菲波那切数列。

/**
  * @param n:第几个数字,下标从1开始
  * @return
  */
def  fibonacci(n: Int): Int ={
    if(n <= 2){
        1
    }else{
        fibonacci(n-1) + fibonacci(n-2)
    }
}

@scala.annotation.tailrec
def  tailFibonacci(n: Int, acc1:Int=1, acc2: Int=1): Int ={
    if(n <= 2){
        acc2
    }else{
        tailFibonacci(n-1, acc2, acc1 + acc2)
    }
}

Scala官方参考资料:

https://docs.scala-lang.org/overviews/index.html

-- 未完待续 --

发布了64 篇原创文章 · 获赞 24 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qm5132/article/details/102130452