2009-05-22 11 views
47

Da Java Pass-Methoden nicht als Parameter erlaubt, welchen Trick verwenden Sie, um Python-ähnliches Listenverständnis in Java zu implementieren?Verständnis von Python-ähnlichen Listen in Java

Ich habe eine Liste (ArrayList) von Strings. Ich muss jedes Element transformieren, indem ich eine Funktion verwende, damit ich eine andere Liste bekomme. Ich habe mehrere Funktionen, die einen String als Eingabe nehmen und einen anderen String als Ausgabe zurückgeben. Wie mache ich eine generische Methode, der die Liste und die Funktion als Parameter gegeben werden können, damit ich mit jedem verarbeiteten Element eine Liste zurückbekomme. Es ist im wörtlichen Sinn nicht möglich, aber welchen Trick soll ich verwenden?

Die andere Möglichkeit besteht darin, für jede kleinere String-Verarbeitungsfunktion eine neue Funktion zu schreiben, die einfach über die gesamte Liste läuft, was irgendwie nicht so cool ist.

+2

als ein fyi, könnten Sie Jython oder Scala verwenden, um Listenergänzungen auf der JVM – geowa4

+3

zu erhalten ... oder Clojure! :) – Ashe

+0

SMH beim Lesen aller Antworten zu diesem Thema. In Python können Sie leicht ein Listenverständnis in einer Zeile von 40-60 Zeichen schreiben. Alle hier vorgeschlagenen Lösungen sind mehrere Zeilen und die meisten von ihnen länger als die einzige Zeile, die sie in Python nehmen würde. – ArtOfWarfare

Antwort

33

Grundsätzlich erstellen Sie eine Funktionsschnittstelle:

public interface Func<In, Out> { 
    public Out apply(In in); 
} 

und dann in einer anonymen Unterklasse auf Ihre Methode übergeben.

Ihre Methode könnte entweder die Funktion auf jedes Element in-place gilt:

public static <T> void applyToListInPlace(List<T> list, Func<T, T> f) { 
    ListIterator<T> itr = list.listIterator(); 
    while (itr.hasNext()) { 
     T output = f.apply(itr.next()); 
     itr.set(output); 
    } 
} 
// ... 
List<String> myList = ...; 
applyToListInPlace(myList, new Func<String, String>() { 
    public String apply(String in) { 
     return in.toLowerCase(); 
    } 
}); 

oder eine neues List schaffen (im Wesentlichen eine Zuordnung von der Eingangsliste zu der Ausgabeliste erstellen):

public static <In, Out> List<Out> map(List<In> in, Func<In, Out> f) { 
    List<Out> out = new ArrayList<Out>(in.size()); 
    for (In inObj : in) { 
     out.add(f.apply(inObj)); 
    } 
    return out; 
} 
// ... 
List<String> myList = ...; 
List<String> lowerCased = map(myList, new Func<String, String>() { 
    public String apply(String in) { 
     return in.toLowerCase(); 
    } 
}); 

Welche davon vorzuziehen ist, hängt von Ihrem Anwendungsfall ab. Wenn Ihre Liste extrem groß ist, kann die In-Place-Lösung die einzig mögliche Lösung sein. Wenn Sie viele verschiedene Funktionen auf dieselbe Originalliste anwenden möchten, um viele abgeleitete Listen zu erstellen, benötigen Sie die Version map.

+1

Aber dann bittest du mich, jede kleine Funktion in eine andere Klasse zu legen, da sie einen Standardnamen haben muss (in deinem Fall "apply"). Recht ? – euphoria83

+1

Nicht unbedingt; Ihre anonyme Klasse kann einfach die kleine Funktion in apply() aufrufen. Das ist so nah wie Java, um Zeiger zu funktionieren, ohne sich in die Gefahren der Reflexion zu wagen. –

+0

doToList erfindet das Rad neu. Was Sie hier gemacht haben, ist ein schlechtes Design dessen, was man normalerweise Map nennt. Die übliche Schnittstelle ist die öffentliche statische Liste Karte (Liste , Func f); Was es tut, ist, eine andere Liste zu erzeugen, anstatt die an Ort und Stelle zu ändern. Wenn Sie die ursprüngliche Liste ändern müssen, ohne die Referenz zu zerstören, tun Sie einfach eine .clear() gefolgt von einer addAll(). Kombiniere das nicht alles in einer Methode. – Pyrolistical

