这个章节主要讲解Scala和Java进行互操作。


  • Javap

  • 异常

  • Trait

  • 对象

  • 闭包函数(closures functions)


Javap


javap是JDK附带的一个工具,而不是JRE。它们之间还是有差别的。Javap反编译class文件,并且向你展示它里面放的是什么。使用起来很简单。


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait

Compiled from "Scalaisms.scala"

public interface com.twitter.interop.MyTrait extends scala.ScalaObject{

    public abstract java.lang.String traitName();

    public abstract java.lang.String upperTraitName();

}


如果你想了解底层的话,你可以查看对应的字节码


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap -c MyTrait\$class

Compiled from "Scalaisms.scala"

public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{

public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);

  Code:

   0: aload_0

   1: invokeinterface #12,  1; //InterfaceMethod com/twitter/interop/MyTrait.traitName:()Ljava/lang/String;

   6: invokevirtual #17; //Method java/lang/String.toUpperCase:()Ljava/lang/String;

   9: areturn

 

public static void $init$(com.twitter.interop.MyTrait);

  Code:

   0: return

 

}


如果你在Java平台上有什么问题,你可以通过javap来排查。



从Java的角度来使用Scala的_class_需要注意的四个要点如下:


  • 类参数

  • 类常量

  • 类变量

  • 异常


我们来创建一个简单的scala类来展示这几个要点


package com.twitter.interop

 

import java.io.IOException

import scala.throws

import scala.reflect.{BeanProperty, BooleanBeanProperty}

 

class SimpleClass(name: String, val acc: String, @BeanProperty var mutable: String) {

  val foo = "foo"

  var bar = "bar"

  @BeanProperty

  val fooBean = "foobean"

  @BeanProperty

  var barBean = "barbean"

  @BooleanBeanProperty

  var awesome = true

 

  def dangerFoo() = {

    throw new IOException("SURPRISE!")

  }

 

  @throws(classOf[IOException])

  def dangerBar() = {

    throw new IOException("NO SURPRISE!")

  }

}


类参数


  • 默认情况下,类参数实际上就是Java里构造函数的参数。这就意味着你不能在这个class之外访问它们。


  • 把类参数定义成一个val/var的方式和下面的代码相同


class SimpleClass(acc_: String) {

  val acc = acc_

}


下面就可以通过Java代码来访问它们了。


常量(Val)


  • 常量(val)都会定义有对应的供Java代码访问的方法。你可以通过”foo()”方法来取得常量(val)“foo”的值。


变量(Var)


  • 变量(var)会多定义一个_$eq方法。你可以这样调用来设置变量的值:


foo$_eq("newfoo");


BeanFactory


你可以通过@BeanProperty注解来标注val和var。这样就会生成类似于POJO的getter/setter方法。假如你想要访问isFoo变量,使用BooleanBeanProperty注解。那么难以理解的foo$eq就可以换成:


setFoo("newfoo");

getFoo();


异常


Scala里没有受检异常(checked exception),但是Java里有。这是一个语言层面上的问题,我们这里不进行讨论,但是在Java里你对异常进行捕获的时候你还是要注意的。dangerFoo和dangerBar的定义里对这进行了示范。在Java里,你不能这样做。


// exception erasure!

// 异常擦除!

try {

    s.dangerFoo();

} catch (IOException e) {

    // UGLY

    // 非常丑陋

}


Java编译器会因为s.dangerFoo不会抛出IOException而报错。我们可以通过捕获Throwable来绕过这个错误,但是这样做没多大用处。


不过,作为一个Scala用户,比较正式的方式是使用throws注解,就像我们之前在dangerBar上的一样。这个手段使得我们能够使用Java里的受检异常(checked exception)。


延伸阅读


支持Java互操作的注解的完整列表在http://www.scala-lang.org/node/106。


Trait


我们怎样可以得到一个接口和对应的实现呢?我们简单看看trait的定义


trait MyTrait {

  def traitName:String

  def upperTraitName = traitName.toUpperCase

}


这个trait有一个抽象的方法(traitName)和一个已实现的方法(upperTraitName)。对于这样的trait,Scala会生成什么样的代码呢?它会生成一个MyTrait接口,同时还会生成对应的实现类MyTrait$class。


MyTrait的实现和你猜想的差不多:


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait

Compiled from "Scalaisms.scala"

public interface com.twitter.interop.MyTrait extends scala.ScalaObject{

    public abstract java.lang.String traitName();

    public abstract java.lang.String upperTraitName();

}


不过MyTrait$class的实现更加有趣:


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait\$class

Compiled from "Scalaisms.scala"

public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{

    public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);

    public static void $init$(com.twitter.interop.MyTrait);

}


MyTrait$class类只有一个静态的接受一个MyTrait实例作为参数的方法。这样给了我们在Java如何实现trait的一条线索。


首先要做的是:


package com.twitter.interop;

 

public class JTraitImpl implements MyTrait {

    private String name = null;

 

    public JTraitImpl(String name) {

        this.name = name;

    }

 

    public String traitName() {

        return name;

    }

}


然后我们会得到下面的错误:


[info] Compiling main sources...

