How to convert List to Map - Key of Map should be a combination of multiple keys - java-8

How to convert List to Map - Key of Map should be a combination of multiple keys
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
public class Student {
private long id;
private String firstName;
private String lastName;
private String street;
private String city;
public static void main(String[] args) {
List<Student> students = Arrays.asList(
Student.builder().id(1).firstName("John").lastName("Doe").build(),
Student.builder().id(1).firstName("Jane").lastName("Doe").build(),
Student.builder().id(1).firstName("Mike").lastName("Doe").build(),
Student.builder().id(1).firstName("Jack").lastName("Doe").build()
);
LinkedHashMap<Long, String> collect = students.stream()
.collect(Collectors.toMap(
Student::getId, Student::getFirstName, (x, y) -> x + ", " + y, LinkedHashMap::new));
System.out.println(collect);
// Answer I am expecting is Ex: {1johnDoe=[id=1,firstName=John, lastName=Doe]}
}
}

Using Java 17
I have implemented the below code using some of the latest java features apart from Java 8.
Records in java 14 : As of JDK 14, we can replace our data classes with records. Records are immutable classes that require only the type and name of fields. We do not need to create constructor, getters, setters, override toString() methods, override hashcode and equals methods.
List.of() in java 9: It is a static method that returns the immutable list of elements passed as arguments. Here in the below scenario, we will get the list of four student objects.
public class Test {
public static void main(String[] args) {
record Student(long id, String firstName, String lastName,String street, String city){}
Student s1 = new Student(1,"F1","L1","S1","C1");
Student s2 = new Student(2,"F2","L2","S2","C2");
Student s3 = new Student(3,"F3","L3","S3","C3");
Student s4 = new Student(4,"F4","L4","S4","C4");
Map<String,Student> output =
List.of(s1,s2,s3,s4).stream().collect(Collectors.toMap(x -> x.id() + x.firstName(),
Function.identity(), (k, v) -> k, LinkedHashMap::new));
System.out.println(output);
}
}
Output:
{1F1=Student[id=1, firstName=F1, lastName=L1, street=S1, city=C1], 2F2=Student[id=2, firstName=F2, lastName=L2, street=S2, city=C2], 3F3=Student[id=3, firstName=F3, lastName=L3, street=S3, city=C3], 4F4=Student[id=4, firstName=F4, lastName=L4, street=S4, city=C4]}

You can do as follows
LinkedHashMap<String, Student> collect = students.stream()
.collect(
Collectors.toMap(
student->(String.join("", Long.toString(student.getId()),student.getFirstName(),student.getLastName())),
student->student,
(v1,v2)->v1,
LinkedHashMap::new));
collect.forEach((k,v)->System.out.println(k+"="+v));
I think, you want key as String and value as Student into map

Related

Convert list of Object to Map<Key, List<Object>> with java 8 stream [duplicate]

This question already has answers here:
How to convert List<V> into Map<K, List<V>>, with Java 8 streams and custom List and Map suppliers?
(3 answers)
Closed 2 years ago.
So I have a list of users with several information including company uid, this value can be found in several users, as it defines to what company the user belongs to. I need to create a Map of userList where the map key is company uid. I have managed to do so using this code:
HashMap<String, List<LDAPUser>> allUsers = new HashMap<>();
userService.findAllUsers().forEach(u -> {
Optional.ofNullable(allUsers.putIfAbsent(u.getCompanyUid(),
new ArrayList<>(Collections.singletonList(u))))
.ifPresent(list -> list.add(u));
});
Even if it works fine, I think there must be a cleaner approach using flatMap, map or collect method from stream, but I can't get it to work basically because I don't see how I can create a list containing all users.
There's a collector to group things, consider the following example
Application.java
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Application {
private static class User {
private final String name;
private final Integer companyId;
public User(String name, Integer companyId) {
this.name = name;
this.companyId = companyId;
}
public String getName() {
return name;
}
public Integer getCompanyId() {
return companyId;
}
#Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", companyId=" + companyId +
'}';
}
}
public static void main(String[] args) {
final List<User> users = Arrays.asList(new User("A", 1), new User("B", 1), new User("C", 2));
final Map<Integer, List<User>> byCompanyId = users.stream()
.collect(Collectors.groupingBy(User::getCompanyId));
System.out.println(byCompanyId);
}
}
that will print
{1=[User{name='A', companyId=1}, User{name='B', companyId=1}], 2=[User{name='C', companyId=2}]}

Java stream : convert list of one object to other

I am trying to learn map function in Stream
public class EmployeeInformationTest {
public static void main(String args[]) {
List<Employee> employees = Arrays.asList(
new Employee("Jai"),
new Employee("Adithya"),
new Employee("Raja"));
List<String> names = employees.stream()
.map(s -> s.getEmployeeName()) // Lambda Expression
.collect(Collectors.toList());
System.out.println(names);
}
}
we have above code and somehow it is giving us List of String from List of Employee. Say, we have other class Person in which we have field as name
public class Person {
private String name;
}
so is it feasible via map or some other function in stream so that I can get the List of Person rather than List of String in above code
sure thing, just change the map function to:
.map(s -> new Person(s.getEmployeeName()))
or if there is no such constructor:
.map(s -> {
Person p = new Person();
p.setName(s.getEmployeeName());
return p;
})

Iterate through elements of List - Java 8

I have a List of String, i need to iterate elements and create a new Object for each element in the list and add to a parent list, how do ido in Java 8 , this is what i tried so far:
List<CustomObject> parentList = new ArrayList<>();
List<String> emailList = fromSomeMethod();
emailList().stream().forEach(email -> parentList.add(new CustomObject(email)));
I am getting an error:
"variable used in lambda expression should be final or effectively final"
Any suggestions ? dont want to do it in the old school way,
Thanks,
List<CustomObject> parentList = emailList().stream()
.map(CustomObject::new)
.collect(Collectors.toList());
No need to complicated things, just map that and collect to a new List
Try like this You should have Parameterized Constructor
public class CustomObject {
private String email;
private boolean isFlag;
//Getter setter
public CustomObject(String email, boolean isFlag) {
this.email = email;
this.isFlag = isFlag;
}
public CustomObject(String email) {
this.email = email;
}
}
List<CustomObject> parentList = emailList.stream().map(CustomObject::new).collect(Collectors.toList());
Use this:
static class CustomObject {
String email;
public CustomObject(String email) {
this.email = email;
}
}
private static void test4() {
List<CustomObject> parentList = new ArrayList<>();
List<String> emailList = Arrays.asList("aa#gmail.com", "bb#yahoo.com");
emailList.stream()
.map(CustomObject::new)
.forEach(parentList::add);
}

How can I get multiple properties from a Java POJO using the Java 8 Stream API?

Given this class written in the Java 8 style, I wanted to see if I dont need to call the stream api twice :
import java.util.*;
public class Foo {
public static void main(String... args) {
List<Person> persons = new ArrayList<>();
init(persons, Person::new, "John", "Doe");
persons.stream()
.map(Person::getFirstName)
.forEach(System.out::println);
persons.stream()
.map(Person::getLastName)
.forEach(System.out::println);
}
#FunctionalInterface
interface PersonFactory {
Person create(String firstName, String lastName);
}
private static void init(List<Person> persons, PersonFactory factory, String fn, String ln) {
persons.add(factory.create(fn, ln));
}
}
class Person {
private final String firstName;
private final String lastName;
public Person(String fName, String lName) {
this.firstName = fName;
this.lastName = lName;
}
public String getFirstName() {return this.firstName;}
public String getLastName() {return this.lastName;}
}
I wanted to see if I could instead stream the "persons" List in one go.
Any suggestions ?
If you don't need to transform object to another, you can try this
persons.forEach(i -> System.out.println(i.getFirstName() + " " + i.getLastName()));
i think it could be helpfull for you using Map
Map<String, String> mapp = persons.stream().collect(HashMap::new,
(m, c) ->{
m.put(c.getFirstname(), "");
m.put(c.getLastname(), "");
},HashMap::putAll);
System.out.println(mapp.keySet().toString());

reduce two group in java streaming api

i want to group the item element according to date such that , if item object's date is greater than present date then , it will fall into futuredate group and if date is less than present date then it will fall into pastdate
and then i want to reduce on these group so that i can get aggregate qty,price of these group, further aggregate value returned from pastdate group shold sit in oldPrice and oldQty of object
public class Item {
private String name;
private int qty;
private int oldQty;
private BigDecimal price;
private BigDecimal oldPrice;
Private Date date;
//constructors, getter/setters
}
Map<String, List<Item>> groupByP =
items.stream().collect(Collectors.groupingBy((row)->{
//logic to seperate items into two group based on date
}));
How to proceed after this
There are two ways to this, the first is first partition and later compute:
(this partitions, then return a Pair that holds the sum of all quantities and an average of the price)
Map<Boolean, List<Item>> partioned = items.stream()
.collect(Collectors.partitioningBy(item -> item.getDate().compareTo(now) > 0));
partioned.get(Boolean.FALSE).stream()
.map(item -> new AbstractMap.SimpleEntry<>(item.getOldQty(), item.getOldPrice()))
.reduce((entry1, entry2) -> {
int sum = entry1.getKey() + entry2.getKey();
BigDecimal averagePrice = entry1.getValue().add(entry2.getValue()).divide(BigDecimal.valueOf(2));
return new AbstractMap.SimpleEntry<>(sum, averagePrice);
}).get();
The second us writing a custom collector, but I doubt it will be more clear.
If you want to split items into to groups you need to use partitioningBy collector.
#AllArgsConstructor
#Getter
public static class Item {
private String name;
private int qty;
private int oldQty;
private BigDecimal price;
private BigDecimal oldPrice;
private Date date;
}
public static void main(String [] args) {
List<Item> items = Lists.newArrayList();
Map<Boolean, List<Item>> partitionedItems = items.stream()
.collect(Collectors.partitioningBy(item -> item.getDate().after(new Date())));
}
In this case you'll get
Map<Boolean, List<Item>>
Now you can:
map.get(true)
returns a list of items that match the condition
map.get(false)
returns a list of items that don't match the condition
Having those two lists you can make any reduction you want.

Resources