【问题标题】:Convert a generic list to an array将通用列表转换为数组
【发布时间】:2011-09-25 05:20:56
【问题描述】:

我已经搜索过这个,但很遗憾,我没有得到正确的答案。

class Helper {
    public static <T> T[] toArray(List<T> list) {
        T[] array = (T[]) new Object[list.size()];
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i);
        }
        return array;
    }
}

测试一下:

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("abc");
    String[] array = toArray(list);
    System.out.println(array);
}

但是抛出了一个错误:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at test.Helper.main(Helper.java:30)

如何解决?


更新

我想要这个方法,因为有时我的代码中的类型太长了:

newEntries.toArray(new IClasspathEntry[0])

我希望致电:

toArray(newEntries)

终于

好像不可能创建这样的方法,非常感谢大家!

【问题讨论】:

  • 你为什么不直接使用List.toArray?
  • @BjörnPollex:因为它返回 Object[] 而不是 T[]。
  • @Krzysiek Björn 的评论询问通用的 &lt;T&gt; T[] toArray(T[]) 版本。它是在 OP 编辑​​之前制作的,他们说有时类型太长。

标签: java generics


【解决方案1】:

这是由于类型擦除造成的。泛型在编译中被移除,因此Helper.toArray 将被编译为返回Object[]。

对于这种特殊情况,我建议您使用List.toArray(T[])。

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

【讨论】:

  • 不应该是String[] array = list.toArray(new String[0]);吗?
  • 两者都可以。
【解决方案2】:

您可以只调用list.toArray(T[] array) 而不必担心自己实现它,但正如aioobe 所说,由于类型擦除,您无法创建泛型类型的数组。如果您需要返回该类型,则需要自己创建一个类型化实例并将其传入。

【讨论】:

  • 明显的警告是您需要一个类型为 T 的数组。这取决于上下文,这可能是也可能不是问题。
  • 我不想用了,因为我的代码里的type太长了:newEntries.toArray(new IClasspathEntry[0]),我希望打电话给toArray(newEntries)
  • 所以写一个叫 toArray 的本地方法,在里面做 newEntries.toArray(new IClasspathEntry[0])
  • 我希望它是一个通用的辅助方法,在Java中是不可能的吗?
  • 要实现这一点,你必须创建像T[] array = new T[list.size()];这样的数组,但在java中这是不可能的。
【解决方案3】:

如果你想通过暴力生成你的方法,并且你可以保证你只会调用有一定限制的方法,你可以使用反射:

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

这种方法有问题。由于列表可以存储 T 的子类型,因此如果您的第一个元素是子类型,则将列表的第一个元素视为代表类型将产生强制转换异常。这意味着 T 不能是接口。此外,如果您的列表为空,您将获得索引越界异常。

仅当您仅计划调用列表的第一个元素与列表的通用类型匹配的方法时才应使用此选项。使用提供的 toArray 方法更加健壮,因为提供的参数告诉你想要返回什么类型的数组。

【讨论】:

  • 这让我觉得很脏。
  • 另外,如果第一个元素为空,你会得到一个空指针异常
  • 我同意它很脏,但我想这是在编译时不知道其类型的情况下创建这样一个数组的唯一方法。至少“第一个元素是子类型”问题可以通过遍历列表中的所有元素并寻找最具体的常见超类型来解决。如果所有元素都将 T 的某些子类型扩展为从例如Integer[] 到 Number[] 是可能的。其他问题(列表为空或仅包含 null 值,并且 T 不能成为接口,除非元素公共超类型实现它)仍然存在。
  • 肮脏但非常有效!
  • @siegi:如果您有兴趣,请在下面实现您的想法。
【解决方案4】:

你不能像这里那样实例化一个泛型类型:

 T[] array = (T[]) new Object[list.size()];

因为,如果T 被绑定到一个类型,那么您将新的Object 数组类型转换为有界类型T。我建议改用List.toArray(T[]) 方法。

【讨论】:

    【解决方案5】:

    查看 Guava 的 Iterables.toArray(list, class)。

    例子:

    @Test
    public void arrayTest() {
        List<String> source = Arrays.asList("foo", "bar");
        String[] target = Iterables.toArray(source, String.class);
    }
    

    【讨论】:

      【解决方案6】:
      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());
      }
      

      【讨论】:

        【解决方案7】:
        String[] array = list.toArray(new String[0]);
        

        【讨论】:

        • 这提出了一个很好的观点。 toArray 的参数确实 not 必须与返回的数组大小相同!它只能用于指定类型!
        • 关于@OgrePsalm33 提出的具体观点,请查看以下线程中AlexR 的评论:stackoverflow.com/questions/7969023/from-arraylist-to-array
        • 根据 Android Studio 的检查:在较旧的 Java 版本中,建议使用预先确定大小的数组,因为创建适当大小的数组所必需的反射调用非常慢。然而,由于 OpenJDK 6 的更新较晚,这个调用是内在的,使得空数组版本的性能相同,有时甚至更好......此外,传递预定大小的数组对于并发或同步集合是危险的,因为数据竞争是可能的如果集合同时收缩,则 size 和 toArray 调用可能会导致数组末尾出现额外的空值。
        【解决方案8】:

        问题是数组的组件类型不是String。

        另外,最好不要提供空数组,例如 new IClasspathEntry[0]。 我认为最好给出一个长度正确的数组(否则 List#toArray 会创建一个新数组,这会浪费性能)。

        由于类型擦除,一个解决方案是给出数组的组件类型。

        例子:

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

        此实现中的类型 C 允许创建具有组件类型的数组,该组件类型是列表元素类型的超类型。

        用法:

        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 !
        }
        

        等待具体化的泛型..

        【讨论】:

        • 我不明白为什么您的示例应该编译...您正在将 STRING 列表转换为 INTEGER 数组:-\
        • 最后一行不编译,这就是目标。它证明了这个静态 toArray() 确保您不能将列表转换为具有不同组件类型或列表项类型的超类型的数组。
        【解决方案9】:

        如前所述,这将起作用:

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

        这也将起作用:

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

        但是,在第一种情况下,将生成一个新数组。你可以看看这是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;
        }
        

        【讨论】:

          【解决方案10】:

          有效的解决方案!

          只需在项目中复制接口和类即可。 这个:

          public interface LayerDataTransformer<F, T> {
              T transform(F from);
          
              Collection<T> transform(Collection<F> from);
          
              T[] toArray(Collection<F> from);
          }
          

          还有这个:

          public abstract class BaseDataLayerTransformer<F, T> implements LayerDataTransformer<F, T> {
          
              @Override
              public List<T> transform(Collection<F> from) {
                  List<T> transformed = new ArrayList<>(from.size());
          
                  for (F fromObject : from) {
                      transformed.add(transform(fromObject));
                  }
          
                  return transformed;
              }
          
              @Override
              public T[] toArray(Collection<F> from) {
                  Class<T> clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
                  T[] transformedArray = (T[]) java.lang.reflect.Array.newInstance(clazz, from.size());
          
                  int index = 0;
                  for (F fromObject : from) {
                      transformedArray[index] = transform(fromObject);
                      index++;
                  }
          
                  return transformedArray;
              }
          }
          

          用法。

          声明 BaseDataLayerTransformer 的子类

          public class FileToStringTransformer extends BaseDataLayerTransformer<File,String> {
              @Override
              public String transform(File file) {
                  return file.getAbsolutePath();
              }
          }
          

          并使用:

          FileToStringTransformer transformer = new FileToStringTransformer();
          List<File> files = getFilesStub();// returns List<File>
          //profit!
          String[] filePathArray = transformer.toArray(files);
          

          【讨论】:

            【解决方案11】:

            我使用这个简单的功能。 IntelliJ 只是讨厌 type cast T[] 但它工作得很好。

            public static <T> T[] fromCollection(Class<T> c, Collection<T> collection) {
                return collection.toArray((T[])java.lang.reflect.Array.newInstance(c, collection.size()));
            }
            

            调用看起来像这样:

            Collection<Integer> col = new ArrayList(Arrays.asList(1,2,3,4));    
            fromCollection(Integer.class, col);
            

            【讨论】:

              【解决方案12】:

              This gist that I wrote 很好地解决了这个问题。

              根据siegi 对Atreys 的回答的建议,我编写了一个构造函数,它找到“最近的共同祖先”(NCA)类并使用该类来创建数组。如果检查 null 并且提供的 Collection 的长度为 0 或全部为 null,则默认类型为 Object。它完全忽略了接口。

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

              以下是在 jshell 中使用它的一些示例(为简洁起见,删除了“未检查”类警告):

              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" }
              

              【讨论】:

              • 这样的代码有一些问题。 (1) 如果集合为空则不起作用,因为Object[] 可能不是T[] 的子类型。 (2) 事后写入数组是不安全的。例如,原始集合可能是new ArrayList&lt;Number&gt;(Arrays.asList(1, 2)),在这种情况下您会得到Integer[]。这将转换为Number[],这本身就很好,但是如果您尝试输入例如,它会抛出ArrayStoreException。 Double 在里面。
              • 嗨@Radiodef,感谢您的反馈。 ArrayStoreException 可以通过使 FDatum 对象不可变来处理,对吗?我们可以将coordinates 参数传递给构造函数并将其深度复制到内部集合中,我们只允许用户通过 unmodifiableCollection() 访问该集合。这也将解决您的问题 (1),因为 NCA 将是不可变的,并且在构造对象时确定。你怎么看?
              • 如果它是不可变的,那么数组存储应该没有问题。虽然,我猜另一种解决方案是使用新类型重新创建数组。因此,在我的示例中,您会发现Double 不能分配给Integer[] 的组件类型,然后您将再次通过最具体的超类型算法运行它并获得Number[]。 (对于空集合/数组,问题是您不能将Object[] 作为T[] 返回到外部世界,但我看不到解决办法。)
              • 啊,我明白了。所以这个解决方案并没有真正回答所提出的问题,即返回一个通用数组。谢谢!
              【解决方案13】:

              当你有一个通用的List&lt;T&gt; 时,你将能够在运行时知道对象的类。因此,最好的实现方式是这样的:

              public static <T> T[] list2Array(Class<T[]> clazz, List<T> elements)
              {
                  T[] array = clazz.cast(Array.newInstance(clazz.getComponentType(), elements.size()));
                  return elements.toArray(array);
              }
              

              为什么需要Class&lt;T[]&gt; 参数?

              因为,我们有一个通用列表,它不会提供必要的信息来获取我们正在寻找的类型的数组,当然,同时保持类型安全。与其他答案相反,它要么返回一个 Object 数组,要么在编译时导致警告。这种方法将为您提供一个干净的解决方案。这里的“hack”是clazz.cast() 调用,对于您声明list2Array() 实例的任何类型,它都会在没有警告的情况下编译。

              现在,如何使用它?

              简单,就这样称呼吧:

              List<String> list = Stream.of("one", "two", "three").collect(Collectors.toList());
              String[] numbers = list2Array(String[].class, list);
              System.out.println(Arrays.toString(numbers));
              

              这是它的编译示例:https://ideone.com/wcEPNI

              为什么有效?

              之所以有效,是因为类文字被编译器视为java.lang.Class 的实例。这也适用于接口、枚举、任意维数组(例如String[].class)、原语和关键字 void。

              Class本身是泛型的(声明为Class&lt;T[]&gt;,其中T[]代表Class对象所代表的类型),也就是说String[].class的类型是Class&lt;String[]&gt;。

              注意:您将无法获得基元数组,因为基元不能用于类型变量。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-07-11
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-11-26
                相关资源
                最近更新 更多