💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
课程内容涵盖了Java互操作性。 [TOC=2,2] ## 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](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"; } }); } ~~~ 注意我们可以使用泛型参数。 Built at [@twitter](http://twitter.com/twitter) by [@stevej](http://twitter.com/stevej), [@marius](http://twitter.com/marius), and [@lahosken](http://twitter.com/lahosken) with much help from [@evanm](http://twitter.com/evanm), [@sprsquish](http://twitter.com/sprsquish), [@kevino](http://twitter.com/kevino), [@zuercher](http://twitter.com/zuercher), [@timtrueman](http://twitter.com/timtrueman), [@wickman](http://twitter.com/wickman), and[@mccv](http://twitter.com/mccv); Russian translation by [appigram](https://github.com/appigram); Chinese simple translation by [jasonqu](https://github.com/jasonqu); Korean translation by [enshahar](https://github.com/enshahar); Licensed under the [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0).