I have a piece of Java code which removes an element from a set contained in the input parameter to Optional#map
boolean ret = methodReturnsOptioanl()
.map(project -> project.getDocIds().remove(docId))
.orElse(false);
where project.getDocIds() returns a Set of string ids and is guaranteed to be not null.
I've tested it and works; ret is false if the Optional is empty or docId doesn't exist in the set.
However, is it ok for Optional#map to do this and alter the state of the member set and return the boolean result of the Set#remove operation?
I've searched around and can't find any definitive answer on this.
I would say no, the best way to do this is to map your project
to the docIds
assigned to your project
Object and to then call the terminal operation Stream#orElse
. This terminal operation should construct a new (Mutable) List/Collection, from which you can then remove the docId
.
With that your code would look like the following:
boolean ret = optionalVal
.map(Class::getDocIds)
.orElse(new ArrayList<>())
.remove(docId);
A more memory efficient solution would however be:
boolean ret = optionalVal
.map(Class::getDocIds)
.orElseGet(ArrayList::new)
.remove(docId);
This has to do with the fact that the Supplier
given to Optional#orElseGet
only gets called when the optionalVal
variable is empty. When you use Optional#orElse
, this method will always be called and with that an empty (and possibly unnecassery) ArrayList
will be constructed and loaded into the Heap. This means that when your Optional is not empty, you construct twice as many Objects as needed instead of just one.
Explanation
The Stream#map
method is a intermediate operation which means that it transforms the Stream into another stream. This is not the case. For that, you can use the orElse
operation as a terminal operation, which produces a List
/Object
as a result in order for you to remove your objectId.
Explanation memory efficient solution
The Optional#orElseGet
only calls the Supplier
when the value is not present. The following test was run to verify this:
public class TestTest {
class TestOptional {
public TestOptional(){
System.out.println("TestOptional constructor called.. " + this);
}
List<String> getDocIds(){
System.out.println("TestOptional#getDocIds called.. " + this);
return new ArrayList<>(Collections.singletonList("test"));
}
List<String> getEmptyDocIds(){
System.out.println("TestOptional#getEmptyDocIds called.. " + this);
return new ArrayList<>();
}
}
@Test(expected = Exception.class)
public void test() throws Exception {
Optional<TestOptional> optionalVal = Optional.of(new TestOptional());
Optional<TestOptional> optionalValEmpty = Optional.empty();
boolean deleted = optionalVal
.map(TestOptional::getDocIds)
.orElse(new TestOptional().getEmptyDocIds())
.remove("test");
System.out.println("One: " + deleted);
System.out.println("\n ### \n");
boolean deletedTwo = optionalVal
.map(TestOptional::getDocIds)
.orElseGet(() -> new TestOptional().getEmptyDocIds())
.remove("test");
System.out.println("Two: " + deletedTwo);
System.out.println("\n ### \n");
boolean deletedThree = optionalValEmpty
.map(TestOptional::getDocIds)
.orElse(new TestOptional().getEmptyDocIds())
.remove("test");
System.out.println("Three: " + deletedThree);
System.out.println("\n ### \n");
boolean deletedFour = optionalValEmpty
.map(TestOptional::getDocIds)
.orElseGet(() -> new TestOptional().getEmptyDocIds())
.remove("test");
System.out.println("Four: " + deletedFour);
assertThat(deleted).isTrue();
assertThat(deletedTwo).isTrue();
assertThat(deletedThree).isFalse();
assertThat(deletedFour).isFalse();
}
}
Output of test:
TestOptional constructor called.. test.TestTest$TestOptional@28f67ac7
TestOptional#getDocIds called.. test.TestTest$TestOptional@28f67ac7
TestOptional constructor called.. test.TestTest$TestOptional@1a407d53
TestOptional#getEmptyDocIds called.. test.TestTest$TestOptional@1a407d53
One: true
###
TestOptional#getDocIds called.. test.TestTest$TestOptional@28f67ac7
Two: true
###
TestOptional constructor called.. test.TestTest$TestOptional@3cda1055
TestOptional#getEmptyDocIds called.. test.TestTest$TestOptional@3cda1055
Three: false
###
TestOptional constructor called.. test.TestTest$TestOptional@79b4d0f
TestOptional#getEmptyDocIds called.. test.TestTest$TestOptional@79b4d0f
Four: false
However: This would not have too much of an impact, if this code is used for short time and not so frequently (as in amount of uses of method), as this method probably will be out of scope in no time. It's however still more work for the Garbage Collector which means unnecessary misuse of bytes of storage.