Search
Duplicate
📒

[Java Study] 02-2. Objects 메서드

상태
완료
수업
Java Study
주제
Util
4 more properties
참고

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 구현
대부분의 Objectsequals()와 동일합니다.
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() 메서드는 런타임에만 클래스 정보를 제공하며, 컴파일시에는 타입 정보를 제공하지 않습니다.
리플렉션은 코드 타입 안전성이 낮고, 성능저하가 발생할 수 있으며 캡슐화를 위반할 수 있으니 사용을 자제하는것이 좋습니다.