<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>almondbreez0_3.log</title>
        <link>https://velog.io/</link>
        <description>백엔드 개발자</description>
        <lastBuildDate>Wed, 24 Jan 2024 15:01:04 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>almondbreez0_3.log</title>
            <url>https://images.velog.io/images/almondbreez0_3/profile/7436a751-203a-4343-8e49-1d794e1b82e7/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. almondbreez0_3.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/almondbreez0_3" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[이펙티브 자바 5]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-5</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-5</guid>
            <pubDate>Wed, 24 Jan 2024 15:01:04 GMT</pubDate>
            <description><![CDATA[<h1 id="이펙티브-자바-5장">이펙티브 자바 5장</h1>
<blockquote>
<p>💡 item 26 : raw 타입은 사용하지 말자</p>
</blockquote>
<p>raw 타입이란 제네릭 타입에서 타입 매개 변수를 전혀 사용하지 않은 타입을 말한다. 현재로서는 제네릭 이전의 코드와 호환되기 위해 사용될 뿐, 런타임 시점에 오류를 발생할 소지가 많다</p>
<ul>
<li>클래스, 인터페이스 선언에 ‘타입 매개변수&#39;가 쓰이면 제네릭 클래스, 인터페이스라고 부름</li>
<li>‘제네릭 타입’은 <strong>매개변수화 타입</strong>을 정의한다.</li>
<li>‘제네릭 타입&#39;을 정의하면 ‘raw 타입&#39; 도 정의된다. (타입 매개변수를 사용하지 않는 타입) ex) List<E> → List (제네릭 도래 전 코드와 호환을 위해 존재)</li>
</ul>
<h2 id="raw-type의-단점">raw type의 단점</h2>
<p>컬렉션의 raw 타입</p>
<pre><code class="language-java">private final Collection stamps = ...;
...
stamps.add(new Coin(...)); // 실수로 동전을 넣는다.</code></pre>
<ul>
<li>따라서 오류가 발생하고 한참 뒤인 런타임에서야 오류를 알아챌 수 있는데 이렇게 되면 원인을 제공한 코드와 런타임에 문제가 발생한코드가 떨어져있어 에러를 잡기위해 코드 전체를 훑어봐야 할 수도 있다.</li>
<li>이런 문제를 해결하기 위해 매개변수화된 컬렉션 타입으로 <strong>타입 안정성</strong>을 확보해야한다.</li>
</ul>
<h2 id="매개변수화된-컬렉션-타입">매개변수화된 컬렉션 타입</h2>
<pre><code class="language-java">private final Collection&lt;Stamp&gt; stamps = ...;</code></pre>
<ul>
<li>이렇게 선언하면 컴파일러가 stamps 컬렉션에 Stamp 인스턴스만 넣어야함을 인지하여 의도대로 동작함을 보장해준다.</li>
<li>만약, 다른 타입의 인스턴스를 넣으려하면 컴파일 오류가 발생하며 문제를 알려준다.</li>
</ul>
<h2 id="list와-listobject"><strong>List와 List<code>&lt;Object&gt;</code></strong></h2>
<ul>
<li>로 타입을 쓰면 제네릭이 안겨주는 안정성과 표현성을 모두 잃게된다.</li>
<li>그럼에도 불구하고 이런 로 타입을 남겨놓은 이유는 자바에 제네릭을 받아들이기 이전 코드와의 호환성 때문이다.</li>
<li>제네릭 타입이 아닌 <code>List</code>와 달리 <code>List&lt;Object&gt;</code>는 모든 타입을 허용한다는 의사를 컴파일러에 전달한 것이다.</li>
</ul>
<h2 id="제네릭-하위-타입-규칙"><strong>제네릭 하위 타입 규칙</strong></h2>
<p><code>List</code>를 받는 메서드에 <code>List&lt;String&gt;</code>을 넘길 수 있지만, <code>List&lt;Object&gt;</code>를 받는 메서드에는 넘길 수 없다.</p>
<p>//가능</p>
<pre><code class="language-java">public static void main(String[] args) {
        final List&lt;String&gt; strings = new ArrayList&lt;&gt;();
        unsafeAdd(strings, Integer.valueOf(42));
        String s = strings.get(0);
}

private static void unsafeAdd(final List list, final Integer valueOf) {
        list.add(0);
}</code></pre>
<p>//불가능</p>
<pre><code class="language-java">public static void main(String[] args) {
        final List&lt;String&gt; strings = new ArrayList&lt;&gt;();
        unsafeAdd(strings, Integer.valueOf(42));
        String s = strings.get(0);
}

private static void unsafeAdd(final List&lt;Object&gt; list, final Integer valueOf) {
        list.add(0);
}</code></pre>
<h2 id="비한정적-와일드카드-타입"><strong>비한정적 와일드카드 타입</strong></h2>
<ul>
<li>제네릭 타입을 사용하고싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 물음표(<code>?</code>)를 사용하자.</li>
</ul>
<h2 id="로-타입을-사용해야하는-경우"><strong>로 타입을 사용해야하는 경우</strong></h2>
<ul>
<li><strong>class 리터럴에는 로타입을 사용해야한다.</strong></li>
</ul>
<pre><code class="language-java">List.class, String[].class, int.class</code></pre>
<ul>
<li><strong>instanceof 연산자 사용시 로 타입을 사용하자.</strong></li>
</ul>
<pre><code class="language-java">if (o instanceof Set) { // 로 타입
    Set&lt;?&gt; s = (Set&lt;?&gt;) o; // 와일드카드 타입
}</code></pre>
<ul>
<li>런타임에는 제네릭 타입 정보가 지워지므로 <code>instanceof</code> 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.</li>
</ul>
<blockquote>
<p>💡 item 27 : <strong><strong>비검사 경고를 제거하라</strong></strong></p>
</blockquote>
<p>비검사 경고가 발생하는 코드</p>
<pre><code class="language-java">Set&lt;Lark&gt; exaltation = new HashSet();</code></pre>
<p>해결한 코드</p>
<pre><code class="language-java">Set&lt;Lark&gt; exaltation = new HashSet&lt;&gt;();</code></pre>
<h2 id="suppresswarningsuncheck를-이용한-비검사-경고-제거"><strong>@SuppressWarnings(&quot;uncheck&quot;)를 이용한 비검사 경고 제거</strong></h2>
<ul>
<li>만약, 경고를 제거할 수는 없지만 <strong>타입 안전하다고 확신</strong>할 수 있다면 <code>@SuppressWarnings(&quot;unchecked&quot;)</code>를 이용해 비검사 경고를 숨기자.</li>
<li>만약, 타입 안전하다고 검증된 코드의 검사를 그대로 두면 진짜 문제를 알리는 경고 코드를 구분하기 쉽지 않다.</li>
<li>또한 타입 안전함을 검증하지 않은 채 경고를 숨기면 잘못된 보안인식을 심어주는 꼴이된다.</li>
</ul>
<h2 id="suppresswarningsuncheck-사용범위"><strong>@SuppressWarnings(&quot;uncheck&quot;) 사용범위</strong></h2>
<ul>
<li><code>@SuppressWarnings(&quot;unchecked&quot;)</code>는 가능한 좁은 범위에 적용하자.</li>
<li><code>@SuppressWarnings</code> 애너테이션은 개별 지역변수 선언부터 클래스 전체까지 어떤 선언에도 달 수 있다.</li>
<li>한줄이 넘는 메서드나 생성자에 <code>@SuppressWarnings</code>가 달려있다면 지역변수나 아주 짧은 메서드 혹은 생성자로 옮기자.</li>
<li>!! 절대로 클래스 전체에 적용해서는 안된다. !!</li>
</ul>
<p>[기존 ArrayList의 toArray 메서드]</p>
<pre><code class="language-java">public &lt;T&gt; T[] toArray(T[] a) {
        if (a.length &lt; size)
            // Make a new array of a&#39;s runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length &gt; size)
            a[size] = null;
        return a;
}</code></pre>
<ul>
<li>위 코드를 컴파일하면 <code>@copyOf()</code>@ 부분에서 경고가 발생한다. 이 경고를 제거하려면 지역변수를 추가해야 한다.</li>
</ul>
<p><strong>[지역변수를 추가해 @SuppressWarnings의 범위를 좁힌다.]</strong></p>
<pre><code class="language-java">public &lt;T&gt; T[] toArray(T[] a) {
    if (a.length &lt; size)
        // 생성한 배열과 매개변수로 받은 배열이 모두 T[]로 같으므로
        // 올바른 형변환이다.
        @SuppressWarnings(&quot;unchecked&quot;) 
        T[] result = (T[]) Arrays.copyOf(elementData, size, a.getClass()); 
        return result
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length &gt; size)
        a[size] = null;
    return a;
}</code></pre>
<ul>
<li><strong><code>@SuppressWarnings(&quot;unchecked&quot;)</code> 에너테이션을 사용할 때면 그 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야한다.</strong></li>
</ul>
<blockquote>
<p>💡 item 28 : raw <strong><strong>배열보다는 리스트를 사용하라</strong></strong></p>
</blockquote>
<h2 id="배열-vs-리스트">배열 VS 리스트</h2>
<p><strong>첫 번째 - 배열은 공변인 반면 리스트는 불공변이다.</strong></p>
<pre><code class="language-java">공변: 함께 변한다
불공변: 함께 변하지 않는다.</code></pre>
<ul>
<li>배열의 경우 <code>Sub</code>가 <code>Super</code>의 하위 타입이라면 <code>Sub[]</code>는 배열 <code>Super[]</code>의 하위 타입이 된다.</li>
<li>반면, 리스트의 경우 서로 다른 타입 <code>Type1</code>, <code>Type2</code>가 있을 때, <code>List&lt;Type1&gt;</code>은 <code>List&lt;Type2&gt;</code>의 하위 타입도 아니고 상위 타입도 아니다.</li>
</ul>
<p><strong>두 번째 - 배열은 실체화된다.</strong></p>
<ul>
<li>배열은 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 그래서 <code>Long</code> 타입 배열에 <code>String</code> 타입 데이터를 입력하려고하면 <code>ArrayStoreException</code>이 발생한다.</li>
<li>반면, 리스트는 타입 정보가 런타임에는 소거된다. 원소 타입을 컴파일시에만 검사하며 런타임에는 알 수 없다는 말이다.</li>
</ul>
<p><strong>[제네릭 배열 생성을 허용하지 않는 이유 - 컴파일되지 않는다.]</strong></p>
<pre><code class="language-java">List&lt;String&gt;[] stringLists = new List&lt;String&gt;[1]; // (1) 허용된다고 가정해보자.
List&lt;Integer&gt; intList = List.of(42);              // (2) 원소가 하나인 List&lt;Integer&gt; 생성
Object[] objects = stringLists;                   // (3) stringLists를 objects에 할당
objects[0] = intList;                             // (4) intList를 objects의 첫번째 원소로 저장한다.
String s = stringLists[0].get(0);                 // (5) stringList[0]에 들어가있는 첫번째 요소는 Integer이므로 형변환 오류 발생.</code></pre>
<ul>
<li>제네릭 배열을 만들지 못하게하는 이유는 컴파일러가 자동 생성한 형변환 코드에서 런타임에 <code>ClassCastException</code>이 발생할 수 있기 때문에 타입 안전하지 않기 때문이다.</li>
<li>또한 런타임에 <code>ClassCastException</code>이 발생하는 것을 막아주겠다는 제네릭 타입 시스템 취지에 어긋나는 일이기도 하다.</li>
</ul>
<p>배열은 실체화 불가 타입</p>
<ul>
<li><code>E</code>, <code>List&lt;E&gt;</code>, <code>List&lt;String&gt;</code> 같은 타입을 <strong>실체화 불가 타입</strong>이라고 한다.</li>
<li>쉽게 말해, 실체화 되지 않아서 런타임에는 컴파일타임보다 타입 정보를 적게 가지는 타입이다</li>
<li>소거 매커니즘 때문에 매개변수화 타입 가운데 실체화될 수 있는 타입은 <code>List&lt;?&gt;</code>, <code>Map&lt;?,?&gt;</code> 같은 비한정적 와일드카드 타입뿐이다.</li>
<li>참고로, 배열은 비한정적 와일드카드로 만들수는 있지만 유용하게 쓰일 일은 거의 없다.</li>
</ul>
<p><strong>@SafeVarargs</strong></p>
<blockquote>
<p><code>@SafeVarargs</code>는 메서드 작성자가 해당 메서드가 타입 안전하다는 것을 보장하는 장치이다.</p>
</blockquote>
<ul>
<li>제네릭 컬렉션에서는 자신의 원소 타입을 담은 배열을 반환하는게 보통은 불가능하다. 또한 제네릭 타입과 가변인수 메서드를 함께 쓰면 해석하기 어려운 경고 메시지를 받게 된다.</li>
<li>가변인수 메서드를 호출할 때마다 가변인수 매개변수를 담을 배열이 하나 만들어지는데 이때 그 배열의 원소가 실체화 불가 타입이라면 경고가 발생한다.</li>
<li>이 때 <code>@SafeVarargs</code>를 사용하면 잠재적 오류에 대한 경고를 무시함으로써 해결할 수 있다. 만약, 메서드가 타입 안전하지 않다면 절대 <code>@SafeVarargs</code>를 사용해서는 안된다.</li>
</ul>
<pre><code class="language-java">public class SafeVars {
    @SafeVarargs
    public static void print(List... names) {
        for (List&lt;String&gt; name : names) {
            System.out.println(name);
        }
    }

    public static void main(String[] args) {
        SafeVars safeVars = new SafeVars();
        List&lt;String&gt; list = new ArrayList&lt;&gt;();

        list.add(&quot;b&quot;);
        list.add(&quot;c&quot;);
        list.add(&quot;a&quot;);
        print(list);
    }
}</code></pre>
<p><strong>배열대신 컬렉션을 사용하자</strong></p>
<ul>
<li>배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분 배열 <code>E[]</code>대신 <code>List&lt;E&gt;</code>를 사용하면 해결된다.</li>
<li><strong>조금 복잡해지고 성능이 나빠질 수 있지만 타입안정성이 보장되고 상호 운용성이 좋아진다.</strong></li>
</ul>
<p><strong>[Chooser - 제네릭 적용 필요]</strong></p>
<pre><code class="language-java">public class Chooser {
    private final Object[] choiceArray;

    public Chooser(final Object[] choiceArray) {
        this.choiceArray = choiceArray;
    }

    public Object choose(){
        Random random = ThreadLocalRandom.current();
        return choiceArray[random.nextInt(choiceArray.length)];
    }
}</code></pre>
<p><strong>[리스트 기반 Chooser - 타입 안정성 확보]</strong></p>
<pre><code class="language-java">public class ListChooser {
    private final List&lt;T&gt; choiceList;

    public ListChooser(final Collection&lt;T&gt; choices) {
        this.choiceList = new ArrayList&lt;&gt;(choices);
    }

    public T choose(){
        Random random = ThreadLocalRandom.current();
        return choiceList[random.nextInt(choiceList.size())];
    }
}</code></pre>
<ul>
<li>코드는 조금 길어졌지만 리스트를 사용함으로써 런타임에 ClassCastException을 만날일이 없어졌다.</li>
</ul>
<p>질문?</p>
<p>위 두 코드 차이가 뭐임?</p>
<pre><code class="language-java">[Chooser - 제네릭 적용 필요]

public class Chooser {
    private final Object[] choiceArray;

    public Chooser(final Object[] choiceArray) {
        this.choiceArray = choiceArray;
    }

    public Object choose(){
        Random random = ThreadLocalRandom.current();
        return choiceArray[random.nextInt(choiceArray.length)];
    }
}
위 클래스를 사용하려면 choose 메서드를 호출할 때마다 반환된 Object를 원하는 타입으로 형변환해야 한다. 만약 타입이 다른 원소가 들어있으면 런타임시에 형변환 오류가 발생한다.

[리스트 기반 Chooser - 타입 안정성 확보]

public class ListChooser {
    private final List&lt;T&gt; choiceList;

    public ListChooser(final Collection&lt;T&gt; choices) {
        this.choiceList = new ArrayList&lt;&gt;(choices);
    }

    public T choose(){
        Random random = ThreadLocalRandom.current();
        return choiceList[random.nextInt(choiceList.size())];
    }
}</code></pre>
<blockquote>
<p>💡 item <strong><strong>29 : 이왕이면 제네릭 타입으로 만들라</strong></strong></p>
</blockquote>
<p><strong>일반 클래스를 제네릭 타입으로 변경</strong></p>
<p><strong>[Object 기반 스택 - 제네릭이 절실한 강력후보]</strong></p>
<ul>
<li>위와 같은 코드 상태로는 스택에서 꺼낸 객체를 형변환해야 하는데 이때 런타임 오류가 발생할 가능성이 있다. 따라서 제네릭 타입 클래스로 바꾸는것이 좋다.</li>
<li>일반 클래스에서 제네릭 타입 클래스로 만드는 시작은 타입 매개변수를 추가하는 것이다. 이때 타입이름으로 보통 <code>E</code>를 사용한다</li>
</ul>
<pre><code class="language-java">public class ObjectStack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public ObjectStack() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }

        Object result = elements[--size];
        elements[size] = null; // 다 쓴 참조 해제

        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}</code></pre>
<p><strong>타입 매개변수 오류를 해결하는 두가지 방법</strong></p>
<pre><code class="language-java">public class ObjectStack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public ObjectStack() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }

        Object result = elements[--size];
        elements[size] = null; // 다 쓴 참조 해제

        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}</code></pre>
<pre><code class="language-java">public class GenericStack&lt;E&gt; {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public GenericStack() {
        this.elements = new E[DEFAULT_INITIAL_CAPACITY]; // 오류발생
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }

        E result = elements[--size];
        elements[size] = null; // 다 쓴 참조 해제

        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}</code></pre>
<ul>
<li>하지만 • 위 코드의 생성자에서 오류가 발생한다. <a href="https://velog.io/@alkwen0996/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EC%95%84%EC%9D%B4%ED%85%9C28-%EB%B0%B0%EC%97%B4%EB%B3%B4%EB%8B%A4%EB%8A%94-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC">아이템 28</a>에서 설명한 것처럼 <code>E</code>와 같은 실체화 불가 타입으로는 배열을 만들 수 없기 때문이다.</li>
<li>위 문제를 해결하는 두 가지 방법이 있다.</li>
</ul>
<p><strong>타입 매개변수 오류를 해결하는 두가지 방법</strong></p>
<p><strong>1. <code>Object</code> 배열을 생성하고 그 다음 제네릭 배열로 형변환하는 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법.</strong></p>
<p><strong>[Object 배열 생성 시 배열 형변환]</strong></p>
<pre><code class="language-java">...
public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
}

public GenericStack() {
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; // 경고 메시지 발생
}

private void ensureCapacity() {
    if (elements.length == size) {
        elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}
...
// 비검사 형변환 경고 메시지 발생
Unchecked cast: &#39;java.lang.Object[]&#39; to &#39;E[]</code></pre>
<ul>
<li>위 코드에서는 비검사 형변환 경고 메시지가 발생한다.</li>
<li>위 코드에서 <code>elements</code> 배열은 <code>private</code> 필드에 저장된 후 클라이언트로 반환되거나 다른 메서드에 전달되지 않는다.</li>
<li><code>push</code> 메서드를 통해 배열에 저장되는 원소의 타입은 항상 <code>E</code>이다.</li>
<li>위 두가지 이유로 이 비검사 형변환은 확실히 안전하다.</li>
<li>비검사 형변환 경고를 없애기 위해 범위를 최대한 줄여 <code>@SuppressWarnings(&quot;unchecked&quot;)</code>를 사용하여 경고를 숨긴다.</li>
</ul>
<p><strong>[비검사 형변환 경고 숨김]</strong></p>
<pre><code class="language-java">...
public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
}

// 배열 elements는 push(E)로 넘어온 E인스턴스만 남는다.
// 타입 안정성을 보장하지만 이 배열의 런타임 타입은 Object[] 이다.
@SuppressWarnings(&quot;unchecked&quot;)
public GenericStack() {
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; // 경고 메시지 발생
}

private void ensureCapacity() {
    if (elements.length == size) {
        elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}
...
// 비검사 형변환 경고 메시지 발생
Unchecked cast: &#39;java.lang.Object[]&#39; to &#39;E[]</code></pre>
<ul>
<li>생성자가 배열 생성말고는 따로 하는 기능이 없기 때문에 생성자 전체에서 경고를 숨겨도 된다.</li>
</ul>
<p><strong>2. <code>elements</code> 필드의 타입을 <code>E[]</code>에서 <code>Object[]</code>로 바꾸는 방법.</strong></p>
<p><strong>[Object 배열은 그대로 두고 pop() 사용시 형변환]</strong></p>
<pre><code class="language-java">// 비검사 경고를 적절히 숨긴다.
public class GenericStack&lt;E&gt; {
    private Object[] elements;
    ...
    public GenericStack() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        // push에서 E 타입만 허용하므로 이 형변환은 안전하다.
        @SuppressWarnings(&quot;unchecked&quot;)
        E result = (E) elements[--size];
        elements[size] = null; // 다 쓴 참조 해제

        return result;
    }
    ...
}</code></pre>
<p><strong>제네릭 배열 생성을 제거하는 두 방법의 장단점</strong></p>
<ul>
<li>1 번째 방법은 가독성이 좋고 코드가 짧다. 또한 배열을 한번만 생성하면 된다. 하지만, <code>E</code>가 <code>Object</code>가 아니기 때문에 (배열의 컴파일타임의 타입과 런타임의 타입이 다르기 때문에) <a href="https://velog.io/@adduci/Java-%ED%9E%99-%ED%8E%84%EB%A3%A8%EC%85%98-Heap-pollution">힙 오염</a>을 일으킨다.</li>
<li>두 번째 방법은 배열을 원소를 읽을때마다 생성해야 한다.</li>
</ul>
<p>이 item에 대해 얘기해보자</p>
<blockquote>
<p>💡 item <strong><strong>아이템30 : 이왕이면 제네릭 메서드로 만들라</strong></strong></p>
</blockquote>
<p><strong>제네릭 메서드 작성방법</strong></p>
<ul>
<li>매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다.<ul>
<li><code>Collections</code>의 <code>binarySearch</code>, <code>sort</code> 등 알고리즘 메서드는 모두 제네릭이다.</li>
</ul>
</li>
<li>제네릭 메서드 작성법은 제네릭 타입 작성법과 비슷하다.</li>
<li>타입 매개변수 목록은 메서드의 제한자와 반환 타입 사이에 온다.</li>
</ul>
<p><strong>[제네릭 메서드]</strong></p>
<pre><code class="language-java">public static &lt;E&gt; Set&lt;E&gt; union (Set&lt;E&gt; s1, Set&lt;E&gt; s2){
    Set&lt;E&gt; result = new HashSet&lt;&gt;(s1);
    result.addAll(s2);
    return result;
}</code></pre>
<ul>
<li>위 코드는 세개의 Set 집합이 타입이 모두 같아야 한다. 이를 한정적 와일드 카드 타입을 이용하면 더 유연하게 개선이 가능하다.</li>
</ul>
<p><strong>제네릭 싱글턴 팩터리</strong></p>
<ul>
<li>불변 객체를 여러 타입으로 활용할 때가 있다. 제네릭은 런타임시 타입 정보가 소거 되므로 하나의 객체를 어떤 타입으로든 매개변수화 할 수 있다.</li>
<li>불변 객체를 여러 타입으로 활용할 때가 있다. 제네릭은 런타임시 타입 정보가 소거 되므로 하나의 객체를 어떤 타입으로든 매개변수화 할 수 있다.</li>
<li>이 정적 팩터리를 <strong>제네릭 싱글턴 팩터리</strong>라고 한다.</li>
</ul>
<p><strong>[제네릭 싱글턴 팩터리 패턴 - 항등함수]</strong></p>
<pre><code class="language-java">private static UnaryOperator&lt;Object&gt; IDENTITY_FN = (t) -&gt; t;

