12장. 모든 클래스의 부모 클래스는 Object 예요

공부하는 감자·2023년 12월 4일
0

자바의 신 3판

목록 보기
12/30

들어가기 전

『자바의 신 3판』 을 읽고 내용 정리 및 공부한 내용을 정리한 글입니다.
서적: 자바의 신 3판 구입처

내용 정리

모든 자바 클래스의 부모인 java.lang.object 클래스

자바에서는 기본적으로 아무런 상속을 받지 않으면 java.lang.Object 클래스를 확장한다.

이를 확인하기 위해선, Object 클래스에 있는 메소드를 호출해볼 수 있다. 아무 선언도 하지 않은 클래스에서 toString()을 호출해보면 컴파일 및 실행까지 아무런 문제가 없는 것을 확인할 수 있다. 즉, Object 클래스를 상속받았음을 알 수 있다.

extends로 부모 클래스를 상속받을 경우

그렇다면 extends로 부모 클래스를 상속받을 때는 어떨까? 자바는 한 번에 이중 상속을 받을 순 없지만, 여러 단계로 상속을 받을 수는 있다.

예를 들어 Parent 클래스와 그를 상속받은 Child 클래스가 있다고 하자. Parent 클래스는 아무런 상속을 받지 않았지만, 실제로는 Object 클래스의 상속을 받았다.

그리고 Parent 클래스를 Child 클래스가 상속받았으므로, Child 클래스는 자동으로 Object 클래스의 메소드들을 상속받는다.

다시 말해서 Child 클래스는 Object 클래스의 자식의 자식이다.

Object ← Parent ← Child

왜 모든 클래스들은 Object의 상속을 받을까?

가장 큰 이유는 Object 클래스에 있는 메소드들을 통해서 클래스의 기본적인 행동을 정의할 수 있기 때문이다.

“이 정도의 메소드는 정의되어 있어야 하고, 처리해 주어야 한다.”는 것을 정의하는 작업이 필요하기 때문에 Object 클래스를 상속받았다고 생각하면 된다.

Object 클래스의 메소드 종류

Object 클래스에 선언되어 있는 메소드는 객체를 처리하기 위한 메소드와 쓰레드를 위한 메소드로 나뉜다. 쓰레드를 위한 메소드는 25장에서 자세히 살펴본다.

💡 쓰레드란?
프로그램이 실행되는 작은 단위 중 하나.

객체 처리를 위한 메소드

반환값메소드설명
protected Objectclone()객체의 복사본을 만들어 리턴한다.
public booleanequals(Object obj)현재 객체와 매개 변수로 넘겨받은 객체가 같은지 확인한다. (같으면 True, 다르면 False)
protected voidfinalize()현재 객체가 더 이상 쓸모가 없어졌을 때 가비지 컬렉터(garbage collector)에 의해 호출된다.
public Class<?>getClass()현재 객체의 Class 클래스의 객체를 리턴한다.
public inthashCode()객체에 대한 해시 코드(hash code) 값을 리턴한다. 해시 코드라는 것은 “16진수로 제공되는 객체의 메모리 주소”를 말한다.
public StringtoString()객체를 문자열로 표현하는 값을 리턴한다.

💡 가비지 컬렉터
자바의 메모리에 있는 쓰레기를 청소하는 로봇이라고 생각하면 된다. 객체를 만들고, 그 객체가 어디에서 쓰인 후 필요가 없어졌을 때 이 로봇이 자바 프로세스 내에 있는 객체들을 뒤져 보면서 어떤 객체를 죽일지 살릴 지를 확인해서 처리해준다.

쓰레드 처리를 위한 메소드

