4

I wanted to extend LinkedHashMultimap (Guava 16.0.1), mostly to add methods that return commonly used pre-populated maps.

public class MyMap extends LinkedHashMultimap<String, Object> {
}

But, as I learned, most Guava collections are final, and those that are not, don't expose public constructors. What are the possible reasons for this design decision? And what would be the best approach to achieve my goal? The best I can think of is to wrap Multimap methods in my class, but that is far from ideal.

EDIT:

Some users pointed to valid reasons for not allowing inheritance. To extend the questions a little bit, I would like to point out couple of things.

  1. If inheritance is not recommended in public API, how come Java's own collection classes are not final?

  2. Suppose I extensively use a following class:

LinkedHashMultimap<Class<? extends BaseEntity>, BiConsumer<? extends BaseEntity, ? extends Object>>

Now, that's a mouthful. If I could just:

public class ConsumerMap extends LinkedHashMultimap<Class<? extends BaseEntity>, BiConsumer<? extends BaseEntity, ? extends Object>> {
}

I could then use ConsumerMap in my code instead of that monstrosity. I believe readability alone would be reason enough to justify inheritance.

Nikša Baldun
  • 1,854
  • 4
  • 28
  • 39
  • 1
    decorator pattern is your way? – SMA Mar 29 '15 at 09:46
  • 5
    If I understand correctly, the methods you would add would have no reason to be instance methods anyway. Why would you need to extends LinkedHashMultimap just to provide utility methods returning pre-populated maps? Composition is generally preferrable to inheritance, but here, it seems you don't even need composition: only a utility class. Could you provide an example of method you would add? – JB Nizet Mar 29 '15 at 09:48
  • @JBNizet: I realize I can do this via utility class, it just seemed cleaner this way. BTW, I don't see why would composition be preferred to inheritance. There are legitimate use cases for both. – Nikša Baldun Mar 29 '15 at 09:58
  • 2
    And this use case is not a legitimate use case, neither for inheritance, nor for composition. Regarding "favor composition over inheritance", read https://books.google.fr/books?id=ka2VUBqHiWkC&pg=PA81&lpg=PA81&dq=bloch+effective+java+composition&source=bl&ots=yZEmOgq-S0&sig=jsQRuS_kbzHAXLC2As8FnlmU6x8&hl=fr&sa=X&ei=Yc0XVfjfD4XWU-TwgbgG&ved=0CCIQ6AEwAA#v=onepage&q=bloch%20effective%20java%20composition&f=false – JB Nizet Mar 29 '15 at 10:01
  • @JBNizet: In this case, inheriting would not have been dangerous because I would only be using superclass methods that are highly unlikely to ever be changed, However, I understand the reasons for preventing inheritance now. – Nikša Baldun Mar 29 '15 at 10:12

3 Answers3

7

As others have pointed out, you should prefer delegation to inheritance.

What you want to do to implement the class you want is to extend ForwardingSetMultimap, using a LinkedHashMultimap as the delegate. Here's an example of how that'd look:

public final class MyMap extends ForwardingSetMultimap<String, Object> {
  private final SetMultimap<String, Object> delegate =
      LinkedHashMultimap.create();

  @Override public SetMultimap<String, Object> delegate() {
    return delegate;
  }

  // add your methods here
}

Aside: You ask "If inheritance if not recommended in public API, how come Java's own collection classes are not final?"

I think the answer is basically that it was a mistake not making Java's own collection classes final (note: some of them, particularly newer ones, actually are final). As @Mick Mnemonic notes, Josh Bloch (who designed the original collection APIs) was involved in the design of Guava's collection APIs: the design decisions in Guava's API reflect lessons learned from the original collection APIs.

ColinD
  • 108,630
  • 30
  • 201
  • 202
  • Finally a satisfactory answer. Sorry, I guess I should have read the Guava documentation to the end. Forwarding decorators were not mentioned in "new collection types" section, so I wasn't aware of them. – Nikša Baldun Mar 30 '15 at 08:59
5

These classes were designed not to be extended. Because inheritance essentially breaks encapsulation, it makes the API cleaner when classes can't be extended. (Related thread here: Good reasons to prohibit inheritance in Java?)

You can generally work around this by using composition instead.

To answer your follow-up questions:

Regarding Java Collections API, I think there are two alternatives: either classes like ArrayList or HashMap were designed to be extended or then this is simply an API design flaw. I suspect the latter; but it is impossible to revert this design choice after the API has been published. Perhaps only abstract classes such as AbstractList should have been declared non-final?

Probably the Guava team thought that use cases such as the one you present are so rare that it's not worth the effort to add support for inheritance (YAGNI).

This talk by Josh Bloch — who's been involved in designing both the Java API and Guava — is an excellent presentation on the topic.

Community
  • 1
  • 1
Mick Mnemonic
  • 7,808
  • 2
  • 26
  • 30
  • That's all well and good, but I still tend to disagree that making classes final is a good thing. It should be left to the library's users to decide whether their use case justifies inheritance. More options is almost always a good thing. And Guava team didn't have to make an effort to support inheritance, in fact they made an effort to disable it. – Nikša Baldun Mar 29 '15 at 14:02
  • 2
    "More options is almost always a good thing." Absolutely not. Particularly when the options make it easy to shoot yourself in the foot. It's very easy to do the wrong thing when extending collection classes. "Prefer composition over inheritance" and "design for inheritance or else prevent it" are two key design principles here. Guava separates normal implementations like `LinkedHashMultimap` from implementations specifically designed for inheritance (the `Forwarding-` classes). – ColinD Mar 29 '15 at 20:07
-1

The chances are:

  1. add default method to the interface in Java 8 (which you can't as the collection types are defined by the Guava library),
  2. extend the implementations (which again you can't as the implementations are mostly final)
  3. provide external helper class with your desired methods
Crazyjavahacking
  • 9,343
  • 2
  • 31
  • 40