3

I've spent many years working with Java 1.6 (maintaining legacy tools) and am just starting to migrate to 1.8. One big change is the functional methods in the java.util.Collections suite. The biggest concern for me is I have several collection extensions which apply careful checks or algorithms on modification. Do the default methods call the already defined put(..), get(...), remove(..) etc functions or do I have to do a major rework to make this work?

E.g. (ignoring null checks, etc. map that only holds values <= 10)

public class LimitedMap extends HashMap<String, Integer>{
    @Override
    public Integer put(String key, Integer value){
        if(value> 10) throw new IllegalArgumentException();
        return super.put(key, value);
    }

    @Override
    public Integer computeIfAbsent(String key, Function<? super String, ? extends Integer> mappingFunction) {
        return super.computeIfAbsent(key, mappingFunction);
    }
}

With this pair of functions: would I still have to do a detailed override and put new checks into the computeIfAbsent function?

Anlon Burke
  • 397
  • 4
  • 12
K Barad
  • 49
  • 3
  • 6
    You don't know, and it could change anyway, which is why you should favor composition over inheritance: http://jtechies.blogspot.fr/2012/07/item-16-favor-composition-over.html. If you had used composition, all the newly added default methods would have simply deegated to your own methods. – JB Nizet May 27 '17 at 15:00
  • If you want to know what the new default methods do, why don't you just look at the source? The JDK comes with the source code for all the classes of the Java Runtime Library, and any good IDE makes it easy to see that source. You are using a good IDE, right? – Andreas May 27 '17 at 15:11
  • You don't even necessarily need to look at the source code. The default implementations are often documented. For example: http://docs.oracle.com/javase/8/docs/api/java/util/Map.html#computeIfAbsent-K-java.util.function.Function- – Radiodef May 27 '17 at 15:21
  • 2
    @Radiodef you're missing the point: the OP's Map implementation extends HashMap, where the default methods are overridden. Whether HashMap's computeIfAbsent() calls put() or not is an implementation detail, and the OP can't rely on that. – JB Nizet May 27 '17 at 15:27
  • 1
    @JBNizet Yeah, you are right. In any case, I completely agree with your comment about composition & delegation. For example, this could have been done pretty trivially with `AbstractMap`. – Radiodef May 27 '17 at 15:52
  • 1
    As @JBNizet said, you should not extend implementation classes not intended to be specialized. You are about to repeat [the failure of others](https://stackoverflow.com/a/26841569/2711488). Not that this example problem was not introduced with Java 8, but Java 8, update 20. This illustrates, how fragile implementation details are. See also [Inheritance, composition and default methods](https://stackoverflow.com/q/31242036/2711488)… – Holger May 31 '17 at 10:51

1 Answers1

3

The only way you could be certain that only the interface methods from pre Java 8 can be used is if you somehow could delegate to the default method implementation in the interface (Map<K, V> in this case).

That is, if you could write something like the following (which you can't).

public class LimitedMap extends HashMap<String, Integer> {

    @Override
    public Integer computeIfAbsent(String key,
            Function<? super String, ? extends Integer> mappingFunction) {

        return Map.super.computeIfAbsent(key, mappingFunction);
    }
}

Unfortunately that is not legal since you only can invoke the method that you've overriden (here the one from HashMap<String, Integer>) but not the one which the inherited method might have overridden (these are the normal rules for super method invocation).

So the only workaround I see for your situation is to create a copy of the interface default method implementation in a helper class like this:

public class Maps {

    public static <K, V> V computeIfAbsent(Map<K, V> map,
            K key, Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v;
        if ((v = map.get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                map.put(key, newValue);
                return newValue;
            }
        }

        return v;
    }
}

This is the implementation from java.util.Map as a static method enhanced by an additional parameter map for the instance to operate on.

With such a helper class you could now write

public class LimitedMap extends HashMap<String, Integer> {

    @Override
    public Integer computeIfAbsent(String key,
            Function<? super String, ? extends Integer> mappingFunction) {

        return Maps.computeIfAbsent(this, key, mappingFunction);
    }
}

That's not the prettiest solution but one that should work with a limited amount of effort.

Anlon Burke
  • 397
  • 4
  • 12