Java에서 일반 배열을 만드는 방법은 무엇입니까?
-
22-08-2019 - |
문제
Java Generics의 구현으로 인해 다음과 같은 코드를 가질 수 없습니다.
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에서 알려진 의도적이고 고의적 인 제네릭의 약점에서 비롯된 것입니다. Erasure를 사용하여 구현되었으므로 "제네릭"클래스는 런타임에 어떤 유형의 인수가 생성되었는지 알지 못하므로 유형을 제공 할 수 없습니다. 안전한 메커니즘 (유형 확인)이 구현되지 않는 한 안전.
다른 팁
당신은 이것을 할 수 있습니다 :
E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];
이것은 일반 컬렉션을 구현하는 제안 된 방법 중 하나입니다. 효과적인 자바; 항목 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.lang.Class
. 하나를 사용하려면 단순히 클래스의 이름을 따르십시오. .class
. 그래서, String.class
역할을합니다 Class
클래스를 나타내는 개체 String
. 이것은 또한 인터페이스, 열거, 모든 차원 배열 (예 : String[].class
), 프리미티브 (예 : int.class
) 및 키워드 void
(즉 void.class
).
Class
그 자체는 일반적입니다 (선언된다 Class<T>
, 어디 T
그 유형을 나타냅니다 Class
객체를 나타내는 것), 즉 String.class
~이다 Class<String>
.
따라서 생성자를 호출 할 때마다 GenSet
, 당신은의 배열을 나타내는 첫 번째 인수에 대해 문자 그대로를 통과합니다. GenSet
인스턴스의 선언 된 유형 (예 : String[].class
~을 위한 GenSet<String>
). 프리미티브는 유형 변수에 사용할 수 없으므로 프리미티브 배열을 얻을 수 없습니다.
생성자 내부에서 방법을 호출합니다 cast
통과 된 것을 반환합니다 Object
인수는 Class
메소드가 호출 된 객체. 정적 방법을 호출합니다 newInstance
안에 java.lang.reflect.Array
로 반환합니다 Object
로 표시된 유형의 배열 Class
객체는 첫 번째 인수와 int
두 번째 논쟁으로 통과되었습니다. 방법을 호출합니다 getComponentType
반환 a Class
배열의 구성 요소 유형을 나타내는 객체 Class
방법이 호출 된 물체 (예 : String.class
~을 위한 String[].class
, null
만약 Class
객체는 배열을 나타내지 않습니다).
마지막 문장은 완전히 정확하지 않습니다. 부름 String[].class.getComponentType()
반환 a Class
클래스를 나타내는 개체 String
, 그러나 그 유형은입니다 Class<?>
, 아니다 Class<String>
, 이것이 당신이 다음과 같은 것을 할 수없는 이유입니다.
String foo = String[].class.getComponentType().cast("bar"); // won't compile
모든 방법에 대해서도 마찬가지입니다 Class
a 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);
}
더 많은 치수로 확장하려면 추가하십시오 []
S 및 치수 매개 변수로 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에서는 Lambda 또는 Method Reference를 사용하여 일종의 일반적인 배열 생성을 수행 할 수 있습니다. 이것은 반사 접근법과 유사합니다 (통과합니다. 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을 수행하지만 더 번거롭습니다.
이것은 5 장 (제네릭)으로 덮여 있습니다. 효과적인 Java, 2 판, 항목 25 ...배열에 목록을 선호합니다
코드는 확인되지 않은 경고가 생성되지만 다음과 같은 주석으로 억제 할 수 있습니다.
@SuppressWarnings({"unchecked"})
그러나 배열 대신 목록을 사용하는 것이 좋습니다.
이 버그/기능에 대한 흥미로운 토론이 있습니다. OpenJDK 프로젝트 사이트.
Java Generics는 컴파일 시간에 유형을 확인하고 적절한 캐스트를 삽입하여 작동하지만 지우기 컴파일 된 파일의 유형. 이로 인해 제네릭 라이브러리는 제네릭을 이해하지 못하는 코드 (고의적 인 디자인 결정)를 이해하지 못하지만 일반적으로 실행 시간에 유형이 무엇인지 알 수 없습니다.
공개 Stack(Class<T> clazz,int capacity)
생성자는 실행 시간에 클래스 객체를 전달해야합니다. 이는 클래스 정보를 의미합니다. ~이다 런타임에 필요한 코드로 제공됩니다. 그리고 Class<T>
형태는 컴파일러가 전달한 클래스 객체가 T 형의 서브 클래스가 아니라 t의 슈퍼 클래스가 아니라 정확하게 t의 클래스 객체인지 확인한다는 것을 의미합니다.
이것은 생성자에서 적절한 유형의 배열 객체를 만들 수 있음을 의미합니다. 즉, 컬렉션에 저장 한 개체의 유형이 컬렉션에 추가되는 지점에서 유형을 확인할 수 있습니다.
안녕하세요, 실이 죽었지 만 이것에 대한 관심을 끌고 싶습니다.
제네릭은 컴파일 시간 동안 유형 확인에 사용됩니다.
- 그러므로 목적은 당신이 필요한 것이 무엇인지 확인하는 것입니다.
- 당신이 반환하는 것은 소비자가 필요로하는 것입니다.
- 이것을 확인하십시오 :
일반적인 수업을 작성할 때 타임 캐스트 경고에 대해 걱정하지 마십시오. 당신이 그것을 사용할 때 걱정하십시오.
이 솔루션은 어떻습니까?
@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
return elems;
}
그것은 작동하고 너무 단순 해 보이기 어렵게 보입니다. 단점이 있습니까?
이 코드도보십시오.
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 및 기타 방법.
클래스 인수를 생성자에게 전달할 필요는 없습니다. 이 시도.
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]
예제는 Java 반사를 사용하여 배열을 만듭니다. 이 작업을 수행하는 것은 일반적으로 TypeSafe가 아니기 때문에 권장되지 않습니다. 대신, 당신이해야 할 일은 내부 목록을 사용하고 배열을 전혀 피하는 것입니다.
나는이 코드 스 니펫을 만들어 간단한 자동 테스트 유틸리티를 위해 전달되는 클래스를 반사적으로 인스턴스화했습니다.
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는 봄의 일부입니다.
실제로 그렇게하는 더 쉬운 방법은 객체 배열을 만들어 다음 예와 같이 원하는 유형으로 캐스팅하는 것입니다.
T[] array = (T[])new Object[SIZE];
어디 SIZE
일정하고 T
유형 식별자입니다
값 목록 전달 ...
public <T> T[] array(T... values) {
return values;
}
다른 사람들이 제안한 강제 캐스트는 나를 위해 일하지 않았으며 불법 캐스팅을 제외하고는 효과가 없었습니다.
그러나이 암시 적 캐스트는 잘 작동했습니다.
Item<K>[] array = new Item[SIZE];
여기서 항목은 멤버를 포함하는 클래스를 정의했습니다.
private K value;
이렇게하면 유형 k 배열 (항목에 값만있는 경우) 또는 클래스 항목에 정의하려는 일반 유형을 얻습니다.
당신이 게시 한 예에서 무슨 일이 일어나고 있는지에 대한 다른 사람은 아무도 대답하지 않았습니다.
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>();
}
}
당신은 수업에 사람을 노출시킬 수 있습니다. 아래 줄은 당신에게 배열을 줄 것입니다. List<Person>
모든 요소에서. 다시 말해서 배열 List<Person>
.
PersonList[] personLists=new PersonList[10];
나는 내가 작업하고 있던 일부 코드에서 이와 같은 것이 필요했고 이것이 내가 그것을 작동시키기 위해 한 일입니다. 지금까지 문제가 없습니다.
객체 배열을 생성하고 어디서나 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[];
}
시작 a new Generic Invoker[]
확인되지 않은 문제를 일으키지 만 실제로 문제가 없어야합니다.
배열에서 얻으려면 배열을 호출해야합니다. [i].
public T get(int index){
return array[index].variable;
}
배열 크기 조정과 같은 나머지는 Array.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];
}
}