【问题标题】:How to implement Scala apply method in Java如何在 Java 中实现 Scala 应用方法
【发布时间】:2014-01-13 08:26:27
【问题描述】:

我想从 Scala 代码中调用一些 Java 代码。我想使用 Scala 的 apply 构造,所以我可以这样称呼它:

val led = OutPin(0)

代替:

val led = new OutPin(0)

我天真地在我的 Java 代码中实现了一个额外的 apply 方法,如下所示:

public class OutPin {

    public OutPin(int pinNumber) {
    }

    public OutPin apply(int pinNumber) {
        return new OutPin(pinNumber);
    }
}

这不会使我的 Scala 代码(上面的第一行)编译,而是给我一个错误:

Object OutPin 不是一个值

在Java中实现Scala的apply方法的正确方法是什么?

【问题讨论】:

  • 尝试将apply设为静态:public static OutPin apply(int pinNumber)
  • 有什么理由不只在 Scala 中实现 apply 函数吗?我想至少,您需要将 apply 设为静态并静态导入 apply 函数。
  • 其实我之前确实尝试过作为静态方法,它没有任何区别。
  • apply 方法必须在类的伴随对象中定义。伴随对象的类与您定义的类不同。

标签: java scala


【解决方案1】:

您的问题不在于 apply 方法本身,而在于尝试在 Java 中实现 Scala 单例对象。

我认为(但不确定)这在设计上是非常困难的,甚至可能是不可能的。

考虑一个非常非常简单的案例:

object Obj;

这将编译为两个 JVM 字节码文件,Obj$.classObj.class。理论上,检查这两个类的字节码应该很容易,并在 Java 中重新表达相同的东西。 Scala 单例对象的基本结构非常非常简单:

  1. 对于单例对象Obj,必须生成Obj.classObj$.class
  2. Obj$ 类必须有一个类型为Obj$public final static 字段,称为MODULE$,它将在类初始化引用单例对象时进行初始化。在 Scala 中,对 Obj.foo() 的调用被映射到 Obj$.MODULE$.foo() [...如果 Obj 有一个名为 foo() 的方法,那就是!]
  3. Java 编译器对这些 Scala 生成的类对一无所知,因此对于 Java 互操作,Obj 类包含静态函数,这些函数仅转发到对 Obj$.MODULE$ 上同名和签名的方法的调用.

这听起来很复杂,但实际上并没有那么复杂。编写一对能走到这一步的 Java 类是微不足道的。但是 Scala 编译器 (2.10.3) 仍然无法识别这对构成 Scala 单例。深入研究 Scala 编译器生成的单例的字节码,您会发现有些细节在合法的 Java 中难以表达。 [提示:javap -c -p -s -v <fully-qualified-class-name>]

例如,最终的静态MODULE$ 字段由静态初始化程序间接初始化。静态初始化器只是构造一个Obj$ 对象,而不直接分配它。赋值发生在私有构造函数中。这在 Java 中是非法的:空白静态 final 肯定必须在静态初始化程序中初始化,并且不能在可能从初始化程序外部多次调用的代码(如私有构造函数)中分配。 Scala 编译器生成尊重空白最终语义的字节码(因为私有构造函数只被调用一次),但超出了 Java 编译器验证这些语义的能力。因此,如果用 Java 表示,此代码将被拒绝。

另外,Obj 类(没有终端美元符号的版本)包含一个 ScalaSig 类型的注解,它看起来相当复杂,并且很难手动复制(在 Java 或 Scala 中),至少对于我们这些不确定这个注释到底是如何工作的人。

在决定将一对类视为“值”之前,我并不确切知道 Scala 编译器会寻找什么,这是一个有效的 Scala 单例对象,但 Scala 的设计者选择不让它变得容易,尽管基本方案的简单性。可能他们希望保留重新组织 scala 单例对象如何转换为字节码的能力。让 Java 程序员合成 scala 单例对象将有效地将当前方案呈现为 Scala 公共 API 的永久部分。

请注意,编写一个普通的非单例类,其实例有一个 apply(...) 方法,因此可以像函数一样调用,这在 Java 中很容易,并且工作正常。这是一只 Java 猫:

public class Cat {
    public String apply( int i ) {
        return "Meow: " + i;
    }
}

这是 Scala 的糖化应用的用法:

Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_45).
Type in expressions to have them evaluated.
Type :help for more information.

scala> val Morris = new Cat;
Morris: Cat = Cat@6b4feafa

scala> Morris(8)
res0: String = Meow: 8

【讨论】:

    【解决方案2】:

    如果您想要一个可在 Scala 中使用的 apply 方法,您应该在 scala 一侧使用object 将您的 java 类与实例化它的类的名称相同

    object OutPin {
    
       def apply(pinNumber :Int) = new OutPin(pinNumber)
    
    }
    

    【讨论】:

      【解决方案3】:

      问题是Scala编译器说你必须在同一个文件中定义两个伴生对象,否则你会得到错误:

      Companions 'class OutPin' and 'object OutPin' must be defined in same file
      

      这是另一种可能适合您的方法。如果你在一个单独的包中定义你的 Scala OutPin,那么它将起作用。例如:

      Java 类:

      package base;
      
      public class OutPin {
          private final int i;
      
          public OutPin(int i) {
              this.i = i;
          }
      
          public int getI() {
              return this.i;
          }
      }
      

      Scala 类:

      package base.scala
      
      object OutPin {
        def apply(i: Int): base.OutPin = new base.OutPin(i)
      }
      

      示例 Scala 客户端:

      import base.scala.OutPin
      
      object Client extends App {
        val op = OutPin(1)
      
        println(op.getI)
      }
      

      运行客户端打印1

      要访问加糖的apply,您必须导入base.scala 而不仅仅是base。如果获取 apply 语法真的很重要,那么它可能是值得的。

      【讨论】:

        猜你喜欢
        • 2012-07-28
        • 2018-02-14
        • 2020-07-15
        • 2014-02-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多