高级函数
高阶函数是指使用其他函数作为参数、或者返回一个函数作为结果的函数。
匿名函数
1 | (x: Int) => x * 3 |
带函数参数的函数
Scala集合类(collections)的高阶函数map。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
def builder = {
val b = bf(repr)
b.sizeHint(this)
b
}
val b = builder
for (x <- this) b += f(x)
b.result
}
val seq = Seq(100, 200, 300)
def doubleSalary(x: Int): Int = {
x * 2
}
seq.map(doubleSalary).foreach(x => println(x))
seq.map(x => x * 2)
seq.map(_ * 2)
闭包
1 | def sum(x: Int) = (y: Int) => x + y |
嵌套方法
在Scala中可以嵌套定义方法。1
2
3
4
5
6
7
8
9
10def factorial(x: Int): Int = {
def fact(x: Int, accumulator: Int): Int = {
if (x <= 1) accumulator
else fact(x - 1, x * accumulator)
}
fact(x, 1)
}
println("Factorial of 2: " + factorial(2))
println("Factorial of 3: " + factorial(3))
柯里化
柯里化:指将原来接受两个参数的函数变成新的接受一个参数的过程。
接受两个参数的过程:1
def mul(x:Int , y: Int )= x * y
接受一个参数的过程1
2def mul(x: Int) = (y: Int) => x + y
mul(1)(2)
scala支持简写成如下的柯里化:1
2
3def mul(x: Int)(y: Int) = x + y
mul(1)(2)
在Scala集合中定义的特质TraversableOnce[+A]。
Traversable: 能横过的;能越过的;可否定的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def foldLeft[B](z: B)(op: (B, A) => B): B = {
var result = z
this foreach (x => result = op(result, x))
result
}
```
foldLeft从左到右,以此将一个二元运算op应用到初始值z和该迭代器(traversable)的所有元素上。以下是该函数的一个用例:
从初值0开始, 这里 foldLeft 将函数 (m, n) => m + n 依次应用到列表中的每一个元素和之前累积的值上。
```scala
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val res = numbers.foldLeft(0)((m, n) => m + n)
val res2 = numbers.foldLeft(0)(_ + _)
println(res) // 55
pringln(res2)
多参数列表有更复杂的调用语法,因此应该谨慎使用。
建议的使用场景包括:
单一的函数参数。
在某些情况下存在单一的函数参数时,例如上述例子foldLeft中的op,多参数列表可以使得传递匿名函数作为参数的语法更为简洁。如果不使用多参数列表,代码可能像这样:1
numbers.foldLeft(0, {(m: Int, n: Int) => m + n})
隐式(IMPLICIT)参数。
1
def execute(arg: Int)(implicit ec: ExecutionContext) = ???
模式匹配
match对应java里的Switch,不过它写在选择器表达式之后。
选择器 match {备选项}
取代了:
switch(选择器){备选项}
match与switch的比较
匹配表达式可以被看做java风格switch的泛化。
match的不同:
1、match是scala表达式,也就是说,它始终以值作为结果;
2、 scala的备选项表达式永远不会”掉到”下一个case;
3、 如果没有模式匹配,MatchErro异常会被抛出。
提取器对象
提取器对象是一个包含有 unapply 方法的单例对象。apply 方法就像一个构造器,接受参数然后创建一个实例对象,反之 unapply 方法接受一个实例对象然后返回最初创建它所用的参数。提取器常用在模式匹配和偏函数中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18case class Person(name: String, age: Int)
def main(args: Array[String]): Unit = {
// 调用工厂构造方法,构造出对象实例
val person = Person("Spark", 6)
// 这种写法居然可以正常编译
val Person(name, age) = person
/*
* 使用了提取器,
* 调用了unapply方法,把实例person中的name和age提取出来赋值给了Person类
*/
println(name + " : " + age) //正常输出: Spark 6
person match {
// match过程就是调用提取器的过程
case Person(name, age) => println("Wow, " + name + " : " + age)
}
}
自定义unapply方法
主要用于模式匹配中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Person(val name: String, val salary: Int)
object Person {
def apply(name: String, salary: Int):Person = {
new Person(name, salary)
}
def unapply(money: Person): Option[(String, Int)] = {
if(money == null) {
None
} else {
Some(money.name, money.salary)
}
}
def main(args: Array[String]): Unit = {
val person = Person("spark", 800);
person match {
// match过程就是调用提取器的过程
case Person(name, age) => println("Wow, " + name + " : " + age)
}
}
}
for 表达式
Scala 提供一个轻量级的标记方式用来表示 序列推导。推导使用形式为 for (enumerators) yield e 的 for 表达式,此处 enumerators 指一组以分号分隔的枚举器。一个 enumerator 要么是一个产生新变量的生成器,要么是一个过滤器。for 表达式在枚举器产生的每一次绑定中都会计算 e 值,并在循环结束后返回这些值组成的序列。
例子1
2
3
4
5
6
7
8
9
10
11case class User(name: String, age: Int)
val userBase = List(User("Travis", 28),
User("Kelly", 33),
User("Jennifer", 44),
User("Dennis", 23))
val twentySomethings = for (user <- userBase if (user.age >=20 && user.age < 30))
yield user.name
twentySomethings.foreach(name => println(name))
这里 for 循环后面使用的 yield 语句实际上会创建一个 List。因为当我们说 yield user.name 的时候,它实际上是一个 List[String]。 user <- userbase="" 是生成器,if="" (user.age="">=20 && user.age < 30) 是过滤器用来过滤掉那些年龄不是20多岁的人。->
泛型
泛型类指可以接受类型参数的类。泛型类在集合类中被广泛使用。
泛型类使用方括号 [] 来接受类型参数。一个惯例是使用字母 A 作为参数标识符,当然你可以使用任何参数名称。
定义一个泛型类
泛型类使用方括号 [] 来接受类型参数。一个惯例是使用字母 A 作为参数标识符,当然你可以使用任何参数名称。1
2
3
4
5
6
7
8
9
10class Stack[A] {
private var elements: List[A] = Nil
def push(x: A) { elements = x :: elements }
def peek: A = elements.head
def pop(): A = {
val currentTop = peek
elements = elements.tail
currentTop
}
}
上面的 Stack 类的实现中接受类型参数 A。 这表示其内部的列表,var elements: List[A] = Nil,只能够存储类型 A 的元素。方法 def push 只接受类型 A 的实例对象作为参数(注意:elements = x :: elements 将 elements 放到了一个将元素 x 添加到 elements 的头部而生成的新列表中)。
使用
要使用一个泛型类,将一个具体类型放到方括号中来代替 A。1
2
3
4
5val stack = new Stack[Int]
stack.push(1)
stack.push(2)
println(stack.pop) // prints 2
println(stack.pop) // prints 1
型变
型变是复杂类型的子类型关系与其组件类型的子类型关系的相关性。 Scala支持泛型类的类型参数的型变注释,允许它们是协变的,逆变的,或在没有使用注释的情况下是不变的。在类型系统中使用型变允许我们在复杂类型之间建立直观的连接,而缺乏型变则会限制类抽象的重用性。1
2
3class Foo[+A] // 一个协变类
class Bar[-A] // 一个逆变类
class Baz[A] // 一个不变类
协变
使用注释 +A,可以使一个泛型类的类型参数 A 成为协变。 对于某些类 class List[+A],使 A 成为协变意味着对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 List[A] 就是 List[B] 的子类型。 这允许我们使用泛型来创建非常有用和直观的子类型关系。
考虑以下简单的类结构:1
2
3
4
5abstract class Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
类型 Cat 和 Dog 都是 Animal 的子类型。 Scala 标准库有一个通用的不可变的类 sealed abstract class List[+A],其中类型参数 A 是协变的。 这意味着 List[Cat] 是 List[Animal],List[Dog] 也是 List[Animal]。 直观地说,猫的列表和狗的列表都是动物的列表是合理的,你应该能够用它们中的任何一个替换 List[Animal]。
在下例中,方法 printAnimalNames 将接受动物列表作为参数,并且逐行打印出它们的名称。 如果 List[A] 不是协变的,最后两个方法调用将不能编译,这将严重限制 printAnimalNames 方法的适用性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17object CovarianceTest extends App {
def printAnimalNames(animals: List[Animal]): Unit = {
animals.foreach { animal =>
println(animal.name)
}
}
val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
printAnimalNames(cats)
// Whiskers
// Tom
printAnimalNames(dogs)
// Fido
// Rex
}
逆变
通过使用注释 -A,可以使一个泛型类的类型参数 A 成为逆变。 与协变类似,这会在类及其类型参数之间创建一个子类型关系,但其作用与协变完全相反。 也就是说,对于某个类 class Writer[-A] ,使 A 逆变意味着对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 Writer[B] 是 Writer[A] 的子类型。
考虑在下例中使用上面定义的类 Cat,Dog 和 Animal :1
2
3abstract class Printer[-A] {
def print(value: A): Unit
}
这里 Printer[A] 是一个简单的类,用来打印出某种类型的 A。 让我们定义一些特定的子类:1
2
3
4
5
6
7
8
9class AnimalPrinter extends Printer[Animal] {
def print(animal: Animal): Unit =
println("The animal's name is: " + animal.name)
}
class CatPrinter extends Printer[Cat] {
def print(cat: Cat): Unit =
println("The cat's name is: " + cat.name)
}
如果 Printer[Cat] 知道如何在控制台打印出任意 Cat,并且 Printer[Animal] 知道如何在控制台打印出任意 Animal,那么 Printer[Animal] 也应该知道如何打印出 Cat 就是合理的。 反向关系不适用,因为 Printer[Cat] 并不知道如何在控制台打印出任意 Animal。 因此,如果我们愿意,我们应该能够用 Printer[Animal] 替换 Printer[Cat],而使 Printer[A] 逆变允许我们做到这一点。1
2
3
4
5
6
7
8
9
10
11
12
13object ContravarianceTest extends App {
val myCat: Cat = Cat("Boots")
def printMyCat(printer: Printer[Cat]): Unit = {
printer.print(myCat)
}
val catPrinter: Printer[Cat] = new CatPrinter
val animalPrinter: Printer[Animal] = new AnimalPrinter
printMyCat(catPrinter)
printMyCat(animalPrinter)
}
这个程序的输出如下:
The cat’s name is: Boots
The animal’s name is: Boots
不变
默认情况下,Scala中的泛型类是不变的。 这意味着它们既不是协变的也不是逆变的。 在下例中,类 Container 是不变的。 Container[Cat] 不是 Container[Animal],反之亦然。1
2
3
4
5
6
7class Container[A](value: A) {
private var _value: A = value
def getValue: A = _value
def setValue(value: A): Unit = {
_value = value
}
}
可能看起来一个 Container[Cat] 自然也应该是一个 Container[Animal],但允许一个可变的泛型类成为协变并不安全。 在这个例子中,Container 是不变的非常重要。 假设 Container 实际上是协变的,下面的情况可能会发生:1
2
3
4val catContainer: Container[Cat] = new Container(Cat("Felix"))
val animalContainer: Container[Animal] = catContainer
animalContainer.setValue(Dog("Spot"))
val cat: Cat = catContainer.getValue
糟糕,我们最终会将一只狗作为值分配给一只猫
幸运的是,编译器在此之前就会阻止我们。
上界
在Scala中,类型参数和抽象类型都可以有一个类型边界约束。这种类型边界在限制类型变量实际取值的同时还能展露类型成员的更多信息。比如像T <: A这样声明的类型上界表示类型变量T应该是类型A的子类。下面的例子展示了类PetContainer的一个类型参数的类型上界。
在Scala中,类型参数和抽象类型都可以有一个类型边界约束。这种类型边界在限制类型变量实际取值的同时还能展露类型成员的更多信息。比如像T <: A这样声明的类型上界表示类型变量T应该是类型A的子类。下面的例子展示了类PetContainer的一个类型参数的类型上界。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26abstract class Animal {
def name: String
}
abstract class Pet extends Animal {}
class Cat extends Pet {
override def name: String = "Cat"
}
class Dog extends Pet {
override def name: String = "Dog"
}
class Lion extends Animal {
override def name: String = "Lion"
}
class PetContainer[P <: Pet](p: P) {
def pet: P = p
}
val dogContainer = new PetContainer[Dog](new Dog)
val catContainer = new PetContainer[Cat](new Cat)
// this would not compile
val lionContainer = new PetContainer[Lion](new Lion)
类PetContainer接受一个必须是Pet子类的类型参数P。因为Dog和Cat都是Pet的子类,所以可以构造PetContainer[Dog]和PetContainer[Cat]。但在尝试构造PetContainer[Lion]的时候会得到下面的错误信息:1
type arguments [Lion] do not conform to class PetContainer's type parameter bounds [P <: Pet]
这是因为Lion并不是Pet的子类。
下界
类型上界 将类型限制为另一种类型的子类型,而 类型下界 将类型声明为另一种类型的超类型。 术语 B >: A 表示类型参数 B 或抽象类型 B 是类型 A 的超类型。 在大多数情况下,A 将是类的类型参数,而 B 将是方法的类型参数。
下面看一个适合用类型下界的例子:
下面看一个适合用类型下界的例子:1
2
3
4
5
6
7
8
9
10
11
12
13trait Node[+B] {
def prepend(elem: B): Node[B]
}
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
def head: B = h
def tail: Node[B] = t
}
case class Nil[+B]() extends Node[B] {
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
}
该程序实现了一个单链表。 Nil 表示空元素(即空列表)。 class ListNode 是一个节点,它包含一个类型为 B (head) 的元素和一个对列表其余部分的引用 (tail)。 class Node 及其子类型是协变的,因为我们定义了 +B。
但是,这个程序 不能 编译,因为方法 prepend 中的参数 elem 是协变的 B 类型。 这会出错,因为函数的参数类型是逆变的,而返回类型是协变的。
要解决这个问题,我们需要将方法 prepend 的参数 elem 的型变翻转。 我们通过引入一个新的类型参数 U 来实现这一点,该参数具有 B 作为类型下界。1
2
3
4
5
6
7
8
9
10
11
12
13trait Node[+B] {
def prepend[U >: B](elem: U): Node[U]
}
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
def head: B = h
def tail: Node[B] = t
}
case class Nil[+B]() extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
}
现在我们像下面这么做:1
2
3
4
5
6trait Bird
case class AfricanSwallow() extends Bird
case class EuropeanSwallow() extends Bird
val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil())
val birdList: Node[Bird] = africanSwallowList
birdList.prepend(new EuropeanSwallow)
可以为 Node[Bird] 赋值 africanSwallowList,然后再加入一个 EuropeanSwallow。
抽象类型
特质和抽象类可以包含一个抽象类型成员,意味着实际类型可由具体实现来确定。例如:1
2
3
4
5trait Buffer {
type T
val element: T
}
}
这里定义的抽象类型T是用来描述成员element的类型的。通过抽象类来扩展这个特质后,就可以添加一个类型上边界来让抽象类型T变得更加具体。
特质和抽象类可以包含一个抽象类型成员,意味着实际类型可由具体实现来确定。例如:1
2
3
4
5abstract class SeqBuffer extends Buffer {
type U
type T <: Seq[U]
def length = element.length
}
注意这里是如何借助另外一个抽象类型U来限定类型上边界的。通过声明类型T只可以是Seq[U]的子类(其中U是一个新的抽象类型),这个SeqBuffer类就限定了缓冲区中存储的元素类型只能是序列。
含有抽象类型成员的特质或类(classes)经常和匿名类的初始化一起使用。为了能够阐明问题,下面看一段程序,它处理一个涉及整型列表的序列缓冲区。1
2
3
4
5
6
7
8
9
10
11
12
13abstract class IntSeqBuffer extends SeqBuffer {
type U = Int
}
def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
new IntSeqBuffer {
type T = List[U]
val element = List(elem1, elem2)
}
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)
这里的工厂方法newIntSeqBuf使用了IntSeqBuf的匿名类实现方式,其类型T被设置成了List[Int]。
把抽象类型成员转成类的类型参数或者反过来,也是可行的。如下面这个版本只用了类的类型参数来转换上面的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15abstract class Buffer[+T] {
val element: T
}
abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
def length = element.length
}
def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
new SeqBuffer[Int, List[Int]] {
val element = List(e1, e2)
}
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)
需要注意的是为了隐藏从方法newIntSeqBuf返回的对象的具体序列实现的类型,这里的型变标号(+T <: Seq[U])是必不可少的。此外要说明的是,有些情况下用类型参数替换抽象类型是行不通的。
复合类型
需求:
有时需要表明一个对象的类型是其他几种类型的子类型。 在 Scala 中,这可以表示成 复合类型,即多个类型的交集。
案例:
假设我们有两个特质 Cloneable 和 Resetable:1
2
3
4
5
6
7
8trait Cloneable extends java.lang.Cloneable {
override def clone(): Cloneable = {
super.clone().asInstanceOf[Cloneable]
}
}
trait Resetable {
def reset: Unit
}
现在假设我们要编写一个方法 cloneAndReset,此方法接受一个对象,克隆它并重置原始对象:1
2
3
4
5def cloneAndReset(obj: ?): Cloneable = {
val cloned = obj.clone()
obj.reset
cloned
}
这里出现一个问题,参数 obj 的类型是什么。 如果类型是 Cloneable 那么参数对象可以被克隆 clone,但不能重置 reset; 如果类型是 Resetable 我们可以重置 reset 它,但却没有克隆 clone 操作。 为了避免在这种情况下进行类型转换,我们可以将 obj 的类型同时指定为 Cloneable 和 Resetable。 这种复合类型在 Scala 中写成:Cloneable with Resetable。
以下是更新后的方法:1
2
3def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
//...
}
复合类型可以由多个对象类型构成,这些对象类型可以有单个细化,用于缩短已有对象成员的签名。 格式为:A with B with C … { refinement }
关于使用细化的例子参考 通过混入(mixin)来组合类。
自类型
自类型用于声明一个特质必须混入其他特质,尽管该特质没有直接扩展其他特质。 这使得所依赖的成员可以在没有导入的情况下使用。
自类型是一种细化 this 或 this 别名之类型的方法。 语法看起来像普通函数语法,但是意义完全不一样。
要在特质中使用自类型,写一个标识符,跟上要混入的另一个特质,以及 =>(例如 someIdentifier: SomeOtherTrait =>)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15trait User {
def username: String
}
trait Tweeter {
this: User => // 重新赋予 this 的类型
def tweet(tweetText: String) = println(s"$username: $tweetText")
}
class VerifiedTweeter(val username_ : String) extends Tweeter with User { // 我们混入特质 User 因为 Tweeter 需要
def username = s"real $username_"
}
val realBeyoncé = new VerifiedTweeter("Beyoncé")
realBeyoncé.tweet("Just spilled my glass of lemonade") // 打印出 "real Beyoncé: Just spilled my glass of lemonade"
因为我们在特质 trait Tweeter 中定义了 this: User =>,现在变量 username 可以在 tweet 方法内使用。 这也意味着,由于 VerifiedTweeter 继承了 Tweeter,它还必须混入 User(使用 with User)。
自类型别名1
2
3
4
5
6
7
8
9
10object Demo {
self =>
def sum(num1: Int, num2: Int): Int = {
num1 + num2
}
def main(args: Array[String]): Unit = {
println(self.sum(1, 2))
}
}
隐式转换
定义:指的是那种以implicit关键字声明的带有单个参数的函数。
通过隐式转换,程序员可以在编写Scala程序时故意漏掉一些信息,让编译器去尝试在编译期间自动推导出这些信息来,这种特性可以极大的减少代码量,忽略那些冗长,过于细节的代码。
1.将方法或变量标记为implicit
2.将方法的参数列表标记为implicit
3.将类标记为implicit
Scala支持两种形式的隐式转换:
隐式值:用于给方法提供参数
隐式视图:用于类型间转换或使针对某类型的方法能调用成功
案例
给File类增加read方法
1 | class RichFile(val f: File) { |
门面类1
2
3
4
5import java.io.File
object RichFilePredef {
implicit def fileToRichFile(f: File) = new RichFile(f)
}
使用
1 | import java.io.File |
排序中的使用案例
Ordered方式
1 | class Girl(val name: String, var faceValue: Int, var age: Int) |
定义比较规则1
2
3
4
5
6
7
8
9
10
11
12/**
* 视图定界
* 使用视图定界,视图定界其实就是隐式转换,将T转换成Ordered
* 有时候,你并不需要指定一个类型是等/子/超于另一个类,你可以通过转换这个类来伪装这种关联关系。
* 一个视界指定一个类型可以被“看作是”另一个类型。这对对象的只读操作是很有用的。
* 更多知识:https://twitter.github.io/scala_school/zh_cn/advanced-types.html
*/
class OrderedChooser[T <% Ordered[T]] {
def choose(first: T, second: T): T = {
if (first > second) first else second
}
}
门面类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23object OrderedPredef {
// 方式一
implicit def gilrToOrdered(girl: Girl):Ordered[Girl] = new Ordered[Girl] {
override def compare(that: Girl): Int = {
if (girl.faceValue == that.faceValue) {
girl.age - that.age
} else {
girl.faceValue - that.faceValue
}
}
}
// 方式二
implicit val gilrToOrdered = (girl: Girl) => new Ordered[Girl] {
override def compare(that: Girl): Int = {
if (girl.faceValue == that.faceValue) {
girl.age - that.age
} else {
girl.faceValue - that.faceValue
}
}
}
}
测试类1
2
3
4
5
6
7
8
9
10object TestOrdered {
def main(args: Array[String]): Unit = {
val girl1 = new Girl("spark", 100,50)
val girl2 = new Girl("mxnet", 90,30)
import OrderedPredef._
val chooser = new OrderingChoose[Girl]
val g = chooser.choose(girl1, girl2)
println(g.faceValue)
}
}
Ordering方式
定义比较规则
1 | /** |
门面类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36object OrderingPredef {
// 方式一
implicit val gilrToOrdering = new Ordering[Girl] {
override def compare(x: Girl, y: Girl): Int = {
x.faceValue - y.faceValue
}
}
// 方式二
implicit object GilrToOrdering extends Ordering[Girl] {
override def compare(x: Girl, y: Girl): Int = {
x.faceValue - y.faceValue
}
}
// 方式三
/**
* 参考:Ordering中的下面方法
* trait IntOrdering extends Ordering[Int] {
* def compare(x: Int, y: Int) =
* if (x < y) -1
* else if (x == y) 0
* else 1
* }
* implicit object Int extends IntOrdering
*/
trait GirlToOrdering extends Ordering[Girl] {
override def compare(x: Girl, y: Girl): Int = {
x.faceValue - y.faceValue
}
}
implicit object Girl extends GirlToOrdering
}
测试类1
2
3
4
5
6
7
8
9
10object TestOrdering {
def main(args: Array[String]): Unit = {
val g1 = new Girl("zhangsan", 50,50)
val g2 = new Girl("lisi", 500,50)
import OrderingPredef._
val choose = new OrderingChoose[Girl]
val g = choose.choose(g1, g2)
println(g.name)
}
}
scala导入类并取别名
导入Map,并取别名1
import java.util.{Map => JMap}
scala Try的使用
spark Utils中的使用案例:1
2
3
4
5
6
7def classIsLoadable(clazz: String): Boolean = {
// scalastyle:off classforname
Try {
Class.forName(clazz, false, getContextOrSparkClassLoader)
}.isSuccess
// scalastyle:on classforname
}
quasiquotes(q字符串)用于代码生成
官方文档: https://docs.scala-lang.org/overviews/quasiquotes/intro.html
参考文档:https://www.cnblogs.com/shishanyuan/p/8455786.html
作用: Quasiquotes允许在Scala语言中对抽象语法树(AST)进行编程式构建,然后在运行时将其提供给Scala编译器以生成字节码。
以q开头的字符串是quasiquotes,虽然它们看起来像字符串,但它们在编译时由Scala编译器解析,并代表其代码的AST。
val tree = q"i am { a quasiquote }"
tree: universe.Tree = i.am(a.quasiquote)
符号详解
== 和===的区别
参考:http://landcareweb.com/questions/25833/scala-sparkzhong-he-zhi-jian-de-qu-bie
== 返回一个布尔值
=== 返回一列(包含两列元素比较的结果)
call-by-value and call-by-name
传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部;
传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部
半生对象
object中的构造器在第一次调用执行一次,以后调用的话不会多次执行。
object会有自己的构造方法,默认是没有参数的构造方法。