메소드설명
void notify()이 객체의 모니터에 대기하고 있는 단일 쓰레드를 깨운다.
void notifyAll()이 객체의 모니터에 대기하고 있는 모든 쓰레드를 깨운다.
void wait()다른 쓰레드가 현재 객체에 대한 notify() 메소드나 notifyAll() 메소드를 호출할 때까지 현재 쓰레드가 대기하고 있도록 한다.
void wait(long timeout)wait() 메소드와 동일한 기능을 제공하며 매개 변수에 지정한 시간만큼만 대기한다. 매개 변수 시간을 넘어 섰을 때에는 현재 쓰레드는 다시 깨어난다. 여기서의 시간은 밀리초로 1/1,000초 단위다.
void wait(long timeout, int nanos)wait() 메소드와 동일한 기능을 제공하며, 밀리초 + 나노초만큼만 대기한다. 뒤에 있는 나노초의 값은 0~999,999 사이의 값만 지정할 수 있다.

toString 메소드

toString() 메소드는 Object 클래스의 메소드 중에서 가장 많이 사용된다. 해당 클래스가 어떤 객체인지를 쉽게 나타낼 수 있는 중요한 메소드다.

이 메소드가 자동으로 호출되는 경우는 다음과 같다.

  • System.out.println() 메소드에 매개 변수로 들어가는 경우
  • 객체에 대하여 더하기 연산을 하는 경우

참조 자료형의 더하기 연산은 String만 가능하다. String을 제외한 참조자료형에 더하기 연산을 수행하면, 자동으로 toString() 메소드가 호출되어 객체의 위치에는 String값이 놓이게 된다.

// 아래 세 가지는 모두 같은 결과를 반환한다.
System.out.println(this);
System.out.println(toString());
System.out.println("" + this);

실제 Object 클래스에 구현되어 있는 toString() 메소드는 다음과 같다.

getClass().getName() + '@' + Integer.toHexString(hashCode())

Object 클래스에 있는 getClass() 결과에 getName() 메소드를 부르면 현재 클래스의 패키지 이름과 클래스 이름이 나온다.

그 다음에는 at(@, 골뱅이)가 붙는다. 이는 앞과 뒤를 구분하기 위한 구분자로, 신경쓰지 않아도 된다.

가장 마지막 부분에는 객체의 해시 코드 값을 출력한다. hashCode()는 int 타입의 값을 리턴해주는데, 그 값을 Integer 클래스에서 제공하는 toHexString() 메소드를 활용해 16진수로 변환하는 작업이 수행된다.

💡 Integer 클래스는 20장에서 설명되어 있다. 간단히 설명하면 Int 값을 보다 쉽게 처리할 수 있도록 도움을 주는 클래스이다.

언제 toString()을 Overriding 해야 하는가?

모든 클래스의 toString()을 오버라이딩할 필요는 없다. 하지만, DTO를 사용할 때는 오버라이딩해 놓는 것이 좋다. 그래야 내용 확인이 쉽기 때문이다.

==와 equals()

==와 !=는 기본 자료형에서만 사용할 수 있다. 참조 자료형에서 사용하면 “주소값”을 비교하기 때문에 사용하면 안된다.

안의 속성값들이 같은지 비교할 때는 equals()를 사용해야 한다.

그런데 오버라이딩하지 않은 equals() 메소드는 hashCode() 값을 비교한다.

hashCode() 값은 해당 객체의 주소값을 리턴하므로, 클래스의 인스턴스 변수값들이 같더라도 서로 다른 생성자로 객체를 생성했으면 해시 코드가 다르니 두 객체는 다르다는 결과가 나온다.

따라서 Object 클래스에 선언되어 있는 equals() 메소드를 오버라이딩해 놓아야지만 제대로 된 비교가 가능하다.

이클립스나 인텔리제이같은 IDE에서는 자동으로 생성한 equals() 메소드를 생성해주는 기능이 있다.

오버라이딩 예시

