Scala学习

《快学Scala》心得体会

1      基础

1.1         声明和定义

Val无法改变它的内容,实际上就是一个常量

Var 声明其值可变的变量

在scala中,与java不同的是,变量或函数的类型总是写在变量或函数名称的后面。

1.2         常用类型

Scala有7种数值类型:Byte、Char、Short、Int、Long、Float、Double,以及一个Boolen类型。与java不同的是,在scala中不需要包装类型,编译器会自动完成基本类型和包装类型之间的转换。如 1.to(10) ;其实是Int值1首先被转换成RichInt,然后调用to方法。

1.3         Apply方法

在Scala中通常会使用类似函数调用的语法。

如”string”(4)其实是将()操作符进行了重载,”string”.apply(4)实际调用的apply方法,在StringOps类中。

2      控制结构和函数

2.1         一切都是对象

与java,c/c++不同的是,scala中Everything is an object,特别是函数也可以做参数。

在scala中,任何语句都有返回值,语句返回Unit

object TimerAnonymous {

    def oncePerSecond(callback: () => Unit) {

        while(true) { callback(); Thread sleep 1000 }

    }

    def main(args: Array[String]) {

        oncePerSecond(() => println("time flies like an arrow..."))

    }

}

2.2         懒值引进

当val被声明为lazy时,它的初始化会被延迟,直到我们首次对它取值。           

应用到Non-Strict中,以及Stream,视图等待,同时spark中的RDD

优点:懒性求值,没用上的参数不会浪费计算资源

对于大数据量或者无限长的数据,有着良好的支持(增量计算)

缺点:重复计算(可通过缓存计算过的值改善)

使用不当,将会使逻辑难以推断(不知道参数到底用没用,或者在什么时候用)

3      数组,集合

3.1         可变与不可变

scala中一般都有可变与不可变的数组,集合等,分别在scala.collection.mutable和scala.collections.immutable中。

3.2         与java的互操作

由于scala是兼容java的,所以代码中会出现,scala代码中调用java中的变量,这是就需要一些转换操作,不然会出现编译错误。

如在写RncKpiJoinHourTaskSpec这个DT时:

import scala.collection.JavaConversions._   //不加这句就会报编译错误,应为usccpchBasics 是java文件中定义的

cellPmCounters.usccpchBasics = List(new UsccpchBasic(111, "D2"))

public List<UsccpchBasic> usccpchBasics;

public List<RrcestCause> rrcestCauseList;

3.3         常用变换

3.3.1        Map

map[B](f: (A) ⇒ B): List[B]

定义一个变换,把该变换应用到列表的每个元素中,原列表不变,返回一个新的列表数据

求平方例子

val nums = List(1,2,3)

val square = (x: Int) => x*x  

val squareNums1 = nums.map(num => num*num)    //List(1,4,9)

val squareNums2 = nums.map(math.pow(_,2))    //List(1,4,9)

val squareNums3 = nums.map(square)            //List(1,4,9)

 

val text = List("Homeway,25,Male","XSDYM,23,Female")

val usersList = text.map(_.split(",")(0)) // List[String] = List(Homeway, XSDYM) 

val usersWithAgeList = text.map(line => {

    val fields = line.split(",")

    val user = fields(0)

    val age = fields(1).toInt

    (user,age)

})// List[(String, Int)] = List((Homeway,25), (XSDYM,23))

3.3.2      flatMap&flatten

flatten: flatten[B]: List[B] 对列表的列表进行平坦化操作

flatMap: flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): List[B] map之后对结果进行flatten

定义一个变换f, 把f应用列表的每个元素中,每个f返回一个列表,最终把所有列表连结起来。

val text = List("A,B,C","D,E,F")

val textMapped = text.map(_.split(",").toList) // List(List("A","B","C"),List("D","E","F"))

val textFlattened = textMapped.flatten          // List("A","B","C","D","E","F")

val textFlatMapped = text.flatMap(_.split(",").toList) // List("A","B","C","D","E","F")

3.3.3        reduce& reduceLeft& reduceRight

reduce[A1 >: A](op: (A1, A1) ⇒ A1): A1

使用 reduce 我们可以处理列表的每个元素并返回一个值。通过使用 reduceLeft 和 reduceRight 我们可以强制处理元素的方向

定义一个变换二元操作,并应用的所有元素。

列表求和

