3

The static method System.getProperties() returns a Properties object containing the System Properties as key-value pairs.

In the SO post How would I print the JVM's system properties using Java 8 and lambdas? Radiodef provided a solution which works fine, and it can be enhanced to also sort the output:

        System.getProperties()
                .entrySet()
                .stream()
                .map(e -> e.getKey() + ": " + e.getValue())
                .sorted()
                .forEach(System.out::println);

Here is a small portion of the sorted output produced by that statement:

Unformatted output

I tried unsucessfully to amend the statement above so that the property values in the output are left-aligned. Since java.vm.specification.version happens to be the longest System Properties key, the formatted presentation of the output shown above should look like this:

Formatted output

Obviously the tricky part is to somehow know the length of the longest key prior to formatting the output. Any suggestions on how to achieve this in a single statement using a Stream, or is it impossible?

Update:

I forgot to state in the original post that an additional constraint is that the solution should also work using a parallel stream. My apologies for the oversight.

skomisa
  • 16,436
  • 7
  • 61
  • 102
  • There's no easy way to do this afaik. You have to know the length of the longest key in advance. That's pretty much it. – markspace Nov 14 '17 at 00:33
  • Why in a single statement? First find the max length, then, in a second pass, pad all the strings... – fps Nov 14 '17 at 01:06
  • @FedericoPeraltaSchaffner Yes, I realize that it is trivial if you already know the length of the longest key before processing the stream, but I want to know whether it can also be achieved simply by processing the stream. – skomisa Nov 14 '17 at 02:00
  • 1
    While this is an interesting challenge, it’s also an excellent illustration of a case where two Streams or loops are a much better idea than trying to force it into one method chain. – VGR Nov 14 '17 at 02:50
  • @VGR The question wasn't seeking advice on alternative approaches to solving the problem; it was simply asking how it could be done using a Stream in a single pass, and several users provided solutions. Of course I doubt if anyone would use a Stream for this specific issue in the real world, but the solutions below are still interesting, and have general applicability. – skomisa Nov 14 '17 at 21:39
  • Academically interesting, yes. General applicability? I would never allow any of them to pass a code review, personally. – VGR Nov 14 '17 at 21:48
  • @Holger Re "the irrational desire to this in one operation", the OP clearly stated "Any suggestions on how to achieve this in a single statement using a Stream, or is it impossible?". There's no need to ascribe "irrational desire" as the motivation for the post. FYI, simple curiosity was the reason. – skomisa Nov 15 '17 at 17:04
  • @skomisa: that was not meant as a personal offense; questions like this are regularly popping up on Stackoverflow, having this common pattern. And it is irrational to attempt to represent two fundamentally different operations as a single statement. It should be obvious that getting the maximum property value of all elements requires processing all elements, while using the result of that in another operation, again applied to all elements requires a step that can only be done *after* the first has been completed. What is gained by having that look like one statement? – Holger Nov 15 '17 at 17:17
  • @Holger Re "It should be obvious that getting the maximum property value of all elements requires processing all elements", that was explicitly stated in the OP: "the tricky part is to somehow know the length of the longest key prior to formatting the output". Again, there's no need to for you personalize your responses, and what may be obvious to you is not necessarily obvious to everyone else. If you don't like the question, just down vote it, or ignore it. – skomisa Nov 15 '17 at 17:48
  • @Holger I would appreciate it if you could edit or delete your comment, I mean the one that points to my (now deleted) answer. I have deleted it because it was incorrect (and way too complex), as it was hiding a second step needed to print the padded keys. I would also appreciate it if you could edit your answer, so that it no longer points to mine's. Thanks! – fps Nov 15 '17 at 18:39
  • @skomisa: there is no tricky part, as doing it before the second step is straight forward and not doing it before, is impossible. Anyway, as already said, this comment was in no way meant to be personal. But I have no problem removing it. – Holger Nov 15 '17 at 19:57

3 Answers3

1

If you would like to try a single statement, you may try:

    AtomicInteger len = new AtomicInteger();
    System.getProperties()
          .entrySet()
          .stream()
          .sorted((e1, e2) -> Integer.compare(e2.getKey().toString().length(), e1.getKey().toString().length()))
          .peek(e -> len.set(Math.max(len.get(), e.getKey().toString().length())))
          .map(e -> String.format("%-" + len.get() + "s: %s", e.getKey(), e.getValue()))
          .sorted()
          .forEach(System.out::println);

It involves 2 sorts, so the elements will go through the peek to find the maximum length first.

It is not impossible to do so, but I do not recommended indeed, as an unnecessary sort is introduced.

