programming language/Java

[Java] 배열 - Arrays 정리하기 : 1탄 - copyOf, asList, binarySearch

공대키메라 2023. 2. 9. 11:15

요즘 필자는 코딩 테스트 책으로 공부하고 있는데, 

 

배열 관련 문제를 푸는데 유용한 Method 를 소개하고 있다.

 

사실 보면 다 아는것이 대부분인데 이것을 적재적소에 사용할 수 있는지는 별개의 문제이다.

 

고로, 이번 기회에 Arrays와 관련된 유용한 기능들을 정리하려고 한다. 

(별로 유용하지 않아도 봐주세요...ㅠㅠ)

 

참고한 사이트는 다음과 같다.

https://www.geeksforgeeks.org/arrays-aslist-method-in-java-with-examples/

https://www.geeksforgeeks.org/system-arraycopy-in-java/

https://coding-factory.tistory.com/548

1. 얕은 복사와 깊은 복사

얕은 복사과 깊은 복사에 대해 간단히 알고가자. 

 

얕은 복사(Shallow Copy) : 복사된 배열이나 원본배열이 변경될 때 서로 간의 값이 같이 변경됩니다.
깊은 복사(Deep Copy) : 복사된 배열이나 원본배열이 변경될 때 서로 간의 값은 바뀌지 않습니다.

 * 출처 : https://coding-factory.tistory.com/548

 

2. Arrays.copyOf()

java.util.Arrays 클래스의 메소드. 특정한 배열을 복사하고, truncate 하거나 padding처리한다.
그래서 copy본은 특정한 길이를 가진다.

 

많은 type의 배열을 복사할 수 있도록 메소드를 지원하고있다.

 

코드 어시스트로 확인한 목록

 

필자는 가장 위에 있는 메소드를 클릭해서 들어가봤다.

 

    ...
    
    public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
        return copy;
    }
    
    ...

 

내부적으로 또 다른 메소드를 호출하고 있는데??? System.arraycopy는 뭐야?

 

	...
 /*
	Params:
    src – the source array.
    srcPos – starting position in the source array.
    dest – the destination array.
    destPos – starting position in the destination data.
    length – the number of array elements to be copied.
    Throws:
    IndexOutOfBoundsException – if copying would cause access of data outside array bounds.
    ArrayStoreException – if an element in the src array could not be stored into the dest array because of a type mismatch.
    NullPointerException – if either src or dest is null.*/
	@HotSpotIntrinsicCandidate
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

 

설명이 매우 길지만 Params의 내용을 보면 다음과 같다. 

첫 번째 src 에는 내가 복사하고자 하는 Array를, 

두 번째 srcPos에는 복사하고자 하는 Array를 어디서부터 복사하고자 하는지 그 시작점을,

세 번째 dest에는 복사한 값을 받을 array를,

네 번째 destPos는 복사한 값이 들어갈 시작점을, 
다섯 번 째 length에는 복사될 길이를 넣어주면 된다.

 

실제로 그런지 테스트를 해보면...

 

	//System.arraycopy 테스트
    
    public static void main(String[] args) {
		int[] target1 = {1,2,3,4,5,6};
		int[] target2 = {9,10,11,12};
		System.arraycopy(target1, 2, target2, 0 , 4);
		for (int i : target2) {
			System.out.println("i = " + i);
		}
	}
    
    /*
    	출력 결과
        	i = 3
            i = 4
            i = 5
            i = 6

    */

 

문제는 최소값으로 복사시에 복사하고자 하는 범위가 뭔가 어긋나면 "Array out of bound exception" 터져버린다.

 

그래서 Arrays.copyOf에서는애초에 그러한 오류를 방지하고자 Math.min 메소드를 사용해서 넘겨주고 있다.

 

또한, 반환시 새로운 배열을 안에서 생성해서 반환해주고 있다.

 

2. Arrays.asList() 

java.util.Arrays 클래스의 메소드. array 기반과 collection기반의 API 사이의 bridge역할을 한다.
반환된 List는 serialize가 가능하고 RandomAccess 를 implement한다.

 

내부 코드를 살펴보자.

 

@SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

    /**
     * @serial include
     */
    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public Object[] toArray() {
            return Arrays.copyOf(a, a.length, Object[].class);
        }

        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return Arrays.copyOf(this.a, size,
                                     (Class<? extends T[]>) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }

        @Override
        public E get(int index) {
            return a[index];
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }

        @Override
        public int indexOf(Object o) {
            E[] a = this.a;
            if (o == null) {
                for (int i = 0; i < a.length; i++)
                    if (a[i] == null)
                        return i;
            } else {
                for (int i = 0; i < a.length; i++)
                    if (o.equals(a[i]))
                        return i;
            }
            return -1;
        }

        @Override
        public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }

        @Override
        public Spliterator<E> spliterator() {
            return Spliterators.spliterator(a, Spliterator.ORDERED);
        }

        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            for (E e : a) {
                action.accept(e);
            }
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            Objects.requireNonNull(operator);
            E[] a = this.a;
            for (int i = 0; i < a.length; i++) {
                a[i] = operator.apply(a[i]);
            }
        }

        @Override
        public void sort(Comparator<? super E> c) {
            Arrays.sort(a, c);
        }

        @Override
        public Iterator<E> iterator() {
            return new ArrayItr<>(a);
        }
    }

 

코드가 좀 길지만 앞에서부터 차근차근 읽어보자.

 

가변 변수로 asList에 값이 들어온다. 

 

이 값을 바탕으로 생성자를 통해 E[] a 변수에 바로 배열을 저장한다.

 

그리고 그것을 ArrayList로 반환해주면 된다.

 

일반적으로 사용하던 ArrayList와 기능들이 별반 다른것이 없다. 코드만 길 뿐!

(이 코드 때문에 글이 길어보인다... ㅠㅠ)

4. Arrays.binarySearch()

이름만 딱 보면 이진 검색인데...

 

과연 정말로 이진 검색을 수행해주고 있는지 볼 것이다.

 

코드 어시스트로 확인한 목록

 

코드를 들어가보니 닉값대로(?) 이진 검색을 해주고 있었다.

 

...
//사용전에 배열이 정렬되어 있어야 한다고 함.
public static int binarySearch(long[] a, long key) {
    return binarySearch0(a, 0, a.length, key);
}

...

// Like public version, but without range checks.
private static int binarySearch0(long[] a, int fromIndex, int toIndex,
                                 long key) {
    int low = fromIndex;
    int high = toIndex - 1;

    while (low <= high) {
        int mid = (low + high) >>> 1;
        long midVal = a[mid];

        if (midVal < key)
            low = mid + 1;
        else if (midVal > key)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found.
}

 

코드를 보면 우리가 흔히 인터넷이 "이진검색" 치면 볼 수 있는 코드가 떡! 내부로 구현되고 있었다.


이번 시간에는 Arrays에 관련된 메소드의 내부를 들여다 보았다.

 

사실 내용도 크게 어려운거 없고 그냥 내부 코드를 쓱 보면 아 이렇게 되어있네 뿌듯 ^^ 하는 정도였다.

 

다음에 이어지는 글에서 copyOfRange(a, 1, 5), equals(a, b), fill(a, 10), find(a, 28)를 더 알아볼 것이다.