Program Tip

Java에서 일반 배열을 만드는 방법은 무엇입니까?

programtip 2020. 9. 27. 13:46
반응형

Java에서 일반 배열을 만드는 방법은 무엇입니까?


Java 제네릭 구현으로 인해 다음과 같은 코드를 가질 수 없습니다.

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

형식 안전성을 유지하면서 어떻게 구현할 수 있습니까?

Java 포럼에서 다음과 같은 솔루션을 보았습니다.

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

그러나 나는 정말로 무슨 일이 일어나고 있는지 이해하지 못한다.


답례로 질문을해야합니다. GenSet"선택"또는 "선택 취소"입니까? 그게 무슨 뜻입니까?

  • 선택 : 강력한 타이핑 . GenSet포함 된 객체의 유형을 명시 적으로 알고 있습니다 (즉, 생성자가 Class<E>인수를 사용하여 명시 적으로 호출 되었으며 메서드는 유형이 아닌 인수가 전달 될 때 예외를 발생 E시킵니다.을 참조하십시오 Collections.checkedCollection.

    ->이 경우 다음과 같이 작성해야합니다.

    public class GenSet<E> {
    
        private E[] a;
    
        public GenSet(Class<E> c, int s) {
            // Use Array native method to create array
            // of a type only known at run time
            @SuppressWarnings("unchecked")
            final E[] a = (E[]) Array.newInstance(c, s);
            this.a = a;
        }
    
        E get(int i) {
            return a[i];
        }
    }
    
  • 선택 취소 : 약한 입력 . 인수로 전달 된 개체에 대해 실제로 유형 검사가 수행되지 않습니다.

    ->이 경우 다음과 같이 작성해야합니다.

    public class GenSet<E> {
    
        private Object[] a;
    
        public GenSet(int s) {
            a = new Object[s];
        }
    
        E get(int i) {
            @SuppressWarnings("unchecked")
            final E e = (E) a[i];
            return e;
        }
    }
    

    배열의 구성 요소 유형은 유형 매개 변수를 지워야 합니다.

    public class GenSet<E extends Foo> { // E has an upper bound of Foo
    
        private Foo[] a; // E erases to Foo, so use Foo[]
    
        public GenSet(int s) {
            a = new Foo[s];
        }
    
        ...
    }
    

이 모든 결과는 Java에서 제네릭의 알려진 고의적 인 약점에서 비롯됩니다. 삭제를 사용하여 구현되었으므로 "제네릭"클래스는 런타임에 생성 된 유형 인수를 알지 못하므로 유형을 제공 할 수 없습니다. 명시적인 메커니즘 (유형 검사)이 구현되지 않는 한 안전합니다.


다음과 같이 할 수 있습니다.

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

이것은 Effective Java 에서 일반 콜렉션을 구현하는 제안 된 방법 중 하나입니다 . 항목 26 . 유형 오류가 없으며 배열을 반복해서 캐스팅 할 필요가 없습니다. 그러나 이것은 잠재적으로 위험하기 때문에 경고를 유발하므로주의해서 사용해야합니다. 주석에서 자세히 설명했듯이 이것은 Object[]이제 우리 E[]유형으로 가장 하고 있으며 ClassCastException안전하지 않게 사용 하면 예기치 않은 오류 또는 s 가 발생할 수 있습니다 .

경험상이 동작은 캐스트 배열이 내부적으로 (예 : 데이터 구조를 백업하기 위해) 사용되고 클라이언트 코드에 반환되거나 노출되지 않는 한 안전합니다. 제네릭 유형의 배열을 다른 코드로 반환해야하는 경우 Array언급 한 리플렉션 클래스가 올바른 방법입니다.


가능한 한 List제네릭을 사용하는 경우 배열보다는 s로 작업하는 것이 훨씬 더 행복 할 것 입니다. 물론 때로는 선택의 여지가 없지만 컬렉션 프레임 워크를 사용하는 것이 훨씬 더 강력합니다.


다음은 제네릭을 사용하여 유형 안전성을 유지하면서 찾고있는 유형의 배열을 정확하게 가져 오는 방법입니다 (배열을 돌려 Object주거나 컴파일 타임에 경고를 발생 시키는 다른 답변과는 반대로 ).

import java.lang.reflect.Array;  

public class GenSet<E> {  
    private E[] a;  

    public GenSet(Class<E[]> clazz, int length) {  
        a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));  
    }  

    public static void main(String[] args) {  
        GenSet<String> foo = new GenSet<String>(String[].class, 1);  
        String[] bar = foo.a;  
        foo.a[0] = "xyzzy";  
        String baz = foo.a[0];  
    }  
}

당신이 볼 수있는 경고없이 컴파일하고, 같은 main당신의 인스턴스 선언 어떤 유형, GenSet등, 당신은 할당 할 수있는 a해당 유형의 배열에, 당신은에서 요소를 할당 할 수 a배열 즉, 해당 유형의 변수 배열의 값이 올바른 유형입니다.

Java Tutorials 에서 설명한대로 클래스 리터럴을 런타임 유형 토큰으로 사용하여 작동합니다 . 클래스 리터럴은 컴파일러에서 java.lang.Class. 하나를 사용하려면 간단히 클래스 이름을 .class. 따라서 클래스를 나타내는 객체 String.class역할을합니다 . 이것은 또한 인터페이스, 열거 형, 임의 차원 배열 (예 :) , 프리미티브 (예 :) 및 키워드 (예 :)에 대해서도 작동합니다 .ClassStringString[].classint.classvoidvoid.class

Class그 자체는 일반 (으로 선언되며 Class<T>, 여기서 객체가 나타내는 T유형을 Class나타냄)은 유형이 String.class입니다 Class<String>.

따라서에 대한 생성자를 호출 할 때마다 인스턴스의 선언 된 유형 (예 : for ) GenSet배열을 나타내는 첫 번째 인수에 대한 클래스 리터럴을 전달 합니다. 프리미티브는 유형 변수에 사용할 수 없기 때문에 프리미티브 배열을 가져올 수 없습니다.GenSetString[].classGenSet<String>

생성자 내에서 메서드를 호출하면 메서드 cast호출 ObjectClass객체가 나타내는 클래스로 전달 된 인수 캐스트가 반환 됩니다. 에서 정적 메서드 newInstance호출하면 첫 번째 인수로 전달 객체가 나타내는 유형의 배열 두 번째 인수로 전달 된에 지정된 길이 의 배열 java.lang.reflect.Array로 반환됩니다 . 메소드 호출 복귀 어레이의 구성 형태를 나타내는 객체에 의해 표현 (예를 들어 메소드가 호출되는 객체 에 대해 , 경우 생성 객체 배열을 나타내지 않음).ObjectClassintgetComponentTypeClassClassString.classString[].classnullClass

마지막 문장은 완전히 정확하지 않습니다. 호출 String[].class.getComponentType()Class클래스를 나타내는 객체를 반환 String하지만 유형이 Class<?>아닌 Class<String>이므로 다음과 같은 작업을 수행 할 수 없습니다.

String foo = String[].class.getComponentType().cast("bar"); // won't compile

객체 Class를 반환하는 모든 메서드도 마찬가지입니다 Class.

이 답변에 대한 Joachim Sauer의 의견과 관련하여 (저는 그것에 대해 언급 할만한 충분한 평판이 없습니다), 캐스트를 사용하는 예제 T[]는 컴파일러 가이 경우 유형 안전성을 보장 할 수 없기 때문에 경고가 발생합니다.


Ingo의 의견에 대한 편집 :

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}

이것은 타입이 안전한 유일한 대답입니다.

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}

단지 추가 이상의 치수로 연장 []의 행 및 측정 파라미터들 newInstance()( T, 타입 파라미터 인 것은 clsA는 Class<T>, d1통한 d5정수이다)

T[] array = (T[])Array.newInstance(cls, d1);
T[][] array = (T[][])Array.newInstance(cls, d1, d2);
T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);

자세한 내용은 Array.newInstance()을 참조하십시오.


Java 8에서는 람다 또는 메서드 참조를 사용하여 일종의 일반 배열 생성을 수행 할 수 있습니다. 이것은 (를 전달하는 Class) 반사 접근법과 유사 하지만 여기서는 반사를 사용하지 않습니다.

@FunctionalInterface
interface ArraySupplier<E> {
    E[] get(int length);
}

class GenericSet<E> {
    private final ArraySupplier<E> supplier;
    private E[] array;

    GenericSet(ArraySupplier<E> supplier) {
        this.supplier = supplier;
        this.array    = supplier.get(10);
    }

    public static void main(String[] args) {
        GenericSet<String> ofString =
            new GenericSet<>(String[]::new);
        GenericSet<Double> ofDouble =
            new GenericSet<>(Double[]::new);
    }
}

예를 들어, <A> A[] Stream.toArray(IntFunction<A[]>).

익명 클래스를 사용하여 사전에 자바 (8)을 수행 할 수 있지만, 더 복잡합니다.


이 내용은 Effective Java, 2nd Edition , 항목 25 의 5 장 (Generics)에서 다룹니다. 배열보다 목록 선호

코드는 작동하지만 확인되지 않은 경고 (다음 주석으로 억제 할 수 있음)가 생성됩니다.

@SuppressWarnings({"unchecked"})

그러나 배열 대신 목록을 사용하는 것이 더 나을 것입니다.

OpenJDK 프로젝트 사이트 에이 버그 / 기능에 대한 흥미로운 토론이 있습니다 .


Java 제네릭은 컴파일 타임에 유형을 확인하고 적절한 캐스트를 삽입하지만 컴파일 된 파일에서 유형을 지워 작동 합니다. 이것은 제네릭을 이해하지 못하는 코드에서 제네릭 라이브러리를 사용할 수있게 해주지 만 (고의적 인 디자인 결정이었습니다) 런타임에 유형이 무엇인지 일반적으로 알 수 없다는 것을 의미합니다.

공용 Stack(Class<T> clazz,int capacity)생성자는 런타임에 Class 객체를 전달 해야합니다. 즉, 런타임에 필요한 코드에 클래스 정보 사용할 수 있습니다. 그리고 Class<T>형식은 컴파일러가 전달하는 Class 객체가 T 유형에 대한 Class 객체인지 정확히 확인합니다. T의 하위 클래스가 아니라 T의 수퍼 클래스가 아니라 정확히 T입니다.

그러면 생성자에서 적절한 유형의 배열 객체를 만들 수 있습니다. 즉, 컬렉션에 저장하는 객체의 유형은 컬렉션에 추가되는 시점에서 해당 유형이 확인됩니다.


안녕하세요 스레드가 죽었음에도 불구하고 이것에 주목하고 싶습니다.

Generics는 컴파일 시간 동안 유형 검사에 사용됩니다.

  • 따라서 목적은 들어오는 것이 필요한 것인지 확인하는 것입니다.
  • 반환하는 것은 소비자가 필요로하는 것입니다.
  • 이것을 확인하십시오 :

여기에 이미지 설명 입력

제네릭 클래스를 작성할 때 형변환 경고에 대해 걱정하지 마십시오. 사용할 때 걱정하십시오.


이 솔루션은 어떻습니까?

@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
    return elems;
}

그것은 작동하고 너무 단순 해 보인다. 단점이 있습니까?


생성자에 Class 인수를 전달할 필요가 없습니다. 이 시도.

public class GenSet<T> {
    private final T[] array;
    @SuppressWarnings("unchecked")
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        Class<?> c = dummy.getClass().getComponentType();
        array = (T[])Array.newInstance(c, capacity);
    }
    @Override
    public String toString() {
        return "GenSet of " + array.getClass().getComponentType().getName()
            + "[" + array.length + "]";
    }
}

GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));

결과:

GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]

이 코드도보십시오.

public static <T> T[] toArray(final List<T> obj) {
    if (obj == null || obj.isEmpty()) {
        return null;
    }
    final T t = obj.get(0);
    final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
    for (int i = 0; i < obj.size(); i++) {
        res[i] = obj.get(i);
    }
    return res;
}

