Uso de transmisiones para recopilar en TreeSet con un comparador personalizado

92

Trabajando en Java 8, tengo una TreeSetdefinición como esta:

private TreeSet<PositionReport> positionReports = 
        new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp));

PositionReport es una clase bastante simple definida así:

public static final class PositionReport implements Cloneable {
    private final long timestamp;
    private final Position position;

    public static PositionReport create(long timestamp, Position position) {
        return new PositionReport(timestamp, position);
    }

    private PositionReport(long timestamp, Position position) {
        this.timestamp = timestamp;
        this.position = position;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public Position getPosition() {
        return position;
    }
}

Esto funciona bien.

Ahora quiero eliminar las entradas de TreeSet positionReportsdonde timestampes más antiguo que algún valor. Pero no puedo encontrar la sintaxis correcta de Java 8 para expresar esto.

Este intento en realidad se compila, pero me da un nuevo TreeSetcon un comparador indefinido:

positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(Collectors.toCollection(TreeSet::new))

¿Cómo expreso que quiero recopilar en TreeSetun comparador como Comparator.comparingLong(PositionReport::getTimestamp)?

Hubiera pensado algo como

positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(
                TreeSet::TreeSet(Comparator.comparingLong(PositionReport::getTimestamp))
            )
        );

Pero esto no compila / parece ser una sintaxis válida para referencias a métodos.

tbsalling
fuente

Respuestas:

118

Las referencias a métodos son para cuando tiene un método (o constructor) que ya se ajusta a la forma del objetivo que está tratando de satisfacer. No puede usar una referencia de método en este caso porque la forma a la que se dirige es una Supplierque no toma argumentos y devuelve una colección, pero lo que tiene es un TreeSetconstructor que sí toma un argumento, y debe especificar cuál es ese argumento es. Por lo tanto, debe adoptar el enfoque menos conciso y usar una expresión lambda:

TreeSet<Report> toTreeSet(Collection<Report> reports, long timestamp) {
    return reports.stream().filter(report -> report.timestamp() >= timestamp).collect(
        Collectors.toCollection(
            () -> new TreeSet<>(Comparator.comparingLong(Report::timestamp))
        )
    );
}
gdejohn
fuente
4
Una cosa a tener en cuenta es que no necesita el comparador si el tipo de su TreeSet (en este caso PositionReport) implementa comparable.
xtrakBandit
35
Siguiendo con @xtrakBandit, nuevamente si no necesita especificar el comparador (clasificación natural), puede hacerlo muy conciso:.collect(Collectors.toCollection(TreeSet::new));
Joshua Goldberg
Recibí este error:toCollection in class Collectors cannot be applied to given types
Bahadir Tasdemir
@BahadirTasdemir El código funciona. Asegúrese de que solo está pasando un argumento a Collectors::toCollection: a Supplierque devuelve a Collection. Supplieres un tipo con un solo método abstracto, lo que significa que puede ser el objetivo de una expresión lambda como en esta respuesta. La expresión lambda no debe tomar argumentos (de ahí la lista de argumentos vacía ()) y devolver una colección con un tipo de elemento que coincida con el tipo de elementos en el flujo que está recopilando (en este caso, a TreeSet<PositionReport>).
gdejohn
15

Esto es fácil, solo use el siguiente código:

    positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(()->new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp)
)));
Владимир Дворник
fuente
9

Puede convertirlo en un SortedSet al final (siempre que no le importe la copia adicional).

positionReports = positionReports
                .stream()
                .filter(p -> p.getTimeStamp() >= oldestKept)
                .collect(Collectors.toSet());

return new TreeSet(positionReports);
Daniel Scott
fuente
7
Debe tener cuidado al hacer esto. PODRÍA perder elementos mientras hace esto. Como en la pregunta anterior, el comparador natural de los elementos es diferente al que OP quiere usar. Entonces, en la conversión inicial, dado que es un conjunto, podría perder algunos elementos que el otro comparador podría no tener (es decir, el primer comparador podría devolver compareTo() 0 mientras que el otro podría no tenerlo para algunas comparaciones. Todos los que compareTo()son 0 está perdido ya que este es un conjunto.)
looneyGod
6

Hay un método de recopilación para esto sin tener que utilizar corrientes: default boolean removeIf(Predicate<? super E> filter). Ver Javadoc .

Entonces su código podría verse así:

positionReports.removeIf(p -> p.timestamp < oldestKept);
Michael Damone
fuente
1

El problema con TreeSet es que el comparador que queremos para clasificar los elementos se usa también para detectar duplicados al insertar elementos en el conjunto. Entonces, si la función de comparación es 0 para dos elementos, descarta incorrectamente uno considerándolo duplicado.

La detección de duplicados debe realizarse mediante un método hashCode correcto separado de los elementos. Prefiero usar un HashSet simple para evitar duplicados con un hashCode considerando todas las propiedades (id y nombre en el ejemplo) y devolver una lista ordenada simple al obtener los elementos (ordenando solo por nombre en el ejemplo):

public class ProductAvailableFiltersDTO {

    private Set<FilterItem> category_ids = new HashSet<>();

    public List<FilterItem> getCategory_ids() {
        return category_ids.stream()
            .sorted(Comparator.comparing(FilterItem::getName))
            .collect(Collectors.toList());
    }

    public void setCategory_ids(List<FilterItem> category_ids) {
        this.category_ids.clear();
        if (CollectionUtils.isNotEmpty(category_ids)) {
            this.category_ids.addAll(category_ids);
        }
    }
}


public class FilterItem {
    private String id;
    private String name;

    public FilterItem(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FilterItem)) return false;
        FilterItem that = (FilterItem) o;
        return Objects.equals(getId(), that.getId()) &&
                Objects.equals(getName(), that.getName());
    }

    @Override
    public int hashCode() {

        return Objects.hash(getId(), getName());
    }
}
Daniel Mora
fuente
1
positionReports = positionReports.stream()
                             .filter(p -> p.getTimeStamp() >= oldestKept)
                             .collect(Collectors.toCollection(() -> new 
TreeSet<PositionReport>(Comparator.comparingLong(PositionReport::getTimestamp))));
Cyril Sojan
fuente