Scala 实战

实用技巧

List

1
2
3
4
5
6
7
List(1, 9, 2, 4, 5) span (_ < 3) // (List(1), List(9, 2, 4, 5)) 碰到不符合就结束
List(1, 9, 2, 4, 5) partition (_ < 3) // (List(1, 2), List(9, 4, 5)) 扫描所有
List(1, 9, 2, 4, 5) splitAt 2 // (List(1, 9), List(2, 4, 5)) 以下标为分割点
List(1, 9, 2, 4, 5) groupBy (5 < _) // Map(false -> List(1, 2, 4, 5), true -> List(9)) 分割成 Map 对象,以 Boolean 类型为 Key

Iterator

grouped

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
36
37
38
39
40
41
42
43
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.BigquerySparkSession._
val conf = new SparkConf()
val builder = SparkSession.builder().config(conf).enableHiveSupport()
val spark = builder.getOrCreateBigquerySparkSession()
val df = spark.sql("use db; select * from table")
val dataset = df.rdd.mapPartitions(iter => {
val records = new util.LinkedList[String]()
// 将每个 partition 中的多行数据,以 100 为长度作为一组,进行一次批处理
iter.grouped(100)
.foreach(rows => {
rows.foreach(row => records.add(JSON.toJSONString(row, false)))
})
// 重新将一组处理后的数据,封装成 Iterator 类
new AbstractIterator[String]() {
var shouldOutput = true
override def hasNext: Boolean = shouldOutput
override def next(): String = {
shouldOutput = false
JSON.toJSONString(records, false)
}
}
})
// 反序列成 list 对象,再将 list 对象 flat 成单个元素,同时,过滤掉其中空白行
val filteredEmptyLine = dataset
.filter(_ != null)
.filter(_.trim.length != 0)
.map(JSON.parseObject(_, classOf[util.LinkedList[String]]))
.filter(_ != null)
.filter(_.size() != 0)
.flatMap(_.toArray)
.filter(_ != null)
.map(JSON.toJSONString(_, false))
.filter(_.trim.length != 0)

Enum

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
scala> object Types extends Enumeration {
| type Types = Value
| val ZERO, ONE, TWO = Value
|
| def toType(types: String): Option[Types] = {
| // Types.values.filter(_.toString.trim.toUpperCase.equals(types.trim.
toUpperCase)).headOption
| Types.values.find(_.toString.trim.toUpperCase.equals(types.trim.toUpp
erCase))
| }
| }
defined object Types
scala> Types.toType("Zero")
res6: Option[Types.Types] = Some(ZERO)
scala> Types.toType("ONE")
res7: Option[Types.Types] = Some(ONE)
scala> Types.toType("two")
res8: Option[Types.Types] = Some(TWO)
scala> Types.toType("three")
res9: Option[Types.Types] = None
scala> Types.toType("THREE")
res10: Option[Types.Types] = None

Case class

和 class 的 8 个不同之处

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 定义
scala> case class Person(name: String, age: Int)
defined class Person
// 初始化
scala> val bj = Person("Benedict Jin", 18)
bj: Person = Person(Benedict Jin,18)
// 参数的访问权限都是 public
scala> bj.name
res0: String = Benedict Jin
scala> bj.age
res1: Integer = 18
// 更加简洁的 toString 打印
scala> bj.toString
res2: String = Person(Benedict Jin,18)
// 默认实现了 hashCode 和 equals 方法
scala> bj.hashCode
res3: Int = 1059149039
scala> Person("Benedict Jin", 18).hashCode
res4: Int = 1059149039
scala> Person("Benedict Jin", 18) == bj
res5: Boolean = true
// 默认实现了 java.io.Serializable 接口,支持序列化
scala> import java.io._
import java.io._
scala> val bos = new ByteArrayOutputStream
bos: java.io.ByteArrayOutputStream =
scala> val oos = new ObjectOutputStream(bos)
oos: java.io.ObjectOutputStream = java.io.ObjectOutputStream@62ddd21b
scala> oos.writeObject(bj)
scala> val obj = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray)
).readObject()
obj: Object = Person(Benedict Jin,18)
scala> Person("Benedict Jin", 18) == obj
res6: Boolean = true
// 支持 match
scala> bj match {
| case Person("Benedict Jin", 18) => println("matched")
| case _ => println("non-matched")
| }
matched
// 默认继承 scala.Product 类,并实现了其中的方法
scala> bj.productArity
res7: Int = 2
scala> bj.productIterator.next()
res8: Any = Benedict Jin
scala> bj.productElement(1)
res9: Any = 18
scala> bj.productPrefix
res10: String = Person

