카테고리 없음

[Java] ==, equals, Arrays.equals, Arrays.deepEquals 비교

키보드발 2022. 10. 9. 20:20

개념을 다시 보다가 Arrays.equals를 보고 조금 혼동이와서 관련 내용을 찾아보았다.

결론부터 얘기하자면

==비교를 주소비교라고 생각하면 안된다. 그건 String에서만 해당하는 이야기다.

equals는 결국 ==비교이다. 타입에 따라 ==비교가 주소에서 끝나느냐 기본타입까지 내려가느냐가 결정 되는 것이다.

Arrays.equals는 a[i]의 equals비교,

Arrays.deepEquals는 a[i]의 기본타입까지 파고들어가서 기본타입의 equal비교이다. -> 즉 값비교이다.

하지만 해당내용을 알기위해서는 아래의 글을 보는 것을 추천한다.

 

==과 equals는 우리가 잘 아는 개념이다.

 

String에서는

✔ ==은 주소 비교

✔ equals는 값비교

이다. 따라서 우리는 equals를 값비교라고 생각해온 것이다.

 

사실 equals가 값비교라고 하는것은 맞으면서 틀리다. 왜냐하면 하나 알아야할게 있다.

Object.equals와 String.equals는 다르다.

 

Object.Equals의 내부코드는 다음과 같다.

내부코드를 보면 알 수 있지만 Object.equals는 ==비교이다.

public class Object {

	public boolean equals(Object obj) {
	    return (this == obj);
	}

String.Equals의 내부코드는 다음과 같다.

public final class String
	
	public boolean equals(Object anObject) {
	    if (this == anObject) {
	        return true;
	    }
	    if (anObject instanceof String) {
	        String aString = (String)anObject;
	        if (coder() == aString.coder()) {
	            return isLatin1() ? StringLatin1.equals(value, aString.value)
	                              : StringUTF16.equals(value, aString.value);
	        }
	    }
	    return false;
	}

-----------------------------------------------------------------------


final class StringLatin1 {

    public static boolean equals(byte[] value, byte[] other) {
        if (value.length == other.length) {
            for (int i = 0; i < value.length; i++) {
                if (value[i] != other[i]) {
                    return false;
                }
            }
            return true;
        }

모든 코드를 이해하기는 힘들지만 대충 문자열의 바이트를 비교하여 같은지 다른지 확인하는 것을 알 수 있다.

 

->즉 Object의 equlas는 첫번째 단계의 값을 비교한다(int a=3이면 3을 가리키고 String s ="test"면 s의 주소값을 가리킨다) String의 equals는 값비교이다. 즉 String의 equals는 값비교임을 알 수 있다.

 

단 알아야할 것은 최종적으로 비교에서 ==을 쓰는것은 같다. 만약 objA와 objB가 참조 변수라면?

단지 object의 equal은 objA는 objA의 주소를 가지고 있고 objB는 ObjB의 주소를 가지고있다.

objA는 obJB와 같나요? -> objA의 주소는 obJB의 주소와 같나요?로 치환되는 것일 뿐이다.

->주소가 달라서 objA와 objB가 다르다는 결과를 낳을 수 있다.

 

하지만 주소가 다르지만 그 주소에 향하는 값이 같을 수가 있다. 따라서 그 주소에 향하는 값(기본타입)으로  == 비교를 하는 것이 String.equals이다.

여기서 항상 배웠던 내용이 있다. 기본타입에는 해당하는 값이 들어 있고 참조타입에는 주소(주소는 또 다른 주소 혹은 )가 들어있다.

즉 String의 가장 깊은 곳 -> 기본타입, 기본타입까지 들어가서 비교하는 것이 String.equals이다.

 

✔ 그렇다면 equals, Arrays,equals는 뭐냐?

첫번째로 eqauls, 말그대로 Object -> 예를 들어 String[][]( int[][], float[][]등 다됨 ) 의 equals를 쓰는 방법이다.

String[][] a = {{"1","2"},{"3","4"}};
String[][] b = {{"1","2"},{"3","4"}};

System.out.println(a.equals(b)); //false

String배열의 equals는 Object의 eqauls를 상속받아 사용한다. 결국 == 비교라는 것이다.

public boolean equals(Object obj) {
    return (this == obj);
}

a에는 {{"1","2"},{"3","4"}};의 주소값 b에는 {{"1","2"},{"3","4"}};의 주소값이 들어있다. 값은 같지만 주소값은 다르다

따라서 false가 나오는 것이다.

object equals는  ==비교이다. index값을 확인하지 않는다는 뜻이다equals를 사용하면 배열이라는 큰 주소의 끼리의 비교라는 것이다. "a의 주소와 b의 주소는 같나요?"와 같은 말이다.

int[] a= {1,2};
int[] b = {1,2};

System.out.println("int[] equal테스트:"+a.equals(b)); //배열에는 주소값이 들어있기때문에 false가 나온다.

 

즉 배열을 값을 비교하기 위해서는 인덱스 까지 비교할 새로운 equals가 필요하다.

-> 그게 바로 Arrays에서 제공하는 Arrays.equals이다.

Arrays.eqauls의 내부코드이다.

public static boolean equals(Object[] a, Object[] a2) {
    if (a==a2)
        return true;
    if (a==null || a2==null)
        return false;

    int length = a.length;
    if (a2.length != length)
        return false;

    for (int i=0; i<length; i++) {
        if (!Objects.equals(a[i], a2[i]))
            return false;
    }

    return true;
}

대충 내용을 보면

1. a와 a2의 주소를 비교

2. a와 a2중 null이 있는지 확인

3. a와 a2의 길이가 같은지 확이

4. a와 a2의 index별 Object의 equals비교

 

4번 이 가장 중요하다. 위에서 말했듯이 Object의 equals는 뭐라고? == 비교이다.

-> 즉 Arrays.eqauls는 a와 b의 인덱스들을 모두 ==비교해주는 것이다.

int[] a= {1,2};
int[] b = {1,2};
System.out.println("Arrays.equals(a, b) = " + Arrays.equals(a, b)); //true

 

따라서 기본타입인 배열의 각 인덱스에는 값이 들어있기때문에 ==비교를 통해 값이 같은지를 확인 할 수 있다.

여기서 문제가 생긴다. 만약 인덱스에 주소값이 들어간다면?

String[][] a2 = {{"1","2"},{"3","4"}};
String[][] b2 = {{"1","2"},{"3","4"}};
System.out.println("Arrays.equals(a2,b2) = " + Arrays.equals(a2, b2)); //false

예상했듯이 false가 나온다. 왜냐하면 a2[0]에는 {"1","2"}의 주소가 들어있는 것이지 값이 들어있는게 아니다.

마찬가지로 b2[0]에는 {"1","2"}의 주소가 들어있는 것이지 값이 들어있는게 아니다.

->a2[0]의 주소는 b2[0]의 주소와 같나요? 와 같은 물음이다.

즉 주소값이 다르고 값이 같을 수 있다는 것이다.

 

따라서 해당 주소의 값을 비교해줄 방법이 필요하다.

 

✔ 그게 바로 Arrays.deepEquals이다.

Arrays안에 있는 deepEquals의 코드이다. 중요한건 deepEquals와 deepEquals0의 코드 흐름이다.

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;
}

자세히 보면 재귀함수로 구현되어있다.

복잡해보이지만 코드를 따라가면 보인다.

예를 들어 a2, b2가 들어간다고 생각하자.

String[][] a2 = {{"1","2"},{"3","4"}};
String[][] b2 = {{"1","2"},{"3","4"}};

맨처음 a2와 b2를 deepEquals에 넣는다. for에서 a2[0]과 b2[0]에 접근하게 된다.

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

a2[0]과 b2[0]을 deepEquals0에 넣는다.

