Dev Book Review/Effective Java

[Effective Java] item13. clone 재정의는 주의해서 진행하라

1. Cloneable interface cloneable : 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스이다. 믹스인 : 클래스가 자신의 "본래 타입"에 추가하여 구현할 수 있는 타입. 선택 가능한 기능을 제공하며, 그 기능을 제공받고자 하는 클래스에서 선언한다 clone 메서드가 선언된 곳이 Cloneable이 아닌 Object이고 그마저도 protected이다. 즉, Cloneable을 구현하는 것만으로 clone 메서드를 호출 할 수 없다. 그러나 문제점에도 불구하고 Cloneable 방식이 널리쓰이고 잇어서 잘 알아두는게 좋다 Cloneable : Object의 protected 메서드인 clone의 동작방식을 결정한다. 상위 클래스(Object)에 정의된 protected 메서드..

[Effective Java] item13. clone 재정의는 주의해서 진행하라

728x90

1. Cloneable interface

cloneable : 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스이다.

믹스인 : 클래스가 자신의 "본래 타입"에 추가하여 구현할 수 있는 타입. 선택 가능한 기능을 제공하며, 그 기능을 제공받고자 하는 클래스에서 선언한다

clone 메서드가 선언된 곳이 Cloneable이 아닌 Object이고 그마저도 protected이다.

즉, Cloneable을 구현하는 것만으로 clone 메서드를 호출 할 수 없다.

그러나 문제점에도 불구하고 Cloneable 방식이 널리쓰이고 잇어서 잘 알아두는게 좋다

 

Cloneable : Object의 protected 메서드인 clone의 동작방식을 결정한다.
상위 클래스(Object)에 정의된 protected 메서드의 동작방식을 변경한 것 : 이례적

  • clone 호출 + Cloneable 구현 O : 객체의 필드들을 하나하나 복사한 객체 반환
  • clone 호출 + Cloneable 구현 X : CloneNotSupportedException

 

2. clone 메서드의 일반 규약

ㄱ. 클론객체와 원본객체의 Identificatinon 값은 다르다.

x.clone() != x

x.clone().getClass() == x.getClass()

 

ㄴ. 클론객체와 원본객체의 logical equation은 같으며 클론 객체와 원본객체의 클래스가 같다.

x.clone().equals(x)

x.clone().getClass() == x.getClass()

 

3. clone 메서드의 사용

상속에서의 주의점

생성자 연쇄(constructor chaining)과 비슷하다 : 상속으로 부모 클래스의 default 생성자를 계속 호출한다. > 마찬가지로 clone에서도 상속으로 연쇄해서 객체를 생성해야한다.

clone 메서드가 super.clone이 아닌 생성자를 호출해 얻은 인스턴스를 반환해도 컴파일러는 불평하지 않을 것이다.

clone을 재정의한 클래스가 final이면 하위 클래스가 없으니 관례를 무시해도 안전하다.

 

상위 클래스를 제대로 상속했을 때 Cloneable 구현

super.clone을 호출한다. (클래스에 정의한 모든 필드가 원본과 같다.)

불변 클래스에서는 굳이 clone 메서드를 제공하지 않는게 좋다 (기존의 super.clone으로 해결)

@Override public PhoneNumber clone(){
  try{
    return (PhoneNumber) super.clone();
  }catch(CloneNotSupportedException e){
    throw new AssertionError();	// 막기위해선 implements Cloneable
  }
}

Object의 clone은 Object를 반환하지만 PhoneNumber에서는 PhoneNumber를 반환하게 하였다.재정의한 메서드의 반환타입은 상위클래스 메서드가 반환하는 타입(Object)의 하위 타입(PhoneNumber)일 수 있다.

자바의 공변 반환 타이핑 (convariant return typing) 지원 덕분이다.

공변 반환 타이핑 : T'가 T의 subType이면, C<T'>는 C<T>의 SubType이다.

 

가변 객체에서 사용할 경우

public class Stack{
  private Object[] elements;
	private int size;
}

@Override public Stack clone(){
  try{
		Stack result = (Stack)super.clone();
    result.elements = elements.clone();
    return result
  }catch (CloneNotSupportedException e){
    throw new AssertionError():
  }
}

이렇게 복사를 하지 않는다면 복사한 객체는 elements의 참조값을 갖고있어, 원본객체를 수정하면 복사객체도 수정되는 현상이 일어날 수 있다.

clone 메서드는 사실상 생성자와 같은 효과를 낸다.
clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야한다.

 

배열의 복제

배열의 clone 메서드를 사용하자!
배열은 clone 기능을 제대로 사용하는 유일한 예이다.

 

HashTable에서의 복제 (복잡한 가변 객체의 복제)

해시테이블 내부 : 버킷들의 배열 + 각 버킷을 키-값 쌍을 담는 연결리스트의 첫 번째 엔트리 참조

1) 배열복제 -> 버킷배열을 복제하나, 버킷 배열안에잇는 연결리스트들은 모두 원본과 같은 것들이다.

2) 각 버킷을 구성하는 연결 리스트를 복사해야한다.

3-1) 연결 리스트를 복제할 때 재귀적으로 호출하는 방식을 사용한다

    스택프레임을 소비하므로 스택 오버플로를 낼 가능성이 있다.

3-2) 재귀 호출대신 반복자를 써서 순회한다.

3-3) 원본 객체의 상태를 다시 생성하는 고수준 메서드를 호출한다. put(key,value)

    저수준 API 보다는 속도가 느리다. + Cloneable 아키텍처의 기초인 필드 단위 객체 복사를 우회한다.

 

4. clone()의 상속에서의 주의점

1) Clonealbe 구현 여부를 하위 클래스에서 선택하도록 해준다.

2) clone을 동작하지 않게 구현해놓고 하위클래스에서 재정의 못하게 한다.

@Override
protected final Object clone() throws CloneNotSupportedException{
  throw new CloneNotSupportedException();
}

 

5. clone() 재정의 방법

  • Cloneable을 구현하는 모든 클래스는 clone을 재정의해야 한다.
  • 접근 제한자는 public으로
  • 반환타입은 클래스 자신으로
  • 가장 먼저 super.clone을 호출 한 후 필요한 필드를 적절히 수정한다
  • 이후 깊은 구조까지 클론한다. (보통 재귀이지만, 항상 정답은 아니다.)
  • 기본 타입 필드, 불변 객체 참조만 갖는 클래스면 아무 필드도 수정할 필요가 없다.
  • 고유 ID는 비록 기본타입, 불변이어도 수정해야한다.

 

