it-swarm.com.de

Python-artiges Listenverständnis in Java

Da Java keine Übergabemethoden als Parameter zulässt, welchen Trick verwenden Sie, um Python wie Listenverständnis in Java zu implementieren?

Ich habe eine Liste (ArrayList) von Strings. Ich muss jedes Element mithilfe einer Funktion transformieren, sodass ich eine weitere Liste bekomme. Ich habe mehrere Funktionen, die einen String als Eingabe annehmen und einen anderen String als Ausgabe zurückgeben. Wie erstelle ich eine generische Methode, die die Liste und die Funktion als Parameter erhält, damit ich mit jedem verarbeiteten Element eine Liste zurückbekomme? Es ist im wörtlichen Sinne nicht möglich, aber welchen Trick sollte ich verwenden?

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

53
euphoria83

Grundsätzlich erstellen Sie eine Funktionsschnittstelle:

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

und übergeben Sie dann eine anonyme Unterklasse an Ihre Methode.

Ihre Methode kann die Funktion entweder direkt auf jedes Element anwenden:

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 erstellen Sie eine neue List (im Wesentlichen erstellen Sie ein Mapping von der Eingabeliste zur Ausgabeliste):

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 bevorzugt ist, hängt von Ihrem Anwendungsfall ab. Wenn Ihre Liste extrem groß ist, kann die In-Place-Lösung die einzig mögliche sein. Wenn Sie viele verschiedene Funktionen auf dieselbe ursprüngliche Liste anwenden möchten, um viele abgeleitete Listen zu erstellen, möchten Sie die Version map.

35
Michael Myers

In Java 8 können Sie Methodenreferenzen verwenden:

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

Oder, wenn Sie eine neue Listeninstanz erstellen möchten:

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

Die Google Collections-Bibliothek bietet viele Klassen für das Arbeiten mit Sammlungen und Iteratoren auf einer viel höheren Ebene als die von normalem Java unterstützten Funktionen und auf funktionale Weise (Filter, Karte, Falz usw.). Sie definiert Funktions- und Prädikatsschnittstellen und Methoden, mit denen sie Sammlungen verarbeiten, so dass dies nicht erforderlich ist. Es hat auch Komfortfunktionen, die den Umgang mit Java-Generics weniger anstrengend machen. 

Ich benutze auch Hamcrest ** zum Filtern von Sammlungen.

Die beiden Bibliotheken lassen sich leicht mit Adapterklassen kombinieren.


** Interessenerklärung: Ich habe Hamcrest mitgeschrieben

17
Nat

Apache Commons CollectionsUtil.transform (Collection, Transformer) ist eine weitere Option.

5
user111246

Ich baue dieses Projekt, um Listenverständnis in Java zu schreiben, jetzt ist es 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 den Ausgabeausdruck auf ähnliche Weise transformieren möchten

// { 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
farolfo

Sie können Lambdas wie folgt für die Funktion verwenden:

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 Verwendung:

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ückkehren:

["1", "2", "3", "4", "cheese"]
0