Group by n fields in Java like SQL using streams API

Interface Aggregatable.java: This interface should be implemented by class on which aggregation can be run.

package prakshar.aggregation;

public interface Aggregatable<T> {
	public void add(T o);
}

Class Employee.java: Employee POJO implementing Aggregatable. Please note the override add method which defines aggregation strategy.

package prakshar.aggregation;

public class Employee implements Aggregatable<Employee>{
	
	String name;
	Integer department;
	String city;
	double salary;
	double bonus;

	public Employee(String name, Integer department, String city, Double salary, Double bonus) {
		this.name = name;
		this.department = department;
		this.city = city;
		this.salary = salary;
		this.bonus = bonus;
	}
	
	public String getName() {
		return name;
	}
	public Integer getDepartment() {
		return department;
	}
	public String getCity() {
		return city;
	}
	public double getSalary() {
		return salary;
	}
	public double getBonus() {
		return bonus;
	}

	@Override
	public void add(Employee o) {
		this.salary += o.salary;
		this.bonus += o.bonus;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(name).append(" # ").append(department).append(" # ").append(city).append(" # ").append(salary).append(" # ").append(bonus).append("\n");
		return sb.toString();
	}
}

Class Field.java: Field class used to store information of property on which the aggregation will happen. Similar to Group by Field1, Field2 in sql.

package prakshar.aggregation;

import org.apache.commons.beanutils.PropertyUtils;

public class Field{
	String name;
	Class type;
	public Field(String name, Class type) {
		super();
		this.name = name;
		this.type = type;
	}
	
	NestedProperty toValue(Object r, Field p) {
		try {
			return new NestedProperty(this, PropertyUtils.getNestedProperty(r, p.getName()));
		} catch (Exception e) {
			
		}
		return null;
	}

	public String getName() {
		return name;
	}

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

	public Class getType() {
		return type;
	}

	public void setType(Class type) {
		this.type = type;
	}
}

Class NestedProperty.java: This is used to hold key of aggregation result.

package prakshar.aggregation;

import java.util.Objects;

public class NestedProperty {
	private final Field property;
	private final Object value;
	
	public NestedProperty(Field property, Object value) {
		this.property = property;
		this.value = value;
	}
	
	@Override
	public boolean equals(Object obj) {
		if( this == obj) {
			return true;
		}
		if( obj == null || getClass() != obj.getClass() ) {
			return false;
		}
		NestedProperty that = (NestedProperty) obj;
		return property == that.property && Objects.equals(value, that.value);
	}
	
	@Override
	public int hashCode() {
		return Objects.hash(property, value);
	}
}

Class AggregationUtil .java: Generic aggregation logic implementation. No need to touch this java file. If you are able to understand that’s good enough.

package prakshar.aggregation;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.beanutils.PropertyUtils;

public class AggregationUtil {
	
	private static <T> List<T> agg(List<T> objects, Field field){
		Class classDefination = objects.get(0).getClass();
		final Class cast = field.getType();
		
		List<?> temp = objects.stream().filter( r -> r!=null )
				.collect(Collectors.groupingBy(
						record -> {
							try {
								return cast.cast(PropertyUtils.getNestedProperty(record, field.getName()));
							}catch(Exception e) {
								System.out.println("Property not found.");
							}
							return null;
						}))
				.entrySet().stream()
				.map( e -> e.getValue().stream()
						.reduce((f1, f2) -> {
							try {
								return (T) add(classDefination, f1, f2);
							} catch (Exception e1) {
								System.out.println("Error is method add()");
							}
							return null;
						})
				).map(f -> f.get())
				.collect(Collectors.toList());
		return (List<T>) temp;
	}
	
	public static <T> List<T> aggregateOnFields(List<T> objects, List<Field> fields){
		if( fields.size() == 1 ) {
			return agg(objects, fields.get(0));
		}else {
			Field last = fields.remove(fields.size()-1);
			Map<List<NestedProperty>, List<T>> aggregatedRecords = objects.stream()
					.collect(Collectors.groupingBy(r -> createGroupingKey(fields, r), Collectors.toList()));
			List<T> result = new ArrayList();
			for(List<T> records : aggregatedRecords.values()) {
				result.addAll(agg(records, last));
			}
			return result;
		}
	}

	private static List<NestedProperty> createGroupingKey(java.util.List<Field> fields, Object r) {
		return fields.stream().map(p -> p.toValue(r, p)).collect(Collectors.toList());
	}

	private static Object add(Class classDefination, Object f1, Object f2) throws Exception {
		Method method = classDefination.getDeclaredMethod("add", classDefination);
		method.invoke(f1, f2);
		return f1;
	}
}

Class AggregationTest.java: Testing aggregation logic with various scenarios.

  • The aggregation supports nested fields. If Employee class contains some complex class say Address( id, pincode) then Field name will be address.id.
  • The aggregation is independent of order of aggregation. Similar to SQL.
package prakshar.aggregation;

import java.util.ArrayList;
import java.util.List;

public class AggregationTest {

	static final Field DEPARTMENT = new Field("department", java.lang.Integer.class);
	static final Field CITY = new Field("city", java.lang.String.class);
	
	public static void main(String[] args) {
		Employee e1 = new Employee("Rahul", 1, "Hyderabad", 50000.00, 10000.00);
		Employee e2 = new Employee("Raj", 1, "Nagpur", 70000.00, 20000.00);
		Employee e3 = new Employee("Suraj", 2, "Nagpur", 60000.00, 25000.00);
		
		List<Employee> employees = new ArrayList();
		employees.add(e1);
		employees.add(e2);
		employees.add(e3);
		
		List<Field> fields = new ArrayList();
		fields.add(DEPARTMENT);
		List<Employee> aggregated = AggregationUtil.aggregateOnFields(employees, fields);
		System.out.println(aggregated); // Aggregate on DEPARTMENT
		fields.clear();
		
		fields.add(CITY);
		aggregated = AggregationUtil.aggregateOnFields(employees, fields);
		System.out.println(aggregated); // Aggregate on CITY
		
		fields.add(DEPARTMENT);
		aggregated = AggregationUtil.aggregateOnFields(employees, fields);
		System.out.println(aggregated); // Aggregate on CITY & DEPARTMENT
	}
}

Group by n fields in Java like SQL using streams API

(Visited 23 times, 1 visits today)

PrAkAsH

Eyeing investment. Please note that the views given in this website are meant for reference and guidance of the readers to explore further on the topics and take informed decisions. These should not be construed as investment advice or legal opinion.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

Solve : *
17 + 10 =