0%

apply, update 语法糖

[TOC]

语法糖,又称为糖衣语法,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说可以增加程序的可读性,从而减少程度代码出错的机会。接下来就会介绍两个 Scala 中的语法糖。

apply

在 Scala 中,如果我们要频繁调用某个 class 或 object 的方法,我们可以通过定义 apply 方法来避免每次写出该函数的调用,而使用一种更加简洁的方式,来看下面的例子:

不使用 apply 方法

1
2
3
4
5
6
7
8
9
10
11
12
scala> class truck {
| def transport( goods: Int ): Unit = {
| println( "truck transport " + goods + "t goods" )
| }
| }
defined class truck

scala> val t = new truck
t: truck = truck@77468bd9

scala> t.transport( 100 )
truck transport 100t goods

使用 apply 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scala> class truck {
| def apply( goods: Int ): Unit = {
| println( "truck transport " + goods + "t goods" )
| }
| }
defined class truck

scala>

scala> val t = new truck
t: truck = truck@68bbe345

scala> t(100)
truck transport 100t goods

上面两个例子的效果是完全一样的,下面这个定义了 apply 的类能让我们在频繁调用某个方法的时候更加方便。当然,apply 方法支持任意类型和任意个参数。

另一种 apply 常用的使用场景是用 object 来创建其伴生类的实例。如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private class truck {
println( "new truck created!" )
}

object truck {
def apply(): Unit = {
new truck
}
}

object TT {
def main (args: Array[String]) {
val t = truck()
}
}

输出:

1
new truck created!

其实上例中,truck()就是调用了truck.apply()方法,编译器在编译时将truck()编译成truck.apply(),如果你对编译后的结果进行反编译就能验证这一点,这留给有兴趣的同学自行实践,这里不展开了。

apply 方法在我们平时写代码时也经常碰到,比如:

1
val l = List(1,2,3)

中的 List(1,2,3) 调用就是调用的 object List 的 apply 方法

1
2
3
4
5
6
7
object List extends scala.collection.generic.SeqFactory[scala.collection.immutable.List] with scala.Serializable {
implicit def canBuildFrom[A] : scala.collection.generic.CanBuildFrom[List.Coll, A, scala.collection.immutable.List[A]] = { /* compiled code */ }
def newBuilder[A] : scala.collection.mutable.Builder[A, scala.collection.immutable.List[A]] = { /* compiled code */ }
override def empty[A] : scala.collection.immutable.List[A] = { /* compiled code */ }
override def apply[A](xs : A*) : scala.collection.immutable.List[A] = { /* compiled code */ }
private[collection] val partialNotApplied : scala.AnyRef with scala.Function1[scala.Any, scala.Any] = { /* compiled code */ }
}

update

除了 apply 方法,还有一个用于赋值时的 update 方法,

1
2
3
4
5
6
7
scala> val a = mutable.Map[ Int, Int ]()
a: scala.collection.mutable.Map[Int,Int] = Map()

scala> a(1) = 1

scala> println( a )
Map(1 -> 1)

当我们调用 a(1) = 1 的时候其实是在调用 a.update(1,1),当然你也可以在自定义类中实现 update 使调用更方便。就像下面这个例子一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scala> class A {
| private var a = 0
|
| def update( i: Int ): Unit = {
| a = i
| println( a )
| }
| }
defined class A

scala> val a = new A
a: A = A@27e47833

scala> a() = 2
2

case class简单介绍

[TOC]

本文将基于下面这个简单的例子来说明 case class

1
case class Person( lastname: String, firstname: String, birthYear: Int )

你可能知道的知识

当你声明了一个 case class,Scala 编译器为你做了这些:

  • 创建 case class 和它的伴生 object

  • 实现了 apply 方法让你不需要通过 new 来创建类实例

    1
    2
    3
    4
    5
    scala> case class Person(lastname: String, firstname: String, birthYear: Int)
    defined class Person

    scala> val p = Person("Lacava", "Alessandro", 1976)
    p: Person = Person(Lacava,Alessandro,1976)
  • 默认为主构造函数参数列表的所有参数前加 val

    1
    2
    3
    4
    5
    6
    7
    scala> println( p.lastname )
    Lacava

    scala> p.lastname = "jhon"
    <console>:10: error: reassignment to val
    p.lastname = "jhon"
    ^
  • 添加天然的 hashCode、equals 和 toString 方法。由于 == 在 Scala 中总是代表 equals,所以 case class 实例总是可比较的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    scala> val p_1 = new Person( "Brown", "John", 1969 )
    p_1: Person = Person(Brown,John,1969)

    scala>val p_2 = new Person( "Lacave", "Alessandro", 1976)
    p_2: Person = Person(Lacave,Alessandro,1976)

    scala> p_1.hashCode
    res1: Int = -1362628729

    scala> p_1.toString
    res2: String = Person(Brown,John,1969)

    scala> p_1.equals(p_2)
    res3: Boolean = false

    scala> p_1 == p_2
    res4: Boolean = false
  • 生成一个 copy 方法以支持从实例 a 生成另一个实例 b,实例 b 可以指定构造函数参数与 a 一致或不一致

    1
    2
    3
    //< 保留 lastname 一致,修改 firstname 和 birthYear
    scala> val p_3 = p.copy(firstname = "Michele", birthYear = 1972)
    p_3: Person = Person(Lacava,Michele,1972)
  • 由于编译器实现了 unapply 方法,一个 case class 支持模式匹配

    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
    scala> case class A( a: Int )
    defined class A

    scala> case class B( b: String )
    defined class B

    scala> def classMath( x: AnyRef ): Unit = {
    | x match {
    | case A(a) => println( "A:" + a )
    | case B(b) => println( "B:" + b )
    | case A => println( A.apply(100) )
    | }
    | }
    classMath: (x: AnyRef)Unit

    scala> val a = A( 1 )
    a: A = A(1)

    scala> val b = B( "b" )
    b: B = B(b)

    scala> classMath( a )
    A:1

    scala> classMath( b )
    B:b

    也许你已经知道,在模式匹配中,当你的 case class 没有参数的时候,你是在使用 case object 而不是一个空参数列表的 case class

    1
    2
    scala> classMath( A )
    A(100)

除了在模式匹配中使用之外,unapply 方法可以让你结构 case class 来提取它的字段,如:

1
2
scala> val Person(lastname, _, _) = p
lastname: String = Lacava

你可能不知道的知识

  • 获取一个函数接收一个 tuple 作为参数,该 tuple 的元素类型与个数与某 case class 相同,那么可以将该 tuple 作为 case class 的 tuple 方法参数来构造 case class 实例

    1
    2
    3
    4
    5
    scala> val meAsTuple: (String, String, Int) = ("Lacava", "Alessandro", 1976)
    meAsTuple: (String, String, Int) = (Lacava,Alessandro,1976)

    scala> Person.tupled( meAsTuple )
    res2: Person = Person(Lacava,Alessandro,1976)
  • 相对用 tuple 来创建 case class 实例,还可以从 case class 实例中解构并提取出 tuple 对象

    1
    2
    3
    4
    5
    6
    7
    scala> val transform: Person => Option[ (String, String, Int) ] = {
    | Person.unapply _
    | }
    transform: Person => Option[(String, String, Int)] = <function1>

    scala> transform( p )
    res0: Option[(String, String, Int)] = Some((Lacava,Alessandro,1976))

另一种定义 case class 的方式

还有另一种很少人知道的定义 case class 的方式,如:

1
case class Person( lastname: String )( firstname: String, birthYear: Int )

这种方式有点像偏函数,有两个参数列表,要注意的是,对这两个参数列表是区别对待的。上文提到的所有 case class 的特性在这种定义方式下只作用于第一个参数列表中的参数(比如在参数前自动加 val,模式匹配,copy 支持等等),第二个及之后的参数列表中的参数和普通的 class 参数列表参数无异。

firstname和birthYear前不再自动添加 val,不再是类的成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scala> val p = Person("Lacava")("Alessandro", 1976)
p: Person = Person(Lacava)

scala> p.lastname
res0: String = Lacava

scala> p.firstname
<console>:11: error: value firstname is not a member of Person
p.firstname
^

scala> p.birthYear
<console>:11: error: value birthYear is not a member of Person
p.birthYear
^

copy 时,当不指定birthYear的值时,不会使用 p 中的birthYear,因为根本没这个值,会报错

1
2
3
4
scala> p.copy()(firstname = "Jhon")
<console>:11: error: not enough arguments for method copy: (firstname: String, birthYear: Int)Person.
Unspecified value parameter birthYear.
p.copy()(firstname = "Jhon")

equals 和 toString 方法也发生了改变:

1
2
3
4
5
6
7
8
9
10
11
scala> val p_1 = Person("Lacava")("Jhon", 2001)
p_1: Person = Person(Lacava)

scala> p.equals(p_1)
res9: Boolean = true

scala> p == p_1
res10: Boolean = true

scala> println ( p.toString )
Person(Lacava)

其他特性不再一一列举..

flatMap

假设有个序列, 里面装了一堆人

1
val xs = Seq(john, mary, alice, bob)

每个人有一个朋友列表, 可以这么访问:

1
x.friends // 返回一个序列, 里面每个元素是一个人

比如 john.friends 可能返回 Seq(harry, hermione, ron)

用 map 的话, 可以把 xs 里的每一个元素都变成朋友列表:

1
2
3
4
5
6
7
8
xs.map(x => x.friends) 
// 得到一个序列的序列:
// Seq(
// Seq(harry, hermione, ron),
// Seq(sam, frodo),
// Seq(),
// Seq(jamie, tyrion, cersei)
// )

然而有些时候, 你并不希望得到这么一个需要访问两层才能拿到朋友对象的序列. 有时, 你希望得到的是一个在第一层就能访问到朋友的序列. 这就需要 flatMap:

1
2
3
xs.flatMap(x => x.friends)
// 得到一个序列
// Seq(harry, hermione, ron, sam, frodo, jamie, tyrion, cersei)

这个过程就像是先 map, 然后再将 map 出来的这些列表首尾相接 (flatten).

Scala

[TOC]

简单介绍

Scala语言是一种面向对象语言,结合了命令式(imperative)和函数式(functional)编程风格,其设计理念是创造一种更好地支持组件的语言。

Object-Oriented Meets Functional

特性

  • 多范式(Multi-Paradigm)编程语言,类似Java、C#;
  • 继承面向对象编程和函数式编程的特性;
    •   面向对象:[1]. 子类继承,[2]. 混入(Mixin)机制;
    •   函数式:支持高阶函数、嵌套函数,模式匹配,支持柯里化(Currying);
    •   类型推断(Type Inference):根据函数体中的具体内容推断方法的返回值类型
  • 更高层的并发模型,可伸缩;
  • 静态类型,编译时检查;
  • 允许与Java互操作(将来会支持与.Net互操作);

与Java对比

  • 如果用作服务器端开发,Scala的简洁、灵活性是Java无法比拟的;
  • 如果是企业级应用开发、Web开发,Java的强大是Scala无法匹敌的;

Scala和Groovy

Groovy的优势在于易用性以及与Java无缝衔接,更注重实效思想,脚本化;

Scala的优势在于性能和一些高级特性,静态类型,更具有学术思想;

关于Lift

一个使用了Scala的开源的Web应用框架,可以使用所有的Java库和Web容器。其他框架:Play、Bowler等。

##基础语法问题

Scala程序的执行入口是提供main方法的独立单例对象。每个Scala函数都有返回值,每个Scala表达式都有返回结果。

在Scala中没有静态的概念,所有东西都是面向对象的。其实object单例对象只是对静态的一种封装而已,在.class文件层面,object单例对象就是用静态(static)来实现的。

###一切皆为对象

Scala中所有的操作符都是方法,操作符面向的使用者都是对象

Scala编程的一个基本原则上,能不用var,尽量不用var,能不用mutable变量,尽量不用mutable变量,能避免函数的副作用,尽量不产生副作用。

  • 对于任何对象,如果在其后面使用(),将调用该对象的apply方法,arr(0) == arr.apply(0)
  • 如果对某个使用()的对象赋值,将该对象的update方法,arr(0)=value == arr.update(0,value)

###字段 + 方法 + 函数

scalac编译器会为类中的var字段自动添加setter和getter方法,会为类中的val方法自动添加getter方法。 其中,getter方法和字段名相同。

在源码中,所有对字段的显示访问,都会在class中编译成通过getter和setter方法来访问。

方法作用于对象,方法是对象的行为。

方法定义格式:

1
def 方法名称(参数列表):返回值 = { 方法体 }
1
2
val 函数名称 = { (参数列表) => { 函数体 } },即 val 函数名称 = { Lambda表达式 }
或 val 函数名称 : (入参类型 => 出参类型) = { 入参 => 函数体 }

方法名意味着方法调用,函数名只代表函数本身

关于方法(Method)和函数(Function)

  • 函数是一等公民,使用val语句可以定义函数,def语句定义方法;
  • 函数是一个对象,继承自FuctionN,函数对象有curried,equals,isInstanceOf,toString等方法,而方法不具有;
  • 函数是一个值,可以给val变量赋值,方法不是值、也不可以给val变量赋值;
  • 通过将方法转化为函数的方式 method _ 或 method(_) 实现给val变量赋值;
  • 若 method 有重载的情况,方法转化为函数时必须指定参数和返回值的类型;
  • 某些情况下,编译器可以根据上下文自动将方法转换为函数;
  • 无参数的方法 method 没有参数列表(调用:method),无参数的函数 function 有空列表(调用:function());
  • 方法可以使用参数序列,转换为函数后,必须用 Seq 包装参数序列;
  • 方法支持默认参数,函数不支持、会忽略之;

参考:学习Scala:Scala中的字段和方法Scala中Method和Function的区别 - 简书

参数

  • 按名传参:By-name parameter,def fun(param: => T)。Evaluated every time it’s used within the body of the function
  • 按值传参:By-value parameter。Evaluated before entry into the function/method

推荐将按值传参(def fun(msg: String))改为按名称传参(def fun(msg: =>String))。这样参数会等到实际使用的时候才会计算,延迟加载计算,可以减少不必要的计算和异常。

###case class

1
case class PubInterfaceLog(interfaceId: Int, busiType: String, respTime: Long, publicId: Long)

最重要的特性是支持模式匹配,Scala官方表示:

It makes only sense to define case classes if pattern matching is used to decompose data structures.

其他特性如下:

  • 编译器自动生成对应的伴生对象和 apply() 方法;
  • 实例化时,普通类必须 new,而 case class 不需要;
  • 主构造函数的参数默认是 public 级别,默认在参数为 val;
  • 默认实现了 equals 和 hashCode,默认是可以序列化的,也就是实现了Serializable,toString 更优雅;
  • 默认生成一个 copy 方法,支持从实例 a 以部分参数生成另一个实例 b;

