[Java] 동일성과 동등성 그리고 String 비교

벼랑 끝 코딩·2025년 2월 25일
0

Java

목록 보기
18/40

비교

자바에서 서로 같음을 비교하는 방법은 두 가지가 있다.
바로 동일성과 동등성.

왜 비교하는 방법이 통일되지 않고 두 가지로 나뉘었는가?
필요하기 때문이다.
왜 필요하냐고?
바로 객체라는 존재 때문이다.

객체는 객체의 위치를 가리키는 참조값을 변수에 저장한다.
이 참조값은 객체가 가지고 있는 데이터와는 전혀 무관하다.
객체를 비교할 때, 참조값을 비교해야겠는가, 객체의 데이터를 비교해야겠는가?
당연하게도 객체의 데이터를 기준으로 비교한다.

여기서 동일성과 동등성의 개념이 등장한다.

동일성

동일(同一) : 둘이나 그 이상(以上)의 것이 서로 꼭 같음. 한결같음.

즉, 동일이란 완전히 같음을 의미한다.
객체의 입장에서는 참조값까지 완전히 동일한 것을 의미한다.

자바에서는 '=='를 사용하여 비교한다.

Clazz clazz1 = new Clazz(); // 참조값 : x001
Clazz clazz2 = new Clazz(); // 참조값 : x002
Clazz clazz3 = clazz1; // 참조값 : x001

clazz1 == clazz2 // false;
clazz1 == clazz3 // true;

동등성

동등(同等) : 등급이나 정도가 같음. 또는 그런 등급이나 정도.

서로 완전히 동일하지 않고, 등급이나 정도, 수준이 같음을 의미한다.
객체 입장에서는 가지고 있는 데이터가 동일함을 의미한다.

class User {
	String name;
    
    public User(String name) {
    	this.name = name;
    }
}

public void equalsMethod() {
	User user1 = new User("김코딩");
    User user2 = new User("김코딩");

이름 필드를 가지는 User 클래스가 있다고 해보자.
user1, user2 객체를 생성하고 동일성 비교할 경우,
참조 값이 다르므로 false가 출력된다.

user1 == user2 // false

이럴땐 동등성 비교를 수행해야 한다.
자바에서 동등성 비교는 Object가 제공하는 eqauls() 메서드를 사용한다.
하지만 user1과 user2를 eqauls() 메서드를 사용해 동등성 비교를 할 경우,
마찬가지로 false가 나온다.

user1.equals(user2) // false

왜냐고?
eqauls() 메서드도 내부를 살펴보면, '=='를 사용해서 비교하기 때문이다.
실제 Object 클래스의 eqauls() 메서드를 살펴보면 다음과 같다.

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

..뭐지?
그럼 뭐 어떻게 비교하라는거지?

equals() 오버라이딩

객체 입장에서 동등성 비교는,
참조값이 아닌 가지고 있는 데이터를 비교하는 것이라고 했다.
하지만 사용자가 정의한 객체의 데이터 비교 기준은 제각각 다를 수 밖에 없다.
누군가는 이름으로, 누군가는 회원 번호로 비교하길 원한다.
따라서 우리의 입맛에 맞게
Objecct에서 제공하는 메서드인 eqauls()를 오버라이딩해서 사용해야 한다.

IntelliJ eqauls() 오버라이딩 자동 생성

하지만 eqauls()를 오버라이딩하는 코드를 짜기란 결코 쉽지 않다.
익숙해져도 객체를 비교하고 싶을 때마다 eqauls()를 오버라이딩하는 것은 매우 번거롭다.
IntelliJ는 다행히 equals() 오버라이딩 메서드를 자동으로 생성해주는 기능을 지원하고 있다.

class User {
	int id;
    String name;
}

다음과 같은 User 객체가 있다고 하면,
id 필드와 name 중 동등성 비교를 하고 싶은 필드를 선택할 수도 있고
원한다면 두 필드를 모두 포함하여 동등성을 비교하는 eqauls()를 자동으로 만들 수 있다.

  • Alt + Insert → equals() and hashCode()
@Override
    public boolean equals(Object o) { // 매개 변수 Object(다형성)
        if (this == o) {
            return true; // 참조값이 같을 경우
        }
        if (o == null || getClass() != o.getClass()) {
            return false; // null 또는 참조 객체가 다른 경우
        }
        Child child = (Child) o; // 다운 캐스팅
        return id == child.id && Objects.equals(name, child.name);
    }

코드를 살펴보면, 참조값이 같으면 true를 반환하고
null이거나 참조 객체가 다르면 false를 반환한다.
결론적으로 매개 변수 타입인 Object를 다운캐스팅하고,
id필드와 String 타입인 name필드가 모두 같으면 true, 아니면 false를 반환한다.

String 비교

class User {
	int id;
    String name;
}

return id == child.id && Objects.equals(name, child.name); // eqauls() 사용

name 필드는 String 타입인데, String은 클래스다.
우리는 지금까지 객체인 클래스의 참조값이 아닌 데이터를 비교하기 위해
동일성과 동등성 비교를 알아보고 있는 것 아닌가?
근데 String도 결국엔 '=='를 사용하는 Object의 eqauls() 메서드를 사용한다고?

이게 어찌된 영문인가?

불변 클래스 String 클래스

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {
               
    @Stable
    private final byte[] value;

	// 코드
}              

String 클래스를 살펴보면,
입력한 문자열이 저장되는 byte[] 배열의 필드에 final 키워드가 사용된 것을 볼 수 있다.
이것은 불변(immutable) 클래스를 의미한다.
불변 클래스인 String 클래스는 한 번 생성된 문자열의 내용을 변경할 수 없다.

String Constant Pool

String name = "김코딩"; // 리터럴 생성
String name = new String("김코딩"); // new 연산자  생성

문자열의 내용을 변경할 수 없는 String 클래스를 생성할 때마다
힙 영역에 새로운 객체를 만드는 것은 매우 비효율적인 작업이다.
그래서 자바는 리터럴로 문자열을 생성하는 경우,
String Constant Pool이라는 곳에 해당 리터럴 String 인스턴스를 생성하여 저장한다.

이후 동일한 문자열을 생성하면,
이전에 생성한 String Constant Pool의 String 인스턴스를 캐싱하여 동일한 메모리 주소를 공유하도록 한다.

그렇기 때문에 String은 Object의 eqauls() 메서드로 비교가 가능한 것이다!

마무리

객체의 참조값을 배경으로 동등성 비교가 필요한 것을 알아봤다.
객체 비교 시에는 항상 eqauls() 메서드를 오버라이딩해서 사용해야 함을 잊지 말자.

profile
복습에 대한 비판과 지적을 부탁드립니다

0개의 댓글