1

Is it possible to collect Strings over collection while grouping? That's how it works in Java 8:

Map<String, String> discountOptions = p.getDiscountOptions().Stream()
    .collect(groupingBy(
        this::getDiscountName,
        Collectors.mapping(this::getValue, Collectors.joining(","))));

I'm curious, is there concise way to do it in Google Guava? That's how I try to replicate it in Guava:

Map<String, Collection<String>> stringCollectionMap = Multimaps.transformValues(
    Multimaps.index(p.getDiscountOptions(), 
        new Function<DiscountOption, String>() {
            @Override
            public String apply(DiscountOption d) {
                return getDiscountName(d);
            }
        }),
    new Function<DiscountOption, String>() {
        @Override
        public String apply(DiscountOption d) {
            return getValue(d);
        }
    }).asMap();

Map<String, String> discountOptions =  Maps.transformValues(
    stringCollectionMap,
    new Function<Collection<String>, String>() {
        @Override
        public String apply(Collection<String> strings) {
            return Joiner.on(",").join(strings);
        }
    });
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
max_dev
  • 554
  • 2
  • 6
  • 15
  • When you say “do it in Google Guava” do you mean “do it without Java 8 language features”? Or why don’t you use lambda expressions/method for these functional interfaces? – Holger Jun 02 '16 at 16:13
  • Yes, without Java 8 and my goal is do it in more imperative way. – max_dev Jun 03 '16 at 09:25

1 Answers1

3

You're not going to get anything more concise than the Java 8 streams API, since the reason it exists is to improve these kind of operations.

Pre-Java 8 functional programming can be jury-rigged with Guava's functional utilities, but as they warn:

As of Java 7, functional programming in Java can only be approximated through awkward and verbose use of anonymous classes.... Excessive use of Guava's functional programming idioms can lead to verbose, confusing, unreadable, and inefficient code.... Imperative code should be your default, your first choice as of Java 7.

Here's an imperative translation of your code:

private static final Joiner COMMA_JOINER = Joiner.on(",");

ListMultimap<String, String> groupedByDiscountName = ArrayListMultimap.create();
for (DiscountOption option : p.getDiscountOptions()) {
  groupedByDiscountName.put(getDiscountName(option), getValue(option));
}

ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for (Entry<String, Collection<String>> e : groupedByDiscountName.asMap().entrySet()) {
  builder.put(e.getKey(), COMMA_JOINER.join(e.getValues());
}
Map<String, String> discountOptions = builder.build();

It's shorter and easier to read. Given your current API this is roughly the best you can do with Java 7.

That said you might consider re-examining your API - in particular it's odd that you'd use static methods (getDiscountName(), getValue()) to extract data from your DiscountOption class - these seem like clear candidates to be instance methods. Similarly you might consider creating a Discounts class that contains one or more DiscountOption instances and provides a toString() that returns your comma separated string. Then you just need to construct your Discounts objects and you're good to go.

dimo414
  • 47,227
  • 18
  • 148
  • 244
  • "You're not going to get anything more concise than the Java 8 streams API" unless you use something like [Kotlin](https://kotlinlang.org/). Kotlin's bytecode is compatible with Java 6 and newer ([FAQ - Kotlin Programming Language](https://kotlinlang.org/docs/reference/faq.html)). – mfulton26 Jun 02 '16 at 18:27
  • 2
    @mfulton26 lets not open the discussion to other languages; there are obviously more concise alternatives in more concise languages. The question is about Java and Guava, not arbitrary JVM languages. – dimo414 Jun 02 '16 at 18:29