2016-03-24 8 views
10

Ho bisogno di unire tutti gli elementi di una lista B in un'altra lista listaA.java 8 unire tutti gli elementi di ListB in ListA se non presenti

Se un elemento è già presente (basato su un controllo di uguaglianza personalizzato) nell'elenco A Non voglio aggiungerlo.

Non voglio usare Set, e non voglio sovrascrivere equals() e hashCode().

I motivi sono, non voglio impedire i duplicati in listA di per sé, voglio solo non unire da listB se ci sono già elementi in listA che considero uguali.

Non voglio eseguire l'override di equals() e hashCode() poiché ciò significherebbe che è necessario verificare che la mia implementazione di equals() per gli elementi sia valida in ogni circostanza. Potrebbe tuttavia essere che gli elementi di listB non sono completamente inizializzati, ovvero potrebbero mancare un ID oggetto, dove potrebbe essere presente in elementi di listA.

mio approccio attuale comporta un'interfaccia e un programma di utilità-Function:

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()) 
     ); 
    } 
} 

E poi userei in questo modo:

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

per ottenere la mia nuova lista in elencoA con tutti gli elementi fusi da B.

È un buon approccio? C'è un modo migliore/più semplice per realizzare lo stesso?

risposta

7

volete qualcosa di simile:

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()) 
    ); 
} 

Non hai bisogno di un'interfaccia HasEqualityFunction. È possibile riutilizzare BiPredicate per verificare se i due oggetti sono uguali per quanto riguarda la logica.

Questo codice filtra solo gli elementi in listB che non sono contenuti in listA secondo il predicato indicato. Attraversa listA tante volte quante sono gli elementi in listB.


Un'implementazione alternativa e meglio performante sarebbe quella di utilizzare una classe wrapper che avvolge gli elementi e ha come equals metodo vostra predicato:

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()) 
    ); 
} 

Questa prima avvolge ogni elemento all'interno di un oggetto Wrapper e raccoglie loro in un Set. Quindi, filtra gli elementi di listB che non sono contenuti in questo set. Il test di uguaglianza viene eseguito delegando al predicato specificato. Il vincolo è che abbiamo anche bisogno di dare un hashFunction per implementare correttamente hashCode.

codice di esempio sarebbe:

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

Grazie, ho già applicato il tuo suggerimento con BiPredicate ed eliminato l'interfaccia. Analizzerò anche il tuo secondo suggerimento più tardi: utilizzare una classe wrapper per poter eseguire l'override di equals e hashCode in un contesto specifico è un'ottima idea. – SebastianRiemer

2

è possibile utilizzare un HashingStrategy basato Set da Eclipse Collections

Se è possibile utilizzare l'interfaccia MutableList:

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)); 
} 

Se non è possibile modificare la tipo di elencoA e elencoB da 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)); 
} 

Nota: contribuisco alle raccolte Eclipse.