01-03. 자료형과 객체 비교
Last updated
Last updated
null
을 허용하지 않지만, 참조 자료형은 null
을 허용합니다. 이 차이에 대해서 설명해주세요.int
, boolean
같은 기본 자료형은 실제 값이 메모리에 직접 저장되기 때문에 값이 없다는 개념인 null
을 가질 수 없습니다. 반면에 String
, Object
같은 참조 자료형은 객체의 메모리 주소를 저장하는 변수입니다. 따라서 참조할 객체가 없거나 아직 할당되지 않은 경우, 해당 메모리 주소가 없다는 의미로 null
을 가질 수 있습니다. null
은 어떤 객체도 참조하고 있지 않음을 나타냅니다.
Wrapper
클래스라는 것이 있는데, 이것이 무엇이며 null
을 사용할 수 있는 이유는 무엇인가요?Wrapper
클래스는 기본 자료형을 객체 형태로 다루기 위해 제공되는 클래스입니다. 예를 들어 int
는 Integer
, char
는 Character
로 감싸는 식이죠. 이 Wrapper
클래스들은 일반적인 참조 자료형과 동일하게 객체의 인스턴스를 나타내기 때문에, null
값을 가질 수 있습니다. 즉, 기본 자료형의 값을 객체로 감싸서 null
을 허용해야 하는 상황이나 컬렉션 프레임워크처럼 객체만을 다루는 곳에서 유용하게 사용됩니다.
Integer a = 100; Integer b = 100;
과 Integer c = 200; Integer d = 200;
코드가 있을 때, a == b
와 c == d
의 결과가 다르게 나올 수 있습니다. 그 이유는 무엇일까요?위의 코드의 결과가 다르게 나올 수 있는 이유는 Integer 클래스의 내부 캐싱(caching) 메커니즘 때문입니다.
자바의 Integer
클래스는 성능 최적화를 위해 -128부터 127까지의 정수 값에 대해서는 미리 생성된 Integer
객체를 캐싱해둡니다. 따라서 Integer.valueOf()
메서드를 통해 이 범위 내의 값을 생성할 때는 항상 동일한 캐시된 객체의 참조를 반환합니다.
Integer a = 100; Integer b = 100;
의 경우, 100
은 캐싱 범위(-128 ~ 127) 안에 있기 때문에 a
와 b
는 동일한 캐시된 Integer
객체를 참조하게 됩니다. 따라서 a == b
는 true
를 반환합니다.
반면 Integer c = 200; Integer d = 200;
의 경우, 200
은 캐싱 범위를 벗어납니다. 이 값들은 매번 새로운 Integer
객체를 생성하게 됩니다. 비록 값이 같더라도 서로 다른 메모리 주소에 있는 객체가 되므로, c == d
는 false
를 반환하게 됩니다.
이러한 특성 때문에 Wrapper
클래스의 값을 비교할 때는 ==
연산자 대신 equals()
메서드를 사용하는 것이 안전하고 권장됩니다. equals()
메서드는 항상 객체의 논리적인 값 동등성을 비교하기 때문입니다.
equals
메서드는 무엇이며, 왜 오버라이딩이 필요한가요?equals
메서드는 두 객체가 논리적으로 동일한지를 비교하는 데 사용됩니다.
Object
클래스에 기본적으로 구현된 equals
메서드는 두 객체의 메모리 주소를 비교하여 동일한 인스턴스인지를 확인합니다. 하지만 우리가 개발할 때는 두 객체가 비록 다른 메모리 주소에 있더라도, 내부의 값이 동일하다면 같은 객체로 취급해야 하는 경우가 많습니다.
예를 들어, 두 개의 Student
객체가 학번이 같다면 같은 학생으로 봐야 하죠. 이럴 때 equals
메서드를 오버라이딩하여 우리가 정의한 논리적인 동등성 기준에 따라 비교하도록 만드는 것입니다.
equals
메서드를 오버라이딩할 때 hashCode
메서드도 반드시 같이 오버라이딩해야 한다고 알고 있습니다. 그 이유는 무엇이며, 만약 같이 오버라이딩하지 않으면 어떤 문제가 발생할 수 있을까요?네, equals
메서드를 오버라이딩했다면 hashCode
도 반드시 함께 오버라이딩해야 합니다. 자바의 Object
클래스 규약에 따르면, equals
메서드가 true
를 반환하는 두 객체는 반드시 같은 hashCode
값을 가져야 합니다.
이 원칙을 지키지 않으면 HashMap
, HashSet
과 같은 해시 기반 컬렉션에서 문제가 발생합니다. HashMap
은 키(Key) 객체의 hashCode
값을 이용해 내부 배열의 인덱스를 빠르게 찾아갑니다. 만약 equals
는 true
를 반환하지만 hashCode
는 다른 두 객체가 있다면, HashMap
은 이들을 다른 객체로 인식하여 다른 인덱스에 저장할 수 있습니다.
예를 들어, HashSet
에 이미 추가된 객체와 equals
는 같지만 hashCode
가 다른 새 객체를 다시 추가하려 할 때, HashSet
은 중복을 인식하지 못하고 같은 객체를 두 번 저장하는 문제가 발생할 수 있습니다. 또한, 기존 객체를 찾으려 할 때 다른 해시 값 때문에 찾지 못하는 상황도 발생할 수 있습니다.
따라서 equals
와 hashCode
의 일관성은 해시 기반 컬렉션의 정확성과 성능에 필수적입니다.
Object
클래스의 기본 hashCode()
메서드 외에 System.identityHashCode(Object obj)
라는 메서드도 존재합니다. 이 System.identityHashCode()
메서드는 언제, 어떤 목적으로 사용되며, 일반적인 hashCode()
메서드와는 어떤 차이점이 있을까요?System.identityHashCode()
는 Object
클래스의 hashCode()
와는 조금 다른 목적으로 사용됩니다. 쉽게 말해, System.identityHashCode(Object obj)
는 해당 객체가 메모리에 어디에 있는지에 기반한 고유한 숫자를 돌려주는 메서드라고 보시면 됩니다. 이건 마치 주민등록번호처럼, 객체 하나하나에 부여된 고유한 식별자 같은 거죠.
일반적인 Object.hashCode()
메서드는 객체의 메모리 주소를 기반으로 해시 코드를 반환합니다. 하지만 우리가 String
클래스처럼 hashCode()
를 오버라이딩해서 '내용이 같으면 같은 해시 코드' 를 돌려주도록 바꿀 수 있습니다. 예를 들어, "hello"라는 문자열 두 개는 메모리 주소가 달라도 hashCode()
값은 같죠. 이처럼 논리적인 '동등성(equality)' 을 나타내는 데 사용됩니다.
반면 System.identityHashCode()
메서드는 개발자가 절대 오버라이딩할 수 없습니다. 항상 JVM이 객체에 부여한 메모리상의 실제 고유 식별자를 바탕으로 해시 코드를 반환합니다. 즉, 두 객체가 equals()
메서드로 true
가 나온다고 해도 (내용은 같지만), 서로 다른 메모리 공간에 있는 별개의 객체라면 System.identityHashCode()
값은 다르게 나옵니다. 이건 객체의 '고유성(Identity)' 을 나타내는 거죠.
Comparable
인터페이스의 compareTo()와
Object
의 equals()
는 어떤 점에서 다르나요?Comparable
인터페이스의 compareTo()
와 Object
클래스의 equals()
는 객체를 비교한다는 점에서는 비슷해 보일 수 있지만, 실제 목적과 동작 방식은 서로 다릅니다.
먼저 equals()
는 두 객체가 논리적으로 같은지, 즉 동등성(equality) 을 확인하는 데 사용됩니다. 기본적으로 Object
클래스에 정의되어 있고, 보통은 우리가 equals()
를 오버라이딩해서 객체의 특정 필드 값이 같으면 true를 반환하도록 구현합니다.
예를 들어 User
객체의 id
가 같으면 같은 사용자로 보고 싶다면 equals()
를 오버라이딩해서 그 기준을 정의할 수 있습니다.
반면에 compareTo()
는 Comparable
인터페이스에 정의된 메서드로, 두 객체 간의 정렬 순서를 비교하는 데 사용됩니다. 즉, 이 객체가 다른 객체보다 작다, 같다, 크다를 판단해서 정렬 기준을 제공하는 것이 목적입니다.
예를 들어 compareTo()
에서 0을 반환하면 두 객체가 정렬상 같다는 의미이고, 음수를 반환하면 현재 객체가 앞에, 양수면 뒤에 정렬된다는 뜻입니다.
여기서 중요한 차이는, equals()
는 단순히 “같다/다르다”를 판단하는 불리언(boolean) 비교이고, compareTo()
는 정렬을 위한 정수(int) 비교라는 점입니다.
또 하나 중요한 포인트는, equals()
가 true이면 compareTo()
는 0을 반환해야 일관성(consistency) 이 유지된다는 것입니다. 이게 맞지 않으면 TreeSet
이나 TreeMap
같은 정렬 기반 자료구조에서 예기치 않은 동작이 발생할 수 있습니다.
equals()
와 compareTo()
의 일관성이 깨졌을 때 어떤 문제가 생기나요?자바에서는 equals()
와 compareTo()
사이에 일관성(consistency) 이 유지되는 것이 중요합니다.
그 일관성이란, 어떤 두 객체가 equals()
로 비교했을 때 true라면, compareTo()
로 비교했을 때도 0을 반환해야 한다는 의미입니다.
그런데 이 일관성이 깨지면, 특히 TreeSet, TreeMap, PriorityQueue 같은 정렬 기반 자료구조에서 예상하지 못한 동작이 발생할 수 있습니다.
예를 들어, 어떤 객체 A와 B가 equals()
로는 같다고 판단되는데 compareTo()
는 0이 아닌 값을 반환한다면, TreeSet
에 A를 넣고 나서 B를 넣었을 때, TreeSet
은 B를 동일한 요소로 보지 않고 중복으로 허용해버릴 수 있습니다.
반대로 compareTo()
는 0을 반환했는데, equals()
는 false인 경우도 문제가 됩니다. 이 경우에는 동일한 위치에 있지만 논리적으로는 다른 객체들이 들어가게 되어, contains()
나 remove()
같은 메서드에서 동작이 이상해질 수 있습니다.
예를 들면, TreeSet.contains(obj)
를 호출했을 때 compareTo()
는 0을 반환해서 존재한다고 판단하지만, equals()
는 false여서 Set
에서 찾지 못하는 상황이 생깁니다.
따라서 Comparable
을 구현할 때는 equals()
와 compareTo()
의 기준을 일치시키는 것이 중요합니다. 기준이 달라야 한다면, HashSet
을 사용하거나 TreeSet
에 별도의 Comparator
를 전달해 비교 로직을 분리하는 것이 좋습니다