分类目录归档:scala

Int与Integer的拆箱问题

一段程序从java迁移到scala后遇到的,

private val map = new java.util.concurrent.ConcurrentHashMap[String, Int]

val c = map.get(key)

// 下面的if语句在eclipse里提示
// comparing values of types Int and Null using `==' will always yield false
if (c == null) { 
    ...  
}

问题简化后如下:

scala> val m = new java.util.concurrent.ConcurrentHashMap[String, Int]
m: java.util.concurrent.ConcurrentHashMap[String,Int] = {}

scala> m.get("key")
res0: Int = 0

scala> val m = new java.util.concurrent.ConcurrentHashMap[String, java.lang.Integer]
m: java.util.concurrent.ConcurrentHashMap[String,Integer] = {}

scala> m.get("key")
res2: Integer = null

为何value设置为scala里的Int类型得到0而用java里的Integer确是null? 跟以前遇到的null造型为值类型时为何不抛异常 是相似的事

$ scala -Xprint:jvm -e 'val m = new java.util.concurrent.ConcurrentHashMap[String, Int]; val c = m.get("key")'
...
anon$1.this.m = new java.util.concurrent.ConcurrentHashMap();
anon$1.this.c = scala.Int.unbox(anon$1.this.m().get("key"));
...

$ scala -Xprint:jvm -e 'val n:Int = null.asInstanceOf[Int]' ...
anon$1.this.n = scala.Int.unbox(null);
...

scala> val n:Int = null.asInstanceOf[Int]
n: Int = 0

scala> scala.Int.unbox(null)
res3: Int = 0



scala> null.asInstanceOf[java.lang.Integer]
res5: Integer = null

scala> null.asInstanceOf[Int]
res6: Int = 0

scala.Int.unbox 注释里提到运行时是 scala.runtime.BoxesRunTime里的unboxToInt方法

package scala.runtime.BoxesRunTime;

public static int unboxToInt(Object i) {
    return i == null ? 0 : ((java.lang.Integer)i).intValue();
}

还是在对待Int类型时,两种语言设计上的差异,Scala里统一了primitive类型和引用类型。

Java/Scala程序员书单

这里列一些对Java/Scala程序员有帮助的书单,先随意记录一些,后续有空会分类一下并补充更多的评价。

《软件框架设计的艺术》
这是Netbeans的创始人写的一本很有价值的书,里面的边角细节也很有料。国内市场上没有对这本书给予应给的赞誉。

《Effective Java 第二版》
被称为Java领域的圣经,小中见大。探讨的不仅仅是Java语言,也包括一些形而上的东西,取决于读的人理解多少。我在面试时喜欢考察一些基础的东西,以及背后的想法和初衷,可惜相当多的程序员这本书都没仔细阅读过。

《并发编程实践》

《Java并发编程》

《Java 并发编程设计原则与模式》

《代码的未来》

《Programming in Scala》
中文版是”Scala编程”,对scala程序员来说这本书你不能不读

《Java解惑》

《深入Java虚拟机》(原书第2版)
有些过时了

《Java与模式》
十几年前正是模式刚流行的时候,阎宏博士的这本书当时在中文圈里引起了很大反响,这本书算得上一本经典巨著。

《观止-微软创建NT和未来的夺命狂奔》
好些年前从温绍锦那儿借来看的,忘了还给他。介绍了NT的开发过程,写给大众看的,偏故事性,读起来很过瘾。

《程序设计语言》第三版

《重构》

《Java Rules中文版》

《企业应用架构模式》

《领域驱动设计》

《Java虚拟机规范(Java SE 7版)》

《Java程序员修炼之道》

《HTTP权威指南》

《TCP/IP详解卷1:协议》

《TCP/IP详解卷2:实现》

《构建高性能Web站点》

《JAVASCRIPT语言精髓与编程实践》

《深入剖析Tomcat》

《Maven实战》

《哥德尔、艾舍尔、巴赫:集异璧之大成》

