[자바 무료 강의] 모든 클래스는 Object 클래스를 상속받는다 - 코드라떼
Lesson List button
코스자바로 배우는 프로그래밍
hamburger button
강의모든 클래스는 Object 클래스를 상속받는다최종수정일 2021-11-21
아이콘약 10분

모든 클래스는 Object 클래스를 상속 받습니다. 그러므로 Object 클래스에 대해 간략히 알아봅시다.

노트 강의

목차


  1. 모든 클래스는 Object 클래스를 상속 받는다

  2. Object 클래스를 상속 받기 때문에 Object 클래스의 메서드를 상속 받는다

  3. [심화] equals 메서드를 재정의 하는 규칙

1. 모든 클래스는 Object 클래스를 상속 받는다


class Fruit extends Object {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }
}

Fruit 클래스가 있는데요. Fruit 클래스는 Object 클래스를 상속받고 있습니다. 이렇게 extends 키워드를 이용하여 명시적으로 선언하지 않는다 하더라도 Object 클래스를 상속받고 있습니다. (컴파일러가 처리합니다.)


우리는 이전에 다형성이라는 것을 배웠었는데요. Java 언어의 다형성의 특징을 통해 Fruit 인스턴스는 Object 참조 자료형 변수에 저장할 수 있습니다.



case 다형성

Object fruit = new Fruit("사과");

case 매개 변수

class Test {

    public String getInstanceName(Object obj) {
        if (obj instanceof Fruit) {
            return "과일";
        } else if (obj instanceOf Car) {
            return "자동차";
        } else {
            return "그 외";
        }
    }
}
Test test = new Test();

Fruit fruit = new Fruit("사과");
String instanceName = test.getInstanceName(fruit);

System.out.println(instanceName); 
//과일

또한 메서드의 매개변수가 Object 참조 자료형인 경우 모든 클래스의 인스턴스를 전달 받을 수 있습니다.


2. Object 클래스를 상속 받기 때문에 Object 클래스의 메서드를 상속 받는다


모든 클래스는 Object 클래스를 상속 받으며, Object 클래스의 요소를 사용할 수 있습니다.


Object 클래스의 메서드들

public class Object {

    public native int hashCode();

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

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    ...
}

Object 클래스에는 다양한 메서드가 선언되어 있는데요. 현재는 각 메서드가 무슨 일을 하는지 알 수 없습니다. 그중 몇 가지 중요한 것만 집고 넘어갑시다.


toString()

toString() 메서드는 메서드를 재정의하지 않는다면 기본적으로 인스턴스의 클래스 이름과 골뱅이, 인스턴스의 해시 코드를 16진수로 변환한 문자열을 반환합니다.

Fruit fruit = new Fruit("사과");

System.out.println(fruit.toString()); 

// Fruit@610455d6

toString() 메서드를 재정의하여 인스턴스의 의미 있는 정보를 제공할 수 있습니다.

class Fruit {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    public String toString() {
        return "Fruit 이름:"+name;
    }
}
Fruit fruit = new Fruit("사과");

// System.out.println 메서드는 인자가 인스턴스인경우
// 내부적으로 toString() 메서드를 호출합니다.

System.out.println(fruit);
// Fruit 이름:사과

hashCode()

hashCode() 메서드는 객체의 hashCode를 반환하는 메서드입니다. hashCode() 메서드는 자바 가상머신에서 특정한 알고리즘을 이용하여 산술한 값을 반환합니다.

Fruit fruit = new Fruit("사과");

System.out.println(fruit.hashCode()); 

// 1627674070

hashCode는 주로 해시 테이블의 데이터 구조에서 저장과 검색을 하는데 사용됩니다.

hashCode를 재정의 할 경우 다음과 같은 의미가 되어야 합니다.


(특별한 경우가 아닌 이상 추천드리지는 않습니다)

  1. hashCode가 같다는 것은 객체의 의미가 같다.

  2. 객체의 의미가 같으면 hashCode도 같다.

  3. hashCode가 다르다는 것은 객체의 의미가 다르다.

  4. 객체의 의미가 다르면 hashCode도 다르다.

좋은 hashCode를 만드는 법은 이 강의에서 다루지 않습니다.


*[번외] 해시코드 알고리즘 옵션

0 – Park-Miller RNG (default)

1 – f (address, the global)

