Scala学习(1)——Scala基础知识

本文要解决的问题:

Spark主要是由Scala语言编写而成的,所以要真正深入了解Spark,必须要熟悉Scala,在此结合阅读《Scala编程》这本书的情况,对Scala语言做一个基本的总结。


Scala的优势

(1)简洁

  • 类型推断
  • 函数创建的文法支持

(2)Java互操作性

  • 可重用Java库
  • 可重用Java工具
  • 没有性能惩罚

Scala工作机制

  • 编译成Java字节码
  • 可在任何标准JVM上运行,甚至是一些不规范的JVM上
  • Scala编译器是Java编译器的作者写的

启动解释器

输入Scala,启动解释器。

C:\Users\shenbinqiang\scala
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_7).
Type in expressions to have them evaluated.
Type :help for more information.

scala>

表达式

scala> 1 + 1
res0: Int = 2

res0是解释器自动创建的变量名称,用来指代表达式的计算结果。它是Int类型,值为2。

Scala中(几乎)一切都是表达式。


你可以给一个表达式的结果起个名字赋成一个不变量(val)。

scala> val two = 1 + 1
two: Int = 2

你不能改变这个不变量的值.


变量

如果你需要修改这个名称和结果的绑定,可以选择使用var。

scala> var name = "steve"
name: java.lang.String = steve

scala> name = "marius"
name: java.lang.String = marius

还有一种val相当于Java里的final类型。


函数

你可以使用def创建函数.

scala> def addOne(m: Int): Int = m + 1
addOne: (m: Int)Int

在Scala中,你需要为函数参数指定类型签名。

scala> val three = addOne(2)
three: Int = 3

如果函数不带参数,你可以不写括号。

scala> def three() = 1 + 2
three: ()Int

scala> three()
res2: Int = 3

scala> three
res3: Int = 3

匿名函数

你可以创建匿名函数。

scala> (x: Int) => x + 1
res2: (Int) => Int = <function1>

这个函数为名为x的Int变量加1。

scala> res2(1)
res3: Int = 2

你可以传递匿名函数,或将其保存成不变量。

scala> val addOne = (x: Int) => x + 1
addOne: (Int) => Int = <function1>

scala> addOne(1)
res4: Int = 2

如果你的函数有很多表达式,可以使用{}来格式化代码,使之易读。

def timesTwo(i: Int): Int = {
  println("hello world")
  i * 2
}

对匿名函数也是这样的。

scala> { i: Int =>
  println("hello world")
  i * 2
}
res0: (Int) => Int = <function1>

在将一个匿名函数作为参数进行传递时,这个语法会经常被用到。


部分应用(Partial application)

你可以使用下划线“”部分应用一个函数,结果将得到另一个函数。Scala使用下划线表示不同上下文中的不同事物,你通常可以把它看作是一个没有命名的神奇通配符。在{ + 2 }的上下文中,它代表一个匿名参数。你可以这样使用它:

scala> def adder(m: Int, n: Int) = m + n
adder: (m: Int,n: Int)Int
scala> val add2 = adder(2, _:Int)
add2: (Int) => Int = <function1>

scala> add2(3)
res50: Int = 5

你可以部分应用参数列表中的任意参数,而不仅仅是最后一个。


柯里化函数

有时会有这样的需求:允许别人一会在你的函数上应用一些参数,然后又应用另外的一些参数。

例如一个乘法函数,在一个场景需要选择乘数,而另一个场景需要选择被乘数。

scala> def multiply(m: Int)(n: Int): Int = m * n
multiply: (m: Int)(n: Int)Int

你可以直接传入两个参数。

scala> multiply(2)(3)
res0: Int = 6

你可以填上第一个参数并且部分应用第二个参数。

scala> val timesTwo = multiply(2) _
timesTwo: (Int) => Int = <function1>

scala> timesTwo(3)
res1: Int = 6

你可以对任何多参数函数执行柯里化。例如之前的adder函数

scala> (adder _).curried
res1: (Int) => (Int) => Int = <function1>

可变长度参数

这是一个特殊的语法,可以向方法传入任意多个同类型的参数。例如要在多个字符串上执行String的capitalize函数,可以这样写:

def capitalizeAll(args: String*) = {
  args.map { arg =>
    arg.capitalize
  }
}

scala> capitalizeAll("rarity", "applejack")
res2: Seq[String] = ArrayBuffer(Rarity, Applejack)

scala> class Calculator {
     |   val brand: String = "HP"
     |   def add(m: Int, n: Int): Int = m + n
     | }
defined class Calculator

scala> val calc = new Calculator
calc: Calculator = Calculator@e75a11

scala> calc.add(1, 2)
res1: Int = 3

scala> calc.brand
res2: String = "HP"

上面的例子展示了如何在类中用def定义方法和用val定义字段值。方法就是可以访问类的状态的函数。


构造函数

构造函数不是特殊的方法,他们是除了类的方法定义之外的代码。让我们扩展计算器的例子,增加一个构造函数参数,并用它来初始化内部状态。

class Calculator(brand: String) {
  /**
   * A constructor.
   */
  val color: String = if (brand == "TI") {
    "blue"
  } else if (brand == "HP") {
    "black"
  } else {
    "white"
  }

  // An instance method.
  def add(m: Int, n: Int): Int = m + n
}

注意两种不同风格的注释。

你可以使用构造函数来构造一个实例:

scala> val calc = new Calculator("HP")
calc: Calculator = Calculator@1e64cc4d

scala> calc.color
res0: String = black

表达式

上文的Calculator例子说明了Scala是如何面向表达式的。颜色的值就是绑定在一个if/else表达式上的。Scala是高度面向表达式的:大多数东西都是表达式而非指令。


函数 vs 方法

函数和方法在很大程度上是可以互换的。由于函数和方法是如此的相似,你可能都不知道你调用的东西是一个函数还是一个方法。而当真正碰到的方法和函数之间的差异的时候,你可能会感到困惑。

scala> class C {
     |   var acc = 0
     |   def minc = { acc += 1 }  //方法
     |   val finc = { () => acc += 1 }  //函数
     | }
defined class C

scala> val c = new C
c: C = [email protected]1af1bd6

scala> c.minc // calls c.minc()

scala> c.finc // returns the function as a value:
res2: () => Unit = <function0>

当你可以调用一个不带括号的“函数”,但是对另一个却必须加上括号的时候,你可能会想哎呀,我还以为自己知道Scala是怎么工作的呢。也许他们有时需要括号?你可能以为自己用的是函数,但实际使用的是方法。

在实践中,即使不理解方法和函数上的区别,你也可以用Scala做伟大的事情。

以下是几点总结:

  • 1.方法不能作为单独的表达式而存在(参数为空的方法除外),而函数可以。
  • 2.函数必须要有参数列表,而方法可以没有参数列表
  • 3.方法名是方法调用的,而函数名只是代表函数对象本身
  • 4.在需要函数的地方,如果传递一个方法,会自动进行ETA展开(把方法转换为函数)
  • 5.传名参数本质上是个方法

继承

class ScientificCalculator(brand: String) extends Calculator(brand) {
  def log(m: Double, base: Double) = math.log(m) / math.log(base)
}

参考《Programming in Scala》指出如果子类与父类实际上没有区别,类型别名是优于继承的。A Tour of Scala 详细介绍了子类化。


重载方法

class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) {
  def log(m: Int): Double = log(m, math.exp(1))
}

抽象类

你可以定义一个抽象类,它定义了一些方法但没有实现它们。取而代之是由扩展抽象类的子类定义这些方法。你不能创建抽象类的实例。

scala> abstract class Shape {
     |   def getArea():Int    // subclass should define this
     | }
defined class Shape

scala> class Circle(r: Int) extends Shape {
     |   def getArea():Int = { r * r * 3 }
     | }
defined class Circle

scala> val s = new Shape
<console>:8: error: class Shape is abstract; cannot be instantiated
       val s = new Shape
               ^

scala> val c = new Circle(2)
c: Circle = [email protected]65c0035b

特质(Traits)

特质是一些字段和行为的集合,可以扩展或混入(mixin)你的类中。

trait Car {
  val brand: String
}

trait Shiny {
  val shineRefraction: Int
}
class BMW extends Car {
  val brand = "BMW"
}

通过with关键字,一个类可以扩展多个特质:

class BMW extends Car with Shiny {
  val brand = "BMW"
  val shineRefraction = 12
}

什么时候应该使用特质而不是抽象类? 如果你想定义一个类似接口的类型,你可能会在特质和抽象类之间难以取舍。这两种形式都可以让你定义一个类型的一些行为,并要求继承者定义一些其他行为。一些经验法则:

优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。

如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。例如,你不能说trait t(i: Int) {},参数i是非法的。

你不是问这个问题的第一人。可以查看更全面的答案: stackoverflow: Scala特质 vs 抽象类 , 抽象类和特质的区别, and Scala编程: 用特质,还是不用特质?


类型

此前,我们定义了一个函数的参数为Int,表示输入是一个数字类型。其实函数也可以是泛型的,来适用于所有类型。当这种情况发生时,你会看到用方括号语法引入的类型参数。下面的例子展示了一个使用泛型键和值的缓存。

trait Cache[K, V] {
  def get(key: K): V
  def put(key: K, value: V)
  def delete(key: K)
}

方法也可以引入类型参数。

def remove[K](key: K)

至此,基础知识部分已经过了一大半了,后续会继续总结。

时间: 2024-12-25 03:19:40

Scala学习(1)——Scala基础知识的相关文章

Scala学习笔记--正则表达式基础知识、如何在scala内使用

正则表达式语法:https://msdn.microsoft.com/zh-cn/library/ae5bf541(VS.80).aspx 基础知识:正则表达式30分钟入门教程 http://www.jb51.net/tools/zhengze.html  版本:v2.33 (2013-1-10) 作者:deerchao 转载请注明来源 使用格式:  见java API  http://docs.oracle.com/javase/7/docs/api/ java : java.util.reg

Scala学习:第一张基础 - 心得

