Java stream : convert list of one object to other - java-8

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;
})

Related

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

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

Create Mono with Map object present within another Mono object

I am new to reactive and not able to get around this.
I have following Dtos:
public class User {
int id;
Map<String, Car> carsMap;
}
public class Car {
String carName;
}
// Response object
public class VehiclesInfo {
List<String> vehicleName;
}
From database I am getting Mono<User> when querying by userId.
And I have to return Mono<VehiclesInfo>.
So, I have to map the carsMap received from Mono<User> into List i.e. List of carName and set that into VehiclesInfo and return that as Mono i.e. Mono<VehiclesInfo>.
I am doing it like below. Please let me know how this can be done without blocking.
// userMono is returned by database query
Mono<User> userMono = getUserInfoById(userId);
Optional<User> userOptional = userMono.blockOptional();
if (userOptional.isPresent()) {
User user1 = userOptional.get();
Flux<Car> carFlux = Flux.fromIterable(user1.getCarsMap().keySet())
.flatMap(i -> {
final Car c = new Car();
c.setCarName(i);
return Mono.just(c);
});
carFlux.subscribe(c -> System.out.println(c.getCarName()));
}

How to get parent POJO class field value using streams

I have two classes - Student and StudentDetails.
My goal is to get data in the form of Map <studentName,Map<subjectName, subjectNo>>.
When using streams, not able to fetch data of parent class(Student).
public class Student {
private String studentName;
private Map<String, Long> studentDetails;
public Map<String, Long> getStudentDetails() {
return studentDetails;
}
public String getStudentName() {
return studentName;
}
}
class StudentDetails {
private String subjectName;
private String subjectNo;
public String getFSubjectName() {
return subjectName;
}
public String getSubjectNo() {
return subjectNo;
}
}
public class Main {
public static void main(String[] args) {
Collection<Student> student = null;
Map<String, Map<String, Long>> sts = student.stream()
.map(st -> st.getStudentDetails().values())
.flatMap(st -> st.stream())
.collect(Collectors.groupingBy(Student::getStudentName,
Collectors.toMap(StudentDetails :: getFatherName, StudentDetails:: getRollNo )));
}
}
Error : The type Student does not define getStudentName(T) that is applicable here.
You "lost" your Student while doing:
.map(st -> st.getStudentDetails().values())
.flatMap(st -> st.stream()) // this is a Stream<StudentDetails> now
Thus if you need it just map it:
.flatMap(st -> st.getStudentDetails()
.values()
.stream()
.map(sd -> new SimpleEntry<>(st, sd)))
.collect(Collectors.groupingBy(
en -> en.getKey().getStudentName(),
Collectors.toMap(
en -> en.getValue().getFartherName(),
en -> en.getValue().getRollNo()
)
))
This can be done without Streams too; you could try to look at Map::compute

Java 8 collectors groupBy and into a separate class

I have a list of Persons which have some duplicate names.
class Person {
String name;
}
I want to convert it to the list of GroupedPersons which contain the common name and the list of all Persons who have that name.
class GroupedPerson {
String name;
List<A> as;
}
Is it possible to do this with one collector and without any intermediate mapping or extra classes?
I suppose one way would be:
persons
.stream()
.collect(Collectors.collectingAndThen(Collectors.groupingBy(
Person::getName),
map -> {
return map.entrySet()
.stream()
.map(en -> new GroupedPerson(en.getKey(), en.getValue()))
.collect(Collectors.toList());
}));
Or, you could use toMap:
persons
.stream()
.collect(Collectors.toMap(
Person::getName,
x -> {
List<Person> l = new ArrayList<>();
l.add(x);
return new GroupedPerson(x.getName(), l);
},
(left, right) -> {
left.getAs().addAll(right.getAs());
return left;
}))
.values();
Yes, you can do that. Here is a way to do it:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class GroupedStream {
public static void main(String[] args) {
List<GroupedPerson> groupedPersons = Arrays.asList("john harry john harry sam jordon bill steve bill".split(" "))
.stream()
.map(name -> new Person(name))
.map(person -> new Object[] {person.name, new ArrayList<>()})
.collect(Collectors.toMap(tuple-> (String) tuple[0], tuple -> (List<Person>) tuple[1] ))
.entrySet()
.stream()
.map(entry -> new GroupedPerson(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
}
static class Person {
public final String name;
public Person(String name) {
super();
this.name = name;
}
}
static class GroupedPerson {
public final String name;
public final List<Person> person;
public GroupedPerson(String name, List<Person> person) {
this.name = name;
this.person = person;
}
}
}
if you modify your GroupedPerson model to be something along the lines of:
public class GroupedPerson {
private String name;
private List<Person> people;
public GroupedPerson(String name) {
this.name = name;
people = new ArrayList<>();
}
public void addPeople(List<Person> people) {
this.people.addAll(people);
}
public void addPerson(Person person){
people.add(person);
}
public List<Person> getPeople(){
return Collections.unmodifiableList(people);
}
public String getName() {
return name;
}
}
Then you can have a Collection of GroupedPerson objects with the names and all the corresponding people with that specific name like this:
Collection<GroupedPerson> resultSet = peopleList
.stream()
.collect(Collectors.toMap(Person::getName,
p -> {
GroupedPerson groupedPerson = new GroupedPerson(p.getName());
groupedPerson.addPerson(p);
return groupedPerson;
},
(p, p1) -> {
p.addPeople(p1.getPeople());
return p;
}
)).values();
if for some reason you don't want the receiver type to be Collection<T> then you can convert to a specific collection type if deemed appropriate by simply doing.
List<GroupedPerson> result = new ArrayList<>(resultSet);

Get list of properties from collection Into unified list using Guava

My company requires that we use Guava's functions & predicates whenever we loop through a collection of objects. While I don't necessarily agree that it should be this way, it's my companies standard so please don't give me comments about how it's not efficient or things like that :)
I need, using Guava in Java 1.7 specifically, to find a way to take a collection of objects, and get all of the inner fields into a unified list. This is as far as I've gotten but I have not been able to get inner fields:
public static final Function<Group, String> GROUP_TO_GROUP_NAME =
new Function<Group, String>()
{
#Override
public String apply(Group group)
{
return group.getGroupName();
}
};
public static List<String> transformToGroupNames(List<GroupCollection> groupCollections)
{
List<Group> groups = new ArrayList<>();
for (GroupCollection groupCollection: groupCollections)
{
groups.addAll(groupCollection.getGroups());
}
return FluentIterable.from(groups)
.transform(GROUP_TO_GROUP_NAME)
.filter(Predicates.notNull())
.toList();
}
My goal is to replace the loop through the groupCollections
To flatten the group collections to a single group collection you can use FluentIterable.transformAndConcat(Function):
public static final Function<GroupCollection, List<Group>> GROUP_COLLECTIONS_TO_GROUP_COLLECTION =
new Function<GroupCollection, List<Group>>() {
#Override
public List<Group> apply(GroupCollection input) {
return input.getGroups();
}
};
public static final Function<Group, String> GROUP_TO_GROUP_NAME =
new Function<Group, String>() {
#Override
public String apply(Group group) {
return group.getGroupName();
}
};
public static List<String> transformToGroupNames(List<GroupCollection> groupCollections) {
return FluentIterable.from(groupCollections)
.transformAndConcat(GROUP_COLLECTIONS_TO_GROUP_COLLECTION)
.transform(GROUP_TO_GROUP_NAME)
.filter(Predicates.notNull())
.toList();
}

Resources