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
Related
I'm trying to learn aggregate functions and lambdas in Java. I have a class:
public class Person {
public enum Privilege{
PRIV1, PRIV2, PRIV3, PRIV4, PRIV4
}
private String name;
private Set<Privilege> privileges;
...
}
and a list of objects of this class.
I want to convert it to EnumMap<Privilege, List<String>>
where List contains names of all people having certain privilege. I created a method to do this:
public static Map<Privilege,List<String>> personsByPrivilege(List<Person> personList){
Map<Privilege, List<String>> resultMap = new EnumMap(Privilege.class);
Arrays.asList(Privilege.values())
.stream()
.forEach(p->resultMap.put(p,new ArrayList<String>()));
for(Person p :personList){
Set<Privilege> personsPrivileges = p.getPrivileges();
for(Privilege pr : personsPrivileges){
resultMap.get(pr).add(p.getName());
}
}
return resultMap;
}
How do I do it using aggregate functions?
I mean e.g. personlist.stream().collect style
You could flatten the list of person -> list of privileges into pairs, then groupby by the privilege, and map with the name
public static Map<Person.Privilege, List<String>> personsByPrivilegeB(List<Person> personList) {
return personList.stream()
.flatMap(pers -> pers.getPrivileges().stream().map(priv -> new Object[]{priv, pers.getName()}))
.collect(groupingBy(o -> (Person.Privilege) o[0], mapping(e -> (String) e[0], toList())));
}
You can add a Pair class and use the below code for achieving your goal
return personList.stream().flatMap(p -> {
String name = p.getName();
return p.getPrivileges().stream()
.flatMap(priv -> Stream.of(new NamePriviledge(priv, name)));
}).collect(Collectors.groupingBy(NamePriviledge::getPrivilege, Collectors.mapping(NamePriviledge::getName, Collectors.toList())));
}
class NamePriviledge {
private final Person.Privilege privilege;
private final String name;
public NamePriviledge(Person.Privilege privilege, String name) {
this.privilege = privilege;
this.name = name;
}
public String getName() {
return name;
}
public Person.Privilege getPrivilege() {
return privilege;
}
#Override
public String toString() {
return "NamePriviledge{" +
"privilege=" + privilege +
", name='" + name + '\'' +
'}';
}
}
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 have wrote the following code sample with java stream reduce:
Person reducedPerson = Person.getPersons().stream()
.parallel() //will return surprising result
.reduce(new Person(), (intermediateResult, p2) -> {
intermediateResult.setAge(intermediateResult.getAge() + p2.getAge());
return intermediateResult;
},
(ir1, ir2) -> {
ir1.setAge(ir1.getAge() + ir2.getAge());
return ir1;
});
System.out.println(reducedPerson);
model:
public class Person {
String name;
Integer age;
public Person() {
age = 0;
name = "default";
}
//...
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public static Collection<Person> getPersons() {
List<Person> persons = new ArrayList<>();
persons.add(new Person("Vasya", 12));
persons.add(new Person("Petya", 32));
persons.add(new Person("Serj", 10));
persons.add(new Person("Onotole", 18));
return persons;
}
}
Each code sample execution returns different result:
example:
Person{name='default', age=256}
or
Person{name='default', age=248}
I have loclized that problem inside combiner because in sequental stream code executes correctly.
Please help to correct combiner.
P.S.
expected result: person with name 'default' and age 72(sum ges of all pepsons in list)
P.S.
same code for Integer as reduce result works properly:
Integer age = Person.getPersons().stream()
.parallel()
.reduce(0, (intermediateResult, p2) -> {
intermediateResult = intermediateResult + p2.getAge();
return intermediateResult;
}, (ir1, ir2) -> {
System.out.println("combiner");
ir1 = ir1 + ir2;
return ir1;
});
System.out.println(age);
To perform mutable reduction, use collect:
reducedPerson = Person.getPersons().parallelStream()
.collect(
Person::new,
(p, q) -> p.setAge(p.getAge() + q.getAge()),
(p, q) -> p.setAge(p.getAge() + q.getAge())
);
collect is specifically designed to accumulate into mutable containers safely even in parallel.
As Boris noted, the problem is mutation within a stream.
Most stream operations accept parameters that describe user-specified
behavior, such as the lambda expression w -> w.getWeight() passed to
mapToInt in the example above. To preserve correct behavior, these
behavioral parameters:
must be non-interfering (they do not modify the stream source); and in
most cases must be stateless (their result should not depend on any
state that might change during execution of the stream pipeline).
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
Here is version using reduce, and the more straightforward version using maptoint and sum.
class gstackoverflow{
public static void main(String... args) {
Person reducedPerson = Person.getPersons().stream()
.parallel() //will NOT return surprising result
.reduce(new Person("default",0),
(ir1, ir2) -> //no longer mutates
new Person(String.join(",", ir1.getName(), ir2.getName()), ir1.getAge() + ir2.getAge())
);
System.out.println(reducedPerson);
//here is a clean(er) way to do it:
int totalAge = Person.getPersons().stream()
.parallel() //will NOT return surprising result
.mapToInt(Person::getAge)
.sum();
System.out.println(totalAge);
}
}
class Person {//no longer mutable
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
final String name;
final Integer age;
//no args constructor removed
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public static Collection<Person> getPersons() {
List<Person> persons = new ArrayList<>();
persons.add(new Person("Vasya", 12));
persons.add(new Person("Petya", 32));
persons.add(new Person("Serj", 10));
persons.add(new Person("Onotole", 18));
return persons;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Person{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class clsWarehouse {
public static void main(String[] args){
class Items {
private String iItemID;
private String strItemName;
private String strItemDescription;
private String iItemPrice;
private String strSize;
private String strSex;
public Items (String id, String name, String description, String price, String size, String sex){
iItemID = id;
strItemName = name;
strItemDescription = description;
iItemPrice = price;
strSize = size;
strSex = sex;
}
}
Map<Integer, Items> ItemMap = new HashMap<Integer, Items>();
ItemMap.put(1, new Items("3", "test1", "test2", "30", "20", "Male"));
for (Map.Entry<Integer, Items> entry : ItemMap.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
Items test = entry.getValue();
String test2 = test.toString();
System.out.println(test2);
}
}
}
Hello,
I want to get the value stored in the map but i get only the memory locations instead, Key = 1, Value = clsWarehouse$1Items#c3c749. Can someone help me with this.
Thanks!
Override the toString() method in your Items class so that when you call it, you control the output. The default implementation in Object produces the behavior you are observing (from the linked Javadocs)
getClass().getName() + '#' + Integer.toHexString(hashCode())
The hashCode() method, by default, uses the memory address.
add the getters that you need in your Items class
Ex (for ItemID) :
public String getItemID()
{
return iItemID;
}
and in your for loop
Items test = entry.getValue();
System.out.println(test.getItemID());
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);