6. clone() 재정의 대체법

ㄱ. 복사 생성자 : 단수히 자신과 같은 클래스의 인스턴스를 인수로 받는 생성자

public Yum(Yum yum){...};

ㄴ. 복사 팩터리 생성

public static Yum newInstance(Yum yum){...};

ㄷ. 복사 생성자, 팩터리의 장점

  • 위험한 객체 생성 매커니즘을 사용하지 않는다. (생성자 없이 객체 생성)
  • 문서화된 규약에 기대지 않는다
  • final 필드 용법과 충돌하지 않음
  • 불필요한 검사 예외를 던지지 않음
  • 형변환 필요가 없다.
  • 해당 클래스가 구현한 '인터페이스' 타입의 인스턴스를 인수로 받을 수 있다.

    인터페이스 기반 복사 생성자 = 변환 생성자 (conversion constructor)
    인터페이스 기반 복사 팩터리 = 변환 팩터리 (conversion factory)

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] Item12. toString을 항상 재정의하라

Object.toString() 메서드 : [클래스이름]@[16진수로 표시한 해시코드] 포맷을 갖는다. 예시 ) PhnoneNumber@adbbd 1. toString() 규약 1) 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보 2) 모든 하위 클래스에서 이 메서드를 재정의하라 => 장점 : 사용하기 편하고, 디버깅하기 쉽다. toString()은 자동으로 불린다 => 직접 호출하지 않아도 어딘가에서 쓰인다. (오류 메세지 효율적 로깅을 위해 필요!) printf(), println(), assert구문, 문자열 연결 연산자(+), 디버거가 객체를 출력할때 toString()에서 반환할 것 : 객체가 가진 주요 정보 모두 (거대하다면 요약 정보) 2. toString() 문서화 포맷 ㄱ. 반환값의 포맷..

[Effective Java] Item12. toString을 항상 재정의하라

728x90

Object.toString() 메서드 : [클래스이름]@[16진수로 표시한 해시코드] 포맷을 갖는다.
예시 ) PhnoneNumber@adbbd

 

1. toString() 규약

1) 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보
2) 모든 하위 클래스에서 이 메서드를 재정의하라
=> 장점 : 사용하기 편하고, 디버깅하기 쉽다.

toString()은 자동으로 불린다 => 직접 호출하지 않아도 어딘가에서 쓰인다. (오류 메세지 효율적 로깅을 위해 필요!)
printf(), println(), assert구문, 문자열 연결 연산자(+), 디버거가 객체를 출력할때

toString()에서 반환할 것 : 객체가 가진 주요 정보 모두 (거대하다면 요약 정보)

 

2. toString() 문서화 포맷

 

ㄱ. 반환값의 포맷을 문서화하여 명시

  • 값클래스는 문서화를 권한다 (ex: 전화번호, 행렬)
  • 명시한 포맷에 맞는 문자열과 객체를 상호 전환할 수 있는 정적 팩터리나 생성자를 함께 제공하기
      예시 ) Integer.toString(); / Integer.parseInt(string);

[장점]

  • 표준적이고, 명확하고, 사람이 읽을 수 있다.
  • 값 입출력 사용
  • CSV 같이 데이터 객체 저장도 가능

[단점]

  • 포맷을 한번 입력하면 영구적으로 해당 포맷에 얽매이게 된다.

 

ㄴ. 반환값의 포맷을 문서에 명시하지 않음

[장점]

  • 향후 릴리즈에서 정보를 더 넣거나 포맷을 개선할 수 있는 유연성을 갖는다.

 

ㄷ. 포맷 명시 여부와 상관없이 toString()이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자

  • get() 메서드 등을 구현하라는 소리인 것 같다.
  • 그렇지 않으면 프로그래머는 toString() 반환값을 파싱할 수 밖에 없다 (접근자를 제어하지 않아 포맷이 API의 역할을 한다.)

 

4. toString() 을 사용하는 경우

정적 유틸리티 클래스 / 부분 열거 타입은 toString을 재정의하지 않아도 된다.

하위 클래스들이 공유해야할 문자열 표현이 있다면 toString을 재정의한다.ex ) 컬렉션 구현체 : 추상 컬렉션 클래스의 toString() 메서드를 상속해서 사용한다.

 

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item11. equals를 재정의하려거든 hashCode도 재정의하라

equals를 재정의한 클래스 모두에서 hasCode도 재정의해야한다. 일반 규약을 어기게 되어 HashMap이나 HashSet 같은 컬렉션 원소로 사용할 때 문제를 일으킬 수 있다. 1. Object 명세의 3가지 규약 equals() 에 사용되는 정보가 변경되지 않았다 -> 항상 같은 값이어야한다. equals(object) true이다 -> 두 객체의 hashCode는 같다. equals(object) false이다 -> hasCode가 다를 필요는 없다. 논리적으로 같은 객체는 같은 해시코드를 반환해야한다. 2. hashcode 동작 방법 좋은 해시 함수라면 서로 다른 인스턴스에 다른 해시코드를 반환한다. 주어진 인스턴스들을 균일하게 분배한다. 만약 같은 값을 반환한다면, 객체가 해시테이블 버킷하..

[Effective Java] item11. equals를 재정의하려거든 hashCode도 재정의하라

728x90

equals를 재정의한 클래스 모두에서 hasCode도 재정의해야한다.
일반 규약을 어기게 되어 HashMap이나 HashSet 같은 컬렉션 원소로 사용할 때 문제를 일으킬 수 있다.

 

1. Object 명세의 3가지 규약

  • equals() 에 사용되는 정보가 변경되지 않았다 -> 항상 같은 값이어야한다.
  • equals(object) true이다 -> 두 객체의 hashCode는 같다.
  • equals(object) false이다 -> hasCode가 다를 필요는 없다.

논리적으로 같은 객체는 같은 해시코드를 반환해야한다.

 

2. hashcode 동작 방법

좋은 해시 함수라면 서로 다른 인스턴스에 다른 해시코드를 반환한다.
주어진 인스턴스들을 균일하게 분배한다.

만약 같은 값을 반환한다면, 객체가 해시테이블 버킷하나에 담기고, 그 객체들이 연결리스트처럼 동작한다.

@Override public int hashCode(){return 42;}

수행시간이 O(1)인 해시 테이블이 O(n)으로 느려진다.

 

3. 좋은 HashCode를 작성하는 방법

ㄱ. 핵심 필드를 ㄴ.a 방법으로 계산한다

int result; 를 선언한 후 값 c로 초기화하는데, 이때 c는 핵심필드를 계산한 값이다.

ㄴ. 객체의 나머지 핵심 필드에 대해 계산을 수행한다.

a. 해당 필드의 해시 코드를 계산한다.

  • 기본 타입 필드 : Type.hashCode(f) = Integer.hashCode(f);
  • 참조 타입 필드 + equals가 재귀적으로 호출 : hascode도 재귀적 호출
    복잡할 것 같으면 표준형(canoical representation)을 만들어 그 표준형의 hashCode를 호출한다.
  • 필드가 배열이면, 핵심원소 각가을 별도 필드처럼 다룬다. (Arrays.hashCode)

b. ㄴ.a에서 계산한 해시코드로 갱신한다 : result = 31 * result + c

ㄷ. result를 반환한다.

 

4. HashCode 작성시 주의 점

ㄱ. 동치인 인스턴스에 대해 똑같은 해시코드를 반환할지 자문하자

@Override public int hashCode(){
	int result = Short.hashCode(areaCode);
	result = 31 * result + Short.hashCode(prefix);
	result = 31 * result + Short.hashCode(lineNum);
	return result;
}

ㄴ. equals 비교에 사용되지 않는 필드는 계산에서 무시하자.

ㄷ. 성능을 높이기 위해 해시코드 계산시 핵심 필드를 생략해서는 안된다.

속도는 빨라지지만 해시 품질이 나빠져, 해시테이블 성능을 떨어뜨릴 수 있다.

ㄹ. hashCode가 반환하는 값의 생성규칙을 API 사용자에게 자세히 공표하지 말자

클라이언트 의존도가 낮아지고, 추후에 계산방식을 바꿀 수 있기 때문이다.

현재 String, Integer 등 여러 자바 라이브러리에서 hashCode 메서드가 반환하는 정확한 값을 알려주고있다.

 

5. Object 클래스의 hash() 메서드

장점 : hashCode 함수를 단 한줄로 작성할 수 있다.

단점 : 속도가 더느리다

  • 입력 인수를 담기위한 배열 생성
  • 기본타입이 있을 때 박싱과 언박싱

객체의 나머지 핵심 필드에 대해 수행한다.

@Override public int hashCode(){
	return Objects.hash(lineNum, prefix, areaCode);
}

 

6. 캐싱 전략

클래스가 불변 + 해시코드 계산 비용이 다 -> 새로 계산X

객체가 주로 해시의 키로 사용될 것 같을때 = 인스턴스 생성시 해시코드 계산
객체가 주로 해시의 키로 사용되지 않을 것 같을 때 = 지연 초기화 전략 (hashCode가 처음 불릴 때 계산)

private int hashCode;

@Override public int hashCode(){
  int result = hashCode;
  if(result == 0){
    int result = Short.hashCode(areaCode);
    result = 31 * result + Short.hashCode(prefix);
    result = 31 * result + Short.hashCode(lineNum);
    hashCode = result;
  }
  return result;
}

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item10. equals는 일반 규약을 지켜 재 정의하라

1. equals를 재정의 하면 안되는 경우 equals는 재정의하기 쉬워보이지만 곳곳에 함정이 있다. 문제를 회피하는 가장 쉬운 길은 아예 재정의하지 않는 것 a. 각 인스턴스가 본질적으로 고유할 때 값 표현 객체가 없을 때를 이야기 하는 것 같다. Bean에 등록해두는 객체 repository, controller, service 등이 이에 해당할 것 같다. DTO, Domain 객체는 값 검증이 필요할 수 있으니 equals를 재정의 해야할 수도 있다. b. 인스턴스의 논리적 동치성(logical equality)를 검사할 일이 없을 때 논리적 동치성 검사 예시 : Pattern의 인스턴스가 같은 정규 표현식을 나타내는 지 검사 c. 상위 클래스에서 재정의한 equals가 하위 클래스에도 들어맞을 ..

[Effective Java] item10. equals는 일반 규약을 지켜 재 정의하라

728x90

1. equals를 재정의 하면 안되는 경우

equals는 재정의하기 쉬워보이지만 곳곳에 함정이 있다. 문제를 회피하는 가장 쉬운 길은 아예 재정의하지 않는 것

a. 각 인스턴스가 본질적으로 고유할 때

값 표현 객체가 없을 때를 이야기 하는 것 같다. Bean에 등록해두는 객체 repository, controller, service 등이 이에 해당할 것 같다.
DTO, Domain 객체는 값 검증이 필요할 수 있으니 equals를 재정의 해야할 수도 있다.

b. 인스턴스의 논리적 동치성(logical equality)를 검사할 일이 없을 때

논리적 동치성 검사 예시 : Pattern의 인스턴스가 같은 정규 표현식을 나타내는 지 검사

c. 상위 클래스에서 재정의한 equals가 하위 클래스에도 들어맞을 때

같은 특징을 가지기 때문에 equals를 상속받아 사용하는 걸 권장하는 듯 하다 > 근데 이유가 뒷편에 나옴

d. 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없을 때

equals가 실수로 호출되는 걸 막고 싶다면

@Override public boolean equals (Object o){
  	throw new AssertionError();	// equals 호출 시 error();
}

 

2. equals를 재정의 해야하는 경우

객체 식별성(object identity) : X - 두 객체가 물리적으로 같은가
논리적 동치성(logical equality) : O

객체 식별성이 아니라 논리적 동치성을 확인해야하는데, 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의 되지 않았을 때. (주로 값 클래스)

public class Fruit{
  private String name; // name이 같을 경우 두 객체는 같다 (논리적 동치성)
}

 

2-1. 값 클래스의 equals를 재정의 할 때 기대 효과

  • 값을 비교하길 원하는 프로그래머의 기대에 부흥
  • Map, Set의 원소로 사용가능

 