16

Die Google Collections library hat viele Klassen für die Arbeit mit Sammlungen und Iteratoren auf einer viel höheren Ebene als normale Java unterstützt und funktional (Filter, Map, Fold, etc.). Es definiert Schnittstellen für Funktionen und Prädikate sowie Methoden, mit denen sie Sammlungen verarbeiten können, ohne dass dies erforderlich ist. Es hat auch Komfortfunktionen, die den Umgang mit Java-Generika weniger anstrengend machen.

Ich verwende auch Hamcrest ** zum Filtern von Sammlungen.

Die beiden Bibliotheken lassen sich problemlos mit Adapterklassen kombinieren.


** Interessenerklärung: Ich als Co-Autor hamcrest

+11

Aus Neugier, warum heißt es Hamcrest? Ich kann immer noch nicht herausfinden, ob es schmackhaft klingt oder nicht. –

+12

Es ist ein Anagramm von "Matcher". – Nat

27

In Java 8 Sie Methode Referenzen verwenden:

List<String> list = ...; 
list.replaceAll(String::toUpperCase); 

Oder, wenn Sie eine neue Liste Instanz erstellen möchten:

List<String> upper = list.stream().map(String::toUpperCase).collect(Collectors.toList()); 
+7

Die Frage ist 7 Jahre alt und Java 8 existierte damals nicht. Dies sollte jetzt die akzeptierte Antwort sein;) – zpontikas

1

Ich baue dieses Projekt Liste schreiben Verständnis in Java ist jetzt ein Proof of concept in https://github.com/farolfo/list-comprehension-in-java

Beispiele

// { x | x E {1,2,3,4}^x is even } 
// gives {2,4} 

Predicate<Integer> even = x -> x % 2 == 0; 

List<Integer> evens = new ListComprehension<Integer>() 
    .suchThat(x -> { 
     x.belongsTo(Arrays.asList(1, 2, 3, 4)); 
     x.is(even); 
    }); 
// evens = {2,4}; 

Und wenn wir wollen den Ausgang Ausdruck in irgendeiner Weise wie

// { x * 2 | x E {1,2,3,4}^x is even } 
// gives {4,8} 

List<Integer> duplicated = new ListComprehension<Integer>() 
    .giveMeAll((Integer x) -> x * 2) 
    .suchThat(x -> { 
     x.belongsTo(Arrays.asList(1, 2, 3, 4)); 
     x.is(even); 
    }); 
// duplicated = {4,8} 
+1

Ein Teil der Schönheit des Python-Listenverständnisses ist, wie kurz es ist. Deine 6 langen Zeilen von Java könnten geschrieben werden als nur "[x * 2 für x in 1, 2, 3, 4 wenn x% 2 == 0]" ... 1 Zeile von 41 Zeichen. Nicht sicher, wie viel von Ihrem Code ist einfach schrecklich zu lesen, weil verdammt verbose Java ist vs, wie viel ist, weil Ihre Bibliothek die Dinge nicht präzise genug macht. – ArtOfWarfare

+0

Es ist immer noch besser als viele andere Lösungen hier, ich mag das wirklich – rhbvkleef

0

Sie lambdas für die Funktion, wie so verwenden können, zu transformieren:

class Comprehension<T> { 
    /** 
    *in: List int 
    *func: Function to do to each entry 
    */ 
    public List<T> comp(List<T> in, Function<T, T> func) { 
     List<T> out = new ArrayList<T>(); 
     for(T o: in) { 
      out.add(func.apply(o)); 
     } 
     return out; 
    } 
} 

die Nutzung:

List<String> stuff = new ArrayList<String>(); 
stuff.add("a"); 
stuff.add("b"); 
stuff.add("c"); 
stuff.add("d"); 
stuff.add("cheese"); 
List<String> newStuff = new Comprehension<String>().comp(stuff, (a) -> { //The <String> tells the comprehension to return an ArrayList<String> 
    a.equals("a")? "1": 
      (a.equals("b")? "2": 
       (a.equals("c")? "3": 
        (a.equals("d")? "4": a 
    ))) 
}); 

wird zurückgegeben:

["1", "2", "3", "4", "cheese"] 
Verwandte Themen