Преобразование общего списка в массив


Я искал это, но, к сожалению, я не получаю правильный ответ.

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

13 ответов:

вы можете просто позвонить list.toArray(T[] array) и не нужно беспокоиться о реализации его самостоятельно, но, как сказал aioobe, вы не можете создать массив универсального типа из-за стирания типа. Если вам нужен этот тип обратно, вам нужно создать типизированный экземпляр самостоятельно и передать его.

это связано с типом стирания. Дженерики удаляются в компиляции, таким образом Helper.toArray будет скомпилирован в возврат Object[].

для этого конкретного случая, я предлагаю вам использовать List.toArray(T[]).

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

Если вы хотите создать свой метод с помощью грубой силы, и вы можете гарантировать, что вы будете вызывать метод только с определенными ограничениями, вы можете использовать отражение:

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[] array = (T[]) new Object[list.size()];

, если T ограничен типом, вы набираете новый Object массив с ограниченным типом T. Я бы предложил использовать List.toArray(T[]) метод вместо этого.

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

смотрите Guava Iterables.toArray(list, class).

пример:

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

как отмечалось ранее, это будет работать:

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

проблема заключается в типе компонента массива, который не является строкой.

кроме того, было бы лучше не предоставлять пустой массив, такой как 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 !
}

ожидание овеществленных дженериков..

работала решение!

просто скопировать интерфейс и класс внутри вашего проекта. Это:

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

Я использую эту функцию просто. 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);

это суть, что я написал дает хорошее решение этой проблемы.

после siegiна Atreys' ответ, я написал конструктор, который находит" ближайший общий предок " (NCA) класс и использует этот класс для создания массива. Если проверяется наличие нулей и если предоставленная коллекция имеет длину 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
 ==> 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
 ==> 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
 ==> Object[4] { 1.0, 3.3, 7, "foo" }

когда у вас есть родовая List<T> вы сможете узнать класс объекта во время выполнения. Поэтому лучший способ реализовать это выглядит так:

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

зачем это нужно