2-2. 값 클래스여도 equals를 재정의 할 필요가 없을 때

인스턴스 통제 클래스 : 값이 같은 인스턴스가 2개 이상 만들어 지지 않음 (ex Static Factory Method Pattern, Enum)
어차피 논리적으로 같은 값이 0개 혹은 1개 이기 때문에 객체 식별성으로 판단한다. (기본 값인 Object의 equals가 확인해 줌)

 

3. Equals 메서드의 규약 - 동치관계

동치 클래스 (equivalence class) : 집합을 서로 같은 원소들로 이루어진 부분집합으로 나누는 연산
equals 메서드가 쓸모 있으려면 모든 원소가 같은 동치류에 속한 어떤 원소와도 서로 교환이 가능해야한다.

 

3-1. 반사성(reflexivity)

null이 아닌 모든 참조 값 x에 대해 x.equals(y)는 true이다.

객체는 자기 자신과 같아야한다.

public class Fruit{
  private String name;
  
  public Fruit(String name){
    this.name = name;
  }
  
  public static void main(){
    List<Fruit> list = new ArrayList<>();
    Fruit f = new Fruit("apple");
      list.add(f);
      list.contains(f); // false일 경우에는 반사성을 만족하지 못하는 경우이다.
    }
}

 

3-2. 대칭성(symmetry)

null이 아닌 모든 참조 값 x, y에 대해 x.equals(y)가 true이면 y.equals(x)도 true이다.

두 객체는 서로에 대한 동치 여부에 똑같이 답해야 한다.

equals 규약을 어기면 그 객체를 사용하는 다른 객체들이 어떻게 반응할지 알 수 없다.

[ 대칭성을 위반한 클래스 ]

public final class CaseInsensitiveString{
  private final String s;
  
  public CaseInsensitiveString(String s){
    this.s = Obejcts.requireNonNull(s);
  }
  
  @Override public boolean equals(Object o){
    if(o instanceof CaseInsensitiveString)
      return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
    if(o instanceof String) // 한방향으로만 작동한다.
      return s.equalsIgnoreCase((String) o);
    return false;
  }
}

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";

cis.equals(s); // true
s.equals(cis); // false

[ 대칭성을 만족하게 수정 ]

@Override public boolean equals(Object o){
  return o instanceof CaseInsensitiveString &&
    ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); 
  // String에 대한 instanceof 부분을 빼고 구현한다.
}

 

3-3. 추이성(trasitivity)

null이 아닌 모든 참조 값 x, y, z에 대해 x.equals(y)가 true이고 y.equals(z)도 true이면, x.equals(z)도 true이다.

첫번째 객체와 두번째 객체가 같고, 두번째 객체와 세번째 객체가 같다면 첫번째 객체와 세번째 객체도 같아야 한다. (삼단논법)

이 추이성 조건때문에, equals를 재 정의 하면 안되는 경우에 superclass에서 equals를 정의한 경우를 언급함

 

문제1. 대칭성 위배 문제에 빠질 수 있다.

// ColorPoint.java 의 equals
@Override public boolean equals(Object o){
  if(!(o instanceof ColorPoint))
    return false;
  return super.equals(o) && ((ColorPoint) o).color == color;
}

public static void main(){
  Point p = new Point(1,2);
  ColorPoint cp = new ColorPoint(1,2, Color.RED);
  p.equals(cp);	// true (Point의 equals로 계산)
  cp.equals(p);	// false (ColorPoint의 equals로 계산: color 필드 부분에서 false)
}

 

문제2. 추이성 위배 문제에 빠질 수 있다.

//ColorPoint.java의 equals
@Override public boolean equals(Obejct o){
  if(!(o instanceof Point))
    return false;
  if(!(o instanceof ColorPoint))
    return o.equals(this);
  return super.equals(o) && ((ColorPoint) o).color == color;
}

public static void main(){
  ColorPoint p1 = new ColorPoint(1,2, Color.RED);
  Point p2 = new Point(1,2);
  ColorPoint p3 = new ColorPoint(1,2, Color.BLUE);
  p1.equals(p2);	// true (ColorPoint의 equals 비교 //2번째 if문에서 Point의 equals로 변환)
  p2.equals(p3);	// true (Point의 equals 비교 // x,y 같으니 true)
  p1.equals(p3);	// false (ColorPoint의 equals 비교)
}

 

문제3. 무한 재귀에 빠질 수 있다.

//SmellPoint.java의 equals
@Override public boolean equals(Obejct o){
  if(!(o instanceof Point))
    return false;
  if(!(o instanceof SmellPoint))
    return o.equals(this);
  return super.equals(o) && ((SmellPoint) o).color == color;
}

public static void main(){
  ColorPoint p1 = new ColorPoint(1,2, Color.RED);
  SmellPoint p2 = new SmellPoint(1,2);
  p1.equals(p2);
  // 처음에 ColorPoint의 equals로 비교 : 2번째 if문 때문에 SmellPoint의 equals로 비교
  // 이후 SmellPoint의 equals로 비교 : 2번째 if문 때문에 ColorPoint의 equals로 비교
  // 무한 재귀의 상태!
}

구체 클래스를 확장해 새로운 값을 추가하면서 equals 규약을 만족시킬 방법은 존재하지 않는다.

그렇다고 instanceof 검사 대신 getClass 검사를 하라는 것은 아니다.

@Override public boolean equals(Object o){
  if(o == null || o.getClass() != getClass())
    return false;
  Point p = (Point) o;
  return p.x == x && p.y == y;
}

리스코프 치환원칙을 위배한다 : Point의 하위클래스는 정의상 여전히 Point이기 때문에 어디서든 Point로 활용되어야한다.

리스코프 치환원칙 (Liskov substitution principle) : 어떤 타입에 있어 중요한 속성이라면 그 하위 타입에서도 마찬가지로 중요하다. 따라서 그 타입의 모든 메서드가 하위 타입에서도 똑같이 잘 작동해야한다.

Point의 하위클래스는 정의상 여전히 Point이므로 어디서든 Point로 활용가능해야한다.

 

우회 방법1. 상속대신 컴포지션을 사용하라

public class ColorPoint{
  private final Point point;
  private final Color color;
 
