0

Want to write an efficient and client friendly way to merge multiple immutable collections into single collection of same instance type

Something like

public static <K> Collection<K> merge(Collection<K>... collection) {
   // return merged collection
}

For this, along with the set of collections clients would also need to pass either the instance type of merged collection (in which case I would need to validate compatibility with the set of collections). I don't want to go this route

Is there any way I can infer the type of collection and validate all collections are of the same type and create a new instance of the same type ? Or is this not a valid use case for a general purpose merge implementation and its better off to just provide individual merge implementations for each type ?

DanMatlin
  • 1,212
  • 7
  • 19
  • 37
  • Does this answer your question? [Combine multiple Collections into a single logical Collection?](https://stackoverflow.com/questions/4896662/combine-multiple-collections-into-a-single-logical-collection) – Amongalen Jul 09 '20 at 13:03
  • While I think we probably could come up with a solution that seems like what you want, I think "its better off to just provide individual merge implementations for each type" is right. Explicit is better than implicit, if you want to combine two Collections into a Set you should do that explicitly, and not create a Collection that may or may not be a Set depending on the dynamic type of its inputs. – MikeFHay Jul 10 '20 at 09:49

1 Answers1

2

This is not easy and requires knowing beforehand an explicit list of types you support. This gets complicated even further by the fact that the immutable types returned by e.g. java.util.List.of(a, b) aren't public types, and their fully qualified type name is not part of the spec (meaning: It could change in a next release of java and that would not be considered a backwards compatibility break; therefore if you rely on the name, your code has a high maintenance burden).

A better idea is perhaps to allow any type of collections, and even a heterogenous list of collections (toss a set, a guava ImmutableList, and a List.of() at it, and a Collections.singleton - your code doesn't care and will merge them all), and have various methods that each return a specifically desired kind of collection.

For example:

import com.google.common.collect.ImmutableList;
public static <K> ImmutableList<K> mergeToList(Collection<K>... collections) {
    var out = ImmutableList.<K>builder();
    for (Collection<K> c : collections) out.addAll(c);
    return out.build();
}

The alternative would be something quite ugly, such as:

import com.google.common.collect.ImmutableList;
public static <K, C extends Collection<K>> C merge(C... collections) {
    if (collections.length == 0) throw new IllegalArgumentException("So many problems; one of them is that it becomes impossible to merge zero collections");
    Class<?> type = collections[0].getClass();
    for (C c : collections) if (c.getClass() != type) throw new IllegalArgumentException("Only pass one kind of collection");
    if (type == ImmutableList.class) {
        // specific code to merge immutable lists.
    } else if (type == Collections.singleton("dummy").getClass()) {
        // specific code to merge j.u.Collections singletons.
    } else if (type == List.of().getClass()) {
        // this becomes virtually impossible; List.of()'s returned type depends on the number of args...
    } else {
        throw new IllegalArgumentException("Unsupported type: " + type);
    }
}

Hopefully that makes clear why you really can't do this. A final option is to have a merge method that takes as parameter a type to merge into, but there is no way to know how to MAKE a collection type. Given com.google.common.collect.ImmutableList, without hardcoding into your source code how to do it, how would you know how to construct a new one?

This is why factories exist in java, and you need one here. collections don't ship with them, so you'd have to explicitly write them for each type you want to support.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • agree with points here. but mergeToList too would return a specific type of list (some particular default) whereas the type of list could be decided based on the input collection (array list or linked list) ? – DanMatlin Jul 09 '20 at 13:24
  • @DanMatlin as I showed, this idea of checking the type(s) of the incoming collections does not hold up. You'd have to hardcode every kind of input (because you can't "just" construct up a new one and then repeatedly call .add() - for example, ImmutableList can't do that), and List.of() will return at least 3 different types depending on how large it is, and surely you would expect merge(List.of(), List.of(1, 2), List.of(1)) to work and return an immutable list containing [1,2,1], no? - but writing that would be [A] difficult and [B] relying on implementation details. – rzwitserloot Jul 09 '20 at 13:30
  • Note also that 'behaviour' is itself a nebulous concept. Sure, you can say: All inputs are Sets, so I want 'set behaviour' (i.e. that duplicates aren't in there). But is 'insertion order' a behaviour, or 'auto-sorted' (TreeSet)? What about the randomaccess lookup behaviour of ArrayList vs. LinkedList? It ends up boiling down to: "I want all inputs to be the exact same type and then the output to also be that type", which is not (practically) possible. – rzwitserloot Jul 09 '20 at 13:32
  • 1+, the points are spot on. to the point of `immutable` collections, I did not understand why there was no addition of an/some marker interface(s) that would specifically call-out that some Collection is `immutable`. I mean there are `AbstractImmutableCollection` and the like, but neither are public. – Eugene Jul 10 '20 at 02:01