Scala是什么?

Scala是一种基于函数式编程和面向对象的高级语言。它开发了Spark等大型应用。它和Java有效集成,底层也是支持JVM的。

它有六大特性:

无缝JAVA互操作

Scala在JVM上运行,因此Java和Scala堆栈可以自由混合以实现完全无缝的集成。

类型推断

根据变量自动推断变量类型,我们只需要定义var(可修改)和val(不可修改)即可。

并发与分配

可以对集合数据进行并行操作,有效支持了集群。

特质

将Java风格接口的灵活性和类的功能结合在一起。提供了多重继承。

模式匹配

类似于Java的swatch...case...,但可以同时匹配类型和类型值。

高阶函数

可以将函数作为参数进行传递。

Scala由谁发起?

Scala是由 Martin Odersky 发起,下面是有关他的一些简介。

Martin是EPFL(瑞士领先的技术大学)编程研究组的教授。他在整个职业生涯中一直不断追求着一个目标:让写程序这样一个基础工作变得高效、简单、且令人愉悦。他可能比世界上任何一个人写过更多的Java和Scala代码。他编写了javac,这是大部分Java程序员所使用的编译器。他也编写了Scala编译器scalac,可谓是Scala社区飞速发展的基石。他著有《Programming in Scala》一书,是最畅销的Scala书籍。他曾经就职于IBM研究院、耶鲁大学、卡尔斯鲁厄大学以及南澳大利亚大学。在此之前,他在瑞士苏黎世联邦理工学院追随Pascal创始人Niklaus Wirth学习,并于1989年获得博士学位。

Scala环境准备

安装

  1. 确保您具有Java 8 JDK(也称为1.8)

    • javac -version在命令行上运行,并确保您看到 javac 1.8.___
    • 如果您没有版本1.8或更高版本,请安装JDK
  2. 接下来,下载并安装IntelliJ Community Edition
  3. 然后,在启动IntelliJ之后,您可以按照有关如何安装IntelliJ插件的说明下载并安装Scala插件 (在插件菜单中搜索“ Scala”)。

创建项目时,我们将安装最新版本的Scala。注意:如果要打开现有的Scala项目,则可以 在启动IntelliJ时单击“ 打开”

创建项目

  1. 打开IntelliJ并单击File => New => Project
  2. 在左侧面板上,选择Scala。在右侧面板上,选择“ IDEA”。
  3. 将该项目命名为HelloWorld
  4. 假设这是您第一次使用IntelliJ创建Scala项目,则需要安装Scala SDK。在Scala SDK字段的右侧,单击“ 创建”按钮。
  5. 选择最高的版本号(例如2.13.1),然后单击“ 下载”。这可能需要几分钟,但是后续项目可以使用相同的SDK。
  6. 创建SDK后,您将返回“新建项目”窗口,点击完成

在Linux配置Scala环境

如果要在LInux服务器运行基于scala的应用程序,又不想全量打包的话,这个时候需要手动配置下Linux环境。

选择版本进行下载,注意下载以.tgz结尾的。

在服务器创建一个目录,比如/opt/scala,然后将文件上传到scala目录。

配置环境变量:

$ vim /etc/profile

  1. export SCALA_HOME=/opt/scala/scala-2.12.10
  2. PATH=$SCALA_HOME/bin

最后注意要使用source /etc/profile 重新加载配置文件。

验证是否配置成功:

$ scala

  1. Welcome to Scala 2.12.10 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_221).
  2. Type in expressions for evaluation. Or try :help.
  3. scala>

出现类似上面的信息,则证明配置成功了。

Scala之旅

1.基础

1.主方法

主方法是一个程序的入口点。JVM要求一个名为main的主方法,接受一个字符串数组的参数。

通过使用对象,你可以如下所示来定义一个主方法。

  1. def main(args: Array[String]): Unit = {
  2. }
2.表达式

表达式是可计算的语句。

  1. println(2 + 5) //7
3.代码块(Blocks)

你可以组合几个表达式,并且用{}包围起来。我们称之为代码块(block)。

代码块中最后一个表达式的结果,也正是整个块的结果。

  1. println({
  2. val y = 2 * 4
  3. y + 1
  4. })//9
4.函数

函数是带有参数的表达式。

你可以定义一个匿名函数(即没有名字),来返回一个给定整数加一的结果

  1. (x: Int) =>x * 2

=>左边代表参数,右边是一个包含参数的表达式。

  1. var double= (x: Int) =>x * 2 //注意位置
  2. println(double(1))//2

函数可带有多个参数。

  1. val add=(x:Int,y:Int)=>x+y
  2. println(add(1,2))//3

或者不带参数。

  1. var getTheAnswer = () => 11
  2. println(getTheAnswer())//11
5.方法

方法的行为和函数非常类似,但是它们之间还是有一些关键差别。

方法由def进行定义,后面跟方法名,参数,返回值类型,等号和方法体。

  1. def addOne(x: Int) = x + 1
  2. println(addOne(11))

方法可以接受多个参数列表。

  1. def addThenMultiple(x: Int, y: Int)(multiplier: Int) = (x + y) * multiplier
  2. println(addThenMultiple(3, 4)(4))

还可以不带参数。

  1. def getAnswer()="hello"
  2. println(getAnswer())
6.类

可以用class定义一个类,后面跟它的名字和构造参数。

  1. class Person(addRess: String, name: String) {
  2. def say(word: String) = {
  3. println(s"${addRess}的${name}说:${word}")
  4. }
  5. }