《An Introduction to Functional Programming Through Lambda Calculus》

《分布式系统概念与设计(原书第3版)》

《实用Common Lisp编程》

《面向模式的软件架构 卷1:模式系统》

《面向模式的软件架构 卷2:并发和联网对象模式》

《面向模式的软件架构 卷3:资源管理模式》

《面向模式的软件架构 卷4:分布式计算的模式语言》

《面向模式的软件架构 卷5:模式与模式语言》

《编程语言实现模式》

《架构之美》

《精通正则表达式》

《浪潮之巅》

《多处理器编程的艺术》

《JAVA核心技术卷II:高级特性》

《Java核心技术 卷I: 基础知识》

《程序员修炼之道——从小工到专家》

Actor里的偏函数与性能

Evan Chan分享的《Akka in Production: Our Story》是一篇非常务实的工程实践分享,里面有很多可借鉴的点子,其中的一个对消息接受的包装逻辑主要预留了扩展点:

trait ActorStack extends Actor {

    def wrappedReceive: Receive // Receive类型是一个偏函数

    def receive: Receive = {
        case x => if (wrappedReceive.isDefinedAt(x)) wrappedReceive(x) else unhandled(x)
    }
}

大部分人在实现业务逻辑时可能如下

class MyActor extends ActorStack {

    def wrappedReceive: Receive = {
        case Something => balabala
    }
}

这种情况下会有个小小的性能浪费,每次接收消息的时候,至少要创建一次偏函数对象。如果消息又正好在wrappedReceive里定义了的话,创建了2次偏函数对象,因为调用了2次wrappedReceive方法;这个是可以避免的,用一个成员把这个偏函数对象保存起来,避免每次都创建:

trait ActorStack extends Actor {

    def wrappedReceive: Receive

    private val logic = wrappedReceive

    def receive: Receive = {
        case x => if (logic.isDefinedAt(x)) logic(x) else unhandled(x)
    }
}

或许看客们又好奇,那么Akka对receive这个函数返回的偏函数又是怎么处理的?是否也存在每次都生产一下(同样的性能浪费),还是保存在某个地方? 没错,它在底层实现里是被放到behaviorStack这个行为栈里的,每次处理逻辑的时候从行为栈里取出这个偏函数进行调用。

Patterns.ask 是使用一个临时创建的actor发消息而非自身

