developer tip

일반 목록을 배열로 변환

copycodes 2020. 11. 30. 17:58
반응형

일반 목록을 배열로 변환


나는 이것을 검색했지만 불행히도 정답을 얻지 못했습니다.

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(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 메서드를 사용하는 것이 훨씬 더 강력합니다.


여기에서했던 것처럼 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);
}

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

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

그리고 이것은 또한 작동합니다.

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

문제는 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 !
}

수정 된 제네릭을 기다리는 중 ..


작동 솔루션!

프로젝트 내에서 인터페이스와 클래스를 복사하기 만하면됩니다. 이 :

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는 그 타입 캐스트 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) 클래스와 클래스가 배열을 만들 수 있다는 용도를 발견 생성자를 썼다. 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" }

제네릭이 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);
}

Class<T[]>매개 변수 가 필요한 이유는 무엇 입니까?

Because, we have a generic list and it will not provide the information necessary to get an array of precisely the type we are looking for, of course, while preserving type safety. As opposed to the other answers, which will either give you back an Object array or result in warnings at compile time. This approach will gives you a clean solution. The "hack" here is the clazz.cast() call, which compiles without warnings for whatever type you declare an instance of list2Array().

Now, how can you use it?

Simple, just call it like this:

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

Here is the compiling sample of this: https://ideone.com/wcEPNI

Why does it work?

It works because class literals are treated by the compiler as instances of java.lang.Class. This also works for interfaces, enums, any-dimensional arrays (e.g. String[].class), primitives and the keyword 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[]>.

Note: You won't be able to get an array of primitives, since primitives can't be used for type variables.

참고URL : https://stackoverflow.com/questions/6522284/convert-a-generic-list-to-an-array

반응형