Scala支持与Java的隐式转换

标签: scala java | 发表时间:2014-01-21 21:55 | 作者:
出处:http://agiledon.github.com/

Neal Ford在几年前提出的“Poly Programming”思想,已经逐渐成为主流。这种思想并非是为了炫耀多语言的技能,然后选择“高大上”。真正的目的在于更好地利用各种语言处理不同场景、不同问题的优势。

由于都运行在JVM上,Java与Scala之间基本能做到无缝的集成,区别主要在于各自的API各有不同。由于Scala为集合提供了更多便捷的函数,因此,Java与Scala在集合之间的互操作,或许是在这种多语言平台下使用最为频繁的。

Scala中操作Java集合

两种情况需要在Scala中操作Java集合。一种是Scala调用了其他的Java库,针对Java集合需要转换为Scala集合,如此才能享受Scala集合提供的福利;另一种是编写了Scala程序,但需要提供给Java库,为了更好地无缝集成,要让Java库体会不到Scala的存在。

Scala调用Java库

为了享用Scala提供的集合特性,在Scala程序中若要调用Java库,通常需要将其转换。例如,JavaXmlConfigure为一个Java类,它的readSoftInfos()方法返回的是一个Java的List。现在,我在Scala中调用该方法(这里以ScalaTest编写的测试来表现Scala程序):

      class XmlConfigureSpec extends FlatSpec with ShouldMatchers {
    it should "load all package soft nodes for version config" in {
      val configure = new JavaXmlConfigure
      val result = configure.readSoftInfos("/config.xml", "version number")
      result.foreach {
        softInfo => println(softInfo)
      }
    }
}

这时,编译器会提示无法找到result的foreach方法。因为这里的result的类型为java.util.List。若要将其转换为Scala的集合,就需要增加如下语句:

      import scala.collection.JavaConversions._

注意,经过隐式转换后,这里的result类型为Seq[SoftInfo]。如果像下面这样显式指定为Scala的List或Set类型,则无法转换:

      val result:Set[SoftInfo] = configure.readSoftInfos("/config.xml", "version number") //or
val result:List[SoftInfo] = configure.readSoftInfos("/config.xml", "version number")

Scala的代码以Java库的形式提供给Java调用者

在JVM平台下进行多语言开发时,多数情况下会以Java为主,而对于一些特定场景,能够更好发挥Scala特性的,例如并发处理等,则会选择Scala。此时,若要做到对Java友好,则对于Scala的方法返回值,应尽量屏蔽Scala的类型信息。

举例来说,我用Scala来读取一个配置文件,并对配置文件进行解析和转换,得到一个Scala的Seq集合对象,如下代码所示:

      class XmlConfigure {
  def readSoftInfos(configFileName: String, version: String)  = {
    val document = XML.load(getClass.getResource(configFileName))

    val pkgSoftNodes = document \\ "PKGSOFT"

    val softInfoNodes = pkgSoftNodes.filter(node => node.attributes.get("version").mkString.equalsIgnoreCase(version))

    (softInfoNodes \\ "SOFTINFO").map {
      softInfoNode => {
        val attributes = softInfoNode.attributes
        new SoftInfo(attributes.get("fileName").mkString,
        attributes.get("softType").mkString,
        attributes.get("softUseType").mkString,
        attributes.get("size").mkString.toLong)
      }
    }
  }
}

如上的readSoftInfos方法返回的是对xml节点进行map的结果,类型为scala的Seq[SoftInfo]。倘若Java代码需要调用这个方法,则还需要对其进行转换,即要求调用者必须具备Scala的知识,这未必友好。

那么应该怎样改善呢?直接的做法就是让readSoftInfos方法返回Java的List,这时候需要使用Scala提供的隐式转换:

      import scala.collection.JavaConversions._

class XmlConfigure {
  def readSoftInfos(configFileName: String, version: String) : java.util.List[SoftInfo] = {
    val document = XML.load(getClass.getResource(configFileName))

    val pkgSoftNodes = document \\ "PKGSOFT"

    val softInfoNodes = pkgSoftNodes.filter(node => node.attributes.get("version").mkString.equalsIgnoreCase(version))

    (softInfoNodes \\ "SOFTINFO").map {
      softInfoNode => {
        val attributes = softInfoNode.attributes
        new SoftInfo(attributes.get("fileName").mkString,
        attributes.get("softType").mkString,
        attributes.get("softUseType").mkString,
        attributes.get("size").mkString.toLong)
      }
    }
  }

此时,只需要导入scala.collection.JavaConversions._,我们并不需要将map返回的Seq显式地转换为java.util.List。对于Java的调用者而言,可以直接认为XmlConfigure就是一个Java类。

Java中操作Scala集合

Java要调用Scala代码,而不幸的,这个需要调用的Scala代码不够体贴,直接返回了Scala的集合类型。由于Java不提供自定义隐式转换的功能,因此,只能调用Scala提供的转换类进行显式转换。例如Scala中的XmlConfigure类,其readSoftInfos()返回的是Scala的Seq:

