a、类型参数化

Scala 的类型参数化是指在定义类、函数时,参数的数据类型并不明确,需要在创建具体的实例或调用函数时才可以确定,这时,可以用一个占位符(通常为A ~ Z中的单个字符)来替代,这类似于java的泛型。如下:

def position[A](xs: List[A], value: A): Int = {xs.indexOf(value)
}

这里的A是表示某种数据类型,它是在调用时指定的。调用方式如下:

scala> val xs = List("one", "two", "three")
xs: List[java.lang.String] = List(one, two, three)
scala> position(xs, "two")
res2: Int = 1
scala> val ys = List(20, 30, 40)
ys: List[Int] = List(20, 30, 40)
scala> position(ys, 40)
res6: Int = 2
scala> position[Int](ys, 300)
res11: Int = -1

尽管最后一个调用明确指明了参数类型为Int,但它是可选的,具体的类型是由Scala的类型推断(type inference)机制确定的
b、类型的协变、逆变

类型的协变和逆变是用来解决类型参数化的泛化问题(继承关系)。

对于一个带类型参数化的类型,比如List[T],如果类型A及其子类型B,满足List[B]也符合List[A]的子类型,那么就称为协变(covariance),如果List[A]是List[B]的子类型,那么就称为逆变(contravariance)。

例如,B是A的子类:

协变: B -> A,List[B] -> List[A]

逆变:B -> A,List[A] -> List[B]

如果一个类型支持协变或逆变,那么这个类型称为可变的(variance),否则是不可变的(invariant)。

在java中,泛型都是不可变的,例如List<String>不是List<Object>的子类,Java并不支持声明点变型(declaration-site variance,即在定义一个类型时声明它为可变型,也称definition-site),而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如:

trait List[+T] // 在类型定义时(declaration-site)声明为协变 

这样就可以把List[String]作为List[Any]的子类型。不过Java支持使用点变型(use-site variance),所谓“使用点“,也就是在声明变量时:

List<? extends Object> list = new ArrayList<String>();

scala为了兼容java泛型通配符的形式,引入存在类型(existential type),也支持了使用点变型(use-site variance)

scala> val a : List[_ <: Any] = List[String]("A")
a: List[_] = List(A)

要注意variance并不会被继承,父类声明为variance,子类如果想要保持,仍需要声明,如下:

scala> trait A[+T]
scala> class C[T] extends A[T]  // C是invariant的
scala> class X; class Y extends X;
scala> val t:C[X] = new C[Y]
<console>:11: error: type mismatch; found   : C[Y]required: C[X]
Note: Y <: X, but class C is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)

必须也对C声明为协变的才行:

scala> class C[+T] extends A[T]scala> val t:C[X] = new C[Y]
t: C[X] = C@6a079142

c、类型的边界:上边界、下边界

上边界:java泛型中,表示某个类型是Test类型的方式为:

<T extends Test>
或使用通配符的方式:
<? extends Test>

这种形式叫做上边界(upper bounds),同样Scala中的类型参数化也有上边界的概念:

[T <: Test]
或用通配符的方式为:
[_ <: Test]

下边界:java泛型中,用来表示某个类型是Test类的父类的方式为:

<T super Test>
或使用通配符:
<? supper Test>

这种形式称作下边界(lower bounds),同样的意思在Scala中的写法为:

[T >: Test]
或者使用通配符:
[_ >: Test]

Scala的上边界和下边界与Java基本一致,不过对较复杂的情况,多重边界,有一些差异:

Java中T是A和B的子类型,称为多重边界,如:<T extends A & B>
Scala里对上界和下界不能有多个,不过变通的做法是使用复合类型(compund type):[T <: A with B]

而对于lower bounds,在java里则不支持multiple bounds的形式:

<T super A & B> //java不支持

Scala里对复合类型同样可以使用lower bound

[T >: A with B]

因为Scala里对于 A with B相当于 (A with B),仍看成一个类型,详见复合类型。
d、高阶函数

