zoukankan      html  css  js  c++  java
  • 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]的超类型。

  • 相关阅读:
    centos7.0 没有netstat 命令问题
    Linux系统下安装rz/sz命令及使用说明
    怎样查看linux版本
    MongoDB 的 GridFS 详细分析
    MongoDb的bin目录下文件mongod,mongo,mongostat命令的说明及使用
    MongoDB基本使用
    安装concrete时提示“...database does not support InnoDB database tables..."如何解决
    最近开始研究PMD(一款采用BSD协议发布的Java程序代码检查工具)
    如何利用论坛做推广 | 一个每天可以吸引50粉丝的推广思路
    那些高阅读量文章的标题都是怎么取的?14种模板直接套用
  • 原文地址:https://www.cnblogs.com/shaozhiqi/p/11535746.html
Copyright © 2011-2022 走看看