-

Java 与 Scala

Javap

javap 的是 JDK 附带的一个工具。不是 JRE,这里是有区别的。javap 反编译类定义,给你展示里面有什么。用法很简单

[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 类 时要考虑的四个要点

  • 类参数
  • 类常量
  • 类变量
  • 异常

我们将构建一个简单的 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 构造函数的参数。这意味着你不能从类的外部访问。
  • 声明一个类参数为 val/var 和这段代码是相同的
class SimpleClass(acc_: String) {
  val acc = acc_
}

这使得它在 Java 代码中就像其他常量一样可以被访问

类常量

  • 常量(val)在 Java 中定义了一个获取方法。你可以通过方法“foo()”访问“val foo”的值

类变量

  • 变量(var)会生成一个 _$eq 方法。你可以这样调用它
foo$_eq("newfoo");

BeanProperty

你可以通过 @BeanProperty 注解 val 和 var 定义。这会按照 POJO 定义生成 getter/setter 方法。如果你想生成 isFoo 方法,使用 BooleanBeanProperty 注解。丑陋的 foo$_eq 将变为

setFoo("newfoo");
getFoo();

异常

Scala 没有像 Java 那样有受检异常(checked exception)。需不需要受检异常是一个我们不会进入的哲学辩论,不过当你需要在Java中捕获它时就 很重要 了。dangerFoo 和 dangerBar 将演示这一点。在 Java 中不能这样做

        // exception erasure!
        try {
            s.dangerFoo();
        } catch (IOException e) {
            // UGLY
        }

Java 会抱怨说 s.dangerFoo 从未抛出过 IOException 异常。我们可以通过捕获 Throwable 来跳过,但是这样不好。

相反,作为一个良好的 Scala 公民,可以很体面地像在 dangerBar 中那样使用 throws 注解。这使我们能够继续在 Java 中使用受检异常。

进一步阅读

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

特质

你如何获得一个接口+实现?让我们看一个简单的特质定义

trait MyTrait {
  def traitName:String
  def upperTraitName = traitName.toUpperCase
}

这个特质有一个抽象方法(traitName)和一个实现的方法(upperTraitName)。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 中来扩展一个特质的提示。

首先尝试下面的操作

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 中使用它会有点奇怪。没有一个使用它们的完美风格,但在 Scala2.8 中用起来并不很糟糕

一个 Scala 单例对象会被编译成由“$”结尾的类。让我们创建一个类和一个伴生对象

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");

现在你可能会问自己,这是神马玩意?这是一个正常的反应。让我们来看看 TraitImpl$ 里面实际上是什么

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$的静态成员。方法实现被委托给该成员。这使得访问代码很难看,但却是可行的。

转发方法(Forwarding Methods)

在 Scala2.8 中处理单例对象变得相对容易一点。如果你有一个类与一个伴生对象,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<String, String>() {
                public String apply(String arg) {
                    return arg + "foo";
                }
            });
    }

注意我们可以使用泛型参数。