<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>반갑습니다!</title>
    <link>https://dev-mb.tistory.com/</link>
    <description>공부한 내용들을 정리하고 있습니다.  
질문이나 피드백은 언제든 환영합니다!</description>
    <language>ko</language>
    <pubDate>Sat, 6 Jun 2026 05:05:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>김덜덜이</managingEditor>
    <item>
      <title>[백준] 11723 집합</title>
      <link>https://dev-mb.tistory.com/308</link>
      <description>&lt;figure id=&quot;og_1630592533248&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;11723번: 집합&quot; data-og-description=&quot;첫째 줄에 수행해야 하는 연산의 수 M (1 &amp;le; M &amp;le; 3,000,000)이 주어진다. 둘째 줄부터 M개의 줄에 수행해야 하는 연산이 한 줄에 하나씩 주어진다.&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://boj.kr/11723&quot; data-og-url=&quot;https://www.acmicpc.net/problem/11723&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/boCUSu/hyLt2KvAnz/iPYlvAV96AvOVd8lOvBdr0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://boj.kr/11723&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://boj.kr/11723&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/boCUSu/hyLt2KvAnz/iPYlvAV96AvOVd8lOvBdr0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;11723번: 집합&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 수행해야 하는 연산의 수 M (1 &amp;le; M &amp;le; 3,000,000)이 주어진다. 둘째 줄부터 M개의 줄에 수행해야 하는 연산이 한 줄에 하나씩 주어진다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 초과를 피하기 위해 이것저것 다양한 시도를 해봤던 문제이다. 비트 연산으로 바로 해결할 수 있을 줄 알았는데, &lt;code&gt;BufferedReader&lt;/code&gt;, &lt;code&gt;BufferedWriter&lt;/code&gt;까지 사용해야 정답처리 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.InputStreamReader
import java.io.OutputStreamWriter

fun main() {
    var s = 0
    val br = BufferedReader(InputStreamReader(System.`in`))
    val bw = BufferedWriter(OutputStreamWriter(System.out))
    repeat(br.readLine().toInt()) {
        val cmd = br.readLine().split(&quot; &quot;)
        when {
            cmd[0] == &quot;add&quot; -&amp;gt; s = s or (1 shl cmd[1].toInt())
            cmd[0] == &quot;remove&quot; -&amp;gt; s = s and (-1 xor (1 shl cmd[1].toInt()))
            cmd[0] == &quot;check&quot; -&amp;gt; bw.write(&quot;${if (s and (1 shl cmd[1].toInt()) != 0) 1 else 0}\n&quot;)
            cmd[0] == &quot;toggle&quot; -&amp;gt; s = s xor (1 shl cmd[1].toInt())
            cmd[0] == &quot;all&quot; -&amp;gt; s = -1
            cmd[0] == &quot;empty&quot; -&amp;gt; s = 0
        }
    }
    bw.flush()
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘 문제 풀이</category>
      <category>백준</category>
      <category>비트연산</category>
      <category>알고리즘</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/308</guid>
      <comments>https://dev-mb.tistory.com/308#entry308comment</comments>
      <pubDate>Thu, 2 Sep 2021 23:12:27 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Java] 아이템 12: toString을 항상 재정의하라</title>
      <link>https://dev-mb.tistory.com/307</link>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;toString&lt;/code&gt; 의 일반 규약&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;#39;&amp;#39;간결하면서 사람이 읽기 쉬운 형태의 유익한 정보&amp;#39;를 반환해야함&lt;/li&gt;
&lt;li&gt;모든 하위 클래스에서 이 메서드를 재정의할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;toString&lt;/code&gt; 을 잘 구현한 클래스를 사용한 시스템은 디버깅하기 쉬움&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;toString&lt;/code&gt; 메서드는 객체를 &lt;code&gt;println&lt;/code&gt;, &lt;code&gt;printf&lt;/code&gt;, 문자열 연결 연산자(+), &lt;code&gt;assert&lt;/code&gt; 구문에 넘길 때, 또는 디버거가 객체를 출력할 때 자동으로 호출됨&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;toString&lt;/code&gt; 을 제대로 정의하지 않으면 쓸모없는 메시지만 로그에 남음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;toString&lt;/code&gt; 은 그 객체가 가진 주요 정보를 모두 반환하는게 좋음&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;toString을 구현하는 방법&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;toString&lt;/code&gt; 을 구현할 때면 반환값의 포맷을 문서화할지 정해야함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;값 클래스라면 문서화하기를 권장&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ex) 전화번호 클래스, 행렬 클래스 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;포맷을 명시하면 그 객체는 표준적이고, 명확하고, 사람이 읽을 수 있게 됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그 값 그대로 입출력에 사용하거나 CSV 파일처럼 사람이 읽을 수 있는 데이터 객체로 저장할 수 있음&lt;/li&gt;
&lt;li&gt;명시한 포맷에 맞는 문자열과 객체를 상호 전환할 수 있는 정적 팩터리나 생성자를 함께 제공하는 것이 좋음&lt;ul&gt;
&lt;li&gt;ex) &lt;code&gt;BigInteger&lt;/code&gt;, &lt;code&gt;BigDecimal&lt;/code&gt;, 대부분의 기본 타입 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;포맷을 한번 명시하면 (그 클래스가 많이 쓰인다면) 평생 그 포맷에 얽매이게 됨&lt;/li&gt;
&lt;li&gt;포맷을 명시하지 않으면 향후 릴리스에서 정보를 더 넣거나 포맷을 개선할 수 있는 유연성을 얻게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;포맷을 명시하던 명시하지 않던 의도를 명확히 밝혀야 함&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;포맷을 명시하는 경우&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/**
 * 이 전화번호의 문자열 표현을 반환한다.
 * 이 문자열은 &amp;quot;XXX-YYY-ZZZZ&amp;quot; 형태의 12글자로 구성된다.
 * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다.
 * 각각의 대문자는 10진수 숫자 하나를 나타낸다.
 * 
 * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면,
 * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면
 * 전화번호의 마지막 네 문자는 &amp;quot;0123&amp;quot;이 된다.
 */
@Override
public String toString() {
    return String.format(&amp;quot;%03d-%03d-%04d&amp;quot;, areaCode, prefix, lineNum);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;포맷을 명시하지 않는 경우&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/**
 * 이 약물에 관한 대략적인 설명을 반환한다.
 * 다음은 이 설명의 일반적인 형태이나,
 * 상세 형식은 정해지지 않았으며 향후 변경될 수 있다.
 * 
 * &amp;quot;[약물 #9: 유형=사랑, 냄세=테레빈유, 겉모습=먹물]&amp;quot;
 */
@Override
public String toString() { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;toString&lt;/code&gt; 이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그렇지 않으면 &lt;code&gt;toString&lt;/code&gt; 의 반환 값을 파싱할 수밖에 없음&lt;ul&gt;
&lt;li&gt;성능 저하&lt;/li&gt;
&lt;li&gt;포맷이 바뀌면 시스템이 망가질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;정적 유틸리티 클래스(아이템 4)는 &lt;code&gt;toString&lt;/code&gt; 을 제공할 이유가 없음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;대부분의 열거 타입(아이템 34)은 따로 재정의할 필요 없음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;하위 클래스들이 공유해야 할 문자열 표현이 있는 추상 클래스는 &lt;code&gt;toString&lt;/code&gt; 을 재정의해야함&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;모든 구체 클래스에서 Object의 toString을 재정의할 것 (상위 클래스에서 이미 알맞게 재정의한 경우는 예외)&lt;/p&gt;
&lt;p&gt;toString을 재정의한 클래스는 사용하기도 즐겁고 그 클래스를 사용한 시스템을 디버깅하기 쉽게 해줌&lt;/p&gt;
&lt;p&gt;toString은 해당 객체에 관한 명확하고 유용한 정보를 읽기 좋은 형태로 반환해야함&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>CS</category>
      <category>Effective Java</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/307</guid>
      <comments>https://dev-mb.tistory.com/307#entry307comment</comments>
      <pubDate>Sun, 8 Aug 2021 20:36:59 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Java] 아이템 11: equals를 재정의하려거든 hashCode도 재정의하라</title>
      <link>https://dev-mb.tistory.com/306</link>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;equals&lt;/code&gt; 를 재정의한 클래스 모두에서 &lt;code&gt;hashCode&lt;/code&gt; 도 재정의해야 함&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;그렇지 않으면 &lt;code&gt;hashCode&lt;/code&gt; 일반 규약을 어기게 되어 &lt;code&gt;HashMap&lt;/code&gt; 이나 &lt;code&gt;HashSet&lt;/code&gt; 같은 컬렉션의 원소로 사용할 때 문제가 생김&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;equals 비교에 사용되는 정보가 변경되지 않았다면, 어플리케이션이 실행되는 동안 그 객체의 hashCode 메서드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야함. 단, 어플리케이션을 다시 실행한다면 이 값은 달라져도 상관없음.&lt;/li&gt;
&lt;li&gt;equals(Object)가 두 객체를 같다고 판단햇다면, 두 객체의 hashCode는 똑같은 값을 반환해야함&lt;/li&gt;
&lt;li&gt;equals(Object)가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없음. 단, 다른 객체에 대해서는 다른 값을 반환해야 해시테이블의 성능이 좋아짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;논리적으로 같은 객체는 같은 해시코드를 반환해야함&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Map&amp;lt;PhoneNumber, String&amp;gt; m = new HashMap&amp;lt;&amp;gt;();
m.put(new PhoneNumber(707, 867, 5309), &amp;quot;제니&amp;quot;);
System.out.println(m.get(new PhoneNumber(707, 867, 5309)));&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드를 실행시키면 &amp;quot;제니&amp;quot;가 출력될 것 같지만 실제로는 &lt;code&gt;null&lt;/code&gt; 을 반환함&lt;/li&gt;
&lt;li&gt;두 &lt;code&gt;PhoneNumber&lt;/code&gt; 객체가 서로 다른 해시코드를 반환했기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;hashCode 구현방법&lt;/h2&gt;
&lt;h3&gt;피해야하는 구현 방법&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public int hashCode() {
    return 42;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;이 코드는 모든 객체에서 똑같은 해시 코드를 반환해주므로, 모든 객체가 해시테이블의 버킷 하나에 담겨 마치 연결리스트(linked list) 처럼 동작함&lt;ul&gt;
&lt;li&gt;평균 수행 시간이 O(1)인 해시테이블이 O(n)이 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;좋은 해시 함수라면 서로 다른 인스턴스에 다른 해시코드를 반환함&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;이상적인 해시 함수는 서로 다른 인스턴스들을 32비트 정수 범위에 균일하게 분배해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;hashCode를 작성하는 요령&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;int&lt;/code&gt; 변수 &lt;code&gt;result&lt;/code&gt; 를 선언한 후 값 &lt;code&gt;c&lt;/code&gt; 로 초기화한다. 이때 &lt;code&gt;c&lt;/code&gt; 는 해당 객체의 첫번째 핵심 필드를 단계 2.1 방식으로 계산한 해시코드다(여기서 핵심 필드란 &lt;code&gt;equals&lt;/code&gt; 비교에 사용되는 필드를 의미. 아이템 10 참조)&lt;/li&gt;
&lt;li&gt;해당 객체의 나머지 핵심 필드 &lt;code&gt;f&lt;/code&gt; 각각에 대해 다음 작업을 수행한다.&lt;ol&gt;
&lt;li&gt;해당 필드의 해시코드 &lt;code&gt;c&lt;/code&gt; 를 계산한다&lt;ol&gt;
&lt;li&gt;기본 타입 필드라면, &lt;code&gt;Type.hashCode(f)&lt;/code&gt; 를 수행한다. 여기서 &lt;code&gt;Type&lt;/code&gt; 은 해당 기본 타입의 박싱 클래스&lt;/li&gt;
&lt;li&gt;참조 타입 필드면서 이 클래스의 &lt;code&gt;equals&lt;/code&gt; 메서드가 이 필드의 &lt;code&gt;equals&lt;/code&gt; 를 재귀적으로 호출해 비교한다면, 이 필드의 &lt;code&gt;hashCode&lt;/code&gt; 를 재귀적으로 호출한다. 계산이 더 복잡해질 것 같으면, 이 필드의 표준형(canonical representation)을 만들어 그 표준형의 &lt;code&gt;hashCode&lt;/code&gt; 를 호출한다. 필드의 값이 &lt;code&gt;null&lt;/code&gt; 이면 0을 사용한다(다른 상수도 괜찮지만 전통적으로 0을 사용함)&lt;/li&gt;
&lt;li&gt;필드가 배열이라면, 핵심 원소 각각을 별도 필드처럼 닥룬다. 이상의 규칙을 재귀적으로 적용해 각 핵심원소의 해시코드를 계산한 다음, 단계 2.2방식으로 갱신한다. 배열에 핵심 원소가 하나도 없다면 단순히 상수(0을 추천)를 사용한다. 모든 원소가 핵심 원소라면 &lt;code&gt;Arrays.hashCode&lt;/code&gt; 를 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;단계 2.1에서 계산한 해시코드 &lt;code&gt;c&lt;/code&gt; 로 &lt;code&gt;result&lt;/code&gt; 를 갱신한다. &lt;code&gt;result = 31 * result + c;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;result&lt;/code&gt; 를 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;파생 필드는 해시코드 계산해서 제외해도 됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다른 필드로부터 계산해낼 수 있는 필드는 모두 무시해도 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;equals&lt;/code&gt; 비교에 사용되지 않는 필드는 반드시 제외해야함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;31 * result&lt;/code&gt; 는 필드를 곱하는 순서에 따라 &lt;code&gt;result&lt;/code&gt; 값을 달라지게 함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클래스에 비슷한 필드가 여러 개일 때 해시 효과를 크게 높여줌&lt;/li&gt;
&lt;li&gt;31은 홀수이면서 소수&lt;ul&gt;
&lt;li&gt;이 숫자가 짝수이고 오버플로가 발생한다면 정보를 잃게됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(result &amp;lt;&amp;lt; 5) - result&lt;/code&gt; 와 같으므로 시프트 연산과 뺄셈으로 최적화 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;적용&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public int hashCode() {
    int result = Short.hashCode(areaCode);
    result = 31 * result + Short.hashCode(prefix);
    result = 31 * result + Short.hashCode(lineNum);
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PhoneNumber&lt;/code&gt; 클래스의 핵심 필드 3개만을 사용해 해시코드를 계산&lt;ul&gt;
&lt;li&gt;동치인 인스턴스들은 같은 해시코드를 갖게됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;hash&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Objects&lt;/code&gt; 클래스는 임의의 개수만큼 객체를 받아 해시코드를 계산해주는 정적 메서드인 &lt;code&gt;hash&lt;/code&gt; 를 제공함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;이 메서드를 활용하면 위에 소개한 방법과 비슷한 수준의 &lt;code&gt;hashCode&lt;/code&gt; 함수를 단 한 줄로 작성할 수 있음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;위의 방식보다 속도는 더 느림&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;입력 인수를 담기 위한 배열의 생성, 입력 중 기본 타입이 있다면 방식과 언방식을 거치기 때문&lt;/li&gt;
&lt;li&gt;성능에 민감하지 않는 상황에만 사용할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public int hashCode() {
    return Objects.hash(lineNUm, prefix, areaCode);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;클래스가 불변이고 해시코드를 계산하는 비용이 크다면, 매번 새로 계산하기보다는 캐싱을 해야함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해시의 키로 사용되지 않는 경우라면 지연 초기화 방법도 있음&lt;ul&gt;
&lt;li&gt;필드를 지연 초기화하려면 그 클래스를 스레드 안전하게 만들어야함 (아이템 83)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Thread Safe&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private int hashCode; // 자동으로 0으로 초기화된다.

@Override
public int hashCode() {
    int result = hashCode;
    if (result == 0) {
        result = Short.hashCode(areaCode);
        result = 31 * result + Short.hashCode(prefix);
        result = 31 * result + Short.hashCode(lineNum);
        hashCode = result;
    }
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;성능을 높이기 위해 해시코드를 계산할 때 핵심 필드를 생략해서는 안됨&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;속도는 빨라지지만, 해시 품질이 나빠져 해시테이블의 성능을 떨어뜨릴 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;hashCode&lt;/code&gt; 가 반환하는 값의 생성 규칙을 API 사용자에게 자세히 공표하지 말 것&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;클라이언트가 이 값에 의지하지 않고, 추후에 계산 방식을 바꿀 수 있도록 해야함&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;equals를 재정의할 때는 hashCode도 반드시 재정의해아함. 그렇지 않으면 프로그램이 제대로 동작하지 않을 것임&lt;/p&gt;
&lt;p&gt;재정의한 hashCode는 Object의 API 문서에 기술된 일반 규약을 따라야 하며, 서로 다른 인스턴스라면 되도록 해시코드도 서로 다르게 구현해야함&lt;/p&gt;
&lt;p&gt;AutoValue 프레임워크 혹은 IDE를 활용하면 equals와 hashCode를 자동으로 만들어줌&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>개발</category>
      <category>Effective Java</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/306</guid>
      <comments>https://dev-mb.tistory.com/306#entry306comment</comments>
      <pubDate>Tue, 3 Aug 2021 23:55:02 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Java] 아이템 10: equals는 일반 규약을 지켜 재정의하라</title>
      <link>https://dev-mb.tistory.com/305</link>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;code&gt;equals&lt;/code&gt; 메서드는 재정의하기 쉬워 보이지만 잘못하면 자칫하면 끔찍한 결과를 초래하므로 특정 상황에 해당한다면 재정의하지 않는 것이 최선&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;재정의하면 안되는 상황&lt;/h2&gt;
&lt;h3&gt;각 인스턴스가 본질적으로 고유할 때&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;값을 표현하는게 아니라 동작하는 개체를 표현하는 클래스일 때&lt;ul&gt;
&lt;li&gt;ex) Thread&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;인스턴스의 &amp;#39;논리적 동치성(logical equality)&amp;#39;을 검사할 일이 없을 때&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;java.util.regex.Pattern&lt;/code&gt; 은 &lt;code&gt;equals&lt;/code&gt; 를 재정의해서 두 &lt;code&gt;Pattern&lt;/code&gt; 의 인스턴스가 같은 정규표현식을 나타내는지(논리적 동치성)를 검사 할 수 있음&lt;ul&gt;
&lt;li&gt;설계자가 클라이언트에서 이 방식을 원하지 않거나 필요하지 않다고 판단하면 재정의하지 않는 것이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞을 때&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;대부분의 &lt;code&gt;Set&lt;/code&gt; 구현체는 &lt;code&gt;AbstractSet&lt;/code&gt; ,  &lt;code&gt;List&lt;/code&gt; 구현체는 &lt;code&gt;AbstractList&lt;/code&gt; , &lt;code&gt;Map&lt;/code&gt; 구현체들은 &lt;code&gt;AbstractMap&lt;/code&gt; 에 구현된 &lt;code&gt;equals&lt;/code&gt; 를 상속받아 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없을 때&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;equals&lt;/code&gt; 가 실수로라도 호출되는 걸 막고 싶다면 다음과 같이 구현할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public boolean equals(Object o) {
    throw new AssertionError(); // 호출 금지!
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;재정의 해야할 때&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;객체 식별성(object identity)이 아니라 논리적 동치성을 확인해야 하는데 상위 클래스의 &lt;code&gt;equals&lt;/code&gt; 가 논리적 동치성을 비교하도록 재정의되지 않았을 경우에는 재정의해야함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;객체 식별성: 두 객체가 물리적으로 같은가를 의미함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;일반적으로 값 클래스(Integer, String ...)가 해당됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;값 클래스라 해도, 값이 같은 인스턴스가 둘 이상 만들어지지 않음을 보장하는 인스턴스 통제 클래스(아이템 1)라면 재정의하지 않아도 됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Enum&lt;/code&gt; (아이템 34)은 논리적으로 같은 인스턴스가 2개 이상 생기지 않으므로 &lt;code&gt;equals&lt;/code&gt; 가 논리적 동치성까지 확인해준다고 볼 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;equals&lt;/code&gt; 가 논리적 동치성을 확인하도록 재정의해두면 &lt;code&gt;Map&lt;/code&gt; 의 키와 &lt;code&gt;Set&lt;/code&gt; 의 원소로 사용할 수 있게됨&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;일반 규약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;equals&lt;/code&gt; 를 재정의할 때는 반드시 일반 규약을 따라야함&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object&lt;/code&gt; 명세에 적힌 규약은 아래와 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;equals 메서드는 동치관계(equivalence relation)를 구현하며, 다음을 만족한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;반사성(reflexivity): null이 아닌 모든 참조 값 x에 대해, x.equals(x)는 true&lt;/li&gt;
&lt;li&gt;대칭성(symmetry): null이 아닌 모든 참조 값 x, y에 대해, x.equals(y)가 true이면 y.equals(x)도 true&lt;/li&gt;
&lt;li&gt;추이성(transitivity): null이 아닌 모든 참조 값 x, y, z에 대해 x.equals(y)가 true이고 y.equals(z)가 true면 x.equals(z)도 true&lt;/li&gt;
&lt;li&gt;일관성(consistency): null이 아닌 모든 참조 값 x, y에 대해 x.equals(y)를 반복해도 항상 true 또는 false&lt;/li&gt;
&lt;li&gt;null-아님: null이 아닌 모든 참조 값 x에 대해, x.equals(null)은 false&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;반사성&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;객체는 자기 자신과 같아야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;대칭성&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;두 객체는 서로에 대한 동치 여부에 똑같이 답해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public final class CaseInsensitiveString {
    private final String s;

    public CaseInsensitiveString(String s) {
        this.s = Objects.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;
    }
    ... // 나머지 코드는 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;CaseInsensitiveString cis = new CaseInsensitiveString(&amp;quot;Polish&amp;quot;);
String s = &amp;quot;polish&amp;quot;;

cis.equals(s); // true
s.equals(cis); // false&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;String&lt;/code&gt; 은 &lt;code&gt;CaseInsensitiveString&lt;/code&gt; 의 존재를 모르기 때문&lt;/li&gt;
&lt;li&gt;이 문제를 해결하기 위해서는 아래와 같이 바뀌어야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public boolean equals(Object o) {
    return o instanceof CaseInsensitiveString &amp;amp;&amp;amp; ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;추이성&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

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

    ... // 나머지 코드는 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ColorPoint extends Point {
    private final Color color;

    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }

    ... // 나머지 코드는 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ColorPoint&lt;/code&gt; 에 &lt;code&gt;equals&lt;/code&gt; 를 구현하지 않으면 색상 정보는 무시하게 되므로 &lt;code&gt;equals&lt;/code&gt; 를 구현해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public boolean equals(Object o) {
    if (!(o instanceof ColorPoint))
        return false;
    return super.equals(o) &amp;amp;&amp;amp; ((ColorPoint) o).color == color;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;위의 메서드는 대칭성에 위배될 수 있음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);

p.equals(cp); // true
cp.equals(p); // false&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ColorPoint.equals&lt;/code&gt; 가 &lt;code&gt;Point&lt;/code&gt; 와 비교할 때는 색상을 무시하도록 수정해보자&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public boolean equals(Object o) {
    if (!(o instanceof Point))
        return false;

    // o가 일반 Point면 색상을 무시하고 비교한다.
    if (!(o instanceof ColorPoint))
        return o.equals(this);

    // o가 ColorPoint면 색상까지 비교한다.
    return super.equals(o) &amp;amp;&amp;amp; ((ColorPoint) o).color == color;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;이 방식은 대칭성은 지켜주지만 추이성을 위배함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;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
p2.equals(p3); // true
p1.equals(p3); // false&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이 방식은 무한 재귀에 빠질 수도 있음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Point&lt;/code&gt; 의 또 다른 하위 클래스 &lt;code&gt;SmellPoint&lt;/code&gt; 만들고 같은 방식으로 &lt;code&gt;equals&lt;/code&gt; 를 구현했을 때, &lt;code&gt;myColorPoint.equals(mySmellPoint);&lt;/code&gt; 를 호출하면 &lt;code&gt;StackOverflowError&lt;/code&gt; 가 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;객체 지향적 추상화의 이점을 포기하지 않는다면, 구체 클래스를 확장해 새로운 값을 추가하면서 &lt;code&gt;equals&lt;/code&gt; 구약을 만족시킬 방법은 없음&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;equals&lt;/code&gt; 안의 &lt;code&gt;instanceof&lt;/code&gt; 검사를 &lt;code&gt;getClass&lt;/code&gt; 검사로 바꾸면 가능할 것 같음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public boolean equals(Object o) {
    if (o == null || o.getClass() != getClass())
        return false;
    Point p = (Point) o;
    return p.x == x &amp;amp;&amp;amp; p.y == y;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;위 코드는 같은 구현 클래스의 객체일 때만 &lt;code&gt;true&lt;/code&gt; 를 반환하지만 리스코프 치환 원칙을 위배함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;리스코프 치환 원칙(Liskov substitution principle): 어떤 타입에 있어 중요한 속성이라면 그 하위 타입에서도 마찬가지로 중요하므로 그 타입의 모든 메서드가 하위 타입에서도 똑같이 잘 작동해야함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 단위 원 안의 모든 점을 포함하도록 unitCircle을 초기화한다.
private static final Set&amp;lt;Point&amp;gt; unitCircle = Set.of(
        new Point(1, 0), new Point(0, -1),
        new Point(-1, 0), new POint(0, -1));

public static boolean onUnitCircle(Point p) {
    return unitCircle.contains(p);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;주어진 점이 (반지름이 1인) 단위 원 안에 있는지 판별하는 메서드가 필요하다고 가정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class CounterPoint extends Point {
    private static final AtomicInteger counter = new AtomicInteger();

    public CounterPoint(int x, int y) {
        super(x, y);
        counter.increaseAndGet();
    }
    public static int numberCreated() { return counter.get(); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이 때 &lt;code&gt;CounterPoint&lt;/code&gt; 의 인스턴스를 &lt;code&gt;onUnitCircle&lt;/code&gt; 메서드에 넘기면 &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt; 값과 무관하게 &lt;code&gt;false&lt;/code&gt; 를 반환함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onUnitCircle&lt;/code&gt; 에서 사용하는 &lt;code&gt;Set&lt;/code&gt; 을 포함한 대부분의 컬렉션은 &lt;code&gt;equals&lt;/code&gt; 를 사용하는데, &lt;code&gt;CounterPoint&lt;/code&gt; 의 인스턴스는 어떤 &lt;code&gt;Point&lt;/code&gt; 와도 같을 수 없기 때문&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Point&lt;/code&gt; 의 &lt;code&gt;equals&lt;/code&gt; 를 &lt;code&gt;instanceof&lt;/code&gt; 기반으로 구현했다면 정상적으로 동작함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;구체 클래스의 하위 클래스에서 값을 추가할 방법은 없지만, &amp;quot;상속 대신 컴포지을 사용하라&amp;quot;(아이템 18)의 조언을 따르면 우회할 수 있음&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Point&lt;/code&gt; 를 상속하는 대신 &lt;code&gt;ColorPoint&lt;/code&gt; 의 &lt;code&gt;private&lt;/code&gt; 필드로 두고, &lt;code&gt;ColorPoint&lt;/code&gt; 와 같은 위치의 일반 &lt;code&gt;Point&lt;/code&gt; 를 반환하는 뷰(view) 메서드(아이템 6)를 &lt;code&gt;public&lt;/code&gt; 으로 추가하는 방식&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ColorPoint {
    private final Point point;
    private final Color color;

    public ColorPoint(int x, int y, Color color) {
        point = new Point(x, y);
        this.color = Objects.requireNonNull(color);
    }

    /*
     * 이 ColorPoint의 Point 뷰를 반환한다.
     */
    public Point asPoint() {
        return point;
    }

    @Override
    public boolean equals(Object o) {
        if(!(o instanceof ColorPoint))
            return false;
        ColorPoint cp = (ColorPoint) ;
        return cp.point.equals(point) &amp;amp;&amp;amp; cp.color.equals(color);
    }
    ... // 나머지 코드는 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Java 라이브러리에서도 구체 클래스를 확장해 값을 추가한 클래스가 종종 있음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ex) &lt;code&gt;java.sql.Timestamp&lt;/code&gt; 는 &lt;code&gt;java.util.Date&lt;/code&gt; 를 확장한 후 &lt;code&gt;nanoseconds&lt;/code&gt; 필드를 추가함&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Timestamp&lt;/code&gt; 의 &lt;code&gt;equals&lt;/code&gt; 는 대칭성을 위배하고, &lt;code&gt;Date&lt;/code&gt; 객체와 한 컬렉션에 넣거나 서로 섞어 사용하면 이상하게 동작할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;추상 클래스의 하위 클래스에서라면 &lt;code&gt;equals&lt;/code&gt; 규약을 지키면서도 값을 추가할 수 있음&lt;/p&gt;
&lt;p&gt;&amp;quot;태그 달린 클래스보다는 클래스 계층구조를 활용하라&amp;quot;는 아이템 23의 조언을 따르는 클래스 계층구조에서는 아주 중요함.&lt;/p&gt;
&lt;p&gt;아무런 값을 갖지 않는 추상 클래스 &lt;code&gt;Shape&lt;/code&gt; 를 위에 두고, 이를 확장하여 &lt;code&gt;radius&lt;/code&gt; 필드를 추가한 &lt;code&gt;Circle&lt;/code&gt; 클래스와 &lt;code&gt;length&lt;/code&gt; 와 &lt;code&gt;width&lt;/code&gt; 필드를 추가한 &lt;code&gt;Rectangle&lt;/code&gt; 클래스를 만들 수 있음&lt;/p&gt;
&lt;p&gt;상위 클래스를 직접 인스턴스로 만드는 게 불가능하다면 지금까지 이야기한 문제들은 일어나지 않음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;일관성&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;두 객체가 같다면 (어느 하나 혹은 두 객체 모두가 수정되지 않는 한) 앞으로도 영원히 같아야 함을 의미&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;클래스가 불변이든 가변이든 &lt;code&gt;equals&lt;/code&gt; 판단에 신뢰할 수 없는 자원이 끼어들게 하면 안됨&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;ex) &lt;code&gt;java.net.URL&lt;/code&gt; 의 &lt;code&gt;equals&lt;/code&gt; 는 주어진 URL과 매핑된 호스트의 IP 주소를 이용해 비교함&lt;ul&gt;
&lt;li&gt;호스트 이름을 IP 주소로 바꾸려면 네트워크를 통해야 하는데, 그 결과가 항상 같다고 보장할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이런 문제를 피하려면 &lt;code&gt;equals&lt;/code&gt; 는 항시 메모리에 존재하는 객체만을 사용한 결정적(deterministic) 계산만 수행해야함&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;null-아님&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;모든 객체가 &lt;code&gt;null&lt;/code&gt; 과 같지 않아야 함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 명시적 null 검사 - 필요 없다!
@Override
public boolean equals(Object o) {
    if (o == null)
        return false;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code&gt;// 묵시적 null 검사 - 이쪽이 낫다.
@Override
public boolean equals(Object o) {
    if(!(o instanceof MyType))
        return false;
    MyType mt = (MyType) o;
    ...
}
```&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;instanceof&lt;/code&gt; 는 입력이 피연산자 또는 입력이 &lt;code&gt;null&lt;/code&gt; 이면 &lt;code&gt;false&lt;/code&gt; 를 반환함&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;양질의 equals 메서드 구현 방법&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;==&lt;/code&gt; 연산자를 사용해 입력이 자기 자신의 참조인지 확인&lt;ul&gt;
&lt;li&gt;단순한 성능 최적화용으로 비교 작업이 복잡할 때 효과가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;instanceof&lt;/code&gt; 연산자로 입력이 올바른 타입인지 확인&lt;/li&gt;
&lt;li&gt;입력을 올바른 타입으로 형변환&lt;/li&gt;
&lt;li&gt;입력 객체와 자기 자신의 대응되는 &amp;#39;핵심&amp;#39; 필드들이 모두 일치하는지 하나씩 검사&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;float&lt;/code&gt;과 &lt;code&gt;double&lt;/code&gt; 을 제외한 기본 타입 필드는 &lt;code&gt;==&lt;/code&gt; 로 비교하고, 참조 타입 필드는 &lt;code&gt;equals&lt;/code&gt; 메서드로, &lt;code&gt;float&lt;/code&gt; 과 &lt;code&gt;double&lt;/code&gt; 필드는 각각 &lt;code&gt;Float.compare(float, float)&lt;/code&gt; 과 &lt;code&gt;Double.compare(double, double)&lt;/code&gt; 로 비교&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Float.NAN&lt;/code&gt;,&lt;code&gt;-0.0f&lt;/code&gt;, 특수한 부동소수 값 때문&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Float.equals&lt;/code&gt; 와 &lt;code&gt;Double.equals&lt;/code&gt; 는 오토박싱을 수반할 수 있어서 성능상 좋지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;null&lt;/code&gt; 도 정상 값으로 취급하는 참조 타입 필드는 &lt;code&gt;Objects.equals(Object, Object)&lt;/code&gt; 로 비교해 &lt;code&gt;NullPointerException&lt;/code&gt; 을 예방할 것&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이전에 나온 &lt;code&gt;CaseInsensitiveString&lt;/code&gt; 예처럼 비교하기 아주 복잡한 필드를 가진 클래스의 경우에는 그 필드의 표준형(canonical form)을 저장해둔 후 표준형끼리 비교하는 것이 경제적임&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;어떤 필드를 먼저 비교하는지가 &lt;code&gt;equals&lt;/code&gt; 의 성능을 좌우할 수 있음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최상의 성능을 원한다면 다를 가능성이 더 크거나 비교하는 비용이 싼 필드를 먼저 비교할 것&lt;ul&gt;
&lt;li&gt;동기화용 락(lock) 필드 같이 객체의 논리적 상태와 관련 없는 필드는 비교하면 안됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;equals&lt;/code&gt; 를 다 구현했다면 &amp;#39;대칭성&amp;#39;, &amp;#39;추이성&amp;#39;, &amp;#39;일관성&amp;#39; 을 확인해볼 것&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public final class PhoneNumber {
    private final short areaCode, prefix, lineNum;

    public PhoneNumber(int areaCode, int prefix, int lineNum) {
        this.areaCode = rangeCheck(areaCode, 999, &amp;quot;지역코드&amp;quot;);
        this.prefix = rangeCheck(prefix, 999, &amp;quot;프리픽스&amp;quot;);
        this.lineNum = rangeCheck(lineNum, 999, &amp;quot;가입자 번호&amp;quot;);
    }

    private static short rangeCheck(int val, int max, String arg) {
        if (val &amp;lt; 0 || val &amp;gt; max)
            throw new IllegalArgumentException(arg + &amp;quot;: &amp;quot; + val);
        return (short) val;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof PhoneNumber))
            return false;
        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNum == lineNum &amp;amp;&amp;amp; pn.prefix == prefix &amp;amp;&amp;amp; pn.areaCode == areaCode;
    }
    ... // 나머지 코드는 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;주의사항&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;equals&lt;/code&gt; 를 재정의할 땐 &lt;code&gt;hashCode&lt;/code&gt; 도 반드시 재정의할 것 (아이템 11)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;너무 복잡하게 해결하려하지 말 것&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;필드들의 동치성만 검사해도 &lt;code&gt;equals&lt;/code&gt; 규약을 어렵지 않게 지킬 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Object&lt;/code&gt; 외의 타입을 매개변수로 받는 &lt;code&gt;equals&lt;/code&gt; 메서드는 선언하지 말 것&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 잘못된 예 - 입력 타입은 반드시 Object여야 한다!
public boolean equals(MyClass o) {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;equals&lt;/code&gt; 를 테스트할 때 &amp;#39;AutoValue&amp;#39; 프레임워크를 활요하면 좋음&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;꼭 필요한 경우가 아니면 &lt;code&gt;equals&lt;/code&gt; 를 재정의하지 말 것&lt;/p&gt;
&lt;p&gt;많은 경우에 &lt;code&gt;Object&lt;/code&gt; 의 &lt;code&gt;equals&lt;/code&gt; 가 원하는 비교를 수행해줌&lt;/p&gt;
&lt;p&gt;재정의해야 할 때는 그 클래스의 핵심 필드 모두를 빠짐없이, 5가지 규약을 확실히 지켜가며 비교해야함&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>개발</category>
      <category>Effective Java</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/305</guid>
      <comments>https://dev-mb.tistory.com/305#entry305comment</comments>
      <pubDate>Thu, 29 Jul 2021 00:14:37 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Java] 아이템 9: try-finally 보다는 try-with-resources를 사용하라</title>
      <link>https://dev-mb.tistory.com/304</link>
      <description>&lt;ul&gt;
&lt;li&gt;Java 라이브러리에는 &lt;code&gt;close&lt;/code&gt; 메서드를 호출해 직접 닫아줘야 하는 자원이 많음&lt;/li&gt;
&lt;li&gt;ex) &lt;code&gt;InputStream&lt;/code&gt; , &lt;code&gt;OutputStream&lt;/code&gt; , &lt;code&gt;java.sql.Connection&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;이런 자원 중 상당수가 안전망으로 &lt;code&gt;finalize&lt;/code&gt; 를 활용하고 있지만 그리 믿을만하지 않음(아이템 8)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;전통적인 방법&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;전통적으로 자원을 제대로 닫힘을 보장하기 위해 try-finally가 쓰임&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;static String firstLineOfFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        br.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;나쁘지 않은 방법이지만 자원을 하나 더 사용한 경우 처리가 어려워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;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)) &amp;gt;= 0)
                out.write(buf, 0, n);
        } finally {
            out.close();
        } 
    }finally {
        in.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;예외는 try 블록과 finally 블록 모두에서 발생할 수 있으므로 위의 코드들은 결점이 있음&lt;ul&gt;
&lt;li&gt;기기에 물리적인 문제가 생긴다면 &lt;code&gt;firstLineOfFile&lt;/code&gt; 메서드 안의 &lt;code&gt;readLine&lt;/code&gt; 메서드가 예외를 던지고, 같은 이유로 &lt;code&gt;close&lt;/code&gt; 메서드도 실패함&lt;/li&gt;
&lt;li&gt;이런 상황이라면 두 번째 예외가 첫 번째 예외를 완전히 집어삼켜서 스택 추적 내역에 첫 번째 예외 관련 정보는 남지 않아 디버깅이 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;개선된 방법&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Java 7에 try-with-resources 가 이 문제를 해결해줌&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;이 구조를 사용하려면 해당 자원이 &lt;code&gt;AutoCloseable&lt;/code&gt; 인터페이스를 구현해야함&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;void&lt;/code&gt;를 반환하는 &lt;code&gt;close&lt;/code&gt; 메서드 하나만 정의된 인터페이스&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;닫아야 하는 자원을 뜻하는 클래스를 작성한다면 &lt;code&gt;AutoCloseable&lt;/code&gt; 을 반드시 구현할 것&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(
        new FileReader(path))) {
        return br.readLine();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;첫 번째 코드에 적용한 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;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)) &amp;gt;= 0)
            out.write(buf, 0, n);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;두 번째 코드에 적용한 예시&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;try-with-resources 버전이 짧고 읽기 수월할 뿐 아니라 문제를 진단하기도 훨씬 좋음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;firstLineOfFile&lt;/code&gt; 메서드의 &lt;code&gt;readLine&lt;/code&gt; 과 (코드에는 나타나지 않는) &lt;code&gt;close&lt;/code&gt; 호출 양쪽에서 예외가 발생하면, &lt;code&gt;close&lt;/code&gt; 에서 발생한 예외는 숨겨지고 &lt;code&gt;readLine&lt;/code&gt; 에서 발생한 예외가 기록됨&lt;ul&gt;
&lt;li&gt;숨겨진 예외들은 스택 추적 내역에 &amp;#39;숨겨졌다(suppressed)&amp;#39;로 출력됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;catch 절&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;try-finally에서처럼 try-with-resources에서도 catch 절을 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;static String firstLineOfFile(String path, String defaultVal) {
    try (BufferedReader br = new BufferedReader(
            new FileReader(path))) {
        return br.readLine();
    } catch (IOException e) {
        return defaultVal;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;꼭 회수해야 하는 자원을 다룰 때는 예외 없이 try-finally 말고, try-with-resources를 사용할 것&lt;/p&gt;
&lt;p&gt;코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용함&lt;/p&gt;
&lt;p&gt;try-finally로 작성하면 실용적이지 못할 만큼 코드가 지저분해지는 경우라도, try-with-resources로는 정확하고 쉽게 자원을 회수할 수 있음&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>개발</category>
      <category>Effective Java</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/304</guid>
      <comments>https://dev-mb.tistory.com/304#entry304comment</comments>
      <pubDate>Tue, 27 Jul 2021 22:37:16 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Java] 아이템 8: finalizer와 cleaner 사용을 피하라</title>
      <link>https://dev-mb.tistory.com/303</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java는 두 가지 객체 소멸자를 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그 중 &lt;code&gt;finalizer&lt;/code&gt; 는 예측할 수 없고, 상황에 따라 위험할 수 있어서 일반적으로 불필요함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오동작, 낮은 성능, 이식성 문제의 원인이 되기도 함&lt;/li&gt;
&lt;li&gt;Java 9에서는 &lt;code&gt;finalizer&lt;/code&gt; 를 deprecated하고 &lt;code&gt;cleaner&lt;/code&gt; 를 대안으로 소개함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cleaner&lt;/code&gt; 는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java의 &lt;code&gt;finalizer&lt;/code&gt; 와 &lt;code&gt;cleaner&lt;/code&gt; 는 C++의 파괴자(destructor)와는 다른 개념
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C++에서 파괴자는 특정 객체와 관련된 자원을 회수하는 보편적인 방법&lt;/li&gt;
&lt;li&gt;Java에서는 접근할 수 없게 된 객체를 가비지 컬렉터가 알아서 회수함&lt;/li&gt;
&lt;li&gt;C++의 파괴자는 비메모리 자원을 회수하는 용도로 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java에서는 try-with-resources와 try-finally를 사용 (아이템 9)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finalizer&lt;/code&gt; 와 &lt;code&gt;cleaner&lt;/code&gt; 는 즉시 수행된다는 보장이 없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;finalizer&lt;/code&gt; 와 &lt;code&gt;cleaner&lt;/code&gt; 로는 제때 실행되어야 하는 작업은 절대 할 수 없음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;ex) 파일 닫기를 &lt;code&gt;finalizer&lt;/code&gt; 와 &lt;code&gt;cleaner&lt;/code&gt; 에게 맡기면 시스템이 동시에 열 수 있는 파일 개수에 한계가 있기 때문에 중대한 오류를 일으킬 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템이 &lt;code&gt;finalizer&lt;/code&gt; 나 &lt;code&gt;cleaner&lt;/code&gt; 실행을 게을리해 파일을 계속 열어 둔다면 새로운 파일을 열지 못해 프로그램이 실패할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finalizer&lt;/code&gt; 나 &lt;code&gt;cleaner&lt;/code&gt; 를 얼마나 신속히 수행할지는 전적으로 가비지 컬렉터 알고리즘에 달려있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가비지 컬렉터마다 천차만별 (JVM에 따라 달라질 수 있음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Java 언어 명세는 &lt;code&gt;finalizer&lt;/code&gt; 나 &lt;code&gt;cleaner&lt;/code&gt; 의 수행 시점 뿐 아니라 수행 여부조차 보장하지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램 생애주기와 상관없는, &lt;b&gt;상태를 영구적으로 수정하는 작업에서는 절대 &lt;code&gt;finalizer&lt;/code&gt; 나 &lt;code&gt;cleaner&lt;/code&gt; 에 의존하면 안됨&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 데이터베이스 같은 공유 자원의 영구 락(lock) 해제를 &lt;code&gt;finalizer&lt;/code&gt; 나 &lt;code&gt;cleaner&lt;/code&gt; 에게 맡기면 안됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;System.gc&lt;/code&gt; 나 &lt;code&gt;System.runFinalization&lt;/code&gt; 메서드에 현혹되면 안됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;finalizer&lt;/code&gt; 와 &lt;code&gt;cleaner&lt;/code&gt; 가 실행될 가능성을 높여주나 보장해주진 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;System.runFinalizersOnExit&lt;/code&gt; 와 &lt;code&gt;Runtime.runFinalizersOnExit&lt;/code&gt; 는 보장해주지만 ThreadStop 이라는 심각한 결함이 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finalizer&lt;/code&gt; 동작 중 발생한 예외는 무시되며, 처리할 작업이 남았더라도 그 순간 종료됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잡지 못한 예외 때문에 해당 객체는 자칫 마무리가 덜 된 상태로 남을 수 있음&lt;/li&gt;
&lt;li&gt;다른 스레드가 훼손된 객체를 사용하려하면 어떻게 동작할지 예측할 수 없음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finalizer&lt;/code&gt; 동작 중 예외가 발생해서 종료되면 경고조차도 출력되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;finalizer&lt;/code&gt; 와 &lt;code&gt;cleaner&lt;/code&gt; 는 심각한 성능 문제를 동반함&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가비지 컬렉터의 효율을 떨어뜨리기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finalizer&lt;/code&gt; 를 사용한 클래스는 &lt;code&gt;finalizer&lt;/code&gt; 공격에 노출되어 심각한 보안 문제를 일으킬 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성자나 직렬화 과정(readObject와 readResolve 메서드)에서 예외가 발생하면 생성되다 만 객체에서 악의적인 하위 클래스의 &lt;code&gt;finalizer&lt;/code&gt; 가 수행될 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 &lt;code&gt;finalizer&lt;/code&gt; 는 정적 필드에 자신의 참조를 할당해 가비지 컬렉터가 수집하지 못하게 막을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체 생성을 막으려면 생성자에서 예외를 던지면 되지만, &lt;code&gt;finalizer&lt;/code&gt; 가 있다면 그렇지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;final&lt;/code&gt; 클래스는 하위 클래스를 만들 수 없으므로 해당 공격에서 안전함&lt;/li&gt;
&lt;li&gt;&lt;code&gt;final&lt;/code&gt; 이 아닌 클래스는 빈 &lt;code&gt;finalize&lt;/code&gt; 메서드를 만들고 &lt;code&gt;final&lt;/code&gt;로 선언해서 방지할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대안&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;AutoCloseable&lt;/code&gt; 을 구현하고, 클라이언트에서 인스턴스를 다 쓰고 나면 &lt;code&gt;close&lt;/code&gt; 메서드를 호출하는 것으로 파일이나 스레드 등 종료해야 할 자원을 담고 있는 객체의 클래스에서 &lt;code&gt;finalizer&lt;/code&gt; 나 &lt;code&gt;cleaner&lt;/code&gt; 를 대신해줄 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 예외가 발생해도 제대로 종료되도록 try-with-resources를 사용해야함(아이템 9)&lt;/li&gt;
&lt;li&gt;각 클래스는 자신이 닫혔는지 추적하는 것이 좋음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;close&lt;/code&gt; 메서드에서 이 객체는 더 이상 유효하지 않음을 피르에 기록하고 다른 메서드는 이 필드를 검사해서 객체가 닫힌 후에 불렸다면 &lt;code&gt;IllegalStateException&lt;/code&gt; 을 던지는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쓰임새&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;cleaner&lt;/code&gt; 와 &lt;code&gt;finalizer&lt;/code&gt; 는 2가지 쓰임새가 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자원의 소유자가 &lt;code&gt;close&lt;/code&gt; 메서드를 호출하지 않는 것에 대비한 안전망 역할
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;cleaner&lt;/code&gt; 나 &lt;code&gt;finalizer&lt;/code&gt; 가 즉시 호출되리란 보장은 없지만, 클라이언트가 하지 않은 자원 회수를 늦게라도 해주는 것이 더 좋음&lt;/li&gt;
&lt;li&gt;안전망 역할의 &lt;code&gt;finalizer&lt;/code&gt; 를 작성할 때는 값어치가 있는지 심사숙고할 것&lt;/li&gt;
&lt;li&gt;ex) &lt;code&gt;FileInputStream&lt;/code&gt;, &lt;code&gt;FileOutputStream&lt;/code&gt;, &lt;code&gt;ThreadPoolExecutor&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;네이티브 피어(native peer)와 연결된 객체
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네이티브 피어: 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 의미&lt;/li&gt;
&lt;li&gt;네이티브 피어는 Java 객체가 아니므로 GC는 존재를 알지 못함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 피어를 회수할 때 네이티브 객체까지 회수하지 못함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;성능 저하를 감당할 수 있고 네이티브 피어가 심각한 자원을 가지고 있지 않을 때는 &lt;code&gt;cleaner&lt;/code&gt; 나 &lt;code&gt;finalizer&lt;/code&gt; 를 사용할만함&lt;/li&gt;
&lt;li&gt;성능 저하를 감당할 수 없거나 네이티브 피어가 사용하는 자원을 즉시 회수해야 한다면 &lt;code&gt;close&lt;/code&gt; 메서드를 이용할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;cleaner&lt;/code&gt; 사용법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;cleaner&lt;/code&gt; 는 사용하기에 조금 까다로움&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class Room implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();

    // 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다!
    private static class State implements Runnable {
        int numJunkPiles; // 방(Room) 안의 쓰레기 수

        State(int numJunkPiles) {
            this.numJunkPiles = numJunkPiles;
        }

        // close 메서드나 cleaner가 호출된다.
        @Override
        public void run() {
            System.out.println(&quot;방 청소&quot;);
            numJunkPiles = 0;
        }
    }

    // 방의 상태. cleanable과 공유한다.
    private final State state;

    // cleanable 객체. 수거 대상이 되면 방을 청소한다.
    private final Cleaner.Cleanable cleanable;

    public Room(int numJunkPiles) {
        state = new State(numJunkPiles);
        cleanable = cleaner.register(this, state);
    }

    @Override
    public void close() throws Exception {
        cleanable.clean();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;State&lt;/code&gt; 는 &lt;code&gt;cleaner&lt;/code&gt; 가 방을 청소할 때 수거할 자원을 담고 있음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numJunkPiles&lt;/code&gt; 필드는 수거할 자원&lt;/li&gt;
&lt;li&gt;&lt;code&gt;State&lt;/code&gt; 는 &lt;code&gt;Runnable&lt;/code&gt; 을 구현하고, 그 안의 &lt;code&gt;run&lt;/code&gt; 메서드는 &lt;code&gt;cleanable&lt;/code&gt; 에 의해 딱 한번만 호출됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cleanable&lt;/code&gt; 객체는 &lt;code&gt;Room&lt;/code&gt; 생성자에서 &lt;code&gt;cleaner&lt;/code&gt; 에 &lt;code&gt;Room&lt;/code&gt; 과 &lt;code&gt;State&lt;/code&gt; 를 등록할 때 얻음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run&lt;/code&gt; 메서드가 호출되는 상황은 둘 중 하나
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Room&lt;/code&gt; 의 &lt;code&gt;close&lt;/code&gt; 메서드를 호출할 때
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;close&lt;/code&gt; 메서드에서 &lt;code&gt;Cleanable&lt;/code&gt; 의 &lt;code&gt;clean&lt;/code&gt; 을 호출하면 이 메서드 안에서 &lt;code&gt;run&lt;/code&gt; 을 호출함&lt;/li&gt;
&lt;li&gt;가비지 컬렉터가 &lt;code&gt;Room&lt;/code&gt; 을 회수할 때까지 클라이언트가 &lt;code&gt;close&lt;/code&gt; 를 호출하지 않는다면, &lt;code&gt;cleaner&lt;/code&gt; 가 &lt;code&gt;State&lt;/code&gt; 의 &lt;code&gt;run&lt;/code&gt; 메서드를 호출함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;State&lt;/code&gt; 인스턴스는 절대로 &lt;code&gt;Room&lt;/code&gt; 인스턴스를 참조하면 안됨&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Room&lt;/code&gt; 인스턴스를 참조할 경우 순환참조가 생겨 가비지 컬렉터가 &lt;code&gt;Room&lt;/code&gt; 인스턴스를 회수하지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정적이 아닌 중첩 클래스는 자동으로 바깥 객체의 참조를 갖게 됨&lt;/b&gt;(아이템 24)&lt;/li&gt;
&lt;li&gt;람다 역시 바깥 객체의 참조를 갖기 쉬우니 사용하지 않는 것이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 코드에서 &lt;code&gt;Room&lt;/code&gt; 의 &lt;code&gt;cleaner&lt;/code&gt; 는 단지 안전망으로만 쓰임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 모든 &lt;code&gt;Room&lt;/code&gt; 생성을 try-with-resources 블록으로 감쌌다면 자동 청소는 전혀 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Adult {
    public static void main(String[] args) {
        try (Room myRoom = new Room(7)) {
            System.out.println(&quot;안녕~&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Adult&lt;/code&gt; 프로그램은 &quot;안녕~&quot; 을 출력한 후, 이어서 &quot;방 청소&quot;를 출력함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Teenager {
    public static void main(String[] args) {
        new Room(99);
        System.out.println(&quot;아무렴&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;방 청소&quot;는 한 번도 출력되지 않음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cleaner&lt;/code&gt; 의 명세는 다음과 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;System.exit&lt;/code&gt; 을 호출할 때의 &lt;code&gt;cleaner&lt;/code&gt; 동작은 구현하기 나름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청소가 이뤄질지는 보장하지 않음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명세에선 명시하지 않았지만 일반적인 프로그램 종료에서도 마찬가지&lt;/li&gt;
&lt;li&gt;내 컴퓨터에서는 &lt;code&gt;Teenager&lt;/code&gt; 의 &lt;code&gt;main&lt;/code&gt; 메서드의 &lt;code&gt;System.gc()&lt;/code&gt; 를 추가하는 것으로 종료 전에 &quot;방 청소&quot;를 출력할 수 있지만, 다른 컴퓨터에서도 그러리라는 보장은 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;cleaner&lt;/code&gt;(Java 8까지는 &lt;code&gt;finalizer&lt;/code&gt;)는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용할 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이런 경우라도 불확실성과 성능 저하에 주의해야함&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>개발</category>
      <category>Effective Java</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/303</guid>
      <comments>https://dev-mb.tistory.com/303#entry303comment</comments>
      <pubDate>Mon, 26 Jul 2021 23:53:13 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Java] 아이템 7: 다 쓴 객체 참조를 해제하라</title>
      <link>https://dev-mb.tistory.com/302</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java처럼 가비지 컬렉터를 갖춘 언어를 사용하다보면 자칫 메모리 관리에 신경 쓰지 않아도 된다고 오해할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;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);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특별한 문제가 없어 보이나 사실 &lt;b&gt;메모리 누수&lt;/b&gt; 문제가 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 스택을 사용하는 프로그램을 오래 실행하다보면 점차 가비지 컬렉션 활동과 메모리 사용량이 늘어나 성능이 저하될 것&lt;/li&gt;
&lt;li&gt;심한 경우에는 디스크 페이징이나 &lt;code&gt;OutOfMemoryError&lt;/code&gt; 를 일으켜 프로그램이 종료될 수도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 코드에서는 스택이 커졌다가 줄어들었을 대 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스택이 그 객체들의 다 쓴 참조(obsolete reference)를 여전히 가지고 있기 때문
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다 쓴 참조(obsolete reference): 앞으로 다시 쓰지 않을 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 누수&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가비지 컬렉션 언어에서는 메모리 누수를 찾기가 아주 까다로움&lt;/li&gt;
&lt;li&gt;객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체뿐 아니라 그 객체가 참조하는 모든 객체(그리고 또 그 객체들이 참조하는 모든 객체∙∙∙)를 회수하지 못함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단 몇 개의 객체가 매우 많은 객체를 회수되지 못하게 할 수 있고 잠재적으로 성능에 악영향을 줄 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스택이 자기 메모리를 직접 관리하기 때문에 메모리 누수가 발생함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 참조를 담는 elements 배열로 저장소 풀을 만들어 원소들을 관리하고 있음&lt;/li&gt;
&lt;li&gt;배열의 활성 영역(0 ~ size - 1)에 속한 원소들이 사용되고 비활성 영역(size ~ elements.size - 1)은 쓰이지 않음&lt;/li&gt;
&lt;li&gt;가비지 컬렉터는 어디부터 어디까지가 활성영역인지 알 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해법&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 참조를 다 썼을 때 &lt;code&gt;null&lt;/code&gt; 처리(참조 해제)하면 메모리 누수를 방지할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public Object pop() {
    if (size == 0)
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 &lt;code&gt;null&lt;/code&gt; 처리한 참조를 실수로 사용하려 하면 프로그램은 즉시 &lt;code&gt;NullPointerException&lt;/code&gt; 을 던지며 종료됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램 오류는 가능한 한 조기에 발견되는게 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 모든 객체를 다 쓰자마자 일일이 &lt;code&gt;null&lt;/code&gt; 처리하는 것은 바람직하지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;객체 참조를 &lt;code&gt;null&lt;/code&gt; 처리하는 일은 예외적인 경우여야 함&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로그래머는 비활성 영역이 되는 순간 &lt;code&gt;null&lt;/code&gt; 처리해서 해당 객체를 더는 쓰지 않을 것임을 가비지 컬렉터에게 알려야함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 누수 발생 케이스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자기 메모리를 직접 관리하는 클래스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원소를 다 사용한 즉시 그 원소가 참조한 객체들을 다 &lt;code&gt;null&lt;/code&gt; 처리해줘야함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 참조를 캐시에 넣고 나서, 이 사실을 잊은 채 , 그 객체를 한참을 그냥 나두는 일이 빈번함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결책&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시 외부에서 키(key)를 참조하는 동안만(값이 아님) 엔트리가 살아 있는 캐시가 필요한 상황
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;WeakHashMap&lt;/code&gt; 을 사용해 캐시를 만들자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다 쓴 엔트리는 즉시 자동으로 제거됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WeakHashMap&lt;/code&gt; 은 이런 상황에서만 유용함을 기억할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;캐시를 만들 때 캐시 엔트리의 유효 기간을 정확히 정의하기 어려움
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식을 흔히 사용함&lt;/li&gt;
&lt;li&gt;쓰지 않는 엔트리를 이따금 청소해줘야하는데, (&lt;code&gt;ScheduledThreadPoolExecutor&lt;/code&gt; 같은) 백그라운드 스레드를 활용하거나 캐시에 새 엔트리를 추가할 때 부수 작업으로 수행하는 방법이 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) &lt;code&gt;LinkedHashMap&lt;/code&gt; 은 &lt;code&gt;removeEldestEntry&lt;/code&gt; 메서드를 사용해 후자의 방식으로 처리함&lt;/li&gt;
&lt;li&gt;더 복잡한 캐시를 만들고 싶다면 &lt;code&gt;java.lang.ref&lt;/code&gt; 패키지를 직접 활용해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리스너(listener) 혹은 콜백(callback)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면 콜백은 계속 쌓여감
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;콜백을 약한 참조(weak reference)로 저장하면 가비지 컬렉터가 즉시 수거해감
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) &lt;code&gt;WeakHashMap&lt;/code&gt; 에 키로 저장하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 종류의 문제는 예방법을 익혀두는 것이 매우 중요&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>개발</category>
      <category>Effective Java</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/302</guid>
      <comments>https://dev-mb.tistory.com/302#entry302comment</comments>
      <pubDate>Sun, 25 Jul 2021 21:03:32 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Java] 아이템 6: 불필요한 객체 생성을 피하라</title>
      <link>https://dev-mb.tistory.com/301</link>
      <description>&lt;ul&gt;
&lt;li&gt;똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많음&lt;ul&gt;
&lt;li&gt;재사용은 빠르고 세련됨&lt;/li&gt;
&lt;li&gt;특히 불변 객체는 언제든 재사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;극단적인 예시&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;String s = new String(&amp;quot;bikini&amp;quot;); // 따라 하지 말 것!&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;실행될 때마다 &lt;code&gt;String&lt;/code&gt; 인스턴스를 새로 만듬&lt;/li&gt;
&lt;li&gt;반복문이나 빈번히 호출되는 메서드 안에 있다면 쓸데없는 &lt;code&gt;String&lt;/code&gt; 인스턴스가 수백만 개 만들어질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;개선&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;String s = &amp;quot;bikini&amp;quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;새로운 인스턴스를 매번 만드는 대신 하나의 &lt;code&gt;String&lt;/code&gt; 인스턴스를 사용함&lt;/li&gt;
&lt;li&gt;같은 가상 머신 안에서 이와 똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함이 보장됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;정적 팩터리 메서드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;생성자 대신 정적 팩터리 메서드(아이템 1)를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있음&lt;ul&gt;
&lt;li&gt;ex) &lt;code&gt;Boolean(String)&lt;/code&gt; 생성자 대신 &lt;code&gt;Boolean.valueOf(String)&lt;/code&gt; 팩터리 메서드를 사용하는 것이 좋음&lt;/li&gt;
&lt;li&gt;생성자는 호출될 때마다 새로운 객체를 만들지만, 팩터리 메서드는 그렇지 않음&lt;/li&gt;
&lt;li&gt;불변 객체만이 아니라 가변 객체라 해도 사용 중에 변경되지 않을 것임을 안다면 재사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;객체 생성 비용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;생성 비용이 아주 비싼 객체가 반복해서 필요하다면 캐싱해서 재사용하는 것이 좋음&lt;ul&gt;
&lt;li&gt;자신이 만드는 객체가 비싼 객체인지를 매번 명확히 알기는 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;static boolean isRomanNumeral(String s) {
        return s.matches(&amp;quot;^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$&amp;quot;);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;String.matches&lt;/code&gt; 는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않음&lt;ul&gt;
&lt;li&gt;메서드 내부에서 만드는 정규표현식용 &lt;code&gt;Pattern&lt;/code&gt; 인스턴스는 한번 쓰고 버려져서 곧바로 GC의 대상이 됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Pattern&lt;/code&gt; 은 입력받은 정규표현식에 해당하는 유한 상태 머신(finite state machine)을 만드므로 인스턴스 생성 비용이 높음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;개선&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;필요한 정규표현식을 표현하는 (불변인) &lt;code&gt;Pattern&lt;/code&gt; 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해두고, 나중에 &lt;code&gt;isRomanNumeral&lt;/code&gt; 메서드가 호출될 때마다 이 인스턴스를 재사용하는 식으로 개선 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class RomanNumerals {
    private static final Pattern ROMAN = Pattern.compile(&amp;quot;^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$&amp;quot;);

    static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;길이가 8인 문자열 기준으로 1.1μs에서 0.17μs로 약 6.5배 빨라짐&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Pattern&lt;/code&gt; 인스턴스를 &lt;code&gt;static final&lt;/code&gt; 필드로 끄집어내고 이름을 지어줌으로써 코드의 의미가 명확해짐&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isRomanNumeral&lt;/code&gt; 메서드를 한 번도 호출하지 않는다면 &lt;code&gt;ROMAN&lt;/code&gt; 필드는 쓸데없이 초기화 된 것이므로 메서드가 처음 호출될 때 필드를 초기화하는 지연 초기화(lazy initialization, 아이템 83) 불필요한 초기화를 없앨 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;단점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;일반적으로 객체가 불변이라면 재사용해도 안전함이 명백하지만 훨씬 덜 명확하거나 직관에 반대되는 상황도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;어댑터는 실제 작업은 뒷단 객체에 위임하고, 자신은 제 2의 인터페이스 역할을 해주는 객체&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어뎁터는 뒷단 객체만 관리하면 됨&lt;/li&gt;
&lt;li&gt;뒷단 객체 외에는 관리할 상태가 없으므로 뒷단 객체 하나당 어댑터 객체 하나씩만 만들어지면 충분함&lt;/li&gt;
&lt;li&gt;ex) &lt;code&gt;Map&lt;/code&gt; 인터페이스의 &lt;code&gt;keySet&lt;/code&gt; 메서드는 &lt;code&gt;Map&lt;/code&gt; 객체 안의 키 전부를 담은 &lt;code&gt;Set&lt;/code&gt; 뷰를 반환&lt;ul&gt;
&lt;li&gt;&lt;code&gt;keySet&lt;/code&gt; 을 호출할 때마다 새로운 &lt;code&gt;Set&lt;/code&gt; 인스턴스가 만들어질거라 생각할 수 있지만, 매번 같은 &lt;code&gt;Set&lt;/code&gt; 을 반환할지도 모름&lt;/li&gt;
&lt;li&gt;반환된 인스턴스들은 모두가 똑같은 &lt;code&gt;Map&lt;/code&gt; 인스턴스를 대변하므로, 반환한 객체 중 하나를 수정하면 다른 모든 객체가 따라서 바뀜&lt;ul&gt;
&lt;li&gt;&lt;code&gt;keySet&lt;/code&gt; 이 뷰 객체를 여러 개 만들어도 상관없지만 그럴 필요도 없고 이득도 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아님&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private static long sum() {
    Long sum = 0L;
    for (long i = 0; i &amp;lt;= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sum&lt;/code&gt; 변수를 &lt;code&gt;long&lt;/code&gt; 이 아닌 &lt;code&gt;Long&lt;/code&gt; 으로 선언해서 불필요한 &lt;code&gt;Long&lt;/code&gt; 인스턴스가 2^31개나 생성됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;아이템 6을 &lt;strong&gt;&amp;quot;객체 생성은 비싸니 피해야 한다&amp;quot;로 오해하면 안됨&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;요즘의 JVM에서 별다른 일을 하지 않는 작은 객체를 생성하고 회수하는 일이 크게 부담되지는 않으므로, 프로그램의 명확성, 간결성, 기능을 위해 객체를 추가로 생성하는 것이라면 괜찮음&lt;/li&gt;
&lt;li&gt;아주 무거운 객체가 아닌 다음에야 단순히 객체 생성을 피하고자 객체 풀(pool)을 만들지는 말자&lt;ul&gt;
&lt;li&gt;데이터베이스 연결 같은 경우 생성 비용이 워낙 비싸니 재사용하는 편이 낫지만, 일반적으로 자체 객체 풀은 코드를 헷갈리게 만들고 메모리 사용량을 늘리고 성능을 떨어뜨림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;방어적 복사(defensive copy)를 다루는 아이템 50과 내용이 대조적임&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;방어적 복사가 필요한 상황에서 객체를 재사용했을 때의 피해가, 필요 없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다는 사실&lt;/strong&gt;을 기억할 것&lt;/li&gt;
&lt;li&gt;방어적 복사에 실패하면 언제 발생할지 모르는 버그와 보안 구멍으로 이어지지만, 불필요한 객체 생성은 그저 코드 형태와 성능에만 영향을 줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <category>Effective Java</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/301</guid>
      <comments>https://dev-mb.tistory.com/301#entry301comment</comments>
      <pubDate>Sun, 25 Jul 2021 17:52:46 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Java] 아이템 5: 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라</title>
      <link>https://dev-mb.tistory.com/300</link>
      <description>&lt;h1&gt;아이템 5: 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;많은 클래스가 하나 이상의 자원에 의존함&lt;ul&gt;
&lt;li&gt;ex) 맞춤법 검사기는 사전(dictionary)에 의존함&lt;/li&gt;
&lt;li&gt;이런 클래스를 정적 유틸리티 클래스로 구현하는 모습을 드물지 않게 볼 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class SpellChecker {
    private static final Lexicon dictionary = ...;

    private SpellChecker() {} // 객체 생성 방지

    public static boolean isValid(String word) { ... }
    public static List&amp;lt;String&amp;gt; suggestions(String typo) { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;싱글턴을 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class SpellChecker {
    private final Lexicon dictionary = ...;

    private SpellChecker(...) {}
    public static SpellChecker INSTANCE = new SpellChecker(...);

    public boolean isValid(String word) { ... }
    public List&amp;lt;String&amp;gt; suggestions(String typo) { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;두 방식 모두 사전을 단 하나만 사용한다고 가정한다는 점에서 좋은 방식이 아님&lt;ul&gt;
&lt;li&gt;실전에서는 사전이 언어별로 따로 있고 특수 어휘용 사전을 별도로 두기도 함&lt;/li&gt;
&lt;li&gt;테스트용 사전이 필요할 수도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;개선&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SpellChecker&lt;/code&gt; 가 여러 사전을 사용할 수 있도록 만들어보자&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;간단한 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;필드에서 final 한정자를 제거하고 다른 사전으로 교체하는 메서드를 추가할 수 있음&lt;ul&gt;
&lt;li&gt;이 방식은 어색하고 오류를 내기 쉬우며 멀티스레드 환경에서는 쓸 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;클래스(SpellChecker)가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원(dictionary)을 사용해야함&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식&lt;/strong&gt;을 사용하면 됨&lt;ul&gt;
&lt;li&gt;의존 객체 주입의 한 형태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class SpellChecker {
    private final Lexicon dictionary;

    public SpellChecker(Lexicon dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary);
    }

    public boolean isValid(String word) { ... }
    public List&amp;lt;String&amp;gt; suggestions(String typo) { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;예에서는 딱 하나의 자원만 사용하지만, 자원이 몇 개든 의존 관계가 어떻든 상관없이 잘 동작함&lt;/li&gt;
&lt;li&gt;불변을 보장하여 (같은 자원을 사용하려는) 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있음&lt;/li&gt;
&lt;li&gt;의존 객체 주입은 생성자, 정적 팩터리, 빌더 모두에 똑같이 응용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;변형&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;생성자에 자원 팩터리를 넘겨주는 방식으로 변형할 수 있음 (팩터리 메서드 패턴을 구현)&lt;ul&gt;
&lt;li&gt;팩터리: 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체&lt;/li&gt;
&lt;li&gt;ex) Java 8의 &lt;code&gt;Supplier&amp;lt;T&amp;gt;&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Supplier&amp;lt;T&amp;gt;&lt;/code&gt; 를 입력으로 받는 메서드는 일반적으로 한정적 와일드카드 타입(bounded wildcard type)을 사용해 팩터리 타입 매개변수를 제한해야함&lt;ul&gt;
&lt;li&gt;이 방식으로 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ex) &lt;code&gt;Mosaic create(Supplier&amp;lt;? extends Tile&amp;gt; tileFactory) { ... }&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;클라이언트가 제공한 팩터리가 생성한 타일(Tile)들로 구성된 모자이크(Mosaic)를 만드는 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;의존 객체 주입의 단점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;의존 객체 주입이 유연성과 테스트 용이성을 개선해주지만, 의존성이 수 천 개나 되는 큰 프로젝트에서는 코드를 어지럽게 만들 수 있음&lt;ul&gt;
&lt;li&gt;대거(Dagger), 주스(Guice), 스프링(Spring)과 같은 의존 객체 주입 프레임워크를 사용해  어질러짐을 해소할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 도작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋음.&lt;/p&gt;
&lt;p&gt;이 자원들을 클래스가 직접 만들게 해서도 안됨.&lt;/p&gt;
&lt;p&gt;대신 필요한 자원을 (혹은 그 자원을 만들어주는 팩터리를) 생성자에 (혹은 정적 팩터리나 빌더에) 넘겨주자.&lt;/p&gt;
&lt;p&gt;의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 기막히게 개선해줌.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>개발</category>
      <category>Effective Java</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/300</guid>
      <comments>https://dev-mb.tistory.com/300#entry300comment</comments>
      <pubDate>Sun, 25 Jul 2021 17:15:15 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Java] 아이템 4: 인스턴스화를 막으려거든 private 생성자를 사용하라</title>
      <link>https://dev-mb.tistory.com/299</link>
      <description>&lt;ul&gt;
&lt;li&gt;단순히 정적 메서드와 정적 필드만을 담은 클래스가 필요할 때가 있다&lt;ul&gt;
&lt;li&gt;ex) &lt;code&gt;java.lang.Math&lt;/code&gt;, &lt;code&gt;java.util.Arrays&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;기본 타입 값, 배열 관련 메서드들이 모여있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ex) &lt;code&gt;java.util.Collections&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩터리)를 모아놓을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;final 클래스와 관련한 메서드들을 모아놓을 수 있음&lt;ul&gt;
&lt;li&gt;final 클래스는 상속이 불가능하므로 하위 클래스에 메서드를 넣는 건 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;인스턴스화를 막는 방법&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;정적 멤버만 담은 유틸리티 클래스는 인스턴스로 만들어 사용하려고 설계된 것이 아니다&lt;ul&gt;
&lt;li&gt;생성자를 명시해주지 않으면 컴파일러가 자동으로 생성자를 만들어주므로 의도치 않게 인스턴스화 가능할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;유틸리티 클래스를 추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없음&lt;ul&gt;
&lt;li&gt;하위 클래스를 만들어 인스턴스화 할 수 있음&lt;/li&gt;
&lt;li&gt;사용자가 상속해서 사용하는 것으로 오해할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;private&lt;/code&gt; 생성자를 추가&lt;/strong&gt;하면 클래스의 인스턴스화를 막을 수 있음&lt;ul&gt;
&lt;li&gt;컴파일러가 기본 생성자를 만드는 경우는 오직 명시된 생성자가 없을 때 뿐&lt;/li&gt;
&lt;li&gt;명시적 생성자가 &lt;code&gt;private&lt;/code&gt;이므로 클래스 바깥에서 접근할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class UtilityClass {
    // 기본 생성자가 만들어지는 것을 막는다(인스턴스화 방지용).
    private UtilityClass() {
        throw new AssertionError();
    }
    ... // 나머지 코드 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드는 어떤 환경에서도 클래스가 &lt;strong&gt;인스턴스화 되는 것을 막아줌&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;생성자가 존재하는데 호출할 수 없다는 것은 직관적이지 않으므로 &lt;strong&gt;적절한 주석&lt;/strong&gt;을 달아주면 좋음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상속을 불가능하게 하는 효과&lt;/strong&gt;도 있음&lt;ul&gt;
&lt;li&gt;모든 생성자는 상위 클래스의 생성자를 호출해야하므로, 하위 클래스에서 상위 클래스의 생성자에 접근할 방법이 없기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <category>Effective Java</category>
      <author>김덜덜이</author>
      <guid isPermaLink="true">https://dev-mb.tistory.com/299</guid>
      <comments>https://dev-mb.tistory.com/299#entry299comment</comments>
      <pubDate>Sat, 24 Jul 2021 00:46:37 +0900</pubDate>
    </item>
  </channel>
</rss>