簡體   English   中英

如何實例化java中scala中定義的嵌套泛型類?

[英]How to instantiate a nested generic class defined in scala from java?

我正在嘗試從Java實例化嵌套的通用Scala類並遇到此編譯錯誤。 有人可以幫忙嗎? 謝謝

outer.scala:

class Outer {
    class Inner[A]
}

sctest.java:

public class sctest{
    public static void main(String[] args) {
        Outer o = new Outer();
        Outer.Inner<String> a = o.new Inner<String>();
    }
}

$ javac sctest.java

sctest.java:4: error: constructor Inner in class Outer.Inner cannot be applied to given types;
    Outer.Inner a = o.new Inner();
                              ^
  required: Outer
  found: no arguments
  reason: actual and formal argument lists differ in length
  where A is a type-variable:
    A extends Object declared in class Outer.Inner
 1 error

我不知道如何通過Java完成這項工作。 詳見附錄。 我將直接跳到解決方案提案。


當你在嘗試從Java調用Scala代碼時遇到Java-Scala互操作問題時,有一個簡單的,雖然可能有點重量級的解決方法,但基本上適用於所有情況。

TL; DR

如果在Java代碼中實例化Scala實體不起作用,則隱藏Java接口后面的所有內容,並在Scala中實現此接口。

解決方案提案

如果你正在使用一些主流的Scala框架,它可能有一個完全獨立的Java API(例如Spark,Akka,Play),那么請使用它

如果它是一個鮮為人知的Scala軟件包而沒有單獨的Java API,請執行以下操作:

  1. 在純Java中寫下一個最小的,干凈的面向用戶的API
  2. 在Java中提供面向框架的接口
  3. 在Scala中實現Java接口
  4. 提供Java API實現的入口點
  5. 在Java代碼中使用pure-Java API

如果您不想實現完整的Java API,則可以在本地使用此方法,以處理與Java項目無法無縫協作的Scala代碼部分。

為什么它應該工作:Scala總是試圖適應Java。 Java完全忽略了Scala。 因此,從Scala調用Java代碼並在Scala中實現Java接口要比反過來容易得多。

以下是這種模式如何應用於您的示例(我將其擴展了一些以使其變得非常重要):

請注意,類名有點難看,我這樣做只是為了省略包聲明,所以所有文件都可以轉儲到src / main / {scala,java}; 不要在實際實現中這樣做。

第0步:查看第三方Scala庫

假設這是我們的第三方Scala庫,它有superImportantMethod並計算superImportantThings

/* Suppose that this is the external Scala library 
 * that you cannot change.
 * A = Outer
 * B = Inner
 * 
 * + I added at least one member and one
 * method, so that it's not so boring and trivial
 */

class A {
  var superImportantMemberOfA: Int = 42
  class B[T](t: T) {
    def superImportantMethod: String = t + "" + superImportantMemberOfA
  }
}

步驟1/2:用於Java項目的最小Java API

我們將在java項目中使用這些接口,並直接使用Scala實現它們:

interface A_j {
  <X> B_j<X> createB(X x);
}

interface B_j<X> {
  String superImportantMethod();
}

第3步:在Scala中實現接口

/** Implementation of the java api in 
  * Scala
  */
class A_javaApiImpl extends A_j {
  private val wrappedA: A = new A

  private class B_javaApiImpl[X](val x: X) extends B_j[X] {
    private val wrappedB: wrappedA.B[X] = new wrappedA.B[X](x)
    def superImportantMethod: String = wrappedB.superImportantMethod
  }

  def createB[X](x: X): B_j[X] = new B_javaApiImpl[X](x)
}

第4步:提供API的入口點

/** Some kind of entry point to the 
  * java API.
  */
object JavaApi {
  def createA: A_j = new A_javaApiImpl
}

第5步:在Java代碼中使用僅Java的API:

public class JavaMain {
  public static void main(String[] args) {

    // Use the Java API in your Java application
    // Notice that now all A_j's and B_j's are
    // pure Java interfaces, so that nothing 
    // should go wrong.
    A_j a = JavaApi.createA(); // the only call of a Scala-method.
    B_j<String> b = a.createB("foobarbaz");
    System.out.println(b.superImportantMethod());
  }
}

現在不會出錯,因為Java(幾乎)從不調用任何Scala方法或構造函數,並且通過定義一個干凈的API,您也可以保證不會遇到任何問題,因為某些Scala概念無法用Java表示。 確實,它確實編譯並運行:

 [info] Running (fork) JavaMain 
 [info] foobarbaz42

