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

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

Related

Springdoc cannot detect POJO's fields to map as individual parameters on UI

My springboot application has a #RestController which takes a POJO class as parameter.
#GetMapping(path="/")
public void sayHello(Person person) {
System.out.println(person);
}
and here's the definition of Person class which is just a POJO.
public class Person {
private String firstName;
private String lastName;
private int age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
#Override
public String toString() {
return "Person [firstName=" + firstName + ", lastName=" + lastName + ", age=" + age + "]";
}
}
This is how springdoc-ui interprets it for showing parameters in UI.
I did not use #RequestBody in the controller yet springdoc assumes the input to be in the form of JSON body. I intend it to be as query parameters as below
Interestingly if I change the swagger implementation to springfox, each fields of the POJO is interpreted as individual parameters in the UI by default. The last screenshot was taken using springfox implemntation. How do I get the same behavior with springdoc?
Using #ParameterObject fixes this.
#GetMapping(path="/")
public void sayHello(#ParameterObject Person person) {
System.out.println(person);
}
Found the solution here:
https://github.com/springdoc/springdoc-openapi/issues/162
https://github.com/springdoc/springdoc-openapi/pull/505

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 merge two streams and return list with different type?

i have two streams and i want to combine them into list with different
i.e i have hashmap
Map<String, List<String>> citiesByZip = new HashMap<>();
that hold this data
Alameda [95246, 95247]
Colusa [95987]
list of persons
class Person {
private String firstName;
private String lastName;
private int income;
private int zipCode;
People(String firstName, String lastName, int income, int zipCode) {
this.firstName = firstName;
this.lastName = lastName;
this.income = income;
this.zipCode = zipCode;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getIncome() {
return income;
}
public int getZipCode() {
return zipCode;
}
}
List<Person> persons= new ArrayList<>();
that hold this data
Junior Jane 20000 95246
Junior Jane 30000 95246
Joseph James 50000 95247
Patricia Allen 60000 95247
Opal Campbell 70000 95987
Dorothy Rook 80004 95987
Mary Nelson 80000 23666
i want to map each person in list to hashmap of counties to find which county person lives in
List <FinalObject> finalObjects= new ArrayList<>();
finalObjects = Stream.concat(peopleStream.stream(), citiesByZip.entrySet().stream())
.collect(Collectors.toMap(
))
this list should return list of final objects
like this
Junior Jane 20000 Alameda
Junior Jane 30000 Alameda
Joseph James 50000 Alameda
.
.
etc
i know that i can do this job in Java 7 with tradition loops but i was wondering if i can do the same thing in java 8 using stream and lambda
First, you need a data structure for an efficient lookup of a particular zip code, as Map<String, List<String>> is not suitable for that. You can convert it like
Map<Integer,String> zipToCity = citiesByZip.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(Integer::valueOf)
.map(zip -> new AbstractMap.SimpleEntry<>(zip, e.getKey())))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
Alternatively, you may use
Map<Integer,String> zipToCity = citiesByZip.entrySet().stream()
.collect(HashMap::new,
(m,e) -> e.getValue().forEach(zip -> m.put(Integer.valueOf(zip), e.getKey())),
Map::putAll);
which doesn’t need temporary AbstractMap.SimpleEntry instances, but looks much like the conventional iteration solution. In fact, for the sequential use case, the loop is actually simpler.
Then, you can convert the Person instances to FinalObject instances with a single stream operation. Since you didn’t specify the FinalObject class, I assume
class FinalObject {
private String firstName, lastName, city;
private int income;
FinalObject(String firstName, String lastName, int income, String city) {
this.firstName = firstName;
this.lastName = lastName;
this.income = income;
this.city = city;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getIncome() {
return income;
}
public String getCity() {
return city;
}
#Override public String toString() {
return firstName+" "+lastName+" "+income+" "+city;
}
}
With this definition, you can do the conversion with the zip lookup like
List<FinalObject> finalObjects = persons.stream()
.map(p -> new FinalObject(p.getFirstName(), p.getLastName(),
p.getIncome(), zipToCity.getOrDefault(p.getZipCode(), "Unknown")))
.collect(Collectors.toList());
Though, it might be beneficial to use delegation instead:
class FinalObject {
private Person p;
String city;
FinalObject(Person p, String city) {
this.p = p;
this.city = city;
}
public String getFirstName() {
return p.getFirstName();
}
public String getLastName() {
return p.getLastName();
}
public int getIncome() {
return p.getIncome();
}
public String getCity() {
return city;
}
#Override public String toString() {
return getFirstName()+" "+getLastName()+" "+getIncome()+" "+city;
}
}
 