高阶函数是在调用时,把一个函数作为参数或返回一个函数,就认为这样的函数为高阶函数。常用高阶函数为map、filter、reduce等。如下:

def addOne(num: Int) = {def ++ = (x:Int) => x + 1++(num)
}
scala> List(1, 2, 3) map addOne
res15: List[Int] = List(2, 3, 4)

f、创建函数对象

函数对象是一个对象,可以把它当做一个函数使用,和普通函数不同的是,函数对象必须要定义一个具体的apply()方法,在使用该函数对象时,实际是调用的apply方法。如下例:

object foldl {def apply[A, B](xs: Traversable[A], defaultValue: B)(op: (B, A) => B) = (defaultValue /: xs)(op)
}

对象fold1为函数对象,它的apply()方法带有两个参数,一个为A类型的集合类型参数,另一个为B参数类型的参数,使用方式为:

scala> foldl(List("1", "2", "3"), "0") { _ + _ }
res0: java.lang.String = 0123
scala> foldl(IndexedSeq("1", "2", "3"), "0") { _ + _ }
res24: java.lang.String = 0123
scala> foldl(Set("1", "2", "3"), "0") { _ + _ }
res25: java.lang.String = 0123

实际上,<object>(<arguments>)为<object>.apply(<arguments>)的 语法糖,使用fold1对象时,实际是调用的fold1对象的apply()方法。

g、Scala的集合结构

Scala中的集合都分布在scala.collection包及其子包中,其中scala.collection.mutable包中定义的是可变集合,对可变集合的操作可能会有副作用,即在集合上做的新增、修改、删除操作,都将改变原集合的状态。而scala.collection.immuable包中定义的为不可变集合,集合的状态不会改变,在集合上的操作(增、删、改),都将返回一个新的集合。
另外,定义在scala.collection包下的集合,既是可变的,也是不可变的。例如scala.collection.Map[A, +B]是collection.mutable.Map[A, B]和collection.immutable.Map[A, +B]的超类。通常scala.collection包下的根集合,定义了同不可变集合一样的接口,并且可变集合可以在不可变集合上添加额外的方法,改变不可变集合的状态。如:

scala> val mapping: collection.Map[String, String] = Map("Ron" -> "admin","Sam" -> "Analyst")
mapping: scala.collection.Map[String,String] =Map(Ron -> admin, Sam -> Analyst)scala> val mapping: collection.Map[String, String] = collection.mutable.Map("Ron" -> "admin", "Sam" -> "Analyst")
mapping: scala.collection.Map[String,String] = Map(Sam -> Analyst, Ron ->admin)

如果不明确指出导入可变集合,Scala默认自动导入不可变集合。

scala.collection.immutable包下的所有类,如下图:

scala.collection.mutable包下的所有类,如下图:

h、常用集合的使用方法

详见:http://blog.csdn.net/qiruiduni/article/details/46788797

i、使用Option ,而不是 Null

相信,java程序员对null的处理是挺痛苦的,如果引用变量是null,将会抛出NullPointerException异常,为避免这种情况,通常要做非空检查,这将是代码变得难以阅读。

Scala采用Option来解决这种问题。Option表示可选值,可以把它看做一个容器(就像List集合),它是一个抽象类,并且定义了两个子类scala.Some(表示一种存在的值) 和scala.None(表示一种不存在的值),它的实例为要么为scala.Some,要么为scala.None。

详细见:http://blog.csdn.net/qiruiduni/article/details/46792387

j、使用懒惰的集合(lazy collection):View和Stream

为了理解lazy collection,先来看下与之相对的strict collections(严格的集合),通常,strict collection会急切的计算它元素。如下例:

scala> List(1, 2, 3, 4, 5).map( _ + 1).head
res43: Int = 2

上例中,为List集合中的每个元素加1,并且仅返回了List的头元素,这段代码的问题是仅List的head元素通过map函数输出,剩下的元素扔需要处理,进行加1的操作,为了更清晰,将上面的代码片段拆分为如下:

scala> val newList = List(1, 2, 3, 4, 5).map( _ + 1)
newList: List[Int] = List(2, 3, 4, 5, 6)
scala> newList.head
res44: Int = 2

对于这种情况,数据量小时,还没什么问题,如果数据量大时,就会占用大量的资源。

Scala提供了view和stream两种方式,可以创建“按需”集合,即延迟计算,在需要的时候才真正计算,这将大大提升性能,特别是在比较耗时的操作。

如上例改为使用view的方式:

scala> List(1, 2, 3, 4, 5).view.map( _ + 1).head
res46: Int = 2

这种情况,在调用map函数时,将产生一个没有计算加1的视图,加1的计算被延迟直到调用head。

使用Stream

Scala的Stream像一个惰性的List,它的元素分为两部分:头部(head)和尾部(tail),尾部中的元素不会被计算直到需要时。因为Stream混入了LinearSeq,所以可以使用List集合中的所有方法。

下例为使用Stream实现的一个Fibonacci函数:

val fib: Stream[Int] = Stream.cons(0, Stream.cons(1, fib.zip(fib.tail).map(t => t._1 + t._2)))

k、并行集合(parallel collections)
Scala的并行集合,采用先拆分后组合的方式,拆分将并行集合拆分为小的Iterable集合,直到达到定义的阈值停止拆分,然后创建一组任务(task),用于并行执行已拆分好的Iterable集合。创建任务是通过Fork/Join框架实现的,Fork/Join框架计算出可用于执行操作的CPU数量,然后使用线程去执行这些任务。最后,将这些任务的输出组合成最终的结果。见下例:

scala> import scala.collection.parallel.immutable._
import scala.collection.parallel.immutable._
<pre name="code" class="html">scala> ParVector(10, 20, 30, 40, 50, 60, 70, 80, 90).map {x =>
println(Thread.currentThread.getName); x / 2 }

ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-0
ForkJoinPool-1-worker-0
res2: scala.collection.parallel.immutable.ParVector[Int] = ParVector(5, 10,
15, 20, 25, 30, 35, 40, 45)

配置并行集合

在Scala中,TaskSupport负责调度并行集合的操作,主要负责追踪线程池、负载均衡及任务的调度。Scala提供了几种TaskSupport的实现:

  • ForkJoinTaskSupport:这将使用fork-join线程池,适用于JVM1.6。
  • ThreadPoolTaskSupport:比ForkJoinTaskSupport低效,它使用普通的线程池执行任务。
  • ExecutionContextTaskSupport:这是并行集合默认的TaskSupport,它的底层也是使用fork-join线程池。
另外,还可以改变并行集合的TaskSupport,如下:
import scala.collection.parallel._
val pv = immutable.ParVector(1, 2, 3)
pv.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(4))

这种情况,tasksupport改为使用了4个工作线程的ForkJoinTask。

需要注意的是,并行集合并不能保证按顺序执行的操作。如map操作,它可以很好的执行,因为他并不依赖于集合中元素的顺序,而foldLeft并不适用于并行集合,因为集合中的元素需要按顺序处理。如下例:
scala> ParVector(10, 20, 30, 40, 50, 60, 70, 80, 90).foldLeft(0) {(a,x) =>
println(Thread.currentThread.getName); a + x }
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
res3: Int = 450

这种情况,仅有一个线程在执行。

并行集合的继承关系图

Scala 的数据结构相关推荐

  1. rdd转换成java数据结构_Spark RDD转换成其他数据结构

    在Spark推荐系统编程中,一般都是通过文件加载成RDD: //在这里默认 (userId, itemId, preference) val fields = sparkContext.textFil ...

  2. Spark快速大数据分析——Scala语言基础(壹)

    Spark快速大数据分析--Scala语言基础(壹) 文章目录 Spark快速大数据分析--Scala语言基础(壹) 前记 Scala的历史 环境搭建: 1.SBT构建工具和REPL: 2.使用IDE ...

  3. scala基础(一)

    Scala 编程 -Scala基础 1.课程目标 安装Scala编译和运行环境 熟悉Scala基本语法以及函数式编程 熟练掌握Scala数据结构使用以及集合方法操作 2.Scala基础 2.1Scal ...

  4. 给Android程序员的一些面试建议,附带学习经验

    我,来自大山. 我,不甘平凡. 笔者80后,出生在江西一个偏远的山村.虽然出生时已经不是那个温饱都是问题的年代,但是也谈不上有个幸福的童年.家里很穷.幼儿园并没有读,因为家里觉得花那个钱没有必要,小学 ...

  5. vhdl语言入门_初学Chisel语言,看这篇就够了:最方便简洁的入门资料整理

    声明:本文是我一个很优秀的学生总结的,放出来供广大chisel语言爱好者参考. Chisel(Constructing Hardware In a Scala Embedded Language)是U ...

  6. Spark复习大纲,有需要的小伙伴可以点个赞找我私信

    REVIEW COVER: chapt1-chapt6 QUESTION TYPE: 选择,填空,简答,阅读程序(写结果),编程题(代码补全) Chap1 Hadoop 生态系统的组件 Hadoop除 ...

  7. 数据分析从零到精通第二课 Hive和Spark入门

    03 离线利器:大数据离线处理工具 Hive 的常用技巧 今天为你介绍数据分析师最常用的数据处理工具 Hive 的一些使用技巧.这些技巧我们在工作中使用得比较频繁,如果运用得当,将为我们省去不少时间精 ...

  8. 太牛了!我的阿里春招之路分享,值得收藏!

    前言 首先声明,楼主不是什么大牛,没有多牛逼的技术,只是公司扩招团队,有幸作为技术面试官,面试了这么多人之后的感想,希望对大家有一点点帮助. 为什么想去字节跳动 实际上,这次的工作变动并不在我计划中. ...

  9. 淘汰了80%的Android面试者,搞懂这些直接来阿里入职

    2021年1月4日,我终于结束了这两个月以来收到的最好的一个公司的视频面试,短短15分钟,我们双方就再无话题了.我是觉得我不够优秀,配不上这个岗位.面试官可能觉得已经看透我了. 让我回顾一下这稍纵即逝 ...

最新文章

  1. 小波矩特征提取matlab代码
  2. AssetBundle——外部加载资源Asset
  3. Kubernetes 与 OpenYurt 无缝转换(命令式)
  4. Hadoop安装过程
  5. 汪子熙微信公众号的写作计划
  6. 腾讯视频中如何把视频进行收藏
  7. 利用Octave解线性方程组
  8. php携程 线程,携程api开发解决方法
  9. 在eclipse中如何搭建ssh框架
  10. LinkedHashMap入门
  11. 学会善于总结,善于表达
  12. 计算 m 的 n 次幂
  13. adb无线连接控制android手机
  14. 10- 天猫用户复购预测 (机器学习集成算法) (项目十) *
  15. 一小时学会使用Springboot整合沙箱环境支付宝支付(附源码)
  16. [数据结构]头插法与尾插法
  17. ant-design vue input通过那个事件可以获得输入框变化的值_VUE使用百度地图教程
  18. P1618 三连击(升级版)【全排列next_permutation】
  19. 2018年全国多校算法寒假训练营练习比赛(第五场)F-The Biggest Water Problem
  20. 数据结构与算法————无向图

热门文章

  1. 电子信息毕设分享 51单片机题目项目汇总 - 100例
  2. python连接并简单操作SQLserver数据库
  3. 史上最新 PHP 调用 jar包教程
  4. Java学习-14 CSS与CSS3美化页面及网页布局
  5. RSK<->以太坊 trusted bridge
  6. 可视化大屏设计尺寸_大屏可视化设计尺寸高级指南
  7. android 输入法框架,Android输入系统(2)——输入系统框架(Android+Linux)
  8. Spring Boot核心原理-自动配置
  9. VoxEdit 主题创作大赛:将 90 年代的复古元素带入 Web3
  10. 【信息收集】第一章 域名信息收集