2 – constant 1

3 – sequential counter

4 – object's address in heap

5 – Xorshift (the fastest)

-XX:hashCode=x


equals(obj)

equals(obj) 메서드는 두 객체가 같을 경우 true를 반환하는 메서드입니다. 여기서 객체라는 의미는 인스턴스 일 수도 있고 동일한 의미의 객체일 수도 있습니다.


기존의 equals 메서드 내용
public boolean equals(Object obj) {
    return (this == obj);
}

equals(obj) 메서드는 재정의 하지 않는 경우 기본적으로 == 연산자를 통하여 인스턴스가 같은지 파악합니다.

== 연산자는 인스턴스 메모리 주소를 비교하기 때문에 동일한 의미의 객체가 있다 하더라도 false 값을 반환할 수 있습니다.


두 인스턴스는 객체로서의 의미는 동일할 수 있으나 같은 인스턴스는 아니다
class Fruit {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }
}
Fruit fruit1 = new Fruit("사과");
Fruit fruit2 = new Fruit("사과");

System.out.println(fruit1.equals(fruit2)); 
// false

객체의 내용은 동일하나 다른 메모리공간에 인스턴스가 적재되어있기 때문에 false 값을 반환합니다.

그러므로 동일한 객체라는 의미를 부여하기 위해서는 equals(obj) 메서드를 재정의해야 합니다.


equals 메서드 재정의
class Fruit {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    // equals 메서드 재정의
    public boolean equals(Object obj) {
        if (obj instanceof Fruit) {
            Fruit tempFruit = (Fruit)obj;
            return name.equals(tempFruit.name);
        }
    }
}
Fruit fruit1 = new Fruit(“사과”);
Fruit fruit2 = new Fruit(“사과”);

System.out.println(fruit1.equals(fruit2)); 
// true

또한 equals(obj) 메서드를 재정의 하면 hashCode() 메서드도 재정의 해야 합니다. 스펙상 권장하는 부분입니다.

class Fruit {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    // equals 메서드 재정의
    public boolean equals(Object obj) {
        if (obj instanceof Fruit) {
            Fruit tempFruit = (Fruit)obj;
            return name.equals(tempFruit.getName());
        }
        return false;
    }

    // hashCode 메서드 재정의
    public int hashCode() {
        int salt = 7;
        return 31 * salt * name.hashCode();
    }
}

3. [심화] equals 메서드를 재정의 하는 규칙


equals(obj) 메서드를 재정의하는 것은 단순하진 않습니다. 논리적인 부분의 요소가 들어가기 때문에 모든 규칙을 만족해야지 안전한 equals(obj) 메서드라고 볼 수 있습니다.

  1. 반사성 - null값이 아닌 참조 x가 존재할 때, x.equals(x)는 true값을 반환해야 한다. (x == x)

  2. 대칭성 - null값이 아닌 참조 x와 y가 존재할 때, x.equals(y)는 y.equals(x)가 true인 경우에만 true 값을 반환해야 한다. (x == y, y == x)

  3. 추이성 - null값이 아닌 참조 x, y, z가 있을 때, x.equals(y)가 true이고 y.equals(z)가 true이면 x.equals(z)도 true 값을 반환해야 한다. (x == y, y == z, x == z)

  4. 일관성 - null값이 아닌 참조 x와 y를 비교할 때  비교 정보가 다르게 수정되지 않는 경우에

  5. 여러번 호출 하더라도 true 또는 false 결과 값이 항상 동일해야 한다.(호출 여부와 상관없이 일관성 있어야 한다.)

  6. null값이 아닌 참조 x에서 x.equals(null)은 항상 false를 반환해야 한다.

도전자 질문
아이콘resCogitans(2021-07-01 04:09 작성됨)
toString() 메서드는 메서드를 재정의하지 않는다면 기본적으로 인스턴스 이름과 골뱅이, 인스턴스의 해시 코드를 16진수로 변환한 문자열을 반환합니다.

Fruit fruit = new Fruit("사과");

System.out.println(fruit.toString()); 

// Fruit@610455d6

-> Fruit은 인스턴스 이름이 아니라 해당 인스턴스의 클래스명 아닌가요?
아이콘코드라떼(2021-07-01 04:32 작성됨)
인스턴스가 아니라 인스턴스의 클래스명 맞습니다^^!
아이콘resCogitans(2021-06-30 15:03 작성됨)
덕분에 궁금했던 부분들이 다 해결됐습니다! 친절하고 자세한 설명 감사드려요.
아이콘코드라떼(2021-07-01 05:32 작성됨)
꼼꼼하게 문서를 작성하지 못했으나 좋은 질문을 하셔서 오히려 감사합니다^^
아이콘resCogitans(2021-06-30 05:07 작성됨)
7. 본문 코드에서 instanceof 연산자가 instanceOf로 작성되어있습니다.

8.     public boolean equals(Object obj) {
        if (obj instanceOf Fruit) {
            Fruit tempFruit = (Fruit)obj;
            return name.equals(tempFruit.name);
        }
    }

코드에 obj가 Fruit의 인스턴스가 아닐 시의 리턴 코드가 빠졌습니다.

아직 이해가 부족해서 그런지, 난잡한 질문을 많이 드려봅니다... 덕분에 재밌게 잘 공부하고 있습니다.
아이콘코드라떼(2021-06-30 13:45 작성됨)
수정되었습니다. 감사합니다^^
아이콘resCogitans(2021-06-30 05:04 작성됨)
일관성 - null값이 아닌 참조 x와 y를 비교할 때  비교 정보가 다르지 수정되지 않는 경우에
여러번 호출 하더라도 true 또는 false 결과 값이 항상 동일해야 한다.(호출 여부와 상관없이 일관성 있어야 한다.)

1. "다르지"가 "다르게" 오타 인것 같은데,

2. 그러면 위 조건을
if (x≠null && y≠null)
일 때, 재정의된 equals(obj)가 함수의 비교를 위해 참조하는 값(주소값이건, isIstanceof건 뭐가 되었건 간에)의 변화가 없다면 일관성을 가져야한다는 것은 알겠는데요, 이 메서드가 일종의 순수함수처럼 되어야 한다는 의미일까요?

3. equals(obj) 오버라이드에 조건이 있는 이유가 두 객체를 비교한다는 기능을 수행하기 위해서 주어지는 제약조건인지, 아니면 시스템이나 문법이나 그런 제약조건으로 인한 문제인건지 알고싶습니다. 아마 전자의 이유로 나타난 규칙 같기는 한데, 굳이 equals(obj)메서드에 대해서만 얘기가 나와 있어서 혹시나 싶었습니다.

4. 아마도 공부가 부족해서 여러 상황들을 안 겪어봐서 그런 것 같긴 한데, 굳이 Object의 equals(obj) 메서드를 오버라이드 해야 하는 상황이 와닿지 않습니다.
    Fruit, Animal, Book, . . 등 다수의 클래스가 있을 때,
    일괄적으로 각 클래스마다 .equals를 사용하면서, Fruit만 기존의 equals와 다른 방식으로 비교하고 싶은 경우, 이런 경우만 생각납니다만, 이게 실제로
    개발을 할 떄 어떤 의미가 있는지를 모르겠습니다. Fruits에 추가적인 메소드를 추가하거나, 해당기능 메소드를 인터페이스에 명시하고 그 인터페이스를
    다수의 클래스들에 implements하는 편이 기존의 equals 기능도 한 클래스 안에서 같이 사용할 수 있어서 더 유용하지 않나요?

5. 또 오버라이드 이전에, equals()자체의 용도가 궁금합니다. 애초에 비교하기 위한 두 인스턴스가 존재한다면 두 인스턴스의 주소값은 다를 수 밖에 없지 않나요? 그렇다면 obj1.equals(obj2)는 항상 false 아닌가요? 문자열 상수가 아닌 이상 true가 나오는 경우가 있을지 궁금합니다.

6. 2번 규칙에 대한 이해가 적절한지 궁금합니다:
가령 부모 클래스 Fruits와 Fruit를 extends 한 자식클래스 Apple이 있고,
Apple에서 equals를 본문처럼 오버라이딩 한 경우와 같은 상황이며,
둘의 name 값이 문자열 상수 "사과"로 동일한 경우,
 fruit.equals(apple)은 false지만 apple.equals(fruit)은 true를 리턴하는 상황이 발생할 수 있는데,

