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.
Related
I need an immutable list where I can get derive a second immutable list preserving all elements of the previous list plus an additional element in Java (without additional libraries).
Note: This question is similar to What is an efficient and elegant way to add a single element to an immutable set? but I need a list and don't have Guava.
What I have tried so far:
var list = List.of(someArrayOfInitialElements);
var newList = Stream.concat(list.stream(), Stream.of(elementToAppend))
.collect(CollectorsCollectors.toUnmodifiableList());
That would work but creating a stream and copying elements one by one seems inefficient to me. You could basically bulk copy memory given that List.of() stores data in a field-based or array-based data structure.
Is there a more efficient solution than using streams? A better data structure in the Java standard library that I am missing?
I would create a new ArrayList append the element and then return that as an unmodifiable list. Something like,
private static <T> List<T> appendOne(List<T> al, T t) {
List<T> bl = new ArrayList<>(al);
bl.add(t);
return Collections.unmodifiableList(bl);
}
And to test it
public static void main(String[] args) {
List<String> al = appendOne(new ArrayList<>(), "1");
List<String> bl = appendOne(al, "2");
System.out.println(bl);
}
I get (unsurprisingly):
[1, 2]
See this code run at IdeOne.com.
The Answer by Frisch is correct, and should be accepted. One further noteā¦
Calling Collections.unmodifiableList produces a collection that is a view onto the original mutable list. So a modification to the original list will "bleed through" to the not-so-immutable second list.
This issue does not apply to the correct code shown in that Answer, because the new ArrayList object deliberately goes out-of-scope. Therefore that new list cannot be accessed for modification. But in other coding scenarios, this issue could be a concern.
List.copyOf
If you want an independent and truly immutable second list, use List.copyOf in Java 10+. This returns an unmodifiable list.
return List.copyOf( bl ) ;
Both answers are great, I would create a bit more generic solution:
private static <T> List<T> append(final List<T> al, final T... ts) {
final List<T> bl = new ArrayList<>(al);
for (final T t : ts) {
bl.add(t);
}
return List.copyOf(bl);
}
It can be used exactly like previous answer:
List<String> al = append(new ArrayList<>(), "1");
List<String> bl = append(al, "2");
System.out.println(bl);
But also slightly more efficient:
List<String> bl = append(new ArrayList<>(), "1", "2");
System.out.println(bl);
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()));
}
Given:
public abstract class Cars {}
...
public class Ford extends Cars {}
...
public class Dodge extends Cars {}
...
public class Volkswagen extends Cars {}
...
If I have two ArrayList objects:
List<Cars> dealer1 = new ArrayList<>;
List<Cars> dealer2 = new ArrayList<>;
dealer1.addAll(asList(new Ford("ford1"), new Dodge("dodge1")));
dealer2.addAll(asList(new Dodge("dodge2"), new Volkswagen("vw1")));
I then want to create a merged list from the two with only one instance of each subclass, such that:
dealerMerged = ["ford1", "dodge1", "vw1"]
OR
dealerMerged = ["ford1", "dodge2", "vw1"]
It doesn't matter which instance makes it into the merged list.
Is this possible? I had a search through and saw something about using Set but that seems to only ensure unique references, unless I've badly misunderstood something.
Overriding equals() will work but DON'T
You can always make your collection distinctful converting it to a Set (as #Arun states in comment) or using distinct operation over the Stream of your collections. But remember those approaches use the equal() methods for that. So a quick thinking would be overriding equals() and return its Class type. But wait ! If you do so you will end up having all Dodge objects equals to each other despite they have different properties like name dodge1, dodge2. You may not only handle a single business in read world. equal() method has lots of other significances. So stay away of doing so.
If you are thinking a Java 8 way, Stateful Filter is perfect
We have a choice to use the filter operation for our concatenated stream. filter operation works pretty straight forward. It takes a predicate and decide which element to take or ignore. This a commonly used function that you will find all over the blogs that solves this problem.
public static <T> Predicate<T> distinctBy(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
Here the distinctBy function returns a predicate (that will be used in filter operation). It maintains state about what it's seen previously and returns whether the given element was seen for the first time. (You can read further explanation about this here)
You can use this Stateful Filter like
Stream.of(dealer1, dealer2)
.flatMap(Collection::stream)
.filter(distinctBy(Cars::getClass))
.collect(Collectors.toList())
.forEach(cars -> System.out.println(cars));
So What we actually did here ?
We concatenated the 2 ArrayList with flatmap that will give us a single stream of the merged elements (If you are new to Stream API. See this Stream Concatenation article
We then exploits the filter() operation that is feed with the distinctBy method which return a predicate.
And you see a ConcurrentHashMap is maintained to track which element satisfies the predicate or not by a boolean flag.
And the predicate uses the getClass() method which returns the full class name, that distinguise the elements as subclasses
We then can collect or iterate over the filtered list.
Try using Map instead of List. You may please try following solution. This will let you put Car instances by their types. Thereby you will always have only one entry per class (this will be the latest entry in your map by the way).
public class CarsCollection {
Map<Class<? extends Cars>, ? super Cars> coll = new HashMap<>();
public <T extends Cars> void add(Class<T> cls, T obj) {
coll.put(cls, obj);
}
}
public class Temp {
public static void main(String[] args) {
CarsCollection nos1 = new CarsCollection();
cars.add(Ford.class, new Ford("ford1"));
cars.add(Dodge.class, new Dodge("dodge1"));
cars.add(Dodge.class, new Dodge("dodge2"));
cars.add(Volkswagen.class, new Volkswagen("vw1"));
System.out.println(cars);
}
}
You could add all the element of the first list into the result list (assuming there is no duplicate in the first list) and then loop through the second list and add the elements to the resulting list only if there is no instance of the same class in the first list.
That could look something like this :
dealerMerged = dealer1;
boolean isAlreadyRepresented;
for (car2 : dealer2) {
isAlreadyRepresented = false;
for (car1 : dealer1) {
if (car1.getClass().equals(car2.getClass())) {
isAlreadyRepresented = true;
}
}
if (!isAlreadyRepresented) {
dealerMerged.add(car2);
}
}
Just use class of the object as key in the map. This example with Java stream does exactly that:
List<Cars> merged = Stream.of(dealer1, dealer2)
.flatMap(Collection::stream)
.collect( Collectors.toMap( Object::getClass, Function.identity(), (c1, c2) -> c1 ) )
.values()
.stream().collect( Collectors.toList() );
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));
I have a List of object and the list is very big. The object is
class Sample {
String value1;
String value2;
String value3;
String value4;
String value5;
}
Now I have to search for a specific value of an object in the list. Say if value3=='three' I have to return those objects (My search is not always based on value3)
The list is
List<Sample> list = new ArrayList<Sample>();
What is the efficient way of doing it?
Thanks.
You can give a try to Apache Commons Collections.
There is a class CollectionUtils that allows you to select or filter items by custom Predicate.
Your code would be like this:
Predicate condition = new Predicate() {
boolean evaluate(Object sample) {
return ((Sample)sample).value3.equals("three");
}
};
List result = CollectionUtils.select( list, condition );
Update:
In java8, using Lambdas and StreamAPI this should be:
List<Sample> result = list.stream()
.filter(item -> item.value3.equals("three"))
.collect(Collectors.toList());
much nicer!
Using Java 8
With Java 8 you can simply convert your list to a stream allowing you to write:
import java.util.List;
import java.util.stream.Collectors;
List<Sample> list = new ArrayList<Sample>();
List<Sample> result = list.stream()
.filter(a -> Objects.equals(a.value3, "three"))
.collect(Collectors.toList());
Note that
a -> Objects.equals(a.value3, "three") is a lambda expression
result is a List with a Sample type
It's very fast, no cast at every iteration
If your filter logic gets heavier, you can do list.parallelStream() instead of list.stream() (read this)
Apache Commons
If you can't use Java 8, you can use Apache Commons library and write:
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
Collection result = CollectionUtils.select(list, new Predicate() {
public boolean evaluate(Object a) {
return Objects.equals(((Sample) a).value3, "three");
}
});
// If you need the results as a typed array:
Sample[] resultTyped = (Sample[]) result.toArray(new Sample[result.size()]);
Note that:
There is a cast from Object to Sample at each iteration
If you need your results to be typed as Sample[], you need extra code (as shown in my sample)
Bonus: A nice blog article talking about how to find element in list.
If you always search based on value3, you could store the objects in a Map:
Map<String, List<Sample>> map = new HashMap <>();
You can then populate the map with key = value3 and value = list of Sample objects with that same value3 property.
You can then query the map:
List<Sample> allSamplesWhereValue3IsDog = map.get("Dog");
Note: if no 2 Sample instances can have the same value3, you can simply use a Map<String, Sample>.
I modifie this list and add a List to the samples try this
Pseudocode
Sample {
List<String> values;
List<String> getList() {
return values}
}
for(Sample s : list) {
if(s.getString.getList.contains("three") {
return s;
}
}
As your list is an ArrayList, it can be assumed that it is unsorted. Therefore, there is no way to search for your element that is faster than O(n).
If you can, you should think about changing your list into a Set (with HashSet as implementation) with a specific Comparator for your sample class.
Another possibility would be to use a HashMap. You can add your data as Sample (please start class names with an uppercase letter) and use the string you want to search for as key. Then you could simply use
Sample samp = myMap.get(myKey);
If there can be multiple samples per key, use Map<String, List<Sample>>, otherwise use Map<String, Sample>. If you use multiple keys, you will have to create multiple maps that hold the same dataset. As they all point to the same objects, space shouldn't be that much of a problem.
You can filter the list:
list.stream().filter(
sample -> sample.getValue4().equals("4")
).forEach(System.out::println)
I propose for+if.
Object result;
for (Object o: objects){
if (o.value3.equals("three")){
result=o;
break;
}
}
no streams, no guavas, I think it's simple.