You can use filter on the streamed list, then simply update the count
public void updateCount(String bookName, int updateBy) {
books.stream().filter(book -> book.getName().equals(bookName)).forEach(
book -> book.setCount(book.getCount() + updateBy)
);
}
Answer from baao on Stack OverflowYou can use filter on the streamed list, then simply update the count
public void updateCount(String bookName, int updateBy) {
books.stream().filter(book -> book.getName().equals(bookName)).forEach(
book -> book.setCount(book.getCount() + updateBy)
);
}
books.stream().filter(book -> book.getName().equals("book2")).findFirst().get().setCount(3);
However it will throw NoSuchElementException if book2 will not exist in List.
That's why you should use Optional.isPresent() check.
Optional<Book> book2 = books.stream().filter(book -> book.getName().equals("book2")).findFirst();
book2.ifPresent(book -> book.setCount(3));
Notice that I am looking also only for the first found book2. If you want to find all Book with specific name you should use foreach syntax instead of findFirst.
If you wanna create new list, use Stream.map method:
List<Fruit> newList = fruits.stream()
.map(f -> new Fruit(f.getId(), f.getName() + "s", f.getCountry()))
.collect(Collectors.toList())
If you wanna modify current list, use Collection.forEach:
fruits.forEach(f -> f.setName(f.getName() + "s"))
You can use just forEach. No stream at all:
fruits.forEach(fruit -> fruit.setName(fruit.getName() + "s"));
I would use streams for that job.
Assuming your class MyObject has getters and setters methods defined (getKey(), getValue(), setKey(), setValue()), you can do:
l1.forEach(myObject1 -> l2.stream()
.filter(myObject2 -> myObject1.getKey().equals(myObject2.getKey()))
.findAny().ifPresent(myObject2 -> myObject1.setValue(myObject2.getValue())));
If you can have duplicate keys then you should modify ifPresent() to forEach():
l1.forEach(myObject1 -> l2.stream()
.filter(myObject2 -> myObject1.getKey().equals(myObject2.getKey()))
.forEach(myObject2 -> myObject1.setValue(myObject2.getValue())));
Not many elegant one line streaming I can think of. My suggestion is split two steps:
- convert
l2to map. - stream map change items in
l1. This is more flexible and easy to debug solution.
static class myObject {
String key;
int val;
public myObject(String key, int val) {
this.key = key;
this.val = val;
}
@Override
public String toString() {
return "myObject{" +
"key='" + key + '\'' +
", val=" + val +
'}';
}
}
public static void main(String[] args) {
myObject a = new myObject("k1", 30);
myObject b = new myObject("k2", 40);
Map<String, myObject> list2map = List.of(a,b).stream().collect(Collectors.toMap(m -> m.key, m -> m));
myObject c = new myObject("k1", 10);
myObject d = new myObject("k2", 20);
List<myObject> l1 = List.of(c, d);
l1 = l1.stream().map(m -> {
m.val = list2map.getOrDefault(m.key, m).val;
return m;
}).collect(Collectors.toList());
System.out.println(l1);
}
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
Spliteratorreports theCONCURRENTcharacteristic.
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
When you create a Stream from the List, you are not allowed to modify the source List from the Stream as specified in the “Non-interference” section of the package documentation. Not obeying this constraint can result in a ConcurrentModificationException or, even worse, a corrupted data structure without getting an exception.
The only solution to directly manipulate the list using a Java Stream, is to create a Stream not iterating over the list itself, i.e. a stream iterating over the indices like
IntStream.range(0, list1.size()).forEach(ix -> list1.set(ix, list1.get(ix)+1));
like in Eran’s answer
But it’s not necessary to use a Stream here. The goal can be achieved as simple as
list1.replaceAll(i -> i + 1);
This is a new List method introduced in Java 8, also allowing to smoothly use a lambda expression. Besides that, there are also the probably well-known Iterable.forEach, the nice Collection.removeIf, and the in-place List.sort method, to name other new Collection operations not involving the Stream API. Also, the Map interface got several new methods worth knowing.
See also “New and Enhanced APIs That Take Advantage of Lambda Expressions and Streams in Java SE 8” from the official documentation.
Holger's answer is just about perfect. However, if you're concerned with integer overflow, then you can use another utility method that was released in Java 8: Math#incrementExact. This will throw an ArithmeticException if the result overflows an int. A method reference can be used for this as well, as seen below:
list1.replaceAll(Math::incrementExact);
Feature Envy
The LoanAccountService needs to know all fields from LoanAccount that gets updated:
account.get().setCreationDate(loanAccount.getCreationDate()); account.get().setLoanAmount(loanAccount.getLoanAmount()); account.get().setNumberOfInstallments(loanAccount.getNumberOfInstallments()); account.get().setType(loanAccount.getType());
Now imagine LoanAccount would get a few more fields and they are updateable too but you forgot to change LoanAccountService.. This means LoanAccountService depends on LoanAccount..
A solution would be to add a new method to LoanAccount:
public LoanAccount updateBy(LoanAccount other) {
this.creationDate = other.creationDate();
this.loanAmount = other.loanAmount();
this.numberOfInstallments = other.numberOfInstallments();
this.type = other.type();
return this;
}
Optional#map
The if-statement
if (account.isPresent()) {
can be replaced by the method map on Optional. With adding the new updateBy method:
public LoanAccount updateBy(LoanAccount other) {
bank.getLoanAccounts()
.stream()
.filter(la -> la.getId().equals(other.getId()))
.findAny()
.map(loanAccount -> loanAccount.updateBy(other))
.orElseThrow(() -> new IllegalArgumentException("The object does not exist."));
return other;
}
Further Improvement
A second Feature Envy is in the following snipped:
bank.getLoanAccounts() .stream() .filter(la -> la.getId().equals(loanAccount.getId()))
Not the LoanAccountService should filter the data but the Bank itself should filter it:
// in Bank.java
public Optinal<List<LoanAccount> findBy(int id) {
return loanAccounts.stream()
.filter(la -> la.getId().equals(id))
.collect(Collectors.toList())
}
All together
// LoanAccountService.java
public LoanAccount updateBy(LoanAccount other) {
bank.findBy(other.getId())
.findAny()
.map(loanAccount -> loanAccount.updateBy(other))
.orElseThrow(() -> new IllegalArgumentException("The object does not exist."));
return other;
}
I would suggest following:
- Place operations to the objects that contain data for this operation. It will improve encapsulation and allow more flexible code reuse. Also it will be easy to test such implementation because of small methods.
- Use more convenient structure. In this case
Map<String, LoanAccount>(map id to loanAccount) instead ofList<LoanAccount>
As you can see in this case service contains only required logic (how to react on loan absence). It is easy to read, test and understand.
LoanAccountService.java
public class LoanAccountService{
private Bank bank;
public LoanAccountService(Bank bank) {
this.bank = bank;
}
public LoanAccount update(LoanAccount loanAccount) {
if (!bank.updateLoanAccount(loanAccount)) {
throw new IllegalArgumentException("The object does not exist.");
}
return loanAccount;
}
}
Bank.java
@Getter
@Setter
public class Bank {
private Map<String, LoanAccount> loanAccounts;
public boolean updateLoanAccount(LoanAccount loanAccount) {
LoanAccount loan = loanAccounts.get(loanAccount.getId());
if (loan != null) {
loan.update(loanAccount);
return true;
}
return false;
}
}
LoanAccount.java
@Getter
@Setter
public class LoanAccount {
private String id;
private Integer numberOfInstallments;
private LoanAccountType type;
private Date creationDate;
private BigDecimal loanAmount;
public void update(LoanAccount loanAccount) {
this.setCreationDate(loanAccount.getCreationDate());
this.setLoanAmount(loanAccount.getLoanAmount());
this.setNumberOfInstallments(loanAccount.getNumberOfInstallments());
this.setType(loanAccount.getType());
}
}
If you can't use this approach, you could made a small refactoring of the service (less code and "if conditions" are good things):
public LoanAccount update(LoanAccount loanAccount) {
LoanAccount account = bank.getLoanAccounts()
.stream()
.filter(la -> la.getId().equals(loanAccount.getId()))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("The object does not exist."));
account.setCreationDate(loanAccount.getCreationDate());
account.setLoanAmount(loanAccount.getLoanAmount());
account.setNumberOfInstallments(loanAccount.getNumberOfInstallments());
account.setType(loanAccount.getType());
return account;
}
Also I think you should return updated loan account.
It's simple actually. You can do something like this:
list.stream()
.filter(d-> d.getAvailableTodayInPerson() == true && d.getDistance() > dis)
.foreach(d -> {
d.setAvailableTodayInPerson(false);
d.setAvailableTodayOutPerson(true);
});
Do you mean?
list.stream().forEach(d -> {
if(d.getAvailableTodayInPerson() == true && d.getDistance() > dis) {
d.setAvailableTodayInPerson(false);
d.setAvailableTodayOutPerson(true);
}
});
For java 8 you can use the stream API and lambdas
List<User> users;
users.forEach((u) -> u.setActive(false));
If you're using Java 8, you can use the Iterable<E>.forEach(Consumer<? super E> action) method as follows:
users.forEach((user) -> user.setActive(false));
Otherwise you'll have to use the standard enhanced-for loop approach:
for (User user : users) {
user.setActive(false);
}
You can use peek
empList.stream().peek(e->{e.orders++;}).collect(Collectors.toList());
Also as correctly pointed by "Vasanth Senthamarai Kannan " you don't need second list as you are not modifying structure of list,
empList.forEach(e->e.orders++);
you can use replaceAll() just define a method like incrementOrder()
empList.replaceAll(Customer::incrementOrder);
public Customer incrementOrder(){
this.orders+=1;
return this;
}
Use findFirst so after finding the first matching element in the list remaining elements will not be processed
Optional<MyObject> result = list.stream()
.filter(obj->obj.getId()==1)
.peek(o->o.setName("Yahoo"))
.findFirst();
Or
//will not return anything but will update the first matching object name
list.stream()
.filter(obj->obj.getId()==1)
.findFirst()
.ifPresent(o->o.setName("Yahoo"));
You can use a Map instead of a list and save the id as a key. https://docs.oracle.com/javase/8/docs/api/java/util/Map.html
Then you can extract it with O(1).