Scala编程快速入门系列(二)

BitTigerio 2018-03-17

目 录

一、Scala概述

二、Scala数据类型

三、Scala函数

四、Scala集合

五、Scala伴生对象

六、Scala trait

七、Actor

八、隐式转换与隐式参数

九、Scala JDBC

由于整理的篇幅较长,所以文章计划分三次发布。第一部分的内容请转至系列(一)。

四、Scala集合

1. Scala集合概述

Scala对集合的操作就是Spark程序的实现方式。Spark中有一个RDD(Resilience弹性的、Distributed分布式、DataSet数据集),spark的程序都是将源数据加载过来变成一个RDD,然后每一步操作都是集合的元素进行操作。对于Spark来说是分布式的操作,但是对于写程序来说无需考虑分布式,只需考虑对集合元素的操作。Scala的集合操作是针对单机的,Spark是针对分布式的,但是代码都类似。

2. List集合

这里举例创建集合和获取集合元素的同时也有一些常用的集合操作函数。

  • 创建List集合

在Scala中创建一个集合不需要new关键字。

object TestCollection {
  val list =List(1,4,6,4,1)
}
  • 获取集合的元素
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    println("获取集合的第2个元素:"+list(1))//集合的下标从0开始
  }
}

使用list(),括号中传入集合元素的位置来获取集合元素。

  • map函数

map函数的本质就是使用匿名函数对集合中每一个元素做同样的操作。

object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    val b= list.map(a=>{println(a+"-----");a+1})
    val c= list.map(_+1)
    println(b)
    println(c)
  }
}

list.map是集合list调用map方法,map方法对集合的每个元素进行操作,具体的操作由匿名函数定义。第一个map函数中的a代表集合List的每一个元素,作为匿名函数的参数,执行方法体打印,然后返回最后一行a+2赋给新的集合相应位置的元素。

list.map(_+1))//这样写是上式的简写形式,下划线代表集合的每一个元素。

  • "+:"和":+"函数向集合添加元素
object TestCollection {
  val list =List(1,4,6,4,1)
  val list2=list.+:("杨辉三角")
  val list3=list.:+("杨辉三角")
  def main(args: Array[String]): Unit = {
    println(list2)
    println(list3)
  }
}

+: 在集合第一个位置添加元素;:+ 在集合最后一个位置添加元素。运行结果如下:

List(杨辉三角, 1, 4, 6, 4, 1)
List(1, 4, 6, 4, 1, 杨辉三角)

  • foreach函数遍历输出

foreach和map很相似,都是对集合的每一个元素做相应的操作,只是map会返回值给集合。如果要打印结果一般用foreach。

object TestCollection {
  val list =List(1,4,6,4,1)
  val list2=list.+:("杨辉三角")
  def main(args: Array[String]): Unit = {
    list2.foreach(i => print("---"+i))//是对集合list2中的每一个元素遍历,i表示集合中的每一个元素。
    list2.foreach(i => {val j = i + "s"; print("---"+ j)})//可以对集合中的元素先进行有关操作
    list2.foreach(print _)//对集合遍历输出可以简化为此式
  }
}
  • distinct函数去重
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    list.distinct.foreach(print _)
  }
}

输出结果为:146

  • slice函数截取集合

slice函数需要两个参数,第一个参数表示从该下标开始截取,第二个参数表示截取到该下标(不包含)。

<span>object TestCollection {
  val list =List(1,4,6,4,1<span>)
  def main(args: Array[String]): Unit =<span> {
    print(list.slice(0,3<span>))
  }
}

结果为:List(1, 4, 6)

  • for循环遍历集合
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    for(i <- list){
      print(i)
    }
  }
}
  • length函数获取集合长度
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    for(i <- 0.to(list.length-1)){
      print(list(i))
    }
  }
}
  • "/:"函数
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]) {
    //list./:是调用./方法,它是一个柯里化函数,其中(100)是第一个参数,({(sum,num)=>print(sum+"--"+num+" ");sum-num})是第二个参数。
    println(list./:(100)({
      (sum,num)=>print(sum+"--"+num+" ");//函数/:的第二个参数——匿名函数需要两个参数,匿名函数第一个参数为/:函数的第一个参数,匿名函数的返回值类型和/:函数的第一个参数类型一致
        sum+num//这里的匿名函数实际上是(sum,num)=>sum-num,就是传入两个参数sum和num,返回sum+num,返回值的类型显然和sum的类型一样。/:方法详见源码解读。
    }));//返回结果为100--1 101--4 105--6 111--4 115--1 116
  }
}
<strong>//源码片段:
</strong>def /:[B](z: B)(op: (B, A) => B): B = foldLeft(z)(op)

def foldLeft[B](z: B)(op: (B, A) => B): B = {
  var result = z
  this foreach (x => result = op(result, x))
  result
}

源码解读:如下代码是/:函数的源码,可见/:是个柯里化函数。其中,[B]是函数/:的泛型;(z: B)是第一个参数,其类型为泛型[B];(op: (B, A) => B)是第二个参数,它是一个匿名函数op,它需要两个参数(B, A),能返回B类型的值。最后的:B是函数/:的返回值类型。/:(z)(op)=foldLeft(z)(op)。

再看foldLeft函数,也是一个柯里化函数,需要两个参数,参数类型和/:的参数类型一致。其方法体可见,首先将传入的第一个B类型的参数z赋值给变量result,然后调用该方法的当前对象(如集合List对象)使用foreach(这里的this foreach和this.foreach是一样的道理)方法遍历当前对象中的所有元素,其元素x的类型就是匿名函数的第二个参数的类型A,这里调用匿名函数op,以result和x为参数,其返回结果赋值给result,通过多次调用匿名函数循环集合的所有元素,最后返回result,作为函数foldLeft的返回值,也就是函数/:的返回值。

  • reduce函数

reduce函数和/:函数很类似,使用的频率很高。

object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    //reduce函数,需要一个匿名函数做参数,此匿名函数的类型是(A1,A1)=>A1,匿名函数第一次的参数是前两个元素;之后,第一个参数上一次的匿名函数返回值,第二个参数是依次位置的集合元素值。
    println(list.reduce((a: Int, b: Int) =>{println(a + "---" + b) ; a+b}))//最终结果是所有元素的和16
    //上式求所有元素的和和以简化为下面的形式
    println(list.reduce(_+_))
  }
}
<strong>//源码片段:
</strong>def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceLeft(op)
def reduceLeft[B >: A](op: (B, A) => B): B = {
  if (isEmpty)
    throw new UnsupportedOperationException("empty.reduceLeft")

  var first = true
  var acc: B = 0.asInstanceOf[B]

  for (x <- self) {
    if (first) {
      acc = x
      first = false
    }
    else acc = op(acc, x)
  }
  acc
}

源码解读:reduce函数最终是要调用reduceLeft函数,顾名思义是需要从左侧开始。reduceLeft函数需要一个匿名函数(op: (B, A) => B),返回类型是B,和传入的第一个参数一样。if (isEmpty)如果集合为空,抛出异常。0.asInstanceOf[B],B是一个泛型,0是int类型,0.asInstanceOf[B]意为将0转为B这个泛型类型。在else acc = op(acc, x)中才开始调用传入的匿名函数op。

3. Tuple元组

  • 创建元组

在Scala中创建元组不需要关键字,只需要括号就行。它的特点是,定义了元组之后,元组的值不可以修改(和Python一致)。

object TestCollection {
  val tuple =(1,4,6,4,1)
}
  • 获取元组元素
object TestCollection {
  val tuple =(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    println(tuple._1)//元组取值是用"._",不能像list集合一样用括号
    println(tuple._5)//Tuple元组下标从1开始。
  }
} 

4. Map集合

Scala中的Map有两种类型,一个是Mutable可以更改的,另一个是Immutable不可更改的。如果没有导包直接写Map的话是默认为Immutable的,如果要创建可以更改key的value值的Map集合需要导包,指定是Mutable的Map。

  • 创建Map集合
object TestCollection {
  //定义一个map集合,[String,Int]分别是键和值的泛型。
  var map = Map[String, Int]("a" -> 1, "b" -> 2);//使用“->”来定义一对key value,每对key/value使用逗号隔开。
  var map2 = Map[String, Int](("a", 3), ("b", 4));//也可以使用(key,value)的形式定义一对key/value,因为Map中的每一个元素都是一个元组。
}
  • 获取集合元素
object TestCollection {
  var map = Map[String, Int]("a" -> 1, "b" -> 2);
  def main(args: Array[String]) {
    println(map("a"));//使用(键)来获取对应的值
  }
}
  • "+="函数添加集合元素
object TestCollection {
  var map = Map[String, Int]("a" -> 1, "b" -> 2);
  map += ("c" -> 3)
  map += Tuple2.apply("d",4)
  def main(args: Array[String]) {
    println(map);//输出结果为Map(a -> 1, b -> 2, c -> 3, d -> 4)
  }
}
  • foreach函数遍历集合
