참고
Object 메서드
NOTE
Object에서 equals, hashCode, toString, clone은 모두 재정의를 염두에 두고 설계되었습니다!
•
Object를 상속하는 모든 클래스는 이 메서드들을 일반 규약에 맞게 재정의해야 합니다.
•
해당 메서드들을 잘못 구현하면 이 규약을 준수한다고 가정하는 클래스(HashMap, HashSet)에서 오동작할 수 있습니다.
참고
IDE에서는 equals, hashCode, toString 등을 자동으로 생성해줍니다. 이 기능은 아래에서 다루는 규약들을 준수한 상태로 작성되므로, 적극적으로 사용하는 것이 좋습니다.
equal - 작성 규칙을 잘지키자
NOTE
equals 메서드는 객체 간 동등성을 비교할 때 사용된다!
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
// 1. null에 대한 비동등성: 모든 객체는 null과 비교했을 때 false
if (obj == null) return false;
// 2. 반사성: 어떤 객체 x에 대해, x.equals(x)는 항상 true
if (this == obj) return true;
// 3. 대상 객체가 Person 클래스의 인스턴스인지 확인
if (!(obj instanceof Person)) return false;
Person other = (Person) obj;
// 대칭성, 추이성, 일관성을 만족하기 위해 name 필드를 기준으로 동등성 비교
return name.equals(other.name);
}
}
Java
복사
규칙이 단순해 보이지만 생각보다 어기기 쉽다. (관련사례는 책 참조)
•
null에 대한 비동등성: x.equals(null)은 항상 false이다.
•
반사성: x=x
•
대칭성: x=y, y=x
•
추이성: x=y, y=z, x=z
•
일관성: while(x=y)
equals 작성 주의사항
•
각 인스턴스가 본질적으로 고유한 경우, 또는 논리적 동치성을 검사할 필요가 없을 때는 재정의하지 않습니다.
•
문제를 너무 복잡하게 해결하려고 하지 말고, Object 이외의 타입을 매개변수로 받지 마세요.
•
equals를 재정의하면 hashCode도 반드시 재정의해야 합니다.
hasCode - equals를 바꾸면 같이 해주자
NOTE
hashCode()는 객체의 해시코드를 반환하며 equals()로 동등하다 판단되면 동일한 해시코드를 반환해야 합니다!
equals() 메서드를 재정의 할 경우, hashCode()도 함께 재정의해야 합니다. 이는 Hash 기반 컬렉션에서 객체의 동등성과 해시 코드 값을 비교하는데 사용되기 때문입니다. 두 메서드가 일관성이 없으면 해시 기반 컬렉션에서 문제가 발생할 수 있습니다.
•
ex) equals()에 의해 동등하다고 판단하지만, hash값이 다르면 다르다고 판단되어 2개 모두 저장해버린다.
hasCode 작성 규칙
흐름도
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) { ... }
@Override
public int hashCode() {
// 문자열의 hashCode 메서드 사용
return name.hashCode();
}
}
Java
복사
equals가 name을 기준으로 판단하기 때문에 hashCode도 동일하게 판단
•
애플리케이션이 실행되는 동안 객체의 equals 비교에 사용되는 정보가 변경되지 않는다면, hashCode는 일관된 값을 반환해야 합니다.
•
equals = true인 경우, hashCode 값은 동일한 정수를 반환해야 합니다.
•
equals = false인 경우, hashCode 값은 다른 정수를 반환하는 것이 좋습니다. 이는 필수 사항은 아니지만, 해시테이블의 성능을 향상시키기 위해 권장됩니다.
만약 hashCode에 항상 같은 상수 값을 부여하면 어떤 결과가 발생할까요?
@Override
public int hashCode() {
return 1;
}
Java
복사
Hash에서 항상 같다고 판단한다.
•
모든 객체가 한 해시테이블의 버킷에 들어가기 때문에, 해시테이블의 성능이 크게 저하됩니다.
◦
O(1) → O(n)
Objects.hash(규약에 맞는 hash값)
Objects.hash(Object...) // hash값 생성
Java
복사
•
책에서는 작성하는 방법을 자세하게 제공하지만, 이미 만들어진 것을 사용하거나 IDE에서 생성해주는것을 사용하는것이 가장 좋습니다.
•
단, 성능에 민감한 경우에는 커스텀하거나 캐싱하는 방법을 사용할 수 있습니다.
toString - 항상 재정의하자
NOTE
toString 메서드의 경우 모든 하위클래스에서 재정의 하는것을 권장하고 있다!
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + '\'' + ", age=" + age + '}';
}
}
Java
복사
더 읽기 좋게 수정
•
toString()은 println, printf등 객체를 출력할 때 자동으로 호출되게 된다.
•
즉 직접 toString()을 호출하지 않아도 많이 쓰이게되므로 재정의 해주는것이 좋다.
clone - 쓰기 어렵고 안좋으니 다른 패턴쓰자
NOTE
clone을 구현하는 방식보다, 복사 생성자와 복사 팩터리 패턴을 사용하는것이 더 좋다!
Clone의 문제점
•
clone 메서드를 사용하려면 Cloneable 인터페이스를 구현해야 합니다. 그러나 이 과정에서 필드 단위로 얕은 복사(shallow copy)가 수행되면 예기치 않은 문제가 발생할 수 있습니다.
•
clone 메서드는 CloneNotSupportedException 예외를 발생시킬 수 있으므로, 이를 처리하는 추가 코드가 필요합니다.
•
상속 구조에서는 clone 메서드가 다양한 문제를 일으킬 수 있습니다. 특히 상위 클래스에서 clone이 적절히 구현되지 않았다면 문제가 더욱 발생할 수 있습니다.
복사 생성자/팩토리 패턴
public class Person {
private String name;
private int age;
// 기본 생성자
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 복사 생성자 - 생성자로 생성
public Person(Person another) {
this.name = another.name;
this.age = another.age;
}
// 복사 팩터리 메서드 - 정적 메소드로 객체생성
public static Person newInstance(Person another) {
return new Person(another.name, another.age);
}
}
Java
복사
복사 생성자와 복사 팩터리 메서드 사용 예
int[][] A = {{1,2,3,4,5}, {6,7,8,9,10}};
int[][] B = A; // B : 단순 변수 선언
int[][] C = A.clone(); // C : clone() 사용 - 2차원부터 얕은복사 사용
B[0][0] = 999;
System.out.println("'B'를 바꾸면 'A'값이 바뀝니다! : "+A[0][0]); /// 999
C[0][0] = 888;
System.out.println("'C'를 바꾸면 'A'값이 바뀝니다! : "+A[0][0]); /// 888
Java
복사
배열 clone
•
배열은 clone()을 적절하게 사용하는 유일한 경우입니다. 따라서 대상이 배열이 아닌 경우, 복사 생성자나 복사 팩터리를 사용하는 것이 좋습니다.
Comparable - 정렬이 필요하면 쓰자
NOTE
Comparaable 인터페이스의 compareTo를 구현하면 순서통해 정렬할 수 있습니다.
public class Member implements Comparable<Member> {
private Long id;
private String name;
private int age;
// Comparator 구현
private static final Comparator<Member> COMPARATOR =
Comparator.comparingLong((Member m) -> m.id)
.thenComparing(m -> m.name)
.thenComparingInt(m -> m.age);
public Member(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public int compareTo(Member o) {
// 단순비교 구현
int result = Long.compare(this.id, o.id); // Long compare
if(result == 0){
result = CharSequence.compare(this.name, o.name); // String compare
if(result == 0 ){
result = Integer.compare(this.age, o.age); // Integer compare
}
}
return 0;
// COMPARATOR 사용
return COMPARATOR.compare(this, o);
}
}
Java
복사
compareTo 구현
•
대부분의 Objects의 equals()와 동일합니다.
•
compareTo 메서드에서 비교할 때 <, > 사용은 피해야 합니다.
•
(x.compareTo(y) == 0) == (x.equals(y)) (필수적인 것은 아닙니다)
Comparator 인터페이스
Comparator 인터페이스는 java.util에 포함되어 있으며 compare(Object o1, Object o2) 메소드를 재정의함으로서 비교합니다. 객체에서 compareTo를 구현하지 않고 기존의 정렬기준을 사용하고 싶을때 사용할 수 있습니다.
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2); // 구현해야하는 함수
// 기본함수(더 많이있다.)
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
// ...
}
Java
복사
Comparator 예제
Comparator
.thenComparing(m -> m.name) // 일반비교
// 기본타입은 기본 메소드를 제공한다.
.comparingLong((Member m) -> m.id) // Long 비교
.thenComparingInt(m -> m.age); // int 비교
.reverse() // 역정렬
.comapre(this, o) // 비교
Java
복사
Compartor 비교 생성 메서드
getClass - 객체의 클래스 정보를 제공
NOTE
getClass() 메서드는 호출된 객체의 런타임 클래스를 나타내는 Class 객체를 반환합니다.
Object obj = new SomeClass();
Class<?> clazz = obj.getClass();
Java
복사
기본 사용법
Class 객체는 자바 리플렉션 API를 사용하여 런타임에 클래스의 메타데이터를 조회하거나 조작할 때 사용된다. 여기서 clazz 변수는 SomeClass의 Class 객체를 참조합니다. 이 객체를 사용해 클래스 이름을 얻거나, 해당 클래스에 정의된 메서드, 필드, 생성자 등에 접근할 수 있습니다.
주요 용도
•
타입 정보 조회
•
리플렉션
•
동적 객체 생성
주의사항
•
getClass() 메서드는 런타임에만 클래스 정보를 제공하며, 컴파일시에는 타입 정보를 제공하지 않습니다.
•
리플렉션은 코드 타입 안전성이 낮고, 성능저하가 발생할 수 있으며 캡슐화를 위반할 수 있으니 사용을 자제하는것이 좋습니다.