List<FinalObject> finalObjects = persons.stream()
.map(p -> new FinalObject(p, zipToCity.getOrDefault(p.getZipCode(), "Unknown")))
.collect(Collectors.toList());

How to use static constructor reference with a three parameters Java 8 Function (without make TriFunction)?

I am currently playing with Java 8 and I found a problem with Function. I would like ton know if there is a way to use function reference (name::methode) with a Function with tree parameters without declare a new functional interface (i.e. TriFunction).
I tried with currying way, but it doesn't work.
I have three classes :
Person.class
public class Person {
public enum Sex {
MALE, FEMALE
}
private String firstName;
private String lastName;
private Sex gender;
public Person(String firstName, String lastName, Sex gender) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Sex getGender() {
return gender;
}
}
PersonFactory
public class PersonFactory {
public static Person create(String firstName, String lastName, String gender) {
// Check firstName Parameter
if(firstName == null || firstName.isEmpty()) {
throw new IllegalArgumentException("The firstName argument expect to not be null or empty");
}
// Check lastName Parameter
if(lastName == null || lastName.isEmpty()) {
throw new IllegalArgumentException("The lastName argument expect to not be null or empty");
}
// Check gender Parameter
if(gender == null || gender.isEmpty()) {
throw new IllegalArgumentException("The gender argument expect to not be null or empty");
} else {
switch(gender) {
case "M":
return new Person(firstName, lastName, Sex.MALE);
case "F":
return new Person(firstName, lastName, Sex.FEMALE);
default:
throw new IllegalArgumentException("The gender parameter is supposed to be either 'M' for male or 'F' for Female");
}
}
}
}
CsVPersonParser
public class CsvPersonParser {
public Person parseLine(String line, String separator, Function<String, Function<String, Function<String, Person>>> creator) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return creator.apply(firstName).apply(lastName).apply(gender);
}
}
Here is my main class :
public class App {
public static void main(String[] args) {
final String IMAGINARY_CSV_FILE_LINE = "Jean,Dupont,M";
CsvPersonParser csvParser = new CsvPersonParser();
csvParser.parseLine("blabla", ",", PersonFactory::create);
}
}
The compilator show : The type PersonFactory does not define create(String) that is applicable here
It seems pretty logical. I have no solution. Is anyone can help me ?
I wonder why there is no way to do it simple without to create new things.
Probably a tri function is quite complex. I suggest that you use a builder to create a person.
The main reasons are, that you are not fixed on parameter ordering and you can extend your person. When you use a trifunction where all parameters are strings its often hard to say which parameter is the first/second/third. And when you want to add an address to a person it becomes more difficult to make it with generic classes like TriFunction.
My suggestion:
public interface PersonBuilder {
PersonBuilder withFirstName(String firstName);
PersonBuilder withLastName(String lastName);
PersonBuilder withGender(String gender);
Person create();
}
Concrete Implementation:
public class DefaultPersonBuilder implements PersonBuilder {
private String firstName;
private String lastName;
private String gender;
#Override
public PersonBuilder withFirstName(String firstName) {
this.firstName = firstName;
return this;
}
#Override
public PersonBuilder withLastName(String lastName) {
this.lastName = lastName;
return this;
}
#Override
public PersonBuilder withGender(String gender) {
this.gender = gender;
return this;
}
#Override
public Person create() {
// Check firstName Parameter
if (firstName == null || firstName.isEmpty()) {
throw new IllegalArgumentException("The firstName argument expect to not be null or empty");
}
[... your implementation using the fields]
}
}
Your parser method:
public Person parseLine(String line, String separator, PersonBuilder person) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return person.withFirstName(firstName).withLastName(lastName).withGender(gender).create();
}
Now you can change the argument order oder add new fields to person without creating a function with 10 parameters. The parser interface is simpler now, too.
There is no way to do that what I wanted. However two other solutions is possible. Use a lambda instead of PersonFactory::create or create a new functional interface.
Here is the result :
New functional interface
#FunctionalInterface
public interface TriFunction<A, B, C, D> {
public D apply(A a, B b, C c);
}
Add a function parseLine with my new functional interface
public class CsvPersonParser {
// Currying style
public Person parseLine(String line, String separator, Function<String, Function<String, Function<String, Person>>> creator) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return creator.apply(firstName).apply(lastName).apply(gender);
}
// New Functionnal interface style
public Person parseLine(String line, String separator, TriFunction<String, String, String, Person> creator) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return creator.apply(firstName, lastName, gender);
}
}
My main class with solutions
public class App {
public static void main(String[] args) {
final String DATA_FILE_SEPARATOR = ",";
final String FAKE_CSV_LINE = "Jean,Dupont,M";
CsvPersonParser csvParser = new CsvPersonParser();
Person person;
// Use curryling style
person = csvParser.parseLine(FAKE_CSV_LINE, DATA_FILE_SEPARATOR, f -> l -> g -> PersonFactory.create(f, l, g));
System.out.println("Currying style : " + person.getFirstName() + " " + person.getLastName() + " " + person.getGender());
// Use new functionnal interface
person = csvParser.parseLine(FAKE_CSV_LINE, DATA_FILE_SEPARATOR, PersonFactory::create);
System.out.println("TriFunction style : " + person.getFirstName() + " " + person.getLastName() + " " + person.getGender());
// Use lambda style
person = csvParser.parseLine(FAKE_CSV_LINE, DATA_FILE_SEPARATOR, (a,b,c) -> PersonFactory.create(a, b, c));
System.out.println("Lambda style : " + person.getFirstName() + " " + person.getLastName() + " " + person.getGender());
}
}

Accessing Subclass properties in a JavaFX TableView ObservableArrayList

I am trying to access getter properties in a subclass with a TableView in JavaFX. I have the following class:
public class PersonType implements Serializable {
private static final long serialVersionUID = 1L;
Person person;
short count;
public PersonType() {
}
public PersonType(Person person, short count) {
super();
this.person = person;
this.count = count;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public short getCount() {
return count;
}
public void setCount(short count) {
this.count = count;
}
Person is like this:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
String firstName;
String lastName;
public Person() {
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Okay - lastly we have the following:
#FXML
private TableColumn tcFirstName;
#FXML
private TableColumn tcLastName;
#FXML
private TableView tblPersonTypes;
ArrayList<PersonType> pType = new ArrayList<PersonType>();
//Can assume that pType here has say 5 entries, the point of this
//is I'm trying to get to the firstName, lastName properties of the
//PersonType in the TableView below like the following:
tcFirstName.setCellValueFactory(new PropertyValueFactory<String,String>("firstName"));
tcLastName.setCellValueFactory(new PropertyValueFactory<String,String>("lastName"));
//Populate Table with Card Records
ObservableList<PersonType> data = FXCollections.observableArrayList(pType);
tblPersonTypes.setItems(data);
And I'm unsure how with a list of PersonTypes I can tell the table columns that I want the firstName and lastName properties of the Person object contained within. I know I could create a new object, and have the "count" from PersonTypes, then the other properties of "firstName", "lastName" etc without having an object property of Person. Any help would be greatly appreciated.
-- EDIT --
Another way I thought to do this was using CellFactories - where I would pass in to the CellValueFactories the Person object, then set the CellFactory to return a String value (firstName for the first name column, etc). And it would look like this:
tcFirstName.setCellValueFactory(new PropertyValueFactory<Person,String>("person"));
tcFirstName.setCellFactory(new Callback<TableColumn<Person,String>,TableCell<Person,String>>(){
#Override
public TableCell<Person,String> call(TableColumn<Person,String> param) {
TableCell<Person,String> cell = new TableCell<Person,String>(){
#Override
public void updateItem(String item, boolean empty) {
if(item!=null){
setGraphic(new Label(item.getFirstName()));
}
}
};
return cell;
}
});
Try this:
tcFirstName.setCellValueFactory(new Callback<CellDataFeatures<PersonType, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<PersonType, String> p) {
// p.getValue() returns the PersonType instance for a particular TableView row
if (p.getValue() != null && p.getValue().getPerson() != null) {
return new SimpleStringProperty(p.getValue().getPerson().getFirstName());
} else {
return new SimpleStringProperty("<No TC firstname>");
}
}
});
}

Resources