这是一个神奇的语言. 安装环境就够折腾了,居然还挑eclipse,最新的4.4居然不支持,要用4.3.2 第一张都是些简单的概念介绍,但是通过第一张可以看出scala和其他语言的语法上存在较大的区别(当然根据我的知识范围也就是指的c#,Java) 关键点: 1.Scala中,我们不需要包装类型. 读到这里时,我对性能产生了担心,于是赶快查了下对Scala和Java的性能对比.有个比较好的文章.内容不多,字字珠玑. 不要使用for循环 不要使用scala.collection.mutable 不要

C#学习笔记(基础知识回顾)之值类型与引用类型转换(装箱和拆箱)

一:值类型和引用类型的含义参考前一篇文章 C#学习笔记(基础知识回顾)之值类型和引用类型 1.1,C#数据类型分为在栈上分配内存的值类型和在托管堆上分配内存的引用类型.如果int只不过是栈上的一个4字节的值,该如何在它上面调用方法? 二:值类型转换为引用类型--装箱 2.1CLR对值类型进行装箱时:新分配托管堆内存,将值类型的实例字段拷贝到新分配的内存中,返回托管堆中新分配对象的地址.这个地址就是一个指向对象的引用. int i = 10; Object obj = i; 三:将引用类型转换为值

[Golong]学习笔记(一) 基础知识

Go编程基础 Go的内置关键字(25个) 不多 break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continute for import return var Go的注释方法(和js一样) 单行注释: // 多行注释: /**/ Go程序一般结构 common_structure.go 通过 pack

如何学习FPGA?FPGA学习必备的基础知识

如何学习FPGA?FPGA学习必备的基础知识 时间:2013-08-12 来源:eepw 作者: 关键字:FPGA   基础知识 FPGA已成为现今的技术热点之一,无论学生还是工程师都希望跨进FPGA的大门.网络上各种开发板.培训班更是多如牛毛,仿佛在告诉你不懂FPGA你就OUT啦.那么我们要玩转FPGA必须具备哪些基础知识呢?下面我们慢慢道来. (一) 要了解什么是FPGA 既然要玩转FPGA,那我们首先最重要的当然是要了解什么FPGA.FPGA(Field-Programmable Gate

Scala学习(一)--Scala基础学习

Scala基础学习 摘要: 在篇主要内容:如何把Scala当做工业级的便携计算器使用,如何用Scala处理数字以及其他算术操作.在这个过程中,我们将介绍一系列重要的Scala概念和惯用法.同时你还将学到作为初学者如何浏览Scaladoc文档 1. 使用Scala解释器 2. 用var和val定义变量 3. 数值类型 4. 使用操作符和函数 5. 浏览Scaladoc Scala解释器 启动Scala解释器的步骤如下: 安装Scala 确保scala/bin目录位于系统PATH中 在你的操作系统中

[算法学习笔记]算法基础知识

算法基础知识 算法的五大要素 有穷性:算法必须能够在有限个步骤内完成. 确定性:算法的每一步必须有确定的定义. 输入 输出 可行性:算法的每个步骤都必须能分解为基本的可执行操作,每个步骤都必须能在有限时间内完成 循环不变式 循环中的循环不变式可以帮助我们理解算法的正确性.为了证明算法的正确,必须证明循环不变式的三个性质: 1. 初始化:循环不变式在循环开始之前是正确的. 2. 保持:循环不变式在循环的每一次迭代开始之前是正确的. 3. 终止:在循环结束时,不变式会给出一个可以对判断算法是否正确有

《汇编语言》学习笔记1——基础知识

第一章   基础知识 汇编语言的组成 汇编指令:机器码的助记符,有对应的机器码(汇编语言的核心) 伪指令:没有对应的机器码,由编译器执行,计算机并不执行 其他符号:如:+.-.*./等,由编译器识别,没有对应的机器码 指令和数据都是应用上的概念,在内存或磁盘上指令和数据都是二进制信息. 存储器被划分为若干个存储单元,每个存储单元从0开始. 一个存储器有128个存储单元,一个存储单元存储一个字节,一个字节有8个二进制位. 内存换算: 1bit=8byte(B).1KB=1024B.1MB=1024

性能测试学习09_分析基础知识

一.基础知识 1.controller的原理 通过场景设计来模拟用户的真实操作并调用vugen中的脚本,然后通过设置压力机产生压力,在场景运行中实时监控用户的执行情况,tps.响应时间.吞吐量.服务器资源使用情况 二.contrller 1.功能截图介绍 2.为什么java vuser协议以进程跑? java vuser协议以线程跑,发现压力根本上不去,还需要实际操作进行研究. 3.创建集合点 1)在事物前一行右键,[insert]→[rendezvous] 2)如图,创建成功 2)回到cont

Scala学习之路----基础入门

一.Scala解释器的使用 REPL:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环) scala解释器也被称为REPL,会快速编译scala代码为字节码,然后交给JVM来执行. 计算表达式:在scala>命令行内,键入scala代码,解释器会直接返回结果. 如果你没有指定变量来存放这个值,那么值默认的名称为res,而且会 显示结果的数据类型,比如Int.Double.String等等. 例如,输入1 + 1,会看到res0: Int