Alex
  • 803
  • 4
  • 9
  • Your solution worked fine except when using a parallelStream(), and I have updated the OP to clarify that as a requirement. My apologies for that. Can your approach be tweaked to work with a parallelStream() as well? – skomisa Nov 14 '17 at 01:54
  • The solution from Evgeniy Dorofeev is better as it involves one sort only. For the parallel stream, I don't think it would benefit you with the power of parallel, as you need to print the output in order. `forEachOrdered()` will guarantee the ordering in parallel, with lock and sync between `Threads`. This makes it equivalent to a single thread stream, but with overheads as well. – Alex Nov 14 '17 at 02:24
  • Understood, and I realize that in the real world using parallelStream() would probably be slower than stream() for processing System properties. The core goal of my post was to find out how to format output from a stream based on information determined during stream processing, and using System.getProperties() was just a vehicle for that. – skomisa Nov 14 '17 at 02:40
  • I see. You may use `forEachOrdered()` as the terminal operator to output things in order for parallel stream. – Alex Nov 14 '17 at 02:41
1

This works:

    AtomicInteger len = new AtomicInteger();
    ((Map<String, String>)(Map)System.getProperties())
    .entrySet()
    .parallelStream()
    .peek(e -> len.set(Integer.max(e.getKey().length(), len.get())))
    .sorted((e1, e2) -> (e1.getKey()).compareTo(e2.getKey()))
    .forEachOrdered(e -> System.out.printf("%-" + len + "s %s%n", e.getKey() + ":", e.getValue()));
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • I updated the OP to clarify that the solution should also work when using parallelStream() instead of stream(). However, your solution already seems to work with parallelStream() as well. Is that behavior guaranteed, or was I just lucky with my testing of your code? – skomisa Nov 14 '17 at 01:55
  • forEachOrdered guarantees correct sequence of sorted entries – Evgeniy Dorofeev Nov 14 '17 at 05:36
  • This is *not* parallel compatible, as `len.set(Integer.max(e.getKey().length(), len.get()))` contains a `get` and `set`, so it’s not an atomic update. But using a correct atomic update is not much harder: `len.accumulateAndGet(e.getKey().length(), Integer::max)`… – Holger Nov 15 '17 at 12:53
0

Just separate the steps.

  1. Determine the maximum key length
  2. Format all entries using the length

E.g. 

int maxLen = System.getProperties().keySet()
    .stream().mapToInt(k -> ((String)k).length()).max().orElse(0);
String format = "%-"+maxLen+"s %s%n";
System.getProperties().forEach((k,v)->System.out.printf(format, k+":", v));

Note that this also prints the colon left-aligned, as in your example output. When you want to sort this map and/or ensure that there is no modification between these two operations, you may use

@SuppressWarnings("unchecked") Map<String,String> map
                                   = new HashMap<>((Map)System.getProperties());
int maxLen = map.keySet().stream().mapToInt(String::length).max().orElse(0);
String format = "%-"+maxLen+"s %s%n";
map.entrySet().stream()
   .sorted(Map.Entry.comparingByKey())
   .forEach(e -> System.out.printf(format, e.getKey()+":", e.getValue()));

It’s worth noting that all other solution are just using different tricks to hide the fact that there are at least two processing steps.

  • Stream.sort is a stateful intermediate operation. It will buffer the entire stream contents, then sort the buffer and only after that proceed with the downstream operation. This answer relies on the peek following the first sort operation to see the maximum before all other values, which isn’t even a guaranteed property. Whereas this answer does it the other way round, relies on peek having seen all values, before the subsequent sorted step passes any elements to the terminal operation.

  • Another, Collector based answer did two steps in one, calculating the maximum key length and accumulating all entries into a new map, but the printing of the formatted output still could only be done after the completion of the entire collect operation.

It is impossible to do this in one step, but if all you want, is to hide the fact that there are two steps, by letting the operation look like one statement, you can do this with the above solution as well:

((Map<String,String>)(Map)System.getProperties()).entrySet().stream()
   .collect(collectingAndThen(toMap(Map.Entry::getKey,Map.Entry::getValue), map -> {
        int maxLen = map.keySet().stream().mapToInt(String::length).max().orElse(0);
        String format = "%-"+maxLen+"s %s%n";
        map.entrySet().stream()
           .sorted(Map.Entry.comparingByKey())
           .forEach(e -> System.out.printf(format, e.getKey()+":", e.getValue()));
        return null;
   }));
Holger
  • 285,553
  • 42
  • 434
  • 765
  • The OP stated "Any suggestions on how to achieve this in a single statement using a Stream, or is it impossible?", so "Just separate the steps" is not a solution. – skomisa Nov 15 '17 at 17:06
  • But at the end you have a solution in one single statement (though a very complex one), still requires two passes. It's impossible to do it in a single pass, though I don't know how to prove it. – fps Nov 15 '17 at 17:09
  • @skomisa: the question “… or is it impossible?” has been answered precisely: “It is impossible to do this in one step…” – Holger Nov 15 '17 at 17:19