Search code examples
javalistcollectionshashmaphashset

containsAll returning different responses in case of Map and HashSet


Objective : Trying to replicate the behaviour of compareEmployeesMap in compareEmployees in a simpler way. However, I'm getting different response with what I tried. I have overridden the hashcode and equals method in both, the Manager and Owner class. Not sure what I am doing wrong hence I'm asking here. Thank you for your guidance.

note : If i use the constructor with just the encodedkey, it works as expected. The constructor with DecimalFields seems to be a problem.

I have added the snippet below.

public static void main(String[] args) {
    List<Human> oldEmployees = new ArrayList<>();
    oldEmployees.add(new Owner("1", new DecimalFields(BigDecimal.ZERO, BigDecimal.TEN, BigDecimal.ONE), 10));
    oldEmployees.add(new Manager("2", new DecimalFields(BigDecimal.ZERO, BigDecimal.TEN, BigDecimal.ONE)));

    List<Human> newEmployees = new ArrayList<>();
    newEmployees.add(new Owner("1", new DecimalFields(BigDecimal.ZERO, BigDecimal.TEN, BigDecimal.ONE), 10));
    newEmployees.add(new Manager("2", new DecimalFields(BigDecimal.ZERO, BigDecimal.TEN, BigDecimal.ONE)));
    //newEmployees.add(new Manager("3", new DecimalFields(BigDecimal.ZERO, BigDecimal.TEN, BigDecimal.ONE)));


    System.out.println(compareEmployees(oldEmployees, newEmployees));
    System.out.println(compareEmployeesMap(oldEmployees, newEmployees));
}

private static boolean compareEmployees(List<Human> oldEmployees, List<Human> newEmployees) {
    return !new HashSet<>(newEmployees).containsAll(oldEmployees);
}

private static boolean compareEmployeesMap(List<Human> oldEmployees, List<Human> newEmployees) {
    Map<String, Boolean> oldEmployeesMap = oldEmployees.stream()
            .collect(Collectors.
                    toMap(Human::getEncodedKey,
                            Manager.class::isInstance));

    if(!oldEmployeesMap.entrySet().isEmpty()){
        Map<String, Boolean> newEmployeesMap = newEmployees.stream()
                .collect(Collectors
                        .toMap(Human::getEncodedKey,
                                Manager.class::isInstance));

        return !newEmployeesMap.entrySet().containsAll(oldEmployeesMap.entrySet());
    }
    return false;
}

Owner Class :

public class Owner implements Human {

private String encodedKey;

private DecimalFields decimalFields;

private Integer age;

public Owner(String encodedKey) {
    this.encodedKey = encodedKey;
}

public Owner(String encodedKey, DecimalFields decimalFields, Integer age) {
    this.encodedKey = encodedKey;
    this.decimalFields = decimalFields;
    this.age = age;
}

@Override
public String getEncodedKey() {
    return encodedKey;
}

public DecimalFields getDecimalFields() {
    return decimalFields;
}

public Integer getAge() {
    return age;
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Owner)) return false;
    Owner owner = (Owner) o;
    return Objects.equals(encodedKey, owner.getEncodedKey())
            && Objects.equals(decimalFields, owner.getDecimalFields())
            && Objects.equals(age, owner.getAge());
}

@Override
public int hashCode() {
    return Objects.hash(encodedKey, decimalFields, age);
}
}

Manager Class :

public class Manager implements Human {

private String encodedKey;

private DecimalFields decimalFields;

public Manager(String encodedKey, DecimalFields decimalFields) {
    this.encodedKey = encodedKey;
    this.decimalFields = decimalFields;
}

public Manager(String encodedKey) {
    this.encodedKey = encodedKey;
}

@Override
public String getEncodedKey() {
    return encodedKey;
}

public DecimalFields getDecimalFields() {
    return decimalFields;
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Manager)) return false;
    Manager manager = (Manager) o;
    return Objects.equals(encodedKey, manager.getEncodedKey())
            && Objects.equals(decimalFields, manager.getDecimalFields());
}

@Override
public int hashCode() {
    return Objects.hash(encodedKey, decimalFields);
}
}

DecimalFields class :

public class DecimalFields {

private BigDecimal min;
private BigDecimal max;
private BigDecimal defaultValue;

public DecimalFields(BigDecimal min, BigDecimal max, BigDecimal defaultValue) {
    this.min = min;
    this.max = max;
    this.defaultValue = defaultValue;
}

public Optional<BigDecimal> getMin() {
    return Optional.ofNullable(min);
}

public Optional<BigDecimal> getMax() {
    return Optional.ofNullable(max);
}

public Optional<BigDecimal> getDefaultValue() {
    return Optional.ofNullable(defaultValue);
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof DecimalFields)) return false;
    DecimalFields that = (DecimalFields) o;
    return Objects.equals(min, that.getMin())
            && Objects.equals(max, that.getMax())
            && Objects.equals(defaultValue, that.getDefaultValue());
}

@Override
public int hashCode() {
    return Objects.hash(min, max, defaultValue);
}
}

interface Human :

public interface Human {

public String getEncodedKey();
}

Solution

  • So i discovered this :

    Compares this BigDecimal with the specified Object for equality. Unlike compareTo, this method considers two BigDecimal objects equal only if they are equal in value and scale (thus 2.0 is not equal to 2.00 when compared by this method).

    Influenced by this answer, I was able to resolve this issue :

    private static boolean compareEmployees(List<Human> oldEmployees, List<Human> newEmployees) {
        Set<String> setOldEmp = oldEmployees.stream().map(bd -> bd.getEncodedKey()).collect(Collectors.toSet());
        Set<String> setNewEmp = newEmployees.stream().map(bd -> bd.getEncodedKey()).collect(Collectors.toSet());
        
        return !setNewEmp.containsAll(setOldEmp);
    }