Ignorar duplicados al producir mapas usando flujos

257
Map<String, String> phoneBook = people.stream()
                                      .collect(toMap(Person::getName,
                                                     Person::getAddress));

Me sale java.lang.IllegalStateException: Duplicate keycuando se encuentra un elemento duplicado.

¿Es posible ignorar tal excepción al agregar valores al mapa?

Cuando hay duplicado, simplemente debe continuar ignorando esa clave duplicada.

Patan
fuente
Si puede usarlo, HashSet ignorará la clave, si ya existe.
sahitya
@ capitán-aryabhatta. ¿Es posible tener valores clave en hashset
Patan

Respuestas:

449

Esto es posible utilizando el mergeFunctionparámetro de Collectors.toMap(keyMapper, valueMapper, mergeFunction):

Map<String, String> phoneBook = 
    people.stream()
          .collect(Collectors.toMap(
             Person::getName,
             Person::getAddress,
             (address1, address2) -> {
                 System.out.println("duplicate key found!");
                 return address1;
             }
          ));

mergeFunction es una función que opera en dos valores asociados con la misma clave. adress1corresponde a la primera dirección que se encontró al recopilar elementos y adress2corresponde a la segunda dirección encontrada: esta lambda simplemente le dice que mantenga la primera dirección e ignora la segunda.

Tunaki
fuente
55
Estoy confundido, ¿por qué no se permiten valores duplicados (no claves)? ¿Y cómo permitir valores duplicados?
Hendy Irawan
¿Hay alguna manera de recuperar la clave por la cual ocurre la colisión? responda aquí: stackoverflow.com/questions/40761954/…
Guillaume
2
¿Es posible ignorar totalmente esta entrada si hay un choque? Básicamente, si alguna vez encuentro claves duplicadas, no quiero que se agreguen. En el ejemplo anterior, no quiero dirección1 o dirección2 en mi mapa.
djkelly99
55
@Hendy Irawan: se permiten valores duplicados. La función de fusión es elegir entre (o fusionar) dos valores que tienen la misma clave .
Ricola
3
@ djkelly99 En realidad puedes, solo tienes que hacer que regrese la función de reasignación null. Consulte toMap doc que apunta a fusionar doc que indica Si la función de reasignación devuelve nulo, la asignación se elimina.
Ricola
98

Como se dijo en JavaDocs :

Si las claves asignadas contienen duplicados (de acuerdo con Object.equals(Object)), IllegalStateExceptionse emite una cuando se realiza la operación de recopilación. Si las claves asignadas pueden tener duplicados, utilícelas toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction)en su lugar.

Entonces deberías usar toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction)en su lugar. Simplemente proporcione una función de fusión , que determinará cuál de los duplicados se coloca en el mapa.

Por ejemplo, si no le importa cuál, simplemente llame

Map<String, String> phoneBook = 
        people.stream()
              .collect(Collectors.toMap(Person::getName, 
                                        Person::getAddress, 
                                        (a1, a2) -> a1));
Alaska
fuente
8

La respuesta @alaster me ayudó mucho, pero me gustaría agregar una información significativa si alguien está tratando de agrupar la información.

Si tiene, por ejemplo, dos Orderscon el mismo codepero diferente quantityde productos para cada uno, y su deseo es sumar las cantidades, puede hacer:

List<Order> listQuantidade = new ArrayList<>();
listOrders.add(new Order("COD_1", 1L));
listOrders.add(new Order("COD_1", 5L));
listOrders.add(new Order("COD_1", 3L));
listOrders.add(new Order("COD_2", 3L));
listOrders.add(new Order("COD_3", 4L));

listOrders.collect(Collectors.toMap(Order::getCode, 
                                    o -> o.getQuantity(), 
                                    (o1, o2) -> o1 + o2));

Resultado:

{COD_3=4, COD_2=3, COD_1=9}
Dherik
fuente
1

Para agrupar por objetos

Map<Integer, Data> dataMap = dataList.stream().collect(Collectors.toMap(Data::getId, data-> data, (data1, data2)-> {LOG.info("Duplicate Group For :" + data2.getId());return data1;}));
fjkjava
fuente
1

Para cualquier otra persona que tenga este problema pero sin que se transmitan claves duplicadas en el mapa, asegúrese de que su función keyMapper no devuelva valores nulos .

Es muy molesto rastrear esto porque el error dirá "Duplicar clave 1" cuando 1 es realmente el valor de la entrada en lugar de la clave.

En mi caso, mi función keyMapper intentó buscar valores en un mapa diferente, pero debido a un error tipográfico en las cadenas estaba devolviendo valores nulos.

final Map<String, String> doop = new HashMap<>();
doop.put("a", "1");
doop.put("b", "2");

final Map<String, String> lookup = new HashMap<>();
doop.put("c", "e");
doop.put("d", "f");

doop.entrySet().stream().collect(Collectors.toMap(e -> lookup.get(e.getKey()), e -> e.getValue()));
Andrés
fuente
0

He encontrado un problema de este tipo al agrupar objetos, siempre los resolví de una manera simple: realice un filtro personalizado con java.util.Set para eliminar objetos duplicados con cualquier atributo de su elección a continuación

