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"))
Answer from Sergii Lagutin on Stack OverflowIf 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"));
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).
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);
You should not use Stream APIs to change the state of an Object.
If you still want to modify it then,
You can iterate each element from the list of A, filter if dob is null, find dob against the respective name in the list of B.
List<A> aList = new ArrayList<>();
List<B> bList = new ArrayList<>();
aList.stream()
.filter( a -> a.dob == null)
.forEach( a -> {
Predicate<B> nameFilter = b -> b.name.equals(a.name);
a.dob = findDob(nameFilter, bList);
});
static String findDob(Predicate<B> nameFilter, List<B> bList) {
B b = bList.stream()
.filter(nameFilter)
.findFirst()
.orElse(new B());
return b.dob;
}
Alternate efficient solution: Considering you have a unique name for each object B, you can prepare lookup and find age using that map, this way you do not need to iterate bList for every iteration of aList
List<A> aList = new ArrayList<>();
List<B> bList = new ArrayList<>();
Map<String, String> nameDobLookup = bList.stream()
.collect(Collectors.toMap(b -> b.name, b -> b.dob));
aList.stream()
.filter(a -> a.dob == null)
.forEach(a -> a.dob = nameDobLookup.get(a.name));
I'd suggest modifying the list of A objects in a forEach loop:
// define: List<A> aList =
// define: List<B> bList =
aList.forEach(aListElement -> {
// find the first B object with matching name:
Optional<B> matchingBElem = bList.stream()
.filter(bElem -> Objects.equals(aListElement.getName(), bElem.getName()))
.findFirst();
// and use it to set the dob value in this A list element:
if (matchingBElem.isPresent()) {
aListElement.setDob(matchingBElem.get().getDob());
}
}
);
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.
I'd first create a mapping from TimeDiscount::getIdOfBook to TimeDiscount:
Map<Long, TimeDiscount> accumulator =
actualPromotions.stream()
.collect(toMap(TimeDiscount::getIdOfBook, Function.identity()));
Then I'd do:
booksToReturn.forEach(e -> {
TimeDiscount timeDiscount = accumulator.get(e.getIdOfBook());
if (timeDiscount != null) e.setDiscountRate(e.getDiscountRate() + timeDiscount.getDiscountRate());
});
or if you want to stay with the use of Optional for some reason.
booksToReturn.forEach(e ->
Optional.ofNullable(accumulator.get(e.getIdOfBook()))
.ifPresent(p -> e.setDiscountRate(e.getDiscountRate() + p.getDiscountRate()))
);
This improves upon the inefficient lookup in actualPromotions.stream() for each element of booksToReturn.
One way you can do it is using:
booksToReturn.forEach(p -> actualPromotions.stream()
.filter(actualPromotion -> actualPromotion.getIdOfBook().equals(p.getIdOfBook()))
.forEach(actualPromotion -> p.setDiscountRate(p.getDiscountRate() + actualPromotion.getDiscountRate())));
Assuming actualPromotion.getIdOfBook() and p.getIdOfBook() would be unique across your Sets.
If the currentList is always a subset of the updatedList - means that all the currentList will appear in the updatedList, you can do the following:
Set<String> setOfId = currentList.stream()
.map(person -> person.getId()) // exctract the IDs only
.collect(Collectors.toSet()); // to Set, since they are unique
List<Person> newList = updatedList.stream() // filter out those who don't match
.filter(person -> setOfId.contains(person.getId()))
.collect(Collectors.toList());
If the updatedList and currentList differ significantly - both can have unique persons, you have to do the double iteration and use Stream::map to replace the Person. If not found, replace with self:
List<Person> newList = currentList.stream()
.map(person -> updatedList.stream() // map Person to
.filter(i -> i.getId().equals(person.getId())) // .. the found Id
.findFirst().orElse(person)) // .. or else to self
.collect(Collectors.toList()); // result to List
Assuming that you want the currentList item to be replaced by the object from the updatedList the following should work:
currentList.stream().map((p) -> {
return updatedList.stream().filter(u -> p.equals(u)).findFirst().orElse(p);
}).collect(Collectors.toList());
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);
}
});
You must have some method/constructor that generates a copy of an existing SampleDTO instance, such as a copy constructor.
Then you can map each original SampleDTO instance to a new SampleDTO instance, and collect them into a new List :
List<SampleDTO> output =
list.stream()
.map(s-> {
SampleDTO n = new SampleDTO(s); // create new instance
n.setText(n.getText()+"xxx"); // mutate its state
return n; // return mutated instance
})
.collect(Collectors.toList());
To make this more elegant way I would suggest create a Method with in the class.
public class SampleDTO {
private String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public SampleDTO(String text) {
this.text = text;
}
public SampleDTO getSampleDTO() {
this.setText(getText()+"xxx");
return this;
}
}
and add it like:
List<SampleDTO> output =list.stream().map(SampleDTO::getSampleDTO).collect(Collectors.toList();
Use Builder for getter and setter
public class LoanAccount { private String id; private Integer numberOfInstallments; // add other properties public String getId() { return id; } public LoanAccount setId(String id) { this.id = id; return this; } public Integer getNumberOfInstallments() { return numberOfInstallments; } public LoanAccount setNumberOfInstallments(Integer numberOfInstallments) { this.numberOfInstallments = numberOfInstallments; return this; }Use this one for update method
public LoanAccount update(LoanAccount loanAccount) { return bank.getLoanAccounts() .stream() .filter(la -> la.getId().equals(loanAccount.getId())) .findFirst().orElseThrow(IllegalArgumentException::new) .setCreationDate(loanAccount.getCreationDate()) .setLoanAmount(loanAccount.getLoanAmount()) .setNumberOfInstallments(loanAccount.getNumberOfInstallments()) .setType(loanAccount.getType()); }
You could use a HashMap where the TKey is the type of your LoanAccount.id.
Then call loanAccounts.put(id, object)
This will update the object if there is already an Id and add a new object if not.
This is a cheap, dirty way. Another way of doing it would be to make your LoanAccount class implement Comparable and in the compareTo() method make a id based comparation.
Do the same thing overriding your equals() and you should be ready to go.
@Override
public boolean equals(object obj) {
if (obj == null) return false;
return ((LoanAccount)obj).getId() == this.getId();
}
something like that. (code wrote by memory, can have errors and lacks validations like the data type)