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()));
}
Related
I am getting a List and I want to replace the first value in this list with '$' sign, So that I could use it in some place else. But i don't want the rest of the characters in the list to be misplaced or changed. It's okay to get the result as a new list.
I already tried to just use the list.add(index, value) function but it changes the order of the list. But I don't want any change to the rest of the list.
Is there any good tutorials to to learn java streams. I find it confusing to master. Thanks in advance.
If the input list is ('H','E','L','L','O') the output should be exactly like ('$','E','L','L','O').
You can access the list using indices. If the index is zero just map it to a $, otherwise just send it to the next step of the stream processing pipeline. Finally collect it into a List. This approach is better than modifying your source container if you are using Java8. Here's how it looks.
List<Character> updatedList = IntStream.range(0, chars.size())
.mapToObj(i -> i == 0 ? '$' : chars.get(i))
.collect(Collectors.toList());
This problem does not lend itself nicely to be solved using Java8 streams. One drawback here is that it runs in linear time and looks we have over engineered it using java8. On the contrary, if you modify your array or list directly chars[0] = '$';, then you can merely do it in constant time. Moreover, the imperative approach is much more compact, readable and obviously has less visual clutter. I just posted the Java8 way of doing it, since you have explicitly asked for that. But for this particular instance, imperative way is far more better in my opinion. Either use an array and set the 0th index or if you need to go ahead with a List for some reason, then consider using List.set instead, as stated in the comments above.
hi you can look it the solution
public static void replaceCharacterList(){
List<String> list =new ArrayList<String>();
list.add("H");
list.add("E");
list.add("L");
list.add("L");
list.add("O");
List<String> newItems = list.stream()
.map(value-> value.replace(list.get(0),"$") )
.collect(toList());
Log.d("replace list","list "+list+" new list "+newItems);
}
and the output will be like this
output: list [H, E, L, L, O] new list [$, E, L, L, O]
also for another example you can also look it to this example without Streams
https://examples.javacodegeeks.com/core-java/util/collections/replace-specific-element-of-list-example/
Similar to this, but with external index
AtomicInteger index = new AtomicInteger();
List<Character> newCollect = list.stream()
.map(ch -> (index.getAndIncrement() == 0) ? '$' : ch)
.collect(toList());
You could always use the traditional array copy and validation in the replace method for the index:
public class Main {
private static List<Character> replace(List<Character> chars,int index, char value) {
Character[] array = characters.toArray(new Character[0]);
array[index] = value;
return Arrays.asList(array);
}
public static void main(String[] args) {
List<Character> characters = new ArrayList<>();
characters.add('H');
characters.add('E');
characters.add('L');
characters.add('L');
characters.add('O');
characters = replace(characters, 0, '$');
for (Character character : characters) {
System.out.println(character);
}
}
}
I have List of items where on each item I need to create some calculation.
Each calculation is built by the preceding element.
So for example:
List<Object> Users=new ArrayList<>();
users.stream().filter(element->calculateSomething(<need-prev-element-input>).findFirst();
The calculateSomething will return true/false depends on the prev element calculation result in the stream
Any idea how can I do that?
Streams are not designed to be able to do any operation like this. You might be able to hack something together to do that, but it'll be awful; you should go back to using normal loops instead.
If you really want to use streams, stream over indexes:
IntStream.range(1, users.size())
.filter(i -> calculateSomething(users.get(i-1) , users.get(i)))
.map(users::get)
.findFirst();
There are also a number of non-standard libraries that let you stream over pairs from a list.
Here's the Java 8 way of doing it:
<T extends User> Optional<T> findUserSomehow(List<T> users) {
for (int idx = 1; idx < users.size(); ++idx)
if (calculateSomething(users.get(idx - 1)))
return Optional.of(users.get(idx));
return Optional.empty();
}
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.
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());
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.