Java 8 peek vs map - java

I have following case: there is a list of objects - ProductData which contains several fields:
public class ProductData
{
....
private String name;
private String xref;
//getters
//setters
}
and there is API which returns list of following objects:
public class RatingTableRow
{
private String planName;
private String planXref;
private int fromAge;
private int toAge;
private int ratingRegion;
//constructor
//getters
//setters
}
but it returns objects with empty plan name field because it's not allowed during extraction of this object. I need to link product data with RatingTableRow by the xref in order to set plan name into the RatingTableRow because I need to use this object later so I created following code to do that:
Map<String, ProductData> productByXref = plans.stream()
.collect(toMap(ProductData::getInternalCode, Function.identity()));
return getRatingTableRows(...).stream
.filter(ratingRow -> productByXref.containsKey(ratingRow.getPlanXref()))
.peek(row -> {
ProductData product = productByXref.get(row.getPlanXref());
row.setPlanName(product.getName());
})....;
I know that java docs say that peek doesn't fit these needs but want to get your suggestions on how to make this task in more correct way.

There is a reason peek is documented to be mainly for debugging purposes.
Something that ends up being processed inside peek might not be eligible for the terminal operation at all and streams are executed only by a terminal operation.
Suppose a trivial example first:
List<Integer> list = new ArrayList<>();
List<Integer> result = Stream.of(1, 2, 3, 4)
.peek(x -> list.add(x))
.map(x -> x * 2)
.collect(Collectors.toList());
System.out.println(list);
System.out.println(result);
Everything looks fine right? Because peek will run for all elements in this case. But what happens when you add a filter (and forget about what peek did):
.peek(x -> list.add(x))
.map(x -> x * 2)
.filter(x -> x > 8) // you have inserted a filter here
You are executing peek for every element, but collecting none. You sure you want that?
This can get even trickier:
long howMany = Stream.of(1, 2, 3, 4)
.peek(x -> list.add(x))
.count();
System.out.println(list);
System.out.println(howMany);
In java-8 the list is populated, but in jdk-9 peek is not called at all. Since you are not using filter or flatmap you are not modifying the size of the Stream and count only needs it's size; thus peek is not called at all. Thus relying on peek is a very bad strategy.

Related

indexing <Stream> data Java

need some help with indexing Stream data in Java. The context is that we need to manually set index for document that is embedded to other document (tldr; the output needs to be Stream in this method)
return Stream.concat(firstStream, secondStream) <- these need to be indexed
.sorted(// sorted using Comparator)
.forEach? .map? // the class has index field with getter and setter so I think need to do `setIndex(i)` but wasnt sure where to get 'i'
Any advice would be greatly appreciated!
If you can construct your streams yourself from lists, use IntStream of indices rather than Stream of objects.
IntStream.range(0, firstList.size()).forEach(i -> firstList.get(i).setIndex(i));
int offsetForSecondList = firstList.size();
IntStream.range(0, secondList.size())
.forEach(i -> secondList.get(i).setIndex(offsetForSecondList + i));
I have not tried to compile the code, so forgive any typo.
Otherwise your AtomicReference approach works too.
Assuming you have a class MyObject:
class MyObject{
int index;
String name;
//getters,setters,cons, toString...
}
Something like below may be a starting point:
public static Stream<MyObject> fooBar(){
//just for example, inorder to get the streams to be concatnated
List<MyObject> first = List.of(new MyObject("foo"),new MyObject("foo"),new MyObject("foo"));
List<MyObject> second = List.of(new MyObject("bar"),new MyObject("bar"),new MyObject("bar"));
AtomicInteger ai = new AtomicInteger(0);
return Stream.concat(first.stream(), second.stream())
.peek(myo -> myo.setIndex(ai.getAndIncrement()));
}

Change all position of elements in List using Java 8

I am a fresher in Java. I just want to change all position of elements in List using Java 8. For example:
public static List<Integer> change(List<Integer> data){
for (int i = 0; i < data.size(); i++) {
Collections.swap(data, i, data.size()-1-i);
}
return data;
}
this is a simple example, it just reverse the list. let me explain more :
i have a list {1,3,4,5,3,7,8,9} >> and i want to change to {3,1,5,4,7,3,9,8}.
But I want to do it in Java 8(Stream). My problem is:
1) How can I get the next element in a stream ?
2) Can I put my own method when I traverse with stream? (for example, I can write my own swap method?) - Like : list.foreach(doSomething());
3) And how can i return result when using foreach?
Try this :
List<String> list = Arrays.asList("A", "B", "C", "D", "1", "2", "3");
// shuffle or randomize
Collections.shuffle(list);
If you want to shuffle in such a way that no item is where it was before then you can simply use
Collections.reverse(list);
and then swap the 1st and the middle item if the length of the list is odd. This way all your items will be at a different index then they were before.
If using streams is required
public static List<Integer> change2( List<Integer> data ) {
return data.stream().collect( LinkedList::new, LinkedList::offerFirst, LinkedList::addAll );
}
The stream is reversed using a LinkedList to collect data ; offerFirst inserting next element in front.
Or with a custom function
final Random random = new Random();
return data.stream().collect(
LinkedList::new,
( list, elem ) -> {
int size = list.size();
list.add( random.nextInt( size + 1 ), elem );
},
LinkedList::addAll
);
You can use the "forEach" that Streams provide.
by saying "a->" you map each element of the data list to the "a" where is every element of the list
This is the best solution if you dont want just to shuffle them but do something more with each one of the elements of the list.
public static List<Integer> change(List<Integer> data){
return data.stream().forEach(a-> "whatever you want to do with them").collect(Collectors.toList());
}
One way is to drive your list through an IntStream and swap elements within forEach:
public static List<Integer> change(List<Integer> data) {
IntStream.iterate(0, i -> i + 2)
.limit(data.size() / 2)
.forEach(i -> Collections.swap(data, i, i + 1));
return data;
}
You have to use Stream.limit so that the stream is not infinite.
However, using Collections.swap to mutate the list within forEach is discouraged because it has side-effects. If the IntStream were declared to be parallel, the code above would be broken. Ideally, the functions and consumers used in a stream should be stateless and without side-effects, as recommended in the java.util.stream package documentation.
Here's another way to achieve what you want, while also adhering to recommendations:
public static List<Integer> changeCorrectly(List<Integer> data) {
return IntStream.iterate(0, i -> i + 2)
.limit(data.size() / 2)
.flatMap(i -> IntStream.of(data.get(i + 1), data.get(i)))
.boxed()
.collect(Collectors.toList());
}
Note: I'm using IntStream.flatMap to return an IntStream of swapped pairs, which has the effect to unbox Integer elements from the data list. Immediately, I'm boxing the IntStream to a Stream<Integer>.
The alternative to avoid this unboxing/boxing stuff is:
public static List<Integer> changeCorrectly(List<Integer> data) {
return IntStream.iterate(0, i -> i + 2)
.limit(data.size() / 2)
.mapToObj(i -> Stream.of(data.get(i + 1), data.get(i)))
.flatMap(Function.identity())
.collect(Collectors.toList());
}
Final comment: this only works when the data list has an even size. Handling the addition of the last element when the data list has an odd size is left as an exercise.

Java: Get different entries between two lists Map<String, Object>

I have two lists Map. I want to get different entries between them base on some fields of Object. I've created a method as below:
private List<Map<String,Object>> GetDifferentEntries(List<Map<String,Object>> setA, List<Map<String,Object>> setB) {
List<Map<String,Object>> tmp = new ArrayList<Map<String,Object>>(setA);
for(Map<String,Object> a : tmp){
for(Map<String,Object> b: setB){
if(a.get("ID").equals(b.get("ID")) && a.get("ID1").equals(b.get("ID1")) ){
setA.remove(a);
}
} }return setA;}
Can I write above method again by using Stream class in java 8?
I have an example:
List1={ [ID=1, actor="A", ID1 = 1, film="F"],
[ID=2, actor="B", ID1 = 1, film="F"],
[ID=3, actor="C", ID1 = 1, film="F"]}
List2={ [ID=1, director="A", ID1 = 1, film="F"],
[ID=5, director="E", ID1 = 1, film="F"]}
Result = { [ID=2, actor="B", ID1 = 1, film="F"],
[ID=3, actor="C", ID1 = 1, film="F"] }
I'm not that familiar with Java 8 streams yet but your method could be optimized (as already stated in my comment) and thus be easier to be refactored to streams.
First you should not use nested loops but rather 2 separate loops. Additionally you might want to use a special key object that provides for ID and ID1 as well as appropriate implementations of hashCode() and equals(). Example:
class Key {
String id;
String id1;
int hashCode() { ... }
boolean equals(Object o) { ... }
}
Then build a LinkedHashMap for the first list (you could use a HashMap but that way you retain the element order):
Map<Key, Map<String, Object>> mapA = new LinkedHashMap<>();
for( for(Map<String,Object> a : setA){
mapA.put( new Key( a.get("ID"), a.get("ID1") ), a );
}
Note that this assumes your lists don't contain duplicates, if they do, you need to do it slightly differently (but I'll leave that to you).
Now that you've got a map for setA you can eliminate the elements that are contained in setB:
for(Map<String,Object> b: setB){
//if the key is not present nothing will be removed
mapA.remove( new Key( b.get("ID"), b.get("ID1") ) );
}
Finally built a list out of mapA:
List<Map<String,Object>> prunedSetA = new ArrayList<>( mapA.values() );
As you can see, you now have 2 loops and at least the second loop might make use of streams. You could do that for the first loop as well but you might lose the ordering. That's not a problem if order isn't important or you re-sort the list at the end.
First of all you should be careful as the method has a side effect on the given parameters. You should NOT modify input parameters (here setA).
Your copy is not deep enough. You created a temporary list. That is ok. But you do not copy the entries as this will cause the side effect of modified input parameters as they come by reference and not by value. The caller cannot trust the input parameters to be the same after he passes them into your method. If you want to avoid the side effect you have also to copy the Maps. Beside that you should only remove elements from the copy.
Next your are only handling the case for the elements that are in SetA. Elements in SetB that are not in SetA ar not recognized. Is this correct?
private List<Map<String, Object>> getDifferentEntries(List<Map<String, Object>> setA, List<Map<String, Object>> setB) {
List<Map<String, Object>> result = makeDeepCopyOf(setA);
setA.stream().forEach((entryA) -> {
setB.stream().forEach((entryB) -> {
if (entryA.get("ID").equals(entryB.get("ID")) && entryA.get("ID1").equals(entryB.get("ID1"))) {
result.remove(entryA);
}
});
});
return result;
}
To go with parallelism you can use parallel streams. It will load your CPUs without caring about thread handling:
setA.stream().parallel().forEach(...
setB.stream().parallel().forEach(...
Then you have to extract the statement that removes an element to a synchronized method as this method will be called asynchronously.
private synchronized removeElementFrom(List<Map<String, Object>> list, Map<String, Object> entry) {
list.remove(entry);
}
OR you consider to wrap the copy of setA into a suitable synchronized datastructure:
List<Map<String, Object>> result = Collections.synchronizedList(makeDeepCopyOf(setA));

Peek the next Element in a stream

Is there a way to peek the next element in a stream? The idea rose from a stream of a list of objects, where two following objects should be compared (to smooth some diffs, but that shouldn't matter here). As an old for loop this would look like:
List<Car> autobahn = getCars();
for (int i = 0; i < autobahn.size()-1; i++) {
if(autobahn.get(i).speed>autobahn.get(i+1).speed)
autobahn.get(i).honk();
}
The best way so far as stream would be:
autobahn.stream()
.limit(autobahn.size()-1)
.filter(car -> car.speed < autobahn.get(autobahn.indexOf(car)+1).speed)
.forEach(car -> car.honk());
The main-problem with this solution is the indexOf method, since there might be twice the same car on the autobahn. A better solution would be some way to peek the next (or the one before) element (with an helping class, this might be even possible, but looks horrible)
BoxedCar boxedCar = new BoxedCar(autobahn.get(0));
autobahn.stream()
.skip(1)
.filter(car -> boxedCar.setContent(car))
.forEach(car -> car.winTheRace());
with helperclass
class BoxedCar {
Car content;
BoxedCar(Car content) {
this.content = content;
}
boolean setContent(Car content) {
double speed = this.content.speed;
this.content = content;
return content.speed > speed;
}
}
or to divert the Stream<Car> into a kind of Stream<(Car,Car)> with the second stream somehow created by the first one (this sounds also awful and here I have no idea, how this would look).
Is there a nice way to do this with streams, or are we stuck to the for-loop?
Sticking with the for loop wouldn't be a bad idea. The Stream API isn't designed for this type of requirement. You can refer to that answer for more insight.
However, a simple way to do this using the Stream API would be to use a Stream over the indexes of your list, supposing that you have random access.
IntStream.range(0, autobahn.size() - 1)
.filter(i -> autobahn.get(i).speed > autobahn.get(i+1).speed)
.forEach(i -> autobahn.get(i).honk());
Note that this highly resemble the for loop.
Using my free StreamEx library:
StreamEx.of(autobahn)
.pairMap((car, nextCar) -> car.speed < nextCar.speed ? car : null)
.nonNull()
.forEach(Car::honk);
Here non-standard pairMap operation is used which can map the adjacent pair of elements to the single element. This works for any stream source (not only random-access indexed list) and can be parallelized pretty well.
what about using an IntStream instead of a loop:
IntStream.range(0, autobahn.size() - 1)
.filter(i -> autobahn.get(i).speed < autobahn.get(i + 1).speed)
.forEach(i -> autobahn.get(i).honk());

Modifying Objects within stream in Java8 while iterating

In Java8 streams, am I allowed to modify/update objects within?
For eg. List<User> users:
users.stream().forEach(u -> u.setProperty("value"))
Yes, you can modify state of objects inside your stream, but most often you should avoid modifying state of source of stream. From non-interference section of stream package documentation we can read that:
For most data sources, preventing interference means ensuring that the data source is not modified at all during the execution of the stream pipeline. The notable exception to this are streams whose sources are concurrent collections, which are specifically designed to handle concurrent modification. Concurrent stream sources are those whose Spliterator reports the CONCURRENT characteristic.
So this is OK
List<User> users = getUsers();
users.stream().forEach(u -> u.setProperty(value));
// ^ ^^^^^^^^^^^^^
// \__/
but this in most cases is not
users.stream().forEach(u -> users.remove(u));
//^^^^^ ^^^^^^^^^^^^
// \_____________________/
and may throw ConcurrentModificationException or even other unexpected exceptions like NPE:
List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList());
list.stream()
.filter(i -> i > 5)
.forEach(i -> list.remove(i)); //throws NullPointerException
The functional way would imho be:
import static java.util.stream.Collectors.toList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateTestRun {
public static void main(String[] args) {
List<String> lines = Arrays.asList("a", "b", "c");
System.out.println(lines); // [a, b, c]
Predicate<? super String> predicate = value -> "b".equals(value);
lines = lines.stream().filter(predicate.negate()).collect(toList());
System.out.println(lines); // [a, c]
}
}
In this solution the original list is not modified, but should contain your expected result in a new list that is accessible under the same variable as the old one
To do structural modification on the source of the stream, as Pshemo mentioned in his answer, one solution is to create a new instance of a Collection like ArrayList with the items inside your primary list; iterate over the new list, and do the operations on the primary list.
new ArrayList<>(users).stream().forEach(u -> users.remove(u));
You can make use of the removeIf to remove data from a list conditionally.
Eg:- If you want to remove all even numbers from a list, you can do it as follows.
final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());
list.removeIf(number -> number % 2 == 0);
To get rid from ConcurrentModificationException Use CopyOnWriteArrayList
Instead of creating strange things, you can just filter() and then map() your result.
This is much more readable and sure. Streams will make it in only one loop.
As it was mentioned before - you can't modify original list, but you can stream, modify and collect items into new list. Here is simple example how to modify string element.
public class StreamTest {
#Test
public void replaceInsideStream() {
List<String> list = Arrays.asList("test1", "test2_attr", "test3");
List<String> output = list.stream().map(value -> value.replace("_attr", "")).collect(Collectors.toList());
System.out.println("Output: " + output); // Output: [test1, test2, test3]
}
}
.peek() is the answer.
users.stream().peek(u -> u.setProperty("value")).foreach(i->{
...
...
});
for new list
users.stream().peek(u -> u.setProperty("value")).collect(Collectors.toList());
This might be a little late. But here is one of the usage. This to get the count of the number of files.
Create a pointer to memory (a new obj in this case) and have the property of the object modified. Java 8 stream doesn't allow to modify the pointer itself and hence if you declare just count as a variable and try to increment within the stream it will never work and throw a compiler exception in the first place
Path path = Paths.get("/Users/XXXX/static/test.txt");
Count c = new Count();
c.setCount(0);
Files.lines(path).forEach(item -> {
c.setCount(c.getCount()+1);
System.out.println(item);});
System.out.println("line count,"+c);
public static class Count{
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
#Override
public String toString() {
return "Count [count=" + count + "]";
}
}
Yes, you can modify or update the values of objects in the list in your case likewise:
users.stream().forEach(u -> u.setProperty("some_value"))
However, the above statement will make updates on the source objects. Which may not be acceptable in most cases.
Luckily, we do have another way like:
List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());
Which returns an updated list back, without hampering the old one.

Categories