2016-03-24 2 views
10

Ich muss alle Elemente einer listB in eine andere Liste listA zusammenführen.java 8 alle Elemente von ListB in ListA zusammenfassen, wenn nicht vorhanden

Wenn ein Element bereits vorhanden ist (basierend auf einer benutzerdefinierten Gleichheitsüberprüfung) in listA Ich möchte es nicht hinzufügen.

Ich möchte Set nicht verwenden, und ich möchte nicht gleich() und hashCode() überschreiben.

Gründe, ich will nicht verhindern, dass Duplikate in listA an sich, ich möchte nur nicht von listB fusionieren, wenn es bereits Elemente in listA gibt, die ich für gleich halte.

Ich möchte equals() und hashCode() nicht überschreiben, denn das würde bedeuten, dass ich sicherstellen muss, dass meine Implementierung von equals() für die Elemente in jedem Fall gilt. Es kann jedoch sein, dass Elemente von listB nicht vollständig initialisiert sind, d. H. Sie können eine Objekt-ID vermissen, wenn diese in Elementen von listA vorhanden ist.

Mein aktueller Ansatz beinhaltet eine Schnittstelle und eine Utility-Funktion:

public interface HasEqualityFunction<T> { 

    public boolean hasEqualData(T other); 
} 

public class AppleVariety implements HasEqualityFunction<AppleVariety> { 
    private String manufacturerName; 
    private String varietyName; 

    @Override 
    public boolean hasEqualData(AppleVariety other) { 
     return (this.manufacturerName.equals(other.getManufacturerName()) 
      && this.varietyName.equals(other.getVarietyName())); 
    } 

    // ... getter-Methods here 
} 


public class CollectionUtils { 
    public static <T extends HasEqualityFunction> void merge(
     List<T> listA, 
     List<T> listB) { 
     if (listB.isEmpty()) { 
      return; 
     } 
     Predicate<T> exists 
      = (T x) -> { 
       return listA.stream().noneMatch(
         x::hasEqualData); 
      }; 
     listA.addAll(listB.stream() 
      .filter(exists) 
      .collect(Collectors.toList()) 
     ); 
    } 
} 

Und dann würde ich es wie folgt verwendet werden:

... 
List<AppleVariety> appleVarietiesFromOnePlace = ... init here with some elements 
List<AppleVariety> appleVarietiesFromAnotherPlace = ... init here with some elements 
CollectionUtils.merge(appleVarietiesFromOnePlace, appleVarietiesFromAnotherPlace); 
... 

auf meine neue Liste in listA mit allen Elementen zu erhalten fusioniert von B.

Ist das ein guter Ansatz? Gibt es einen besseren/leichteren Weg, das Gleiche zu erreichen?

Antwort

7

Sie wollen etwas wie folgt aus:

public static <T> void merge(List<T> listA, List<T> listB, BiPredicate<T, T> areEqual) { 
    listA.addAll(listB.stream() 
         .filter(t -> listA.stream().noneMatch(u -> areEqual.test(t, u))) 
         .collect(Collectors.toList()) 
    ); 
} 

Sie keine HasEqualityFunction Schnittstelle benötigen. Sie können BiPredicate erneut verwenden, um zu testen, ob die beiden Objekte hinsichtlich Ihrer Logik gleich sind.

Dieser Code filtert nur die Elemente in listB, die gemäß dem angegebenen Prädikat nicht in listA enthalten sind. Es passiert listA so oft wie es Elemente in listB gibt.


Eine alternative und bessere performante Implementierung wäre eine Wrapper-Klasse zu verwenden, die Ihre Elemente wickelt und hat als equals Methode Ihr Prädikat:

public static <T> void merge(List<T> listA, List<T> listB, BiPredicate<T, T> areEqual, ToIntFunction<T> hashFunction) { 

    class Wrapper { 
     final T wrapped; 
     Wrapper(T wrapped) { 
      this.wrapped = wrapped; 
     } 
     @Override 
     public boolean equals(Object obj) { 
      return areEqual.test(wrapped, ((Wrapper) obj).wrapped); 
     } 
     @Override 
     public int hashCode() { 
      return hashFunction.applyAsInt(wrapped); 
     } 
    } 

    Set<Wrapper> wrapSet = listA.stream().map(Wrapper::new).collect(Collectors.toSet()); 

    listA.addAll(listB.stream() 
         .filter(t -> !wrapSet.contains(new Wrapper(t))) 
         .collect(Collectors.toList()) 
    ); 
} 

Dieser erste Wraps jedes Element innerhalb eines Wrapper Objekt und sammelt sie in eine Set. Dann filtert es die Elemente von listB, die nicht in diesem Satz enthalten sind. Der Gleichheitstest wird durch Delegieren an das gegebene Prädikat durchgeführt. Die Einschränkung ist, dass wir auch hashFunction geben müssen, um hashCode richtig zu implementieren.

Beispielcode wäre:

List<String> listA = new ArrayList<>(Arrays.asList("foo", "bar", "test")); 
List<String> listB = new ArrayList<>(Arrays.asList("toto", "foobar")); 
CollectionUtils.merge(listA, listB, (s1, s2) -> s1.length() == s2.length(), String::length); 
System.out.println(listA); 
+0

Danke, ich habe Ihren Vorschlag bereits mit dem BiPredicate übernommen und das Interface beseitigt. Ich werde später auch auf Ihren zweiten Vorschlag eingehen - eine Wrapper-Klasse zu verwenden, um equals überschreiben zu können, und hashCode in einem bestimmten Kontext ist eine großartige Idee. – SebastianRiemer

2

können Sie ein verwenden HashingStrategy Basis Set von Eclipse Collections

Wenn Sie MutableList Schnittstelle verwenden:

public static void merge(MutableList<AppleVariety> listA, MutableList<AppleVariety> listB) 
{ 
    MutableSet<AppleVariety> hashingStrategySet = HashingStrategySets.mutable.withAll(
     HashingStrategies.fromFunctions(AppleVariety::getManufacturerName, 
      AppleVariety::getVarietyName), 
     listA); 
    listA.addAllIterable(listB.asLazy().reject(hashingStrategySet::contains)); 
} 

Wenn Sie nicht das ändern kann Art der ListeA und ListeB von List:

public static void merge(List<AppleVariety> listA, List<AppleVariety> listB) 
{ 
    MutableSet<AppleVariety> hashingStrategySet = HashingStrategySets.mutable.withAll(
     HashingStrategies.fromFunctions(AppleVariety::getManufacturerName, 
      AppleVariety::getVarietyName), 
     listA); 
    listA.addAll(ListAdapter.adapt(listB).reject(hashingStrategySet::contains)); 
} 

Hinweis: Ich arbeite für Eclipse Collections.

Verwandte Themen