Search code examples
javalistarraylistcopyclone

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);
    }

}

Solution

  • 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);