public class MemberDTO {
	public boolean equals(Object obj) {
		if (this == obj) return true;  // 주소가 같으므로 true
		if (obj == null) return false; // obj가 null이므로 false

		// 클래스의 종류가 다르므로 false
		if (getClass() != obj.getClass()) return false;
		// 같은 클래스이므로 형 변환 실행
		MemberDTO other = (MemberDTO) obj;

		// 각 인스턴스 변수가 같은지 비교하는 작업 수행
		if (name == null) {
			// name이 null일 때 비교 대상의 name이 null이 아니면 false
			if (other.name != null) return false;
			// 두 개의 email 값이 다르면 false
		} else if (!name.equals(other.name)) return false;

		if (phone == null) {
			if (other.phone != null) return false;
		} else if (!phone.equals(other.phone)) return flase;

		return true;
	}
}

오버라이딩 규칙

equals() 메소드를 Overriding할 때는 반드시 다음 다섯 가지를 만족시켜야 한다.

  • 재귀 reflexiv: null이 아닌 x라는 객체의 x.equals(x) 결과는 항상 true여야만 한다.
  • 대칭 symmetric: null 아닌 x와 y 객체가 있을 때 y.equals(x)가 true를 리턴했다면, y.equals(x)도 true를 리턴해야만 한다.
  • 타동적 transitive: null이 아닌 x, y, z가 있을 때 x.equlas(y)가 true를 리턴하고, y.equals(z)가 true를 리턴하면, x.equals(z)는 반드시 true를 리턴해야만 한다.
  • 일관 consistent: null이 아닌 x와 y가 있을 때 객체가 변경되지 않은 상황에서는 몇 번을 호출하더라도, x.equals(y)의 결과는 항상 true이거나 항상 false여야만 한다.
  • null과의 비교: null이 아닌 x라는 객체의 x.equalus(null) 결과는 항상 false여야만 한다.

equals() 메소드를 Overriding할 때에는 hashCode() 메소드도 같이 Overriding해야만 한다.

왜냐하면, equals() 메소드를 오버라이딩해서 객체가 서로 같다고 이야기할 수는 있겠지만, 그 값이 같다고 해서 그 객체의 주소 값이 같지는 않기 때문이다.

다시 말하면, equals() 메소드의 결과가 true인데도 불구하고 hashCode() 메소드의 값은 다르게 된다.

따라서 같은 hashCode() 메소드 결과를 갖도록 하려면 함께 오버라이딩 해줘야 한다.

자동으로 생성한 hashCode() 오버라이딩 결과는 다음과 같다.

public class MemberDTO {
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((email == null) ? 0 : email.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((phone == null) ? 0 : phone.hashCode());
        return result;
    }
}

💡 equals() 메소드는 객체 비교가 필요할 경우에만 Overriding해주면 된다.

hashCode()

hashCode() 메소드는 기본적으로 객체의 메모리 주소를 16진수로 리턴한다. 만약 어떤 두 개의 객체가 서로 동일하다면, hashCode() 값은 무조건 동일해야만 한다.

따라서, equals() 메소드를 override하면, hashCode() 메소드도 override해서 동일한 결과가 나오도록 만들어야만 한다.

오버라이딩 규칙

hashCode() 메소드를 오버라이딩할 때는 다음 조건을 따라야 한다.

  • 자바 애플리케이션이 수행되는 동안에 어떤 객체에 대해서 이 메소드가 호출될 때에는 항상 동일한 int 값을 리턴해 주어야 한다. 하지만, 자바를 실행할 때마다 같은 값이어야 할 필요는 전혀 없다.
  • 어떤 두 개의 객체에 대하여 equals() 메소드를 사용하여 비교한 결과가 true일 경우에, 두 객체의 hashCode() 메소드를 호출하면 동일한 int 값을 리턴해야만 한다.
  • 두 객체를 equals() 메소드를 사용하여 비교한 결과 false를 리턴했다고 해서, hashCode() 메소드를 호출한 int 값이 무조건 달라야 할 필요는 없다. 하지만, 이 경우에 서로 다른 int 값을 제공하면 hashtable의 성능을 향상시키는 데 도움이 된다.