调用

  1. val person=new Person("地球","刘星雨")
  2. person.say("我是火星派过来拯救你们的~") //地球的刘星雨说:我是火星派过来拯救你们的~
7.对象

对象是它们自己定义的单实例,你可以看作类的单例。

你可以使用object关键字来定义对象。

  1. object IdFactory {
  2. private var counter = 0
  3. def create() = {
  4. counter += 1
  5. counter
  6. }
  7. }

调用

  1. println(IdFactory.create()) //1
8.特质(Trait)

特质相当于接口。特质包含某些字段和方法类型。可以组合多个特质。

你可以使用trait关键字来定义特质。

  1. trait Say {
  2. def name = "hello"
  3. }
  4. object Test extends Say {
  5. override def name: String = "world"
  6. def main(args: Array[String]): Unit = {
  7. }
  8. }
  9. //其中上面的name方法可以重写,也可以不重写。
9.for循环
  1. for(i<-0 until 10){
  2. println(i)
  3. }

循环0到9

10.Scala调用Java代码

Java代码:

  1. public class Test {
  2. public static void say(){
  3. System.out.println("gogogogo");
  4. }
  5. public void walk(){
  6. System.out.println("走路中...");
  7. }
  8. }

Scala代码:

  1. object Test {
  2. def main(args: Array[String]): Unit = {
  3. val t3=new Test
  4. t3.walk()
  5. //走路中...
  6. }
  7. }

二. 统一类型

1.Scala类型结构

结构图如下:(箭头之间代表继承关系)

  1. val list: List[Any] = List(
  2. "hello",
  3. 123,
  4. 12.11f,
  5. true,
  6. () => "What's happen"
  7. )
2.类型转换

类型转换可以按照下面的方向进行转换:

3.Nothing和Null

Nothing是所有类型的子类型,没有一个值是 Nothing类型的,它的用途之一是非正常终结信号,如抛异常,程序退出或者无限循环。

Null是所有引用类型的子类型,它有一个单例由null所定义。Null主要使得Scala满足和其它JVM语言的 互操作性的。

三.Class

class 类名{实例方法。}

1.类定义

一个最简单的类定义是class+标识符,类名首字母应大写。

  1. class User

但是通常里面都定义方法。

  1. class User{
  2. def say(name:String)={
  3. println("我叫"+name)
  4. }
  5. }
2.构造器

构造器可以通过一个默认值来拥有可选参数。

  1. class User(x: Int = 0, y: Int = 0) {
  2. def defaultPoint = {
  3. println("x=" + x + ",y=" + y)
  4. }
  5. }

调用

  1. val point1=new Point(1,2)
  2. val point2=new Point
  3. point1.defaultPoint //x=1,y=2
  4. point2.defaultPoint //x=0,y=0
3.私有成员Getter和Setter的用法

成员默认是公有的public。使用private访问修饰符可以在类外部隐藏它们。

  1. class Number(val y: Int) {
  2. private var _x = 0
  3. def x = _x
  4. def x_=(newValue: Int) = {
  5. _x = newValue
  6. }
  7. def getNumber = {
  8. println(_x)
  9. }
  10. }

调用

  1. val number = new Number(2)
  2. number.x = 10
  3. number.getNumber

四.Trait(特质)

特质(Traits)用于在类之间共享接口(Interface)和字段(Fields)。它们类似于Java 8的接口。类和对象(Ojbects)可以扩展特质,但是特质不能被实例化,因为特质没有参数。

1.定义一个特质

最简单的特质就是trait关键字+标识符

trait Color

特质也可以定义泛型

  1. trait Color[C] {
  2. def defaultColor(): C
  3. def getColor: C = {
  4. defaultColor()
  5. }
  6. }
2.使用特质
  1. class MyColor extends Color[String] {
  2. override def defaultColor: String = "红色"
  3. override def getColor: String = {
  4. defaultColor
  5. }
  6. }

调用

  1. val color = new MyColor();
  2. println(color.getColor) //红色
3.子类型

凡是需要特质的地方,都可以用子类型来替换。

  1. trait Person {
  2. def name: String
  3. }
  4. class ZhangSan extends Person {
  5. override def name: String = "张三"
  6. }
  7. class LiSi extends Person {
  8. override def name: String = "李四"
  9. }

调用

  1. val zhangSan = new ZhangSan
  2. val liSi = new LiSi
  3. var person = ArrayBuffer.empty[Person]
  4. person.append(zhangSan)
  5. person.append(liSi)
  6. person.foreach(p => println(p.name))

五.Tuple(元组)

在Scala中元组是一个可以容纳不同类型元素的类。元组是不可变的。

当我们从函数返回多个值时,元组会派上用场。

元组可以创建如下:

  1. val ingredient=(12,"hello",1.1):Tuple3[Int,String,Double]

这将创建一个包含Int元素,一个String元素和一个Double元素的元组。

Scala包含一系列元素:Tuple1,Tuple10等,直到Tuple22。因此我们创建一个n个 元素(n位于1~22之间)的元组时,Scala基本上从上述一组实例中实例化一个类,使用组成元素进行参数化。

1.访问元素

使用下划线来访问元组中的元素。'tuple._n'取出了第n个元素。

调用上面的实例

  1. println(ingredient._2) //hello
2.解构元素数组
  1. val ingredient=(12,"hello",1.1)
  2. val(age,word,num)=ingredient
  3. println(age) //12
  4. println(word) //hello

元素数组也可以用于模式匹配。

  1. val personList = List(("张三", 22), ("李四", 24), ("王五", 20))
  1. personList.foreach(p => {
  2. p match {
  3. case ("张三", distance1) => println("我是张三") //我是张三
  4. case p if (p._1 == "李四") => println("我是李四") //我是李四
  5. case _ => println("我是无名氏") //我是无名氏
  6. }
  7. })

或者,在'for'表达式中。

  1. for (x <- personList) {
  2. println(x._1 + "," + x._2)
  3. }

输出:

张三,22

李四,24

王五,20

六.通过混入(mixin)来组合类

当某个特质被用于组合类时,被称为混入。

  1. abstract class A {
  2. val message: String
  3. }
  4. class B extends A {
  5. val message = "hello"
  6. }
  7. trait C extends A {
  8. def upperMessage = message.toUpperCase()
  9. }
  10. class D extends B with C

调用

  1. val d = new D
  2. println(d.message)
  3. println(d.upperMessage)

七.高阶函数

高阶函数是指使用其它函数作为参数、或者返回一个函数作为结果的函数。

最常见的一个例子是scala集合类(collections)的高阶函数map

  1. val listNum = Seq(1, 2, 3)
  2. val addOne = (x: Int) => x + 1
  3. val result=listNum.map(addOne)
  4. result.foreach(println) //2 3 4
1.强制转换方法为函数

你同样可以传入一个方法作为高阶函数的参数,这是因为Scala编译器会将方法强制转换成一个函数。

  1. val listNum = Seq(1, 2, 3)
  2. def addTwo(x:Int)=x+2
  3. val addTwoResult=listNum.map(addTwo)
  4. addTwoResult.foreach(println)
2.接收函数作为参数的函数

有的时候你需要将函数作为参数减少代码冗余。

  1. //定义一个处理方法
  2. private def add(listNum: List[Int], addFunction: Int => Int) = listNum.map(addFunction)
  3. //定义加1计算
  4. def addOne(listNum: List[Int]) = {
  5. add(listNum, x => x + 1)
  6. }
  7. //定义加2计算
  8. def addTwo(listNum: List[Int]) = {
  9. add(listNum, x => x + 2)
  10. }

调用

  1. val listNum = List(1, 2, 3, 4)
  2. val addOneResult = addOne(listNum)
  3. val addTwoResult = addTwo(listNum)
  4. addOneResult.foreach(println) //2 3 4 5
  5. addTwoResult.foreach(println) //3 4 5 6
3.返回函数的参数

有的时候方法需要返回一个函数

  1. //定义一个方法让x和y相加,然后返回一个带z参数的函数。
  2. def add(x: Int, y: Int) = (z: Int) => x + y + z

调用

  1. var addResult = add(1, 2)
  2. var result = addResult(3)
  3. println(result)//6

八.嵌套方法

在Scala中可以嵌套定义方法,例如下面对象提供了一个factorial方法计算给定的阶乘:

  1. def factorial(x: Int): Int = {
  2. def fact(x: Int, accumulator: Int): Int = {
  3. if (x <= 1) accumulator
  4. else fact(x - 1, x * accumulator)
  5. }
  6. fact(x, 1)
  7. }

调用

  1. println("Factorial of 3: " + factorial(3))//6

九.多参数列表

方法可以定义多个参数, 当使用较少的参数列表调用多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。这被称为柯里化

下面是一个例子,在Scala集合 trait TraversableOnce 定义了 foldLeft

  1. def foldLeft[B](z: B)(op: (B, A) => B): B

调用

  1. val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  2. val res = numbers.foldLeft(1)((m,n)=>m*n)
  3. println(res) //3628800
1.单一的函数参数

在某些情况下存在单一的函数参数时,例如上述例子foldLeft中的op,多参数列表可以使得传递匿名函数作为参数的语法更为简洁。如果不使用多参数列表,代码可能像这样:

  1. numbers.foldLeft(0,{(m:Int,n:Int)=>m+n})
2.隐式(IMPLICIT)参数

如果要指定参数列表中的某些参数为隐式(implicit),应该使用多参数列表。例如:

  1. def execute(arg: Int)(implicit ec: ExecutionContext) = ???

十.Case Class(案例类)

案例类(Case Class)和普通类差不多,也是通过new创建,但是案例类非常适合不可变的数据。

1.定义一个案例类

一个最简单的案例类由关键字case class

  1. case class Test()

调用

  1. val test1 = Test//直接调用
  2. val test2 = new Test//使用new调用

可以看到方法是可以直接不通过new进行调用的,这是因为case类默认有个apply方法来负责创建对象。

2.比较
  1. case class Test1(x: Int, y: Int) //定义一个case类
  2. class Test2(x: Int, y: Int) //定义一个普通类

调用

  1. val test1_1 = Test1(2, 4)
  2. val test1_2 = Test1(2, 4)
  3. if (test1_1 == test1_2) println("两个类数据相同") else println("两个类是数据不相同")//两个类数据相同
  4. val test2_1 = new Test2(2, 4)
  5. val test2_2 = new Test2(2, 4)
  6. if (test2_1 == test2_2) println("两个类数据相同") else println("两个类是数据不相同")//两个类是数据不相同

通过演示可以看到,case 类比较的是具体的值,而普通类比较的是引用。

3.拷贝
  1. case class Test(name: String, age: Int)

调用

  1. val test = Test("张三", 18)
  2. val test2 = test.copy(name = "李四")//将test值copy给test2,并覆盖name
  3. println(test2.name)//李四
  4. println(test2.age)//18

十一.模式匹配

模式匹配是校验某个值(value)是否匹配一个模式的机制,一个成功的匹配同时会将值解构成其组成成分。它是Java中switch的升级版,同时会代替一系列if/else语句。

1.语法

一个模式匹配包含特定的值,match关键字,以及至少一个case

  1. var x: Int =11
  2. def matchTest = x match {
  3. case 1 => "one"
  4. case 2 => "two"
  5. case 3 => "three"
  6. case _=>"no"
  7. }
  8. println(matchTest)//no,如果不定义_=>则匹配不到11会报异常。
2.案例类(case classses)的匹配

案例类非常适用于模式匹配。

  1. abstract class Person
  2. case class ZhangSan(msg: String) extends Person //继承Person
  3. case class LiSi(msg: String) extends Person //继承Person

调用

  1. def talk(person: Person) = person match { //
  2. case ZhangSan(msg) => "张三说" + msg
  3. case LiSi(message) => "李四说" + message
  4. }
  5. val zhangSan = new ZhangSan("我吃饭了")
  6. val lisi = new LiSi("一起去打羽毛球")
  7. println(talk(zhangSan))
  8. println(talk(lisi))
3.模式守卫(Pattern gaurds)

为了让匹配更具体,可以加上模式守卫,也就是在case 后面加上if。例如还是上面案例经过简单改造:

  1. def talk(person: Person) = person match { //
  2. case ZhangSan(msg,isShow) if isShow => "张三说" + msg //加了个控制是否显示的参数
  3. case LiSi(message,isShow) if isShow => "李四说" + message//加了个控制是否显示的参数
  4. case other=>"我什么也没说"
  5. }
  1. val zhangSan = new ZhangSan("我吃饭了",false)
  2. val lisi = new LiSi("一起去打羽毛球",true)
  3. println(talk(zhangSan))//我什么也没说
  4. println(talk(lisi))//李四说一起去打羽毛球
4.仅匹配类型

上面的匹配的实例,也可以仅仅匹配类型。

  1. case class ZhangSan(msg: String) extends Person //继承Person
  2. case class LiSi(msg: String) extends Person //继承Person
  1. def talk(person: Person) = person match { //
  2. case p: ZhangSan => "张三说" + p.msg //仅匹配ZhangSan类型
  3. case p: LiSi => "李四说" + p.msg //仅匹配LiSi类型
  4. }
  5. val zhangSan = new ZhangSan("我吃饭了")
  6. val lisi = new LiSi("一起去打羽毛球")
  7. println(talk(zhangSan))//张三说我吃饭了
  8. println(talk(lisi))//李四说一起去打羽毛球
5.密封类

特质(trait)和类(class)都可以用sealed标记为密封的,这意味着所有子类必须和定义类在相同文件中,从而保证所有子类都是已知的。

  1. sealed abstract class Person

十二.Object

Object属于一种单例对象,在Scala中是没有Static类的,Object充当了静态类,里面的方法能直接通过Object名称.方法名调用。

Main方法是定义在Object里面的,Class里面是不能定义Main的,只能进行编译,然后被调用。

object MyObj{静态方法。}

1.定义一个Object对象
  1. object Test {
  2. def googleEmial(userName: String): String = s"${userName}@Google.com"
  3. }
  1. object Test2 {
  2. def main(args: Array[String]): Unit = {
  3. println(googleEmial("zhangsan"))//zhangsan@Google.com
  4. }
  5. }
2.伴生对象

伴生类是存在同名的两个Object和Class,它们的私有成员可以共享。下面举例:

  1. object Person {
  2. private def say(userName: String) = println(userName)
  3. }
  4. class Person() {
  5. var userName = "张三 "
  6. val result=Person.say(userName)//这里就可以调用私有方法打印了
  7. }

调用

  1. val person=new Person
  2. person.result

十三.正则表达式模式

正则表达式是找出数据中指定的字符串。.r方法可使任意字符串变成一个正则表达式。

  1. val numberPartten = "[0-9]".r
  2. def numP(str: String) = numberPartten.findFirstMatchIn(str) match {
  3. case Some(_) => println("ok")
  4. case None => println("no")
  5. }
  6. numP("12d") //ok ,因为带有数字中的0~9
  7. numP(str = "d") //no,因为

十四.提取器对象

提取器是一个包含unapply方法的单利对象,apply方法就像一个构造器,接收参数并返回方法,反之unapply方法接收一个实例对象然后返回最初创建它所用的参数。提取器常用在模式匹配和偏函数中。

  1. import scala.util.Random
  2. object CustomerID {
  3. def apply(name: String) = s"$name--${Random.nextLong}"
  4. def unapply(customerID: String): Option[String] = {
  5. val stringArray: Array[String] = customerID.split("--")
  6. if (stringArray.tail.nonEmpty) Some(stringArray.head) else None
  7. }
  8. }
  9. val customer1ID = CustomerID("Sukyoung") // Sukyoung--23098234908
  10. customer1ID match {
  11. case CustomerID(name) => println(name) // prints Sukyoung
  12. case _ => println("Could not extract a CustomerID")
  13. }

十五.for表达式

Scala是一个轻量级的标记方式用来表示列的推导。推导使用形势为for(enumerator) yield e 的for表达式。此处 enumerators 指一组以分号分割的枚举器。一个enumerator要么是 一个产生新变量的枚举器,要么是一个过略器。for表达式在枚举器每一次绑定中都会计算e值,并在循环结束后返回这些组成的序列。

  1. case class Person(name: String, age: Int)

调用

  1. val personList = List(Person("张三", 11),
  2. Person("李四", 20),
  3. Person("赵六", 66)
  4. )
  5. val filterPerson = for (p <- personList if (p.age > 18))
  6. yield p
  7. filterPerson.foreach(x=>println("name:"+x.name+",age:"+x.age))// name:李四,age:20 name:赵六,age:66

十六.泛型类

泛型类是可以使用类型参数的类。

1.定义一个泛型类
  1. class Person[T] {
  2. def get(t: T) = t
  3. }
2.使用
  1. val person1 = new Person[String]
  2. val result1 = person1.get("hello")
  3. println(result1)//hello
  4. val person2=new Person[Int]
  5. val result2=person2.get(2)
  6. println(result2)//2

十七.型变

型变是复杂类型的子类型关系与其组件类型的子类型关系的相关性。 Scala支持泛型类的类型参数的型变注释,允许它们是协变的,逆变的,或在没有使用注释的情况下是不变的。 在类型系统中使用型变允许我们在复杂类型之间建立直观的连接,而缺乏型变则会限制类抽象的重用性。

1.协变

子类型转换成父类型就叫协变。

使用注释 +A,可以使一个泛型类的类型参数 A 成为协变。 对于某些类 class List[+A],使 A 成为协变意味着对于两种类型 AB,如果 AB 的子类型,那么 List[A] 就是 List[B] 的子类型。 这允许我们使用泛型来创建非常有用和直观的子类型关系。

考虑以下简单的类结构:

  1. abstract class Animal {
  2. def name: String
  3. }
  4. case class Cat(name: String) extends Animal
  5. case class Dog(name: String) extends Animal
  1. object CovarianceTest extends App {
  2. def printAnimalNames(animals: List[Animal]): Unit = {
  3. animals.foreach { animal =>
  4. println(animal.name)
  5. }
  6. }
  7. val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
  8. val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
  9. printAnimalNames(cats)
  10. // Whiskers
  11. // Tom
  12. printAnimalNames(dogs)
  13. // Fido
  14. // Rex
  15. }
2.逆变

父类型转换成子类型就叫逆变。

通过使用注释 -A,可以使一个泛型类的类型参数 A 成为逆变。 与协变类似,这会在类及其类型参数之间创建一个子类型关系,但其作用与协变完全相反。 也就是说,对于某个类 class Writer[-A] ,使 A 逆变意味着对于两种类型 AB,如果 AB 的子类型,那么 Writer[B]Writer[A] 的子类型。

考虑在下例中使用上面定义的类 CatDogAnimal

  1. abstract class Printer[-A] {
  2. def print(value: A): Unit
  3. }
  1. class AnimalPrinter extends Printer[Animal] {
  2. def print(animal: Animal): Unit =
  3. println("The animal's name is: " + animal.name)
  4. }
  5. class CatPrinter extends Printer[Cat] {
  6. def print(cat: Cat): Unit =
  7. println("The cat's name is: " + cat.name)
  8. }
  9. object ContravarianceTest extends App {
  10. val myCat: Cat = Cat("Boots")
  11. def printMyCat(printer: Printer[Cat]): Unit = {
  12. printer.print(myCat)
  13. }
  14. val catPrinter: Printer[Cat] = new CatPrinter
  15. val animalPrinter: Printer[Animal] = new AnimalPrinter
  16. printMyCat(catPrinter)
  17. printMyCat(animalPrinter)
  18. }

这个程序的输出如下:

  1. The cat's name is: Boots
  2. The animal's name is: Boots
3.不变

子类型和父类型不能互相转换叫不变。

  1. class Container[A](value: A) {
  2. private var _value: A = value
  3. def getValue: A = _value
  4. def setValue(value: A): Unit = {
  5. _value = value
  6. }
  7. }
  1. val catContainer: Container[Cat] = new Container(Cat("Felix"))
  2. val animalContainer: Container[Animal] = catContainer
  3. animalContainer.setValue(Dog("Spot"))
  4. val cat: Cat = catContainer.getValue // 糟糕,我们最终会将一只狗作为值分配给一只猫

十八.类型上界

在Scala中,类型参数和抽象类型都可以有个类型约束。这种类型边界在限制取值的同时,还能展露更多信息,比如T<:A,这样声明的类型上界T应该是A的子类。

  1. abstract class Person {//定义父类
  2. def name: String
  3. }
  4. abstract class On extends Person {}
  5. class ZhangSan extends On {
  6. override def name: String = "张三"
  7. }
  8. class LiSi extends On {
  9. override def name: String = "李四"
  10. }
  11. class WangWu extends Person {//这个继承的是Person,注意和以上继承的区别
  12. override def name: String = "王五"
  13. }
  14. class OnContainer[oc <: On](o: On) {//限制参数范围只能是On的子类
  15. }

调用

  1. val zhangSan = new OnContainer[ZhangSan](new ZhangSan)
  2. val liSi = new OnContainer[LiSi](new LiSi)
  3. var wangWu = new OnContainer[WangWu](new WangWu) //超出限定,所以会报错

十九.类型下界

类型上界 将类型限制为另一种类型的子类型,而 类型下界 将类型声明为另一种类型的超类型。 术语 B >: A 表示类型参数 B 或抽象类型 B 是类型 A 的超类型。 在大多数情况下,A 将是类的类型参数,而 B 将是方法的类型参数。

二十.内部类

在Scala中一个类可以作为另一个类的内部成员。

  1. class Person() {
  2. class LiSi() {
  3. def name: String = "我是李四"
  4. }
  5. class WangWu() {
  6. def name = "我是王五"
  7. }
  8. }

调用

  1. val person = new Person
  2. val liSi: person.LiSi = new person.LiSi
  3. val wangWu: person.WangWu = new person.WangWu
  4. println(liSi.name)
  5. println(wangWu.name)

二十一.抽象类型(T)

特质(trait)和抽象类(abstract class)可以包含一个抽象类成员,意味着实际类型可由具体实现来确定。

  1. trait Person {
  2. type T
  3. val elements: T
  4. }
  5. class ZhangSan extends Person {
  6. override type T = Int
  7. override val elements: Int = 11
  8. }

调用

  1. val zhangSan = new ZhangSan
  2. println(zhangSan.elements)//11

二十二.复合类型

有时要表名一个类型是其它类型的子类型。这Scala中,这也可以表示成复合类型,及多个类型的交集。

假设我们有两个特质 CloneableResetable

  1. trait Cloneable extends java.lang.Cloneable {
  2. override def clone(): Cloneable = {
  3. super.clone().asInstanceOf[Cloneable]
  4. }
  5. }
  6. trait Resetable {
  7. def reset: Unit
  8. }

现在假设我们要编写一个方法 cloneAndReset,此方法接受一个对象,克隆它并重置原始对象:

  1. def cloneAndReset(obj: ?): Cloneable = {
  2. val cloned = obj.clone()
  3. obj.reset
  4. cloned
  5. }

这里出现一个问题,参数 obj 的类型是什么。 如果类型是 Cloneable 那么参数对象可以被克隆 clone,但不能重置 reset; 如果类型是 Resetable 我们可以重置 reset 它,但却没有克隆 clone 操作。 为了避免在这种情况下进行类型转换,我们可以将 obj 的类型同时指定为 CloneableResetable。 这种复合类型在 Scala 中写成:Cloneable with Resetable

以下是更新后的方法:

  1. def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { //这里就同时拥有clone和reset了。
  2. //...
  3. }

二十三.自类型

自类型用于声明一个特质必须混入其它特质。

  1. trait Person {
  2. def userName: String
  3. }
  4. trait Cls {
  5. this: Person =>
  6. def say = println(userName)
  7. }
  8. class ZhangSan extends Person with Cls {
  9. override def userName: String = "张三"

调用

  1. val zhangSan = new ZhangSan
  2. zhangSan.say //张三

二十四.隐式参数

自动匹配隐式类型进行传递。

  1. abstract class Monoid[A] {
  2. def add(x: A, y: A): A
  3. def unit: A
  4. }
  5. object ImplicitTest {
  6. implicit val stringMonoid: Monoid[String] = new Monoid[String] {
  7. def add(x: String, y: String): String = x concat y
  8. def unit: String = ""
  9. }
  10. implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
  11. def add(x: Int, y: Int): Int = x + y
  12. def unit: Int = 0
  13. }
  14. def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
  15. if (xs.isEmpty) m.unit
  16. else m.add(xs.head, sum(xs.tail))
  17. def main(args: Array[String]): Unit = {
  18. println(sum(List(1, 2, 3))) // uses IntMonoid implicitly
  19. println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly
  20. }
  21. }

二十五.隐式转换

一个从类型 S 到类型 T 的隐式转换由一个函数类型 S => T 的隐式值来定义,或者由一个可转换成所需值的隐式方法来定义。

隐式转换在两种情况下会用到:

  • 如果一个表达式 e 的类型为 S, 并且类型 S 不符合表达式的期望类型 T
  • 在一个类型为 S 的实例对象 e 中调用 e.m, 如果被调用的 m 并没有在类型 S 中声明。

例如,当调用一个接受 java.lang.Integer 作为参数的 Java 方法时,你完全可以传入一个 scala.Int。那是因为 Predef 包含了以下的隐式转换:

  1. import scala.language.implicitConversions
  2. implicit def int2Integer(x: Int) =
  3. java.lang.Integer.valueOf(x)

二十六.多态方法

Scala可以按值和类型进行参数化。语法和泛型类似。类型参数括在方括号中,而值参数在圆括号中。

  1. def listOfDuplicates[A](x: A, length: Int): List[A] = {
  2. if (length < 1)
  3. Nil
  4. else
  5. x :: listOfDuplicates(x, length - 1)
  6. }
  7. println(listOfDuplicates[Int](3, 4)) // List(3, 3, 3, 3)
  8. println(listOfDuplicates("La", 8)) // List(La, La, La, La, La, La, La, La)

二十七.类型推断

Scala编译器通常可以推断出表达式的类型,因此你不必显示声明它。

1.省略类型
  1. val name1="张三"//省略类型,编译器会根据"张三"自动推断出是String类型
  2. val name2:String="张三"//不省略类型
2.参数
  1. Seq(1,2,3).map(x=>x*2)
3.何时不要依赖类型推断
  • 通常认为,公公访问的 API应该具有显示类型声明以具有可读性
  • 针对重新复制的 情况
  1. var test = "hello"
  2. test = 1//会报错

二十八.运算符

在Scala中运算符即是方法。任何具有单个参数的方法都可以用 中缀运算符(我们平时用的最多的那种。)

1.定义和使用运算符
  1. println(10 + 1)//11
2.优先级
  1. * / %
  2. + -
  3. :
  4. = !
  5. < >
  6. &
  7. ^
  8. |

二十九.传名参数

传名参数仅在被使用时触发实际参数的求值运算。它们与传值参数正好相反。要将一个参数变为传名参数,只需在它的类型前加上=>

  1. def whileLoop(condition: => Boolean)(body: => Unit): Unit =
  2. if (condition) {
  3. body
  4. whileLoop(condition)(body)
  5. }
  6. var i = 2
  7. whileLoop (i > 0) {
  8. println(i)
  9. i -= 1
  10. } // prints 2 1

方法 whileLoop 使用多个参数列表来分别获取循环条件和循环体。 如果 condition 为 true,则执行 body,然后对 whileLoop 进行递归调用。 如果 condition 为 false,则永远不会计算 body,因为我们在 body 的类型前加上了 =>

现在当我们传递 i > 0 作为我们的 condition 并且 println(i); i-= 1 作为 body 时,它表现得像许多语言中的标准 while 循环。

如果参数是计算密集型或长时间运行的代码块,如获取 URL,这种延迟计算参数直到它被使用时才计算的能力可以帮助提高性能。

三十.注解

注解将元信息与定义进行关连。例如,方法之前的注解 @deprecated 会导致编译器在该方法被使用时打印警告信息。

  1. object DeprecationDemo extends App {
  2. @deprecated("deprecation message", "release # which deprecates method")
  3. def hello = "hola"
  4. hello
  5. }
1.确保编码正确性的注解

如果不满足条件,某些注解实际上会导致编译失败。

  1. import scala.annotation.tailrec
  2. def factorial(x: Int): Int = {
  3. @tailrec
  4. def factorialHelper(x: Int, accumulator: Int): Int = {
  5. if (x == 1) accumulator else factorialHelper(x - 1, accumulator * x)
  6. }
  7. factorialHelper(x, 1)
  8. }

方法 factorialHelper 使用注解 @tailrec 确保方法确实是尾递归的。 如果我们将方法 factorialHelper 的实现改为以下内容,它将编译失败:

  1. import scala.annotation.tailrec
  2. def factorial(x: Int): Int = {
  3. @tailrec
  4. def factorialHelper(x: Int): Int = {
  5. if (x == 1) 1 else x * factorialHelper(x - 1)
  6. }
  7. factorialHelper(x)
  8. }

我们将得到一个错误信息 “Recursive call not in tail position”.

2.影响代码生成的注解

@inline 这样的注解会影响生成的代码 。

三十一.默认参数值

scala可以定义默认参数值,这样调用者就可以忽略这些默认值的参数。

  1. def person(name: String = "", age: Int = 18): Unit = {
  2. println(name + "" + age + "岁了")
  3. }

调用

  1. person("张三")//张三18岁了
  2. person("李四", 11)//李四11岁了

三十二.命名参数

当调用方法时,实际名称可以通过其对应形式的参数的名称来标记。

  1. def person(name: String = "", age: Int = 18): Unit = {
  2. println(name + "" + age + "岁了")
  3. }

调用

  1. person(name="王五")//王五18岁了

三十三.包和导入

Scala使用包资源来创建命名空间,从而允许你创建模块化程序。

1.创建包
  1. package users {
  2. package administrators {
  3. class NormalUser
  4. }
  5. package normalusers {
  6. class NormalUser
  7. }
  8. }
2.导入
  1. import endTest._ //导入endTest包下所有成员
  2. import endTest.Test //导入类Test

三十四.包对象

Scala 提供包对象作为在整个包中方便的共享使用的容器。

包对象中可以定义任何内容,而不仅仅是变量和方法。 例如,包对象经常用于保存包级作用域的类型别名和隐式转换。 包对象甚至可以继承 Scala 的类和特质。

按照惯例,包对象的代码通常放在名为 package.scala 的源文件中。

每个包都允许有一个包对象。 在包对象中的任何定义都被认为是包自身的成员。

看下例。 假设有一个类 Fruit 和三个 Fruit 对象在包 gardening.fruits 中;

  1. // in file gardening/fruits/Fruit.scala
  2. package gardening.fruits
  3. case class Fruit(name: String, color: String)
  4. object Apple extends Fruit("Apple", "green")
  5. object Plum extends Fruit("Plum", "blue")
  6. object Banana extends Fruit("Banana", "yellow")

包对象与其他对象类似,这意味着你可以使用继承来构建它们。 例如,一个包对象可能会混入多个特质:

  1. package object fruits extends FruitAliases with FruitHelpers {
  2. // helpers and variables follows here
  3. }

系列传送门

