java

    equals를 재정의하려거든 hashCode도 재정의하라(2) - [3장. 모든 객체의 공통 메서드(아이템11)]

    앞에서 살펴본 것처럼 좋은 해시 함수라면 서로 다른 인스턴스에 다른 해시 코드를 반환한다. 이상적인 해시 함수는 주어진 (서로 다른) 인스턴스들을 32비트 정수 범위에 균일하게 분배해야 한다. 이상을 완벽하게 실현하기는 어렵지만 비슷하게 만들기는 그다지 어렵지 않다. 해당 글을 통해 해시 코드를 구현하는 4가지 방법과 주의 사항을 알아보자. 1. 전형적인 hashCode 구현 🥑 핵심 필드 중에 하나를 골라서 hashCode를 구한다. 기본 타입(primitive type) 필드라면 해당 타입의 hashCode라는 메서드를 사용해서 구하면 된다. (Type.hashCode(f)) 참조 타입(reference tyep) 필드라면 해당 타입의 hashCode를 호출해서 구한다. (Point라는 타입을 사용한..

    equals를 재정의하려거든 hashCode도 재정의하라(1) - [3장. 모든 객체의 공통 메서드(아이템11)]

    equals를 재정의한 클래스 모두에서 hashCode도 재정의해야 한다. 그렇지 않으면 hashCode 일반 규약을 어기게 되어 해당 클래스의 인스턴스를 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제를 일으킬 것이다. 다음은 Object 명세에서 발췌한 hashCode에 대한 규약이다. equals 비교에 사용되는 정보가 변경되지 않았다면 hashCode는 매번 같은 값을 리턴해야 한다. 정보가 변경되거나, 애플리케이션을 다시 실행했다면 값이 달라질 수 있다. 두 객체에 대한 equals가 같다면, hashCode의 값도 같아야 한다. 두 객체에 대한 equals가 다르더라도, hashCode의 값은 같을 수 있다. 하지만, 해시 테이블 성능을 고려해 다른 값을 리턴하는 것이 좋..

    equals는 일반 규약을 지켜 재정의하라(3) - [3장. 모든 객체의 공통 메서드(아이템10)]

    이번 글에서는 지금까지의 내용을 종합해서 양질의 equals 메서드 구현 방법과 주의 사항에 대해서 알아보고자 한다. 1. equals 구현 방법 == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다. instanceof 연산자로 입력이 올바른 타입인지 확인한다. 입력을 올바른 타입으로 형변환한다. 입력 객체와 자기 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사한다. float와 double을 제외한 기본 타입 필드는 == 연산자로 비교하고, 참조 타입 필드는 각각의 equals 메서드로, float과 double 필드는 각각 정적 메서드인 Float.compare(float, float)와 Double.compare(double, double)로 비교한다. (float와 doubl..

    equals는 일반 규약을 지켜 재정의하라(2) - [3장. 모든 객체의 공통 메서드(아이템10)]

    이번 글에서는 equals 메서드를 재정의할 때 반드시 지켜야 하는 일반 규약 5가지를 조금 더 자세히 알아보고자 한다. 1. 반사성(reflexivity) A.equals(A) == true 반사성은 단순히 말하면 객체는 자기 자신과 같아야 한다는 뜻이다. 이 요건은 일부러 어기는 경우가 아니라면 만족시키지 못하기가 더 어려워 보인다. 2. 대칭성(symmetry) A.equals(B) = B.equals(A) 대칭성은 두 객체는 서로에 대한 동치 여부에 똑같이 답해야 한다는 뜻이다. 대소문자를 구별하지 않는 문자열을 구현한 다음 클래스가 있다고 하자. public final class CaseInsensitiveString { private final String s; public CaseInsens..

    equals는 일반 규약을 지켜 재정의하라(1) - [3장. 모든 객체의 공통 메서드(아이템10)]

    꼭 필요한 경우가 아니면 equals를 재정의하지 말자. equlas 메서드는 재정의하기 쉬워 보이지만 곳곳에 함정이 도사리고 있어서 자칫하면 끔찍한 결과를 초래하기 때문이다. 이번 글에서는 'equals를 재정의 하지 않아도 되는 4가지 상황'과 '만약 equals 재정의를 해야 한다면, 만족해야 하는 5가지 규약'에 대해서 알아볼 것이다. 1. 재정의 하지 않아도 되는 4가지 상황 🚩 1. 각 인스턴스가 본질적으로 고유한 경우 🌴 싱글톤, ENUM 등 각 인스턴스가 본질적으로 고유한 경우 equals를 재정의 하지 않아도 된다. 2. 인스턴스의 '논리적 동치성(logical equality)'을 검사할 일이 없는 경우 🌴 설계자가 논리적 동치성 방식을 원하지 않거나 애초에 필요하지 않다고 판단하는 경..

    try-finally 보다는 try-with-resources를 사용하라 - [2장 객체 생성과 파괴(아이템9)]

    전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다. 하지만 아래의 2가지 문제점으로 인해 try-with-resources의 사용이 권장된다. 복수의 자원을 처리하게 되면 코드가 지저분해진다. 스택 추적 내역에 이전의 예외에 관한 정보는 남지 않게 되어, 실제 시스템에서의 디버깅을 몹시 어렵게 한다. try-with-resources의 장점들을 통해 해당 기능을 알아보자. 장점 1. 코드를 간결하게 작성할 수 있다. try-finally의 기본구조는 아래와 같다. static String firstLineOfFile(String path) throws IOException { BufferedReader br = new BufferedReader(new FileReader(p..

    finalizer와 cleaner 사용을 피하라 - [2장 객체 생성과 파괴(아이템8)]

    자바는 finalizer와 cleaner라는 두 가지 객체 소멸자를 제공한다. 하지만 여러 가지 문제점으로 인해 사용이 자제된다. 해당 글에서 문제점들과 적절한 쓰임새에 대해서 알아보자. 📢문제점 1. finalizer와 cleaner는 즉시 수행된다는 보장이 없다. finalizer나 cleaner를 얼마나 신속히 수행할지는 전적으로 가비지 컬렉터 알고리즘에 달렸으며, 이는 가비지 컬렉터 구현마다 천차만별이다. finalizer나 cleaner 수행 시점에 의존하는 프로그램의 동작 또한 마찬가지다. 📢문제점 2. finalizer와 cleaner는 실행되지 않을 수도 있다. 이는 접근할 수 없는 일부 객체에 딸린 종료 작업을 전혀 수행하지 못한 채 프로그램이 중단될 수도 있다는 얘기다. 따라서 프로그램..

    다 쓴 객체 참조를 해제하라 - [2장 객체 생성과 파괴(아이템7)]

    자바에서는 가비지 컬렉터가 메모리 관리를 도와준다. 하지만 가비지, 컬렉터가 있다고 해도 메모리 누수가 발생하지 않는 것은 아니다. 이번 글에서는 다 쓴 객체 참조를 해제하여 메모리 누수를 방지할 수 있는 3가지 상황을 알아보고자 한다. 1. Stack에서의 메모리 누수 & 해결책 public class Stack{ private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack(){ elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e){ ensureCapacity(); el..

    불필요한 객체 생성을 피하라 - [2장 객체 생성과 파괴(아이템6)]

    해당 장에서는 불필요하게 객체 생성을 하는 3가지 경우를 말해주고 있다. 하나씩 알아보자. 1. 문자열 public static void main(String[] args){ String hello1 = "hello"; String hello2 = new String("hello"); String hello3 = "hello"; System.out.println(hello1 == hello2); // false // 다른 인스턴스 System.out.println(hello1.equals(hello2)); // true // 같은 문자열 System.out.println(hello1 == hello3); // true // 같은 인스턴스 System.out.println(hello1.equals(hell..

    자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 - [2장 객체 생성과 파괴(아이템5)]

    아이템의 제목을 통해 '모든 경우에 의존 객체 주입을 사용해야겠다'라고 이해하지 않기를 바란다. 사용하는 자원에 따라 동작이 달라지는 클래스인 경우에 '의존 객체 주입'을 고려해보도록 하자. (앞으로 나올 예시에서도 확인할 수 있듯이 사전의 종류(Dictionary)에 따라 SpellChecker라는 클래스의 동작이 달라지는 경우에 의존 객체 주입을 사용하는 것을 알 수 있다.) public class SpellChecker{ private static final Dictionary dictionary = new Dictionary(); // 이것이 제목에서 나온 자원을 직접 명시하는 예시이다. private SpellChecker(){} public static boolean isValid(String..

    인스턴스화를 막으려거든 private 생성자를 사용하라 - [2장 객체 생성과 파괴(아이템4)]

    정적 메서드만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 클래스가 아니다. 하지만 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만들어준다. 즉, 매개변수를 받지 않는 public 생성자가 만들어지며, 사용자는 이 생성자가 자동 생성된 것인지 구분할 수 없다. 그래서 이번 아이템을 통해 인스턴스 생성을 방지하는 방법을 알아보고자 한다. public abstract class UtilityClass{ // 원래는 기본생성자가 있지만, 출력을 통해 눈으로 확인하기 위해 아래와 같이 작성하였다. public UtilityClass(){ System.out.println("Contructor"): } } public class DefaultUtilityClass extends Utili..

    private 생성자나 열거 타입으로 싱글턴임을 보증하라 - [2장 객체 생성과 파괴(아이템3)]

    해당 장에서는 싱글턴을 만드는 방식을 소개하고 있다. private 생성자를 이용하는 2가지 방식과 열거 타입을 이용하는 1가지 방식, 총 3가지 방식을 소개하고 있다. 각각의 장점과 단점에 대해서 알아보자. 1. 'private 생성자 + public static final 필드'를 이용하는 방법 public class Elvis{ /** * 싱글톤 오브젝트 */ public static final Elvis INSTANCE = new Elvis(); private Elvis(){} public void leaveTheBuilding(){ System.out.println("Whoa baby, I'm outta here!"); } public void sing(){ System.out.println("..