<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩하는 털보</title>
    <link>https://rockintuna.tistory.com/</link>
    <description>코딩하는 털보의 기술 블로그입니다요~</description>
    <language>ko</language>
    <pubDate>Thu, 2 Jul 2026 08:37:13 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>이정인</managingEditor>
    <image>
      <title>코딩하는 털보</title>
      <url>https://tistory1.daumcdn.net/tistory/4399141/attach/3f0aee8a8783437d83df801a3ef7ff96</url>
      <link>https://rockintuna.tistory.com</link>
    </image>
    <item>
      <title>ITEM 65 리플렉션 주의</title>
      <link>https://rockintuna.tistory.com/332</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;

/*
리플렉션보다는 인터페이스를 활용하라
 */
public class Item65 {

    /*
    리플렉션으로 하고자 하는 것
    - 클래스의 생성자, 메서드, 필드에 접근 및 조작

    단점
    - 컴파일타임의 검사를 하지 못함
    - 코드가 지저분해지고 장황해짐
    - 성능 저하

    리플렉션은 아주 제한된 형태로만 사용해야 그 단점을 피하고 이점만 취할 수 있다.

    컴파일 타임에 이용할 수 없는 클래스를 사용하는 프로그램에서는
    인스턴스 생성에서만 리플랙션을 사용하고
    인스턴스를 인터페이스 또는 상위 클래스로 참조하여 사용할 수 있다.
     */

    //Set&amp;lt;String&amp;gt;로 참조되는 인스턴스를 사용할 프로그램
    //컴파일타임에는 클래스를 알 수 없음
    public static void main(String[] args) {

        Class&amp;lt;? extends Set&amp;lt;String&amp;gt;&amp;gt; cl = null;
        try {
            //클래스 로딩 리플렉션
            //비검사 형변환
            cl = (Class&amp;lt;? extends Set&amp;lt;String&amp;gt;&amp;gt;) Class.forName(args[0]);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        Constructor&amp;lt;? extends Set&amp;lt;String&amp;gt;&amp;gt; cons = null;
        try {
            //생성자 사용 리플렉션
            cons = cl.getDeclaredConstructor();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

        Set&amp;lt;String&amp;gt; set;
        try {
            //인스턴스 생성 리플렉션
            //Set 인터페이스 타입으로 참조함.
            set = cons.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        /*
        이 코드에서 보여지는 리플렉션의 단점
        1. 리플렉션에서 발생할 수 있는 런타임 예외가 많다.
        2. 코드가 매우 장황하다. (정말 단순한 기능인 것에 비해)

        리플렉션 tip.
        - 모든 리플렉션 예외를 작성할때는 ReflectiveOperationException 을 사용하면 편하다.

       리플렉션이 적합한 경우
        - 컴파일 타임에 알 수 없는 클래스를 사용해야 할 때
        - 런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성 관리
         */
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/332</guid>
      <comments>https://rockintuna.tistory.com/332#entry332comment</comments>
      <pubDate>Wed, 21 May 2025 23:40:17 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 64 객체는 인터페이스를 사용해 참조하라</title>
      <link>https://rockintuna.tistory.com/331</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.HashMap;
import java.util.Map;

/*
객체는 인터페이스를 사용해 참조하라
 */
public class Item64 {

    /*
    적합한 인터페이스가 있다면
    매개변수, 반환값, 변수, 필드를
    클래스가 아닌 인터페이스 타입으로 선언하자.
     */

    public static void main(String[] args) {

        //프로그램이 훨씬 유연해진다.
        Map&amp;lt;String, String&amp;gt; set = new HashMap&amp;lt;&amp;gt;();

        //ConcuurentHashMap으로 구현체를 바꿔야하는 경우 생성자만 바꾸면 된다.
//        Map&amp;lt;String, String&amp;gt; set = new ConcurrentHashMap&amp;lt;&amp;gt;();
        //이후 참조를 사용하는 코드는 변경할게 없다.
        //using set code...

        /*
        인터페이스 타입을 사용했다면 주변코드에서는 이 인터페이스의 일반 규약 내에서의 기능에 의존하여 사용하자.
        그렇지 않다면 구현체를 바꿀 때 오류가 발생할 수 있다.

        인터페이스 대신 클래스 타입을 사용해도 되는 예
        - String, BigInteger ... 등의 값 클래스
        - 클래스 기반으로 작성된 프레임워크 객체들 (OutputStream 등)
        - 인터페이스에는 없는 특별한 메서드를 제공하는 클래스의 메서드를 꼭 사용해야 할 때
        (ex) PriorityQueue.comparator())

        되도록이면 적절한 인터페이스를 먼저 찾아보고 사용하면 프로그램을 유연하게 만들 수 있다.
        적합한 인터페이스가 없다면 클래스 계층구조 상 필요한 기능을 만족하는 가장 덜 구체적인(가장 상위의) 클래스를 타입으로 사용하자.
         */
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/331</guid>
      <comments>https://rockintuna.tistory.com/331#entry331comment</comments>
      <pubDate>Tue, 20 May 2025 23:09:21 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 63 StringBuilder</title>
      <link>https://rockintuna.tistory.com/330</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import org.springframework.util.StopWatch;

/*
문자열 연결은 느리니 주의하라
 */
public class Item63 {

    public static void main(String[] args) {
        //문자열 연결
        String comb = &quot;abc&quot; + &quot;def&quot;;

        /*
        문자열 연결 연산자로 문자열 n개를 잇는 시간은 n^2에 비례한다. (O(N^2))
        문자열은 불변이기 때문에 연결되는 문자열 모두를 복사해야 한다.

        성능적으로 매우 좋지 못하므로
        연결을 자주 시키거나 여러 문자열을 연결해야 한다면
        문자열 연결 대신 StringBuilder를 사용하자.

        이 둘의 성능 차이는 연결하는 문자열의 수가 많아질수록 커진다.
         */
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        String result1 = &quot;&quot;;
        for (int i = 0; i &amp;lt; 100000; i++) {
            result1 += i;
        }
        stopWatch.stop();
        System.out.println(stopWatch.getTotalTimeMillis());
        //2356 ms

        stopWatch = new StopWatch();
        stopWatch.start();
        //tip.
        //StringBuilder를 초기화할 때 크기를 미리 지정해줄 수 있다면 더 빨리 작업할 수 있다.
        StringBuilder result2 = new StringBuilder(500000);
        for (int i = 0; i &amp;lt; 100000; i++) {
            result2.append(i);
        }
        stopWatch.stop();
        System.out.println(stopWatch.getTotalTimeMillis());
        //3 ms
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/330</guid>
      <comments>https://rockintuna.tistory.com/330#entry330comment</comments>
      <pubDate>Mon, 19 May 2025 22:19:02 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 62 다른 타입이 적절하다면 문자열 사용을 피하라</title>
      <link>https://rockintuna.tistory.com/329</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
다른 타입이 적절하다면 문자열 사용을 피하라
 */
public class Item62 {

    /*
    문자열을 쓰면 안되는 사례
     */

    /*
    1. 문자열은 다른 값 타입을 대신하기에 적합하지 않다.
    입력 받을 데이터가 수치형이라면 -&amp;gt; int, float, BigInteger
    &quot;예 / 아니오&quot; 같은 질문의 답이라면 -&amp;gt; enum, boolean

    2. 문자열은 열거 타입을 대신하기에 적합하지 않다.
    상수를 열거할 때는 enum이 String보다 월등히 낫다.

    3. 문자열은 혼합 타입을 대신하기에 적합하지 않다.
     */
    private String key;
    String compoundKey = this.getClass().getName() + &quot;#&quot; + key;
    /*
     오류 발생 가능성이 높다.
     각 요소를 개별로 접근하기 어렵고 느리다.
     적절한 공통 메서드 (equals, toString, compareTo)를 구현할 수 없다.
     ==&amp;gt;&amp;gt;&amp;gt; 전용 클래스(보통 private 정적 멤버 클래스)를 만들자

    4. 문자열은 권한을 표현하기에 적합하지 않다.
    문자열을 key로 사용한 권한 사용 시나리오는 적합하지 않다.
    서로 동일한 key를 사용하여 오류가 날 수 있고 보안에도 취약하다.

    문자열은 텍스트를 표현하고자 할 때만 사용하자!
     */

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/329</guid>
      <comments>https://rockintuna.tistory.com/329#entry329comment</comments>
      <pubDate>Sun, 18 May 2025 23:28:57 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 61 박싱된 기본 타입 대신 기본 타입 사용</title>
      <link>https://rockintuna.tistory.com/328</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.Comparator;

/*
박싱된 기본 타입보다는 기본 타입을 사용하라
 */
public class Item61 {

    /*
    기본 타입과 박싱된 기본 타입의 차이
    1. 기본 타입은 값만 가지고 있으나 박싱된 기본 타입은 값에 더해 식별성이라는 속성을 가진다.
    &quot;식별성&quot;, 박싱된 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다.
    2. 박싱된 기본 타입은 유효하지 않은 값 (null)을 가질 수 있다
    3. 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 효율적이다.

    JAVA에서는 오토박싱과 오토언박싱 기능을 통해 기본 타입과 박싱된 기본 타입을 구분하지 않고 사용할 수 있지만
    위 차이 때문에 주의하며 사용해야 한다.
     */

    public static Comparator&amp;lt;Integer&amp;gt; naturalOrder =
            (i, j) -&amp;gt; (i &amp;lt; j) ? -1 : (i == j) ? 1 : 0;
    //잘못된점!
    //(i &amp;lt; j)는 오토언박싱되어 값을 비교한다.
    //(i == j)는 값을 비교하는게 아니고 두 객체 참조의 식별성을 검사한다.
    //곧, 박싱된 기본타입에서 == 를 사용하여 값을 비교하면 안된다.

    public static void test(String[] args) {
        Integer i1 = 42;
        Integer i2 = 42;

        int compare = naturalOrder.compare(i1, i2);
        System.out.println(compare); //0이 아닌 1을 return 한다!

        //실무에서 박싱된 기본 타입을 다루는 비교자가 필요하면 Comparator.naturalOrder() 사용
        Comparator&amp;lt;Integer&amp;gt; tComparator
                = Comparator.naturalOrder();
        int compare1 = tComparator.compare(i1, i2);
        System.out.println(compare1); // return 0
    }

    static Integer i;

    public static void main(String[] args) {
        if (i == 42) {
            System.out.println(&quot;i is 42&quot;);
        }
        //NullPointerException이 발생한다.
        // 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 풀린다.
        // (i == 42) 연산에서 i가 int로 언박싱되면서 NullPointerException이 발생하는 것이다.

        /*
        박싱된 기본 타입은 언제쓰나?
        - 컬렉션의 원소, 키, 값으로 사용해야 할 때
        - 리플렉션을 통해 메서드를 호출할 때
         */
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/328</guid>
      <comments>https://rockintuna.tistory.com/328#entry328comment</comments>
      <pubDate>Sat, 17 May 2025 23:31:57 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 60 float, double</title>
      <link>https://rockintuna.tistory.com/327</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.math.BigDecimal;

/*
정확한 답이 필요하다면 float와 double은 피하라
 */
public class Item60 {

    /*
    float과 double은 과학/공학 계산용으로 이진 부동소수점 연산에 쓰이며
    넓은 범위의 수를 정밀한 근사치로 계산하도록 설계되어있다.
    따라서 정확한 결과가 필요할 때는 사용하면 안된다.
     */
    public static void main(String[] args) {
        System.out.println(1.03 - 0.42);
        //결과는 0.61이 아닌 0.6100000000000001

        System.out.println(1 - 9 * 0.1);
        //결과는 0.1이 아닌 0.09999999999999998

        /*
        정확한 계산이 필요하다면 BigDecimal, int 혹은 long을 사용하자
         */
        BigDecimal decimal1 = BigDecimal.valueOf(1.03);
        BigDecimal decimal2 = BigDecimal.valueOf(0.42);
        System.out.println(decimal1.subtract(decimal2));
        //0.61

        /*
        BigDecimal 단점
        - 기본타입보다 사용하기 불편함
        - 훨씬 느림

        따라서 성능이 매우 중요하다면 소수점을 직접 관리하여 int나 long을 사용하는 것도 방법이다.
         */
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/327</guid>
      <comments>https://rockintuna.tistory.com/327#entry327comment</comments>
      <pubDate>Fri, 16 May 2025 23:01:10 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 59 JAVA 표준 라이브러리</title>
      <link>https://rockintuna.tistory.com/326</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.Random;
import java.util.SplittableRandom;
import java.util.concurrent.ThreadLocalRandom;

/*
라이브러리를 익히고 사용하라
 */
public class Item59 {

    static Random random = new Random();

    static int random(int n) {
        return Math.abs(random.nextInt()) % n;
    }
    /*
    이 코드의 문제
    1. n이 2의 제곱수면 같은 수열이 반복된다.
    2. n이 2의 제곱수가 아니면 몇몇 숫자가 더 자주 반환된다.
    3. 지정한 범위 바깥의 수가 종종 튀오나올 수 있다.
    int의 최소값 -2147483648을 Math.abs()에 입력하면 절대값이 반환되지 않는다.
     */

    public static void main(String[] args) {
        int value = Integer.MIN_VALUE;
        System.out.println(value); // -2147483648
        System.out.println(Math.abs(-2147483647)); // -2147483648
    }

    //Random.nextInt()는 위와 같은 오류가 없다.
    static int random2(int n) {
        return random.nextInt(n);
    }

    public static void main2(String[] args) {
        //java 7 부터는 Random 보다 ThreadLocalRandom 을 사용하자
        ThreadLocalRandom random = ThreadLocalRandom.current();
        random.nextLong();

        //thread safe가 필요한 경우 SplittableRandom 을 사용하자
        SplittableRandom splittableRandom = new SplittableRandom();
        splittableRandom.nextLong();
    }

    /*
    ==== 바퀴를 다시 발명하지 말자 ====

    표준 라이브러리(java.util, java.lang, java.io 등)를 사용하면
    - 그 코드를 작성한 전문가의 지식과 다른 프로그래머들의 경험을 활용할 수 있다.
    - 그리고 애플리케이션의 핵심 기능 개발에 집중할 수 있다.
    - 지속적으로 알아서 성능이 개선된다.
    - 추가 기능이 계속 개발된다.
    - 재사용, 공유, 유지보수하기 쉬운 코드가 된다.

     표준 라이브러리는 있는지 몰라서 못쓰는 경우가 많으니
     JAVA 메이저 릴리즈 설명 공시를 읽어보면 좋다.
     특히
     컬렉션 프레임워크 / 스트림 라이브러리
     java.util.concurrent의 동시성 제어

     표준 라이브러리에서 필요한 기능을 찾지 못했다면
     구아바 같은 서드파티 라이브러리를 사용하는 것도 좋다.
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/326</guid>
      <comments>https://rockintuna.tistory.com/326#entry326comment</comments>
      <pubDate>Thu, 15 May 2025 22:16:15 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 58 for 대신 for-each</title>
      <link>https://rockintuna.tistory.com/325</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.Iterator;
import java.util.List;

/*
전통적인 for 문보다는 for-each 문을 사용하라
 */
public class Item58 {

    public static void main(String[] args) {
        List&amp;lt;Integer&amp;gt; list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        //전통적인 배열 순회 for문
        for (int i = 0; i &amp;lt; args.length; i++) {
            String arg = args[i];
        }
        //전통적인 collection 순회 for문
        for (Iterator&amp;lt;Integer&amp;gt; iterator = list.iterator(); iterator.hasNext();) {
            Integer next = iterator.next();
            System.out.println(next);
        }

        /*
        전통적인 for 문의 단점
         - 반복자와 인덱스 변수는 코드를 지저분하게 한다. (어차피 필요한건 담고있는 원소들임)
         - 요소가 많아서 코드 실수로 오류가 발생하기 쉽다.
         - 컬렉션이냐 배열이냐에 따라 형태가 많이 달라진다.

        for-each 문 (향상된 for 문)의 장점
        - 위 문제가 모두 해결됨
        - 중첩 for 문 시 실수가 줄어듦
        - 더 간단 명료하며 성능은 그대로
         */
        for (Integer i : list) {
            System.out.println(i);
        }
        for (String arg : args) {
            System.out.println(arg);
        }

        /*
        for-each 문을 사용할 수 없는 상황
        - 파괴적인 필터링 : 순회하며 remove 할 때 =&amp;gt; removeIf 사용
        - 변형 : 순회하며 원소의 값 일부 혹은 전체를 교체해야 할 때 (인덱스 필요)
        - 병렬 반복 : 여러 컬렉션을 병렬로 순회할 때 (인덱스 필요)

        이 상황이 아니면 for-each문을 사용하자.
         */
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/325</guid>
      <comments>https://rockintuna.tistory.com/325#entry325comment</comments>
      <pubDate>Wed, 14 May 2025 22:43:52 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 57 지역변수의 범위를 최소화하라</title>
      <link>https://rockintuna.tistory.com/324</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.Iterator;
import java.util.List;

/*
지역변수의 범위를 최소화하라
 */
public class Item57 {

    /*
    지역변수 범위가 최소화되었을 때 장점
    - 가독성
    - 유지보수성
    - 오류 방지

    지역변수 범위를 줄이는 방법
    - 가장 처음 쓰일 때 선언하기
    - 선언과 동시에 초기화하기 (try-catch 예외)
    - 메서드를 작게 유지하고 한 가지 기능에 집중하기

    컬렉션을 순회할 때 while보다 for문을 권장
    - for문은 반복 변수의 범위가 반복문 몸체와 괄호 안으로 제한된다.
    - 따라서 while보다 for문을 쓰는 편이 낫다.
    - 똑같은 이름의 변수를 여러 반복문에서 써도 서로 아무런 영향을 주지 않는다.
    - while보다 짦아서 가독성이 좋다.
    - 만약 반복이 종료된 이후에도 변수를 써야 하는게 아니라면 for문을 사용하자.
     */

    public static void main(String[] args) {
        List&amp;lt;Integer&amp;gt; list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        for (Integer i : list) {
            System.out.println(i);
        }
        //compile error
//        i = 3;

        Iterator&amp;lt;Integer&amp;gt; iterator = list.iterator();
        while (iterator.hasNext()) {
            Integer next = iterator.next();
            System.out.println(next);
        }
        iterator = list.iterator();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/324</guid>
      <comments>https://rockintuna.tistory.com/324#entry324comment</comments>
      <pubDate>Tue, 13 May 2025 23:51:08 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 55 Optional</title>
      <link>https://rockintuna.tistory.com/323</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.*;
import java.util.stream.Stream;

/*
옵셔널 반환은 신중히 하라
 */
public class Item55 {
    /*
    메서드에서 특정 조건에서 값을 반환할 수 없는 경우 선택지
    - 예외 : 진짜 예외적일때만 사용해야 함
    - null : null 처리 코드를 작성해야함 (안하면 나중에 오류 발생 가능)
    - Optional

    보통은 T를 반환하지만 특정 조건에서 아무것도 반환하지 않아야 하는 메서드는
    T 대신 Optional&amp;lt;T&amp;gt;를 반환하도록 하면 된다.
    =&amp;gt; 예외보다 유연하고 null보다 오류 가능성이 적음
     */
    public Optional&amp;lt;Integer&amp;gt; max(Collection&amp;lt;Integer&amp;gt; values) {
        if ( values == null || values.isEmpty() ) {
            //빈 Optional 생성
            return Optional.empty();
        }

        int max = Integer.MIN_VALUE;
        //todo
        //값이 있는 Optional 생성
        return Optional.of(max);
    }

    /*
    Optional을 반환하는 메서드에서는 null을 반환하면 안된다.

    Optional은 검사 예외와 취지가 비슷한데, 메서드를 사용하는 사용자에게
    메서드의 반환값이 있을수도 있고 없을수도 있다는 것을 명확하게 알려준다.
    (null이나 비검사 예외는 인지하기 어렵다.)
     */

    /*
    Optional 객체 사용
     */
    public static void main(String[] args) {
        Item55 item55 = new Item55();
        Optional&amp;lt;Integer&amp;gt; optional = Item30.max(List.of(1, 2, 3, 4, 5));

        //값 뽑기
        optional.get(); //비어있는 경우 예외 발생
        optional.orElse(defaultInt()); //비어있는 경우 기본값 (비어있지 않아도 값은 초기화 됨)
        optional.orElseGet(Item55::defaultInt); //비어있는 경우 기본값 (Suppler를 입력하여 초기화 지연)

        //etc
        // - filter
        // - map
        // - flatMap
        // - ifPresent

        //존재 여부 체크
        optional.isPresent();
        optional.isEmpty();

        //존재 여부 체크는 대부분 위 메서드들로 대체할 수 있기 때문에
        //위 메서드를 사용해보는 것이 더 Optional 용법에 맞는 코드이다.
        //이와 관련된 몇가지 팁

        if ( optional.isPresent() ) {
            Integer i = optional.get();
            System.out.println(i * i);
        } else {
            System.out.println(0);
        }
        //==== 같은 동작
        System.out.println(optional.map(i -&amp;gt; i * i).orElse(0));

        Stream&amp;lt;Optional&amp;lt;Integer&amp;gt;&amp;gt; streamOfOptionals = Stream.of(optional);
        streamOfOptionals
                .filter(Optional::isPresent)
                .map(Optional::get);
        //==== 같은 동작
        streamOfOptionals
                .flatMap(Optional::stream);


        /*
        Optional 주의할 점
        - 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다.
        (빈 Optional보다 빈 컨테이너를 반환하는게 좋다)
        - 성능이 매우 중요한 상황에서는 맞지 않을 수 있다.
        - 옵셔널을 컬렉션이나 배열의 키, 값, 원소로 사용하는 건 적절하지 않다.
        - 반환값 이외의 용도로 쓰는 경우는 매우 드물다.
        - 박싱된 기본 타입을 담는 Optional의 경우 특별한 타입의 Optional을 사용하자.
         */
        OptionalInt optionalInt;
        OptionalDouble optionalDouble;
        OptionalLong optionalLong;
    }

    public static int defaultInt() {
        return 1;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/323</guid>
      <comments>https://rockintuna.tistory.com/323#entry323comment</comments>
      <pubDate>Mon, 12 May 2025 23:25:55 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 54 null 대신 빈 컬렉션/배열 반환</title>
      <link>https://rockintuna.tistory.com/322</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/*
null이 아닌, 빈 컬렉션이나 배열을 반환하라
 */
public class Item54 {

    private List&amp;lt;String&amp;gt; names;
    /*
    null을 반환하면
    null을 반환하는 쪽, 받는 쪽 모두에서 null에 대해 특별한 취급을 하는
    추가적인 코드를 작성해야 한다.
     */
    public List&amp;lt;String&amp;gt; names() {
        if (names.isEmpty()) {
            return null;
        } else {
            return names;
        }
    }

    public static void main(String[] args) {
        Item54 item = new Item54();
        if ( item.names() != null ) {
            //todo
        }
    }

    /*
    빈 컬렉션 / 배열을 반환해도 괜찮을 이유
    - 성능적으로 큰 차이 없음
    - 매번 새로 할당하지 않고도 반환할 수 있음 (빈 불변 컬렉션)
    Collections.emptyList()
    Collections.emptySet()
    Collections.emptyMap()
     */
    public List&amp;lt;String&amp;gt; names2() {
        if ( names.isEmpty() ) {
            //성능 최적화에 해당하니 꼭 필요한 경우만 사용하자
            return Collections.emptyList();
        }
        return new ArrayList&amp;lt;&amp;gt;(names);
    }

    //배열 버전
    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    public String[] names3() {
        //리스트가 toArray에 매개변수로 입력되는 배열보다 크면
        //리스트 크기의 배열을 반환
        //그렇지 않으면 매개변수로 입력된 배열의 크기로 반환
        return names.toArray(new String[0]);
    }

    public String[] names4() {
        //또는 미리 할당한 배열 리턴
        return names.toArray(EMPTY_STRING_ARRAY);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/322</guid>
      <comments>https://rockintuna.tistory.com/322#entry322comment</comments>
      <pubDate>Sun, 11 May 2025 22:46:30 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 53 가변인수 메서드</title>
      <link>https://rockintuna.tistory.com/321</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
가변인수는 신중히 사용하라
 */
public class Item53 {

    //가변인수 메서드
    public int min(int... values) {
        if (values.length == 0) {
            throw new IllegalArgumentException();
        }
        int min = values[0];
        for (int i = 1; i &amp;lt; values.length; i++) {
            min = Math.min(min, values[i]);
        }
        return min;
    }

    public static void main2(String[] args) {
        Item53 item53 = new Item53();
        //인수를 넣지 않아도 컴파일 에러가 발생하지 않는다.
        //인수의 길이가 0인 것은 런타임에 알 수 있다.
        item53.min();
    }

    //추천 변경 방식
    //필수 매개변수가 필요하다면 아래와 같이 사용하자
    public int min(int firstArg, int... remainingArgs) {
        int min = firstArg;
        for (int i = 1; i &amp;lt; remainingArgs.length; i++) {
            min = Math.min(min, remainingArgs[i]);
        }
        return min;
    }

    /*
    가변인수 메서드는 내부적으로 인수의 개수와 길이가 같은 배열을 생성한다.
    그렇기 때문에 성능적으로 좋지 않을 수 있다.
    이를 개선하기 위해서는 다중정의를 사용하면 좋다.

    이를테면 아래와 같은 가변인수 메서드에서
     */
    public void fooOrg(int... args) {}

    //아래와 같이 변경하는 것이다.
    //인수의 개수 3개까지는 가변인수를 사용하지 않아 별도의 배열을 생성하지 않는다
    //4개 이상인 경우에만 가변인수를 사용한다.
    public void foo(int arg1) {}
    public void foo(int arg1, int arg2) {}
    public void foo(int arg1, int arg2, int arg3) {}
    public void foo(int arg1, int arg2, int arg3, int... args) {}

    public static void main(String[] args) {
        Item53 item53 = new Item53();
        //클라이언트에서 사용할때는 동일하게 사용하면 된다.
        item53.fooOrg(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        item53.foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/321</guid>
      <comments>https://rockintuna.tistory.com/321#entry321comment</comments>
      <pubDate>Sat, 10 May 2025 22:13:38 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 52 오버로딩 신중하게</title>
      <link>https://rockintuna.tistory.com/320</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.ArrayList;
import java.util.List;

/*
다중정의는 신중히 사용하라
 */
public class Item52 {
    /*
    다중정의 = 오버로딩

    재정의한 메서드는 동적으로 선택되는 한편
    다중정의한 메서드는 정적으로 선택된다.
    즉, 컴파일 타임에 입력되는 매개변수의 타입에 따라 이미 정해진다.

    이게 문제가 되는 이유는
    개발자가 선택되길 의도한 메서드가 선택되지 않을 수 있기 때문이다.
    따라서 프로그램이 오동작하기 쉬워진다.

    다중정의가 선택되는 알고리즘이 어렵고
    개발자가 이를 이해하고 어떤 메서드가 선택될지를 구분하는 것이 어려워지기 때문에 개발이 어려워진다.
     */
    public static String classify(String string) {
        return &quot;String&quot;;
    }

    public static String classify(Integer integer) {
        return &quot;Integer&quot;;
    }

    public static String classify(Object object) {
        return &quot;Object&quot;;
    }

    public static void main(String[] args) {
        List&amp;lt;Object&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
        String string = &quot;String&quot;;
        list.add(string);
        Integer integer = Integer.valueOf(1);
        list.add(integer);
        Object object = new Object();
        list.add(object);

        /*
        결과는 String Integer Object
         */
        System.out.println(classify(string));
        System.out.println(classify(integer));
        System.out.println(classify(object));

        /*
        결과는 Object Object Object
         */
        for (Object o : list) {
            System.out.println(classify(o));
        }
    }
    /*
    혼동을 일으키는 다중정의를 피하려면
     - 매개변수의 개수가 같은 다중정의는 만들지 말자
     (대신 다른 이름의 메서드 사용하며, 생성자라면 정적 팩터리 메서드를 사용하자)
     - 매개변수의 개수가 같아야 한다면 서로 근본적으로 다른 타입을 사용하자.
     (근본적으로 다르다는 것은 서로 형변환 될 수 없다는 것을 의미함)
     - 근본적으로 같은 타입을 궂이궂이 사용해야 한다면 같은 객체를 입력받는 다중정의 메서드 모두 동일하게 동작해야 한다.
     이 방법은 덜 특수한(더 일반적인) 메서드로 일을 넘기는 것 (forward)
     - 참조 타입을 사용하는 다중정의 조심 (int, Object)
     - 서로 다른 함수형 인터페이스이더라도 같은 위치의 인수로 받으면 안된다.
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/320</guid>
      <comments>https://rockintuna.tistory.com/320#entry320comment</comments>
      <pubDate>Fri, 9 May 2025 21:58:41 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 51 메서드 시그니처 설계 요령</title>
      <link>https://rockintuna.tistory.com/319</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
메서드 시그니처를 신중히 설계하라
 */
public class Item51 {

    /*
    메서드 설계 요령

    1. 메서드 이름을 신중히
    - 표준 명명 규칙 따르기
    - 동일한 패키지에 속한 다른 메서드와 일관되게
    - 개발자들에게 일반적으로 받아들여지는 이름 사용
    - 긴 이름 피하기

    2. 편의 메서드를 너무 많이 만들지 말자
    클래스 또는 인터페이스에 메서드 수가 너무 많아지면 이해, 사용, 문서화, 테스트, 유지보수가 어려워진다.
    따라서 편의를 위한 메서드는 아주 자주 쓰이는게 아니라면 만들지 말자

    3. 매개변수 목록은 짧게
    - 4개 이하
    - 동일한 타입의 매개변수 여러개는 사용하기 어렵고 오류 발생 가능성 있음
    =&amp;gt; 여러 메서드로 쪼개기
    =&amp;gt; 도우미 클래스 (일반적으로 정적 멤버 클래스)
    =&amp;gt; 빌더 패턴 응용

    4. 매개변수의 타입으로 클래스보다는 인터페이스
    인터페이스를 매개변수로 사용하면 메서드 사용이 유연해진다.

    5. boolean 보다는 원소 2개짜리 enum 이 낫다.
    */
    private TemperatureScale temperatureScale;
    private boolean isFahrenheit;

    public enum TemperatureScale { FAHRENHEIT, CELSIUS }

    public static Item51 of(boolean isFahrenheit) {
        Item51 item51 = new Item51();
        item51.isFahrenheit = isFahrenheit;
        return item51;
    }

    public static Item51 of(TemperatureScale temperatureScale) {
        Item51 item51 = new Item51();
        item51.temperatureScale = temperatureScale;
        return item51;
    }

    /*
    위 boolean 메서드 보다는 아래 enum 메서드가 더 직관적이다.
    선택지를 추가하기도 쉽다.
    enum 의 장점을 살릴 수도 있다. (상수별 메서드 구현, 전략 열거 타입 패턴 등)
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/319</guid>
      <comments>https://rockintuna.tistory.com/319#entry319comment</comments>
      <pubDate>Thu, 8 May 2025 20:12:53 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 50 방어적 복사본</title>
      <link>https://rockintuna.tistory.com/318</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;


import java.util.Date;

/*
적시에 방어적 복사본을 만들라
 */
public class Item50 {

    /*
    자바는 비교적 안전한 언어임에 분명하지만,
    그래도 방어적으로 프로그래밍 할 필요가 있다.
     */

    //Date는 가변 타입이다.
    //불변으로 하고싶으면 Date 대신 Instant 이나 LocalDateTime,ZonedDateTime 을 사용하자.
    //Date는 낡은 API이므로 사용하지 말자.
    private final Date start;
    private final Date end;
    private boolean isCopied;

    public Item50(Date start, Date end, boolean isCopied) {
        this.start = start;
        this.end = end;
        this.isCopied = isCopied;
    }

    //방어적 복사본을 사용한 생성자
    public Item50(Date start, Date end) {

        //방어적 복사에 clone을 사용하지 않는다.
        //확장 가능한 타입의 경우 clone 메서드가 재정의될 수 있어 위험하기 때문
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());

        //유효성 검사...
        //유효성 검사를 복사 후에 하는 이유는
        //이 찰나의 시간 안에 다른 스레드에서 가변 인수를 변경할 수 있기 때문 (TOCTOU 공격)
    }

    public Date start() {
        return start;
    }

    public Date copiedStart() {
        return new Date(start.getTime());
    }

    public Date end() {
        return end;
    }

    public Date copiedEnd() {
        return new Date(end.getTime());
    }

    public static void main(String[] args) {
        Date start = new Date();
        Date end = new Date();
        Item50 item1 = new Item50(start, end, true);
        //가변 타입 필드는 쉽게 수정된다.
        end.setYear(1992);
        assert item1.end().getYear() == 1992;
        item1.end().setYear(1994);
        assert item1.end().getYear() == 1994;

        Item50 item2 = new Item50(start, end);
        //방어적 복사본을 사용한 생성자와 메서드는 쉽게 수정되지 않는다.
        end.setYear(2000);
        assert item2.end().getYear() != 2000;
        item2.copiedEnd().setYear(2020);
        assert item2.end().getYear() != 2020;
    }

    /*
    가변인 객체를 내부에 저장하거나 클라이언트에 반환할 때는 반드시 심사숙고해야한다.
    안심할 수 없다면 방어적 복사본을 사용하자.
    방어적 복사는 성능 저하가 따르고 항상 쓸 수 있는게 아니므로 되도록 불변 객체를 사용하자.
    신뢰할 수 있는 클라이언트라면 가변 객체를 수정하지 않도록 문서화하자.
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/318</guid>
      <comments>https://rockintuna.tistory.com/318#entry318comment</comments>
      <pubDate>Wed, 7 May 2025 23:28:00 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 49 매개변수 유효성 검사</title>
      <link>https://rockintuna.tistory.com/317</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.math.BigInteger;
import java.util.Objects;

/*
매개변수가 유효한지 검사하라
 */
public class Item49 {

    private String name;

    /*
    오류는 빨리 발견되면 좋다.

    메서드의 매개변수에 특별한 제약이 있는 경우,
    매개변수 값이 잘못됐을 때 본문이 시작되기 전에 예외 처리를 하고 (생성자에서 특히),
    public protected 메서드는 이 예외를 문서화 해야 한다.
     */

    /**
     *
     * @param m
     * @return 현재 값 mod m
     * @throws ArithmeticException m이 0보다 작거나 같으면 발생
     */
    public BigInteger mod(BigInteger m) {
        if (m.signum() &amp;lt;= 0) {
            throw new ArithmeticException(&quot;Modulus must be positive&quot;);
        }
        //todo
        return m;
    }

    /*
    null 검사를 할때는 Objects.requireNonNull 메서드를 사용하면 편리하다.
     */
    public void setName(String name) {
        this.name = Objects.requireNonNull(name, &quot;name is null.&quot;);
    }

    public static void main(String[] args) {
        Item49 item49 = new Item49();
        item49.setName(null);
    }

    /*
    public이 아닌 메서드는 단언문(assert)을 사용해 매개변수 유효성을 검증할 수 있다.
    단언문은 실패하면 AssertionError 를 던지며,
    런타임에는 아무런 효과나 성능 저하가 없다. (java 명령줄에 -ea 또는 --enableassertions 플래그를 설정하면 영향을 준다.)

    검사를 안해도 되는 경우
    - 검사 비용이 지나치게 높거나 실용적이지 않을 때
    - 처리 과정에서 암묵적으로 검사가 수행될 때
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/317</guid>
      <comments>https://rockintuna.tistory.com/317#entry317comment</comments>
      <pubDate>Tue, 6 May 2025 23:45:45 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 48 스트림 병렬화</title>
      <link>https://rockintuna.tistory.com/316</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.math.BigInteger;
import java.util.List;
import java.util.stream.Stream;

import static java.math.BigInteger.ONE;
import static java.math.BigInteger.TWO;

/*
스트림 병렬화는 주의해서 적용하라
 */
public class Item48 {
    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; list = List.of(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot;, &quot;f&quot;, &quot;g&quot;, &quot;h&quot;);

        /*
        parallel() 메서드를 사용하면 스트림 파리프라인을 병렬 실행할 수 있게 된다.
         */
        list.stream()
                .map(String::toLowerCase)
                .parallel()
                .forEach(System.out::println);

        /*
        하지만 스트림 병렬화는 주의해서 사용해야 하는데,
        아래 코드처럼
        데이터 소스가 Stream.iterate거나
        중간 연산으로 limit을 쓰면
        파이프라인 병렬화로는 성능 개선을 기대할 수 없다.
         */
        primes().map(p -&amp;gt; TWO.pow(p.intValueExact()).subtract(ONE))
                .filter(mersenne -&amp;gt; mersenne.isProbablePrime(50))
                .limit(20)
                .parallel()
                .forEach(System.out::println);
    }

    static Stream&amp;lt;BigInteger&amp;gt; primes() {
        return Stream.iterate(TWO, BigInteger::nextProbablePrime);
    }

    /*
    일반적으로 병렬화를 사용하면 좋은 스트림
    - 데이터 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스
    - 또는 배열, int 범위, long 범위일
    - 축소 종단 연산 (reduce, min, max, count, sum, anyMatch, allMatch, noneMatch)
    (반면 가변 축소 collect는 병렬화에 적합하지 않다.)

    스트림을 잘못 사용하면 성능뿐만 아니라 결과 자체가 잘못되거나 예상치 못한 동작이 발생할 수 있다.(safety failure)

    스트림 병렬화는 오직 성능 최적화 수단이다.
    계산이 정확한지, 그리고 성능이 좋아졌는지를 꼭 테스트 해야한다.
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/316</guid>
      <comments>https://rockintuna.tistory.com/316#entry316comment</comments>
      <pubDate>Mon, 5 May 2025 22:13:04 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 47 스트림 대신 컬렉션 반환</title>
      <link>https://rockintuna.tistory.com/315</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/*
반환 타입으로는 스트림보다 컬렉션이 낫다
 */
public class Item47 {

    /*
    스트림은 for-each 문에서 사용될 수 없고 iterable은 스트림으로 사용할 수 없다.
    자바는 이 둘의 변환을 지원해주지 않지만 쉽게 구현할 수 있다.
     */

    //stream -&amp;gt; iterable
    public static &amp;lt;E&amp;gt; Iterable&amp;lt;E&amp;gt; iterableOf(Stream&amp;lt;E&amp;gt; stream) {
        return stream::iterator;
    }
    //iterable -&amp;gt; stream
    public static &amp;lt;E&amp;gt; Stream&amp;lt;E&amp;gt; streamOf(Iterable&amp;lt;E&amp;gt; iterable) {
        return StreamSupport.stream(iterable.spliterator(), false);
    }

    /*
    굳이 반환이 필요 없는 경우,
    즉, 오직 스트림 파이프라인에서만 사용될 걸 알거나, 반복문에서만 쓰일 걸 안다면
    각각 Stream, Iterable로 반환하자

    만약 공개 API라면 만약 둘 다 지원하게 해주는게 옳다.
    Collection 인터페이스는 Iterable의 하위 타입이면서 stream 메서드를 제공한다.
    따라서 원소 시퀀스를 반환하는 공개 API의 반환 타입은 Collection이나 그 하위타입을 쓰자.

    위에 있는 메서드 처럼 클라이언트에서 변환을 할 수는 있겠지만,
    Collection을 반환하여 사용하는 것 보다 성능적으로 좋지 않다.
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/315</guid>
      <comments>https://rockintuna.tistory.com/315#entry315comment</comments>
      <pubDate>Sun, 4 May 2025 23:37:24 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 46 스트림에는 순수 함수 사용, 수집기</title>
      <link>https://rockintuna.tistory.com/314</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.*;
import java.util.stream.Stream;

import static java.util.stream.Collectors.*;

/*
스트림에서는 부작용 없는 함수를 사용하라
 */
public class Item46 {

    /*
    스트림의 각 변환 단계는 순수 함수여야 한다.

    순수 함수 :
    오직 입력만이 결과에 영향을 주는 함수
    함수 안에서 외부에 가변 참조를 하지 않아야 하며
    이렇게 하려면 모든 스트림 연산에 건네는 모든 함수 객체는 부작용이 없어야 한다.
    */
    public static void main(String[] args) {
        /*
        아래 코드는 정상적으로 동작하긴 하지만
        스트림 답지 못하다. 즉, 반복문을 단순히 스트림으로 표현한 것 뿐이다.
        스트림 내부에서 외부 상태 (freq 맵)을 변경하고 있다.
        */
        File file = null;
        Map&amp;lt;String, Long&amp;gt; freq = new HashMap&amp;lt;&amp;gt;();
        try (Stream&amp;lt;String&amp;gt; words = new Scanner(file).tokens()) {
            words.forEach(
                    word -&amp;gt; {
                        freq.merge(word.toLowerCase(), 1L, Long::sum);
                    }
            );
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }

        /*
        변경된 아래 코드는 결과는 동일하지만 스트림 다운 진행을 보여준다.
        */
        Map&amp;lt;String, Long&amp;gt; freq2;
        try (Stream&amp;lt;String&amp;gt; words = new Scanner(file).tokens()) {
            freq2 = words.collect(groupingBy(String::toLowerCase, counting()));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }

        //스트림 팁
        // forEach 연산은 스트림 연산중 가장 &quot;덜&quot; 스트림답다.
        // forEach 연산은 스트림 계싼 결과를 보고할 때만 사용하고 계산에는 쓰지 말자.


        /*
        스트림에서 수집기(collector)는 collect()에서 주로 사용된다.
        수집기는 스트림에서 사용될 때 스트림의 원소들을 객체 하나(일반적으로 collection)에 취합하는 역할을 한다.

        스트림에서 Collection을 생성하는 수집기를 반환하는 메서드
        - toList()
        - toSet()
        - toCollection(collectionFactory)
         */
        List&amp;lt;String&amp;gt; collect = freq2.keySet().stream()
                .sorted(Comparator.comparingLong(freq2::get).reversed())
                .limit(10)
                .collect(toList());

    }

    /*
        스트림에서 Map을 생성하는 수집기를 반환하는 메서드
        - toMap(keyMapper, valueMapper)
        - toMap(keyMapper, valueMapper, mergeFunction)
        - toMap(keyMapper, valueMapper, mergeFunction, mapFactory)
        - toConcurrentMap(keyMapper, valueMapper)
        - groupingBy(분류 함수 classifier) : 분류함수는 요소의 키를 구하는데 사용되며, Map의 value는 요소들의 List이다.
        - groupingBy(분류 함수 classifier, downstream) : 추가로 downstream 수집기를 입력하면
        수집기가 생성하는 맵의 value로서 요소 리스트 이외에 다른 구조, 값을 사용할 수 있다.
        - groupingBy(분류 함수 classifier, mapFactory, downstream)
        - groupingByConcurrent(분류 함수 classifier)
        - partitioningBy(predicate) : 키가 boolean인 Map을 반환하는 수집기
        downstream 전용 수집기 메서드
        - counting()
        - summing + Int() || Long() || Double()
        - averaging + Int() || Long() || Double()
        - summarizing + Int() || Long() || Double()
        - ...
        CharSequence 요소 전용 문자열 생성기
        - joining()
        - joining(delimiter)
     */

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/314</guid>
      <comments>https://rockintuna.tistory.com/314#entry314comment</comments>
      <pubDate>Sun, 4 May 2025 00:18:48 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 45 스트림</title>
      <link>https://rockintuna.tistory.com/313</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

/*
스트림은 주의해서 사용하라
 */
public class Item45 {

    /*
    스트림 : 원소의 시퀀스
    스트림 파이프라인 : 원소들로 수행하는 연산 단계

    스트림 파이프라인의 단계
    소스 스트림 - (중간 연산) - 종단 연산
    종단 연사이 없는 스트림은 아무 작업도 하지 않는다.

    스트림은 잘 쓰면 프로그램을 짧고 깔끔하게 만들 수 있지만,
    그렇다고 스트림을 너무 과용하면 프로그램이 읽거나 유지보수하기 어려워진다.
    모든 반복문을 스트림으로 변경하지는 않아도 되니, 가독성이 더 좋은 경우에만 스트림으로 변환하자.
     */

    /*
    스트림 주의사항
     */

    //도우미 메서드를 활용하는 것은 스트림을 깔끔하게 만드는 것에 도움이 된다.

    //스트림은 원소로 기본 타입 int, long, double만 지원한다.
    //char는 지원하지 않으며
    //String의 chars() 메서드에서 반환하는 스트림의 원소는 int이다.
    //char 값을 처리할 때는 스트림을 삼가는 편이 좋다.
    public static void main(String[] args) {
        IntStream intStream = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        LongStream longStream = LongStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        DoubleStream doubleStream = DoubleStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        IntStream chars = &quot;Lee JeongIn&quot;.chars();
    }

    /*
    스트림은 함수 객체로 처리 표현
    반복문은 코드 블록으로 처리 표현

    함수 객체로는 불가능한 것들
    - 범위 안의 지역변수를 읽고 수정할 수 없다. (final or 사실상 final인 변수만 읽을 수 있고 수정은 불가능)
    - return, break, continue 사용 불가능
    - 코드 블록은 메서드 선언에 명시된 검사 예외를 던질 수 있음

    스트림이 잘 맞는 경우
    - 원소들의 시퀀스를 일관되게 변환
    - 원소들의 시컨스를 필터링
    - 원소들의 시퀀스를 하나의 연산을 사용해 결합
    - 원소들의 시퀀스를 컬렉션에 모은다.
    - 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는다.

    이전 단계의 원소가 필요한 코드의 경우, 스트림과는 어울리지 않는다.

    스트림 사용 팁
    - 스트림을 반환하는 메서드 이름은 원소의 복수 명사로 (ex) primes
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/313</guid>
      <comments>https://rockintuna.tistory.com/313#entry313comment</comments>
      <pubDate>Fri, 2 May 2025 22:29:01 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 44 표준 함수형 인터페이스</title>
      <link>https://rockintuna.tistory.com/312</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.function.*;

/*
표준 함수형 인터페이스를 사용하라
 */
public class Item44 {

    /*
    java.util.function 패키지에는 다양한 용도의 표준 함수형 인터페이스가 있다.
    용도에 맞는게 있다면 직접 구현하지 말고 이걸 활용하자

    표준 함수형 인터페이스의 장점
     - API가 다루는 개념의 수가 줄어들어 익히기 쉬워진다.
     - 유용한 디폴트 메서드를 많이 제공하여 다른 코드와 상호운용성이 좋다.

     */

    // 기본적인 표준 함수형 인터페이스
    //UnaryOperator : 반환값과 인수의 타입이 동일 (인수 1개)
    UnaryOperator&amp;lt;String&amp;gt; identityOperator = UnaryOperator.identity();

    //UnaryOperator : 반환값과 인수의 타입이 동일 (인수 2개)
    BinaryOperator&amp;lt;String&amp;gt; maxByOperator = BinaryOperator.maxBy(String::compareTo);

    //Predicate : 인수하나를 받아 boolean 반환
    Predicate&amp;lt;String&amp;gt; isEmptyPredicate = o -&amp;gt; !o.isEmpty();

    //Function : 인수와 반환타입이 다름
    Function&amp;lt;String, Integer&amp;gt; stringToIntFunction = Integer::valueOf;
    //Operator는 Function&amp;lt;T, T&amp;gt;를 상속받는다.

    //Supplier : 인수 없음
    Random random = new Random();
    Supplier&amp;lt;Integer&amp;gt; randomIntegerSupplier = () -&amp;gt; random.nextInt();

    //Consumer : 인수하나를 받고 반환값이 없음
    List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
    Consumer&amp;lt;String&amp;gt; addToListConsumer = a -&amp;gt; list.add(a);

    //기본 인터페이스들은 저마다 기본 타입 int, long, double용으로 변형 인터페이스가 존재한다.
    //ex) IntPredicate
    IntPredicate isNotZeroPredicate = value -&amp;gt; value != 0;

    //Function 인터페이스는 기본 타입용 변형이 추가로 더 있다.
    // 입력 및 출력이 모두 기본타입
    LongToIntFunction longToIntFunction;
    IntToDoubleFunction intToDoubleFunction;
    // 등등...
    // 출력이 기본타입
    ToLongFunction&amp;lt;String&amp;gt; toLongFunction;
    ToIntFunction&amp;lt;Item44&amp;gt; toIntFunction;

    //기본 인터페이스에서 인수를 하나 추가로 받는 변형
    BiPredicate&amp;lt;String, Long&amp;gt; biPredicate;
    BiConsumer&amp;lt;String, Integer&amp;gt; biConsumer;
    BiFunction&amp;lt;String, Integer, Long&amp;gt; biFunction;

    //Supplier boolean 반환 변형
    BooleanSupplier booleanSupplier = () -&amp;gt; random.nextBoolean();

    /*
    주의점
    - 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지 말자
    --&amp;gt; 성능 저하

    표준 함수형 인터페이스 대신 직접 구현해야 하는 경우
    - 요구사항을 지원하지 않을 때
    - 훨신 명확한 명칭을 사용하는게 더 나을 때 (ex) Comparator
    - 반드시 따라야 하는 규약이 추가로 있을 때
    - 유용한 디폴트 메서드가 있으면 좋을 때
     */


    /*
    @FunctionalInterface 애너테이션의 의미
    - 이 인터페이스는 람다용으로 설계됨
    - 추상 메서드 하나 (컴파일러가 검사)
    - 메서드 추가 불가능 (컴파일러가 검사)

    만약 함수형 인터페이스를 직접 구현한다면, @FunctionalInterface를 꼭 달자.
     */
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/312</guid>
      <comments>https://rockintuna.tistory.com/312#entry312comment</comments>
      <pubDate>Thu, 1 May 2025 23:00:33 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 43 메서드 참조</title>
      <link>https://rockintuna.tistory.com/311</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/*
람다보다는 메서드 참조를 사용하라
 */
public class Item43 {

    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; words = Arrays.asList(&quot;ab&quot;, &quot;b&quot;, &quot;ce&quot;, &quot;dfe&quot;, &quot;easd&quot;);

        //람다
        Collections.sort(words, (o1, o2) -&amp;gt; Integer.compare(o1.length(), o2.length()));

        //메서드 참조
        Collections.sort(words, Comparator.comparingInt(String::length));

        //람다
        Collections.sort(words,
                (o1, o2) -&amp;gt; {
                    //first order by length, and order by first character
                    int compare = Integer.compare(o1.length(), o2.length());
                    if (compare == 0) {
                        return Character.compare(o1.charAt(0), o2.charAt(0));
                    } else {
                        return compare;
                    }
        });

        //람다로 작성되는 코드를 메서드로 만들고 람다 대신 그 메서드 참조를 사용해보자
        //메서드 참조
        Collections.sort(words, Item43::compareByLengthAndFirstCharacter);
    }

    private static int compareByLengthAndFirstCharacter(String o1, String o2) {
        //first order by length, and order by first character
        int compare = Integer.compare(o1.length(), o2.length());
        if (compare == 0) {
            return Character.compare(o1.charAt(0), o2.charAt(0));
        } else {
            return compare;
        }
    }

    /*
        메서드 참조는 람다보다 더 간결하다.
        특히 매개변수가 많을 때 더욱 효과가 크다.

        항상 그런것은 아니다.
        - 메서드와 람다가 같은 클래스에 있는 경우
        - 정적 팩터리 메서드
        의 경우 람다가 더 짧고 명확하다. o
    */

    /*
    메서드 참조 유형
     */
    private void methodReferenceType() {
        List&amp;lt;String&amp;gt; words = Arrays.asList(&quot;ab&quot;, &quot;b&quot;, &quot;ce&quot;, &quot;dfe&quot;, &quot;easd&quot;);

        // 정적 메서드 참조
        List&amp;lt;Integer&amp;gt; list1 = words.stream().map(Integer::parseInt).toList();

        //한정적 인스턴스 메서드 참조
        List&amp;lt;Boolean&amp;gt; list2 = words.stream().map(new String(&quot;abcdefg&quot;)::contains).toList();

        //비한정적 인스턴스 메서드 참조
        List&amp;lt;String&amp;gt; list3 = words.stream().map(String::toLowerCase).toList();
        //정적 메서드 참조와 비슷해보이지만
        //정적 메서드 참조는 인스턴스가 메서드 인수로 사용되고
        //비한정적 인스턴스 메서드 참조는 주어진 인스턴스의 메서드를 사용한다.

        //클래스 생성자
        class Tag {
            private String name;

            public Tag(String name) {
                this.name = name;
            }
        }

        List&amp;lt;Tag&amp;gt; list4 = words.stream().map(Tag::new).toList();

        //배열 생성자
        List&amp;lt;char[]&amp;gt; list5 = words.stream().map(str -&amp;gt; str.length())
                .map(char[]::new).toList();
        //생성자 참조는 팩터리 객체로 사용된다.
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/311</guid>
      <comments>https://rockintuna.tistory.com/311#entry311comment</comments>
      <pubDate>Wed, 30 Apr 2025 22:45:08 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 42 람다</title>
      <link>https://rockintuna.tistory.com/310</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/*
익명 클래스보다는 람다를 사용하라
 */
public class Item42 {


    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; words = Arrays.asList(&quot;ab&quot;, &quot;b&quot;, &quot;ce&quot;, &quot;dfe&quot;, &quot;easd&quot;);

        //익명 클래스로 생성한 함수 객체
        Collections.sort(words, new Comparator&amp;lt;String&amp;gt;() {
            @Override
            public int compare(String o1, String o2) {
                return Integer.compare(o1.length(), o2.length());
            }
        });

        /*
        예전 버전의 자바에서는 위와 같이 작성해야 했으나 코드가 길다는 단점이 있었다.
        자바 8부터 람다로 함수형 인터페이스를 구현하는 기능을 사용할 수 있다.

        함수형 인터페이스 :
        추상 메서드 하나만 있는 인터페이스,
        ex) Comparator
         */
        //람다식으로 변경
        Collections.sort(words, (o1, o2) -&amp;gt; Integer.compare(o1.length(), o2.length()));
        /*
        람다식은 작은 규모의 함수 객체 구현을 매우 간결하게 해준다.
        타입 추론을 통해 함수 객체 타입 Comparator, 매개변수 타입 String, 반환값 타입 int가 추론되었다.
         */

        //비교자 생성 정적 메서드, method reference, List.sort() 사용
        words.sort(Comparator.comparingInt(String::length));

        /*
        람다식을 안써야할 때
        - 코드가 세줄 이상
        - 코드에 설명이 필요할 때
        람다식을 못쓰는 상황
        - 인터페이스가 아닐 때
        - 인터페이스인데 추상 메서드가 여러개일 때
        - 인스턴스 자신을 참조(this)할 필요가 있을 때
        =&amp;gt; 익명 클래스

        주의 : 람다, 익명 클래스는 직렬화 하면 안됨
         */

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/310</guid>
      <comments>https://rockintuna.tistory.com/310#entry310comment</comments>
      <pubDate>Tue, 29 Apr 2025 22:14:24 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 41 마커 인터페이스</title>
      <link>https://rockintuna.tistory.com/309</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.io.Serializable;

/*
정의하려는 것이 타입이라면 마커 인터페이스를 사용하라
 */
public class Item41 implements Serializable {

    /*
    마커 인터페이스:
    아무 메서드 없이 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스
    ex) Serializable 는 아무 메서드가 없는 마커 인터페이스이다.
    어떤 객체가 특정 속성이나 능력을 가진 것임을 표시하고 싶을 때 사용한다.
    */
    public interface Serializable {}  // 마커 인터페이스

    /*

    vs 마커 애너테이션
    - 마커 인터페이스는 타입으로 사용할 수 있음 =&amp;gt; 컴파일 타임 검증에 유용하게 사용
    - 적용 대상을 더 정밀하게 지정할 수 있음
    예를들어 인터페이스 MajorType을 구현한 클래스에만 마커를 적용하고 싶다면
    마커 애너테이션은 이렇다할 방법이 없지만
    마커 인터페이스를 사용할 때는 MajorType을 상속받은 마커 인터페이스를 구현하면 된다.
     */
    //제한
    public interface MajorType {
        String majorMethod();
    }

    //마커 인터페이스
    public interface Major extends MajorType {
    }

    //마킹된 클래스
    class MajorTypeConcreteClass implements Major {
        @Override
        public String majorMethod() {
            return &quot;&quot;;
        }
    }


    /*
    이런 장점을 사용할 일이 없다면 마커 애너테이션이 나을 수 있다.

    내가 사용하는 프레임워크에서 애너테이션 시스템을 강력하게 사용할 수 있다면 마커 애너테이션,
    타입 시스템을 더 잘 사용하고, 인스턴스 타입으로 사용하고 싶다면 마커 인터페이스
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/309</guid>
      <comments>https://rockintuna.tistory.com/309#entry309comment</comments>
      <pubDate>Mon, 28 Apr 2025 22:11:07 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 40 @Override 애너테이션</title>
      <link>https://rockintuna.tistory.com/308</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.Objects;

/*
@Override 애너테이션을 일관되게 사용하라
 */
public class Item40 {

    private int x;
    private int y;

    public boolean equals(Item40 o) {
        if (o == null || getClass() != o.getClass()) return false;
        return x == o.x &amp;amp;&amp;amp; y == o.y;
    }

    public int hashCode() {
        return Objects.hash(x, y);
    }

    /*
    위 코드는 얼핏 제대로 동작할 것 같지만, 메서드 equals는 의도한 대로 동작하지 않을 것이다.
    위 equals 메서드는 Object equals 메서드를 제정의하지 않는다.
    Object equals 메서드를 재정의하려면 매개변수 타입을 Object로 해야 한다.
    하지만 컴파일러는 위와 같은 상황에서 이 내용에 대한 경고를 알려주지 못한다.

    우리는 컴파일러에게 이 메서드가 Object equals를 재정의한다는 의도를 명시하여,
    컴파일러가 제대로 검사를 할 수 있도록 할 수 있다.
    예를 들어 아래 코드는 컴파일 단계에서 에러가 발생한다.
    method does not override or implement a method from a supertype
     */
//    @Override
//    public boolean equals2(Item40 o) {
//        if (o == null || getClass() != o.getClass()) return false;
//        return x == o.x &amp;amp;&amp;amp; y == o.y;
//    }

    //올바르게 수정
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Item40 item40)) return false;
        return x == item40.x &amp;amp;&amp;amp; y == item40.y;
    }

    public static void main(String[] args) {

    }

    /*
    즉, @Override 애너테이션을 일관되게 사용하라는 말은
    상위 클래스 또는 인터페이스의 메서드를 재정의할 때는 예외없이 @Override 애너테이션을 달라는 말이다.
    (추상 메서드를 재정의할 때는 달지 않더라도 의미 없지만 그래도 일괄적으로 붙이는게 좋다면 상관 없다.)
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/308</guid>
      <comments>https://rockintuna.tistory.com/308#entry308comment</comments>
      <pubDate>Sun, 27 Apr 2025 22:48:22 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 39 애너테이션</title>
      <link>https://rockintuna.tistory.com/307</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/*
명명 패턴보다 애너테이션을 사용하라
 */
public class Item39 {

    /*
    명명 패턴의 단점
    - 오타로 인한 기능 오류
    - 올바른 요소에만 사용되리라 보증할 방법 없음
    - 프로그램 요소를 매개변수로 전달할 마땅한 방법이 없음
     */

    /*
    애너테이션은 위의 모든 문제를 해결해준다.

    메타 에너테이션
    - @Retention(RetentionPolicy.RUNTIME) : Runtime에도 유지
    - @Target(ElementType.METHOD) : 메서드에 사용

    매개변수 없는 애너테이션 : 마커 애너테이션
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test {

    }

    /*
    매개변수를 받는 애너테이션
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface ExceptionTest {
        Class&amp;lt;? extends Throwable&amp;gt; value();
    }

    @ExceptionTest(RuntimeException.class)
    private static void m1() {

    }

    /*
    배열 매개변수를 받는 애너테이션
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface ArrayExceptionTest {
        Class&amp;lt;? extends Throwable&amp;gt;[] value();
    }

    @ArrayExceptionTest({RuntimeException.class})
    private static void m2() {

    }
    //배열 매개변수 대신 반복 가능 애너테이션을 사용할수도 있음

    /*
    다른 프로그래머가 소스코드에 추가 정보를 제공할 수 있는 도구를 만드는 일을 한다면 적당한 애너테이션 타입을 정의하여 제공하는 것이 좋다.
    이런 도구 제작자를 제외하고는 거의 애너테이션을 정의할 일이 없지만
    자바에서 제공하는 애너테이션 타입들을 사용하긴 해야하므로 이런 내용을 알고 있으면 좋다.
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/307</guid>
      <comments>https://rockintuna.tistory.com/307#entry307comment</comments>
      <pubDate>Sat, 26 Apr 2025 22:05:59 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 38 열거 타입 확장</title>
      <link>https://rockintuna.tistory.com/306</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
 */
public class Item38 {
    /*
    대부분 열거 타입에는 확장이 필요 없으나,
    연산 코드 상수 열거의 경우에는 필요할 수 있다.
    (기본 연산 기능 외에 커스텀 연산 기능 확장)

    하지만 열거 타입은 확장을 제공하지 않기 때문에
    인터페이스를 구현할 수 있다는 점을 활용한 확장을 흉내내는 방법을 사용한다.
    1. 인터페이스 정의
    2. (기본 구현)인터페이스를 구현하는 enum 상수 정의
     */
    interface Operation {
        double apply(double a, double b);
    }

    //상수별 메서드 구현
    public enum BasicOperation implements Operation {
        PLUS {
            @Override
            public double apply(double a, double b) {
                return a + b;
            }
        },
        MINUS {
            @Override
            public double apply(double a, double b) {
                return a - b;
            }
        },
        MULTIPLY {
            @Override
            public double apply(double a, double b) {
                return a * b;
            }
        },
        DIVIDE {
            @Override
            public double apply(double a, double b) {
                return a / b;
            }
        }
    }

    /*
    여기서 기본 구현 외에 기능을 확장한다면,
    (ex) 나머지 계산 기능 추가
    인터페이스 Operation을 구현하는 다른 enum을 추가한다.
     */
    public enum AddtionalOperation implements Operation {
        REMAINDER {
            @Override
            public double apply(double a, double b) {
                return a % b;
            }
        }
    }

    /*
    클라이언트에서 기본 열거 타입과 확장된 열거 타입을 적절히 사용하려면
    BasicOperation 대신 인터페이스 Operation을 타입으로 사용해야 한다.

    어디까지나 확장을 흉내낸것이므로 열거 타입끼리 구현을 상속할 수 없으며,
    코드 중복을 제거하기 위해서는 도우미 메서드로 분리하는 것이 도움이 될 수 있다.
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/306</guid>
      <comments>https://rockintuna.tistory.com/306#entry306comment</comments>
      <pubDate>Fri, 25 Apr 2025 23:01:29 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 37 EnumMap</title>
      <link>https://rockintuna.tistory.com/305</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.*;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toSet;

/*
ordinal 인덱싱 대신 EnumMap을 사용하라
 */
public class Item37 {
    /*
    ordinal 사용의 단점
    - 타입 안전하지 않음
    - 컴파일러는 ordinal과 배열의 인덱스의 관계를 알 수 없다.
    -&amp;gt; IndexOutOfBounds, NullPointer 등 런타임 에러가 발생 가능
     */

    private String name;
    private Style style;

    public String getName() {
        return name;
    }

    public Style getStyle() {
        return style;
    }

    public enum Style {
        BOLD,
        ITALIC,
        UNDERLINE;
    }

    public Item37(String name, Style style) {
        this.name = name;
        this.style = style;
    }

    public static void main(String[] args) {
        List&amp;lt;Item37&amp;gt; itemList = new ArrayList&amp;lt;&amp;gt;();
        itemList.add(new Item37(&quot;A&quot;, Style.BOLD));
        itemList.add(new Item37(&quot;B&quot;, Style.ITALIC));
        itemList.add(new Item37(&quot;C&quot;, Style.ITALIC));
        itemList.add(new Item37(&quot;D&quot;, Style.UNDERLINE));

        /*
        EnumMap
        - 열거 타입 상수를 키로 사용
        - 타입 안전
        - 비검사 형변환 없음
         */
        //생성자는 한정적 타입 토큰
        Map&amp;lt;Style, Set&amp;lt;Item37&amp;gt;&amp;gt; styleWithItemSetMap = new EnumMap&amp;lt;&amp;gt;(Style.class);
        for (Style style : Style.values()) {
            styleWithItemSetMap.put(style, new HashSet&amp;lt;&amp;gt;());
        }
        for (Item37 item37 : itemList) {
            styleWithItemSetMap.get(item37.style).add(item37);
        }

        //stream을 사용한 코드
        Map&amp;lt;Style, Set&amp;lt;Item37&amp;gt;&amp;gt; map = itemList.stream().collect(groupingBy(i -&amp;gt; i.getStyle(),
                () -&amp;gt; new EnumMap&amp;lt;&amp;gt;(Style.class), toSet()));

        /*
        ordinal 사용 X
        -&amp;gt; 대신 인스턴스 필드 및 EnumMap을 사용하자
         */
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/305</guid>
      <comments>https://rockintuna.tistory.com/305#entry305comment</comments>
      <pubDate>Thu, 24 Apr 2025 20:44:56 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 36 EnumSet</title>
      <link>https://rockintuna.tistory.com/304</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.EnumSet;
import java.util.Set;

/*
비트 필드 대신 EnumSet을 사용하라
 */
public class Item36 {
    //비트 열거 패턴
    public static final int STYLE_BOLD = 1 &amp;lt;&amp;lt; 0;
    public static final int STYLE_ITALIC = 1 &amp;lt;&amp;lt; 1;
    public static final int STYLE_UNDERLINE = 1 &amp;lt;&amp;lt; 2;

    private int styles;

    //styles = 여러 비트 값들을 or 연산으로 모은 값 : 비트 필드
    public void applyStyle(int styles) {
        this.styles = styles;
    }

    public static void main(String[] args) {
        //비트 필드
        Item36 item36 = new Item36();
        item36.applyStyle(STYLE_BOLD | STYLE_ITALIC);

        //EnumSet
        Set&amp;lt;Style&amp;gt; styleSet = EnumSet.of(Style.UNDERLINE, Style.ITALIC);
        item36.applyStyles(styleSet);
    }

    /*
    비트 필드는 비트 연산을 통한 교집합/합집합 같은 집한 연산이 가능하지만
    아래와 같은 단점이 있다.
    - 정수 열거 상수의 단점
    - 값 자체를 통해서는 해석하기 어려움
    - 비트 필드의 요소들을 순회하기 어려움
    - 최대 몇 비트가 필요한지 예측하여 적절한 타입을 선택해야 함

    EnumSet 클래스는 비트 필드의 대안이 된다.
    - 타입 안전
    - Set 인터페이스 구현체
    - 내부적으로는 비트 벡터로 구현되어 성능이 좋음
    - 비트/집합 연산을 직접하는 것 보다 쉬운 API를 제공
     */

    public enum Style {
        BOLD,
        ITALIC,
        UNDERLINE;
    }

    private Set&amp;lt;Style&amp;gt; styleSet;

    public void applyStyles(Set&amp;lt;Style&amp;gt; styleSet) {
        this.styleSet = styleSet;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/304</guid>
      <comments>https://rockintuna.tistory.com/304#entry304comment</comments>
      <pubDate>Wed, 23 Apr 2025 15:05:39 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 35 enum ordinal 메서드 대신 인스턴스 필드 사용</title>
      <link>https://rockintuna.tistory.com/303</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
enum의 ordinal 메서드 대신 인스턴스 필드를 사용하라
 */
public class Item35 {

    enum DAY {
        MONDAY,
        TUESDAY,
        WEDNESDAY,
        THURSDAY,
        FRIDAY,
        SATURDAY,
        SUNDAY
    }

    public static void main(String[] args) {
        /*
        ordinal() 메서드는 정의된 열거 타입 상수의 순서를 리턴한다.
        enum의 ordinal 메서드에 의미를 두어 사용하는 건 좋지 않다.
        - enum 상수의 순서가 바뀌면 ordinal 결과도 바뀌기 때문에 오류가 발생할 수 있다.
        - 다른 상수에서 동일한 정수값을 가지게 할 수 없다.
        - 정수값은 1씩 증가해야하며
        - 정수값을 중간에 띄우려면 더미 상수를 만들어야 한다.

        이런 단점들이 있으니 ordinal 대신 인스턴스 필드에 정수값을 저장하는 것이 옳다.
        ordinal은 그냥 사용하지 말자.
         */
        int ordinal = DAY.MONDAY.ordinal();
        System.out.println(ordinal); // 0
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/303</guid>
      <comments>https://rockintuna.tistory.com/303#entry303comment</comments>
      <pubDate>Wed, 23 Apr 2025 15:04:51 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 34 열거 타입 (enum)</title>
      <link>https://rockintuna.tistory.com/302</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toMap;

/*
int 상수 대신 열거 타입을 사용하라
 */
public class Item34 {
    /*
    열거 타입의 장점
    - 값이 아닌 인스턴스로 존재하므로 여러 기능을 제공
    - 싱글턴임이 보증 됨
    - 컴파일 타임 타입 안정성
    - 상수를 추가하거나 순서를 바꿔도 다시 컴파일 하지 않아도 됨
    - 임의의 메서드나 필드를 추가할 수 있음
    - 임의의 인터페이스를 구현하게 할 수 있음

    필요한 원소를 컴파일타임에 다 알 수 있는 상수 집합이라면 항상 열거 타입을 사용하자.
    상수의 개수가 불변일 필요는 없다.
     */
    enum DAY {
        MONDAY,
        TUESDAY,
        WEDNESDAY,
        THURSDAY,
        FRIDAY,
        SATURDAY,
        SUNDAY
    }

    //enum 팁 1 : 상수별 메서드 구현
    //switch-case 보다 안전하고 유연하다
    //만약 상수끼리 구현의 공유가 필요하다면 중첨 enum을 사용하고 내부 enum에서 메서드를 구현하자
    //(전략 열거 타입 패턴)
    enum Operation {
        PLUS() {
            @Override
            public double apply(double a, double b) {
                return a + b;
            }
        },
        MINUS {
            @Override
            public double apply(double a, double b) {
                return a - b;
            }
        },
        MULTIPLY {
            @Override
            public double apply(double a, double b) {
                return a * b;
            }
        },
        DIVIDE {
            @Override
            public double apply(double a, double b) {
                return a / b;
            }
        };

        public abstract double apply(double a, double b);

        //enum 팁 2 : fromString 메서드
        //toString을 재정의한 경우 있으면 좋음
        private static final Map&amp;lt;String, Operation&amp;gt; stringToEnum =
                Stream.of(values()).collect(toMap(Enum::toString, operation -&amp;gt; operation));

        public static Optional&amp;lt;Operation&amp;gt; fromString(String string) {
            return Optional.ofNullable(stringToEnum.get(string));
        }


    }


}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/302</guid>
      <comments>https://rockintuna.tistory.com/302#entry302comment</comments>
      <pubDate>Tue, 22 Apr 2025 23:29:13 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 33 타입 안전 이종 컨테이너</title>
      <link>https://rockintuna.tistory.com/301</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import org.springframework.core.ParameterizedTypeReference;

import java.util.HashMap;
import java.util.Map;

/*
타입 안전 이종 컨테이너를 고려하라
 */
public class Item33 {
    /*
    하나의 컨테이너(제네릭 인스턴스)에서 여러 매개변수화 타입을 쓰려면?
*/
    public static void main(String[] args) {
        //String.class 리터럴의 타입은 Class&amp;lt;String&amp;gt;
        assert String.class instanceof Class&amp;lt;String&amp;gt;;

        Favorites favorites = new Favorites();
        favorites.putFavorite(String.class, &quot;A&quot;);
    }

    /*
    타입 안전 이종 컨테이너 패턴 :
    컨테이너 대신 키를 매개변수화(Class&amp;lt;T&amp;gt;)하고
    컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함께 제공(타입 토큰이라고 함)하는 방식
     */
    static class Favorites {
        private Map&amp;lt;Class&amp;lt;?&amp;gt;, Object&amp;gt; favorites = new HashMap&amp;lt;&amp;gt;();

        public &amp;lt;T&amp;gt; void putFavorite(Class&amp;lt;T&amp;gt; key, T value) {
            favorites.put(key, value);
        }

        public &amp;lt;T&amp;gt; T getFavorite(Class&amp;lt;T&amp;gt; key) {
            /*
            아래와 같이 비검사 형변환 하는 것 보다
            return (T) favorites.get(key);

            Class cast(Object obj) 메서드를 사용하면 타입 안전하다.
             */
            return key.cast(favorites.get(key));
        }
    }
    /*
    타입 안전 이종 컨테이너 패턴의 예시인 Favorites,
    모든 키의 타입이 제각각이라 여러 가지 타입의 원소를 담을 수 있다.

    타입 안전 이종 컨테이너의 한계
    1. key에 Class 로 타입을 입력하면 타입 안정성이 깨진다.
    2. List&amp;lt;String&amp;gt;.class는 문법 오류인 것 처럼 실체화 불가 타입에는 사용할 수 없다.
    (2번 문제는 ParameterizedTypeReference(슈퍼 타입 토큰)를 사용하면 해결할 수 있다, 완벽하지는 않음)
     */
}


&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/301</guid>
      <comments>https://rockintuna.tistory.com/301#entry301comment</comments>
      <pubDate>Mon, 21 Apr 2025 22:25:53 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 32 제네릭과 가변인수를 같이 사용할 때 주의</title>
      <link>https://rockintuna.tistory.com/300</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.List;

/*
제네릭과 가변인수를 함께 쓸 때는 신중하라
 */
public class Item32 {

    /*
    제네릭과 가변인수를 함께 쓸 때는 타입 안정성이 깨진다.
    이게 허용되는 이유는 실무에서 매우 유용하기 때문,
    사용하고자 한다면 타입 안전한지 확인이 필요하다.
     */

    //제네릭과 가변인수를 함께 쓸 때 타입 안정성이 깨진다.
    public static void dangerous(List&amp;lt;String&amp;gt;... stringLists) {
        //내부적으로 제네릭 배열이 만들어지며, 외부로 노출되는데
        Object[] objects = stringLists;
        //이 배열에 값을 저장하는 것은 안전하지 않다.
        List&amp;lt;Integer&amp;gt; intList = List.of(42);
        objects[0] = intList; //힙 오염 발생
        String s = stringLists[0].toString(); //런타임 오류 ClassCastException
    }

    //@SafeVarargs 어노테이션은 이 제네릭 가변인수 메서드가 타임 안전함을 보장한다.
    //메서드 작성 측에서의 경고도 없애고
    //클라이언트에서 @SuppressWarnings 같은 어노테이션으로 경고를 숨기는 작업을 하지 않아도 된다.
    @SafeVarargs
    public static void safe(List&amp;lt;String&amp;gt;... stringLists) {
        /*
        varargs 매개변수 배열에 아무것도 저장하거나 덮어쓰지 않고,
        그 배열의 참조가 밖으로 노출되지 않는다면 (@SafeVarargs가 달린 정말 안전한 메서드 제외)
        안전하다고 보장할 수 있다.
         */
        String s = stringLists[0].toString();
    }

    /*
    @SafeVarargs 사용 원칙
    - varargs 매개변수 배열에 아무것도 저장하거나 덮어쓰지 않기
    - 그 배열의 참조가 신뢰할 수 없는 코드에 노출되지 않도록 하기
    - 위 규칙을 모든 메서드에 적용하고 제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 모든 메서드에 @SafeVarargs 달기

    varargs 매개변수 대신 List로 구현하는 것도 방법
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/300</guid>
      <comments>https://rockintuna.tistory.com/300#entry300comment</comments>
      <pubDate>Sun, 20 Apr 2025 23:21:58 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 31 한정적 와일드카드 타입</title>
      <link>https://rockintuna.tistory.com/299</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/*
한정적 와일드카드를 사용해 API 유연성을 높이라
 */
public class Item31&amp;lt;E&amp;gt; {
    List&amp;lt;E&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

    public void addAll(List&amp;lt;E&amp;gt; array) {
        this.list.addAll(array);
    }

    public static void main(String[] args) {
        Item31&amp;lt;Number&amp;gt; item = new Item31&amp;lt;&amp;gt;();
        List&amp;lt;Integer&amp;gt; integerList = new ArrayList&amp;lt;&amp;gt;();
        /*매개변수화 타입은 불공변이기 때문에
        List&amp;lt;Number&amp;gt; 타입의 인자값으로 List&amp;lt;Integer&amp;gt; 타입을 입력할 수 없음.
        아래 코드는 오류 발생 (incompatible types)
        item.addAll(integerList);
        List&amp;lt;Number&amp;gt; 타입의 인자값으로 List&amp;lt;Object&amp;gt; 타입을 입력할 수 없음.
        item.addAllTo(new ArrayList&amp;lt;Object&amp;gt;());
        */

        /*
        이런 상황을 대처하기 위해
        &quot;한정적 와일드카드 타입&quot; 이라는 특별한 매개변수화 타입 지원
        ? extends E 는 E의 하위 타입,
        List&amp;lt;? extends E&amp;gt;는 E의 하위 타입의 리스트,
        ? super E 는 E의 상위 타입,
        List&amp;lt;? super E&amp;gt;는 E의 상위 타입의 리스트,
         */
        item.addAllExtendsE(integerList);
        item.addAllToSuperE(new ArrayList&amp;lt;Object&amp;gt;());
    }

    public void addAllExtendsE(List&amp;lt;? extends E&amp;gt; array) {
        this.list.addAll(array);
    }

    public void addAllTo(List&amp;lt;E&amp;gt; list) {
        list.addAll(this.list);
    }

    public void addAllToSuperE(List&amp;lt;? super E&amp;gt; list) {
        list.addAll(this.list);
    }

    /*
    유연성을 극대화하려면 메서드 또는 생성자의 입력 매개변수에 와일드카드 타입을 사용하라.
    PECS : producer-extends, consumer-super
    입력 매개변수가 원소의 생산자일 때 : extends
    입력 매개변수가 원소의 소비자일 때 : super
    입력 매개변수가 원소의 생산자이면서 소비자일 때는 와일드카드 타입을 쓰지 말아야 한다.

    타입 매개변수에도 위의 공식을 적용할 수 있다.
    Comparable는 E의 소비자 이기 때문에 super를 사용한 와일드 카드 타입으로 바꾼다.
    E 또는 E의 상위 클래스에서 Comparable를 확장한 것임을 확정한다.
     */
    public static &amp;lt;E extends Comparable&amp;lt;? super E&amp;gt;&amp;gt; Optional&amp;lt;E&amp;gt; min(List&amp;lt;E&amp;gt; list) {
        //todo
        return list.stream().sorted().findFirst();
    }

    /*
    와일드카드와 타입 매개변수 둘 중 하나를 선택하여 제네릭 메서드를 구현할 수 있다.
     */
    //1) 비한정적 타입 매개변수
    public static &amp;lt;E&amp;gt; void swap1(List&amp;lt;E&amp;gt; array, int i, int j) {}
    //2) 비한정적 와일드카드
    public static void swap2(List&amp;lt;?&amp;gt; array, int i, int j) {}

    /*
    적절한 선택 기준 및 사용법
    - 메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체
    - List&amp;lt;?&amp;gt;를 사용하여 실제 타입을 알 수 없기 때문에 문제가 발생한다면, List&amp;lt;E&amp;gt;를 사용하는 private 도우미 제네릭 메서드 추가
     */
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/299</guid>
      <comments>https://rockintuna.tistory.com/299#entry299comment</comments>
      <pubDate>Sat, 19 Apr 2025 23:43:27 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 30 제네릭 메서드</title>
      <link>https://rockintuna.tistory.com/298</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.UnaryOperator;

/*
이왕이면 제네릭 메서드로 만들자
 */
public class Item30 {

    /*
    입력값이나 반환 값을 명시적으로 형변환해야 하는 메서드보다 제네릭 메서드가 더 안전하며 사용하기 쉽다.
     */

    //로 타입을 매개변수나 반환 타입으로 사용하면 경고가 발생하며, 타입 안전하지 않다.
    public List addRawTypeList(List list1, List list2) {
        return new ArrayList&amp;lt;&amp;gt;(list1.size() + list2.size());
    }

    /*
    제네릭 클래스가 아니어도 제네릭 메서드를 정의할 수 있다.
    접근제어자와 반환 타입 사이에 메서드에서 사용할 매개변수화 타입 선언 '&amp;lt;E&amp;gt;'
    경고 없음, 타입 안전, 형변환이 불필요하므로 사용이 쉬움
     */
    public &amp;lt;E&amp;gt; List&amp;lt;E&amp;gt; addGenericList(List&amp;lt;E&amp;gt; list1, List&amp;lt;E&amp;gt; list2) {
        return new ArrayList&amp;lt;&amp;gt;(list1.size() + list2.size());
    }

    //제네릭 메서드 팁
    public static void main(String[] args) {
        String string = &quot;Hello World&quot;;
        //불변 객체를 여러 타입으로 활용 예시
        //제네릭 싱글턴 팩터리 패턴으로
        //String을 UnaryOperator로 활용
        UnaryOperator&amp;lt;String&amp;gt; stringIdentityFunction = IdentityFunctionFactory.getIdentityFunction();
        String apply = stringIdentityFunction.apply(string);
        assert apply.equals(string);

        //Integer를 UnaryOperator로 활용
        UnaryOperator&amp;lt;Integer&amp;gt; integerIdentityFunction = IdentityFunctionFactory.getIdentityFunction();
        Integer integer = 1;
        Integer apply2 = integerIdentityFunction.apply(integer);
        assert integer.equals(apply2);

        Optional&amp;lt;String&amp;gt; max = Item30.max(List.of(&quot;d&quot;, &quot;e&quot;, &quot;c&quot;, &quot;a&quot;));
        max.ifPresent(System.out::println);
    }

    //재귀적 타입 한정
    //매개변수 타입 E는 Comparable&amp;lt;E의 상위 클래스&amp;gt;를 구현해야 한다.
    //즉, 서로 비교 가능한 것이어야 함
    //타입 안전하면서 비교 가능한 타임임을 보장
    public static &amp;lt;E extends Comparable&amp;lt;? super E&amp;gt;&amp;gt; Optional&amp;lt;E&amp;gt; max(List&amp;lt;E&amp;gt; list) {
        if (list.isEmpty()) {
            return Optional.empty();
        }
        E max = list.getFirst();

        for (E e : list) {
            if (max.compareTo(e) &amp;lt; 0) {
                max = e;
            }
        }
        return Optional.ofNullable(max);
    }
}

//제네릭 싱글턴 팩터리 패턴
class IdentityFunctionFactory {
    private static final UnaryOperator&amp;lt;Object&amp;gt; IDENTITY_FUNCTION = o -&amp;gt; o;

    //입력되는 값이 그대로 변하지 않는 다는 것이 위 선언에서 보장되므로
    //@SuppressWarnings(&quot;unchecked&quot;)으로 경고 제거
    @SuppressWarnings(&quot;unchecked&quot;)
    public static &amp;lt;T&amp;gt; UnaryOperator&amp;lt;T&amp;gt; getIdentityFunction() {
        return (UnaryOperator&amp;lt;T&amp;gt;) IDENTITY_FUNCTION;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/298</guid>
      <comments>https://rockintuna.tistory.com/298#entry298comment</comments>
      <pubDate>Fri, 18 Apr 2025 23:00:39 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 29 제네릭 타입으로 변경</title>
      <link>https://rockintuna.tistory.com/297</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
이왕이면 제네릭 타입으로 만들자
 */
public class Item29 {
    /*
    기존 타입 중 제네릭이었어야 하는 게 있다면 제네릭 타입으로 변경하자.
    제네릭 타입은 하위 버전의 Java에서 호환이 가능하기 때문에
    기존 클래스를 제네릭 타입으로 바꾸더라도
    원래 클래스를 사용하던 클라이언트에는 영향이 없다.
    동시에 새로운 사용자는 훨씬 편하고 타입 안전하게 사용할 수 있다.
     */
}

//제네릭 배열 생성을 우회하는 방법 1
class Item29GenericArray1&amp;lt;E&amp;gt; {
    private final E[] array;

    //제네릭 타입 배열로 형변환
    //요소를 입력할 때 E 타입만 입력되는 것으로 제한 할 수 있다면
    //@SuppressWarnings(&quot;unchecked&quot;)으로 경고 제거
    @SuppressWarnings(&quot;unchecked&quot;)
    public Item29GenericArray1() {
        this.array = (E[]) new Object[10];
    }

    public void add(E e) {
        //add 구현
    }

    public E get(int index) {
        return array[index];
    }
}

//제네릭 배열 생성을 우회하는 방법 2
class Item29GenericArray2&amp;lt;E&amp;gt; {
    private final Object[] array;

    public Item29GenericArray2() {
        this.array = new Object[10];
    }

    public void add(E e) {
        //add 구현
    }

    //뽑은 요소를 형변환
    //마찬가지로 입력이 E로 제한된다고 확정할 수 있다면
    //요소 뽑기에서 @SuppressWarnings(&quot;unchecked&quot;)으로 경고 제거
    public E get(int index) {
        //get 구현
        @SuppressWarnings(&quot;unchecked&quot;)
        E e = (E) this.array[index];
        return e;
    }
}

/*
1번 방법을 주로 쓰지만 (가독성, 생성자에서만 형변환), 힙 오염이 있음
List를 사용할 수 있다면 좋지만 사용하지 못하는 경우, 또는 성능이 중요한 경우 위와 같이 구현!
 */&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/297</guid>
      <comments>https://rockintuna.tistory.com/297#entry297comment</comments>
      <pubDate>Thu, 17 Apr 2025 22:59:44 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 28 배열보다는 리스트</title>
      <link>https://rockintuna.tistory.com/296</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/*
배열보다는 리스트를 사용하자
 */
public class Item28 {

    public static void main(String[] args) {
        /*
        배열은 공변 리스트는 불공변
         */
        Object[] array = new String[]{&quot;a&quot;,&quot;b&quot;,&quot;c&quot;};
        //리스트는 불공변이 때문에 아래 코드는 컴파일 에러 발생
//        List&amp;lt;Object&amp;gt; list = new ArrayList&amp;lt;String&amp;gt;();

        /*
        언제나 런타임 에러보다는 컴파일에러가 낫다는 측면에서
        위처럼 List는 컴파일에서 에러가 발생하지만
        배열은 아래 코드에서 런타임에 에러(ArrayStoreException)가 발생한다.
         */
//        array[0] = 30;

        /*
        배열은 실체화되지만
        제네릭은 런타임에서 타입 정보가 소거된다.
         */
        //제네릭은 컴파일 타임에서 타임 안정성을 지키지만 런타임에서 타입 정보가 소거되어 안전하지 않음
        // (List&amp;lt;String&amp;gt; 이든 List&amp;lt;Integer&amp;gt;든 런타임에서는 둘다 List
        // 제네릭 배열이나 Raw type을 사용하는 경우 문제 발생)
        //아래 코드 컴파일 에러
//        List&amp;lt;Object&amp;gt; list = new ArrayList&amp;lt;String&amp;gt;();
        //배열은 컴파일 타임에서 타입 안전하지 않음
        Object[] objects = new String[1]; // 배열은 공변(covariant) 허용
        objects[0] = 123; //   컴파일 통과! 하지만 런타임에 예외 발생

        /*
        쉽게 말하면 제네릭은 컴파일 타임때 배열보다 더 타입 체크를 엄격하게 한다.
        하지만 런타임에서 타입 정보 소거 때문에 문제가 발생할 수 있다.
         */

        /*
        제네릭 배열은 불가능하다.
        아래 코드는 컴파일 에러가 발생한다.
         */
//        new ArrayList&amp;lt;String&amp;gt;[];

        /*
        배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우
        보통 배열 대신 List를 사용하면 해결된다.
         */

        Collection&amp;lt;String&amp;gt; collection = new ArrayList&amp;lt;&amp;gt;();
        collection.add(&quot;a&quot;);
        //아래 코드는 런타임 오류(ClassCastException) 발생
        //Object[]를 String[]으로 캐스팅 할 수 없기 때문
        String[] array2 = (String[]) collection.toArray();

        //이 코드는 런타임 오류 없는대신 아래 Chooser 클래스에서 경고 발생
        Chooser&amp;lt;String&amp;gt; stringChooser = new Chooser&amp;lt;&amp;gt;(collection);
        System.out.println(stringChooser.choose());

        //경고는 아래처럼 타입 안정성을 보장하지 못하는 경우를 말함
        List&amp;lt;Integer&amp;gt; ints = Arrays.asList(1, 2, 3);
        Chooser&amp;lt;String&amp;gt; chooser = new Chooser(ints);
        //여기서 런타임 에러(ClassCastException) 발생
        String choose = chooser.choose();
    }
}

class Chooser&amp;lt;T&amp;gt; {
    private final T[] array;
    private final List&amp;lt;T&amp;gt; list;

    Chooser(Collection&amp;lt;T&amp;gt; array) {
        //아래 코드는 위처럼 오류가 발생하지 않는데,
        //런타임에서는 타입 정보가 소거되므로 경고만 하고 그냥 허용함
        //대신 컴파일러는 타입 안전을 보장하지 못한다는 경고를 한다.
        this.array = (T[]) array.toArray();

        //배열 대신 리스트를 사용하면 컴파일 오류도 없고 경고도 없음.
        this.list = new ArrayList&amp;lt;&amp;gt;(array);
    }

    public T choose() {
        return array[0];
    }

    public T choose2() {
        return list.getFirst();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/296</guid>
      <comments>https://rockintuna.tistory.com/296#entry296comment</comments>
      <pubDate>Wed, 16 Apr 2025 23:23:39 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 27 비검사 경고 제거</title>
      <link>https://rockintuna.tistory.com/295</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/*
비검사 경고를 제거하라
 */
public class Item27 {
    public static void main(String[] args) {
        //컴파일시 -Xlint:uncheck 옵션을 추가하면 어떤 문제가 있는지 설명해준다.
        Set&amp;lt;String&amp;gt; set = new HashSet();

        /*
        비검사 경고가 발생한다는 것은 타입 안정성을 보장하지 못한다는 말이다.
        =&amp;gt; 런타임에서 타입 관련 오류가 발생할 가능성이 있다.

        만약 경고를 제거할 수 없으나 비즈니스 로직상 타입 안전하다고 확신할 수 있다면
        @SuppressWarnings(&quot;unchecked&quot;) 어노테이션을 달아 경고를 숨길 수 있다.
        @SuppressWarnings는 가능한 한 좁은 범위에 적용하여 심각한 경고를 놓치는 일이 없도록 하자
        좀은 범위의 예 : 지역 변수
         */
        @SuppressWarnings(&quot;unchecked&quot;)
        List&amp;lt;String&amp;gt; list = new ArrayList();
        list.add(&quot;a&quot;);

        /*
        @SuppressWarnings 어노테이션을 사용할 때는 경고를 무시해도 안전한 이유를 주석으로 남겨서
        비즈니스 로직이 변경될 때 타입 안정성을 잃는 경우를 방지하자
         */

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/295</guid>
      <comments>https://rockintuna.tistory.com/295#entry295comment</comments>
      <pubDate>Tue, 15 Apr 2025 23:11:29 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 26 제네릭 타입의 로 타입은 사용하지 말자</title>
      <link>https://rockintuna.tistory.com/294</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.ArrayList;
import java.util.List;

/*
로 타입은 사용하지 말자
 */
public class Item26 {

    public static void main(String[] args) {

        //제네릭 타입 List&amp;lt;E&amp;gt;
        //타입 매개변수를 선언하면 타입 안정성을 확보할 수 있다.
        List&amp;lt;String&amp;gt; stringList = new ArrayList&amp;lt;&amp;gt;();

        //제네릭 인터페이스 List&amp;lt;E&amp;gt; 의 로 타입 List
        //로 타입은 이전 Java 버전과의 호환성 때문에 제공됨
        //런타임에서 오류(ClassCastException 등) 발생 가능성이 높다!
        List rawTypeList = new ArrayList&amp;lt;&amp;gt;();
        rawTypeList.add(1L);
        rawTypeList.add(&quot;abc&quot;);

        /*
        로타입을 절대 쓰면 안돼는 이유
        - 로타입을 쓰면 제네릭이 안겨주는 안정성과 표현력을 모두 잃게 된다.
         */
    }

    //어떤 타입이든 넣고 싶다면 대신 List&amp;lt;Object&amp;gt;를 사용하자
    //List&amp;lt;Object&amp;gt;를 매개변수로 받으면 List&amp;lt;String&amp;gt; 등은 인자값으로 사용할 수 없다.
    //(List&amp;lt;String&amp;gt;은 List&amp;lt;Object&amp;gt;의 하위 타입이 아니다.)
    public static void demo0(List&amp;lt;Object&amp;gt; objectTypeList) {
        //unchecked warning이 발생하지 않는다.
        objectTypeList.add(10);
    }

    /*
    매개변수로 어떤 타입 매개변수의 제네릭 클래스든 받고 싶다면?
    로 타입 보다는 wildcard 타입을 대신 사용하자
    wildcard는 원소를 사용하거나 입력할 수 없게 만들어 타입 안정성을 보장한다.
     */
    public static void demo1(List rawTypeList) {
        //unchecked warning
        rawTypeList.add(10);
    }

    public static void demo2(List&amp;lt;?&amp;gt; wildcardList) {
        //입력시 컴파일 에러
        //wildcardList.add(Integer.valueOf(10));
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/294</guid>
      <comments>https://rockintuna.tistory.com/294#entry294comment</comments>
      <pubDate>Mon, 14 Apr 2025 23:45:04 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 25 파일당 탑레벨 클래스는 하나씩</title>
      <link>https://rockintuna.tistory.com/293</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;vbscript&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
톱래밸 클래스는 한 파일에 하나만
 */
class Item25 {
    public static void main(String[] args) {
        System.out.println(Item25Other1.NAME + Item25Other2.NAME);
        /*
        class Item25Other1 {
           public static final String NAME = &quot;pan&quot;;
        }

        class Item25Other2 {
            public static final String NAME = &quot;cake&quot;;
        }

        동일한 문장의 클래스 파일 2개 Item25Other1.java, Item25Other2.java에 위와 같이 2개의 클래스를 작성하면
        한 클래스에 대한 정의가 여러 개 만들어지기 때문에 컴파일러에 소스 파일을 전달하는 순서에 따라 동작이 달라질 수 있다.
        컴파일러가 이름이 중복되는 것을 알게 되는 순간 컴파일 에러가 발생한다.

        톱레벨 클래스를 한 파일당 하나만 담으면 위와 같은 문제는 발생하지 않는다.

        굳이 여러 톱레벨 클래스를 한 파일에 담고 싶다면(다른 클래스에 달린 부차적인 클래스) 정적 멤버 클래스를 사용

         */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/293</guid>
      <comments>https://rockintuna.tistory.com/293#entry293comment</comments>
      <pubDate>Sun, 13 Apr 2025 23:35:30 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 24 중첩 클래스</title>
      <link>https://rockintuna.tistory.com/292</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
멤버 클래스는 되도록 static 으로 만들자
 */
public class Item24 {

    private String name;
    /*
    중첩 클래스 : 다른 클래스 안에서 정의된 클래스
    중첨 클래스는 자신을 감싸는 바깥 클래스에서만 내부적으로 사용되어야 한다.
    중첨 클래스 종류
     - 정적 멤버 클래스
     - (비정적) 멤버 클래스
     - 익명 클래스
     - 지역 클래스
     정적 멤버 클래스를 제외한 3가지는 모두 내부 클래스(inner class) 이다.
     */

    private String getName() {
        return name;
    }

    /*
         정적 멤버 클래스 : 바깥 클래스의 private 멤버에 접근할 수 있다는 것을 제외하면 일반 클래스와 동일하다
         주로 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스로 쓰인다.
         */
    public static class Item24StaticMemberClass {
        private String outerClassName;

        public String getName() {
            Item24 item24 = new Item24();
            //바깥 클래스의 private 멤버에 접근할 수 있다
            outerClassName = item24.name;
            return item24.getName();
        }
    }

    /*
     비정적 멤버 클래스 :
     비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결된다.
     바깥 인스턴스 없이는 독립적으로 비정적 멤버 클래스 인스턴스를 생성할 수 없다.
     어댑터를 정의할 때 자주 쓰인다.
     ex) ArrayList의 iterator()
     public Iterator&amp;lt;E&amp;gt; iterator() {
        return new Itr();
    }
    Itr는 ArrayList의 비정적 멤버 클래스다.
     */
    public class Item24NonStaticMemberClass {
        private String outerClassName;

        public String getName() {
            //정규화된 this를 통해 바깥 클래스 인스턴스의 참조를 가져올 수 있다.
            outerClassName = Item24.this.name;
            return Item24.this.getName();
        }

    }

    //비정적 멤버 클래스의 인스턴스는 보통 바깥 클래스 인스턴스 메서드 안에서 생성된다.
    public void createItem24NonStaticMemberClass() {
        Item24NonStaticMemberClass item24NonStaticMemberClass = new Item24NonStaticMemberClass();
    }

    /*
    멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들자
    - 암묵적 외부 참조 : 참조 저장 공간 등 불필요한 시간 공간 소비, 눈에 보이지 않는 참조 때문에 발생하는 오류 디버깅 어려움
    - 바깥 클래스 인스턴스를 가비지 컬렉터에서 수거하지 못하는 메모리 누수 가능성

    익명 클래스는 즉석에서 작은 함수 객체나 처리 객체를 만들때 (람다로 대체) 또는 정적 팩터리 메서드를 구현할 때 사용
     */

    /*
    중첩 클래스가 한 메서드 안에서만 쓰이면서 &amp;amp;&amp;amp;
    그 인스턴스를 생성하는 지점이 단 한 곳이고 &amp;amp;&amp;amp;
    해당 타입으로 쓰기에 적합한 클래스나 인터페이스가 이미 있는 경우에만
    익명 클래스로 생성
    그렇지 않다면 지역 클래스 사용

    지역 클래스
     */
    private String getLocalClass() {
        class LocalClass {
            private String name;

            public String getName() {
                return Item24.this.name;
            }
        }
        return new LocalClass().getName();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/292</guid>
      <comments>https://rockintuna.tistory.com/292#entry292comment</comments>
      <pubDate>Sat, 12 Apr 2025 23:59:09 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 23 태그 달린 클래스 대신 클래스 계층구조</title>
      <link>https://rockintuna.tistory.com/291</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
태그 달린 클래스보다는 클래스 계층구조를 활용하자
 */
public class Item23 {

    //의미와 관계없는 필드
    private final String name;

    //특정 의미에서만 사용되는 필드
    private final Integer dimensionLines;
    private final Integer texture;

    //태그
    enum Type {
        DRAWING_2D, DRAWING_3D
    }

    //현재 인스턴스의 의미를 태그로 선택
    private Type type;

    //태그와 상관 없이 동일한 메서드
    public String getName() {
        return this.name;
    }

    //태그에 따라 분기되는 메서드
    public void action() {
        switch (this.type) {
            case DRAWING_2D -&amp;gt; System.out.println(&quot;DRAWING_2D&quot;);
            case DRAWING_3D -&amp;gt; System.out.println(&quot;DRAWING_3D&quot;);
        }
    }
    /*
    이런 클래스 설계의 단점
    - 쓸데없는 코드 [enum, 태그 필드, switch-case, 등]
    - (분기로 나뉘게 될)여러 구현이 한 클래스에 있어서 가독성이 안좋음
    - 의미 마다 특별하게 쓰이는 필드더라도 모두 한 클래스에 있어야 한다, final이라면 굳이 입력까지(null이라도) 해야한다.
    - 다른 의미의 태그가 추가되면 코드가 수정된다.
    - 실수로 case 구현을 안하더라도 컴파일 오류는 발생하지 않고 비즈니스 로직 오류나 런타임 에러가 발생한다.
    - 인스턴스의 의미를 알려면 꼭 태그를 참조해야 한다.
     */
    public Item23(String name, Integer dimensionLines, Integer texture) {
        this.name = name;
        this.dimensionLines = dimensionLines;
        this.texture = texture;
    }
}

// ==&amp;gt;&amp;gt; 클래스 계층구조를 활용한 서브타이핑으로 리팩터링 강추
abstract class Item23Root {
    private final String name;
    public String getName() {
        return name;
    }
    abstract void action();

    public Item23Root(String name) {
        this.name = name;
    }
}

class Item23Drawing2D extends Item23Root {
    private final Integer dimensionLines;
    @Override
    void action() {
        System.out.println(&quot;DRAWING_2D&quot;);
    }

    public Item23Drawing2D(String name, Integer dimensionLines) {
        super(name);
        this.dimensionLines = dimensionLines;
    }
}

class Item23Drawing3D extends Item23Root {
    private final Integer texture;
    @Override
    void action() {
        System.out.println(&quot;DRAWING_3D&quot;);
    }

    public Item23Drawing3D(String name, Integer texture) {
        super(name);
        this.texture = texture;
    }
}


&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/291</guid>
      <comments>https://rockintuna.tistory.com/291#entry291comment</comments>
      <pubDate>Fri, 11 Apr 2025 23:16:43 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 22 인터페이스는 타입 정의 용도로 사용</title>
      <link>https://rockintuna.tistory.com/290</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
인터페이스는 타입을 정의하는 용도로만 사용하자
 */
public class Item22 implements Item22Interface{

    public static void main(String[] args) {
        
        //인터페이스를 구현한 클래스의 인스턴스는 그 인터페이스 타입이 된다.
        Item22Interface demo = new Item22();
    }
}

interface Item22Interface {
    
}

class Item22NoConstants implements Item22InterfaceConstants {
    public static void main(String[] args) {
        Item22InterfaceConstants demo = new Item22NoConstants();
        System.out.println(AMOUNT);
    }
}

/*
상수 인터페이스 : 메서드 없이 상수 전달을 위해 상수만 정의한 인터페이스, 인터페이스를 잘못 사용한 것
내부 구현인 상수를 외부 API로 제공하는 것이기 때문에 변경이 어렵다.
이 인터페이스를 구현하는 클래스가 있다면 이 상수들에 종속될 수 있다. 
 */
interface Item22InterfaceConstants {
    static final double AMOUNT = 100.0;
    static final double BALANCE = 100.0;
}
/*
상수를 공개할 필요가 있다면 위 방식 대신
- 상수와 특별히 관련되어 있는 클래스 또는 인터페이스에서 상수를 추가
- enum 사용
- 인스턴스화 할 수 없는 유틸리티 클래스 내부 
 */
class Item22Utility {
    private Item22Utility() {};

    public static final double AMOUNT = 100.0;
    public static final double BALANCE = 100.0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/290</guid>
      <comments>https://rockintuna.tistory.com/290#entry290comment</comments>
      <pubDate>Thu, 10 Apr 2025 22:14:56 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 21 디폴트 메서드</title>
      <link>https://rockintuna.tistory.com/289</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import org.apache.commons.collections4.collection.SynchronizedCollection;

import java.util.ArrayList;
import java.util.Collection;

/*
인터페이스는 구현하는 쪽을 생각해 설계하자
 */
public class Item21 implements Item21Interface{

    public static void main(String[] args) {
        /*
        모든 상황에 대해 불변식을 지키는 디폴트 메서드를 작성하기는 어렵다.
         */
        Collection&amp;lt;String&amp;gt; memberList = SynchronizedCollection.synchronizedCollection(new ArrayList&amp;lt;&amp;gt;());
        /*
        예를들어 Collection 인터페이스의 removeIf 디폴트 메서드를
        Collection 인터페이스를 구현하는 apache commons 의 SynchronizedCollection에서 사용하면
        SynchronizedCollection 가 가지는 동기화 기능을 적용받지 못한다.
        즉, thread-safe 하지 못하게 동작하거나 오류가 발생한다.
         */
        memberList.removeIf(s -&amp;gt; s.startsWith(&quot;#&quot;));

        /*
        최신 버전의 SynchronizedCollection 은 이를 보완하기 위해 removeIf를 재정의했다.
        public boolean removeIf(Predicate&amp;lt;? super E&amp;gt; filter) {
            synchronized(this.lock) {
                return this.decorated().removeIf(filter);
            }
        }
         */
    }

    /*
    기존에 사용하던 인터페이스에 디폴트 메서드를 추가하는 것은 되도록 피해야 한다.
    이를 구현하는 구체 클래스의 기존 구현과 충돌하여 오류가 발생할 수 있다.

    Item21Interface에 새로 isEmpty(String item) 디폴트 메서드가 추가되면서
    아래 메서드는 컴파일 오류가 발생한다.
    새로운 인터페이스 설계 단계에서는 잘 사용하면 좋다.
     */
    public String isEmpty(String item) {
        return item.isEmpty() ? &quot;true&quot; : &quot;false&quot;;
    }
}

/*
인터페이스를 설계할 때는
구현 테스트, 인터페이스 인스턴스 활용 테스트 등
주의를 기울여 용도에 맞는지 확인해야 한다.
 */
interface Item21Interface {

    default boolean isEmpty(String item) {
        return item.isEmpty();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/289</guid>
      <comments>https://rockintuna.tistory.com/289#entry289comment</comments>
      <pubDate>Wed, 9 Apr 2025 22:40:07 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 18 컴포지션</title>
      <link>https://rockintuna.tistory.com/288</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

/*
상속보다는 컴포지션을 사용하라
 */
public class Item18 extends Item18SuperClass {

    /*
    내가 제어할 수 없는 다른 패키지의 상위 클래스를 상속받았을 때 문제다.
    이 상위 클래스에서 상속받아 사용할 수 있는 내부 구현이 달라지는 경우,
    해당 내부 구현을 사용하는 메서드에서 오류가 발생하기 쉽다.
    상위 클래스 변화에 따라 하위 클래스 모두 변경해야 할 수 있다.
     */
    @Override
    public String getName() {
        return super.getName();
    }

    /*
    상위 클래스를 완벽히 이해하지 않는이상 재정의는 위험성이 있다.
    재정의 메서드는 의도치 않게 호출될 수 있으며 (내부 구현, 자기 사용),
    새로운 메서드가 생길 때 마다 재정의가 필요한 경우도 생긴다.
     */
    /*
    재정의를 하지 않고 다른 메서드 이름을 사용한다고 하면 다른 문제가 생긴다.
    이 메서드는 상위 클래스에서 새로운 메서드가 생길 때 시그니처가 동일한 경우
    오류가 발생하거나(리턴 타입이 다른경우) 재정의 되어버린다(리턴 타입이 같은 경우)
     */
}

//forwarding class
class Item18ForwardingClass {
    //컴포지션
    private Item18SuperClass composition;

    public Item18ForwardingClass(Item18SuperClass composition) {
        this.composition = composition;
    }

    //forwarding method
    String getName() {
        //forwarding
        return composition.getName();
    }

    /*
    컴포지션의 경우 재정의가 아니므로 자기 사용에 의해 의도치 않은 호출이 없다.
    컴포지션 클래스에 새로운 메서드가 생기는 것이 전달 클래스에는 더이상 아무런 영향을 끼치지 않는다.

    위와 같이 내부 객체의 메서드들을 대응해 응답하는 방식을 &quot;전달&quot; 이라고 한다.
    */
}

/*
재사용 가능한 전달 클래스를 상속받아 추가적인 기능을 부여하는 클래스를 래퍼 클래스라고 한다.
 */
class Item18RapperClass extends Item18ForwardingClass {

    public Item18RapperClass(Item18SuperClass composition) {
        super(composition);
    }

    public String subStringName(int idx) {
        return super.getName().substring(idx);
    }
}

/*
    상속의 단점
     - 상위 클래스의 내부 구현 변경에 취약함
     - 자기 사용에 의한 의도치 않은 동작
     - 상위 클래스 내부 구현이 불필요하게 노출되어야 함
     - 클라이언트가 노출된 내부에 직접 접근할 수 있음 (Override는 접근 제한자를 더 낮게 설정할 수 없음)
     
    상속은 A is a B, B가 A의 &quot;진짜&quot; 하위 타입인 경우에만 사용하자.
    다만 그런 경우가 별로 없기도 하고
    상속 자체의 단점
*/

class Item18SuperClass {

    private String name;

    protected String getName() {
        return name;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <category>composition</category>
      <category>Effective JAVA</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/288</guid>
      <comments>https://rockintuna.tistory.com/288#entry288comment</comments>
      <pubDate>Mon, 7 Apr 2025 23:44:07 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 17 불변 객체</title>
      <link>https://rockintuna.tistory.com/287</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.math.BigInteger;

/*
변경 가능성을 최소화하라
 */
public final class Item17 {

    /*
     불변 객체 : 생성된 시점부터 파괴될 때 까지 상태가 변경되지 않는 객체

    클래스를 불변으로 만들기
    1. setter가 없어야함
    2. 클래스를 확장할 수 없어야함 (대표적으로 final 클래스)
    3. 모든 필드를 private final로 선언
    4. 가변 객체를 참조하는 필드가 있다면 해당 필드를 사용하는 생성자, getter, readObject 메서드에서 방어적 복사를 한다.

    불변으로 만들 수 없다면 변경할 수 있는 부분을 최소화하자
    - 별다른 합당한 이유가 없다면 모든 필드는 private final
    - 생성자는 초기화 작업, 불변식 설정이 완료된 객체를 생성
     */

    private final int x;
    private final int y;

    Item17(int x, int y) {
        this.x = 0;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public Item17 plus(int x, int y) {
        return new Item17(this.x + x, this.y + y);
    }
    //함수형 프로그래밍
    //피연산자에 함수를 적용하여 결과를 반환하지만 피연산자는 그대로인 패턴
    //함수형 프로그래밍을 사용하여 개발하면 불변이 되는 영역의 비율이 높아진다.

    /*
    불변 객체의 장점
    설계, 구현, 사용이 쉽고 오류 발생을 줄일 수 있고 안전함
     - 근본적으로 thread-safe, 추가적인 동기화 작업 불필요
     - 정적 팩터리 메서드를 사용하여 동일한 객체의 중복 생성을 방지할 수 있다.
      =&amp;gt; 모든 생성자를 private으로 하고 정적 팩터리 메서드를 제공하면 클래스를 확장할 수 없게 만들수도 있음
     - 방어적 복사가 필요없다.
     - 자유롭게 공유할수 있고 불변 객체끼리는 내부 데이터를 공유할 수 있다. (공유할 내부 데이터가 가변이더라도 수정할 방법이 없으므로 상관 없음)
     - Map,Set 등의 객체에서 불변 객체를 구성요소로 사용하면 불변식을 유지하기 쉽다.
     - 실패 원자성(예외가 발생해도 객체 상태는 유효해야 함)을 제공한다.

    불변 객체의 단점
     - 다른 값의 객체가 필요하면 반드시 새로운 객체로 만들어야 함 (내부 값이 여러 종류일수록 비효율적)
        =&amp;gt; 가변 동반 클래스 (불변 객체 String 을 생성하는 다단계 연산을 제공하는 StringBuilder)
     */

    private static final Item17 start = new Item17(0, 0);

    public Item17 getStart() {
        return start;
    }
    //쓰임이 동일한 불변 객체의 중복 생성을 방지한다. =&amp;gt; 메모리 절약

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <category>Effective JAVA</category>
      <category>불변 객체</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/287</guid>
      <comments>https://rockintuna.tistory.com/287#entry287comment</comments>
      <pubDate>Sat, 5 Apr 2025 23:45:50 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 16 접근자 메서드</title>
      <link>https://rockintuna.tistory.com/286</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.List;

/*
public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
 */
public class Item16 {

    public List&amp;lt;String&amp;gt; memberList;
    /*
    public class에서 public 필드를 사용하면
    1. 내부 표현을 변경하려면 API가 변경되어야 한다.
     : public class의 public 필드 자체가 API 이면서 내부 표현이기 때문
    2. 불변식을 보장할 수 없다
     : 클라이언트에서 언제든지 필드의 인스턴스를 변경하거나 수정할 수 있다.
    3.부수 작업을 수행할 수 없다.
     : 접근자 메서드에서는 부가 작업을 추가하여 제공할 수 있다.
     */

    public int length;
    //불변 필드 이더라도 불변식만 보장할 뿐 위 문제는 동일하다.

    private Integer width;

    public int getWidth() {
        return width;
    }
    /*
    getter setter 메서드를 사용하면 모든 문제가 해결된다.
     */

    /*
    하지만 package-private이나 private 중첩 클래스에서는 public 필드를 쓰면 오히려 좋을 때도 있다.
     */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <category>Effective JAVA</category>
      <category>getter</category>
      <category>public 필드</category>
      <category>Setter</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/286</guid>
      <comments>https://rockintuna.tistory.com/286#entry286comment</comments>
      <pubDate>Fri, 4 Apr 2025 22:37:32 +0900</pubDate>
    </item>
    <item>
      <title>ITEM 15 정보 은닉</title>
      <link>https://rockintuna.tistory.com/285</link>
      <description>&lt;div style=&quot;background-color: #282c34; color: #bbbbbb;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package me.rockintuna.effectivejava.item;

import java.util.List;

/*
클래스와 멤버의 접근 권한을 최소화 하라
*/
public class Item15 {

    /*
    정보 은닉, 캡슐화의 장점
    컴포넌트들을 서로 독립시킨다. =&amp;gt; 개발,테스트,최적화,적용,분석,수정을 개별적으로 할 수 있게 해준다.
    - 개발 속도 (병렬 개발)
    - 관리 비용 (유지보수 쉬움)
    - 유닛 테스트를 통한 컴포넌트 성능 최적화
    - 재사용성
    - 거대한 시스템 개발을 쉽게 해줌

    Java의 정보 은닉 장치 [접근 제한자]를 통해 모든 클래스와 멤버의 접근성을 가능한 좁히자.

    public 접근 제한자 =&amp;gt;
    외부 접근을 허용한다는 것 (API) =&amp;gt;
    수정, 교체, 제거에 제한이 생김
    */
    public int id;
    // 위 멤버 변수는 외부에서 접근할 수 있으므로 변수 이름 또는 타입을 변경하면
    // 이 변수를 사용하는 클라이언트에 문제를 발생시킬 수 있다.

    public List&amp;lt;String&amp;gt; getMemberList() {
        // 구현
        return null;
    }
    // 이 메서드도 마찬가지로 메서드 응답 타입이나 이름 또는 구현이 바뀐다면
    // 클라이언트에 문제를 발생시킬 수 있다.

    private static interface Item15Interface {
        void method();
    }
    private static class Item15TopLevel {
        void method() {
            //
        }
    }
    //하나의 클래스에서만 사용하는 인터페이스나 탑레벨 클래스는 
    //클래스 내부에서 private static으로 중첩하여 생성하자
    //이렇게 하면 method()의 접근제한자가 package-private 이더라도 동일 패키지의 다른 클래스에서 사용할 수 없다.
    
    interface Item15Interface2 {
        void method();
        //public 일 필요가 없다면 package-private을 사용하자
        //접근 제한이 패키지 외부인지 내부인지에 따라서
        //public은 완전하게 패키지의 API이며
        //package-private은 내부 구현이다.
    }
    
    private String name;
    //공개 API를 제외한 모든 멤버는 private으로 만들자.
    //그 후 필요한 경우 package-private으로 풀어주자.
    // ::: Serializable을 구현한 클래스는 private 필드들도 API가 될 수 있다.

    String title;
    //package-private은 테스트 코드에서 활용할 수 있다.
    
    public String content;
    //public 클래스에는 public 필드를 사용하지 말자
    //public 가변 필드는 불변식을 보장할 수 없으며 일반적으로 thread-safe 하지 않다.
    
    //예외로,
    public static final String DEFAULT_NAME = &quot;jilee&quot;;
    //클래스가 표현하는 추상개념을 완성하는데 꼭 필요한 구성요소인 상수
    //꼭 기본 타입 또는 불변 객체를 사용해야 한다
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Book/이펙티브 자바</category>
      <category>Effective JAVA</category>
      <category>Java</category>
      <category>정보은닉</category>
      <category>캡슐화</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/285</guid>
      <comments>https://rockintuna.tistory.com/285#entry285comment</comments>
      <pubDate>Thu, 3 Apr 2025 23:34:23 +0900</pubDate>
    </item>
    <item>
      <title>ArrayList와 동시성</title>
      <link>https://rockintuna.tistory.com/284</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;redis에서 서로 독립적으로 조회하는 작업을 여러 스레드로 병렬 처리하기 위해 parallelStream을 사용하였다.&lt;/p&gt;
&lt;div style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// Get Projects
public List&amp;lt;ProjectResponseDto&amp;gt; getProjects(String rootId) {
  RelationshipResponseDto[] relationships = service.getRelationships(rootId);

  List&amp;lt;ProjectResponseDto&amp;gt; projectResponseDtoList = new ArrayList&amp;lt;&amp;gt;();
  if (relationships != null) {
    Arrays.stream(hasProject).toList().parallelStream().forEach(relationshipResponseDto -&amp;gt; {
      Project project = service.getProject(project);
      projectResponseDtoQueue.add(ProjectResponseDto.builder()
        .id(project.getId())
        .name(project.getName())
        .build());
    });
  }
  return projectResponseDtoList;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 조회되어야 하는 데이터는 3개로 고정되어 있는데, response를 확인해보면 가끔 2개 또는 1개인 경우가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현상을 자세히 확인하기 위해 테스트 요청을 해보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1742376959546&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for i in {1..100}
do
curl --location 'http://localhost:8020/projects/1' 2&amp;gt;/dev/null | jq length &amp;gt;&amp;gt; test.log
done&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgAzFs/btsMPLBMZcb/n8Tn9RDdjtga0Ev72GiTu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgAzFs/btsMPLBMZcb/n8Tn9RDdjtga0Ev72GiTu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgAzFs/btsMPLBMZcb/n8Tn9RDdjtga0Ev72GiTu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgAzFs%2FbtsMPLBMZcb%2Fn8Tn9RDdjtga0Ev72GiTu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;235&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100회 시도했을 때 10번 정도가 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parallelStream()은 병렬 작업이지만 동기적으로 처리되기 때문에 모든 스레드가 작업을 완료할 때 까지 대기할텐데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 병렬 처리에서 사용하는 ArrayList였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1742377186861&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;ArrayList (Java Platform SE 8 )&quot; data-og-description=&quot;Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;ArrayList (Java Platform SE 8 )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Note that this implementation is not synchronized.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java ArrayList는 동기화 되지 않는다라고 명시되어있다. 따라서 여러 스레드에서 동시에 ArrayList를 사용하면 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArrayList 대신 thread safe 구현체인 ConcurrentLinkedDeque를 사용하였다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// Get Projects
public List&amp;lt;ProjectResponseDto&amp;gt; getProjects(String rootId) {
  RelationshipResponseDto[] relationships = service.getRelationships(rootId);

  Queue&amp;lt;ProjectResponseDto&amp;gt; projectResponseDtoQueue = new ConcurrentLinkedDeque&amp;lt;&amp;gt;();
  if (relationships != null) {
    Arrays.stream(hasProject).toList().parallelStream().forEach(relationshipResponseDto -&amp;gt; {
      Project project = service.getProject(project);
      projectResponseDtoQueue.add(ProjectResponseDto.builder()
        .id(project.getId())
        .name(project.getName())
        .build());
    });
  }
  return new ArrayList&amp;lt;&amp;gt;(projectResponseDtoQueue);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Diary/Today I Learned</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/284</guid>
      <comments>https://rockintuna.tistory.com/284#entry284comment</comments>
      <pubDate>Wed, 19 Mar 2025 18:43:15 +0900</pubDate>
    </item>
    <item>
      <title>@Transactional 어노테이션이 protected에서는 동작하지 않는 이유</title>
      <link>https://rockintuna.tistory.com/283</link>
      <description>&lt;p&gt;@Transactional 어노테이션에 대한 이해도가 낮아 발생했던 문제를 해결하면서 공부한 내용을 기록한다.&lt;/p&gt;
&lt;p&gt;실제 작성했던 코드는 아니지만 동일한 상황을 재현&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
@RequiredArgsConstructor
public class MyService {
  private final MyEntityRepository myEntityRepository;
  private final MyEntityHistoryRepository myEntityHistoryRepository;

  public void saveEntity(String name) {
    saveEntityAndHistory(name);
  }

  @Transactional
  protected void saveEntityAndHistory(String name) {
    myEntityRepository.save(new MyEntity(name));
    myEntityHistoryRepository.save(new MyEntityHistory(name));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Controller에서 saveEntity() 메서드를 호출한다는 가정으로 문자열을 받으면 해당 문자열로 Entity와 EntityHistory를 DB에 저장하는 매우 간단한 코드이다.&lt;/p&gt;
&lt;p&gt;Entity와 EntityHistory는 동일한 메서드 내에서 @Transactional 어노테이션으로 인해 선언적 트랜젝션으로 동작하며 하나라도 save 작업에 문제가 있는 경우 두 처리 모두 rollback 될 것으로 예상했다.&lt;/p&gt;
&lt;p&gt;오류가 발생하지 않는 경우 문제가 있다는 것을 인지하지 못했으나 saveEntityAndHistory() 메서드에서 오류가 발생했을 때 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;예를들어 아래와 같이 두 save() 중간에 Rumtime Exception을 발생시켜보았다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
@RequiredArgsConstructor
public class MyService {
  private final MyEntityRepository myEntityRepository;
  private final MyEntityHistoryRepository myEntityHistoryRepository;

  public void saveEntity(String name) {
    saveEntityAndHistory(name);
  }

  @Transactional
  protected void saveEntityAndHistory(String name) {
    myEntityRepository.save(new MyEntity(name));
    throwRuntimeException();
    myEntityHistoryRepository.save(new MyEntityHistory(name));
  }

  private void throwRuntimeException() {
    throw new RuntimeException(&amp;quot;Exception&amp;quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;나는 선언적 트랜젝션에 의해 Entity 저장 작업이 롤백되어 Entity, Entity History 모두 저장되지 않는 것을 예상했다.&lt;/p&gt;
&lt;p&gt;그러나&amp;#x20;&lt;/p&gt;
&lt;p&gt;Entity History는 예상되로 저장되지 않았지만 Entity 롤백되지 않고 DB에 저장되었다.&lt;/p&gt;
&lt;p&gt;@Transactional을 붙였음에도 잘 적용되지 않은 것일까?&lt;/p&gt;
&lt;p&gt;Transaction이 활성화 되었는지 확인하는 코드를 추가했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Transactional
  protected void saveEntityAndHistory(String name) {
    if (TransactionSynchronizationManager.isActualTransactionActive()) {
      System.out.println(&amp;quot;Transaction is active.&amp;quot;);
    } else {
      System.out.println(&amp;quot;No active transaction.&amp;quot;);
    }
    myEntityRepository.save(new MyEntity(name));
    throwRuntimeException();
    myEntityHistoryRepository.save(new MyEntityHistory(name));
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과는 &amp;quot;No active transaction.&amp;quot; ....&lt;/p&gt;
&lt;p&gt;왜일까?&lt;/p&gt;
&lt;p&gt;서비스 코드를 수정해보자&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
@RequiredArgsConstructor
public class MyService {
  private final MyEntityRepository myEntityRepository;
  private final MyEntityHistoryRepository myEntityHistoryRepository;

  @Transactional
  public void saveEntity(String name) {
    if (TransactionSynchronizationManager.isActualTransactionActive()) {
      System.out.println(&amp;quot;Transaction is active.&amp;quot;);
    } else {
      System.out.println(&amp;quot;No active transaction.&amp;quot;);
    }
    myEntityRepository.save(new MyEntity(name));
    throwRuntimeException();
    myEntityHistoryRepository.save(new MyEntityHistory(name));
  }

  private void throwRuntimeException() {
    throw new RuntimeException(&amp;quot;Exception&amp;quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;고친것&amp;#x20;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;@Transactional 어노테이션을 외부에서 호출하는 메서드에 달았다.&lt;/li&gt;
&lt;li&gt;@Transactional 어노테이션이 달린 메서드가 public으로 변경되었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;결과는 &amp;quot;Transaction is active.&amp;quot;, Entity도 정상적으로 롤백되었다.&lt;/p&gt;
&lt;p&gt;원인을 찾아야한다.&lt;/p&gt;
&lt;p&gt;@Transactional의 공식문서에는 짧지만 알고 있으면 좋을 내용들이 많았다.&lt;/p&gt;
&lt;p&gt;{% embed url=&amp;quot;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&amp;quot;&quot;&gt;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&amp;quot;&lt;/a&gt; %}&lt;/p&gt;
&lt;p&gt;&lt;code&gt;The default mode (proxy) processes annotated beans to be proxied by using Spring’s AOP framework&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;@Transactional 어노테이션은 기본적으로 Spring AOP를 사용한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;@Transactional의 경우 Spring AOP의 대표적인 예시이며, AOP를 통해 메서드에 부가적인 기능(선언적 트랜젝션)을 추가한 것이다.&lt;/p&gt;
&lt;p&gt;Spring AOP는 CGLIB 또는 JDK 동적 프록시를 사용하여 프록시 빈을 생성한다. 즉 MyService의 메서드가 외부에서 호출되는 경우 MyService에 직접 호출하는 게 아니고 프록시 빈을 거쳐서 호출이 전달된다.&lt;/p&gt;
&lt;p&gt;즉 다른 인스턴스(프록시 빈)가 이 메서드를 호출 가능해야한다!&lt;/p&gt;
&lt;p&gt;고로 private 메서드는 @Transactional를 적용할 수 없다. 나머지 접근제어는 아래에서.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;The @Transactional annotation is typically used on methods with public visibility. As of 6.0, protected or package-visible methods can also be made transactional for class-based proxies by default.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;사실 public만 사용해야 하는 줄 착각하고 있었고 문제도 그것 때문인 줄 알았다. 그러나 spring 6.0 이상에서는 class-based proxy에서 protected와 package-private(default)에서도 @Transactional을 선언할 수 있다.&lt;/p&gt;
&lt;p&gt;class-based proxy라 함은 인터페이스 대신 클래스를 통해 프록시 빈이 만들어진, 즉 CGLIB로 상속을 통해 프록시를 만들게 되었을 때이다.&lt;/p&gt;
&lt;p&gt;상속을 이용한 프록시 빈에서는 protected에 접근할 수 있으며, Transactional 구현에 따라 default에서도 접근할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;인터페이스의 추상메서드는 public이므로 어차피 다른 접근 제어를 사용할 수 없다.&lt;/p&gt;
&lt;p&gt;제목은 &amp;quot;@Transactional 어노테이션이 protected에서는 동작하지 않는 이유&amp;quot; 이지만 실제로는 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;내 코드가 정상적이지 않았던 진짜 이유이다.&lt;/p&gt;
&lt;p&gt;self-invocation이라고 표현된 메서드에서 동일한 클래스 내의 메서드 호출은 @Transactional이 메서드에 걸려 있어도 트랜젝션이 적용되지 않는다고 한다.&lt;/p&gt;
&lt;p&gt;saveEntityAndHistory() 메서드는 MyService 클래스의 saveEntity()안에서 내부적으로 호출되는데 이는 프록시가 인터셉트하지 않기 때문이다.&lt;br&gt;프록시가 인터셉트하지 않는다는 것은 AOP로 구현한 Aspect, 즉 트랜젝션이 적용되지 않는 것이다.&lt;/p&gt;
&lt;p&gt;하지만 @Transactional의 전파를 통해 내부에서 호출되는 메서드의 오류를 해당 메서드를 호출하는 외부 접근 메서드에서 처리할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
@RequiredArgsConstructor
public class MyServiceImpl implements MyService {
  private final MyEntityRepository myEntityRepository;
  private final MyEntityHistoryRepository myEntityHistoryRepository;

  @Transactional
  public void saveEntity(String name) {
    saveEntityAndHistory(name);
  }

  private void saveEntityAndHistory(String name) {
    myEntityRepository.save(new MyEntity(name));
    myEntityHistoryRepository.save(new MyEntityHistory(name));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위처럼 오류는 saveEntityAndHistory()에서 발생하지만, saveEntity에서 롤백을 할 수 있다.&lt;/p&gt;
&lt;p&gt;@Transactional 어노테이션 및 AOP에 대한 이해가 부족한 상태에서 무의식적으로 코딩하는 것이 문제를 발생시킨 것 같다.&lt;/p&gt;
&lt;p&gt;그래도 왜 문제가 발생했는지와 AOP에 대해서 조금더 알게된 좋은 경험이 된 것 같다.&lt;/p&gt;</description>
      <category>IT Study/Spring Boot</category>
      <author>이정인</author>
      <guid isPermaLink="true">https://rockintuna.tistory.com/283</guid>
      <comments>https://rockintuna.tistory.com/283#entry283comment</comments>
      <pubDate>Thu, 6 Mar 2025 22:06:03 +0900</pubDate>
    </item>
  </channel>
</rss>