      import scala.collection.JavaConversions;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

public class XmlConfigureJavaTest {
    @Test
    public void should_load_xml_file() {
        XmlConfigure xmlConfigure = new XmlConfigure();
        List<SoftInfo> softInfos = JavaConversions.asJavaList(xmlConfigure.readSoftInfos("/config.xml", "version number"));
        assertThat(softInfos.size(), is(7));
    }
}

在readSoftInfos()函数返回的为Scala集合类型的情况下,若不进行显示转换,则无法通过编译。

Scala的隐式转换

Scala对Java集合与Scala集合之间的互相转换都用到了Scala提供的隐式转换功能。我们导入的JavaConversions就是承担这种转换的一个Facade Object。它扩展了两个trait:WrapAsScala和WrapAsJava。在JavaConversions对象中定义的方法实际上是将请求委派自它继承的trait的隐式转换函数。例如将Seq转换为java的List:

      object JavaConversions extends WrapAsScala with WrapAsJava {
  def asJavaList[A](b : Seq[A]): ju.List[A] = seqAsJavaList[A](b)
}

seqAsJavaList就是定义在WrapAsJava中的隐式转换函数。在这个函数中又作了一个模式匹配。如果匹配JListWrapper,则调用传入的wrapped参数的asInstanseOf进行类型转换;否则,就将该seq作为参数传递给包装器SeqWrapper。包装器SeqWrapper是Scala定义的样例类(case class),扩展自Java的AbstractList:

      //WrapAsJava
import java.{ lang => jl, util => ju }, java.util.{ concurrent => juc }
import scala.language.implicitConversions

trait WrapAsJava {
  import Wrappers._
  implicit def seqAsJavaList[A](seq: Seq[A]): ju.List[A] = seq match {
    case JListWrapper(wrapped) => wrapped.asInstanceOf[ju.List[A]]
    case _ => new SeqWrapper(seq)
  }
}

//Wrappers
import java.{ lang => jl, util => ju }, java.util.{ concurrent => juc }
import WrapAsScala._
import WrapAsJava._

private[collection] trait Wrappers {
  case class SeqWrapper[A](underlying: Seq[A]) extends ju.AbstractList[A] with IterableWrapperTrait[A] {
    def get(i: Int) = underlying(i)
  }
}

隐式转换与扩展方法

在前面我们提到,在Scala中如果导入了JavaConversions,那么即使得到的是Java的List对象,我们仍然可以对其调用foreach函数。即如下代码:

            val result = configure.readSoftInfos("/config.xml", "version number")
      result.foreach {
        softInfo => println(softInfo)
      }

若为result加上类型,应该会更清晰: Liquid error: invalid byte sequence in US-ASCII

显然,这里的result为java.util.List类型,为何却可以调用foreach函数呢?这种形式让我想起C#提供的扩展方法。例如在C# 3.0之前的集合类型,如List,并没有例如first(),where()等方法,但通过引入的扩展方法机制,我们可以对List进行静态扩展,但调用的时候却好像是集合对象自身拥有的实例方法那样。这一实现与动态语言的直接扩展不同,而是C#的一种语法糖。通过使用隐式转换,Scala也可以做到这一点。

上面代码中的result,实则是通过隐式转换,将其转换为一个扩展自scala的Iterable[+A],而最终扩展自trait IterableLike,其中定义了foreach()函数。当然,在这个foreach()函数中,实则又调用了object Iterator的foreach()函数:

      trait IterableLike[+A, +Repr] extends Any with Equals with TraversableLike[A, Repr] with GenIterableLike[A, Repr] {
self =>

  def foreach[U](f: A => U): Unit =
    iterator.foreach(f)

}

我们可以利用这种机制为已定义好的无法修改的类(尤其是Java提供的类)进行扩展。例如为java.io.File进行扩展,使其支持read功能:

      class RichFile(val from: File) {
  def read = Source.fromFile(from.getPath).mkString
}

implicit def file2RichFile(from: File) = new RichFile(from)

直接import该隐式转换,File就可以像真正提供read方法那样调用了:

      val fileContent = new File("README.txt").read

相关 [scala java] 推荐:

Yammer从Scala转向Java

- - InfoQ中文站
经历了一年之久的尝试,Yammer将要从Scala迁回至Java,因为他们发现简洁的语言所带来的好处根本无法抵消培训新员工以及调试性能问题所产生的代价. 文中所提到的邮件也表明通过避免某些模式可以实现性能上的一些改进.

Scala支持与Java的隐式转换

- - 简单文本
Neal Ford在几年前提出的“Poly Programming”思想,已经逐渐成为主流. 这种思想并非是为了炫耀多语言的技能,然后选择“高大上”. 真正的目的在于更好地利用各种语言处理不同场景、不同问题的优势. 由于都运行在JVM上,Java与Scala之间基本能做到无缝的集成,区别主要在于各自的API各有不同.

Scala 2.10 字节码将不再兼容 Java 1.5

- - ITeye资讯频道
近日,Scala 开发团队宣称 从 Scala 2.10 版本开始,字节码将不再兼任 Java 1.5. Scala官方称, 做出此决定是基于以下几方面考虑的:. 从 09年10月开始 Java 5 已经结束生命周期,官方不再支持. Java 1.5 的并发限制导致 Scala 并发库的连锁反应. 加大了 Scala 构建和测试套件的复杂度.

Heroku已支持Scala

- gnawux - InfoQ中文站
今日JavaOne大会上,被SalesForce.com近期收购的平台即服务(PaaS)提供商,Heroku,宣布增加了对Scala的支持. Heroku目前正同Typesafe公司合作,共同致力于在Heroku平台中增加对Scala支持的工作. Typesafe,“Scala语言的母公司”,最初由Scala创始人Martin Odersky与他人联合创办.

Scala设计模式

- - ITeye博客
       我的话: 在国外网站上看到一篇文章,里面详细描述了很多设计模式,并且用Java及Scala两种语言描述,清晰的让我们看到各种常规的设计模式,在Scala中是如何在语言特性层面直接支持的. 基于文章很nice,我利用今天的空闲时间将其翻译,希望大家能一起学习,讨论. 翻译比较倡促,也就两小时左右,有何不当,请在下面留言指出.

scala 开发spark程序

- - 研发管理 - ITeye博客
Spark内核是由Scala语言开发的,因此使用Scala语言开发Spark应用程序是自然而然的事情. 如果你对Scala语言还不太熟悉,可以阅读网络教程 A Scala Tutorial for Java Programmers或者相关 Scala书籍进行学习. 本文将介绍3个Scala Spark编程实例,分别是WordCount、TopK和SparkJoin,分别代表了Spark的三种典型应用.

快速了解Scala技术栈

- - 逸言
我无可救药地成为了Scala的超级粉丝. 在我使用Scala开发项目以及编写框架后,它就仿佛凝聚成为一个巨大的黑洞,吸引力使我不得不飞向它,以至于开始背离Java. 固然Java 8为Java阵营增添了一丝亮色,却是望眼欲穿,千呼万唤始出来. 而Scala程序员,却早就在享受lambda、高阶函数、trait、隐式转换等带来的福利了.

Play Framework 2.0预览版发布,核心使用Scala重写

- Tim - ITeye资讯频道
根据Play!开发团队透露,团队正在着力开发Play的下一个主要版本(也就是Play 2.0),该版本集成了一个全新的构建系统和异步功能,实现原生的Java和Scala支持. 是一个Rail风格的full-stack(全栈的)Java Web应用框架,采用目前Java开源界最流行的RESTful架构设计.

【外刊IT评论网】Clojure语言 vs Scala语言

- Tairan Wang - 外刊IT评论
本文是从 Clojure vs Scala - anecdote 这篇文章翻译而来. 我在这里是想跟大家分享一些从World Singles 系统里获得的经验 …. 早在2009年11月,我们就开始使用Scala语言了. 我们有一个需要运行很长时间的操作,把大量的数据变更信息从会员信息数据库中取出,以XML打包文件的形式发送到自定义搜索引擎里.

Java中的锁(Locks in Java)

- - 并发编程网 - ifeve.com
原文链接 作者:Jakob Jenkov 译者:申章 校对:丁一. 锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂. 因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字( 译者注:这说的是Java 5之前的情况).