  public Point asPoint(){ //view 메서드 패턴
    return point
  }
  
  @Override public boolean equals(Object o){
    if(!(o instanceof ColorPoint)){
      return false;
    }
    ColorPoint cp = (ColorPoin) o;
    return cp.point.equals(point) && cp.color.equals(color);
  }
}
  • ColorPoint - ColorPoint : ColorPoint의 equals를 이용하여 color 값까지 모두 비교
  • ColorPoint - Point : ColorPoint를 asPoint() 메소드를 이용해 Point로 바꾸어서, Point의 equlas를 이용해 x,y만 비교
  • Point - Point : Point의 equals를 이용하여 x,y 값 모두 비교

 

우회 방법2. 추상클래스의 하위클래스 사용하기

추상클래스의 하위클래스에서는 equals 규약을 지키면서도 값을 추가할 수 있다.

상위 클래스를 직접 인스턴스로 만드는게 불가능하기 때문에 하위클래스끼리의 비교가 가능해진다.

 

3-4. 일관성(consistency)

null이 아닌 모든 참조 값 x, y에 대해 x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.

두 객체가 같다면 (어느 하나 혹은 두 객체 모두가 수정되지 않는한) 앞으로도 영원히 같아야한다.
가변객체 = 비교 시점에 따라 서로 다를 수 있다.불변객체 = 한번 다르면 끝까지 달라야한다.

equals는 항시 메모리에 존재하는 객체만을 사용한 결정적(deterministic) 계산만 수행해야한다.
클래스가 불변이든 가변이든 equals의 판단에 신뢰할 수 없는 자원이 끼어들면 안된다 : ex URL과 매핑된 호스트의 IP 주소

 

3-5. null-아님

null이 아닌 모든 참조값 x에 대해, x.equals(null)은 false이다.

모든 객체가 null 과 같지 않아야한다.

1) 명시적 null 검사

@Override public boolean equals(Object o){
  if( o == null){
    return false;
  }
}

2) 묵시적 null 검사

@Override public boolean equals(Obejct o){
  if(!(o instanceof MyType)) 	// instanceof 자체가 타입과 무관하게 null이면 false 반환함.
    return false;
  MyType mt = (MyType) o;
}

 

4. 양질의 equals 메서드를 구현하는 4단계

4-1. == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.

object identity를 검사한다.

 

4-2. instanceof 연산자로 입력이 올바른 타입인지 확인한다.

  • 올바른 타입의 경우 : equals가 정의된 클래스로 리턴이 되는가
  • 올바른 타입이 아닌경우 ( == 구현한 특정 인터페이스) :구현한 (서로다른) 클래스간 비교가 가능하게 해야함
    : 이런 구현체들은 인터페이스의 equals를 이용하여 비교해야한다.

 

4-3. 입력을 올바른 타입으로 형변환 한다.

2번에서 instatnceof를 이용했기 때문에 100% 성공함

 

4-4. 입력 객체와 자기 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사한다.

2번에서 인터페이스를 사용했다면 입력의 필드값을 가져올 때도 인터페이스 메서드를 사용해야한다.

 

5. Equals 구현할 때 주의할 추가사항

5-1. 기본타입과 참조타입

  • 기본타입 : == 연산자 비교
  • 참조타입 : equals() 비교
  • 배열 필드 : 원소 각각을 지침대로 비교한다. / 모두가 핵심 필드라면 Arrays.equals()를 사용한다.

float, double 필드 : Float.compare(float, float), Double.compare(double, double) 비교 (부동 소수 값)
Float.equals(float)나 Double.equals(double) 은 오토 박싱을 수반할 수 있어 성능상 좋지 않다.

 

5-2. null 정상값 취급 방지

Object.equals(object, object) 로 비교하여 NullPointException 발생을 예방하자

 

5-3. 필드의 표준형을 저장하자.

비교하기 복잡한 필드는 필드의 표준형(canonical form)을 저장한후 비교 : 불변 클래스에 제격이다.

 

5-4. 필드 비교 순서는 equals 성능을 좌우한다.

  • 다를 가능성이 높은 필드 우선
  • 비교 비용이 싼 필드 우선
  • 핵심필드 / 파생필드 구분하자.

 

5-5. equals 재정의할 땐 hashCode도 반드시 재정의하자.

 

5-6. 너무 복잡하게 해결하려 들지 말자.

 

5-7. Object 외의 타입을 매개변수로 받는 equals 메서드는 선언하지 말자

public boolean equals(MyClass o) // @ㅒverrider가 아니다! // 재정의가 아니라 다중정의 상태임!

 

5-8. AutoValue 프레임워크

Lombok이랑 비슷한 느낌의 라이브러리 : https://www.baeldung.com/introduction-to-autovalue

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] Chapter 2 Summary : 객체 생성과 파괴

Item1. 생성자 대신 정적 팩터리 메서드를 고려하라 정적 팩터리 메서드와 public 생성자는 각각의 쓰임새가 있다. 그래도 정적 팩터리 사용이 유리한 경우가 더 많다. 무작정 public 생성자를 제공하던 습관을 고치자 Link : https://jyami.tistory.com/56 item1. 생성자 대신 정적 팩터리 메서드를 고려하라 1. 정적 팩터리 메서드의 장점 ㄱ. 이름을 가질 수 있다. 생성자정적 팩터리 메서드 시그니처가 같은 생성자가 여러개 필요할 것 같을때 정적 팩터리 메서드를 사용하자 생성자 정적 팩터리 메서드 특징설명 X O.. jyami.tistory.com Item2. 생성자에 매개변수가 많다면 빌더를 고려하라 생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 ..

[Effective Java] Chapter 2 Summary : 객체 생성과 파괴

728x90

Item1. 생성자 대신 정적 팩터리 메서드를 고려하라

  • 정적 팩터리 메서드와 public 생성자는 각각의 쓰임새가 있다.
  • 그래도 정적 팩터리 사용이 유리한 경우가 더 많다.
  • 무작정 public 생성자를 제공하던 습관을 고치자
  • Link : https://jyami.tistory.com/56
 

item1. 생성자 대신 정적 팩터리 메서드를 고려하라