        boolean eq = deepEquals0(e1, e2);

deepEquals0에서는 a2[1]과 b[2]이 기본타입인지 판단한다.

 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);

아니라면 다시 deepEquals로 보낸다. 

deepEquals서는 a2[0][0]을 deepEquals0으로 보내고 a2[0][0]과 b[0][0]이 기본타입인지 판단한다.

아니라면 다시 deepEquals로 보낸다. 

이런식으로 재귀함수를 사용한다.

 

예를들어 a2[0]이라고 생각해보자. a2[0]에는 {"1","2"}의 주소가 들어있다. 즉 기본타입이아니라 참조타입 -> Object이다.(b2도 마찬가지이다)

따라서 deepEquals0가 deepEqulas로 보내 a2[0][0]에 접근하게하고 (b2도 마찬가지이다)

deepEquals가 deepEquals0으로 보내 a2[0][0]이 기본타입인지 판단하게 한다..(b2도 마찬가지이다)

이후 기본타입임이 판별되고 equals를 통해 ==비교를 한다. 최종적으로 값타입이기때문에 ==비교가 가능해진것이다.

String[][] a2 = {{"1","2"},{"3","4"}};
String[][] b2 = {{"1","2"},{"3","4"}};
System.out.println("Arrays.deepEquals(a2, b2) = " + Arrays.deepEquals(a2, b2));
      //Arrays.deepEquals(a2, b2) = true

즉 Arrays.equals는 a[i]의 equals비교,

Arrays.deepEquals는 a[i]의 기본타입까지 파고들어가서 기본타입의 equal비교이다. -> 즉 값비교이다.

 

 

공부자료:

 

https://www.logicbig.com/tutorials/core-java-tutorial/java-se-api/arrays-equality.html

 

Java - Arrays.equals vs Arrays.deepEquals vs equals

In this tutorial we will explore the different ways to compare two arrays. Assuming that we have two arrays: array1 and array2 of any types: array1. equals(array2) will be true only if they are the same instances (referencing same object) Arrays. equals(ar

www.logicbig.com