이러한 제약들 때문에, 직접 두 메소드들을 작성하는 것은 별로 권장하지 않는다. 요즘은 각종 개발 툴에서 자동으로 생성해주는 기능을 제공해주므로, 해당 기능을 사용하는 것을 권장한다.

정리해 봅시다.

Q. 모든 클래스의 최상위 부모 클래스인 Object 클래스는 어떤 패키지에 선언되어 있나요 ?

Me: java.lang

Q. 클래스가 어떻게 선언되어 있는지 확인할 수 있는 명령어(실행파일)의 이름은 무엇인가요?

Me: javap

Q. Object 클래스에 선언되어 있는 모든 메소드를 Overriding해야 하나요?

Me: X

Q. Object 클래스의 clone() 메소드의 용도는 무엇인가요?

Me: 해당 객체를 복사하여 반환한다.

Q. System.out.println() 메소드를 사용하여 클래스를 출력했을 때 "최종적으로" 호출되는 Object 클래스에 있는 메소드는 무엇인가요?

Me: toString()

Q. 객체의 주소를 비교하는 것이 아닌, 값을 비교하려면 Object 클래스에 선언되어 있는 어떤 메소드를 overrding해야 하나요?

Me: equals()

Q. Object 클래스에 선언되어 있는 hashCode()라는 메소드는 어떤 타입의 값을 리턴 하나요?

Me: int타입

질문

💡 책에 있는 내용이 아닙니다.

책을 읽으며 설명이 더 필요하거나, 추가로 궁금한 점에 대해 질문 형식으로 작성 후, 답을 구해보고 있습니다.
참고한 사이트나 영상은 [출처]로 달아두었으며, 오류 지적은 언제나 환영합니다.

Q. Object.finalize() 는 실제로 쓰이는가?

메소드의 사용법 및 의의

Object 클래스에 있는 메소드이다. 특정 객체에 대한 참조가 더 이상 없다고 판단할 때 가비지 컬렉션이 객체의 finalize를 호출한다. 이 메소드는 비어 있는 메소드일 뿐, 따로 오버라이딩 하지 않을 경우 아무 동작도 하지 않는다.

문서에서는 나와 있듯이 하위 클래스에서 시스템 리소스를 삭제하거나 다른 정리를 수행하기 위해 finalize 메소드를 오버라이딩하여 작성할 수 있다고 한다.

즉, 가비지 수집 대상이 되었을 때 (객체가 소멸되기 전에) 애플리케이션 개발자가 의도한 기능을 수행하여 별도의 리소스 정리 작업을 할 수 있도록 할 때 오버라이딩해서 사용하는 메소드이다.

오버라이딩 시

finalize() 메소드를 오버라이드 하면 해당 객체는 가비지 컬렉터가 조금 특별하게 처리한다. 가비지 수집 즉시 회수되지 않고 종료화 대상으로 먼저 등록된다. 그래서 finalize() 를 오버라이드 하지 않는 객체보다 수명이 한 사이클 정도 더 길다고 볼 수 있다.

좀 더 상세하게 단계별로 정리하면 다음과 같습니다.

  1. finalize() 를 오버라이드 한 객체는 큐로 이동
  2. 별도의 finalize 스레드가 해당 큐를 비우면서 각 객체마다 정의된 finalize() 메소드를 호출
  3. finalize() 가 종료되면 해당 객체는 다음 GC 사이클에 진짜 수집될 준비를 마침

가비지 컬렉터가 finalize() 메소드를 오버라이드 한 객체와 그렇지 않는 객체를 구별할 수 있는 이유는, 객체가 finalize() 메소드를 오버라이드 하게되면 해당 객체의 생성자 바디가 성공적으로 반환되는 시점에 해당 객체를 종료화 가능한 객체 목록에 등록하는 식으로 JVM 에 구현되어 있기 때문이다.

