sourcecode

Java에서 범용 어레이를 작성하는 방법

copyscript 2022. 8. 19. 21:01
반응형

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를 참조해 주세요.

    이 경우는, 다음과 같이 기입해 주세요.

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

    어레이의 컴포넌트 타입은 type 파라미터의 소거여야 합니다.

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

이는 효과적인 Java 항목 26에서 범용 컬렉션을 구현하는 권장 방법 중 하나입니다.유형 오류도 없고 어레이를 반복 캐스팅할 필요도 없습니다.그러나 이는 잠재적으로 위험하므로 주의하여 사용해야 합니다.댓글에 자세히 써있듯이Object[].E[], 예기치 않은 나 「예기치 않은 에러」를 이 있습니다.ClassCastException안전하지 않게 사용되는 경우.

일반적으로 이 동작은 캐스트 어레이가 내부적으로 사용되고(데이터 구조 백업 등), 클라이언트 코드에 반환되거나 노출되지 않는 한 안전합니다.코드로 , 「반사」는 「반사」입니다.Array을 사용법


가능한 경우라면, 고객님은 보다 행복한 시간을 보내실 수 있습니다.List을 사용하다물론 선택의 여지가 없는 경우도 있지만 컬렉션 프레임워크를 사용하는 것이 훨씬 더 강력합니다.

다음은 제네릭을 사용하여 원하는 유형의 배열을 확보하면서 유형 안전을 유지하는 방법입니다(다른 답변과 달리).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 튜토리얼에서 설명한 대로 클래스 리터럴을 런타임 유형 토큰으로 사용하여 작동합니다.클래스 리터럴은 컴파일러에 의해 의 인스턴스로 취급됩니다.java.lang.Class를하려면 , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,.classString.class을 하다ClassStringenum, 의 차원 배열enum, enum, enum, enum, any-dimension)에도 유효합니다.String[].class primitive(예: primitive:int.class 및 )는, 「」입니다.void (예:)void.class

Class이다.Class<T>서, snowledge.T'형'은 '형의입니다.Class representing는 오브젝트 이 나타내는 것을 합니다.String.classClass<String>.

래서의 를 걸면 so so so so so so so so so so so so so so so so so so so so so so so so so so so so so so so?GenSet하고, 클래스 은, 「클래스 리터럴」의 첫 번째 인수는 「클래스 리터럴」입니다GenSet된 유형 "Discerted Type")String[].class★★★★★★에GenSet<String>타입 변수에는 프리미티브를 사용할 수 없기 때문에, 프리미티브의 배열을 취득할 수 없습니다.

「」를 한다.cast된 반환을 .Object" "로 Class이치노 메서드 " " 를 합니다.newInstancejava.lang.reflect.Array로서 반환하다Object로 대표되는 유형의 배열Class 및 ""로 의 오브젝트"int두 번째 인수로 통과되었습니다.getComponentType를 반환하다Class의 오브젝트.Class "Manager")String.class★★★★★★에String[].class,nullClass개체가 배열을 나타내지 않습니다.)

그 마지막 문장은 완전히 정확하지 않다. " "String[].class.getComponentType()를 반환하다ClassString, 그 은 「」, 「」입니다Class<?> 아니라, 이에요.Class<String>'이렇게 하면 안 돼요'라고 합니다.

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

방법은 입니다.ClassClass★★★★★★ 。

답변에 대한 요아힘 사우어의 코멘트에 대해(본인은 코멘트를 할 만한 평판이 없습니다), 출연자를 사용한 예시는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 파라미터입니다.cls는 입니다.Class<T>,d1 through를 통해.d5 정수 : are ) ) :

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

자세한 것은, 을 참조해 주세요.

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

예를 들어, 이것은 에 의해 사용됩니다.

Java 8 이전 버전에서도 익명의 수업을 사용할 수 있지만, 더 번거롭습니다.

이는 유효 자바 제2판 항목 25의 제5장(일반)에 설명되어 있습니다.어레이보다 리스트 우선

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

@SuppressWarnings({"unchecked"})

단, 어레이 대신 목록을 사용하는 것이 좋습니다.

OpenJDK 프로젝트 사이트에서 이 오류/기능에 대한 흥미로운 설명이 있습니다.

Class 인수를 컨스트럭터에 전달할 필요는 없습니다.이거 먹어봐.

public class GenSet<T> {

    private final T[] array;

    @SafeVarargs
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        this.array = Arrays.copyOf(dummy, 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]

Java 제네릭은 컴파일 시 유형을 체크하고 적절한 캐스트를 삽입하지만 컴파일된 파일의 유형을 지우는 방식으로 작동합니다.이것에 의해, 범용 라이브러리는, 범용적인 코드(고의적인 설계 결정)를 이해하지 못하는 코드로 사용할 수 있게 됩니다만, 통상, 실행시에 타입을 특정할 수 없습니다.

★★★★★★★★★★★★★★★★」Stack(Class<T> clazz,int capacity)생성자는 런타임에 클래스 개체를 전달해야 합니다. 즉, 클래스 정보는 런타임에 필요한 코드를 작성할 수 있습니다.그리고 그Class<T> Tform의 합니다.T의 서브클래스도 아니고 T의 슈퍼클래스도 아니고, 정확히는 T입니다.

이는 생성자에 적절한 유형의 배열 개체를 만들 수 있음을 의미합니다. 즉, 컬렉션에 저장하는 개체 유형은 컬렉션에 추가된 시점에서 해당 유형이 선택됨을 의미합니다.

실타래가 끊어졌지만, 이 점에 주목해 주셨으면 합니다.

제네릭은 컴파일 시 타입 체크에 사용됩니다.따라서, 그 목적은 다음과 같은 사항을 확인하는 것이다.

  • 들어오는 것은 당신이 필요로 하는 것이다.
  • 반품하는 것은 소비자가 필요로 하는 것입니다.

체크해 주세요.

여기에 이미지 설명 입력

일반 클래스를 작성할 때는 타이핑 경고에 대해 걱정하지 마십시오. 사용할 때는 걱정하십시오.

이 솔루션은 어떻습니까?

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

그것은 효과가 있고 너무 단순해 보여서 사실이 아니다.무슨 단점이 있나요?

이 예에서는 Java 리플렉션을 사용하여 배열을 만듭니다.이것은 활자판이 아니기 때문에 일반적으로 권장되지 않습니다.대신 내부 목록을 사용하여 어레이를 전혀 사용하지 않도록 해야 합니다.

다음 코드도 참조해 주세요.

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

        // ...

    }
}

어레이의 유연성을 높이기 위해 링크 목록을 사용할 수 있습니다.예를 들어, Array List 및 Java.util에 있는 다른 메서드를 사용할 수 있습니다.ArrayList 클래스

값 목록을 전달하는 중...

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

간단한 자동 테스트 유틸리티로 통과된 클래스를 반영적으로 인스턴스화하기 위해 코드 스니펫을 만들었습니다.

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는 봄의 일부입니다.

다른 사람들이 제안한 강제 캐스팅은 제게 효과가 없었고, 불법 캐스팅을 예외로 했어요.

그러나 이 암묵적인 캐스팅은 잘 작동했습니다.

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

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

private K value;

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

실제로는 오브젝트 배열을 생성하여 다음 예시와 같이 원하는 타입으로 캐스트하는 것이 보다 쉬운 방법입니다.

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

서 ''는SIZE로, 「」입니다.T입니다.

당신이 올린 예에서 무슨 일이 일어나고 있는지에 대한 질문에 답한 사람은 아무도 없습니다.

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를 클래스의 할 수 .가 입니다.List<Person>모든 요소에서., 「」의 입니다.List<Person>.

PersonList[] personLists=new PersonList[10];

제가 작업하던 코드에 이런 게 필요했는데 이게 바로 작동시키기 위해 제가 한 일입니다.아직까지는 문제 없습니다.

Java에서는 범용 어레이 생성이 허용되지 않지만 다음과 같이 할 수 있습니다.

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

오브젝트 배열을 생성하여 모든 곳에 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[]체크되지 않은 상태에서 문제가 발생하지만 실제로는 문제가 없을 것입니다.

어레이에서 가져오려면 어레이[i]를 호출해야 합니다.다음과 같은 변수:

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

고정 사이즈의 범용 어레이를 랩하는 경우는, 그 어레이에 데이터를 추가하는 방법이 있기 때문에, 다음과 같이 어레이를 적절히 초기화할 수 있습니다.

import java.lang.reflect.Array;

class Stack<T> {
    private T[] array = null;
    private final int capacity = 10; // fixed or pass it in the constructor
    private int pos = 0;

    public void push(T value) {
        if (value == null)
            throw new IllegalArgumentException("Stack does not accept nulls");
        if (array == null)
            array = (T[]) Array.newInstance(value.getClass(), capacity);
        // put logic: e.g.
        if(pos == capacity)
             throw new IllegalStateException("push on full stack");
        array[pos++] = value;
    }

    public T pop() throws IllegalStateException {
        if (pos == 0)
            throw new IllegalStateException("pop on empty stack");
        return array[--pos];
    }
}

이 경우 java.displect.reflect를 사용합니다.Array.newInstance를 사용하여 어레이를 만듭니다.이 인스턴스는 오브젝트[]가 아니라 실제 T[]가 됩니다.최종적이지 않은 것은 여러분의 수업에서 관리되기 때문에 걱정하지 말아야 합니다.사용하는 타입을 취득하기 위해서는 push()에 non-null 객체가 필요하기 때문에 push()에서 push하여 예외를 발생시키는 데이터에 체크를 추가했습니다.

그래도 이것은 다소 의미가 없습니다.푸시를 통해 데이터를 저장하고 T 요소만 입력하도록 보장하는 메서드의 시그니처입니다.따라서 배열이 Object[] 또는 T[]인 것은 거의 무관합니다.

vnportnoy에 따르면

GenSet<Integer> intSet[] = new GenSet[3];

null 참조 배열을 만듭니다.

for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

안전한 타입입니다.

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

반응형