1. 정적 팩터리 메서드의 장점 ㄱ. 이름을 가질 수 있다. 생성자정적 팩터리 메서드 시그니처가 같은 생성자가 여러개 필요할 것 같을때 정적 팩터리 메서드를 사용하자 생성자 정적 팩터리 메서드 특징설명 X O..

jyami.tistory.com

 

Item2. 생성자에 매개변수가 많다면 빌더를 고려하라

  • 생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다.
  • 매개변수중 다수가 필수가 아니거나 같은 타입이면 특히 더 그렇다.
  • 빌더는 점층적 생성자보다 클라이언트를 읽고 쓰기가 훨씬 간결하다.
  • 빌더는 자바빈즈보다 훨씬 안전하다.
  • Link : https://jyami.tistory.com/57
 

item2. 생성자에 매개변수가 많다면 빌더를 고려해라

정적 팩터리 + 생성자의 제약 = 선택적 매개변수가 많을 때 적절히 대응하기 어렵다 1. 대안A) 점층적 생성자 패턴 (telescoping constructor pattern) 필수 매개변수 + 선택 매개변수 원하는 매개변수를 모두 포..

jyami.tistory.com

 

Item3. private 생성자나 열거 타입으로 싱글턴임을 보증하라

  • 싱글턴 : 인스턴스를 오직 하나만 생성할 수 있는 클래스이다.
  • public static 멤버가 final 필드 / 정적 팩터리 메서드를 public static 멤버로 제공 / Enum 타입 방식 으로 싱글턴을 만들 수 있다.
  • 대부분 상황에서 원소가 하나뿐인 Enum이 싱글턴을 만드는 가장 좋은 방법이다.
  • Link : https://jyami.tistory.com/58
 

item3. private 생성자나 열거 타입으로 싱글턴임을 보증하라

1. 싱글턴이란? 싱글턴(singleton) : 인스턴스를 오직 하나만 생성할 수 있는 클래스 함수와 같은 무상태(stateless) 객체 - 정적 멤버클래스 이야기인가? (Enum 같은거?) 설계상 유일해야하는 시스템 컴포넌트 클..

jyami.tistory.com

 

Item4. 인스턴스화를 막으려거든 private 생성자를 사용하라

  • 정적 메서드와 정적 필드만을 담는 클래스에서 인스턴스 화를 막을 때 private 생성자를 사용하라
  • Private 생성자를 쓰지 않으면 컴파일러가 자동으로 기본 생성자를 만들어 인스턴스화가 가능해진다.
  • Link : https://jyami.tistory.com/59
 

item4. 인스턴스화를 막으려거든 private 생성자를 사용하라

1. 정적 메서드와 정적 필드만을 담을 클래스의 쓰임새 기본타입 값이나 배열 관련 메서드를 모아 둘 때 (java.lang.Math, java.util.Arrays) 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(팩터리)..

jyami.tistory.com

 

Item5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

  • 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.
  • 이 자원들을 클래스가 직접 만들게 해서도 안된다.
  • 필요한 자원(그 자원을 만들어주는 팩터리를) 생성자에 (정적 팩터리나 빌더에) 넘겨주자. (의존 객체 주입)
  • 클래스 유연성, 재사용성, 테스트 용이성을 개선해준다.
  • Link : https://jyami.tistory.com/60
 

item5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다. 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식을 사용하자 의존 객체 주입의 형태!! // 정..

jyami.tistory.com

 

Item6. 불필요한 객체 생성을 피하라

  • 같은 기능의 객체를 매번 생성하기보다 불변객체나, 정적 팩터리를 이용해 객체 하나를 재사용하라
  • 생성비용이 비싼 객체가 반복해서 필요하다면 캐싱하여 재사용길 권한다.
  • 어댑터 패턴에서 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다.
  • 박싱된 기본타입보단 기본타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자
  • 가벼운 객체를 다룰땐 직접 만든 객체 풀보단 GC가 훨씬 빠르다.
  • Link : https://jyami.tistory.com/61
 

item6. 불필요한 객체 생성을 피하라

1. 객체 재사용 똑같은 기능의 객체를 매번 사용하기 보다는 객체 하나를 재사용하는 편이 나을 때가 많다. 불변 객체는 언제든 재사용가능하다. String s = new String("hello"); // Heap 영역에 존재 String s =..

jyami.tistory.com

 

Item7. 다 쓴 객체는 참조를 해제하라

  • 메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다.
  • 이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다.
  • 예방법을 알아두자 ex) LRU에서 WeakHashMap 사용
  • Link : https://jyami.tistory.com/62
 

item7. 다 쓴 객체 참조를 해제하라

1. GC의 메모리 누수 : null로 해제 GC가 다쓴객체를 알아서 회수해간다고, 메모리 관리에 아예 신경을 안쓰면 안된다. // 코드 7-1 메모리 누수가 일어나는 위치는 어디인가? (36쪽) public class Stack { privat..

jyami.tistory.com

 

Item8. finalizer와 cleaner 사용을 피해라

  • cleaner(자바 8까지는 finalizer)는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자.
  • 이런 경우에 불확실성과 성능저하에 주의해야한다.
  • Link : https://jyami.tistory.com/63
 

item8. finalizer와 cleaner 사용을 피해라

1. 자바의 객체 소멸자 finailzer : 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다. 오동작, 낮은 성능, 이식성 문제의 원인 "쓰지말자" cleaner : finailzer보단 덜 위험하지만, 여전히 예..

jyami.tistory.com

 

Item9. try-finally 보다는 try-with-resources를 사용하라

  • try-finally는 코드 가독성에 있어서 지저분하고, 두번째 예외가 첫번째 예외를 집어삼키는 경우가 있을 수 있다.
  • try-with-resources는 일기 쉬워 문제 진단에 유리하며, 숨겨진 예외도 suppressed 꼬리표를 달고 출력된다.
  • try-with-resources는 catch를 이용해 try 중첩없이 다수의 예외처리가 가능하다.
  • Link : https://jyami.tistory.com/64
 

item9. try-finally 보다는 try-with-resources를 사용하라

close()를 사용해 직접 닫아줘야하는 자바 자원 : InputStream OutputStream, java.sql.Connection 1. 자원이 닫힘을 보장하는 수단 try-finally의 단점 코드 가독성에있어서 지저분하다. 두번째 예외가 첫번째 예..