JDK9 버전에서 Deprecated되었다고 한다.

  • 해당 메소드 사용 시 출력되는 경고문

이는 아래 문제들을 야기시킬 수 있기 때문이다.

  1. 객체의 수명 연장
  2. finalize()가 신속하게 실행된다는 보장이 없다. 실행이 보장되지도 않는다.
  3. finalize()에서 발생하는 예외는 무시가 되어서, 로직이 불완전한 상태로 종료되어 문제를 발생시킬 수 있다.
  4. GC 과정에서 수행하기 때문에 성능 저하가 발생할 수 있다.

따라서 Closable 인터페이스의 close() 메소드에 객체와 엮여 있는 리소스의 정리를 구현하고, 객체의 사용이 끝났을 때 호출해주는 식으로 정리하는게 맞다.

Q. String은 equals()를 오버라이딩하지 않아도 잘 비교가 되는데?

String 클래스에서 Object.equals()를 오버라이딩한 상태이다. 아래는 자바 API 문서에서 발췌.

Q. print()시 실행되는 메소드가 toString()이 아니다.

뒷장에서는 print()를 통하여 출력할 때 valueOf() 라는 메소드를 출력한다고 되어 있다. valueOf()는 객체가 null일 경우 문자열 “null”을 반환하고, null이 아닐 경우 내부적으로 toString()을 출력하도록 되어 있다.

즉, toString()과 다르게 객체가 null이어도 NPE(NullPointException)을 발생시키지 않으므로, valueOf()를 사용하도록 한 것 같다.

💡 테스트 해봤을 때, 더하기 연산도 마찬가지로 문자열 null을 출력하는 것을 확인했다.

Q. hashcode()는 객체의 주소값을 반환하는가?

Java 8의 hashCode() 의 설명을 보면 아래와 같이 쓰여 있다.

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java™ programming language.)

Object 클래스에서 정의한 hashCode 메서드는 가능한 한 실제 객체들에 대해 구별된 정수를 반환하도록 설계되어 있습니다. (이는 일반적으로 객체의 내부 주소를 정수로 변환하는 방식으로 구현되지만, 이 구현 기술은 Java™ 프로그래밍 언어에 의해 필수적으로 요구되지 않습니다.)

참고로 이 설명은 Java 21 버전에서 없어졌다.

설명을 읽어보면 자바에서는 hashCode의 구현 방식에 대한 특정한 기준을 제시하지 않고 있다는 것을 알 수 있다.

hashCode의 구현은 JVM 에 달려있다.

자바 언어 명세에서 hashcode()에 대한 구체적인 구현 방법을 명시하지 않기 때문이라고 한다.

즉, hashcode()는 명세에 따라 동일한 동작을 보장하지만, 그 동작을 어떻게 구현할지는 JVM 제조사에게 달려있다는 말이다.

Java API의 명세

  1. Java 애플리케이션 실행 중에, 동일한 객체에 대해 hashcode()가 여러 번 호출되더라도 항상 동일한 정수를 일관되게 반환해야 한다.
  • 실행 중에만 일관되면 된다.
  1. 만약 두 객체가 equals()의 결과가 동일하다면, hashcode()도 동일한 정수를 반환해야 한다.
  2. 만약 두 객체가 equals()의 결과가 동일하지 않아도, hashcode()에서 서로 다른 정수를 생성해야 할 필요는 없다.
  • 하지만, 동일하지 않은 객체에 대해 서로 다른 정수를 생성하는 것이 해시 테이블의 성능을 향상시킨다.
  • 해시 테이블은 해시 함수를 사용하여 키를 버킷(bucket)으로 매핑하고, 이 버킷에 키-쌍을 저장한다. 만약 두 개 이상의 키가 동일한 해시 값을 같는다면 충돌을 일으키고, 이 충돌을 처리하기 위해 연결 리스트나 트리 등의 자료구조를 사용한다.
  • 즉, 서로 다른 객체에 대해 hashcode 값이 다르다면 서로 다른 버킷에 할당될 가능성이 높아지므로 충돌 가능성을 낮추므로 성능이 향상되는 것이다.

