자바 toString(), equals(), hashCode()의 연관성

ILLION·2023년 5월 23일
0

JAVA

목록 보기
2/2
  1. toString()
    자바에서 toString() 메소드는 Object 클래스에 정의된 메소드로 모든 객체가 상속받는 기본 메소드입니다. 이 메소드는 객체를 문자열로 표현하는 데 사용됩니다. 하지만 어떤 객체를 출력을 할 때 클래스이름@a65b32c <- 이런식의 출력결과를 봤을겁니다. 일부 사람들은 클래스 뒤의 문자열이 그 객체의 메모리주소값이라고 착각하실 순 있지만 사실은 메모리주소가 아닌 객체의 해쉬코드(hashcode)입니다. 빠르게 먼저 예시코드를 보여드리겠습니다.
public class Animal {
    private String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {
        Animal animal = new Animal("Lion", 5);
        System.out.println(animal);
    }
}

결과

이렇게 기본적으로 Object 클래스에서 상속받은 toString() 메소드를 그대로 사용하면 클래스 이름과 16진수로 표시된 해쉬코드를 반환하게 됩니다. 하지만 이 문자열은 객체의 내부 상태를 잘 표현하지 못해 toString() 메소드를 Override하여 객체의 내부 상태를 잘 표현해야 합니다.

그럼 예제코드에서 toString() 메소드를 Override한 코드를 확인해봅시다.

public class Animal {
    private String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() { // <- Override한 메소드
        return "Animal{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String[] args) {
        Animal animal = new Animal("Lion", 5);
        System.out.println(animal);
    }
}

결과

위 예제코드에서 Animal클래스는 toString() 메소드를 오버라이딩해서
Animal{name=이름, age=나이} 와 같은 형식으로 문자열을 반환하도록 구현한것입니다.
toString() 메소드는 System.out.println() 메소드와 같은 출력 메소드에서 객체를 출력 할 경우 자동으로 호출되어 출력됩니다.
System.out.println(animal) == System.out.println(animal.toString())

2.equals()

자바에서 equals() 메소드는 객체 간의 논리적 동등성을 비교하기 위해 사용되는 메소드입니다.
equals() 메소드는 toString() 메소드와 같이 Object 클래스에 정의되어 있으며, 모든 클래스가 상속받습니다.

물리적 동등성 : 두 객체의 참조 값(메모리 주소)이 같은 것
논리적 동등성 : 두 객체가 물리적으론 다른 위치에 있지만 내부 상태(id or name변수 등등)가 같은 것

// -------------------Override를 하지않을 때------------------------
public class User {
    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public static void main(String[] args) {

        User user1 = new User(1111, "홍길동");
        User user2 = new User(1111, "홍길동");

        System.out.println(user1.equals(user2));
    }
}

결과

Override하지 않은 단순한 Object 클래스의 equals() 메소드는 비교 연산자인 == 과 같은 기능으로써 객체의 참조 값을 비교하는 기능을 가진다.
그래서 물리적으론 다르지만 논리적인 동등성을 갖기 위해선 equals() 메소드를 Override를 해야한다.

// -------------------Override를 했을 때------------------------
public class User {
    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id;
    }

    public static void main(String[] args) {

        User user1 = new User(1111, "홍길동");
        User user2 = new User(1111, "홍길동");

        System.out.println(user1.equals(user2));
    }
}

결과

Object 클래스의 equals() 메소드를 재정의 한 것으로 객체의 내부상태를 비교해서 boolean타입으로 반환합니다.

  1. hashCode()

hashCode() 메소드는 객체의 해쉬코드를 반환하고 메소드입니다. 해쉬코드는 객체를 식별하기 위해 사용되는 정수(int)값입니다. Object 클래스에 정의되어 있습니다.
Override를 하지 않으면 기본 동작으로 System.identityHashCode(객체)를 호출하여 현재 객체의 내부 주소에 대한 해쉬코드를 반환합니다. 이 해쉬코드는 객체의 주소 값을 바탕으로 생성되며, 객체의 물리적인 동등성을 검사하는 데에는 사용할 수 있지만, 논리적인 동등성에는 어렵습니다.

public class User {
    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id;
    }

    public static void main(String[] args) {

        User user1 = new User(1111, "홍길동");
        User user2 = new User(1111, "홍길동");

        System.out.println(user1.equals(user2));
        System.out.println(user1.hashCode());
        System.out.println(System.identityHashCode(user1));
        System.out.println(user2.hashCode());
        System.out.println(System.identityHashCode(user2));
    }
}

결과

이렇듯 Override를 안한 상태에선 hashCode() 메소드의 기본 동작은 객체의 고유한 해쉬코드를 반환하는 System.identityHashCode() 메소드와 같습니다.

hashCode() 메소드의 목적은 객체의 논리적인 동등성 검사를 위한 해쉬 기반 자료구조에서 객체를 빠르게 찾기 위한 용도입니다. hashCode() 메소드를 재정의할 때는 equals() 메소드와 함께 재정하여 동등한 내부 상태를 가진 객체들에 대해 동인한 해쉬코드를 반환해야 합니다.

hashCode() 메소드를 Override한 예시코드를 보여드리겠습니다.

import java.util.Objects;

public class User {
    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public static void main(String[] args) {

        User user1 = new User(1111, "홍길동");
        User user2 = new User(1111, "홍길동");

        System.out.println(user1.equals(user2));
        System.out.println(user1.hashCode());
        System.out.println(System.identityHashCode(user1));
        System.out.println(user2.hashCode());
        System.out.println(System.identityHashCode(user2));
    }
}

결과

이렇게 hashCode() 메소드를 재정의 함으로써 서로 다른 객체여도 내부 상태가 같은 객체를 같은 해쉬코드를 반환하게 됩니다.

  1. 결론
  1. 기본적으로 Object 클래스에서 상속받은 toString() 메소드를 그대로 사용하면 출력값 중 @뒤의 16진수의 문자열은 그 객체의 메모리 주소가 아닌 해쉬코드이다.

  2. toString() 메소드는 System.out.println() 메소드와 같은 출력 메소드에서 객체를 출력 할 경우 자동으로 호출되어 출력됩니다.
    System.out.println(animal) == System.out.println(animal.toString())

  3. Override하지 않은 단순한 Object 클래스의 equals() 메소드는 비교 연산자인 == 과 같은 기능으로써 객체의 참조 값을 비교하는 기능을 가진다.

  4. hashCode() 메소드를 Override를 하지 않고 실행하면 기본 동작으로 System.identityHashCode(객체)를 호출하여 현재 객체의 내부 주소에 대한 고유한 해쉬코드를 반환합니다.

  5. hashCode() 메소드와 equals() 메소드는 해쉬맵 같은 해쉬 기반 자료구조에 사용되므로 같이 재정의를 해야한다.

profile
결과를 중요시하기보단 과정을 중요하게 생각하는 마음가짐

0개의 댓글