jyami.tistory.com

 

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item9. try-finally 보다는 try-with-resources를 사용하라

close()를 사용해 직접 닫아줘야하는 자바 자원 : InputStream OutputStream, java.sql.Connection 1. 자원이 닫힘을 보장하는 수단 try-finally의 단점 코드 가독성에있어서 지저분하다. 두번째 예외가 첫번째 예외를 집어삼켜버려 실제 시스템에서 디버깅을 어렵게 한다. static void copy(String src, String dst) throws IOException{ InputStream in = new FileInputStream(src); try{ OutputStream out = new FileOutputStream(dst); try{ byte[] buf = new byte[BUFFER_SIZE]; int n; while ((n = in.read(..

[Effective Java] item9. try-finally 보다는 try-with-resources를 사용하라

728x90

close()를 사용해 직접 닫아줘야하는 자바 자원 : InputStream OutputStream, java.sql.Connection

 

1. 자원이 닫힘을 보장하는 수단 try-finally의 단점

  • 코드 가독성에있어서 지저분하다.
  • 두번째 예외가 첫번째 예외를 집어삼켜버려 실제 시스템에서 디버깅을 어렵게 한다.
static void copy(String src, String dst) throws IOException{
  InputStream in = new FileInputStream(src);
  try{
    OutputStream out = new FileOutputStream(dst);
    try{
      byte[] buf = new byte[BUFFER_SIZE];
      int n;
      while ((n = in.read(buf)) >= 0)
        out.write(buf, 0, n);
    }finally{
      out.close();
    }
  }finally{
    in.close();
  }
}

firstLineOfFile 메서드안의 readLine 메서드가 예외 > close 메서드 실패 > 두번째 예외가 첫번째 예외를 집어삼킨다.

 

2. 자원 회수의 최선책 try-with-resources

AutoCloseable 인터페이스 구현 : close() 메서드 하나만 정의하였다.

static void copy(String src, String dst) throws IOException {
  try (InputStream   in = new FileInputStream(src);
       OutputStream out = new FileOutputStream(dst)) {
    byte[] buf = new byte[BUFFER_SIZE];
    int n;
    while ((n = in.read(buf)) >= 0)
      out.write(buf, 0, n);
  } catch (IOException e) {
    return defaultVal;
  }
}
  • 읽기 쉽고 문제 진단에 유리하다.
  • catch를 이용해 try문을 중첩하지 않고도 다수의 예외 처리가 가능하다.
  • 숨겨진 예외도 버려지지 않고, suppressed 꼬리표를 달고 출력된다.

readLine()과 close() 호출 양쪽에서 예외가 발생하면, close() 예외는 숨겨지고 readLine()에서 발생한 예외가 기록된다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item8. finalizer와 cleaner 사용을 피해라

1. 자바의 객체 소멸자 finailzer : 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다. 오동작, 낮은 성능, 이식성 문제의 원인 "쓰지말자" cleaner : finailzer보단 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다. C++의 파괴자 try-with-resources, try-finally 2. finalizer와 cleaner 사용을 피해야하는 이유 a. 즉시 수행된다는 보장이 없다. 객체에 접근할 수 없게 된 이후부터 실행되기까지 얼마나 걸릴지 알 수 없다.= 제때 실행되어야하는 작업은 절대 할 수 없다. 수행 시점이 전적으로 GC 알고리즘에 달려있으며, 구현 방식에 따라 천차만별이다. finalizer 쓰레드는 다른 애플리케이션보다 우..

[Effective Java] item8. finalizer와 cleaner 사용을 피해라

728x90

1. 자바의 객체 소멸자

finailzer : 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다. 오동작, 낮은 성능, 이식성 문제의 원인 "쓰지말자"

cleaner : finailzer보단 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.

C++의 파괴자 <-> try-with-resources, try-finally

 

2. finalizer와 cleaner 사용을 피해야하는 이유

a. 즉시 수행된다는 보장이 없다.

객체에 접근할 수 없게 된 이후부터 실행되기까지 얼마나 걸릴지 알 수 없다.= 제때 실행되어야하는 작업은 절대 할 수 없다.

수행 시점이 전적으로 GC 알고리즘에 달려있으며, 구현 방식에 따라 천차만별이다.

finalizer 쓰레드는 다른 애플리케이션보다 우선순위가 낮다.

 

b. 수행 여부도 보장하지 않는다.

접근할 수 없는 객체에 딸린 종료작업을 수행하지 못한채 프로그램이 중단될 수도 있다.

상태를 영구적으로 수정하는 작업에서 절대 finalizer나 cleaner에 의존해서는 안된다.

System.gc, System.runFinalization 메서드에 현혹되지 말자 : 실행 가능성은 높여주나 보장해주진않는다.

System.runFinalizerOnExit / Runtime.runFinalizerOnExit : 심각한 결함

 

c. finalizer 동작중 발생한 예외가 무시되며, 처리할 작업이 남았어도 그 순간 종료된다.

잡지못한 예외로 객체가 마무리가 덜된 상태로 남아있을 수 있다 > 훼손된 객체를 사용하려 할 때 예측할 수 없다.

 

d. finalizer와 cleaner는 심각한 성능문제도 동반한다.

AutoCloseable 객체를 생성해 GC 수거시간 : 12ns
finalizer 수거시간 : 550ns -> GC의 효율을 떨어뜨린다.

 

e. finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수도 있다.

생성자나 직렬화 과정에서 예외가 발생하면 생성되다 만 객체에서 악위적인 하위 클래스의 finalizer가 수행될 수 있다.
일그러진 객체 -> 애초에 허용되지 않았을 작업 수행 가능

객체 생성을 막으려면 생성자에서 예외를 던지면 되는데, finalizer가 있으면 그렇지 않다.

final이 아닌 클래스를 finalizer 공격으로부터 방어하는 방법아무일도 하지 않는 finalize 메서드를 만들고 final로 선언 (하위클래스가 안만들어짐)

 

3. finalizer, cleaner의 대안

AutoCloseable을 구현하고, 클라이언트에서 인스턴스를 다 쓰면 close 메서드를 호출한다.예외가 발생해도 잘 종료 되도록 try-with-resources를 사용한다

 

4. finalizer, cleaner의 쓰임새

a. 자원의 소유자가 close() 메서드를 호출하지 않는 것에 대비한 안전망 역할

안전망 역할의 finalizer를 작성할때 그럴만한 값어치가 있는지 고민하자

자바라이브러리 : FileInputStream, FileOutputStream, ThreadPoolExecutor

 

b. 네이티브 피어(native peer)와 연결된 객체

네이티브 피어 = 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체자바 객체가 아니라 GC가 존재를 알지 못한다.

성능 저하를 감당할 수 있고, 네이티브 피어가 심각한 자원을 갖고있지 않을 경우에 적당하다.즉시 회수해야한다면 .close()를 사용하자

 

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item7. 다 쓴 객체 참조를 해제하라

1. GC의 메모리 누수 : null로 해제 GC가 다쓴객체를 알아서 회수해간다고, 메모리 관리에 아예 신경을 안쓰면 안된다. // 코드 7-1 메모리 누수가 일어나는 위치는 어디인가? (36쪽) 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(); elements[size++] = e; } public Object pop() { i..

[Effective Java] item7. 다 쓴 객체 참조를 해제하라

728x90

1. GC의 메모리 누수 : null로 해제

GC가 다쓴객체를 알아서 회수해간다고, 메모리 관리에 아예 신경을 안쓰면 안된다.

// 코드 7-1 메모리 누수가 일어나는 위치는 어디인가? (36쪽)
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();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    /**
     * 원소를 위한 공간을 적어도 하나 이상 확보한다.
     * 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }

//    // 코드 7-2 제대로 구현한 pop 메서드 (37쪽)
//    public Object pop() {
//        if (size == 0)
//            throw new EmptyStackException();
//        Object result = elements[--size];
//        elements[size] = null; // 다 쓴 참조 해제
//        return result;
//    }

    public static void main(String[] args) {
        Stack stack = new Stack();
        for (String arg : args)
            stack.push(arg);

        while (true)
            System.err.println(stack.pop());
    }
}

메모리 누수 문제 = 가비지 컬렉션 활동과 메모리 사용량이 늘어나 성능이 저하된다.

스택에서 pop()이 된 객체를 GC가 회수하지 않는다. 프로그램에서 그 객체를 더이상 사용하지 않아도! (다 쓴 참조를 여전히 갖고있어서)

GC의 메모리 누수 : 객체 참조 하나를 살두면, 이 객체뿐 아니라, 이 객체가 참조하는 모든 객체를 회수하지 못한다.
solution : 해당 참조를 다 썼을 때 null 처리한다.

 

2. Null 처리는 예외적이다.

객체 참조를 Null 처리하는 일은 예외적인 경우여야한다.참조를 담은 변수를 유효 범위(scope) 밖으로 밀어낸다.

  • 스택은 자기 메모리를 직접 관리한다.
  • 배열의 활성영역에 속한 원소만 사용되고 비활성 영역은 쓰이지 않는다. (GC가 알 방법이 없다.)
  • 비활성 영역이 되는 순간 Null 처리로 GC에게 알릴 때 사용한다.

 

3. 메모리 누수의 주범

a. 자기 메모리를 직접 관리하는 클래스

프로그래머가 항시 메모리 누수에 주의한다. (ex stack)

 

b. 캐시

객체 참조를 캐시에 넣고, 객체를 다 쓴 후에도 놔누는 경우

해결책

  • WeakHashMap을 사용해 캐시를 만든다. -> 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시
  • 시간이 지날수록 엔트리 가치를 떨어뜨리는 방식 ->ScheduledThreadPoolExecutor (쓰지 않는 엔트리 청소)

 

c. 리스너(listener), 콜백(callback)

클라이언트가 콜백을 등록만하고 해지하지 않는경우, 계속 쌓인다.

해결책

  • 콜백을 약한 참조(weak reference)로 저장한다. -> WeakHashMaP에 키로 저장한다.

 

4. 참고자료

https://d2.naver.com/helloworld/329631

java.lang.ref 패키지의 객체 참조 종류 4가지

  • strong reference
  • soft reference
  • weak reference
  • phantom reference

LRU(least recently used) 캐시 같은 애플리케이션 쉽게 작성가능

 

GC의 reachability

reachable : 어떤 객체에 유효한 참조가 있다. (root set : 유효한 최초의 참조)

unreachable : 어떤 객체에 유효한 참조가 없다. (GC 대상)

 

soft, weak, phantom reference

- reference Object

SoftReference, WeakReference, PhantomReference 3가지 클래스에서 생성된 객체

 

- WeakReference, Weak Reachable

WeakReference<Sample> wr = new WeakReference<Sample>(new Sample());
Sample ex = wr.get(); // 참조되었다.
ex = null; // weakly reachable 객체

GC 동작마다 회수된다. LRU 캐시 구성시 주로 WeakReference를 사용한다.

WeakReferencerence 객체내의 weakly reachable 객체에 대한 참조가 null로 설정되면, GC에 의해 메모리 회수

 

- SoftReference, Softly Reachable

Strong reachable이 아니면서 SoftReference 객체로만 참도된 객체자주 사용될수록 더 오래 살아남는다.

(마지막 strong reference가 GC된 때로부터 지금까지의 시간) > (옵션 설정 값 N) * (힙에 남아있는 메모리 크기)

SoftReferencerence 객체내의 softly reachable 객체에 대한 참조가 null로 설정되면, GC에 의해 메모리 회수

 

- PhantomReference, Phantomly Reachable

GC 대상 객체를 처리하는 작업 (= 객체의 파이널 라이즈 작업) 이후에 GC 알고리즘 따라 메모리 회수한다.

Softly Weakly : GC 대상 여부를 판별Phantomly : 파이널라이즈 이후 작업을 수행하게 한다. Phatom reference를 reference queue에 넣음

파이널라이즈 이후 처리해야하는 리소스 정리 등 작업에서 유용하게 사용할 수 있다. (거의 사용사지 않는다.)

 

Reference Queue

WeakHashMap 클래스 : Reference Queue + WeakReference 를 사용해 구현되어있다.

Phantom Reference는 참조된 객체를 phantomly reachable로 만들고 eunque한다.

 ReferenceQueue<Object> rq = new ReferenceQueue<Object>(); 
 PhantomReference<Object> pr = new PhantomReference<Object>(referent, rq);

댓글

Comments