Set<String> uniqueNames = new HashSet<>();
Map<String, String> phoneBook = people
                  .stream()
                  .filter(person -> person != null && !uniqueNames.add(person.getName()))
                  .collect(toMap(Person::getName, Person::getAddress));

Espero que esto ayude a cualquiera que tenga el mismo problema.

Shessuky
fuente
-1

Asumiendo que tienes personas es una lista de objetos

  Map<String, String> phoneBook=people.stream()
                                        .collect(toMap(Person::getName, Person::getAddress));

Ahora necesitas dos pasos:

1)

people =removeDuplicate(people);

2)

Map<String, String> phoneBook=people.stream()
                                        .collect(toMap(Person::getName, Person::getAddress));

Aquí hay un método para eliminar duplicados

public static List removeDuplicate(Collection<Person>  list) {
        if(list ==null || list.isEmpty()){
            return null;
        }

        Object removedDuplicateList =
                list.stream()
                     .distinct()
                     .collect(Collectors.toList());
     return (List) removedDuplicateList;

      }

Agregar un ejemplo completo aquí

 package com.example.khan.vaquar;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class RemovedDuplicate {

    public static void main(String[] args) {
        Person vaquar = new Person(1, "Vaquar", "Khan");
        Person zidan = new Person(2, "Zidan", "Khan");
        Person zerina = new Person(3, "Zerina", "Khan");

        // Add some random persons
        Collection<Person> duplicateList = Arrays.asList(vaquar, zidan, zerina, vaquar, zidan, vaquar);

        //
        System.out.println("Before removed duplicate list" + duplicateList);
        //
        Collection<Person> nonDuplicateList = removeDuplicate(duplicateList);
        //
        System.out.println("");
        System.out.println("After removed duplicate list" + nonDuplicateList);
        ;

        // 1) solution Working code
        Map<Object, Object> k = nonDuplicateList.stream().distinct()
                .collect(Collectors.toMap(s1 -> s1.getId(), s1 -> s1));
        System.out.println("");
        System.out.println("Result 1 using method_______________________________________________");
        System.out.println("k" + k);
        System.out.println("_____________________________________________________________________");

        // 2) solution using inline distinct()
        Map<Object, Object> k1 = duplicateList.stream().distinct()
                .collect(Collectors.toMap(s1 -> s1.getId(), s1 -> s1));
        System.out.println("");
        System.out.println("Result 2 using inline_______________________________________________");
        System.out.println("k1" + k1);
        System.out.println("_____________________________________________________________________");

        //breacking code
        System.out.println("");
        System.out.println("Throwing exception _______________________________________________");
        Map<Object, Object> k2 = duplicateList.stream()
                .collect(Collectors.toMap(s1 -> s1.getId(), s1 -> s1));
        System.out.println("");
        System.out.println("k2" + k2);
        System.out.println("_____________________________________________________________________");
    }

    public static List removeDuplicate(Collection<Person> list) {
        if (list == null || list.isEmpty()) {
            return null;
        }

        Object removedDuplicateList = list.stream().distinct().collect(Collectors.toList());
        return (List) removedDuplicateList;

    }

}

// Model class
class Person {
    public Person(Integer id, String fname, String lname) {
        super();
        this.id = id;
        this.fname = fname;
        this.lname = lname;
    }

    private Integer id;
    private String fname;
    private String lname;

    // Getters and Setters

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFname() {
        return fname;
    }

    public void setFname(String fname) {
        this.fname = fname;
    }

    public String getLname() {
        return lname;
    }

    public void setLname(String lname) {
        this.lname = lname;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", fname=" + fname + ", lname=" + lname + "]";
    }

}

Resultados:

Before removed duplicate list[Person [id=1, fname=Vaquar, lname=Khan], Person [id=2, fname=Zidan, lname=Khan], Person [id=3, fname=Zerina, lname=Khan], Person [id=1, fname=Vaquar, lname=Khan], Person [id=2, fname=Zidan, lname=Khan], Person [id=1, fname=Vaquar, lname=Khan]]

After removed duplicate list[Person [id=1, fname=Vaquar, lname=Khan], Person [id=2, fname=Zidan, lname=Khan], Person [id=3, fname=Zerina, lname=Khan]]

Result 1 using method_______________________________________________
k{1=Person [id=1, fname=Vaquar, lname=Khan], 2=Person [id=2, fname=Zidan, lname=Khan], 3=Person [id=3, fname=Zerina, lname=Khan]}
_____________________________________________________________________

Result 2 using inline_______________________________________________
k1{1=Person [id=1, fname=Vaquar, lname=Khan], 2=Person [id=2, fname=Zidan, lname=Khan], 3=Person [id=3, fname=Zerina, lname=Khan]}
_____________________________________________________________________

Throwing exception _______________________________________________
Exception in thread "main" java.lang.IllegalStateException: Duplicate key Person [id=1, fname=Vaquar, lname=Khan]
    at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1253)
    at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at com.example.khan.vaquar.RemovedDuplicate.main(RemovedDuplicate.java:48)
Vaquar Khan
fuente