Преобразование общего списка в массив
Я искал это, но, к сожалению, я не получаю правильный ответ.
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 ответов:
вы можете просто позвонить
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[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); }
зачем это нужно