从Java访问元组的奇怪行为

mga*_*ido 6 java scala tuples

我正在寻找关于我在Java访问Scala中创建的元组时发现的一种非常奇怪的行为的解释和/或版本控制细节(如果可能).

我将用一个简单的测试来展示这种奇怪的行为.我创建了这个Scala类:

class Foo {
  def intsNullTuple = (null.asInstanceOf[Int], 2)
  def intAndStringNullTuple =  (null.asInstanceOf[Int], "2")
}
Run Code Online (Sandbox Code Playgroud)

然后我运行这个Java程序:

Tuple2<Object, Object> t = (new Foo()).intsNullTuple();
t._1(); // returns 0 !
t._1; // return null
Tuple2<Object, String> t2 = (new Foo()).intAndStringNullTuple();
t._1(); // returns null
t._1; // return null
Run Code Online (Sandbox Code Playgroud)

有没有人对此的原因有任何解释?而且,在我的测试中,我使用的是Java 1.8和Scala 2.11.8.任何人都可以提供有关使用_1Java代码与旧版Scala 2.11和2.10版本以及Java 1.7 的兼容性的任何建议吗?我读过_1Java无法访问,但我可以在测试中访问它.因此,我正在寻找支持它的版本.

谢谢.

Yuv*_*kov 8

有没有人对此的原因有任何解释?

这是因为Scala具有过载的特殊性Tuple2<Int, Int>,而Tuple2<Int, String>不是.您可以从签名中看到它Tuple2:

case class Tuple2[@specialized(Int, Long, Double, Char, Boolean/*, AnyRef*/) +T1, @specialized(Int, Long, Double, Char, Boolean/*, AnyRef*/) +T2](_1: T1, _2: T2)
Run Code Online (Sandbox Code Playgroud)

这意味着Scala编译器为特殊情况发出一个类,其中T1T2是一个特殊的元组类型,在我们的例子中有一个特殊的类采用两个整数,大致如下:

class Tuple2Special(i: Int, j: Int)
Run Code Online (Sandbox Code Playgroud)

在查看反编译的字节代码时,我们可以看到这一点:

Compiled from "Foo.scala"
public class com.testing.Foo {
  public scala.Tuple2<java.lang.Object, java.lang.Object> intsNullTuple();
    Code:
       0: new           #12                 // class scala/Tuple2$mcII$sp
       3: dup
       4: aconst_null
       5: invokestatic  #18                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
       8: iconst_2
       9: invokespecial #22                 // Method scala/Tuple2$mcII$sp."<init>":(II)V
      12: areturn

  public scala.Tuple2<java.lang.Object, java.lang.String> intAndStringNullTuple();
    Code:
       0: new           #27                 // class scala/Tuple2
       3: dup
       4: aconst_null
       5: ldc           #29                 // String 2
       7: invokespecial #32                 // Method scala/Tuple2."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
      10: areturn

  public com.testing.Foo();
    Code:
       0: aload_0
       1: invokespecial #35                 // Method java/lang/Object."<init>":()V
       4: return
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下intsNullTuple,您会看到new操作码调用Tuple2$mcII$sp,这是专用版本.这就是你调用_1()yield 的原因0,因为这是值类型的默认值Int,而_1不是专门的,并且调用重载返回a Object,而不是Int.

scalac在使用-Xprint:jvm标志进行编译时也可以查看:

? scalac -Xprint:jvm Foo.scala
[[syntax trees at end of                       jvm]] // Foo.scala
package com.testing {
  class Foo extends Object {
    def intsNullTuple(): Tuple2 = new Tuple2$mcII$sp(scala.Int.unbox(null), 2);
    def intAndStringNullTuple(): Tuple2 = new Tuple2(scala.Int.box(scala.Int.unbox(null)), "2");
    def <init>(): com.testing.Foo = {
      Foo.super.<init>();
      ()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

另一个有趣的事实是Scala 2.12改变了行为,并改为intAndStringNullTuple打印0:

public scala.Tuple2<java.lang.Object, java.lang.String> intAndStringNullTuple();
  Code:
     0: new           #27                 // class scala/Tuple2
     3: dup
     4: aconst_null
     5: invokestatic  #18                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
     8: invokestatic  #31                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
     11: ldc           #33                 // String 2
     13: invokespecial #36                 // Method scala/Tuple2."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
     16: areturn
Run Code Online (Sandbox Code Playgroud)

产量:

t1 method: 0
t1 field: null
t2 method: 0
t2 field: 0
Run Code Online (Sandbox Code Playgroud)

从现在开始null转换为0via unboxToInt并包装在一个Integer实例中boxToInteger.

编辑:

在与Lightbend的相关人员交谈之后,发生这种情况的原因是2.12中对字节码生成器(后端)进行了返工(更多信息请参见https://github.com/scala/scala/pull/5176).