入门大数据---Scala学习的更多相关文章

  1. 入门大数据---Flink学习总括

    第一节 初识 Flink 在数据激增的时代,催生出了一批计算框架.最早期比较流行的有MapReduce,然后有Spark,直到现在越来越多的公司采用Flink处理.Flink相对前两个框架真正做到了高 ...

  2. 大数据Hadoop学习之搭建hadoop平台(2.2)

    关于大数据,一看就懂,一懂就懵. 一.概述 本文介绍如何搭建hadoop分布式集群环境,前面文章已经介绍了如何搭建hadoop单机环境和伪分布式环境,如需要,请参看:大数据Hadoop学习之搭建had ...

  3. 大数据spark学习第一周Scala语言基础

    Scala简单介绍 Scala(Scala Language的简称)语言是一种能够执行于JVM和.Net平台之上的通用编程语言.既可用于大规模应用程序开发,也可用于脚本编程,它由由Martin Ode ...

  4. Spark大数据的学习历程

    Spark主要的编程语言是Scala,选择Scala是因为它的简洁性(Scala可以很方便在交互式下使用)和性能(JVM上的静态强类型语言).Spark支持Java编程,但对于使用Java就没有了Sp ...

  5. 入门大数据---Spark整体复习

    一. Spark简介 1.1 前言 Apache Spark是一个基于内存的计算框架,它是Scala语言开发的,而且提供了一站式解决方案,提供了包括内存计算(Spark Core),流式计算(Spar ...

  6. Java开发者想尝试转行大数据,学习方向建议?

      ​前言 相信很多Java开发者都对大数据有一定的了解,随着大数据时代的到来,也有很多Java程序员想要转行大数据.大数据技术中大多数平台使用的都是Java语言,因此,对于大数据技术的学习来说,Ja ...

  7. 布客·ApacheCN 编程/后端/大数据/人工智能学习资源 2020.11

    公告 我们始终与所有创作者站在一起,为创作自由而战.我们还会提供一切必要的技术支持. 我们全力支持科研开源(DOCX)计划.希望大家了解这个倡议,把这个倡议与自己的兴趣点结合,做点力所能及的事情. 我 ...

  8. 布客&#183;ApacheCN 编程/后端/大数据/人工智能学习资源 2020.9

    公告 ApacheCN 项目的最终目标:五年内备份并翻译 Github 上的所有教程(其实快被我们啃完了,剩下的不多了). 警告各位培训班:对 ApacheCN 宣传文章的举报,也将视为对 Apach ...

  9. 布客&#183;ApacheCN 编程/后端/大数据/人工智能学习资源 2020.7

    公告 我们的群共享文件有备份到 IPFS 的计划,具体时间待定. 我们的机器学习群(915394271)正式改名为财务提升群,望悉知. 请关注我们的公众号"ApacheCN",回复 ...

随机推荐

  1. Shell编程案例:修改运维脚本输出效果

    1. 需求:每日运维检查脚本dailymonitor.sh显示对服务器测试结果,其中命令 zabbix_get -s 192.168.111.21 -p 10050 -k "net.tcp. ...

  2. Chisel3 - Tutorial - Tbl

    https://mp.weixin.qq.com/s/e8vJ8claauBtiuedxYYaJw   实现可以动态索引的表.   参考链接: https://github.com/ucb-bar/c ...

  3. CSS选择器有哪些?哪些属性可以继承?

    CSS选择符: id选择器(#myid). 类选择器(.myclassname). 标签选择器(div, h1, p). 相邻选择器(h1 + p). 子选择器(ul > li). 后代选择器( ...

  4. (Java实现) 删数问题

    删数问题(需知道的数学定理) 给定n位正整数a,去掉其中任意k≤n 个数字后,剩下的数字按原次序排列组成一个新 的正整数.对于给定的n位正整数a和正整数 k,设计一个算法找出剩下数字组成的新数最 小的 ...

  5. Java实现洛谷 P1873 砍树(StreamTokenizer+IO+二分)

    P1873 砍树 输入输出样例 输入 5 20 4 42 40 26 46 输出 36 PS: get新知识,以前只知道STringTokenizer并没有了解过StreamTokenizer,这次才 ...

  6. Java实现蓝桥杯历届试题高僧斗法

    历届试题 高僧斗法 时间限制:1.0s 内存限制:256.0MB 提交此题 锦囊1 锦囊2 问题描述 古时丧葬活动中经常请高僧做法事.仪式结束后,有时会有"高僧斗法"的趣味节目,以 ...

  7. Java实现 LeetCode 1013 将数组分成和相等的三个部分

    1013. 将数组分成和相等的三个部分 给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false. 形式上,如果可以找出索引 i+1 < j 且满足 ...

  8. Java实现 蓝桥杯VIP 算法训练 麦森数

    算法训练 麦森数 时间限制:1.0s 内存限制:256.0MB 问题描述 形如2P-1的素数称为麦森数,这时P一定也是个素数.但反过来不一定,即如果P是个素数,2P-1不一定也是素数.到1998年底, ...

  9. Java实现蓝桥杯3n+1问题

    3n+1 [问题描述] 考虑如下的序列生成算法:从整数 n 开始,如果 n 是偶数,把它除以 2:如果 n 是奇数,把它乘 3 加1.用新得到的值重复上述步骤,直到 n = 1 时停止.例如,n = ...

  10. Java实现最优二叉查找树

    1 问题描述 在了解最优二叉查找树之前,我们必须先了解何为二叉查找树? 引用自百度百科一段讲解: 二叉排序树(Binary Sort Tree)又称二叉查找树(Binary Search Tree), ...