[java] how to instanceof List<MyType>?

The major concern here is that the collections don't keep the type in the definition. The types are only available in runtime. I came up with a function to test complex collections (it has one constraint though).

Check if the object is an instance of a generic collection. In order to represent a collection,

  • No classes, always false
  • One class, it is not a collection and returns the result of instanceof evaluation
  • To represent a List or Set, the type of the list comes next e.g. {List, Integer} for List<Integer>
  • To represent a Map, the key and value types come next e.g. {Map, String, Integer} for Map<String, Integer>

More complex use cases could be generated using the same rules. For example in order to represent List<Map<String, GenericRecord>>, it can be called as

    Map<String, Integer> map = new HashMap<>();
    map.put("S1", 1);
    map.put("S2", 2);
    List<Map<String, Integer> obj = new ArrayList<>();
    obj.add(map);
    isInstanceOfGenericCollection(obj, List.class, List.class, Map.class, String.class, GenericRecord.class);

Note that this implementation doesn't support nested types in the Map. Hence, the type of key and value should be a class and not a collection. But it shouldn't be hard to add it.

    public static boolean isInstanceOfGenericCollection(Object object, Class<?>... classes) {
        if (classes.length == 0) return false;
        if (classes.length == 1) return classes[0].isInstance(object);
        if (classes[0].equals(List.class))
            return object instanceof List && ((List<?>) object).stream().allMatch(item -> isInstanceOfGenericCollection(item, Arrays.copyOfRange(classes, 1, classes.length)));
        if (classes[0].equals(Set.class))
            return object instanceof Set && ((Set<?>) object).stream().allMatch(item -> isInstanceOfGenericCollection(item, Arrays.copyOfRange(classes, 1, classes.length)));
        if (classes[0].equals(Map.class))
            return object instanceof Map &&
                    ((Map<?, ?>) object).keySet().stream().allMatch(classes[classes.length - 2]::isInstance) &&
                    ((Map<?, ?>) object).values().stream().allMatch(classes[classes.length - 1]::isInstance);
        return false;
    }