@SuppressWarinings(&quot;unchecked&quot;)
public static &lt;T&gt; UnaryOperator&lt;T&gt; identityFunction(){
    return (UnaryOperator&lt;T&gt;) IDENTITY_FN;
}</code></pre>
<ul>
<li>항등함수는 입력 값을 수정 없이 그대로 반환하는 특별한 함수이므로 <code>T</code>가 어떤 타입이든 <code>UnaryOperator&lt;T&gt;</code>를 사용해도 타입 안전하다.</li>
</ul>
<p><strong>재귀적 한정적 타입</strong></p>
<ul>
<li><strong>재귀적 타입 한정은</strong> 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정하는 개념이다.</li>
<li>이런 재귀적 타입 한정은 주로 타입의 자연적 순서를 지정해주는 <code>Comparable</code>과 함께 사용된다.</li>
</ul>
<pre><code class="language-java">public interface Comparable&lt;T&gt;{
    int compareTo(T o);
}</code></pre>
<ul>
<li>타입 매개변수 <code>T</code>는 <code>Comparable&lt;T&gt;</code>를 구현한 타입이 비교할 수 있는 원소의 타입을 정의한다.</li>
</ul>
<p><strong>[재귀적 타입 한정을 이용해 상호 비교 할 수 있음을 표현]</strong></p>
<pre><code class="language-java">public static &lt;E extends Comparable&lt;E&gt;&gt; E max(Collection&lt;E&gt; c);</code></pre>
<ul>
<li><code>&lt;E extends Comparable&lt;E&gt;&gt;</code>가 모든 타입 <code>E</code>는 자신과 비교할 수 있다는 의미를 갖는다.</li>
<li>아래는 재귀적 타입 한정을 이용한 메서드를 구현했다. 컴파일오류나 경고는 발생하지 않으며 컬렉션에 담긴 원소의 자연적 순서를 기준으로 최댓값을 계산한다.</li>
</ul>
<p><strong>[재귀적 타입 한정을 이용한 최댓값 계산 메서드]</strong></p>
<pre><code class="language-java">public static &lt;E extends Comparable&lt;E&gt;&gt; E max(Collection&lt;E&gt; c){
    if(c.isEmpty()){
       throw new IllegalArgumentException(&quot;컬렉션이 비었습니다.&quot;);
    }

    E result = null;
    for (E e : c){
        if(result == null || e.compareTo(result) &gt; 0){
            result = Objects.requireNonNull(e);
        }
    }

    return result;
}</code></pre>
<blockquote>
<p>💡<strong><strong>item31 한정적 와일드 카드를 사용해 API의 유연성을 높여라</strong></strong></p>
</blockquote>
<p>제네릭은 기본적을 불공변이다. List<Object>와 List<String>은 서로 아무런 관계가 없는 타입이라는 뜻이다. 그러나 경우에 불공변보다 조금 더 유연한 방식이 필요할 때가 있다</p>
<p>→ 한정적 와일드 카드를 활용해 제네릭의 유연성을 높인다.</p>
<pre><code class="language-java">public class Stack&lt;E&gt; {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}
//여기에 일련의 원소들을 추가하는 메서드를 추가한다고 하자
public void pushAll(Iterable&lt;E&gt; src) {
    for (E e : src) {
        push(e);
    }
}</code></pre>
<p>이 코드는 문제는 없지만 완벽한 코드라 볼 수 없다. 그 이유는 만약 Stack<Number>로 선언한 후 pushAll(intVal)을 호출하면 어떻게 될까? intVal은 Integer이다.</p>
<p>결과 : 논리적으로 볼 때는 될 것 같았지만 컴파일 조차 되지 않는다. 그 이유는 불공변이기 때문에 Number를 상속한 Integer라도 Iterable<Integer>와 Iterable<Number> 는 서로 아무런 연관이 없는 타입이기 때문이다.</p>
<pre><code class="language-java">//한정적 와일드 카드를 통해 해결한 코드
public void pushAll(Iterable&lt;? extends E&gt; iterable) {
    for (E e : iterable) {
        push(e);
    }
}</code></pre>
<h2 id="비한정적-와일드카드-적용-방법"><strong>비한정적 와일드카드 적용 방법</strong></h2>
<ul>
<li><code>&lt;? extends E&gt;</code>를 통해 우리는 와일드 카드를 활용하면 상속에 유연한 제네릭 코드를 만들 수 있음을 알았다. 그러나 비한정적 와일드 카드 적용에는 일련의 규칙이 있는데 이를 알아보자.</li>
</ul>
<p><strong>PECS(펙스)</strong></p>
<ul>
<li>Producer-Extends Consumer-super 라는 공식으로 공급할 때는 extends를 활용하고 사용할 때는 super를 사용하는 공식이다. 예를 통해 확인해보자</li>
</ul>
<p><strong>Producer - extends</strong></p>
<pre><code class="language-java">public static &lt;E&gt; Set&lt;E&gt; union(Set&lt;E&gt; s1, Set&lt;E&gt; s2)</code></pre>
<ul>
<li>위의 코드를 살펴보면 s1, s2는 생산자이다. 여기서 생산자란 의미는 s1, s2를 입력으로 사용해서 무언가 작업한다는 것이다. 이 경우 E보다 하위 타입을 입력받더라도 공변이면 E로 자동 형변환해서 저장된다.</li>
<li>제네릭에서 가능하게 해야하기 때문에 다음과 같이 코드를 변경하면 된다.</li>
</ul>
<pre><code class="language-java">public static &lt;E&gt; Set&lt;E&gt; union(Set&lt;? extends E&gt; s1, Set&lt;? extends E&gt; s2);</code></pre>
<p><strong>Consumer - super</strong></p>
<pre><code class="language-java">//Collection&lt;E&gt; 타입의 dst에 Stack 원소들을 담는 메서드이다. 이를 상속에 유연한 공변 입장에서 생각해보자..
public void popAll(Collection&lt;E&gt; dst) {
    while (!isEmpty()) {
        dst.add(pop());
    }
}</code></pre>
<ul>
<li>해당 코드는 Collection<E> 타입의 dst에 Stack 원소들을 담는 메서드이다. 이를 상속에 유연한 공변 입장에서 생각해보자..</li>
</ul>
<pre><code class="language-java">public void popAll(Collection&lt;? super E&gt; dst) {
    while (!isEmpty()) {
        dst.add(pop());
    }
}</code></pre>
<ul>
<li>Comparable은 항상 소비자라 한다. 그 이유는 E를 가져와서 대소 관계를 처리하기 때문이다.</li>
</ul>
<p>다음과 같이 비교하면 쉬울 것 같다.</p>
<ul>
<li><strong>공급자</strong>: 어떤 인자들을 받아서 사용하는데 상위 타입으로 정의되어 있고 하위 타입을 받아서 작업하는 다형성을 활용하는 경우</li>
<li><strong>소비자</strong>: 해당 타입이 데이터를 가져가는 경우(데이터를 가져가는 경우는 해당 타입과 상위타입만 가능)나, 하위타입에서 정의되지 않고 상위 타입에서 정의된 무엇인가를 활용하는 경우</li>
</ul>
<blockquote>
<p>💡<strong><strong>item32 제네릭과 가변인수를 함께 쓸 때는 신중해라</strong></strong></p>
</blockquote>
<p>아예 이해 못함 말하면서 이해하고 싶음</p>
<p><strong>제네릭 가변인수는 제대로 적용되지 않는다.</strong></p>
<ul>
<li>배열로 선언하든 제네릭으로 선언하든 가변인수로 선언하면 내부적으로 배열이 만들어진다.</li>
<li>문제는 배열은 공변이고 제네릭은 불공변이다. 제네릭은 컴파일시에 타입을 체크하지만 배열은 런타임에 타입을 체크한다</li>
<li>이러한 불일치성과 제네릭으로 선언해도 내부적으로 배열로 생성되기 때문에 잠재적으로 힙오염이나 ClassCastException을 일으킬 수 있다.</li>
</ul>
<p><strong>안전하게 사용하기 @SafeVarargs</strong></p>
<ul>
<li>varargs의 타입을 안전하게 사용하기 위해서 자바에서는 @SafeVarags 애너테이션을 제공해준다. 그러나 해당 어노테이션을 적용할 때는 @SupressWarnings와 마찬가지로 완전하게 안전할 때 사용해야 한다. 그런 경우를 살펴보자.</li>
</ul>
<p><strong>1.복사해서 사용한다.</strong></p>
<pre><code class="language-java">@SafeVarargs
static &lt;T&gt; List&lt;T&gt; flatten(List&lt;? extends T&gt; ...lists) {
    for (List&lt;? extends T&gt; list: lists) {
        result.addAll(list);
    }
    return result;
}</code></pre>
<p><strong>2. 수정없이 받아서 사용만 하고 외부에 varargs를 노출시키지 않는다.</strong></p>
<ul>
<li>외부에 노출시키지 않고 내부적으로만 사용하며 단순히 데이터만 활용하는 경우 안전하다.</li>
</ul>
<p><strong>3. 재정의하는 메서드에 사용하지 않는다.</strong></p>
<ul>
<li>재정의하는 메서드의 경우 문제가 생길 우려가 있다. 그렇기에 자바 8에서는 final 인스턴스 메서드와 정적 메서드에만 사용가능했다.</li>
<li>그러나 자바9 부터는 private 인스턴스 메서드에도 붙일 수 있기 때문에 조심해서 사용하도록 하자.</li>
</ul>
<p><strong>List.of 를 사용하기</strong></p>
<ul>
<li>List.of는 가변인자를 받아서 ImmutableList를 만들어주는 좋은 정적 메서드이다. 이를 활용하면 비검사 경고나, 예외에 대해서 안전하게 사용할 수 있다.</li>
</ul>
<p>힙 오염에 대해 얘기해보자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 자바 7장]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-7%EC%9E%A5</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-7%EC%9E%A5</guid>
            <pubDate>Wed, 24 Jan 2024 15:00:31 GMT</pubDate>
            <description><![CDATA[<h1 id="이펙티브자바7장">이펙티브자바7장</h1>
<blockquote>
<p>💡 Item 42 :익명 클래스보다는 람다를 사용하라</p>
</blockquote>
<p>JDK 1.1 이후 </p>
<p>함수 객체를 만드는 주요 수단은 익명 클래스가 되었다. </p>
<p>익명 클래스란, 별도의 클래스 선언으로 확장하지 않고 코드부에서 바로 구현하는 기술이다. 일회성으로 사용하고 버려지는 경우 따로 클래스를 생성하는 비용을 줄일 수 있다는 장점이 있다</p>
<pre><code class="language-java">Collections.sort(words, new Comparator&lt;String&gt;() {
    public int compare(String s1, String s2){
         return Integer.compare(s1.length(), s2.length());
   }
});</code></pre>
<p>Comparator 인터페이스는 정렬을 담당하는 추상 전략을 뜻하며, 문자열을 정렬하는 구체적인 전략을 익명 클래스로 구현하고 있기 때문에 전략 패턴과도 비슷하다.</p>
<p>JAVA 8 등장</p>
<p>함수형 인터페이스라 부르는 인터페이스들의 인스턴스를 <strong>람다식(lambda expression)</strong>을 사용해 만들 수 있게 되었다. 람다 함수는 익명 클래스보다 훨씬 간결하기 때문에, 어떤 동작을 하는지가 명확히 드러난다는 장점이 있다.</p>
<pre><code class="language-java">Collections.sort(words, (s1,s2) -&gt; Integer.compare(s1.length(), s2.length()));</code></pre>
<h3 id="람다의-타입-추론">람다의 타입 추론</h3>
<p>람다는 컴파일러가 대신 문맥을 살펴 타입을 추론해주기 때문에, 코드에서 생략해도 괜찮다. <strong>따라서 타입을 명시해야 코드가 명확할 때만 제외하고는 람다의 모든 매개변수 타입은 생략하자.</strong></p>
<ul>
<li><p>람다와 제네릭: 컴파일러는 타입 정보 대부분을 제네릭에서 얻기 때문에, 제네릭을 사용하라는 조언들은 람다와 함께 쓸 때 더욱 중요해진다.  제네릭 타입이아니라 raw 타입을 사용하면 타입 추론이 안되어 컴파일 오류가 발생한다</p>
</li>
<li><p>비교자 생성 메서드 : 람다 자리에 비교자 생성 메서드를 사용하면 코드를 더 간결하게 만들 수 있다</p>
</li>
</ul>
<pre><code class="language-java">Collections.sort(words,comparingInt(String::length));
words.sort(comparingInt(String::length));</code></pre>
<h3 id="예시--enum-타입">예시 : Enum 타입</h3>
<pre><code class="language-java">public enum Operation {
    PLUS  (&quot;+&quot;) {
        public double apply(double x, double y) {return x + y; }
    },
    MINUS (&quot;-&quot;) {
        public double apply(double x, double y) {return x + y; }
    },  
    ...
}</code></pre>
<p>람다 사용 후:</p>
<pre><code class="language-java">public enum Operation {
    PLUS  (&quot;+&quot;, (x, y) -&gt; x + y),
    MINUS (&quot;-&quot;, (x, y) -&gt; x - y),
    TIMES (&quot;*&quot;, (x, y) -&gt; x * y),
    DIVIDE(&quot;/&quot;, (x, y) -&gt; x / y);

    private final String symbol;
    private final DoubleBinaryOperator op;  // 함수형 인터페이스 

    Operation(String symbol, DoubleBinaryOperator op) {
        this.symbol = symbol;
        this.op = op;
    }

    @Override public String toString() { return symbol; }

    public double apply(double x, double y) {
        return op.applyAsDouble(x, y);
    }
}</code></pre>
<h3 id="주의점">주의점</h3>
<ol>
<li>적은 코드로 구현하기 어렵거나 내부에서 인스턴스 필드나 메서드를 사용해야 한다면 상수별 클래스 몸체를 사용하자</li>
<li>람다는 함수형 인터페이스에서만 사용가능하다</li>
<li>람다는 자기 자신을 참조할 수 없다 (this사용 불가)</li>
<li>람다를 직렬화하는 일은 극히 삼가야 한다</li>
</ol>
<h3 id="익명-클래스-단점과-함수형-프로그래밍">익명 클래스 단점과 함수형 프로그래밍</h3>
<p>익명 클래스는 여전히 코드가 길어지기 때문에 함수형 프로그래밍에 적합하지 않다</p>
<p>한수형 프로그래밍은 선언적 프로그래밍, 즉 어떻게가 아닌 무엇을 달성한지에 초점을 맞춘다. </p>
<p>명령형 프로그래밍 예시 </p>
<pre><code class="language-java">public int add(int[] arr) {  // 명령형 프로그래밍
    int result = 0;
    for (int i = 0; i &lt; arr.length; i++){
      result += arr[i];
    }
    return result;
}</code></pre>
<ol>
<li>배열을 반복해서 더하는 모든 과정을 자세하게 설명하고 있다</li>
<li>메모리에 저장된 정보, 즉 상태를 변화시키고 있다</li>
<li>무슨 일이 일어나고 있는지 코드를 분석해야 하기 때문에 가독성이 떨어진다</li>
</ol>
<p>함수형 프로그래밍 예시</p>
<pre><code class="language-java">public int add(int[] arr) {
  return Arrays.stream(arr)
              .reduce((prev, current) =&gt; prev + current) // Integer::sum
            .getAsInt();
}</code></pre>
<ol>
<li>어떻게가 아닌, 필요한 데이터인 “무엇”에 대해 집중할 수 있다</li>
<li>상태를 변경하는 지점들이 map, reduce 내부로 추상화 되었기 때문에 클라이언트 코드에서 직접 상태를 변경하지 않아도 된다</li>
<li>map과 reduce 함수에만 익숙하다면 가독성이 높다</li>
</ol>
<blockquote>
<p>📚<strong>핵심 정리</strong></p>
<p>익명 클래스는 함수형 인터페이스가 아닌 타입의 인스턴스를 만들 때만 사용하고, 작은 함수 객체를 쉽게 표현할 수 있는 람다를 사용하는 것이 좋다.</p>
</blockquote>
<blockquote>
<p>💡 Item 43: 람다보다는 메서드 참조를 사용하라</p>
</blockquote>
<p>메서드 참조란, 함수 객체를 람다보다 간결하게 만드는 방법이다</p>
<p>람다</p>
<pre><code class="language-java">map.merge(key, 1, (count,incr) -&gt; count + incr);</code></pre>
<ul>
<li>키, 값, 함수를 인자로 받으며 주어진 키가 맵 안에 아직 없다면 주어진 키,값 쌍을 그대로 저장하고 키가 이미 있다면 키,인자로 받은 함수의 결과쌍을 저장한다</li>
<li>하지만 현재 람다는 단순 두 인수의 합을 반환할 뿐이기 때문에 불필요한 코드 부분(매개변수를 받는 부분)이 존재한다</li>
</ul>
<p>메서드 참조</p>
<pre><code class="language-java">map.merge(key,1,Integer::sum);</code></pre>
<p>장점: 더 짧고 간결한 코드를 생성할 수 있다</p>
<p>즉, 람다로 구현했을 때 너무 길고 복잡하면 람다로 작성할 코드를 새로운 메서드에 담은 다음, 람다 대신 그 메서드 참조를 사용하면 된다</p>
<pre><code class="language-java">메서드와 람다가 같은 클래스에 있는 경우 람다가 더 간결하다.

service.excecute(CoshThisClassNameIsHumongous::action);
service.execute(() -&gt; action());</code></pre>
<h3 id="메서드-참조-유형">메서드 참조 유형</h3>
<ol>
<li>정적 메서드 참조</li>
</ol>
<pre><code class="language-java">Integer::parseInt
//람다
str -&gt; Integer.parseInt(str)</code></pre>
<ol>
<li>한정적 </li>
</ol>
<pre><code class="language-java">Instant.now()::isAfter
//람다
Instant then = Instant.now(); t -&gt; then.isAfter(t)</code></pre>
<ol>
<li>비한정적 인스턴스 메서드 참조</li>
</ol>
<pre><code class="language-java">String::toLowerCase
//람다
str -&gt; str.toLowerCase()</code></pre>
<ol>
<li>클래스 생성자</li>
</ol>
<pre><code class="language-java">TreeMap&lt;K,V&gt;::new
//람다
() -&gt; new TreeMap&lt;K,V&gt;()</code></pre>
<ol>
<li>배열 생성자</li>
</ol>
<pre><code class="language-java">int[]::new
//람다
len -&gt; new int[len]</code></pre>
<h3 id="예외">예외</h3>
<p>람다로는 불가능하나 메서드 참조로는 가능한 유일한 예는 바로 제네릭 함수 타입 구현이다</p>
<pre><code class="language-java">interface G1{
    &lt;E extends Exception&gt; Object m() throws E;
}
interface G extends G1, G2 {}
함수형 인터페이스로 F를 함수 타입으로 표현하면 다음과 같다
&lt;F extends Exception&gt; () -&gt; String throws F</code></pre>
<blockquote>
<p>💡ITEM 44: 표준 함수형 인터페이스를 사용해라</p>
</blockquote>
<ul>
<li>Map의새로운 키를 추가하는 put 메서드에서 removeEldestEntry메서드를 호출해 true가 반환되면 맵에서 가장 오래된 원소를 제거한다. LinkedHashMap에서 removeEldestEntry를 다음과 같이 재정의해서 캐시로 사용할 수 있다</li>
</ul>
<pre><code class="language-java">//기존
protected boolean removeEldestEntry(Map.Entry&lt;K,V&gt; eldest) {
  return false;
}
//변경 
@Override
protected boolean removeEldestEntry(Map.Entry&lt;K,V&gt; eldest) {
  return size() &gt; 100; // 최근 원소 100개 유지
}
//람다로 구현한 함수형 인터페이스
@FunctionInterface interface EldestEntryRemovalFunction&lt;K, V&gt; {
    boolean remove(Map&lt;K,V&gt; map, Map.Entry&lt;K, V&gt; eldest);
}</code></pre>
<p>위의 EldestEntryRemovalFunction도 동작하지만 자바 표준 라이브러리에서 이미 제공해주므로 굳이 사용할 필요가 없다</p>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/cf3cd22c-4430-4410-ad02-80d2a1605c55/486832b9-551f-4c62-8aa9-d7942a25c075/Untitled.png" alt="Untitled"></p>
<h3 id="직접-구현">직접 구현</h3>
<p>표준 함수형인터페이스 중 필요한 용도에 맞는게 없다면 직접 구현해야한다</p>
<p>@FunctionInterface</p>
<ul>
<li><p><code>@FunctionInterface</code> 어노테이션은 프로그래머의 의도를 명시하는 것으로 3가지 목적이 있다.</p>
<ol>
<li>해당 인터페이스가 람다용으로 설계된 것임을 명시</li>
<li>해당 인터페이스가 추상 메서드를 오직 한개만 가지고 있어야 컴파일 가능</li>
<li>유지보수 과정에서 누군가 실수로 메서드를 추가하지 못하게 막아줌</li>
</ol>
</li>
<li><p>주의점</p>
<ul>
<li><p>서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드를 다중정의해서는 안된다. 클라이언트에게 불필요한 모호함만 주며, 다음과 같이 모호함으로 인해 문제가 발생할 수 있다</p>
<pre><code class="language-java">public interface ExecutorService extends Executor {
  &lt;T&gt; Future&lt;T&gt; submit(Callback&lt;T&gt; task);
  Future&lt;?&gt; submit(Runnable task);
}
//ExecutorService 인터페이스는 Callable&lt;T&gt;와 Runnable을 각각 인수로 하여 다중정의했다. 
//올바른 메서드를 알려주기 위해서는 submit 메서드를 사용할 때마다 형변환을 해줘야한다</code></pre>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡ITEM 45: 스트림은 주의해서 사용해라</p>
</blockquote>
<p>스트림이란? 데이터 원소의 유한/무한 시퀀스를 뜻함</p>
<p>스트림 파이프라인이란? 원소들로 수행하는 연산 단계를 표현</p>
<ul>
<li>스트림의 원소는 어디로부터든 올 수 있으며, 대표적으로 배열, 컬렉션, 파일 등을 통해서 만들 수 있다</li>
</ul>
<h3 id="스트림-파이프라인">스트림 파이프라인</h3>
<p>스트림을 생성하는 연산으로 종단연산을 통해 끝나며 그 사이에 스트림을 변환하거나 계산하는 한 개 이상의 중간 연산이 포함 될 수 있다</p>
<ul>
<li>지연 평가<ul>
<li>평가는 종단 연산이 호출될때 진행되며, 종단 연산에 사용되지 않은 데이터 원소는 계산에 쓰이지 않는다. 이러한 지연 평가가 무한 스트림을 다룰 수 있게 해주는 핵심이다.</li>
<li>종단 연산이 없는 스트림 파이프라인은 아무 일도 하지 않는 명령어 no-op과 같으니 종단 연산을 뺴먹는 일이 절대 없도록 하자</li>
</ul>
</li>
</ul>
<p>스트림을 제대로 사용하면 프로그램이 짧고 깔끔해지만, 잘못 사용하면 읽기 어렵고 유지보수도 힘들어진다</p>
<pre><code class="language-java">//스트림을 과하게 사용하는 예
public class Anagrams {
    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        try (Stream&lt;String&gt; words = Files.lines(dictionary)) {
            words.collect(groupingBy(word -&gt; word.chars().sorted()
                    .collect(StringBuilder::new,
                        (sb, c) -&gt; sb.append((char) c),
                        StringBuilder::append).toString()))
                .values().stream()
                .filter(group -&gt; group.size() &gt;= minGroupSize)
                .map(group -&gt; group.size() + &quot;: &quot; + group)
                .forEach(System.out::println);
        }
    }
}</code></pre>
<p>적절히 사용한 예</p>
<pre><code class="language-java">public class Anagrams {
    public static void main(String[] args) {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        try (Stream&lt;String&gt; words = Files.lines(dictionary)) {
            words.collect(groupingBy(word -&gt; alphabetize(word)))
                .values().stream()
                .filter(group -&gt; group.size() &gt;= minGroupSize)
                .forEach(g -&gt; System.out.println(g.size() + &quot;: &quot; + g));
        }
    }

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}</code></pre>
<p><strong>스트림을 사용하기 좋은 경우</strong></p>
<ul>
<li>원소들의 시퀀스를 일관되게 변환</li>
<li>원소들의 시퀀스를 필터링</li>
<li>원소들의 시퀀스를 하나의 연산을 사용하여 결합(더하기, 최솟값 구하기 등)</li>
<li>원소들의 시퀀스를 컬렉션에 모으는 경우</li>
<li>원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는 경우</li>
</ul>
<p><strong>스트림으로 처리하기 어려운 일</strong></p>
<ul>
<li>한 데이터가 파이프라인의 여러 단계를 통과할 때, 각 단계에서 값들에 동시에 접근하기 어려운 경우<ul>
<li>스트림은 한 값을 다른 값에 매핑하고 나면 원래의 값은 잃는 구조</li>
</ul>
</li>
<li>매핑 객체가 필요한 단계가 여러 곳인 경우</li>
</ul>
<h2 id="결론">결론</h2>
<p>스트림으로 바꾸는게 가능하더라도 코드 가독성과 유지보수 측면에서 손해볼 수 있기때문에 기존 코드는 스트림을 사용하도록 리팩터링하되, 새 코드가 더 나아 보일때만 반영해야한다.</p>
<p>즉, <strong>스트림과 반복 중 어느쪽이 나은지 확신하기 어렵다면, 둘다 구현해보고 더 나은 쪽을 정하는 것</strong>을 권장한다.</p>
<blockquote>
<p>💡 I<strong><strong>TEM 46: 스트림에서 부작용 없는 함수를 사용해라</strong></strong></p>
</blockquote>
<h2 id="스트림-패러다임">스트림 패러다임</h2>
<p>스트림 패러다임의 핵심은 <strong>계산을 일련의 변환으로 재구성하는 부분</strong>이다. 각 변환 단계는 가능한 이전 단계의 결과를 받아 처리하는 <strong>순수 함수</strong>여야한다.</p>
<ul>
<li>순수함수 : 오직 입력만이 결과에 영향을 주는 함수<ul>
<li>다른 가변 상태를 참조하지 않음</li>
<li>함수 스스로 다른 상태를 변경하지 않음</li>
</ul>
</li>
</ul>
<h3 id="스트림-패러다임은-이해하지-못한채-api만-사용한-예">스트림 패러다임은 이해하지 못한채 API만 사용한 예</h3>
<pre><code class="language-java">Map&lt;String, Long&gt; freq = new HashMap&lt;&gt;();
try(Stream&lt;String&gt; words = new Scanner(file).tokens()) {
  words.forEach(word -&gt; {
    freq.merge(word.toLowerCase(), 1L, Long::sum);
  });
}
//forEach : 스트림이 수행한 연산 결과를 보여줄 때 사용하고, 계산할 때는 사용하지 말자.
//(스트림 계산 결과를 기존 컬렉션에 추가하는 등 다른 용도로도 쓸 수 있다.)

//올바르게 사용한 것
Map&lt;String, Long&gt; freq;
try(Stream&lt;String&gt; words = new Scanner(file).tokens()) {
  freq = words.collect(groupingBy(String::toLowerCase, counting()));
}</code></pre>
<p><strong>Collector</strong></p>
<ul>
<li><code>java.util.stream.Collectors</code>  : 자주 사용하는 API 제공<code>Collectors</code> 의 멤버를 정적 임포트(static import)해 사용하면, 스트림 가독성이 좋아짐</li>
<li>스트림의 원소를 손쉽게 컬렉션으로 생성 가능</li>
<li>최종 처리(스트림 종료 작업)</li>
</ul>
<p>toList()</p>
<pre><code class="language-java">List&lt;String&gt; topTen = freq.keySet().stream()
  .sorted(comparing(freq::get).reversed()) // Comparator.comparing
  .limit(10)
  .collect(toList()); // List 형태로 반환</code></pre>
<p>toMap()</p>
<ul>
<li><code>toMap(keyMapper, valueMapper)</code> : 각 원소가 고유한 키에 매핑되어 있을 때 적합</li>
</ul>
<pre><code class="language-java">private static final Map&lt;String, Operation&gt; stringToEnum = 
Stream.of(values()).collect(toMap(Object::toString, e -&gt; e));</code></pre>
<ul>
<li>인수 3개 받는 <code>toMap</code> : 어떤 키와 그 키에 연관된 원소들 중 하나를 골라 연관 짓는 맵을 만들때 유용</li>
</ul>
<pre><code class="language-java">Map&lt;Artist, Album&gt; topHits = albums.collect(
    toMap(Album::artist, a-&gt;a, maxBy(comparing(Album::sales)))); //</code></pre>
<ul>
<li>마지막에 쓴 값을 취하는 수집기</li>
</ul>
<pre><code class="language-java">toMap(keyMapper, valueMapper, (oldVal, newVal) -&gt; newVal);

Stream&lt;String&gt; s = Stream.of(&quot;apple&quot;, &quot;banana&quot;, &quot;apricot&quot;, &quot;orange&quot;, &quot;apple&quot;);
Map&lt;Character, String&gt; m = s.collect(Collectors.toMap(s1 -&gt; s1.charAt(0), s1 -&gt; s1, (oldVal, newVal) -&gt; oldVal + &quot;|&quot; + newVal)); 
// {a=apple|apricot|apple, b=banana, o=orange}</code></pre>
<ul>
<li>네번째 인수로 맵 팩터리(<code>EnumMap</code>, <code>TreeMap</code>, <code>HashMap</code>)를 받는 toMap</li>
</ul>
<pre><code class="language-java">Stream&lt;String&gt; s = Stream.of(&quot;apple&quot;, &quot;banana&quot;, &quot;apricot&quot;, &quot;orange&quot;, &quot;apple&quot;);
  LinkedHashMap&lt;Character, String&gt; m = s.collect(
                 Collectors.toMap(s1 -&gt; s1.charAt(0), s1 -&gt; s1, (s1, s2) -&gt; s1 + &quot;|&quot; + s2,
                                                  LinkedHashMap::new));</code></pre>
<p>groupingBy()</p>
<p>입력으로 분류 함수(classifier)를 받고 출력으로 원소들을 카테고리별로 모아 놓은 맵을 담은 수집기 반환한다.</p>
<pre><code class="language-java">L// 알파벳화한 단어를 알파벳화 결가가 같은 단어들의 리스트로 매핑하는 맵 생성
words.collect(groupingBy(word -&gt; alphabetsize(word)))</code></pre>
<blockquote>
<p>💡 I<strong><strong>TEM 47: 반환 타입으로는 스트림보다 컬렉션이 낫다.</strong></strong></p>
</blockquote>
<ul>
<li><code>Collection</code> 인터페이스는 <code>Iterable</code>의 하위 타입이고, <code>stream</code> 메서드도 제공하여 즉, 반복과 스트림을 동시에 지원한다.</li>
</ul>
<p>스트림 반복문</p>
<ul>
<li>Stream -&gt; Iterable 어댑터</li>
</ul>
<pre><code class="language-java">public static &lt;E&gt; Iterable&lt;E&gt; iterableOf(Stream&lt;E&gt; stream){
  return stream::iterator;
}</code></pre>
<ul>
<li><code>Stream&lt;E&gt;</code>를 <code>Iterable&lt;E&gt;</code>로 중개해주는 어댑터를 사용하면 어떠한 스트림도 for-each 반복문을 사용할 수 있다.</li>
</ul>
<pre><code class="language-java">for(ProcessHandle p : iterableOf(ProcessHandle.allProcesses())){
  // 프로세스 처리 로직
}</code></pre>
<ul>
<li>Iterable -&gt; Stream 어댑터</li>
</ul>
<pre><code class="language-java">public static &lt;E&gt; Stream&lt;E&gt; streamOf(Iterable&lt;E&gt; iterable){
  return StreamSupport.stream(iterable.spliterator(), false);
}</code></pre>
<p>전용 컬렉션 구현</p>
<ul>
<li><strong>반환할 시퀀스가 크지만 표현을 간결하게 할 수 있다면 전용 컬렉션을 구현하는 방안을 검토</strong>하는 것이 좋다</li>
</ul>
<p><code>AbstractCollection</code>을 활용해 <code>Collection</code> 구현체를 작성할때는 아래 3개 메서드는 반드시 구현해야한다.</p>
<ul>
<li><p><code>Iterable</code>용 메서드</p>
</li>
<li><p><code>contains</code></p>
</li>
<li><p><code>size</code></p>
</li>
<li><p>만약, <code>contains</code>와 <code>size</code>를 구현하는게 불가능한 경우 <code>Stream</code> 이나 <code>Iterable</code>로 구현하는 것이 낫다.</p>
</li>
</ul>
<blockquote>
<p>💡 <strong><strong>ITEM 48: 스트림 병렬화는 주의해서 사용해라</strong></strong></p>
</blockquote>
<p>안정성과 응답가능 상태 유지</p>
<ul>
<li>동시성 프로그래밍을 할 때는 안정성(safety)과 응답 가능(liveness) 상태를 유지하기 위해 노력해야하는데, 병렬 스트림 파이프라인 프로그래밍에서도 동일하다.</li>
</ul>
<p>예시)</p>
<ul>
<li>스트림을 사용해 20개의 메르센 소수를 생성하는 프로그램이다.</li>
</ul>
<pre><code class="language-java">public static void main(String[] args) {
  primes().map(p -&gt; TWO.pow(p.intValueExact()).subtract(ONE))
        .filter(mersenne -&gt; mersenne.isProbablePrime(50))
        .limit(20)
        .forEach(System.out::println);
}

