Lavish Kothari
Lavish Kothari

Reputation: 2331

Copy a list to another list without losing the runtime type of original list

I want to copy a List<T> to another list without losing the runtime type. All of the techniques that come to my mind is not achieving this. (Also, I don't want to simply return the reference of the input list as I don't want edits in input list to be reflected into the copied list)

What I tried (see all the copy* methods) is listed below. As expected, none of them is giving (x.getClass() == y.getClass()) to be true

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

public class TestX {
    public static void main(String[] args) {
        List<Integer> x = new LinkedList<>();
        x.add(1);
        x.add(2);
        x.add(3);

        List<Integer> y = copy2(x);
        System.out.println("is runtime type equal? " + (x.getClass() == y.getClass()));
        // I want (x.getClass() == y.getClass()) to be true
        
        y.add(4);
        System.out.println(x); // should be [1, 2, 3]
        System.out.println(y); // should be [1, 2, 3, 4]
    }

    private static <T> List<T> copy0(List<T> input) {
        return new ArrayList<>(input);
    }

    @SuppressWarnings("unchecked")
    private static <T> List<T> copy1(List<T> input) {
        Object[] x = input.toArray();
        return (List<T>) List.of(x);
    }

    private static <T> List<T> copy2(List<T> input) {
        return input.stream().collect(Collectors.toList());
    }

    private static <T> List<T> copy3(List<T> input) {
        // Since Java 10
        return List.copyOf(input);
    }

}

Upvotes: 1

Views: 248

Answers (3)

ZIHAO LIU
ZIHAO LIU

Reputation: 387

Try like this, the code shows below ignore try... catch...

private static <T> List<T> copy2(List<T> input) { 
    List<T> instance = input.getClass().newInstance();
    return input.stream().collect(Collectors.toCollection(() -> instance));
}

Upvotes: 0

Elliott Frisch
Elliott Frisch

Reputation: 201447

A LinkedList is not an ArrayList. If you want both types to be equal, then you might use reflection to invoke the default constructor of the actual class called in. Something like,

private static <T> List<T> copy0(List<T> input) {
    Class<?> cls = input.getClass();
    try {
        List<T> al = (List<T>) cls.getConstructor(null).newInstance(null);
        Iterator<T> iter = input.iterator();
        while (iter.hasNext()) {
            al.add(iter.next());
        }
        return al;
    } catch (Exception e) {
    }
    return null;
}

Which outputs

is runtime type equal? true

when I test it with your code. Note this makes dangerous assumptions about constructors and is not the approach I would take in a real project.

Upvotes: 0

ernest_k
ernest_k

Reputation: 45319

The best approach is to make the caller send in a function that will create a new list of the same type. The alternative would be to use reflection, which would begin a stream of dangerous assumptions.

The following two are examples of copy implementations adapted from your code:

private static <T> List<T> copy0(List<T> input, Supplier<List<T>> newListCreator) {
    List<T> newList = newListCreator.get();
    newList.addAll(input);

    return newList;
}

private static <T> List<T> copy2(List<T> input, Supplier<List<T>> newListCreator) {
    return input.stream().collect(Collectors.toCollection(newListCreator));
}

And you can call either in this way:

List<Integer> y = copy2(x, LinkedList::new);

Upvotes: 1

Related Questions