이럴 때 부모클래스에 equals를 오버라이딩 하고, 자식클래스는 그냥 상속만 받게 하는 식의 제약같은 것을 의미하는 것인지 궁금합니다.
아이콘코드라떼(2021-06-30 13:55 작성됨)
1. 감사합니다. 수정했습니다^^!

2. 순수함수처럼 되어야 하는 의미가 맞습니다. 너무나 당연한 이야기인데 혹시나 순수함수가 아니도록
코드를 작성하는 경우가 있을 수 있습니다. 이런 경우를 하지 말라고 정의되었습니다

*이런식으로 하지 말자
class Fruit {
    private String name;
    private boolean flag = true;

    // equals 메서드 재정의
    public boolean equals(Object obj) {
        if (flag && obj instanceOf Fruit) {
            Fruit tempFruit = (Fruit)obj;
            flag = false;
            return name.equals(tempFruit.name);
        }
        return false;
    }
}

equals(obj) 메서드에 '동일한 인자를 전달하여 호출 시, 호출 횟수에 따라 다른 결과 값이 발생하는 이런 코드를 작성하지 말라'로 볼 수 있습니다.

3. 전자가 맞습니다.  equals(obj) 메서드를 '올바르게' 정의하기 위한 '명시적인 규칙'입니다. 두 객체를 비교한다는 기능인 equals(obj) 메서드를 오버라이드 할 때, 코드 작성자 기준이 아닌 사용자 기준으로 봤을 때 사용자는 코드를 모르는 상황에서 다양한 경우에 사용할 수 있기 때문입니다.  equals(obj)가 재정의되어 있을 경우 '5가지 제약 조건을 지키고 있는 코드'라고 사용자는 '묵시적'으로 생각합니다.

4. equals(obj) 메서드를 오버라이드 하는 경우는 많이 없으나 그래도 예시를 드린다면 아래와 같습니다.

class Address {
    String firstAddress; // 시/도
    String secondAddress; // 시/군/구
    String thirdAddress; // 읍/면/동

    Address(String first, String second, String third) {
        this.firstAddress = first;
        this.secondAddress = second;
        this.thirdAddress = third;
    }
}

예시로 주소를 저장하는 클래스가 존재한다고 가정합시다.

Address address1 = new Address("서울시", "강남구", "개포1동");
Address address2 = new Address("서울시", "강남구", "개포1동");

두 인스턴스는 서로 다른 메모리에 적재되어 있으므로 동일하다고 볼 수는 없으나 '의미상'으로는 동일하다고
볼 수 있습니다. 그리고 해당 객체들을 사용하다가 해당 객체가 '의미상' 주소가 동일한지 아닌지 확인해야 한다면 일반적으로는 이렇게 하겠지요.

boolean isEqual = address1.firstAddress.equals(address2.firstAddress) &&
address1.secondAddress.equals(address2.secondAddress) &&
address1.thirdAddress.equals(address2.thirdAddress);

이렇게 세 개를 비교하여 나온 결과 값이 true 일 때 동일한 주소라는 것을 파악할 수 있습니다.

만약에 equals(obj) 메서드를 '올바르게' 오버라이드 했다면 이런 코드를 작성할 수 있습니다. 코드의 ‘의미’도 더 명확해지고 '가독성'도 좋아집니다.
boolean isEqual = address1.equals(address2);

5. 4번의 예시로 같이 설명이 될 것 같습니다.

6. 본문의 예시가 좋은 예시가 아니라서 도전자님께서 헷갈리시게 되는 것 같습니다. (지금 보니 수정할 필요성을 느낍니다)

본문의 equals의 예제 코드 의도는 이렇게 메서드를 재정의 할 수 있다는 정도까지만 보여 드린 부분이었습니다. 그래서 5가지 제약조건이 포함되어 있지 않습니다. 이 부분은 좀 더 고민해서 내용을 수정하겠습니다.

감사합니다.^^
이용약관|개인정보취급방침
알유티씨클래스|대표, 개인정보보호책임자 : 이병록
이메일 : cs@codelatte.io
사업자등록번호 : 824-06-01921
통신판매업신고 : 2021-성남분당C-0740
주소 : 경기도 성남시 분당구 대왕판교로645번길 12, 9층 24호
파일
파일파일
Root
파일

Fruit 클래스의 equals 메서드와 hashCode 메서드가 재정의 되어있습니다.

Output
root$
Lesson List button
코스자바로 배우는 프로그래밍
hamburger button
강의모든 클래스는 Object 클래스를 상속받는다최종수정일 2021-11-21
아이콘약 10분

모든 클래스는 Object 클래스를 상속 받습니다. 그러므로 Object 클래스에 대해 간략히 알아봅시다.

노트 강의

목차


  1. 모든 클래스는 Object 클래스를 상속 받는다

  2. Object 클래스를 상속 받기 때문에 Object 클래스의 메서드를 상속 받는다

  3. [심화] equals 메서드를 재정의 하는 규칙

1. 모든 클래스는 Object 클래스를 상속 받는다


class Fruit extends Object {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }
}

Fruit 클래스가 있는데요. Fruit 클래스는 Object 클래스를 상속받고 있습니다. 이렇게 extends 키워드를 이용하여 명시적으로 선언하지 않는다 하더라도 Object 클래스를 상속받고 있습니다. (컴파일러가 처리합니다.)


우리는 이전에 다형성이라는 것을 배웠었는데요. Java 언어의 다형성의 특징을 통해 Fruit 인스턴스는 Object 참조 자료형 변수에 저장할 수 있습니다.



case 다형성

Object fruit = new Fruit("사과");

case 매개 변수

class Test {

    public String getInstanceName(Object obj) {
        if (obj instanceof Fruit) {
            return "과일";
        } else if (obj instanceOf Car) {
            return "자동차";
        } else {
            return "그 외";
        }
    }
}
Test test = new Test();

Fruit fruit = new Fruit("사과");
String instanceName = test.getInstanceName(fruit);

System.out.println(instanceName); 
//과일

또한 메서드의 매개변수가 Object 참조 자료형인 경우 모든 클래스의 인스턴스를 전달 받을 수 있습니다.


2. Object 클래스를 상속 받기 때문에 Object 클래스의 메서드를 상속 받는다


모든 클래스는 Object 클래스를 상속 받으며, Object 클래스의 요소를 사용할 수 있습니다.


Object 클래스의 메서드들

public class Object {

    public native int hashCode();

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

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    ...
}

Object 클래스에는 다양한 메서드가 선언되어 있는데요. 현재는 각 메서드가 무슨 일을 하는지 알 수 없습니다. 그중 몇 가지 중요한 것만 집고 넘어갑시다.


toString()

toString() 메서드는 메서드를 재정의하지 않는다면 기본적으로 인스턴스의 클래스 이름과 골뱅이, 인스턴스의 해시 코드를 16진수로 변환한 문자열을 반환합니다.

Fruit fruit = new Fruit("사과");

System.out.println(fruit.toString()); 

// Fruit@610455d6

toString() 메서드를 재정의하여 인스턴스의 의미 있는 정보를 제공할 수 있습니다.

class Fruit {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    public String toString() {
        return "Fruit 이름:"+name;
    }
}
Fruit fruit = new Fruit("사과");

// System.out.println 메서드는 인자가 인스턴스인경우
// 내부적으로 toString() 메서드를 호출합니다.

System.out.println(fruit);
// Fruit 이름:사과

hashCode()

hashCode() 메서드는 객체의 hashCode를 반환하는 메서드입니다. hashCode() 메서드는 자바 가상머신에서 특정한 알고리즘을 이용하여 산술한 값을 반환합니다.

Fruit fruit = new Fruit("사과");

System.out.println(fruit.hashCode()); 

// 1627674070

hashCode는 주로 해시 테이블의 데이터 구조에서 저장과 검색을 하는데 사용됩니다.

hashCode를 재정의 할 경우 다음과 같은 의미가 되어야 합니다.


(특별한 경우가 아닌 이상 추천드리지는 않습니다)

  1. hashCode가 같다는 것은 객체의 의미가 같다.

  2. 객체의 의미가 같으면 hashCode도 같다.

  3. hashCode가 다르다는 것은 객체의 의미가 다르다.

  4. 객체의 의미가 다르면 hashCode도 다르다.

좋은 hashCode를 만드는 법은 이 강의에서 다루지 않습니다.


*[번외] 해시코드 알고리즘 옵션

0 – Park-Miller RNG (default)

1 – f (address, the global)

2 – constant 1

3 – sequential counter

4 – object's address in heap

5 – Xorshift (the fastest)

-XX:hashCode=x


equals(obj)

equals(obj) 메서드는 두 객체가 같을 경우 true를 반환하는 메서드입니다. 여기서 객체라는 의미는 인스턴스 일 수도 있고 동일한 의미의 객체일 수도 있습니다.


기존의 equals 메서드 내용
public boolean equals(Object obj) {
    return (this == obj);
}

equals(obj) 메서드는 재정의 하지 않는 경우 기본적으로 == 연산자를 통하여 인스턴스가 같은지 파악합니다.

== 연산자는 인스턴스 메모리 주소를 비교하기 때문에 동일한 의미의 객체가 있다 하더라도 false 값을 반환할 수 있습니다.


두 인스턴스는 객체로서의 의미는 동일할 수 있으나 같은 인스턴스는 아니다
class Fruit {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }
}
Fruit fruit1 = new Fruit("사과");
Fruit fruit2 = new Fruit("사과");

System.out.println(fruit1.equals(fruit2)); 
// false

객체의 내용은 동일하나 다른 메모리공간에 인스턴스가 적재되어있기 때문에 false 값을 반환합니다.

그러므로 동일한 객체라는 의미를 부여하기 위해서는 equals(obj) 메서드를 재정의해야 합니다.


equals 메서드 재정의
class Fruit {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    // equals 메서드 재정의
    public boolean equals(Object obj) {
        if (obj instanceof Fruit) {
            Fruit tempFruit = (Fruit)obj;
            return name.equals(tempFruit.name);
        }
    }
}
Fruit fruit1 = new Fruit(“사과”);
Fruit fruit2 = new Fruit(“사과”);

System.out.println(fruit1.equals(fruit2)); 
// true

또한 equals(obj) 메서드를 재정의 하면 hashCode() 메서드도 재정의 해야 합니다. 스펙상 권장하는 부분입니다.

class Fruit {
    private String name;

    Fruit(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    // equals 메서드 재정의
    public boolean equals(Object obj) {
        if (obj instanceof Fruit) {
            Fruit tempFruit = (Fruit)obj;
            return name.equals(tempFruit.getName());
        }
        return false;
    }

    // hashCode 메서드 재정의
    public int hashCode() {
        int salt = 7;
        return 31 * salt * name.hashCode();
    }
}

3. [심화] equals 메서드를 재정의 하는 규칙


equals(obj) 메서드를 재정의하는 것은 단순하진 않습니다. 논리적인 부분의 요소가 들어가기 때문에 모든 규칙을 만족해야지 안전한 equals(obj) 메서드라고 볼 수 있습니다.

  1. 반사성 - null값이 아닌 참조 x가 존재할 때, x.equals(x)는 true값을 반환해야 한다. (x == x)

  2. 대칭성 - null값이 아닌 참조 x와 y가 존재할 때, x.equals(y)는 y.equals(x)가 true인 경우에만 true 값을 반환해야 한다. (x == y, y == x)

  3. 추이성 - null값이 아닌 참조 x, y, z가 있을 때, x.equals(y)가 true이고 y.equals(z)가 true이면 x.equals(z)도 true 값을 반환해야 한다. (x == y, y == z, x == z)

  4. 일관성 - null값이 아닌 참조 x와 y를 비교할 때  비교 정보가 다르게 수정되지 않는 경우에

  5. 여러번 호출 하더라도 true 또는 false 결과 값이 항상 동일해야 한다.(호출 여부와 상관없이 일관성 있어야 한다.)

  6. null값이 아닌 참조 x에서 x.equals(null)은 항상 false를 반환해야 한다.

도전자 질문
아이콘resCogitans(2021-07-01 04:09 작성됨)
toString() 메서드는 메서드를 재정의하지 않는다면 기본적으로 인스턴스 이름과 골뱅이, 인스턴스의 해시 코드를 16진수로 변환한 문자열을 반환합니다.

Fruit fruit = new Fruit("사과");

System.out.println(fruit.toString()); 

// Fruit@610455d6

-> Fruit은 인스턴스 이름이 아니라 해당 인스턴스의 클래스명 아닌가요?
아이콘코드라떼(2021-07-01 04:32 작성됨)
인스턴스가 아니라 인스턴스의 클래스명 맞습니다^^!
아이콘resCogitans(2021-06-30 15:03 작성됨)
덕분에 궁금했던 부분들이 다 해결됐습니다! 친절하고 자세한 설명 감사드려요.
아이콘코드라떼(2021-07-01 05:32 작성됨)
꼼꼼하게 문서를 작성하지 못했으나 좋은 질문을 하셔서 오히려 감사합니다^^
아이콘resCogitans(2021-06-30 05:07 작성됨)
7. 본문 코드에서 instanceof 연산자가 instanceOf로 작성되어있습니다.

8.     public boolean equals(Object obj) {
        if (obj instanceOf Fruit) {
            Fruit tempFruit = (Fruit)obj;
            return name.equals(tempFruit.name);
        }
    }

코드에 obj가 Fruit의 인스턴스가 아닐 시의 리턴 코드가 빠졌습니다.

아직 이해가 부족해서 그런지, 난잡한 질문을 많이 드려봅니다... 덕분에 재밌게 잘 공부하고 있습니다.
아이콘코드라떼(2021-06-30 13:45 작성됨)
수정되었습니다. 감사합니다^^
아이콘resCogitans(2021-06-30 05:04 작성됨)
일관성 - null값이 아닌 참조 x와 y를 비교할 때  비교 정보가 다르지 수정되지 않는 경우에
여러번 호출 하더라도 true 또는 false 결과 값이 항상 동일해야 한다.(호출 여부와 상관없이 일관성 있어야 한다.)

1. "다르지"가 "다르게" 오타 인것 같은데,

2. 그러면 위 조건을
if (x≠null && y≠null)
일 때, 재정의된 equals(obj)가 함수의 비교를 위해 참조하는 값(주소값이건, isIstanceof건 뭐가 되었건 간에)의 변화가 없다면 일관성을 가져야한다는 것은 알겠는데요, 이 메서드가 일종의 순수함수처럼 되어야 한다는 의미일까요?

3. equals(obj) 오버라이드에 조건이 있는 이유가 두 객체를 비교한다는 기능을 수행하기 위해서 주어지는 제약조건인지, 아니면 시스템이나 문법이나 그런 제약조건으로 인한 문제인건지 알고싶습니다. 아마 전자의 이유로 나타난 규칙 같기는 한데, 굳이 equals(obj)메서드에 대해서만 얘기가 나와 있어서 혹시나 싶었습니다.

4. 아마도 공부가 부족해서 여러 상황들을 안 겪어봐서 그런 것 같긴 한데, 굳이 Object의 equals(obj) 메서드를 오버라이드 해야 하는 상황이 와닿지 않습니다.
    Fruit, Animal, Book, . . 등 다수의 클래스가 있을 때,
    일괄적으로 각 클래스마다 .equals를 사용하면서, Fruit만 기존의 equals와 다른 방식으로 비교하고 싶은 경우, 이런 경우만 생각납니다만, 이게 실제로
    개발을 할 떄 어떤 의미가 있는지를 모르겠습니다. Fruits에 추가적인 메소드를 추가하거나, 해당기능 메소드를 인터페이스에 명시하고 그 인터페이스를
    다수의 클래스들에 implements하는 편이 기존의 equals 기능도 한 클래스 안에서 같이 사용할 수 있어서 더 유용하지 않나요?

5. 또 오버라이드 이전에, equals()자체의 용도가 궁금합니다. 애초에 비교하기 위한 두 인스턴스가 존재한다면 두 인스턴스의 주소값은 다를 수 밖에 없지 않나요? 그렇다면 obj1.equals(obj2)는 항상 false 아닌가요? 문자열 상수가 아닌 이상 true가 나오는 경우가 있을지 궁금합니다.

6. 2번 규칙에 대한 이해가 적절한지 궁금합니다:
가령 부모 클래스 Fruits와 Fruit를 extends 한 자식클래스 Apple이 있고,
Apple에서 equals를 본문처럼 오버라이딩 한 경우와 같은 상황이며,
둘의 name 값이 문자열 상수 "사과"로 동일한 경우,
 fruit.equals(apple)은 false지만 apple.equals(fruit)은 true를 리턴하는 상황이 발생할 수 있는데,

이럴 때 부모클래스에 equals를 오버라이딩 하고, 자식클래스는 그냥 상속만 받게 하는 식의 제약같은 것을 의미하는 것인지 궁금합니다.
아이콘코드라떼(2021-06-30 13:55 작성됨)
1. 감사합니다. 수정했습니다^^!

2. 순수함수처럼 되어야 하는 의미가 맞습니다. 너무나 당연한 이야기인데 혹시나 순수함수가 아니도록
코드를 작성하는 경우가 있을 수 있습니다. 이런 경우를 하지 말라고 정의되었습니다

*이런식으로 하지 말자
class Fruit {
    private String name;
    private boolean flag = true;

