0

My Question is inspired by this Question, but is aimed at using Java Streams to arrive an a List<Integer>.

I have this code that kind of works. It seems to be returning an ArrayList, presumably ArrayList<Integer>. But the compiler refuses to let me declare the result as such.

String input = "1 2 3 4 5";
Stream stream = Arrays.stream( input.split( " " ) );
var x = stream.map( s -> Integer.valueOf( ( String ) s ) ).collect( Collectors.toList() );

This runs when using the new var feature of recent Java versions.

System.out.println( x.getClass() );
System.out.println( x );

class java.util.ArrayList

[1, 2, 3, 4, 5]

I have two questions:

  • Why is x reported as an ArrayList yet I cannot declare x to be an ArrayList (error: incompatible types), such as:
    ArrayList<Integer> x = stream.map( s -> Integer.valueOf( ( String ) s ) ).collect( Collectors.toList() );
  • Is there a better way to use streams to convert this string of digits to a List of Integer?
2
  • 1
    You need to declare your stream Stream<String> stream. Commented Sep 26, 2018 at 22:37
  • When it is truly about processing digits, you can use var x = input.chars().filter(c -> c != 32).map(c -> c-'0').boxed().collect(Collectors.toList()); Commented Sep 27, 2018 at 14:12

4 Answers 4

3

First, your Stream is raw. Using a raw type means that anything that uses a type parameter is erased to its upper bound, here Object. So map returns another raw Stream and collect returns an Object. Easy fix: Stream<String>.

Stream<String> stream = Arrays.stream( input.split( " " ) );

Second, Collectors.toList is specified to return List<T>, or List<String> here.

There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier).

If you aren't satisfied with List and you absolutely need an ArrayList, supply one:

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

Incidentally, you can replace the lambda expression

s -> Integer.valueOf( ( String ) s )

with the method reference

Integer::valueOf

After these changes, your code might look like this:

String input = "1 2 3 4 5";
Stream< String > stream = Arrays.stream( input.split( " " ) );
List< Integer > x = stream.map( Integer::valueOf ).collect( Collectors.toList() );

Or, if you insist on precisely ArrayList rather than List, make that:

String input = "1 2 3 4 5";
Stream< String > stream = Arrays.stream( input.split( " " ) );
ArrayList< Integer > x = stream.map( Integer::valueOf ).collect( Collectors.toCollection( ArrayList::new ) );

Once these changes are made, this seems like a reasonably good way of converting a string containing space-separated integers to an ArrayList<Integer>. A minor improvement would be to change the split regular expression argument to "\\s+", to represent one or more whitespace characters. In case "1 2" arrives, with multiple spaces between numbers, this will prevent empty strings that would match in between space characters.

Sign up to request clarification or add additional context in comments.

3 Comments

Don't build intermediate array. Replace Arrays.stream( input.split( " " ) ) with Pattern.compile(" ").splitAsStream(input)
@Andreas I do appreciate seeing your suggestion demonstrated in the Answer by Oleksandr. But I value this Answer as-is because it maintains the context of my original code while clarifying and correcting my misunderstandings.
@BasilBourque Guess I should have put that comment in the question, huh. ;-)
3

You can create an ArrayList, but you shouldn't:

ArrayList<Integer> x =
    stream.map(Integer::valueOf)
          .collect(Collectors.toCollection(ArrayList::new));

Refactoring:

List<Integer> x =
    Arrays.stream(input.split(" "))
          .map(Integer::valueOf)
          .collect(Collectors.toList());

Or, with the Pattern.splitAsStream:

List<Integer> x =
    Pattern.compile(" ").splitAsStream("1 2 3 4 5")
           .map(Integer::valueOf)
           .collect(Collectors.toList());

Comments

1
List<Integer> list = Arrays.stream("1 2 3 4 5".split(" ")).map(s -> Integer.valueOf(s)).collect(Collectors.toList());

This compiles. According to the collectors source code: "There are no guarantees on the type, mutability, * serializability, or thread-safety of the {@code List} returned;"

Comments

1

Why is x reported as an ArrayList yet I cannot declare x to be an ArrayList.

Because

  1. collect(...) is returning a statically inferred type, based on the type of the result of Collectors.toList().
  2. That is (in this case) Collector<String,​?,​List<String>> ... according to the Collectors javadoc.

The fact that the actual object is an ArrayList is an implementation detail that could conceivably change in future versions of Java.

Is there a better way to use streams to convert this string of digits to a List of Integer?

I will leave that to others to say. However, I think that a simpler / cleaner way to do this is to not use streams for this task.

    String input = "1 2 3 4 5";
    var x = new ArrayList<>(Arrays.asList(input.split(" ")));

The above code is simpler and most likely more efficient than the streams-based versions I have seen.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.