I need to implement a pivot table in Java and I know how to do with Java 8 Streams features. There are a lot of good solution over the web but I need something more and I don't understand how to do that: I need to create a more dynamic table where ideally you don't know for what columns you have to aggregate.
For example, if I have the columns ("Nation", "Company", "Industry","Number of employes") I have to give as input:
A custom aggregation function (ex. sum) for the measure
A variable order of aggregation: ex., I want first aggregate for Nation and I gave as argument "Nation" or for Nation and Company and I gave as argument something like "Nation->Company".
In other words, I don't know which are the fields for my aggregation and basically I need a way to implement a generic GROUP BY SQL clause, so something like:
// Given an the Arraylist ("Nation", "Company", "Industry","Number of employes") called data with some rows
Map<String, List<Object[]>> map = data.stream().collect(
Collectors.groupingBy(row -> row[0].toString() + "-" + row[1].toString()));
for (Map.Entry<String, List<Object[]>> entry : map.entrySet()) {
final double average = entry.getValue().stream()
.mapToInt(row -> (int) row[3]).average().getAsDouble();
It's not what I need because it is too explicit.
I need to:
Split the input Arraylist in sublist by value given by the header name which I extract from my data (or more, it depends for how many column I have to group by)
Aggregate each sublist
Union the sublist
Could someone help or boost me? Thanks
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
class Input {
private String nation, company, industry;
private int employees;
public Input(String nation, String company, String industry, int employees) {
super();
this.nation = nation;
this.company = company;
this.industry = industry;
this.employees = employees;
}
public String getNation() {
return nation;
}
public void setNation(String nation) {
this.nation = nation;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
public int getEmployees() {
return employees;
}
public void setEmployees(int employees) {
this.employees = employees;
}
#Override
public String toString() {
return String.format(
"Nation : %s, Company : %s, Industry : %s, Employees : %s",
nation, company, industry, employees);
}
}
public class CustomGroupBy {
// Generic GroupBy
static Map<String, List<Input>> groupBy(List<Input> input,
Function<Input, String> classifier) {
return input.stream().collect(Collectors.groupingBy(classifier));
}
public static void main(String[] args) {
List<Input> input = Arrays.asList(new Input("India", "A", "IT", 12),
new Input("USA", "B", "ELECTRICAL", 90), new Input("India",
"B", "MECHANICAL", 122), new Input("India", "B", "IT",
12), new Input("India", "C", "IT", 200));
// You need to pass this in parameter
Function<Input, String> groupByFun = i -> i.getNation() + "-"
+ i.getCompany();
// Example-1
Map<String, List<Input>> groupBy = groupBy(input, Input::getCompany);
// Example-2
Map<String, List<Input>> groupBy2 = groupBy(input, groupByFun);
System.out.println(groupBy2);
List<Double> averages = groupBy
.entrySet()
.stream()
.map(entry -> entry.getValue().stream()
.mapToInt(row -> row.getEmployees()).average()
.getAsDouble()).collect(Collectors.toList());
System.out.println(averages);
}
}
You can make it generic by passing the functional interface. It is just for your reference.
I see two ways to make it generic. The first one is to use reflection to discover the method to call from the string representation of the field. The second one, is to create a generic get method that take a String in argument and return the value of the proper field. The second one is safer so I'll focus on that one. I'll start from the answer of #Anant Goswami which had done most of the work already.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
class Scratch {
// Input class from Anant Goswami in previous reply
static class Input {
private String nation, company, industry;
private int employees;
public Input(String nation, String company, String industry, int employees) {
super();
this.nation = nation;
this.company = company;
this.industry = industry;
this.employees = employees;
}
public String getNation() {
return nation;
}
public void setNation(String nation) {
this.nation = nation;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
public int getEmployees() {
return employees;
}
public void setEmployees(int employees) {
this.employees = employees;
}
#Override
public String toString() {
return String.format(
"Nation : %s, Company : %s, Industry : %s, Employees : %s",
nation, company, industry, employees);
}
public Object get(String field){
switch (field.toLowerCase()){
case "nation": return getNation();
case "company": return getCompany();
case "industry": return getIndustry();
case "employees": return getEmployees();
default: throw new UnsupportedOperationException();
}
}
}
private static Map<String, List<Input>> group(List<Input> inputs, String... fields){
Function<Input, String> groupBy = i -> Arrays.stream(fields).map(f -> i.get(f).toString()).collect(Collectors.joining("-"));
Map<String, List<Input>> result = inputs.stream().collect(Collectors.groupingBy(groupBy));
System.out.println(result);
return result;
}
public static void main(String[] args) {
List<Input> input = Arrays.asList(new Input("India", "A", "IT", 12),
new Input("USA", "B", "ELECTRICAL", 90), new Input("India",
"B", "MECHANICAL", 122), new Input("India", "B", "IT",
12), new Input("India", "C", "IT", 200));
group(input, "company");
group(input, "nation", "Company");
}
}
Which give as output
{A=[Nation : India, Company : A, Industry : IT, Employees : 12], B=[Nation : USA, Company : B, Industry : ELECTRICAL, Employees : 90, Nation : India, Company : B, Industry : MECHANICAL, Employees : 122, Nation : India, Company : B, Industry : IT, Employees : 12], C=[Nation : India, Company : C, Industry : IT, Employees : 200]}
{India-B=[Nation : India, Company : B, Industry : MECHANICAL, Employees : 122, Nation : India, Company : B, Industry : IT, Employees : 12], India-C=[Nation : India, Company : C, Industry : IT, Employees : 200], India-A=[Nation : India, Company : A, Industry : IT, Employees : 12], USA-B=[Nation : USA, Company : B, Industry : ELECTRICAL, Employees : 90]}
Related
I try to inject a string to a list using stream, but only inject the first row.
Input:
1, Kelvin, 1
2, kelvin, 1
3, John, 1
Expected:
1, Kelvin, 2, Alvin
3, John, 1, Alvin
Result:
1, Kelvin, 2 Alvin
3, John, 1, null
Code:
String lastName = "Alvin"
Map<Object, Person> map = list.stream().collect(Collectors.toMap(
f -> f.getCode() + f.getName(),
Function.identity(),
(s, a) -> new Person(s.getCode(), s.getName, s.getAmount(), lastName)));
The issue is that you're injecting lastName in mergeFunction parameter of Collectors.toMap function, instead of valueMapper parameter.
Collectors.toMap takes 3 arguments:
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction
keyMapper will generate a key for your entry in map,
valueMapper will generate a value for your entry in map,
mergeFunction is function which is called, when the value mapping function is applied to equal element (Object.equals()), so both values are merged using the provided merging function.
You should provide correct mapping function as valueMapper argument. Provided by you Function.identity() will always return original input parameter:
Returns a function that always returns its input argument.
Type parameters:
<T> – the type of the input and output objects to the function
Returns:
a function that always returns its input argument
static <T> Function<T, T> identity() {
return t -> t;
}
Apparently you mixed up the valueMapper and mergeFunction of the Collectors.toMap. Here is my best guess with the information given:
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.function.Function;
public class Mapper {
public class Person {
private Integer code;
private String name;
private String lastName;
public Person(Integer code, String name, String lastName) {
this.code = code;
this.name = name;
this.lastName = lastName;
}
public Integer getCode() {
return code;
}
public String getName() {
return name;
}
public String getLastName() {
return lastName;
}
public String toString() {
return this.code + ", " + this.name + ", " + this.lastName;
}
}
public static void main(String []args) {
new Mapper().run();
}
public void run() {
List<Person> persons = new ArrayList<>();
persons.add(new Person(1, "Kelvin", null));
persons.add(new Person(2, "Alvin", null));
persons.add(new Person(3, "John", null));
String lastName = "Alvin";
Map<Object, Person> map = persons.stream().collect(Collectors.toMap(
f -> f.getCode() + f.getName(),
p -> new Person(p.getCode(), p.getName(), lastName)));
for (Map.Entry<Object, Person> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
Output:
1Kelvin = 1, Kelvin, Alvin
2Alvin = 2, Alvin, Alvin
3John = 3, John, Alvin
While there already are good answers I just wanted to provide my answer with a different approach to a solution.
Instead of using the valueMapper of the Collector, I used the map function of the stream with the adjustment of using a builder pattern inside of the Person pojo to aid in the usability for this.
Please note that this approach will only work if the builder pattern is used and the class itself is returned by the result of the map operation.
Run it and see it working with Ideone: https://ideone.com/WFcZCo
/* package whatever; // don't place package name! */
import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
List<Person> personList = new ArrayList<>();
Person kelvin = new Person("1","Kelvin");
Person alvin = new Person("2","Alvin");
Person john = new Person("3","John");
personList.add(kelvin);
personList.add(alvin);
personList.add(john);
String lastName = "Alvin";
Map<Object, Person> map = personList.stream()
.map(p -> p.setLastName(lastName))
.collect(Collectors.toMap(
f -> f.getCode() + f.getName(),
Function.identity()
));
System.out.println("Result:");
for(Map.Entry<Object, Person> entry : map.entrySet()){
Object key = entry.getKey();
Person person = entry.getValue();
System.out.println(person);
}
}
}
class Person {
private String code;
private String name;
private String lastname;
public Person(String code, String name){
this.code = code;
this.name = name;
}
public Person(String code, String name, String lastname){
this.code = code;
this.name = name;
this.lastname = lastname;
}
public String getCode(){
return code;
}
public String getName(){
return name;
}
public String getLastname(){
return lastname;
}
public Person setLastName(String lastname){
this.lastname = lastname;
return this;
}
public String toString(){
return code + ", " + name + ", " + lastname;
}
}
And here the result:
Result:
1, Kelvin, Alvin
2, Alvin, Alvin
3, John, Alvin
I have a class
public class Person {
private String name;
private String country;
private String city;
private String pet;
private int totalCountryToCityCount;
private int petCount;
public Person(String name, String country, String city, String pet, int total, int petCount) {
this.name = name;
this.country = country;
this.city = city;
this.pet = pet;
this.totalCountryToCityCount = total;
this.petCount = petCount;
}
public String getName() {
return name;
}
public String getCountry() {
return country;
}
public String getCity() {
return city;
}
public String getPet() {
return pet;
}
public int getPetCount() {
return petCount;
}
public int getTotalCountryToCityCount() {
return totalCountryToCityCount;
}
}
and Given a list of Person class, I have do aggregations based upon the different properties of the class.
For eg -
Person person1 = new Person("John", "USA", "NYC", "Max", 1, 2);
Person person2 = new Person("Steve", "UK", "London", "Lucy", 2, 8);
Person person3 = new Person("Anna", "USA", "NYC", "Max", 4, 32);
Person person4 = new Person("Mike", "USA", "Chicago", "Duke", 5, 1);
Person person5 = new Person("Test", "INDIA", "HYD", "Tommy", 4, 32);
Person person6 = new Person("Test1", "INDIA", "HYD", "Tommy", 4, 65);
Person person7 = new Person("Tim", "USA", "Chicago", "Duke", 5, 111);
Person person8 = new Person("Tim", "USA", "Chicago", "Puke", 5, 111);
Person person9 = new Person("Test1", "INDIA", "DELHI", "Tommy", 4, 65);
List<Person> persons = Arrays
.asList(person1, person2, person3, person4, person5, person6, person7, person8,
person9);
Now I need to get a result such that I should get the total "totalCountryToCityCount" based upon the combinations of country and city and I should get total "petCount" based upon combinations of country,city and pet. I am able to get them separately using groupingBy and summingint
private Map<String, Map<String, Integer>> getTotalCountForCountry(List<Person> persons) {
return persons.stream().collect(groupingBy(Person::getCountry, getCityCount()));
}
public Collector<Person, ?, Map<String, Integer>> getCityCount() {
return groupingBy(Person::getCity, summingInt(Person::getTotal));
}
public Map<String, Map<String, Map<String, Integer>>> threeLevelGrouping(List<Person> persons) {
return persons
.stream().collect(
groupingBy(Person::getCountry, groupByCityAndPetName()
)
);
}
private Collector<Person, ?, Map<String, Map<String, Integer>>> groupByCityAndPetName() {
return groupingBy(Person::getCity, groupByPetName());
}
private Collector<Person, ?, Map<String, Integer>> groupByPetName() {
return groupingBy(Person::getPet, summingInt(Person::getPetCount));
}
which gives the result
{USA={Chicago={Puke=111, Duke=112}, NYC={Max=34}}, UK={London={Lucy=8}}, INDIA={DELHI={Tommy=65}, HYD={Tommy=97}}}
{USA={Chicago=15, NYC=5}, UK={London=2}, INDIA={DELHI=4, HYD=8}}
but the actual result which I want is :-
{USA={Chicago={15,{Puke=111, Duke=112}}, NYC={5,{Max=34} }, UK={London={2, {Lucy=8}}, INDIA={DELHI={4, {Tommy=65}}, , HYD={8,{Tommy=97}}}}
is there a way to achieve the same using Java stream API
I also tried using the code -
personList.stream().collect(groupingBy(person -> person.getCountry(), collectingAndThen(reducing(
(a, b) -> new Person(a.getName(), a.getCountry(), a.getCity(), a.getPet(),
a.getTotal() + b.getTotal(), a.getPetCount() + b.getPetCount())),
Optional::get)))
.forEach((country, person) -> System.out.println(country + person));
But was getting the result -
USAPerson{name='John', country='USA', city='NYC'}
UKPerson{name='Steve', country='UK', city='London'}
INDIAPerson{name='Test', country='INDIA', city='HYD'}
with the counts surprisingly removed
What you are looking for really is Collectors::teeing, but only available in java-12:
System.out.println(
persons.stream()
.collect(Collectors.groupingBy(
Person::getCountry,
Collectors.groupingBy(
Person::getCity,
Collectors.teeing(
Collectors.summingInt(Person::getTotalCountryToCityCount),
Collectors.groupingBy(
Person::getPet,
Collectors.summingInt(Person::getPetCount)
),
SimpleEntry::new
)
))));
A back-port for java-8 it is available here.
class Employee {
public string department;
public int salary;
}
List<Employee> allEmployees = ...
I need to have a list that will have only 1 top salary employee for each department. allEmployees is the source list.
You can do that with a grouping collector:
Map<String, Employee> topEmployees =
allEmployees.stream()
.collect(groupingBy(
e -> e.department,
collectingAndThen(maxBy(comparingInt(e -> e.salary)), Optional::get)
));
with the static imports
import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.maxBy;
This code creates a Stream of all the employees and groups them with their department with the help of Collectors.groupingBy. For all the values classified to the same key, we need to keep only the employee with the maximum salary, so we collect them with Collectors.maxBy and the comparator compares the salary with Comparator.comparingInt. Since maxBy returns an Optional<Employee> (to handle the case where there the list is empty), we wrap it with a call to Collectors.collectingAndThen with a finisher that just returns the employee: we know in this case that the optional won't be empty.
Alternative solution:
Map<String, Employee> topEmployees =
allEmployees.stream()
.collect(Collectors.toMap(
e -> e.department,
e -> e,
BinaryOperator.maxBy(Comparator.comparingInt(e -> e.salary))
));
When we encounter the first employee from the department, we add a new entry to the Map. When another employee is found, one with higher salary is kept. This way you don't need to meddle with optionals.
/Let's say you have a list of employee as List employeeList;
To find the departmentwise salary first you need to have comparator for employees/
Comparator<Employee> bySalary = Comparator.comparing(Employee::getSalary);
Then to find departement wise highest salary you do
Map<String, Optional<Employee>> collect =
employeeList.stream().collect(
Collectors.groupingBy(
Employee::getDept,
Collectors.reducing(BinaryOperator.maxBy(bySalary))
)
);
What are we doing here is,
we are grouping the employees on the basis of there department.
And along with Grouping we are saying return me the highest salary taker for that department,
And finally pick only max one.
To see the output override toString method in Employee class and do */
collect.entrySet().stream().forEach(System.out::println);
This is not a solution honestly.
Since I can't comment due to the low reputation, This is just a tiny observation which led to this correction/improvisation.
I ran into this same scenario & I tried Tagir Valeev's solution mentioned above which worked for me.
I used it as it is in my IDE, But the returned result was erroneous as the compiler complained as follows:
Compile Time Error: cannot convert from Map<Object, Object> to Map<String, Employee>.
That's where I understood the expected return type here is of type class Object & not what we expect it to be i.e Map<String, Employee>
I simply had to receive the my result into Map<Object, Object> instead of Map<String, Employee>.
Hence the following code where Map<Object, Object> topEmployees is iterated & the final expected result is stored into the Map<String, Employee> finalResult;
Tagir Valeev's solution is as below which I tweaked at receiving end:
Map<Object, Object> topEmployees = empList.stream()
.collect(Collectors.groupingBy(e -> e.department,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingDouble(e -> e.salary)), Optional::get
)
)
);
The extra code I wrote is as below:
for(Map.Entry<Object, Object> token : topEmployees.entrySet()) {
finalResult.put((String) token.getKey() , (Employee) token.getValue());
}
Hope this helps someone. Thanks.
Map<String, Optional> result = li.stream().collect(Collectors.groupingBy(Emp::getDept,
Collectors.reducing(BinaryOperator.maxBy(Comparator.comparing(Emp::getSalary)))
));
max salary in each department java 8
import java.util.*;
import java.util.stream.Collectors;
Map<String, Employee> stringEmployeeMap= employeeList.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.maxBy(
Comparator.comparingDouble(Employee::getSalary)
), Optional::get)
)
);
import static java.util.Comparator.comparingDouble;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.maxBy;
empList.stream().collect(
groupingBy(e -> e.department,
collectingAndThen(maxBy(comparingDouble(e -> e.salary)),
Optional::get))).
forEach((k, v) -> System.out.println(k + ":" + v));
package com.example.demo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.el.stream.Optional;
import org.springframework.expression.spel.ast.OpAnd;
import org.springframework.util.comparator.Comparators;
public class Java8Test {
public static void main(String[] args) {
int[] arr = new int[] {3,98,4,1,3,5,7,9,8,7,6,55,44,33,22,34};
Arrays.stream(arr).filter(x->x%2==0).forEach(action -> {
System.out.println("Array event number : " +action);
});
List<Employee> empList = new ArrayList<Employee>();
empList.add(new Employee(101, "siva", 101, "active", 2000));
empList.add(new Employee(102, "ready", 101, "active", 5000));
empList.add(new Employee(103, "raju", 102, "inactive", 6000));
empList.add(new Employee(104, "sunder", 102, "inaactive", 4000));
empList.add(new Employee(105, "sunil", 103, "active", 3500));
empList.add(new Employee(106, "sunath", 103, "inactive", 4200));
empList.add(new Employee(107, "suresh", 104, "active", 2050));
Map<Integer, java.util.Optional<Employee>> mapMaxSalByDept= empList.stream().collect(Collectors.groupingBy(
Employee::getEmpDepId, Collectors.reducing(BinaryOperator.maxBy(Comparator.comparing(Employee::getEmpSalary)))));
mapMaxSalByDept.entrySet().forEach(action-> {
System.out.println("DEMP Id : " + action.getKey()+ "Empl Details : "+ action.getValue());
});
//nth max salary n=3
Stream<Employee> mapMaxSalByDept1Nth= empList.stream().sorted(Comparator.comparing(Employee:: getEmpSalary).reversed()).limit(3).skip(2);
mapMaxSalByDept1Nth.forEach(action-> {
System.out.println("Empl Details : "+ action);
});
}
}
package com.example.demo;
public class Employee {
private int empId;
private String empName;
private int empDepId;
private String status="active";
private int empSalary;
public Employee(int empId, String empName, int empDepId, String status, int empSalary) {
super();
this.empId = empId;
this.empName = empName;
this.empDepId = empDepId;
this.status = status;
this.empSalary = empSalary;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + empDepId;
result = prime * result + empId;
result = prime * result + ((empName == null) ? 0 : empName.hashCode());
result = prime * result + empSalary;
result = prime * result + ((status == null) ? 0 : status.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (empDepId != other.empDepId)
return false;
if (empId != other.empId)
return false;
if (empName == null) {
if (other.empName != null)
return false;
} else if (!empName.equals(other.empName))
return false;
if (empSalary != other.empSalary)
return false;
if (status == null) {
if (other.status != null)
return false;
} else if (!status.equals(other.status))
return false;
return true;
}
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public int getEmpDepId() {
return empDepId;
}
public void setEmpDepId(int empDepId) {
this.empDepId = empDepId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public int getEmpSalary() {
return empSalary;
}
public void setEmpSalary(int empSalary) {
this.empSalary = empSalary;
}
#Override
public String toString() {
return "Employee [empId=" + empId + ", empName=" + empName + ", empDepId=" + empDepId + ", status=" + status
+ ", empSalary=" + empSalary + "]";
}
}
List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee("Mayur", "IT", "100", 1000));
employeeList.add(new Employee("Raj", "IT", "101", 2000));
employeeList.add(new Employee("Anshul", "IT", "102", 3000));
employeeList.add(new Employee("Hari", "EC", "102", 3000));
employeeList.add(new Employee("Ram", "EC", "102", 3000));
Map<String, Optional<Employee>> map = employeeList.stream().collect(Collectors.groupingBy(Employee::getDepartment, Collectors.maxBy(Comparator.comparingInt(Employee::getSalary))));
map.entrySet().forEach(System.out::println);
Output :
IT=Optional[Employee{name='Anshul', department='IT', employeeID='102', salary=3000}]
EC=Optional[Employee{name='Hari', department='EC', employeeID='102', salary=30000}]
I looking for solution how to make List (or something) with unique Product. The reason i want to do it is total price of Product. Each sets can contains same Product.
Here's my classes.
public class Product {
public String name; // unique name
public double price;
public double qty;
}
&
public class Sets {
public Product item1;
public Product item2;
...
public Product item7;
public static listsProduct<Product> = new Arraylists<Product>();
}
I'm trying to make a Lists but i don't know how to add a unique product. To add Product i use reflection.
My method:
public void getProducts() throws NoSuchMethodException, Exception {
Sets object = this;
Class clazz = object.getClass();
Field[] fields = clazz.getFields();
Method m1 = Product.class.getMethod("getname", null);
for (Field field : fields) {
if(field.get(object)!=null) {
System.out.println(field.getName()+" = " + m1.invoke(field.get(object),null));
Product e=(Product) field.get(object);
if (listsProduct==null ) listsProduct.add((Produkt) field.get(object));
if (!(listsProduct.contains(field.get(object)))) listsProduct.add(e);
}
}
It's adding a Product correctly but how make UNIQUE lists?
Thanks in advance for any help !
EDIT :
So... What do u want achieve?
eg.
sets :
1) butter, milk, peanut
2) goodie, butter, xxx
3) milk, peanut, xxx
result:
List of unique product
butter
milk
peanut
xxx
goodie
if product exist on lists sum price
You do not need to use the Reflection API for things like this!
One way to get a list of unique products would be to use Map::merge with the Product name as key and the Product::merge method from this example as the remapping function.
Given a class Product...
public class Product {
String name;
double price;
int qty;
public Product(String name, double price, int qty) {
this.name = name; this.price = price; this.qty = qty;
}
public Product merge(Product p) {
price += p.price;
qty += p.qty;
return this;
}
#Override
public String toString() {
return String.format("%s x %d (%.2f)", name, qty, price);
}
}
And a List of Sets of Products:
List<Set<Product>> sets = asList(
new HashSet<>(asList(new Product("cheese", 5, 1), new Product("milk", 3, 3))),
new HashSet<>(asList(new Product("peanut", 1, 1), new Product("bread", 2, 1))),
new HashSet<>(asList(new Product("cheese", 5, 1), new Product("peanut", 1, 1)))
);
A Collection of unique Products can be created like this:
private static Collection<Product> listProducts(List<Set<Product>> sets) {
Map<String, Product> uniques = new HashMap<>();
for(Set<Product> set : sets)
for(Product p : set)
uniques.merge(p.name, p, (a, b) -> a.merge(b));
return uniques.values();
}
Full working example:
import static java.util.Arrays.*;
import java.util.*;
public class UniqProd {
public static class Product {
String name;
double price;
int qty;
public Product(String name, double price, int qty) {
this.name = name;
this.price = price;
this.qty = qty;
}
public Product merge(Product p) {
price += p.price;
qty += p.qty;
return this;
}
#Override
public String toString() {
return String.format("%s x %d (%.2f)", name, qty, price);
}
}
public static void main(String[] args) {
List<Set<Product>> sets = asList(
new HashSet<>(asList(new Product("cheese", 5, 1), new Product("milk", 3, 3))),
new HashSet<>(asList(new Product("peanut", 1, 1), new Product("bread", 2, 1))),
new HashSet<>(asList(new Product("cheese", 5, 1), new Product("peanut", 1, 1)))
);
listProducts(sets).forEach(System.out::println);
}
private static Collection<Product> listProducts(List<Set<Product>> sets) {
Map<String, Product> uniques = new HashMap<>();
for (Set<Product> set : sets)
for (Product p : set)
uniques.merge(p.name, p, (a, b) -> a.merge(b));
return uniques.values();
}
}
If you, for some reason, must/want to use your Sets class instead of a java.util.Set, construct a List of the products in your Sets instance before iterating it:
private static Collection<Product> listProducts(List<Sets> sets) {
Map<String, Product> uniques = new HashMap<>();
for (Sets set : sets)
for (Product p : asList(set.item1, set.item2, set.item3))
uniques.merge(p.name, p, (a, b) -> a.merge(b));
return uniques.values();
}
I still didn't get why you want to use reflection , I am giving below answer to solve your problem of how to store unique products ?
If you want to add unique products . you must define that what makes the product Unique? Lets assume that product name will help you identifying whether they are not-equal/unique.
To define it let's write equals &hashcode method in product class
public class Product {
public String name; // unique name
public double price;
public double qty;
public boolean equals(Object obj){
if (obj ==null || ! obj instanceOf Prpduct) return false;
else return ((Product) obj).name.equals(name);// also write a null check conditions for name field.
}
public interest hashCode(){
return name.hashCode();
}
}
Now change your data structure from List to Set
which will store only distinct Products , like below
`
public class Sets {
public Product item1;
public Product item2;
...
public Product item7;
public static Set<Product> productSet= new HashSet<Product>();
}
`
With this whenever you will add productSet only unique would get stored.
I am trying to compile this program. It works perfectly for 2 Strings(Name, phone number) But not for 3 Strings (Name, phone number and sex).
CODE (Not working code - 3 Strings (Name, phone number and sex))
import java.util.Map;
import java.util.TreeMap;
public class Ann {
String name, phone;
public Ann() {
}
public static void testMap() {
Map<String, String, String> theMap = new TreeMap<String, String,String>();
// new HashMap<K,V>(); could also be used
theMap.put("Roger M", "090-997-2918", "Male");
theMap.put("Jane M", "090-997-1987", "FeMale");
theMap.put("Stacy K", "090-997-9188", "FeMale");
theMap.put("Gary G", "201-119-8765", "Male");
theMap.put("Jane M", "090-233-0000", "FeMale");
System.out.println("Testing TreeMap and Map");
System.out.print("Stacy K has phone ");
System.out.print(theMap.get("Stacy K"));
System.out.print("\n");
System.out.print("Jane M has phone ");
System.out.print(theMap.get("Jane M"));
} // testMap()
public static void main(String[] args) {
testMap();
}
}
ERROR
wrong number of type arguments; required 2
wrong number of type arguments; required 2
WORKING CODE (For 2 Strings (Name, phonenumber))
import java.util.Map;
import java.util.TreeMap;
public class Ann {
String name, phone;
public Ann() {
}
public static void testMap() {
Map<String, String> theMap = new TreeMap<String, String>();
// new HashMap<K,V>(); could also be used
theMap.put("Roger M", "090-997-2918");
theMap.put("Jane M", "090-997-1987");
theMap.put("Stacy K", "090-997-9188");
theMap.put("Gary G", "201-119-8765");
theMap.put("Jane M", "090-233-0000");
System.out.println("Testing TreeMap and Map");
System.out.print("Stacy K has phone ");
System.out.print(theMap.get("Stacy K"));
System.out.print("\n");
System.out.print("Jane M has phone ");
System.out.print(theMap.get("Jane M"));
} // testMap()
public static void main(String[] args) {
testMap();
}
}
I want the code to work for about 5 attributes like name , phone, sex,age,address. If someone can help me compile the code at the top of the question, I can figure out the rest.
Thanks
You can't just add type parameters arbitrarily to generic types - they are defined with a certain number, and have to use that many (disregarding raw types). The type parameters have specific meanings for the implementation - how would the HashMap class know what you wanted to get out if you called map.get(name)?
You should encapsulate all the properties into a class (e.g. Person or Contact) and then create a Map<String, Person> to map from the name to the person. For example:
public enum Gender
{
FEMALE, MALE;
}
public final class Person
{
private final String name;
private final Gender gender;
private final Date dateOfBirth;
private final String address;
private final String telephone;
public Person(String name, Gender gender, Date dateOfBirth,
String address, String telephone)
{
// You probably want to put some validation in here
this.name = name;
this.gender = gender;
this.dateOfBirth = dateOfBirth;
this.address = address;
this.telephone = telephone;
}
public String getName()
{
return name;
}
// etc for the other properties
}
...
Map<String, Person> map = new HashMap<String, Person>();
Person jon = new Person("Jon", Gender.MALE, /* etc */);
map.put("Jon", jon);