简体   繁体   中英

Map: Defining a Method for Type Integer and Double but not String

I'm attempting to define a method putIfGreaterThan() for my new Map class ( given a key it replaces the old value with the new value only if the new value is greater than the old value ).

I understand I could accomplish this either via composition (by having a private final Map<String, Double> map; in new class and then passing a Map to constructor) or by implementing the Map interface in my new class and passing a Map to the constructor (although I'm not sure which approach is superior).

My main problem is that I need to be able to call the method putIfGreaterThan() on <String, Double> and <String, Integer> but not on <String, String> (bec it doesn't make sense to call it on <String, String> ). If I use generics ( <K, V> ) the client can pass a <String, String> which is not allowed. On the other hand if I allow a Double I will not be able to pass an Integer or vice versa. How can I define a method to allow either Integer or Double but not String?

Note: I'm unable to define two constructors (one for Double and one for Integer) bec I get the error: Erasure of method XX is the same as another method in type XX .

You can use the decorator pattern and limit the generics to subtypes of Number , which you can than compare without casting with a little trick taken from this answer . It takes the string representation of the number instances and creates an instance of BigDecimal - thus circumventing casting of any kind.

Below you find the relevant implementation details of the decorator, of course you'll need to override the remaining methods of the Map interface.

public class GreaterThanDecorator<K, V extends  Number> implements Map<K, V> {

    private final Map<K, V> delegate;

    public GreaterThanDecorator(Map<K, V> delegate) {
        this.delegate = delegate;
    }

    public V putIfGreaterThan(K key, V value) {
        V old = delegate.get(key);
        if (old == null) {
            delegate.put(key, value);
            return null;
        }

        BigDecimal newValue = new BigDecimal(value.toString());
        BigDecimal oldValue = new BigDecimal(old.toString());

        if (newValue.compareTo(oldValue) >= 1)
            old = delegate.put(key, value);

        return old;
    }

} 

Feel free to forbid the other subtypes of Number , ie by throwing an exception, as you see fit.

Generics and numbers don't go together well, unfortunately. But you can do something like this:

public class MyMap {
    private final Map<String, Number> map = new HashMap<>();

    public void putInt(String key, int value) {
        map.put(key, value);
    }

    public void putDouble(String key, int value) {
        map.put(key, value);
    }

    public void putIfGreaterThan(String key, Number value) {
        if (value instanceof Double) {
            double doubleValue = (Double) value;
            map.compute(key, (k, v) -> {
                if (!(v instanceof Double) || v.doubleValue() > doubleValue) {
                    return v;
                } else {
                    return value
                }
            });
        } else if (value instanceof Integer) {
            int intValue = (Integer) value;
            map.compute(key, (k, v) -> {
                if (!(v instanceof Integer) || v.intValue() > intValue) {
                    return v;
                } else {
                    return value
                }
            });
        } else {
            throw new IllegalArgumentException("Expected Double or Integer, but got " + value);
        }
    }

}

Ideally you'd want to declare a method introducing a type parameter which extends V & Comparable<? super V> extends V & Comparable<? super V> , but that is not valid Java.

However, you can define a static method without using the & .

public static <K, V extends Comparable<? super V>> void putIfGreater(
    Map<K,V> map, K key, V value
) {
    V old = map.get(key);
    if (old != null && value.compareTo(old) > 0) {
        map.put(key, value);
    }

    // Or: 
    //map.computeIfPresent(key, (k, old) -> value.compareTo(old) > 0 ? value : old);
}

If you were going down the route of different construction, then the type would need to be different for the case where V is Comparable<? super V> Comparable<? super V> . If two constructors have the same erasure, it's probably time to have meaningfully named static creation methods.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM