Java 8 collectors groupBy and into a separate class - java-8

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

Related

Returning Optional<Student> or Optional.empty()

I'm playing a little bit with Optional to understand how it works. Let's say I have this class:
public class Student {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and I want to return a Student by Id or Optional.empty if it doesn't find it. This is what I have so far:
public class Main {
static List<Student> students = new ArrayList<>();
public static void main(String[] args) {
students.add(new Student(1, "name1"));
students.add(new Student(2, "name2"));
students.add(new Student(3, "name3"));
System.out.println(getStudentById(1).get().getName());
}
public static Optional<Student> getStudentById(int id) {
return students
.stream()
.filter( s -> s.getId() == id)
.findFirst();
}
}
That works but I wanted to add this line:
.findFirst()
.orElse(Optional.empty());
and I got this:
Error:(23, 39) java: incompatible types: no instance(s) of type variable(s) T exist so that java.util.Optional conforms to com.company.Student
Also I'd like to know if that is the correct way to go over a list, I mean element by element or there is something better?
If you read javadocs of Stream#findFirst() you will find that you already have what you need:
Returns an Optional describing the first element of this stream, or an
empty Optional if the stream is empty. If the stream has no encounter
order, then any element may be returned.
So just do
return students
.stream()
.filter( s -> s.getId() == id)
.findFirst();

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

HATEOAS on Spring Flux/Mono response

I've been using Spring HATEOAS following the guidelines:
https://spring.io/guides/gs/rest-hateoas/#initial
package hello;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
#RestController
public class GreetingController {
private static final String TEMPLATE = "Hello, %s!";
#RequestMapping("/greeting")
public HttpEntity<Greeting> greeting(#RequestParam(value = "name", required = false, defaultValue = "World") String name) {
Greeting greeting = new Greeting(String.format(TEMPLATE, name));
greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());
return new ResponseEntity<Greeting>(greeting, HttpStatus.OK);
}
}
Now I want to use a repository and output a Flux/Mono response:
#RestController
class PersonController {
private final PersonRepository people;
public PersonController(PersonRepository people) {
this.people = people;
}
#GetMapping("/people")
Flux<String> namesByLastname(#RequestParam Mono<String> lastname) {
Flux<Person> result = repository.findByLastname(lastname);
return result.map(it -> it.getFullName());
}
}
How can I use Spring HATEOAS on a Flux/Mono response? Is it possible at all?
Update as there is support to use HATEOAS with Spring Web Flux.
public class Person extends ResourceSupport
{
public Person(Long uid, String name, String age) {
this.uid = uid;
this.name = name;
this.age = age;
}
private Long uid;
private String name;
private String age;
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
Using the above Person in Controller as follows
#GetMapping("/all")
public Flux getAllPerson() {
Flux<List<Person>> data = Flux.just(persons);
return data.map(x -> mapPersonsRes(x));
}
private List<Resource<Person>> mapPersonsRes(List<Person> persons) {
List<Resource<Person>> resources = persons.stream()
.map(x -> new Resource<>(x,
linkTo(methodOn(PersonController.class).getPerson(x.getUid())).withSelfRel(),
linkTo(methodOn(PersonController.class).getAllPerson()).withRel("person")))
.collect(Collectors.toList());
return resources;
}
Or if you want for one person, you can also use Mono
#GetMapping("/{id}")
public Mono<Resource<Person>> getPerson(#PathVariable("id") Long id){
Mono<Person> data = Mono.justOrEmpty(persons.stream().filter(x -> x.getUid().equals(id)).findFirst());
Mono person = data.map(x -> {
x.add(linkTo(methodOn(PersonController.class).getPerson(id)).withSelfRel());
return x;
});
return person;
}
This is simple use of .map function provided by Flux/Mono.
I hope this will be helpful for later viewers.
I think this project does not support (yet?) the new reactive support in Spring Framework. Your best bet is to reach out to the maintainers and contribute to the project (creating an issue and explaining what you're trying to achieve is a start!).

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

Playframework! Using #select for a Enum property

I'm trying to create a #select input for a enum field. Everything works fine until the form is submitted. It fails with a weird validation error -> "error.invalid"
here's my code
Enum class
package model;
...
public enum UserType {
UserType_Admin("Administrator"), UserType_Monitor("Monitor"), UserType_Audit("Audit");
private String desc;
private UserType(String desc) {
this.desc = desc;
}
#Override
public String toString() {
return Messages.get(desc);
}
public String getLabel() {
return toString();
}
public String getKey() {
return super.toString();
}
public static Map<String, String> options() {
LinkedHashMap<String, String> options = new LinkedHashMap<String, String>();
for (UserType ut : UserType.values()) {
Integer o = ut.ordinal();
options.put(o.toString(), ut.desc);
}
return options;
}
}
My Entity
#Entity
public class User extends Model {
...
#Id
public Long userID;
public UserType user_type;
}
Scala template
#form(routes.Users.save(userID)) {
#select(
userForm("user_type"),
options(model.UserType.options),
'_label -> Messages("UserType"), '_default -> Messages("choose_user_type"),
'_showConstraints -> true
)
}
on the controller the Save method:
public static Result save(Long userID) {
Form<User> userForm = form(User.class).bindFromRequest();
if (userForm.hasErrors()) { <- here it says that has errors
return badRequest(useredit.render(new Session(session()), userID,
userForm, new User()));
}
...
}
if I inspect the userForm variable, I get:
Form(of=class model.User, data={user_type=0}, value=None,
errors={user_type=[ValidationError(user_type,error.invalid,[])]})
The field user_type has the correct value, 0 if I choose the first item, 1 for the second, etc.
Screenshot
Anyone has a clue or a workaround for this? Maybe disable validation for this field? Tks guys

Resources