简体   繁体   中英

Optional.map - how does it work exactly?

I'm trying to get into Optional issues in Java 8. I've written an extremely simple program, consisting of one class and main() method.

I expect the output data to be [aaa, DDD, ccc] . However, I'm getting [aaa, bbb, ccc] . But if change to s = new TestClass("DDD") line to the commented one, I get what I want.

So how does map() work? Can it map an object only by editing it? Why doesn't it work properly if I create a new instance and return it?

class:

public class TestClass {
    String str;

    public TestClass(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return str;
    }
}

main() method:

public static void main(String[] args) {
        List<TestClass> list = new ArrayList<TestClass>();
        list.add( new TestClass("aaa") );
        list.add( new TestClass("bbb") );
        list.add( new TestClass("ccc") );

        list.stream()
                .filter( s -> s.str.equals("ccc") || s.str.equals("bbb") )
                .findFirst()
                .map( s -> {
                    // s.str = "DDD";     this works just fine
                    s = new TestClass("DDD");
                    return s;
                } );

        System.out.println(list);   
    }

With s.str = "DDD" you are modifying or mutating the TestClass instance in the list setting its str field to value DDD .

With s = new TestClass("DDD") , you are not touching the instances in the list. The variable s is a local variable to that lambda block. Assigning a new object reference to it will not change the str field of the object it was pointing to earlier.

Usually, with a map, you have to collect it or do something with the result. But here you are not doing anything with the mapped result.

Please change your code to :

        Optional<Object> result = list.stream()
            .filter( s -> s.str.equals("ccc") || s.str.equals("bbb") )
            .findFirst()
            .map( s -> {
                // s.str = "DDD";     this works just fine
                s = new TestClass("DDD");
                return s;
            } );

    System.out.println(result); 

and run again - it should give you some hint.

Please notice that you don't use result in your version at all, and modifying source list in such cases is not the best habit (possible multi threading issues)

You are assigning a new Object within a method.

Your new reference will be valid only within the method scope, but as soon as you return, the reference will point to its original instance.

In java this is the expected behavior.

Eg:

String s = "foo";
changeString(s);
print(s); // prints "foo"

where

void changeString(String s) {
    s = "bar";
}

Nobody forbids you to change your object properties though, as you do with s.str = "DDD" (unless your object is immutable, of course).


In your specific case, you are not doing anything with the map lambda result, therefore your changes are lost.

Actually map is useless in your case even when you just do s.str = "DDD" as it could be done within a forEach .

But . since you are working on only one result, which may not even exist (Optional), you should use

...findFirst().ifPresent(s -> s.str = "DDD" );

You should use map only when you need to transform an object into a different type for further processing.

the 'map' method is used to map each element to its corresponding result. If you want to change one element of your list, you need to collect the results into a new List. using the .collect() method.

List<TestClass> list = new ArrayList<TestClass>();
    list.add( new TestClass("aaa") );
    list.add( new TestClass("bbb") );
    list.add( new TestClass("ccc") );

    List<TestClass> result = list.stream()
            .map( s -> {
                if (s.toString().equals("bbb")) {
                    s = new TestClass("DDD");
                }
                return s;
            }).collect(Collectors.toList());

    for(TestClass t : result){
        System.out.println(t);
    }

The result of this is:
aaa
DDD
ccc

Tidying up the stream:

List<TestClass> result = list.stream()
            .map( s -> s.toString().equals("bbb") ? new TestClass("DDD") : s)
            .collect(Collectors.toList());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM