6

I already read this related post. When it comes to String operations, streams seem to attract a huge amount of ceremony. If you want to parse a String as a stream of chars on which you might want to do some operations, you need to convert them to an IntStream first, map to Object, then cast the int to char, eventually casting the char to String and then return it.

And people say imperative style programming has a ceremony overhead. Please correct me if I am completely doing this wrong. My intention is not to mock around but to understand Java streams better because I generally appreciate them.

// Simple method which encrypts all chars in a string
String input = "Hel!lo";

String result = input.chars()                               // Need to convert into an IntStream
        .mapToObj(e -> Character.toUpperCase((char) e))     // Need to map to Object (!) and then cast to char
        .map(CryptoMath::encryptChar)                       // Calling the encryption
        .map(String::valueOf)                               // Need to cast to String again...
        .collect(joining(""));                              // Finally done

System.out.println(result);
Tunaki
  • 132,869
  • 46
  • 340
  • 423
AdHominem
  • 1,204
  • 3
  • 13
  • 32
  • 1
    You can simply treat the `char` as an `int`. But this problem is really a legacy of the whole Java primitive types problem. – Boris the Spider Apr 23 '16 at 09:39
  • Well if I do that I need to change each method with char parameters to take ints instead, which is code smell in my opinion. Which problem regarding primitives are you referring to? You have some link? – AdHominem Apr 23 '16 at 09:46
  • 1
    Just Google it. You can't use generics with primitives. They had to write the same code for `Stream`, `IntSteam` **and** `LongStream`. They chose not to create a `CharStream`. This is an anchor holding back modern Java; and not one that can be cut off. – Boris the Spider Apr 23 '16 at 09:54
  • 1
    Note, you can use `IntStream.range(0, input.length()).mapToObj(i -> input.charAt(i))`, which you might prefer. – Boris the Spider Apr 23 '16 at 09:55
  • 4
    Engineering is all compromises and tradeoffs. Yes, it would have been possible to do yet another specialized implementation for `CharStream`, but this was not without disadvantages either, and likely would still have been an unsatisfying compromise. (A similar choice was made when the for-each loop was added, not allowing for-eaching over the chars of a `String`.) In some languages, strings are modeled as sequences of characters, but Java did not make that choice, and this is the fundamental impediment here, not streams. – Brian Goetz Apr 23 '16 at 16:51
  • 1
    @BrianGoetz, if java `String` is not a sequence of characters, what does it mean that it implements `CharSequence`? – Hank D Apr 24 '16 at 00:50
  • @BrianGoetz Wouldn't be this a good use case for [specialization](http://cr.openjdk.java.net/~briangoetz/valhalla/specialization.html) (aka generics over primitives)? What's its state, btw? – fps Apr 24 '16 at 01:40
  • @HankD Thought experiment: how would your question be different if `CharSequence` were called `InterfaceThatAllowsYouToAccessThingsAsIfTheyWereSequencesOfCharacters`? – Brian Goetz Apr 24 '16 at 14:17
  • 1
    @FedericoPeraltaSchaffner When we can specialize generics over primitives, then `Stream` becomes ordinary generic instantiation, and types like `IntStream` become far less relevant. (There's still the question of what to do with int-specific methods, like `sum()`). Working on it. – Brian Goetz Apr 24 '16 at 14:19
  • @BrianGoetz, to paraphrase a U.S. president, I guess it depends on what the definition of "is a" is. I shouldn't confuse a role something plays with the thing itself. – Hank D Apr 24 '16 at 15:14
  • @HankD Funny you say that ... I had started to write that "It depends whether you interpret is-a in the functional (structural) sense or in the OO (inheritance) sense", but thought that wouldn't be as helpful :) – Brian Goetz Apr 24 '16 at 16:05
  • If your method `CryptoMath.encryptChar` doesn’t support `int` code points, you should insert a type cast from `int` to `char` *there* instead of changing every other operation into a mess and blame the Stream API. Just `input.chars().map(Character::toUpperCase).map(ch -> CryptoMath.encryptChar((char)ch)). …` … – Holger Apr 25 '16 at 18:30

2 Answers2

4

If you can operate on Unicode code points instead of characters, it becomes a bit less cumbersome than operating on char:

String input = "Hel!lo";

String result = input.codePoints()
                     .map( Character::toUpperCase )
                     .collect( StringBuilder::new,
                               StringBuilder::appendCodePoint,
                               StringBuilder::append )
                     .toString();

System.out.println(result);

There's no boxing required, no conversion to string at the point of collection, and you are less likely to be tripped up by surrogate pairs in your input data. One of those nice occasions where it's less painful to implement something that caters for a broader set of inputs.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
russw_uk
  • 1,267
  • 8
  • 10
  • 1
    -1 this is a suggestion from the related post, that the OP had already read and did not find a satisfactory answer. You should cite the suggestion in the other post and explain why it DOES provide an answer for the OP. – Hank D Apr 24 '16 at 00:45
  • Had I noticed that comment in the related post I might have done so, but to my discredit I skimmed it and missed it. In my defense, it was a small comment rather than an answer and didn't provide a code example that makes it clear how much simpler it would be to use. You never know, setting it out as above with a clear example may have allowed the OP to say "actually, that will work for me", assuming unlike me he had seen and discarded that comment from the linked post. If not, he's perfectly at liberty not to accept it as an answer. – russw_uk Apr 25 '16 at 21:33
1

With Eclipse Collections, you can supplement the missing parts in Java standard library. The following will work using CharAdapter, assuming CryptoMath.encryptChar() returns char.

String result = CharAdapter.adapt(input)
        .collectChar(Character::toUpperCase) 
        .collectChar(CryptoMath::encryptChar)
        .makeString("");

Note: I am a committer for Eclipse Collections.

itohro
  • 151
  • 5