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
Related
The idea is to return the second youngest persons from an array Person type.
I have this but don't know if it's the best solution, how do you see that approach? or what would be another good idea to resolve this. Thanks any suggestions
this is the class:
public class Persona {
private String nombre;
private Integer edad;
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public Integer getEdad() {
return edad;
}
public void setEdad(Integer edad) {
this.edad = edad;
}
public Persona(String nombre, Integer edad) {
this.nombre = nombre;
this.edad = edad;
}
}
and the method to make the return also a list with example values (I just sorted the list and then using a filter quit the people with the first value and then filter with the new first value to save only that values)
public static void main(String[] args) {
ArrayList<Persona> listaPrueba = new ArrayList<Persona>();
listaPrueba.add(new Persona("Patricia", 21));
listaPrueba.add(new Persona("Juan", 22));
listaPrueba.add(new Persona( "Ana", 45));
listaPrueba.add(new Persona("John", 22));
listaPrueba.add(new Persona( "Max", 21));
listaPrueba.add(new Persona("Peter", 50));
segundoMayor(listaPrueba).stream().forEach(item -> System.out.println(item.getNombre() + " " +item.getEdad()));
}
public static List<Persona> segundoMayor(ArrayList<Persona> lista){
lista.sort(Comparator.comparing(Persona::getEdad));
List<Persona> listaFiltrada = lista.stream().filter(item -> !item.getEdad().equals(lista.get(0).getEdad())).collect(Collectors.toList());
return listaFiltrada.stream().filter(item -> item.getEdad().equals(listaFiltrada.get(0).getEdad())).collect(Collectors.toList());
}
the output should be:
Juan 22
John 22
I don't have my Java IDE setup at the moment, so consider this to be pseudo code (and write your own unit tests to check for bugs). But my solution would be to iterate the list of Persona objects to find the second-youngest age, then iterate the list again to find all objects with that age. Then you can add matching Persona objects to a collection, and/or print them out to the console.
int youngestAge = 1000, secondYoungestAge = 1000;
for (Persona p : listaPrueba) {
int edad = p.getEdad();
if (edad < youngestAge) {
secondYoungestAge = youngestAge;
youngestAge = edad;
} else if (edad < secondYoungestAge) {
secondYoungestAge = edad;
}
}
if (secondYoungestAge == 1000) {
throw new NoSuchElementException("No matching Persona!");
}
List<Persona> secondYoungestPersona = new ArrayList<>();
for (Persona p : listaPrueba) {
int edad = p.getEdad();
if (edad == secondYoungestAge) {
secondYoungestPersona.add(p);
// AND/OR write the name and age to console
System.out.print(p.getNombre() + " " + edad + " ");
}
}
This ought to be more efficient than using a TreeMap (sorted by age) or sorting the entire ArrayList (because you don't care about the order of the rest of the items, you just care about the second-youngest items). But if you need to be sure, then carefully write a JMH benchmark to test the three approaches.
Very intresting.
I wrote a solution with streams. Perhaps, it doesn't have the best performance but it has a high readability.
public static List<Persona> segundoMayor(final ArrayList<Persona> lista) {
// first, sort it by age
final var sorted = lista
.stream()
.sorted(Comparator.comparing(Persona::getEdad))
.toList();
// now, find the second youngest age
final int secondYoungestAge = sorted
.stream()
.map(Persona::getEdad)
.distinct()
.skip(1)
.findFirst()
.orElse(0);
// at the end: only take the youngest
// and second youngest with takeWhile (so we can reduce the iterations)
// and filter them for the second youngest age
return sorted
.stream()
.takeWhile(persona -> persona.getEdad() <= secondYoungestAge)
.filter(persona -> persona.getEdad() == secondYoungestAge)
.toList();
}
I prefer to find the list in a single parse of the array like this
public static List<Persona> segundoMayor(List<Persona> lista){
lista.sort(Comparator.comparing(Persona::getEdad));
Persona first = lista.get(0), second = null;
List<Persona> secondRankList = new ArrayList<>();
for(Persona persona: lista) {
if(second == null && persona.getEdad() > first.getEdad()) {
second = persona;
}
if(second != null && second.getEdad().equals(persona.getEdad())) {
secondRankList.add(persona);
} else if(second != null && !second.getEdad().equals(persona.getEdad())){
break;
}
}
return secondRankList;
}
A kind of mostly functional approach:
private static List<Persona> segundoMayor(List<Persona> lista){
return lista
.stream()
.collect(Collectors.groupingBy(Persona::getEdad))
.entrySet()
.stream()
.sorted(Comparator.comparing(Entry::getKey))
.map(Entry::getValue)
.skip(1)
.findFirst()
.orElse(Collections.emptyList()); // or orElse(List.of());
// or orElsehrow();
Basically:
group by age into a Map<Integer, List<Persona>>
sort the groups by age
extract the Persona list for each age
skip the first list (so the second youngest is next)
get the first list left on the stream
if none is available, result in an empty list
Replace orElse with orElseThrow to throw a NoSuchElementException instead of returning an empty list, in the case that the input list does not contain at least two Persona with different ages.
This probably is not be the most efficient way: creating maps for all ages, but, on the other side, sorting less elements (one entry per age).
Normally, one would use a heap to efficiently find the n smallest elements.
Create a heap with the reverse of the desired order (a max heap, in this case).
For each element,
add the element to the heap;
if the heap has more than n items, remove the top of the heap.
With this approach, if there are more than n ties for smallest element, you get the first n results that you looked at.
This case is a little different because you want to find an unconstrained number of elements that tie for the nth smallest attribute. That requires some extra steps.
First, you can build a general-purpose "top hits" collector for use on with streams:
/**
* #param limit the maximum number of items to collect
* #param order a function to establish item ordering
* #param <T> the type of items to collect
* #return a collector for a limited number of items ordered <em>last</em>
*/
public static <T> Collector<T, ?, List<T>> top(int limit, Comparator<? super T> order) {
Objects.requireNonNull(order);
Supplier<Queue<T>> supplier = () -> new PriorityQueue<>(order);
BiConsumer<Queue<T>, T> accumulator = (q, e) -> {
q.add(e);
if (q.size() > limit) q.remove();
};
BinaryOperator<Queue<T>> combiner = (q1, q2) -> {
q2.forEach(e -> accumulator.accept(q1, e));
return q1;
};
Function<Queue<T>, List<T>> finisher = q -> {
List<T> list = new ArrayList<>(q.size());
while (!q.isEmpty()) list.add(q.remove());
Collections.reverse(list);
return list;
};
return Collector.of(supplier, accumulator, combiner, finisher);
}
/**
* #param limit the maximum number of items to collect
* #param order a function to establish item ordering
* #param <T> the type of items to collect
* #return a collector for a limited number of items ordered <em>first</em>
*/
public static <T> Collector<T, ?, List<T>> bottom(int limit, Comparator<? super T> order) {
return top(limit, order.reversed());
}
Now you can apply it to your case, with some extra processing. First, the age of each person is filtered, considering only distinct, non-null values. The second-lowest age, if it exists, is then used as a filter in a second pass over the input.
List<Persona> secondYoungest(Collection<Persona> personas) {
List<Integer> youngest = personas.stream()
.map(Persona::getEdad)
.filter(Objects::nonNull)
.distinct()
.collect(bottom(2, Integer::compare));
if (youngest.size() != 2) {
throw new IllegalArgumentException("Second youngest age is undefined");
}
Integer age = youngest.get(1);
return personas.stream()
.filter(p -> age.equals(p.getEdad()))
.collect(Collectors.toList());
}
I have an pojo class like the one below
public CategoryModel {
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
}
I have an arraylist created like the one below.
List<CategoryModel> variantCategoryModelList = new ArrayList<>();
CategoryModel cat1= new CategoryModel();
cat1.setName(TEST1);
CategoryModel cat2= new CategoryModel();
cat2.setName(TEST1);
list.add(cat1);
list.add(cat2);
I have to check, if the value "TEST1" & "TEST2" present in the list and return "true" if both values present in the "list" and I tried something like the one below, though my "list" has both the values, its returning false.Could you please help me check what I am doing wrong btw I am using JDK 11.
final Optional<CategoryModel> optionalData = variantCategoryModelList.stream().
filter(valueData -> TEST1.equalsIgnoreCase(valueData.getName())
&& TEST2.equalsIgnoreCase(valueData.getName())).findFirst();
if(optionalData.isPresent()){
return true;
}
You could map your CategoryModel to name and collect to list of strings and call List.containsAll :
return variantCategoryModelList.stream()
.map(CategoryModel::getName)
.collect(Collectors.toList())
.containsAll(Arrays.asList("TEST1","TEST2"));
Set would be a more natural (and faster) data structure:
return variantCategoryModelList.stream()
.map(CategoryModel::getName)
.collect(Collectors.toSet())
.containsAll(Set.of("TEST1", "TEST2"));
Your problem was and (&&) instead of or.
So:
Set<String> soughtNames = Set.of("TEST1", "TEST2");
return variantCategoryModelList.stream()
.filter(cm -> soughtNames.contains(cm.getName()))
.distinct()
.count() == 2L;
As #fps commented, distinct() is needed on a list to prevent ["Test1", "Test1"] to be accepted, or ["Test1", "Test1", "Test2"] failing.
This is obviously inefficient as it will - having found 2 entries -, still walk to the end.
You want:
Set<String> soughtNames = Set.of("TEST1", "TEST2");
return soughtNames.stream()
.allMatch(soughtName ->
variantCategoryModelList.stream()
.anyMatch(cm -> soughtName.equals(cm.getName()));
Or a bit retro-style:
return
variantCategoryModelList.stream()
.anyMatch(cm -> "TEST1".equals(cm.getName())) &&
variantCategoryModelList.stream()
.anyMatch(cm -> "TEST2".equals(cm.getName()));
Here's a way to do it:
Set<String> set = Set.of("TEST1", "TEST2");
boolean result = list.stream()
.filter(cat -> set.contains(cat.getName().toUpperCase())
.distinct()
.limit(2)
.count() == 2L;
This streams the list of categories, then keeps only those categories whose name is either TEST1 or TEST2. We then remove duplicates and stop after we've found two (already distinct) category names. This ensures short-circuiting. Finally, we check if we have exactly two elements at the end.
I have a list of object List(Item) from service.
I want to convert the List(Item) list to DB table ItemEntity object using Java 8 stream. In that List(Item) Item-> have VarietyList, if the VarietyList(itemVariety) have data then need to create that many ItemEntity object. if the VarietyList(itemVariety) is empty then need to create one ItemEntity object.
Below Java for each code works perfectly, i need this code in JAVA 8 Stream function.
List<Item> itemList = from some services;
List<ItemEntity> itemEnt= new ArrayList();
for (Item item : itemsList) {
if (CollectionUtils.isNotEmpty(item.getVarietyList())) {
for (ItemVariety itemVariety : item.getVarietyList()) {
itemEnt.add(loadItemData(item , itemVariety));
}
} else {
itemEnt.add(loadItemData(item, null));
}
}
private ItemEntity loadItemData(Item itemType, ItemVariety itemVariety) {
ItemEntity itemEntity = new ItemEntity();
itemEntity.setName(itemType.getName());
if (itemVariety != null) {
itemEntity.setVarietyName(itemVariety .getName());
}
return cropEntity;
}
Please suggest me the best way in JAVA8
Generally speaking, a for loop over a list of instances of class A that creates an instance of a class B for each item in the original list can be turned into
List<A> as = .... ;
List<B> = as.stream().map(a -> createB(a)).collect(Collectors.toList());
where
private B createB(A a) {
//returns new B() based on given a
}
When for each item in the original list you can create more than one instance of B, you for loop can be turned into
List<A> as = .... ;
List<B> = as
.stream()
.flatMap(a -> CreateBs(a))
.collect(Collectors.toList());
where
private Stream<B> createBs(A a) {
// returns Stream<B> based on a
}
You are in the second scenario, so you createBs(A) is
private Stream<ItemEntity> createItemEntity(Item item) {
return item.getVarietyList().isEmpty() ?
Stream.of(loadItemData(item, null)) :
item.getVarietyList().stream(x -> mapItemVarietyToItemEntity(item, x));
}
private ItemEntity mapItemVarietyToItemEntity(Item item, ItemVariety variety) {
loadItemData(item, variety);
}
I can't run the code above at the moment, but I hope it can help you toward the solution you're searching for.
If you really wants to use the stream api you could do it something like below, but IMO it's much more readable in plain old java.
List<ItemEntity> itemEnt = itemsList.stream()
.flatMap(item -> {
return CollectionUtils.isNotEmpty(item.getVarietyList())
? item.getVarietyList.stream().map(variety -> loadItemData(item, variety))
: Stream.of(loadItemData(item, null))
}).collect(Collectors.toList());
private ItemEntity loadItemData(Item itemType, ItemVariety itemVariety) {
ItemEntity itemEntity = new ItemEntity();
itemEntity.setName(itemType.getName());
if (itemVariety != null) {
itemEntity.setVarietyName(itemVariety .getName());
}
return cropEntity;
}
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()));
Is there a 'best practice' for mutating elements within a Stream? I'm specifically referring to elements within the stream pipeline, not outside of it.
For example, consider the case where I want to get a list of Users, set a default value for a null property and print it to the console.
Assuming the User class:
class User {
String name;
static User next(int i) {
User u = new User();
if (i % 3 != 0) {
u.name = "user " + i;
}
return u;
}
}
In java 7 it'd be something along the lines of:
for (int i = 0; i < 7; i++) {
User user = User.next(i);
if(user.name == null) {
user.name = "defaultName";
}
System.out.println(user.name);
}
In java 8 it would seem like I'd use .map() and return a reference to the mutated object:
IntStream.range(0, 7)
.mapToObj(User::next)
.map(user -> {
if (user.name == null) {
user.name = "defaultName";
}
return user;
})
//other non-terminal operations
//before a terminal such as .forEach or .collect
.forEach(it -> System.out.println(it.name));
Is there a better way to achieve this? Perhaps using .filter() to handle the null mutation and then concat the unfiltered stream and the filtered stream? Some clever use of Optional? The goal being the ability to use other non-terminal operations before the terminal .forEach().
In the 'spirit' of streams I'm trying to do this without intermediary collections and simple 'pure' operations that don't depend on side effects outside the pipeline.
Edit: The official Stream java doc states 'A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care.' Given that this would be a non-interfering operation, what specifically makes it dangerous? The examples I've seen reach outside the pipeline, which is clearly sketchy.
Don't mutate the object, map to the name directly:
IntStream.range(0, 7)
.mapToObj(User::next)
.map(user -> user.name)
.map(name -> name == null ? "defaultName" : name)
.forEach(System.out::println);
It sounds like you're looking for peek:
.peek(user -> {
if (user.name == null) {
user.name = "defaultName";
}
})
...though it's not clear that your operation actually requires modifying the stream elements instead of just passing through the field you want:
.map(user -> (user.name == null) ? "defaultName" : user.name)
It would seem that Streams can't handle this in one pipeline. The 'best practice' would be to create multiple streams:
List<User> users = IntStream.range(0, 7)
.mapToObj(User::next)
.collect(Collectors.toList());
users.stream()
.filter(it -> it.name == null)
.forEach(it -> it.name = "defaultValue");
users.stream()
//other non-terminal operations
//before terminal operation
.forEach(it -> System.out.println(it.name));