[error] /Users/mmcbride/projects/interop/src/main/java/com/twitter/interop/JTraitImpl.java:3: com.twitter.interop.JTraitImpl is not abstract and does not override abstract method upperTraitName() in com.twitter.interop.MyTrait

[error] public class JTraitImpl implements MyTrait {

[error]        ^


我们_可以_自己来实现他们。不过还有一种更加诡异的方式。


package com.twitter.interop;

 

    public String upperTraitName() {

        return MyTrait$class.upperTraitName(this);

    }


我们只需要把相应的方法调用代理到Scala的实现上。并且我们还可以根据实际需要进行重写。


对象


在Scala里,是用对象来实现静态方法和单例模式的。如果在Java里使用它们就会显得比较怪。在语法上没有什么比较优雅的方式来使用它们,但是在Scala 2.8里就没有那么麻烦了。


Scala对象会被编译成一个名称带有“$”后缀的类。我们来创建一个类以及对应的对象(Object)。我们来创建一个类以及对应的伴生对象(companion object)。


class TraitImpl(name: String) extends MyTrait {

  def traitName = name

}

 

object TraitImpl {

  def apply = new TraitImpl("foo")

  def apply(name: String) = new TraitImpl(name)

}


我们可以通过下面这种奇妙的方式在Java里访问它:


MyTrait foo = TraitImpl$.MODULE$.apply("foo");


现在你也许会问自己,这究竟是神马?这是一个很正常的反应。我们现在一起来看看TraintImpl$内部究竟是怎么实现的。


local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap TraitImpl\$

Compiled from "Scalaisms.scala"

public final class com.twitter.interop.TraitImpl$ extends java.lang.Object implements scala.ScalaObject{

    public static final com.twitter.interop.TraitImpl$ MODULE$;

    public static {};

    public com.twitter.interop.TraitImpl apply();

    public com.twitter.interop.TraitImpl apply(java.lang.String);

}


其实它里面没有任何静态方法。相反,它还有一个静态成员叫做MODULE$。实际上方法的调用都是代理到这个成员变量上的。这种实现使得访问起来觉得比较恶心,但是如果你知道怎么使用MODULE$的话,其实还是很实用的。


转发方法(Forwarding Method)


在Scala 2.8里,处理Object会比较简单点。如果你有一个类以及对应的伴生对象(companion object),2.8 的编译器会在伴生对象里生成转发方法。如果使用2.8的编译器进行构建,那么你可以通过下面的方法来访问TraitImpl对象:


MyTrait foo = TraitImpl.apply("foo");


闭包函数


Scala最重要的一个特点就是把函数作为一等公民。我们来定义一个类,它里面包含一些接收函数作为参数的方法。


class ClosureClass {

  def printResult[T](f: => T) = {

    println(f)

  }

 

  def printResult[T](f: String => T) = {

    println(f("HI THERE"))

  }

}


在Scala里我可以这样调用:


val cc = new ClosureClass

cc.printResult { "HI MOM" }


但是在Java里却没有这么简单,不过也没有想象的那么复杂。我们来看看ClosureClass最终到底编译成怎样:


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap ClosureClass

Compiled from "Scalaisms.scala"

public class com.twitter.interop.ClosureClass extends java.lang.Object implements scala.ScalaObject{

    public void printResult(scala.Function0);

    public void printResult(scala.Function1);

    public com.twitter.interop.ClosureClass();

}


这个看起来也不是很可怕。”f: => T” 转换成”Function0″,”f: String => T” 转换成 “Function1″。Scala定义了从Function0到Function22,一直支持到22个参数。这么多确实已经足够了。


现在我们只需要弄清楚,怎么在Java去实现这个功能。事实上,Scala提供了AbstractFunction0和AbstractFunction1,我们可以这样来传参:


@Test public void closureTest() {

    ClosureClass c = new ClosureClass();

    c.printResult(new AbstractFunction0() {

            public String apply() {

                return "foo";

            }

        });

    c.printResult(new AbstractFunction1() {

            public String apply(String arg) {

                return arg + "foo";

            }

        });

}


注意我们还可以使用泛型来参数化参数的类型。


更多相关文章

  1. VS中scanf等函数报错解决方法
  2. jQuery编程基础精华02(属性、表单过滤器,元素的each,表单选择器,子元
  3. jQuery的$.getJSON方法在IE浏览器下失效的解决方案
  4. JavaScript / jQuery:如何链接使用console.log()的方法
  5. 在体html中搜索文本的最佳方法是什么?
  6. Jquery Validate 相关参数及常用的自定义验证规则
  7. jQuery 事件绑定方法(bind hover toggle live.... )、删除事件方法
  8. jQuery Ajax 方法调用 Asp.Net WebService
  9. jqueryui autocomplete 插件 点击 显示选项设置方法

随机推荐

  1. 在Yii中获取当前控制器和操作ID
  2. 基于PHP聊天室的编程思想
  3. 检查PHP是否启用了JavaScript
  4. php计算几分钟前、几小时前、几天前的几
  5. jQuery ajax调用不会调用我的php页面
  6. 复制到剪贴板没有瑞士法郎。只使用javasc
  7. 使用codeigniter发布到wordpress.com博客
  8. 用简单的馅饼从原子进料中拉出
  9. php curl模拟登陆抓取数据
  10. php 自己做的简单论坛