我已经阅读了answer关于将Scala代码转换为Java代码的问题。它说:
我认为从scala转换回标准java是不可能的,因为Scala做了一些相当低级别的字节码操作。我90%确定他们做了一些无法完全转换回普通Java代码的东西。
那么Scala语句或代码可以生成无法转换为java的字节码?
P.S。我通常同意这个答案,但想要一个具体的例子用于学习目的。
答案 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.字节码是手写的,不执行!)
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代码。