val nums = List(1,2,3)

val sum1 = nums.reduce((a,b) => a+b)   //6

val sum2 = nums.reduce(_+_)            //6

val sum3 = nums.sum                 //6

 

reduceLeft: reduceLeft[B >: A](f: (B, A) ⇒ B): B

reduceRight: reduceRight[B >: A](op: (A, B) ⇒ B): B

reduceLeft从列表的左边往右边应用reduce函数,reduceRight从列表的右边往左边应用reduce函数

val nums = List(2.0,2.0,3.0)

val resultLeftReduce = nums.reduceLeft(math.pow)  // = pow( pow(2.0,2.0) , 3.0) = 64.0

val resultRightReduce = nums.reduceRight(math.pow) // = pow(2.0, pow(2.0,3.0)) = 256.0

3.3.4        fold,foldLeft,foldRight

fold: fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1 带有初始值的reduce,从一个初始值开始,从左向右将两个元素合并成一个,最终把列表合并成单一元素。

foldLeft: foldLeft[B](z: B)(f: (B, A) ⇒ B): B 带有初始值的reduceLeft

foldRight: foldRight[B](z: B)(op: (A, B) ⇒ B): B 带有初始值的reduceRight

val nums = List(2,3,4)

val sum = nums.fold(1)(_+_)  // = 1+2+3+4 = 9

val nums = List(2.0,3.0)

val result1 = nums.foldLeft(4.0)(math.pow) // = pow(pow(4.0,2.0),3.0) = 4096

val result2 = nums.foldRight(1.0)(math.pow) // = pow(1.0,pow(2.0,3.0)) = 8.0

3.3.5        filter, filterNot

filter: filter(p: (A) ⇒ Boolean): List[A]

filterNot: filterNot(p: (A) ⇒ Boolean): List[A]

filter 保留列表中符合条件p的列表元素 , filterNot,保留列表中不符合条件p的列表元素

val nums = List(1,2,3,4)

val odd = nums.filter( _ % 2 != 0) // List(1,3)

val even = nums.filterNot( _ % 2 != 0) // List(2,4)

4      类,对象,特质

4.1         与java中的区别

Trait有点类似于java中的interface,但也有不同之处。

①java的interface只定义方法名称和参数列表,不能定义方法体。而trait则可以定义方法体。

trait Friendly {

  def greet() = "Hi"                    

}

②在java中实现接口用implement,而在scala中,实现trait用extends。

Scala中不再有implement这个关键词。但类似的,scala中的class可以继承0至多个traits。

class Dog extends Friendly {

  override def greet() = "Woof"

}

此处,需要注意的一点是,与java不同,在scala中重写一个方法是需要指定override关键词的。如果重写一个方法时,没有加上override关键词,那么scala编译会无法通过。

③ java的interface和scala的trait的最大区别是,scala可以在一个class实例化的时候混合进一个trait。

trait Friendly {

  def greet() = "Hi"

}

class Dog extends Friendly {

  override def greet() = "Woof"

}

class HungryDog extends Dog {

  override def greet() = "I'd like to eat my own dog food"

}

trait ExclamatoryGreeter extends Friendly {

  override def greet() = super.greet() + "!"

}

var pet: Friendly = new Dog

println(pet.greet())

 

pet = new HungryDog

println(pet.greet())

pet = new Dog with ExclamatoryGreeter

println(pet.greet())

pet = new HungryDog with ExclamatoryGreeter

println(pet.greet())

4.2         在写DT时出现的一些问题

由于Scala中没有静态方法一说,于是产生了一种新的特性,单例对象,object,里面存放的就相当于java里面的静态方法,字段。

为了得到既有实例方法和静态方法的类,可以通过创建与类同名的对象来达到目的。

在公司的以前的一些业务代码中,由于没有写DT,发现现在在写时,有些单例类型Oject的方法无法写测试

object A  ===>

 object A extends A

class A{

}

然后在测试用例中,新建A类,并将不关心的方法进行overwirte为空,这样就可以在不影响其他业务的情况下也能完成DT

应为对外界其实也有个单例类型object A。

但是在这样处理时,要注意,Oject里面的样例类case class应该继续放到oject里面,其他内容放到class A里;应为如果不这样处理,那么后面得到的样例类实例化对象就会不同,达不到我们想要的结果。

假如定义了一个类Outer,在里面定义了样本类Inner,并持有一个Int值

scala> class Outer {

     |     case class Inner(value:Int)

     | }

defined class Outer

 

scala> val outer1 = new Outer; val outer2 = new Outer

outer1: Outer = Outer@560245

outer2: Outer = Outer@af0d85

 

scala> val inner1 = new outer1.Inner(1); val inner2 = new outer2.Inner(1)

inner1: outer1.Inner = Inner(1)    // 内部类赋值一样

inner2: outer2.Inner = Inner(1)    // 但是,注意他们的类型是不一样的,一个是outer1.Inner,另一个是outer2.Inner,它是依赖于具体对象的一个类型

scala> inner1 == inner2              // 在比较的时候返回false,虽然它们持有的值都是1

res5: Boolean = false

这样就在处理一些合并时,本来以为Key是相同的记录不会合并在一起。

5         协变与逆变

5.1              Java中的泛型

已知StringObject的子类,从直觉上我们可能认为List<String>应该是List<Object>的子类型,但是看下面例子:

private static void printList(List<Object> list) {

  for (Object obj : list) {

       System.out.println(obj.toString());

    }

}

public static void main(String[] args) {
    List<String> names = new ArrayList<String>();
    names.add("aaa");
    names.add("bbb");
    printList(names);    // compile error!!!
}

在调用printList时,传入的是一个List<String>,需要的是一个List<Object>,如果List<String>是List<Object>的子类,这个地方应该可以编译通过,结果不是,说明他们之间没什么关系。但是为了满足一些客观现实情况,我们在scala中映入了协变和逆变的概率。

5.2              Scala中的协变和逆变

1.不变: class Box[T]

2.协变: class Box[+T]

如果A是B的子类,那么Box [A]也是Box [B]的子类型

一般情况下,在定义协变类型时,主要用于返回,不要用于方法的参数,否则可能出现下面的结果:

class Box[+T](v: T) {
  def get: T = ???
  def set(v: T) = ???  // 编译错误
}
val stringBox = new Box[String]("abc")
val anyBox: Box[Any] = stringBox
anyBox.set(123) 
如果编译器不做这样的限制,我们可能把一个int型的数据插入到需要StringBox中。

3.逆变: class Box[-T]

如果A是B的超类,那么Box [A]是Box [B]的子类型 逆变

class Box[-T](v: T) {
  def get: T = ???  // compile error
  def set(v: T) = ???
}

--

val anyBox = new Box[Any](123)
val stringBox: Box[String] = anyBox
stringBox.get  
如果不做返回值的限制,那么我们可能就会从string的盒子拿出一个int值,有违常理。破坏了我们的类型系统。所以逆变的类型参数一般放在方法的参数,而不是返回值。
下面我们看一个协变的应用:
定义一个泛型类型Friend[T],表示希望与类型T的人成为朋友的人。
Trait Friend[T]{
  def befriend(someone:T)
}

有这样一个函数

def makeFriendWith(s: Student,f: Friend[Student]){f.befriend(s)}

其中,

class Person extends Friend[Person]

class Student extends Person

val susan = new Student

val fred = new Person

客观上,fred可以和任何Person做朋友,那么就应该可以喝任何student做朋友,所以,我们要求函数调用makeFriendWith(susan,fred)是可以的,即Friend[Person]应该是Friend[Student]的子类型,于是我们将Friend定义为逆变的就可以达到要求:

Trait Friend[-T]{
  def befriend(someone:T)
}

5.3     类型变量界定

协变逆变与上下界的不同,上下界表明传入的参数类型是满足么个类型的超类或者子类,那么方法中就应该具备一些方法。

比如需要对一个Pair类型中的两个组件进行比较时:

class Pair[T](val first:T,val second: T){

 def smaller = if(first.compareTo(second<0)first else second

}

但是这样会报错,因为T是个泛型,不知道他是否有compareTo这个方法。这个时候我们就可以采用上界的方法来解决这个问题:

class Pair[T<:Comparable[T]](val first:T,val second: T){

 def smaller = if(first.compareTo(second<0)first else second

}

这样就限制了T必须是Comparable[T]的子类型,肯定具备compareTo方法。类似的下界T<:Comparable[T]要求T必须是Comparable[T]的超类型。

posted @ 2019-09-17 17:43  ~清风煮酒~  阅读(175)  评论(0编辑  收藏  举报