用ask发消息给另一个actor时,在接收方收到消息时对发送者做了watch,但发现并不是目标actor,而是一个临时的actor: Actor[akka://AkkaActorSystem/temp/$a]

发送者actor里的逻辑:

val f = targetActor ? Register
Await.result(f, timeout.duration)

接收者actor里的逻辑:

case Register => {
  this.observable = sender
  watch(sender)
  become(...)
}

后来发现这个是因为ask模式就这么设计的,它使用一个临时创建的actor来转发消息并等待对方返回。

跟踪到ask方法内部,是创建了一个PromiseActorRef:

val a = new PromiseActorRef(provider, result)产生了一个临时的Actor[akka://AkkaActorSystem/temp/$a] 被用来代替当前actor发送消息给目标,当它接收到目标回应的结果后,会被卸载掉。

scala雾中风景(24): break与异常捕获

这段代码如果把异常捕获部分的Exception替换为Throwable会有什么不同?

import scala.util.control.Breaks.break
import scala.util.control.Breaks.breakable

object Main {

  @throws(classOf[Exception])
  def prepare() = false

  def main(args:Array[String]) {
    var i = 0

    while(i < 3) {
      breakable {

        try{
          i+=1
          if(!prepare()) break
          println("do something here")
        }catch {
          //case e: Throwable => e.printStackTrace
          case e: Exception => e.printStackTrace
        }

        println("done")
      }
    }

  }
}

问题的关键在于scala里的break是通过异常机制实现的流控,与java里完全不同。break是抛出了一个BreakControl的异常,也是从Throwable接口继承下来的。所以当应用在捕获异常时,try代码块里有break流控语句,需要注意对流控异常不要干预,如果这种情况下真要捕获Throwable的话,正确的方式是:

try{
    if (...) break
    ...

}catch {
    case e0: ControlThrowable => throw e0 // 不要干预流控的异常
    case e1: Throwable => e1.printStackTrace
}

null造型为值类型时为何不抛异常

问题简化后看一下代码片段:

// Number引用类型
scala> def foo[T](t: T) = {  t.asInstanceOf[Number] }
foo: [T](t: T)Number

scala> foo(null)
res3: Number = null

// Int值类型
scala> def foo[T](t: T) = {  t.asInstanceOf[Int] }
foo: [T](t: T)Int

scala> foo(null)
res4: Int = 0

// 其它值类型
scala> class V(val i:Int) extends AnyVal
defined class V

scala> null.asInstanceOf[V]
res10: V = V@0

把 null 造型为java.lang.Number这种引用类型的时候没有问题,但造型为scala里的值类型时,为何不抛出异常?与直觉不符。背后是装箱,拆箱所致。

在stackoverflow上也有讨论

// 不涉及到unboxing
scala> val a:Any = null.asInstanceOf[Int]
a: Any = null

// 涉及到unboxing
scala> val a = null.asInstanceOf[Int]
a: Int = 0  

// 在scala.Int伴生对象里定义的运行时拆箱逻辑
def unbox(x: java.lang.Object): Int = x.asInstanceOf[java.lang.Integer].intValue()

另外,有人问为什么不抛出空指针异常,因为规范里是抛异常:

asInstanceOf[T ] returns the “null” object itself if T conforms to scala.AnyRef,and throws a NullPointerException otherwise.
A reference to any other member of the “null” object causes a NullPointerException to be thrown.

这个规范已经更新了,2.10里的规范已经没有要求抛出异常了。

https://issues.scala-lang.org/browse/SI-4437

https://github.com/scala/scala-dist/pull/104

对actor的邮箱计数

假设某个actor里消息有下面几种类型:

def wrappedReceive: PartialFunction[Any, Unit] = {
    case "kafkaReady"     => sendRequest()
    case Some(Task(data)) => assign(data)
    case None             => await()
    case "done"           => next()
}

当我想要查看这个actor的邮箱有没有堆积时,一个简单的方式是通过jmap来看类型的实例数:

$ jmap -histo:live 12662 

num     #instances         #bytes  class name
----------------------------------------------
...
3:   51906   9965952    com.wacai.xxx.dataobject.XX
...
6:   149338  3584112    java.util.concurrent.ConcurrentLinkedQueue$Node
7:   149318  3583632    akka.dispatch.Envelope
...
17:  7266    232512     scala.collection.mutable.ListBuffer
...
21:  7274    116384     scala.Some
22:  7266    116256     com.wacai.xxx.bridge.actor.Task

注意必须以live方式执行jmap触发一次full gc回收掉无用对象,否则计数不准确。上面大致可以看出Some[Task]对象实例有7266个,Envelop有149318个,因为None是一个单例,所以尽管可能堆积多条这样的消息,实例数里却只有1个,只能通过 Envelop的实例数减去其他类型的实例数来判断,但问题是Envelop实例数是所有actor的消息实例数;另外像String类型这样的消息类型也可能存在多个,很难准确判断每个actor的邮箱到底堆积了多少条。

Akka在2.0的时候去掉了mailboxSize方法,参考官方的这篇blog, 里面说了为何不再支持这个方法,也提到如果你真的想要获取mailboxSize可以自己扩展一下,于是按照这个思路,我们对enqueuedequeue做一个计数,以便在某些场景判断mailbox是否有堆积。

class MyMailbox extends akka.dispatch.UnboundedMailbox.MessageQueue {
    private val counter = new java.util.concurrent.atomic.AtomicInteger(0)

    override def dequeue(): Envelope = {
        counter.decrementAndGet()
        super.dequeue()
    }

    override def enqueue(receiver: ActorRef, handle: Envelope): Unit = {
        counter.incrementAndGet()
        super.enqueue(receiver, handle)
    }

    override def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit = {
        counter.set(0)
        super.cleanUp(owner, deadLetters)
    }

    def getSize(): Int = counter.get()
}

class MyMailboxType(settings: ActorSystem.Settings, config: Config) extends MailboxType {

    override def create(owner: Option[ActorRef], system: Option[ActorSystem]) = 
        (owner, system) match {
        case (Some(o), Some(s)) ⇒
            val mailbox = new MyMailbox
            MailboxExtension(s).register(o, mailbox)
            mailbox
        case _ ⇒ throw new Exception("no mailbox owner or system given")
    }
}

然后放到一个akka的extension里使用:

object MailboxExtension extends ExtensionId[MailboxExtension] with ExtensionIdProvider {
    override def createExtension(s: ExtendedActorSystem) = new MailboxExtension(s)

    override def lookup = this

    def getSize()(implicit context: ActorContext): Int = 
                    MailboxExtension(context.system).getSize()
}


class MailboxExtension(val system: ExtendedActorSystem) extends Extension {

    private val mboxMap = new java.util.concurrent.ConcurrentHashMap[ActorRef, MyMailbox]

    def register(actorRef: ActorRef, mailbox: MyMailbox): Unit = mboxMap.put(actorRef, mailbox)

    def unregister(actorRef: ActorRef): Unit = mboxMap.remove(actorRef)

    def getSize()(implicit context: ActorContext): Int = {
        val mbox = mboxMap.get(context.self)
        if (mbox == null)
            throw new IllegalArgumentException("Mailbox not registered for: " + context.self)
        mbox.getSize()
    }
}

scalastyle工具

scalastyle 是个简单易用的code style检测工具,非常轻巧。有助于团队风格一致。集成在maven里用很方便。

从github里找到一个scalastyle_config.xml,有几个默认开启的选项对我们不太适用,可以关闭:

// 对文件开头的注释(licence)检测,非api代码的话建议关闭
class="org.scalastyle.file.HeaderMatchesChecker" level="warning" enabled="false"

// 强制 if 后边适用花括号
class="org.scalastyle.scalariform.IfBraceChecker" level="warning" enabled="false"

// 结尾必须有换行符
class="org.scalastyle.file.NewLineAtEofChecker" level="warning" enabled="false"

// 注释内容必须在注释符之后有个空格,如果ide格式化能保证的话最好,做不到且觉得心烦可以去掉
class="org.scalastyle.scalariform.SpaceAfterCommentStartChecker" level="warning" enabled="false"

// 如果开启,对于返回值为Unit的函数(也称为过程函数)要显式的声明返回值类型 Unit,没必要
class="org.scalastyle.scalariform.ProcedureDeclarationChecker" level="warning" enabled="false"

// 要看情况,如果日志里刻意打印一些连续的字符,可以把这个警告关闭
class="org.scalastyle.scalariform.MultipleStringLiteralsChecker" level="warning" enabled="false"

另外,程序里如果有正常使用println的情况,可以在RegexChecker里去掉值为”println”的”regex”参数

华东地区scala爱好者聚会(2015上海)

昨天下午在上海陆家嘴软件园组织了2015华东地区scala爱好者聚会,原本有6个topic,不巧老高和Intel的钟翔时间冲突没有参与。

报名人数有40人,到场的30人左右。4个topic分别是:

《scala配置与宏》
《scala大数据处理》
《某金融公司的scala使用经验》
《中等创业公司的后端技术选型》

聚会上有几个杭州的创业公司专门赶去的,他们比较进取,产品完全采用scala开发,甚至App端也采用scala。

分享的PPT除了某公司一篇不便公开的,其他都已经放在github上了。