Seems that this is what you are after:
customers.stream()
.filter(c -> c.getTrack() != null)
.map(c -> c.getTrack().getId())
.flatMap(id -> tracks.stream().filter(track -> Long.valueOf(track.getId()).equals(id)))
.collect(Collectors.toSet());
Just note that for each id you are iterating the entire list of tracks; this has O(n*m) complexity. This is generally see as bad and you can improve it.
To make it better you would first create a HashSet of ids from Customer; having that HashSet you can now call contains on it with the ids you are interested in, since contains has a time complexity of O(1) (it's really called amortized complexity of O(1)). So now your complexity becomes O(n) + O(1), but since O(1) is a constant, it's really O(n) - much better that what you had before. In code:
Set<Long> set = customers.stream()
.filter(c -> c.getTrack() != null)
.map(c -> c.getTrack().getId())
.collect(Collectors.toSet());
Set<CusomerTrack> tracksToSave = tracks.stream()
.filter(track -> set.contains(track.getId())
.collect(Collectors.toSet()));
Answer from Eugene on Stack OverflowSeems that this is what you are after:
customers.stream()
.filter(c -> c.getTrack() != null)
.map(c -> c.getTrack().getId())
.flatMap(id -> tracks.stream().filter(track -> Long.valueOf(track.getId()).equals(id)))
.collect(Collectors.toSet());
Just note that for each id you are iterating the entire list of tracks; this has O(n*m) complexity. This is generally see as bad and you can improve it.
To make it better you would first create a HashSet of ids from Customer; having that HashSet you can now call contains on it with the ids you are interested in, since contains has a time complexity of O(1) (it's really called amortized complexity of O(1)). So now your complexity becomes O(n) + O(1), but since O(1) is a constant, it's really O(n) - much better that what you had before. In code:
Set<Long> set = customers.stream()
.filter(c -> c.getTrack() != null)
.map(c -> c.getTrack().getId())
.collect(Collectors.toSet());
Set<CusomerTrack> tracksToSave = tracks.stream()
.filter(track -> set.contains(track.getId())
.collect(Collectors.toSet()));
An additional way favoring method reference usage :
Set<Track> tracks =
customers.stream()
.map(Customer::getTrack) // customer to track
.filter(Objects::nonNull) // keep non null track
.map(Track::getId) // track to trackId
.flatMap(trackId -> tracks.stream() // collect tracks matching with trackId
.filter(t-> Long.valueOf(t.getId()).equals(trackId))
)
.collect(toSet());
Can we collect two lists from Java 8 streams?
You cannot but if you have a way to group elements of the Lists according to a condition.
In this case you could for example use Collectors.groupingBy() that will return Map<Foo, List<Bar>> where the values of the Map are the two List.
Note that in your case you don't need stream to do only filter.
Filter the invalid list with removeIf() and add all element of that in the first list :
invalidList.removeIf(o -> conditionToRemove);
goodList.addAll(invalidList);
If you don't want to change the state of goodList you can do a shallow copy of that :
invalidList.removeIf(o -> conditionToRemove);
List<Foo> terminalList = new ArrayList<>(goodList);
terminalList.addAll(invalidList);
One way using Java 8 streams API is to use the Collectors.partitioningBy collector.
Consider I have a List of String elements: the input list has strings with various lengths; only strings with length 3 are valid.
List<String> input = Arrays.asList("one", "two", "three", "four", "five");
Map<Boolean, List<String>> map =
input.collect(Collectors.partitioningBy(s -> s.length() == 3));
System.out.println(map); // {false=[three, four, five], true=[one, two]}
List<String> valid = map.get(true); // [one, two]
List<String> invalid = map.get(false); // [three, four, five]
The resulting Map has only two records; one with valid and the other with not-valid input list elements. The map record with key=true has the valid string as a List: one, two. The other is a key=false and the not-valid strings: three, four, five.
Note that Collectors.partitioningBy produces always two records in the resulting map irrespective of the existence of valid or not-valid values.
There are still some things unclear. Like why you are insisting on creating that SimpleDateFormat instance that you are not using anywhere. Or whether there is a significance in calling getCreatedBy() multiple times. Assuming that it is not necessary, the following code is equivalent
baseManager.findObjectsByNamedQuery(Class1.class, "Class1.findAll", new Object[]{})
.stream()
.map(item -> item.getCreatedBy())
.map(createdBy -> Json.createObjectBuilder()
.add("createdBy", createdBy)
.add("phone", baseManager.findObjectsByNamedQuery(
Class2.class, "Class2.findByCreatedBy", new Object[] {createdBy})
.get(0).getPhone())
)
.forEach(jpb -> groupsBuilder.add(jpb));
Itโs still unclear to me whether (or why) findObjectsByNamedQuery is not a varargs method. It would be quite natural to be a varargs method, not requiring these explicit new Object[] { โฆ } allocations.
With pure Java8 Stream API:
public void convertItemsToJSon(List<Item> items) {
...
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Map<Item, List<Class2>> sqlItems = items
.stream()
.collect(Collectors.toMap(Function.identity(), (item) -> baseManager.findObjectsByNamedQuery(Class2.class, "Class2.findByCratedBy", new Object[]{item.getCreatedBy()})));
sqlItems.entrySet()
.stream()
.map(sqlItem -> buildJson(sqlItem.getKey(), sqlItem.getValue()))
.forEach(groupsBuilder::add);
...
}
private JsonObjectBuilder buildJson(Item item, List<Class2> class2Items) {
return Json.createObjectBuilder().add("createdBy", item.getCreatedBy());
}
With StreamEx library
public void convertItemsToJSonStreamEx(List<Item> items) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
...
StreamEx.of(items)
.cross(item -> baseManager.findObjectsByNamedQuery(Class2.class, "Class2.findByCratedBy", new Object[]{item.getCreatedBy()}).stream())
.mapKeys(item -> Json.createObjectBuilder().add("createdBy", item.getCreatedBy()))
.mapKeyValue(this::addField)
.forEach(groupsBuilder::add);
...
}
private JsonObjectBuilder addField(JsonObjectBuilder json, Class2 class2) {
// You logic how to convert class2 to field in JSON
return json;
}
The easiest way is to create an IntStream to generate the indices, and then map each index to the String you want to create.
IntStream.range(0, Math.min(names.size(), ages.size()))
.mapToObj(i -> names.get(i)+":"+ages.get(i))
.forEach(System.out::println);
Also you might be interested in this SO question Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip), because this is the kind of functionality you're asking for.
My StreamEx library has some syntactic sugar for this case:
StreamEx.zip(names, ages, (name, age) -> name+":"+age).forEach(System.out::println)
Basically inside it's the same as in accepted answer. The only difference is that IllegalArgumentException will be thrown if size of lists differs.
You need to work on both lists in parallel so I don't think you can stream the lists themselves. However you can stream the indices and work on that:
IntStream.range(0, aList.size())
.parallel()
.forEach(i -> {
if (i < bList.size()) aList.get(i).doSomethingWith(bList.get(i));
else aList.get(i).doSomething();
});
Just because Stream is new it does not mean that you should forget about all other tools Java provides. Even using these good old tools becomes smoother using Java 8:
List<A> aList;
List<B> bList; // aList is larger than bList
ExecutorService exec = Executors.newCachedThreadPool();
int a=aList.size(), b=bList.size();
assert a>b;
Future<?> f1=exec.submit(()->IntStream.range(0, b)
.parallel().forEach(i->aList.get(i).doSomethingWith(bList.get(i)))
);
Future<?> f2=exec.submit(()->aList.subList(b, a)
.stream().parallel().forEach(A::doSomething)
);
f1.get();
f2.get();
exec.shutdown();