参考:Scala case class那些你不知道的知识

###apply() 方法

apply方法是Scala提供的一个语法糖

对apply方法的简单测试:

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
class ApplyTest {
println("class ApplyTest")
def apply() {
println("class APPLY method")
}
}
object ApplyTest {
println("object ApplyTest")
def apply() = {
println("object APPLY method")
new ApplyTest()
}
}

// 对象名+括号,调用类的apply方法
val a1 = new ApplyTest()
a1() // == a1.apply()
// 输出 class ApplyTest, class APPLY method

// 类名+括号,调用对象的apply方法
val a2 = ApplyTest()
// 输出 object ApplyTest, object APPLY method, class ApplyTest

val a2 = ApplyTest()
a2()
// 输出 object ApplyTest, object APPLY method, class ApplyTest, class APPLY method

val a3 = ApplyTest
// 输出 object ApplyTest

val a3 = ApplyTest
a3()
// 输出 object ApplyTest, object APPLY method, class ApplyTest

###几个零碎的知识点

  • Array **长度不可变, 值可变;Tuple** 亦不可变,但可以包含不同类型的元素;
  • List **长度不可变, 值也不可变,::: 连接两个List,::** 将一个新元素放到List的最前端,空列表对象 Nil
  • _:通配符,类似Java中的*,下划线 “_ “ 的用法总结
  • _=:自定义setter方法;
  • _*:参数序列化,将参数序列 Range 转化为 Seq
  • i to j : [i, j]; i until j : [i, j)
  • lazy **val**表示延迟初始化,无lazy var

###映射 + 对偶 + 元组

映射由对偶组成,映射是对偶的集合。对偶即键值对,键->值,(键, 值),是最简单的元组。元组是不同类型的值的聚集。

映射是否可变表示整个映射是否可变,包括元素值、映射中元素个数、元素次序等:

  • 不可变映射:直接用(scala.collection.mutable.)Map声明,维持元素插入顺序,支持 += 、-=;
  • 可变映射:用scala.collection.immutable.Map声明,不维持元素插入顺序,支持 +、-;

###构造器

1主构造器

1
2
3
class Student(var ID : String, var Name : String) {
println("主构造器!")
}
  • 主构造器直接跟在类名后面,主构造器的参数最后会被编译成字段
  • 主构造器执行时,会执行类中所有的语句
  • 如果主构造器参数声明时不加val或var,相当于声明为private级别

2从构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student(var ID : String, var Name : String) {
println("主构造器!")

var Age : Int = _
var Address : String = _
private val Email : String = Name + ID + "@163.com"

println("从构造器!")
def this(ID:String, Name:String, Age:Int, Address:String) {
this(ID, Name)
this.Age = Age
this.Address = Address
}
}
  • 从构造器定义在类内部,方法名为this
  • 从构造器必须先调用已经存在的主或从构造器

###集合

Scala语言的一个设计目标是可以同时利用面向对象和面向函数的方法编码,因此提供的集合类分成了可以修改的集合类和不可以修改的集合类两大类型。Scala除了Array和List,还提供了Set和Map两种集合类。

  • 通过 scala.collection.JavaConversions.*mapAsScalaMap *可将 Java 的 Map 转换为 Scala 类型的 Map;
  • 通过 scala.collection.JavaConversions.*mapAsJavaMap *可将 Scala 的映射转换为 Java 类型的映射;
  • toMap 方法将对偶集合转化为映射

###OptionNoneSome

OptionNoneSome

一致目标:所有皆为对象 + 函数式编程,在变量和函数返回值可能不会引用任何值的时候使用 Option 类型

  • 在没有值的时候,使用None;
  • 如果有值可以引用,使用Some来包含这个值;

None 和 Some 均为 Option 的子类,但是 None 被声明为一个对象,而不是一个类.

###伴生对象

当单例对象(object Name,Singleton Object)与类(class Name)同名时,该单例对象称作类的伴生对象(Companion Object),该类称作单例对象的伴生类。没有伴生类的单例对象称为孤立对象(Standalone Object),最常见的就是包含程序入口main()的单例对象。

  • 同名且在同一个源文件中定义
  • 可以互相访问其私有成员

参考:Scala学习笔记-伴生对象于孤立对象

  • 伴生对象的属性、方法指向全局单例对象 MODULE$
  • ``伴生类的属性、方法被定义为是对象相关的

单例对象在第一次访问时初始化,不可以new、不能带参数,而类可以new、可以带参数。

  • 伴生对象中定义的字段和方法, 对应同名.class类中的静态方法,对应同名$.class虚构类中的成员字段和方法
  • 伴生类中定义的字段和方法, 对应同名.class类中的成员字段和成员方法

同名.class类中的静态方法,会访问单例的虚构类的实例化对象, 将相关的逻辑调用转移到虚构类中的成员方法中,即:.class类提供程序的入口,$.class提供程序的逻辑调用。

  • 伴生对象中的逻辑,转移到$.class虚构类中去处理
  • 伴生类中的逻辑,转移到.class同名类中的成员方法中去处理

通过伴生对象来访问伴生类的实例,提供了控制实例个数的机会。虚构类的单例性,保证了伴生对象中信息的唯一性。

那么:伴生对象如何体现单例呢?

因为伴生类不是单例的,如何实现单例模式呢?方法2种:

1)将伴生类中的所有逻辑全部移到单例对象中,去除伴生类, 单例对象成为孤立对象, 该孤立对象天然就是单例的

2)若必须存在伴生类,如何保证伴生类是单例的? 将伴生类的主构造器私有, 并在伴生对象中创建一个伴生类的对象, 该对象就是唯一的

1
2
3
4
5
6
7
8
9
class A private {
}
object A {
val single = new A()
}
// true
var a1 = A.single
var a2 = A.single;
println("a1 eq a2 : " + (a1 eq a2))

参考:学习Scala:孤立对象的实现原理学习Scala:伴生对象的实现原理

###Trait

特质,类似有函数体的 Interface,利用关键字 with 混入。

###IO读写

Scala本身提供 Read API,但是 Write API 需要利用Java的API,文件不存在会自动创建。

1Read

1
2
3
import scala.io.Source
val content = Source.fromURL(url).getLines() // 网络资源
val lines = Source.fromFile(filePath).getLines() // 文件

2Write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.File
import java.io.PrintWriter
import java.io.FileWriter
import java.io.BufferedWriter

// 文件首次写入,ok; 不支持追加
val writer = new PrintWriter(new File("sqh.txt"))
writer.println("xxx")

// 支持追加;但是有换行问题
val writer = new FileWriter(new File("sqh.txt"), true)
writer.write("xxx")

// 支持追加,换行解决方法1
val writer = new BufferedWriter(new FileWriter(new File("sqh.txt"), true))
writer.write("xxx")
writer.newLine()

// 支持追加,换行解决方法2
val writer = new PrintWriter(new FileWriter(new File("sqh.txt"), true))
writer.println("xxx")

writer.flush()
writer.close

注意,关闭流前,先刷新。PrintWriter 不支持追加,FileWriter 支持追加,但存在换行问题。可以将 FileWriter 包装成 BufferedWriter 或 PrintWriter 解决换行问题。

##参考

方法参数

一、普通参数

1
2
3
4
5
def addInt( a:Int, b:Int ) : Int = {
var sum:Int = 0
sum = a + b
return sum
}

二、传值调用和传名调用: =>

两者的区别主要在于变量名和变量类型之间是否多了一个 =>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object Test {
def main(args: Array[String]) {
delayed(time());
delayed2(time());
}

def time() = {
println("获取时间,单位为纳秒")
System.nanoTime
}
// 传名调用
def delayed2( t: => Long ) = {
println("在 delayed 方法内")
println("参数: " + t)
t
}
// 传值调用
def delayed( t: Long ) = {
println("在 delayed 方法内")
println("参数: " + t)
t
}
}

三、参数顺序

通过在调用者端添加

参数名=

1
2
3
4
5
6
7
8
9
object Test {
def main(args: Array[String]) {
printInt(b=5, a=7);
}
def printInt( a:Int, b:Int ) = {
println("Value of a : " + a );
println("Value of b : " + b );
}
}

四、可变参数

感觉就是一个数组

1
2
3
4
5
6
7
8
9
10
11
12
object Test {
def main(args: Array[String]) {
printStrings("Runoob", "Scala", "Python");
}
def printStrings( args:String* ) = {
var i : Int = 0;
for( arg <- args ){
println("Arg value[" + i + "] = " + arg );
i = i + 1;
}
}
}

五、默认参数

1
2
3
4
5
6
7
8
9
10
object Test {
def main(args: Array[String]) {
println( "返回值 : " + addInt() );
}
def addInt( a:Int=5, b:Int=7 ) : Int = {
var sum:Int = 0
sum = a + b
return sum
}
}

六、高阶函数

1
2
3
4
5
6
7
8
9
10
object Test {
def main(args: Array[String]) {7
println( apply( layout, 10) )
}
// 函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v
def apply(f: => String, v: Int) = f(v)
// 泛型
def layout[A](x: A) = "[" + x.toString() + "]"

}

七、匿名函数

1
2
3
4
5
var inc = (x:Int) => x+1
print(inc(2))

var userDir = () => { System.getProperty("user.dir") }
println( userDir() )

八、偏函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Date

object Test {
def main(args: Array[String]) {
val date = new Date
val logWithDateBound = log(date, _ : String)

logWithDateBound("message1" )
Thread.sleep(1000)
logWithDateBound("message2" )
Thread.sleep(1000)
logWithDateBound("message3" )
}

def log(date: Date, message: String) = {
println(date + "----" + message)
}
}

运行 bat ,调用sql文件即可。

创建bat文件

1
2
3
4
@echo off
echo ###用生成的文件操作DB2数据库###
db2cmd db2 -tvf "importall.sql" -l import.log
pause

创建sql文件

1
2
3
4
connect to zl user test using testtest;
load client from 'E:\zds\czj_data\test-20091029' of del insert into test_tmp;
update CCARD.card set SPLIT_DATE = '20161111' where SPLIT_DATE is null
connect reset;

db2cmd 部分参数

  • -c 执行 DB2 命令窗口,然后终止。
    -w 一直等到 DB2 命令窗口终止。
    -i 从进行调用的 shell 继承环境。
    -t 从进行调用的 shell 继承标题。

db2 部分参数

  • -t 指明在缺省情况下用分号(;)终止每条命令
  • -v 指明应将每条命令显示到标准输出
  • -f 指明从输入文件读取命令
  • -l 指明将命令记录到输出文件中
  • -r 指明将结果保存到报告文件中
  • -s stop commands on command error

更多参数请访问 db2 命令 options

对象序列化(serialization)反序列化(deserialization)是将对象转化为便于传输的格式进行发送和接收的两个操作。常见的序列化格式有字节数组,json字符串,xml字符串等。

本次讨论的是java中的对象字节序列化

1.序列化方法

使用此方法进行序列化的对象必须实现Serializable接口,不然在进行序列化时会抛出NotSerializableException异常。

1
2
3
4
5
6
7
8
public static byte[] toBytes(Serializable obj) throws IOException {
try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
){
objectOutputStream.writeObject(obj);
return byteArrayOutputStream.toByteArray();
}
}

ObjectOutputStream的writeObject方法将对象序列化后写入一个字节流中,而这个字节流就是在初始化ObjectOutputStream对象时传入的字节流,这里使用ByteArrayOutputStream,可以获取到字节流中的字节数组。

2.反序列化方法

对应序列化,反序列化应该是将字节数组转化为对象。

1
2
3
4
5
6
7
8
public static Serializable toObj(byte[] bytes) throws IOException, ClassNotFoundException {
try(ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
){
Object o = objectInputStream.readObject();
return (Serializable) o;
}
}

ByteArrayInputStream将字节数组转化为字节流。而ObjectInputStream的readObject方法将字节流转化为对象。

3.序列化例子

建立一个Student类,并实现Serializable接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student implements Serializable{
private int id;
private String code;
private String name;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", code='" + code + '\'' +
", name='" + name + '\'' +
'}';
}
//省略set/get方法
···

测试代码:

1
2
3
4
5
6
7
8
//创建student对象
Student student = new Student(1,"2014213880","刘瑞杰");
//序列化
byte[] bytes = toBytes(student);
System.out.println(bytes.length);
//反序列化
Student student0 = (Student) toObj(bytes);
System.out.println(student0);

输出结果:

1
2
111
Student{id=1, code='2014213880', name='刘瑞杰'}

4.关于serialVersionUID

serialVersionUID 这个变量,如果不赋值,JAVA会自动根据类的相关信息自动生成。

现在设想一个场景,如果将对象序列化后,并没有马上反序列化,而是将对象保存到文件中,然后修改类的定义,比如在Student类中加一个字段,然后进行反序列化。

这时就是出现异常:

1
2
3
4
Exception in thread "main" java.io.InvalidClassException: serilize.Student;
local class incompatible:
stream classdesc serialVersionUID = 6643383736516292602,
local class serialVersionUID = -1315177638240754633

4.1 为什么时出现这种情况呢?

当进行序列化操作时,JAVA会记录类的serialVersionUID,如果没有设置,则会根据类的信息自动生成一份serialVersionUID。

当进行反序列化时,同样会根据类信息自动生成serialVersionUID。

如果在序列化后 类的相关字段被修改,那么生成的serialVersionUID就会与原先的不同,这就会引起InvalidClassException

4.2 如何避免InvalidClassException

设置 serialVersionUID值即可。

1
private final static long serialVersionUID = 1L;

5. 关于transient

如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。

6. 静态成员变量

由于静态变量是类级别的,在序列化过程中,静态变量会被忽略,并不参与序列化过程。

串行执行不同tasks

一、监听器

使用监听器(TriggerListener, JobListener or SchedulerListener)

通过在 JobListener.jobWasExecuted() 中 立即执行一个新的任务。

这种方法可能侵入性比较强,因为需要在监听器中定义任务的关联关系和需要考虑任务信息的持久化。

One way is to use a listener (i.e. a TriggerListener, JobListener or SchedulerListener) that can notice the completion of a job/trigger and then immediately schedule a new trigger to fire. This approach can get a bit involved, since you’ll have to inform the listener which job follows which and you may need to worry about persistence of this information.

二、使用 JobDataMap

在 JobData 中保存下一个执行任务的标识,在执行的最后执行下一个任务。

Another way is to build a Job that contains within its JobDataMap the name of the next job to fire, and as the job completes (the last step in its Execute() method) have the job schedule the next job.

#同一任务并发执行与数据共享
@(springboot)[quartz]

[TOC]

1. 禁止同一个 JobDetail 中的多个实例并发执行

Quartz 定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行。

同一任务的含义指同一个JobKey

1.1 方法一、使用 xml

Spring 配置文件中加入:

1
2
3
4
5
6
7
8
<bean id="resultJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 调用的类 -->
<property name="targetObject" ref="resultJob"></property>
<!-- 调用类中的方法 -->
<property name="targetMethod" value="createDemEmps"></property>
<!-- false 表示等上一个任务执行完后再开启新的任务 -->
<property name="concurrent" value="false" />
</bean>

1.2 方法二、使用注解

当不使用 Spring 的时候,只需要在 Job 的实现类上加 @DisallowConcurrentExecution 的注解。

1
2
3
4
5
6
7
8
9
@DisallowConcurrentExecution
public class DaemonExecutionSchedule implements Job{
private Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
...
}
}

2. 同一个 JobDetail 中多个实例的数据共享

@PersistJobDataAfterExecution 是用在 Job 实现类上,表示一个有状态的任务,意思是当正常执行完 Job 后,JobDataMap 中的数据应该被改动,以被下一次调用时用。

注意:当使用 @PersistJobDataAfterExecution 注解时,为了避免并发时,存储数据造成混乱,强烈建议把 @DisallowConcurrentExecution 注解也加上。

3. 示例

假设定时任务的时间间隔为 3 秒,但 job 执行时间是 10 秒。当设置 @DisallowConcurrentExecution 以后程序会等任务执行完毕后再去执行。

当设置 @PersistJobDataAfterExecution 时,在执行完 Job 的 execution 方法后保存 JobDataMap 当中固定数据,以便任务在重复执行的时候具有相同的 JobDataMap;在默认情况下也就是没有设置 @PersistJobDataAfterExecution 的时候每个 job 都拥有独立 JobDataMap。

任务类:

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
package org.quartz.examples;

import org.quartz.*;
import java.util.Date;

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class TaskJob implements Job {

public static final String NUM_EXECUTIONS = "NumExecutions";
public static final String EXECUTION_DELAY = "ExecutionDelay";

/**
* 静态变量可以保持工作状态,但无法达到预期效果
*/
private static int _staticCounter = 0;

/**
* Quartz 每次执行作业时都会重新实例化,非静态变量无法保持工作状态
*/
private int _counter = 0;

/**
* 需要一个公共的空构造方法,以便 scheduler 随时实例化 job
*/
public TaskJob() {
}

/**
* 该方法实现需要执行的任务
*/
public void execute(JobExecutionContext context) throws JobExecutionException {
System.err.println("---> " + context.getJobDetail().getKey() + " 运行中[" + new Date() + "]");

JobDataMap map = context.getJobDetail().getJobDataMap();

int executeCount = 0;
if (map.containsKey(NUM_EXECUTIONS)) {
executeCount = map.getInt(NUM_EXECUTIONS);
}

// 增量计数并将其存储回 JobDataMap,这样可以适当保持工作状态
executeCount++;
map.put(NUM_EXECUTIONS, executeCount);

// 只要有任务执行都会递增,无法达到预期效果
_staticCounter++;

// 本地变量递增加,但实际上无法保持工作状态
_counter++;

long delay = 5000L;
if (map.containsKey(EXECUTION_DELAY)) {
delay = map.getLong(EXECUTION_DELAY);
}

try {
// 模拟一个耗时的 job
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.err.println(context.getJobDetail().getKey() + " 的静态变量 _staticCounter 为:" + _staticCounter + ",非静态变量 scheduler 为:" + _counter);
System.err.println(context.getJobDetail().getKey() + " 完成了(" + executeCount + ")次 <---");
}
}

任务调度类:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
package org.quartz.examples;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class Executer {

public void run() throws Exception {
// 通过 schedulerFactory 获取一个调度器
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();

// 创建 jobDetail 实例,绑定 Job 实现类
// 指明 job 的名称,所在组的名称,以及绑定 job 类
JobDetail job1 = JobBuilder.newJob(TaskJob.class)
.withIdentity("statefulJob1", "group1")
// 给定的键-值对添加到 JobDetail 的 JobDataMap 中
.usingJobData(TaskJob.EXECUTION_DELAY, 10000L).build();

// 定义调度触发规则,先立即执行一次,然后每隔 3 秒执行一次
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();

// 把作业和触发器注册到任务调度中
Date firstRunTime = sched.scheduleJob(job1, trigger);
System.out.println(job1.getKey() + " 开始运行于:" + firstRunTime + ",重复:" + trigger.getRepeatCount() + " 次,每次间隔 "
+ trigger.getRepeatInterval() / 1000 + " 秒");

// 任务 job1 方法中拿到的 JobDataMap 的数据是共享的
// 这里要注意一个情况: 就是 JobDataMap 的数据共享只针对一个 job1 任务
// 如果在下面在新增加一个任务 那么他们之间是不共享的,比如下面的 job2
// 创建第二个 JobDetail 实例
JobDetail job2 = JobBuilder.newJob(TaskJob.class)
.withIdentity("statefulJob2", "group1")
// 给定的键-值对添加到 JobDetail 的 JobDataMap 中
.usingJobData(TaskJob.EXECUTION_DELAY, 10000L)
.build();

// 定义调度触发规则,先立即执行一次,然后每隔 3 秒执行一次
trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger2", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().
withIntervalInSeconds(3)
.repeatForever()
// 指定失效时的策略
.withMisfireHandlingInstructionNowWithExistingCount())
.build();

// 这个 job2 与 job1 执行的 JobDataMap 不共享
// 把作业和触发器注册到任务调度中
firstRunTime = sched.scheduleJob(job2, trigger);
System.out.println(job2.getKey() + " 开始运行于:" + firstRunTime + ",重复:" + trigger.getRepeatCount() + " 次,每次间隔 "
+ trigger.getRepeatInterval() / 1000 + " 秒");

// 启动计划程序(实际上直到调度器已经启动才会开始运行)
sched.start();

// 等待 60 秒,使我们的 job 有机会执行
Thread.sleep(60000);

// 等待作业执行完成时才关闭调度器
sched.shutdown(true);

SchedulerMetaData metaData = sched.getMetaData();
System.out.println("一共运行了:" + metaData.getNumberOfJobsExecuted() + " 个任务");
}

public static void main(String[] args) throws Exception {
Executer example = new Executer();
example.run();
}
}