OpenJDK의 구현 방식

OpenJDK에서는 System.identityHashCode(this)를 호출해서 정수 값을 생성한다고 한다.

하단 참고 사이트의 첫번째 링크인 How does the default hashCode() work?에서 해당 메소드를 파헤쳐서 분석했는데, 해당 내용을 간략히 정리하면 다음과 같다.

System.identityHashCode(this)는 내부적으로 총 6가지의 방식을 제공한다.

  1. A randomly generated number.
  2. A function of memory address of the object.
  3. A hardcoded 1 (used for sensitivity testing.)
  4. A sequence.
  5. The memory address of the object, cast to int.
  6. Thread state combined with xorshift
  • OpenJDK 6과 OpenJDK 7은 1번 사용
  • OpenJDK 8에서는 5번을 기본값으로 사용
  • OpenJDK 9에서는 5번을 기본값으로 사용 (기본값 유지)

즉, OpenJDK에서는 최소한 버전 6이후부터는 hashCode와 메모리 주소는 관련이 없다.

만약 hashCode가 메모리 주소라면

다음과 같은 문제가 생길 수 있다.

  • 객체의 위치 이동
    • 객체가 생성될 때 메모리 주소가 할당된다. 그런데, GC가 일어나면 객체의 위치가 변경될 수 있다. 이 경우, 메모리 주소가 달라졌으므로 hashCode의 값도 변경되어야 하는데 hashCode는 실행 시간 동안 일관된 값을 반환해야 한다(자바 명세).
  • 보안 문제
    • 메모리 주소를 알면 해당 객체에 직접 접근하는 것이 가능하기 때문에, 보안 상 위험할 수 있다.
  • 해시 충돌
    • 서로 다른 객체가 같은 메모리 주소를 가리키고 있다면, 이 둘은 같은 hashCode 값을 갖게 된다. 따라서 해시 테이블에서 충돌을 일으킬 수 있다.
    • 또한, 메모리 주소를 사용하면 객체의 내용을 기반으로 해시를 계산하는 것보다 훨씬 제한된 범위의 값을 얻게 되므로 해시 충돌이 더 자주 발생할 수 있다.

결론

hashCode는 명세만 정해져 있고 구현은 JVM에 따라 달라진다. 일반적으로는 객체의 내부 식별자(객체의 메모리 주소, 객체의 참조값, 또는 다른 고유한 값)를 기반으로 해시 코드를 반환하는 System.identityHashCode(this)를 사용한다고 한다.

따라서 객체의 메모리 주소를 사용한다고 확신할 수는 없다. 다만, OpenJDK는 memory 주소와 전혀 관계가 없다.

Q. equals()와 hashcode()를 오버라이딩해야하는 이유

equals()를 오버라이딩하는 이유

  • equals() 는 객체의 내용이 동일한지 여부를 확인하는데 사용되며, 이는 객체 간의 논리적인 동등성을 나타낸다. (객체의 동등성을 비교)
  • Object 클래스에서 상속한 equals()는 객체의 참조(주소)가 동일한지 여부를 검사한다. 하지만 대부분의 경우 객체의 내용이 같은지를 비교하는게 필요하다. (ex. String의 equals())

hashCode()를 오버라이딩하는 이유

  • hashCode() 메서드는 해시 기반 컬렉션(HashMap, HashSet 등)에서 객체를 저장하고 검색하는 데 사용된다.
  • 해시 기반 컬렉션은 객체의 해시 코드를 기반으로 데이터를 관리하므로, 동등한 객체는 동일한 해시 코드를 반환해야 한다.
    • '동등성'과 '동일성'에 대해서는 아래에서 설명한다.

따라서 equals()를 오버라이딩하면서 hashCode()도 함께 오버라이딩하여 일관성을 유지해야 한다.

컬렉션 프레임워크에서의 동작

컬렉션 프레임워크는 뒷장에서 배우겠지만, 다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화된 방법을 제공하는 클래스의 집합이다.

그 중 HashMap에서의 동작을 살펴 보자.

아래는 ChatGPT의 도움을 받았습니다.

  1. 객체 삽입
    • 객체의 hashCode()로 얻은 해시 코드로 해시 테이블의 버킷(인덱스)을 찾아간다.
    • 동일한 버킷에 이미 다른 키-값 쌍이 존재한다면, equals()로 동등성을 확인하여 중복 여부를 결정한다.
  2. 객체 검색
    • 해시 코드를 기반으로 버킷을 찾고, 해당 버킷에 저장된 키와 검색하려는 키의 동등성을 equals()로 확인하여 일치하는지 여부를 결정한다.
  3. 해시 충돌
    • 해시 충돌이 발생하면 같은 버킷에 여러 키가 저장될 수 있다. 이때 equals()를 사용하여 실제로 같은 키인지를 확인한다.

즉, hashCode()를 기반으로 데이터를 저장하고, hashCode()는 같은 값이 나올 수 있으므로 equals()로 동등한 지 검사한다.

이처럼 해시 기반의 컬렉션에서는 문제가 생길 수도 있으므로 둘 다 오버라이딩 해야한다.

물론, 사용하지 않는데 모두 오버라이딩 할 필요는 없다. 꼭 필요한 경우에 위 사항들을 고려해서 오버라이딩 하도록 하자.

Q. 동등성과 동일성

동일성

두 객체에게 할당된 메모리 주소가 같을 때, 두 객체는 동일하다고 말한다.

다시 말해서, 같은 메모리 주소를 가리키고 있고 같은 값을 가지고 있으면 두 객체는 동일하다고 할 수 있다.

동등성

두 객체의 메모리 주소가 다르더라도, 객체의 내용이 같을 때 동등하다라고 말한다.

즉, 동일하면 동등하다. 하지만 동등하면 동일하다고 말할 수는 없다.

==와 equals, 그리고 hashCode 측면에서

== 연산자는 객체의 동일성을 판별하기 위해 사용하며, equals()는 두 객체의 동등성을 판별하기 위해 사용한다.

equals()는 재정의하지 않으면 내부적으로 == 연산자와 같은 로직을 수행한다. 따라서 equals()는 각 객체의 특성에 맞게 재정의를 해야 동등성의 기능을 수행한다.

hashCode()는 동일성이라고 할 수 있다. equals()가 같으면 hashCode()도 같아야 한다. 즉, 객체의 내용이 같으면 그 객체는 동일한 객체이다.

참고 사이트

equals()와 hashCode() 총 정리

.equals와 .hashCode()는 항상 함께 오버라이딩해야한다.

☕ 자바 equals / hashCode 오버라이딩 - 완벽 이해하기

해시코드는 메모리 주소값이 아니다

How does the default hashCode() work?

OKKY - java.lang.Object.hashcode() 값은 실제 메모리 주소와 어떤 연관이 있나요?

hashCode는 정말 메모리주소와 관련이 있을까?

hashcode()와 equals() 파헤치기

해시코드는 고유하지 않다

☕ 자바 객체의 hashCode는 고유하지 않다 ❌

해시코드가 중복될 경우 HashMap에서는 어떻게 동작할까?

자바 hashCode()

finalize() 관련 참고 사이트

[이펙티브 자바] 규칙7. GC가 호출하는 Object.finalize()

Java finalize() 은퇴식

'finalize 메소드 퇴역 이후' 자바 오류를 처리하고 클린업하는 방법

profile
책을 읽거나 강의를 들으며 공부한 내용을 정리합니다. 가끔 개발하는데 있었던 이슈도 올립니다.

0개의 댓글