Convert a generic list to an array

前端 未结 13 1248
隐瞒了意图╮
隐瞒了意图╮ 2020-12-05 17:13

I have searched for this, but unfortunately, I don\'t get the correct answer.

class Helper {
    public static  T[] toArray(List list) {
           


        
相关标签:
13条回答
  • 2020-12-05 17:32

    If you want to produce your method through brute force, and you can guarantee that you'll only call the method with certain restrictions, you can use reflection:

    public static <T> T[] toArray(List<T> list) {
        T[] toR = (T[]) java.lang.reflect.Array.newInstance(list.get(0)
                                               .getClass(), list.size());
        for (int i = 0; i < list.size(); i++) {
            toR[i] = list.get(i);
        }
        return toR;
    }
    

    This approach has problems. As list can store subtypes of T, treating the first element of the list as the representative type will produce a casting exception if your first element is a subtype. This means that T can't be an interface. Also, if your list is empty, you'll get an index out of bounds exception.

    This should only be used if you only plan to call the method where the first element of the list matches the Generic type of the list. Using the provided toArray method is much more robust, as the argument provided tells what type of array you want returned.

    0 讨论(0)
  • 2020-12-05 17:33

    This gist that I wrote gives a good solution to this problem.

    Following siegi's suggestion on Atreys' answer, I wrote a constructor which finds the "nearest common ancestor" (NCA) class and uses that class to create the array. If checks for nulls and if the provided Collection is length 0 or all nulls, the default type is Object. It totally ignores Interfaces.

    import java.util.Collection;
    import java.util.HashSet;
    import java.util.List;
    import java.util.ArrayList;
    import java.lang.reflect.Array;
    import java.util.Iterator;
    
    public class FDatum<T> {
    
      public T[] coordinates;
    
      // magic number is initial size -- assume <= 5 different classes in coordinates
      public transient HashSet<Class> classes = new HashSet<Class>(5);
    
      public FDatum (Collection<T> coordinates) {
    
        // to convert a generic collection to a (sort of) generic array,
        //   we need to bend the rules:
    
        //   1. default class T is Object
        //   2. loop over elements in Collection, recording each unique class:
        //     a. if Collection has length 0, or
        //        if all elements are null, class T is Object
        //     b. otherwise, find most specific common superclass, which is T
    
        // record all unique classes in coordinates
        for (T t : coordinates)  this.classes.add(t.getClass());
    
        // convert to list so we can easily compare elements
        List<Class> classes = new ArrayList<Class>(this.classes);
    
        // nearest common ancestor class (Object by default)
        Class NCA = Object.class;
    
        // set NCA to class of first non-null object (if it exists)
        for (int ii = 0; ii < classes.size(); ++ii) {
          Class c = classes.get(ii);
          if (c == null) continue;
          NCA = c; break;
        }
    
        // if NCA is not Object, find more specific subclass of Object
        if (!NCA.equals(Object.class)) {
          for (int ii = 0; ii < classes.size(); ++ii) {
            Class c = classes.get(ii);
            if (c == null) continue;
    
            // print types of all elements for debugging
            System.out.println(c);
    
            // if NCA is not assignable from c,
            //   it means that c is not a subclass of NCA
            // if that is the case, we need to "bump up" NCA
            //   until it *is* a superclass of c
    
            while (!NCA.isAssignableFrom(c))
              NCA = NCA.getSuperclass();
          }
        }
    
        // nearest common ancestor class
        System.out.println("NCA: " + NCA);
    
        // create generic array with class == NCA
        T[] coords = (T[]) Array.newInstance(NCA, coordinates.size());
    
        // convert coordinates to an array so we can loop over them
        ArrayList<T> coordslist = new ArrayList<T>(coordinates);
    
        // assign, and we're done!
        for (int ii = 0; ii < coordslist.size(); ++ii)
          coords[ii] = coordslist.get(ii);
    
        // that's it!
        this.coordinates = coords;
      }
    
      public FDatum (T[] coordinates) {
        this.coordinates = coordinates;
      }
    
    }
    

    Here are some examples of using it in jshell ("unchecked" class warnings removed for brevity):

    jshell> FDatum d = new FDatum(new ArrayList(Arrays.asList((double)1, (Double)3.3)))
    class java.lang.Double
    NCA: class java.lang.Double
    d ==> com.nibrt.fractal.FDatum@9660f4e
    
    jshell> d.coordinates
    $12 ==> Double[2] { 1.0, 3.3 }
    
    jshell> d = new FDatum(new ArrayList(Arrays.asList((double)1, (Double)3.3, (byte)7)))
    class java.lang.Byte
    class java.lang.Double
    NCA: class java.lang.Number
    d ==> com.nibrt.fractal.FDatum@6c49835d
    
    jshell> d.coordinates
    $14 ==> Number[3] { 1.0, 3.3, 7 }
    
    jshell> d = new FDatum(new ArrayList(Arrays.asList((double)1, (Double)3.3, (byte)7, "foo")))
    class java.lang.Byte
    class java.lang.Double
    class java.lang.String
    NCA: class java.lang.Object
    d ==> com.nibrt.fractal.FDatum@67205a84
    
    jshell> d.coordinates
    $16 ==> Object[4] { 1.0, 3.3, 7, "foo" }
    
    0 讨论(0)
  • 2020-12-05 17:34
    public static <T> T[] toArray(Collection<T> c, T[] a) {
        return c.size()>a.length ?
            c.toArray((T[])Array.newInstance(a.getClass().getComponentType(), c.size())) :
            c.toArray(a);
    }
    
    /** The collection CAN be empty */
    public static <T> T[] toArray(Collection<T> c, Class klass) {
        return toArray(c, (T[])Array.newInstance(klass, c.size()));
    }
    
    /** The collection CANNOT be empty! */
    public static <T> T[] toArray(Collection<T> c) {
        return toArray(c, c.iterator().next().getClass());
    }
    
    0 讨论(0)
  • 2020-12-05 17:36

    See Guava's Iterables.toArray(list, class).

    Example:

    @Test
    public void arrayTest() {
        List<String> source = Arrays.asList("foo", "bar");
        String[] target = Iterables.toArray(source, String.class);
    }
    
    0 讨论(0)
  • 2020-12-05 17:37

    The problem is the component type of the array that is not String.

    Also, it would be better to not provide an empty array such as new IClasspathEntry[0]. I think it is better to gives an array with the correct length (otherwise a new one will be created by List#toArray which is a waste of performance).

    Because of type erasure, a solution is to give the component type of the array.

    Example:

    public static <C, T extends C> C[] toArray(Class<C> componentType, List<T> list) {
        @SuppressWarnings("unchecked")
        C[] array = (C[])Array.newInstance(componentType, list.size());
        return list.toArray(array);
    }
    

    The type C in this implementation is to allow creation of an array with a component type that is a super type of the list element types.

    Usage:

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("abc");
    
        // String[] array = list.toArray(new String[list.size()]); // Usual version
        String[] array = toArray(String.class, list); // Short version
        System.out.println(array);
    
        CharSequence[] seqArray = toArray(CharSequence.class, list);
        System.out.println(seqArray);
    
        Integer[] seqArray = toArray(Integer.class, list); // DO NOT COMPILE, NICE !
    }
    

    Waiting for reified generics..

    0 讨论(0)
  • 2020-12-05 17:38

    As pointed earlier this will work:

    String[] array = list.toArray(new String[0]);
    

    And this will also work:

    String[] array = list.toArray(new String[list.size()]);
    

    However, in the first case a new array will be generated. You can see how this is implemented in Android:

    @Override public <T> T[] toArray(T[] contents) {
        int s = size;
        if (contents.length < s) {
            @SuppressWarnings("unchecked") T[] newArray
                = (T[]) Array.newInstance(contents.getClass().getComponentType(), s);
            contents = newArray;
        }
        System.arraycopy(this.array, 0, contents, 0, s);
        if (contents.length > s) {
            contents[s] = null;
        }
        return contents;
    }
    
    0 讨论(0)
提交回复
热议问题