    // equals 메서드 재정의
    public boolean equals(Object obj) {
        if (flag && obj instanceOf Fruit) {
            Fruit tempFruit = (Fruit)obj;
            flag = false;
            return name.equals(tempFruit.name);
        }
        return false;
    }
}

equals(obj) 메서드에 '동일한 인자를 전달하여 호출 시, 호출 횟수에 따라 다른 결과 값이 발생하는 이런 코드를 작성하지 말라'로 볼 수 있습니다.

3. 전자가 맞습니다.  equals(obj) 메서드를 '올바르게' 정의하기 위한 '명시적인 규칙'입니다. 두 객체를 비교한다는 기능인 equals(obj) 메서드를 오버라이드 할 때, 코드 작성자 기준이 아닌 사용자 기준으로 봤을 때 사용자는 코드를 모르는 상황에서 다양한 경우에 사용할 수 있기 때문입니다.  equals(obj)가 재정의되어 있을 경우 '5가지 제약 조건을 지키고 있는 코드'라고 사용자는 '묵시적'으로 생각합니다.

4. equals(obj) 메서드를 오버라이드 하는 경우는 많이 없으나 그래도 예시를 드린다면 아래와 같습니다.

class Address {
    String firstAddress; // 시/도
    String secondAddress; // 시/군/구
    String thirdAddress; // 읍/면/동

    Address(String first, String second, String third) {
        this.firstAddress = first;
        this.secondAddress = second;
        this.thirdAddress = third;
    }
}

예시로 주소를 저장하는 클래스가 존재한다고 가정합시다.

Address address1 = new Address("서울시", "강남구", "개포1동");
Address address2 = new Address("서울시", "강남구", "개포1동");

두 인스턴스는 서로 다른 메모리에 적재되어 있으므로 동일하다고 볼 수는 없으나 '의미상'으로는 동일하다고
볼 수 있습니다. 그리고 해당 객체들을 사용하다가 해당 객체가 '의미상' 주소가 동일한지 아닌지 확인해야 한다면 일반적으로는 이렇게 하겠지요.

boolean isEqual = address1.firstAddress.equals(address2.firstAddress) &&
address1.secondAddress.equals(address2.secondAddress) &&
address1.thirdAddress.equals(address2.thirdAddress);

이렇게 세 개를 비교하여 나온 결과 값이 true 일 때 동일한 주소라는 것을 파악할 수 있습니다.

만약에 equals(obj) 메서드를 '올바르게' 오버라이드 했다면 이런 코드를 작성할 수 있습니다. 코드의 ‘의미’도 더 명확해지고 '가독성'도 좋아집니다.
boolean isEqual = address1.equals(address2);

5. 4번의 예시로 같이 설명이 될 것 같습니다.

6. 본문의 예시가 좋은 예시가 아니라서 도전자님께서 헷갈리시게 되는 것 같습니다. (지금 보니 수정할 필요성을 느낍니다)

본문의 equals의 예제 코드 의도는 이렇게 메서드를 재정의 할 수 있다는 정도까지만 보여 드린 부분이었습니다. 그래서 5가지 제약조건이 포함되어 있지 않습니다. 이 부분은 좀 더 고민해서 내용을 수정하겠습니다.

감사합니다.^^
이용약관|개인정보취급방침
알유티씨클래스|대표, 개인정보보호책임자 : 이병록
이메일 : cs@codelatte.io|운영시간 09:00 - 18:00(평일)
사업자등록번호 : 824-06-01921|통신판매업신고 : 2021-성남분당C-0740
주소 : 경기도 성남시 분당구 대왕판교로645번길 12, 9층 24호(경기창조혁신센터)
파일
파일파일
Root
파일

Fruit 클래스의 equals 메서드와 hashCode 메서드가 재정의 되어있습니다.

Output
root$