Use Java 8 stream to convert enum to set - java-8

I have an enum looks like:
public enum Movies {
SCIFI_MOVIE("SCIFI_MOVIE", 1, "Scifi movie type"),
COMEDY_MOVIE("COMEDY_MOVIE", 2, "Comedy movie type");
private String type;
private int id;
private String name;
Movies(String type, int id, String name) {
this.type = type;
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
}
I know that I can use stream to create a Set of Movies enum with:
Set<Movie> Movie_SET = Arrays.stream(Movie.values()).collect(Collectors.toSet());
What if I want to create a Set of enum Movies id. Is there a way to do that with stream?

Yes, assuming you have a getter for your id, your Movies enum might look like this:
public enum Movies {
SCIFI_MOVIE("SCIFI_MOVIE", 1, "Scifi movie type"),
COMEDY_MOVIE("COMEDY_MOVIE", 2, "Comedy movie type");
private String type;
private int id;
private String name;
Movies(String type, int id, String name) {
this.type = type;
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
}
Then, you can get the set of ids by using Stream.map():
Set<Integer> movieIds = Arrays.stream(Movies.values()).map(Movies::getId)
.collect(Collectors.toSet());
BTW, an alternative way to create the set of all movies is to use EnumSet.allOf():
Set<Integer> movieIds = EnumSet.allOf(Movies.class).stream().map(Movies::getId)
.collect(Collectors.toSet());

You can use EnumSet implementation for this purpose e.g.
For obtaining the Set of Movies:
Set<Movies> movies = EnumSet.allOf(Movies.class);
For obtaining only movies ids:
Set<Integer> moviesIds = movies.stream().map(Movies::getId).collect(Collectors.toSet());

If you are able to get stream of Enum values then rest could easily be achieved.
You could use custom Collector impl (My favorite of all time), see example below of a custom collector:-
Set<Integer> movieIds = Arrays
.stream(Movies.values())
.collect(
HashSet::new,
(set, e) -> set.add(e.getId()),
HashSet::addAll
);
Or you could use map to fetch ids only and the collect to collect these ids to a Set as usual.
Set<Integer> movieIds = Arrays
.stream(Movies.values())
.map(Movies::getId)
.collect(Collectors.toSet());

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

Android room. error: Cannot figure out how to read this field from a cursor

the SQLite database contains three tables 1) employee 2) skills 3) departments. The idea is this - the employee table stores data such as id, name, last_name, salary. Also, an employee has data such as skill and department, but there can be several data for one employee, so I created two separate skills and departments tables and linked them using the key to the employee table where the primary key for employee is id. Now with the help of id I need to display all the information about employee including his skills which can be several and departments. I implement the whole process using the ROOM library.
Here is the request I make
#Query("SELECT employ.id ,employ.name ,employ.last_name, employ.salary, " +
"skill.skill, department.department_name FROM employ INNER JOIN skill,department " +
"ON employ.id = :id AND skill.employ_id = :id AND department.employ_id = :id ")
AllAboutEmployee getAllAboutEmployee(String id);
Here is the AllAboutEmployee class whose object accepts the result of the request
public class AllAboutEmployee {
#ColumnInfo(name = "id")
private String id;
#ColumnInfo(name = "name")
private String name;
#ColumnInfo(name = "last_name")
private String lastName;
#ColumnInfo(name = "salary")
private String salary;
#ColumnInfo(name = "department_name")
private List<String> departmentsList; // THE ERROR IS ON THIS LINE
#ColumnInfo(name = "skill")
private List<String> skillList; // THE ERROR IS ON THIS LINE
public AllAboutEmployee(String id, String name, String lastName, String salary, List<String> departmentsList, List<String> skillList) {
this.id = id;
this.name = name;
this.lastName = lastName;
this.salary = salary;
this.departmentsList = departmentsList;
this.skillList = skillList;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getSalary() {
return salary;
}
public void setSalary(String salary) {
this.salary = salary;
}
public List<String> getDepartmentsList() {
return departmentsList;
}
public void setDepartmentsList(List<String> departmentsList) {
this.departmentsList = departmentsList;
}
public List<String> getSkillList() {
return skillList;
}
public void setSkillList(List<String> skillList) {
this.skillList = skillList;
}
}
So ther are two fields int the AllAboutEmployee class with the List type, in order to put several skills and several departments there. It is in these fields that an error occurs. Thank you in advance for your help
Wow.. that's so cool.. I was coding all day and got this error too!
You have to create a TypeCoverter to store your data into your Room's Database.
In this case you have two List, that are not types recognizable by Database, so you have to create an Converter for it to store as a String, and another method to do the inverse.
Something like:
class TypeCoverter{
#TypeConverter
fun arrayListToString(arrayList: ArrayList<String>?): String? {
if (arrayList.isNullOrEmpty()) return null
val string = StringBuilder()
for (item in arrayList) {
val isNotTheLastItemInTheArrayList = (item == arrayList.last()).not()
if (isNotTheLastItemInTheArrayList) {
string.append(item).append(COMMA)
} else {
string.append(item)
}
}
return string.toString()
}
}
#TypeConverter
fun stringToArrayList(string: String?): ArrayList<String>? {
when {
string.isNullOrEmpty() -> {
return null
}
string.contains(COMMA).not() -> {
val list = ArrayList<String>()
list.add(string)
return list
}
else -> {
return string.split(COMMA.toRegex()).dropLastWhile { it.isEmpty() } as ArrayList<String>
}
}
}
That's actually in Kotlin, but you can see how it works.

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

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.

How to use Java 8 Collectors groupingBy to get a Map with a Map of the collection?

Imagine these classes
class Subject {
private int id;
private Type type;
private String origin;
private String name;
Subject(int id, Type type, String origin, String name) {
this.id = id;
this.type = type;
this.origin = origin;
this.name = name;
}
// Getters and Setters
}
enum Type {
TYPE1,
TYPE2
}
I have a list of those Subject classes
List<Subject> subjects = Arrays.asList(
new Subject(1, Type.TYPE1, "South", "Oscar"),
new Subject(2, Type.TYPE2, "South", "Robert"),
new Subject(3, Type.TYPE2, "North", "Dan"),
new Subject(4, Type.TYPE2, "South", "Gary"));
I would like to get as a result of using Collectors.groupingBy() a Map grouping first the Subject objects by Subject.origin and then grouped by Subject.type
Getting as a result an object like this Map<String, Map<Type, List<Subject>>>
groupingBy accepts a downstream collector, which can also be a groupingBy:
subjects.stream()
.collect(groupingBy(
Subject::getOrigin,
groupingBy(Subject::getType)
));
Stream<Subject> subjects = Stream.of(
new Subject(1, Subject.Type.TYPE1, "South", "Oscar"),
new Subject(2, Subject.Type.TYPE2, "South", "Robert"),
new Subject(3, Subject.Type.TYPE2, "North", "Dan"),
new Subject(4, Subject.Type.TYPE2, "South", "Gary"));
Map<String, Map<Subject.Type, List<Subject>>> subjectByOriginByType;
subjectByOriginByType = subjects.collect(Collectors.groupingBy(
s->s.getOrigin(), Collectors.groupingBy(
s->s.getType(),
Collectors.mapping((Subject s) -> s, Collectors.toList()))));

Resources