2014-10-30 9 views
8

nehme ich dieses Scala Merkmal haben:Java Interoperabilität Elende mit Scala Generika und Boxen

trait UnitThingy { 
    def x(): Unit 
} 

eine Implementierung Java Providing ist einfach genug:

import scala.runtime.BoxedUnit; 

public class JUnitThingy implements UnitThingy { 
    public void x() { 
    return; 
    } 
} 

Nun wollen wir mit einem allgemeinen Merkmal beginnen:

trait Foo[A] { 
    def x(): A 
} 

trait Bar extends Foo[Unit] 

der Ansatz oben wird nicht funktionieren, da die Einheit x n zurückgibt ow geboxt, aber die Abhilfe ist einfach genug:

import scala.runtime.BoxedUnit; 

public class JBar implements Bar { 
    public BoxedUnit x() { 
    return BoxedUnit.UNIT; 
    } 
} 

Nehmen wir nun an habe ich eine Implementierung mit x auf der Scala Seite definiert bekam:

trait Baz extends Foo[Unit] { 
    def x(): Unit =() 
} 

Ich weiß, dass ich das nicht x aus sehen Java, so definiere ich meine eigene:

import scala.runtime.BoxedUnit; 

public class JBaz implements Baz { 
    public BoxedUnit x() { 
    return BoxedUnit.UNIT; 
    } 
} 

Aber das sprengt:

[error] .../JBaz.java:3: error: JBaz is not abstract and does not override abstract method x() in Baz 
[error] public class JBaz implements Baz { 
[error]  ^
[error] /home/travis/tmp/so/js/newsutff/JBaz.java:4: error: x() in JBaz cannot implement x() in Baz 
[error] public BoxedUnit x() { 
[error]     ^
[error] return type BoxedUnit is not compatible with void 

Und wenn ich den abstrakten Klasse-dass-Delegierten-to-super-Trait Trick versuchen:

abstract class Qux extends Baz { 
    override def x() = super.x() 
} 

Und dann:

public class JQux extends Qux {} 

Es ist sogar noch schlimmer:

[error] /home/travis/tmp/so/js/newsutff/JQux.java:1: error: JQux is not abstract and does not override abstract method x() in Foo 
[error] public class JQux extends Qux {} 
[error]  ^

(Beachten Sie, dass diese Definition von JQux würde gut funktionieren, wenn Baz wurde nicht erweitert Foo[Unit] .)

Wenn man sich anschaut, was javap sagt über Qux, es ist nur komisch:

public abstract class Qux implements Baz { 
    public void x(); 
    public java.lang.Object x(); 
    public Qux(); 
} 

denke ich, sowohl die hier Probleme mit Baz und Qux scalac Fehler sein, aber ist es eine Abhilfe? Ich interessiere mich nicht wirklich für den Baz Teil, aber gibt es irgendeinen Weg, den ich von Qux in Java erben kann?

Antwort

8

Sie sind keine Scalac-Käfer; Der Scala-Compiler arbeitet in Ihrem Auftrag hart daran, den Unterschied zwischen Prozeduren und Methoden aufzuzeigen, der Java-Compiler dagegen nicht.

Für die Effizienz und Java-Kompatibilität werden Methoden, die Unit nicht-generisch zurückgeben, tatsächlich als Prozeduren implementiert (d. H. Rückgabetyp ist void). Dann wird die generische Implementierung implementiert, indem die void-Version aufgerufen wird und zurückgegeben wird.

public abstract class Qux implements Baz { 
    public void x(); 
    Code: 
     0: aload_0  
     1: invokestatic #17   // Method Baz$class.x:(LBaz;)V 
     4: return   

    public java.lang.Object x(); 
    Code: 
     0: aload_0  
     1: invokevirtual #22   // Method x:()V 
     4: getstatic  #28   // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit; 
     7: areturn 

Das Problem ist, dass während Javac wird für Sie mit spezifischen vs. generic Object das gleiche tun abgeleitetes Typen zurückgeben, ist es nicht die Object versteht - void Crossover.

Das ist eine Erklärung. Es gibt eine Abhilfe, obwohl es kompliziert die Scala Hierarchie:

trait Bazz[U <: Unit] extends Bar[Unit] { 
    def x() =().asInstanceOf[U] // Must go here, not in Baz! 
} 
trait Baz extends Bazz[Unit] {} 

Jetzt haben Sie Scala gezwungen, die Möglichkeit einer nicht-exactly- Unit Rückgabetyp zu prüfen, so hält es BoxedUnit für die Rückkehr; und Baz wirft diese Möglichkeit weg, aber es erzeugt kein neues void x(), um Java zu verwirren.

Dies ist zerbrechlich, um es milde auszudrücken. Es kann aber auch ein Problem für die Java und Scala Teams sein: Java ist nicht glücklich, solange die BoxedUnit Version da ist; es ärgert sich aktiv über die void Version. (Sie können eine abstrakte Klasse mit beiden erzeugen, indem Sie zweimal von Foo erben; da es nicht funktioniert, sind die Details unwichtig.) Scala könnte es alleine schaffen, indem es geänderten Bytecode mit einer zusätzlichen BoxedUnit-Methode überall dort ausgibt, wo Java es erwartet. ..nicht sicher.

+0

Vielleicht "Bug" ist das falsche Wort - ich meine etwas mehr wie "Verrat an einer ziemlich vernünftigen Erwartung". Mit dem 'BoxedUnit.UNIT'-Trick kann ich eine generische 'Unit'-Methode implementieren, warum bricht dann das Hinzufügen einer Vererbungsebene das? Es ist nicht so, dass es die Methode in irgendeiner Weise weniger generisch macht. –

+1

Es gibt nicht eine Vererbungsebene, sondern eine neue 'public void x()' Methode, und javac ist nicht glücklich darüber. Es glaubt, dass _das_ die Eigenschaft ist, die das Merkmal implementiert, obwohl es eine BoxedUnit-Version gibt. Es sucht nach einer Signatur von BoxedUnit x() ', was es selbst erzeugen würde. –

+0

(Es gibt eine 'BoxedUnit'-Version herum, aber es ist getippt als' Objekt', meine ich.) –