附錄一

(失敗)嘗試從Java實例化通用內部Scala類

我從這些定義開始(從您的問題略微縮寫代碼):

.scala:

class A {
  class B[T]
}

的.java:

public class JavaMain {
  public static void main(String[] args) {
    A a = new A();
    A.B<String> b = a.new B<String>(a);
  }
}

請注意,javac編譯器為B構造函數需要一個類型為A的參數,這個參數已經有些可疑了,並不是很直觀。

它已編譯,但當我嘗試運行它時,我收到以下隱秘的錯誤消息:

[錯誤]線程“main”中的異常java.lang.NoSuchMethodError:A $ B。(LA; LA;)V [錯誤]在JavaMain.main(JavaMain.java:4)

我完全不知道這應該是什么意思,所以我反編譯生成的“.class”文件:

反編譯的A.class:

public class A {

    public class B<T> {
        public /* synthetic */ A A$B$$$outer() {
            return A.this;
        }

        public B() {
            if (A.this == null) {
                throw null;
            }
        }
    }

}

反編譯A $ B.class:

public class A.B<T> {
    public /* synthetic */ A A$B$$$outer() {
        return A.this;
    }

    public A.B() {
        if (A.this == null) {
            throw null;
        }
    }
}

反編譯的JavaMain.class:

public class JavaMain {
    public static void main(String[] arrstring) {
        A a;
        A a2 = a = new A();
        a2.getClass();
        A.B b = new A.B(a2, a);
    }
}

new AB(a2, a)部分對我來說甚至看起來都不是有效的java(也不是javac)。 所以,我基本上想說的是:一切都崩潰和燒傷,我不知道為什么。 因此,我只是建議實施上述解決方法。

希望有所幫助。

對我來說這看起來像個錯誤。 代碼編譯沒有type參數,但是當你添加它時,編譯只是意外失敗。 出於某種原因,在存在類型參數的情況下,javac開始認為Outer.Inner的構造Outer.Inner需要兩個類型為Outer參數。 因此,實際編譯的代碼是o.new Inner<String>(o) ,由於方法類型錯誤而在運行時失敗。

無論如何,除了反復調用構造函數之外,我無法想到在Java中使用任何其他方法。 它不是很優雅但是我們至少可以將正確的參數傳遞給<init>方法:

MethodHandle constructor = MethodHandles.lookup().findConstructor(Outer.Inner.class,
        MethodType.methodType(void.class, Outer.class));

Outer outer = new Outer();
Outer.Inner<String> inner = (Outer.Inner<String>) constructor.invokeExact(outer);

似乎是由存儲通用方法類型信息的簽名屬性引起的。 它在編譯時用於檢查使用正確的泛型參數調用方法,但據我所知,它在運行時沒有用。 Java編譯器似乎將隱式outer參數從此簽名中刪除,因此當Scala編譯器執行相反操作時會混淆。

Java和Scala中的等效代碼段具有相同的描述符(Ltest/Outer;Ljava/lang/Object;)V

// Java
package test;
class Outer { class Inner<A> {
    public Inner(A a) {}
    // Signature: (TA;)V
}}

// Scala
package test
class Outer { class Inner[A](a: A) {
  // Signature: (Ltest/Outer;TA;)V
}}

找到一些相關的討論: Java內部類描述符和簽名屬性之間的不一致? (班級檔案)


引自JVMS 4.7.9.1:

由Signature屬性編碼的方法簽名可能與method_info結構(第4.3.3節)中的方法描述符不完全對應。 特別是,無法保證方法簽名中的形式參數類型的數量與方法描述符中的參數描述符的數量相同。 對於大多數方法,數字是相同的,但Java編程語言中的某些構造函數具有隱式聲明的參數,編譯器使用參數描述符表示該參數,但可以省略方法簽名 有關涉及參數注釋的類似情況,請參閱§4.7.18中的注釋。

這可能不是最一致的答案,但似乎這是指定的行為。

你可以向Outer添加一個泛型方法來實例化Inner實例嗎?

outer.scala:

class Outer {
  class Inner[A]
  def inner[A](): Inner[A] = new Inner[A]
}

sctest.java:

public class sctest {
  public static void main(String[] args) {
    Outer o = new Outer();
    Outer.Inner<String> a = o.inner();
  }                                                                    
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM