JavaRush /Java Blog /Random-KO /커피 브레이크 #130. Java 배열을 올바르게 사용하는 방법 - Oracle의 팁

커피 브레이크 #130. Java 배열을 올바르게 사용하는 방법 - Oracle의 팁

Random-KO 그룹에 게시되었습니다
출처: Oracle 배열 작업에는 리플렉션, 일반 및 람다 표현식이 포함될 수 있습니다. 나는 최근에 C로 개발하는 동료와 이야기를 나누었습니다. 대화는 배열과 C와 비교하여 Java에서 어떻게 작동하는지에 대한 이야기로 바뀌었습니다. Java가 C와 유사한 언어로 간주된다는 점을 고려하면 이것이 조금 이상하다는 것을 알았습니다. 실제로 그들은 많은 유사점을 가지고 있지만 차이점도 있습니다. 간단하게 시작해 보겠습니다. 커피 브레이크 #130.  Java 배열로 올바르게 작업하는 방법 - Oracle의 팁 - 1

배열 선언

Java 튜토리얼을 따르면 배열을 선언하는 두 가지 방법이 있음을 알 수 있습니다. 첫 번째는 간단합니다.
int[] array; // a Java array declaration
구문은 다음과 같습니다. C와 어떻게 다른지 확인할 수 있습니다.
int array[]; // a C array declaration
다시 자바로 돌아가자. 배열을 선언한 후에는 배열을 할당해야 합니다.
array = new int[10]; // Java array allocation
배열 선언과 초기화를 한번에 할 수 있나요? 실제로는 그렇지 않습니다.
int[10] array; // NOPE, ERROR!
그러나 값을 이미 알고 있다면 즉시 배열을 선언하고 초기화할 수 있습니다.
int[] array = { 0, 1, 1, 2, 3, 5, 8 };
의미를 모른다면? int 배열 을 선언하고, 할당하고, 사용하는 데 가장 자주 보게 될 코드는 다음과 같습니다 .
int[] array;
array = new int[10];
array[0] = 0;
array[1] = 1;
array[2] = 1;
array[3] = 2;
array[4] = 3;
array[5] = 5;
array[6] = 8;
...
Java 원시 데이터 유형 의 배열인 int 배열을 지정했다는 점에 유의하십시오 . 기본 요소 대신 Java 객체 배열을 사용하여 동일한 프로세스를 시도하면 어떤 일이 발생하는지 살펴보겠습니다.
class SomeClass {
    int val;
    // …
}
SomeClass[] array = new SomeClass[10];
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
위 코드를 실행하면 배열의 첫 번째 요소를 사용하려고 시도한 직후 예외가 발생합니다. 왜? 배열이 할당되더라도 배열의 각 세그먼트에는 빈 개체 참조가 포함됩니다. 이 코드를 IDE에 입력하면 자동으로 .val이 채워지므로 오류가 혼란스러울 수 있습니다. 버그를 수정하려면 다음 단계를 따르세요.
SomeClass[] array = new SomeClass[10];
for ( int i = 0; i < array.length; i++ ) {  //new code
    array[i] = new SomeClass();             //new code
}                                           //new code
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
그러나 그것은 우아하지 않습니다. 나는 왜 더 적은 코드로 배열과 그 배열 내의 객체를 쉽게 할당할 수 없는지, 심지어 한 줄에 모두 할당할 수 없는지 궁금했습니다. 그 답을 찾기 위해 여러 가지 실험을 해봤습니다.

Java 배열 사이에서 열반 찾기

우리의 목표는 우아하게 코딩하는 것입니다. “클린 코드” 규칙에 따라 배열 할당 패턴을 정리하기 위해 재사용 가능한 코드를 만들기로 결정했습니다. 첫 번째 시도는 다음과 같습니다.
public class MyArray {

    public static Object[] toArray(Class cls, int size)
      throws Exception {
        Constructor ctor = cls.getConstructors()[0];
        Object[] objects = new Object[size];
        for ( int i = 0; i < size; i++ ) {
            objects[i] = ctor.newInstance();
        }

        return objects;
    }

    public static void main(String[] args) throws Exception {
        SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32); // see this
        System.out.println(array1);
    }
}
"see this"라고 표시된 코드 줄은 toArray 구현 덕분에 내가 원하는 대로 정확하게 보입니다 . 이 접근 방식은 리플렉션을 사용하여 제공된 클래스의 기본 생성자를 찾은 다음 해당 생성자를 호출하여 해당 클래스의 개체를 인스턴스화합니다. 프로세스는 각 배열 요소에 대해 생성자를 한 번씩 호출합니다. 굉장한! 작동하지 않는 것이 유감입니다. 코드는 잘 컴파일되지만 실행 시 ClassCastException 오류가 발생합니다. 이 코드를 사용하려면 Object 요소의 배열을 만든 다음 다음과 같이 배열의 각 요소를 SomeClass 클래스로 캐스팅 해야 합니다 .
Object[] objects = MyArray.toArray(SomeClass.class, 32);
SomeClass scObj = (SomeClass)objects[0];
...
이건 우아하지 않아요! 더 많은 실험을 거친 후 리플렉션, 제네릭 및 람다 식을 사용하여 여러 가지 솔루션을 개발했습니다.

해결 방법 1: 리플렉션 사용

여기서는 기본 java.lang.Object 클래스를 사용 하는 대신 java.lang.reflect.Array 클래스를 사용하여 지정한 클래스의 배열을 인스턴스화합니다 . 이는 본질적으로 한 줄의 코드 변경입니다.
public static Object[] toArray(Class cls, int size) throws Exception {
    Constructor ctor = cls.getConstructors()[0];
    Object array = Array.newInstance(cls, size);  // new code
    for ( int i = 0; i < size; i++ ) {
        Array.set(array, i, ctor.newInstance());  // new code
    }
    return (Object[])array;
}
이 접근 방식을 사용하여 원하는 클래스의 배열을 얻은 다음 다음과 같이 작업할 수 있습니다.
SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32);
이는 필수 변경 사항은 아니지만 Array 리플렉션 클래스를 사용하여 각 배열 요소의 내용을 설정하도록 두 번째 줄이 변경되었습니다. 이거 엄청나 네! 그러나 올바르지 않은 세부 사항이 하나 더 있습니다. SomeClass[] 에 대한 캐스트가 별로 좋아 보이지 않습니다. 다행히도 제네릭을 사용한 솔루션이 있습니다.

해결 방법 2: 제네릭 사용

컬렉션 프레임워크는 유형 바인딩을 위해 제네릭을 사용 하고 많은 작업에서 제네릭에 대한 캐스트를 제거합니다. 여기에서는 제네릭을 사용할 수도 있습니다. 예를 들어 java.util.List를 살펴보겠습니다 .
List list = new ArrayList();
list.add( new SomeClass() );
SomeClass sc = list.get(0); // Error, needs a cast unless...
위 코드 조각의 세 번째 줄은 첫 번째 줄을 다음과 같이 업데이트하지 않으면 오류가 발생합니다.
List<SomeClass> = new ArrayList();
MyArray 클래스 에서 제네릭을 사용하여 동일한 결과를 얻을 수 있습니다 . 새 버전은 다음과 같습니다.
public class MyArray<E> {
    public <E> E[] toArray(Class cls, int size) throws Exception {
        E[] array = (E[])Array.newInstance(cls, size);
        Constructor ctor = cls.getConstructors()[0];
        for ( int element = 0; element < array.length; element++ ) {
            Array.set(array, element, ctor.newInstance());
        }
        return arrayOfGenericType;
    }
}
// ...
MyArray<SomeClass> a1 = new MyArray(SomeClass.class, 32);
SomeClass[] array1 = a1.toArray();
좋아 보인다. 제네릭을 사용하고 선언에 대상 유형을 포함하면 다른 작업에서 해당 유형을 추론할 수 있습니다 . 또한 다음을 수행하면 이 코드를 한 줄로 줄일 수 있습니다.
SomeClass[] array = new MyArray<SomeClass>(SomeClass.class, 32).toArray();
임무 완수됐죠? 글쎄요. 어떤 클래스 생성자를 호출하는지 상관하지 않는 경우에는 문제가 없지만 특정 생성자를 호출하려는 경우에는 이 솔루션이 작동하지 않습니다. 이 문제를 해결하기 위해 계속해서 리플렉션을 사용할 수 있지만 그렇게 되면 코드가 복잡해집니다. 다행히도 다른 솔루션을 제공하는 람다 표현식이 있습니다.

해결 방법 3: 람다 식 사용

인정하겠습니다. 이전에는 람다 표현식에 특별히 관심이 없었지만 그 표현에 감사하는 법을 배웠습니다. 특히 객체 컬렉션을 처리하는 java.util.stream.Stream 인터페이스가 마음에 들었습니다. Stream은 제가 Java 배열 열반에 도달하는 데 도움이 되었습니다. 람다를 사용하는 첫 번째 시도는 다음과 같습니다.
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .toArray(SomeClass[]::new);
읽기 쉽도록 이 코드를 세 줄로 나누었습니다. 모든 조건을 충족하는 것을 볼 수 있습니다. 간단하고 우아하며, 개체 인스턴스의 채워진 배열을 생성하고, 특정 생성자를 호출할 수 있습니다. toArray 메소드 매개변수 SomeClass []::new 에 주의하세요 . 지정된 유형의 배열을 할당하는 데 사용되는 생성기 함수입니다. 그러나 현재 상태로는 이 코드에 작은 문제가 있습니다. 무한한 크기의 배열을 생성한다는 것입니다. 이것은 그다지 최적이 아닙니다. 하지만 문제는 다음과 같이 Limit 메소드를 호출하여 해결할 수 있습니다 .
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .limit(32)   // calling the limit method
    .toArray(SomeClass[]::new);
이제 배열은 32개 요소로 제한됩니다. 아래와 같이 배열의 각 요소에 대해 특정 개체 값을 설정할 수도 있습니다.
SomeClass[] array = Stream.generate(() -> {
    SomeClass result = new SomeClass();
    result.val = 16;
    return result;
    })
    .limit(32)
    .toArray(SomeClass[]::new);
이 코드는 람다 표현식의 강력한 기능을 보여 주지만 코드가 깔끔하거나 간결하지 않습니다. 제 생각에는 다른 생성자를 호출하여 값을 설정하는 것이 훨씬 더 좋을 것 같습니다.
SomeClass[] array6 = Stream.generate( () -> new SomeClass(16) )
    .limit(32)
    .toArray(SomeClass[]::new);
나는 람다 표현식 기반 솔루션을 좋아합니다. 특정 생성자를 호출하거나 배열의 각 요소를 사용하여 작업해야 할 때 이상적입니다. 더 간단한 것이 필요할 때 일반적으로 제네릭 기반 솔루션이 더 간단하기 때문에 사용합니다. 그러나 람다 식이 우아하고 유연한 솔루션을 제공한다는 것을 직접 확인할 수 있습니다.

결론

오늘 우리는 Java에서 리플렉션, 제네릭 및 람다 표현식을 사용하여 프리미티브 배열 선언 및 할당 , Object 요소 배열 할당 작업을 수행하는 방법을 배웠습니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION