2

Here is a very simple example:

private boolean f(List x) {
    return x != null && !x.isEmpty();
}

private boolean f(Map x) {
    return x != null && !x.isEmpty();
}

Code inside both function is same, they just operate on different objects. I want to merge them into one function to avoid code repeatation.

I tried something like:

private <T> boolean f(T x) {
    return x != null && !x.isEmpty();
}

But it gives error on x.isEmpty()

Thiyagu
  • 17,362
  • 5
  • 42
  • 79
  • 3
    Possible duplicate of [Java generics parameter bounding to any of a range of types](https://stackoverflow.com/questions/6592832/java-generics-parameter-bounding-to-any-of-a-range-of-types) – pushkin Aug 06 '18 at 18:07
  • Don't reinvent the wheel, just use apache-commons. https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/ListUtils.html https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/MapUtils.html – Sergey Morozov Aug 06 '18 at 18:07
  • https://stackoverflow.com/questions/9141960/generic-class-that-accepts-either-of-two-types – Thiyagu Aug 06 '18 at 18:08
  • @SergeyMorozov apache-commons? –  Aug 06 '18 at 18:09
  • @pushkin not a duplicate, there is no common bound for a `List` and `Map` that you could call `isEmpty` on – Eugene Aug 06 '18 at 19:06
  • and what is the use to close this with a duplicate that simply says "you can't"? There are good answers here anyone voting to close it for that duplicate should retract their votes IMO – Eugene Aug 06 '18 at 19:31
  • Merging them is actually more complicated than leaving them separated. – Peter Lawrey Aug 07 '18 at 12:17

6 Answers6

13

Where the parts of what you want to use are covered by an interface implemented by both classes, use that interface.

But in your case, List and Map don't share an interface that provides isEmpty, so your options are:

  1. Overloading (the following is your code unchanged)

    private boolean f(List x) {
        return x != null && !x.isEmpty();
    }
    private boolean f(Map x) {
        return x != null && !x.isEmpty();
    }
    
  2. Accepting Object and using instanceof branches

    private boolean f(Object x) {
        // (roughly)
        if (x == null) {
            return false;
        }
        if (x instanceof List) {
            return !((List)x).isEmpty();
        }
        if (x instanceof Map) {
            return !((Map)x).isEmpty();
        }
        throw new IllegalArgumentException(); // Or whatever
    }
    
  3. Accepting Object and using reflection to get the isEmpty method (if any) and call it

    private boolean f(Object x) {
        // (roughly)
        try {
            return x != null && !(boolean)x.getClass().getMethod("isEmpty").invoke(x);
        } catch (Exception e) {
            throw new IllegalArgumentException(); // Or whatever
        }
    }
    

Of those, overloading seems the cleanest approach to me, not least because it gives you a compile-time error if you try to use f on a type it can't handle (whereas the others are runtime errors, blech), but it's (at least slightly) a matter of opinion.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Good answer. An appoint. if x == null you should return false. Right? – Joe Taras Aug 06 '18 at 18:25
  • @JoeTaras - In fact, all of my `return`s were inverted, I misread what `f` returned. Fixed, thanks. :-) – T.J. Crowder Aug 06 '18 at 18:28
  • @T.J.Crowder I like point number 3 (plus one), I would use `MethodHandles` for this, though. https://stackoverflow.com/a/51714450/1059372 – Eugene Aug 06 '18 at 19:30
2

You want to refer to both the List and the Map polymorphically using a common ancestor interface which sadly doesn't exist. Since Java is strictly typed the compiler is looking for a method definition which matches this signature which can't be found.

You can't do it precisely in the way you have asked.

Take a look at how Apache commons solved this issues with this method using instanceof to cast to a relevant type org.apache.commons.collections.CollectionUtils#sizeIsEmpty

public static boolean sizeIsEmpty(Object object) {
    if (object instanceof Collection) {
        return ((Collection) object).isEmpty();
    } else if (object instanceof Map) {
        return ((Map) object).isEmpty();
    } else if (object instanceof Object[]) {
        return ((Object[]) object).length == 0;
    } else if (object instanceof Iterator) {
        return ((Iterator) object).hasNext() == false;
    } else if (object instanceof Enumeration) {
        return ((Enumeration) object).hasMoreElements() == false;
    } else if (object == null) {
        throw new IllegalArgumentException("Unsupported object type: null");
    } else {
        try {
            return Array.getLength(object) == 0;
        } catch (IllegalArgumentException ex) {
            throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
        }
    }
}
David
  • 7,652
  • 21
  • 60
  • 98
1

You can try as follow:

private Object boolean f(Object x) {
    if (x instanceof List) {
        List l = (List)x;
        return !l.isEmpty();
    } else if (x instanceof Map) {
        Map m = (Map)x;
        return !m.isEmpty();
    }
    return false;
}
Joe Taras
  • 15,166
  • 7
  • 42
  • 55
  • 1
    I am a fan of `instanceof`. Nevertheless, every time I refer to it in any answer; other users immediately mention [this](https://stackoverflow.com/questions/20589590/why-not-use-instanceof-operator-in-oop-design) – Yahya Aug 06 '18 at 18:19
  • @Yahya: Yes, you're right about your link. But in this case I suppose is very simple way to proceed – Joe Taras Aug 06 '18 at 18:21
  • Instanceof is the worst thing in java, but if you like to use it, keep in mind that null check is not needed, because "x instanceof List" would return false if x is null. – Dmitry Zvorygin Aug 06 '18 at 18:22
  • @DmitryZvorygin:Yes, in my case if object is null it return false – Joe Taras Aug 06 '18 at 18:24
  • 1
    @JoeTaras - you shouldn't add " l != null " and " m != null " condiitons - they are useless. – Dmitry Zvorygin Aug 06 '18 at 18:27
1

You can't really, since isEmpty() is not inherited from a common interface. One workaround is to accept a predicate that determines emptiness:

private static <T> boolean isNonEmpty(T value, Predicate<T> isEmpty) {
    return value != null && !isEmpty.test(value);
}

Sample use:

isNonEmpty(Arrays.asList(1, 2, 3), List::isEmpty)
isNonEmpty(new HashMap<>(), Map::isEmpty)
isNonEmpty("foo", String::isEmpty)

Arguably this helper method is not all that helpful.

Eugene
  • 117,005
  • 15
  • 201
  • 306
shmosel
  • 49,289
  • 6
  • 73
  • 138
0

Since you have tagged , you can make you of Optional class.

private static boolean isEmpty(Optional<Collection> collection){
    return collection.map(Collection::isEmpty).orElse(false);
}

Some example:

public static void main(String[] args) {
    ArrayList arrayList = null;
    System.out.println(isEmpty(Optional.ofNullable(arrayList)));

    Map map = null;
    //Convert Map entries to entryset (Collection) via Optional::map
    System.out.println(isEmpty(Optional.ofNullable(map).map(Map::entrySet)));
}

Output:

false
false
Pankaj Singhal
  • 15,283
  • 9
  • 47
  • 86
0

I love point 3 in T.J.Crowder's answer, but I would use a slightly different approach (and a bit faster):

private static boolean f(Object x) {
    MethodType methodType = MethodType.methodType(boolean.class);
    Lookup l = MethodHandles.lookup();
    try {
        MethodHandle handle = l.findVirtual(x.getClass(), "isEmpty", methodType);
        return (boolean) handle.invoke(x);
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
}
Eugene
  • 117,005
  • 15
  • 201
  • 306