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
역할을합니다 . 이것은 또한 인터페이스, 열거 형, 임의 차원 배열 (예 :) , 프리미티브 (예 :) 및 키워드 (예 :)에 대해서도 작동합니다 .Class
String
String[].class
int.class
void
void.class
Class
그 자체는 일반 (으로 선언되며 Class<T>
, 여기서 객체가 나타내는 T
유형을 Class
나타냄)은 유형이 String.class
입니다 Class<String>
.
따라서에 대한 생성자를 호출 할 때마다 인스턴스의 선언 된 유형 (예 : for ) 의 GenSet
배열을 나타내는 첫 번째 인수에 대한 클래스 리터럴을 전달 합니다. 프리미티브는 유형 변수에 사용할 수 없기 때문에 프리미티브 배열을 가져올 수 없습니다.GenSet
String[].class
GenSet<String>
생성자 내에서 메서드를 호출하면 메서드 cast
가 호출 Object
된 Class
객체가 나타내는 클래스로 전달 된 인수 캐스트가 반환 됩니다. 에서 정적 메서드 newInstance
를 호출하면 첫 번째 인수로 전달 된 객체가 나타내는 유형의 배열 과 두 번째 인수로 전달 된에 지정된 길이 의 배열 java.lang.reflect.Array
로 반환됩니다 . 메소드 호출 복귀 어레이의 구성 형태를 나타내는 객체에 의해 표현 (예를 들어 메소드가 호출되는 객체 에 대해 , 경우 생성 객체 배열을 나타내지 않음).Object
Class
int
getComponentType
Class
Class
String.class
String[].class
null
Class
마지막 문장은 완전히 정확하지 않습니다. 호출 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
, 타입 파라미터 인 것은 cls
A는 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
'Program Tip' 카테고리의 다른 글
쉘 스크립트의 YYYY-MM-DD 형식 날짜 (0) | 2020.09.27 |
---|---|
Access-Control-Allow-Origin 헤더는 어떻게 작동합니까? (0) | 2020.09.27 |
Python에 사용할 IDE는 무엇입니까? (0) | 2020.09.27 |
메모장 ++에서 탭을 공백으로 변환 (0) | 2020.09.27 |
Android 에뮬레이터 디스플레이를 어떻게 회전합니까? (0) | 2020.09.27 |