Josh
Josh

Reputation: 7405

Java generics: matching the type of a parameter

How would I modify this so the resulting Collection (newNodes) is the same type as the incoming one (nodes)?

public void setNodes(Collection<NodeInfo> nodes) {
    Collection<NodeInfo> newNodes = new TreeSet<NodeInfo>();
    for (NodeInfo ni: nodes) {
        newNodes.add(ni.clone());
    }
}

I suspect it's something like...

public void setNodes(<T extends Collection<NodeInfo>> nodes) {
    Collection<NodeInfo> newNodes = new T<NodeInfo>()

Is this possible?

Upvotes: 1

Views: 1675

Answers (4)

Ernest Friedman-Hill
Ernest Friedman-Hill

Reputation: 81684

Note that many of the Collection implementations in the JDK implement Cloneable themselves. One "best effort" approach might be like this:

public Collection<NodeInfo> setNodes(Collection<NodeInfo> nodes) throws CloneNotSupportedException {
    Collection<NodeInfo) newNodes;

    if (nodes instanceof Cloneable) 
        newNodes = (Collection<NodeInfo>) newNodes.clone();
    else
        // Fallback in case we have a non-cloneable collection
        newNodes = new TreeSet<NodeInfo>();

    newNodes.clear();
    for (NodeInfo ni: nodes) {
        newNodes.add(ni.clone());
    }
    return newNodes;
}

This returns the same kind of collection for many inputs, but will fall back to returning a TreeSet as a default if it can't do any better.

Upvotes: 0

Thorn G
Thorn G

Reputation: 12766

Close, but no cigar. If I understand what you want to do, your method should look like:

public <T extends NodeInfo> void setNodes(Collection<T> nodes) {
    Collection<T> newNodes = new TreeSet<T>();
    for(T t : nodes) {
        newNodes.add(t);
    }
}

Upvotes: 2

DaveFar
DaveFar

Reputation: 7447

Unfortunately, you cannot do new T in Java: Since generics are implemented in Java via type erasure, the type information that is provided by a type parameter is only statically usable information, i.e. no longer available at runtime. Hence Java does not permit generic creation of objects (cf. Angelika Lange's Generics FAQ).

Alternatively, you could use:

  • a type token, i.e. use a Class<T> object as parameter to make the type available at runtime
  • use the signature void setNodes(Collection<NodeInfo> nodes, Collection<NodeInfo> newNodes) if you are able to create the suitable Collection elsewhere
  • use a standard Collection implementation if one is suitable, e.g. ArrayList<NodeInfo>
  • deep clone nodes, e.g. using The Cloning Library:

    Cloner cloner=new Cloner();

    @SuppressWarnings("unchecked") Collection<NodeInfo> newNodes = cloner.deepClone(nodes);

Upvotes: 1

jacobm
jacobm

Reputation: 14025

Unfortunately, it's not possible as you've written it in Java. If you need this effect, you've got a few choices:

If you're trying to optimize for a particular kind of collection, you can use an instanceof check to detect it. (For instance the Guava libraries often do this to detect immutable collections and handle them specially.)

If you really just need one collection to populate, you can ask the caller to provide you one.

public <C extends Collection<NodeInfo>> void setNodes(C nodes, C newNodes) {
  for (NodeInfo ni : nodes) {
    newNodes.add(ni);
  }
}

If you need the ability to make an arbitrary number of these collections on demand, then you can define a factory interface and make the caller provide an instance of it:

interface Factory<C extends Collection<NodeInfo>> {
  C newCollection();
}

public <C extends Collection<NodeInfo>> void setNodes(C nodes, Factory<C> factory) {
  C newNodes = factory.newCollection();
  for (NodeInfo ni : nodes) {
    newNodes.add(ni);
  }
}

Upvotes: 2

Related Questions