2014-10-29 11 views
5

Ich habe einige Java-Code, der eine Liste anhand einiger Eingaben filtert. Es nutzt derzeit eine Lambda, zum Beispiel:So konvertieren Sie Lambda-Filter mit dynamischen Werten zu Methodenreferenzen

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) { 
    List<ComplexObject> complexObjects = retrieveAllComplexObjects(); 
    return complexObjects 
       .stream() 
       .filter(compObject -> allowedTags.contains(compObject.getTag())) 
       .collect(Collectors.toList()); 
} 

Was ich tun möchte, ist die Filterlogik auf eine andere Methode zu bewegen, es testbar wiederverwendbar und leicht Einheit zu machen. Daher wollte ich anstelle des an die Filtermethode übergebenen Lambda eine Methodenreferenz verwenden. Einfach zu tun, wenn die Filterlogik ziemlich statisch ist (d. H. Die Liste der erlaubten Tags ist zur Kompilierungszeit bekannt), aber ich kann nicht herausfinden, wie dies mit dynamischen Daten im Filter zu tun ist.

Was ich wollte, war eine Möglichkeit, eine Methode Referenz zu verwenden und dann die zweite dynamische passieren param heißt

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) { 
    List<ComplexObject> complexObjects = retrieveAllComplexObjects(); 
    return complexObjects 
       .stream() 
       .filter(this::filterByAllowedTags, allowedTags) 
       .collect(Collectors.toList()); 
} 

So ist es möglich, zu tun, was ich will, oder bin ich diese Situation falsch möglicherweise anbietet?

Antwort

4

Ich würde vorschlagen, eine Predicate als Parameter übergeben. Auf diese Weise kann der Anrufer auf beliebige Kriterien filtern basiert es will, einschließlich allowedTags oder was auch immer:

List<String> allowedTags = ... ; 
    List<ComplexObject> result = 
     retrieveObjectsFilteredBy(cobj -> allowedTags.contains(cobj.getTag())); 

Aber man könnte sogar noch weiter gehen, je nachdem, wie viel:

public List<ComplexObject> retrieveObjectsFilteredBy(Predicate<ComplexObject> pred) { 
    List<ComplexObject> complexObjects = retrieveAllComplexObjects(); 
    return complexObjects.stream() 
     .filter(pred) 
     .collect(Collectors.toList()); 
} 

Das ist wie so genannt würde Refactoring sind Sie bereit zu tun. Anstatt eine "List" zurück zu geben, wie wäre es mit einer Stream? Und anstatt die retrieve-filter-Methode, die eine List zurückgibt, wie wäre es mit einer Stream auch zurück?

public Stream<ComplexObject> retrieveObjectsFilteredBy2(Predicate<ComplexObject> pred) { 
    Stream<ComplexObject> complexObjects = retrieveAllComplexObjects2(); 
    return complexObjects.filter(pred); 
} 

Und die rufenden Seite würde wie folgt aussehen:

List<String> allowedTags = ... ; 
    List<ComplexObject> result = 
     retrieveObjectsFilteredBy2(cobj -> allowedTags.contains(cobj.getTag())) 
      .collect(toList()); 

Jetzt können Sie, dass die Abrufen-Filter-Methode eines beliebigen Wertes ist nicht, sehen, wenn man es genau hinschauen Zugabe so Sie könnten es nur inline als auch in den Anrufer:

List<String> allowedTags = ... ; 
    List<ComplexObject> result = 
     retrieveAllComplexObjects2() 
      .filter(cobj -> allowedTags.contains(cobj.getTag())) 
      .collect(toList()); 

natürlich, je nachdem, was der Anrufer tun will, ist es vielleicht nicht die Ergebnisse in einer Liste gesammelt werden sollen; Vielleicht möchten Sie die Ergebnisse mit forEach() oder etwas anderem verarbeiten.

Jetzt können Sie noch den Filter in seine eigene Methode ausklammern, zum Testen/Debuggen, und Sie können eine Methode Referenz verwenden:

boolean cobjFilter(ComplexObject cobj) { 
    List<String> allowedTags = ... ; 
    return allowedTags.contains(cobj.getTag()); 
} 

    List<ComplexObject> result = 
     retrieveAllComplexObjects2() 
      .filter(this::cobjFilter) 
      .collect(toList()); 

Wenn Sie nicht wollen, der Filter die erlaubten Tags haben gebaut hinein, man es sich von einem Prädikat in eine Funktion höherer Ordnung, die ein Prädikat stattdessen kehrt ändern können:

Predicate<ComplexObject> cobjFilter(List<String> allowedTags) { 
    return cobj -> allowedTags.contains(cobj.getTag()); 
} 

    List<String> allowedTags = ... ; 
    List<ComplexObject> result = 
     retrieveAllComplexObjects2() 
      .filter(cobjFilter(allowedTags)) 
      .collect(toList()); 

welche dieser Varianten hängt am meisten Sinn macht, was wie Ihre Anwendung aussieht und welche Art von Dynamik, die Sie beim Filtern benötigen.

+0

Viele gute Option hier. Gute Antwort. –

2

Wie wäre es mit dem Folgenden? Es extrahiert das Prädikat in einer separaten Methode, um es leicht testbar zu machen, und kann leicht wiederverwendet werden.

public Predicate<ComplexObject> tagAllowed(List<String> allowedTags) { 
    return (ComplexObject co) -> allowedTags.contains(co.getTag()); 
} 

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) { 
    List<ComplexObject> complexObjects = retrieveAllComplexObjects(); 
    return complexObjects 
       .stream() 
       .filter(tagAllowed(allowedTags)) 
       .collect(Collectors.toList()); 
} 
2

Das Problem mit der Methode Referenz this::filterByAllowedTags ist, dass es die Form hat:

(ComplexObject, List<String>) -> boolean 

Aber es in filter() übergeben wird ist, die eine Lambda der Form erwartet:

(ComplexObject) -> boolean 

Mit anderen Worten, this::filterByAllowedTags kann nie ein Predicate sein, aber es könnte eine alternative Schnittstelle sein Predicate2. Dann brauchen Sie auch eine Überladung von Filter, die eine Predicate2 dauert.

Eclipse Collections (vormals GS Collections) hat die Methode select() die wie filter() verhält und selectWith() die wie die Überlast verhält ich gerade beschrieben.

Mit select():

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) 
{ 
    // retrieveAllComplexObjects returns a MutableList, possibly FastList 
    MutableList<ComplexObject> complexObjects = retrieveAllComplexObjects(); 
    // select() returns MutableList here which extends List 
    return complexObjects.select(compObject -> allowedTags.contains(compObject.getTag())); 
} 

Mit selectWith():

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) 
{ 
    // retrieveAllComplexObjects returns a MutableList, possibly FastList 
    MutableList<ComplexObject> complexObjects = retrieveAllComplexObjects(); 
    // select() returns MutableList here which extends List 
    return complexObjects.selectWith(this::filterByAllowedTags, allowedTags); 
} 

private boolean filterByAllowedTags(ComplexObject complexObject, List<String> allowedTags) 
{ 
    return allowedTags.contains(complexObject.getTag()); 
} 

Hinweis: Ich bin ein Committer für Eclipse Kollektionen.

Verwandte Themen