Object Class

rivermt·2023년 3월 28일
0

JAVA

목록 보기
3/9

Object 클래스

모든 클래스의 최고 조상이기 때문에 Object 클래스의 멤버들은 모든 클래스에서 바로 사용 가능하다.

멤버변수는 없고, 오직 11개의 메서드만을 가지고 있다.

이 메서드들은 모든 인스턴스가 가져야 할 기본적인 것들이며, 우선 이 중에서 중요한 몇가지만 보자.

equals(Object obj)

매개변수로 객체의 참조변수를 받아서 비교하여 그 결과를 boolean값으로 알려주는 역할을 한다.

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

두 객체의 같고 다름을 참조변수의 값으로 판단한다.
-> 객체를 생성할 때, 메모리의 비어있는 공간을 찾아 생성하므로 서로 다른 두 개의 객체가 같은 주소를 갖는 일은 있을 수 없다.
-> 서로 다른 두 객체를 equals를 통해 비교하면 항상 false

실제로 가지고 있는 값을 비교할 수 는 없을까?

public boolean equals(Object obj) {
	if(obj != null && obj instanceof Person) {
    reutrn id == ((Person)obj).id;
  } else {
  		return false;
  }
}

-> 오버라이딩을 활용하면 가능하다.
-> String 클래스에서는 오버라이딩을 통해서 String인스턴스가 가지는 문자열 값을 비교하도록 되어있다.

equals 메서드를 재정의할 때는 반드시 일반규약을 따라야한다.

  • 반사성 : 객체는 자기 자신과 같아야 한다.
  • 대칭성 : 두 객체는 서로에 대한 동치 여부에 똑같이 답해야 한다.
  • 추이성 : 첫 번째, 두 번째 객체가 같고, 두 번째, 세 번째 객체가 같다면 첫 번째, 세 번째 객체도 같아야 한다.
  • 일관성 : 두 객체가 같다면 앞으로도 영원히 같아야 한다.
  • Non null : 모든 객체가 null과 같지 않아야 한다.
  1. == 연산자를 통해 입력이 자기 자신의 참조인지 확인한다.
  2. Instanceof 연산자로 입력이 올바른 타입인지 확인한다.
  3. 입력을 올바른 타입으로 형변환 한다.
  4. 입력 객체와 자기 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사한다.

@AutoValue

구글이 제공하는 자바 라이브러리 중 하나로, 불변 객체를 생성하기 위한 코드를 자동으로 생성해주는 기능을 제공한다.

자동으로 equals(), hashCode(), toString() 등의 메서드가 생성된다.

https://chromium.googlesource.com/external/github.com/google/auto/+/auto-value-1.0/value/README.md

hashCode()

이 메서드는 해싱기법에 사용되는 해시함수를 구현한 것이다.

해시 함수는 찾고자하느 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드를 반환한다.

32bit JVM에서는 서로 다른 두 객체는 결코 같은 해시코드를 가질 수 없었지만

64bit JVM에서는 8 byte 주소값으로 해시코드(4 byte) 를 만들기 때문에 해시코드가 중복될 수 있다.

"equals() 메서드를 오버라이딩 할 때 반드시 hashCode() 메서드도 함께 오버라이딩 해야한다."

hashCode() 메서드는 객체를 식별하기 위한 정수값을 반환하는 메서드다. 이 값은 객체가 저장되는 해시 테이블에서 객체를 찾기 위한 인덱스로 사용된다. 따라서, 객체의 hashCode() 값은 객체의 주소값을 기반으로 계산되며, 객체의 값이 같더라도 서로 다른 메모리에 저장되어 있다면 hashCode() 값이 다를 수 있다.

반면에 equals() 메서드는 객체의 내용이 같은지 비교하는 메소드입니다. 두 객체가 equals() 메서드를 호출하여 true를 반환한다면, 두 객체는 내용이 같은 것으로 간주된다.

하지만, equals() 메서드를 오버라이딩하여 내용이 같은 두 객체를 비교할 때, 서로 다른 메모리에 저장된 두 객체가 같은 hashCode() 값을 반환하지 않는다면 해시 테이블에서 해당 객체를 찾을 수 없게 된다. 이는 프로그램의 성능에 영향을 미칠 수 있으며, 예기치 않은 결과를 초래할 수 있다.

따라서 equals() 메서드를 오버라이딩할 때는, hashCode() 메서드도 함께 오버라이딩하여 두 메서드가 서로 일관된 값을 반환하도록 해야 한다. 이렇게 함으로써 객체를 해시 테이블에서 안전하게 사용할 수 있으며, 프로그램의 성능을 최적화할 수 있다.

https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

파란색으로 선택한 두 번째 조항을 보면 논리적으로 같은 객체는 같은 해시코드를 반환해야한다고 되어있다.

toString()

이 메서드는 인스턴스에 대한 정보를 문자열(String)로 제공할 목적으로 정의한 것이다.

Object 클래스에 정의된 toString()은 다음과 같다.

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

toString()은 일반적으로 인스턴스나 클래스에 대한 정보 또는 인스턴스 변수들의 값을 문자열로 변환하여 반환하도록 오버라이딩한다.

Object 클래스에서 접근제어자가 public으로 되어있기 때문에 오버라이딩 하는 클래스에서도 접근제어자를 public으로 해야한다.

clone()

이 메서드는 자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다.

단순히 인스턴스 변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다.

예를 들어 배열의 경우, 복제된 인스턴스도 같은 배열의 주소를 가지기 때무에 복제된 인스턴스의 작업이 원래의 인스턴스에 영향을 미치게 된다. 이런 경우 clone()메서드를 오버라이딩해서 새로운 배열을 생성하고 배열의 내용을 복사하도록 해야한다.

clone()을 사용하려면 복제할 클래수가 Cloneable 인터페이스를 구현하고 clone()을 오버라이딩하면서 접근 제어자를 protected에서 public으로 변경한다. 그래야만 상속관계가 없는 다른 클래스에서 clone()을 호출할 수 있다.

또한 반드시 예외처리를 해주어야한다.

class Point implements Cloneable {
  int x, y;
  
  Point(int x, int y) {
    this.x = x;
    this.y = y;
  }
  
  public String toString() {
    return "x=" + x + ", y=" + y;
  }
  
  public Object clone() {
    Object obj = null;
    try {
      obj = super.clone(); // clone()은 반드시 예외처리를 해주어야한다.
    } catch(CloneNotsupportedException e) {    }
    return obj;
  }
  
}

Cloneable 인터페이스를 구현한 인스턴스만 clone()을 통한 복제가 가능하다.

그 이유는 인스턴스의 데이터를 보호하기 위해서다. Cloneable인터페이스가 구현되 있다는 것은 클래스 작성자가 복제를 허용한다는 의미를 가진다.

배열을 복제하는 여러 방법

int[] arr = {1, 2, 3, 4, 5};
int[] copyArr = Arrays.copyOf(arr, arr.length);
int[] arr = {1, 2, 3, 4, 5};
int[] copyArr = new int[arr.length];
System.arraycopy(arr, 0, copyArr, 0, arr.length);
int[] arr = {1, 2, 3, 4, 5};
int[] copyArr = arr.clone();

얕은 복사와 깊은 복사

clone()은 단순히 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 객체까지 복제하지는 않는다.

객체배열을 clone()으로 복제하는 경우 원본과 복제본이 같은 객체를 공유하므로 완전한 복제라고 보기 어려운데

이러한 복제를 얕은 복사(shallow copy)라고 한다.

얕은 복사에서는 원본을 변경하면 복사본도 영향을 받는다.

반면 원본이 참조하고 잇는 객체까지 복제하는 것을 깊은 복사(deep copy)라고 하며,

깊은 복사에서는 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.

clone()을 통해 깊은 복사를 하려면 복제된 객체가 새로운 인스턴스를 참조하도록 해야한다.

getClass()

이 메서드는 자신이 속한 클래스의 Class 객체를 반환하는 메서드이다.

Class 객체는 이름이 Class인 클래스의 객체이다.

public final class Class implements ... {
	// Class 클래스
}

Class 객체는 클래스의 모든 정보를 담고 있으며 클래스 당 1개만 존재한다.

그리고 클래스 파일이 클래스 로더(ClassLoader)에 의해서 메모리에 올라갈 때, 자동으로 생성된다.

클래스 로더 (ClassLoader)

클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.

먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고 있으면 객체의 참조를 반환하고 없으면

클래스 패스(classpath)에 지정된 경로를 따라서 클래스 파일을 찾는다. 못찾으면 ClassNotFoundException이 발생하고 찾으면, 해당 클래스 파일을 읽어서 Class객체로 반환한다.

더 자세한 내용은 차후에 따로 다룰 예정이다.

Class 객체를 얻는 방법

클래스의 정보가 필요할 때, 먼저 Class 객체에 대한 참조를 얻어 와야 하는데, 해당 Class 객체에 대한 참조를 얻는 방법은 여러가지가 있다.

Class cObj = new Card().getClass(); // 생성된 객체로부터 얻는 방법
Class cObj = Card.class; 						// 클래스 리터럴(*.class)로부터 얻는 방법
Class cObj = Class.forName("Card"); // 클래스 이름으로부터 얻는 방법

forName()의 경우 특정 클래스 파일, 예를 들어 데이터베이스 드라이버를 메모리에 올릴 때 주로 사용한다.

Class.forName("oracle.jdbc.driver.OracleDriver")

Class 객체를 이용하면 클래스에 정의된 멤버의 이름이나 개수 등, 클래스에 대한 모든 정보를 얻을 수 있기 때문에

Class 객체를 통해서 객체를 생성하고 메서드를 호출하는 등 보다 동적인 코드를 작성할 수 있다.

참고자료

자바의 정석 by 남궁성
이펙티브자바 item 10, 11

profile
화이팅!!

0개의 댓글