object TestCollection {
  var map = Map[String, Int]("a" -> 1, "b" -> 2);
  def main(args: Array[String]) {
    map.foreach(kv=>{
      println(kv+" "+kv._1+" "+kv._2)//这里的kv是集合m1的每一个元素,它是一组键值对,在Scala中是一个元组,所以要取得每一个元素的键和值可以使用元组的取值方法,kv._1获得kv的键,kv._2获得kv的值。
    })//其结果为(a,1) a 1\n(b,2) b 2
  }
}
  • keys迭代器
object TestCollection {
  var map = Map[String, Int]("a" -> 1, "b" -> 2);
  def main(args: Array[String]): Unit = {
    map.keys.foreach(k => println(map(k)))//map.keys获得map的所有keys,返回一个迭代器;然后可以使用foreach遍历,也可以在通过键获取值。
  }
}

五、Scala伴生对象

1. 伴生对象的概念

所谓伴生,就是在语言层面上,把static成员和非static成员用不同的表达方式,class(非静态成员)和object(静态成员),但双方具有相同的包名和命名(class_name和object_name可以完全一样),编译器会把他们编译到一起。编译会生成.class文件,编译时会把名称相同的class非静态的和object静态的编译到一起。

2. Object&Class

  • 案例一
class Test{
  var field = "field" //类的属性
  def doSomeThing = println("do something")//类的方法,调用需要new 对象后才可以调用
}

object Test{
  val a = "a string" //伴生对象的属性
  def printAString = println(a)//这个方法是静态的,可以使用Test.printString来调用。
}

编译这个文件,同样生成两个class,一个TEST.class和一个Test$.class,这个Test$.class叫做虚构类。

  • 案例二(静态方法和属性)
class TestObject {
  val str = "Good!"
  def func() = {
    println("Hello World!");
  }
}

object TestObject {
  val str= 100;
  val single = new TestObject();
  def func() = {//定义在object里是静态方法
    println("Hello Scala!");
  }
  /**
    * main函数是static的,main函数如果定义在class中会当做普通函数,函数名为main而已。
    */
  def main(args: Array[String]) {
    //创建class的实例需要用new关键字
    val t1 = new TestObject();
    println(t1.str);//调用实例的str属性
    t1.func();//调用实例的func01函数

    TestObject.func();//Object名.静态方法名。
    println(TestObject.str);//Object名.静态属性名。
  }
}<br />

执行结果如下:

Good!
Hello World!
Hello Scala!
100
  • 案例三(构造器)
class TestConstructor(val a: Int, val b: Int) {//class类后面的小括号,是默认的构造器
  var x = a;//把参数a和b赋给变量
  var y = b;

  def this(xArg: Int) { //this也是构造器,在方法体里面要调用默认的构造器。
    this(xArg, 123);
    println("I'm this constructor");
  }
}

object TestConstructor {//这个对象是伴生着这个类出来的,所以叫伴生对象
  def main(args: Array[String]) {
    val p1 = new TestConstructor(321);//使用 this(xArg: Int)构造器
    println(p1.x)//321
    println(p1.y)//123
    val p2 = new TestConstructor(222, 333);//使用Point(val x: Int, val y: Int)构造器
    println(p2.x)//222
    println(p2.y)//333
  }
}

六、Scala trait

trait可以认为是一种特性,但是不等同于Java中的接口,因为Java接口中没有实现的方法,train可以有实现了的方法(方法体),trait的作用在于提取封装共性,供各种类型的类共同使用。用法详见下面例子:

trait Listen {//和java的接口很像,但是不能new trait名。
  val name: String //定义变量name
  def listen() = {
    println( name + " is listening")
  }
}
trait Read {
  val name: String
  def read() = {
    println(name + " is reading")
  }
}
trait Speak {
  val name: String
  def speak() = {
    println(name + " is speaking.")
  }
}
class Human(val name: String) {//默认构造器需要一个属性:名字
  def speak() = {//方法
    println("Look, "+name + " is speaking.")
  }
}
class Animal(val name: String) {}//创建一个动物类,构造器也是一个参数名字。
//extends继承Animal类。Dog(override val name: String),重写name;with关键字可以加上trait特性。和Java中的继承类,实现接口类似。
class Dog(override val name: String) extends Animal(name: String) with Speak with Listen with Read {
  //重写一个方法只需在方法def前面加关键字override。
  override def toString(): String = "Hello, My name is " + name + "! "
}
object TestTrait {
  def main(args: Array[String]) {
    //创建Human类的对象,命名为张三。hi调用Human类的speak方法。
    val h1 = new Human("张三")
    h1.speak()
    //创建Dog对象,调用了trait中listen方法和speak方法。
    val dog = new Dog("八公")
    dog.listen()
    dog.speak()
    //调用Dog类重写了的toString方法。
    println(dog)
  }
}

相关推荐

kekeromer / 0评论 2020-04-30