모든 종류의 객체 목록을 동일한 유형의 배열로 변환합니다.


저에게 맞는 빠르고 쉬운 방법을 찾았습니다. Java JDK 8에서만 이것을 사용했습니다. 이전 버전에서 작동할지 모르겠습니다.

특정 유형 매개 변수의 제네릭 배열을 인스턴스화 할 수는 없지만 이미 생성 된 배열을 제네릭 클래스 생성자에 전달할 수 있습니다.

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

이제 기본적으로 다음과 같이 배열을 만들 수 있습니다.

class GenArrayDemo {
    public static void main(String[] args) {
        int size = 10; // array size
        // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
        Character[] ar = new Character[size];

        GenArray<Character> = new Character<>(ar); // create the generic Array

        // ...

    }
}

배열에 대한 더 많은 유연성을 위해 연결 목록을 사용할 수 있습니다. Java.util.ArrayList 클래스에있는 ArrayList 및 기타 메소드.


예제는 Java 리플렉션을 사용하여 배열을 만드는 것입니다. 이것은 형식이 안전하지 않기 때문에 일반적으로 권장되지 않습니다. 대신해야 할 일은 내부 목록을 사용하고 배열을 전혀 사용하지 않는 것입니다.


간단한 자동 테스트 유틸리티를 위해 전달되는 클래스를 반사적으로 인스턴스화하기 위해이 코드 조각을 만들었습니다.

Object attributeValue = null;
try {
    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }
    else if(!clazz.isInterface()){
        attributeValue = BeanUtils.instantiateClass(clazz);
    }
} catch (Exception e) {
    logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}

이 세그먼트를 참고하십시오.

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

배열 시작을 위해 Array.newInstance (배열의 클래스, 배열의 크기) . 클래스는 기본 (int.class) 및 객체 (Integer.class)가 될 수 있습니다.

BeanUtils는 Spring의 일부입니다.


실제로 그렇게하는 더 쉬운 방법은 다음 예제와 같이 객체의 배열을 만들고 원하는 유형으로 캐스팅하는 것입니다.

T[] array = (T[])new Object[SIZE];

여기서 SIZE상수이고, T타입 식별자


값 목록 전달 중 ...

public <T> T[] array(T... values) {
    return values;
}

다른 사람들이 제안한 강제 캐스트는 저를 위해 일하지 않았고 불법 캐스팅의 예외를 던졌습니다.

그러나이 암시 적 캐스트는 잘 작동했습니다.

Item<K>[] array = new Item[SIZE];

여기서 Item은 멤버를 포함하여 정의한 클래스입니다.

private K value;

이렇게하면 K 유형 (항목에 값만있는 경우)의 배열 또는 Item 클래스에 정의하려는 일반 유형을 얻을 수 있습니다.


귀하가 게시 한 예제에서 무슨 일이 일어나고 있는지에 대한 질문에 아무도 대답하지 않았습니다.

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

다른 사람들이 말했듯이 제네릭은 컴파일 중에 "지워진다". 따라서 런타임에 제네릭의 인스턴스는 구성 요소 유형이 무엇인지 알지 못합니다. 그 이유는 역사적으로 Sun은 기존 인터페이스 (소스 및 바이너리 모두)를 손상시키지 않고 제네릭을 추가하기를 원했습니다.

반면에 배열은 런타임에 자신의 구성 요소 유형을 알고있다.

이 예제는 생성자를 호출하는 코드 (유형을 알고 있음)가 클래스에 필요한 유형을 알려주는 매개 변수를 전달하도록하여 문제를 해결합니다.

따라서 응용 프로그램은 다음과 같은 클래스를 구성합니다.

Stack<foo> = new Stack<foo>(foo.class,50)

생성자는 이제 (런타임에) 구성 요소 유형이 무엇인지 알고 해당 정보를 사용하여 리플렉션 API를 통해 배열을 구성 할 수 있습니다.

Array.newInstance(clazz, capacity);

마지막으로 컴파일러가 반환 된 배열 Array#newInstance()이 올바른 유형 인지 알 수있는 방법이 없기 때문에 유형 캐스트가 있습니다 .

이 스타일은 약간 못 생겼지 만 어떤 이유로 든 런타임에 구성 요소 유형을 알아야하는 제네릭 유형을 만드는 데 가장 나쁜 해결책이 될 수 있습니다 (배열 생성 또는 구성 요소 유형의 인스턴스 생성 등).


이 문제에 대한 일종의 해결 방법을 찾았습니다.

아래 줄은 일반적인 배열 생성 오류를 발생시킵니다.

List<Person>[] personLists=new ArrayList<Person>()[10];

그러나 List<Person>별도의 클래스로 캡슐화 하면 작동합니다.

import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

getter를 통해 PersonList 클래스의 사람을 노출 할 수 있습니다. 아래 줄 List<Person>은 모든 요소에 있는 배열을 제공합니다 . 즉, List<Person>.

PersonList[] personLists=new PersonList[10];

나는 내가 작업하고 있던 일부 코드에서 이와 같은 것이 필요했고 이것이 내가 작동하도록 한 것입니다. 지금까지 아무런 문제가 없습니다.


Object 배열을 만들고 모든 곳에서 E로 캐스트 할 수 있습니다. 예, 그렇게하는 것은 매우 깨끗한 방법은 아니지만 적어도 작동해야합니다.


이 시도.

private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;

public MatrixData(int m, int n)
{
    this.m = m;
    this.n = n;

    this.elements = new Element[m][n];
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            this.elements[i][j] = new Element<T>();
        }
    }
}

이 문제에 대한 쉽고 복잡한 해결 방법은 메인 클래스 내에 두 번째 "홀더"클래스를 중첩하고이를 사용하여 데이터를 보관하는 것입니다.

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}

이 질문과 관련이 없을 수도 있지만 generic array creation사용시 " "오류가 발생하는 동안

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

나는 다음 작품을 찾고 (그리고 나를 위해 일했습니다) @SuppressWarnings({"unchecked"}):

 Tuple<Long, String>[] tupleArray = new Tuple[10];

이 코드가 효과적인 제네릭 배열을 생성하는지 궁금합니다.

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

편집 : 아마도 필요한 크기가 알려져 있고 작은 경우 그러한 배열을 만드는 다른 방법은 단순히 필요한 수의 "null"을 zeroArray 명령에 공급하는 것입니까?

분명히 이것은 createArray 코드를 사용하는 것만 큼 다재다능하지는 않습니다.


캐스트를 사용할 수 있습니다.

public class GenSet<Item> {
    private Item[] a;

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}

실제로 일반 배열을 시작할 수없는 것을 우회하는 매우 독특한 솔루션을 찾았습니다. 당신이해야 할 일은 다음과 같이 일반 변수 T를 취하는 클래스를 만드는 것입니다.

class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

그런 다음 배열 클래스에서 다음과 같이 시작하십시오.

GenericInvoker<T>[] array;
public MyArray(){
    array = new GenericInvoker[];
}

시작하면 new Generic Invoker[]선택되지 않은 문제가 발생하지만 실제로는 문제가 없어야합니다.

배열에서 가져 오려면 다음과 같이 array [i] .variable을 호출해야합니다.

public T get(int index){
    return array[index].variable;
}

배열 크기 조정과 같은 나머지는 다음과 같이 Arrays.copyOf ()를 사용하여 수행 할 수 있습니다.

public void resize(int newSize){
    array = Arrays.copyOf(array, newSize);
}

추가 기능은 다음과 같이 추가 할 수 있습니다.

public boolean add(T element){
    // the variable size below is equal to how many times the add function has been called 
    // and is used to keep track of where to put the next variable in the array
    arrays[size] = new GenericInvoker(element);
    size++;
}

private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}

일반 배열 생성은 Java에서 허용되지 않지만 다음과 같이 할 수 있습니다.

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}

참고 URL : https://stackoverflow.com/questions/529085/how-to-create-a-generic-array-in-java

반응형