Java 與 Scala

2022-05-13 10:38 更新

Javap

javap 的是 JDK 附帶的一個(gè)工具。不是 JRE,這里是有區(qū)別的。javap 反編譯類定義,給你展示里面有什么。用法很簡(jiǎn)單

[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();
}

如果你是底層控可以看看字節(jié)碼

[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 類 時(shí)要考慮的四個(gè)要點(diǎn)

  • 類參數(shù)
  • 類常量
  • 類變量
  • 異常

我們將構(gòu)建一個(gè)簡(jiǎn)單的 Scala 類來(lái)展示這一系列實(shí)體

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!")
  }
}

類參數(shù)

  • 默認(rèn)情況下,類參數(shù)都是有效的 Java 構(gòu)造函數(shù)的參數(shù)。這意味著你不能從類的外部訪問(wèn)。
  • 聲明一個(gè)類參數(shù)為 val/var 和這段代碼是相同的
class SimpleClass(acc_: String) {
  val acc = acc_
}

這使得它在 Java 代碼中就像其他常量一樣可以被訪問(wèn)

類常量

  • 常量(val)在 Java 中定義了一個(gè)獲取方法。你可以通過(guò)方法“foo()”訪問(wèn)“val foo”的值

類變量

  • 變量(var)會(huì)生成一個(gè) _$eq 方法。你可以這樣調(diào)用它
foo$_eq("newfoo");

BeanProperty

你可以通過(guò) @BeanProperty 注解 val 和 var 定義。這會(huì)按照 POJO 定義生成 getter/setter 方法。如果你想生成 isFoo 方法,使用 BooleanBeanProperty 注解。丑陋的 foo$_eq 將變?yōu)?/p>

setFoo("newfoo");
getFoo();

異常

Scala 沒(méi)有像 Java 那樣有受檢異常(checked exception)。需不需要受檢異常是一個(gè)我們不會(huì)進(jìn)入的哲學(xué)辯論,不過(guò)當(dāng)你需要在Java中捕獲它時(shí)就 很重要 了。dangerFoo 和 dangerBar 將演示這一點(diǎn)。在 Java 中不能這樣做

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

Java 會(huì)抱怨說(shuō) s.dangerFoo 從未拋出過(guò) IOException 異常。我們可以通過(guò)捕獲 Throwable 來(lái)跳過(guò),但是這樣不好。

相反,作為一個(gè)良好的 Scala 公民,可以很體面地像在 dangerBar 中那樣使用 throws 注解。這使我們能夠繼續(xù)在 Java 中使用受檢異常。

進(jìn)一步閱讀

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

特質(zhì)

你如何獲得一個(gè)接口+實(shí)現(xiàn)?讓我們看一個(gè)簡(jiǎn)單的特質(zhì)定義

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

這個(gè)特質(zhì)有一個(gè)抽象方法(traitName)和一個(gè)實(shí)現(xiàn)的方法(upperTraitName)。Scala 為我們生成了什么呢?一個(gè)名為 MyTrait 的的接口,和一個(gè)名為 MyTrait$class 的實(shí)現(xiàn)類。

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 實(shí)例為參數(shù)的靜態(tài)方法。這給了我們一個(gè)如何在 Java 中來(lái)擴(kuò)展一個(gè)特質(zhì)的提示。

首先嘗試下面的操作

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;
    }
}

我們會(huì)得到以下錯(cuò)誤

[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]        ^

我們 可以 自己實(shí)現(xiàn)。但有一個(gè)鬼鬼祟祟的方式。

package com.twitter.interop;

    public String upperTraitName() {
        return MyTrait$class.upperTraitName(this);
    }

我們只要把調(diào)用代理到生成的 Scala 實(shí)現(xiàn)上就可以了。如果愿意我們也可以覆蓋它。

單例對(duì)象

單例對(duì)象是 Scala 實(shí)現(xiàn)靜態(tài)方法/單例模式的方式。在 Java 中使用它會(huì)有點(diǎn)奇怪。沒(méi)有一個(gè)使用它們的完美風(fēng)格,但在 Scala2.8 中用起來(lái)并不很糟糕

一個(gè) Scala 單例對(duì)象會(huì)被編譯成由“$”結(jié)尾的類。讓我們創(chuàng)建一個(gè)類和一個(gè)伴生對(duì)象

class TraitImpl(name: String) extends MyTrait {
  def traitName = name
}

object TraitImpl {
  def apply = new TraitImpl("foo")
  def apply(name: String) = new TraitImpl(name)
}

我們可以像這樣天真地在 Java 中訪問(wèn)

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

現(xiàn)在你可能會(huì)問(wèn)自己,這是神馬玩意?這是一個(gè)正常的反應(yīng)。讓我們來(lái)看看 TraitImpl$ 里面實(shí)際上是什么

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

其實(shí)它里面沒(méi)有任何靜態(tài)方法。取而代之的是一個(gè)名為MODULE$的靜態(tài)成員。方法實(shí)現(xiàn)被委托給該成員。這使得訪問(wèn)代碼很難看,但卻是可行的。

轉(zhuǎn)發(fā)方法(Forwarding Methods)

在 Scala2.8 中處理單例對(duì)象變得相對(duì)容易一點(diǎn)。如果你有一個(gè)類與一個(gè)伴生對(duì)象,2.8 編譯器會(huì)生成轉(zhuǎn)發(fā)方法在伴生類中。所以,如果你用 2.8,你可以像這樣調(diào)用 TraitImpl 單例對(duì)象的方法

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

閉包函數(shù)

Scala 的最重要的特點(diǎn)之一,就是把函數(shù)作為頭等公民。讓我們來(lái)定義一個(gè)類,它定義了一些以函數(shù)作為參數(shù)的方法。

class ClosureClass {
  def printResult[T](f: => T) = {
    println(f)
  }

  def printResult[T](f: String => T) = {
    println(f("HI THERE"))
  }
}

在 Scala 中可以像這樣調(diào)用

val cc = new ClosureClass
cc.printResult { "HI MOM" }

在 Java 中卻不那么容易,不過(guò)也并不可怕。讓我們來(lái)看看 ClosureClass 實(shí)際上被編譯成什么:

[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();
}

這也不是那么恐怖?!癴: => T”被轉(zhuǎn)義成“Function0”,“f: String => T”被轉(zhuǎn)義成“Function1”。Scala 實(shí)際上從 Function0 定義到 Function22,最多支持 22 個(gè)參數(shù)。這真的應(yīng)該足夠了。

現(xiàn)在我們只需要弄清楚如何在 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";
                }
            });
    }

注意我們可以使用泛型參數(shù)。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)