programming language/Java

[Java] 배열 - Arrays 정리하기 : 2탄 - copyOfRange, equals, fill, deepEquals

공대키메라 2023. 2. 9. 23:57

지난 글에서 Arrays와 관련된 메서드들을 정리했다.

 

지난 내용이 궁금하면 여기 클릭!

 

이번 글에서는 copyOfRange, equals, fill, deepEquals 의 내부 코드를 살펴보고 사용법에 대해 알아볼 것이다.

 

참고한 내용은 다음과 같다.

https://www.geeksforgeeks.org/equals-and-deepequals-method-to-compare-two-arrays-in-java/

https://yeh35.github.io/blog.github.io/documents/java/java-assert/

1. Arrays.copyOfRange

지난 글에서 보았던 copyOf와 왠지 비슷할 것 같은 느낌이 든다.

 

copyOf

	//copyOf    
    @HotSpotIntrinsicCandidate
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

copyOfRange

@HotSpotIntrinsicCandidate
public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
    int newLength = to - from;
    if (newLength < 0)
        throw new IllegalArgumentException(from + " > " + to);
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, from, copy, 0,
                     Math.min(original.length - from, newLength));
    return copy;
}

 

그럼 그렇지! 그냥 copyOf를 단순히 복사하는게 아닌, 지정한 범위 내로 복사해 원하는 길이의 배열을 만들어낼 수 있다.

 

결국에 System.arraycopy를 사용하는 것이니 넘겨주는 파라미터만 조금 다른것 뿐이다.

 

//Arrays.copyOfRange 테스트
public static void main(String[] args) {
    int[] target = {1,2,3,4,5,6,7};
    int[] ints = Arrays.copyOfRange(target, 2, target.length - 1);
    for (int anInt : ints) {
        System.out.println("anInt = " + anInt);
    }
    /*
    	출력 결과
        anInt = 3
        anInt = 4
        anInt = 5
        anInt = 6
    */

}

2. Arrays.equals()

Arrays.equals도 찾아보니 지원하는 메소드가 아주 많다. 

 

가장 간단하게 맨 위의 친구를 선택해보았다.

 

 

Arrays.equals()

public static boolean equals(int[] a, int[] a2) {
    if (a==a2)  // 주소가 다르면 "==" 비교시 false인데... 왜이러징...?
        return true;
    if (a==null || a2==null) // 둘 중 하나라도 null이면 비교 자체가 불가능하니 false반환하는 듯
        return false;

    int length = a.length;
    if (a2.length != length) // 같으려면 길이가 같아야 하니 길이 체크도 하는 듯
        return false;

    return ArraysSupport.mismatch(a, a2, length) < 0; // 이거는 뭐야...?
}

 

코드를 잘 보다가 ArraysSupport.mismatch가 있는데 필자는 처음 보기에 찾아보기로 했다.

 

ArraySupport?

arrays와 함께 작동하는 유틸리티 메소드. 두개의 원시 타입 배열들 사이에 불일치를 찾기 위한 일련의 메소드들을 포함한다.
또한, 포함된 것에는 재할당된 배을의 새로운 길이를 계산하는 메소드가 있다. 

mismatch 메소드는 vector 기반의 기술을 사용하고 두 배열을 비교하는 vectorizedMismatch를 구현한다. 

...

 

여기에 또 vectorizedMismatch라는게 있어서 파고 파고 들어가니 너무 deep해지기 때문에...

우선은 vectorizedMismatch란 결국 vector기술을 사용해서 두 배열을 비교하는 거구나! 하고 넘어가려고 한다.

(다음에 기회가 되면 더 알아볼 계획이다.)

 

핵심은 ArraySupport가 Arrays를 도와주는 역할을 클래스 이름 그대로 수행하고 있다는 점이다.

 

ArraySupport.mismatch

public static int mismatch(boolean[] a,
                           boolean[] b,
                           int length) {
    int i = 0;
    if (length > 7) {
        if (a[0] != b[0])
            return 0;
        i = vectorizedMismatch(
                a, Unsafe.ARRAY_BOOLEAN_BASE_OFFSET,
                b, Unsafe.ARRAY_BOOLEAN_BASE_OFFSET,
                length, LOG2_ARRAY_BOOLEAN_INDEX_SCALE);
        if (i >= 0)
            return i;
        i = length - ~i;
    }
    for (; i < length; i++) {
        if (a[i] != b[i])
            return i;
    }
    return -1;
}

 

대강 보니 뭐가 안맞으면 일치하지 않는 index를 찾아서 반환해주는 것 같다.

 

    public static void main(String[] args) {
        int[] target = {1,2,3,4,5,6,7};
        int[] target2 = {1,2,3,4,5,6};
        int mismatch = Arrays.mismatch(target, target2);
        System.out.println(mismatch);
        // 결과 : 6
    }

 

