Spliting string, flatMapping it and creating objects using Java streams? - java

I have 2 classes going like this:
public class Seat extends Moviefiable {
private int number;
private Hall hall;
public Seat(int id, boolean active, int number, Hall hall) {
super(id, active);
this.number = number;
this.hall = hall;
}
public Seat() {this(-1, false, -1, null);}
public static Seat getById(int idSeat) throws SQLException {
Seat seat = SeatDAO.getById(idSeat);
return seat;
}
}
public class Ticket extends Moviefiable {
private Projection projection;
private Seat seat;
private LocalDateTime purchasingDate;
private User user;
public Ticket(int id, boolean active, Projection projection, Seat seat, LocalDateTime purchasingDate, User user) {
super(id, active);
this.projection = projection;
this.seat = seat;
this.purchasingDate = purchasingDate;
this.user = user;
}
public Ticket() {this(-1, false, null, null, null, null);}
Now, I need to create two or more Tickets, depending how many tickets loggedInUser chooses. From my JSP page, I'll get something like this:
String uri = request.getQueryString(); //uri looks like this: seats=2&seats=3
I want to create two Ticket objects for two Seat objects. In uri string, chars 2 and 3 are primary keys for seats.
The idea is to use Java streams to perform splitting and creating objects. This is I have so far.
ArrayList<Ticket> newTicketsForSeats = Stream.of(uri.split("&"))
.map(s -> s.split("seats=")[1])
//.flatMap(Arrays::stream)
.mapToInt(Integer::valueOf)
.mapToObj(s -> {
try {
return new Ticket(-1, true, projection, SeatDAO.getById(s),
LocalDateTime.now(), loggedInUser);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
})
.collect(Collectors.toCollection(ArrayList::new));
When I made a test, like this to see the output I'm getting:
Stream.of(uri.split("&"))
.map(s -> s.split("seats=")[1])
.mapToInt(Integer::valueOf)
.forEach(System.out::println); //2 3
Which is okey. But when I run code above, I got:
java.lang.ArrayIndexOutOfBoundsException: 1
at servlets.ConfirmPurchaseServlet.lambda$0(ConfirmPurchaseServlet.java:62)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
projection is also object, which I obtained, this is just reference, it is elsewhere in servlet. loggedInUser I got from session. I'm newbie regarding Java streams, any additional explanation would be excellent.
I get that map() requires IntUnaryOperator lambda expression, something like this:
s -> s * 10
I don't know how to "map" from int (primary key for Seat) to Seat object.
Cheers.

Your main problem is that you are trying to assign the stream output to an ArrayList. Collectors.toList() returns a List that cannot be expected to be a particular implementation.
So either do this:
List<SomeClass> list = ...collect(Collectors.toList());
or this
ArrayList<SomeClass> list = ... collect(Collectors.toCollection(ArrayList::new));
The following should work but it may need some tweaking based on your other classes.
ArrayList<Ticket> newTicketsForSeats = Stream.of(uri.split("&"))
.map(s -> s.split("seats="))
.flatMap(Arrays::stream)
.mapToInt(Integer::valueOf)
.mapToObj(s -> new Ticket(-1, true, projection, SeatDAO.getById(s),
LocalDateTime.now(), loggedInUser))
.collect(Collectors.toCollection(ArrayList::new));

Either of these would probably work:
List<Ticket> newTicketsForSeats = Stream.of(uri.split("&"))
.map(s -> s.replace("seats=", ""))
.mapToInt(Integer::valueOf)
.mapToObj(s -> new Ticket(-1, true, projection, SeatDAO.getById(s), LocalDateTime.now(), loggedInUser))
.collect(Collectors.toList());
or
List<Ticket> newTicketsForSeats = Stream.of(uri.split("&"))
.map(s -> s.replace("seats=", ""))
.map(s -> new Ticket(-1, true, projection, SeatDAO.getById(Integer.valueOf(s)), LocalDateTime.now(), loggedInUser))
.collect(Collectors.toList());
BTW: you don't seem to have a Ticket constructor which matches the signature:
Ticket(int, boolean, Projection, Seat, LocalDateTime, boolean)

Related

Using Java Streams API to dynamically convert a list into a group map with enum key

Java 8 Streams here. I have the following classes:
public enum Category {
Thing,
Thang,
Fizz
}
#Data // using lombok to generate ctors/getters/setters/etc.
public class LineItem {
private Long id;
private String name;
private Category category;
private BigDecimal amount;
}
#Data
public class PieSlice {
private String label;
private BigDecimal value = BigDecimal.ZERO;
public void addAmount(BigDecimal amount) {
value = value.add(amount);
}
}
In my code I am given a List<LineItem> and I want to convert it to a Map<Category,PieSlice> using the Streams API, if at all possible.
Using the non-Stream way, the conversion would look like:
List<LineItem> lineItems = getSomehow();
Map<Category,PieSlice> sliceMap = new HashMap<>();
PieSlice thingSlice = new PieSlice();
PieSlice thangSlice = new PieSlice();
PieSlice fizzSlice = new PieSlice();
for (LineItem lineItem : lineItems) {
if (lineItem.getCategory().equals(Category.Thing)) {
thingSlice.addAmount(lineItem.getAmount());
} else if (lineItem.getCategory().equals(Category.Thang)) {
thangSlice.addAmount(lineItem.getAmount());
} else if (lineItem.getCategory().equals(Category.Fizz)) {
fizz.addAmount(lineItem.getAmount());
} else {
throw new RuntimeException("uncategorized line item");
}
}
sliceMap.put(Category.Thing, thingSlice);
sliceMap.put(Category.Thang, thangSlice);
sliceMap.put(Category.Fizz, fizzSlice);
The problem is that I need to edit the code every time I add a new Category. Is there a way to do this via the Streams API, regardless of what Category values exist?
Try this.
List<LineItem> lineItems = List.of(
new LineItem(1L, "", Category.Thing, BigDecimal.valueOf(100)),
new LineItem(2L, "", Category.Thang, BigDecimal.valueOf(200)),
new LineItem(3L, "", Category.Fizz, BigDecimal.valueOf(300)),
new LineItem(4L, "", Category.Thing, BigDecimal.valueOf(400))
);
Map<Category, PieSlice> sliceMap = lineItems.stream()
.collect(
groupingBy(LineItem::getCategory,
mapping(LineItem::getAmount,
collectingAndThen(
reducing(BigDecimal.ZERO, BigDecimal::add),
amount -> {
PieSlice pieSlice = new PieSlice();
pieSlice.addAmount(amount);
return pieSlice;
}))));
sliceMap.entrySet().stream()
.forEach(System.out::println);
output:
Fizz=PieSlice [label=null, value=300]
Thang=PieSlice [label=null, value=200]
Thing=PieSlice [label=null, value=500]
You can use the collect operation to achieve this
Map<Category, PieSlice> sliceMap = lineItems
.stream()
.collect(
Collectors.groupingBy(
LineItem::getCategory,
Collectors.reducing(
new PieSlice(),
item -> {
PieSlice slice = new PieSlice();
slice.addAmount(item.getAmount());
return slice;
},
(slice, anotherSlice) -> {
slice.addAmount(anotherSlice.getValue());
return slice;
}
)
)
);
What this piece of code does is a 2-step reduction. First, we take lineItems and group them by their category - reducing the initial list to a map, we achieve this by using Collectors.groupingBy. If we were to use this collector without the second argument, the result would be of type Map<Category, List<LineItem>>. Here is where the Collectors.reducing reducer comes to play - it takes the list of LineItems which are already grouped by their category and turns them into a singular PieSlice, where the original values are accumulated.
You can read more on reduction operations and the standard reducers provided by the JDK here.
The problem is that I need to edit the code every time I add a new Category. Is there a way to do this via the Streams API, regardless of what Category values exist?
You can obtain all declared enum-constants using either values() or EnumSet.allOf(Class<E>).
If you need the resulting map to contain the entry for every existing Category-member, you can provide a prepopulated map through the supplier of collect() operation.
Here's how it might be implemented:
Map<Category, PieSlice> sliceMap = lineItems.stream()
.collect(
() -> EnumSet.allOf(Category.class).stream()
.collect(Collectors.toMap(Function.identity(), c -> new PieSlice())),
(Map<Category, PieSlice> map, LineItem item) ->
map.get(item.getCategory()).addAmount(item.getAmount()),
(left, right) ->
right.forEach((category, slice) -> left.get(category).addAmount(slice.getValue()))
);

How to get a list output from forEach loop in Java 8 Streams

I have two different lists of same objects but different properties and with a common identifier in those objects. I would like to iterate over the first list and get the corresponding object from the second (which has common properties) and then wrap those objects around and finally add that object to a list using Java Streams.
This is the example I have taken.
private class Person {
private String name;
private boolean isSenior;
private Person(String name, boolean isSenior) {
this.name = name;
this.isSenior = isSenior;
}
public String getName() {
return name;
}
public boolean isSenior() {
return isSenior;
}
#Override
public String toString() {
return name + ": " + isSenior;
}
}
private class PersonWrapper {
private Person jrPerson;
private Person srPerson;
private PersonWrapper(Person jrPerson, Person srPerson) {
this.jrPerson = jrPerson;
this.srPerson = srPerson;
}
public Person getJrPerson() {
return jrPerson;
}
public Person getSrPerson() {
return srPerson;
}
#Override
public String toString() {
return jrPerson.toString() + "-" + srPerson.toString();
}
}
Now, in the main class, I will create two list instances like this
List<Person> jrPersons = new ArrayList<>();
List<Person> srPersons = new ArrayList<>();
and add the objects in the following manner
jrList.add(new Person("John", false));
jrList.add(new Person("Paul", false));
jrList.add(new Person("Mike", false));
seniorList.add(new Person("John", true));
seniorList.add(new Person("Paul", true));
seniorList.add(new Person("Mike", true));
Now, I want to iterate over the jrList and find the corresponding Person object in the srList (same name). Then I would wrap these objects as PersonWrapper and that object to a list.
So far, this is what I have been doing
List<PersonWrapper> wrapperList = new ArrayList<>();
jrList.forEach(jr -> seniorList.stream().filter(sr -> jr.getName().equals(sr.getName())).map(sr -> new PersonWrapper(jr, sr)).collect(Collectors.toList()));
Now, I would like to know how the Collectors.toList() can be substituted by wrapperList or how the output from Collectors.toList() be added to wrapperList.
Please help me in achieving this.
Instead of using a forEach just use streams from the beginning:
List<PersonWrapper> wrapperList = jrList.stream()
.flatMap(jr -> seniorList.stream()
.filter(sr -> jr.getName().equals(sr.getName()))
.map(sr -> new PersonWrapper(jr, sr))
)
.collect(Collectors.toList());
By using flatMap you can flatten a stream of streams (Stream<Stream<PersonWrapper>>) into a single stream (Stream<PersonWrapper>)
If you can't instantiate wrapperList by yourself or really need to append to it. You can alter above snippet to following:
List<PersonWrapper> wrapperList = new ArrayList<>();
jrList.stream()
.flatMap(jr -> seniorList.stream()
.filter(sr -> jr.getName().equals(sr.getName()))
.map(sr -> new PersonWrapper(jr, sr))
)
.forEach(wrapperList::add);
While Lino's answer is certainly correct. I would argue that if a given person object in jrList can only ever have one corresponding match in seniorList maximum, in other words, if it's a 1-1 relationship then you can improve upon the solution given by Lino by finding the first match as follows:
List<PersonWrapper> resultSet = jrList.stream()
.map(p -> seniorList.stream()
.filter(sr -> p.getName().equals(sr.getName()))
.findFirst()
.map(q -> new PersonWrapper(p, q))
.get())
.collect(Collectors.toList());
or if there is no guarantee that each person in jrList will have a corresponding match in seniorList then change the above query to:
List<PersonWrapper> resultSet = jrList.stream()
.map(p -> seniorList.stream()
.filter(sr -> p.getName().equals(sr.getName()))
.findFirst()
.map(q -> new PersonWrapper(p, q))
.orElse(null))
.filter(Objects::nonNull)
.collect(Collectors.toList());
The difference is that now instead of calling get() on the result of findFirst() we provide a default with orElse in case findFirst cannot find the corresponding value and then we filter the null values out in the subsequent intermediate operation as they are not needed.
Replace your looping logic with below code.
jrList.forEach(jr -> seniorList.stream().filter(sr -> jr.getName().equals(sr.getName()))
.map(sr -> wrapperList.add(new PersonWrapper(jr, sr))).collect(Collectors.toList()));

Java 8 Map a List to another list and count

I am looking to create a list of history values for an existing list so that I can save it in DB to be displayed later in a table
Class Data {
Date date;
int int1;
int int2;
}
class DataHistory {
Date date;
int sum_Int1_beforeOrEqualDate;
int sum_Int2_beforeOrEqualDate;
String someOtherValues;
}
For example I have several lines perDate with all values. What I would like to achieve is :
My input :
date, int1, int2
01/01/18, 2, 3
01/01/18, 0, 1
02/01/18, 0, 1
02/01/18, 3, 0
03/01/18, 1, 3
...
My output :
date, sum_Int1_beforeOrEqualDate, sum_Int2_beforeOrEqualDate
01/01/18, 2, 4
02/01/18, 3, 1
03/01/18, 1, 3
...
I have tried several things, mainly with Map, but has never been able to do it with List-->List.
What I have tried to do is :
Edit: My lastAttempt, which clearly shows I don't know what i am doing..
List<OutputList> outputList =
inputlist
.stream()
.map( e -> new DataHistory())
.collect(Collectors.groupingBy(int1));
I believe you're trying to simply sum the values grouping by date. So assuming you have parsed data as a List
List<Data> list = getDataAsList();
List<DataHistory> historyList = list.stream()
.collect(Collectors.groupingBy(data -> data.date)).entrySet().stream()
.map((entry) -> {
DataHistory history = new DataHistory();
history.date = entry.getKey();
List<Data> dataList = entry.getValue();
history.sum_Int1_beforeOrEqualDate = dataList.stream().mapToInt(data -> data.int1).sum();
history.sum_Int2_beforeOrEqualDate = dataList.stream().mapToInt(data -> data.int2).sum();
return history;
})
.collect(Collectors.toList());
Tell me if I got the logic correct.
What you could do is use Collections.reducing which works pretty good.
List<DataHistory> dataHistories =
list.stream()
.collect(Collectors.groupingBy(Data::getDate,
Collectors.reducing(DataHistory::new,
DataHistoryHelper::merge)))
.values();
This solution assumes you have a constructor in DataHistory taking a Data as parameter.
public DataHistory(Data o) {
this.date = o.getDate();
// and so on
}
And that you have a method (anywhere) that takes care of merging two DataHistory objects
public DataHistory merge(DataHistory o1, DataHistory o2) {
DataHistory merged = new DataHistory();
merged.setSum_Int1_beforeOrEqualDate(o1.getSum_Int1_beforeOrEqualDate + o2.getSum_Int1_beforeOrEqualDate);
// and so on
return merged;
}
You can accomplish the task at hand using the toMap collector:
Collection<DataHistory> resultSet =
myList.stream()
.collect(Collectors.toMap(Data::getDate,
e -> new DataHistory(e.getDate(), e.getInt1(), e.getInt2(), null),
DataHistory::merge)).values();
This assumes you have a constructor defined as follows in your DataHistory class:
public DataHistory(Date date, int sum_Int1_beforeOrEqualDate,
int sum_Int2_beforeOrEqualDate, String someOtherValues) {
this.date = date;
this.sum_Int1_beforeOrEqualDate = sum_Int1_beforeOrEqualDate;
this.sum_Int2_beforeOrEqualDate = sum_Int2_beforeOrEqualDate;
this.someOtherValues = someOtherValues;
}
and a merge function defined as such:
public DataHistory merge(DataHistory other){
this.sum_Int1_beforeOrEqualDate += other.getSum_Int1_beforeOrEqualDate();
this.sum_Int2_beforeOrEqualDate += other.getSum_Int2_beforeOrEqualDate();
return this;
}
in the DataHistory class.
Further, if you explicitly require a List<DataHistory> as opposed to a Collection<DataHistory> then you can do:
List<DataHistory> historyList = new ArrayList<>(resultSet);
Note that I am passing null to the DataHistory constructor for the fourth parameter simply because I don't know what data to pass, so I'll leave that for you to decide upon.

Java 8 lambdas using if else on list stream [duplicate]

I am trying to use Java 8 Streams to find elements in a LinkedList. I want to guarantee, however, that there is one and only one match to the filter criteria.
Take this code:
public static void main(String[] args) {
LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
System.out.println(match.toString());
}
static class User {
#Override
public String toString() {
return id + " - " + username;
}
int id;
String username;
public User() {
}
public User(int id, String username) {
this.id = id;
this.username = username;
}
public void setUsername(String username) {
this.username = username;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public int getId() {
return id;
}
}
This code finds a User based on their ID. But there are no guarantees how many Users matched the filter.
Changing the filter line to:
User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
Will throw a NoSuchElementException (good!)
I would like it to throw an error if there are multiple matches, though. Is there a way to do this?
Create a custom Collector
public static <T> Collector<T, ?, T> toSingleton() {
return Collectors.collectingAndThen(
Collectors.toList(),
list -> {
if (list.size() != 1) {
throw new IllegalStateException();
}
return list.get(0);
}
);
}
We use Collectors.collectingAndThen to construct our desired Collector by
Collecting our objects in a List with the Collectors.toList() collector.
Applying an extra finisher at the end, that returns the single element — or throws an IllegalStateException if list.size != 1.
Used as:
User resultUser = users.stream()
.filter(user -> user.getId() > 0)
.collect(toSingleton());
You can then customize this Collector as much as you want, for example give the exception as argument in the constructor, tweak it to allow two values, and more.
An alternative — arguably less elegant — solution:
You can use a 'workaround' that involves peek() and an AtomicInteger, but really you shouldn't be using that.
What you could do instead is just collecting it in a List, like this:
LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
List<User> resultUserList = users.stream()
.filter(user -> user.getId() == 1)
.collect(Collectors.toList());
if (resultUserList.size() != 1) {
throw new IllegalStateException();
}
User resultUser = resultUserList.get(0);
For the sake of completeness, here is the ‘one-liner’ corresponding to #prunge’s excellent answer:
User user1 = users.stream()
.filter(user -> user.getId() == 1)
.reduce((a, b) -> {
throw new IllegalStateException("Multiple elements: " + a + ", " + b);
})
.get();
This obtains the sole matching element from the stream, throwing
NoSuchElementException in case the stream is empty, or
IllegalStateException in case the stream contains more than one matching element.
A variation of this approach avoids throwing an exception early and instead represents the result as an Optional containing either the sole element, or nothing (empty) if there are zero or multiple elements:
Optional<User> user1 = users.stream()
.filter(user -> user.getId() == 1)
.collect(Collectors.reducing((a, b) -> null));
The other answers that involve writing a custom Collector are probably more efficient (such as Louis Wasserman's, +1), but if you want brevity, I'd suggest the following:
List<User> result = users.stream()
.filter(user -> user.getId() == 1)
.limit(2)
.collect(Collectors.toList());
Then verify the size of the result list.
if (result.size() != 1) {
throw new IllegalStateException("Expected exactly one user but got " + result);
User user = result.get(0);
}
Guava provides MoreCollectors.onlyElement() which does the right thing here. But if you have to do it yourself, you could roll your own Collector for this:
<E> Collector<E, ?, Optional<E>> getOnly() {
return Collector.of(
AtomicReference::new,
(ref, e) -> {
if (!ref.compareAndSet(null, e)) {
throw new IllegalArgumentException("Multiple values");
}
},
(ref1, ref2) -> {
if (ref1.get() == null) {
return ref2;
} else if (ref2.get() != null) {
throw new IllegalArgumentException("Multiple values");
} else {
return ref1;
}
},
ref -> Optional.ofNullable(ref.get()),
Collector.Characteristics.UNORDERED);
}
...or using your own Holder type instead of AtomicReference. You can reuse that Collector as much as you like.
Use Guava's MoreCollectors.onlyElement() (Source Code).
It does what you want and throws an IllegalArgumentException if the stream consists of two or more elements, and a NoSuchElementException if the stream is empty.
Usage:
import static com.google.common.collect.MoreCollectors.onlyElement;
User match =
users.stream().filter((user) -> user.getId() < 0).collect(onlyElement());
The "escape hatch" operation that lets you do weird things that are not otherwise supported by streams is to ask for an Iterator:
Iterator<T> it = users.stream().filter((user) -> user.getId() < 0).iterator();
if (!it.hasNext()) {
throw new NoSuchElementException();
} else {
result = it.next();
if (it.hasNext()) {
throw new TooManyElementsException();
}
}
Guava has a convenience method to take an Iterator and get the only element, throwing if there are zero or multiple elements, which could replace the bottom n-1 lines here.
Update
Nice suggestion in comment from #Holger:
Optional<User> match = users.stream()
.filter((user) -> user.getId() > 1)
.reduce((u, v) -> { throw new IllegalStateException("More than one ID found") });
Original answer
The exception is thrown by Optional#get, but if you have more than one element that won't help. You could collect the users in a collection that only accepts one item, for example:
User match = users.stream().filter((user) -> user.getId() > 1)
.collect(toCollection(() -> new ArrayBlockingQueue<User>(1)))
.poll();
which throws a java.lang.IllegalStateException: Queue full, but that feels too hacky.
Or you could use a reduction combined with an optional:
User match = Optional.ofNullable(users.stream().filter((user) -> user.getId() > 1)
.reduce(null, (u, v) -> {
if (u != null && v != null)
throw new IllegalStateException("More than one ID found");
else return u == null ? v : u;
})).get();
The reduction essentially returns:
null if no user is found
the user if only one is found
throws an exception if more than one is found
The result is then wrapped in an optional.
But the simplest solution would probably be to just collect to a collection, check that its size is 1 and get the only element.
I think this way is more simple:
User resultUser = users.stream()
.filter(user -> user.getId() > 0)
.findFirst().get();
An alternative is to use reduction:
(this example uses strings but could easily apply to any object type including User)
List<String> list = ImmutableList.of("one", "two", "three", "four", "five", "two");
String match = list.stream().filter("two"::equals).reduce(thereCanBeOnlyOne()).get();
//throws NoSuchElementException if there are no matching elements - "zero"
//throws RuntimeException if duplicates are found - "two"
//otherwise returns the match - "one"
...
//Reduction operator that throws RuntimeException if there are duplicates
private static <T> BinaryOperator<T> thereCanBeOnlyOne()
{
return (a, b) -> {throw new RuntimeException("Duplicate elements found: " + a + " and " + b);};
}
So for the case with User you would have:
User match = users.stream().filter((user) -> user.getId() < 0).reduce(thereCanBeOnlyOne()).get();
Using reduce
This is the simpler and flexible way I found (based on #prunge answer)
Optional<User> user = users.stream()
.filter(user -> user.getId() == 1)
.reduce((a, b) -> {
throw new IllegalStateException("Multiple elements: " + a + ", " + b);
})
This way you obtain:
the Optional - as always with your object or Optional.empty() if not present
the Exception (with eventually YOUR custom type/message) if there's more than one element
Guava has a Collector for this called MoreCollectors.onlyElement().
Using a Collector:
public static <T> Collector<T, ?, Optional<T>> singleElementCollector() {
return Collectors.collectingAndThen(
Collectors.toList(),
list -> list.size() == 1 ? Optional.of(list.get(0)) : Optional.empty()
);
}
Usage:
Optional<User> result = users.stream()
.filter((user) -> user.getId() < 0)
.collect(singleElementCollector());
We return an Optional, since we usually can't assume the Collection to contain exactly one element. If you already know this is the case, call:
User user = result.orElseThrow();
This puts the burden of handeling the error on the caller - as it should.
Using Reduce and Optional
From Fabio Bonfante response:
public <T> T getOneExample(Collection<T> collection) {
return collection.stream()
.filter(x -> /* do some filter */)
.reduce((x,y)-> {throw new IllegalStateException("multiple");})
.orElseThrow(() -> new NoSuchElementException("none"));
}
We can use RxJava (very powerful reactive extension library)
LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
User userFound = Observable.from(users)
.filter((user) -> user.getId() == 1)
.single().toBlocking().first();
The single operator throws an exception if no user or more then one user is found.
As Collectors.toMap(keyMapper, valueMapper) uses a throwing merger to handle multiple entries with the same key it is easy:
List<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
int id = 1;
User match = Optional.ofNullable(users.stream()
.filter(user -> user.getId() == id)
.collect(Collectors.toMap(User::getId, Function.identity()))
.get(id)).get();
You will get a IllegalStateException for duplicate keys. But at the end I am not sure if the code would not be even more readable using an if.
I am using those two collectors:
public static <T> Collector<T, ?, Optional<T>> zeroOrOne() {
return Collectors.reducing((a, b) -> {
throw new IllegalStateException("More than one value was returned");
});
}
public static <T> Collector<T, ?, T> onlyOne() {
return Collectors.collectingAndThen(zeroOrOne(), Optional::get);
}
If you don't mind using a 3rd party library, SequenceM from cyclops-streams (and LazyFutureStream from simple-react) both a have single & singleOptional operators.
singleOptional() throws an exception if there are 0 or more than 1 elements in the Stream, otherwise it returns the single value.
String result = SequenceM.of("x")
.single();
SequenceM.of().single(); // NoSuchElementException
SequenceM.of(1, 2, 3).single(); // NoSuchElementException
String result = LazyFutureStream.fromStream(Stream.of("x"))
.single();
singleOptional() returns Optional.empty() if there are no values or more than one value in the Stream.
Optional<String> result = SequenceM.fromStream(Stream.of("x"))
.singleOptional();
//Optional["x"]
Optional<String> result = SequenceM.of().singleOptional();
// Optional.empty
Optional<String> result = SequenceM.of(1, 2, 3).singleOptional();
// Optional.empty
Disclosure - I am the author of both libraries.
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Integer value = list.stream().filter((x->x.intValue()==8)).findFirst().orElse(null);
I have used Integer type instead of primitive as it will have null pointer exception. you just have to handle this exception... looks succinct, I think ;)
Tried a sample code for my self and here is the solution for that.
User user = Stream.of(new User(2), new User(2), new User(1), new User(2))
.filter(u -> u.getAge() == 2).findFirst().get();
and the user class
class User {
private int age;
public User(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
If you don't use Guava or Kotlin, here's a solution based on #skiwi and #Neuron answers.
users.stream().collect(single(user -> user.getId() == 1));
or
users.stream().collect(optional(user -> user.getId() == 1));
where single and optional are statically imported functions returning corresponding collectors.
I reasoned it would look more succinct if the filtering logic had been moved inside the collector. Also nothing would break in the code if you happened to delete the string with .filter.
The gist for the code https://gist.github.com/overpas/ccc39b75f17a1c65682c071045c1a079
public List<state> getAllActiveState() {
List<Master> master = masterRepository.getActiveExamMasters();
Master activeMaster = new Master();
try {
activeMaster = master.stream().filter(status -> status.getStatus() == true).reduce((u, v) -> {
throw new IllegalStateException();
}).get();
return stateRepository.getAllStateActiveId(activeMaster.getId());
} catch (IllegalStateException e) {
logger.info(":More than one status found TRUE in Master");
return null;
}
}
In this above code, As per the condition if its find more than one true in the list then it will through the exception.
When it through the error will showing custom message because it easy maintain the logs on server side.
From Nth number of element present in list just want only one element have true condition if in list there are more than one elements having true status at that moment it will through an exception.
after getting all the this we using get(); to taking that one element from list and stored it into another object.
If you want you added optional like Optional<activeMaster > = master.stream().filter(status -> status.getStatus() == true).reduce((u, v) -> {throw new IllegalStateException();}).get();
User match = users.stream().filter((user) -> user.getId()== 1).findAny().orElseThrow(()-> new IllegalArgumentException());
Inspired by #skiwi, I solved it the following way:
public static <T> T toSingleton(Stream<T> stream) {
List<T> list = stream.limit(1).collect(Collectors.toList());
if (list.isEmpty()) {
return null;
} else {
return list.get(0);
}
}
And then:
User user = toSingleton(users.stream().filter(...).map(...));
Have you tried this
long c = users.stream().filter((user) -> user.getId() == 1).count();
if(c > 1){
throw new IllegalStateException();
}
long count()
Returns the count of elements in this stream. This is a special case of a reduction and is equivalent to:
return mapToLong(e -> 1L).sum();
This is a terminal operation.
Source: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

Merge 2 lists in a Functional Reactive way

Let's imagine the following object :
class People {
public int id;
public String name;
public Date dateOfDeath;
}
I have 2 lists of people.
In the first one, a People object has its ID and NAME properly set. In the second one, a People object has its ID and DATEOFDEATH properly set.
I need to combine the 2 lists in order to have a single list with a full People object (name and date of death).
In a full procedural way, this could be done with a double for loop like this :
for (People fullPeople : firstList) {
for (People peopleWithDateOfDeath : secondList) {
if (peopleWithDateOfDeath.id == fullPeople.id) {
fullPeople.dateOfDeath = peopleWithDateOfDeath.dateOfDeath;
break;
}
}
}
secondList = null;
// first list is good :)
How can I implement this in a functional way? I am using Rx-Java but any example with Java 8 Streams is easily convertible.
You can avoid O(n2) complexity by building a map of id to dateOfDeath:
Map<Integer, Date> deaths = secondList.stream()
.collect(toMap(p -> p.id, p -> p.dateOfDeath));
fullPeople.stream()
.filter(p -> deaths.containsKey(p.id))
.forEach(p -> p.dateOfDeath = deaths.get(p.id));
Or, if you want to avoid mutating existing people:
List<People> mergedPeople = fullPeople.stream()
.map(p -> deaths.containsKey(p.id)
? new People(p.id, p.name, deaths.get(p.id))
: p
).collect(toList());
You could do it like this:
List<People> persons =
names.stream()
.map(p -> new People(p.id, p.name, dates.stream()
.filter(pd -> pd.id == p.id)
.map(pd -> pd.dateOfDeath)
.findFirst()
.orElse(null))
)
.collect(Collectors.toList());
where names is the list of persons having the names and dates is the list of persons having the date of death. This assumes that the People class has a 3 argument constructor taking the id, name and date of death.
For all person with names, the person having the same id is looked up in the other list with filter and we map the result to the dateOfDeath. If a match is found, the date is returned, otherwise, orElse is invoked and null is returned.
Note that this will not merge any person that is present in the dates list but not in the names list.
Sample code:
List<People> names = new ArrayList<>();
List<People> dates = new ArrayList<>();
names.add(new People(1, "Name 1", null));
names.add(new People(2, "Name 2", null));
dates.add(new People(1, null, new Date()));
dates.add(new People(3, null, new Date()));
List<People> peoples = codeFromAbove();
System.out.println(peoples);
// prints
// [[id=1, name=Name 1, date=Sun Oct 18 19:48:58 CEST 2015],
// [id=2, name=Name 2, date=null]]
with:
class People {
public int id;
public String name;
public Date dateOfDeath;
public People(int id, String name, Date dateOfDeath) {
this.id = id;
this.name = name;
this.dateOfDeath = dateOfDeath;
}
#Override
public String toString() {
return "[id="+id+", name="+name+", date="+dateOfDeath+"]";
}
}

Categories

Resources