从Java访问元组的奇怪行为

时间:2017-12-02 08:30:58

标签: java scala tuples

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

我将通过简单的测试来展示奇怪的行为。 我创建了这个Scala类:

class Foo {
  def intsNullTuple = (null.asInstanceOf[Int], 2)
  def intAndStringNullTuple =  (null.asInstanceOf[Int], "2")
}

然后我运行这个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

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

感谢。

2 个答案:

答案 0 :(得分: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)

的签名中看到它
T1

这意味着Scala编译器为T2class Tuple2Special(i: Int, j: Int) 是特殊元组类型之一的特殊情况发出一个类,在我们的示例中有一个特殊的类占用两个整数,大致类似这样:

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
}

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

intsNullTuple

对于new,您会看到Tuple2$mcII$sp操作码调用_1(),这是专用版本。这就是您调用0产生Int的原因,因为这是值类型_1的默认值,而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>(); () } } } 也可以查看此内容:

intAndStringNullTuple

另一个有趣的事实是,Scala 2.12改变了行为,并改为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

t1 method: 0
t1 field: null
t2 method: 0
t2 field: 0

收率:

null

现在0已通过unboxToInt转换为Integer,并通过boxToInteger包含在int个实例中。

编辑:

在Lightbend与相关人员交谈后,发生这种情况是由于字节码生成器(后端)在2.12中完成的返工(更多信息见https://github.com/scala/scala/pull/5176)。

答案 1 :(得分:2)

首先,需要呼叫,在 Scala 中,一切都是对象,没有primitive type(对于您的代码,它是&#39; s Int)与 Java 不一样,但 Scala 需要编译为 Java字节码才能在 JVM中运行,因为对象基本类型消耗更多内存,所以 Scala specialized来解决这个问题,这意味着生成specialized注释时的>原始类型参数方法。

因此,对于您的代码,Tuple2,以及Int, Long, Double, Char, Boolean专门。这将生成相应的基本类型构造函数,如:

Tuple2(int _v1, int _v2) --> `Tuple2$mcII$sp`
Tuple2(long _v1, long _v2) 
...

还有另一件事需要明确,即 Box UnBox ,这意味着编译器将决定变量是否需要将其转换为<编译时强>原始类型或将变量转换为对象,找到更多BoxesRunTime

对于intsNullTuple,请参阅字节码:

 scala>:javap -c Foo
 public scala.Tuple2<java.lang.Object, java.lang.Object> intsNullTuple();
    Code:
       0: new           #17                 // class scala/Tuple2$mcII$sp
       3: dup
       4: aconst_null
       5: invokestatic  #23                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
       8: iconst_2
       9: invokespecial #27                 // Method scala/Tuple2$mcII$sp."<init>":(II)V
      12: areturn

正如您可以看到上面的代码,编译器决定<{> 1}} unbox 对象int,这将返回一个基本类型BoxesRunTime.unboxToInt Tuple2 $ mcII $ sp(int _1,int _2)`。

对于int. so it's actually will invoke,请参阅字节码:

intAndStringNullTuple

你也可以看到 public scala.Tuple2<java.lang.Object, java.lang.String> intAndStringNullTuple(); Code: 0: new #32 // class scala/Tuple2 3: dup 4: aconst_null 5: invokestatic #23 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I 8: invokestatic #36 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; 11: ldc #38 // String 2 13: invokespecial #41 // Method scala/Tuple2."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V 16: areturn boxToInteger,它实际上会调用Object

以及为什么Tuple2(Object _1, Object _2)返回_1()0返回_1,因为 Java 泛型仅支持null类型,Object,当您调用Tuple2<Object, Object>实际调用_1()时,它等于调用java.lang.Object _1()

public int _1$mcI$sp();

因此scala> :javap -c scala.Tuple2$mcII$sp Compiled from "Tuple2.scala" public final class scala.Tuple2$mcII$sp extends scala.Tuple2<java.lang.Object, java.lang.Object> implements scala.Product2$mcII$sp { public final int _1$mcI$sp; public final int _2$mcI$sp; public int _1$mcI$sp(); Code: 0: aload_0 1: getfield #14 // Field _1$mcI$sp:I 4: ireturn ... public java.lang.Object _1(); Code: 0: aload_0 1: invokevirtual #33 // Method _1:()I 4: invokestatic #56 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; 7: areturn 将返回_1()

0直接为_1实际访问Tuple2<Object, Object>字段,因为它是Object,因此它应该是 null

scala> :javap -c scala.Tuple2
Compiled from "Tuple2.scala"
public class scala.Tuple2<T1, T2> implements scala.Product2<T1, T2>, scala.Serializable {
  public final T1 _1;

  public final T2 _2;

最后,根据我的理解,自取消装箱 专门以来,我们需要始终尝试调用{{1} } _1()