Process elements of Set<Foo> and create Set<Bar> using streams

tom :

I have a Set<String> of "hostname:port" pairs and from that I'd like to create a Set<InetSocketAddress>. I tried it like:

Set<InetSocketAddress> ISAAddresses = StrAddresses
    .stream().map(addr -> new InetSocketAddress(
        addr.split(":")[0],
        Integer.parseInt(addr.split(":")[1])));

But this produces the following error in IntelliJ:

Incompatible types. Required Set<InetSocketAddress> but 'map' was inferred to Stream<R>: no instance(s) of type variable(s) R exist so that Stream<R> conforms to Set<InetSocketAddress>

Something must be wrong with how I'm using the map and the lambda.

Zabuza :

The Stream#map function does not return a Map. It transforms (maps) the current elements of your stream to other elements. So it generates from a Stream<X> a Stream<Y> using the given transformation function which takes X and outputs Y.

StrAddresses.stream()                           // String
    .map(addr -> new InetSocketAddress(
        addr.split(":")[0],
        Integer.parseInt(addr.split(":")[1]))); // InetSocketAddress

You start with a Stream<String> and end up with a Stream<InetSocketAddress>.

To quote from its documentation:

Returns a stream consisting of the results of applying the given function to the elements of this stream.


If you want to transform that stream into a Set you need to use the Stream#collect method like so:

StrAddresses.stream()
    .map(addr -> new InetSocketAddress(
        addr.split(":")[0],
        Integer.parseInt(addr.split(":")[1])))
    .collect(Collectors.toSet());

The utility method Collectors.toSet() returns a collector for a well optimized Set. If you for example explicitly want a HashSet you can use this instead:

.collect(Collectors.toCollection(HashSet::new));

From its documentation:

Performs a mutable reduction operation on the elements of this stream. A mutable reduction is one in which the reduced value is a mutable result container, such as an ArrayList [...]


As a small note, you currently split the same element twice each time:

addr.split(":")[0],                     // First
Integer.parseInt(addr.split(":")[1])))  // Second

You could save that additional split procedure by memorizing the value before. In this case this can be done elegantly by using a second Stream#map call. First we transform from Stream<String> to Stream<String[]> and then to Stream<InetSocketAddress>:

StrAddresses.stream()                                 // String
    .map(addr -> addr.split(":"))                     // String[]
    .map(addrData -> new InetSocketAddress(
        addrData[0], Integer.parseInt(addrData[1])))  // InetSocketAddress
    .collect(Collectors.toSet());

Note that Stream#map is a lazy operation. This means that Java will not transform the whole Stream from A to B once you call the method. It will wait until a non-lazy (finalizing) operation like Stream#collect comes, then traverse the Stream and apply each lazy operation element-wise. So you can add as many Stream#map calls as you like without producing extra loops over the whole Stream.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=463970&siteId=1