static Stream&lt;BigInteger&gt; primes() {
  return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}</code></pre>
<ul>
<li>. 데이터 소스가 <code>Stream.iterate()</code> 이거나 중간 연산으로 <code>limit()</code>을 사용하면 파이프라인 병렬화로는 성능 개선을 할 수 없다. 즉, <strong>스트림 파이프라인을 마구잡이로 병렬화하면 안되며, 오히려 성능이 나빠질 수 있다.</strong></li>
</ul>
<ol>
<li>병렬화 하기 좋은 경우: <strong>참조 지역성이 뛰어난 경우</strong></li>
</ol>
<ul>
<li><code>ArrayList</code></li>
<li><code>HashMap</code></li>
<li><code>HashSet</code></li>
<li><code>ConcurrentHashMap</code></li>
<li>배열</li>
<li>int 범위</li>
<li>long 범위</li>
<li>위 자료구조들은 <strong>모두 데이터를 원하는 크기로 정확하고 쉽게 나눌 수 있어, 일을 다수의 스레드에 분배하기 좋다</strong>.</li>
<li>원소들을 순차적으로 실행할 때 <strong>참조 지역성이 뛰어나다</strong>. (<em>참조지역성 : 이웃한 원소의 참조들이 메모리에 연속해서 저장되어 있음.</em>)</li>
</ul>
<ol>
<li>종단 연산 - 축소(reduction)</li>
</ol>
<ul>
<li><p>종단 연산에서 수행하는 작업량이 파이프라인 전체 작업에서 상당 비중으로 차지하며, 순차적인 연산이라면 파이프라인 병렬 수행의 효과는 제한될 수 밖에 없다</p>
</li>
<li><p>축소(reduction)는 파이프라인에서 만들어진 모든 원소를 하나로 합치는 작업이다.</p>
</li>
<li><p>reduce 메서드</p>
</li>
<li><p>min, max, count, sum 완성된 형태로 제공되는 메서드</p>
</li>
<li><p><code>anyMatch</code>, <code>allMatch</code>, <code>noneMatch</code> 와 같이 조건에 맞으면 바로 반환하는 메서드</p>
</li>
</ul>
<p>위 메서드는 병렬화에 적합하지만, 가변 축소를 수행하는 Stream의 <code>collect</code> 메서드는 컬렉션들을 합치는 부담이 크기때문에 병렬화에 적합하지 않다.</p>
<pre><code>Stream 타입이 나은 예: 부분 리스트를 스트림으로 변환하여 처리하기</code></pre><p>마무리</p>
<ul>
<li><strong>스트림을 잘못 병렬화하면 성능이 나빠질 뿐만 아니라 결과 자체가 잘못되거나 예상 못한 동작(safety failure)이 발생할 수 있다.</strong><ul>
<li><code>Stream</code> 명세대로 동작하지 않을 때, 발생할 수 있음</li>
<li>예를들어, Stream reduce 연산의 <code>accumulator</code>와 <code>combiner</code> 함수는 반드시 결합 법칙을 만족하고, 간섭받지 않고, 상태를 갖지 않아야한다.</li>
</ul>
</li>
<li>위 조건을 다 만족하더라도, 병렬화에 드는 추가 비용을 상쇄하지 못한다면, 성능 향상이 미미할 수 있음.<ul>
<li>스트림 안의 원소 수와 원소당 수행되는 코드 줄 수를 곱해 수십만이 되어야 성능향상을 느낄 수 있다.</li>
</ul>
</li>
<li>스트림 병렬화는 오직 성능 최적화 수단이다.<ul>
<li>변경 전후로 테스트해 병렬화 사용에 가치가 있는지 확인해야한다.</li>
</ul>
</li>
<li>계산이 정확하고, 확실히 성능이 좋아졌을 경우에만 병렬화를 실 운영에 적용해야한다.</li>
<li>조건이 잘 갖춰지면, parallel 메서드 호출 하나로 프로세서 코어 수에 비례하는 성능 향상을 만끽할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 자바 6장 ]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-6%EC%9E%A5</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-6%EC%9E%A5</guid>
            <pubDate>Wed, 24 Jan 2024 15:00:02 GMT</pubDate>
            <description><![CDATA[<h1 id="이펙티브자바6장">이펙티브자바6장</h1>
<blockquote>
<p>Item 34 int 상수 대신 열거 타입을 사용해라</p>
</blockquote>
<h3 id="정수-열거-패턴---상당히-취약하다">정수 열거 패턴 - 상당히 취약하다</h3>
<pre><code class="language-java">public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;

public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;</code></pre>
<p>단점이 많다. 타입 안전을 보장할 방법이 없고 표현력도 좋지 않다</p>
<h3 id="가장-단순한-열거-타입">가장 단순한 열거 타입</h3>
<pre><code class="language-java">public enum Apple {FUJI, PIPPIN, GRANNY_SMITH}
public enum Orange {NAVEL, TEMPLE, BLOOD}</code></pre>
<p>열거 타입 자체가 클래스이고 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개한다. </p>
<p>열거 타입은 밖에서 접근할 수 있는 생성자를 제공하지 않으므로 사실상 final이다</p>
<p> <code>열거 타입의 toString 메서드는 출력하기에 적합한 문자열을 내어준다.</code></p>
<p>💡Apple과 Orange를 예로 들면, 과일의 색을 알려주거나 과일 이미지를 반환하는 메서드를 추가하고 싶을 때? 행성에 대한 열거 타입을 설명하면서 Enum 클래스가 고차원의 추상 개념 하나를 완벽히 표현해낼 수도 있는 예를 들어보겠다 </p>
<pre><code class="language-java">public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS (4.869e+24, 6.052e6),
    EARTH (5.975e+24, 6.378e6),
    MARS (6.419e+23, 3.393e6),
    JUPITER(1.899e+27, 7.149e7),
    SATURN(5.685e+26,6.027e7),
    URANUS(8.683e+25,2.556e7),
    NEPTUNE(1.024e+26, 2.477e7)

    private final double mass;
    private final double radius;
    private final double surfaceGravity;

    private static final double G = 6.67300E-11;

    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
        surfaceGravity = G * mass / (radius * radius);
    }

    public double mass() {return mass;}
    public double radius() {return radius;}
    public double surfaceGravity() {return surfaceGravity;}

    public double surfaceWeight(double mass ) {
        return mass* surfaceGravity;
    }

}</code></pre>
<p>이 Enum클래스를 기반으로 어떤 객체의 지구에서의무게를 입력받아 여덟 행성에서의 무게를 출력하는 일은 다음처럼 짧은 코드로 작성할 수 있다</p>
<pre><code class="language-java">public class WeightTable {
    public static void main(String[] args) {
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight / Planet.EARTH.surfaceGravity();
        for (Planet p : Planet.values())
                System.out.printg(&quot;%s에서의 무게는 %f이다.%n&quot;, p, p.surfaveWeight(mass))
    }
}</code></pre>
<ul>
<li>기능을 클라이언트에 노출해야할 합당한 이유가 없다면 private 혹은 package-private으로 선언하라</li>
</ul>
<h3 id="열거-타입-상수마다-동작이-달라지는-메서드">열거 타입 상수마다 동작이 달라지는 메서드</h3>
<pre><code class="language-java">enum Operation {
    PLUS {
        @Override
        public double apply(double x, double y) {
            return x+y;
        }
    },
    MINUS {
        @Override
        public double apply(double x, double y) {
            return x-y;
        }
    },
    TIMES {
        @Override
        public double apply(double x, double y) {
            return x*y;
        }
    },
    DIVIDE {
        @Override
        public double apply(double x, double y) {
            return x/y;
        }
    };

    public abstract double apply(double x, double y);
}

@Test
public void operationApplyTest() {
    double x = 10;
    double y = 15;

    for (Operation value : Operation.values()) {
        System.out.printf(&quot;%f %s %f = %f%n&quot;, x, value, y, value.apply(x, y));
    }
}</code></pre>
<h3 id="상수별-클래스-몸체와-데이터를-사용한-열거-타입">상수별 클래스 몸체와 데이터를 사용한 열거 타입</h3>
<pre><code class="language-java">enum Operation {
    PLUS (&quot;+&quot;) {
        @Override
        public double apply(double x, double y) {
            return x+y;
        }
    },
    MINUS (&quot;-&quot;) {
        @Override
        public double apply(double x, double y) {
            return x-y;
        }
    },
    TIMES (&quot;*&quot;) {
        @Override
        public double apply(double x, double y) {
            return x*y;
        }
    },
    DIVIDE (&quot;/&quot;) {
        @Override
        public double apply(double x, double y) {
            return x/y;
        }
    };

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    public abstract double apply(double x, double y);

    @Override
    public String toString() {
        return symbol;
    }
}

@Test
public void operationApplyTest() {
    double x = 10;
    double y = 15;

    for (Operation value : Operation.values()) {
        System.out.printf(&quot;%f %s %f = %f%n&quot;, x, value, y, value.apply(x, y));
    }
}
출처: https://jake-seo-dev.tistory.com/54 [제이크서 위키 블로그:티스토리]</code></pre>
<ul>
<li>toString()을 재정의하여 symbol 필드를 반환하게 하였다</li>
</ul>
<h3 id="enum에서-tostring-구현-이후에-fromstring메서드-구현하기">Enum에서 toString() 구현 이후에 fromString메서드 구현하기</h3>
<pre><code class="language-java">enum Operation {
    PLUS (&quot;+&quot;) {
        @Override
        public double apply(double x, double y) {
            return x+y;
        }
    },
    MINUS (&quot;-&quot;) {
        @Override
        public double apply(double x, double y) {
            return x-y;
        }
    },
    TIMES (&quot;*&quot;) {
        @Override
        public double apply(double x, double y) {
            return x*y;
        }
    },
    DIVIDE (&quot;/&quot;) {
        @Override
        public double apply(double x, double y) {
            return x/y;
        }
    };

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    public abstract double apply(double x, double y);

    @Override
    public String toString() {
        return symbol;
    }

    public static final Map&lt;String, Operation&gt; stringToEnum = Stream.of(values()).collect(Collectors.toMap(Object::toString, e -&gt; e));

    public static Optional&lt;Operation&gt; fromString(String symbol) {
        return Optional.ofNullable(stringToEnum.get(symbol));
    }
}</code></pre>
<h3 id="값에-따라-분기하여-코드를-공유하는-열거-타입">값에 따라 분기하여 코드를 공유하는 열거 타입</h3>
<pre><code class="language-java">enum PayrollDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;

    private static final int MINS_PER_SHIFT = 8 * 60;

    int pay(int minutesWorked, int payRate) {
        int basePay = minutesWorked * payRate;
        int overtimePay;

        switch(this) {
            // 주말
            case SATURDAY : case SUNDAY :
                overtimePay = basePay / 2;
                break;
            // 주중
            default:
                overtimePay = minutesWorked &lt;= MINS_PER_SHIFT ? 0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
        }

        return basePay + overtimePay;
    }
}</code></pre>
<ul>
<li>위험한 코드이다 . 휴가나 다른 깜박한 경우를 추가안했을 때를 대비 못한다</li>
<li>이런 한계를 극복하기 위해 전략 열거 타입 패턴이 나온다</li>
</ul>
<h3 id="전략-열거-타입-패턴">전략 열거 타입 패턴</h3>
<pre><code class="language-java">enum PayrollDay {
    MONDAY(PayType.WEEKDAY)
    , TUESDAY(PayType.WEEKDAY)
    , WEDNESDAY(PayType.WEEKDAY)
    , THURSDAY(PayType.WEEKDAY)
    , FRIDAY(PayType.WEEKDAY)
    , SATURDAY(PayType.WEEKEND)
    , SUNDAY(PayType.WEEKEND);

    private final PayType payType;

    PayrollDay(PayType payType) {
        this.payType = payType;
    }

    public int pay(int minutesWorked, int payRate) {
        return payType.pay(minutesWorked, payRate);
    }

    // 전략 열거 타입
    enum PayType {
        WEEKDAY {
            @Override
            int overtimePay(int minsWorked, int payRate) {
                return minsWorked &lt;= MINS_PER_SHIFT ? 0 : (minsWorked - MINS_PER_SHIFT) * payRate / 2;
            }
        },
        WEEKEND {
            @Override
            int overtimePay(int minsWorked, int payRate) {
                return minsWorked * payRate / 2;
            }
        };

        abstract int overtimePay(int mins, int payRate);
        private static final int MINS_PER_SHIFT = 8 * 60;

        int pay(int minsWorked, int payRate) {
            int basePay = minsWorked * payRate;
            return basePay + overtimePay(minsWorked, payRate);
        }
    }
}</code></pre>
<h3 id="정리">정리</h3>
<ul>
<li>필요한 원소를 컴파일타임에 다 알 수 있는 상수 집합이라면 열거 타입을 사용하자.<ul>
<li>ex) 태양계 행성, 한 주의 요일, 체스 말</li>
<li>ex) 메뉴 아이템, 연산 코드, 명령줄 플래그</li>
</ul>
</li>
<li>대다수 열거 타입은 명시적 생성자나 메서드 없이 쓰이지만, 각 상수를 특정 데이터와 연결짓거나 상수마다 다르게 동작할 때는 필요하다.<ul>
<li>이 경우, 보통은 추상 메서드를 선언한 뒤, <code>switch</code>문 대신 상수별 메서드 구현이 낫다.</li>
<li>열거 타입 상수가 같은 코드를 공유한다면, 전략 열거 타입 패턴을 사용하자.</li>
</ul>
</li>
</ul>
<blockquote>
<p>Item 35 Ordinal 메서드 대신 인스턴스 필드를 사용하라</p>
</blockquote>
<p>모든 열거타입은 해당 상수가 그 열거타입에서 몇번째 위치인지를 반환하는 ordinal이라는 메서드를 제공한다</p>
<pre><code class="language-java">enum Ensemble {
    SOLO, DUET, TRIO, QUARTET, QUINTET,
    SEXTET, SEPTET, OCTET, NONET, DECTET;

    public int numberOfMusicians() {
        return ordinal() + 1;
    }
}</code></pre>
<ul>
<li>동작은 하지만 유지보수하기 끔찍하다</li>
<li>상수 선언을 바꾸는 순간 numberOfMusicians가 오동작한다</li>
</ul>
<p>해결책은? 열거 타입 상수에 연결된 값은 ordinal 메서드로 얻지 말고 인스턴스 필드에 저장하자</p>
<pre><code class="language-java">enum Ensemble {
    SOLO(1)
    , DUET(2)
    , TRIO(3)
    , QUARTET(4)
    , QUINTET(5)
    , SEXTET(6)
    , SEPTET(7)
    , OCTET(8)
    , NONET(9)
    , DECTET(10);

    private final int numberOfMusicians;

    Ensemble(int numberOfMusicians) {
        this.numberOfMusicians = numberOfMusicians;
    }

    public int numberOfMusicians() {
        return numberOfMusicians;
    }
}</code></pre>
<p>대부분 프로그래머는 이 메서드(ordinal())을 쓸 일이 없다. 이 메서드는 EnumSet과 EnumMap같이 열거 타입 기반의 범용 자료구조에 쓸 목적으로 설계되었다</p>
<blockquote>
<p>Item 34 비트 필드 대신 EnumSet을 사용하라</p>
</blockquote>
<p>비트 필드 열거 상수 코드의 장점 </p>
<ol>
<li>여러 상수를 하나의 집합으로 모을 수 있는 장점</li>
<li>합집합 교집합 등의 연산에 유리하다</li>
</ol>
<p>비트 필드 열거 상수 코드의 단점</p>
<ol>
<li>정수 열거 상수의 단점을 그대로 가져간다</li>
<li>모든 원소를 순회하기 쉽지 않다</li>
</ol>
<h3 id="enumset으로-비트-필드-대체해보기">EnumSet으로 비트 필드 대체해보기</h3>
<pre><code class="language-java">enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

public void applyStyles(Set&lt;Style&gt; styles) {
    StringJoiner sj = new StringJoiner(&quot;, &quot;);

    for (Style style : styles) {
        sj.add(style.name());
    }

    System.out.println(&quot;applied styles = &quot; + sj);
}</code></pre>
<ul>
<li>원소가 총 64개 이하라면RegularEnumSet을 사용하여 비트필드만큼의 성능을 내준다.<ul>
<li>그 이상은JumboEnumSet을 사용하도록 내부적으로 판단해서, 크기에도 유연하다</li>
</ul>
</li>
<li>원소를 순회하기도 쉽다.</li>
</ul>
<blockquote>
<p>Item 37 Ordinal 인덱싱 대신 EnumMap을 사용하라</p>
</blockquote>
<h3 id="ordinal을-배열-인덱스로-이용한-예제--안좋은-방법">Ordinal을 배열 인덱스로 이용한 예제- 안좋은 방법</h3>
<pre><code class="language-java">static class Plant {
    enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL }

    final String name;
    final LifeCycle lifeCycle;

    Plant(String name, LifeCycle lifeCycle) {
        this.name = name;
        this.lifeCycle = lifeCycle;
    }

    @Override
    public String toString() {
        return &quot;Plant{&quot; +
                &quot;name=&#39;&quot; + name + &#39;\&#39;&#39; +
                &quot;, lifeCycle=&quot; + lifeCycle +
                &#39;}&#39;;
    }
}
@Test
public void plantsByLifeCycleTest() {
    Set&lt;Plant&gt;[] plantsByLifeCycle = (Set&lt;Plant&gt;[]) new Set[Plant.LifeCycle.values().length];

    List&lt;Plant&gt; garden = new ArrayList&lt;&gt;(List.of(
            new Plant(&quot;A&quot;, Plant.LifeCycle.ANNUAL),
            new Plant(&quot;B&quot;, Plant.LifeCycle.PERENNIAL),
            new Plant(&quot;C&quot;, Plant.LifeCycle.BIENNIAL),
            new Plant(&quot;D&quot;, Plant.LifeCycle.ANNUAL)
    ));

    for (int i = 0; i &lt; plantsByLifeCycle.length; i++) {
        plantsByLifeCycle[i] = new HashSet&lt;&gt;();
    }

    for (Plant plant : garden) {
        plantsByLifeCycle[plant.lifeCycle.ordinal()].add(plant);
    }

    for (int i = 0; i &lt; plantsByLifeCycle.length; i++) {
        System.out.printf(&quot;%s: %s%n&quot;, Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
    }
}</code></pre>
<ul>
<li>Ordinal()을 통해 인덱스를 짚고 있다</li>
<li>단점<ul>
<li>Set클래스는 제네릭 타입을 받는데, 제네릭 타입은 배열과 호환성이 좋지 않다. 그래서 비검사 형변환을 수행해야 하고 깔끔히 컴파일되지 않는다</li>
<li>정확한 정수값을 사용하는지 스스로 보증해야한다 열거 타입만큼 명확하지 않다</li>
</ul>
</li>
</ul>
<h3 id="enummap을-사용해-데이터와-열거-타입을-매핑">EnumMap을 사용해 데이터와 열거 타입을 매핑</h3>
<pre><code class="language-java">@Test
public void plantEnumMapTest() {
    List&lt;Plant&gt; garden = new ArrayList&lt;&gt;(List.of(
            new Plant(&quot;A&quot;, Plant.LifeCycle.ANNUAL),
            new Plant(&quot;B&quot;, Plant.LifeCycle.PERENNIAL),
            new Plant(&quot;C&quot;, Plant.LifeCycle.BIENNIAL),
            new Plant(&quot;D&quot;, Plant.LifeCycle.ANNUAL)
    ));

    Map&lt;Plant.LifeCycle, Set&lt;Plant&gt;&gt; plantsByLifeCycle = new EnumMap&lt;&gt;(Plant.LifeCycle.class);

    for (Plant.LifeCycle lifeCycle : Plant.LifeCycle.values()) {
        plantsByLifeCycle.put(lifeCycle, new HashSet&lt;&gt;());
    }

    for (Plant plant : garden) {
        plantsByLifeCycle.get(plant.lifeCycle).add(plant);
    }

    System.out.println(&quot;plantsByLifeCycle = &quot; + plantsByLifeCycle);
}</code></pre>
<h3 id="람다로-코드-줄여보기">람다로 코드 줄여보기</h3>
<pre><code class="language-java">@Test
public void plantEnumMapTestWithLambda() {
    List&lt;Plant&gt; garden = new ArrayList&lt;&gt;(List.of(
            new Plant(&quot;A&quot;, Plant.LifeCycle.ANNUAL),
            new Plant(&quot;B&quot;, Plant.LifeCycle.PERENNIAL),
            new Plant(&quot;C&quot;, Plant.LifeCycle.BIENNIAL),
            new Plant(&quot;D&quot;, Plant.LifeCycle.ANNUAL)
    ));

    EnumMap&lt;Plant.LifeCycle, Set&lt;Plant&gt;&gt; lifeCycleSetEnumMap =
            garden.stream().collect(
                    groupingBy(
                            p -&gt; p.lifeCycle,
                            () -&gt; new EnumMap&lt;&gt;(Plant.LifeCycle.class),
                            toSet()
                    )
            );

    System.out.println(&quot;lifeCycleSetEnumMap = &quot; + lifeCycleSetEnumMap);
}</code></pre>
<ul>
<li>람다식으로 하면 값이 있는 것에 해당하는 중첩 맵을 만든다</li>
</ul>
<h3 id="2차원-배열에-ordinal-이용-안좋은-방법">2차원 배열에 ordinal 이용-안좋은 방법</h3>
<pre><code class="language-java">enum Phase {
    SOLID, LIQUID, GAS;

    enum Transition {
        MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;

        private static final Transition[][] TRANSITIONS = {
                {null, MELT, SUBLIME}, // SOLID
                {FREEZE, null, BOIL}, // LIQUID
                {DEPOSIT, CONDENSE, null} // GAS
        };

        public static Transition from(Phase from, Phase to) {
            return TRANSITIONS[from.ordinal()][to.ordinal()];
        }
    }
}</code></pre>
<ul>
<li>Phase는 각각 다른 상태로 변화할 수 있는 열거 형태이다</li>
<li>SOLID → LIQUID = MELT가 되는 식</li>
<li>ordinal은 배열 인덱스의 관계를 몰라서 오류가 발생하는 가능성 높아진다</li>
</ul>
<h3 id="enummap을-이용하여-2차원-배열의-인덱스로-이요한-예제">EnumMap을 이용하여 2차원 배열의 인덱스로 이요한 예제</h3>
<pre><code class="language-java">public enum Phase {
    SOLID, LIQUID, GAS, PLASMA;

    public enum Transition {
        MELT(SOLID, LIQUID),
        FREEZE(LIQUID, SOLID),
        BOIL(LIQUID, GAS),
        CONDENSE(GAS, LIQUID),
        SUBLIME(SOLID, GAS),
        DEPOSIT(GAS, SOLID),
        IONIZE(GAS, PLASMA),
        DEIONIZE(PLASMA, GAS);

        private final Phase from;
        private final Phase to;

        Transition(Phase from, Phase to) {
            this.from = from;
            this.to = to;
        }

        private final static Map&lt;Phase, Map&lt;Phase, Transition&gt;&gt; map =
                Stream.of(values()).collect(groupingBy(
                        t -&gt; t.from,
                        () -&gt; new EnumMap&lt;&gt;(Phase.class),
                        toMap(t -&gt; t.to, t -&gt; t,
                                (x, y) -&gt; y,
                                () -&gt; new EnumMap&lt;&gt;(Phase.class)
                        )
                ));

        public static Transition from(Phase from, Phase to) {
            return map.get(from).get(to);
        }
    }
}
</code></pre>
<ul>
<li>첫번째 수집기인 groupingBy에서는 전이를 이전 상태를 기준으로 묶고</li>
<li>두번째 수집기인 toMap에서는 이후 상태를 전이에 대응시키는 EnumMap을 생성</li>
<li>EnumMap을 사용하면 유지보수하기 좋다</li>
</ul>
<h3 id="핵심-정리">핵심 정리</h3>
<ul>
<li>배열의 인덱스를 위해 ordinal사용하지 말고 EnumMap을 사용하자</li>
<li>다차원 관계는 EnumMap&lt;..., EnumMap&lt;...&gt;&gt;로 표기하자</li>
</ul>
<blockquote>
<p>Item 38 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라</p>
</blockquote>
<ul>
<li>열거 타입의 확장이 지원되지는 않는다<ul>
<li>하지만 가끔 필요 시, 인터페이스를 통해 확장하면 된다</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public interface Operation {
    double apply(double x, double y);
}

public enum BasicOperation implements Operation {
    PLUS(&quot;+&quot;) {
        @Override
        public double apply(double x, double y) {
            return x+y;
        }
    },
    MINUS(&quot;-&quot;) {
        @Override
        public double apply(double x, double y) {
            return x-y;
        }
    },
    TIMES(&quot;*&quot;) {
        @Override
        public double apply(double x, double y) {
            return x*y;
        }
    },
    DIVIDE(&quot;/&quot;) {
        @Override
        public double apply(double x, double y) {
            return x/y;
        }
    };

    private final String symbol;

    BasicOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

public enum ExtendedOperation implements Operation {
    EXP(&quot;^&quot;) {
        @Override
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER(&quot;%&quot;) {
        @Override
        public double apply(double x, double y) {
            return x % y;
        }
    };

    private final String symbol;

    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}</code></pre>
<h3 id="클래스로-받아보기">클래스로 받아보기</h3>
<pre><code class="language-java">@Test
public void operationTest() {
    double x = 4.0;
    double y = 2.0;
    getEnumClass(BasicOperation.class, x, y);
    getEnumClass(ExtendedOperation.class, x, y);
}

private &lt;T extends Enum&lt;T&gt; &amp; Operation&gt; void getEnumClass(Class&lt;T&gt; enumClass, double x, double y) {
    for (Operation operation : enumClass.getEnumConstants()) {
        System.out.printf(&quot;%f %s %f = %f%n&quot;, x, operation, y, operation.apply(x, y));
}</code></pre>
<ul>
<li>enum타입의 클래스를 받아 getEnumConstants()를 통해 클래스가 가진 apply메서드를 실행해보았다</li>
<li>&lt;T extends Enum&lt;T? &amp; Operation&gt;의 의미는 enum이며 Operation을 상속받았다는 것을 이야기한다</li>
</ul>
<h3 id="컬렉션으로-받아보기">컬렉션으로 받아보기</h3>
<pre><code class="language-java">@Test
public void operationTest() {
    double x = 4.0;
    double y = 2.0;

    getEnumCollection(Arrays.asList(BasicOperation.values()), x, y);
    getEnumCollection(Arrays.asList(ExtendedOperation.values()), x, y);
}

private void getEnumCollection(Collection&lt;? extends Operation&gt; opSet, double x, double y) {
    for (Operation operation : opSet) {
        System.out.printf(&quot;%f %s %f = %f%n&quot;, x, operation, y, operation.apply(x, y));
    }
}</code></pre>
<ul>
<li>enum타입의 값이 들어있는 list를 넘겨본 형태이다</li>
<li>특정 연산에서는 EnumSet과 EnumMap을 사용하지 못한다</li>
</ul>
<h1 id="스터디-질문-왜-사용-못하는지-얘기해보자">스터디 질문 왜 사용 못하는지 얘기해보자</h1>
<blockquote>
<p>item 39 명명 패턴보다는 애너테이션을 사용하라</p>
</blockquote>
<p>명명패턴이란?</p>
<ul>
<li>메서드의 이름앞을 test…로 짓는 등 이름에 팩턴을 주어 Reflection등으로 패턴 검출시 특정 작업을 수행하는 식의 코딩 형식</li>
</ul>
<p>단점</p>
<ul>
<li>오탈자의 위험</li>
<li>메서드, 파라미터, 클래스명에 대한 설정이 불가능</li>
<li>프로그램 요소를 매개변수로 전달할 마땅할 방법이 없다</li>
</ul>
<h3 id="샘플-1-일반-메서드-애너테이션">샘플 1: 일반 메서드 애너테이션</h3>
<pre><code class="language-java">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodTest {
}</code></pre>
<ul>
<li>@Retention과@Target은 메타 애너테이션이라 불린다.</li>
<li>@Retention은 생존기간을 나타낸다<ul>
<li>RetentionPolicy.RUNTIME:런타임에도 유지되어야 한다는 표시</li>
</ul>
</li>
<li>@Target(ElementType.METHOD):해당 애너테이션이 반드시 메서드에 적용되어야 한다는 것을 알려준다</li>
<li>@MethodTest: 애너테이션과 같이 아무 매개 변수 없이 단순히 대상에 마킹한다는 뜻으로 마킹 애너테이션이라 한다.</li>
</ul>
<pre><code class="language-java">public class Sample {
    @MethodTest
    public static void m1() { }
    public static void m2() { }

    @MethodTest
    public static void m3() {
        throw new RuntimeException(&quot;실패&quot;);
    }
    public static void m4() { }

    @MethodTest
    public void m5() { } // 잘못 사용한 예: 정적 메서드가 아니다.
    public static void m6() { }
    @MethodTest
    public static void m7() {
        throw new RuntimeException(&quot;실패&quot;);
    }
    public static void m8() { }
}</code></pre>
<h3 id="예제-샘플-2-특정-예외를-던져야만-성공하는-테스트">예제 샘플 2: 특정 예외를 던져야만 성공하는 테스트</h3>
<pre><code class="language-java">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionSingleTest {
    Class&lt;? extends Throwable&gt; value();
}</code></pre>
<ul>
<li>Value는 기본 파라미터 값을 의미<ul>
<li>Target 애너테이션의 경우 기본 파라미터 값으로 ElementType을 받는다고 보면 된다</li>
<li>여기서는 Throwable을 상속하는 모든 클래스를 받기 때문에 모든 예외 포용 가능</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class Sample2 {
    @ExceptionSingleTest(ArithmeticException.class)
    public static void m1() {
        int i = 0; // 성공
        i = i / i;
    }

    @ExceptionSingleTest(ArithmeticException.class)
    public static void m2() {
        int[] a = new int[0]; // 실패, 다른 예외 발생
        int i = a[1];
    }

    @ExceptionSingleTest(ArithmeticException.class)
    public static void m3() { } // 실패, 예외가 발생하지 않음
}</code></pre>
<pre><code class="language-java">@Test
public void sample2ClassAnnotationTest() throws ClassNotFoundException {
    int tests = 0;
    int passed = 0;
    Class&lt;?&gt; testClass = Class.forName(&quot;item39.Sample2&quot;);
    Method[] declaredMethods = testClass.getDeclaredMethods();

    for (Method m : declaredMethods) {
        if(m.isAnnotationPresent(ExceptionSingleTest.class)) {
            tests++;
            try {
                m.invoke(null);
                System.out.println(m + &quot;, 테스트 실패 (예외를 던지지 않음)&quot;);
            } catch (InvocationTargetException wrappedExc) {
                Throwable exc = wrappedExc.getCause();
                Class&lt;? extends Throwable&gt; excType = m.getAnnotation(ExceptionSingleTest.class).value(); // 애너테이션 매개변수의 값을 추출한다.

                if(excType.isInstance(exc)) {
                    System.out.printf(&quot;테스트 %s 성공: 기대한 예외 %s, 발생한 예외 %s%n&quot;, m, excType.getName(), exc);
                    passed++;
                } else {
                    System.out.printf(&quot;테스트 %s 실패: 기대한 예외 %s, 발생한 예외 %s%n&quot;, m, excType.getName(), exc);
                }
            } catch (Exception exc) {
                System.out.println(&quot;잘못 사용한 @ExceptionTest: &quot; + m);
            }
        }
    }

    System.out.printf(&quot;성공: %d, 실패: %d%n&quot;, passed, tests - passed);
}</code></pre>
<ul>
<li>Sample2 클래스에 선언된 메서드 중@ExceptionSingleTest 애너테이션이 달린 것을 찾는다.</li>
<li>InvocationTargetException의 하위 예외가 일어나고, 그 예외가@ExceptionSingleTest 애너테이션의 인수로서 들어온 예외와 일치해야만passed의 카운트가 올라간다.<ul>
<li>InvocationTargetException은 리플렉션 API의 통합 예외로.getCause() 메서드를 통해서 실제 예외가 무엇인지 알 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="샘플-3-샘플2와-동일한데-배열로-받는-애너테이션">샘플 3: 샘플2와 동일한데 배열로 받는 애너테이션</h3>
<pre><code class="language-java">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionArrayTest {
    Class&lt;? extends Throwable&gt;[] value();
}</code></pre>
<ul>
<li>단 하나의 애너테이션을 받더라도 아무런 에러가 나오지 않는다<ul>
<li>ex)@ExceptionArrayTest(IndexOutOfBoundsException)처럼 작성해도 문제없다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class Sample3 {
    @ExceptionArrayTest({
            IndexOutOfBoundsException.class
            , NullPointerException.class
    })
    public static void doublyBad() {
        List&lt;String&gt; list = new ArrayList&lt;&gt;();
        list.add(5, null);
    }
}</code></pre>
<h3 id="샘플-4-repeatable이용하여-여러개의-값을-받을-수-있는-애너테이션-만들기">샘플 4: @Repeatable이용하여 여러개의 값을 받을 수 있는 애너테이션 만들기</h3>
<pre><code class="language-java">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionRepeatableTest {
    Class&lt;? extends Throwable&gt; value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
    ExceptionRepeatableTest[] value();
}</code></pre>
<pre><code class="language-java">public class Sample4 {
    @ExceptionRepeatableTest(IndexOutOfBoundsException.class)
    @ExceptionRepeatableTest(NullPointerException.class)
    public static void doublyBad() {
        List&lt;String&gt; list = new ArrayList&lt;&gt;();
        list.add(5, null);
    }
}</code></pre>
<h3 id="핵심-정리-1">핵심 정리</h3>
<ul>
<li>어노테이션으로 할 수 있는 일을 굳이 명명 패턴으로 처리하지 말자</li>
<li>자바 프로그래머라면 어노테이션 타입들을 사용해야 한다</li>
</ul>
<blockquote>
<p>item 40 Override 애너테이션을 일관되게 사용하라</p>
</blockquote>
<p>해당 애너테이션을 일관되게 사용한다면 여러가지 버그들을 예방해준다</p>
<p>▶️ 영어 알파벳 2개로 구성된 문자열(바이그램)을 표현하는 클래스</p>
<pre><code class="language-java">public class Bigram {
    private final char first;
    private final char second;

    public Bigram(char first, char second) {
        this.first  = first;
        this.second = second;
    }

    public boolean equals(Bigram b) {
        return b.first == first &amp;&amp; b.second == second;
    }

    public int hashCode() {
        return 31 * first + second;
    }

    public static void main(String[] args) {
        Set&lt;Bigram&gt; s = new HashSet&lt;&gt;();
        for (int i = 0; i &lt; 10; i++)
            for (char ch = &#39;a&#39;; ch &lt;= &#39;z&#39;; ch++)
                s.add(new Bigram(ch, ch));
        System.out.println(s.size());
    }
}</code></pre>
<p>→ main 메서드가 실행되고 Set은 중복을 허용하지 않으니 26이 출력 될 것 같지만, 실제로는 260이 출력된다.</p>
<p>문제의 원인: equals 메소드를 재정의한게 아니라 다중정의해버린 것이다. Object의 <code>equals()</code> 를 재정의하려면 매개변수 타입을 Object로 해야 하는데 그렇지 않아서 상속이 아닌 별개의 equals 메서드를 정의한 꼴이 되었다.</p>
<p>수정하면</p>
<pre><code class="language-java">@Override public boolean equals(Object o) {
        if (!(o instanceof Bigram2))
            return false;
        Bigram2 b = (Bigram2) o;
        return b.first == first &amp;&amp; b.second == second;
}</code></pre>
<p>equals에 문제가 있음에도 컴파일에는 성공하기 때문에, <code>@Override</code> 를 달지 않게 되면 위의 잘못한 점을 컴파일러가 알려주지 못한다.</p>
<h2 id="override의-예외사항">Override의 예외사항</h2>
<p>구체 클래스에서 상위 클래스의 메서드를 재정의할 때는 굳이 @Override를 달지 않아도 된다. 구체 클래스인데 구현하지 않은 추상 메서드가 남아 있다면 컴파일러가 자동으로 사실을 알려주기 떄문이다</p>
<ul>
<li>재정의는 인터페이스의 메서드를 재정의할 때도 사용할 수 있다.  단, 추상 클래스나 인터페이스에는 상위 클래스나 상위 인터페이스의 메서드를 재정의하는 모든 메서드에 @Override를 다는 것이 좋다</li>
<li>예컨데 Set 인터페이스는 Collection인터페이스를 확장했지만 새로 추가한 멧드는 없기 때문에 모든 메서드 선언에 @Override를 달아 실수로 추가한 메서드가 없음을 보장한다</li>
</ul>
<blockquote>
<p><strong>핵심 정리:</strong> 재정의한 모든 메서드에 @Override 애너테이션을 의식적으로 달면, 여러분이 실수했을때 컴파일러가 바로 알려줄 것이다. 예외는 한 가지 뿐이다. 구체 클래스에서 상위 클래스의 추상 메서드를 재정의한 경우에는 이 애너테이션을 달지 않아도 된다.”</p>
</blockquote>
<blockquote>
<p>item 41: 정의하려는 것이 타입이라면 마커 인터페이스를 사용하여라</p>
</blockquote>
<p>마커 인터페이스란?: </p>
<ul>
<li>아무 메서드도 담고 있지 않고 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스를 의미한다</li>
<li>대표적인 예로, Serializable 인터페이스가 있다. 단순히 자신을 구현한 클래스의 인스턴스는 ObjectOutputStream을 통해 직렬화할 수 있다고 알려주는 역할을 한다</li>
</ul>
<p>마커 인터페이스의 장점</p>
<ul>
<li>마커 인터페이스는 두가지 면에서 애너테이션 보다 장점을 가진다<ul>
<li>마커 인터페이스는 타입이기 때문에 애너테이션을 사용했다면 런타임에서나 발견될 오류를 컴파일 타임에 잡을 수 있다</li>
<li>적용 대상을 더 정밀하게 지정할 수 있다 그냥 마킹하고 싶은 클래스에서만 그 인터페이스를 구현하면 마킹된 타입은 자동으로 그 인터페이스의 하위 타입임이 보장된다.</li>
</ul>
</li>
</ul>
<p>어떤 상황에서 마커 애너테이션과 인터페이스를 써야 할까?</p>
<ol>
<li>클래스와 인터페이스 외의 프로그램 요소(모듈/패키지/필드/지역변수 등)에 마킹해야 할때는 <strong>애너테이션</strong>을 사용한다.</li>
<li><strong>마킹이 된 객체를 매개변수로 받는 메서드</strong>를 작성할 일이 있다면 마커 인터페이스를 사용해야 한다. 마커 인터페이스를 해당 메서드의 매개변수 타입으로 사용하여 컴파일 타임에 오류를 잡아낼 수 있기 때문이다.</li>
</ol>
<blockquote>
<p>📚 <strong>핵심 정리</strong></p>
<p>새로 추가하는 메서드 없이 단지 타입 정의가 목적이라면 마커 인터페이스를 정의하자. 클래스나 인터페이스 외의 프로그램 요소에 마킹해야 하거나, 애너테이션을 적극 활용하는 프레임워크의 일부로 마커를 편입시키고자 한다면 마커 애너테이션을 사용하자.</p>
</blockquote>
<p>💡 추가 리서치</p>
<pre><code class="language-java">public interface Deletable {
}</code></pre>
<p>개체를 제거할 수 있는지 여부를 나타내는 마커를 만들 수 있다</p>
<p>데이터베이스에서 엔티티를 삭제를 하려면 이 엔티티를 나타내는 객체가 Deletable 마커 인터페이스를 구현해야 한다</p>
<pre><code class="language-java">public class Entity implements Deletable {
    // implementation details
}</code></pre>
<p>데이터베이스에서 엔티티를 제거하는 메서드가 있는 DAO 개체가 있다고 가정하면 마커 인터페이스를 구현한 객체만 삭제할 수 있도록 delete()메서드를 작성할 수 있다</p>
<pre><code class="language-java">public class ShapeDao {

    // other dao methods

    public boolean delete(Object object) {
        if (!(object instanceof Deletable)) {
            return false;
        }

        // delete implementation details

        return true;
    }
}</code></pre>
<p>결론 :, <strong>우리는 객체의 런타임 동작에 대한 표시를 JVM에 제공하고 있다.</strong>  객체가 마커 인터페이스를 구현하는 경우 데이터베이스에서 삭제할 수 있다. <strong>그래서</strong> 컴파일 타임에 오류를 잡을 수 있는 건가봄?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 자바 2장]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-2%EC%9E%A5</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-2%EC%9E%A5</guid>
            <pubDate>Wed, 24 Jan 2024 14:59:18 GMT</pubDate>
            <description><![CDATA[<h1 id="이펙티브-자바-2장">이펙티브 자바 2장</h1>
<h2 id="💡-item-2-생성자에-매개변수가-많다면-빌더를-고려하라">💡 Item 2 생성자에 매개변수가 많다면 빌더를 고려하라</h2>
<ul>
<li><p>점층적 생성자 패턴</p>
<pre><code class="language-java">  public class NutritionFacts {

      private final int calories;
      private final int fat;
      private final int sodium;

      public NutritionFacts(int calories) {
          this(calories, 0, 0);
      }

      public NutritionFacts(int calories, int fat) {
          this(calories, fat, 0);
      }

      public NutritionFacts(int calories, int fat, int sodium) {
          this.calories = calories;
          this.fat = fat;
          this.sodium = sodium;
      }
  }</code></pre>
<p>  문제 : 확장하기 어렵다. </p>
</li>
<li><p>자바빈즈 패턴</p>
<pre><code class="language-java">  public class NutritionFacts {

      private int calories = 0;
      private int fat = 0;
      private int sodium = 0;

      public NutritionFacts() { } //매개변수 없는 생성자로 객체를 만든 뒤,
                                                                  //setter 메소드로 값을 설정한다.

      public void setCalories(int calories) {
          this.calories = calories;
      }

      public void setFat(int fat) {
          this.fat = fat;
      }

      public void setSodium(int sodium) {
          this.sodium = sodium;
      }
  }</code></pre>
<p>  객체 하나를 만드려면 메서드를 여러개 호출해야 하고 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다. 일관성이 무너지면 클래스를 불변으로 만들 수 없는 큰 단점이 존재한다</p>
</li>
<li><p>빌더 패턴</p>
<p>  필요한 객체를 직접 만드는 대신 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻는다.</p>
<pre><code class="language-java">  public class NutritionFacts {

      private final int calories;
      private final int fat;
      private final int sodium;

      public static class Builder {
          //필수 매개변수
          private final int calories;

          //선택 매개변수
          private int fat = 0;
          private int sodium = 0;

          public Builder(int calories) {
              this.calories = calories;
          }

                  //setter 메소드
          public Builder fat(int val) {
              fat = val;
              return this;
          }

          public Builder sodium(int val) {
              sodium = val;
              return this;
          }

          public NutritionFacts build() {
              return new NutritionFacts(this);
          }
      }

      private NutritionFacts(Builder builder) {
          calories = builder.calories;
          fat = builder.fat;
          sodium = builder.sodium;
      }
  }</code></pre>
<pre><code class="language-java">  NutritionFacts cocaCola = new NutritionFacts(250).fat(30).sodium(20).build();</code></pre>
<p>  유연하다는 장점이 있지만 단점은 객체를 만들기 전에 빌더 부터 만들어야 한다. 생성 비용이 크진 않지만 성능에 민감한 상황에서는 문제가 될 수 있다.</p>
<p>  하지만 계층적으로 설계된 클래스와 함께 쓰기에 빌더가 좋다</p>
<pre><code class="language-java">  public abstract class Pizza{
      public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
      final Set&lt;Topping&gt; toppings;

          abstract static class Builder&lt;T extends Builder&lt;T&gt;&gt;{
              EnumSet&lt;Topping&gt; toppings = EnumSet.noneOf(Topping.class);
                  public T addTopping(Topping topping){
                      toppings.add(Objects.requireNonNull(topping));
                      return self();
                  }
                  abstract Pizza build();

                  protected abstract T self();
                  }

                  Pizza(Builder&lt;?&gt; builder){
                      toppings = builder.toppings.clone();
                  }

    }</code></pre>
<p>  Pizza.Builder 클래스는 재귀적 타입 한정을 이용하는 제네릭 타입이다. 여기에 추상 메서드인 self를 더해 하위 클래스에서 형변환하지 않고도 메서드 연쇄를 지원할 수 있다.</p>
<pre><code class="language-java">  public class NyPizza extends Pizza{
      public enum Size { SMALL, MEDIUM, LARGE }
      private final Size size;

      public static class Builder extends Pizza.Builder&lt;Builder&gt;{
          private final Size size;

          public Builder(Size size){
              this.size = Objects.requireNonNull(size);
          }

          @Override public NyPizza build(){
              return new NyPizza(this);
          }

          @Override protected Builder self(){
              return this;
          }
      }

      private NyPizza(Builder builder){
          super(builder);
          size = builder.size();
      }

      }</code></pre>
</li>
</ul>
<h2 id="💡item-3-private-생성자나-열거타입으로-싱글턴임을-보증하라">💡Item 3 private 생성자나 열거타입으로 싱글턴임을 보증하라</h2>
<ul>
<li><p>먼저 싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스</p>
</li>
<li><p>싱글턴을 만드는 방법 3가지</p>
<ol>
<li><p>Private 생성자와 public static 필드</p>
<pre><code class="language-java"> public class Elvis {
     public static final Elvis INSTANCE = new Elvis();
     private Elvis { ... }
     public void leaveTheBuilding() { ... }
 }</code></pre>
<p> 해당 클래스가 싱글턴임이 API에 명백히 드러난다. public static 필드가 final이니 절대로 다른 객체를 참조할 수 없다.</p>
<p> 문제 :</p>
<ul>
<li>권한이 있는 클라이언트에서 리플레션 API를 이용해 private 생성자를 호출 할 수 있다</li>
<li>생성되는 시점을 조절할 수 없다<ul>
<li>방어 방법: 생성자를 수정하여 두 번째 객체가 생성되려 할 때 예외를 던진</li>
</ul>
</li>
</ul>
</li>
<li><p>정적 팩터리 메서드를 public static 멤버로 제공되는 방식</p>
<pre><code class="language-java"> public class Elvis {
     private static final Elvis INSTANCE = new Elvis();
     private Elvis { ... }
     public static Elvis getInstance() {return INSTANCE;}

     public void leaveTheBuilding() { ... }
 }</code></pre>
<p> 장점:</p>
<ol>
<li><p>API를 바꾸지 않고도 싱글톤이 아니게 변경할 수 있다</p>
</li>
<li><p>정적 팩터리를 제너릭 싱글톤 팩터리로 만들 수 있다(public static필드가 final이니 절대로 다른 객체를 참조할 수 없기 때문이다)</p>
</li>
<li><p>정적 펙터리의 메서드 참조를 공급자로 사용할 수 있다. 가령 Elvis::getInstance를 Supplier<Elvis>로 사용하는 식. </p>
<p>하지만 여기서 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어지지 않으려면 이 코드를 추가해야한다</p>
<pre><code class="language-java">// 싱글턴임을 보장해주는 readResolve 메서드
private Object readResolve() {
 // &#39;진짜&#39; Elvis를 반환하고, 가짜 Elvis는 가비지 컬렉터에 맡다.
 return INSTANCE;
}</code></pre>
</li>
</ol>
</li>
<li><p>원소가 하나인 열거 타입을 선언한다</p>
<pre><code class="language-java"> public enum Elvis {
     INSTANCE;
     public void leaveTheBuilding() { ... }
 }</code></pre>
<p> 장점:</p>
<ol>
<li><p>간결하다</p>
</li>
<li><p>추가 노력없이 직렬화할수 있다</p>
</li>
<li><p>복잡한 직렬화 상황이나 리플렉션 공격에도 제 2의 인스턴스가 생기는 일을 완벽히 막아준다</p>
<p>문제점: </p>
</li>
<li><p>싱글턴이 Enum 외의 클래스를 상속해야 한다면, 이 방법은 사용할 수 없다</p>
</li>
<li><p>열거 타입이 다른 인터페이스를 구현하도록 선언할 수 없다.</p>
</li>
</ol>
</li>
</ol>
</li>
</ul>
<h2 id="💡item-4-인스턴스를-막으려거든-private-생성자를-사용하라">💡Item <strong>4. 인스턴스를 막으려거든 private 생성자를 사용하라</strong></h2>
<p>·<strong>정적 메서드와 정적 필드만 담은 클래스</strong>는 객체 지향적으로 사고하지 않는 사람들이 종종 남용하는 방식이지만, 나름의 쓰임새가 있다.</p>
<ol>
<li><p>java.lang.Math, java.util.Arrays처럼 <strong>기본 타입 값</strong>이나 <strong>배열 관련 메서드</strong>들을 모아놓을 수 있다.</p>
</li>
<li><p>java.util.Collections처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩터리)를 모아놓을 수 있다.</p>
</li>
<li><p>final 클래스와 관련된 메서드를 모아 놓을 때 사용한다. final 클래스를 상속해서 하위 클래스에 메서드를 넣는 건 불가기 때문이다.</p>
</li>
</ol>
<ul>
<li><strong>private 생성자</strong>를 추가해 클래스의 <strong>인스턴스화를 막아서 사용</strong>할 수 있다. (생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만든다)</li>
</ul>
<pre><code class="language-java">// 인스턴스를 만들 수 없는 유틸리티 클래스
public class UtilityClass {
    // 기본 생성자가 만들어지는 것을 막는다(인스턴스화 방지용).
    // 악의적 리플렉션을 막을 수 있다.
    private UtilityClass() {
        throw new AssertionError();
    }
}</code></pre>
<ul>
<li>상속을 불가능하게 하는 효과도 있다. 모든 생성자는 명시적이든 묵시적이든 상위 클래스를 생성자를 호출해야하는데, 이를 private 선언으로 막아버렸기 때문이다.</li>
</ul>
<h2 id="💡item-5-자원을-직접-명시하지-말고-의존-객체-주입을-사용하라">💡Item <strong>5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라</strong></h2>
<blockquote>
<p>많은 클래스가 하나 이상의 자원에 의존한다. 사용하는 자원에 따라 동작이 달라지는 클래스에 경우 클래스가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원을 사용해야 한다.</p>
</blockquote>
<p><strong>static 유틸리티를</strong> 잘못 사용한 예시<strong>- 유연하지 않고 테스트하기 어렵다.</strong></p>
<pre><code class="language-java">public class SpellChecker {
    private static final Lexicon dictionary = ...; // 의존하는 리소스 (의존성)
    private SpellChecker() {} // 객체 생성 방지 

    public static boolean isValid(String word) { ... } 
    public static List&lt;String&gt; suggestions(String typo) { ... } 
}</code></pre>
<p>인스턴스를 만들 필요가 없기 때문에 private한 생성자를 만들어주고, public static 메소드들이 있다.</p>
<p><strong>싱글턴을 잘못 사용한 예- 유연하지 않고 테스트하기 어렵다.</strong></p>
<pre><code class="language-java">public class SpellChecker { 
    private final Lexicon dictionary; 

    public boolean isValid(String word) { return true; } 
    public List&lt;String&gt; suggestions(String typo) { return null; }
}</code></pre>
<h3 id="의존-객체-주입">의존 객체 주입</h3>
<p>제대로 사용하려면 <strong>의존성을 바깥으로 분리하여 외부로부터 주입받도록 해야 한다. 쉽게 말하면 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식이다.</strong></p>
<pre><code class="language-java">public class SpellChecker { 
    private final Lexicon dictionary; 

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

    public boolean isValid(String word) { return true; } 
    public List&lt;String&gt; suggestions(String typo) { return null; }</code></pre>
<p>불변을 보장하여 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있다</p>
<h3 id="팩토리-메서드-패턴">팩토리 메서드 패턴</h3>
<p>생성자에 자원 팩터리를 넘겨주는 방</p>
<p>예시 <code>Supplier&lt;T&gt;</code> 인터페이스</p>
<pre><code class="language-java">@FunctionalInterface
public interface Supplier&lt;T&gt; {
        /**
     * Gets a result.
     *
     * @return a result
     */
    T get(); // T 타입 객체를 찍어낸다
}</code></pre>
<pre><code class="language-java">Mosaic create(Supplier&lt;? extends Tile&gt; tileFactory) { ... }</code></pre>
<p><code>Supplier&lt;T&gt;</code>를 입력으로 받는 메서드는 <strong>한정적 와일드카드 타입</strong>을 사용해 팩터리의 타입 매개변수를 제한</p>
<pre><code class="language-java">public class SpellChecker { 
    private final Lexicon dictionary; 

    private SpellChecker(Supplier&lt;Lexicon&gt; dictionary) { 
            this.dictionary = Objects.requireNonNull(dictionary); 
     } 

    public boolean isValid(String word) { return true; } 
    public List&lt;String&gt; suggestions(String typo) { return null; } 

    public static void main(String[] args) {
        Lexicon lexicon = new KoreanDictionary();
        SpellChecker spellChecker = new SpellChecker(() -&gt; lexicon);
        spellChecker.isValid(&quot;hello&quot;);
    }
}

interface Lexicon{}

class KoreanDictionary implements Lexicon {}
class TestDictionary implements Lexicon {} // test가 가능해짐</code></pre>
<p>결론 : 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해준다.</p>
<h2 id="💡item-6-불필요한-객체-생성을-피하라">💡Item <strong>6. 불필요한 객체 생성을 피하라</strong></h2>
<blockquote>
<p>똑같은 기능의 객체를 매번 생성하기보다 객체 하나를 재사용하는 편이 나을 때가 많다. 재사용은 빠르고 세련되다. 특히 불변 객체(아이템 17)는 언제든 재사용할 수 있다</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준]1303번]]></title>
            <link>https://velog.io/@almondbreez0_3/%EB%B0%B1%EC%A4%801303%EB%B2%88</link>
            <guid>https://velog.io/@almondbreez0_3/%EB%B0%B1%EC%A4%801303%EB%B2%88</guid>
            <pubDate>Sun, 21 Jan 2024 14:22:32 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/1303">링크텍스트</a></p>
<p>DFS 토픽을 가지고 문제를 풀어보려고 한다</p>
<h2 id="문제-풀이">문제 풀이</h2>
<p>처음에 이 문제를 읽고 이해를 못했다.. 그래서 여러 풀이를 보다가
이 분 풀이를 보고 문제를 이해했다</p>
<blockquote>
<p><a href="https://9hyuk9.tistory.com/57">링크텍스트</a></p>
</blockquote>
<p>&quot;대각선&quot;이라는 말을 보고 이어지더라도 대각선이면 뭉치로 안본다로 보았고 또한, 혼자 있는 경우도 포함인 줄 몰랐다. 문제를 많이 풀어서 해석하는 능력을 키우는것이 정말 필요하다고 느꼈다</p>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.util.*;
import java.lang.*;
import java.io.*;

// The main method must be in a class named &quot;Main&quot;.
class Main {
    static char[][] map;
    static boolean[][] visited;
    static int N,M;
    static int dy[] = {-1, 1, 0, 0};
    static int dx[] = {0, 0, -1, 1};
    static int ally;
    static int enemy;
    static int allyCount;
    static int enemyCount;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken()); // 가로 
        M = Integer.parseInt(st.nextToken()); // 세로

        map = new char[M][N];
        visited = new boolean[M][N];

        ally = 0;
        enemy = 0;
        allyCount = 0;
        enemyCount = 0;

        for (int i =0; i&lt;M;i++){
            String str = br.readLine();
            for(int j = 0; j&lt;N;j++){
                   char ch = str.charAt(j);
                map[i][j] = ch; 
            }
        }

        for (int i = 0; i&lt;M;i++) {
            for (int j = 0;j&lt;N;j++) {
                if (!visited[i][j]){
                    dfs(i,j,map[i][j]);
                    ally += allyCount * allyCount;
                    enemy += enemyCount * enemyCount;

                    allyCount = 0;
                    enemyCount = 0;
                }
            }
        }
        System.out.println(ally + &quot; &quot; + enemy);
    }
    private static void dfs(int x, int y, char t) {
        visited[x][y] = true;

        if (t == &#39;W&#39;)
            allyCount++;
        else
            enemyCount++;

        for (int i = 0; i &lt; 4; i++) {
            int nowX = x + dx[i];
            int nowY = y + dy[i];

            if (nowX &gt;= 0 &amp;&amp; nowY &gt;= 0 &amp;&amp; nowX &lt; M &amp;&amp; nowY &lt; N) {
                if (!visited[nowX][nowY] &amp;&amp; map[nowX][nowY] == t) {
                    dfs(nowX, nowY, t);
                }
            }
        }
    }

}</code></pre>
<h2 id="오류">오류</h2>
<ul>
<li>런타임 에러 (StringIndexOutOfBounds)    <pre><code class="language-java">import java.util.*;
import java.lang.*;
import java.io.*;
</code></pre>
</li>
</ul>
<p>// The main method must be in a class named &quot;Main&quot;.
class Main {
    static char[][] map;
    static boolean[][] visited;
    static int N,M;
    static int dx[] = {-1, 1, 0, 0};
    static int dy[] = {0, 0, -1, 1};
    static int ally;
    static int enemy;
    static int allyCount;
    static int enemyCount;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;</p>
<pre><code>    st = new StringTokenizer(br.readLine());
    N = Integer.parseInt(st.nextToken()); // 가로 
    M = Integer.parseInt(st.nextToken()); // 세로

    map = new char[N][M];
    visited = new boolean[N][M];

    ally = 0;
    enemy = 0;
    allyCount = 0;
    enemyCount = 0;

    for (int i =0; i&lt;N;i++){
        String str = br.readLine();
        for(int j = 0; j&lt;M;j++){
               char ch = str.charAt(j);
            map[i][j] = ch; 
        }
    }

    for (int i = 0; i&lt;N;i++) {
        for (int j = 0;j&lt;M;j++) {
            if (!visited[i][j]){
                dfs(i,j,map[i][j]);
                ally += allyCount * allyCount;
                enemy += enemyCount * enemyCount;

                allyCount = 0;
                enemyCount = 0;
            }
        }
    }
    System.out.println(ally + &quot; &quot; + enemy);
}
private static void dfs(int x, int y, char t) {
    visited[x][y] = true;

    if (t == &#39;W&#39;)
        allyCount++;
    else
        enemyCount++;

    for (int i = 0; i &lt; 4; i++) {
        int nowX = x + dx[i];
        int nowY = y + dy[i];

        if (nowX &gt;= 0 &amp;&amp; nowY &gt;= 0 &amp;&amp; nowX &lt; N &amp;&amp; nowY &lt; M) {
            if (!visited[nowX][nowY] &amp;&amp; map[nowX][nowY] == t) {
                dfs(nowX, nowY, t);
            }
        }
    }
}</code></pre><p>}</p>
<pre><code>저 블로그 글쓴이랑 똑같은 오류...하하



## 알게 된 점
```java
String str = br.readLine();</code></pre><p>줄에서 문자 읽을 때 쓰는것, 나는 항상 int만 해서 몰랐다..ㅜㅜ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UMC 5주차 스터디 - 실전 SQL]]></title>
            <link>https://velog.io/@almondbreez0_3/UMC-5%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-%EC%8B%A4%EC%A0%84-SQL</link>
            <guid>https://velog.io/@almondbreez0_3/UMC-5%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-%EC%8B%A4%EC%A0%84-SQL</guid>
            <pubDate>Wed, 08 Nov 2023 14:32:45 GMT</pubDate>
            <description><![CDATA[<p>📌 기초데이터베이스 시간에 배우는 내용이라서 복습하는 시간을 가졌고, 페이지네이션부분에 대한 공부를 추가로 하였다</p>
<p><strong>✏️ Right Join이란?</strong>
오른쪽 테이블의 모든 값이 출력되는 조인
<strong>✏️ Left Join이란?</strong>
왼쪽 테이블의 모든 값이 출력되는 조인
좌측 테이블 데이터에 추가로 우측 정보를 조인하는 문법이다</p>
<p><strong>✏️ Inner Join</strong>
양측 모두에 존재하는 것만 결과로 만든다 하지만 Left 조인은 좌측테이블 중 조인 불가능한 것들도 모두 결과로 만든다. 이때 조인이 불가능한 우측테이블 값은 Null로 채워진다</p>
<blockquote>
<p> 📌 해시태그를 통한 책의 검색</p>
</blockquote>
<pre><code class="language-sql">select * from book where id in 
    (select book_id from book_hash_tag
    where hash_tag_id = (select id from hashtag where name = &#39;UMC&#39;));</code></pre>
<blockquote>
<p> 📌 좋아요 개수 순으로 목록 조회</p>
</blockquote>
<pre><code class="language-sql">select * from book as b
where book.id = book_likes.id AND 
join (select count(*) as like_count
          from book_likes
          group by book_id) as likes on b.id = likes.book_id
order by likes.like_count desc;
</code></pre>
<h2 id="페이징">페이징</h2>
<p>멋사 축제 사이트할 때 주점 목록을 넘겨주는 것을 구현한 적이 있는데, 그렇게 많은 데이터를 다뤄보는 것이 처음이기도 했고 타임 어택 프로젝트기도 해서 페이징에 대해 생각도 못하고 그냥 그대로 넘겨줬었는데, 통신할때 프론트 측에서 최적화가 어렵다는 요청이 들어왔었다. 그제서야 페이지네이션에 대한 내용이 생각이 났었는데 홍대 축제까지 얼마 안남았었기 때문에 프론트 측에서 최대한 최적화를 하려고 노력했었는데 굉장히 죄송했었다. 이번 umc 스터디에서 페이징에 대해 공부해보는 겸 기본 코드를 리팩토링 해보는 시간도 가져보았다.</p>
<h4 id="📌-1-offset-paging">📌 1. Offset Paging</h4>
<pre><code class="language-sql">select *
from book
order by likes desc
limit 10 offset 0;</code></pre>
<p>위와 같이 limit을 통해 한 페이지에서 보여줄 데이터의 개수를 정하고 offset으로 몇 개를 건너뛸지를 정한다</p>
<pre><code class="language-sql">select * 
from book
order by likes desc
limit y offset(x-1) * y</code></pre>
<p>하지만 1페이지에서 2페이지로 가는 순간 책이 실시간 추가가 된다면 2페이지에서 1페이지에서 본 것들을 또 보게 될 단점이 존재한다</p>
<h4 id="📌--2-cursor-based-페이징">📌  2. Cursor based 페이징</h4>
<p>cursor로 무언가를 가르켜 페이징을 하는 방법이다. 여기서 커서는 마지막으로 조회한 콘텐츠이다</p>
<p>조금 더 실용적으로 접근하기 위해 spring data jpa에서 pagination을 구현하는 방법이 없을까 해서 구글링 한 결과 pageable이란 라이브러리가 있다는 것을 알게 되었다</p>
<p>개발자가 직접 구현해서 사용할 수 있으나 jpa에서는 이를 편하게 사용할 수 있도록 Pageable 이라는 객체를 제공한다</p>
<h2 id="pageable을-사용한userrepository-usercontroller를-만들어-보자">Pageable을 사용한UserRepository, UserController를 만들어 보자</h2>
<pre><code class="language-java">public interface UserRepository extends JpaRepository&lt;Job, Long&gt; {
    List&lt;User&gt; findByLastName(String lastName, Pageable pageable);
}</code></pre>
<pre><code class="language-java">    @Controller
    public class UserController {
        @GetMapping(&quot;/users&quot;)
        public List&lt;UserResponse&gt; findByLastName(@RequestParam String lastName,Pageable pageable) {//생략}</code></pre>
<p>위와 같은 방식으로 Pageable 객체를 인수로 넘겨줌으로써 JpaRepository로 부터 원하는 Page만큼의 User목록을 반환받을 수있다</p>
<blockquote>
<p>GET /users?lastName=kim&amp;page=3&amp;size=10&amp;sort=id,DESC</p>
</blockquote>
<h2 id="적용">적용</h2>
<p>내 예전 코드(우웩)</p>
<pre><code class="language-java">    @GetMapping(&quot;/pubs/{department}&quot;)
    public PubResponseDto getPubsByDepartment(@PathVariable String department) {
        User user = authentiatedUserUtils.getCurrentUser();
        return pubService.getPubByDepartment(department,user);
    }</code></pre>
<p>적용</p>
<pre><code class="language-java">    @GetMapping(&quot;/pubs&quot;)
    public PubResponseDto getPubsByDepartment(@RequestParam String department,Pageable pageable) {
        User user = authentiatedUserUtils.getCurrentUser();
        return pubService.getPubByDepartment(department,user);
    }</code></pre>
<pre><code class="language-java">public interface PubRepository extends JpaRepository&lt;Pub,Long&gt; {
   List&lt;Pub&gt; findByDepartment(String department,Pageable);
}</code></pre>
<p>실제로 적용할때 이 블로그 포스팅 참고하자!
<a href="https://www.baeldung.com/spring-data-jpa-pagination-sorting">https://www.baeldung.com/spring-data-jpa-pagination-sorting</a></p>
<p><a href="https://jaime-note.tistory.com/61">https://jaime-note.tistory.com/61</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NGINX + docker-compose+ github actions로 배포하기  ]]></title>
            <link>https://velog.io/@almondbreez0_3/NGINX-docker-compose-github-actions%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-AWS-EC2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EC%97%90-docker-compose-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@almondbreez0_3/NGINX-docker-compose-github-actions%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-AWS-EC2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EC%97%90-docker-compose-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 03 Nov 2023 16:04:55 GMT</pubDate>
            <description><![CDATA[<ol>
<li>EC2 생성</li>
<li>탄력적 IP할당</li>
<li>RDS 연결 
(내가 배포 정리한 글 보면서)
4번부터 여기서 이어서 하기</li>
</ol>
<p>EC2 Docker 설치하기</p>
<pre><code>$ sudo apt-get update</code></pre><pre><code>$sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common</code></pre><pre><code>$curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -</code></pre><p>다음으로는 Docker Repository를 등록해보자. 이는 Docker 환경을 구축할 때 필수적인 절차이다.</p>
<pre><code>$ sudo add-apt-repository \
&quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable&quot;</code></pre><p>Repository 등록이 완료되었다면, apt-get 패키징 툴을 통해 도커를 설치한다.</p>
<pre><code>$ sudo apt-get update &amp;&amp; sudo apt-get install docker-ce docker-ce-cli containerd.io</code></pre><p>도커가 잘 설치되었는지 버전 확인을 해보자.</p>
<pre><code>$ docker -v</code></pre><p>EC2에 Docker-compose 설치하기</p>
<pre><code>sudo curl \
     -L &quot;https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)&quot; \
     -o /usr/local/bin/docker-compose</code></pre><p>실행 권한 적용</p>
<pre><code>sudo chmod +x /usr/local/bin/docker-compose</code></pre><p>설치 확인</p>
<pre><code>docker-compose --version</code></pre><ul>
<li>자바 설치하기</li>
</ul>
<p>무료 도메인 등록하기 위해 AWS를 이용하여 https를 이용하지 않기 위해 Cerbot을 통해 인증서 발급을 할 것이다.
원래 이거 할라 했는데 NO 필요 없을듯..</p>
<ol>
<li>certbot 설치하기<pre><code>sudo snap install certbot --classic</code></pre></li>
</ol>
<pre><code>certbot certonly --manual --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory -d {도메인} -d *.{도메인}</code></pre><pre><code>Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter &#39;c&#39; to cancel): {이메일 입력}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let&#39;s Encrypt project and the non-profit organization that
develops Certbot? We&#39;d like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Account registered.
Requesting a certificate for mytamra.ga and *.mytamra.ga

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:

_acme-challenge.{당신의 도메인}.

with the following value:

BCAPvx9hSb_gso0xrYT_pmuPxVLxS-dDFzi_Fq027rg

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue</code></pre><p>터미널에서 위와 같은 메시지를 확인할 수 있다.
여기서 바로 엔터를 입력하지 말고 읽어보면 DNS TXT 레코드를 생성하라는 안내가 나온다.</p>
<p>_acme-challenge.{당신의 도메인} 라는 이름으로 BCAPv~ 라는 값을 입력해주면 된다.</p>
<p>이를 위해 AWS Route53 메뉴로 이동하여 레코드 생성을 한다.
레코드 유형을 TXT로 바꾼다</p>
<p>유형 부분은 TXT로 변경해준 뒤 이름에는 _acme-challenge를 넣어주고, 값에는 콘솔창에 나와있는 문자를 넣어준다.</p>
<p>실제 레코드가 적용되는데는 몇 분이 걸리기 때문에 조금 기다려준 뒤에 콘솔로 돌아가 엔터를 눌러준다.</p>
<blockquote>
<p>다른 터미널 창을 키고 아래 명령어를 통해 적용이 되었는지 확인이 가능하다.
    nslookup -q=TXT _acme-challenge.{your domain}</p>
</blockquote>
<p>letsencrypt를 이용해 발급받은 인증서는 3개월마다 갱신을 해주어야 한다. 자동 갱신 설정도 있으니 참고하자. 같은 이름의 레코드 생성은 불가능하다. 아까 생성한 레코드에서 값만 추가해주면 된다.</p>
<p>성공적으로 인증서를 발급받았다.</p>
<p>어디에 인증서가 저장되었는지는 Certificate is saved at 에서 확인할 수 있고, 그에 대한 key는 Key is saved at 에 저장되어 있다.</p>
<p>/etc/letsencrypt/live/{당신의 도메인}/ 내부에 위치한다.</p>
<p>이제 발급받은 인증서를 활용해 nginx reverse proxy 설정을 해보자.</p>
<p>Nginx설정</p>
<ol>
<li>Nginx 설정파일 생성하기
ec2 인스턴스 ~/data/nginx 디렉토리 내부 app.config이름으로 설정 파일 생성</li>
</ol>
<pre><code>server {
        listen 80;
        server_name {your domain};

        server_tokens off;

        location / {
                return 301 https://$host$request_uri;
        }
}
server {
        listen 443 ssl;
        server_name {your domain};

        server_tokens off;

        location / {
                proxy_pass  http://{your domain}:8080/;
                proxy_set_header        Host    $http_host;
                proxy_set_header        X-Real-IP       $remote_addr;
                proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}</code></pre><p>EC2 내에서 {your domain}으로 들어오는 80 포트 요청을 읽어낸다. SSL 적용을 위하여 443(https)로 리다이렉트하였고, 8080(Spring)으로 reverse proxy** 하도록 구성했다.</p>
<p>EC2 내에서 {your domain}으로 들어오는 80 포트 요청을 읽어낸다. SSL 적용을 위하여 443(https)로 리다이렉트하였고, 8080(Spring)으로 reverse proxy** 하도록 구성했다.</p>
<p>1-2 /etc/letsencrypt/options-ssl-nginx.conf 파일 생성하기</p>
<pre><code># This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file. Contents are based on https://ssl-config.mozilla.org

ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

ssl_ciphers &quot;ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384&quot;;</code></pre><p>1-3. OpenSSL로 디피-헬만 파라미터 키 만들기</p>
<pre><code>cd etc/letsencrypt
sudo openssl dhparam -out ssl-dhparams.pem 4096</code></pre><ol start="2">
<li>docker-compose.yml 생성하기
2-1. docker-compose.yml 작성하기
EC2 인스턴스 루트 위치에 docker-compose.yml을 작성하여 nginx 컨테이너를 올려볼 것이다<pre><code>version: &#39;3&#39;
</code></pre></li>
</ol>
<p>services:
  nginx:
    image: nginx:1.15-alpine
    restart: unless-stopped
    volumes:
      - ./data/nginx:/etc/nginx/conf.d
      - /etc/letsencrypt:/etc/letsencrypt
      - /var/log/nginx/mytamla:/var/log/nginx/mytamla
    ports:
      - &quot;80:80&quot;
      - &quot;443:443&quot;
    command:
      &quot;/bin/sh -c &#39;while :; do sleep 6h &amp; wait $${!}; nginx -s reload; done &amp; nginx -g &quot;daemon off;&quot;&#39;&quot;</p>
<pre><code>2-2. docker-compose 동작시키기</code></pre><p>docker-compose up -d</p>
<pre><code>2-3. 정상적으로 동작하는지 확인하기</code></pre><p>docker ps -a</p>
<pre><code>
Github actions를 이용하여 스프링부트 자동 배포하기 위해
1. Dockerfile 작성하기</code></pre><p>FROM openjdk:11
ARG JAR_FILE=./build/libs/{빌드한 jar 파일명}.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;, &quot;--spring.profiles.active=prod&quot;]</p>
<pre><code>2. docker-compose 설정하기
2-1. docker-compose 설치하기
지난번 EC2 인스턴스에서 docker-compose를 설치했으니 스킵하도록 하겠다.

2-2. docker-compose.yml 작성하기
docker-compose 파일을 작성하여 EC2 서버에 배포할 것이다. 프로젝트 루트 경로에 docker-compose.yml 파일을 생성하자.

docker-compose.yml을 작성하여 각각 독립된 컨테이너의 실행 정의를 실시한다.</code></pre><p>version: &quot;3&quot; # 버전 지정</p>
<p>services: # 컨테이너 설정
  nginx:
    image: nginx:1.15-alpine
    restart: unless-stopped
    volumes:
      - ./data/nginx:/etc/nginx/conf.d
      - /etc/letsencrypt:/etc/letsencrypt
      - /var/log/nginx/mytamla:/var/log/nginx/mytamla
    ports:
      - &quot;80:80&quot;
      - &quot;443:443&quot;
    command:
      &quot;/bin/sh -c &#39;while :; do sleep 6h &amp; wait $${!}; nginx -s reload; done &amp; nginx -g &quot;daemon off;&quot;&#39;&quot;
  redis:
    image: redis
    container_name: bapmate-redis-container
    ports:
      - &quot;6379:6379&quot;
    networks: 
      - bapmate-net
  database: 
    container_name: mysql # 컨테이너 이름
    image: mysql/mysql-server:latest # 컨테이너에서 사용하는 base image 지정
    environment: # 컨테이너 안의 환경변수 설정
      MYSQL_DATABASE: {database name}
      MYSQL_USER: {database user}
      MYSQL_PASSWORD: {database pwd}
      MYSQL_ROOT_HOST: &#39;%&#39;
      MYSQL_ROOT_PASSWORD: rootpwd
    command: # 명령어 설정
      - --default-authentication-plugin=mysql_native_password
    ports: # 접근 포트 설정 
      - 3305:3306 # Host:Container
    networks:
      - db_network
    restart: always  # 컨테이너 실행 시 재시작
  bapmate:
    build: .
    expose:
      - 8080
    depends_on:
      - database
      - redis</p>
<p>networks: # 커스텀 네트워크 추가
  db_network: bapmate-net # 네트워크 이름
    driver: bridge</p>
<pre><code>
sudo docker network inspect 만든네트워크이름
-&gt;네트워크 생성됬는지 확인


3. Github Action CI/CD 작성하기
3-1. CI/CD

&gt; CI(Continuous Integration)는 지속적 통합을 나타내는 용어이다.

어플리케이션의 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트되어 공유 레포지토리에 통합되는 것을 의미한다.

클래스와 기능에서부터 전체 애플리케이션을 구성하는 서로 다른 모듈에 이르기까지 모든 것에 대한 테스트를 수행할 수 있으며, 코드를 병합하는 과정에서 충돌이 생긴다면 CI를 통해 버그를 수정할 수 있다.

이로 인해 개발하는 코드의 품질을 좀 더 향상시킬 수 있으며, 새로운 업데이트의 검증 및 릴리즈의 시간을 단축시킬 수 있다.

CD(Continuous Deliver, Countinuous Deplotment)는 지속적인 배포를 나타내는 용어이다.
(* 보통은 전자인 지속적 제공의 의미가 강하다.)


* Github Actions
Github Actions는 소프트웨어 개발 라이프사이클 안에서 PR, push 등의 이벤트 발생에 따라 자동화된 작업을 진행할 수 있게 해주는 기능이다.
CI/CD나 Testing, Cron Job 등 작업을 수행할 수 있다.

CI</code></pre><h1 id="workflow-이름은-구별이-가능할-정도로-자유롭게-적어주어도-된다">Workflow 이름은 구별이 가능할 정도로 자유롭게 적어주어도 된다.</h1>
<h1 id="필수-옵션은-아니다">필수 옵션은 아니다.</h1>
<p>name: Java CI with Gradle</p>
<h1 id="main-브랜치에-pr-이벤트가-발생하면-workflow가-실행된다">main 브랜치에 PR 이벤트가 발생하면 Workflow가 실행된다.</h1>
<h1 id="브랜치-구분이-없으면-on-pull_request로-해주어도-된다">브랜치 구분이 없으면 on: [pull_request]로 해주어도 된다.</h1>
<p>on:
  pull_request:
    branches: [ &quot;main&quot; ]</p>
<h1 id="테스트-결과-작성을-위해-쓰기권한-추가">테스트 결과 작성을 위해 쓰기권한 추가</h1>
<p>permissions: write-all</p>
<h1 id="해당-workflow의-job-목록">해당 Workflow의 Job 목록</h1>
<p>jobs:
    # Job 이름으로, build 라는 이름으로 Job이 표시된다.
  build:
      # Runner가 실행되는 환경을 정의
    runs-on: ubuntu-latest</p>
<pre><code># build Job 내의 step 목록
  # uses 키워드를 통해 Action을 불러올 수 있다.
  # 해당 레포지토리로 check-out하여 레포지토리에 접근할 수 있는 Acion 불러오기
- uses: actions/checkout@v3
# 여기서 실행되는 커맨드에 대한 설명으로, Workflow에 표시된다. 
# jdk 세팅

steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
  uses: actions/setup-java@v3
  with:
    java-version: &#39;17&#39;
    distribution: &#39;temurin&#39;


  # gradle 캐싱
- name: Gradle Caching
  uses: actions/cache@v3
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles(&#39;**/*.gradle*&#39;, &#39;**/gradle-wrapper.properties&#39;) }}
    restore-keys: |
      ${{ runner.os }}-gradle-

### CI
#gradlew 권한 추가
- name: Grant Execute Permission For Gradlew
  run: chmod +x gradlew

#test를 제외한 프로젝트 빌드
- name: Build With Gradle
  run: ./gradlew build -x test

#test를 위한 mysql설정
- name: Start MySQL
  uses: samin/mysql-action@v1.3
  with:
    host port: 3305
    container port: 3305
    mysql database: &#39;{database name}&#39;
    mysql user: &#39;{database user}&#39;
    mysql password: &#39;{database pwd}&#39;

#테스트를 위한 test properties 설정
- name: Make application-test.properties
  run: |
    cd ./src/test/resources
    touch ./application.properties
    echo &quot;${{ secrets.PROPERTIES_TEST }}&quot; &gt; ./application.properties
  shell: bash

#test코드 빌드
- name: Build With Test
  run: ./gradlew test

#테스트 결과 파일 생성
- name: Publish Unit Test Results
  uses: EnricoMi/publish-unit-test-result-action@v1
  if: ${{ always() }}
  with:
    files: build/test-results/**/*.xml</code></pre><pre><code>
</code></pre><p>name: CD</p>
<p>on:
  push: #해당 브랜치에 push(merge) 했을 때
    branches:
      - main</p>
<p>permissions: write-all #테스트 결과 작성을 위해 쓰기권한 추가</p>
<p>jobs:
  build:
    runs-on: ubuntu-latest</p>
<pre><code>steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
  uses: actions/setup-java@v3
  with:
    java-version: &#39;17&#39;
    distribution: &#39;temurin&#39;


  #gradle 캐싱
  - name: Gradle Caching
    uses: actions/cache@v3
    with:
      path: |
        ~/.gradle/caches
        ~/.gradle/wrapper
      key: ${{ runner.os }}-gradle-${{ hashFiles(&#39;**/*.gradle*&#39;, &#39;**/gradle-wrapper.properties&#39;) }}
      restore-keys: |
        ${{ runner.os }}-gradle-
  ### CD
  #배포를 위한 prod properties 설정
  - name: Make application-prod.properties
    run: |
      cd ./src/main/resources
      touch ./application-prod.properties
      echo &quot;${{ secrets.PROPERTIES_PROD }}&quot; &gt; ./application-prod.properties
    shell: bash

  #test를 제외한 프로젝트 빌드
  - name: Build With Gradle
    run: ./gradlew build -x test

  #도커 빌드 &amp; 이미지 push
  - name: Docker build &amp; Push
    run: |
      docker login -u ${{ secrets.DOCKER_ID }} -p ${{ secrets.DOCKER_PASSWORD }}
      docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/goorm .
      docker push ${{ secrets.DOCKER_REPO }}/bapmate
  #docker-compose 파일을 ec2 서버에 배포
  - name: Deploy to Prod
    uses: appleboy/ssh-action@master
    id: deploy-prod
    with:
      host: ${{ secrets.EC2_HOST }}
      username: ${{ secrets.EC2_USERNAME }}
      key: ${{ secrets.EC2_PRIVATE_KEY }}
      envs: GITHUB_SHA
      script: |
         docker stop bapmate
         docker rm bapmate
         sudo docker pull ${{ secrets.DOCKER_REPO }}/bapmate
         docker run -d --name bapmate -p 8080:8080 ${{ secrets.DOCKER_REPO }}/goorm
         docker rmi -f $(docker images -f &quot;dangling=true&quot; -q)
        docker-compose up -d
        docker image prune -f</code></pre><p>```</p>
<p>3-5. Secrets 등록하기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UMC 서버 워크북 정리]]></title>
            <link>https://velog.io/@almondbreez0_3/UMC-%EC%84%9C%EB%B2%84-%EC%9B%8C%ED%81%AC%EB%B6%81-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@almondbreez0_3/UMC-%EC%84%9C%EB%B2%84-%EC%9B%8C%ED%81%AC%EB%B6%81-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 27 Sep 2023 11:21:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>3학년 2학기 때 운이 좋게 서버 운영진으로써 스터디를 진행하게 되었다.</p>
