专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
JavaGuide  ·  快手面试,被面试官速通了! ·  2 天前  
芋道源码  ·  简化本地Feign调用,这样封装真香! ·  2 天前  
芋道源码  ·  系统上线前,SQL脚本的9大坑 ·  3 天前  
JavaGuide  ·  真的建议赶紧搞个软考证书!(红利期) ·  5 天前  
芋道源码  ·  既生“/”,何生“\”? ·  5 天前  
51好读  ›  专栏  ›  ImportNew

Scala 与 Java 的交互操作

ImportNew  · 公众号  · Java  · 2017-01-06 20:18

正文

(点击上方公众号,可快速关注)


英文:Scala School

译文:朱伟杰

如需转载,发送「转载」二字查看说明


这个章节主要讲解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";

            }

        });

}


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


觉得本文对你有帮助?请分享给更多人

关注「ImportNew」,提升Java技能