什么scala语句或代码可以生成一个无法转换为java的字节码?

时间:2014-07-04 04:07:50

标签: java scala bytecode

我已经阅读了answer关于将Scala代码转换为Java代码的问题。它说:

我认为从scala转换回标准java是不可能的,因为Scala做了一些相当低级别的字节码操作。我90%确定他们做了一些无法完全转换回普通Java代码的东西。

那么Scala语句或代码可以生成无法转换为java的字节码?

P.S。我通常同意这个答案,但想要一个具体的例子用于学习目的。

6 个答案:

答案 0 :(得分:8)

答案实际上取决于您想要尝试转换代码的难度。

由于Java和Scala都是完整的,因此任何一个程序都可以简单地转换为另一个程序,但这并不是真正有趣或有用。

您真正想要的是将结果转换为可读的惯用代码。从这个角度来看,即使Java代码也不能自动转换为Java,因为编译会丢失信息(尽管与C相比相对较少),并且机器在编写人类可读代码方面不如人类好。

如果您有Java和Scala专家,他们可能会用Java重写您的Scala代码库,最终会得到合理惯用的Java代码。但它不像Scala那样可读,因为Scala是一种旨在改进Java的语言。 Scala尝试从Java中删除疣并提供强大的高级编程功能,无需使用所有经典的Java样板。因此,Java等效代码库将不具备可读性。

从这个角度来看,答案是“Scala中没有Java的任何功能”。

答案 1 :(得分:6)

Scala的嵌套块没有Java等价物。

Scala中的嵌套块(取自this question):

def apply(x: Boolean) = new Tuple2(null, {
  while (x) { }
  null
})

生成字节码

 0: new           #12                 // class scala/Tuple2
 3: dup           
 4: aconst_null   
 5: iload_1       
 6: ifne          5
 9: aconst_null   
10: invokespecial #16                 // Method scala/Tuple2."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
13: areturn   

在指令0处,一个未初始化的对象被推入堆栈,然后在指令10处初始化。在这两个点之间有一个向后跳跃从6到5.这实际上揭示了OpenJDK字节码验证器中的一个错误,因为它拒绝了这个尽管JVM规范可以接受它,但代码仍然可以接受。这可能是通过测试得出的,因为这个字节码不能从Java生成。

在Java中,嵌套块不是计算值的表达式,那么最接近的Java等价物将是

public Tuple2 apply(boolean x){
  while(x){}
  return new Tuple2(null,null);
}

哪个会编译成类似于

的东西
 0: iload_1
 1: ifne          0
 3: new           #12                 // class scala/Tuple2
 6: dup
 7: aconst_null
 8: dup
 9: invokespecial #16                 // Method scala/Tuple2."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
12: areturn

请注意,在向后跳转时,堆栈上没有未初始化的对象。 (N.B.字节码是手写的,不执行!)


来自Li,White和Singer的

This paper显示了JVM语言的差异,包括它们编译的字节码。它发现在字节码的N-gram分析中,在Java 执行的字节码中找不到Scala执行的<4> 5克的58.5%。这并不是说Java 不能生成这些字节码,而是它们不存在于Java语料库中。

答案 2 :(得分:2)

正如您所指出的,Scala最终会编译为JVM字节码。来自JVM指令集的明显指令是 goto goto

Scala编译器可能使用goto 作为实例来优化循环或尾递归方法。在这种情况下,在Java中你必须模仿goto的行为。

正如锑的暗示,图灵完整语言至少可以模仿另一种图灵完整语言。然而,由此产生的程序可能是重量级的,并且不是最理想的。

作为最后一点,反编译器可能有所帮助。我不熟悉反编译器的内在函数,但我认为它们非常依赖于模式。我的意思是,例如,Java源模式f(x)编译为字节码模式f'(x),因此有很多努力和经验,有些设法将Bytecode f'(y)反编译为Java源f(y)

但是,我还没有听说过Scala反编译器(可能有人在研究它)。

[编辑]关于模仿goto

的行为,我原来的意思

我在循环中记住了switch/case语句,并且cdshines在循环中使用标记的break/continue显示了另一种方式(尽管我相信使用“被忽视和谴责”功能不是标准)。

在其中任何一种情况下,为了跳回到先前的指令,需要一个惯用的Java循环(for/while/do-while)(任何其他建议?)。无限循环使其易于实现,条件循环需要更多工作,但我认为这是可行的。

此外,goto不限于循环。为了向前跳,Java需要其他结构。

一个反例:在C中,有一些限制,但你不必经历这么长的时间,因为有goto指令。

作为相关主题,如果您对Scala中的非惯用跳转感兴趣, c.f。 this old Q&A of mine。我的观点是,不仅Scala编译器可能以一种在Java中不自然的方式发出goto,而且开发人员可以在Scala宏的帮助下对其进行严格控制。

LabelDef标记的表达式。在语言语法中不可表达,但是由编译器生成以模拟while / do-while循环,以及模式匹配器。在我过去的测试中,它也可以用于前向跳转。在Scala Internals中,开发人员写了关于删除LabelDef的内容,但我不知道他们是否以及何时会这样做。

因此,是的,您可以在Java中重现goto的行为,但由于这样做的复杂性,这不是我所谓的标准 Java,恕我直言。也许我的措辞不正确,但在我看来,通过复杂手段复制基本行为是对这种行为的“模仿”。

干杯。

答案 3 :(得分:1)

这实际上取决于你如何定义那么Scala语句或代码可以产生哪些字节码无法转换为java?

最终,一些scala功能由存储元信息的名为ScalaSignature(scala签名)的后盾支持。从2.10开始,它可以被视为 secret api ,它被scala反射机制(与java反射完全不同)抽象出来。文档很少,但你可以查看这个pdf to get the details(从那时起可能会有重大变化)。除非你是后备字节码操作工具,否则无法在原生java中生成相同的结构。

在一个更放松的意义上,有一些宏和implicts只与scalac交互,并且在java中没有直接模拟。是的,您可以编写java代码,与scalac生成的结果相同,但是您无法编写将直接编译的动态指令。

答案 4 :(得分:1)

我碰巧使用了大量的字节代码,而且我曾经wrote a summary的字节代码功能通过编写Java代码无法重现。但是,所有这些不存在的特性都是组成字节代码指令的惯例。在Java 8中,any existing opcode被Java类文件格式使用。这并不奇怪,因为Java语言驱动Java字节代码格式的演变。一个例外可能是为了更好地支持JVM上的动态语言而引入的INVOKEDYNAMIC指令,但即使这条指令也在Java 8中用于实现lambda表达式。因此,可能存在不是由 javac 编译器生成的字节代码指令的组合/顺序,但是没有仅由另一种JVM语言使用的特定指令。

在我在摘要中命名的字节代码feautes中,我最明显地说抛出未声明的已检查异常而不捕获它们是Scala支持的功能,而不是Java支持的功能。否则,我会说 scalac 没有 javac 所不知道的低级字节码操作。根据我的经验,大多数Scala类也可以用Java明确编写。

答案 5 :(得分:-4)

我认为没有这样的代码。 AFAIK只有一个java无法生成的jvm指令 - invoke_dynamic。此指令适用于动态语言,而scala是静态类型语言,这意味着它也无法生成它。因此,可以将scala代码转换为java代码,并且可能是不可读的java代码。