</blockquote>
<blockquote>
<p>매 주 스터디마다 많은 양의 서버 워크북 중 뭐를 보는 것을 추천하는지나 꼭 정리하고 넘어갈 점들을 정리해보고자 한다</p>
</blockquote>
<p>우리 대학교 기준 3학년 2학기에 운영체제라는 과목을 듣는데, 이를 수강하기 이전에 서버 공부를 시작하면 많이 생소하고 어려울 수 있다. 완벽하게 이해까지는 못해도 워크북에 걸려 있는 정리 블로그를 간단하게 읽어보고 시작해보자.</p>
<h3 id="ip주소와-포트-번호">IP주소와 포트 번호</h3>
<p><strong>IP 주소</strong>
네트워크 상에서 유일하게 식별이 가능한 값
<strong>포트 번호</strong>
컴퓨터 프로세스간의 식별 값</p>
<h3 id="tcp">TCP</h3>
<p> Transmission Control Protocol의 약자이고 이 프로토콜은 모두 패킷을 한 컴퓨터에서 다른 컴퓨터로 전달해주는 IP 프로토콜을 기반으로 구현되어 있고 연결 지향적인 특징을 갖고 있다.</p>
<h3 id="system-call">System Call</h3>
<p>운영 체제의 커널이 제공하는 서비스에 대해 응용 프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스이다</p>
<p>사용자 프로그램이 디스크 파일에 접근하거나 화면에 결과를 출력하는 등의 작업이 필요한 경우, 즉 사용자 프로그램이 특권 명령의 수행을 필요로 하는 경우, 운영체제에게 특권 명령의 대행을 요청하는 것이 시스템 콜</p>
<h3 id="하드웨어-인터럽트">하드웨어 인터럽트</h3>
<blockquote>
<p>인터럽트란? 
CPU가 프로그램을 실행하고 있을 때, 입출력 하드웨어 등의 장치나 예외상황이 발생하여 처리가 필요할 경우에 마이크로프로세서에게 알려 처리할 수 있도록 하는 것</p>
</blockquote>
<p>하드웨어 인터럽트는 하드웨어가 발생시키는 인터럽트로 CPU가 아닌 다른 하드웨어 장치가 cpu에 어떤 사실을 알려주거나 cpu서비스를 요청해야 할 경우 발생
소프트웨어 인터럽트랑 구별을 하자!!</p>
<blockquote>
<p>소프트웨어 인터럽트란?
소프트웨어(사용자 프로그램)가 발생시키는 인터럽트이다. 종류는 예외 상황이나 system call등이 존재</p>
</blockquote>
<h3 id="리눅스의-파일과-파일-디스크립터">리눅스의 파일과 파일 디스크립터</h3>
<p>리눅스에서 모든 요소를 파일로 취급
파일 디스크립터는 socket()의 리턴 값</p>
<h3 id="socket시스템-콜">socket()시스템 콜</h3>
<p>소켓을 만드는 시스템 콜</p>
<h3 id="bind-시스템-콜">bind() 시스템 콜</h3>
<p>생성한 소켓에 실제 IP주소와 포트번호를 부여하는 시스템 콜
OS에게 어떤 소켓에 IP주소와 포트번호를 부여할지 알려주기 위해 파라미터에 소켓의 파일 디스크립터 포함
참고로 클라이언트는 통신 시 포트 번호가 자동으로 부여되기에 bind 시스템 콜은 서버에서만 사용</p>
<h3 id="listen-시스템-콜">listen() 시스템 콜</h3>
<p>연결지향인 TCP에서만 사용
파라미터로 파일 디스크립터(소켓)를 클라이언트의 연결 요청을 받아들임
최대로 받아주는 크기를 backlog로 설정</p>
<h3 id="accept-시스템-콜">accept() 시스템 콜</h3>
<p>accept 시스템 콜은 backlog queue에서 syn을 보내 대기 중인 요청을 큐로 하나씩 연결에 대한 수립을 해줌</p>
<h3 id="멀티-프로세스">멀티 프로세스</h3>
<p>accept System call 이후 이루어진다.
프로세스란 프로그램이 메모리에 올라와 cpu를 할당받고 프로그램이 실행되고 있는 상태. 동적인 개념
<img src="https://velog.velcdn.com/images/almondbreez0_3/post/9df0ef71-2cf5-4a6b-ab44-687bb118068d/image.png" alt=""></p>
<p>멀티 프로세스란?</p>
<ul>
<li>여러 개의 프로세스가 서로 협력적으로 일을 처리하는 것</li>
<li>여러 개의 프로세스가 작업을 병렬 처리하는 것</li>
<li>각 프로세스 간 메모리 구분이 필요하거나 독립된 주소 공간을 가져야 할 때 사용</li>
</ul>
<p>장점</p>
<ul>
<li>하나의 프로세스가 죽어도 문제가 확산 되지 않는다</li>
<li>독립된 구조이기 때문에 안정성이 높다
단점</li>
<li>멀티 스레드보다 많은 메모리 공간과 CPU시간을 차지한다</li>
<li>주소공간의 공유가 잦을 경우 오버헤드가 발생하여 성능 저하가 발생할 수 있다</li>
</ul>
<h3 id="병렬-처리">병렬 처리</h3>
<ul>
<li>병렬 처리란 여러개의 프로세서를 통해 하나의 프로그램을 처리하는 것</li>
<li>병렬 처리를 수행 시 처리부하를 분담하게 되어 처리 속도의 향상을 기대할 수 있다</li>
<li>병렬 처리는 단일 프로세스가 아닌 다중 프로세서로 작업을 처리한다</li>
<li>프로그램 코드 상 병렬 처리는 멀티 스레드환경에서 task를 분산해서 처리한다는 의미이다</li>
</ul>
<blockquote>
<p>후기: 챌린저 입장으로 정리를 하지 않고 중요한 것 위주로 정리를 하다 보니 2학년 데이터 통신 때 배운 내용들과 4기 UMC 서버 워크북을 했던 것들에 대한 복습이 됬던 것 같다. 또한, 운영체제 과목을 현재 수강중인데 시스템 콜과 멀티 프로세스 등의 용어들이 나와 익숙했다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링부트와 AWS로 혼자 구현하는 웹서비스-등록/수정/조회 API 만들기]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%99%80-AWS%EB%A1%9C-%ED%98%BC%EC%9E%90-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%93%B1%EB%A1%9D%EC%88%98%EC%A0%95%EC%A1%B0%ED%9A%8C-API-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%99%80-AWS%EB%A1%9C-%ED%98%BC%EC%9E%90-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%93%B1%EB%A1%9D%EC%88%98%EC%A0%95%EC%A1%B0%ED%9A%8C-API-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 13 Sep 2023 05:26:13 GMT</pubDate>
            <description><![CDATA[<p>API를 만들기 위해서는 총 3개의 클래스가 필요하다</p>
<ul>
<li>Request 데이터를 받을 Dto</li>
<li>API 요청을 받을 Controller</li>
<li>트랜잭션, 도메인 기능 간의 순서를 보장하는 Service </li>
</ul>
<p>Service만 비즈니스 로직을 처리해야 한다는 것이 아니라 Service는 트랜잭션,도메인 간 순서 보장의 역할만 한다</p>
<h2 id="spring-웹-계층">Spring 웹 계층</h2>
<ul>
<li>Web Layer<ul>
<li>흔히 사용하는 컨트롤러와 JSP/Freemaker등의 뷰 템플릿 영역</li>
<li>필터, 인터셉터, 컨트롤러 어드바이스 등 외부 요청과 응답에 대한 전반적인 영역을 이야기한다. </li>
</ul>
</li>
<li>Service Layer<ul>
<li>@Service에 사용되는 서비스 영역이다</li>
<li>일반적으로 Controller와 Dao의 중간 영역에서 사용된다</li>
<li>@Transactional이 사용되어야 하는 영역이기도 한다</li>
</ul>
</li>
<li>Repository Layer<ul>
<li>Database와 같이 데이터 저장소에 접근하는 영역이다</li>
</ul>
</li>
<li>Dtos<ul>
<li>Dto는 계층 간에 데이터 교환을 위한 객체를 이야기하며 Dtos는 이들의 영역을 얘기한다</li>
</ul>
</li>
<li>Domain Model<ul>
<li>도메인이라 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고 공유할 수 있도록 단순화 시킨 것을 도메인 모델이라고 한다</li>
<li>이를테면 택시 앱이라고 하면 배차, 탑승, 요금 등이 모두 도메인이 될 수 있다.</li>
<li>@Entity를 사용해보신 분들은 @Entity가 사용된 영역 역시 도메인 모델이라고 이해해주시면 된다.</li>
<li>다만 무조건 데이터베이스의 테이블과 관계가 있어야만 하는 것은 아니다</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    @Transactional
    public Long save(PostsSaveRequestDto requestDto){
        return postsRepository.save(requestDto.toEntity()).getId();
    }
}</code></pre>
<pre><code class="language-java">@RequiredArgsConstructor
@RestController
public class PostsApiController {
    private final PostsService postsService;

    @PostMapping(&quot;/api/v1/posts&quot;)
    public Long save(@RequestBody PostsSaveRequestDto requestDto){
        return postsService.save(requestDto);
    }
}</code></pre>
<blockquote>
<p>생성자로 Bean객체를 받도록 하면 @Autowired와 동일한 효과를 볼 수 있다. @RequiredArgsConstructor에서 final이 선언된 모든 필드를 인자값으로 하는 생성자를 대신 생성해준다.</p>
</blockquote>
<blockquote>
<p>@NoArgsConstructor은 해당 클래스에 매개변수 없는 기본 생성자를 생성합니다.</p>
</blockquote>
<blockquote>
<p>@AllArgsConstructor은 이 생성자는 객체를 생성할 때 모든 필드 값을 인자로 받아서 객체를 초기화합니다. 이를 통해 객체를 생성할 때 모든 필드 값을 설정할 수 있습니다.</p>
</blockquote>
<pre><code class="language-java">@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private PostsRepository postsRepository;

    @AfterEach
    public void tearDown() throws Exception{
        postsRepository.deleteAll();
    }

    @Test
    public void Posts_등록된다() throws Exception {
        //given
        String title = &quot;title&quot;;
        String content = &quot;content&quot;;
        PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                .title(title)
                .content(content)
                .author(&quot;author&quot;)
                .build();

        String url = &quot;http://localhost:&quot; + port +&quot;/api/v1/posts&quot;;

        //when
        ResponseEntity&lt;Long&gt; responseEntity = restTemplate.postForEntity(url,requestDto,Long.class);

        //then
        org.assertj.core.api.Assertions.assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        org.assertj.core.api.Assertions.assertThat(responseEntity.getBody()).isGreaterThan(0L);


        List&lt;Posts&gt; all = postsRepository.findAll();
        org.assertj.core.api.Assertions.assertThat(all.get(0).getTitle()).isEqualTo(title);
        org.assertj.core.api.Assertions.assertThat(all.get(0).getContent()).isEqualTo(content);

    }
}</code></pre>
<p>이 책에 있는 Dto를 따라 작성하면서 항상 Response에 대한 Dto를 제대로 안짜고 있었다는 것을 알게되었다</p>
<pre><code class="language-java">@Getter
public class PostsResponseDto {
    private Long id;
    private String title;
    private String content;
    private String author;

    public PostsResponseDto(Posts entity){
        this.id=entity.getId();
        this.title = entity.getTitle();
        this.content = entity.getContent();
        this.author = entity.getAuthor();
    }
}
</code></pre>
<p>이 PostsResponseDto는 Entity의 필드 중 일부만 사용하므로 생성자로 Entity를 받아 필드에 값을 넣습니다</p>
<pre><code class="language-java">    @Transactional
    public Long update(Long id, PostsUpdateRequestDto requestDto){
        Posts posts = postsRepository.findById(id).orElseThrow(() -&gt; new IllegalArgumentException(&quot;해당 게시글이 없습니다. id=&quot;+ id));
        posts.update(requestDto.getTitle(), requestDto.getContent());

        return id;
    }

    public PostsResponseDto findById (Long id){
        Posts entity = postsRepository.findById(id).orElseThrow(() -&gt; new IllegalArgumentException(&quot;해당 게시글이 없습니다. id=&quot;+ id));
        return new PostsResponseDto(entity);
    }</code></pre>
<p>update Service에는 데이터베이스에 쿼리를 날리는 부분이 없다. 이게 가능한 이유는 JPA의 영속성 컨텍스트 때문이다.</p>
<blockquote>
<p>영속성 컨텍스트는 엔티티를 영구 저장하는 환경이다. </p>
</blockquote>
<p>결과:</p>
<p><img src="https://velog.velcdn.com/images/almondbreez0_3/post/c6f80146-3255-4bc4-82f4-256415b5da16/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/almondbreez0_3/post/784edb6b-316b-4477-a42b-2822ae6a6d88/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링부트와 AWS로 혼자 구현하는 웹서비스-JPA를 사용하는 이유]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%99%80-AWS%EB%A1%9C-%ED%98%BC%EC%9E%90-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4-JPA%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%99%80-AWS%EB%A1%9C-%ED%98%BC%EC%9E%90-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4-JPA%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Mon, 11 Sep 2023 13:21:32 GMT</pubDate>
            <description><![CDATA[<h2 id="게시판">게시판</h2>
<ul>
<li>게시글 조회</li>
<li>게시글 등록</li>
<li>게시글 수정</li>
<li>게시글 삭제</li>
</ul>
<h2 id="회원">회원</h2>
<ul>
<li>구글/ 네이버 로그인</li>
<li>로그인한 사용자 글 작성 권한</li>
<li>본인 작성 글에 대한 권한 관리</li>
</ul>
<h2 id="프로젝트-시작">프로젝트 시작</h2>
<p>domain패키지는 도메인을 담을 패키지</p>
<pre><code class="language-java">@NoArgsConstructor
@Getter
@Entity
public class Posts {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 500,nullable = false)
    private String title;

    @Column(columnDefinition = &quot;TEXT&quot;, nullable = false)
    private String content;

    private String author;

    @Builder
    public Posts(String title, String content, String author){
        this.title=title;
        this.content=content;
        this.author =author;
    }
}
</code></pre>
<blockquote>
<p>@Entity </p>
</blockquote>
<ul>
<li>테이블과 링크될 클래스임을 나타낸다</li>
<li>기본값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍으로 테이블 이름을 매칭한다</li>
</ul>
<blockquote>
<p>@GeneratedValue </p>
</blockquote>
<ul>
<li>PK의 생성 규칙을 나타낸다</li>
</ul>
<blockquote>
<p>@Column</p>
</blockquote>
<ul>
<li>테이블의 칼럼을 나타내며 굳이 선언하지 않더라도 해당 클래스의 필드는 모두 칼럼이 된다</li>
<li>사용하는 이유는, 기본값 외에 추가로 변경이 팔요한 옵션이 있으며 사용한다</li>
<li>문자열의 경우 VARCHAR 기본값이 255인데 500으로 늘리고 싶거나 타입을 TEXT로 변경하고 싶거나(ex.content)등의 경우에 사용된다</li>
</ul>
<blockquote>
<p>@NoArgsConstructor</p>
</blockquote>
<ul>
<li>기본 생성자 자동 추가</li>
<li>public Posts(){}와 같은 효과</li>
</ul>
<blockquote>
<p>@Builder</p>
</blockquote>
<ul>
<li>해당 클래스의 빌더 패턴 클래스를 생성</li>
<li>생성자 상단에 선언 시 생성자에 포함된 필드</li>
</ul>
<p>💡Entity클래스에선는 절대 Setter메소드를 만들지 않는다</p>
<p>Builder를 쓰면 장점이 있다
예를 들어 다음과 같은 생성자가 있다면 new Example(b,a)처럼 a와 b의 위치를 변경해서 코드를 실행하기 전까지는 문제를 찾을 수 없다.</p>
<pre><code class="language-java">public Example(String a, String b){
    this.a = a;
    this.b = b;
}</code></pre>
<p>하지만 Builder 어느 필드에 어떤 값을 채워야 할지 명확하게 인지할 수 있다.</p>
<pre><code>Example.builder()
    .a(a)
    .b(b)
    .build();</code></pre><h2 id="jpa-repository">Jpa Repository</h2>
<pre><code class="language-java">import org.springframework.data.jpa.repository.JpaRepository;

public interface PostsRepository extends JpaRepository&lt;Posts,Long&gt; {

}</code></pre>
<p>JpaRepository&lt;Entity,PK타입&gt;를 상속하면 기본적인 CRUD메소드 </p>
<ul>
<li>@Repository를 추가할 필요도 없습니다</li>
<li>Entity클래스와 기본 Entity Repository는 함께 위치해야 한다</li>
</ul>
<h2 id="test">Test</h2>
<pre><code class="language-java">
@ExtendWith(SpringExtension.class)
@SpringBootTest
class PostsRepositoryTest {
    @Autowired
    PostsRepository postsRepository;

    @After
    public void cleanup(){
        postsRepository.deleteAll();
    }

    @Test
    public void 게시물저장_불러오기(){
        //given
        String title = &quot;테스트 게시물&quot;;
        String content = &quot;테스트 본문&quot;;

        postsRepository.save(Posts.builder()
                        .title(title)
                        .content(content)
                        .author(&quot;bee1162@naver.com&quot;)
                .build());

        //when
        List&lt;Posts&gt; postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);
        org.assertj.core.api.Assertions.assertThat(posts.getTitle()).isEqualTo(title);
        org.assertj.core.api.Assertions.assertThat(posts.getContent()).isEqualTo(content);
    }


}</code></pre>
<blockquote>
<p>spring.jpa.show_sql=true</p>
</blockquote>
<p>application.properties에 추가하면 이런식으로 로그에 찍힌다</p>
<p><img src="https://velog.velcdn.com/images/almondbreez0_3/post/c5fee63c-59ca-4a94-93ff-6d3df663d921/image.png" alt=""></p>
<p>h2에서 MySQL로 바꾸면</p>
<blockquote>
<p>spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링부트와 AWS로 혼자 구현하는 웹서비스-JPA를 사용하는 이유]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%99%80-AWS%EB%A1%9C-%ED%98%BC%EC%9E%90-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4-1</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%99%80-AWS%EB%A1%9C-%ED%98%BC%EC%9E%90-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4-1</guid>
            <pubDate>Mon, 11 Sep 2023 10:45:09 GMT</pubDate>
            <description><![CDATA[<p>💡객체를 관계형 데이터베이스에서 관리하는 것이 중요하다</p>
<blockquote>
<p>스프링과 관계형 데이터 베이스를 사용할 때 SQL을 사용해야 하는 이유는 그 둘간의 패러다임 불일치 문제 때문에 사용한다.
관계형 데이터 베이스는 어떻게 데이터를 저장할지에 초점이 맞춰진 반면 객체지향 프로그래밍 언어는 메시지를 기반으로 기능과 속성을 한 곳에서 관리하는 기술이다</p>
</blockquote>
<p>💡객체 지향 프로그래밍에서 부모가 되는 객체를 가져오려면</p>
<pre><code>User user = findUser();
Group group = user.getGroup();</code></pre><p>💡관계형 데이터베이스에서 부모가 되는 객체를 가져오려면</p>
<pre><code>User user =userDao.findUser();
Group group = groupDao.findGroup(user.getGroupId());</code></pre><p>이렇게 하게 되면 User따로, Group따로 조회하게 된다. 
이렇게 되면 User와 Group이 어떤 관계인지 알 수 없게된다.</p>
<p>💡서로 지향하는 바가 다른 객체지향 프로그래밍 언어와 DBMS를 연결해주기 위해 JPA가 등장하게 되었다.</p>
<h3 id="spring-data-jpa"><strong>Spring data JPA</strong></h3>
<p>Spring data JPA라는 모듈을 이용하여 JPA기술을 다룬다</p>
<blockquote>
<p>JPA &lt;- Hibernate &lt;- Spring data JPA</p>
</blockquote>
<p>장점</p>
<ul>
<li>구현체 교체의 용이성</li>
<li>저장소 교체의 용이성</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링부트와 AWS로 혼자 구현하는 웹서비스-테스트코드 작성]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%99%80-AWS%EB%A1%9C-%ED%98%BC%EC%9E%90-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8%EC%99%80-AWS%EB%A1%9C-%ED%98%BC%EC%9E%90-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Sat, 09 Sep 2023 12:04:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>클러치 프로젝트에서 테스트를 하지 않고 서비스를 구현하였는데 리팩토링에 들어가면서 단위 테스트를 도입해보려고 한다</p>
</blockquote>
<h3 id="테스트-코드란">테스트 코드란?</h3>
<p>TDD는 테스트가 주도하는 개발이고 테스트 코드를 먼저 작성하는 것부터 시작한다</p>
<ul>
<li>항상 실패하는 테스트를 먼저 작성(RED)</li>
<li>테스트가 통과하는 프로덕션 코드를 작성하고(GREEN)</li>
<li>테스트가 통과하면 프로덕션 코드를 리팩토링(REFACTOR)</li>
</ul>
<p>단위테스트는 TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 이야기한다. TDD와 달리 테스트 코드를 꼭 먼저 작성 해야하는 것이 아니라 순수하게 테스트 코드만 작성하는 것을 이야기한다. </p>
<h2 id="단위테스트의-장점">단위테스트의 장점</h2>
<ul>
<li>단위 테스트는 개발단계 초기에 문제를 발견하게 도와준다</li>
<li>단위 테스트는 개발자가 나중에 코드를 리팩토링하거나 라이브러리 업그레이드 등에서 기존 기능이 올바르게 작동하는지 확인할 수 있다.</li>
<li>단위 테스트는 기능에 대한 불확실성을 감소시킬 수 있다</li>
</ul>
<h2 id="저자가-제시한-개발-방식">저자가 제시한 개발 방식</h2>
<p>테스트 코드를 작성하지 않았을 때</p>
<pre><code class="language-markdown">1. 코드를 작성하고
2. 프로그램을 실행한 뒤
3. Postman과 같은 API 테스트 도구로 HTTP요청하고
4. 요청 결과를 System.out.println()으로 눈으로 검증하고
5. 결과가 다르면 다시 프로그램을 중지하고 코드를 수정한다
</code></pre>
<p>이 방법은 현재 내가 개발을 하면서 택하고 있는 방법이고 항상 번거롭다고 느꼈었다. 이번 학습을 통해 테스트 코드를 작성하면서 효율적인 개발 환경을 구축해보고 싶다.</p>
<h2 id="실습">실습</h2>
<blockquote>
<p>일반적으로 패키지 명은 웹사이트 주소의 역순</p>
</blockquote>
<blockquote>
<p>@SpringBootApplication으로 인해 스프링 부트의 자동 설정, 스프링 빈 읽기와 생성을 모두 자동으로 설정. 특히 이 어노테이션이 있는 위치부터 읽어가기 때문에 이 클래스는 항상 프로젝트의 최상단에 위치해야 한다</p>
</blockquote>
<blockquote>
<p>내장 WAS는 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS를 실행하는 것을 이야기한다 -&gt; 항상 서버에 톰캣을 설치할 필요 없이 Jar파일로 실행하면 된다</p>
</blockquote>
<p>스프링 부트에서는 내장 WAS를 사용하는 것을 권장하고 있다. 이유는 &quot;언제 어디서나 같은 환경에서 스프링 부트를 배포&quot;할 수 있기 때문이다.</p>
<pre><code class="language-java">
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping(&quot;/hello&quot;)
    public String hello(){
        return &quot;hello&quot;;
    }
}</code></pre>
<blockquote>
<p>@RestController</p>
</blockquote>
<ul>
<li>컨트롤러에서 JSON을 반환하는 컨트롤러를 만들어 준다</li>
<li>예전에는 @ResponseBody를 각 메소드마다 선언했던 것을 한번에 사용할 수 있게 해준다고 생각하면 된다</li>
</ul>
<p>위 코드가 제대로 작동하는지 테스트를 해보았다.
일반적으로 테스트 클래스는 대상 클래스 이름에 Test를 붙인다</p>
<ol>
<li><p>클래스명에 커서를 대고 ctrl + enter를 누른다</p>
</li>
<li><p>HelloControllerTest 클래스를 생성한다 (나는 JUnit5사용)</p>
<pre><code class="language-java">@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = HelloController.class)
public class HelloControllerTest {
 @Autowired
 private MockMvc mvc;

 @Test
 public void hello가_리턴된다() throws Exception{
     String hello = &quot;hello&quot;;
     mvc.perform(get(&quot;/hello&quot;))
             .andExpect(status().isOk())
             .andExpect(content().string(hello));
 }
}</code></pre>
</li>
</ol>
<blockquote>
<p>@ExtendWith(SpringExtension.class): 스프링 부트 테스트와 JUnit 사이에 연결자 역할을 한다</p>
</blockquote>
<blockquote>
<p>WebMvcTest:여러 스프링 테스트 어노테이션 중 Web에 집중할 수 있는 어노테이션이다.</p>
</blockquote>
<blockquote>
<p>@Autowired:스프링이 관리하는 Bean을 주입받는다</p>
</blockquote>
<blockquote>
<p>private MockMvc mvc: 웹 API를 테스트 할 때 사용하고 Spring MVC 테스트의 시작점이다.</p>
</blockquote>
<blockquote>
<p> mvc.perform(get(&quot;/hello&quot;)): MockMVC를 통ㅇ해 /hello주소로 HTTP GET요청을 한다</p>
</blockquote>
<blockquote>
<p>.andExpect(status().isOk()):mvc.perform의 결과를 검증한다. 응답 본문의 내용을 검증한다. Controller에서 &quot;hello&quot;를 리턴하기 때문에 이 값이 맞는지 검증한다</p>
</blockquote>
<p>Test해보면 이런 화면을 볼 수 있다
<img src="https://velog.velcdn.com/images/almondbreez0_3/post/5490623b-7d38-4138-8c37-480e9bb9fbe4/image.png" alt=""></p>
<p>Test가 성공했으니 Build를 해보면 localhost:8080에서 성공적으로 Build가 된것을 볼 수 있다</p>
<p><img src="https://velog.velcdn.com/images/almondbreez0_3/post/06535723-34a0-431f-a8d3-11043935df40/image.png" alt=""></p>
<h2 id="롬복-적용하기">롬복 적용하기</h2>
<pre><code class="language-java">@Getter
@RequiredArgsConstructor
public class HelloResponseDto {
    private final String name;
    private final int amount;
}</code></pre>
<blockquote>
<p>@Getter: 선언된 모든 필드의 get메서드를 생성해준다
@RequiredArgsConstructor:선언된 모든 final 필드가 포함된 생성자를 생성해준다. final이 없는 필드는 생성자에 포함되지 않는다.</p>
</blockquote>
<pre><code class="language-java">public class HelloResponseDtoTest {
    @Test
    public void 롬복_기능_테스트(){
        String name = &quot;test&quot;;
        int amount = 1000;

        //when
        HelloResponseDto dto = new HelloResponseDto(name,amount);

        //then
        org.assertj.core.api.Assertions.assertThat(dto.getName()).isEqualTo(name);
        org.assertj.core.api.Assertions.assertThat(dto.getAmount()).isEqualTo(amount);
    }
}</code></pre>
<blockquote>
<p>assertThat: assertj라는 테스트ㅡ 검증 라이브러리의 검증 메소드이다. 검증하고 싶은 대상을 메소드 인자로 받는다. 메소드 체이닝이 지원되어 isEqualTo와 같이 메소드를 이어서 사용할 수 있다.</p>
</blockquote>
<blockquote>
<p>isEqualTo: assertj의 동등 비교 메소드이다. assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공</p>
</blockquote>
<p>여기서 Junit의 기본 assertThat을 안쓰는 이유는 assertj는 coreMatchers와 달리 추가적으로 라이브러리가 필요하지 않고 자동완성이 좀 더 확실하게 지원된다</p>
<pre><code class="language-java">    @Test
    public void helloDto가_리턴된다() throws Exception{
        String name =&quot;hello&quot;;
        int amount = 1000;

        mvc.perform(get(&quot;/hello/dto&quot;)
                .param(&quot;name&quot;,name)
                .param(&quot;amount&quot;,String.valueOf(amount)))
                .andExpect(status().isOk())
                .andExpect(jsonPath(&quot;$.name&quot;,is(name)))
                .andExp![](https://velog.velcdn.com/images/almondbreez0_3/post/f1f363b7-dec5-4fb7-bc37-6a6521a3ad3c/image.jpg)
![](https://velog.velcdn.com/images/almondbreez0_3/post/b830c198-77d5-4fee-a8d0-947ff5cdab1f/image.jpg)
![](https://velog.velcdn.com/images/almondbreez0_3/post/6ca7944c-8a4e-404c-a461-10860860d0d6/image.jpg)
ect(jsonPath(&quot;$.amount&quot;,is(amount)));
    }</code></pre>
<p>여기까지 생성한 test함수 모두 Test통과함을 볼 수 있다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1장 마이크로서비스를 왜 쓰는가]]></title>
            <link>https://velog.io/@almondbreez0_3/1%EC%9E%A5-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4%EB%A5%BC-%EC%99%9C-%EC%93%B0%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@almondbreez0_3/1%EC%9E%A5-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4%EB%A5%BC-%EC%99%9C-%EC%93%B0%EB%8A%94%EA%B0%80</guid>
            <pubDate>Sat, 09 Sep 2023 06:38:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>클러치 프로젝트 리팩토링을 시작하기에 앞서 더 나은 관리 방법에 관심이 생겨 MSA 설계 유형에 대해 공부를 시작하게 되었다. 이에 대한 공부는 &#39;도커, 쿠버네티스, 테라폼으로 구현하는 마이크로서비스&#39;라는 책을 정해 하려고 한다</p>
</blockquote>
<p>지속적으로 앱을 개발하고 향상시키려면 고객의 요구를 감당할 수 있는 확장성도 지녀야 한다고 생각한다. MSA 설계 방식은 기존의 모놀리스 앱보다 어렵고 복잡하며 시간이 많이 소요되는 설계 방식이다. 
최근 개발 생태계는 접근성 좋고 저렴한 클라우드 인프라 비용과 예전보다 더욱 향상된 도구들과 자동화에 대한 수요 증가 등의 다양한 여건이 맞아떨어진다. 앱은 복잡해지지만, MSA는 복잡성을 더 잘 관리할 방법을 제공한다. 
이</p>
<h2 id="이-책에서-배울-것들">이 책에서 배울 것들</h2>
<ul>
<li>개별적으로 동작하는 마이크로서비스를 만든다</li>
<li>도커를 사용해 마이크로서비스 패키지를 만들고 게시한다</li>
<li>도커 컴포즈를 사용해 개발 환경에서 마이크로 서비스를 개발한다</li>
<li>코드와 마이크로서비스 앱을 JEST와 Cypress로 테스트한다</li>
<li>MongoDB와 RabbitMQ와 같은 서드파티 서버를 자신의 앱과 통합한다</li>
<li>HTTP와 래빗MQ 메시지를 사용해 마이크로서비스끼리 통신한다</li>
<li>마이크로서비스 동작에 필요한 데이터와 파일을 저장한다</li>
<li>테라폼으로 마이크로서비스를 운영시스템에 배포한다</li>
<li>코드를 업데이트하면 자동으로 앱을 배포하도록 지속적 전달(CD)파이프라인을 생성한다</li>
</ul>
<h2 id="마이크로서비스란">마이크로서비스란?</h2>
<blockquote>
<p>마이크로서비스는 개별젹으로 배포 일정을 갖고 업데이트 운영이 가능한 작고 독립적인 소프트웨어 프로세스이다.</p>
</blockquote>
<h2 id="마이크로서비스-앱이란">마이크로서비스 앱이란?</h2>
<blockquote>
<p>마이크로서비스 앱은 프로젝트의 주요 기능들을 수행하기 위해 서로 협업하는 작은 서비스들로 구성된 분산 프로그램
클러스터는 클러스터 오케스트레이션 플랫폼 위에 존재한다. 오케스트레이션은 서비스들에 대한 자동화된 관리를 말한다.
클러스터, 데이터베이스,가상 인프라 모두 우리가 선택한 클라우드 서비스에 존재한다.</p>
</blockquote>
<h2 id="모놀리스의-문제점">모놀리스의 문제점</h2>
<blockquote>
<p>모놀리스는 전체적인 앱이 단일 프로세스로 동작하는 경우를 말한다
담당자의 변경이 있을 때, 시간이 지나면서 앱의 목표가 희미해질때 코드들이 중구난방으로 될 가능성이 크다.</p>
</blockquote>
<p>하지만 모놀리스는 새로운 앱을 만들 때 좋은 시작점이기도 해서 마이크로서비스 앱이 요구하는 많은 기술적 투자를 시행하기 이전의 초기 단계라고 가정하면 비즈니스 모델의 유효성을 테스트하기 적합하다
그래서 모놀리스는 나중에 버릴 수 있는 실험적 모델을 만들어보기에 적합하다. 작은 범위에서 또는 나중에 추가적인 기능 확장이 필요 없이 빠르게 잘 동작하게 만들려는 앱들을 모놀리스로 만들기에 적합하다.</p>
<h2 id="마이크로서비스의-장점">마이크로서비스의 장점</h2>
<ul>
<li>서비스를 격리해 무거운 서비스를 위한 자원을 따로 확보해 사용하도록 구성</li>
<li>세밀한 제어가 가능하다</li>
<li>배포 위험의 최소화</li>
<li>자신이 이미 보유한 기술 선택</li>
</ul>
<h2 id="마이크로서비스의-단점">마이크로서비스의 단점</h2>
<ul>
<li>배우기가 더 어렵다</li>
<li>분산 애플리케이션은 복잡하다</li>
</ul>
<h2 id="마이크로서비스-최신-도구-사용">마이크로서비스 최신 도구 사용</h2>
<p>도커:패키지를 만들거나 서비스를 배포한다, 도커 컴포즈:개발 환경에서 마이크로서비스 앱을 테스트한다, 쿠버네티스: 클라우드에 앱을 호스트한다, 테라폼: 클라우드 인프라, 쿠버네티스 클러스터를 만들고 앱을 배포한다</p>
<h2 id="책에서-소개한-규칙">책에서 소개한 규칙</h2>
<ul>
<li>미래에 필요한 검증을 위해 지나치게 미리 설계하지 말자. 자신의 앱을 위한 단순한 설계부터 시작하자</li>
<li>최대한 단순하게 유지하기 위해서 개발 과정에 지속적인 리팩토링을 적용하자</li>
<li>좋은 설계가 자연스럽게 완성되도록 하자<h2 id="마이크로서비스-관련-규칙">마이크로서비스 관련 규칙</h2>
</li>
<li>단일 책임 원칙</li>
<li>느슨한 연결</li>
<li>강한 응집력<h2 id="ddd도메인-기반-설계">DDD(도메인 기반 설계)</h2>
비즈니스 영역과 소프트웨어로서의 비즈니스를 이해하는 방법
DDD의 의미적 구분은 마이크로서비스의 구성에 적합하다</li>
</ul>
<blockquote>
<p>많은 개발자들은 DRY(Don&#39;t Repeat Yourself)라는 원칙을 안고 살지만 마이크로서비스 분야에서는 반복되는 코드들이 등장한다
따라서 DRY의 원칙은 포기하지 않고 반복되는 코드가 존재하는 것이 합리적이라면 그렇게 되도록 할 것이다.</p>
</blockquote>
<h2 id="요약">요약</h2>
<ul>
<li>마이크로서비스 앱을 개발하는 방법을 배우기 휘해 이론보다는 실용적인 접근 방법을 쓴다( 개발을 직접 해보면서 익히기)</li>
<li>마이크로서비스는 개별적으로 동작이 가능한 작고 독립적인 프로세스</li>
<li>마이크로서비스 앱은 협업을 통해 앱의 기능을 구성하는 수많은 작은 프로세스로 이루어져 있다</li>
<li>모놀리스는 하나의 커다란 서비스를 가진 앱이다</li>
<li>마이크로서비스 앱을 만드는 것이 모놀리스보다 더 복잡하지만 생각만큼 어렵진 않다</li>
<li>마이크로서비스를 기반으로 제작한 앱은 모놀리스 앱보다 더 나은 유연성 확장성, 안정성, 내결함성을 가진다</li>
<li>도커, 쿠버네티스, 테라폼과 같은 최신 도구들은 마이크로서비스 제작을 이전보다 훨씬 쉽게 만든다</li>
<li>DDD는 마이크로서비스를 설계하는 효과적인 방법이다</li>
<li>DDD를 사용한 의미적 구분은 마이크로서비스의 경계를 구분하는 데 적합하다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/almondbreez0_3/post/27bbeb9e-491d-455a-bd22-7fddc51d7e28/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커]]></title>
            <link>https://velog.io/@almondbreez0_3/%EB%8F%84%EC%BB%A4</link>
            <guid>https://velog.io/@almondbreez0_3/%EB%8F%84%EC%BB%A4</guid>
            <pubDate>Sat, 19 Aug 2023 12:18:18 GMT</pubDate>
            <description><![CDATA[<h2 id="도커란-무엇인가">도커란 무엇인가?</h2>
<h3 id="도커">도커</h3>
<p>도커는 데이터 또는 프로그램을 격리시키는 기능을 제공한다
우리가 현재 사용하는 컴퓨터에는 여러 기능이 함께 동작하고 있는데, 도커는 이와 같은 다양한 프로그램과 데이터를 각각 독립된 환경에 격리하는 기능을 제공한다
컨테이너는 개별 software의 실행에 필요한 실행환경을 독립적으로 운용할 수 있도록 기반환경 또는 다른 실행환경과의 간섭을 막고 실행의 독립성을 확보해주는 운영체계 수준의 격리 기술이다</p>
<h3 id="컨테이너와-도커-엔진">컨테이너와 도커 엔진</h3>
<p>소프트웨어는 OS와 라이브러리에 의존성을 띄고 있어, 하나의 컴퓨터에서 성격이 다른 소프트웨어를 한번에 실행할 때 어려움을 가질 수 있다
컨테이너는 개별 Software의 실행에 필요한 실행환경을 독립적으로 운용할 수 있도록 기반환경 또는 다른 실행환경과의 간섭을 막고 실행의 독립성을 확보해주는 운영체계 수준의 격리 기술이다.
쉽게 말하자면 작은 방이 컨테이너고 그 컨테이너를 다루는 기능을 제공하는 소프트웨어가 도커이다.</p>
<h3 id="컨테이너를-만들려면-이미지가-필요하다">컨테이너를 만들려면 이미지가 필요하다</h3>
<p>도커 엔진이 있어야지 컨테이너를 만들 수 있고 컨테이너를 만들려면 컨테이너의 틀과 같은 역할을 하는 이미지가 필요하다. 
이미지는 담고 있는 소프트웨어의 종류에 따라 다양하게 존재한다
ex)아파치 컨테이너를 만들고 싶다면 아파치 이미지, MySQL 컨테이너를 만들고 싶다면 MySQL 이미지 사용</p>
<h3 id="도커는-리눅스-컴퓨터에서-사용한다">도커는 리눅스 컴퓨터에서 사용한다</h3>
<p>반드시 리눅스 운영체제가 필요하다. 컨테이너에서 동작시킬 프로그램도 리눅스용 프로그램이다.</p>
<h3 id="그래서-데이터나-프로그램을-독립된-환경에-격리해야하는-이유">그래서 데이터나 프로그램을 독립된 환경에 격리해야하는 이유</h3>
<p>도커는 주로 서버환경을 격리하기 위해 사용되지만 데이터나 프로그램을 독립된 환경에 격리해야하는 이유가 무엇일까?</p>
<ul>
<li>대부분의 프로그램은 프로그램 단독으로 동작하는 것이 아닌 어떤 실행 환경이나 라이브러리, 다른 프로그램과 함께 동작한다</li>
<li>소프트웨어는 단일 프로그램이 아닌 여러 개의 프로그램으로 구성된 경우가 많다. 즉, 소프트웨어는 여러 프로그램 하나를 업데이트하면 다른 프로그램에도 영향을 미치게 된다.또한 다른 프로그램과 특정한 폴더를 공유하거나 같은 경로에 설정 정보를 저장하는 경우도 있다.<h3 id="프로그램의-격리란">프로그램의 격리란?</h3>
도커 컨테이너를 사용해 프로그램을 격리하면 여러 프로그램이 한 서버에서 실행되면서 발생하게 되는 위와 같은 문제들을 대부분 해결할 수 있다!
시스템 A와 B의 버전이 각각 달라도 한 세트로 묶어 따로 격리시켜 사용할 수 있다<h2 id="서버와-도커">서버와 도커</h2>
도커는 서버에서 사용되는 소프트웨어이다
서버는 말 그대로 어떤 서비스를 제공하는 것이다
또한 일반적으로 한 대의 서버 컴퓨터에는 웹 서버를 한 개 밖에 실행하지 못했는데, 컨테이너 기술을 사용하면 여러개의 웹 서버를 올릴 수 있다.
컨테이너는 자유롭게 옮길 수 있다.
(사실 옮긴다기 보다는, 컨테이너의 정보를 내보내기한 다음, 다른 도커 엔진에서 복원하는 형태이다)
이는 자원의 효율성 측면에서 차이가 난다.
VM는 하나씩 늘 때마다 OS를 위한 자원을 할당해주어야 하는 반면에,
도커는 어플리케이션을 구동하는데 필요한 모든 패키지만 있으면 컨테이너를 구동시킬 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring boot-Multipart사용하면서 발생한 오류]]></title>
            <link>https://velog.io/@almondbreez0_3/Spring-boot-Multipart%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B4%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@almondbreez0_3/Spring-boot-Multipart%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B4%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Wed, 09 Aug 2023 00:47:48 GMT</pubDate>
            <description><![CDATA[<h2 id="오류">오류</h2>
<pre><code class="language-json">{
  &quot;check&quot;: false,
  &quot;information&quot;: {
    &quot;timestamp&quot;: &quot;2023-08-09T09:22:34.6360137&quot;,
    &quot;message&quot;: &quot;org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type &#39;application/octet-stream&#39; is not supported&quot;,
    &quot;code&quot;: null,
    &quot;status&quot;: 500,
    &quot;class&quot;: null,
    &quot;errors&quot;: []
  }
}</code></pre>
<p>분명히 react로 프론트 혼자 구현해보면서 통신하면 잘 되는데 스웨거에서 하면 이런 오류가 떳다</p>
<h2 id="해결법">해결법</h2>
<p>무한 구글링 중에 이 글을 발견해서
<a href="https://stackoverflow.com/questions/16230291/requestpart-with-mixed-multipart-request-spring-mvc-3-2">https://stackoverflow.com/questions/16230291/requestpart-with-mixed-multipart-request-spring-mvc-3-2</a></p>
<p>이 파일만 추가해주니까 이 문제가 해결되고 swagger에서도 이미지 업로드 해볼 수 있었다</p>
<pre><code class="language-java">package clutch.clutchserver.global.common;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;

import java.lang.reflect.Type;

@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {

    /**
     * Converter for support http request with header Content-Type: multipart/form-data
     */
    public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
    }

    @Override
    public boolean canWrite(Class&lt;?&gt; clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Type type, Class&lt;?&gt; clazz, MediaType mediaType) {
        return false;
    }

    @Override
    protected boolean canWrite(MediaType mediaType) {
        return false;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[배포]AWS+SPRING BOOT + Docker + github action을 사용한 배포-2]]></title>
            <link>https://velog.io/@almondbreez0_3/%EB%B0%B0%ED%8F%ACAWSSPRING-BOOT-Docker-github-action%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B0%B0%ED%8F%AC-2</link>
            <guid>https://velog.io/@almondbreez0_3/%EB%B0%B0%ED%8F%ACAWSSPRING-BOOT-Docker-github-action%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B0%B0%ED%8F%AC-2</guid>
            <pubDate>Mon, 07 Aug 2023 10:35:20 GMT</pubDate>
            <description><![CDATA[<p>이번에는 AWS, Docker, Github action을 이용해 서버 자동 배포를 구현해본 것을 설명해보려고 합니다</p>
<ol>
<li>EC2에서 도커 설치해주기</li>
<li>Github Actions Script 파일 생성해준다
Github -&gt;Actions -&gt; Java with Gradle</li>
<li>내가 작성한 코드<pre><code>name: Java CI with Gradle
</code></pre></li>
</ol>
<p>on:
  push:
    branches:
      - main</p>
<p>permissions:
  contents: read</p>
<p>jobs:
  build:</p>
<pre><code>runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
  uses: actions/setup-java@v3
  with:
    java-version: &#39;17&#39;
    distribution: &#39;temurin&#39;

     # gradle 캐싱
- name: Gradle Caching
  uses: actions/cache@v3
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles(&#39;**/*.gradle*&#39;, &#39;**/gradle-wrapper.properties&#39;) }}
    restore-keys: |
      ${{ runner.os }}-gradle-

- name: Set up NTP
  run: |
    sudo apt-get update
    sudo apt-get install -y ntp
    sudo service ntp start


- name: make application-db.yml
  run: |
      cd ./src/main/resources
      touch ./application-db.yml
      echo &quot;${{ secrets.DB_PROPERTIES }}&quot; &gt; ./application-db.yml
  shell: bash

- name: make application-real.yml
  run: |
    cd ./src/main/resources
    touch ./application-real.yml
    echo &quot;${{ secrets.PROPERTIES_PROD }}&quot; &gt; ./application-real.yml
  shell: bash

- name: Grant execute permission for gradlew
  run: chmod +x ./gradlew
- name: Build with Gradle
  run: ./gradlew build -x test

- name: Docker build
  run: |
    docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
    docker build -t app -f DockerFile .
    docker tag app ${{ secrets.DOCKER_USERNAME }}/clutchserver:latest
    docker push ${{ secrets.DOCKER_USERNAME }}/clutchserver:latest


- name: Deploy
  uses: appleboy/ssh-action@master
  with:
    host: ${{ secrets.HOST_PROD }} # EC2 인스턴스 퍼블릭 DNS
    username: ubuntu
    key: ${{ secrets.PRIVATE_KEY }} # pem 키
    # 도커 작업
    script: |
      docker pull ${{ secrets.DOCKER_USERNAME }}/clutchserver:latest
      docker stop $(docker ps -a -q)
      docker run -d --log-driver=syslog -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/clutchserver:latest
      docker rm $(docker ps --filter &#39;status=exited&#39; -a -q)
      docker image prune -a -f</code></pre><pre><code>4. 중요한 정보를 담는 곳
![](https://velog.velcdn.com/images/almondbreez0_3/post/33422755-bcf3-491c-902a-f6ea4d90fcb0/image.png)
# 내가 겪은 문제점들
1. MySql이 제대로 연결 되지 않아서 502 에러 뜨면서 배포가 계속 안됬었다
 - 더 구체적인 문제는 바로 .env파일이 이 깃허브 yml파일에서 생성이 되지 않고 있어서 문제였다
 - env파일을 생성하는 방법을 몰라서 application.yml을 2개로 나누어서 </code></pre><pre><code>- name: make application-db.yml
  run: |
      cd ./src/main/resources
      touch ./application-db.yml
      echo &quot;${{ secrets.DB_PROPERTIES }}&quot; &gt; ./application-db.yml
  shell: bash

- name: make application-real.yml
  run: |
    cd ./src/main/resources
    touch ./application-real.yml
    echo &quot;${{ secrets.PROPERTIES_PROD }}&quot; &gt; ./application-real.yml
  shell: bash</code></pre><pre><code>


application-db안에 db관련 secret key들을 넣어줬다
application-real안에는 전체적인 secret key들을 넣어줬다
이렇게 생성이 되다 보니 결국에 MySQL이 제대로 연결이 되어 배포가 성공적으로 되었다.
2. main브랜치에다가 Push할 때마다 workflow에 github에서 배포 상태를 보여줬는데 계속 배포 성공했다고 뜨는데 배포가 안되는 이유를 몰랐는데,여기서 얻은 교훈은 ec2터미널에 직접 들어가서 수동 배포를 해보면서 직접 에러 로그를 봐가면서 배포해줘야한다고 느꼈다. 깃헙 workflow에 성공한다고 뜨더라도 직접 터미널에 쳐서 로그를 뜯어보면 오류가 나온다!

# 후기

생각보다 삽질 하면서 얻어가는 지식도 많고 노하우가 좀 생기는 것 같다...특히 github action 구글링 처음 했을때 저 파일 예시들 보면 이해가 안갔는데 이제는 해당 줄 하나하나 무슨 역할을 하고 어떤 파일을 생성하는지 알 수 있다.
아직 부족한 점이 너무나 많다고 느껴서 Docker랑 Docker-compose, Nginx등 다양한 서버 배포와 관련된 시스템이나 서비스들에 대한 공부를 하고 싶어졌다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[배포]AWS+SPRING BOOT + Docker + github action을 사용한 배포-1]]></title>
            <link>https://velog.io/@almondbreez0_3/%EB%B0%B0%ED%8F%ACAWSSPRING-BOOT-Docker-github-action%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B0%B0%ED%8F%AC-1</link>
            <guid>https://velog.io/@almondbreez0_3/%EB%B0%B0%ED%8F%ACAWSSPRING-BOOT-Docker-github-action%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B0%B0%ED%8F%AC-1</guid>
            <pubDate>Mon, 07 Aug 2023 10:17:17 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@almondbreez0_3/%EB%B0%B0%ED%8F%ACAWSSPRING-BOOT-DOCKER-github-action%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B0%B0%ED%8F%AC-0">https://velog.io/@almondbreez0_3/%EB%B0%B0%ED%8F%ACAWSSPRING-BOOT-DOCKER-github-action%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B0%B0%ED%8F%AC-0</a>
이 글에 이어지는 글입니다
이번에는 Http-&gt;Https로 로드밸런싱을 하는 과정까지 설명해 보겠습니다</p>
<h1 id="도메인-구입">도메인 구입</h1>
<ol>
<li>무료 도메인등록
https://내도메인.한국에서 무료 도메인을 등록할 수 있다<h4 id="여기서-겪은-문제점">여기서 겪은 문제점</h4>
</li>
</ol>
<p>이 부분을 해야 했는데 안해서 계속 왜 안되는거야!!라면서 하루를 삽질을 했다^^
<img src="https://velog.velcdn.com/images/almondbreez0_3/post/3483ebe1-fbae-4b88-a5d5-8074c9463499/image.png" alt=""></p>
<h1 id="ssl-인증서-발급-받기">SSL 인증서 발급 받기</h1>
<ol>
<li>AWS의 Certificate Manager에서 원하는 도메인에 대한 SSL인증서를 받는다<ul>
<li>퍼블릭 인증서 요청하기<ul>
<li>퍼블릭 인증서 요청할 도메인 등록하기</li>
<li>나는 clutch.p-e.kr, .clutch.p-e.kr 두개를 등록하였다</li>
</ul>
</li>
</ul>
</li>
<li>Route 53에 레코드 생성하기</li>
<li>승인이 날 때까지 기다리기<h1 id="aws-로드밸랜서-사용하기">AWS 로드밸랜서 사용하기</h1>
</li>
<li>승인이 된다면 EC2에서 Target Group을 만들어준다
<img src="https://velog.velcdn.com/images/almondbreez0_3/post/3099bb6f-8dd4-4181-8298-c7e61e971720/image.png" alt="">
대상 그룹 생성하기 주황색 버튼을 눌러준다</li>
<li>포트 번호는 내가 띄울 서비스의 포트 번호를 입력해야 한다. 나는 스프링부트를 이용한 프로젝트이니까</li>
</ol>
<p><img src="https://velog.velcdn.com/images/almondbreez0_3/post/c2776a0b-7786-432b-8b6d-db8426eab869/image.png" alt="">
3. EC2에서 로드 밸린서 생성하기 
 <img src="https://velog.velcdn.com/images/almondbreez0_3/post/8b401d48-562b-46a5-8c25-24ea1957d085/image.png" alt=""></p>
<p> <img src="https://velog.velcdn.com/images/almondbreez0_3/post/00c92a1e-8838-4d28-83cb-222f1c40361e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/almondbreez0_3/post/8886ca44-a8e0-49a5-bfe6-9e1e7f88419a/image.png" alt=""></p>
<ol start="4">
<li><p>가용 영역을 설정할 때 ec2와 동일한 가용영역으로 꼭 설정해야한다(확인하기 귀찮으면 다 골라도 상관없다)
<img src="https://velog.velcdn.com/images/almondbreez0_3/post/147a6ca9-4d8f-4f1b-afe8-1b9ee7098c94/image.png" alt=""></p>
</li>
<li><p>보안 그룹 설정할 때 ec2용으로 만들어 둔것을 써도 돠고 따로 새로 만들어도 된다.</p>
</li>
<li><p>SSL/TLS인증서를 추가해주는 부분에서는 아까 받은 인증서를 추가해준다</p>
</li>
<li><p>가장 중요한 부분인, 리스너를 등록하고 해당 포트로 들어오는 요청을 타겟 그룹으로 넘겨주게 된다
포트 8080 -&gt; 포트 443으로 넘겨주기 위해서 리스너 등록을 꼭 해준다
<img src="https://velog.velcdn.com/images/almondbreez0_3/post/bdfcf4d8-4b7b-4f72-8da4-4711648b55d7/image.png" alt=""></p>
</li>
<li><p>8080포트로 들어오는 요청은 Redirect, 443포트로 들어오는 요청을 만들어준 인스턴스로 연결해준다</p>
<ol>
<li>8080 포트 규칙 변경. manage rule클릭해준다</li>
<li>연필 클릭해서 기존 규칙 삭제 후 https 443포트로 리다이렉션해준다</li>
</ol>
</li>
<li><p>등록한 로드밸랜서를 AWS Route 53의 도메인의 레코드에 등록한다</p>
</li>
<li><p>EC2의 인바운드 규칙에 443추가해준다</p>
</li>
</ol>
<p>이모든 과정을 거치면 https를 이용하여 원하는 도메인으로 플젝에서 만든 서비스를 웹사이트에서 아무데서나 접속할 수 있다
<a href="https://www.clutch.p-e.kr/">https://www.clutch.p-e.kr/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[배포]AWS+SPRING BOOT + DOCKER + github action을 사용한 배포-0]]></title>
            <link>https://velog.io/@almondbreez0_3/%EB%B0%B0%ED%8F%ACAWSSPRING-BOOT-DOCKER-github-action%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B0%B0%ED%8F%AC-0</link>
            <guid>https://velog.io/@almondbreez0_3/%EB%B0%B0%ED%8F%ACAWSSPRING-BOOT-DOCKER-github-action%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B0%B0%ED%8F%AC-0</guid>
            <pubDate>Mon, 07 Aug 2023 09:33:15 GMT</pubDate>
            <description><![CDATA[<p>클러치 플젝에서 배포를 담당했습니다. 처음 배포를 해보는 것이었지만 여러 에러를 겪어가면서 배포하는 방법에 대해 자세히 알게 되었습니다. 이번 글에서는 EC2연결과 RDS연동, 그리고 aws를 이용해 http를 https로 전환해주는 것까지 할것입니다.</p>
<h1 id="1-ec2인스턴스-연결하기">1. EC2인스턴스 연결하기</h1>
<p><img src="https://velog.velcdn.com/images/almondbreez0_3/post/c3ff3aef-0e6c-4c11-b627-f50ac89cb09f/image.png" alt=""></p>
<ul>
<li><p>여기서 Ubuntu를 선택해준다
<img src="https://velog.velcdn.com/images/almondbreez0_3/post/79b77b6c-cbad-40aa-84e6-5eaca2f8d521/image.png" alt=""></p>
</li>
<li><p>새 키 페어 생성 눌러서
<img src="https://velog.velcdn.com/images/almondbreez0_3/post/7ebd23b5-9d55-4e4c-acc0-04a5b33c4fef/image.png" alt=""></p>
</li>
<li><p>이 키는 잃어버리면 안되니까 저장을 꼭 해놓자!</p>
<h1 id="2-ec2와-연결한-후-탄력적-ip주소-할당을-해보자">2. EC2와 연결한 후 &#39;탄력적 IP주소 할당&#39;을 해보자</h1>
<p>탄력적 IP주소를 할당하면 인스턴스를 중지하고 다시 시작하면 IP주소가 변하는 것을 방지해준다
탄력적 Ip칸을 가서 탄력적 IP주소 할당을 눌러서 생성한 EC2인스턴스와 연결해준다
다시 자신이 연결한 EC2를 가보면,
탄력적IP주소에 할당된 ip주소가 적혀있는것을 볼 수 있다</p>
<h1 id="3-rds연동하기">3. RDS연동하기</h1>
<p>이 과정에서는 생성한 인스턴스의 VPC ID, 서브넷 ID, 보안 그룹 정보가 필요하고 VPC ID, 서브넷 ID은 세부정보 에서, 보안그룹은 보안에서 확인할 수 있다</p>
</li>
<li><p>DB의 보안그룹 생성해준다: 네트워크 및 보안 -&gt; 보안그룹에서 DB 보안그룹을 생성한다</p>
</li>
<li><p>인바운드 규칙을 추가하고 유형을 MYSQL/Aurora, VPC는 위에서 확인한 EC2의 VPC ID, 소스는 DB의 보안그룹과 동일한 것을 선택한다</p>
<h3 id="rds-들어가기">RDS 들어가기</h3>
</li>
</ul>
<ol>
<li>DB 서브넷 그룹 생성하기</li>
</ol>
<ul>
<li>위에서 확인한 VPC ID를 선택하고 가용영역과 서브넷은 모든 것으로 선택한다</li>
</ul>
<ol start="2">
<li>파라미터 그룹 생성</li>
</ol>
<ul>
<li>char: utf8</li>
<li>collation&quot; utf8_general_ci</li>
<li>zone: Asia/Seoul</li>
</ul>
<ol start="3">
<li>DB 생성 </li>
</ol>
<ul>
<li>데이터베이스 생성 버튼을 누르고 표준 생성, MySQL, free tier을 선택하고 사용자 이름과 암호를 선택한다</li>
<li>여기서 원래 ec2 터미널에서 DB에 있는 연결 &amp;보안을 들어가 엔드 포인트를 입력해서 연결하면 된다<pre><code># 방법1
mysql -u &lt;사용자이름&gt; -p --host &lt;엔드포인트&gt;
# 방법2
mysql -h &lt;엔드포인트&gt; -P &lt;포트번호&gt; -u &lt;사용자이름&gt; -p </code></pre></li>
</ul>
<ol start="4">
<li>MySQL Workbench에서 aws rds 연결하기</li>
</ol>
<ul>
<li>ec2랑 rds의 </li>
<li>메인화면에서 +버튼을 클릭해준다
<img src="https://velog.velcdn.com/images/almondbreez0_3/post/8e6e4286-b6aa-47ce-a8e3-c724ec4b6356/image.png" alt=""></li>
<li>connection name: 식별 가능한 이름 아무거나 입력한다</li>
<li>Hostname: RDS의 엔드포인트를 입력한다</li>
<li>Username: RDS를 생성할 때 유저이름으로 설정했던 것을 입력해준다</li>
<li>Password: RDS를 생성할 때 비번 설정했던 것을 입력한다</li>
<li>Default Schema는 비워놔도 무방하다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 부트 초기 설정]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 07 Aug 2023 03:30:23 GMT</pubDate>
            <description><![CDATA[<ol>
<li><p>spring security, spring data jpa, spring web, lombok, mysql Dependecy추가</p>
</li>
<li><p>스프링 3.1.1,java 17</p>
</li>
<li><p>corsconfig파일</p>
<pre><code>@Configuration
@RequiredArgsConstructor
public class CorsConfig  implements WebMvcConfigurer {
 @Override
 public void addCorsMappings(CorsRegistry registry) {
     ArrayList&lt;String&gt; allowedOriginPatterns = new ArrayList&lt;&gt;();
     allowedOriginPatterns.add(&quot;http://localhost:3000&quot;);
     allowedOriginPatterns.add(&quot;http://localhost:8080&quot;);

     String[] patterns = allowedOriginPatterns.toArray(String[]::new);
     registry.addMapping(&quot;/**&quot;)
             .allowedMethods(&quot;*&quot;)
             .allowedOriginPatterns(patterns)
             .allowCredentials(true)
             .maxAge(3600L);
 }
}</code></pre></li>
<li><p>spring security기본설정</p>
<pre><code>@Configuration
@RequiredArgsConstructor
public class CorsConfig  implements WebMvcConfigurer {
 @Override
 public void addCorsMappings(CorsRegistry registry) {
     ArrayList&lt;String&gt; allowedOriginPatterns = new ArrayList&lt;&gt;();
     allowedOriginPatterns.add(&quot;http://localhost:3000&quot;);
     allowedOriginPatterns.add(&quot;http://localhost:8080&quot;);

     String[] patterns = allowedOriginPatterns.toArray(String[]::new);
     registry.addMapping(&quot;/**&quot;)
             .allowedMethods(&quot;*&quot;)
             .allowedOriginPatterns(patterns)
             .allowCredentials(true)
             .maxAge(3600L);
 }
}</code></pre></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[장고 CSRF오류]]></title>
            <link>https://velog.io/@almondbreez0_3/%EC%9E%A5%EA%B3%A0-CSRF%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@almondbreez0_3/%EC%9E%A5%EA%B3%A0-CSRF%EC%98%A4%EB%A5%98</guid>
            <pubDate>Mon, 07 Aug 2023 03:17:03 GMT</pubDate>
            <description><![CDATA[<p>localhost:3000과 localhost:8000에서 통신 연습과 테스트를 하는데 계속 csrf오류가 났다.</p>
<p>그래서 오랜 구글링 끝에
<a href="https://stackoverflow.com/questions/15409366/django-socialapp-matching-query-does-not-exist">https://stackoverflow.com/questions/15409366/django-socialapp-matching-query-does-not-exist</a></p>
<pre><code>MIDDLEWARE = [
    &#39;django.middleware.security.SecurityMiddleware&#39;,
    &#39;django.contrib.sessions.middleware.SessionMiddleware&#39;,
    &#39;django.middleware.common.CommonMiddleware&#39;,
    #&#39;django.middleware.csrf.CsrfViewMiddleware&#39;,
    &#39;django.contrib.auth.middleware.AuthenticationMiddleware&#39;,
    &#39;django.contrib.messages.middleware.MessageMiddleware&#39;,
    &#39;django.middleware.clickjacking.XFrameOptionsMiddleware&#39;,
    &#39;corsheaders.middleware.CorsMiddleware&#39;,
]</code></pre><p>csrf주석처리하면 작동된다!</p>
]]></description>
        </item>
    </channel>
</rss>