从Java访问元组的奇怪行为

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Java访问元组的奇怪行为相关的知识,希望对你有一定的参考价值。

我正在寻找关于我在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访问,但我可以在我的测试中访问它。因此,我正在寻找支持它的版本。

谢谢。

答案

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

这是因为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)

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

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

另一个有趣的事实是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

产量:

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

从现在开始,null通过0转变为unboxToInt,并通过Integer包裹在boxToInteger实例中。

Edit:

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

另一答案

首先,需要调用,在Scala中,一切都是Object,没有primitive type(对于你的代码,它的Int)不像Java,但是Scala需要编译到Java Bytecode以在JVM中运行,因为Object消耗更多的内存比原始类型,所以Scala有specialized来解决这个问题,它意味着当用specialized用类型注释时生成原始类型参数方法。

所以对于你的代码,它是qazxsw poi,它专门用于qazxsw poi。这将生成对应的基本类型构造函数,如:

Tuple2

还有另一件事需要明确,那就是Box和UnBox,这意味着编译器将决定变量是否需要在编译时将其转换为原始类型或将变量转换为Object,找到它更多Int, Long, Double, Char, Boolean

对于Tuple2(int _v1, int _v2) --> `Tuple2$mcII$sp` Tuple2(long _v1, long _v2) ... ,请参阅字节码:

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 ,这是返回一个原始类型intTuple2 $ mcII $ sp(int _1,int _2)`。

对于BoxesRunTime.unboxToInt,请参阅字节码:

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,因为Java泛型只支持_1类型,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,所以它应该为null。

Tuple2<Object, Object>

最后,所以对于我的理解,由于盒子和unbox专门,我们需要总是尝试调用Object而不是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;

以上是关于从Java访问元组的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

Python:用于元组的 Pandas DataFrame

使用制服时 Xamarin OpenGL 片段着色器的奇怪行为

如何使用字段名称的变量访问命名元组的字段?

Java中元组的使用

从 finally 块返回时 Java 的奇怪行为

访问元组的字段