Java Optional#map可以将输入参数的状态更改为函数lambda吗?

Java Optional#map可以将输入参数的状态更改为函数lambda吗?,java,java-8,monads,optional,Java,Java 8,Monads,Optional,我有一段Java代码,它将输入参数中包含的一个元素从集合中删除到可选的#map 其中project.getDocIds()返回一组字符串ID,并保证不为null 我已经测试过了,效果很好;如果可选项为空或集合中不存在docId,则ret为false 但是,可选的#map可以这样做并改变成员集的状态并返回集合#remove操作的布尔结果吗 我四处搜索,找不到任何明确的答案。我会说不,最好的方法是将项目映射到分配给项目对象的docid,然后调用终端操作流#orElse。这个终端操作应该构造一个新的(

我有一段Java代码,它将输入参数中包含的一个元素从集合中删除到可选的#map

其中project.getDocIds()返回一组字符串ID,并保证不为null

我已经测试过了,效果很好;如果可选项为空或集合中不存在docId,则ret为false

但是,可选的#map可以这样做并改变成员集的状态并返回集合#remove操作的布尔结果吗


我四处搜索,找不到任何明确的答案。

我会说不,最好的方法是将
项目
映射到分配给
项目
对象的
docid
,然后调用终端操作
流#orElse
。这个终端操作应该构造一个新的(可变的)列表/集合,然后从中删除
docId

这样,您的代码将如下所示:

boolean ret = optionalVal
              .map(Class::getDocIds)
              .orElse(new ArrayList<>())
              .remove(docId);
这与以下事实有关:
Optional#orElseGet
Supplier
仅在
optionalVal
变量为空时调用。当您使用
Optional#orElse
时,将始终调用此方法,并由此构造一个空的(可能是不必要的)
ArrayList
并将其加载到堆中。这意味着,当可选项不为空时,将构造两倍于所需数量的对象,而不是一个

解释

Stream#map
方法是一个中间操作,这意味着它将流转换为另一个流。事实并非如此。为此,您可以使用
orElse
操作作为终端操作,该操作将生成一个
列表
/
对象
,以便删除objectId

解释高效内存解决方案

当值不存在时,
可选的#orElseGet
仅调用
供应商
。运行以下测试以验证这一点:

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();
    }
}

然而:如果此代码使用时间短且不太频繁(如方法的使用量),则不会产生太大影响,因为此方法可能很快就会超出范围。然而,垃圾收集器还有更多的工作要做,这意味着不必要地滥用存储字节。

这样使用
map()
可以吗?不,因为
map()
表达了将可选元素转换为新类型的意图,而这不是您要做的

虽然没有要求
map()
操作没有副作用,并且您的原始代码可以满足您的需要,但它并没有满足人们对
map()
的期望。未来的读者可能需要再看一眼,才能掌握您的代码在做什么。我想提出一些更明显的建议:

var project = methodReturnsOptional();
boolean ret = project.isPresent() && project.get().getDocIds().remove(docId);

由您决定代码是否对未来的读者(包括您未来的自己)足够明显。在我看来,您不需要可选的。从技术上讲,您不需要在任何地方使用
optional
,但这似乎符合主要的预期用途,即可能返回
null
(假设
optionalVal
来自一个方法调用)。我的函数式程序员对调用
map
修改状态感到不舒服。我更喜欢类似于Remco的答案,但这是一种风格,而不是“对/错”@RavindraRanwala我已经更新了代码,以明确可选项来自方法调用。此问题被错误地关闭。虽然(当前)答案在很大程度上依赖于意见,但问题似乎是在寻求权威事实,而不是征求意见。
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
var project = methodReturnsOptional();
boolean ret = project.isPresent() && project.get().getDocIds().remove(docId);