2011-07-01 17 views
41

Rozważmy następujący kod Scala:Korzystanie Scala z Java: przechodzącą funkcji jako parametry

package scala_java 
object MyScala { 
    def setFunc(func: Int => String) { 
    func(10) 
    } 
} 

Teraz w Javie, chciałbym użyć MyScala jak:

package scala_java; 
public class MyJava { 
    public static void main(String [] args) { 
     MyScala.setFunc(myFunc); // This line gives an error 
    } 
    public static String myFunc(int someInt) { 
     return String.valueOf(someInt); 
    } 
} 

Jednakże powyższe nie nie działa (zgodnie z oczekiwaniami, ponieważ Java nie zezwala na programowanie funkcjonalne). Jaki jest najłatwiejszy sposób obejścia funkcji w Javie? Chciałbym ogólne rozwiązanie, które działa z funkcjami mającymi dowolną liczbę parametrów.

EDYCJA: Czy Java 8 ma lepszą składnię niż klasyczne rozwiązania omówione poniżej?

Odpowiedz

25

Musisz ręcznie utworzyć egzemplarz Function1 w Javie. Coś jak:

final Function1<Integer, String> f = new Function1<Integer, String>() { 
    public int $tag() { 
     return Function1$class.$tag(this); 
    } 

    public <A> Function1<A, String> compose(Function1<A, Integer> f) { 
     return Function1$class.compose(this, f); 
    } 

    public String apply(Integer someInt) { 
     return myFunc(someInt); 
    } 
}; 
MyScala.setFunc(f); 

Jest to wzięte z Daniel Spiewak’s “Interop Between Java and Scala” article.

+4

Koniecznie sprawdź artykuł i klasę "FunUtils" Daniela w komentarzach, która pomaga usunąć niektóre z potrzebnych elementów dla '$ tag' i' compose'. –

+0

To bardzo pouczające. Widziałem wcześniej artykuł, ale nie czytałem komentarzy. – Jus12

7

Najprostszym sposobem dla mnie jest zdefiniował java interfejs jak:

public interface JFunction<A,B> { 
    public B compute(A a); 
} 

Następnie zmodyfikować kod Scala, przeciążenia setFunc przyjąć także JFunction obiektów, takich jak:

object MyScala { 
    // API for scala 
    def setFunc(func: Int => String) { 
    func(10) 
    } 
    // API for java 
    def setFunc(jFunc: JFunction[Int,String]) { 
    setFunc((i:Int) => jFunc.compute(i)) 
    } 
} 

Ty naturalnie użyje pierwszej definicji ze scala, ale nadal będzie mógł użyć drugiej z java:

public class MyJava { 
    public static void main(String [] args) { 
    MyScala.setFunc(myFunc); // This line gives an error 
    } 

    public static final JFunction<Integer,String> myFunc = 
    new JFunction<Integer,String>() { 
     public String compute(Integer a) { 
     return String.valueOf(a); 
     } 
    }; 

} 
+0

@ Jus12. Dzięki, to jest naprawione. – paradigmatic

+0

W rzeczywistości, funkcje Scala są * już * zaimplementowane jako typ SAM za kulisami. Można zatem bezpośrednio zaimplementować 'FunctionN' (zgodnie z odpowiedzią Jeana-Philippe'a) i nie trzeba wprowadzać własnego typu' JFunction'. –

+0

Nie zgadzam się, podczas gdy można bezpośrednio utworzyć instancję funkcji scala z języka Java, wynikowy kod jest raczej brzydki. Jeśli chcesz tylko uzyskać dostęp do kodu scala czasami, jest w porządku. Ale jeśli chcesz udostępnić interfejs API języka Java, uzyskasz lepszy kod, dodając wyższy poziom interfejsu i metody scala zaprojektowane dla interfejsu API języka Java. Pomoże to na przykład poradzić sobie z brakiem dorozumianych rozwiązań. – paradigmatic

58

W pakiecie scala.runtime znajdują się klasy abstrakcyjne o nazwie AbstractFunction1 itd. Dla innych arii. Aby z nich korzystać z Java wystarczy zastąpić apply, tak:

Function1<Integer, String> f = new AbstractFunction1<Integer, String>() { 
    public String apply(Integer someInt) { 
     return myFunc(someInt); 
    } 
}; 

Jeśli jesteś na Java 8 i chcesz używać Java 8 składni lambda za to, sprawdź https://github.com/scala/scala-java8-compat.

+2

Dzięki! Twoja odpowiedź jest najbardziej pomocna! Próbując zaimplementować funkcję 1, Java podpowiada mi, że muszę wprowadzić dziesiątki metod - funkcja 1 ma specjalizacje dla kilku typów pierwotnych. –

+1

To * jest * odpowiedź, której szukasz :) Sprawdź również http://twitter.github.io/scala_school/java.html – alexr

+0

Pamiętaj, że jeśli kompilujesz się na Androida lub iOS, nie będziesz w stanie używać scala-java8-compat, ponieważ retrolambda może modyfikować tylko to, do czego masz pliki źródłowe. Źródła nie mają też klas mostów lambda (JFunction0 itd.), Ponieważ są generowane przez kod bezpośrednio z sbt do plików .class. Być może istnieje sposób na włączenie plików .class do projektu, w których retrolambda może je zmodyfikować, ale nie zainwestowałem wysiłku, aby to zrozumieć. – voxoid

Powiązane problemy