<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>lucius__k.log</title>
        <link>https://velog.io/</link>
        <description>끄적끄적</description>
        <lastBuildDate>Thu, 07 Mar 2024 06:18:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. lucius__k.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/lucius__k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Item39] 명명 패턴보다 애너테이션을 사용하라]]></title>
            <link>https://velog.io/@lucius__k/Item39-%EB%AA%85%EB%AA%85-%ED%8C%A8%ED%84%B4%EB%B3%B4%EB%8B%A4-%EC%95%A0%EB%84%88%ED%85%8C%EC%9D%B4%EC%85%98%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@lucius__k/Item39-%EB%AA%85%EB%AA%85-%ED%8C%A8%ED%84%B4%EB%B3%B4%EB%8B%A4-%EC%95%A0%EB%84%88%ED%85%8C%EC%9D%B4%EC%85%98%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Thu, 07 Mar 2024 06:18:50 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<h1 id="junit으로-알아보는-명명-패턴의-단점">JUnit으로 알아보는 명명 패턴의 단점</h1>
<p>전통적으로 도구나 프레임워크가 특별히 다뤄야 할 프로그램 요소에는 딱 구분되는 명명 패턴을 적용해왔다.</p>
<p>테스트 프레임워크인 JUnit은 버전 3까지 테스트 메서드 이름을 test로 시작하게끔 했다.</p>
<p>효과적이지만 단점도 크다.</p>
<ol>
<li><p><strong>오타가 나면 안된다.</strong></p>
<p>실수로 이름을 <code>tsetSafetyOverride</code>로 지으면 JUnit은 이 메서드를 무시하고 지나간다.</p>
<p>개발자는 테스트가 통과되었다고 오해할 수 있다.</p>
</li>
<li><p><strong>올바른 프로그램 요소에서만 사용되리라 보증할 방법이 없다.</strong></p>
<p>메서드가 아닌 클래스 이름을 <code>TestSafetyMechanisms</code>로 지어 테스트를 한다고 해보자.</p>
<p>해당 클래스에 존재하는 테스트 메서드들을 수행해주길 기대하지만 JUnit은 클래스 이름에는 관심이 없기 때문에 테스트가 수행되지 않는다.</p>
</li>
<li><p><strong>프로그램 요소를 매개변수로 전달할 마땅한 방법이 없다.</strong></p>
<p>예외가 발생해야 하는 테스트에 기대하는 예외 타입을 테스트에 매개변수로 전달해야 하는 상황에서,</p>
<p>예외의 이름을 테스트 메서드 이름에 덧붙이는 방법도 있지만, 보기도 나쁘고 깨지기도 쉽다.</p>
<p>컴파일러는 메서드 이름에 덧붙인 문자열이 예외를 가리키는지 알 방법이 없다.</p>
<p>또 테스트를 실행하기 전에는 그런 이름의 클래스가 존재하는지 혹은 예외가 맞는지조차 알 수 없다.</p>
</li>
</ol>
<hr>
<h1 id="애너테이션으로의-전환">애너테이션으로의 전환</h1>
<p>애너테이션은 위에서 말한 모든 문제를 해결해주며, JUnit도 버전 4부터 도입하였다.</p>
<p>작은 테스트 프레임워크를 제작하여 애너테이션의 동작 방식을 알아보자.</p>
<h2 id="정적-테스트-메서드-수행">정적 테스트 메서드 수행</h2>
<p><code>Test</code>라는 이름의 애너테이션을 정의하자.</p>
<p>자동으로 수행되는 간단한 테스트용 애너테이션이다.</p>
<pre><code class="language-java">/**
 * 테스트 메서드임을 선언하는 애너테이션이다.
 * 매개변수 없는 정적 메서드 전용이다.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}</code></pre>
<p>위의 <code>@Test</code> 애너테이션을 보면 <code>@Retention</code>, <code>@Target</code>이 있는 것을 볼 수 있는데,</p>
<p>이처럼 애너테이션 선언에 다는 애너테이션을 <em>메타애너테이션</em>이라고 한다.</p>
<p><code>@Retention(RetentionPolicy.RUNTIME)</code> 메타애너테이션은 <code>@Test</code>가 런타임에도 유지되어야 한다는 표시다.</p>
<p><code>@Target(ElementType.METHOD)</code> 메타애너테이션은 <code>@Test</code>가 반드시 메서드 선언에서만 사용돼야 한다고 알려준다.</p>
<p>주석에 &quot;매개변수 없는 정적 메서드 전용이다.&quot;라고 쓰여 있는데, 이 제약을 컴파일러가 강제할 수 있으면 좋겠지만, 그렇게 하려면 애너테이션 처리기를 직접 구현해야 한다.</p>
<p>관련 방법은 javax.annotation.processing API 문서를 참고하면 된다.</p>
<pre><code class="language-java">public class Sample {

    @Test
    public static void m1() {} // 성공해야 한다.

    public static void m2() {}

    @Test
    public static void m3() { // 실패해야 한다.
        throw new RuntimeException(&quot;실패&quot;);
    }

    public static void m4() {}

    @Test
    public void m5() {} // 잘못 사용한 예: 정적 메서드가 아니다.

    public static void m6() {}

    @Test
    public static void m7() { // 실패해야 한다.
        throw new RuntimeException(&quot;실패&quot;);
    }

    public static void m8() {}
}
</code></pre>
<p>위의 코드는 <code>@Test</code> 애너테이션을 실제 적용한 모습이다.</p>
<p>이런 애너테이션을 &quot;아무 매개변수 없이 단순히 대상에 마킹(marking)한다&quot;는 뜻에서 마커(marker) 애너테이션이라고 한다.</p>
<p>위의 코드에서 홀수번의 메서드들에만 <code>@Test</code> 애너테이션이 있고,</p>
<p>3, 7번 메서드에서는 예외가 발생하고,</p>
<p>5번 메서드는 인스턴스 메서드이므로 잘못 사용되었다.</p>
<p>즉, 1개의 테스트 메서드만 성공, 2개는 실패, 1개는 잘못 사용되었다.</p>
<p>그리고 <code>@Test</code> 애너테이션이 없는 4개의 메서드는 무시되어야 한다.</p>
<pre><code class="language-java">public class RunTests {

    public static void main(String[] args) {
        int tests  = 0;
        int passed = 0;
        for (Method m : Sample.class.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + &quot; 실패: &quot; + exc);
                } catch (Exception exc) {
                    System.out.println(&quot;잘못 사용한 @Test: &quot; + m);
                }
            }
        }
        System.out.printf(&quot;성공: %d, 실패: %d%n&quot;, passed, tests - passed);
    }
}
</code></pre>
<p>위의 코드를 보면, <code>Sample</code> 클래스에서 <code>@Test</code> 애너테이션이 있는 모든 메서드를 차례로 호출한다.</p>
<p>테스트 메서드가 예외를 던지면 리플렉션 메커니즘이 <code>InvocationTargetException</code>으로 감싸서 다시 던진다.</p>
<p><code>InvocationTargetException</code>을 잡아 원래 예외에 담긴 실패 정보를 추출해 출력한다.</p>
<p><code>InvocationTargetException</code>외의 예외가 발생하면 <code>@Test</code> 애너테이션을 잘못 사용했다는 뜻이므로 해당 예외를 잡아 적절한 오류 메시지를 출력한다.</p>
<p><code>RunTests</code>를 실행했을 때의 출력 메시지</p>
<pre><code>잘못 사용한 @Test: public void Sample.m5()
public static void Sample.m7() 실패: java.lang.RuntimeException: 실패
public static void Sample.m3() 실패: java.lang.RuntimeException: 실패
성공: 1, 실패: 3</code></pre><h2 id="예외를-던져야만-성공하는-테스트">예외를 던져야만 성공하는 테스트</h2>
<p>특정 예외를 던져야만 성공하는 테스트를 지원하기위해 새로운 애너테이션을 정의하자.</p>
<pre><code class="language-java">/**
 * 명시한 예외를 던져야만 성공하는 테스트 메서드용 애너테이션
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class&lt;? extends Throwable&gt; value();
}</code></pre>
<p>이 애너테이션의 매개변수 타입은 <code>Class&lt;? extends Throwable&gt;</code>이다.</p>
<p>여기서의 와일드카드 타입은 &quot;Throwable을 확장한 클래스의 Class 객체&quot;라는 뜻으로 모든 예외 타입을 다 수용한다.</p>
<p>다음은 이 애너테이션을 실제로 활용하는 코드다.</p>
<pre><code class="language-java">public class Sample2 {

    @ExceptionTest(ArithmeticException.class)
    public static void m1() { // 성공해야 한다.
        int i = 0;
        i = i / i;
    }

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

    @ExceptionTest(ArithmeticException.class)
    public static void m3() {} // 실패해야 한다. (예외가 발생하지 않음)
}</code></pre>
<p>이 애너테이션을 다룰 수 있도록 테스트 도구를 수정해보면,</p>
<pre><code class="language-java">public static void main(String[] args) {
    int tests = 0;
    int passed = 0;
    for (Method m : Sample2.class.getDeclaredMethods()) {
        if (m.isAnnotationPresent(ExceptionTest.class)) {
            tests++;
            try {
                m.invoke(null);
                System.out.printf(&quot;테스트 %s 실패: 예외를 던지지 않음%n&quot;, m);
            } catch (InvocationTargetException wrappedEx) {
                Throwable exc = wrappedEx.getCause();
                Class&lt;? extends Throwable&gt; excType = m.getAnnotation(ExceptionTest.class).value();
                if (excType.isInstance(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>
<p><code>@Test</code>애너테이션용 코드와 비슷해보이지만, 이 코드는 애너테이션 매개변수의 값을 추출하여 테스트 메서드가 올바른 예외를 던지는지 확인하는 데 사용한다.</p>
<h3 id="배열을-이용한-여러-예외-테스트">배열을 이용한 여러 예외 테스트</h3>
<p>예외를 여러 개 명시하고 그중 하나가 발생하면 성공하게 만들 수도 있다.</p>
<pre><code class="language-java">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class&lt;? extends Throwable&gt;[] value();
}</code></pre>
<p>위처럼 <code>@ExceptionTest</code> 애너테이션의 매개변수 타입을 Class 객체의 배열로 수정하자.</p>
<p>배열 매개변수를 받는 애너테이션용 문법은 유연하다.</p>
<p>위처럼 <code>@ExceptionTest</code> 애너테이션을 수정해도 앞에서 작성한 테스트 메서드들도 수정없이 사용 가능하다.</p>
<p>원소가 여럿인 배열을 지정할 때 중괄호로 감싸고 쉼표로 구분해주면된다.</p>
<pre><code class="language-java">@ExceptionTest({IndexOutOfBoundsException.class, NullPointerException.class})
public static void doublyBad() { // 성공해야 한다.
    List&lt;String&gt; list = new ArrayList&lt;&gt;();

    // 자바 API 명세에 따르면 다음 메서드는 IndexOutOfBoundsException이나 NullPointerException을 던질 수 있다.
    list.addAll(5, null);
}</code></pre>
<p>위의 새로운 <code>@ExceptionTest</code>를 지원하도록 코드를 수정하면,</p>
<pre><code class="language-java">if (m.isAnnotationPresent(ExceptionTest.class)) {
    tests++;
    try {
        m.invoke(null);
        System.out.printf(&quot;테스트 %s 실패: 예외를 던지지 않음%n&quot;, m);
    } catch (Throwable wrappedEx) {
        Throwable exc = wrappedEx.getCause();
        int oldPassed = passed;
        Class&lt;? extends Throwable&gt;[] excTypes = m.getAnnotation(ExceptionTest.class).value();
        for (Class&lt;? extends Throwable&gt; excType : excTypes) {
            if (excType.isInstance(exc)) {
                passed++;
                break;
            }
        }
        if (passed == oldPassed) {
            System.out.printf(&quot;테스트 %s 실패: %s %n&quot;, m, exc);
        }
    }
}</code></pre>
<h3 id="repeatable을-이용한-여러-예외-테스트">@Repeatable을 이용한 여러 예외 테스트</h3>
<p>자바 8에서는 배열 매개변수를 사용하는 대신 애너테이션에 <code>@Repeatable</code> 메타애너테이션을 다는 방식을 이용할 수 있다.</p>
<p>단, 주의할 점이 있다.</p>
<ol>
<li>@Repeatable이 있는 애너테이션을 반환하는 &#39;컨테이너 애너테이션&#39;을 하나 더 정의하고, @Repeatable에 이 컨테이너 애너테이션의 class 객체를 매개변수로 전달해야 한다.</li>
<li>컨테이너 애너테이션은 내부 애너테이션 타입의 배열을 반환하는 value 메서드를 정의해야 한다.</li>
<li>컨테이너 애너테이션 타입에는 적절한 보존 정책(@Retention)과 적용 대상(@Target)을 명시해야 한다. 그렇지 않으면 컴파일되지 않는다.</li>
</ol>
<pre><code class="language-java">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
    Class&lt;? extends Throwable&gt; value();
}

// 컨테이너 애너테이션
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
    ExceptionTest[] value();
}</code></pre>
<p>앞의 배열 방식 대신 반복 가능 애너테이션을 적용하면,</p>
<pre><code class="language-java">@ExceptionTest(IndexOutOfBoundsException.class)
@ExceptionTest(NullPointerException.class)
public static void doublyBad() { ... }</code></pre>
<p>반복 가능 애너테이션을 여러 개 달면 하나만 달았을 때와 구분하기 위해 해당 &#39;컨테이너&#39; 애너테이션 타입이 적용된다.</p>
<p><code>isAnnotationPresent</code> 메서드에서 컨테이너 애너테이션과 반복 가능 애너테이션이 있는지 모두 확인해야 하므로 각각 사용해야 한다.</p>
<p>반복 가능 버전으로 테스트 수행 메서드를 수정하면,</p>
<pre><code class="language-java">if (m.isAnnotationPresent(ExceptionTest.class) || m.isAnnotationPresent(ExceptionTestContainer.class)) {
    tests++;
    try {
        m.invoke(null);
        System.out.printf(&quot;테스트 %s 실패: 예외를 던지지 않음%n&quot;, m);
    } catch (Throwable wrappedEx) {
        Throwable exc = wrappedEx.getCause();
        int oldPassed = passed;
        ExceptionTest[] excTests = m.getAnnotationsByType(ExceptionTest.class);
        for (ExceptionTest excTest : excTests) {
            if (excTest.value().isInstance(exc)) {
                passed++;
                break;
            }
        }
        if (passed == oldPassed) {
            System.out.printf(&quot;테스트 %s 실패: %s %n&quot;, m, exc);
        }
    }
}</code></pre>
<hr>
<p>위의 만든 애너테이션을 이용한 테스트 프레임워크에서 애너테이션이 명명패턴보다 낫나는 점을 확실히 보여준다.</p>
<p>애너테이션으로 할 수 있는 일을 명명 패턴으로 처리할 이유는 없다.</p>
<p>자바 프로그래머라면 예외 없이 자바가 제공하는 애너테이션 타입들은 사용해야 한다.</p>
<p>IDE나 정적 분석 도구가 제공하는 애너테이션을 사용하면 해당 도구가 제공하는 진단 정보의 품질을 높여줄 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Item38] 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라]]></title>
            <link>https://velog.io/@lucius__k/Item38-%ED%99%95%EC%9E%A5%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%98%EB%A9%B4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@lucius__k/Item38-%ED%99%95%EC%9E%A5%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%98%EB%A9%B4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sat, 02 Mar 2024 12:57:52 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<h1 id="열거-타입의-확장">열거 타입의 확장</h1>
<p>거의 모든 상황에서 열거 타입이 타입 안전 열거 패턴보다 우수하다.</p>
<p>하지만 타입 안전 열거 패턴은 확장할 수 있으나 열거 타입은 그럴 수 없다는 단점이 있다.</p>
<p>열거 타입이 임의의 인터페이스를 구현할 수 있다는 사실을 이용하면 비슷한 효과를 볼 수 있다.</p>
<hr>
<h1 id="인터페이스를-이용한-열거-타입의-확장">인터페이스를 이용한 열거 타입의 확장</h1>
<pre><code class="language-java">public interface Operation {
  double apply(double x, double y);
}

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

    private final String symbol;

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

    @Override
    public String toString() {
        return symbol;
    }
}</code></pre>
<p>열거 타입인 <code>BasicOperation</code>은 확장할 수 없지만, <code>Operation</code>은 확장할 수 있다.</p>
<p>-&gt; 인터페이스를 연산의 타입으로 사용</p>
<p>지수 연산(EXP)과 나머지 연산(REMAINDER)을 추가하면,</p>
<pre><code class="language-java">public enum ExtendedOperation implements Operation {
    EXP(&quot;^&quot;) {
        public double apply(final double x, final double y) {return Math.pow(x, y);}
    },
    REMAINDER(&quot;%&quot;) {
        public double apply(final double x, final double y) {return x % y;}
    };

    private final String symbol;

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

    @Override
    public String toString() {
        return symbol;
    }
}</code></pre>
<p><code>Operation</code> 인터페이스를 사용하도록 작성되어 있기만 하면 언제든 새로운 연산을 적용할 수 있다.</p>
<p>개별 인스턴스 수준이 아닌 타입 수준에서도 사용할 수 있다.</p>
<pre><code class="language-java">public static void main(String[] args) {
  double x = 4;
  double y = 2;
  test(ExtendedOperation.class, x, y);
}

private static &lt;T extends Enum&lt;T&gt; &amp; Operation&gt; void test(Class&lt;T&gt; opEnumType, double x, double y) {
  for (Operation op : opEnumType.getEnumConstants()) {
    System.out.printf(&quot;%f %s %f = %f%n&quot;, x, op, y, op.apply(x, y));
  }
}</code></pre>
<p><code>test</code>메서드에 <code>ExtendedOperation</code>의 class 리터럴을 넘겨 확장된 연산들이 무엇인지 알려준다.</p>
<p>여기서 class 리터럴은 한정적 타입 토큰 역할을 한다.</p>
<p><code>onEnumType</code> 매개변수의 선언 <code>&lt;T extends Enum&lt;T&gt; &amp; Operation&gt; Class&lt;T&gt;</code></p>
<p>-&gt; Class 객체가 열거 타입인 동시에 <code>Operation</code>의 하위 타입이어야 한다.</p>
<p>두 번째 대안은 Class 객체 대신 한정적 와일드 카드 타입인 <code>Collection&lt;? extends Operation&gt;</code>을 넘기는 방법이다.</p>
<pre><code class="language-java">public static void main(String[] args) {
  double x = 4;
  double y = 2;
  test(Arrays.asList(ExtendedOperation.values()),x , y);
}

private static void test(Collection&lt;? extends Operation&gt; opSet, double x, double y) {
  for (Operation op : opSet) {
    System.out.printf(&quot;%f %s %f = %f%n&quot;, x, op, y, op.apply(x, y));
  }
}</code></pre>
<p>이 방안의 test 메서드는 여러 구현 타입의 연산을 조합해 호출할 수 있다.</p>
<h2 id="문제점">문제점</h2>
<p>열거 타입끼리 구현을 상속할 수 없다.</p>
<p>아무 상태에도 의존하지 않는 경우에는 디폴트 구현을 이용해 인터페이스에 추가하는 방법이 있다.</p>
<p>하지만 위의 예에서는 연산 기호를 저장하고 찾는 로직이 <code>BasicOperation</code>과 <code>ExtendedOperation</code> 모두에 들어가야 한다.</p>
<p>중복량이 적으면 문제되지 않지만, 많다면 그 부분을 별도의 도우미 클래스나 정적 도우미 메서드로 분리하는 방식으로 코드 중복을 없앨 수 있다.</p>
<hr>
<h1 id="linkoption">LinkOption</h1>
<p>자바 라이브러리도 이러한 패턴을 사용한다.</p>
<p><code>java.nio.file.LinkOption</code> 열거 타입은 <code>CopyOption</code>과 <code>OpenOption</code> 인터페이스를 구현했다.</p>
<p><img src="https://github.com/NoSubject-Study/effective-java-study/assets/103320798/a57126c2-02fc-4e4e-8c9c-acd716ed25f8" alt="LinkOption"></p>
<p>뿐만 아니라, <code>CopyOption</code>의 구현체로 <code>ExtendedCopyOption</code>, <code>StandardCopyOption</code>가 있고,</p>
<p><code>OpenOption</code>의 구현체로 <code>ExtendedOpenOption</code>, <code>StandardOpenOption</code>가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SOLID, 객체 지향 설계의 5가지 원칙]]></title>
            <link>https://velog.io/@lucius__k/SOLID</link>
            <guid>https://velog.io/@lucius__k/SOLID</guid>
            <pubDate>Sat, 24 Feb 2024 08:17:30 GMT</pubDate>
            <description><![CDATA[<p>※ 2024.01 원티드 프리온보딩 백엔드 챌린지 강의를 기반으로 작성했습니다.</p>
<h1 id="solid">SOLID</h1>
<h2 id="solid-설계-원칙의-중요성">SOLID 설계 원칙의 중요성</h2>
<p>SOLID는 로버트 C. 마틴이 2000년 초반 작성한 <a href="https://web.archive.org/web/20150906155800/http:/www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf">에세이</a>에 의해 개발되었다.
좋은 설계 원칙이 없으면 소프트웨어가 경직되고, 깨지기 쉽고, 움직이지 않고, 점성이 있게 된다고 경고한다.
SOLID 원칙은 이러한 문제가 있는 설계 패턴을 해결하기 위해 개발되었다.</p>
<p>SOLID 원칙의 광범위한 목표는 개발자가 다른 영역에 영향을 주지 않고 소프트웨어의 한 영역을 변경할 수 있도록 종속성을 줄이는 것이다.
또한 디자인을 더 쉽게 이해하고, 유지 관리하고, 확장할 수 있도록 하기 위한 것이다.
궁극적으로 이러한 설계 원칙을 사용하면 소프트웨어 엔지니어가 문제를 방지하고 적응력이 뛰어나고 효과적이며 민첩한 소프트웨어를 더 쉽게 구축할 수 있다.</p>
<p>원칙에는 많은 이점이 있지만 원칙을 따르면 일반적으로 더 길고 복잡한 코드를 작성하게 된다. 즉, 디자인 프로세스를 확장하고 개발을 조금 더 어렵게 만들 수 있다. 그러나 이러한 추가적인 노력으로 인해 소프트웨어를 유지 관리, 테스트 및 확장하기가 훨씬 쉬워지기 때문에 그만한 가치가 있다.</p>
<p>이러한 원칙을 따르는 것이 만병통치약은 아니며 설계 문제를 피할 수 없다. 즉, 이 원칙을 올바르게 따를 경우 가독성, 유지 관리 용이성, 디자인 패턴 및 테스트 용이성에 대한 더 나은 코드로 이어지기 때문에 널리 사용되고 있다. 현재 환경에서 모든 개발자는 이러한 원칙을 알고 활용해야 한다.</p>
<hr>
<h2 id="단일-책임-원칙-single-responsibility-principle">단일 책임 원칙 (Single Responsibility Principle)</h2>
<blockquote>
<p>&quot;클래스는 하나의 책임이나 기능만을 담당해야 한다.
즉, 기본적으로 작은 단위와 단일 기능을 가진 클래스를 설계해야 한다.&quot;</p>
</blockquote>
<p>그렇다면 어떻게 단일 책임을 맡고 있는지 알 수 있을까?</p>
<ul>
<li>클래스에 속성, 메서드가 많아서 가독성, 유지보수에 어려움을 느낄 때</li>
<li>많은 수의 클래스에 의존하는 경우</li>
<li>클래스의 이름을 비즈니스적으로 명확하게 명명할 수 없는 경우</li>
<li>응집도가 낮은 경우 = 메서드들이 클래스의 적은 수의 속성만 사용하는 경우</li>
<li>다른 이유로 클래스를 변경해야 하는 경우</li>
</ul>
<p>위의 경우에 해당하는 경우 단일 책임 원칙을 잘 따르지 못한다고 말할 수 있다.
하지만 확실한 기준이 없으므로 각 상황에 맞게 잘 판단해야 한다.</p>
<h3 id="단일-책임-원칙이-주는-이점">단일 책임 원칙이 주는 이점</h3>
<ul>
<li><p><strong>코드 유지 관리성</strong>: 단일 책임 원칙을 준수하면 각 클래스에 특정 책임이 있으므로 특정 문제와 관련된 코드를 더 쉽게 찾고 수정할 수 있다. 이를 통해 의도하지 않은 부작용이 발생할 위험을 줄이고 유지 보수를 더 쉽게 할 수 있다.</p>
</li>
<li><p><strong>코드 재사용성</strong>: 단일 책임이 있는 클래스는 다양한 컨텍스트에서 재사용할 수 있는 경향이 있다. 다른 클래스나 코드에 대한 의존도가 낮기 때문에 다양한 시나리오에서 더 쉽게 추출하고 재사용할 수 있다.</p>
</li>
<li><p><strong>테스트 용이성</strong>: 클래스에 단일 책임이 있는 경우 집중적이고 세분화된 단위 테스트를 작성하는 것이 간단해진다. 책임이 적을수록 테스트 범위가 줄어들어, 보다 표적화되고 포괄적인 테스트가 가능하다.</p>
</li>
</ul>
<h3 id="단일-책임-원칙-적용">단일 책임 원칙 적용</h3>
<ul>
<li><p><strong>책임 식별</strong>: 단일 책임 원칙을 효과적으로 적용하려면 먼저 코드베이스 내에서 책임을 식별해야 한다. 책임은 변화의 이유로 생각할 수 있다. 클래스의 어떤 측면이 독립적으로 바뀔 수 있는지 고려하고 그것들을 별도의 책임으로 파악한다.</p>
</li>
<li><p><strong>리팩터링</strong>: 클래스 내에서 여러 책임을 식별한 후에는 각각 단일 책임이 있는 더 작고 집중적인 클래스로 리팩터링하는 것이 좋다. 이 프로세스에는 메서드, 속성을 추출하거나 완전히 새로운 클래스를 만드는 작업이 포함될 수 있다.</p>
</li>
<li><p><strong>협업과 응집도</strong>: 단일 책임 원칙을 준수하는 것과 과도한 수의 작고 고립된 클래스로 나누는 것 사이의 균형을 맞추는 것이 중요하다. 또한, 높은 수준의 협업이 이루어지고 긴밀하게 상호 작용하는 클래스는 응집력 있는 컨텍스트를 유지하기 위해 함께 그룹화되어야 한다.</p>
</li>
</ul>
<hr>
<h2 id="개방-폐쇄-원칙-open-closed-principle">개방 폐쇄 원칙 (Open-Closed Principle)</h2>
<blockquote>
<p>&quot;수정에는 닫혀있지만, 확장에는 열려있어야 한다.
다시 말해, 새로운 기능을 추가할 때 기존 코드의 수정이 아닌 새로운 코드의 작성이 이루어져야 한다.&quot;</p>
</blockquote>
<h3 id="개방-폐쇄-원칙-적용">개방 폐쇄 원칙 적용</h3>
<ul>
<li><p><strong>추상화와 다형성</strong>: 다른 클래스에서 사용할 공통 메서드가 있는 인터페이스와 추상 클래스를 정의할 수 있다. 이런 식으로 동일한 메서드 또는 클래스의 새 버전이 필요할 때마다 기본 클래스를 상속하고 새로운 구현으로 메서드를 재정의하기만하면 된다. 이러한 방식으로 이미 작동 중인 기능을 수정하지 않고도 코드를 쉽게 확장할 수 있다.</p>
</li>
<li><p><strong>종속성 주입</strong>: 구체 클래스를 상속하는 것이 항상 좋은 생각은 아니며 때로는 추상 클래스도 마찬가지다. 많은 개발자가 상속 클래스가 기본 클래스의 정확한 유형이 아닌 경우 상속보다 컴포지션을 사용할 것을 제안한다.(Effective Java Item 18: 상속보다는 컴포지션을 사용하라) 메서드에서 특정 클래스의 인스턴스화를 하는 대신 해당 클래스를 주입받아 사용할 수 있다. 이러한 방식으로 기능에 의존하는 코드를 수정하지 않고도 기능을 쉽게 교체하거나 확장할 수 있다.</p>
</li>
<li><p><strong>디자인 패턴</strong>: 개방 폐쇄 원칙 사용을 장려하는 다양한 디자인 패턴이 있다. 전략, 데코레이터 및 관찰자 패턴의 경우를 예로 들 수 있다. 이러한 디자인 패턴은 기존 코드를 수정하지 않고도 새로운 기능을 얻을 수 있게 설계되었다.</p>
</li>
</ul>
<hr>
<h2 id="리스코프-치환-원칙">리스코프 치환 원칙</h2>
<blockquote>
<p>&quot;상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.&quot;</p>
</blockquote>
<p>리스코프 치환 원칙은 상속의 동작에 중점을 둔다. 수퍼 클래스는 서브 클래스의 객체로 대체가 가능해야 한다. 서브 클래스의 경우 수퍼 클래스의 메서드 동작을 수정하여 성능을 높이거나 낮출 수 있지만 기본적인 동작에서 벗어나서는 안된다. 이 원칙은 수퍼 클래스를 서브 클래스로 대체하여 다른 방식으로 동작을 수행할 수 있는 권한을 제공하지만 입력, 출력, 예외 모두 수퍼 클래스를 따르며, 버그가 없는 코드를 보장한다.</p>
<h3 id="리스코프-치환-원칙-적용">리스코프 치환 원칙 적용</h3>
<ul>
<li><p><strong>파라미터 유효성 검사</strong>: 서브 클래스에 재정의된 메서드는 수퍼 클래스의 메서드와 동일한 입력 파라미터 값을 허용해야 한다. 즉, 서브 클래스에서 덜 엄격한 유효성 검사를 할 수는 있지만 더 엄격한 유효성 검사를 하면 안된다. 그렇지 않다면 수퍼 클래스의 메서드를 호출하는 코드가 서브 클래스와 함께 사용될 경우 예외가 발생할 수 있다.</p>
</li>
<li><p><strong>Is-a 관계</strong>: 클래스를 상속할 때 서브 클래스가 수퍼 클래스와 &quot;is-a&quot; 관계를 가져야 한다. 서브 클래스가 수퍼 클래스의 모든 메서드와 필드를 가질 수 없거나 일부 필드 또는 메서드의 기본적인 동작을 변경해야 하는 경우 상속을 하면 안 된다.</p>
</li>
<li><p><strong>인터페이스 사용</strong>: 서브 클래스에서 수퍼 클래스의 모든 메서드를 지원할 수 없는 경우가 생길 수 있다. 이럴 경우에는 상속을 받기보다 인터페이스를 이용해 구현하는 것이 좋은 방법이 될 수 있다. 이렇게 불필요한 상속을 피하고 상속에 대한 책임을 질 수 있다.</p>
</li>
</ul>
<h3 id="circle-ellipse-problem">Circle-Ellipse Problem</h3>
<p>만약 <code>Ellipse</code>클래스와 이 클래스를 상속받는 <code>Circle</code>클래스가 아래와 같이 있다고 하자.</p>
<pre><code class="language-java">public class Ellipse {

    protected double radiusX;
    protected double radiusY;

    public void setRadius(double radiusX, double radiusY) {
        this.radiusX = radiusX;
        this.radiusY = radiusY;
    }

    @Override
    public String toString() {
        return &quot;Ellipse{&quot; +
                &quot;radiusX=&quot; + radiusX +
                &quot;, radiusY=&quot; + radiusY +
                &#39;}&#39;;
    }
}

public class Circle extends Ellipse{

    public void setRadius(double radius) {
        this.radiusX = radius;
        this.radiusY = radius;
    }
}

public static void main(String[] args) {
    Ellipse ellipse = new Circle();
    ellipse.setRadius(5.0, 10,0); // 원의 정의를 깨뜨림
    System.out.println(ellipse);
}</code></pre>
<p>위의 코드를 보면 <code>Ellipse</code>타입에 구현체로 <code>Circle</code>클래스를 사용하고 있다.</p>
<p>그리고 <code>setRadius</code>메서드를 호출해 5.0과 10.0을 파라미터로 넣어주고 있는데, <code>Circle</code>은 <code>Ellipse</code>와 달리 <code>radius</code>파라미터를 하나만 받는데 <code>Ellipse</code>타입으로 사용하고 있기 때문에 <code>Ellipse</code>의 반지름을 받는 메서드를 호출하고 있는 것을 볼 수 있다.</p>
<p>그리고 해당 원을 출력하면 아래와 같은 결과를 볼 수 있다.
<code>Ellipse{radiusX=5.0, radiusY=10.0}</code></p>
<p>명백히 리스코프 치환 원칙에 위배된 코드인 것이다.</p>
<h2 id="인터페이스-분리-원칙-interface-segregation-principle">인터페이스 분리 원칙 (Interface Segregation Principle)</h2>
<blockquote>
<p>클라이언트는 자신이 사용하는 메서드에만 의존해야 한다.</p>
</blockquote>
<p>클라이언트는 자신이 사용하지 않는 메서드에 의존해서는 안된다는 것을 뜻한다.</p>
<p>예를 들어서 아래와 같은 코드가 있다.</p>
<pre><code class="language-java">public interface Worker {

    void work();

    void eat();
}

public class Developer implements Worker {
    @Override
    public void work() {
        System.out.println(&quot;개발 작업을 수행합니다.&quot;);
    }

    @Override
    public void eat() {
        System.out.println(&quot;식사를 합니다.&quot;);
    }
}

public class Robot implements Worker {
    @Override
    public void work() {
        System.out.println(&quot;로봇 작업을 수행합니다.&quot;);
    }

    @Override
    public void eat() { // 로봇은 식사를 하지 않다. 불필요한 구현

    }
}</code></pre>
<p><code>Developer</code>클래스는 <code>Worker</code>인터페이스를 온전히 구현할 수 있지만, <code>Robot</code>클래스는 온전히 구현할 수 없다. 로봇은 식사를 하지 않기 때문이다. 즉, <code>Robot</code> 클래스는 자신이 사용하지 않는 메서드에 의존을 하고 있는 것이다. 이럴 경우 인터페이스 분리 원칙을 위반하고 있는 것이다.</p>
<p>이럴 경우 아래와 같이 인터페이스를 분리하여 해결할 수 있다.</p>
<pre><code class="language-java">public interface Worker {

    void work();
}

public interface Eatable {

    void eat();
}

public class Developer implements Worker, Eatable {
    @Override
    public void work() {
        System.out.println(&quot;개발 작업을 수행합니다.&quot;);
    }

    @Override
    public void eat() {
        System.out.println(&quot;식사를 합니다.&quot;);
    }
}

public class Robot implements Worker {
    @Override
    public void work() {
        System.out.println(&quot;로봇 작업을 수행합니다.&quot;);
    }
}</code></pre>
<h2 id="의존-역전-원칙-dependency-inversion-principle">의존 역전 원칙 (Dependency Inversion Principle)</h2>
<blockquote>
<p>고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.</p>
</blockquote>
<p>먼저, 위에서 고수준 모듈과 저수준 모듈이 뜻하는 게 무엇인지 알 필요가 있다.
<strong>고수준 모듈</strong>: 의미있는 단일 기능을 제공하는 모듈
<strong>저수준 모듈</strong>: 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현</p>
<p>아래와 같은 코드가 있다.</p>
<pre><code class="language-java">public class Computer {

    private Keyboard keyboard;
    private Monitor monitor;

    public Computer(final Keyboard keyboard, final Monitor monitor) {
        this.keyboard = keyboard;
        this.monitor = monitor;
    }

    public void start() {
        keyboard.connect();
        monitor.turnOn();
        System.out.println(&quot;컴퓨터가 시작되었습니다.&quot;);
    }
}

public interface Keyboard {

    void connect();
}

public interface Monitor {

    void turnOn();
}

public class StandardKeyboard implements Keyboard {

    @Override
    public void connect() {
        System.out.println(&quot;키보드가 연결되었습니다.&quot;);
    }
}

public class LEDMonitor implements Monitor {

    @Override
    public void turnOn() {
        System.out.println(&quot;모니터가 켜졌습니다.&quot;);
    }
}

public static void main(String[] args) {
    Keyboard keyboard = new StandardKeyboard();
    Monitor monitor = new LEDMonitor();
    Computer computer = new Computer(keyboard, monitor);

    computer.start();
}</code></pre>
<p>여기서 고수준 모듈은 <code>Computer</code>로 저수준 모듈은 <code>Keyboard</code>, <code>Monitor</code>로 볼 수 있다.
그리고 <code>Computer</code>에서 직접 구현에 의존하는 것이 아닌 추상 타입인 <code>Keyboard</code>, <code>Monitor</code>에 의존하고 있다.</p>
<h3 id="의존성-역전-원칙-위반">의존성 역전 원칙 위반</h3>
<p>의존성 역전 원칙을 위반하는 코드는 아래와 같을 것이다.</p>
<pre><code class="language-java">public class Computer {

    private StandardKeyboard keyboard;
    private LEDMonitor monitor;

    ...
}</code></pre>
<p><code>Computer</code>가 <code>Keyboard</code>, <code>Monitor</code>의 구현인 <code>StandardKeyboard</code>와 <code>LEDMonitor</code>에 의존하고 있다.</p>
<p>고수준 모듈이 저수준 모듈의 구현에 의존하게 될경우 유연성, 확장성에 문제가 있다.
만약 새로운 유형의 키보드가 추가될 경우 <code>Computer</code>의 내부 구현이 변경되어야 한다.
즉, OCP를 위반하게 된다.</p>
<h1 id="references">References</h1>
<p><a href="https://www.bmc.com/blogs/solid-design-principles/">https://www.bmc.com/blogs/solid-design-principles/</a>
<a href="https://medium.com/@shashikantrbl123/single-responsibility-principle-srp-4700d3c668aa">https://medium.com/@shashikantrbl123/single-responsibility-principle-srp-4700d3c668aa</a>
<a href="https://medium.com/@shashikantrbl123/open-closed-principle-ocp-3db28cc453bb">https://medium.com/@shashikantrbl123/open-closed-principle-ocp-3db28cc453bb</a>
<a href="https://medium.com/@shashikantrbl123/liskovs-substitution-principle-lsp-ca9e218a7c54">https://medium.com/@shashikantrbl123/liskovs-substitution-principle-lsp-ca9e218a7c54</a>
<a href="https://stackify.com/solid-design-liskov-substitution-principle/">https://stackify.com/solid-design-liskov-substitution-principle/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Item37] ordinal 인덱싱 대신 EnumMap을 사용하라]]></title>
            <link>https://velog.io/@lucius__k/Item37-ordinal-%EC%9D%B8%EB%8D%B1%EC%8B%B1-%EB%8C%80%EC%8B%A0-EnumMap%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@lucius__k/Item37-ordinal-%EC%9D%B8%EB%8D%B1%EC%8B%B1-%EB%8C%80%EC%8B%A0-EnumMap%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Wed, 21 Feb 2024 15:57:33 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<h1 id="ordinal-인덱싱을-활용했을-경우">ordinal 인덱싱을 활용했을 경우</h1>
<p>식물을 간단히 나타낸 클래스가 있다.</p>
<pre><code class="language-java">public class Plant {
    enum LifeCycle {ANNUAL, PERENNIAL, BIENNIAL}

    final String name;
    final LifeCycle lifeCycle;

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

    @Override
    public String toString() {
        return name;
    }
}
</code></pre>
<p>정원에 심은 식물들을 배열 하나로 관리하고, 이들을 생애주기별로 총 3개의 집합을 만들고 정원에 있는 식물들을 해당하는 집합에 넣는다고 가정해보면.</p>
<pre><code class="language-java">Set&lt;Plant&gt;[] plantsByLifeCycle = (Set&lt;Plant&gt;[]) new Set[Plant.LifeCycle.values().length]; // 비검사 형변환
for (int i = 0; i &lt; plantsByLifeCycle.length; i++) {
    plantsByLifeCycle[i] = new HashSet&lt;&gt;();
}

for (Plant plant : plantList) {
    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>
<p>위의 같이 열거 타입의 <code>ordinal</code> 인덱싱을 활용해 코드를 작성할 수 있다.</p>
<p>동작은 하지만 문제점들이 존재한다.</p>
<ol>
<li>배열은 제네릭과 호환되지 않으니 비검사 형변환을 수행해야 한다.</li>
<li>배열은 각 인덱스의 의미를 모르니 출력 결과에 직접 의미를 달아야 한다.</li>
<li>정확한 정숫값을 사용한다는 것을 직접 보증해야 한다.</li>
</ol>
<hr>
<h1 id="해결책">해결책</h1>
<p>배열은 각 열거 타입 상수를 값으로 매핑하는 일을 한다.</p>
<p><code>Map</code>으로 매핑하여 사용할 수 있다.</p>
<p>열거 타입을 키로 사용하도록 설계한 <code>EnumMap</code>이 있다.</p>
<pre><code class="language-java">EnumMap&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 : plantList) {
    plantsByLifeCycle.get(plant.lifeCycle).add(plant);
}
System.out.println(plantsByLifeCycle);</code></pre>
<ol>
<li>성능이 배열을 사용했을 경우와 비슷하다.</li>
<li>안전하지 않은 형변환을 하지 않는다.</li>
<li>맵의 키인 열거 타입이 그 자체로 출력용 문자열을 제공하여 출력 결과에 직접 의미를 달 필요가 없다.</li>
<li>배열 인덱스를 계산하는 과정에서 오류가 날 가능성이 없다.</li>
</ol>
<hr>
<h1 id="enummap">EnumMap</h1>
<p><code>EnumMap</code>의 성능이 <code>ordinal</code>을 활용한 배열을 사용했을 경우와 비슷한 이유는 내부에서 배열을 사용하기 때문이다.</p>
<p><img src="https://github.com/NoSubject-Study/effective-java-study/assets/103320798/3be32d88-66b3-4665-9418-c3badb65532f" alt="EnumMapConstructor"></p>
<p>생성자를 보면 <code>vals = new Object[keyUniverse.length];</code> 코드를 볼 수 있는데, 매개변수로 들어온 열거 타입의 모든 상수를 가져온 뒤, 개수만큼의 크기를 가진 <code>Object</code> 배열을 생성한다.</p>
<p><img src="https://github.com/NoSubject-Study/effective-java-study/assets/103320798/8a992e88-2f25-412a-a5ef-0895b0480e77" alt="EnumMap"></p>
<p>그 후 값을 넣거나 뺄 때, 키로 들어온 열거 타입 상수의 <code>ordinal</code> 메서드를 호출한 뒤 반환된 정숫값을 인덱스로 활용한다.</p>
<p><img src="https://github.com/NoSubject-Study/effective-java-study/assets/103320798/2678c1dc-f527-40b6-8407-fe48e4758688" alt="EnumMapGet"></p>
<p><img src="https://github.com/NoSubject-Study/effective-java-study/assets/103320798/2d3d28e9-645a-4e60-91b3-495424cb3c48" alt="EnumMapPut"></p>
<hr>
<h1 id="stream-활용">Stream 활용</h1>
<p>스트림을 활용하면 코드를 더 줄일 수 있다.</p>
<pre><code class="language-java">System.out.println(plantList.stream()
                .collect(groupingBy(plant -&gt; plant.lifeCycle)));</code></pre>
<pre><code class="language-java">Map&lt;Plant.LifeCycle, List&lt;Plant&gt;&gt; map = plantList.stream()
                .collect(groupingBy(plant -&gt; plant.lifeCycle)); // HashMap 구현체를 사용</code></pre>
<p>위에서는 <code>EnumMap</code>이 아닌 맵 구현체를 사용했기 때문에 <code>EnumMap</code>을 사용했을 경우의 이점이 사라진다는 문제가 있다.</p>
<pre><code class="language-java">groupingBy(Function&lt;? super T, ? extends K&gt; classifier,
                                  Supplier&lt;M&gt; mapFactory,
                                  Collector&lt;? super T, A, D&gt; downstream);</code></pre>
<p>매개변수 3개를 가진 <code>Collectors.groupingBy</code> 메서드는 <code>mapFactory</code> 매개변수에 원하는 맵 구현체를 명시해 호출할 수 있다.</p>
<pre><code class="language-java">System.out.println(plantList.stream()
                .collect(groupingBy(plant -&gt; plant.lifeCycle, 
                        () -&gt; new EnumMap&lt;&gt;(Plant.LifeCycle.class), toSet())));</code></pre>
<pre><code class="language-java">EnumMap&lt;Plant.LifeCycle, Set&lt;Plant&gt;&gt; map = plantList.stream()
                .collect(groupingBy(plant -&gt; plant.lifeCycle,
                        () -&gt; new EnumMap&lt;&gt;(Plant.LifeCycle.class), toSet()));</code></pre>
<h2 id="enummap을-활용했을-경우와의-차이">EnumMap을 활용했을 경우와의 차이</h2>
<p>Stream을 활용하게 되면 특정 열거 타입에 해당하는 객체가 존재하지 않으면 해당 키를 만들지 않게 된다.</p>
<pre><code class="language-java">Plant annual = new Plant(&quot;annual&quot;, Plant.LifeCycle.ANNUAL);
Plant perennial = new Plant(&quot;perennial&quot;, Plant.LifeCycle.PERENNIAL);
List&lt;Plant&gt; plantList = List.of(annual, perennial);
System.out.println(plantList.stream()
                .collect(groupingBy(plant -&gt; plant.lifeCycle,
                        () -&gt; new EnumMap&lt;&gt;(Plant.LifeCycle.class), toSet())));</code></pre>
<p>위의 코드를 실행하면 아래와 같이 출력된다.</p>
<pre><code>{ANNUAL=[annual], PERENNIAL=[perennial]}</code></pre><p>하지만 위의 <code>EnumMap</code>을 활용할 경우의 코드에서는 특정 열거 타입에 해당하는 객체가 존재하지 않아도 해당 열거 타입의 키를 만들게 된다.</p>
<pre><code class="language-java">Plant annual = new Plant(&quot;annual&quot;, Plant.LifeCycle.ANNUAL);
Plant perennial = new Plant(&quot;perennial&quot;, Plant.LifeCycle.PERENNIAL);
List&lt;Plant&gt; plantList = List.of(annual, perennial);
EnumMap&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 : plantList) {
    plantsByLifeCycle.get(plant.lifeCycle).add(plant);
}
System.out.println(plantsByLifeCycle);</code></pre>
<p>위의 코드를 실행하면 아래와 같이 출력된다.</p>
<pre><code>{ANNUAL=[annual], PERENNIAL=[perennial], BIENNIAL=[]}</code></pre><hr>
<h1 id="중첩-배열의-경우">중첩 배열의 경우</h1>
<p>아래에 상태가 열거 타입으로 존재하고 두 상태와 전이를 매핑한 코드가 있다.</p>
<pre><code class="language-java">public enum Phase {
    SOLID, LIQUID, GAS;

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

        // 행은 from의 ordinal을, 열은 to의 ordinal을 인덱스로 사용한다.
        private static final Transition[][] TRANSITIONS = {
                {null, MELT, SUBLIME},
                {FREEZE, null, BOIL},
                {DEPOSIT, CONDENSE, null}
        };

        // 한 상태에서 다른 상태로의 전이를 반환한다.
        public static Transition from(Phase from, Phase to) {
            return TRANSITIONS[from.ordinal()][to.ordinal()];
        }
    }
}</code></pre>
<p>위의 코드도 <code>ordinal</code>을 이용하고 있으므로 마찬가지로 아래와 같은 문제가 존재한다.</p>
<p>컴파일러는 <code>ordinal</code>과 배열 인덱스의 관계를 알지 못하므로 <code>Phase</code>, <code>Trasition</code> 열거 타입을 수정하게 될 경우 <code>TRANSITIONS</code>를 함께 수정하지 않거나 잘못 수정하면 런타임 오류가 일어날 수 있다.
(<code>ArrayIndexOutOfBoundsException</code>, <code>NullPointerException</code> 예외가 발생할 수 있고 혹은 이상하게 동작할 수도 있다.)</p>
<p>상태의 가짓수가 늘어나면 <code>TRANSITIONS</code>의 크기도 제곱해서 커지며 <code>null</code>로 채워지는 칸도 늘어날 것이다.</p>
<p>이 경우에도 <code>EnumMap</code>을 활용하는 편이 좋다.</p>
<pre><code class="language-java">public enum Phase {
    SOLID, LIQUID, GAS;

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

        private final Phase from;
        private final Phase to;

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

        // 맵을 초기화한다.
        private static final 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>
<p>여기에 새로운 상태인 플라즈마(PLASMA)를 추가한다고 가정해보자.</p>
<p>이 상태와 연결된 전이는 두 가지인데,</p>
<p>기체에서 플라즈마로 변하는 이온화(IONIZE),</p>
<p>플라즈마에서 기체로 변하는 탈이온화(DEIONIZE)다.</p>
<p>만일 배열로 만든 코드에 추가한다면, <code>Phase</code>에 1개, <code>Phase.Transition</code>에 2개를 추가하고, 원소 9개를 가진 배열들의 배열을 원소 16개로 교체해야 한다.</p>
<p>하지만 <code>EnumMap</code>을 활용한 코드에서는 아래와 같이 상태 목록에 <code>PLASMA</code>를 추가하고, 전이 목록에 <code>IONIZE(GAS, PLASMA)</code>와 <code>DEIONIZE(PLASMA, GAS)</code>만 추가하면 된다.</p>
<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);

        ... // 나머지 코드는 그대로다.
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Item36] 비트 필드 대신 EnumSet을 사용하라]]></title>
            <link>https://velog.io/@lucius__k/Item36-%EB%B9%84%ED%8A%B8-%ED%95%84%EB%93%9C-%EB%8C%80%EC%8B%A0-EnumSet%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@lucius__k/Item36-%EB%B9%84%ED%8A%B8-%ED%95%84%EB%93%9C-%EB%8C%80%EC%8B%A0-EnumSet%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Fri, 16 Feb 2024 07:51:00 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<p>열거한 값들이 집합으로 사용될 경우, 예전에는 각 상수에 서로 다른 2의 거듭제곱 값을 할당한 정수 열거 패턴을 사용해왔다.</p>
<h1 id="비트-필드bit-field">비트 필드(bit field)</h1>
<pre><code class="language-java">public class Text {
  public static final int STYLE_BOLD = 1 &lt;&lt; 0; // 1 -&gt; 0001
  public static final int STYLE_ITALIC = 1 &lt;&lt; 1; // 2 -&gt; 0010
  public static final int STYLE_UNDERLINE = 1 &lt;&lt; 2; // 4 -&gt; 0100
  public static final int STYLE_STRIKETHROUGH = 1 &lt;&lt; 3; // 8 -&gt; 1000

  // 매개변수 styles는 0개 이상의 STYLE_ 상수를 비트별 OR한 값이다.
  public void applyStyles(int styles) {...}
}</code></pre>
<p><code>text.applyStyles(STYLE_BOLD | STYLE_ITALIC);</code></p>
<p>위와 같은 식으로 비트별 OR 연산을 사용해 여러 상수를 하나의 집합으로 모을 수 있으며, 이렇게 만들어진 집합을 비트 필드(bit field)라고 한다.</p>
<p>위의 예시에서 비트 필드</p>
<blockquote>
<p><code>STYLE_BOLD | STYLE_ITALIC</code> -&gt; 0011 -&gt; 3</p>
<p><code>STYLE_BOLD | STYLE_UNDERLINE</code> -&gt; 0101 -&gt; 5</p>
<p>...</p>
</blockquote>
<p>비트 필드를 사용하면 집합 연산을 효율적으로 수행할 수 있다.</p>
<hr>
<h2 id="비트-필드의-단점">비트 필드의 단점</h2>
<ul>
<li><p>해석하기 어렵다.</p>
<p>3을 보고 <code>STYLE_BOLD | STYLE_ITALIC</code> 을 해석하기 어렵다.</p>
</li>
<li><p>비트 필드 하나에 녹아 있는 모든 원소를 순회하기 어렵다.</p>
<p>비트 필드에 어떤 원소가 들어있는지 해석하기가 까다롭고 해석하여 어떤 원소들이 들어있는지 파악하더라고 순회하기 어렵다.</p>
</li>
<li><p>최대 몇 비트가 필요한지 미리 예측하여 적절한 타입(int, long)을 선택해야 한다.</p>
<p>위에서는 4개의 상수만을 가졌지만 상수가 더욱 많아질 경우 자료형 선택이 까다로워진다.</p>
</li>
</ul>
<hr>
<h1 id="enumset">EnumSet</h1>
<p><code>EnumSet</code> 클래스를 이용하여 열거 타입 상수의 값으로 구성된 집합을 효과적으로 표현해준다.</p>
<p>원소가 총 64개 이하인 경우, <code>long</code> 타입 변수에 원소들을 표현하므로 비트 필드에 비견되는 성능을 보여준다.</p>
<p><img src="https://github.com/NoSubject-Study/effective-java-study/assets/103320798/ab571303-2dcd-46a3-89d1-ccae459c20a7" alt="noneOf"></p>
<p><code>EnumSet</code>의 경우 추상 클래스이며 원소의 개수에 따라 다른 구현체를 제공하는데,</p>
<p>원소의 개수가 64개 이하인 경우, 위의 사진 처럼 <code>RegularEnumSet</code>을 생성하는데,</p>
<p><img src="https://github.com/NoSubject-Study/effective-java-study/assets/103320798/144e3257-1649-4347-b4ba-3a90b949d404" alt="regularenumset"></p>
<p><code>elements</code> 변수가 <code>long</code> 타입으로 선언되어 있다.</p>
<hr>
<h2 id="열거-타입과-enumset을-사용할-경우">열거 타입과 EnumSet을 사용할 경우</h2>
<pre><code class="language-java">public class Text  {
  public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

  // 어떤 Set을 넘겨도 되나, EnumSet이 가장 좋다.
  public void applyStyles(Set&lt;Style&gt; styles) {...}
}</code></pre>
<p><code>text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));</code></p>
<p>위의 코드는 <code>EnumSet</code> 인스턴스를 사용하는 클라이언트 코드이다.</p>
<p><code>EnumSet</code>은 다양한 기능의 정적 팩터리를 제공한다.</p>
<p><code>EnumSet</code>을 사용하면 비트 필드를 사용했을 경우의 단점들을 볼 수 없다.</p>
<p>열거 타입의 인스턴스가 그대로 넘어오기 때문에 해석하기 편하며, 자료형을 <code>EnumSet</code>이 결정해주므로 자료형에 대해 고민할 필요가 없어진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 이해하기]]></title>
            <link>https://velog.io/@lucius__k/2024.01-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80-1%EC%B0%A8</link>
            <guid>https://velog.io/@lucius__k/2024.01-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80-1%EC%B0%A8</guid>
            <pubDate>Fri, 19 Jan 2024 11:40:04 GMT</pubDate>
            <description><![CDATA[<p>※ 2024.01 원티드 프리온보딩 백엔드 챌린지 강의를 기반으로 작성했습니다.</p>
<h1 id="소프트웨어의-가치">소프트웨어의 가치</h1>
<p>소프트웨어는 사람들에게 새로운 가치를 제공한다.
그리고 소프트웨어는 시간이 지남에 따라 변화하는 요구사항에 적응해야 한다.</p>
<p>이를 위해서 개발자가 할 수 있는 일은 당연히 새로운 가치를 제공할 수 있고, 변화하는 요구사항에 적응할 수 있는 소프트웨어를 만드는 것이다.</p>
<p>그럼 변화하는 요구사항에 적응할 수 있는 소프트웨어를 어떻게 만들 수 있을까?
변화하는 요구사항의 특징은 다음과 같다.</p>
<ol>
<li>유연성</li>
<li>확장성</li>
<li>유지 보수성</li>
</ol>
<p>위의 3가지 특징을 갖고 있다. 높은 유연성, 확장성, 유지 보수성을 확보하는 방법 중 객체 지향 프로그래밍을 통해 확보하는 방법을 알아보자.</p>
<hr>
<h1 id="의존">의존</h1>
<p>의존이라는 말은 객체 지향 언어를 공부하게 되면 자주 듣게 된다.</p>
<p>의존의 사전적 정의는 다음과 같다.</p>
<blockquote>
<p>어떠한 일을 자신의 힘으로 하지 못하고, 다른 어떤 것의 도움을 받아 의지하다.</p>
</blockquote>
<p>그렇다면 코드에서는 의존이 어떻게 표현될까?</p>
<ol>
<li>객체 참조에 의한 연관 관계</li>
<li>메서드 반환 타입이나 파라미터로서의 의존 관계</li>
<li>상속에 의한 의존 관계</li>
<li>구현에 의한 의존 관계</li>
</ol>
<hr>
<h2 id="객체-참조에-의한-연관-관계">객체 참조에 의한 연관 관계</h2>
<pre><code class="language-java">public class A {

    private B b;

    public void someMethod() {
        b.someMethod();
    }
}

public class B {

    public void someMethod() {
        ...
    }
}</code></pre>
<p><code>A</code>클래스가 멤버 변수로 <code>B</code>를 갖고 있다.
이때 <code>A</code>클래스는 <code>B</code>클래스에 의존을 하고 있다고 말할 수 있다.</p>
<hr>
<h2 id="메서드-반환-타입이나-파라미터로서의-의존-관계">메서드 반환 타입이나 파라미터로서의 의존 관계</h2>
<pre><code class="language-java">public class ClassA {

    public ClassC someMethod(ClassB b) {
        return b.someMethod();
    }
}

public class ClassB {

    public ClassC someMethod() {
        return new ClassC();
    }
}

public class ClassC [
    ...
}</code></pre>
<p><code>ClassA</code>의 <code>someMethod</code>메서드를 보면 반환 타입이 <code>ClassC</code>이고, 파라미터로서 <code>ClassB</code>를 받고 있다. 이렇게 되면 <code>ClassA</code>는 <code>ClassB</code>, <code>ClassC</code>에 의존한다고 할 수 있다. 그리고 <code>ClassB</code>도 마찬가지로 <code>someMethod</code>메서드의 반환 타입이 <code>ClassC</code>이므로 <code>ClassB</code>는 <code>ClassC</code>에 의존하고 있다고 할 수 있다.</p>
<hr>
<h2 id="상속에-의한-의존-관계">상속에 의한 의존 관계</h2>
<pre><code class="language-java">public class SuperClass {

    public void functionInSuper() {
        ...
    }
}

public class subClass extends SuperClass {

    @Override
    public void functionInSuper() [
        ...
    }
}</code></pre>
<p><code>subClass</code>는 <code>SuperClass</code>를 상속받고 있다. 이 경우에도 <code>subClass</code>는 <code>SuperClass</code>에 의존하고 있다.</p>
<hr>
<h2 id="구현에-의한-의존-관계">구현에 의한 의존 관계</h2>
<pre><code class="language-java">public interface InterfaceA {
    void functionInInterfaceA();
}

public class ClassB implements InterfaceA {

    @Override
    public void functionInInterfaceA() {
        ...
    }
}</code></pre>
<p><code>ClassB</code>는 <code>InterfaceA</code>를 구현하고 있다. 이때도 <code>ClassB</code>는 <code>InterfaceA</code>를 의존하고 있는 것이다.</p>
<hr>
<h2 id="의존이-가지는-진짜-의미">의존이 가지는 진짜 의미</h2>
<p>의존이 가지는 진짜 의미는 변경 전파 가능성이다.
<code>A</code>가 <code>B</code>를 의존하고 있을 경우 <code>B</code>에 변경이 있다면 <code>A</code>도 해당 변경에 의해 변경될 가능성이 있는 것이다.
그러므로 우리는 필요한 의존만 유지하며 의존성을 최소화해야 한다.</p>
<hr>
<h1 id="절차지향-프로그래밍과-객체-지향-프로그래밍의-차이">절차지향 프로그래밍과 객체 지향 프로그래밍의 차이</h1>
<h2 id="절차지향">절차지향</h2>
<p>절차지향은 아래와 같은 특징이 있다.</p>
<ul>
<li>프로시저에 중점을 둔다.</li>
<li>프로그램은 일련의 절차적 단계로 구성되고, 데이터와 프로시저가 별도로 존재한다.</li>
</ul>
<hr>
<h2 id="객체-지향">객체 지향</h2>
<p>객체 지향은 데이터와 기능이 하나의 객체에 묶여있다.</p>
<hr>
<h2 id="의존이라는-관점에서의-해석">의존이라는 관점에서의 해석</h2>
<p>코드로 의존을 바라보자.
절차 지향은 순서대로 입금, 출금, 잔액 출력의 절차를 진행한다.</p>
<pre><code class="language-java">public static void main(String[] args) {
    int accountBalance = 10000;
    // 입금
    accountBalance = deposit(accountBalance, 5000);

    // 출금
    accountBalance = withdraw(accountBalance, 3000);

    // 잔액 출력
    System.out.println(accountBalance);
}

public static int deposit(int balance, int amount) {
    return balance + amount;
}

public static int withdraw(int balance, int amount) {
    if (balance &lt; amount) {
        System.out.println(&quot;잔액이 부족합니다.&quot;);
        return balance;
    } else {
        return balance - amount;
    }
}</code></pre>
<p>위 코드를 객체 지향으로 바꾸게 되면</p>
<pre><code class="language-java">public class BankAccount {

    private int balance;

    public BankAccount(int balance) {
        this.balance = balance;
    }

    public void deposit(int amount) {
        this.balance += amount;
    }

    public void withdraw(int amount) {
        if (balance &lt; amount) {
            System.out.println(&quot;잔액이 부족합니다.&quot;);
        } else {
            this.balance -= amount;
        }
    }

    public void printBalance() {
        System.out.println(balance);
    }
}

public class Main {

    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount(10000);

        // 입금
        bankAccount.deposit(5000);

        // 출금
        bankAccount.withdraw(3000);

        // 잔액 출력
        bankAccount.printBalance();
    }
}</code></pre>
<p>위의 코드처럼 나타낼 수 있는데 <code>BankAccount</code>클래스로 객체화 하여 이 객체는 잔액이라는 상태와 입금, 출금, 잔액 출력이라는 기능을 갖고 있다.</p>
<p>그렇다면 여기서 VIP에게는 이체 수수료가 면제된다는 요구사항이 추가됐다고 가정해보자.</p>
<p>절차지향의 경우는 아래와 같을 것이다.</p>
<pre><code class="language-java">public static void main(String[] args) {
    int accountBalance = 10000;
    int bankFee = 1000;
    boolean isVIP = true;
    // 입금
    accountBalance = deposit(accountBalance, 5000);

    // 수수료 적용
    if (!isVIP) {
        accountBalance = applyBankFee(accountBalance, bankFee);
    }

    // 출금
    accountBalance = withdraw(accountBalance, 3000);

    // 수수료 적용
    if (!isVIP) {
        accountBalance = applyBankFee(accountBalance, bankFee);
    }


    // 잔액 출력
    System.out.println(accountBalance);
}

public static int deposit(int balance, int amount) {
    return balance + amount;
}

public static int withdraw(int balance, int amount) {
    if (balance &lt; amount) {
        System.out.println(&quot;잔액이 부족합니다.&quot;);
        return balance;
    } else {
        return balance - amount;
    }
}

public static int applyBankFee(int balance, int fee) {
    return balance - fee;
}</code></pre>
<p>요구사항이 추가되면 실제 실행되는 코드에 변경이 생기게 된다.</p>
<p>반면에 객체 지향 코드를 보면</p>
<pre><code class="language-java">public class BankAccount {

    private int balance;
    private boolean isVIP;
    private int bankFee = 1000;

    public BankAccount(int balance, boolean isVIP) {
        this.balance = balance;
        this.isVIP = isVIP;
    }

    public void deposit(int amount) {
        this.balance += amount;
        if (!isVIP) {
            applyBankFee();
        }
    }

    private void applyBankFee() {
        this.balance -= bankFee;
    }

    public void withdraw(int amount) {
        if (balance &lt; amount + (isVIP ? 0 : bankFee)) {
            System.out.println(&quot;잔액이 부족합니다.&quot;);
        } else {
            this.balance -= amount;
            if (!isVIP) {
                applyBankFee();
            }
        }
    }

    public void printBalance() {
        System.out.println(balance);
    }
}

public class Main {

    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount(10000, true); // 기본 잔액과 VIP 고객 여부

        // 입금
        bankAccount.deposit(5000);

        // 출금
        bankAccount.withdraw(3000);

        // 잔액 출력
        bankAccount.printBalance();
    }
}</code></pre>
<p><code>BankAccount</code>에 요구사항에 맞는 변화가 생기게 되고 실제 실행되고 있는 코드는 거의 변하지 않았음을 알 수 있다.</p>
<p>이를 통해서 알 수 있는 것은 객체 지향적인 설계를 통해서 의존을 다룰 수 있고 이를 통해 변경이 전파되는 것을 최소화할 수 있다.</p>
<p>객체는 자체적으로 상태와 행동을 갖고 있고, 이를 외부에서 &#39;어떠한 행동을 할 줄 안다.&#39;의 시선으로 바라볼 뿐이라 내부에서 어떻게 행동을 하는지 모르기 때문에 객체 밖으로 변경이 전파되지 않는 것이다.</p>
<p>그렇다면 객체 지향 설계가 의존을 다루는 핵심은 무엇일까?</p>
<hr>
<h1 id="객체-지향-핵심-이해하기">객체 지향 핵심 이해하기</h1>
<p>객체 지향 프로그래밍의 선구자인 앨런 케이는 다음의 3가지를 객체 지향의 핵심이라고 말한다.</p>
<ul>
<li>Message Passing</li>
<li>Encapulation</li>
<li>Dynamic Binding</li>
</ul>
<hr>
<h2 id="message-passing">Message Passing</h2>
<p>클라이언트와 서버의 입장에서 말을 하면,
클라이언트는 자신의 목적을 달성하기 위해 어떤 API를 호출해야 하는지 알고 있지만, 서버가 구체적으로 어떻게 일을 하는지 모른다.
서버는 그저 들어온 요청에 맞는 자신이 알고있는, 할 수 있는 일을 할 뿐이다.
클라이언트와 서버를 객체로 표현하면,</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/50ad1885-2062-4cba-b3ab-e799a3e335b3/image.png" alt="object"></p>
<p>위의 그림처럼 뒤에서 무슨 일이 일어나는지 관심이 없고 그저 응답을 주고, 받을 뿐이다.</p>
<p>커피 주문하는 코드를 예시로 보면,</p>
<pre><code class="language-java">public class Customer {

    public void order(Barista barista, String coffeeType) {
        barista.makeCoffee(coffeeType);
    }
}

public class Barista {

    public void makeCoffee(String coffeeType) {
        System.out.printf(&quot;%s 커피를 만드는 중입니다.&quot;, coffeeType);
    }
}

public void main(String[] args) {
    Customer customer = new Customer();
    Barista barista = new Barista();
    customer.order(barista, &quot;아메리카노&quot;); // 메시지 패싱 예시
}</code></pre>
<p>커피를 주문하라는 메시지(<code>order</code>)를 <code>Customer</code>에게 보내고 <code>Customer</code>는 커피를 만들라는 메시지(<code>makeCoffee</code>)를 <code>Barista</code>에게 전달한다.
이렇듯 메시지는 명령 + 인자로 이루어져있다. 코드상에 <code>Customer</code>는 <code>Barista</code>가 어떻게 커피를 만드는지 전혀 알지 못한다. 그저 만들라는 메시지를 보내고 커피를 받기만 하면 된다. 그리고 메시지를 전달받은 <code>Barista</code>는 누가 메시지를 보냈는지 중요하지 않고 그저 메시지를 전달받고 커피를 만들 뿐이다.</p>
<hr>
<h2 id="encapsulation">Encapsulation</h2>
<p>Encapsulation은 캡슐화라는 의미로, 객체의 내부 상태와 동작을 외부로부터 숨기는 방법이다.</p>
<p>캡슐화를 통해서 다음과 같은 이점을 얻을 수 있다.</p>
<hr>
<h3 id="낮은-결합도">낮은 결합도</h3>
<p>낮은 결합도를 얻을 수 있는데 이는 변경을 더 쉽게 할 수 있음을 뜻한다.</p>
<p>높은 결합도의 코드의 예시는 다음과 같다.</p>
<pre><code class="language-java">class HighCouplingClass {
    int data = 10;
}

class AnotherHighCouplingClass {
    int data;

    public AnotherHighCouplingClass(HighCouplingClass hc) {
        this.data = hc.data;
    }
}

HighCouplingClass hc = new HighCouplingClass();
AnotherHighCouplingClass ahc = new AnotherHighCouplingClass(hc);</code></pre>
<p>위의 코드를 보면 <code>AnotherHighCouplingClass</code>는 <code>HighCouplingClass</code>의 인스턴스를 직접 참조하고 있다.</p>
<p>낮은 결합도의 코드의 예시를 보면,</p>
<pre><code class="language-java">class LowCouplingClass {
    int data = 10;
}

class AnotherLowCouplingClass {
    int data;

    public AnotherLowCouplingClass(int data) {
        this.data = data;
    }
}

LowCouplingClass lc = new LowCouplingClass();
AnotherLowCouplingClass alc = new AnotherLowCouplingClass(lc.data);</code></pre>
<p>이번에는 <code>AnotherLowCouplingClass</code>가 <code>LowCouplingClass</code> 인스턴스를 직접 참조하는 것이 아니라 <code>int</code> 타입의 데이터를 받고 있다.</p>
<p>여기서 만약 코드의 변경이 있다고 하고, 높은 결합도의 코드의 경우에는</p>
<pre><code class="language-java">class HighCouplingClass {
    String data = &quot;10&quot;;
}

class AnotherHighCouplingClass {
    int data;

    public AnotherHighCouplingClass(HighCouplingClass hc) {
        this.data = Integer.parseInt(hc.data);
    }
}

HighCouplingClass hc = new HighCouplingClass();
AnotherHighCouplingClass ahc = new AnotherHighCouplingClass(hc);</code></pre>
<p>위의 코드처럼 되는데 <code>HighCouplingClass</code>의 <code>data</code> 변수의 타입을 <code>String</code>으로 바꿨을 뿐인데, 이를 사용하는 <code>AnotherHighCouplingClass</code>의 생성자 내부의 코드도 변경이 일어나는 것을 볼 수 있다. 이처럼 객체 참조를 하게 되면 의존이 전파될 수 있다.</p>
<p>낮은 결합도를 가진 코드의 경우에는</p>
<pre><code class="language-java">class LowCouplingClass {
    String data = 10;
}

class AnotherLowCouplingClass {
    int data;

    public AnotherLowCouplingClass(int data) {
        this.data = data;
    }
}

LowCouplingClass lc = new LowCouplingClass();
AnotherLowCouplingClass alc = new AnotherLowCouplingClass(Integer.parseInt(lc.data));</code></pre>
<p>위처럼 <code>LowCouplingClass</code>에서 변경이 일어나도 <code>AnotherLowCouplingClass</code>에서는 변경이 일어나지 않는 것을 볼 수 있다.</p>
<hr>
<h3 id="자율적인-객체">자율적인 객체</h3>
<p>소통은 인터페이스로 하고, 구현은 클래스 내부에서 마음대로 할 수 있게 된다.</p>
<pre><code class="language-java">public class Car {

    private int speed;
    private int fuelLevel;

    public void accelerate() {
        speed += 1;
    }

    public void brake() {
        speed -= 1;
    }

    public void turnLeft() {
        // turn left...
    }

    public void turnRight() {
        // turn right...
    }
}</code></pre>
<p><code>Car</code> 객체와 소통하려는 다른 객체는 오직 노출된 인터페이스(현재의 경우에는 메서드)만을 통해서 소통이 가능하다.
만일 차가 브레이크를 밟으면 로그를 남긴다는 요구사항이 새로 생길 경우 <code>brake</code>메서드 내부에 로깅을 하는 코드만 작성하면 되므로 외부에 변경을 전파하지 않게 된다.</p>
<hr>
<h3 id="dynamic-binding">Dynamic Binding</h3>
<p>동적 바인딩(Dynamic Binding)은 런타임 시점에 참조 변수와 실제 객체 타입을 확인하여 함수를 호출하는 방식이다.
동적 바인딩은 다형성이 적용된 코드에서 발생하는 하나의 현상으로 볼 수 있는데 다형성이란 하나의 참조 변수로 여러 개의 객체를 참조할 수 있는 특성을 뜻한다.</p>
<p>객체 지향에서 다형성을 사용하면 다른 객체에게 보내는 메시지가 실제로 어떤 메서드를 호출할 지 런타임에 결정된다.</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/266bc61a-f54b-4c85-a3c2-1ae87d4c4b54/image.png" alt="polymorphism"></p>
<p>위의 그림은 다형성을 나타내는 그림인데 코드로 나타내면 아래와 같다.</p>
<pre><code class="language-java">public class Coffee {
    private String name;
    public Coffee(String name) {
        this.name = name;
    }
}

public class CoffeeMachine implements CoffeeMaker {

    @Override
    public Coffee makeCoffee(String coffeeType) {
        System.out.printf(&quot;커피머신이 %s 커피를 만드는 중입니다.&quot;, coffeeType);
        return new Coffee(coffeeType);
    }
}

public class HandDrip implements CoffeeMaker {

    @Override
    public Coffee makeCoffee(String coffeeType) {
        System.out.printf(&quot;핸드드립으로 %s 커피를 만드는 중입니다.&quot;, coffeeType);
        return new Coffee(coffeeType);
    }
}

public interface CoffeeMaker {

    Coffee makeCoffee(String coffeeType);
}

public class Customer {
    public void order(Barista barista, String coffeeType) {
        LocalDateTime now = LocalDateTime.now();
        barista.makeCoffee(now, coffeeType);
    }
}
</code></pre>
<pre><code class="language-java">public class Barista {

    private CoffeeMaker determineCoffeeMaker(LocalDatetime now) {
        if (now.hour &lt; 8 || now.hour &gt; 20) {
            return new CoffeeMachine();
        } else {
            return new HandDrip();
        }
    }

    public Coffee makeCoffee(LocalDateTime now, String coffeeType) {
        System.out.printf(&quot;%s 커피를 만드는 중입니다.&quot;, coffeeType);
        CoffeeMaker coffeeMaker = determineCoffeeMaker(now);
        return coffeeMaker.makeCoffee(coffeeType);
    }
}

public static void main(String[] args) {
    Customer customer = new Customer();
    Barista barista = new Barista();
    customer.order(barista, &quot;아메리카노&quot;);
}</code></pre>
<p><code>Customer</code>가 커피를 주문할 경우 <code>Barista</code>는 커피머신으로 커피를 탈지, 핸드드립으로 커피를 탈지 실제로 타는 시간에 따라 결정되는 것을 볼 수 있다.
이처럼 다형성을 이용하면 런타임에 실제 메시지를 결정할 수 있게 된다.</p>
<hr>
<h1 id="객체의-협력-책임-역할-개념-이해하기">객체의 협력, 책임, 역할 개념 이해하기</h1>
<p>객체는 Message Passing을 통해 요청하고 협력한다.</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/f32eeb3f-b442-453a-b805-b5c20c303b03/image.png" alt="message"></p>
<p><code>Customer</code>는 <code>Barista</code>에게 커피를 만드는 것을 요청했다.
이는 <code>Customer</code>는 <strong><code>Barista</code>가 커피를 만들 수 있다는 것을 알고 있음</strong>을 뜻하고, <code>Customer</code>는 <strong>커피를 만들도록 시켰다</strong>는 것을 뜻한다.
그리고 <code>Barista</code>는 <strong>자신이 커피를 만들 수 있다는 것을 알고 있고, 할 수 있다</strong>는 것이다.</p>
<p>이로써 <code>Customer</code>의 책임은</p>
<ul>
<li><code>Barista</code>가 무엇을 하는지 아는 것</li>
<li><code>Barista</code>가 할 줄 아는 것을 시키는 것</li>
</ul>
<p><code>Barista</code>의 책임은</p>
<ul>
<li>커피를 만드는 방법을 아는 것</li>
<li>커피를 만드는 것</li>
</ul>
<p>이렇게 볼 수 있다. 위처럼 책임은 객체가 무엇을 할 수 있는지, 객체가 무엇을 아는지에 따라 달라진다.</p>
<p>그리고 객체는 무언가를 할 줄 아는 객체에게 그에 대한 책임을 할당하게 된다.
단, 어떻게 할 줄 아는지는 모르고, 그저 할 줄 안다는 것만 안다.</p>
<p><code>makeCoffee</code>라는 메시지의 책임이 왜 <code>Barista</code>에게 할당되었는지에 대해 생각해보면 <code>Barista</code>가 커피를 만드는 방법을 알기 때문이다.</p>
<p>그리고 <code>CoffeeMaker</code>가 커피를 만드는 역할을 하게 된다.
위에서 <code>CoffeeMaker</code>가 <code>CoffeeMachine</code>이 될 수도, <code>HandDrip</code>이 될 수도 있었는데 이렇듯 역할은 구체적인 객체를 바꿔 끼울 수 있는 슬롯을 뜻한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Item65] 리플렉션보다는 인터페이스를 사용하라]]></title>
            <link>https://velog.io/@lucius__k/Item65-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98%EB%B3%B4%EB%8B%A4%EB%8A%94-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@lucius__k/Item65-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98%EB%B3%B4%EB%8B%A4%EB%8A%94-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Fri, 19 Jan 2024 10:03:05 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<h1 id="리플렉션">리플렉션</h1>
<p>자바에는 리플렉션이라는 기능을 이용해 알 지 못하는 클래스를 생성하거나 메서드를 호출할 수 있다. 하지만 유용해보이는 리플렉션에 많은 단점이 존재한다.</p>
<pre><code class="language-java">public class TestClass {

     public static void main(String[] args) {
         Class&lt;?&gt; clazz = Class.forName(&quot;생성하려는 클래스 이름&quot;);
        Object obj = clazz.getConstructor().newInstance(); // 객체 생성
     }
}</code></pre>
<h1 id="리플렉션의-단점">리플렉션의 단점</h1>
<h2 id="컴파일-타임-타입-검사가-주는-이점을-하나도-누릴-수-없다">컴파일 타임 타입 검사가 주는 이점을 하나도 누릴 수 없다.</h2>
<p>리플렉션을 이용하여 메서드를 호출한다고 가정해보자.
만약 호출하려는 메서드가 클래스에 존재하지 않으면 어떻게 될까?</p>
<pre><code class="language-java">public class TestClass {

    public static void main(String[] arags) throws Exception {
        TestClass testClass = new TestClass();
        Method testMethod = TestClass.class.getMethod(&quot;unknownMethod&quot;); // 존재하지 않는 unknownMethod 메서드
        testMethod.invoke(testClass); // 메서드 호출
    }
}</code></pre>
<p>위의 코드를 실행하면 아래와 같은 예외가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/2a428090-43cc-42ee-8057-9a84e0f7f512/image.png" alt="exception">
<code>NoSuchMethodException</code>, 해당 메서드가 존재하지 않다는 것이다.
이렇듯 리플렉션을 이용하지 않았으면 컴파일 시점에 알 수 있던 문제를 런타임 시에 알 수 있다.</p>
<p>또 예외 검사도 마찬가지다.</p>
<pre><code class="language-java">public class TestClass {

    public void doSomething() throws IOException {
        ...
    }

    public static void main(String[] args) {
        try {
            TestClass testClass = new TestClass();
            testClass.doSomething();
        } catch (IOException e) {
            System.out.println(&quot;IOException 발생!!&quot;);
        }
    }
}</code></pre>
<p>위와 같이 <code>doSomething</code> 메서드를 호출하게 되면 <strong>Checked Exception</strong>인 <code>IOException</code>으로 인해 예외를 다뤄야 하는데 이를 리플렉션을 이용해 호출하게 되면</p>
<pre><code class="language-java">public class TestClass {

    public void doSomething() throws IOException {
        ...
    }

    public static void main(String[] args) {
        TestClass testClass = new TestClass();
        Method doSomething = TestClass.class.getMethod(&quot;doSomething&quot;);
        doSomeThing.invoke(testClass); // 예외 검사 X
    }
}</code></pre>
<p>위와 같이 예외 검사를 할 수 없게 된다.</p>
<h2 id="성능이-저하된다">성능이 저하된다.</h2>
<pre><code class="language-java">public class TestClass {
    public int testMethod(int value) {
        return value;
    }
}</code></pre>
<p>위에 int 타입의 정수를 받아 그대로 반환하는 메서드가 있고, 이를 10000번 실행한다고 가정해보자.</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        TestClass testClass = new TestClass();
        for (int i = 0; i &lt; 10_000; i++) {
            testClass.testMethod(i);
        }
        System.out.println(System.currentTimeMillis() - startTime); // 1
    }
}</code></pre>
<p>우선 생성자를 이용해 객체를 생성하여 실행하였을 경우 1ms의 시간이 소요되는 것을 볼 수 있다.</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        TestClass testClass = new TestClass();
        Method testMethod = TestClass.class.getMethod(&quot;testMethod&quot;, int.class);
        for (int i = 0; i &lt; 10_000; i++) {
            testMethod.invoke(testClass, i);
        }
        System.out.println(System.currentTimeMillis() - startTime); // 11
    }
}</code></pre>
<p>위처럼 리플렉션을 이용해 메서드를 호출하게 되면 11ms이 소요된다. 생성자를 이용해 호출했을 경우에 비해 11배나 느린 속도이다.</p>
<h2 id="코드가-지저분하고-장황해진다">코드가 지저분하고 장황해진다.</h2>
<p>리플렉션을 이용해 객체를 생성하게 되면 코드가 길어지고 가독성이 떨어진다.
먼저, 생성자를 이용하여 객체를 생성하게 되면 코드 한 줄로 작성할 수 있다.</p>
<pre><code class="language-java">TestClass testClass = new TestClass();</code></pre>
<p>하지만 위의 <code>TestClass</code>를 리플렉션을 이용해 생성하게 되면</p>
<pre><code class="language-java">
Class&lt;?&gt; clazz = null;
try {
    clazz = Class.forName(&quot;package.TestClass&quot;);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

Constructor&lt;?&gt; constructor = null;
try {
    constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
}

try {
    Object o = constructor.newInstance();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}</code></pre>
<p>위 처럼 23줄에 걸쳐 생성을 하게 된다. 그리고 가독성도 물론 좋지 않다.</p>
<p>6가지의 예외가 발생할 수 있기 때문에 코드가 길어졌는데 이는 리플렉션을 이용했기 때문에 발생할 수 있게 된다.</p>
<p>자바 7부터 리플렉션 예외들이 <code>ReflectiveOperationException</code>을 상위 클래스로 두기 때문에 아래와 같이 더 간단하게 작성할 수 있다.</p>
<pre><code class="language-java">try {
    Class&lt;?&gt; clazz = Class.forName(&quot;package.testClass&quot;);
    Constructor&lt;?&gt; constructor = clazz.getConstructor();
    Object o = constructor.newInstance();
} catch (ReflectiveOperationException e) {
    e.printStackTrace();
}</code></pre>
<h1 id="리플렉션-활용">리플렉션 활용</h1>
<p>코드 분석 도구나 의존관계 주입 프레임워크처럼 리플렉션을 써야 하는 복잡한 애플리케이션이 몇 가지 있다. 이런 도구들마저 리플렉션 사용을 줄이고 있다. 만약 애플리케이션을 제작하는데 반드시 리플렉션을 사용해야만 하는 경우가 아니라면 사용을 피해야 한다.
만일 리플렉션을 사용한다면 아주 제한된 형태로만 사용하여 리플렉션으로 인한 성능감소를 최소화해야 한다. 되도록 객체 생성에만 사용하고, 생성된 객체를 이용할 때는 적절한 인터페이스나 컴파일타임에 알 수 있는 상위 클래스로 형변환해 사용해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JIT 컴파일러와 코드 캐시]]></title>
            <link>https://velog.io/@lucius__k/JIT-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EC%99%80-%EC%BD%94%EB%93%9C-%EC%BA%90%EC%8B%9C</link>
            <guid>https://velog.io/@lucius__k/JIT-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EC%99%80-%EC%BD%94%EB%93%9C-%EC%BA%90%EC%8B%9C</guid>
            <pubDate>Thu, 11 Jan 2024 04:36:20 GMT</pubDate>
            <description><![CDATA[<h1 id="바이트-코드란">바이트 코드란?</h1>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/b5ecd6e0-1837-4389-9b8f-071e20574b8e/image.png" alt="Compilation">
자바 코드를 컴파일러(javac)를 사용하여 컴파일하면 바이트 코드(class 파일)로 컴파일된다.
그리고 JVM이 해당 자바 바이트코드를 인터프리팅하며 애플리케이션이 실행된다.
그러므로 자바 코드를 컴파일하여 바이트 코드로 만들면 JVM이 실행될 수 있는 모든 플랫폼에서 실행할 수 있는 것이다. 그리고 또한 JVM 호환 바이트 코드로 컴파일 할 수 있는 모든 언어는 JVM에서 실행될 수 있다.</p>
<h1 id="just-in-time-compilation의-개념">Just In Time Compilation의 개념</h1>
<p>처음 애플리케이션이 구동되면 다른 인터프리터와 같이 JVM은 코드를 한 줄씩 해석해나간다.
하지만 이렇게 한 줄씩 해석해 나가면 성능이 좋지 않다.
자바에서는 이러한 문제를 해결하기 위해 Just In Time 컴파일, 줄여서 JIT 컴파일이라는 기능이 있다.</p>
<p>JVM은 가장 자주 실행되는 코드 분기, 메소드 또는 메소드의 일부를 모니터링하여 해당 코드를 네이티브 코드로 컴파일할 수 있다.
따라서 애플리케이션의 일부분은 바이트 코드로써 인터프리팅하여 실행되고, 일부분은 네이티브 코드로 컴파일된 코드를 실행하게 된다. 이렇게 네이티브 코드로 실행하게 되면 더 좋은 성능을 가질 수 있다.
JVM이 코드를 인터프리팅하면서 코드를 프로파일링하고, 컴파일하여 최적화할 수 있는 부분을 찾아내어 해당 코드를 JIT 컴파일러를 이용하여 네이티브 코드로 컴파일하고 컴파일이 됐을 경우 컴파일된 네이티브 코드를 실행하게 된다.
네이티브 코드로 컴파일하는 과정은 별도의 스레드에서 진행하게 되므로 애플리케이션이 정지되는 일은 없다.</p>
<p>JIT 컴파일로 인해 한 가지 주의해야 할 점이 있는데, 이는 컴파일되기 전과 후의 성능이 다르므로 성능을 측정할 경우에 주의해야 한다. 애플리케이션이 시작된 직후의 성능과 시간이 조금 지나고 난 후의 성능이 다를 수 있다.</p>
<p>그렇다면 어떤 메서드, 혹은 코드 블록에 JIT 컴파일이 사용되는지 알아보자.</p>
<h1 id="예시-코드">예시 코드</h1>
<pre><code class="language-java">public class Main {

    public static void main(String args[]) {
        PrimeNumbers primeNumbers = new PrimeNumbers();
        int input = 5000;
        primeNumbers.generateNumbers(input);
    }
}</code></pre>
<pre><code class="language-java">public class PrimeNumbers {

    private List&lt;Integer&gt; primes;

    public void generateNumbers(int max) {
        primes = new ArrayList&lt;&gt;();
        primes.add(2);
        int next = 2;
        while (primes.size() &lt;= max) {
            next = getNextPrimeAbove(next);
            primes.add(next);
        }
    }

    private int getNextPrimeAbove(int previous) {
        int testNumber = prevous + 1;
        while (!isPrime(testNumber))
            testNumber++;
        return testNumber;
    }

    private boolean isPrime(int testNumber) {
        for (int i = 2; i &lt; testNumber; i++) {
            if (testNumber % i == 0)
                return false;
        }
        return true;
    }
}</code></pre>
<p>input 값만큼의 소수를 생성하는 generateNumbers 메서드가 있고 해당 메서드를 5000번 호출한 후 어떤 컴파일이 발생했는지 확인하자.</p>
<p>위의 main 메서드를 실행하기에 앞서 VMOptions으로 -XX:+PrintCompilation 플래그를 설정한다.
(java -XX:+PrintCompilation Main)
그러면 아래와 같은 결과를 볼 수 있다.</p>
<pre><code>                                                ...
     87   31       3       java.util.ImmutableCollections$SetN$SetNIterator::nextIndex (56 bytes)
     88   32       3       java.util.ImmutableCollections$SetN$SetNIterator::hasNext (13 bytes)
     88   30       3       java.util.concurrent.ConcurrentHashMap::spread (10 bytes)
     98   33     n 0       java.lang.invoke.MethodHandle::linkToStatic(LLLLLLL)L (native)   (static)
     99   34       1       java.lang.Enum::ordinal (5 bytes)
    100   35     n 0       java.lang.invoke.MethodHandle::linkToStatic(LL)I (native)   (static)
    100   36     n 0       java.lang.Object::hashCode (native)   
    100   37   !   3       java.util.concurrent.ConcurrentHashMap::putVal (432 bytes)
    101   38     n 0       java.lang.invoke.MethodHandle::invokeBasic(LLLLLL)L (native)   
    101   39     n 0       java.lang.invoke.MethodHandle::linkToSpecial(LLLLLLLL)L (native)   (static)
    101   41     n 0       jdk.internal.misc.Unsafe::compareAndSetLong (native)   
    103   40       3       java.util.concurrent.ConcurrentHashMap::addCount (289 bytes)
    104   42     n 0       java.lang.System::arraycopy (native)   (static)
    104   43       3       java.lang.String::length (11 bytes)
                                                ...
    186  206 %     4       jvm.section2.firstexample.PrimeNumbers::isPrime @ 2 (35 bytes)
    186  200       1       java.lang.Boolean::booleanValue (5 bytes)
    186  205       1       java.util.ArrayList::size (5 bytes)
    186  208       3       java.lang.invoke.MethodType::hashCode (53 bytes)
    186  209     n 0       java.lang.Object::clone (native)   
    187  207       3       java.util.ArrayList::add (25 bytes)
    187  210       3       jvm.section2.firstexample.PrimeNumbers::getNextPrimeAbove (43 bytes)
    188  211       3       java.lang.Class::getName (18 bytes)
    190  210       3       jvm.section2.firstexample.PrimeNumbers::getNextPrimeAbove (43 bytes)   made not entrant
    190  212       3       jvm.section2.firstexample.PrimeNumbers::getNextPrimeAbove (43 bytes)
    191  213       4       jvm.section2.firstexample.PrimeNumbers::isPrime (35 bytes)
    194  214       1       java.lang.invoke.MethodType::ptypes (5 bytes)
    194  215     n 0       java.lang.invoke.MethodHandle::linkToStatic(L)L (native)   (static)
    194  201       3       jvm.section2.firstexample.PrimeNumbers::isPrime (35 bytes)   made not entrant
    194  216       3       java.lang.invoke.MethodType::parameterCount (6 bytes)
    197  217       3       java.lang.String::startsWith (138 bytes)
    237  218       4       jvm.section2.firstexample.PrimeNumbers::getNextPrimeAbove (43 bytes)
    251  212       3       jvm.section2.firstexample.PrimeNumbers::getNextPrimeAbove (43 bytes)   made not entrant</code></pre><p>첫 번째 열은 가상 머신이 시작된 이후 경과된 시간 (밀리초)
두 번째 열은 메서드나 코드 블록이 컴파일된 순서(컴파일ID)를 의미한다.
두 번째 열이 순서대로 정렬되어 있지 않은 이유는 일부 부분이 다른 부분보다 컴파일하는 데 시간이 더 오래 걸렸다는 것을 의미한다. 멀티스레딩 문제 또는 컴파일되는 코드의 복잡성이나 길이로 인해 발생할 수 있다.
그리고 속성들이 나타난다.
%는 OSR이 발생했음을 의미 (OSR: 스택 프레임에 컴파일되지 않은 코드를 실행하고 있다면 컴파일된 코드로 교체한다.)
s는 동기화 메서드를 의미한다.
!는 메서드에 예외 처리가 포함되었다는 것을 뜻한다.
그리고 나타나는 숫자가 컴파일 단계를 뜻한다.</p>
<p>위의 결과를 보면 isPrime 메서드가 4단계로 컴파일 된 것을 볼 수 있다.</p>
<h1 id="c1-c2-컴파일러">C1, C2 컴파일러</h1>
<p>JVM에는 C1, C2라고 불리는 JIT 컴파일러가 있다. 이 컴파일러들을 이용해 네이티브 코드로 컴파일하는 것인데 각 컴파일러가 수행할 수 있는 컴파일 단계가 있다.</p>
<h2 id="레벨-0---인터프리터-코드">레벨 0 - 인터프리터 코드</h2>
<p>JVM은 애플리케이션 구동 시 모든 바이트 코드들을 인터프리팅하며 코드를 읽는다. 이 단계에서는 일반적으로 컴파일된 언어에 비해 성능이 좋지 않다. 그 후 JIT 컴파일러는 런타임에 핫 코드를 컴파일한다. 이 단계에서 수집된 프로파일링 정보를 바탕으로 최적화를 수행한다.
(자주 실행되는 코드 섹션에 대한 바이트 코드를 핫스팟이라고 한다.)</p>
<h2 id="레벨-1---간단한-c1-컴파일-코드">레벨 1 - 간단한 C1 컴파일 코드</h2>
<p>이 레벨에서 C1 컴파일러를 사용하여 코드를 컴파일하지만 프로파일링 정보는 수집하지 않는다. 중요하지 않다고 간주되는 코드에 레벨 1을 적용한다. 이 단계에서의 코드들은 중요하지 않고 복잡성이 낮기 때문에 더 높은 단계로 컴파일을 해도 성능이 좋아지지 않는다고 판단하여 프로파일링 정보를 수집하지 않는다.</p>
<h2 id="레벨-2---제한된-c1-컴파일-코드">레벨 2 - 제한된 C1 컴파일 코드</h2>
<p>가벼운 프로파일링과 함께 C1 컴파일러를 사용하여 코드를 컴파일한다. C2 컴파일러의 큐가 가득 찼을 경우 코드를 레벨 2로 컴파일한다. 나중에 전체 프로파일링을 사용하여 레벨 3으로 컴파일한 후 C2 큐의 사용량이 줄어들면 레벨 4로 다시 컴파일한다.</p>
<h2 id="레벨-3---전체-c1-컴파일-코드">레벨 3 - 전체 C1 컴파일 코드</h2>
<p>전체 프로파일링과 함께 C1 컴파일러를 사용하여 코드를 컴파일한다. 레벨 3의 컴파일 단계는 기본 컴파일 단계이다. JVM은 레벨 1의 컴파일 단계가 아닐 경우, 컴파일러 큐가 가득 찬 경우를 제외한 모든 경우에 레벨 3단계의 컴파일을 진행한다.</p>
<h2 id="레벨-4---c2-컴파일-코드">레벨 4 - C2 컴파일 코드</h2>
<p>C2 컴파일러를 사용하여 컴파일된 코드의 단계이다. 레벨 1의 중요하지 않다고 판단되지 않은 코드들을 제외한 모든 메서드를 해당 단계로 컴파일할 수 있다. 레벨 4 단계의 코드가 완전히 최적화된 것으로 간주되는 경우 프로파일링 정보 수집을 중지한다.</p>
<h2 id="컴파일-비활성화">컴파일 비활성화</h2>
<p>-XX:-TieredCompilation 플래그를 사용하여 컴파일을 비활성화할 수 있다. 이 플래그를 설정하면 사용할 JIT 컴파일러를 선택해야 하는데 지정하지 않을 경우 CPU를 기반으로 결정하게 된다. -Xint 플래그를 사용해 컴파일러를 사용하지 않고 인터프리터만 사용할 수 있지만 성능에 좋지 않다.</p>
<h2 id="임계값-설정">임계값 설정</h2>
<p>계층화된 컴파일이 활성화된 경우 메서드 호출의 임계값을 설정하여 각 컴파일 수준을 결정할 수 있다.
-XX:+PrintFlagsFinal 플래그를 사용하여 기본 임계값을 볼 수 있고, -XX:Tier4CompileThreshold=10000과 같은 설정으로 임계값을 변경할 수 있다.</p>
<h1 id="코드-캐시">코드 캐시</h1>
<p>코드 캐시는 JVM이 네이티브 코드로 컴파일된 바이트 코드를 저장하는 영역이다.
실행 가능한 네이티브 코드의 각 블록을 nmethod라고 부른다.</p>
<h2 id="코드-캐시-튜닝">코드 캐시 튜닝</h2>
<p>코드 캐시의 크기는 고정되어 있다.
코드 캐시에 배치할 코드가 제한된 크기를 넘어설 경우 삽입할 공간을 만들기 위해 일부를 제거할 수 있다.</p>
<p>코드 캐시가 가득 찰 경우 그리고 아래와 같은 경고 문구를 볼 수 있다.</p>
<pre><code>VM warning: CodeCache is full. Compiler has been disabled</code></pre><p>코드 캐시가 가득 차면 JIT 컴파일러가 비활성화된다. 그리고 코드 캐시에 저장된 코드들이 활발히 사용되고 있으므로 제거할 코드가 없다는 것을 뜻한다.</p>
<p>-XX:+PrintCodeCache 플래그로 코드 캐시를 확인할 수 있다. (자바 8까지의 경우의 예)</p>
<pre><code>CodeCache: size=32768Kb used=542Kb max_used=542Kb free=32226Kb</code></pre><p>코드 캐시는 32mb(size)의 크기를 가졌으며 542kb(used)가 사용 중이며 32226kb(free) 만큼의 메모리를 더 사용할 수 있다는 것을 뜻한다.</p>
<p>코드 캐시의 크기를 변경할 수 있는 플래그가 있다.</p>
<ul>
<li><p>-XX:InitailCodeCacheSize
애플리케이션이 시작될 때 코드 캐시의 크기이다.
기본 크기는 메모리에 따라 다르지만 일반적으로 160kb이다.</p>
</li>
<li><p>-XX:ReservedCodeCacheSize
코드 캐시의 최댓값이다.
코드 캐시는 시간이 지남에 따라 ReservedCodeCacheSize로 설정한 값까지 커질 수 있다.
기본값은 48mb이다.</p>
</li>
<li><p>-XX:CodeCacheExpansionSize
코드 캐시가 가득 찰 경우 확장시킬 크기이다.
코드 캐시를 늘릴 때 얼마만큼 늘릴지 정할 때 사용한다.</p>
</li>
</ul>
<p>자바 9부터는 코드 캐시를 세 개의 영역으로 나누며 각 영역에는 특정 유형의 컴파일된 코드가 포함되어 있다.</p>
<pre><code>CodeHeap &#39;non-profiled nmethods&#39;: size=120032Kb used=72Kb max_used=72Kb free=119959Kb
 bounds [0x0000000116dde000, 0x000000011704e000, 0x000000011e316000]
CodeHeap &#39;profiled nmethods&#39;: size=120028Kb used=261Kb max_used=261Kb free=119766Kb
 bounds [0x000000010f8a7000, 0x000000010fb17000, 0x0000000116dde000]
CodeHeap &#39;non-nmethods&#39;: size=5700Kb used=1085Kb max_used=1099Kb free=4614Kb
 bounds [0x000000010f316000, 0x000000010f586000, 0x000000010f8a7000]
 total_blobs=554 nmethods=225 adapters=243
 compilation: enabled
              stopped_count=0, restarted_count=0
 full_count=0</code></pre><p>non-nmethods는 바이트 코트 인터프리터와 같은 jVM 내부 관련 코드가 포함된다.
-XX:NonNMethodCodeHeapSize 플래그를 사용하여 크기를 설정할 수 있다.</p>
<p>profiled nmethods는 수명이 짧은 가볍게 최적화되고 프로파일링된 메서드를 포함한다.
-XX:ProfiledCodeHeapSize 플래그를 사용하여 크기를 설정할 수 있다.</p>
<p>non-profiled nmethods는 완전히 최적화되고 긴 수명을 가진 프로파일링되지 않은 메서드를 포함한다.
-X::NonProfiledCodeHeapSize 플래그를 사용하여 크기를 설정할 수 있다.</p>
<h1 id="references">References</h1>
<p><a href="https://openjdk.org/jeps/197#:~:text=Instead%20of%20having%20a%20single,internal%20(non%2Dmethod)%20code">https://openjdk.org/jeps/197#:~:text=Instead%20of%20having%20a%20single,internal%20(non%2Dmethod)%20code</a>
<a href="https://www.baeldung.com/jvm-code-cache">https://www.baeldung.com/jvm-code-cache</a>
<a href="https://www.baeldung.com/jvm-tiered-compilation">https://www.baeldung.com/jvm-tiered-compilation</a>
<a href="https://www.udemy.com/course/java-application-performance-and-memory-management/">https://www.udemy.com/course/java-application-performance-and-memory-management/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Item61] 박싱된 기본 타입보다는 기본 타입을 사용하라]]></title>
            <link>https://velog.io/@lucius__k/Item61-%EB%B0%95%EC%8B%B1%EB%90%9C-%EA%B8%B0%EB%B3%B8-%ED%83%80%EC%9E%85%EB%B3%B4%EB%8B%A4%EB%8A%94-%EA%B8%B0%EB%B3%B8-%ED%83%80%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC-isl45vj2</link>
            <guid>https://velog.io/@lucius__k/Item61-%EB%B0%95%EC%8B%B1%EB%90%9C-%EA%B8%B0%EB%B3%B8-%ED%83%80%EC%9E%85%EB%B3%B4%EB%8B%A4%EB%8A%94-%EA%B8%B0%EB%B3%B8-%ED%83%80%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC-isl45vj2</guid>
            <pubDate>Wed, 03 Jan 2024 12:31:34 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<h1 id="기본-타입과-박싱된-기본-타입의-차이-3가지">기본 타입과 박싱된 기본 타입의 차이 3가지</h1>
<h2 id="1-식별성">1. 식별성</h2>
<p>박싱된 기본 타입은 객체로 저장되기 때문에 식별성을 갖는다.</p>
<pre><code class="language-java">public void main(String[] args) {
  Integer a = Integer.valueOf(1000);
  Integer b = Integer.valueOf(1000);
  System.out.println(a == b);
  System.out.println(a.equals(b));
}</code></pre>
<pre><code>false
true</code></pre><h3 id="캐시-사용">캐시 사용</h3>
<p>대부분의 박싱된 기본 타입은 캐시를 갖고 있다. (-127 ~ 128)</p>
<pre><code class="language-java">public void main(String[] args) {
  Integer a = Integer.valueOf(100);
  Integer b = Integer.valueOf(100);
  System.out.println(a == b);
  System.out.println(a.equals(b));
}</code></pre>
<pre><code>true
true</code></pre><hr>
<h2 id="2-초기화">2. 초기화</h2>
<p>기본 타입은 초기화를 하지 않아도 유효한 값으로 초기화가 되지만 박싱된 기본 타입은 그렇지 않다.</p>
<pre><code class="language-java">public class Test {

  int a;
  Integer b;

  public void main(String[] args) {
    Test t = new Test();
    System.out.println(t.a);
    System.out.println(t.b);
  }
}</code></pre>
<pre><code>0
null</code></pre><hr>
<h2 id="3-성능">3. 성능</h2>
<p>박싱된 기본 타입으로 연산을 수행했을 경우 박싱과 언박싱을 반복적으로 하므로 성능에 저하가 생긴다.</p>
<pre><code class="language-java">public void main(String[] args) {
  Long a = Long.valueOf(0);
  long startTime = System.currentTimeMillis();
  for (long i = 0; i &lt; 100000000; i++) { // 10억까지
    a += i;
  }
  System.out.println(&quot;result Time: &quot; + (System.currentTimeMillis() - startTime));
}</code></pre>
<pre><code>result Time: 602</code></pre><p>반복된 박싱과 언박싱을 하여 연산을 하였을 경우 602ms,</p>
<pre><code class="language-java">public void main(String[] args) {
  long a = 0;
  long startTime = System.currentTimeMillis();
  for (long i = 0; i &lt; 100000000; i++) {
    a += i;
  }
  System.out.println(&quot;result Time: &quot; + (System.currentTimeMillis() - startTime));
}</code></pre>
<pre><code>result Time: 54</code></pre><p>기본 타입을 사용하였을 경우 54ms가 소요된다.</p>
<hr>
<h1 id="박싱된-기본-타입이-사용되는-경우">박싱된 기본 타입이 사용되는 경우</h1>
<h2 id="컬렉션">컬렉션</h2>
<pre><code class="language-java">List&lt;Integer&gt; list = new ArrayList&lt;&gt;();
List&lt;int&gt; list = new ArrayList&lt;&gt;(); // 컴파일 에러</code></pre>
<p>컬렉션의 원소, 키, 값으로 사용된다.</p>
<h2 id="제네릭">제네릭</h2>
<p>위의 컬렉션과 같은 원리이다.</p>
<p>제네릭 타입 매개변수로 기본 타입을 사용할 순 없다.</p>
<hr>
<h1 id="박싱과-언박싱">박싱과 언박싱</h1>
<h2 id="박싱">박싱</h2>
<p>기본 타입인 int가 박싱을 하게될 때 다음과 같은 메서드를 호출한다.</p>
<pre><code class="language-java">@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
  if (i &gt;= IntegerCache.low &amp;&amp; i &lt;= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}</code></pre>
<h2 id="언박싱">언박싱</h2>
<p>박싱된 기본 타입을 언박싱하게 될 때 다음과 같은 메서드를 호출한다.</p>
<pre><code class="language-java">@HotSpotIntrinsicCandidate
public int intValue() {
  return value;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ArrayList 파헤치기]]></title>
            <link>https://velog.io/@lucius__k/ArrayList-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0</link>
            <guid>https://velog.io/@lucius__k/ArrayList-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0</guid>
            <pubDate>Sun, 31 Dec 2023 16:52:43 GMT</pubDate>
            <description><![CDATA[<p>JDK 11 버전의 ArrayList입니다.</p>
<h1 id="arraylist">ArrayList</h1>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/04884abc-5c1f-4194-8303-324f9d0d9c4b/image.png" alt="ArrayList"></p>
<hr>
<h2 id="필드">필드</h2>
<h3 id="default_capacity">DEFAULT_CAPACITY</h3>
<pre><code class="language-java">private static final int DEFAULT_CAPACITY = 10;</code></pre>
<p>상수이며 용량의 기본값이며 10으로 초기화된다.</p>
<h3 id="empty_elementdata">EMPTY_ELEMENTDATA</h3>
<pre><code class="language-java">private static final Object[] EMPTY_ELEMENTDATA = {};</code></pre>
<p>상수이며 빈 원소들의 배열이다.</p>
<h3 id="defaultcapacity_empty_elementdata">DEFAULTCAPACITY_EMPTY_ELEMENTDATA</h3>
<pre><code class="language-java">private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};</code></pre>
<p>상수이며 빈 원소들의 배열이다.</p>
<h3 id="max_array_size">MAX_ARRAY_SIZE</h3>
<pre><code class="language-java">private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;</code></pre>
<hr>
<h3 id="elementdata">elementData</h3>
<pre><code class="language-java">transient Object[] elementData;</code></pre>
<p>원소들을 저장하는 공간이다. elementData 배열의 길이가 ArrayList의 용량을 뜻한다.
빈 ArrayList를 생성하면 DEFAULTCAPACITY_EMPTY_ELEMENTDATA(빈 배열)로 초기화된다.
그리고 첫 원소가 추가 되면 DEFAULT_CAPACITY(10)로 용량이 늘어나게 된다.</p>
<h3 id="size">size</h3>
<pre><code class="language-java">private int size;</code></pre>
<p>ArrayList의 크기이다.
원소들의 개수를 뜻한다.</p>
<h3 id="modcount">modCount</h3>
<pre><code class="language-java">protected transient int modCount = 0;</code></pre>
<p>AbstractList 클래스에 선언된 필드이다.</p>
<p><strong>구조적인 변경이 일어난 횟수를 뜻한다.</strong>
해당 필드의 쓰임새는 아래에서 설명한다.</p>
<hr>
<h2 id="생성자">생성자</h2>
<p>ArrayList에는 총 3개의 생성자가 존재한다.</p>
<h3 id="public-arraylist">public ArrayList()</h3>
<pre><code class="language-java">public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}</code></pre>
<p>기본 생성자이다.
elementData를 DEFAULTCAPACITY_EMPTY_ELEMENTDATA로 초기화한다.</p>
<h3 id="public-arraylistint-initialcapacity">public ArrayList(int initialCapacity)</h3>
<pre><code class="language-java">public ArrayList(int initialCapacity) {
    if (initialCapacity &gt; 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException(&quot;Illegal Capacity: &quot; + initialCapacity);
    }
}</code></pre>
<p>초기 용량을 입력받아 ArrayList를 생성한다.</p>
<p>초기 용량이 음수이면 IllegalArgumentException 예외가 발생한다.
초기 용량이 0이면 elementData를 EMPTY_ELEMENTDATA(빈 배열)로 초기화한다.
초기 용량이 0 이상이면 elementData를 new Object[initialCapacity]로 초기화한다.</p>
<h3 id="public-arraylistcollection-extends-e-c">public ArrayList(Collection&lt;? extends E&gt; c)</h3>
<pre><code class="language-java">public ArrayList(Collection&lt;? extewnds E&gt; c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        elementData = EMPTY_ELEMENTDATA;
    }
}</code></pre>
<p>c의 원소들을 복사하여 ArrayList를 생성한다.</p>
<p>c에 원소가 존재하지 않으면 elementData를 EMPTY_ELEMENTDATA로 초기화한다.
c에 원소가 존재하고 c가 ArrayList 클래스면 c의 elementData를 복사하여 생성한다.
c에 원소가 존재하고 c가 ArrayList 클래스가 아니면 해당 Collection의 원소들의 배열을 복사하여 elementData를 초기화한다.</p>
<hr>
<h2 id="arraylist-메서드">ArrayList 메서드</h2>
<h3 id="trimtosize">trimTosize</h3>
<p>오버라이딩되지 않은 순수 ArrayList에 있는 메서드이다.</p>
<pre><code class="language-java">public void trimToSize() {
    modCount++;
    if (size &lt; elementData.length) {
        elementData = (size == 0)
            ? EMPTY_ELEMENTDATA
            : Arrays.copyOf(elementData, size);
    }
}</code></pre>
<p>현재 인스턴스의 elementData의 크기가 실제로 갖고있는 원소들의 수보다 크면 원소들의 수에 맞는 크기를 가진 elementData로 교체한다.
실제로 교체가 일어나지 않아도 modCount를 증가시킨다.</p>
<h3 id="ensurecapacity">ensureCapacity</h3>
<p>오버라이딩되지 않은 순수 ArrayList에 있는 메서드이다.</p>
<pre><code class="language-java">public void ensureCapacity(int minCapacity) {
    if (minCapacity &gt; elementData.length 
            &amp;&amp; !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
            &amp;&amp; minCapacity &lt;= DEFAULT_CAPACITY)) {
        modCount++;
        grow(minCapacity);
    }
}</code></pre>
<p>미리 minCapacity의 값으로 elementData의 크기를 확장시켜놓는 메서드이다</p>
<p>minCapacity가 elementData의 크기보다 크고,
elementData가 DEFAULTCAPACITY_EMPTY_ELEMENTDATA가 아니고,
minCapacity가 DEFAULT_CAPACITY(10)보다 커야한다.</p>
<p>위의 조건이 만족되면 구조적인 변경이 일어나므로 modCount를 증가시킨다.
grow() 메서드를 이용해 elementData의 크기를 변경한다.</p>
<h3 id="size-1">size</h3>
<pre><code class="language-java">public int size() {
    return size;
}</code></pre>
<p>size를 반환한다.</p>
<h3 id="isempty">isEmpty</h3>
<pre><code class="language-java">public boolean isEmpty() {
    return size == 0;
}</code></pre>
<p>원소가 존재하는지 확인한다.</p>
<h3 id="contains">contains</h3>
<pre><code class="language-java">public boolean contains(Object o) {
    return indexOf(o) &gt;= 0;
}</code></pre>
<p>o가 elementData의 원소로 존재하면 true를 반환한다.</p>
<h3 id="indexof">indexOf</h3>
<pre><code class="language-java">public int indexOf(Object o) {
    return indexOfRange(o, 0, size);
}</code></pre>
<p>elementData에서 o의 인덱스를 반환한다.
o가 여러개 있을 경우 가장 먼저 발견되는 인덱스를 반환하며 o를 찾을 때 elementData의 첫번째 원소부터 차례대로 비교하여 찾는다.
만약 o가 존재하지 않으면 -1을 반환한다.</p>
<h3 id="lastindexof">lastIndexOf</h3>
<pre><code class="language-java">public int lastIndexOf(Object o) {
    return lastIndexOfRange(o, 0, size);
}</code></pre>
<p>elementData에서 끝에서 부터 o를 찾아 인덱스를 반환한다.
만약 o가 존재하지 않으면 -1을 반환한다.</p>
<h3 id="clone">clone</h3>
<p>ArrayList 클래스에서 public 접근제한자로 변경하였으므로 List타입으로 변수를 선언하면 해당 메서드를 사용할 수 없다.</p>
<pre><code class="language-java">public Object clone() {
    try {
        ArrayList&lt;?&gt; v = (ArrayList&lt;?&gt;) super.clone();
        v.elementData = Arrays.copryOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
}</code></pre>
<p>얕은 복사를 하여 객체를 반환한다.
ArrayList는 Cloneable 인터페이스를 구현하였으므로 CloneNotSupportedException 예외가 발생할 수 없다.</p>
<h3 id="toarray">toArray</h3>
<pre><code class="language-java">public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}</code></pre>
<p>현재 인스턴스가 갖고 있는 원소들을 Object 배열로 반환한다.</p>
<pre><code class="language-java">public &lt;T&gt; T[] toArray(T[] a) {
    if (a.length &lt; size) {
        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>
<p>인자로 들어온 배열 a의 크기가 size 보다 작으면 새로운 배열을 만들어서 반환합니다.
size보다 배열의 크기가 크면 a의 elementData 원소들을 차례대로 넣은 후 나머지 값들은 null로 초기화한 후 반환한다.</p>
<h3 id="get">get</h3>
<pre><code class="language-java">public E get(int index) {
    Objects.checkIndex(index, size);
    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index];
}</code></pre>
<p>해당 인덱스에 해당하는 원소를 반환한다.
인덱스가 인스턴스의 원소 개수보다 크면 IndexOutOfBoundsException 예외가 발생한다.(<strong>Objects.checkIndex()</strong>)</p>
<h3 id="set">set</h3>
<pre><code class="language-java">public E set(int index, E element) {
    Objects.checkIndex(index, size);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}</code></pre>
<p>인덱스에 해당하는 원소를 element로 교체하고 기존에 존재하던 원소를 반환한다.
인덱스가 인스턴스의 원소 개수보다 크면 IndexOutOfBoundsException 예외가 발생한다.(<strong>Objects.checkIndex()</strong>)</p>
<h3 id="add">add</h3>
<pre><code class="language-java">public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length) {
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}</code></pre>
<p>modCount를 증가시킨다.
e를 elementData 배열에 추가한다.
만약 elementData의 크기와 원소들의 수가 동일하면(즉, 배열이 가득찼을 경우) 배열의 크기를 키우고(grow 메서드) 추가한다.
해당 컬렉션이 중복을 지원하지 않는 경우에 중복된 원소를 추가하려고 하면 false를 반환한다. (ArrayList와는 상관없다.)</p>
<pre><code class="language-java">public void add(int index, E element) {
    rangeCheckForAdd(index);
    modCount++;
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    System.arraycopy(elementData, index, elementData, index + 1, s - index);
    elementData[index] = element;
    size = s + 1;
}</code></pre>
<p>지정된 index에 element를 삽입한다.
지정된 index부터 끝까지 존재하는 모든 원소를 오른쪽으로 한 칸 씩 이동시킨 후 element를 삽입한다.
지정된 index가 현재 원소들의 수(size)보다 큰 경우 IndexOutOfBoundsException 예외가 발생한다.(<strong>rangeCheckForAdd() 메서드</strong>)</p>
<h3 id="remove">remove</h3>
<pre><code class="language-java">public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    E oldValue = (E) es[index];
    fastRemove(es, index);

    return oldValue;
}</code></pre>
<p>지정된 index에 있는 원소를 fastRemove 메서드를 이용하여 제거하고 반환한다.
제거한 후 오른쪽에 있는 원소들을 왼쪽으로 한 칸 씩 이동시킨다.
지정된 index가 현재 원소들의 수(size)보다 큰 경우 IndexOutOfBoundsException 예외가 발생한다.(<strong>rangeCheckForAdd() 메서드</strong>)</p>
<pre><code class="language-java">public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i&lt; size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i &lt; size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}</code></pre>
<p>o와 같은 원소의 인덱스를 찾아 fastRemove 메서드를 이용하여 제거한다.
원소를 찾아 제거하면 true를, 찾지 못하면 false를 반환한다.</p>
<p>원소를 제거할 때 fastRemove() 메서드를 사용하여 제거하는 것을 볼 수 있다.</p>
<pre><code class="language-java">public void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) &gt; i) {
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}</code></pre>
<p>modCount를 증가시킨다.
제거될 원소의 인덱스가 배열의 마지막을 가리키고 있지 않으면 제거될 원소의 오른쪽에 존재하는 원소들을 한 칸 씩 왼쪽으로 이동시킨다.
그리고 배열의 마지막 원소를 제거한다.</p>
<h3 id="equals">equals</h3>
<pre><code class="language-java">public boolean equals(Object o) {
    if (o == this) {
        return true;
    }

    if (!(o instanceof List)) {
        return false;
    }

    final int expectedModCount = modCount;
    boolean equal = (o.getClass() == ArrayList.class)
            ? equalsArrayList((ArrayList&lt;?&gt;) o)
            : equalsRange((List&lt;?&gt;) o, 0, size);

    checkForComodification(expectedModCount);
    return equal;
}</code></pre>
<p>비교할 대상인 o가 ArrayList 클래스라면 equalsArrayList() 메서드로 비교하고,
ArrayList가 아닌 List 인터페이스의 구현체이면 equalsRange 메서드로 비교한다.</p>
<pre><code class="language-java">private boolean equalsArrayList(ArrayList&lt;?&gt; other) {
    final int otherModCount = other.modCount;
    final int s = size;
    boolean equal;
    if (equal = (s == other.size)) {
        final Object[] otherEs = other.elementData;
        final Object[] es = elementData;
        if (s &gt; es.length || s &gt; otherEs.length) {
            throw new ConcurrentModificationException();
        }
        for (int i = 0; i &lt; s; i++) {
            if (!Objects.equals(es[i], otherEs[i])) {
                equal = false;
                break;
            }
           }
    }
    other.checkForComodification(otherModCount);
    return equal;
}</code></pre>
<p>equalsArrayList() 메서드를 보면 먼저 원소들의 개수(size)를 비교하고, 원소 하나하나에 equals 메서드를 사용하여 같은지 확인한다.</p>
<pre><code class="language-java">boolean equalsRange(List&lt;?&gt; other, int from, int to) {
    final Object[] es = elementData;
    if (to &gt; es.length) {
        throw new ConcurrentModificationException();
    }
    var oit = other.iterator();
    for (; form &lt; to; from++) {
        if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) {
            return false;
        }
    }
    return !oit.hasNext();
}</code></pre>
<p>equalsRange() 메서드를 보면 비교할 대상의 iterator() 메서드를 사용하여 Iterator를 이용하여 원소의 개수와 원소들이 같은지 확인한다.</p>
<h3 id="hashcode">hashcode</h3>
<pre><code class="language-java">public int hashCode() {
    int expectedModCount = modCount;
    int hash = hashCodeRange(0, size);
    checkForComodification(expectedModCount);
    return hash;
}

int hashCodeRange(int from, int to) {
    final Object[] es = elementData;
    if (to &gt; es.length) {
        throw new ConcurrentModificationException();
    }
    int hashCode = 1;
    for (int i = from; i &lt; to; i++) {
        Object e = es[i];
        hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
    }
    return hashCode;
}</code></pre>
<p>hashCode를 생성하여 반환한다.
hashCode는 원소의 hashCode를 이용하여 생성한다.</p>
<h3 id="clear">clear</h3>
<pre><code class="language-java">public void clear() {
    modCount++;
    final Object[] es = elementData;
    for (int to = size; i = size = 0; i &lt; to; i++)
        es[i] = null;
}</code></pre>
<p>modCount를 증가시키고
모든 원소를 null로 변환하고 size를 0으로 초기화한다.</p>
<h3 id="addall">addAll</h3>
<pre><code class="language-java">public boolean addAll(Collection&lt;? extends E&gt; c) {
    Object[] a = c.toArray();
    modCount++;
    int numNew = a.length;
    if (numNew == 0)
        return false;
    Object[] elementData;
    final int s;
    if (numNew &gt; (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);
    System.arraycopy(a, 0, elementData, s, numNew);
    size = s + numNew;
    return true;
}</code></pre>
<p>현재 인스턴스의 배열의 끝 부분부터 추가하려는 컬렉션의 원소들을 추가한다.
modCount를 증가시킨다.
만일 추가하려는 컬렉션에 원소가 존재하지 않을 경우 false를 반환한다.
현재 인스턴스의 elementData에 남은 공간이 추가하려는 원소의 수보다 적을 경우 배열을 증가시키고 원소들을 추가한다.
추가에 성공하면 true를 반환한다.</p>
<pre><code class="language-java">public boolean addAll(int index, Collection&lt;? extends E&gt; c) {
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    modCount++;
    int numNew = a.length;
    if (numNew == 0)
        return false;
    Object[] elementData;
    final int s;
    if (numNew &gt; (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);

    int numMoved = s - index;
    if (numMoved &gt; 0)
        System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
    System.arraycopy(a, 0, elementData, index, numNew);
    size = s + numNew;
    return true;
}</code></pre>
<p>지정된 인덱스에 c의 원소들을 추가한다.
기존에 존재하던 원소들을 오른쪽으로 모두 이동시킨다.</p>
<p>인덱스가 음수이거나 size보다 크면 IndexOutOfBoundsException 예외를 발생시킨다.</p>
<p>위의 addAll 메서드와 같은 과정으로 진행하는데 중간에 배열의 인덱스들을 오른쪽으로 이동시키는 과정이 추가된다.</p>
<h3 id="removeall-retainall">removeAll, retainAll</h3>
<pre><code class="language-java">public boolean removeAll(Collection&lt;?&gt; c) {
    return batchRemove(c, false, 0, size);
}</code></pre>
<p>removeAll 메서드는 elementData에 있는 원소들 중 c에도 존재하는 원소들을 제거한다.</p>
<pre><code class="language-java">public boolean retainAll(Collection&lt;?&gt; c) {
    return batchRemove(c, true, 0, size);
}</code></pre>
<p>retainAll 메서드는 elementData에 있는 원소들 중 c에는 존재하지 않는 원소들을 제거합니다. (c에 존재하는 원소들만 남겨둔다.)</p>
<pre><code class="language-java">boolean batchRemove(Collection&lt;?&gt; c, boolean complement, final int from, final int end) {
    Objects.requireNonNull(c);
    final Object[] es = elementData;
    int r;
    for (r = from; ; r++) {                                // A
        if (r == end)
            return false;
        if (c.contains(es[r]) != complement)
            break;
    }
    int w = r++;
    try {
        for (Object e; r &lt; end; r++) {                    // B
            if (c.contains(e = es[r]) == complement)
                es[w++] = e;
    } catch (Throwable ex) {
        System.arraycopy(ex, r, es, w, end - r);
        w += end - r;
        throw ex;
    } finally {
        modCount += end - w;                            // C
        shiftTailOverGap(es, w, end);                    // D
    }
    return true;
}</code></pre>
<h4 id="removeall의-경우-batchremove의-흐름">removeAll의 경우 batchRemove의 흐름</h4>
<ul>
<li>A: c의 원소들 중 현재 인스턴스에 중복이 존재하지 않으면 false를 반환한다. (제거할 원소가 없으면 false를 반환)
  w에는 처음으로 중복되는 원소의 인덱스가 담겨있다.  </br></li>
<li>B: c와 중복되지 않은 원소들만 w자리에 넣고 w를 증가시킨다. (중복되지 않은 원소들을 당겨온다.)
 결국 w는 변경된 사이즈가 된다. </br></li>
<li>C: 제거된 원소의 수만큼 modCount를 증가시킨다.  </br></li>
<li>D: w이후의 원소들을 null로 초기화하여 메모리를 해제한다.</li>
</ul>
<h4 id="retainall의-경우-batchremove의-흐름">retainAll의 경우 batchRemove의 흐름</h4>
<ul>
<li>A: 모든 원소가 중복된다면 제거할 원소가 없으므로 false를 반환한다.
  w에는 처음으로 중복되지 않은 원소의 인덱스가 담겨있다.  </br></li>
<li>B: 중복되는 원소를 발견하면, w 인덱스에 넣고 w를 증가시킨다. (중복되는 원소들만 당겨온다.)
  결국 w는 변경된 사이즈가 된다.  </br></li>
<li>C: 중복되지 않은 원소의 수, 즉, 제거된 원소의 수만큼 modCount가 증가한다.  </br></li>
<li>D: w이후의 원소들을 null로 초기화하여 메모리를 해제한다.</li>
</ul>
<h3 id="removeif">removeIf</h3>
<pre><code class="language-java">public boolean removeIf(Predicate&lt;? super E&gt; filter) {
    return removeIf(filter, 0, size);
}</code></pre>
<p>filter로 들어온 조건에 부합하는 원소들을 제거한다.</p>
<pre><code class="language-java">boolean removeIf(Predicate&lt;? super E&gt; filter, int i, final int end) {
    Objects.requireNonNull(filter);
    int expectedModCount = modCount;
    final Object[] es = elementData;
    for (; i &lt; end &amp;&amp; !filter.test(elementAt(es, i)); i++) // 조건에 부합하는 첫 번째 원소를 찾을 때까지 i를 증가시킨다.
        ;
    if (i &lt; end) { // 조건에 부합하는 원소가 있을 경우
        final int beg = i;
        final long[] deathRow = nBits(end - beg);
        deathRow[0] = 1L;
        for (i = beg + 1; i &lt; end; i++) // 조건에 부합하는 원소들을 찾는다.
            if (filter.test(elementAt(es, i)))
                setBits(deathRow, i - beg);
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        modCount++;
        int w = beg;
        for (i = beg; i &lt; end; i++) { // 조건에 부합하는 원소들을 제거한다.
            if (isClear(deathRow, i - beg))
                es[w++] = es[i];
        shiftTailOverGap(es, w, end);
        return true;
    } else { // 조건에 부합하는 원소가 없을 경우
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        return false;
    }
}</code></pre>
<p>조건에 부합하는 원소들을 찾는 방법과 확인하는 방법에 대해서는 아직 이해를 할 수 없다...</p>
<h3 id="replaceall">replaceAll</h3>
<pre><code class="language-java">public void replaceAll(UnaryOperator&lt;E&gt; operator) {
    replaceAllRange(operator, 0, size);
    modCount++;
}</code></pre>
<p>원소들을 수정할 때 사용한다.</p>
<pre><code class="language-java">List&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;good&quot;);
list.add(&quot;job&quot;);
list.add(null);

list.replaceAll(s -&gt; s == null ? &quot;default value&quot; : s);</code></pre>
<p>예를 들어 위처럼 문자열 타입의 리스트에서 사용할 경우 null 값이 들어있을 경우 &quot;default value&quot;로 변환하는 식으로 사용할 수 있다.</p>
<pre><code class="language-java">private void replaceAllRange(UnaryOperator&lt;E&gt; operator, int i, int end) {
    Objects.requireNonNull(operator);
    final int expectedModCount = modCount;
    final Object[] es = elementData;
    for (; modCount == expectedModCount &amp;&amp; i &lt; end; i++)
        es[i] = operator.apply(elementAt(es, i));
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}</code></pre>
<p>operator가 null 값인지 체크하고, 현재 인스턴스의 모든 원소를 순회하며 operator의 apply 메서드의 반환값을 적용한다.</p>
<h3 id="sort">sort</h3>
<pre><code class="language-java">public void sort(Comparator&lt;? super E&gt; c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    modCount++;
}</code></pre>
<p>Comparator 를 이용하여 배열을 정렬한다.
modCount를 증가시킨다.</p>
<hr>
<h2 id="배열의-크기를-늘리는-과정">배열의 크기를 늘리는 과정</h2>
<p>ArrayList는 grow() 메서드를 이용하여 현재 배열의 크기를 증가시킨다.</p>
<h3 id="grow">grow</h3>
<pre><code class="language-java">private Object[] grow(int minCapacity) {
    return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}</code></pre>
<p>elementData를 새로운 크기의 배열로 복사하여 교체한다.
newCapacity 메서드를 이용해 새로운 크기를 만들어 내어 교체한다.</p>
<h3 id="newcapacity">newCapacity</h3>
<pre><code class="language-java">private int newCapacity(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity &gt;&gt; 1); // 기존 배열의 1.5배 크기
    if (newCapacity - minCapacity &lt;= 0) { // 유저가 입력한 크기가 1.5배 크기보다 크거나 같을 경우
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // 기본 생성자로 생성한 후 원소를 추가하지 않았을 경우
            return Math.max(DEFAULT_CAPACITY, minCapacity); // 10과 입력한 크기를 비교하여 큰 값 반환
        if (minCapacity &lt; 0) // 메모리를 초과하였는지 체크
            throw new OutOfMemoryError();
        return minCapacity; // 유저가 입력한 값 반환
    }
    return (newCapacity - MAX_ARRAY_SIZE &lt;= 0) // 기존 배열의 1.5배 크기가 배열의 가용 최대 크기보다 작은지
            ? newCapacity // 작으면 기존 배열의 1.5배 크기 반환
            : hugeCapacity(minCapacity); // 최대 가용 크기보다 클 경우 해당 메서드 호출
}</code></pre>
<pre><code class="language-java">private static int hugeCapacity(int minCapacity) {
    if (minCapacity &lt; 0)
        throw new OutOfMemoryError();
    return (minCapacity &gt; MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;
}</code></pre>
<p>쉽게 설명하자면 배열의 크기를 A로 키울 때 A가 기존 배열의 1.5배 크기보다 클 경우 크기를 A로 키운다.
하지만 배열의 1.5배 크기보다 작을 경우 기존 배열의 1.5배 크기로 키운다.</p>
<p>하지만 배열의 크기를 키울 때 메모리를 초과하여 음수값으로 변경되면 OutOfMemoryError 에러가 발생한다.</p>
<hr>
<h2 id="modcount와-concurrentmodificationexception">modCount와 ConcurrentModificationException</h2>
<p>ArrayList 클래스의 메서드들을 보면 아래와 같은 코드를 자주 볼 수 있다.</p>
<pre><code class="language-java">if (modCount != expectedModCount) {
    throw new ConcurrentModification();
}</code></pre>
<p>이는 ArrayList 클래스가 멀티 쓰레드 환경을 지원하지 않기 때문에 멀티쓰레드 환경에서의 병렬 실행을 방지하기 위한 코드이다.</p>
<p>만약 String 타입의 리스트가 있다고 가정해보자.
한 쓰레드는 해당 리스트에 &quot;good&quot;이라는 문자열을 추가하고, 한 쓰레드에서는 &quot;good&quot;이라는 문자열을 &quot;cool&quot;문자열로 변환한다.</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/3bca6359-7545-4d73-b43f-a00e19767b40/image.png" alt="threads"></p>
<p>위의 코드를 실행하면 아래와 같은 예외가 발생하는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/e9d3c60a-c10e-4bb7-ba61-8a24819f6dc8/image.png" alt="exception"></p>
<p>위의 replaceAll 메서드를 보면 modCount와 expectedModCount가 다르면 ConcurrentModificationException 예외가 발생하는 코드를 볼 수 있다.</p>
<p>현재 메서드를 실행하는 동안 modCount가 변경되면 (즉, 구조적으로 변경이 일어나면) 멀티 쓰레드 환경에서의 사용을 감지하고 ConcurrentModificationException 예외가 발생하는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Enhanced For 문에서 remove를 하면 안되는 이유]]></title>
            <link>https://velog.io/@lucius__k/Enhanced-For-%EB%AC%B8%EC%97%90%EC%84%9C-remove%EB%A5%BC-%ED%95%98%EB%A9%B4-%EC%95%88%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@lucius__k/Enhanced-For-%EB%AC%B8%EC%97%90%EC%84%9C-remove%EB%A5%BC-%ED%95%98%EB%A9%B4-%EC%95%88%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Wed, 27 Dec 2023 15:30:15 GMT</pubDate>
            <description><![CDATA[<h1 id="remove를-사용하게-되면">remove를 사용하게 되면</h1>
<p>Enhanced For 문에서 ArrayList의 remove 메서드를 사용하게 되면 아래와 같은 예외를 마주칠 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/2d64b3e0-8ee3-44b7-87d8-e7bb2732ed57/image.png" alt="exception"></p>
<p>Enhanced For 문에서 remove 메서드를 사용하면 왜 예외가 발생하는지 알아보자.</p>
<hr>
<h1 id="enhanced-for-문을-사용하려면">Enhanced For 문을 사용하려면</h1>
<p>Enhanced For 문을 사용하기 위해서는 객체가 Iterable 인터페이스를 구현해야 한다.</p>
<pre><code class="language-java">public interface Iterable&lt;T&gt; {

    Iterator&lt;T&gt; iterator();

    default void forEach(Consumer&lt;? super T&gt; action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator&lt;T&gt; spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}</code></pre>
<p>Enhanced For 문을 사용하면 우선 iterator() 메서드를 호출하게 된다.
iterator() 메서드는 그저 Iterator 객체를 반환하면 된다.</p>
<pre><code class="language-java">public interface Iterator&lt;E&gt; {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException(&quot;remove&quot;);
    }

    default void forEachRemaining(Consumer&lt;? super E&gt; action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}</code></pre>
<p>Enhanced For 문이 진행되는 과정을 보면 아래와 같다.</p>
<ol>
<li>해당 객체의 iterator() 메서드를 호출한다.</li>
<li>위의 메서드에서 반환된 Iterator 객체의 hasNext() 메서드의 반환값이 false면 for문이 종료되고 true면 다음 단계로 진행한다.</li>
<li>next() 메서드를 호출하여 원소를 반환한다.</li>
<li>Enhanced For 문의 몸체의 로직을 수행한다.</li>
<li>2번부터 다시 진행한다.</li>
</ol>
<hr>
<h1 id="arraylist">ArrayList</h1>
<p>remove() 메서드를 호출했을 때 예외가 발생하는 이유를 알기 위해서 ArrayList를 대표로 알아보자.</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/06618967-1623-4493-9ccb-00d59b70b74d/image.png" alt="arraylist"></p>
<p>ArrayList 클래스는 위와 같이 구성되어 있다.
ArrayList 클래스는 Iterable 인터페이스를 구현하고, AbstractList를 상속받는다는 것을 기억하자.
우선 ArrayList의 iterator() 메서드를 보자.</p>
<pre><code class="language-java">public Iterator&lt;E&gt; iterator() {
    return new Itr();
}</code></pre>
<p>Enhanced For 문이 선언되는 시점에 해당 메서드가 호출이 되어 Itr (ArrayList의 내부 클래스) 객체를 반환하는 것이다.</p>
<p>그런 다음 hasNext()와 next() 메서드를 호출한다.</p>
<p>아래는 Itr 클래스의 hasNext()와 next() 메서드 이다.</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/bac04e9c-4fc2-448e-af3f-868139c0d488/image.png" alt="method"></p>
<p>hasNext() 메서드에서 cursor 와 size가 같지 않으면 true를 반환한다.
여기서 <strong>cursor 는 다음 원소의 인덱스</strong>, <strong>size는 현재 ArrayList의 크기(원소의 개수)</strong>를 뜻한다.</p>
<p>next() 메서드에서는 cursor를 이용해 다음 원소를 반환한다.
메서드를 보면 cursor가 elementData의 길이보다 크거나 같으면 ConcurrentModificationException 예외를 발생시키는 것을 볼 수 있다.</p>
<pre><code class="language-java">
transient Object[] elementData; // ArrayList의 원소들이 실제로 저장되는 배열

if (i &gt;= elementData.length) {
    throw new ConcurrentModificationException();
}</code></pre>
<p>그렇다면 remove 메서드를 사용하여 원소의 수를 줄여 cursor보다 원소들의 수가 적으면 ConcurrentModificationException 예외가 발생하는 것이라고 생각할 수 있다.
하지만 아니다.
예외가 발생하는 위치를 보면 next() 메서드의 첫 줄인 checkForComodification() 메서드에서 예외가 발생한다.</p>
<hr>
<h1 id="예외가-발생하는-이유">예외가 발생하는 이유</h1>
<p>checkForComodification() 메서드는 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/e8bae5e4-396c-4f4c-a98a-ae35bc4e9c75/image.png" alt="checkforcomodification"></p>
<p>modCount와 expectedModCount가 같지 않으면 예외가 발생하는 것을 볼 수 있다.
그렇다면 modCount와 expectedModCount는 무엇일까</p>
<h2 id="modcount">modCount</h2>
<p>expectedModCount는 Itr 클래스에 선언되어 있는데 그저 modCount의 값을 복사한다.
modCount는 ArrayList가 상속받는 AbstractList에 선언되어 있고 주석에 이렇게 적혀있다.</p>
<pre><code>The number of times this list has been structurally modified.</code></pre><p>해석해 보면 구조적으로 변경된 횟수를 뜻한다.
ArrayList 객체에 구조적인 변경이 있을 경우 modCount의 값이 증가하게 된다.</p>
<p>Enhanced For 문을 사용하여 Itr 객체를 생성하면 현재 ArrayList의 구조적으로 변경된 횟수인 modCount를 expectedModCount라는 이름으로 저장하고 해당 ArrayList 객체가 구조적인 변경이 있을 경우에 modCount가 증가하여 ConcurrentModificationException 예외가 발생하는 것이다.</p>
<p>modCount가 증가하는 메서드는 아래와 같다.</p>
<pre><code class="language-java">trimToSize()
ensureCapacity()
add()
addAll()
remove()
clear()
removeIf()
replaceAll()
sort()</code></pre>
<p>remove() 메서드를 사용하여 원소를 제거하는 것 뿐만 아니라 원소의 추가, 정렬 등도 구조적인 변경이 있으므로 예외가 발생한다는 것을 알 수 있다.
ArrayList의 원소들이 저장된 elementData 배열에 변경이 일어나면 Enhanced For 문이 제대로 작동할 수 없기 때문에 예외가 발생하는 것이다.</p>
<p>ArrayList 뿐만 아니라 다른 컬렉션들도 예외가 발생한다.(LinkedList, PriorityQueue, HashSet, ...)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Item60] 정확한 답이 필요하다면 float와 double은 피하라]]></title>
            <link>https://velog.io/@lucius__k/Item60-%EC%A0%95%ED%99%95%ED%95%9C-%EB%8B%B5%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%98%EB%8B%A4%EB%A9%B4-float%EC%99%80-double%EC%9D%80-%ED%94%BC%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@lucius__k/Item60-%EC%A0%95%ED%99%95%ED%95%9C-%EB%8B%B5%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%98%EB%8B%A4%EB%A9%B4-float%EC%99%80-double%EC%9D%80-%ED%94%BC%ED%95%98%EB%9D%BC</guid>
            <pubDate>Tue, 26 Dec 2023 12:48:43 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<h1 id="float와-double-타입을-피해야하는-이유">float와 double 타입을 피해야하는 이유</h1>
<p>float와 double 타입은 과학과 공학 계산용으로 설계되었다. 이진 부동소수점 연산에 쓰이기 때문에 정확한 값을 계산할 수 없다.</p>
<pre><code class="language-java">System.out.println(0.1 + 0.2);</code></pre>
<pre><code class="language-text">결과: 0.30000000000000004</code></pre>
<hr>
<h2 id="부동소수점">부동소수점</h2>
<p><img src="https://codetorial.net/articles/_images/floating_point_04.png" alt="부동소수점"></p>
<p>부동소수점을 표현하는 방식도 정하는 방식에 따라 다를 수 있지만 일반적으로 사용하고 있는 방식은 IEEE에서 표준으로 제안한 방식이다.(IEEE - 754)</p>
<h3 id="-9625를-부동소수점으로-표현하게-되면">-9.625를 부동소수점으로 표현하게 되면</h3>
<p>부호가 음수이므로 1이 된다.</p>
<pre><code>1 | 00000000 | 0000000 00000000 00000000</code></pre><p>9.625를 2진수로 표현하게 되면 1001.101이 된다.
1001.101을 소수점 왼쪽에 1만 남도록 소수점을 이동하게 되면 1.001101이 된다.
아래와 같이 표현할 수 있다.</p>
<pre><code>정규화된 부동소수점
1001.101 = 1.001101 X 2^3</code></pre><p>여기서 소수점의 오른쪽 부분을 가수에 채워주도록 한다.</p>
<pre><code>1 | 00000000 | 00110100 00000000 00000000</code></pre><p>127과 정규화된 부동소수점의 2의 지수인 3을 더한 값인 130을 2진수로 나타내어 지수 부분에 채워주도록 한다.</p>
<pre><code>1 | 10000010 | 0011010 00000000 00000000</code></pre><hr>
<h3 id="무한-소수">무한 소수</h3>
<p>위처럼 9.625와 같은 숫자가 아닌 0.1을 부동소수점으로 나타낸다고 가정해 보자.
먼저 2진수를 나타내면
0.000110011... 과 같이 끝이 없는 소수가 되는데 이를 무한 소수라고 한다.
그리고 이것을 부동소수점으로 표현하기 위해서 반올림하여 가수 부분을 채우게 된다.
그렇기 때문에 대부분의 실수 계산에 오차가 생기는 것이다.</p>
<hr>
<h1 id="정확한-계산을-위한-대안">정확한 계산을 위한 대안</h1>
<h2 id="bigdecimal">BigDecimal</h2>
<p>정확한 계산을 하기 위해 BigDecimal을 사용할 수 있다.</p>
<pre><code class="language-java">BigDecimal decimal1 = new BigDecimal(&quot;.1&quot;);
BigDecimal decimal2 = new BigDecimal(&quot;.2&quot;);
System.out.println(decimal1.add(decimal2));</code></pre>
<pre><code>결과: 0.3</code></pre><p>하지만 BigDecimal은 기본 타입보다 느리다는 단점을 갖고 있다.</p>
<p>아래는 BigDecimal을 이용해 1부터 10000000까지 모두 더했을 경우에 걸린 시간을 계산한 결과이다.</p>
<pre><code class="language-java">BigDecimal result = new BigDecimal(&quot;0&quot;);
long startTime = System.currentTimeMillis();
for (int i = 1; i &lt;= 10000000; i++) {
    result = result.add(new BigDecimal(i));
}
System.out.println(&quot;ResultTime = &quot; + (System.currentTimeMillis() - startTime));
System.out.println(&quot;Result = &quot; + result);</code></pre>
<pre><code>ResultTime = 144
result = 50000005000000</code></pre><p>아래는 long 타입을 이용해 계산한 결과이다.</p>
<pre><code class="language-java">long result = 0;
long startTime = System.currentTimeMillis();
for (int i = 1; i &lt;= 10000000; i++){
    result += i;
}
System.out.println(&quot;ResultTime = &quot; + (System.currentTimeMillis() - startTime));
System.out.println(&quot;Result = &quot; + result);</code></pre>
<pre><code>ResultTime = 8
result = 50000005000000</code></pre><p>long 타입을 이용한 계산이 훨씬 빠른 것을 볼 수 있다.</p>
<p>일회성 계산을 하는 경우에는 BigDecimal이 좋은 방법일 수 있다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>성능 저하를 신경 쓰지 않겠다면 BigDecimal을 사용하면 된다.
하지만 성능이 중요하고 소수점을 직접 추적할 수 있고 숫자가 너무 크지 않다면 int나 long을 사용하여 소수점을 직접 관리하여 계산하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Item58] 전통적인 for 문보다는 for-each 문을 사용하라]]></title>
            <link>https://velog.io/@lucius__k/%EC%A0%84%ED%86%B5%EC%A0%81%EC%9D%B8-for-%EB%AC%B8%EB%B3%B4%EB%8B%A4%EB%8A%94-for-each-%EB%AC%B8%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@lucius__k/%EC%A0%84%ED%86%B5%EC%A0%81%EC%9D%B8-for-%EB%AC%B8%EB%B3%B4%EB%8B%A4%EB%8A%94-for-each-%EB%AC%B8%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Wed, 20 Dec 2023 06:35:25 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<h1 id="for-문과-for-each-문의-비교">for 문과 for-each 문의 비교</h1>
<p>아래는 for 문으로 컬렉션을 순회하는 코드다.</p>
<pre><code class="language-java">for (Iterator&lt;Element&gt; i = c.iterator(); i.hasNext();) {
    Element e = i.next();
    ...
}</code></pre>
<p>아래는 for 문으로 배열을 순회하는 코드다.</p>
<pre><code class="language-java">for (int i = 0; i &lt; a.length; i++) {
    int n = a[i];
    ...
}</code></pre>
<p>위의 for 문들은 while 문을 사용하는 것보다는 안전하지만 가장 좋은 방법은 아니다.
반복자와 인덱스가 사용되어 코드를 지저분하게 하며 추가적인 요소들로 인한 오류가 발생할 가능성이 높아진다.
컬렉션을 순회할 경우 반복자는 3번 등장하며, 배열을 순회할 경우 인덱스는 4번 등장하여 변수를 잘못 사용할 가능성이 생기게 된다.</p>
<p>위의 문제들은 for-each 문을 사용하면 모두 해결된다.</p>
<p>for-each 문의 정식 명칭은 &#39;향상된 for 문(enhanced for statement)&#39;이다.</p>
<p>컬렉션을 순회하는 for 문을 for-each 문으로 표현하면 다음과 같다.</p>
<pre><code class="language-java">for (Element e : elements) {
    ...
}</code></pre>
<p>반복자가 없어 코드의 가독성이 좋아지고 오류가 생길 확률이 줄어들게 된다.</p>
<pre><code class="language-java">enum Suit { CLUB, DIAMOND, HEART, SPADE }
enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING }

...

static Collection&lt;Suit&gt; suits = Arrays.asList(Suit.values());
static Collection&lt;Rank&gt; ranks = Arrays.asList(Rank.values());

List&lt;Card&gt; deck = new ArrayList&lt;&gt;();
for (Iterator&lt;Suit&gt; i = suits.iterator(); i.hasNext();) {
    for (Iterator&lt;Rank&gt; j = ranks.iterator(); j.hasNext;) {
        deck.add(new Card(i.next(), j.next())); // Error !!
    }
}</code></pre>
<p>위의 코드에는 오류가 있다.
언뜻 보기에 각 Rank 마다 Suit와 매핑하여 Deck에 추가하는 것 처럼 보이지만 i.next()를 계속 사용하기 때문에 i에 Suit가 존재하지 않게 되면 NoSuchElementException을 던지게 될 것이다.</p>
<p>이는 for-each 문을 사용하게 되면 간단하게 오류를 피할 수 있다.</p>
<pre><code class="language-java">for (Suit suit : suits)
    for (Rank rank : ranks)
        deck.add(new Card(suit, rank));</code></pre>
<hr>
<h1 id="for-each-문을-사용할-수-없는-상황">for-each 문을 사용할 수 없는 상황</h1>
<ul>
<li><span style="color:#8dc5fc"> 파괴적인 필터링(destructive filtering)</span> - 컬렉션을 순회하면서 원소를 제거해야 한다면 반복자의 remove 메서드를 호출해야 한다. 자바 8부터는 Collection의 removeIf 메서드를 사용해 컬렉션을 순회하는 일을 피할 수 있다.</li>
</ul>
<details>
<summary>Collection의 removeIf</summary>
<div>
    <img src="https://velog.velcdn.com/images/lucius__k/post/8daaa5b4-19d2-455f-9b41-6d22b9ccaf05/image.png" alt="removeIf" />
  <p>removeIf() 매개변수로 필터링 조건을 전달하면 해당하는 원소들이 제거된다.</p>
  <hr/>
</div>
</details>

<ul>
<li><p><span style="color:#8dc5fc"> 변형(transforming)</span> - 리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용해야 한다.</p>
</li>
<li><p><span style="color:#8dc5fc"> 병렬 반복(parallel iteration)</span> - 여러 컬렉션을 병렬로 순회해야 한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 한다.</p>
</li>
</ul>
<p>위의 세 상황에서는 for 문을 사용해야 한다.</p>
<p>for-each 문은 Iterable 인터페이스를 구현한 객체라면 무엇이든 순회할 수 있다.</p>
<pre><code class="language-java">public interface Iterable&lt;E&gt; {
    Iterator&lt;E&gt; iterator();
}</code></pre>
<p>Iterable 인터페이스를 구현하기는 까다롭지만 원소들을 묶음으로 관리하는 타입을 작성한다면 Iterable을 구현하여 for-each 문을 사용할 수 있도록 하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OAuth 2.0 이해하기]]></title>
            <link>https://velog.io/@lucius__k/OAuth-2.0-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@lucius__k/OAuth-2.0-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 19 Dec 2023 05:27:28 GMT</pubDate>
            <description><![CDATA[<h1 id="oauth-20">OAuth 2.0</h1>
<p>다른 서비스의 OAuth를 이용하는 개발자의 시선에서 글을 작성하였습니다.</p>
<hr>
<h2 id="기존의-문제점">기존의 문제점</h2>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/96a68ba1-13ed-4732-ba19-cd7e9a947c65/image.png" alt="before-oauth"></p>
<p>우리가 A라는 애플리케이션을 만들었다고 가정하자.
우리 애플리케이션은 사용자의 구글 캘린더에 있는 정보들을 이용하는 서비스다.
그렇다면 우리의 서비스는 사용자에게 구글 이메일과 비밀번호를 받아서 구글서버와 통신을 하여 캘린더 정보들을 받아와야 한다.
하지만 아래와 같은 문제점들이 존재하게 된다.</p>
<ul>
<li>사용자의 구글 아이디, 비밀번호를 일반 텍스트로써 저장해야 한다.</li>
<li>우리의 서비스는 구글의 캘린더 데이터뿐만 아니라 모든 데이터에 접근이 가능하다.</li>
<li>사용자가 구글 데이터 접근을 취소하기 위해서는 구글 계정의 비밀번호를 변경해야 한다.</li>
<li>우리의 서비스가 공격받아 사용자들의 계정을 빼앗길 수 있다.</li>
</ul>
<p>위와 같은 문제점들을 해결하기 위해 OAuth 프로토콜을 이용하여 사용자의 구글 계정에 접근할 수 있다.</p>
<hr>
<h2 id="각-역할-설명">각 역할 설명</h2>
<h3 id="resource-owner">Resource Owner</h3>
<p>데이터를 소유하고 있는 사람을 뜻한다.
위의 예를 이용하면 <strong>사용자</strong>라고 볼 수 있다.</p>
<h3 id="resource-server">Resource Server</h3>
<p>Client의 요청을 받고 데이터를 응답하는 서버를 뜻한다.
위의 예를 이용하면 캘린더 데이터를 획득할 수 있는 <strong>구글</strong>이라고 볼 수 있다.</p>
<h3 id="client">Client</h3>
<p>Resource Owner를 대신하여 데이터에 접근하는 애플리케이션을 뜻한다.
위의 예를 이용하면 <strong>우리의 서비스</strong>라고 볼 수 있다.</p>
<h3 id="authorization-server">Authorization Server</h3>
<p>Resource Owner의 인증을 받는 서버를 뜻한다.
위의 예를 이용하면 <strong>구글의 인증서버</strong>라고 볼 수 있다.</p>
<hr>
<h2 id="oauth-20-흐름">OAuth 2.0 흐름</h2>
<p><img src="https://velog.velcdn.com/images/lucius__k/post/15937496-5e43-4878-a509-5433c0f8315f/image.png" alt=""></p>
<p>※ 구글의 OAuth 2.0을 예를 들어 설명</p>
<p><strong>(1)</strong> 유저가 &#39;구글 계정으로 로그인&#39; 버튼 클릭 -&gt;
<strong>(2)</strong> 구글 인증서버로 연결되는 URL 제공 -&gt;
<strong>(3, 4)</strong> 유저의 데이터 접근 권한 승인 -&gt;
<strong>(5, 6)</strong> Authorization Code를 redirect_uri로 전송 -&gt;
<strong>(7, 8, 9)</strong> Authorization Code를 구글 인증서버에 제공하여 Access Token을 받는다. -&gt;
<strong>(10, 11, 12, 13)</strong> 해당 Access Token으로 구글 API를 이용하여 유저의 데이터에 접근한다.</p>
<p>먼저 유저가 &#39;구글 계정으로 로그인&#39;과 같은 버튼을 클릭하였을 경우 우리는 구글의 인증서버 URL을 제공해야 합니다.</p>
<h3 id="1-구글의-oauth-20-서버로-리디렉션">1. 구글의 OAuth 2.0 서버로 리디렉션</h3>
<p>유저가 OAuth 2.0을 이용한 로그인 버튼을 클릭하였을 경우 구글이 로그인 페이지를 제공할 수 있도록 구글의 OAuth 2.0 서버로 리디렉션해야 합니다. 리디렉션 할 경우에 각 쿼리스트링을 설정해주어야 합니다.
필수 쿼리 스트링은 아래와 같습니다.</p>
<ul>
<li><p>client_id : 애플리케이션의 클라이언트 ID입니다. 이는 구글에 우리의 서비스를 등록할 경우 확인할 수 있습니다.</p>
</li>
<li><p>redirect_uri : 우리의 서비스가 유저의 리소스에 접근하는 것이 승인되었을 경우 유저를 리디렉션하는 uri를 뜻합니다.</p>
</li>
<li><p>response_type : 승인 코드를 반환하는지 여부를 결정합니다. 구글에서는 <strong>code</strong>로 매개변수 값을 설정할 것을 요구합니다.</p>
</li>
<li><p>scope : 우리의 서비스가 유저의 어떤 리소스에 접근할 것인지 나타내는 범위 목록입니다.</p>
</li>
</ul>
<p>아래는 사용 예입니다.</p>
<pre><code class="language-text">https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly&amp;
 response_type=code&amp;
 redirect_uri=https%3A//oauth2.example.com/code&amp;
 client_id=client_id</code></pre>
<p>요청 URL을 만든 후 사용자를 이 URL로 리디렉션합니다.</p>
<p>그후 유저가 승인을 하면 구글은 Authorization Code를 유저에게 전송해주고 유저는 redirect_uri로 해당 code를 전송합니다.</p>
<h3 id="2-액세스-토큰으로-승인-코드-교환">2. 액세스 토큰으로 승인 코드 교환</h3>
<p>유저가 Authorization Code를 우리의 서버로 전송하면 우리는 해당 코드를 구글 인증서버에 전송해 Access Token을 받을 수 있습니다. 여기서도 마찬가지로 전송해야하는 값들이 존재합니다.</p>
<ul>
<li><p>client_id : 애플리케이션의 클라이언트 ID입니다.</p>
</li>
<li><p>client_secret : 애플리케이션의 클라이언트 secret입니다. 구글에 우리의 서비스를 등록할 경우 클라이언트 ID와 함께 확인할 수 있습니다.</p>
</li>
<li><p>code : 유저로부터 전송된 Authorization Code 입니다.</p>
</li>
<li><p>grant_type : authorization_code로 설정해야 합니다. (<a href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3">공식 문서 양식</a>)</p>
</li>
<li><p>redirect_uri : 위에서 지정한 redirect_uri와 같습니다.</p>
</li>
</ul>
<p>아래는 예입니다.</p>
<pre><code class="language-text">POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&amp;
client_id=your_client_id&amp;
client_secret=your_client_secret&amp;
redirect_uri=https%3A//oauth2.example.com/code&amp;
grant_type=authorization_code</code></pre>
<p>이렇게 Access Token을 요청하면 응답에 아래와 같은 필드가 포함됩니다.</p>
<ul>
<li><p>access_token : 애플리케이션에서 구글 API 요청에 사용되는 토큰입니다.</p>
</li>
<li><p>expires_in : Access Token의 남은 수명(초)입니다.</p>
</li>
<li><p>scope : Access Token에 부여되는 접근 권한입니다.</p>
</li>
<li><p>token_type : 반환된 토큰의 유형입니다.</p>
</li>
</ul>
<h2 id="references">References</h2>
<p><a href="https://datatracker.ietf.org/doc/html/rfc6749">https://datatracker.ietf.org/doc/html/rfc6749</a></p>
<p><a href="https://developers.google.com/identity/protocols/oauth2/web-server?hl=ko">https://developers.google.com/identity/protocols/oauth2/web-server?hl=ko</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Item57] 지역변수의 범위를 최소화하라]]></title>
            <link>https://velog.io/@lucius__k/%EC%A7%80%EC%97%AD%EB%B3%80%EC%88%98%EC%9D%98-%EB%B2%94%EC%9C%84%EB%A5%BC-%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@lucius__k/%EC%A7%80%EC%97%AD%EB%B3%80%EC%88%98%EC%9D%98-%EB%B2%94%EC%9C%84%EB%A5%BC-%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EB%9D%BC</guid>
            <pubDate>Wed, 13 Dec 2023 14:23:07 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<p>지역변수의 범위를 최소화하면 코드 가독성, 유지보수성이 높아지고 오류 가능성이 낮아진다.</p>
<p>다음은 지역변수의 범위를 줄이는 3가지 방법이다.</p>
<h2 id="사용시점에-선언하기">사용시점에 선언하기</h2>
<p>지역변수는 사용할 시점에 선언하는 것이 가장 좋다.
만약 미리 선언하게 되면 코드가 어수선해져 가독성이 떨어진다.
또한 변수를 실제로 사용하는 시점에 해당 변수의 타입, 초기값이 기억나지 않을 수 있다.</p>
<p>그리고 만약 사용하는 블록 바깥에서 선언하게 될 경우 모두 사용하고 나서도 살아 있게 되므로 오류를 불러 일으키기 쉽다.</p>
<pre><code class="language-java">public void badMethod() { // 좋지 않은 코드
    Animal d = new Dog();
    Animal c = new Cat();

    Information information = getInformation();

    String dogName = information.getDogName();
    String catName = information.getCatName();
    d.setName(dogName);
    c.setName(catName);

    addAnimal(d, c);
}

public void goodMethod() {
    Information information = getInformation();
    String dogName = information.getDogName();
    String catName = information.getCatName();

    Animal d = new Dog();
    d.setName(dogName);

    Animal c = new Cat();
    c.setName(catName);

    addAnimal(d, c);
}</code></pre>
<h2 id="선언과-동시에-초기화하기">선언과 동시에 초기화하기</h2>
<p>초기화에 필요한 정보가 충분하지 않다면 충분해질 때까지 선언을 미뤄야 한다.
이는 가독성도 좋지않고, 불완전한 객체를 생성함으로써 개발자에게 실수를 유도할 수 있기 때문이다.</p>
<p>하지만 try-catch 문은 예외다.
변수를 초기화 하는 표현식에서 예외가 발생할 가능성이 있다면 try 블록 안에서 초기화해야 한다.
이는 메서드로 예외가 전파될 수 있기 때문이다.</p>
<p>변수를 정확히 초기화하지 못하더라도 try 블록 바깥에 선언해야 할 경우에는 try 블록 바로 앞에서 선언해야 한다.
아래가 그 예이다.</p>
<pre><code class="language-java">public Connection getConnection() {
    Connection conn = null;

    try {
        Class.forName(DB_DRIVER);
        conn = DriverManager.getConnection(DB_URL, ID, PASSWORD);
    } catch (Exception e) {
        ...
    }

    return conn;
}</code></pre>
<p>그리고 반복문을 사용할 경우 반복 변수의 범위는 for 키워드와 몸체 사이의 괄호, 반복문의 몸체 안으로 제한된다. 따라서 반복 변수를 반복문이 종료되고 나서도 사용해야 하는 경우가 아니라면 while 문보다 for 문을 사용하는 편이 좋다.</p>
<pre><code class="language-java">for (Element e : c) {
    ... // e로 무언가를 한다.
}</code></pre>
<p>만일 반복자를 사용해야 하는 경우라면 for-each 문 대신 for 문을 사용하는 것이 좋다.</p>
<pre><code class="language-java">for (Iterator&lt;Element&gt; i = c.iterator(); i.hasNext();) {
    Element e = i.next();
    ... // e와 i로 무언가를 한다.
}</code></pre>
<p>다음의 코드를 보면 while 문보다 for 문이 더 나은 이유를 알 수 있다.</p>
<pre><code class="language-java">Iterator&lt;Element&gt; i = c.iterator();
while (i.hasNext()) {
    doSomething(i.next());
}

...

Iterator&lt;Element&gt; i2 = c2.iterator();
while (i.hasNext()) { // Bug!
    doSomething(i2.next());
}</code></pre>
<p>while 문을 사용하게 될 경우 while 블록 바깥에서도 해당 변수가 살아있기 때문에 실수를 할 가능성이 높다. 그리고 이는 컴파일 시점에는 알 수 없는 런타임 에러이므로 나중에 해당 버그를 찾아내기 어렵다.</p>
<p>하지만 for 문을 사용하게 되면 for 문 몸체 안으로 변수의 범위가 좁혀지기 때문에 바깥에서는 사용을 할 수 없어 실수할 가능성이 줄어들게 된다.</p>
<pre><code class="language-java">for (Iterator&lt;Element&gt; i = c.iterator(); i.hasNext();) {
    doSomething(i.next());
}

for (Iterator&lt;Element&gt; i = c2.iterator(); i.hasNext();) {
    doSomething(i.next());
}</code></pre>
<p>위의 두 for 문에서 i는 각각 다른 변수이다.</p>
<p>또한 for 문을 사용할 경우 while 문보다 짧아서 가독성이 좋다는 장점도 있다.</p>
<pre><code class="language-java">for (int i = 0, n = expensiveComputation(); i &lt; n; i++) {
    ... // i로 무언가를 한다.
}</code></pre>
<p>다음은 while 문을 사용했을 경우이다.</p>
<pre><code class="language-java">int i = 0;
while (i &lt; expensiveComputation()) {
    ... // i로 무언가를 한다.
    i++;
}</code></pre>
<p>for 문을 사용하게 되면 변수 n에 expensiveComputation() 메서드의 반환 값을 저장하여 다시 메서드를 호출할 필요가 없게 된다. 하지만 while 문을 사용하게 되면 매 비교마다 호출해야 한다.</p>
<h2 id="메서드를-작게-유지하기">메서드를 작게 유지하기</h2>
<p>한 메서드에서 여러가지 기능을 처리한다면 그중 한 기능과 관련된 지역변수라도 다른 기능을 수행하는 코드에서 접근할 수 있을 것이다. 메서드를 기능별로 나누면 간단히 해결할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Item2] 생성자에 매개변수가 많다면 빌더를 고려하라]]></title>
            <link>https://velog.io/@lucius__k/%EC%83%9D%EC%84%B1%EC%9E%90%EC%97%90-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EA%B0%80-%EB%A7%8E%EB%8B%A4%EB%A9%B4-%EB%B9%8C%EB%8D%94%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@lucius__k/%EC%83%9D%EC%84%B1%EC%9E%90%EC%97%90-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EA%B0%80-%EB%A7%8E%EB%8B%A4%EB%A9%B4-%EB%B9%8C%EB%8D%94%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sat, 09 Dec 2023 07:13:01 GMT</pubDate>
            <description><![CDATA[<p>※ Effective Java 3/E</p>
<p>생성자에 매개변수가 많을 경우 사용할 수 있는 3가지 패턴이 있다.</p>
<h1 id="점층적-생성자-패턴">점층적 생성자 패턴</h1>
<p>생성자에 매개변수가 많이 있을 경우에 점층적 생성자 패턴(Telescoping Constructor Pattern)을 사용하여 다음과 같이 구현할 수 있다.</p>
<pre><code class="language-java">public class NutritionFacts {

    private final int servingSize; // (ml, 1회 제공량) 필수
    private final int servings; // (회, 총 n회 제공량) 필수
    private final int calories; // (1회 제공량당) 선택
    private final int fat; // (g/1회 제공량) 선택
    private final int sodium; // (mg/1회 제공량) 선택
    private final int carbohydrate; // (g/1회 제공량) 선택

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

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

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

    ...

    public NutritionFacts(int servingSize, 
                            int servings, 
                            int calories, 
                            int fat, 
                            int sodium, 
                            int carbohydrate) {

        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }
}</code></pre>
<p>위와 같이 매개변수 개수만큼 생성자를 늘리는 방식을 점층적 생성자 패턴이라고 한다.</p>
<p>클라이언트는 원하는 생성자를 호출해 인스턴스를 생성할 수 있다.</p>
<p>하지만 매개변수가 많을 경우 아래와 같은 단점이 존재한다.</p>
<h2 id="점층적-생성자-패턴의-단점">점층적 생성자 패턴의 단점</h2>
<pre><code class="language-java">NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);</code></pre>
<p>해당 코드를 보았을 때 영향정보를 한눈에 알아보기 쉽지않다.</p>
<p>따라서 해당 코드를 작성하는 사람도 실수를 하기 쉬워진다.</p>
<p>위와 같이 같은 타입의 매개변수가 연달아 있으면 찾기 어려운 버그로 이어질 수 있다.</p>
<p>만일 매개변수의 순서를 잘못 입력할 경우 컴파일러에러가 아닌 런타임에러로 이어지게 된다.</p>
<h1 id="자바빈즈-패턴">자바빈즈 패턴</h1>
<p>다른 대안으로는 자바빈즈 패턴(JavaBeans Pattern)이 있다.</p>
<pre><code class="language-java">public class NutritionFacts {

    // 기본값이 있다면 기본값으로 초기화한다.
    private int servingSize = -1; // 필수, 기본값 없음
    private int servings = -1; // 필수, 기본값 없음
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    public NutritionFacts() {} // 생략 가능

    // 세터 메서드들
    public void setServingSize(int servingSize) { this.servingSize = servingSize;}
    public void setServings(int servings) { this.servings = servings;}
    public void setCalories(int calories) { this.calories = calories;}
    public void setFat(int fat) { this.fat = fat;}
    public void setSodium(int sodium) { this.sodium = sodium;}
    public void setCarbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate;}
}</code></pre>
<p>위와 같이 기본 생성자만 구현을 한 후, setter 메서드를 이용해 값들을 설정하는 방식을 자바빈즈 패턴이라고 한다.</p>
<p>자바빈즈 패턴을 사용할 경우 점층적 생성자 패턴의 단점이 보이지 않는 것을 알 수 있다.</p>
<p>클라이언트는 코드를 작성할 경우 실수를 할 확률이 줄어들고 가독성이 좋아진다.</p>
<p>하지만 마찬가지로 자바빈즈 패턴에도 단점이 존재한다.</p>
<h2 id="자바빈즈-패턴의-단점">자바빈즈 패턴의 단점</h2>
<pre><code class="language-java">NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);</code></pre>
<p>우선 객체를 하나 생성할 경우 생성자 이외의 많은 메서드들을 호출해야한다.</p>
<p>그리고 객체에 값들을 설정하기 전까지 일관성이 무너진 상태에 놓이게 된다.</p>
<p>일관성이 깨진 객체를 사용할 경우 런타임에러로 디버깅하기 쉽지 않다.</p>
<p>그리고 불변 클래스로 만들 수 없게 된다.</p>
<p>이는 스레드 안전성을 위해서는 추가적인 작업이 필요하다는 것을 의미한다.</p>
<h1 id="빌더-패턴">빌더 패턴</h1>
<p>위의 두 패턴들의 장점들을 모두 가지고 있는 빌더 패턴이 있다.</p>
<pre><code class="language-java">public class NutritionFacts {

    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {

        // 필수 매개변수
        private final int servingSize;
        private final int servings;

        // 선택 매개변수 - 기본값으로 초기화
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

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

        public Builder fat(int fat) {
            this.fat = fat;
            return this;
        }

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

        public Builder carbohydrate(int carbohydrate) {
            this.carbohydrate = carbohydrate;
            return this;
        }

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

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}</code></pre>
<p>클라이언트는 생성자를 직접 호출하는 대신, 필수 매개변수만으로 빌더 객체를 생성한다.</p>
<p>그 후 빌더 객체의 setter 메서드들을 호출해 값을 설정한 후 build 메서드를 호출해 원하는 객체를 생성할 수 있다.</p>
<p>빌더 패턴을 사용하면 불변 클래스로 만들 수 있고, 가독성도 좋아진다.</p>
<p>다음은 클라이언트가 빌더 패턴으로 객체를 생성하였을 경우의 코드이다.</p>
<pre><code class="language-java">NutritionFacts cocaCola = new NutritionFacts.builder(240, 8)
                                            .calories(100)
                                            .sodium(35)
                                            .carbohydrate(27)
                                            .build();</code></pre>
<p>이처럼 값을 설정할 수 있는데 builder 객체 자신을 반환하기 때문에 메서드를 연쇄적으로 사용할 수 있다.</p>
<p>이런 방식을 플루언트 API(fluent API) 혹은 메서드 연쇄(method chaining)라고 한다.</p>
<h2 id="계층적으로-설계된-클래스와-함께-사용할-경우">계층적으로 설계된 클래스와 함께 사용할 경우</h2>
<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();

        // 하위 클래스는 이 메서드를 재정의(overriding)하여
        // &quot;this&quot;를 반환하도록 해야 한다.
        protected abstract T self();
    }

    Pizza(Builder&lt;?&gt; builder) {
        toppings = builder.toppings.clone();
    }
}</code></pre>
<p>Pizza 추상 클래스안에 Builder 추상 클래스가 있고 구체 클래스에서는 해당 Builder 구체 클래스를 갖게 한다.</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>
<pre><code class="language-java">// 칼초네 피자 - 소스를 넣을지 선택하는 매개변수가 필수
public class Calzone extends Pizza {

    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder&lt;Builder&gt; {

        private boolean sauceInside = false; // 기본값

        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

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

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

    private Calzone(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }
}</code></pre>
<p>위에 Pizza 추상 클래스를 상속받은 하위 클래스 2개가 있다.</p>
<p>각각의 클래스는 Pizza 추상 클래스를 상속받고, Builder 클래스는 추상 클래스의 Builder 클래스를 상속받는다.</p>
<p>build() 메서드에서 Pizza가 아닌 상속 받은 클래스를 반환하도록 하는데 이를 공변 반환 타이핑(covariant return typing)이라고 한다.</p>
<details>
<summary>공변 반환 타이핑</summary>
<div markdown="1">
  <hr/>
  <p>JDK 1.5에서 추가된 기능이다.</p>
  <p>부모 클래스의 메서드를 Overriding하는 경우 반환 타입을 하위 타입으로 변경할 수 있다.</p>
  <hr/>
</div>
</details>

<p>self() 메서드는 addTopping() 메서드에서 메서드 연쇄를 지원하기 위해 this를 반환하도록 오버라이딩해야한다.</p>
<p>아래는 클라이언트에서의 코드이다.</p>
<pre><code class="language-java">NyPizza nyPizza = new NyPizza.Builder(SMALL)
                                .addTopping(SAUSAGE)
                                .addTopping(ONION)
                                .build();

Calzone czPizza = new Calzone.Builder()
                                .addTopping(HAM)
                                .sauceInside()
                                .build();</code></pre>
<p>빌더 패턴을 사용하였을 경우에 생성자로는 누릴 수 없는 가변인수 메서드를 여러 개 사용할 수 있다.</p>
<ul>
<li>가변인수 메서드 사용 예</li>
</ul>
<pre><code class="language-java">NyPizza nyPizza = new NyPizza.Builder(SMALL)
                                .addToppings(SAUSAGE, ONION, ...)
                                .build();</code></pre>
<h2 id="빌더-패턴와-맞지-않은-경우">빌더 패턴와 맞지 않은 경우</h2>
<p>멤버 변수가 적은 클래스의 경우에는 빌더 패턴은 작성해야 하는 코드량만 늘릴 수 있다.</p>
<p>멤버 변수의 개수를 확인하고, 미래에 멤버 변수가 추가될 수 있는지를 염두에 두고 사용해야 한다.</p>
<p><strong>※ 하지만 Lombok 라이브러리를 사용하게 되면 간단하게 빌더 패턴을 구현할 수 있다.</strong></p>
<p>그리고 또한 가변 객체를 생성할 경우이다. 가변 객체를 생성하게 될 경우 setter 메서드를 사용할 수 있는데 빌더 패턴을 사용하게 되면 작성해야 하는 코드가 많아진다.</p>
<h2 id="빌더-패턴-사용-예">빌더 패턴 사용 예</h2>
<p>빌더 패턴을 Java API에서 찾을 수 있다.</p>
<pre><code class="language-java">Locale locale = new Locale.Builder()
                            .setLanguage(&quot;sr&quot;)
                            .setScript(&quot;Latn&quot;)
                            .setRegion(&quot;RS&quot;)
                            .build();

Calendar calendar = new Calendar.Builder()
                                .setCalendarType(&quot;iso8601&quot;)
                                .setWeekDate(2012, 1, MONDAY)
                                .build();</code></pre>
<p>이외에도 다음과 같은 사용 예시가 있다.</p>
<ul>
<li>Stream.Builder</li>
<li>IntStream.Builder</li>
<li>LongStream.Builder</li>
<li>DoubleStream.Builder</li>
<li>StringBuilder</li>
<li>Connection.prepareStatement(...);</li>
</ul>
<h1 id="references">References</h1>
<p><a href="https://amenable.tistory.com/54">https://amenable.tistory.com/54</a></p>
<p><a href="https://stackoverflow.com/questions/2169190/example-of-builder-pattern-in-java-api">https://stackoverflow.com/questions/2169190/example-of-builder-pattern-in-java-api</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 모델 3단계 (스키마란?)]]></title>
            <link>https://velog.io/@lucius__k/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8-3%EB%8B%A8%EA%B3%84-%EC%8A%A4%ED%82%A4%EB%A7%88%EB%9E%80</link>
            <guid>https://velog.io/@lucius__k/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8-3%EB%8B%A8%EA%B3%84-%EC%8A%A4%ED%82%A4%EB%A7%88%EB%9E%80</guid>
            <pubDate>Thu, 07 Dec 2023 09:13:45 GMT</pubDate>
            <description><![CDATA[<h1 id="스키마란">스키마란?</h1>
<p>스키마란 데이터베이스에 저장되는 데이터 구조와 제약조건을 정의한 것이다.</p>
<h1 id="데이터-모델-3단계">데이터 모델 3단계</h1>
<p>데이터 모델은 다음과 같은 3단계로 이루어진다.</p>
<ul>
<li>외부 모델: 사용자가 보는 데이터베이스 구조</li>
<li>개념 모델: DB 내의 전체 테이블 집합 (전체 DB 구조)</li>
<li>내부 모델: 디스크 저장 구조의 세부 사항과 DB에 대한 접근 경로(인덱스)들의 집합</li>
</ul>
<p>하나씩 자세히 알아보도록 하자.</p>
<hr>
<h1 id="외부-모델">외부 모델</h1>
<ul>
<li>사용자가 보는 데이터베이스의 구조이다.</li>
<li>사용자가 필요로 하는 &quot;테이블의 일부(뷰)&quot;</li>
<li>특정 사용자의 관심 부분을 뷰로 제공하고, 나머지는 각 사용자로부터 은폐한다.</li>
<li>사용자가 보는 데이터는 테이블 조각, 일부 이다.</li>
<li>외부 모델 혹은 뷰를 정리해놓은 것이 외부 스키마이다.</li>
</ul>
<h1 id="개념-모델">개념 모델</h1>
<ul>
<li>개념 스키마는 전체 테이블 리스트라고도 볼 수 있다.</li>
<li>외부 모델을 내부 모델로 매핑하는 역할을 한다.</li>
<li>외부 모델을 내부 모델로 매핑해주는 것이 개념 스키마이다.</li>
</ul>
<h1 id="내부-모델">내부 모델</h1>
<ul>
<li>디스크 저장 구조를 명시하며 세부 사항과 DB에 대한 접근 경로(인덱스)들의 집합이다.</li>
<li>내부 모델을 정리해놓은 것이 내부 스키마이다.</li>
</ul>
<hr>
<h1 id="흐름도">흐름도</h1>
<p><img src="https://blog.kakaocdn.net/dn/bvqMrR/btqN02bgEod/pkZLTK4VDB99FQrFckK2X1/img.png" alt="스키마 흐름도">
<a href="https://lifegoesonme.tistory.com/418">이미지 출처</a></p>
<p>사용자가 질의어를 사용하면 개념 스키마가 내부 스키마로 매핑하고 내부 스키마가 저장 장치에서 해당 데이터를 찾아와 사용자에게 전달한다.</p>
<hr>
<h1 id="데이터-독립성">데이터 독립성</h1>
<p>관계형 DBMS의 3단계 모델은 뛰어난 데이터 독립성을 제공한다.</p>
<h2 id="모델간-사상">모델간 사상</h2>
<ul>
<li>사용자가 외부 스키마(뷰)를 참조하여 데이터를 요구한다.</li>
<li>데이터베이스 내에서 개념 스키마에 대한 요구로 변환한다.</li>
<li>다시 내부 스키마에 대한 요구로의 변환 과정을 거친다.</li>
<li>해당 디스크에 접근하여 데이터를 추출한 후 사용자에게 데이터를 다양한 형태로 제공한다.</li>
</ul>
<h2 id="데이터-독립성-1">데이터 독립성</h2>
<ul>
<li>상위 수준의 스키마를 변경할 필요 없이 하위 수준의 스키마를 변경할 수 있는 성질로써 저장 구조가 변경되어도 뷰 사용자는 영향을 받지 않는 성질</li>
<li>사용자 편의성, 응용의 안정성 등 데이터베이스의 지속적인 발전을 위해 중요한 성질</li>
</ul>
<hr>
<h1 id="references">References</h1>
<p><a href="http://www.kocw.net/home/cview.do?cid=ac8b9a43cd972958">http://www.kocw.net/home/cview.do?cid=ac8b9a43cd972958</a></p>
<p><a href="https://terms.tta.or.kr/dictionary/dictionaryView.do?subject=%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4+%EC%8A%A4%ED%82%A4%EB%A7%88">https://terms.tta.or.kr/dictionary/dictionaryView.do?subject=%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4+%EC%8A%A4%ED%82%A4%EB%A7%88</a></p>
]]></description>
        </item>
    </channel>
</rss>