14

I have a List<String> and through only using the stream API I was settings all strings to lowercase, sorting them from smallest string to largest and printing them. The issue I'm having is capitalizing the first letter of the string.

Is that something I do through .stream().map()?

public class Main {

    public static void main(String[] args) {

        List<String> list = Arrays.asList("SOmE", "StriNgs", "fRom", "mE", "To", "yOU");
        list.stream()
            .map(n -> n.toLowerCase())
            .sorted((a, b) -> a.length() - b.length())
            .forEach(n -> System.out.println(n));;

    }

}

Output:

me
to
you
some
from
strings

Desired output:

Me
To
You
Some
From
Strings
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
Devin
  • 277
  • 2
  • 15

3 Answers3

14

Something like this should suffice:

 list.stream()
     .map(n -> n.toLowerCase())
     .sorted(Comparator.comparingInt(String::length))
     .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1))
     .forEachOrdered(n -> System.out.println(n));
  1. note that I've changed the comparator, which is essentially the idiomatic approach to do it.
  2. I've added a map operation after sorting to uppercase the first letter.
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • Yes that works, thank you! I was unaware I could use Map multiple times for the same stream. – Devin Dec 11 '18 at 23:19
  • 2
    @Devin you can chain as many intermediate operations as you like. – Ousmane D. Dec 11 '18 at 23:19
  • 3
    Keep in mind that `forEach` makes no ordering guaranties, so to be sure to see the elements in the sorted order, you have to use `forEachOrdered`. – Holger Dec 12 '18 at 07:47
  • @Devin, use as many intermediate operations (filter/map/distinct/sorted/peek) as you need. After you have applied a terminal method (reduce/collect/foreach) you have "ended" the stream. – Viktor Mellgren Dec 12 '18 at 08:58
  • Will fail for empty strings. Use `.map(s -> s.isEmpty() ? "" : Character.toUpperCase(s.charAt(0)) + s.substring(1))` – ssz Jan 06 '19 at 21:28
  • @ssz there are certainly some edge cases as such but it was never part of the requirement and may be the strings will never be empty in the first place? we don't know... the current solution seems to answer exactly what was asked. Also, note that one could also say "what if `s` is `null` and so forth. I think these edge cases which are not mentioned as part of the requirements should be left to the OP and future readers to accommodate if needed. However, thanks for picking this out as it's a good point!! ;-) – Ousmane D. Jan 06 '19 at 21:37
  • @Aomine It is a part of job to think about edge cases, and it is indicator of good code, because most bugs are about edge cases. But, actually, empty string is not even an edge case, but rather very common issue. + NPE is not possible here, because it will happen inside the first `map`, while the question is about the second. Well, in my opinion, `null` can really be considered as an edge case here, but for a good answer it would be also worth adding the `.filter(Objects::nonNull)` construct before the first `map`. – ssz Jan 06 '19 at 21:57
  • @ssz there is nowhere in the question which says strings can be empty. adding the said check will hide a bug rather than letting the program blow up if the strings should never be empty in the first place. the same applies to the null check. – Ousmane D. Jan 06 '19 at 22:03
  • @Aomine in the real world you always have empty strings. If you answer is not about how to "set strings all lowercase but capitalize first letter", but about how to do it for "SOmE", "StriNgs", "fRom", "mE", "To" and "yOU" - then OK. It is such a trivial question, and it would be nice to have a good answer for it, not to produce bad programmers who not thinking about edge cases. And again: empty string is just a regular string, while `null` is not even a string. When you read a file or what ever else - you have empty strings, not `null`s. – ssz Jan 06 '19 at 22:21
14
list.stream()
    .map(s -> s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase())
    .sorted(Comparator.comparingInt(String::length))
    .forEach(System.out::println);

For readability, the line performing capitalisation should be moved into a method,

public class StringUtils {
    public static String capitalise(String s) {
        return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
    }
}

so you can refer to it via an eloquent method reference:

list.stream()
    .map(StringUtils::capitalise)
    .sorted(Comparator.comparingInt(String::length))
    .forEach(System.out::println);
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • 2
    This will exhibit better locality than the accepted answer, which splits the map on either side of the sort. – Alex Reinking Dec 12 '18 at 06:08
  • 3
    @AlexReinking While a agree with you, I just want to point out that the only reason why I’ve decided against using one map operation at the time of posting was to keep the logic on each intermediate operation short and easy see to follow thus making it easier for the OP. Remember at the end of the day, the OP seems like a new person to the Streams API, talking about anything else is a bonus but takes things away from the their main point which is to simply uppercase the first character. btw, seems like now the OP knows that one can use more than one “same” intermediate operation :). – Ousmane D. Dec 12 '18 at 06:43
3

You can use WordUtils::capitalizeFully from Apache Commons Lang for this.

 list.stream()
     .sorted(Comparator.comparingInt(String::length))
     .map(WordUtils::capitalizeFully)
     .forEach(System.out::println);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
fastcodejava
  • 39,895
  • 28
  • 133
  • 186
  • 1
    OP wants to capitalise only the first letter. Besides, you didn't mention where `WordUtils` comes from... – Andrew Tobilko Dec 11 '18 at 23:39
  • 1
    @AndrewTobilko WordUtils::capitalizeFully does just that. I have mentioned where `WordUtils` comes from now. – fastcodejava Dec 11 '18 at 23:42
  • 4
    New code should use the [`commons-text`](https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/WordUtils.html) version of this method instead, as the [`commons-lang`](https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/text/WordUtils.html) one has been deprecated. – Alex Reinking Dec 12 '18 at 06:10