1

I have a requirement where I have to aggregate a number of objects based on its properties. Object has around 10 properties and aggregation must be done on all its properties. For example - If there are two objects A and B of some class C with properties p1, p2, p3,...p10, (all properties are of String type) then these two objects must be considered equal only if all its corresponding properties are equal. For this I have two approaches in mind using HashMap in Java-

Approach 1 - Using key as Object of tyep C and Value as Integer for count and increase the count every time an existing object is found in Map otherwise create a new key value pair. HahsMap<C, Integer> But in this approach since I have to aggregate on all the properties, I will have to write(override) an equals() method which will check all the string properties for equality and similarly some implementation for hashCode().

Approach 2 - Using key as a single string made by concatenation of all the properties of object and value as a wrapper object which will have two properties one the object of type C and another a count variable of Integer type. For each object(C) create an String key by concatenation of its properties and if key already exists in the Map, get the wrapper object and update its count property, otherwise create a new key, value pair.

HashMap<String, WrapperObj>

In this approach I don't have to do any manual task to use String as key and also it is considered a good practice to use String as key in Map.

Approach 2 seems easy to implement and efficient as opposed to Approach 2 every time when equals is called all the properties will be checked one by one. But I am not sure whether Approach 2 in a standard way of comparing two objects and performing this kind of operation.

Please suggest if there is any other way to implement this requirement, like if there is any better way to implement equals() method for using it as key when all its properties should be taken into consideration when checking for equality of objects.

Example - Class whose objects needs aggregation with hash and equals implementation in case of Approach 1

public class Report {

private String p1;
private String p2;
private String p3;
private String p4;
.
.
.
private String p10;
@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((p1 == null) ? 0 : p1.hashCode());
    result = prime * result + ((p2 == null) ? 0 : p2.hashCode());
    result = prime * result + ((p3 == null) ? 0 : p3.hashCode());
    result = prime * result + ((p4 == null) ? 0 : p4.hashCode());
    return result;
}
@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (!(obj instanceof Report))
        return false;
    Report other = (Report) obj;
    if (p1 == null) {
        if (other.p1 != null)
            return false;
    } else if (!p1.equals(other.p1))
        return false;
    if (p2 == null) {
        if (other.p2 != null)
            return false;
    } else if (!p2.equals(other.p2))
        return false;
    if (p3 == null) {
        if (other.p3 != null)
            return false;
    } else if (!p3.equals(other.p3))
        return false;
    if (p4 == null) {
        if (other.p4 != null)
            return false;
    } else if (!p4.equals(other.p4))
        return false;
    .
    .
    .
    if (p10 == null) {
        if (other.p10 != null)
            return false;
    } else if (!p10.equals(other.p10))
        return false;
    return true;
}
}

Code For aggregation Approach 1-

Map<Report, Integer> map = new HashMap<Report, Integer>();
    for(Report report : reportList) {
        if(map.get(report) != null)
            map.put(report, map.get(report)+1);
        else
            map.put(report, 1);
    }

Approach 2 - With wrapper class and not implementing equals and hash for Report class.

public class Report {

private String p1;
private String p2;
private String p3;
private String p4;

public String getP1() {
    return p1;
}
public void setP1(String p1) {
    this.p1 = p1;
}
public String getP2() {
    return p2;
}
public void setP2(String p2) {
    this.p2 = p2;
}
public String getP3() {
    return p3;
}
public void setP3(String p3) {
    this.p3 = p3;
}
public String getP4() {
    return p4;
}
public void setP4(String p4) {
    this.p4 = p4;
}

Report warpper class - public class ReportWrapper {

private Report report;
private Integer count;

public Report getReport() {
    return report;
}
public void setReport(Report report) {
    this.report = report;
}
public Integer getCount() {
    return count;
}
public void setCount(Integer count) {
    this.count = count;
}
}

Code For aggregation Approach 2-

    Map<String, ReportWrapper> map = new HashMap<String, 
    ReportWrapper>();
    for(Report report : reportList) {
        String key = report.getP1() + ";" + report.getP2() + 
       ";" + report.getP3() +
       ";" + .....+ ";" + report.getP10();
        ReportWrapper rw = map.get(key);
        if(rw != null) {
            rw.setCount(rw.getCount()+1);
            map.put(key, rw);
        }
        else {
            ReportWrapper wrapper = new ReportWrapper();
            wrapper.setReport(report);
            wrapper.setCount(1);
            map.put(key, wrapper);
        }
    }

PSI: Here I am more concerned about, which approach is better.

Jainesh kumar
  • 93
  • 1
  • 9
  • A small example would be helpful in your case rather than a wall of text. – Eritrean Jul 08 '20 at 09:52
  • Your second approach can lead to false results if concatenation happens to produce the same result. Example: `str1 = "stack" str2 = "overflow" str11 = "stackover" str22 = "flow"` -> `str1 + str2 == str11 + str22` but `str1 != str11 && str2 != str22` – Eritrean Jul 08 '20 at 10:00
  • 1
    Note that nowadays it's less of a hassle to write `equals` (use `Object.equals(this.p1,other.p1)&&Object.equals(this.p2,other.p2)...` - which takes care of null handling). Also, use `Objects.hash(...)` to simplify the hash function. Using `equals` and `hashCode` will also enable you to easily collect frequencies using `Stream` and `Collectors.groupingBy`. – RealSkeptic Jul 08 '20 at 11:56

1 Answers1

0

Consider using the equals and hashcode methods that you can get generated from an IDE or use a tool like Lombok which will do it for you using an annotation and you don't have to write any code.

For lombok:

This is what IDEA generates if you want to go that route. No special process required.

@Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Report report = (Report) o;
        return Objects.equals(prop1, report.prop1) &&
                Objects.equals(prop2, report.prop2) &&
                Objects.equals(prop3, report.prop3) &&
                Objects.equals(prop4, report.prop4) &&
                Objects.equals(prop5, report.prop5) &&
                Objects.equals(prop6, report.prop6) &&
                Objects.equals(prop7, report.prop7) &&
                Objects.equals(prop8, report.prop8) &&
                Objects.equals(prop9, report.prop9);
    }

    @Override
    public int hashCode() {
        return Objects.hash(prop1, prop2, prop3, prop4, prop5, prop6, prop7, prop8, prop9);
    }
Joe W
  • 2,773
  • 15
  • 35
  • Basically I have to aggregate objects not just compare two objects. I have added the example code in the question. – Jainesh kumar Jul 08 '20 at 10:29
  • Added a note. Recommend using the tools you've got at hand. Don't need to do this manually in most cases. – Joe W Jul 08 '20 at 11:28