3

(Title sounds a bit too fancy but I couldn't find a match so we'll hope it's descriptive.)

I have a working piece of code as follows:

@SuppressWarnings("unchecked")
public static Copyable[] copyOf(Copyable[] objects, Class type) {
    Copyable[] copies = Arrays.copyOf(objects, objects.length, type);
    for( int i=0; i < copies.length; ++i ) {
        if( objects[i] == null )
            throw new IllegalArgumentException("Copyable[] contains null reference", new NullPointerException());
        copies[i] = objects[i].copy();
    }

    return copies;
}

Not as pretty as I would like, since I have to pass in the array class, but it works for what I want it to do: allocate a new array of a Copyable implementor and fill it using the implemented method.

The problem I'm having is that this gets compiled by GWT, whose JRE Emulation library barfs on correct Java that it hasn't implemented. I need to do this without calling Arrays.copyOf(), and hopefully without reflection.

Note: I'm working on a clone() version but don't really want to rely on clone() either. I feel like there must be a cleaner solution.

orbfish
  • 6,349
  • 12
  • 50
  • 71

5 Answers5

2

How about this:

public static Copyable[] copyOf(Copyable[] objects) {
    Copyable[] copies = new Copyable[objects.length];
    for( int i=0; i < copies.length; ++i ) {
        if( objects[i] == null )
            throw new IllegalArgumentException("Copyable[] contains null reference", new NullPointerException());
        copies[i] = objects[i].copy();
    }
    return copies;
}

There's no need to actually copy the objects array into copies before making copies of the elements themselves.

EDIT

If your Copyable objects are serializable, you can just serialize and then deserialize the array to create a copy. There's a utility function DeepCopy.java in gwt-test-utils that may do exactly what you need.

Ted Hopp
  • 222,293
  • 47
  • 371
  • 489
  • I need to construct an array of the subtype. If I try to downcast that after the method returns I get a ClassCastException. – orbfish Nov 23 '11 at 18:51
  • Not sure how I could justify putting something from a test library into production code, but I'll take a look. – orbfish Nov 23 '11 at 19:08
  • @orbfish - I don't think that cod is itself under test; it's a utility to aid in testing. Besides, it's a pretty simple and well-known hack for copying a data structure. – Ted Hopp Nov 23 '11 at 19:18
1

Why can't you just do: Copyable[] copies = new Copyable[objects.length]; ? That line allocates the array and then you're already filling it with the for loop you included.

Chris
  • 21,620
  • 4
  • 53
  • 47
  • I need to construct an array of the subtype. If I try to downcast that after the method returns I get a ClassCastException. – orbfish Nov 23 '11 at 18:50
  • I'm not sure i understand. The copies array should be able to hold anything that descends from Copyable. What is the cast you're trying to do? – Chris Nov 23 '11 at 18:53
  • Argh I do not appear to be able to edit my own post.... OK here goes: say MyDO implements Copyable. The caller of the method creates a MyDO[] and passes it to (my) copyOf(). It takes the return and tries to cast it back to (MyDO). At this point, I would get a ClassCastException because the new array was constructed as new Copyable[]. It needs to be constructed as the subtype for this to work for me. Or so I've found. – orbfish Nov 23 '11 at 18:58
1

Perhaps with Generics:

public interface Copyable<T> {
  T copy();
}



import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

public class Test {
  static class My implements Copyable<My> {
    int x;

    public My copy() {
      My my = new My();
      my.x = this.x;
      return my;
    }
  }

  @SuppressWarnings("unchecked")
  public static <T extends Copyable<T>> T[] copyOf(T[] objects, Class<T> type) {
    List<T> copies = new ArrayList<T>();
    for (int i = 0; i < objects.length; ++i) {
      if (objects[i] == null) throw new IllegalArgumentException("Copyable[] contains null reference",
          new NullPointerException());
      copies.add((T) objects[i].copy());
    }
    T typeVar = null;
    try {
      typeVar = (T) Class.forName(type.getName()).newInstance();
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
    Object t = Array.newInstance(typeVar.getClass(),0);
    return copies.toArray((T[])t); 
  }

  public static void main(String[] args) {
    My[] stuff = new My[1];
    My elem = new My();
    elem.x = 1;
    stuff[0] = elem;
    My[] copies = copyOf(stuff, My.class);
    System.out.println(copies[0].x);
  }
}
GriffeyDog
  • 7,882
  • 3
  • 20
  • 32
  • Clever to abstract my interface! I know there are reasons you're not supposed to mix arrays and generics but I'll have to review that and take a look. – orbfish Nov 23 '11 at 19:34
  • I think with Generics, one does not even need an ArrayList: `T[] copies = (T[]) new Object[objects.length];` and later `return copies;` without casting. – arne.b Nov 23 '11 at 19:46
  • I've updated the code to something that actually works. It may be able to be simplified. But, you should be able to use the returned array as one of your Copyable subtype arrays. – GriffeyDog Nov 23 '11 at 20:46
0

Building on GriffeyDog's answer and some fun I had with generics recently, here is a solution without Class objects that works directly on arrays:

@SuppressWarnings("unchecked")
public static <T extends Copyable> T[] copyOf(T[] objects) {
    T[] copies = (T[]) new Object[objects.length];
    for (int i = 0; i < copies.length; ++i) {
        if (objects[i] == null)
            throw new IllegalArgumentException(/*...*/);
        copies[i] = (T) objects[i].copy();
    }
    return copies;
}
Community
  • 1
  • 1
arne.b
  • 3,934
  • 2
  • 21
  • 43
0

You can rely on clone() for the array itself, then use your copy() for each object:

@SuppressWarnings("unchecked")
public static Copyable[] copyOf(Copyable[] objects) {
    Copyable[] copies = (Copyable[]) objects.clone();
    for( int i=0; i < copies.length; ++i ) {
        if( objects[i] == null )
            throw etc;
        copies[i] = objects[i].copy();
    }
    return copies;
}

Every array implements the clone() method that returns a shallow copy of the array. So I think it's clean enough for your needs.

Notice that this way, you also don't need the argument Class type.

fernacolo
  • 5,982
  • 4
  • 35
  • 57