Optional#map 可以将输入参数的状态更改为 Function lambda 吗?
Is it ok for Optional#map to change the state of the input parameter to the Function lambda?
我有一段 Java 代码,它从 Optional#map
的输入参数中包含的集合中删除一个元素
boolean ret = methodReturnsOptioanl()
.map(project -> project.getDocIds().remove(docId))
.orElse(false);
其中 project.getDocIds() return 是一组字符串 ID,并且保证不为空。
我已经测试过它并且有效;如果 Optional 为空或集合中不存在 docId,则 ret 为 false。
但是,Optional#map 可以执行此操作并更改成员集的状态和 return Set#remove 操作的布尔结果吗?
我四处搜索,找不到任何明确的答案。
我会说不,最好的方法是将您的 project
映射到分配给您的 project
对象的 docIds
,然后调用终端操作 Stream#orElse
。此终端操作应构造一个新的(可变的)List/Collection,然后您可以从中删除 docId
.
你的代码将如下所示:
boolean ret = optionalVal
.map(Class::getDocIds)
.orElse(new ArrayList<>())
.remove(docId);
然而,更高效的内存解决方案是:
boolean ret = optionalVal
.map(Class::getDocIds)
.orElseGet(ArrayList::new)
.remove(docId);
这与这样一个事实有关,即 Optional#orElseGet
的 Supplier
仅在 optionalVal
变量为空时才被调用。当您使用 Optional#orElse
时,将始终调用此方法,并且将构造一个空的(可能是不必要的)ArrayList
并将其加载到堆中。这意味着当您的 Optional 不为空时,您将根据需要构造两倍数量的对象,而不是一个。
说明
Stream#map
方法是一个中间操作,这意味着它将 Stream 转换为另一个流。不是这种情况。为此,您可以将 orElse
操作用作终端操作,它会生成 List
/Object
作为结果,以便您删除 objectId。
说明内存高效解决方案
Optional#orElseGet
仅在 不存在 值时调用 Supplier
。以下测试是 运行 验证这一点:
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();
}
}
测试输出:
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
但是:这不会有太大的影响,如果这个代码使用的时间很短并且不那么频繁(如方法的使用量),因为这个方法可能会立即超出范围。然而,垃圾收集器还有更多工作要做,这意味着不必要地滥用存储字节。
这样使用map()
可以吗?不,因为 map()
表达了将可选元素转换为新类型的意图,而这不是您正在做的。
虽然没有要求 map()
操作没有副作用,并且您的原始代码会做您想要的,但它并没有做人们期待 map()
。未来的读者可能需要再看一遍才能了解您的代码在做什么。我会建议更明显的东西:
var project = methodReturnsOptional();
boolean ret = project.isPresent() && project.get().getDocIds().remove(docId);
我有一段 Java 代码,它从 Optional#map
的输入参数中包含的集合中删除一个元素boolean ret = methodReturnsOptioanl()
.map(project -> project.getDocIds().remove(docId))
.orElse(false);
其中 project.getDocIds() return 是一组字符串 ID,并且保证不为空。
我已经测试过它并且有效;如果 Optional 为空或集合中不存在 docId,则 ret 为 false。
但是,Optional#map 可以执行此操作并更改成员集的状态和 return Set#remove 操作的布尔结果吗?
我四处搜索,找不到任何明确的答案。
我会说不,最好的方法是将您的 project
映射到分配给您的 project
对象的 docIds
,然后调用终端操作 Stream#orElse
。此终端操作应构造一个新的(可变的)List/Collection,然后您可以从中删除 docId
.
你的代码将如下所示:
boolean ret = optionalVal
.map(Class::getDocIds)
.orElse(new ArrayList<>())
.remove(docId);
然而,更高效的内存解决方案是:
boolean ret = optionalVal
.map(Class::getDocIds)
.orElseGet(ArrayList::new)
.remove(docId);
这与这样一个事实有关,即 Optional#orElseGet
的 Supplier
仅在 optionalVal
变量为空时才被调用。当您使用 Optional#orElse
时,将始终调用此方法,并且将构造一个空的(可能是不必要的)ArrayList
并将其加载到堆中。这意味着当您的 Optional 不为空时,您将根据需要构造两倍数量的对象,而不是一个。
说明
Stream#map
方法是一个中间操作,这意味着它将 Stream 转换为另一个流。不是这种情况。为此,您可以将 orElse
操作用作终端操作,它会生成 List
/Object
作为结果,以便您删除 objectId。
说明内存高效解决方案
Optional#orElseGet
仅在 不存在 值时调用 Supplier
。以下测试是 运行 验证这一点:
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();
}
}
测试输出:
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
但是:这不会有太大的影响,如果这个代码使用的时间很短并且不那么频繁(如方法的使用量),因为这个方法可能会立即超出范围。然而,垃圾收集器还有更多工作要做,这意味着不必要地滥用存储字节。
这样使用map()
可以吗?不,因为 map()
表达了将可选元素转换为新类型的意图,而这不是您正在做的。
虽然没有要求 map()
操作没有副作用,并且您的原始代码会做您想要的,但它并没有做人们期待 map()
。未来的读者可能需要再看一遍才能了解您的代码在做什么。我会建议更明显的东西:
var project = methodReturnsOptional();
boolean ret = project.isPresent() && project.get().getDocIds().remove(docId);