繁体   English   中英

将通用列表转换为数组

[英]Convert a generic list to an array

我已经搜索过这个,但不幸的是,我没有得到正确的答案。

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)

最后

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

这是由于类型擦除。 generics 在编译中被删除,因此Helper.toArray将被编译为返回一个Object[]

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

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

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

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

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 方法更加健壮,因为提供的参数告诉您想要返回什么类型的数组。

您不能像在此处那样实例化 Generic 类型:

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

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

请参阅 Guava 的Iterables.toArray(list, class)

例子:

@Test
public void arrayTest() {
    List<String> source = Arrays.asList("foo", "bar");
    String[] target = Iterables.toArray(source, String.class);
}
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());
}
String[] array = list.toArray(new String[0]);

问题是数组的组件类型不是字符串。

此外,最好不要提供空数组,例如 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 !
}

等待具体化 generics..

如前所述,这将起作用:

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

这也将起作用:

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

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

有效的解决方案!

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

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

我简单地使用这个 function。 IntelliJ 只是讨厌这种类型 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);

我写的这个要点很好地解决了这个问题。

根据siegiAtreys回答的建议,我编写了一个构造函数,它找到“最近的共同祖先”(NCA)class 并使用该 class 创建数组。 如果检查空值并且提供的 Collection 长度为 0 或全部为空,则默认类型为 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 中使用它的一些示例(为简洁起见,删除了“未选中”class 警告):

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

当您拥有通用List<T>时,您将能够在运行时知道 object 的 class。 因此,实现它的最佳方式是这样的:

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<T[]>参数?

因为,我们有一个通用列表,它不会提供获取精确类型数组所需的信息,当然,同时保持类型安全。 与其他答案相反,它要么返回 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

为什么它有效?

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

Class itself is generic (declared as Class<T[]> , where T[] stands for the type that the Class object is representing), meaning that the type of String[].class is Class<String[]> .

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM