Explain Codes LogoExplain Codes Logo

Why is String.chars() a stream of ints in Java 8?

java
stream-api
performance-optimization
primitive-types
Anton ShumikhinbyAnton Shumikhin·Mar 13, 2025
TLDR

String.chars() provides an IntStream of Unicode code points, which are the int representations for each character. This choice enhances efficiency by reducing the overhead of boxing to Character objects.

"abc".chars() .forEach(System.out::println); // Kindly prints your code points: 97 98 99

Opt for chars() for stream-based operations on characters, capitalizing on int qualities for direct logical and mathematical processing on the stream.

Handling the character conversion

To transform these int code points into Character objects, we use the mapToObj function. This is beneficial because it helps to avoid any lossy conversion. It's kinda like a safety belt for your data!

"Hello".chars() .mapToObj(i -> (char)i) .forEach(System.out::println); // Politely prints your characters: H e l l o

Now, we're using mapToObj to convert each value to a Character before any operation, safeguarding the right data type for character sequences.

Going for efficiency with IntStream

Implementation of IntStream minimizes unneeded autoboxing, decreasing performance overhead. By avoiding creation of Character objects, we tighten up memory usage and processing time, resulting in a leaner operation.

"Optimized".chars() // Efficiency gains! Merry Christmas to your memory and processor!

Complex transformations: We got this!

For times when you need to manipulate characters based on their properties, using an IntStream opens the door to more sophisticated operations like filtering and mapping — all thanks to the numeric code point values. It’s like having a Swiss Army knife on your utility belt!

String cleaned = "123abc!".chars() .filter(Character::isAlphabetic) .mapToObj(c -> String.valueOf((char)c)) .collect(Collectors.joining()); // Boom! You get a clean string: "abc".

Beating pitfalls: The workaround

Direct method references like System.out::println expect an int, so they don't communicate well with our IntStream packed with character code points.

"Watch out!".chars() .forEach(System.out::println); // It's a trap! It prints integers, not your lovely characters

So, here's the trick: use mapToObj to convert types before you print, and you'll get the output you want!

"Magic!".chars() .mapToObj(i -> (char)i) .forEach(System.out::println); // Abracadabra! It correctly prints the characters.

Inside IntStream, and why it matters

Java 8 saw the introduction of primitive stream specializations — IntStream, LongStream, and DoubleStream. These handle primitive types more efficiently by avoiding boxing to their Wrapper class counterparts, leading to enhanced performance.

"Performance!".chars() // Gives your performance a steroid boost!

These specializations make a conscious trade-off favoring performance over a swarm of specialized methods.

Changing lanes: Going from IntStream to others

We can switch lanes between different types of streams using methods like mapToObj. This ability to convert from IntStream to Stream<String>, for example, is another aspect of the Java Stream API that leans towards efficiency.

Stream<String> stringStream = "Conversion".chars() .mapToObj(i -> String.valueOf((char)i)); // Now we’re on the String lane!

Balancing act: API design

Designing the Java Stream API involves balancing performance and usability. The decision to have String.chars() return an IntStream, prioritizing a high-performing and efficient system, is an outcome of this exercise. Sometimes that means we, as developers, need to take extra steps to get where we want.