Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Java Generics to recurse over an object with a list of objects (each with another list of objects)

Tags:

java

generics

I have three interfaces:

public interface Combinable<V> {
    V add(V other);
}

public interface Sublistable<V> {
    boolean hasSublist();
    List<V> getSublist();
    void setSublist(List<V> sublist);
}

public interface HasUniqueIdentifier {
    String getUniqueIdentifier();
}

and 4 classes that implement some or all of these interfaces:

public class Grandparent implements HasUniqueIdentifier, 
                                    Sublistable<Parent>, 
                                    Combinable<Grandparent> 
{   List<Parent> sublist; ... }

public class Parent implements HasUniqueIdentifier, 
                               Sublistable<Child>, 
                               Combinable<Parent> 
{   List<Child> sublist; ... }

public class Child implements HasUniqueIdentifier, 
                              Sublistable<Grandchild>, 
                              Combinable<Child> 
{   List<Grandchild> sublist; ...  }

public class Grandchild implements HasUniqueIdentifier, 
                                   Combinable<Grandchild> 
{    }

I would like to have a generic method that does the following:

public <V, T extends HasUniqueIdentifier & Combinable<T> & Sublistable<V>> 
List<T> combine(List<T> items) {
    Multimap<String, T> similarItemMap = HashMultimap.create();
    for (T item: items) {
        similarItemMap.put(item.getUniqueIdentifier(), item);
    }

    List<T> output = new ArrayList<T>();
    for (Collection<T> similarCollection : similarItemMap.asMap().values()) {
        List<T> similarItems = Lists.newArrayList(similarCollection);
        T source = similarItems.get(0);
        for (int i = 0; i < similarItems.size(); i++) {
            source = source.add(similarItems.get(i));
        }
        output.add(source);
    }

    for (T item : output) {
        if (item.hasSublist()) {
            item.setSublist(combine(item.getSublist));
        }
    }
    return output;
}

Realizing that this could create an infinite loop (unless the bottom class -- Grandchild -- implemented Sublistable and set hasSublist() { return false; }, or something), as well as the fact that this method is kind of crazy with generics, I have to ask: Is there some way that I can rewrite this a bit so that I can call the method in this way:

combine(listOfGrandparents)

or should I give up on this being one method and try to refactor it in a better way?


Edit: To better explain what it is that I'm trying to do, I have a list of objects of type A. Each object a has a list of objects of type B. Each object b has a list of objects of type C, and so on, until eventually type T (for some varying level of T) doesn't have a sublist anymore.

Each type basically has three things that it needs to do for a "merge" or "combine" method:

  1. Collect all "like" items into a container, using the item.getUniqueIdentifier() method
  2. Combine all the like items into one item, using the source.add(other) method
  3. If the item has a sublist, do a merge on the sublist.

Since each item behaves so similarly, it would be nice if I could use a single method instead of having to have n methods, one for each type. Unfortunately, since none of the types are guaranteed to be the same (other than having implemented some or all of the given interfaces above), creating a generic method proves to be difficult. Is there some way to do this that I'm missing?


Edit 2: I've found a method that kind of works. Basically, it changes to this:
public <V extends HasUniqueIdentifier & Combinable<V>, 
        T extends HasUniqueIdentifier & Combinable<T>> 
List<T> combine(List<T> items) {
    Multimap<String, T> similarItemMap = HashMultimap.create();
    for (T item: items) {
        similarItemMap.put(item.getUniqueIdentifier(), item);
    }

    List<T> output = new ArrayList<T>();
    for (Collection<T> similarCollection : similarItemMap.asMap().values()) {
        List<T> similarItems = Lists.newArrayList(similarCollection);
        T source = similarItems.get(0);
        for (int i = 0; i < similarItems.size(); i++) {
            source = source.add(similarItems.get(i));
        }
        output.add(source);
    }

    for (T item : output) {
        if (item instanceof Sublistable<?>) {
            @SuppressWarnings("unchecked")
            Sublistable<V> sublistableItem = ((Sublistable<V>)sublistableItem);
            if (sublistableItem.hasSublist()) {
                sublistableItem.setSublist(combine(sublistableItem.getSublist));
            }
        }
    }
    return output;
}

Unfortunatey, this method requires both a @SupressWarnings and an instanceof, which I would like to avoid if possible. I haven't found anything else, though, yet.

like image 701
ashays Avatar asked Mar 06 '13 07:03

ashays


1 Answers

Your first solution is fine, given that GrandChild implements Sublistable<Void> vacuously. The tree is more uniform and easier to handle recursively. You may also want to have one merged interface Node<T,V> extends HasUniqueIdentifier, Combinable<T>, Sublistable<V>{}

If you don't want GrandChild implements Sublistable, your 2nd solution is also fine. A single instanceof to test a marker interface isn't a sin. The code can be rewritten as

public <T extends HasUniqueIdentifier & Combinable<T>> 
List<T> combine(List<T> items) 
{
    ...
    List<T> output = new ArrayList<T>();
    ...

    for (T item : output) {
        if (item instanceof Sublistable<?>) 
            combineSublist((Sublistable<?>)item);

    return output;
}

private <V> void combineSublist(Sublistable<V> sublistableItem)
{
    if (sublistableItem.hasSublist()) {
        sublistableItem.setSublist(combine(sublistableItem.getSublist));
}
like image 188
irreputable Avatar answered Sep 21 '22 09:09

irreputable