자료형과 객체 비교
자료형 -> null 처리 -> Wrapper -> equals -> hashCode -> compareTo
Q. 자바에서 기본 자료형과 참조 자료형의 차이는 무엇이며, null과의 관계는 어떻게 되나요?
자바에서는 자료형을 크게 두 가지로 나눌 수 있습니다. 하나는 기본 자료형
이고, 다른 하나는 참조 자료형
입니다.
기본 자료형
에는 int
, double
, boolean
, char
같은 것들이 있고, 이들은 실제 값 자체를 메모리에 직접 저장합니다. 예를 들어 int a = 10;
이라고 하면, 변수 a
에는 10이라는 값이 스택 메모리 공간에 그대로 저장됩니다.
반면에 참조 자료형
은 객체를 참조하는 방식입니다. 대표적으로 String
, Integer
, 사용자 정의 클래스 같은 것들이 여기에 속합니다. 이 경우에는 실제 객체는 힙 메모리에 만들어지고, 변수에는 그 객체가 있는 메모리 주소, 즉 참조값이 저장됩니다.
이 구조 때문에 두 자료형의 null과의 관계가 다르게 나타납니다. 기본 자료형은 실제 값을 담고 있어야 하기 때문에, null
이라는 "값 없음" 상태를 가질 수 없습니다. 하지만 참조 자료형은 단순히 객체의 주소를 참조하고 있기 때문에, "아직 어떤 객체도 참조하고 있지 않다" 는 의미로 null
이 저장될 수 있습니다.
예를 들어 String name = null;
이라고 하면, name
이라는 변수는 현재 어떤 문자열도 참조하지 않는 상태가 되며, 이 상태에서 메서드를 호출하면 NullPointerException
이 발생할 수 있습니다.
Q. Wrapper 클래스는 기본형을 객체로 바꿔주는데, 실무에서는 어떤 상황에서 사용되며 어떤 장점이 있나요?
자바에는 int
, boolean
같은 기본 자료형이 있는데, 이들은 객체가 아니기 때문에 컬렉션이나 다양한 API에서 바로 사용할 수 없는 제약이 있습니다. 그래서 자바에서는 각 기본형에 대응하는 Wrapper 클래스를 제공합니다.
예를 들어 int
에는 Integer
, boolean
에는 Boolean
, double
에는 Double
이 대응되며, 이들은 기본값을 객체로 감싸는 일종의 포장(wrapper) 역할을 합니다.
Wrapper 클래스는 참조 자료형이기 때문에, 첫 번째로 컬렉션에 저장할 때 유용합니다. 자바의 List
, Map
같은 컬렉션 클래스는 객체만 저장할 수 있기 때문에, 기본형을 저장하려면 Integer
, Double
같은 Wrapper 클래스를 사용해야 합니다.
두 번째로는 null 값을 저장할 수 있다는 점입니다. 기본형은 항상 값을 가져야 해서 int age;
처럼 선언하면 초기화되지 않은 상태로는 쓸 수 없지만, Integer age = null;
이라고 하면 "아직 값이 없음"을 명시적으로 표현할 수 있습니다.
이건 특히 데이터베이스 연동 같은 실무 상황에서 자주 사용됩니다. 예를 들어 DB에서 특정 값이 존재하지 않을 때는 null로 처리되는데, 이걸 기본형으로 받으면 에러가 나기 때문에 Wrapper 타입으로 처리해야 합니다.
세 번째는 Optional과의 연계 사용입니다. Optional<Integer>
처럼 null이 될 수 있는 값을 감쌀 때, 내부 값은 반드시 참조형이어야 하기 때문에 기본형은 사용할 수 없습니다. 이런 구조에서는 Wrapper 클래스가 필수적입니다.
Q. 그럼 Wrapper
클래스들은 서로 비교할 때 어떻게 동작하나요?
Wrapper
클래스들은 서로 비교할 때 어떻게 동작하나요?Wrapper 클래스는 모두 Object
를 상속받은 참조형이기 때문에, 비교할 때는 기본형처럼 ==
를 쓰는 것이 아니라 equals()
메서드를 써야 합니다.
예를 들어 Integer a = 1000; Integer b = 1000;
일 때 a == b
는 false지만 a.equals(b)
는 true입니다.
이유는 ==
는 객체의 주소(참조) 를 비교하고, equals()
는 내부 값의 논리적 동등성을 비교하기 때문입니다.
다만 자바에서는 -128 ~ 127
사이의 Integer
값은 캐싱이 되어 있어서 이 범위 안에서는 ==
도 true가 나올 수 있지만, 그건 예외적인 최적화입니다.
따라서 Wrapper 클래스 간 비교는 항상 equals()
를 사용하는 게 안전하며, 내부적으로도 이미 equals()
가 잘 오버라이딩 되어 있어서 값 비교가 정확히 되도록 설계되어 있습니다.
Q. 자바에서 equals()
메서드는 무엇이며, 왜 오버라이딩이 필요한가요?
equals()
메서드는 무엇이며, 왜 오버라이딩이 필요한가요?자바에서 equals()
메서드는 두 객체가 논리적으로 같은지 비교하는 기능을 합니다.
Object 클래스의 기본 구현은 ==
과 마찬가지로 두 객체의 참조 주소가 같은지를 비교합니다. 즉, 같은 객체를 참조할 때만 true를 반환합니다.
하지만 대부분의 경우에는 객체의 주소가 아니라 내부 값이 같은지를 비교하고 싶을 때가 많습니다.
예를 들어, 우리가 User
라는 클래스를 만들고 id
와 name
이 같으면 같은 사용자로 간주하고 싶다면, equals()
를 오버라이딩해서 필드 값을 기준으로 비교하도록 바꿔줘야 합니다.
실제로 String
, Integer
, LocalDate
같은 자바의 주요 클래스들도 equals()
를 오버라이딩해서 내용이 같으면 같다고 판단하도록 구현되어 있습니다.
Q. equals()
메서드를 오버라이딩할 때, hashCode()
도 같이 오버라이딩해야 하나요?
equals()
메서드를 오버라이딩할 때, hashCode()
도 같이 오버라이딩해야 하나요?네, 꼭 같이 오버라이딩해야 합니다.
자바에서는 equals()
가 true인 두 객체는 반드시 동일한 hashCode()
값을 가져야 한다는 규칙이 있습니다.
이 규칙이 중요한 이유는 HashMap
, HashSet
같은 해시 기반 컬렉션에서 hashCode()
를 먼저 비교해서 위치를 찾고, 그 다음에 equals()
로 동등성을 판단하기 때문입니다.
만약 equals()
는 오버라이딩했지만 hashCode()
는 그대로 두면, 논리적으로 같은 객체인데도 서로 다른 해시값을 가져서 컬렉션에서는 다른 객체로 처리되는 문제가 발생합니다.
이로 인해 데이터를 찾지 못하거나, 중복이 허용되지 않아야 할 상황에서 중복 삽입이 되는 등의 문제가 생길 수 있습니다.
그래서 실무에서는 equals()
를 오버라이딩할 때는 항상 hashCode()
도 함께 구현해주는 것이 중요합니다.
Q. Comparable
인터페이스의 compareTo()와
Object
의 equals()
는 어떤 점에서 다르나요?
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
같은 정렬 기반 자료구조에서 예기치 않은 동작이 발생할 수 있습니다.
Q. equals()
와 compareTo()
의 일관성이 깨졌을 때 어떤 문제가 생기나요?
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
를 전달해 비교 로직을 분리하는 것이 좋습니다
Last updated