打印 trait 中的字段

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
36
37
38
scala> trait T {
| var t: String = ""
| }
defined trait T
// 正常情况下,不会打印 trait 中的字段
scala> case class Person(name: String) extends Serializable with T
defined class Person
scala> Person("Benedict Jin")
res6: Person = Person(Benedict Jin)
// 通过 ScalaRunTime._toString(this) 来覆写 toString 方法,可以避免 case class 的 toString 方法被篡改
scala> import scala.runtime.ScalaRunTime
import scala.runtime.ScalaRunTime
scala> case class Person2(name: String) extends Serializable with T {
| override def toString: String = ScalaRunTime._toString(this)
| }
defined class Person2
scala> Person2("Benedict Jin").toString
res7: String = Person2(Benedict Jin)
// 重写 toString 来打印 trait 中的字段
scala> case class Person3(name: String) extends Serializable with T {
| override def toString: String = s"Person3(${this.name}, ${this.t})"
| }
defined class Person3
scala> val p3 = Person3("Benedict Jin")
p3: Person3 = Person3(Benedict Jin, )
scala> p3.t = "t"
p3.t: String = t
scala> p3.toString
res8: String = Person3(Benedict Jin, t)

单元测试

详见,《在不同场景下,如何选择合适的 JVM 语言

常见问题

classOf 和 getClass 的区别

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
36
37
38
scala> class A
defined class A
scala> val a = new A
a: A = A@1d483de4
// A 的任意子类
scala> a.getClass
res0: Class[_ <: A] = class A
// A 类本身
scala> classOf[A]
res1: Class[A] = class A
// 两者是等价的
scala> a.getClass == classOf[A]
res2: Boolean = true
// getClass 的返回值,是不能直接赋值给 Class[A] 的
scala> val c: Class[A] = a.getClass
<console>:13: error: type mismatch;
found : Class[?0] where type ?0 <: A
required: Class[A]
Note: ?0 <: A, but Java-defined class Class is invariant in type T.
You may wish to investigate a wildcard type such as `_ <: A`. (SLS 3.2.10)
val c:Class[A] = a.getClass
^
// 需要声明 Class[_ <: A] 才行,类似于 Java 里面的 Class<? extends A>
scala> val c: Class[_ <: A] = a.getClass
c: Class[_ <: A] = class A
// 但是获取全限定名的返回值,却是一样的
scala> c.getName
res3: String = A
scala> classOf[A].getName
res4: String = A

稳定的标识符模式

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 如果不用 ` 反引号将 a / b / c 变量括起来
// 那么这些变量,其实就变成了指向 i 的别名,已经和 match 外层的 a / b / c 变量无关了
scala> def m(i: Int) = {
| val a = 3
| val b = 2
| val c = 1
| i match {
| case `a` => 0
| case `b` => -1
| case `c` => 4
| case _ => 2
| }
| }
m: (i: Int)Int
scala> m(1)
res17: Int = 4
scala> m(2)
res18: Int = -1
scala> m(3)
res19: Int = 0
// 另外,使用反引号将变量括起来之后,scala 会在字节码层面做优化
/*
0: iconst_3
1: istore_2
2: iconst_2
3: istore_3
4: iconst_1
5: istore 4
7: iload_1
8: istore 5
10: iload_2
11: iload 5
13: if_icmpne 22
16: iconst_0
17: istore 6
19: goto 50
22: iload_3
23: iload 5
25: if_icmpne 34
28: iconst_m1
29: istore 6
31: goto 50
34: iload 4
36: iload 5
38: if_icmpne 47
41: iconst_4
42: istore 6
44: goto 50
47: iconst_2
48: istore 6
50: iload 6
52: ireturn
*/

Tips: Full code is here and here.

在函数调用里面,对变量进行赋值

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
scala> val map = new java.util.LinkedHashMap[String, String]()
map: java.util.LinkedHashMap[String,String] = {}
scala> map.put("1", "1")
res0: String = null
scala> map.put("2", "2")
res1: String = null
scala> map.put("3", "3")
res2: String = null
scala> map
res3: java.util.LinkedHashMap[String,String] = {1=1, 2=2, 3=3}
scala> var s = "1 "
s: String = "1 "
scala> map.containsValue(s)
res4: Boolean = false
// 虽然已经将 s.trim 赋值给 s 变量了,但是传入 map.containsValue 方法的仍然是未进行 trim 操作之前的 s 变量
// 这里和 Java 是不一样的,Java 会将已经赋值之后的变量值,传入到方法中
scala> map.containsValue(s = s.trim)
res5: Boolean = false
scala> s
res6: String = 1
scala> "1".equals(s)
res7: Boolean = true

Java 的 Lambda 表达式转为 Scala 的 Function

Java 的 Lambda 表达式

1
final static ThreadLocal<String> BLOG_ID = ThreadLocal.withInitial(() -> "null");

反面示例

1
2
3
4
5
6
7
8
9
10
11
12
13
// 直接填入 lambda 表达式,IDE 并不会报错,但是会在代码编译阶段报错
private val BLOG_ID: ThreadLocal[String] = ThreadLocal.withInitial(() -> "null")
// 即便把 function 单独拿出来作为 Supplier 变量进行申明,仍然会出现编译错误
val blogIdFunc: java.util.function.Supplier[String] = () => "null"
// 具体编译报错,如下:
val func: java.util.function.Supplier[String] = () => "null"
error: type mismatch;
found : () => String
required: java.util.function.Supplier[String]
val func: java.util.function.Supplier[String] = () => "null"
^

正面示例

1
2
3
4
// 写成 new Supplier[T]{...} 的写法之后,则可以完成编译
// 不过,奇怪的是,编译器仍然会提示,可以将代码优化为 () => "null"
val blogIdFunc: java.util.function.Supplier[String] = new Supplier[String] { override def get(): String = "null" }
private val BLOG_ID: ThreadLocal[String] = ThreadLocal.withInitial(blogIdFunc)

补充

1
2
3
4
5
6
7
8
9
// 如果这部分代码还需要运行在 JDK7 及以下版本的 JVM 环境中,则可以改成普通的 "初始化 ThreadLocal" 方式
private val BLOG_ID: ThreadLocal[String] = new ThreadLocal[String]() {
override def initialValue(): String = "null"
}
// 另外,如果存在 "跨父子线程" 和 "线程池缓存" 的场景,则可以使用 TransmittableThreadLocal 来替换原生的 ThreadLocal
// 不过,在使用 TTL 时,需要注意以下两点:
// 1) 根据 JDK 版本选择合适版本的 TTL 框架(JDK7 只能使用 2.2.2 及以下版本的 TTL)
// 2) 及时使用 remove 方法完成主动清理,提高 GC 效率

Tips: Full code is here.

如何传递变长参数

描述

1
2
3
4
5
6
7
8
9
10
11
12
scala> def detail(d: Any*): Unit = println("%s_%s".format(d))
detail: (d: Any*)Unit
scala> detail("a", "b")
java.util.MissingFormatArgumentException: Format specifier '%s'
at java.util.Formatter.format(Formatter.java:2519)
at java.util.Formatter.format(Formatter.java:2455)
at java.lang.String.format(String.java:2940)
at scala.collection.immutable.StringLike$class.format(StringLike.scala:318)
at scala.collection.immutable.StringOps.format(StringOps.scala:29)
at .detail(<console>:11)
... 32 elided

解决

1
2
3
4
5
6
// 在函数中,想要将传入的变长参数,保持成多个参数的特性,传递下去的话,需要声明 : _*
scala> def detail(d: Any*): Unit = println("%s_%s".format(d: _*))
detail: (d: Any*)Unit
scala> detail("a", "b")
a_b

补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 如果直接用 String.format,会因为占位符和变长参数 数量不一致,导致报错
def countSubString(str: String, sub: String): Int = str.sliding(sub.length).count(slide => slide == sub)
def patchSubString(str: String, sub: String): String = str.patch(str.lastIndexOf(sub), "", sub.length)
// 封装 superFormat 方法来解决该问题
def superFormat(str: String, detail: String*): String = {
val holderSize: Int = countSubString(str, "%s")
val detailSize: Int = detail.size
var result: String = str
if (detailSize < holderSize) {
for (_ <- 0 until (holderSize - detailSize)) {
result = patchSubString(result, "%s")
}
}
result.format(detail: _*)
}

Tips: Full code is here and here.

资料

Book

欢迎加入我们的技术群,一起交流学习 🎉 🎉

Technical Discussion Group:(人工智能 1020982(高级)& 1217710(进阶)| BigData 1670647)

  • 本文作者: Benedict Jin
  • 本文链接: https://yuzhouwan.com/posts/18651/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
显示 Gitment 评论