运行结果:

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
group1.statefulJob1 开始运行于:Wed Apr 19 17:04:22 CST 2017,重复:-1 次,每次间隔 3 秒
group1.statefulJob2 开始运行于:Wed Apr 19 17:04:22 CST 2017,重复:-1 次,每次间隔 3 秒

---> group1.statefulJob2 运行中[Wed Apr 19 17:04:22 CST 2017]
---> group1.statefulJob1 运行中[Wed Apr 19 17:04:22 CST 2017]
group1.statefulJob2 的静态变量 _staticCounter 为:2,非静态变量 scheduler 为:1
group1.statefulJob1 的静态变量 _staticCounter 为:2,非静态变量 scheduler 为:1
group1.statefulJob2 完成了(1)次 <---
group1.statefulJob1 完成了(1)次 <---
---> group1.statefulJob1 运行中[Wed Apr 19 17:04:32 CST 2017]
---> group1.statefulJob2 运行中[Wed Apr 19 17:04:32 CST 2017]
group1.statefulJob1 的静态变量 _staticCounter 为:4,非静态变量 scheduler 为:1
group1.statefulJob1 完成了(2)次 <---
group1.statefulJob2 的静态变量 _staticCounter 为:4,非静态变量 scheduler 为:1
group1.statefulJob2 完成了(2)次 <---
---> group1.statefulJob1 运行中[Wed Apr 19 17:04:42 CST 2017]
---> group1.statefulJob2 运行中[Wed Apr 19 17:04:42 CST 2017]
group1.statefulJob2 的静态变量 _staticCounter 为:6,非静态变量 scheduler 为:1
group1.statefulJob1 的静态变量 _staticCounter 为:6,非静态变量 scheduler 为:1
group1.statefulJob1 完成了(3)次 <---
group1.statefulJob2 完成了(3)次 <---
---> group1.statefulJob1 运行中[Wed Apr 19 17:04:52 CST 2017]
---> group1.statefulJob2 运行中[Wed Apr 19 17:04:52 CST 2017]
group1.statefulJob2 的静态变量 _staticCounter 为:8,非静态变量 scheduler 为:1
group1.statefulJob2 完成了(4)次 <---
group1.statefulJob1 的静态变量 _staticCounter 为:8,非静态变量 scheduler 为:1
group1.statefulJob1 完成了(4)次 <---
---> group1.statefulJob2 运行中[Wed Apr 19 17:05:02 CST 2017]
---> group1.statefulJob1 运行中[Wed Apr 19 17:05:02 CST 2017]
group1.statefulJob2 的静态变量 _staticCounter 为:10,非静态变量 scheduler 为:1
group1.statefulJob1 的静态变量 _staticCounter 为:10,非静态变量 scheduler 为:1
group1.statefulJob2 完成了(5)次 <---
group1.statefulJob1 完成了(5)次 <---
---> group1.statefulJob1 运行中[Wed Apr 19 17:05:12 CST 2017]
---> group1.statefulJob2 运行中[Wed Apr 19 17:05:12 CST 2017]

group1.statefulJob2 的静态变量 _staticCounter 为:12,非静态变量 scheduler 为:1
group1.statefulJob2 完成了(6)次 <---
group1.statefulJob1 的静态变量 _staticCounter 为:12,非静态变量 scheduler 为:1
group1.statefulJob1 完成了(6)次 <---

一共运行了:12 个任务