정리하자면, Arrays.equals 메소드는 validation으로 길이는 같은지, 널이 있는지 확인한 후 ArraySupport.mismatch를 호출해서 전부 동일한지 다시 확인하는 것이다.

3. Arrays.fill

Arrays.fill 메소드의 경우에는 살펴보니

   1. 단순히 특정 배열에 한 수로 꽉 도배를 하고 싶은 경우의 기능과

   2. 범위를 입력한 시작점부터, 입력한 마지막지점까지 넣길 원하는 수를 넣는 기능

을 지원한다.

 

Arrays.fill 코드 어시스트

 

...

public static void fill(int[] a, int fromIndex, int toIndex, int val) {
    rangeCheck(a.length, fromIndex, toIndex);
    for (int i = fromIndex; i < toIndex; i++)
        a[i] = val;
}

public static void fill(int[] a, int val) {
    for (int i = 0, len = a.length; i < len; i++)
        a[i] = val;
}

...

 

뭐 차이가 없는데?

 

너무 간단하니 금방 넘어가자!

 

4. Arrays.deepEquals

Arrays클래스의 내부를 보던 중 deepEquals 메소드가 있어서 또 이게 신기해서 한 번 들여다보았다.

 

public static boolean deepEquals(Object[] a1, Object[] a2) {
    if (a1 == a2)
        return true;
    if (a1 == null || a2==null)
        return false;
    int length = a1.length;
    if (a2.length != length)
        return false;

    for (int i = 0; i < length; i++) {
        Object e1 = a1[i];
        Object e2 = a2[i];

        if (e1 == e2)
            continue;
        if (e1 == null)
            return false;

        // Figure out whether the two elements are equal
        boolean eq = deepEquals0(e1, e2);

        if (!eq)
            return false;
    }
    return true;
}

static boolean deepEquals0(Object e1, Object e2) {
    assert e1 != null;
    boolean eq;
    if (e1 instanceof Object[] && e2 instanceof Object[])
        eq = deepEquals ((Object[]) e1, (Object[]) e2);
    else if (e1 instanceof byte[] && e2 instanceof byte[])
        eq = equals((byte[]) e1, (byte[]) e2);
    else if (e1 instanceof short[] && e2 instanceof short[])
        eq = equals((short[]) e1, (short[]) e2);
    else if (e1 instanceof int[] && e2 instanceof int[])
        eq = equals((int[]) e1, (int[]) e2);
    else if (e1 instanceof long[] && e2 instanceof long[])
        eq = equals((long[]) e1, (long[]) e2);
    else if (e1 instanceof char[] && e2 instanceof char[])
        eq = equals((char[]) e1, (char[]) e2);
    else if (e1 instanceof float[] && e2 instanceof float[])
        eq = equals((float[]) e1, (float[]) e2);
    else if (e1 instanceof double[] && e2 instanceof double[])
        eq = equals((double[]) e1, (double[]) e2);
    else if (e1 instanceof boolean[] && e2 instanceof boolean[])
        eq = equals((boolean[]) e1, (boolean[]) e2);
    else
        eq = e1.equals(e2);
    return eq;
}

 

* assert 

Assert는 개발/테스트 단계에서 파라미터가 제대로 넘어왔는지 계산이 제대로 됬는지 혹은 특정 메소드가 작동하는 한계상황(Null이 들어오면 작동안함)을 정하는 용도로 사용한다.

 (출처 : https://yeh35.github.io/blog.github.io/documents/java/java-assert/)

 

위의 코드를 보면 배열 안에 배열이 있는 경우에도 동일한지 검사하게 작성이 되있고, 그렇지 않은 경우에는 value값만 비교를 하고 있었다.

 

5. Arrays.equals() vs Arrays.deepEquals()

사실 생각에도 없던 친구인데 얼핏 보기에는 같은 기능을 하는 거 같은데 왜 두개가 있는지를 모르겠다.

 

그러면 두 개의 메소드의 차이점은 무엇일까? 

 

이에 대해 geeksforgeeks에 정리 되어있으니 참고하면 된다.(여기 클릭)

 

이 글에서는 deepEquals는 nested-array 를 비교할 때에, equals는 non-nested array를 비교할 때 사용하길 추천한다.

 


안에 내부 로직을 들여다보니 특별히 코드가 엄청 대단하다거나 그런것은 아니엇다.

 

누구나 납득할 만한 코드로, 다만 많은 경우의 수를 생각해서 모든 상황에서도 쓸 수 있게끔 잘 만들어져 있을 뿐이었다.

 

지원하는 메소드들도 엄청 어려운 것은 아니엇으나 다시 보니 절로 고개가 끄덕여진다.