<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-jhjhj.log</title>
        <link>https://velog.io/</link>
        <description>어제보다 더 나은</description>
        <lastBuildDate>Sun, 03 Apr 2022 12:20:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-jhjhj.log</title>
            <url>https://images.velog.io/images/dev_jhjhj/profile/74c4aa68-5386-48a4-aa82-ad8159ea8e9f/jhj_icon.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-jhjhj.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_jhjhj" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[3장. 함수 정의와 호출]]></title>
            <link>https://velog.io/@dev_jhjhj/3%EC%9E%A5.-%ED%95%A8%EC%88%98-%EC%A0%95%EC%9D%98%EC%99%80-%ED%98%B8%EC%B6%9C</link>
            <guid>https://velog.io/@dev_jhjhj/3%EC%9E%A5.-%ED%95%A8%EC%88%98-%EC%A0%95%EC%9D%98%EC%99%80-%ED%98%B8%EC%B6%9C</guid>
            <pubDate>Sun, 03 Apr 2022 12:20:57 GMT</pubDate>
            <description><![CDATA[<h2 id="31-코틀린에서-컬렉션-만들기">3.1 코틀린에서 컬렉션 만들기</h2>
<p>코틀린 컬렉션은 자바 컬렉션과 똑같은 클래스이다. 표준 자바 컬렉션을 활용하면 자바 코드와 상호 작용하기 훨씬 더 쉽다. 각각의 언어에서 호출할 때, 컬렉션을 서로 변환할 필요가 없다.</p>
<p>But, 코틀린에서는 자바보다 더 많은 기능을 쓸 수 있다.</p>
<pre><code class="language-kotlin">val strings = listOf (&quot;first”, &quot;second”, ”fourteenth&quot;)
printin (strings. last ()) // fourteenth

val numbers = setOf (1, 14, 2)
printin (numbers.max())  // 14</code></pre>
<h2 id="32-함수를-호출하기-쉽게-만들기">3.2 함수를 호출하기 쉽게 만들기</h2>
<p>자바 컬렉션에는 기본적으로 toString 구현이 들어있다. 코틀린을 이용해서 joinTostring 함수 구현하기</p>
<pre><code class="language-kotlin">// joinToString() 함수의 초기 구현
//&lt;T&gt; 제네릭 타입
fun &lt;T&gt; joinToString(
    collection: Collection&lt;T&gt;,
    separator: String,
    prefix: String, postfix: String
): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index &gt; 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}</code></pre>
<h3 id="321-이름-붙인-인자">3.2.1 이름 붙인 인자</h3>
<p>코틀린에서는 함수를 호출할 때, 인자에 이름을 붙여서 호출이 가능하다.</p>
<p>호출시 인자 중 어느 하나라도 이름을 명시한다면, 혼동을 막기 위해 그 뒤에 오는 모든 이니자의 이름을 꼭 명시해야한다.</p>
<pre><code class="language-kotlin">fun main() {
    val list = listOf(1, 2, 3)
    println(joinToString(list, &quot;;&quot;, &quot;(&quot;, &quot;)&quot;))

// 이름을 붙인 인자
    println(joinToString(list, separator = &quot;;&quot;, prefix = &quot;(&quot;, postfix = &quot;)&quot;))
}
</code></pre>
<h3 id="322-디폴트-파라미터-값">3.2.2 디폴트 파라미터 값</h3>
<p>자바에서는 일부 클래스에서 오버로딩한 메소드가 너무 많다. 그렇기 때문에 개발자들이 반복적인 설명 주석을 달아야하는 경우가 많다.</p>
<p>코틀린에서는 함수를 선언할 때, 파라미터의 디폴트 값을 지정할 수 있어서, 이런 오버로드를 피할 수 있다.</p>
<pre><code class="language-kotlin">fun &lt;T&gt; joinToString(
    collection: Collection&lt;T&gt;,
// 디폴트 값이 지정된 파라미터들
    separator: String = &quot;,&quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;
): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index &gt; 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}

println(joinToString(list, &quot;;&quot;, &quot;(&quot;, &quot;)&quot;))
println(joinToString(list))
println(joinToString(list, &quot;; &quot;))</code></pre>
<p>자바에는 디폴트 파라미터 값이라는 개념이 없다. 코틀린 함수를 자바에서 호출하라 경우, 코틀린 함수가 디폴트 파라미터 값을 제공하더라고 모든 인자를 명시해야 한다.</p>
<p>자바에서 코틀린 함수를 자주 호출해야한다면, <code>@JvmOverloads</code> 애노테이션을 함수에 추가</p>
<p><code>@JvmOverloads</code> 를 추가하면, 코틀린 컴파이리러가 자동으로 맨 마지막 파라미터부터 파라미터를 하나씩 생략한 오버로딩한 자바 메소드를 추가해준다.</p>
<h3 id="323-정적인-유틸리티-클래스-없애기--최상위-함수와-프로퍼티">3.2.3 정적인 유틸리티 클래스 없애기 : 최상위 함수와 프로퍼티</h3>
<p>자바에서는 모든 코드를 클래스의 메소드로 작성해야하는 데, 실전에서는 어느 한 클래스에 포함하기 어려운 코드들이 많다. 그래서 Utill 클래스를 만들기도 한다.</p>
<p>하지만, 코틀린에서는 이런 고민을 할 필요가 없다.</p>
<p>→ 함수를 직접 소스파일의 최상위 수준, 모든 다른 클래스의 밖에 위치시키면 된다.</p>
<ul>
<li>JVM은 새로운 클래스로 정의해주며, Java와 비교하자면 ... static 함수로 선언</li>
</ul>
<pre><code class="language-kotlin">package strings;

public class JoinKt {
        public static String joinToString (. . .) { ... }
}</code></pre>
<p><strong>최상위 프로퍼티</strong></p>
<p>함수와 마찬가지로 프로퍼티도 파일의 최상위 수준에 놓을 수 있다.</p>
<p>이런 프로퍼티 값은 정적 필드에 저장된다.</p>
<pre><code class="language-kotlin">val opCount=0   // 최상위 프로퍼티 선언

fun performOperation() {
    opCount++   // 최상위 프로퍼티 값 변경
  // ...
}

fun reportOperationCount() {
  println(&quot;Operation performed $opCount times&quot;)  // 최상위 프로퍼티 값 읽기
}</code></pre>
<p><code>const</code> 키워드를 사용하면 java의 상수, <code>public static final 필드</code>로 컴파일하게 만들 수 있다. (단, primitive type과 String 타입의 프로퍼티만 가능)</p>
<h2 id="33-메서드를-다른-클래스에-추가--확장-함수와-확장-프로퍼티">3.3 메서드를 다른 클래스에 추가 : 확장 함수와 확장 프로퍼티</h2>
<p><strong>확장함수란 ?</strong></p>
<ul>
<li>어떤 클래스의 멤버 메소드처럼 호출할 수 있지만, 클래스 밖에서 선언된 함수</li>
<li>확장함수를 만들려면 추가하려는 함수 이름 앞에 그 함수가 확잘할 클래스의 이름을 덧붙인다.</li>
<li>클래스 이름을 <strong>수신</strong> <strong>객체 타입,</strong> 확장 함수가 호출되는 대상이 되는 값을 <strong>수신 객체</strong></li>
</ul>
<p><img src="https://media.vlpt.us/images/dev_jhjhj/post/ed910f62-aa07-4f7a-9967-2a46da822d57/3_3_1.png" alt=""></p>
<pre><code class="language-kotlin">package strings
fun String.lashChar(): Char = this.get(this.length - 1)

println(&quot;Kotlin&quot;.lastChar())</code></pre>
<p><code>String</code>이 수신 객체 타입, <code>“Kotlin”</code>이 수신 객체</p>
<ul>
<li>확장 함수 내부에서는 수신 객체의 메소드나 프로퍼티를 사용할 수 있다.</li>
<li>but, 캡슐화는 지킨다 →  클래스 내부에서만 사용할 수 있는 private 멤버나 protected 멤버를 사용할 수 없다.</li>
</ul>
<h3 id="331-임포트와-확장-함수">3.3.1 임포트와 확장 함수</h3>
<p>확장 함수를 사용하기위해서는 import를 해줘야만 한다.</p>
<p>코틀린 문법상 확장 함수는 반드시 짧은 이름을 써야한다. import 할 때 이름을 바구는 것이 확장 함수 이름을 충돌할 수 있는 유일한 방법이다.</p>
<pre><code class="language-kotlin">import strings.lastChar
// import strings.*
// import string.lastChar as last   // 별칭 사용 가능

val c = &quot;Kotlin&quot;.lastChar()
// val c = &quot;Kotlin&quot;.last()
</code></pre>
<h3 id="332-자바에서-확장-함수-호출">3.3.2 자바에서 확장 함수 호출</h3>
<p>확장함수는 수신 객체를 첫번째로 받는 정적 메소드이다. 이런 설계로 확장 함수를 자바에서 사용하기 편하다. 확장함수를 <code>StringUtil.kt</code> 파일에 정의했다면 다음과 같이 호출할 수 있다.</p>
<pre><code class="language-java">char c = StringUtilKt.lastChar(&quot;Java&quot;);</code></pre>
<h3 id="333-확장-함수로-유틸리티-함수-정의">3.3.3 확장 함수로 유틸리티 함수 정의</h3>
<p>joinsToString 함수 확장함수로 만들어서 코틀린 라이브러리가 제공하는 함수처럼 만들기</p>
<pre><code class="language-kotlin">fun &lt;T&gt; Collection&lt;T&gt;.joinToString(
    separator: String = &quot;, &quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) {
        if (index &gt; 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

fun main() {
    val list = listOf(1, 2, 3)
    println(list.joinToString(&quot;; &quot;, &quot;(&quot;, &quot;)&quot;))
        // (1; 2; 3)
}</code></pre>
<p>확장 함수는 단지 정적 메소드 호출에 대한 문법적인 편의일 뿐이다. 클래스가 아닌 더 구체적인 타입을 수신 객체 타입으로 지정할 수 있다.</p>
<p>따라서 문자열의 컬렉션에 대해서만 호출할려면 다음과 같이 정의하면 된다.</p>
<pre><code class="language-kotlin">fun &lt;String&gt;.joinToString(
    separator: String = &quot;, &quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;
) = joinToString(separator, prefix, postfix)

fun main() {
    printin (listOf (&quot;one&quot;, &quot;two&quot;, &quot;eight&quot;).join (&quot; &quot;))
        // (1; 2; 3)
}</code></pre>
<h3 id="334-확장함수는-오버라이드-할-수-없다">3.3.4 확장함수는 오버라이드 할 수 없다.</h3>
<p>확장함수는 정적 메소드 (static)특징을 가지고 있기 때문에 오버라이드를 할 수 없다.</p>
<h3 id="335-확장-프로퍼티">3.3.5 확장 프로퍼티</h3>
<p>확장 프로퍼티를 추가하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문을 사용할 수 있다.</p>
<p>하지만, 기존 클래스의 프로퍼티처럼 완벽한 프로퍼티의 기능을 지원하지는 못한다.</p>
<ul>
<li>확장 프로퍼티는 아무런 상태를 가질 수 없다.</li>
<li>최소한의 getter는 꼭 정의해야한다.</li>
<li>초기화 코드를 쓸 수 없다.</li>
</ul>
<pre><code class="language-kotlin">// 확장 프로퍼티 선언
val String.lastChar: Char
    get() = get(length - 1 )

// 변경 가능한 확장 프로퍼티 선언
var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value: Char) {
        this.setCharAt(length - 1, value)
    }

val sb = StringBuilder(&quot;Kotlin?&quot;)
    sb.lastChar = &#39;!&#39;
    println(sb) // Kotlin!</code></pre>
<h2 id="34-컬렉션-처리--가변-길이-인자-중위-함수-호출-라이브러리-지원">3.4 컬렉션 처리 : 가변 길이 인자, 중위 함수 호출, 라이브러리 지원</h2>
<h3 id="341-자바-컬렉션">3.4.1 자바 컬렉션</h3>
<p>코틀린은 자바 컬렉션 라이브러리를 확장해서 사용한다.</p>
<h3 id="342-가변-인자-인자의-개수가-달라질-수-있는-함수">3.4.2 가변 인자: 인자의 개수가 달라질 수 있는 함수</h3>
<pre><code class="language-kotlin">//리스트 함수는 원하는 만큼 원소를 인자 값으로 전달할 수 있다.
val lislt = listOf(2, 3, 5, 7, 11)

fun listOf&lt;T&gt;(vararg values: T): List&lt;T&gt; {...}</code></pre>
<p>자바의 가변길이 인자 → 키워드 <code>...</code>   ex) String ...str</p>
<p>코틀린의 가변길이 인자 <code>varargs</code>  ex ) vararg values : T</p>
<p>이미 배열에 들어있는 원소를 가변 길이 인자로 넘길 때</p>
<p>자바의 경우 → 배열을 그냥 넘긴다.</p>
<p>코틀린의 경우 → 배열을 명시적으로 풀어서 배열의 각 원소가 인자로 전달되게 한다. <code>스프레드 연산자 사용 *</code></p>
<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;){
    val list = listOf(&quot;args: &quot;, *args)
    println(list)
}</code></pre>
<h3 id="343-값의-쌍-다루기--중위-호출과-구조-분해-선언">3.4.3 값의 쌍 다루기 : 중위 호출과 구조 분해 선언</h3>
<p>중위 호출 : <code>to</code></p>
<p>수신객체 (공백) to</p>
<p>인자가 하나뿐인 일반 메소드, 인자가 하나뿐인 확장 함수에 중위호출 사용 가능</p>
<p>함수에 <code>infix</code> 키워드를 붙이면, 중위 호출해서 사용 가능하다.</p>
<pre><code class="language-kotlin">val map = mapOf(1 to &quot;one&quot;, 2 to &quot;two&quot;, 3 to &quot;three&quot;)

1.to(&quot;one&quot;)  // &#39;to&#39; 메소드 일반적인 호출 방식
2.to &quot;two&quot;  // &#39;to&#39; 메소드를 중위 호출 방식으로 호출

infix fun Any.to(other: Any) = Pair(this, other)
val (number, name) = 1 to &quot;one&quot;  // 구조분해

for((index, element) in collection.withIndex()){
    println(&quot;$index: $element&quot;)
}</code></pre>
<p>참고 : Pair 는 코틀린의 표준 라이브러리</p>
<p>to 함수를 이용해서 순서쌍을 만든다음 구조 분해를 통해, 그 순서쌍을 풀었다.</p>
<p><img src="https://media.vlpt.us/images/dev_jhjhj/post/8ff9b65d-451c-41ae-9fc2-d55e83df1901/pair.png" alt=""></p>
<p>출처 : (서적) Kotlin in Action 드미트리 제메로프, 스베트라나 이사코바 저자 /오현석 옮김</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2장. 코틀린 기초]]></title>
            <link>https://velog.io/@dev_jhjhj/2%EC%9E%A5.-%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@dev_jhjhj/2%EC%9E%A5.-%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Thu, 24 Mar 2022 14:45:25 GMT</pubDate>
            <description><![CDATA[<h1 id="21-기본요소--함수와-변수">2.1 기본요소 : 함수와 변수</h1>
<h2 id="211-hello-world-">2.1.1 Hello, World !</h2>
<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;) {
    println(&quot;Hello, world!&quot;)
}</code></pre>
<ul>
<li>함수를 선언할 때 fun 키워드 사용</li>
<li>파라미터 이름 뒤에 그 파라미터의 타입을 쓴다.</li>
<li>함수를 최상위 수준에 정의 가능/ 자바와 달리, 꼭 클래스 안에 함수를 넣을 필요가 없다.</li>
<li>배열도 일반적인 클래스, 코틀린에는 자바와 달리 배열 처리를 위한 문법이 따로 없다.</li>
<li>System.out.println대신에 println 이라고 쓴다.</li>
<li>끝 줄에 세미콜론 생략 가능하다.</li>
</ul>
<h2 id="212-함수">2.1.2 함수</h2>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/46a6a552-fdb1-4cce-9126-8d4d10a79d7e/%E1%84%8F%E1%85%A9%E1%84%90%E1%85%B3%E1%86%AF%E1%84%85%E1%85%B5%E1%86%AB_%E1%84%92%E1%85%A1%E1%86%B7%E1%84%89%E1%85%AE.png" alt=""></p>
<p><strong>✅ 문(statement)와 식(expression)의 구분</strong></p>
<ul>
<li>문(statement)은 자신은 둘러싸고 잇는 가장 안쪽 블록의 최상위 요소로 아무런 값을 만들어 내지 않는다.</li>
<li>식(expression)은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있다.</li>
</ul>
<p>코틀린에서 if는 식이다. (문이 아니다.) 자바에서는 모든 제어 구조가 문(statement)이지만, 코틀린에서는 Loop를 제외하면 대부분 제어 구조가 식(expression)이다.</p>
<p>반면, 대입문은 자바에서는 식이었으나, 코틀린에서는 문(statement)이다.</p>
<h3 id="식이-본문인-함수">식이 본문인 함수</h3>
<pre><code class="language-kotlin">
// 블록이 본문인 함수
fun maxEx1(a: Int, b: Int) : Int {
    return if (a &gt; b) a else b
}

// 식이 본문인 함수
fun maxEx2(a: Int, b:Int) : Int = if (a&gt;b) a else b

// 반환 타입을 생략한 함수
fun maxEx3(a: Int, b:Int) = if (a&gt;b) a else b
</code></pre>
<ul>
<li><p>블록이 본문인 함수 : 본문이 중괄호로 둘러싸인 함수</p>
</li>
<li><p>식이 본문인 함수 : 등호와 식으로 이뤄진 함수</p>
</li>
<li><p>반환 타입을 생략할 수 있는 이유 (식이 본문인 함수의 반환 타입만 생략 가능)</p>
<p>  코틀린은 정적 타입 지정 언어 이므로 컴파일 시점에 모든 식의 타입을 지정해야한다. 하지만 식이 본문이니 함수의 경우, 굳이 반환 타입을 적지 않아도 컴파일러가 함수 본문 식을 분석해서 결과 타입을 함수 반환 타입으로 정해준다. → 타입 추론이 가능하다.</p>
</li>
</ul>
<h2 id="213-변수">2.1.3 변수</h2>
<pre><code class="language-kotlin">// 타입 생략
val answer = 42
// 타입 명시
val answer: Int = 42</code></pre>
<h3 id="변경-가능한-변수와-변경-불가능한-변수">변경 가능한 변수와 변경 불가능한 변수</h3>
<ul>
<li><p>val : immutable한 참조를 저장하는 변수</p>
<p>  val로 선언된 변수는 일단 초기화 하고 나면 재대입 불가능, java의 final과 같다.</p>
</li>
<li><p>var : mutable 참조, 변수의 값이 바뀔 수 있다. 자바의 일반 변수에 해당</p>
</li>
</ul>
<p>기본적으로 모든 변수는 val, 불변 변수로 선언하고 추후 필요에 따라 var 로 변경하는 것을 추천한다.</p>
<p>immutable한 참조와 immutable한 객체를 부수 효과가 없는 함수와 조합해 사용하면, 코드가 함수형 코드에 가까워진다.</p>
<ul>
<li>val 참조 자체는 불변이지만, 그 참조가 가리키는 객체의 내부 값은 변경될 수 있다.</li>
</ul>
<pre><code class="language-kotlin">val languages = arrayListOf(&quot;Java&quot;)   // 불변 참조를 선언
languages.add(&quot;Kotlin&quot;)   // 참조가 가리키는 객체 내부 변경</code></pre>
<ul>
<li>var 키워드를 사용하면 변수 값 변경 가능, 변수 타입은 고정되어서 바뀌지 않는다.</li>
</ul>
<p>컴파일러는 변수 선언 시점의 초기화 된 식으로부터 변수의 타입을 추론, 변수 재대입이 이뤄질 때, 이미 먼저 추론한 변수 타입으로 컴파일한다.</p>
<pre><code class="language-kotlin">// Error : type mismach 발생 - 컴파일 오류
var answer = 123;
answer = &quot;hello world&quot;</code></pre>
<h3 id="214-더-쉽게-문자열-형식-지정--문자열-템플릿">2.1.4 더 쉽게 문자열 형식 지정 : 문자열 템플릿</h3>
<pre><code class="language-kotlin">// 문자열 템플릿 사용
fun main(args: Array&lt;String&gt;) {
    val name = if(args.size &gt; 0) args[0] else &quot;Kotlin&quot;
    println(&quot;Hello, $name!&quot;)

    if(args.size &gt; 0){
        println(&quot;Bye, ${args[0]}&quot;)   // args 배열의 원소를 넣기 위해${} 구문 사용
    }

    println(&quot;\$&quot;)  // 달려 표시 ($)를 쓸 경우에는 이스케이프(\) 사용

}</code></pre>
<ul>
<li><p>코틀린에서도 변수를 문자열 안에 사용할 수 있다. → 문자열 리터럴이 필요한 곳에 $변수명</p>
</li>
<li><p>한글과 문자 템플릿 사용할 경우 주의 점</p>
<ul>
<li><p>코틀린은 자바와 마찬가지로 변수 이름에 한글이 들어갈 수 있다. (모든 유니코드 문자는 변수명 사용 가능)</p>
</li>
<li><p>문자열 템플릿 안에 <code>$name님, 반가워요</code> 처럼 “$변수명”과 한글을 붙여서 사용할 경우, 컴파일 에러가 발생한다. 코틀린 컴파일러가 영문자와 한글을 한꺼번에 식별자로 사용해서 그렇다.</p>
<p>→ <code>${name}님, 반가워요</code>  변수 명을 {}로 감싸라, 평소에소 {}로 감싸서 사용하는 것이 가독성에 좋다.</p>
</li>
</ul>
</li>
</ul>
<p></br></br></p>
<h1 id="22-클래스와-프로퍼티">2.2 클래스와 프로퍼티</h1>
<pre><code class="language-java">// Java Person 클래스
public class Person {
    private final String name;

    public String getName() {
        return name;
    }

    public Person(String name) {
        this.name = name;
    }
}</code></pre>
<p>Java의 경우 필드가 늘어날 수록 생성자의 파라미터 개수도 늘어나게 된다. 즉 반복적으로 늘어나는 코드가 많아진다.</p>
<pre><code class="language-kotlin">// 코틀린으로 변환한 Person 클래스
class Person (val name : String)</code></pre>
<ul>
<li>코틀린의 접근제어자는 기본이 public 이다. 따라서 생략 가능하다.</li>
</ul>
<h3 id="221-프로퍼티">2.2.1 프로퍼티</h3>
<p>자바에서는 필드와 접근자를 한데 묵어 Property라고 부른다. 코틀린 프로퍼티는 자바의 필드와 접근자 메소드를 완전히 대신한다.</p>
<pre><code class="language-kotlin">class Person(
    val name: String,   // private 변수, 읽기 전용, 단순한 공개 getter만 만들어낸다.
    var isMarried: Boolean  // private 변수, 공개 getter, 공개 setter 만들어낸다.
)

val person = Person(&quot;Bob&quot;, true)  // new 키워드 사용 안하고 생성자 호출
// 프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 getter 호출
println(person.name)
println(person.isMarried)</code></pre>
<h3 id="222-커스텀-접근자">2.2.2 커스텀 접근자</h3>
<pre><code class="language-kotlin">class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {
            return height == width
        }
            // get() = height == width
}</code></pre>
<p>isSquare 프로퍼티는 자체 구현을 제공하는 getter가 존재</p>
<p>클라이언트가 프로퍼티에 접근할 때, get() 에 해당하는 로직으로 프로퍼티 값을 매번 다시 계산한다.</p>
<p>파라미터가 없는 함수를 정의하는 방식과 커스텀 getter를 정의하는 방식은 구현이나 성능상 차이는 없다.</p>
<h3 id="223-코틀린-소스코드-구조--디렉터리와-패키지">2.2.3 코틀린 소스코드 구조 : 디렉터리와 패키지</h3>
<p>모든 코틀린 파일의 맨 앞에는 package문을 넣을 수 있다. 
같은 패키지에 속해 있다면 다른 파일에서 정의한 선언일지라고 직접 사용 가능</p>
<p>반면, 다른 패키지에서 정의한 선언을 사용하려면 import를 통해 선언</p>
<pre><code class="language-kotlin">// 클래스와 함수 선언을 패키지에 넣기
package geometry.shapes

import java.util.*

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() = height == width
}

fun createRandomRectangle(): Rectangle {
    val random = Random()
    return Rectangle(random.nextInt(), random.nextInt())
}</code></pre>
<pre><code class="language-kotlin">// 다른 패키지에 있는 함수 임포트하기
package geometry.shapes.createRandomRectangle

fun main(args: Array&lt;String&gt;) {
    println(createRandomRectangle().isSquare)
}</code></pre>
<p>자바와 같이 패키지별로 디렉터리를 구성하는 편이 낫다. 특히, 자바와 코틀린을 함께 사용하는 프로젝트에서는 자바 방식을 따르는 게 중요하다. 
</br></br></p>
<h1 id="23-선택-표현과-처리--enum과-when">2.3 선택 표현과 처리 : enum과 when</h1>
<h3 id="231-클래스-정의">2.3.1 클래스 정의</h3>
<pre><code class="language-kotlin">// 간단한 enum 클래스
enum class Color {
    RED, ORANGE, YELLOW, GREEN, INDIGO, VIOLET
}</code></pre>
<p>Java에서는 <code>enum</code>으로 선언, 코틀린에서는 <code>enum class</code> 로 선언</p>
<pre><code class="language-kotlin">// 프로퍼티와 메소드가 있는 enum 클래스

enum class Color(val r: Int, val g: Int, val b: Int)   // 상수 프로퍼티 정의
{  

    RED(255, 0, 0),
    ORANGE(255, 165, 0),
    YELLOW(255, 255, 0),
    GREEN(0, 255, 0),
    BLUE(0, 0, 255),
    INDIGO(75, 0, 130),
    VIOLET(238, 130, 238);    // 반드시 세미콜론으로 종료

    fun rgb() = (r * 256 + g) * 256 + b  // enum 클래스 안에 메소드 정의
}

fun main() {
    println(Color.BLUE.rgb())
}</code></pre>
<h3 id="232-when으로-enum-클래스-다루기">2.3.2 when으로 enum 클래스 다루기</h3>
<pre><code class="language-kotlin">fun getMnemonic(color: Color) =
 when (color) {   // 함수 반환 값으로 when 식을 사용
    Color.RED -&gt; &quot;Rechard&quot;
    Color.ORANGE -&gt; &quot;Of&quot;
    Color.YELLOW -&gt; &quot;York&quot;
    Color.GREEN -&gt; &quot;Grave&quot;
    Color.BLUE -&gt; &quot;Battle&quot;
    Color.INDIGO -&gt; &quot;In&quot;
    Color.VIOLET -&gt; &quot;Vain&quot;
}

fun getWarmth(color: Color) = when (color) {
    Color.RED, Color.ORANGE, Color.YELLOW -&gt; &quot;warm&quot;
    Color.GREEN -&gt; &quot;neutral&quot;
    Color.BLUE, Color.INDIGO, Color.VIOLET -&gt; &quot;cold&quot;
}

fun main() {
    println(getMnemonic(Color.BLUE))
    println(getWarmth(Color.ORANGE))
}</code></pre>
<p>자바와 달리 각 분기 끝에 break 를 넣지 않아도 된다.</p>
<p>한 분기 안에 여러 값을 매치 패턴으로 사용할 경우, 콤마(,)로 구분한다.</p>
<p>enum 상수 값을 import 해서 enum 클래스 수식자 없이 enum을 사용 가능하다.</p>
<pre><code class="language-kotlin">import ch2.Color.*

fun getMnemonic(color: Color) = when (color) {
    RED -&gt; &quot;Rechard&quot;
    ORANGE -&gt; &quot;Of&quot;
    YELLOW -&gt; &quot;York&quot;
    GREEN -&gt; &quot;Grave&quot;
    BLUE -&gt; &quot;Battle&quot;
    INDIGO -&gt; &quot;In&quot;
    VIOLET -&gt; &quot;Vain&quot;
}</code></pre>
<h3 id="233-when과-임의의-객체를-함께-사용">2.3.3 when과 임의의 객체를 함께 사용</h3>
<p>자바의 <code>swtich</code>와 달리 코틀린 <code>when</code> 의 분기 조건은 임의의 객체를 허용한다.</p>
<pre><code class="language-kotlin">fun mix(c1: Color, c2: Color) =
    // when 식의 인자로 아무 객체나 사용 가능, when은 받은 인자 객체가 각 분기 조건에 있는 객체와 같은지 테스트
    when (setOf(c1, c2)) {
        setOf(RED, YELLOW) -&gt; ORANGE
        setOf(YELLOW, BLUE) -&gt; GREEN
        setOf(BLUE, VIOLET) -&gt; INDIGO
        // 매치되는 분기 조건이 없으면 이 문장 실행
        else -&gt; throw Exception(&quot;Dirty color&quot;)
    }</code></pre>
<ul>
<li><p>setOf 함수 란?</p>
<p>  인자로 전달받은 여러 객체를 그 객체들을 포함하는 집한인 Set 객체로 만드는 함수,</p>
<p>  Set은 원소가 모여있는 컬렉션으로 각 원소의 순서는 중요하지 않다.</p>
</li>
</ul>
<h3 id="234-인자-없는-when-사용">2.3.4 인자 없는 when 사용</h3>
<p>when에 아무 인자도 없으려면 각 분기의 조건이 불리언 결과를 계산하는 식이어야 한다.</p>
<pre><code class="language-kotlin">fun mixOptimized(c1: Color, c2: Color) =
    // when 에 아무 인자가 없다.
    when {
        (c1 == RED &amp;&amp; c2 == YELLOW) ||
                (c1 == YELLOW &amp;&amp; c2 == RED) -&gt; ORANGE
        (c1 == YELLOW &amp;&amp; c2 == BLUE) ||
                (c1 == BLUE &amp;&amp; c2 == YELLOW) -&gt; GREEN
        (c1 == BLUE &amp;&amp; c2 == VIOLET) ||
                (c1 == VIOLET &amp;&amp; c2 == BLUE) -&gt; INDIGO
        else -&gt; throw Exception(&quot;Dirty color&quot;)
    }</code></pre>
<h3 id="235-스마트-캐스트--타입-검사와-타입-캐스트를-조합">2.3.5 스마트 캐스트 : 타입 검사와 타입 캐스트를 조합</h3>
<p><code>( 1 + 2 ) + 4</code> 와 같은 산술식을 계산하는 함수를 만들어 보자.</p>
<pre><code class="language-kotlin">// 식을 위한 인터페이스
interface Expr

// value라는 프로퍼티만 존재하는 단순한 클래스, Expr 인터페이스를 구현
class Num(val value: Int) : Expr

// Expr 타입의 객체라면 Sum 연산의 인자가 될 수 있다.
// 따라서, Num이나 다른 Sum이 Sum의 인자로 올 수 있다.
class Sum(val left: Expr, val right: Expr) : Expr

// Java 스타일
// if 사용해서 식 계산
fun eval(e: Expr): Int {
    if (e is Num) {
        val n = e as Num
        return n.value
    }
    if (e is Sum) {
        // 변수 e 에 대해 스마트 캐스트를 사용한다.
        return eval(e.right) + eval(e.left)
    }

    throw IllegalArgumentException(&quot;Unkown expression&quot;)
}</code></pre>
<p>Expr은 식을 위한 인터페이스</p>
<p>Sum, Num 클래스는 Expr 인터페이스를 구현하는 클래스</p>
<p>코틀린에서는 <code>is</code> 를 사용해서 변수 타입을 검사 (Java의 <code>instanceof</code> 와 유사) </p>
<ul>
<li>하지만 Java에서는 <code>instanceof</code> 로 변수타입 확인 후, 그 타입에 속한 멤버에 접근하기 위해서는 개발자가 명시적으로 타입 캐스팅이 필요</li>
<li>코틀린에서는 <code>is</code> 로 검사한 후, 참이면, 컴파일러가 알아서 캐스팅 해준다. → Smart Cast</li>
<li>스마트 캐스트는 <code>is</code> 로 변수에 든 값의 타입을 검사한 다음, 그 값이 바뀔 수 없는 경우에만 동작</li>
</ul>
<p>명시적으로 타입 캐스팅 하려면 <code>as</code> 키워드를 사용 → <code>val n = e as Num</code></p>
<h3 id="236-리팩토링--if를-when으로-변경">2.3.6 리팩토링 : if를 when으로 변경</h3>
<p>코틀린의 if는 식 → 값을 반환한다. 값을 만들어 낸다.→ 따라서 자바와 달리 3항 연산자가 따로 없다.</p>
<p>Java의 if는 문 → 값을 반환하지 않는다.</p>
<pre><code class="language-kotlin">// 값을 만들어내는 if 식

fun eval(e: Expr): Int =
    if (e is Num) {
        e.value
    } else if (e is Sum) {
        eval(e.right) + eval(e.left)
    } else {
        throw IllegalArgumentException(&quot;Unkown expression&quot;)
    }</code></pre>
<pre><code class="language-kotlin">// if 중첩 대신 when 사용하기
fun eval(e: Expr): Int =
    when (e) {
        is Num -&gt; e.value  // 인자 타입을 검사하는 when 분기에서 스마트 캐스트 됨
        is Sum -&gt; eval(e.left) + eval(e.right)
        else -&gt; throw IllegalArgumentException(&quot;Unkown expression&quot;)
    }</code></pre>
<h3 id="237-if와-when의-분기에서-블록-사용">2.3.7 if와 when의 분기에서 블록 사용</h3>
<p>if나 when 모두 분기에 블록 사용 가능 → 블록의 마지막 문장이 블록 전체의 결과가 된다.</p>
<pre><code class="language-kotlin">fun evalWithLogging(e: Expr): Int =
    when (e) {
        is Num -&gt; {
            println(&quot;num: ${e.value}&quot;)
            e.value   // 블록의 마지막 식이므로 e.value가 반환된다.
        }
        is Sum -&gt; {
            val left = evalWithLogging(e.left)
            val right = evalWithLogging(e.right)
            println(&quot;sum: $left + $right&quot;)
            left + right
        }
        else -&gt; throw IllegalArgumentException(&quot;Unkown expression&quot;)
    }</code></pre>
<h2 id="24-대상을-이터레이션--while-과-for-루프">2.4 대상을 이터레이션 : while 과 for 루프</h2>
<h3 id="241-while-루프">2.4.1 while 루프</h3>
<pre><code class="language-kotlin">// 조건이 참인 동안 본문 반복 실행
while(조건) {
    /*....*/
}

// 맨 처음 무조건 본문 한번 실행 후, 조건이 참인 동안 본문 반복 실행
do{
    /*....*/
} while(조건)</code></pre>
<h3 id="242-수에-대한-이터레이션--범위와-수열">2.4.2 수에 대한 이터레이션 : 범위와 수열</h3>
<p>코틀린은 자바의 for 루프에 해당하는 요소가 없다.</p>
<pre><code class="language-kotlin">val oneToTen = 1..10 // 양 끝 구간 포함, 1 ~ 10</code></pre>
<pre><code class="language-kotlin">fun fizzbuxx(i: Int) =
    when {
        i % 15 == 0 -&gt; &quot;FizzBuzz &quot;
        i % 3 == 0 -&gt; &quot;Fizz &quot;
        i % 5 == 0 -&gt; &quot;Buzz &quot;
        else -&gt; &quot;$i &quot;
    }

for (i in 1..100) {
        println(fizzbuxx(i))
    }

    for (i in 100 downTo 1 step 2){
        print(fizzbuxx(i))
    }</code></pre>
<p><code>step</code> : 증가값 </p>
<p><code>downTo</code> : 역방향</p>
<p><code>..</code> : 항상 범위의 끝 포함</p>
<p><code>until</code> : 끝 값 포함 하지 않음 → <code>for(x in 0 until size)</code>  or <code>for (x in 0 .. size-1)</code></p>
<h3 id="243-맵에-대한-이터레이션">2.4.3 맵에 대한 이터레이션</h3>
<pre><code class="language-kotlin">// A ~ F 까지 문자 범위 이터레이션
for(c in &#39;A&#39;..&#39;F&#39;){
    // 아스키 코드를 2진 표현으로 변경
    val binary = Integer.toBinaryString(c.toInt())
    // c를 키로 c의 2진 표현을 맵에 넣는다.
    binaryReps[c] = binary  // 자바의 map put 과 유사: binaryReps.put(c, binary)
}

// 맵에 대한 이터레이션, 맵의 Key와 Value를 두 변수에 대입
for((letter, binary) in binaryReps) {
    println(&quot;$letter = $binary&quot;)
}</code></pre>
<pre><code class="language-kotlin">val list = arrayListOf(&quot;10&quot;, &quot;11&quot;, &quot;1001&quot;)

// 인덱스와 함께 컬렉션을 이터레이션 한다.
for ((idx, element) in list.withIndex()){
    println(&quot;$idx: $element&quot;)
}</code></pre>
<h3 id="244-in으로-컬렉션이나-범위의-원소-검사">2.4.4 in으로 컬렉션이나 범위의 원소 검사</h3>
<p>in 연산자 : 어떤 값이 범위에 속하는 지 확인</p>
<p>!in 연산자 : 어떤 값이 범위에 속하지 않는 지 확인</p>
<pre><code class="language-kotlin">fun isLetter(c: Char) = c in &#39;a&#39;..&#39;z&#39; || c in &#39;A&#39;..&#39;Z&#39;
fun isNotDigit(c: Char) = c !in &#39;0&#39;..&#39;9&#39;

println(isLetter(&#39;q&#39;))      // true
println(isNotDigit(&#39;x&#39;))    // true

fun recognize(c: Char) = when (c) {
    in &#39;0&#39;..&#39;9&#39; -&gt; &quot;It&#39;s a digit!&quot;
    in &#39;a&#39;..&#39;z&#39;, in &#39;A&#39;..&#39;Z&#39; -&gt; &quot;It&#39;s a letter!&quot;
    else -&gt; &quot;I don&#39;t know&quot;
}

println(recognize(&#39;8&#39;))     // It&#39;s a digit!</code></pre>
<p>비교가 가능한 클래스(java.lang.Comparable 인터페이스를 구현한 클래스)라면 그 클래스의 인스턴스 객체를 사용해 범위를 만들 수 있다.</p>
<h2 id="25-코틀린의-예외-처리">2.5 코틀린의 예외 처리</h2>
<p>코틀린의 기본 예외 처리 구문은 자바와 유사</p>
<p>예외 인스턴스를 만들 때 new 키워드를 붙일 필요가 없다.</p>
<p>자바와 달리 코틀린의 throw는 식이므로, 다른 식에 포함이 가능</p>
<pre><code class="language-kotlin">val percentage =
    if (number in 0..100)
        number
    else
                // throw는 식이다. -&gt; 값을 만들어낸다.
        throw IllegalArgumentException(&quot;A percentage value must be between 0 and 100: $number&quot;)</code></pre>
<h3 id="251-try-catch-finally">2.5.1 try, catch, finally</h3>
<pre><code class="language-kotlin">fun readNumber(reader: BufferedReader): Int? {
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    } catch (e: NumberFormatException) {
        return null
    } finally {
        reader.close()
    }
}</code></pre>
<p>Java 코드와 가장 큰 차이는 throws 절에 코드가 없다는 점</p>
<ul>
<li><p>Java에서는 함수를 작성할 때, 함수 선언 뒤에 throws IOException을 붙여야한다.</p>
<p>  → IOException이 Checked Exception이기 때문, 자바에서는 Checked Exception을 명시적으로 처리해야한다.</p>
<p>  → 어떤 함수가 던질 가능성이 있는 예외나 그 함수가 호출한 다른 함수에서 발생할 수 있는 모든 예외를 모두 catch 로 처리해야 하며, 처리하지 않은 예외는 throws 절에 명시</p>
</li>
</ul>
<p>코틀린도 다른 최신 JVM 언어 처럼 Checked Exception, UnChecked Exception을 구별하지 않는다. (자바는 Checked Exception 처리를 강제한다.)</p>
<h3 id="252-try를-식으로-사용">2.5.2. try를 식으로 사용</h3>
<p>코틀린의 try 키워드는 식이다. → 값을 만들어낸다.</p>
<p>따라서 try 의 값을 변수에 대입할 수 있다. try는 if와 달리 중괄호 {}로 둘러싸야한다.</p>
<pre><code class="language-kotlin">fun readNumber(reader: BufferedReader) {
        // 예외가 발생하지 않으면, 이 값을 사용
    val number = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        // return //catch 블록 다음 코드는 실행 되지 않음
        null      // 예외가 발생하면 null값을 사용
    }
    println(number)
}</code></pre>
<p>출처 : (서적) Kotlin in Action 드미트리 제메로프, 스베트라나 이사코바 저자 /오현석 옮김</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kafka 메세지 쓰기]]></title>
            <link>https://velog.io/@dev_jhjhj/Kafka-%EB%A9%94%EC%84%B8%EC%A7%80-%EC%93%B0%EA%B8%B0</link>
            <guid>https://velog.io/@dev_jhjhj/Kafka-%EB%A9%94%EC%84%B8%EC%A7%80-%EC%93%B0%EA%B8%B0</guid>
            <pubDate>Sun, 20 Mar 2022 12:20:16 GMT</pubDate>
            <description><![CDATA[<h1 id="31-프로듀서-개요">3.1 프로듀서 개요</h1>
<p>프로듀서가 데이터가 데이터를 전송할 때 내부적으로 처리되는 단계는 다음과 같다.
      <img src="https://images.velog.io/images/dev_jhjhj/post/bcb68189-b5dd-4ca0-abe3-d5857176cdbe/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-08-24%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.05.29.png" alt="">  </p>
<ul>
<li><ol>
<li>카프카에 쓰려는 메시지를 갖는 ProducerRecord를 생성 (전송할 토픽과 값을 포함, 선택적으로 Key 값과 파티션 지정 가능)</li>
</ol>
</li>
<li><ol start="2">
<li>메세지 객체들이 네트워크로 전송될 수 있도록 바이트 배열로 직렬화  진행, 직렬처리기(serializer)와 컴포넌트(클래스)가 처리</li>
</ol>
</li>
<li><ol start="3">
<li>해당 데이터는 파티셔너와 컴포넌트(클래스)에 전달</li>
</ol>
</li>
</ul>
<p>파티션 지정 않았다면, ProducerRecord의 키를 기준으로 파티셔너가 하나의 파티션 선택
파티션이 선택되면 해당 레코드(ProducerRecord 객체)의 메시지가 저장될 토픽과 파티션을 프로듀서가 알게됨
토픽과 파티션으로 전송될 레코드들을 모은 레코드 배치 추가, 별개의 스레드가 그 배치를 카프카 브로커에 전송</p>
<ul>
<li><ol start="4">
<li>브로커 수신된 메시지 처리 후 응답</li>
</ol>
</li>
</ul>
<p>성공 → RecodrMetadata 객체 반환(토픽, 파티션, 패티션 내부의 메시지 오프셋 제공)
실패 → 에러 반환 (에러 수신한 프로듀서는 메시지 쓰기 포기하고 에러 반환전에 몇번 더 재전송 시도 할 수 있음)</p>
<h1 id="32-카프카-프로듀서-구성하기">3.2 카프카 프로듀서 구성하기</h1>
<ul>
<li>카프카 프로듀서를 수행하는 객체는 ProducerRecord</li>
<li>카프카에 메시지를 쓰려면 ProducerRecord 객체를 먼저 생성</li>
<li>ProducerRecord 은 세개의 필수 속성을 갖음 (이 외에도 원하는 속성 설정 가능)<ul>
<li>bootstrap.server<ul>
<li>카프카 클러스터에 최초 연결을 위해 프로듀서가 사용하는 브로커들의 host:port 목록을 설정</li>
</ul>
</li>
<li>key.serializer</li>
<li>프로듀서가 생성하는 레코드(ProducerRecord 객체)의 메시지 키를 직렬화하기 위한 클래스 이름을 이 속성에 설정 (key 없이 값만 전송할 때도 key.serializer 설정 필요)</li>
<li>key.serializer에 설정하는 클래스는 &quot;org.apache.kafka.common.serialization.Serializer&quot; 인터페이스를 구현해야 </li>
<li>카프카 클라이언트 패키지에는 다음과 같은 3가지 Serializer가 포함되어 있다.
ByteArraySerializer, StringSerializer,IntegerSerializer</li>
</ul>
</li>
</ul>
<p>따라서 많이 사용하는 타입들을 직렬화 할때는 따로 직렬처리기를 구현하지 않아도 됨
    * value.serializer
    - 레코드 메시지 값 직렬화 하는 데 사용되는 클래스 이름을 여기서 설정
    - 직렬화 방법은 key.serializer와 동일</p>
<pre><code>private Properties kafkaProps = new Properties ();        // 1. 카프카 Properties 객체 생성
kafkaProps.put(&quot;bootstrap.servers&quot;, &quot;brokerl:9892,broker:9892&quot;);

kafkaProps.put(&quot;key.serializer&quot;, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;); //  메세지 key와 value 모두 문자열 타입 사용으로 StringSerializer 사용
kafkaProps.put(&quot;value.serializer&quot;, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;); 

Producer&lt;String, String&gt; producer = new KafkaProducer &lt;String, String&gt;(kafkaProps);        // 새로운 프로듀서 객체 생성, 메세지 key, value (예시는 String)을 설정하고 Properties 객체를 생성자 인자로 전달</code></pre><p>자세한 설정 참조 :  <a href="http://kafka.apache.org/documentation.html#producerconfigs">http://kafka.apache.org/documentation.html#producerconfigs</a></p>
<h4 id="메세지-전송-방법에는-3가지">메세지 전송 방법에는 3가지</h4>
<ul>
<li>Fire-and-forget(전송 후 망각)<ul>
<li>send() 메서드로 메시지 전송만 (성공 여부 후속 조치 X)</li>
<li>전송 실패할 경우, 프로듀서가 자동으로 재전송 시도</li>
<li>but, 메시지 유실 가능성 있음</li>
</ul>
</li>
</ul>
<ul>
<li><p>Synchronous send(동기식 전송)</p>
<ul>
<li>send() 메서드로 메시지 전송하면 Java의 Future 객체 반환 → Future 객체의 get() 메소드 호출 후, 작업 완료 대기 → 브로커로부터 처리결과 반환</li>
</ul>
</li>
<li><p>Asynchronous send(비동기식 전송)</p>
<ul>
<li>send() 메서드 호출할 때, callback 메서드를 구현한 객체를 매개변수로 전달(뒤에 설명)</li>
<li>이 객체가 구현된 콜백 메서드는 카프카 브로커로부터 응답을 받을 때 자동 호출 →send()가 성공적으로 수행되었는지 확인 가능</li>
</ul>
</li>
</ul>
<h1 id="33-카프카에-메시지-전송하기">3.3 카프카에 메시지 전송하기</h1>
<pre><code>ProducerRecord&lt;String, String&gt; record = new ProducerRecord&lt;&gt;(&quot;CustomerCountury&quot;, &quot;Precision Products&quot;, &quot;France&quot;);  // step 1

try {
    producer.send(record);      // step 2
} catch (Exception e){
    e.printStackTrace();        // step 3
}</code></pre><ul>
<li><p>step 1</p>
<ul>
<li>프로듀서는 전송할 메시지를 갖는 레코드 객체를 생성 (ProducerRecord는 생성자를 여러 개 갖을 수 있음)</li>
<li>예시에서는 데이터를 저장할 토픽 이름(항상 String type), 카프카로 전송할 key와 값(예제에서는 String type)을 인자로 받는 생성자 사용</li>
<li>key과 값 타입은 직렬처리기와 프로듀서 객체(producer)에서 사용하는 타입과 같아야 함</li>
</ul>
</li>
<li><p>step 2</p>
<ul>
<li>프로듀서 객체의 send()메서드를 사용해서 레코드 전송</li>
<li>전송된 메시지는 버퍼(배치)에 수록된 후, 스레드에서 브로커로 전송</li>
<li>브로커가 수신된 메시지 성공적으로 쓰면 → RecodeMetadata 반환 → send()메서드가 이를 받아서 자바의 Future 객체를 반환
(예시에서는 메서드 반환값 무시 → 메세지 전송 성공/실패 모름, 실제로는 이렇게 안함)</li>
</ul>
</li>
<li><p>step 3</p>
<ul>
<li>카프카에게 메시지 전송 전에 프로듀서에 에러가 생기면 예외 처리</li>
</ul>
</li>
</ul>
<h2 id="331-동기식으로-메시지-전송하기">3.3.1 동기식으로 메시지 전송하기</h2>
<pre><code class="language-java">ProducerRecord&lt;String, String&gt; record = new ProducerRecord&lt;&gt;(&quot;CustomerCountury&quot;, &quot;Precision Products&quot;, &quot;France&quot;);

try {
    producer.send(record).get();        // step 1
} catch (Exception e){
    e.printStackTrace();        // step 2
}</code></pre>
<ul>
<li><p>step 1</p>
<ul>
<li>Future 객체의 get() 메서드를 사용해서 카프카 응답을 대기</li>
<li>ProducerRecord 객체가 전송 성공 시, RecodeMetadata 를 받게 되고, 이 객체를 사용해서 카프카에 쓴 메시지의 오프셋 (offset)을 알 수 있다.</li>
<li>ProducerRecord 객체가 전송 실패하면 예외 처리 발생</li>
</ul>
</li>
<li><p>step2</p>
<ul>
<li>예외 처리</li>
<li>카프카 메시지 전송 전 에러</li>
<li>전송 중 카프카 브로커가 전송 재시도 불가능 에러</li>
<li>사용 가능한 전송 재시도 횟수 전부 소진</li>
</ul>
</li>
<li><p>카프카 프로듀서 사용할 때 발생하는 에러</p>
<ul>
<li>재시도 가능(retriable) 에러</li>
<li>연결 에러 : 다시 연결을 시도하여 성공하면 해결 됨</li>
<li>no lea    der(리더 없음) 에러 : 해당 파티션의 새로운 리더가 선출되면 해결 됨
재시도 불가능한 에러</li>
<li>메세지가 너무 클 때 : 카프카 프로듀서는 재시도 않고 즉시 예외 반환</li>
</ul>
</li>
</ul>
<h2 id="332-비동기식으로-메시지-전송하기">3.3.2 비동기식으로 메시지 전송하기</h2>
<p>비동기식으로 메시지 전송할 때, 에러를 처리하기 위해 프로듀서에 콜백(callback)을 추가</p>
<pre><code class="language-java">private class DemoProducerCallback implements Callback {        // step 1
    @Override
    public void onCompletion(RecordMetadata recordMetadata, Exception e){
        if(e != null) {
            e.printStackTrace();        // step 2
        }
    }
}

ProducerRecord&lt;String, String&gt; record = new ProducerRecord&lt;&gt;(&quot;CustomerCountury&quot;, &quot;Biomedical Materials&quot;, &quot;USA&quot;);        // step 3
producer.send(record, new DemoProducerCallback());      // step 4</code></pre>
<ul>
<li>step 1<ul>
<li>콜백 구현 시, org.apache.kafka.clients.producer.Callback 인터페이스 상속</li>
</ul>
</li>
<li>step 2<ul>
<li>카프카가 에러 반환 시, onCompletion() 메서드에서 예외 처리</li>
</ul>
</li>
<li>step 3<ul>
<li>전송할 메세지 객체 생성</li>
</ul>
</li>
<li>step 4<ul>
<li>send() 메서드로 메시지 전송 시, 콜백 객체에 인자 전달</li>
<li>따라서 메시지 쓴 후, 응답을 반환할 때, 이 객체의 onCompletion() 메서드가 자동 호출</li>
</ul>
</li>
</ul>
<h1 id="34-프로듀서-구성하기">3.4 프로듀서 구성하기</h1>
<p>프로듀서를 구성하는 다양한 매개 변수 중, 프로듀서의 메모리 사용, 성능, 신뢰성 등에 영향을 주는 매개변수에 대해 설명</p>
<p><strong>acks</strong></p>
<p>전송된 레코드(ProducerRecord객체)를 수신하는 파티션 리플리카(복제 서버로 동작하는 브로커)의 수</p>
<p>메시지 유실될 가능성에 큰 영향을 준다.</p>
<p><strong>acks=0일 때</strong>
프로듀서는 브로커의 응답을 기다리지 않는다. (서버 응답 대기 없어 성능은 빠르고, 높은 처리량 필요시 사용 / 단,  메시지 유실 가능성 높음)
<strong>acks=1일 때</strong>
리더 리플리카가 메시지 수신할 때, 프로듀서는 브로커로 부터 수신 성공 응답 받음
리더에 메시지를 쓸 수 없다면... → 프로듀서는 에러 응답 → 메시지 재전송
리더가 중단되어 새로운 리플리카가 리더로 선출된 경우 → 메시지 유실 가능성
<strong>acks=all 일 때</strong>
동기화된 모든 리플리카가 메시지 수신 시, 프로듀서는 브로커의 성공 응답을 받음
가장 안전, 단, 브로커의 메시지를 수신할 때 까지 기다려야 해서 느림</p>
<p><strong>buffer.memory</strong></p>
<p>브로커에게 전송될 메시지의 버퍼로 사용할 메모리의 양 설정</p>
<p><strong>compression.type</strong></p>
<p>이 매개변수 설정 시, 메시지 압축되어 전송</p>
<p><strong>retries</strong></p>
<p>에러가 발생하기 전에 프로듀서가 메시지를 재전송하는 횟수를 제어</p>
<p><strong>retry.backoff.ms</strong></p>
<p>프로듀서가 메시지 재전송 시, 기본적으로 100밀리초를 대기하는 데, 이 매개변수를 사용해서 시간 간격 조정 가능</p>
<p><strong>batch.size</strong></p>
<p>다수의 레코드가 전송될 때, 프로듀서가 batch에 메시지를 모으는 데, 이 매개변수를 사용해서 각 batch에 사용될 메모리양을 제어</p>
<p><strong>linger.ms</strong></p>
<p>현재 배치를 전송하기 전까지 기다리는 시간(밀리초)를 나타냄</p>
<p><strong>client.id</strong></p>
<p>어떤 클라이언트에 전송된 메시지인지 식별하기 위해 브로커가 사용하는 매개변수(문자열 가능)</p>
<p><strong>max.in.flight.requests.per.connection</strong></p>
<p>서버의 응답을 받지 않고, 프로듀서가 전송하는 메세지의 개수를 제어</p>
<p>값이 클 수록, 메모리 사용은 증가하지만 처리량은 좋아짐</p>
<p>단, 너무 큰 값으로 사용하면 배치 처리가 비효율 → 오히려 처리량 감소</p>
<p><strong>timeout.ms / request.time.out / metadata.fetch.timeout.ms</strong></p>
<ul>
<li><p>동기화된 리플리카들이 메시지를 인지하는 동안, 브로커가 대기하는 시간 제어</p>
</li>
<li><p>데이터 전송 시 프로듀서가 서버의 응답을 대기 시간 제어</p>
</li>
<li><p>메타데이터 요청 시 프로듀서가 서버의 응답 대기 시간 제어</p>
</li>
</ul>
<p><strong>max.block.ms</strong></p>
<p>프로듀서 전송 버퍼가 가능차거나, 메타 데이터를 요청했지만 사용할 수 없을 때, 해당 매개변수의 값만큼 일시 중단</p>
<p><strong>max.request.size</strong></p>
<p>프로듀서가 전송하는 쓰기 요청의 크기를 제어</p>
<p><strong>recieve.buffer.bytes와 send.buffer.bytes</strong></p>
<p>데이터를 읽고 쓸 때 소켓이 사용하는 TCP 송수신 버퍼의 크기</p>
<h1 id="35-직렬처리기">3.5 직렬처리기</h1>
<h2 id="351-커스텀-직렬처리기">3.5.1 커스텀 직렬처리기</h2>
<ul>
<li><p>카프카로 전송하는 객체가 단순 문자열, 정수가 아닐 때, Avro, Thrift, Protobuf 같은 범용 직렬화 라이브러리 사용해서 커스텀 직렬처리기 생성 가능</p>
</li>
<li><p>하지만, 범용 직렬화 라이브러리의 사용을 적극 권장</p>
</li>
<li><p>전송하려는 객체 필드의 타입이 변경되거나 새로 추가된다면, 기존 메세지와 새 메세지 간의 호환 유지에 문제가 생김</p>
</li>
</ul>
<p>-&gt; 범용직렬처리기(JSON, 아파치 Avro, Thrift, Protobuf) 사용을 권장</p>
<h2 id="352-아파치-avro를-사용해서-직렬화하기">3.5.2 아파치 Avro를 사용해서 직렬화하기</h2>
<h4 id="아파치-avro">아파치 Avro</h4>
<ul>
<li>언어 중립적인 데이터 직렬화 시스템</li>
<li>언어 독립적인 스키마로 데이터 구조를 표한하는 데 주로 JSON 형식</li>
<li>직렬화 역시 JSON을 지원하지만, 주로 이진 파일 사용</li>
</ul>
<pre><code class="language-json">{
    &quot;namespace&quot;: &quot;customerManagement.avro&quot;,
    &quot;type&quot;: &quot;record&quot;,
    &quot;name&quot;: &quot;Customer&quot;,
    &quot;fields&quot;: [
        {&quot;name&quot;: &quot;id&quot;,&quot;type&quot;: &quot;int&quot;},
        {&quot;name&quot;: &quot;name&quot;,&quot;type&quot;: &quot;string&quot;},
        // 스키마 변경 전 (구버전)
        // {&quot;name&quot;: &quot;faxNumber&quot;,&quot;type&quot;: [&quot;null&quot;,&quot;string&quot;],&quot;default&quot;: &quot;null&quot;}]
        // 스키마 변경 후 (신버전)
        , {&quot;name&quot;: &quot;email&quot;,&quot;type&quot;: [&quot;null&quot;,&quot;string&quot;],&quot;default&quot;: &quot;null&quot;}]
}</code></pre>
<h4 id="avro가-메시지-시스템에-사용하는-데-적합한-이유">Avro가 메시지 시스템에 사용하는 데 적합한 이유</h4>
<ul>
<li><p>메시지를 쓰는 애플리케이션이 새로운 스키마로 전환하더라고, 애플리케이션은 지속해서 변경 없이 메시지 처리 가능</p>
</li>
<li><p>팩스번호를 없애고  email 필드 추가할 경우 → 스키마 변경 전, 후의 애플리케이션 모두 카프카 이상없이 사용하는 방법 고려 필요</p>
</li>
<li><p>업그레이드 이전 데이터를 읽는 애플리케이션</p>
<ul>
<li>getName(), getId(), getFaxNumber() 메서드 존재 / 신버전의 스키마를 사용해서 쓴 메시지를 읽으면 getFaxNumber()는 null을 반환할 것</li>
</ul>
</li>
<li><p>업그레이드 이후 데이터를 읽는 애플리케이션</p>
<ul>
<li>getFaxNumber()는 없고 getEmail() 메서드 존재 / 구버전의 스키마를 사용해서 쓴 메시지를 읽으면 getEmail()은 null을 반환할 것</li>
</ul>
</li>
<li><p>_즉, 데이터를 읽는 모든 애플리케이션 변경 X, 기존 데이터 변경 X</p>
</li>
</ul>
<h4 id="주의사항">주의사항</h4>
<p>데이터를 쓰는 데 사용되는 스키마와 
읽는 애플리케이션에서 기대하는 스키마가 호환되어야 함
역직렬처리기는 데이터를 쓸 때 사용되었던 스키마를 사용해야 한다</p>
<h2 id="354-카프카에서-avro-레코드-사용하기">3.5.4 카프카에서 Avro 레코드 사용하기</h2>
<p>스키마 전체를 레코드에 담기에는 크기 부담</p>
<p>따라서, 카프카에서 사용되는 모든 스키마를 레지스트리에 저장 → 스키마 레지스트리를 사용</p>
<p>모든 데이터는 레지스트리에 저장, 카프카 레코드에는 사용하는 스키마의 식별자 (id)만 저장하면 된다.</p>
<p>컨슈머는 해당 식별자(id)를 이용해서 스키마 레지스트리의 스키마를 가져온 후, 이를 맞춰서 데이터를 역직렬화 가능</p>
<p>이때 모든 작업은 직렬처리기와 역직렬처리기에서 수행된다.</p>
<p>카프카에 데이터를 쓰는 코드에서 스키마 레지스트리 관련 코드를 추가하지 않고, 직렬처리기를 사용하듯이 Avro를 사용하면 된다.</p>
<p>Avro 사용 방법 참고 :  <a href="http://avro.apache.org/docs/current">http://avro.apache.org/docs/current</a></p>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/c090ffd2-42b2-4768-91c0-c36f9e3bb67e/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-08-26%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.55.46.png" alt=""></p>
<h4 id="스키마-레지스트리란">스키마 레지스트리란?</h4>
<ul>
<li>카프카 클라이언트 사이에서 메시지의 스키마를 저장하고 관리하는 웹 애플리케이션</li>
</ul>
<h5 id="등장배경">등장배경</h5>
<ul>
<li>다수의 프로듀서가 존재할 때, 컨슈머 입장에서는 어떤 프로듀서가 메시지를 보냈는 지 모른다.</li>
<li>또한, 프로듀서의 쓰기 작업은 메시지 큐의 가장 말단에서만 실행 중간에 수정 작업을 진행할 수 없다.</li>
</ul>
<p>만약, 다수의 프로듀서 중, 하나의 프로듀서가 스키마를 변경하여 메시지를 발행 → 토픽에 순차적으로 쌓이면...</p>
<ul>
<li>컨슈머는 변경된 스키마에 대해 적절히 대응하지 못한다. </li>
</ul>
<h5 id="스키마-레지스트리의-목적">스키마 레지스트리의 목적</h5>
<ul>
<li>토픽별로 메시지 key와 value 스키마 버전 관리</li>
<li>스키마 호환성 규칙 강제</li>
<li>스키마 버전 별 호환성을 강제하여, 스키마가 변경되어도 컨슈머 입장에서 메시지를 적절하게 구독할 수 있게 한다.</li>
</ul>
<h5 id="스키마-버전-조회">스키마 버전 조회</h5>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/8d1cdf3f-436b-4ac4-b85a-4c20713939bd/schema-registry-and-kafka.png" alt=""></p>
<pre><code>             (그림, 스키마 저장 및 검색을 위한 Confluent Schema Registry)</code></pre><p>스키마 레지스트리는 스키마 생성, 조회, 관리에 대한 HTTP API를 제공</p>
<p>API 상세는 <a href="https://docs.confluent.io/home/overview.html">https://docs.confluent.io/home/overview.html</a> 에서 참조</p>
<p>참고 ) <a href="https://docs.confluent.io/platform/current/schema-registry/index.html#schemas-subjects-and-topics">https://docs.confluent.io/platform/current/schema-registry/index.html#schemas-subjects-and-topics</a></p>
<h1 id="36-파티션">3.6 파티션</h1>
<h4 id="기본-파티션">기본 파티션</h4>
<ul>
<li><p>ProducerRecord 객체는 토픽 이름과 key, 값을 포함 (대부분)</p>
</li>
<li><p>카프카 메시지는 key와 값의 쌍으로 구성되지만, 기본값이 null로 설정된 키와 함께 토픽과 값만 갖는 ProducerRecord 객체도 생성이 가능 </p>
</li>
<li><p>key의 목적</p>
<ul>
<li>메시지를 식별하는 추가 정보를 가짐</li>
<li>메시지를 쓰는 토픽의 여러 파티션 중 하나를 결정하는 역할</li>
<li>같은 key를 가진 모든 메시지는 같은 파티션에 저장됨</li>
</ul>
</li>
</ul>
<h4 id="기본-파티셔너의-특징">기본 파티셔너의 특징</h4>
<ul>
<li>키가 null이고, 카프카의 기본 파티셔너를 사용하면<ul>
<li>사용 가능한 토픽의 파티션들 중 하나가 무작위로 선택됨</li>
<li>각 메시지에 저장된 메시지 개수의 균형을 맞추기 위해 라운드 로빈 알고리즘을 사용함</li>
</ul>
</li>
<li>키가 있고 기본 파티셔너를 사용하면<ul>
<li>카프카에서 key의 해시값을 구한 후, 그 값에 따라 특정 파티션에 메시지 저장</li>
</ul>
</li>
</ul>
<h1 id="37-구버전의-프로듀서-api들">3.7 구버전의 프로듀서 API들</h1>
<p>이 장에서는 org.apache.kafka.client 패키지에 포함된 자바 프로듀서 클라이언트에 대해 알아봤지만 여전히 스칼라 언어로 작성된 구버전 프로듀서가 존재</p>
<ul>
<li><p>kafka.producer 패키지와 핵심 카프카 모듈(SyncProducers, AsyncProducer)에 포함 </p>
<ul>
<li>SyncProducers
메시지 추가 전송하기 전에 이미 전송된 메시지가 서버에서 인지되는 것을 기다림</li>
<li>AsyncProducer
백그라운드로 실행되어 메시지 수집 후, 별도 스레드에서 전송, 전송 성공여부 프로듀서 클라이언트에 알려주지 않음</li>
</ul>
</li>
<li><p>하지만, 현재 버전의 카프카에서 SyncProducers, AsyncProducer 모두 지원</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[1장. 코틀린이란 무엇이며 왜 필요한가?]]></title>
            <link>https://velog.io/@dev_jhjhj/1%EC%9E%A5.-%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80</link>
            <guid>https://velog.io/@dev_jhjhj/1%EC%9E%A5.-%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80</guid>
            <pubDate>Sun, 13 Mar 2022 06:35:01 GMT</pubDate>
            <description><![CDATA[<h1 id="11-코틀린-맛보기">1.1 코틀린 맛보기</h1>
<pre><code class="language-kotlin">// Int? = null 널이 될 수 있는 타입 (Int?) 과 파라미터 디폴트 값
data class Person(val name: String, val age: Int? = null)

fun main() {

    val persons = listOf(Person(&quot;영희&quot;)
        , Person(&quot;철수&quot;, age=29))

        // :? 엘비스 연산자 -&gt; null 일 경우 0을 리턴 
    val oldest = persons.maxOf { i -&gt; i.age ?: 0}
    println(&quot;나이가 가장 많은 사람 : $oldest&quot;)

}</code></pre>
<h1 id="12-코틀린의-주요-특성">1.2 코틀린의 주요 특성</h1>
<h2 id="121-대상-플랫폼">1.2.1 대상 플랫폼</h2>
<p>서버, 안드로이드 등 자바가 실행되는 모든 곳에서 사용</p>
<h2 id="122-정적-타입-지정">1.2.2 정적 타입 지정</h2>
<ul>
<li><p>코틀린은 타입추론 (Type inference)가 가능 → 모든 변수의 타입을 프로그래머가 직접 명시할 필요 없음</p>
</li>
<li><p>자바와 마찬가지로  코틀린도 정작 타입 지정 언어</p>
</li>
<li><p>정적 타입 지정이란 ?</p>
<ul>
<li>모든 프로그램 <strong>구성 요소의 타입을 컴파일 시점에 알 수 있고</strong>, 프로그램 안에서 객체의 필드나 메소드를 사용할 때 마다 <strong>컴파일러가 타입을 검증</strong>해준다.</li>
</ul>
</li>
<li><p>정적 타입 지정의 장점</p>
<ul>
<li>성능 : 성능 시점에 어떤 메소드 호출할 지 알아내는 과정이 필요 없어서 메소드 호출이 빠르다.</li>
<li>신뢰성 : 컴파일러가 프로그램 정확성 검증하기 때문에 프로그램 오류 중단 가능성이 적어짐</li>
<li>유지보수성</li>
<li>도구 지원 : 더 안전하게 리팩토링이 가능함</li>
</ul>
</li>
</ul>
<h2 id="123-함수형-프로그래밍과-객체지향-프로그래밍">1.2.3 함수형 프로그래밍과 객체지향 프로그래밍</h2>
<h3 id="일급시민-함수-first-class-함수">일급시민 함수 (First Class 함수)</h3>
<p>함수를 일반 값처럼 다룰 수 있다. 함수를 변수에 저장, 함수를 아규먼트로 다른 함수에 전달 가능, 새로운 함수 만들어서 반환 가능</p>
<h3 id="불변성">불변성</h3>
<p>immutable 객체를 사용해서 프로그램 작성</p>
<h3 id="side-effect-없음">Side Effect 없음</h3>
<p>입력이 같으면 항상 같은 출력을 반환</p>
<p>다른 객체의 상태를 변경하지 않으며, 함수 외부나 다른 바깥 환경과 상호작용하지 않는 순수한 함수 사용</p>
<h3 id="함수형-프로그래밍의-장점">함수형 프로그래밍의 장점</h3>
<ul>
<li>간결성 : 명령형 코드에 비해 더 간결하고 우아하다.</li>
<li>다중스레드를 사용해도 안전하다. (thread-safe 하다)</li>
<li>함수형 프로그램은 테스트 하기 쉽다.</li>
</ul>
<h2 id="124-무료-오픈소스">1.2.4 무료 오픈소스</h2>
<ul>
<li>무료 오픈소스, 주요 IDE와 빌드시스템 완전 지원</li>
</ul>
<h2 id="13-코틀린-응용">1.3 코틀린 응용</h2>
<h3 id="131-코틀린-서버-프로그래밍">1.3.1 코틀린 서버 프로그래밍</h3>
<ul>
<li>브라우저에서 HTML 페이지를 반환하는 웹 애플리케이션 개발 가능</li>
<li>모바일 애플리케이션에서 HTTP 를 통해 JSON API를 제공하는 백엔드 애플리케이션 개발 가능</li>
<li>RPC 프로토콜을 통해 서로 통신하는 MSA 가능</li>
</ul>
<h3 id="132-코틀린-안드로이드-프로그래밍">1.3.2 코틀린 안드로이드 프로그래밍</h3>
<h2 id="14-코틀린의-철학">1.4 코틀린의 철학</h2>
<p>자바와 상호운영성에 초점을 맞추고 실용적, 간결, 안전한 언어</p>
<h3 id="안정성">안정성</h3>
<ul>
<li>NPE를 없애기 위해 노력</li>
<li>ClassCaseException 방지</li>
<li>어떤 객체를 다른 타입으로 cast 하기 전에 미리 검사하지 않으면, ClassCaseException이 발생할 수 있다.</li>
<li>Java에서는 귀찮아서 타입 검증을 생략하는 경우가 많은데, 코틀린은 타입검사와 cast가 연산자에 의해 이루어 진다.</li>
</ul>
<pre><code class="language-kotlin">if(value is String)   // 타입 검사
    println(value.toUpperCase())   // 해당 타입의 메소드를 사용
</code></pre>
<h3 id="상호운용성">상호운용성</h3>
<ul>
<li>코틀린의 클래스나 메소드를 일반적인 자바 클래스나 메소드와 똑같이 사용 가능, 자바와 코틀린 코드를 프로젝트에서 섞어서 사용이 가능하다.</li>
<li>기존 Java에서 사용하는 라이브러리 그대로 사용 가능</li>
</ul>
</br>
---

<p>출처 : (서적) Kotlin in Action 드미트리 제메로프, 스베트라나 이사코바 저자 /오현석 옮김</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Stream과 병렬처리 - 2]]></title>
            <link>https://velog.io/@dev_jhjhj/Java-Stream%EA%B3%BC-%EB%B3%91%EB%A0%AC%EC%B2%98%EB%A6%AC2</link>
            <guid>https://velog.io/@dev_jhjhj/Java-Stream%EA%B3%BC-%EB%B3%91%EB%A0%AC%EC%B2%98%EB%A6%AC2</guid>
            <pubDate>Sun, 06 Mar 2022 10:09:37 GMT</pubDate>
            <description><![CDATA[<h1 id="7-looping-메소드-peek-foreach">7. Looping 메소드 (peek(), forEach())</h1>
<h2 id="peek">peek()</h2>
<p>중간처리 looping 메소드, 중간처리 단계에서 전체 요소를 루핑을 돌면서 추가적인 작업을 위해 사용</p>
<p>마지막에 최종 처리 메소드가 실행되지 않으면 동작하지 않는다.</p>
<h2 id="foreach">forEach()</h2>
<p>최종처리 looping 메소드</p>
<p>파이프라인 마지막에 루핑하면서 요소를 하나씩 처리</p>
<p>forEach가 최종처리 메소드이기 때문에 이후에 또다른 최종처리 메소드를 호출하면 안된다.</p>
<pre><code class="language-java">import java.util.Arrays;

public class LoopingExample {
    public static void main(String[] args) {

        int[] intArr = {1, 2, 3,4,5,6};

        System.out.println(&quot;peek은 중간처리 메소드 - peek을 마지막에 호출한 경우, 동작하지 않음&quot;);
        Arrays.stream(intArr)
                .filter(a -&gt; a%2 ==0)
                .peek(n-&gt; System.out.println(n));

        // 최종 처리 메소드 sum()을 마지막에 호출하면 peek 동작함
        int total = Arrays.stream(intArr)
                .filter(a -&gt; a%2 ==0)
                .peek(n-&gt; System.out.println(n))
                .sum();
        System.out.println(&quot;total = &quot; + total);

        System.out.println(&quot;forEach()는 최종처리 루핑 메소드 - 마지막에 호출한 경우, 동작함&quot;);
        Arrays.stream(intArr)
                .filter(a -&gt; a%2 ==0)
                .forEach(n-&gt; System.out.println(n));
    }
}</code></pre>
<h1 id="8-matching-allmatch-anymatch-nonematch">8. Matching (allMatch(), anyMatch(), noneMatch())</h1>
<p>최종 처리 단계에서 요소들이 특정 조건에 맞는 지 확인할 수 있도록 matching 메소드를 지원한다.</p>
<h2 id="allmatch">allMatch()</h2>
<p>모든 요소들이 매개값으로 주어진 predicate 값이 조건을 만족하는 지 조사한다.</p>
<h2 id="anymatch">anyMatch()</h2>
<p>최소한 한개 이상의 요소가 매개값으로 주어진 predicate 값이 조건을 만족하는 지 조사한다.</p>
<h2 id="nonematch">noneMatch()</h2>
<p>모든 요소가 매개값으로 주어진 predicate 값이 조건을 만족하지 않는 지 조사한다.</p>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/8dd58f13-dab5-4f4a-8833-57b5e18a738a/allMatch.png" alt=""></p>
<pre><code class="language-java">import java.util.Arrays;

public class MatchExample {
    public static void main(String[] args) {
        int[] intArray = {1, 2, 3, 4, 5};

        boolean result = Arrays.stream(intArray)
                .allMatch(a -&gt; a % 2 == 0);
        System.out.println(&quot;모두 2의 배수인가 ? = &quot; + result);

        result = Arrays.stream(intArray)
                .anyMatch(a -&gt; a % 3 == 0);
        System.out.println(&quot;3의 배수인 요소가 하나라도 있나 ? = &quot; + result);

        result = Arrays.stream(intArray)
                .noneMatch(a -&gt; a % 7 == 0);
        System.out.println(&quot;모든 요소가 7의 배수가 아닌가? = &quot; + result);

    }
}</code></pre>
<h1 id="9-기본-집계-sum-count-average-max-min">9. 기본 집계 (sum(), count(), average(), max(), min())</h1>
<p>집계는 최종 처리 기능으로 요소들을 처리한다.</p>
<p>카운팅, 합계, 평균값, 최댓값, 최솟값 등 하나의 값을 산출한다.</p>
<p>집계는 대량의 데이터를 가공해서 축소하는 리덕션(Reduction) 이라고 볼 수 있다.</p>
<h2 id="스트림이-제공하는-기본-집계-함수">스트림이 제공하는 기본 집계 함수</h2>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/f2b24c3f-d65a-456e-93bd-a4012ab7372b/%E1%84%89%E1%85%B3%E1%84%90%E1%85%B3%E1%84%85%E1%85%B5%E1%86%B7%E1%84%8B%E1%85%B5%E1%84%8C%E1%85%A6%E1%84%80%E1%85%A9%E1%86%BC%E1%84%92%E1%85%A1%E1%84%82%E1%85%B3%E1%86%AB%20%E1%84%80%E1%85%B5%E1%84%87%E1%85%A9%E1%86%AB%E1%84%8C%E1%85%B5%E1%86%B8%E1%84%80%E1%85%A8.png" alt=""></p>
<pre><code class="language-java">import java.util.Arrays;

public class AggregateExample {

    public static void main(String[] args) {
        int[] intArr = {1, 2, 3, 4, 5, 6, 7};

        long count = Arrays.stream(intArr)
                .filter(a -&gt; a % 2 == 0)
                .count();
        System.out.println(&quot;2의 배수 개수 : &quot; + count);

        long sum = Arrays.stream(intArr)
                .filter(n -&gt; n % 2 == 0)
                .sum();
        System.out.println(&quot;2의 배수인 요소들의 합 : &quot; + sum);

        double avg = Arrays.stream(intArr)
                .filter(n -&gt; n % 2 == 0)
                .average()
                .getAsDouble();
        System.out.println(&quot;2의 배수인 요소들의 평균, double 타입 : &quot; + avg);

        int max = Arrays.stream(intArr)
                .filter(n -&gt; n % 2 == 0)
                .max()
                .getAsInt();
        System.out.println(&quot;2의 배수인 요소들의 최댓값, double 타입 : &quot; + max);

        int first = Arrays.stream(intArr)
                .filter(n -&gt; n % 3 == 0)
                .findFirst()
                .getAsInt();
        System.out.println(&quot;3의 배수인 요소들 중에서 가장 첫 요소 : &quot; + first);
    }
}</code></pre>
<h2 id="optional-클래스">Optional 클래스</h2>
<p>Optional 클래스는 단순히 집계 값만 저장하는 것이 아니라, 집계 값이 존재하지 않을 경우(null 일 경우), 디폴트 값을 설정할 수 있고 집계 값을 처리하는 Cunsumer도 등록할 수 있다.</p>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/e25be9b9-e232-4bf1-b8d6-28f53ed118d1/Optional.png" alt=""></p>
<pre><code>importjava.util.ArrayList;
importjava.util.List;
importjava.util.OptionalDouble;

public classOptionalExample {

public static voidmain(String[] args) {
        List&lt;Integer&gt; list =newArrayList&lt;&gt;();

//  java.util.NoSuchElementException발생한다.
        //요소 값이 null이기 떄문 !
//        double avg = list.stream()
//                .mapToInt(Integer::intValue)
//                .average()
//                .getAsDouble();

        // 1. isPresent()사용
OptionalDouble optionalAvg = list.stream()
                .mapToInt(Integer::intValue)
                .average();

if(optionalAvg.isPresent()){
            System.out.println(&quot;isPresent() 사용 : &quot; + optionalAvg);
        }else{
            System.out.println(0.0);
        }

// 2. orElse
doubleavg = list.stream()
                .mapToInt(Integer::intValue)
                .average()
                .orElse(0.0);

        System.out.println(&quot;orElse() 사용 = &quot; + avg);

// 3. ifPresent람다식
list.stream()
                .mapToInt(Integer::intValue)
                .average()
                .ifPresent(a -&gt; System.out.println(&quot;ifPresent() 사용 : &quot; + a));

    }
}</code></pre><h1 id="10-커스텀-집계reduce">10. 커스텀 집계(reduce())</h1>
<p>기본 집계 메소드 외에도 프로그램화해서 다양한 집계 결과물을 만들 수 있도록 reduce() 메소드를 제공한다.</p>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/2b4fc222-3f71-4119-a0e3-d07c8b569a4c/reduce().png" alt=""></p>
<p>각 인터페이스에는 매개타입으로 XXXOperator, 리턴 타입으로 OptionalXXX, int, longm double 을 가지는 reduce() 메소드가 오버로딩 되어 있다.</p>
<p>스트림 요소가 없을 경우, 디폴트 값으로 <code>identity</code> 가 리턴된다.</p>
<p>XXXOperator 매개값은 집계 처리를 위한 람다식을 대입한다.</p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.List;

public class ReductionExample {

    public static void main(String[] args) {
        List&lt;Person&gt; personList = Arrays.asList(new Person(&quot;홍길동&quot;, 20, Person.MALE)
                , new Person(&quot;김미미&quot;, 21, Person.FEMALE)
                , new Person(&quot;JHJ&quot;, 22, Person.FEMALE));

        int sum1 = personList.stream()
                .mapToInt(Person::getAge)
                .sum();

        int sum2 = personList.stream()
                .map(Person::getAge)
                .reduce((a, b) -&gt; a + b)
                .get();

        int sum3 = personList.stream()
                .map(Person::getAge)
                .reduce(0, (a, b) -&gt; a + b);

        System.out.println(&quot;sum1 = &quot; + sum1);
        System.out.println(&quot;sum2 = &quot; + sum2);
        System.out.println(&quot;sum3 = &quot; + sum3);
    }
}</code></pre>
<p>출처 : <a href="https://www.google.com/search?q=%EC%9D%B4%EA%B2%83%EC%9D%B4+%EC%9E%90%EB%B0%94%EB%8B%A4&amp;oq=%EC%9D%B4%EA%B2%83%EC%9D%B4+%EC%9E%90%EB%B0%94%EB%8B%A4&amp;aqs=chrome..69i57j0i20i263i512j0i512l3j69i60j69i61l2.2118j0j7&amp;sourceid=chrome&amp;ie=UTF-8">이것이 자바다 (저자 : 신용권, 출판사 : 한빛미디어)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Stream과 병렬처리 - 1]]></title>
            <link>https://velog.io/@dev_jhjhj/Java-Stream%EA%B3%BC-%EB%B3%91%EB%A0%AC%EC%B2%98%EB%A6%AC-1</link>
            <guid>https://velog.io/@dev_jhjhj/Java-Stream%EA%B3%BC-%EB%B3%91%EB%A0%AC%EC%B2%98%EB%A6%AC-1</guid>
            <pubDate>Sun, 27 Feb 2022 06:32:59 GMT</pubDate>
            <description><![CDATA[<h1 id="1-스트림-소개">1. 스트림 소개</h1>
<h2 id="스트림stream-이란">스트림(Stream) 이란?</h2>
<p>스트림(Stream)은 Java 8 버전 이후부터 추가되었으며, Collection과 배열의 저장요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 도와주는 반복자</p>
<h2 id="stream-vs-iterator">Stream vs Iterator</h2>
<p>Java 7 이전에는 Collection의 저장 요소를 참조하기 위해서 Iterator를 사용하였다. </p>
<p>Java 8 이후에는 Stream을 사용해서 저장 요소를 하나씩 참조가 가능하며, forEach() 메소드와 같은 Consumer 함수 인터페이스를 이용해서 람다식으로 사용이 가능하다.</p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;

public class IteratorAndStreamExample {

    public static void main(String[] args) {

        List&lt;String&gt; list = Arrays.asList(&quot;사과&quot;, &quot;바나나&quot;, &quot;포도&quot;);

        // Java 7 이전, Iterator 사용
        Iterator&lt;String&gt; iterator = list.iterator();
        while (iterator.hasNext()){
            String fruit = iterator.next();
            System.out.println(&quot;fruit-iterator = &quot; + fruit);
        }

        // Java8 이후, Stream 사용
        Stream&lt;String&gt; stream = list.stream();
        stream.forEach(fruit -&gt; System.out.println(&quot;fruit-stream = &quot; + fruit));
    }
}</code></pre>
<h2 id="스트림의-특징">스트림의 특징</h2>
<h3 id="람다식으로-요소-처리-코드를-제공">람다식으로 요소 처리 코드를 제공</h3>
<p>Stream이 제공하는 대부분의 메소드들은 함수적 인터페이스를 매개 타입으로 가진다. 따라서 람다식 또는 메소드 참조를 이용해서 컬렉션의 요처들을 매개값으로 전달이 가능하다.</p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class LamdaExpressionExample {

    public static void main(String[] args) {
        List&lt;Person&gt; list = Arrays.asList(
                new Person(&quot;홍길동&quot;, 20),
                new Person(&quot;JHJ&quot;, 20));

        Stream&lt;Person&gt; stream = list.stream();
        stream.forEach(p -&gt; {
            System.out.println(&quot;이름 :  &quot; + p.getName() + &quot;, 나이 : &quot; + p.getAge());
        });
    }
}

public class Person{
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}</code></pre>
<h3 id="내부-반복자를-사용하므로-병렬처리가-쉽다">내부 반복자를 사용하므로 병렬처리가 쉽다.</h3>
<ul>
<li>외부 반복자 : 개발자가 코드로 직접 컬렉션의 요소를 가져오는 패턴 (index를 활용한 for문, iterator, while문 등)</li>
<li>내부 반복자 : 컬렉션 내부에서 요소들을 반복시키고 개발자는 요소당 처리해야할 코드만 제공하는 패턴</li>
</ul>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/f186379d-a597-41e9-945b-7d71b96d5930/%E1%84%82%E1%85%A2%E1%84%87%E1%85%AE%20%E1%84%87%E1%85%A1%E1%86%AB%E1%84%87%E1%85%A9%E1%86%A8%E1%84%8C%E1%85%A1.png" alt=""></p>
<ul>
<li>내부 반복자를 사용하면 컬렉션 내부에서의 요소를 어떻게 반복 시킬지는 컬렉션에 맡겨 둔다. 개발자는 요소 처리에만 집중할 수 있다.</li>
<li>멀티코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬작업을 도와주기 때문에 하나씩 처리하는 순차적인 외부 반복자보다는 내부 반복자를 사용하는 것이 좀 더 효율적이다.</li>
<li>병렬처리란 ? 
한가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것
런타임시 하나의 작업을 여러개의 작업으로 나눠서 처리 후, 결과를 자동으로 결합</li>
</ul>
<pre><code class="language-java">import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class ParallelExample {

    public static void main(String[] args) {
        List&lt;String&gt; list = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;coconut&quot;, &quot;grape&quot;);

        // 순차처리
        Stream&lt;String&gt; stream = list.stream();
        stream.forEach(ParallelExample::print);

        // 병렬처리 : 병렬 스트림으로 스트림 선언
        Stream&lt;String&gt; parallelStream = list.parallelStream();
        parallelStream.forEach(ParallelExample::print);
    }

    private static void print(String s) {
        System.out.println(s + &quot; : &quot; + Thread.currentThread().getName());
    }
}

// 결과
apple : main
banana : main
coconut : main
grape : main
coconut : main
grape : main
banana : ForkJoinPool.commonPool-worker-5
apple : ForkJoinPool.commonPool-worker-19</code></pre>
<h3 id="스트림은-중간처리와-최종-처리를-할-수-있다">스트림은 중간처리와 최종 처리를 할 수 있다.</h3>
<p>컬렉션 요소에 대해서 중간처리 (매핑, 필터링, 정렬)를 한 후, 최종 처리 (반복, 카운팅, 평균, 총합 등)의 집계처리가 가능하다.</p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.List;

public class MapAndReduceExample {
    public static void main(String[] args) {
        List&lt;Person&gt; personList = Arrays.asList(new Person(&quot;홍길동&quot;, 20),
                new Person(&quot;김OO&quot;, 21),
                new Person(&quot;JHJ&quot;, 21));

        double ageAvg = personList.stream()
                .mapToInt(Person::getAge)
                .average()
                .getAsDouble();

        System.out.println(&quot;평균 나이 = &quot; + ageAvg);
    }
}</code></pre>
<h1 id="2-스트림의-종류">2. 스트림의 종류</h1>
<p>Java 8 부터 <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html">java.util.stream</a> 패키지에 스트림(Stream) API 들이 포함되어 있다.</p>
<p>부모 인터페이스 : <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/BaseStream.html">BaseStream</a></p>
<p>자식 인터페이스 : <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html">Stream</a>, <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html">IntStream</a>, <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/LongStream.html">LongStream</a>, <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/DoubleStream.html">DoubleStream</a></p>
<h1 id="3-스트림-파이프-라인">3. 스트림 파이프 라인</h1>
<h2 id="리덕션reduction이란">리덕션(Reduction)이란?</h2>
<p>대량의 데이터를 가공해서 축소하는 것 (데이터의 합계, 평균값, 카운팅, 최대값, 최소값 등)</p>
<p><strong>하지만, 컬렉션의 요소를 바로 리덕션화해서 집계할 수 없을 경우, 집계하기 좋도록 필터링, 매핍, 정렬, 그룹피이 등 중간처리가 필요</strong></p>
<h2 id="중간처리와-최종-처리">중간처리와 최종 처리</h2>
<p>스트림은 데이터의 필터링, 매핑, 정렬, 그룹핑 등의 중간처리와 합계, 평균, 카운팅, 최대값, 최소값 등의 최종 집계 처리를 파이프라인으로 해결한다.</p>
<p>파이프라인은 여러개의 스트림이 연결되어 있는 구조를 말하는 데, 최종 처리를 제외하고 모두 중간처리 스트림이다.</p>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/59e591d1-5e30-4bf9-a7f8-da5fe98aaaa2/%E1%84%8C%E1%85%AE%E1%86%BC%E1%84%80%E1%85%A1%E1%86%AB%E1%84%8E%E1%85%A5%E1%84%85%E1%85%B5%E1%84%8B%E1%85%AA%20%E1%84%8E%E1%85%AC%E1%84%8C%E1%85%A9%E1%86%BC%E1%84%8E%E1%85%A5%E1%84%85%E1%85%B5.png" alt=""></p>
<p>중간 스트림이 생성될 때, 바로 중간처리를 진행하는 것이 아니라, <code>최종처리가 시작되기 전까지 중간처리는 Lazy 된다. 최종 처리가 시작되면 그때 컬렉션 요소들이 중간 스트림에서 처리 → 최종 처리</code></p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.List;
import java.util.OptionalDouble;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamPipelineExample {

    public static void main(String[] args) {
        List&lt;Person&gt; list = Arrays.asList(new Person(&quot;홍길동&quot;, 20, Person.MALE),
                new Person(&quot;김미미&quot;, 20, Person.FEMALE),
                new Person(&quot;김호호&quot;, 25, Person.MALE));

//    중간처리
//    Stream&lt;Person&gt; maleFemaleStream = list.stream();
//    Stream&lt;Person&gt; maleStream = maleFemaleStream.filter(m-&gt; m.getGender() == Person.MALE);
//    IntStream ageStream = maleStream.mapToInt(Person::getAge);
//    최종 처리
//    OptionalDouble optionalDouble = ageStream.average();
//    double ageAge = optionalDouble.getAsDouble();

        double ageAge = list.stream()
                .filter(m -&gt; m.getGender() == Person.MALE)
                .mapToInt(Person::getAge)
                .average()   // 최종처리
                .getAsDouble();

        System.out.println(&quot;ageAge = &quot; + ageAge);

    }
}</code></pre>
<h1 id="4-필터링-distinct-filter">4. 필터링 (distinct(), filter())</h1>
<p>필터링은 중간 처리 기능으로 요소를 걸러내는 역할</p>
<p>모든 스트림이 공통으로 가지고 있다.</p>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/0c1366ef-b906-4622-9b62-20c3d9c59468/%E1%84%91%E1%85%B5%E1%86%AF%E1%84%90%E1%85%A5%E1%84%85%E1%85%B5%E1%86%BC.png" alt=""></p>
<h3 id="distinct">distinct()</h3>
<p>Stream의 경우, Object.equals()가 true이면 동일한 객체로 판단해서 중복 제거</p>
<p>IntStream, LongStream, DoubleStream은 동일값이면 중복 제거</p>
<h3 id="filter">filter()</h3>
<p>매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링</p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.List;

public class FilteringExample {

    public static void main(String[] args) {
        List&lt;String&gt; fruits = Arrays.asList(&quot;사과&quot;, &quot;바나나&quot;, &quot;포도&quot;, &quot;귤&quot;, &quot;사과&quot;, &quot;포도&quot;, &quot;포도송이&quot;, &quot;포도알&quot;);

        // 중복제거
        fruits.stream()
                .distinct()
                .forEach(f-&gt; System.out.println(f));

        // 필터링
        fruits.stream()
                .filter(f-&gt; f.startsWith(&quot;포도&quot;))
                .forEach(f-&gt; System.out.println(f));

        // 중복 제거 후, 필터링
        fruits.stream()
                .distinct()
                .filter(f-&gt; f.startsWith(&quot;포도&quot;))
                .forEach(f-&gt; System.out.println(f));
    }
}</code></pre>
<h1 id="5-매핑-flatmapxxx-mapxxx-asxxxstream-boxed">5. 매핑 (flatMapXXX(), mapXXX(), asXXXStream(), boxed())</h1>
<p>mapping은 중간 처리 기능으로 스트림의 요소를 다른 요소로 대체하는 것을 말한다.</p>
<h2 id="flatmapxxx">flatMapXXX()</h2>
<p>flatMap은 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하나의 스트림으로 연결하여 새로운 스트림을 리턴한다.</p>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/0711fb85-97ea-434b-8264-2e2bae83baa9/flatMap.png" alt=""></p>
<h3 id="flatmap-메소드-종류">flatMap 메소드 종류</h3>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/d41a368a-8353-405b-9721-27cf3f7a6df6/flatMap_method.png" alt=""></p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.List;

public class FlatMapExample {

    public static void main(String[] args) {
        List&lt;String&gt; list = Arrays.asList(&quot;hello world&quot;, &quot;good-bye world&quot;);
        list.stream()
                .flatMap(data -&gt; Arrays.stream(data.split(&quot; &quot;)))
                .forEach(word -&gt; System.out.println(word));

        List&lt;String&gt; list2 = Arrays.asList(&quot;10, 20, 30&quot;,&quot;40, 50, 60&quot;);
        list2.stream()
                .flatMapToInt(data -&gt; {
                    String[] strArr = data.split(&quot;,&quot;);
                    int[] intArr = new int[strArr.length];

                    for (int i = 0; i &lt; strArr.length; i++) {
                        intArr[i] = Integer.parseInt(strArr[i].trim());
                    }
                    return Arrays.stream(intArr);
                })
                .forEach(number -&gt; System.out.println(&quot;number = &quot; + number));

    }
}</code></pre>
<h2 id="mapxxx-메소드">mapXXX() 메소드</h2>
<p>map 메소드는 요소를 대체하는 요소로 구성된 새로운 스트림을 반환한다.</p>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/accccac2-d58a-4a80-891e-afebc96619e0/map.png" alt=""></p>
<h3 id="map의-메소드-종류">map의 메소드 종류</h3>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/a1faba62-e702-45bc-9a6a-54a42bd01418/map_method.png" alt=""></p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.List;

public class MapExample {
    public static void main(String[] args) {
        List&lt;Person&gt; personList = Arrays.asList(
                new Person(&quot;홍길동&quot;, 20, Person.MALE),
                new Person(&quot;김미미&quot;, 25, Person.FEMALE),
                new Person(&quot;JHJ&quot;, 23, Person.FEMALE)
        );

        personList.stream()
                .mapToInt(Person::getAge)
                .forEach(age -&gt; System.out.println(&quot;age = &quot; + age));
    }
}</code></pre>
<h2 id="asdoublestream-aslongstream-boxed-메소드">asDoubleStream(), asLongStream(), boxed() 메소드</h2>
<p>asDoubleStream() 메소드는 IntStream의 int 요소 또는 LongStream의 long 요소를 double 타입으로 반환해서 DoubleStream을 생성한다.</p>
<p>asLongStream() 메소드는 IntStream의 int 요소를 long 타입으로 변환해서 LongStream을 생성한다.</p>
<p>boxed() 메소드는 int, long, double 요소를 Integer, Long, Double 요소로 박싱해서 Stream을 생성한다.</p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.stream.IntStream;

public class AsDoubleBoxedExample {
    public static void main(String[] args) {

        int[] intArr = {1, 2, 3, 4, 5};

        IntStream intStream = Arrays.stream(intArr);
        intStream
                .asDoubleStream()
                .forEach(d -&gt; System.out.println(d));

        intStream = Arrays.stream(intArr);
        intStream
                .boxed()
                .forEach(obj -&gt; System.out.println(obj.intValue()));
    }
}</code></pre>
<h1 id="6-정렬-sorted">6. 정렬 (sorted)</h1>
<p>중간단계에서 스트림 요소를 정렬하여 최종 처리 순서를 변경할 수 있다. </p>
<h3 id="요소-정렬-메소드">요소 정렬 메소드</h3>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/ee11ee62-2d97-48f9-b43e-2c5963f8f0f8/%E1%84%8C%E1%85%A5%E1%86%BC%E1%84%85%E1%85%A7%E1%86%AF.png" alt=""></p>
<h3 id="객체-요소를-정렬할-때">객체 요소를 정렬할 때</h3>
<p>객체 요소일 경우, 클래스에 <code>Comparable</code> 를 구현하지 않으면, <code>sorted()</code> 메소드 호출했을 때 <code>ClassCastException</code> 발생한다. 그렇기 때문에 Comparable 인터페이스를 상속받으면, <code>Comparable</code>를 구현한 요소에서만 <code>sorted()</code> 메소드를 사용해야 한다. </p>
<pre><code class="language-java">public class Person implements Comparable&lt;Person&gt;{
    public static int MALE = 0;
    public static int FEMALE = 1;

    String name;
    int age;
    int gender;

    public Person(String name, int age, int gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getGender() {
        return gender;
    }

    @Override
    public int compareTo(Person o) {
        // age &lt; o.age 음수 리턴
        // age == o.age 0 리턴
        // age &gt; o.age 양수 리턴
        return Integer.compare(age, o.age); 
    }
}</code></pre>
<h3 id="comparable을-구현한-객체의-기본-비교-comparable-와-반대-비교">Comparable을 구현한 객체의 기본 비교 (Comparable) 와 반대 비교</h3>
<pre><code class="language-java">// 기본비교 ex) 오름차순
sorted();
sorted((a, b)-&gt; a.compareTo(b));
sorted(Comparator.naturalOrder());

// 반대비교 ex) 내림차순
sorted((a, b)-&gt; b.compareTo(a));
sorted(Comparator.reverseOrder());</code></pre>
<p>만약 객체 요소가 Comparable를 구현하지 않았다면, Comparator를 매개값으로 갖는 sorted() 메소드를 사요하면 된다.</p>
<pre><code class="language-java">// a가 작으면 음수, 같으면 0, a가 크면 양수
sorted((a, b) -&gt; {....})</code></pre>
<h3 id="정렬-예제">정렬 예제</h3>
<pre><code class="language-java">import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.IntStream;

public class SortingExample {
    public static void main(String[] args) {

        IntStream intStream = Arrays.stream(new int[] {5, 2, 1, 3, 4, 6});

        // 숫자 오름차순으로 정렬
        intStream
                .sorted()
                .forEach(n -&gt; System.out.println(n));

        // 객체 요소일 경우
        List&lt;Person&gt; personList = Arrays.asList(
                new Person(&quot;홍길동&quot;, 20, Person.MALE),
                new Person(&quot;김미미&quot;, 25, Person.FEMALE),
                new Person(&quot;JHJ&quot;, 23, Person.FEMALE)
        );

        // 나이 내림차순으로 정렬
        personList
                .stream()
                .sorted(Comparator.reverseOrder())
                .forEach(p -&gt; System.out.println(p.getAge()));

        // 나이 오름차순
        personList
                .stream()
                .sorted((a, b) -&gt; a.compareTo(b))
                .forEach(p -&gt; System.out.println(p.getAge()));
    }
}</code></pre>
<p>출처 : <a href="https://www.google.com/search?q=%EC%9D%B4%EA%B2%83%EC%9D%B4+%EC%9E%90%EB%B0%94%EB%8B%A4&amp;oq=%EC%9D%B4%EA%B2%83%EC%9D%B4+%EC%9E%90%EB%B0%94%EB%8B%A4&amp;aqs=chrome..69i57j0i20i263i512j0i512l3j69i60j69i61l2.2118j0j7&amp;sourceid=chrome&amp;ie=UTF-8">이것이 자바다 (저자 : 신용권, 출판사 : 한빛미디어)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Section 5. Spring AOP]]></title>
            <link>https://velog.io/@dev_jhjhj/Section-5.-Spring-AOP</link>
            <guid>https://velog.io/@dev_jhjhj/Section-5.-Spring-AOP</guid>
            <pubDate>Sat, 19 Feb 2022 06:56:46 GMT</pubDate>
            <description><![CDATA[<h1 id="스프링-aop-개념">스프링 AOP 개념</h1>
<h3 id="aopaspect-oriented-programming">AOP(Aspect-Oriented-Programming)</h3>
<p>OOP를 보완하는 수단으로 흩어진 Aspect를 모듈화 할 수 있는 프로그래밍 기법</p>
<h3 id="흩어진-관심사-crosscutting-concerns">흩어진 관심사 (Crosscutting Concerns)</h3>
<p>Concerns는 여러 클래스들에 걸쳐서 나타나는 비슷한 코드들, Filed 주입들을 말한다..</p>
<p>ex ) Transaction 처리, 성능관련 로깅 등</p>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/10469f72-3bae-4485-a24e-3850f7154756/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-19%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.12.19.png" alt=""></p>
<h3 id="aop를-적용하면">AOP를 적용하면...</h3>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/f7d1b08f-9436-463f-b60b-d1b335b74194/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-19%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.12.47.png" alt=""></p>
<p>⇒ 이처럼 공통된 로직, 코드들을 모듈화하여 묶어서 개발할 수 있따.</p>
<h3 id="aop의-주요-개념">AOP의 주요 개념</h3>
<ul>
<li>Aspect와 Target<ul>
<li>Aspect는 모듈을 묶음화 한 것</li>
<li>Target은 적용 대상</li>
</ul>
</li>
<li>Advice<ul>
<li>모듈이 해야할 일들</li>
</ul>
</li>
<li>Joint point와 Pointcut<ul>
<li>joint point는 합류점을 의미한다. 어떤 메소드, 클래스, filed값 접근시점, 실행시점에 이 Advice를 끼워 넣어라</li>
<li>Pointcut은 이 모듈을 어디에 적용하는 지를 의미한다.</li>
</ul>
</li>
</ul>
<h3 id="aop의-구현체">AOP의 구현체</h3>
<ul>
<li>AOP는 꼭 Spring, Java에만 국한된 것이 아니다.</li>
<li>참고 : <a href="https://ko.wikipedia.org/wiki/%EA%B4%80%EC%A0%90_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D">https://ko.wikipedia.org/wiki/관점_지향_프로그래밍</a></li>
<li>AspectJ는 자바, 스프링은 Spring AOP(극한적인 기능만 존재)</li>
</ul>
<h3 id="aop-적용-방법">AOP 적용 방법</h3>
<ul>
<li>컴파일 시점<ul>
<li>Java를 .class 파일로 컴파일하는 시점에 조작된 바이트 코드를 실행시킨다. 로드, 런타임은 따로 안들지만, 별도의 컴파일 과정이 필요하다.</li>
</ul>
</li>
<li>로드 타임<ul>
<li>클래스 파일을 로딩하는 시점에 weaving, load time weaving</li>
<li>클래스 로딩 시간 증가, load time weaver를 Java agent 에 설정해줘야 함</li>
<li>다양한 Aspect 사용 가능</li>
</ul>
</li>
<li>런타임<ul>
<li>Proxy 객체로 감싸는 것, 최초의 Bean 설정 시간이 증가한다.</li>
</ul>
</li>
</ul>
<p>⇒ 컴파일 시점, 로드타임에 AOP 적용 방법은 주로 Aspect에서 사용</p>
<p>⇒ 런타임 시점에 AOP 적용은 주로 Spring AOP에서 사용한다.</p>
</br>
---


<h1 id="스프링-aop--프록시-기반-aop">스프링 AOP : 프록시 기반 AOP</h1>
<h3 id="스프링-aop-특징">스프링 AOP 특징</h3>
<ul>
<li>Proxy 기반의 AOP 구현체</li>
<li>Spring Bean에만 AOP를 적용할 수 있다.</li>
<li>모든 AOP 기능을 제공하는 것이 목적이 아니라, 스프링 IoC와 연동해서 Enterprise Application에서 가장 흔한 문제에 대한 해결책을 제공하는 것이 목적이다.</li>
</ul>
<h3 id="proxy-pattern">Proxy Pattern</h3>
<ul>
<li>프록시 패턴을 사용하는 이유<ul>
<li>기존 코드 변경없이 접근 제어, 부가기능을 추가하기 위해서</li>
<li>클라이언트는 기존 코드를 건드리지 않고, 인터페이스 타입의 프록시 객체를 사용할 수 있다.</li>
</ul>
</li>
</ul>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/4d867c9c-0880-4dc3-a520-ba55736048cd/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-19%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.07.29.png" alt=""></p>
<ul>
<li><p>프록시 패턴의 문제점</p>
<ul>
<li><p>매번 프록시 캘르스를 작성해야 한다.</p>
</li>
<li><p>여러 클래스, 여러 메소드에 적용하려면 객체들과의 관계도가 복잡해진다.</p>
<p>ex ) </p>
<pre><code class="language-java">
// interface
public interfaceEventService {

  voidcreateEvent();

  voidpublishEvent();

  voiddeleteEvent();
}

// Proxy
@Primary
@Service
public class ProxySimpleEventService implements EventService{

  @Autowired
  SimpleEventService simpleEventService;

  @Override
  public void createEvent() {
      long begin = System.currentTimeMillis();
      simpleEventService.createEvent();
      System.out.println(System.currentTimeMillis()-begin);
  }

  @Override
  public void publishEvent() {
      long begin = System.currentTimeMillis();
      simpleEventService.publishEvent();
      System.out.println(System.currentTimeMillis()-begin);

  }

  @Override
  public void deleteEvent() {
      simpleEventService.deleteEvent();
  }
}

// Real Subject
@Service
public class SimpleEventService implements EventService{

  @Override
  public void createEvent() {
      long begin = System.currentTimeMillis();
      try {
          Thread.sleep(1000);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      System.out.println(&quot;create an evnet&quot;);

      // Crosscutting concern : 각 메소드 마다 공통된 모듈 
      System.out.println(System.currentTimeMillis()-begin);
  }

  @Override
  public void publishEvent() {
      long begin = System.currentTimeMillis();

      try {
          Thread.sleep(2000);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      System.out.println(&quot;Published an event&quot;);

      System.out.println(System.currentTimeMillis()-begin);
  }

  public void deleteEvent(){
      System.out.println(&quot;Delete event&quot;);
  }
}</code></pre>
</li>
</ul>
</li>
</ul>
<p>⇒ 그래서 Spring AOP 등장</p>
<h3 id="spring-aop">Spring AOP</h3>
<ul>
<li>스프링 IoC 컨테이너가 제공하는 기반 시설과 Dynamic 프록시를 사용해서 여러 복잡한 문제를 해결한다.</li>
<li>Dynamic Proxy : 동적으로 프록시 객체를 생성하는 방법<ul>
<li>자바가 제공하는 방법은 인터페이스 기반 프록시 생성</li>
<li>CGlib은 클래스 기반 프록시도 지원</li>
</ul>
</li>
<li>스프링 IoC : 기존 빈을 대체하는 동적 프록시 빈을 만들어서 등록시켜준다.<ul>
<li>클라이언트 입장에서 코드 변경이 없다.</li>
<li>AbstractAutoProxyCreator는 BeanPostProcessor를 상속받는다.</li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.html">https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.html</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html">https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html</a></li>
</ul>
</li>
</ul>
</br>
---

<h1 id="스프링-aop--aop">스프링 AOP : @AOP</h1>
<p>애노테이션 기반의 스프링 @AOP</p>
<p>사용하기 위해서는 spring-boot-starter-aop 의존성 필요</p>
<pre><code class="language-xml">&lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-aop&lt;/artifactId&gt;
 &lt;/dependency&gt;</code></pre>
<h3 id="aspect-정의">Aspect 정의</h3>
<ul>
<li>Aspect에서는 Advice(해야할 일)과 Point-cut(어디에 적용할 것인가, 적용시점)를 정의해야 한다.</li>
<li>Aspect로 사용할 클래스는 Bean으로 등록되어야 한다.</li>
<li>@Around 애노테이션을 이용해서 Point-cut을 적용할 수 있다.</li>
</ul>
<pre><code class="language-java">@Aspect
@Component
public class PerfAspect {

    // createEvent, publlishEvent 메소드에 적용될 aop 이다.
    // @Around에 Pointcut을 정의할 수 있다. 즉 적용시점을 정의할 수 있다.
   // @Around(&quot;execution(* me.jhjhj..*.EventService.*(..))&quot;)
   // @Around(&quot;bean(simpleEventService)&quot;)
    @Around(&quot;@annotation(PerfLogging)&quot;)
    public Object logPerf(ProceedingJoinPoint pjp) throws Throwable{
        long begin = System.currentTimeMillis();
        Object retVal = pjp.proceed();
        System.out.println(System.currentTimeMillis() - begin);
        return retVal;
    }

    // 메소드 실행 이전에
    @Before(&quot;bean(simpleEventService)&quot;)
    public void hello(){
        System.out.println(&quot;hello&quot;);
    }
}

// RetentionPolicy에도 다양한 스코프가 존재, 
// RetentionPolicy.CLASS : 이 애노테이션을 class 파일까지만 적용하겠다.
@Documented
@Retention(RetentionPolicy.CLASS)
public @interface PerfLogging {
}</code></pre>
<h3 id="point-cut-정의">Point-Cut 정의</h3>
<ul>
<li>@Pointcut(표현식)</li>
<li>주요 표현식<ul>
<li>execution : 패키지 경로 (패키지 경로에 해당하는 대상들 모두가 aop 에 적용)</li>
<li>@annotation : 별도의 애노테이션 정의 후, aop로 사용하고 싶은 메소드, 클래스만 적용이 가능하다.</li>
<li>bean : bean 이름 기재</li>
</ul>
</li>
<li>Point-Cut 조합<ul>
<li>&amp;&amp;, ||, !</li>
</ul>
</li>
</ul>
<h3 id="advice-정의">Advice 정의</h3>
<ul>
<li>@Before</li>
<li>@AfterReturning</li>
<li>@AfterThrowing</li>
<li>@Around</li>
</ul>
<p>✅ Spring AOP 공식 레퍼런스 : <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts">https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts</a></p>
</br>
---
출처 : [[인프런 강의] 백기선 - 스프링 프레임워크 핵심기술](https://www.inflearn.com/course/spring-framework_core)]]></description>
        </item>
        <item>
            <title><![CDATA[Section 4. SpEL (Spring Expression Language)]]></title>
            <link>https://velog.io/@dev_jhjhj/Section-4.-SpEL-Spring-Expression-Language</link>
            <guid>https://velog.io/@dev_jhjhj/Section-4.-SpEL-Spring-Expression-Language</guid>
            <pubDate>Wed, 16 Feb 2022 09:49:07 GMT</pubDate>
            <description><![CDATA[<h1 id="spring-expression-language란"><a href="https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions">Spring Expression Language</a>란?</h1>
<ul>
<li>객체의 그래프를 조작하고 조회하는 기능을 제공</li>
<li>문법은 EL (<a href="https://docs.oracle.com/javaee/5/tutorial/doc/bnahq.html">Unified EL</a>)과 비슷하지만, 메소드 호출 및 문자열 템플릿 기능 등 추가적인 기능을 제공한다.</li>
<li>OGNL, MVEL, JBoss EL 등의 Java에서 사용할 수 있는 여러 EL이 있지만, SpEL은 모든 Spring 프로젝트에 전반에 걸쳐 사용할 EL로 만들어졌다.</li>
<li>Spring 3.0 부터 지원한다.</li>
</ul>
<h3 id="spel의-구성">SpEL의 구성</h3>
<ul>
<li><p>ExpressionParser parser = new SpelExpressionParser()</p>
<ul>
<li><p>ExpressionParser 인터페이스는 문자열 표현을 파싱한다ㅏ.</p>
<pre><code class="language-java">ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(&quot;&#39;Hello World&#39;&quot;);
String message = (String) exp.getValue();</code></pre>
</li>
</ul>
</li>
<li><p>Expression expression = parser.parseExpression(“SpEL 표현식”)</p>
</li>
<li><p>StandardEvaluationContext context = new StandardEvaluationContext(bean)</p>
<ul>
<li>EvaluationContext 인터페이스는 프로퍼티, 메서드, 필드를 처리하고 타입변환을 수행하는 표현식을 평가할 때 EvaluationContext 인터페이스를 사용한다.</li>
<li>StandardEvaluationContext는 setRootObject() 메서드나 생성자에 루트객체를 전달해서 평가에 사용할 루트객체를 지정하는 곳이다.</li>
</ul>
</li>
<li><p>String value = expression.getvalue(context, String.class)</p>
</li>
</ul>
<h3 id="spel-문법">SpEL 문법</h3>
<ul>
<li><code>#{표현식}</code></li>
<li><code>${”프로퍼티”}</code></li>
<li>표현식 안에 프로퍼티를 쓸 수 있지만, 반대는 안됨<ul>
<li><code>#{${my.data(프로퍼티명) + 1}}</code></li>
</ul>
</li>
<li>레퍼런스에 더 자세히 기재되어 있음</li>
</ul>
<pre><code class="language-java">// SpEL #, 표현식을 참고하는 방법
    @Value(&quot;#{1 + 1}&quot;)
    int value;

    @Value(&quot;#{&#39;hello&#39; + &#39;worlld&#39;}&quot;)
    String greeting;

    @Value(&quot;#{1 eq 1}&quot;)
    boolean trueOrFalse;

    @Value(&quot;test&quot;)
    String test;

    // property를 참고하는 방법
    @Value(&quot;${my.value}&quot;)
    int myValue;

    @Value(&quot;#{${my.value} eq 100}&quot;)
    boolean isMyValue100;

    @Value(&quot;#{sample.data}&quot;)
    int sampleData;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(&quot;value = &quot; + value);
        System.out.println(&quot;greeting = &quot; + greeting);
        System.out.println(&quot;trueOrFalse = &quot; + trueOrFalse);
        System.out.println(&quot;test = &quot; + test);
        System.out.println(&quot;myValue = &quot; + myValue);
        System.out.println(&quot;isMyValue100 = &quot; + isMyValue100);
        System.out.println(&quot;sampleData = &quot; + sampleData);

        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(&quot;2 + 100&quot;);
        Integer value = expression.getValue(Integer.class);
        System.out.println(&quot;value = &quot; + value);

    }</code></pre>
<h3 id="사용하는-곳">사용하는 곳</h3>
<ul>
<li><code>@Value</code>  애노테이션</li>
<li>@ConditionalOnExpression 애노테이션</li>
<li><a href="https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html">Spring Security</a><ul>
<li>메소드 시큐리티, @PreAuthorize, @PostAuthorize, @PreFilter, @PostFillter</li>
<li>XML Intercepter URL 설정</li>
</ul>
</li>
<li><a href="https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions">Spring Data</a><ul>
<li>@Query 애노테이션</li>
</ul>
</li>
<li><a href="https://blog.outsider.ne.kr/997">Thymeleaf</a></li>
</ul>
<hr>
<p>출처 : <a href="https://www.inflearn.com/course/spring-framework_core">[인프런 강의] 백기선 - 스프링 프레임워크 핵심기술</a>
참고 : <a href="https://blog.outsider.ne.kr/997">https://blog.outsider.ne.kr/997</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Section 3. 데이터 바인딩]]></title>
            <link>https://velog.io/@dev_jhjhj/Section-3.-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%94%EC%9D%B8%EB%94%A9</link>
            <guid>https://velog.io/@dev_jhjhj/Section-3.-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%94%EC%9D%B8%EB%94%A9</guid>
            <pubDate>Tue, 15 Feb 2022 11:55:39 GMT</pubDate>
            <description><![CDATA[<h1 id="1-데이터-바인딩-추상화--propertyeditor">1. 데이터 바인딩 추상화 : PropertyEditor</h1>
<p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/DataBinder.html">org.springframework.validation.Databinder</a></p>
<h3 id="데이터-바인딩이란-">데이터 바인딩이란 ?</h3>
<p>Property 값을 타겟 객체에 설장하는 기능이다.</p>
<p>사용자의 입력 값을 Model 값에 동적으로 변환해서 넣어주는 기능이다.</p>
<h3 id="사용하는-이유">사용하는 이유</h3>
<p>사용자가 입력하는 값은 대부분 문자열, 숫자 일 것이다. 그 값을 객체가 가지고 있는 속성 타입이나 도메인 타입으로 변환해서 넣어주기 위해 사용한다.</p>
<h3 id="propertyeditor">PropertyEditor</h3>
<ul>
<li>Spring 3.0 이전까지 Databind가 변환 작업을 사용하던 interface (고전적인 방법이다.)</li>
<li>Thread-Safe 하지 않다.<ul>
<li>statefulll 하다.</li>
<li>상태 정보를 저장하고 있기 때문에 싱글톤 객체로 등록하여 사용하는 것은 금한다.</li>
<li>Object와 String 간의 변환만 가능, 제한적인 사용</li>
</ul>
</li>
</ul>
<pre><code class="language-java">
@RestController
public class EventController {

    @InitBinder
    public void init(WebDataBinder webDataBinder){
        webDataBinder.registerCustomEditor(Event.class, new EventEditor());
    }

    @GetMapping(&quot;/event/{event}&quot;)
    public String getEvent(@PathVariable Event event){
        System.out.println(&quot;event = &quot; + event);
        return event.getId().toString();
    }
}

// Thread-Safe 하지 않는다.
// 절대로 proportyEditor 절대로 싱글톤 빈으로 등록해서 사용하면 안됨
// thread-scope의 빈 일 경우에는 그나마 가능
public class EventEditor extends PropertyEditorSupport {

    @Override
    public String getAsText() {
        Event event = (Event) getValue();
        return event.getId().toString();
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        setValue(new Event(Integer.parseInt(text)));
    }
}
</code></pre>
<hr>
</br>

<h1 id="2-데이터-바인딩-추상화--converter와-formatter">2. 데이터 바인딩 추상화 : Converter와 Formatter</h1>
<h3 id="converter">Converter</h3>
<p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/converter/Converter.html">https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/converter/Converter.html</a></p>
<ul>
<li>Source 타입을 Target 클래스로 변환할 수 있는 인터페이스</li>
<li>상태 정보를 저장하지 않음<ul>
<li>stateless, Thread-Safe 하다.</li>
</ul>
</li>
<li>ConverterRegistry에 등록하여 사용한다.</li>
</ul>
<pre><code class="language-java">public class EventConverter {

    @Component
    public static class StringToEventConverter implements Converter&lt;String, Event&gt; {
        @Override
        public Event convert(String source) {
            return new Event(Integer.parseInt(source));
        }
    }

    @Component
    public static class EventtoStringConverter implements Converter&lt;Event, String&gt; {

        @Override
        public String convert(Event source) {
            return source.getId().toString();
        }
    }

}</code></pre>
<h3 id="formatter">Formatter</h3>
<p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/format/Formatter.html">https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/format/Formatter.html</a></p>
<ul>
<li>PropertyEditor와 유사</li>
<li>Object와 String 간의 변환을 담당하는 인터페이스</li>
<li>문자열을 Locale에 따라 다국화(i18n) 기능도 제공</li>
<li>FormatterRegistry에 등록하여 사용한다.</li>
<li>👉  보통 Web과 관련된 데이터 바인딩을 진행할 경우, Formatter 사용을 추천 (보통 String to Object 이기 때문)</li>
</ul>
<pre><code class="language-java">@Component
public class EventFormatter implements Formatter&lt;Event&gt; {

    @Override
    public Event parse(String text, Locale locale) throws ParseException {
        return new Event(Integer.parseInt(text));
    }

    @Override
    public String print(Event object, Locale locale) {

        return object.getId().toString();
    }
}</code></pre>
<h3 id="conversionservice">ConversionService</h3>
<p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/ConversionService.html">https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/ConversionService.html</a></p>
<ul>
<li><code>ConversionService</code>에서 <code>Converter</code>와 <code>Formatter</code>를 등록해준다.</li>
<li>Spring MVC, Bean 설정, SpEL에 사용한다.</li>
<li><code>DefaultFormattingConversionService</code> 클래스에서 <code>FormatterRegistry</code>, <code>ConversionService</code> 인터페이스를 다 구현한다.</li>
<li>상속 구조</li>
</ul>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/8a8d1492-65e0-48af-b58b-c0b6ec752c88/defaultformatter.png" alt=""></p>
<h3 id="springboot에서는">SpringBoot에서는...</h3>
<ul>
<li><p>웹 애플리케이션인 경우에는 <code>DefaultFormattingVonversionService</code> 를 상속하여 만든 <code>WebConversionService</code>를 빈으로 등록해준다.</p>
</li>
<li><p>Formatter와 Converter 로 사용할 클래스를 빈으로 등록하면, 자동으로 Formatter와 Converter  역할로 등록해준다.</p>
</li>
<li><p>SpringBoot에 등록되어 있는 Formatter와 Converter 를 확인하는 방법</p>
<pre><code class="language-java">  @Component
  public class AppRunner implements ApplicationRunner {

      @Autowired
      private ConversionService conversionService;

      @Override
      public void run(ApplicationArguments args) throws Exception {
          System.out.println(conversionService);
      }

  }</code></pre>
</li>
</ul>
<h3 id="테스트-코드">테스트 코드</h3>
<ul>
<li>MockMvcTest 사용 시 기본적인 spring web 관련 빈들만 생성하여 테스트를 진행한다.</li>
<li>따라서 <code>@WebMvcTest({EventFormatter.class, EventController.class})</code> 와 같이 Bean으로 등록된 클래스들을 추가하여 테스트 할 수 있다.</li>
</ul>
<hr>
<p>출처 : <a href="https://www.inflearn.com/course/spring-framework_core">[인프런 강의] 백기선 - 스프링 프레임워크 핵심기술</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Section 2. Resource / Validation]]></title>
            <link>https://velog.io/@dev_jhjhj/Section-2.-Resource-Validation</link>
            <guid>https://velog.io/@dev_jhjhj/Section-2.-Resource-Validation</guid>
            <pubDate>Sun, 13 Feb 2022 07:51:34 GMT</pubDate>
            <description><![CDATA[<h1 id="1-resource-추상화">1. Resource 추상화</h1>
<p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/Resource.html">org.springframework.core.io.Resource</a></p>
<h3 id="특징">특징</h3>
<ul>
<li><code>java.net.URL</code>을 추상화했다. → java.net.URL을 Spring core io Resource로 감싼 것</li>
</ul>
<h3 id="추상화-한-이유는-">추상화 한 이유는 ?</h3>
<ul>
<li>기존 <code>java.net.URL</code> 에는 classpath를 기준으로 리소스를 읽어오는 기능이 없었다.</li>
<li>기존 <code>java.net.URL</code> 에는 ServletContext를 기분으로 상대 경로를 읽어오는 기능이 없었다.</li>
</ul>
<h3 id="구현체">구현체</h3>
<ul>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/AbstractFileResolvingResource.html">AbstractFileResolvingResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/AbstractResource.html">AbstractResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/ByteArrayResource.html">ByteArrayResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/ClassPathResource.html">ClassPathResource</a> : 지원하는 prefix : classpath</li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/DefaultResourceLoader.ClassPathContextResource.html">DefaultResourceLoader.ClassPathContextResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/DescriptiveResource.html">DescriptiveResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/FileSystemResource.html">FileSystemResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/FileUrlResource.html">FileUrlResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/InputStreamResource.html">InputStreamResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/PathResource.html">PathResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/support/ServletContextResource.html">ServletContextResource</a> : 웹 애플리케이션 루트에서 상대 경로로 리소스를 찾는다. (가장 많이 사용)</li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/resource/TransformedResource.html">TransformedResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/resource/TransformedResource.html">TransformedResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/UrlResource.html">UrlResource</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/VfsResource.html">VfsResource</a></li>
</ul>
<h3 id="소스-읽어오기">소스 읽어오기</h3>
<p>Resource의 타입은 location 문자열과 ApplicationContext 타입에 따라 결정된다.</p>
<ul>
<li><p>예시</p>
<ul>
<li>ClassPathXmlApplicationContext -&gt; ClassPathResource</li>
<li>FileSystemXmlApplicationContext -&gt; FileSystemResource</li>
<li>WebApplicationContext -&gt; ServletContextResource</li>
</ul>
</li>
<li><p>리소스 로더에 <code>prefix classpath:/</code> 가 있을 때</p>
<ul>
<li><p>리소스 로더의 타입은 <code>AnnotationConfigServletWebServerApplicationContext</code></p>
</li>
<li><p>해당 리소스 타입은 <code>ClassPathResource</code></p>
<pre><code class="language-java">  @Autowired
      ApplicationContext resourceLoader;

      @Override
      public void run(ApplicationArguments args) throws Exception {
                  // 리소스 로더 타입은 class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
          System.out.println(&quot;resourceLoader.getClass() : &quot; + resourceLoader.getClass());

          Resource resource = resourceLoader.getResource(&quot;classpath:/test.txt&quot;);
          // 리소스 타입은 class org.springframework.core.io.ClassPathResource
                  System.out.println(&quot;resource.getClass() = &quot; + resource.getClass());
      }</code></pre>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>리소스 로더를 prefix 없이 받을 때</p>
<ul>
<li><p>리소스 로더의 타입은 <code>AnnotationConfigServletWebServerApplicationContext</code></p>
</li>
<li><p>해당 리소스 타입은 <code>ServletContextResource</code> → 디폴트로 ServletContextResource로 받는다.</p>
<pre><code class="language-java">@Autowired
  ApplicationContext resourceLoader;

  @Override
  public void run(ApplicationArguments args) throws Exception {
              // 리소스 로더 타입은 class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
      System.out.println(&quot;resourceLoader.getClass() : &quot; + resourceLoader.getClass());

      Resource resource = resourceLoader.getResource(&quot;test.txt&quot;);
      // 리소스 타입은 class org.springframework.core.io.ServletContextResource
              System.out.println(&quot;resource.getClass() = &quot; + resource.getClass());
  }</code></pre>
</li>
</ul>
</li>
<li><p>따라서 Resource의 경로를 작성할 때는 Prefix를 사용하는 것을 권장한다. (명시적이고 가시성 좋음)</p>
</li>
</ul>
<p>📕 <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-as-dependencies">https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-as-dependencies</a></p>
<h2 id="brbr"></br><br></h2>
<h1 id="2-validation-추상화">2. Validation 추상화</h1>
<p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/Validator.html">org.springframework.validation.Validator</a></p>
<p>객체를 검증하기 위해 사용하는 인터페이스 이다.</p>
<p>애노테이션을 사용하여 객체를 검증한다.</p>
<h3 id="인터페이스">인터페이스</h3>
<ul>
<li><p><code>boolean* supports(Class&lt;?&gt; clazz)</code> : 어떤 객체를 검증할 것인지 정의</p>
</li>
<li><p><code>void validate(Object target, Errors errors)</code> : 실제 검증 로직을 구현</p>
<ul>
<li>ValidationUtils 사용하면 편함</li>
</ul>
</li>
<li><p>Springboot 2.0.5 이상 버전</p>
<ul>
<li>LocalValidatorFactoryBean 으로 자동 등록</li>
<li>참고<ul>
<li><a href="https://beanvalidation.org/">https://beanvalidation.org/</a></li>
<li><a href="https://docs.jboss.org/hibernate/beanvalidation/spec/1.1/api/">https://docs.jboss.org/hibernate/beanvalidation/spec/1.1/api/</a></li>
</ul>
</li>
</ul>
<h2 id="brbr-1"></br><br>       </h2>
</li>
</ul>
<p>출처 : <a href="https://www.inflearn.com/course/spring-framework_core">[인프런 강의] 백기선 - 스프링 프레임워크 핵심기술</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Section 1. IoC 컨테이너와 빈 (2)]]></title>
            <link>https://velog.io/@dev_jhjhj/Section-1.-IoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EB%B9%88-2</link>
            <guid>https://velog.io/@dev_jhjhj/Section-1.-IoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EB%B9%88-2</guid>
            <pubDate>Sun, 13 Feb 2022 06:17:17 GMT</pubDate>
            <description><![CDATA[<h1 id="6-environment-1부-프로파일profile">6. Environment 1부 프로파일(Profile)</h1>
<p>ApplicationContext는 <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/env/EnvironmentCapable.html">EnvironmentCapable</a>이라는 인터페이스를 상속받는다.</p>
<p>getEnvironment() 함수로 컴포넌트와 연관된 <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/env/Environment.html">Environment</a> 를 리턴한다.</p>
<h3 id="profile-이란-">Profile 이란 ?</h3>
<ul>
<li>Bean들의 그룹</li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/env/Environment.html">Environment</a>  의 역할은 Active할 Profile을 확인하고 설정한다.</li>
</ul>
<h3 id="profile-usecase">Profile UseCase</h3>
<ul>
<li>Test 환경에서 A라는 Bean을 사용하지만, Release 환경에서는 B 라는 Bean을 사용하고 싶을 때</li>
<li>사용 환경에 따라 Bean의 등록 및 사용 유무를 설정하고 싶을 때</li>
</ul>
<h3 id="profile-정의하기">Profile 정의하기</h3>
<ul>
<li>Class에 정의<ul>
<li>@Configuration @Profile(”test”)</li>
<li>@Component @Profile(”test”)</li>
</ul>
</li>
<li>메소드에 정의<ul>
<li>@Bean @Profile(”test”)</li>
</ul>
</li>
</ul>
<h3 id="profile-설정하기">Profile 설정하기</h3>
<ul>
<li>VM Option에 설정 : -DSpring.profiles.active=”test”</li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/ActiveProfiles.html">@ActiveProfiles</a> (테스트용)</li>
</ul>
<h3 id="profile-표현식">Profile 표현식</h3>
<p>Profile 표현식에 논리연산자를 사용할 수 있다.</p>
<p>ex ) @Profile(”!prod”) → prod 환경이 아닐 때 bean 사용</p>
<ul>
<li>! (not)</li>
<li>&amp; (and)</li>
<li>|  (or)</li>
</ul>
<hr>
<h1 id="7-environment-2부-프로퍼티property">7. Environment 2부. 프로퍼티(Property)</h1>
<h3 id="property-란">Property 란?</h3>
<ul>
<li>개발 환경 구성에 필요한 설정값들, 다양한 방법으로 정의가 가능</li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/env/Environment.html">Environment</a>  의 역할은 Property 소스 설정 및 Property 값 가져오기</li>
</ul>
<h3 id="property의-우선순위">Property의 우선순위</h3>
<ul>
<li>StandardServletEnvironment의 우선순위<ul>
<li>ServeltConfig의 매개변수</li>
<li>ServletContext의 매개변수</li>
<li>JNDI (java.comp/env)</li>
<li>JVM 시스템 Property(-Dkey=”value”)</li>
<li>JVM 시스템 환경변수 (운영체제 환경변수)</li>
</ul>
</li>
</ul>
<h3 id="propertysource">@PropertySource</h3>
<ul>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/env/Environment.html">Environment</a> 를 통해 Property를 추가하는 방법이다.</li>
<li>사용 방법 : <code>@PropertySource(&quot;classpath:/com/jhj/app.properties&quot;)</code><ul>
<li>소스코드 상 Config 파일에 애노테이션을 추가</li>
<li>property 파일에 기재된 설정 값들은 <code>@Value(${...})</code> 형태로 주입받아 사용이 가능하다. → BeanPostProcessor에 의해서 처리되기 때문에BeanPostProcessor나 BeanFactoryPostProcessor 내에서는 사용할 수 없다.</li>
</ul>
</li>
</ul>
<hr>
<h1 id="8-messagesource">8. MessageSource</h1>
<p>국제화 기능(i18n) 기능을 제공하는 인터페이스 → 메세지를 다국화 하는 기능</p>
<ul>
<li><p>ApplicationContext는 MessageSource를 상속받는다.</p>
</li>
<li><p>SpringBoot를 사용하면, 다른 설정 없이 <a href="http://messages.properties">messages.properties</a> 파일로 메세지 다국화 기능 사용이 가능하다.</p>
<ul>
<li><p>messages.properties</p>
</li>
<li><p>messages_ko_KR.preporties</p>
</li>
<li><p>messages_en_US.properties</p>
</li>
<li><p>Local.getDefault() : 운영체제의 기본으로 설정되니 국가의 값을 가져옴 (ref. <a href="https://docs.oracle.com/javase/7/docs/api/java/util/Locale.html#getDefault">ttps://docs.oracle.com/javase/7/docs/api/java/util/Locale.html#getDefault</a>)</p>
<pre><code class="language-java">  messageSource.getMessage(&quot;greeting&quot;, new String[]{&quot;jhjhj&quot;}, Locale.getDefault());
  messageSource.getMessage(&quot;greeting&quot;, new String[]{&quot;jhjhj&quot;}, Locale.KOREA);
  messageSource.getMessage(&quot;greeting&quot;, new String[]{&quot;jhjhj&quot;}, Locale.US);</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="reloading-기능이-있는-메세지-소스">Reloading 기능이 있는 메세지 소스</h3>
<p>캐시 설정을 이용해서 메세지 reload가 가능하다.</p>
<pre><code class="language-java">@Bean
    public MessageSource messageSource(){
        var messageSource =  new ReloadableResourceBundleMessageSource();
        messageSource.setBasename(&quot;classpath:/messages_ko_KR&quot;);
        messageSource.setDefaultEncoding(&quot;UTF-8&quot;);
        messageSource.setCacheSeconds(3);

        return  messageSource;
    }</code></pre>
<hr>
<h1 id="9-applicationeventpublisher">9. ApplicationEventPublisher</h1>
<p>ApplicationContext extend <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationEventPublisher.html">ApplicationEventPublisher</a></p>
<p>이벤트 프로그래임에 필요한 Interface를 제공한다. <a href="https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4">Observer Pattern (옵저버 패턴)</a> 의 구현체이다.</p>
<h3 id="event-만들기">Event 만들기</h3>
<ul>
<li>이벤트로 만들 클래스는 ApplicationEvnet 클래스( <em>`</em>org.springframework.context.ApplicationEvent`)를 상속받는다.</li>
<li><strong>Spring 4.2 버전 이후부터 이 클래스를 상속받지 않아도</strong> 이벤트로 사용할 수 있다. → 이벤트 핸들러로 사용될 클래스를 빈으로 등록하고 핸들러 메소드에 <code>@EventListener</code> 애노테이션을 붙이면 된다.</li>
</ul>
<h3 id="event-발생-시키는-방법">Event 발생 시키는 방법</h3>
<pre><code class="language-java">ApplicationEventPublisher.publishEvent(new Evnet);</code></pre>
<h3 id="event-처리하는-방법">Event 처리하는 방법</h3>
<ol>
<li>ApplicationListener&lt;이벤트&gt; 로 구현한 클래스를 생성 후, Bean으로 등록한다.</li>
<li>Spring 4.2 버전부터는 <code>@EventListener</code> 애노테이션을 메소드에 사용해서 이벤트 리스너 메소드로 사용할 수 있다.</li>
<li>기본적으로 동기 (synchronized) 이다. 비동기로 사용하고 싶다면 <code>@Async</code>  애노테이션을 클래스에 사용하면 된다.</li>
<li>이벤트 발생 순서를 정하고 싶다면, 이벤트 리스너에 <code>@Order</code> 애노테이션을 사용하고, 우선순위에 따라 옵션값을 설정하면 된다.</li>
</ol>
<h3 id="스프링이-기본으로-제공하는-event">스프링이 기본으로 제공하는 Event</h3>
<ul>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/ContextClosedEvent.html">ContextClosedEvent</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/ContextRefreshedEvent.html">ContextRefreshedEvent</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/ContextStartedEvent.html">ContextStartedEvent</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/ContextStoppedEvent.html">ContextStoppedEvent</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/support/RequestHandledEvent.html">RequestHandledEvent</a></li>
</ul>
<hr>
<h1 id="10-resourceloader">10. ResourceLoader</h1>
<p>Resource를 읽어오는 기능을 제공하는 Interface 이다.</p>
<p>ApplicationContext가 ResourceLoader를 상속받는다.</p>
<ul>
<li>ApplicationContext extends <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/ResourceLoader.html">ResourceLoader</a></li>
</ul>
<h3 id="resource-읽어오는-방법">Resource 읽어오는 방법</h3>
<ul>
<li>파일 시스템에서 읽어오기</li>
<li>classpath에서 읽어오기 (/target/classes가 root)</li>
<li>URL 로 읽어오기</li>
<li>상대/절대 경로로 읽어오기</li>
</ul>
<p>→ 자세한 방법은 공식 레퍼런스 참조</p>
<hr>
<p></br><br></p>
<p>출처 : <a href="https://www.inflearn.com/course/spring-framework_core">[인프런 강의] 백기선 - 스프링 프레임워크 핵심기술</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Section 1. IoC 컨테이너와 빈 (1)]]></title>
            <link>https://velog.io/@dev_jhjhj/Section-1.-IoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EB%B9%88-1</link>
            <guid>https://velog.io/@dev_jhjhj/Section-1.-IoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EB%B9%88-1</guid>
            <pubDate>Sat, 12 Feb 2022 09:04:08 GMT</pubDate>
            <description><![CDATA[<h1 id="1-스프링-ioc-컨테이너와-빈">1. 스프링 IoC 컨테이너와 빈</h1>
<h3 id="inversion-of-control-제어의-역전">Inversion Of Control (제어의 역전)</h3>
<ul>
<li>객체에 대한 제어 흐름을 직접 제어하는 게 아니라, 외부에서 관리하는 것 
</br> → 스프링 IoC 컨테이너를 사용 가능 (꼭 Spring 에서만 사용되는 것이 아니라, 일반 POJO에서도 코드로 구현 가능하다.)</li>
</ul>
<h3 id="dependency-injection-의존-관계-주입">Dependency Injection (의존 관계 주입)</h3>
<ul>
<li>어떤 객체가 사용하는 의존 객체를 개발자가 직접 코드로 구현하는 것이 아니라, 외부에서 주입받아 사용하는 방법을 말한다. </br> 

</li>
</ul>
<h3 id="spring-ioc-컨테이너">Spring IoC 컨테이너</h3>
<ul>
<li><a href="https://docs.spring.io/spring-framework/docs/5.0.8.RELEASE/javadoc-api/org/springframework/beans/factory/BeanFactory.html">BeanFactory</a> : 스프링 컨테이너 가장 최상단의 인터페이스 (reference docs 참고)</li>
<li>Bean 설정 소스부터 Bean의 정의를 읽어들여 Bean을 구성하고 제공한다.    </br> </li>
</ul>
<h3 id="bean">Bean</h3>
<ul>
<li><p>Spring IoC 컨테이너가 관리하는 객체</p>
</li>
<li><p>Bean으로 등록이 되어 있어야 Spring IoC 컨테이너에 의해 의존성 주입이 가능하다.</p>
</li>
<li><p>Spring IoC 컨테이너가 Bean의 LifeCircle 관리</p>
</li>
<li><p>기본적인 Scope은 싱글톤이다. (싱글톤 : 인스턴스 1개 / 프로토타입 : 매번 다른 인스턴스 생성)</p>
<p></br> → 기본적으로 싱글톤으로 생성되기 때문에 메모리 관점, 런타임시 성능 최적화에도 효율적임</p>
</br> 

</li>
</ul>
<h3 id="applicationcontext"><a href="https://docs.spring.io/spring-framework/docs/5.0.8.RELEASE/javadoc-api/org/springframework/context/ApplicationContext.html">ApplicationContext</a></h3>
<ul>
<li><p>BeanFactory 인터페이스를 상속 받는 인터페이스</p>
</li>
<li><p>가장 많이 사용하는 인터페이스</p>
</li>
<li><p>메시지 소스 처리 기능 (i18n, internationalization, 메세지 다국화)</p>
</li>
<li><p>이벤트 발행 기능</p>
</li>
<li><p>Resource 로딩 기능 등... reference docs 참고</p>
</li>
</ul>
<h3 id="🖊-참고">🖊 참고</h3>
<p>   <a href="https://docs.spring.io/spring-framework/docs/5.0.8.RELEASE/javadoc-api/org/springframework/beans/factory/BeanFactory.html">BeanFactory (Spring Framework 5.0.8.RELEASE API)</a>
   <a href="https://docs.spring.io/spring-framework/docs/5.0.8.RELEASE/javadoc-api/org/springframework/context/ApplicationContext.html">ApplicationContext (Spring Framework 5.0.8.RELEASE API)</a></p>
<hr>
<p></br><br></p>
<h1 id="2-applicationcontext와-다양한-빈-설정-방법">2. ApplicationContext와 다양한 빈 설정 방법</h1>
<h3 id="spring-ioc-컨테이너의-역할">Spring IoC 컨테이너의 역할</h3>
<ul>
<li><p>Bean Instance 생성</p>
</li>
<li><p>의존 관계 설정</p>
</li>
<li><p>Bean을 제공
</br></br> </p>
<h3 id="applicationcontext-설정-방법">ApplicationContext 설정 방법</h3>
</li>
<li><p>ClassPathXmlApplicationContext(XML) → 과거 방법</p>
<pre><code class="language-java">ApplicationContext context = new ClassPathXmlApplicationContext(&quot;application.xml&quot;);       // xml 기반으로 bean 설정</code></pre>
</li>
<li><p>AnnotationConfigApplicationContext(Java) → Spring 2.5 버전부터 annotation 기반</p>
</li>
<li><p>@Configuration 어노테이션을 통해, Config 파일 안에 있는 @Bean들을 빈을 등록</p>
<pre><code class="language-java">ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);   // java Config 파일을 기반으로 bean 설정</code></pre>
<p></br></br> </p>
<h3 id="springbootapplication">@SpringBootApplication</h3>
</li>
<li><p>해당 어노테이션이 붙어있는 클래스의 package를 기반으로 Bean 등록 → SpringBoot</p>
<pre><code class="language-java">@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan</code></pre>
<p></br></br> </p>
<h3 id="bean-설정">Bean 설정</h3>
</li>
<li><p>Bean 에 대한 명세 및 정의를 담고 있다.</p>
<ul>
<li>이름, 클래스, Scope, constructor argument, property 등
</br></br> <h3 id="componentscan">ComponentScan</h3>
</li>
</ul>
</li>
<li><p>설정 방법</p>
<ul>
<li>XML 설정 (과거) : context:component-scan</li>
<li>자바 설정 : @ComponentScan</li>
</ul>
</li>
</ul>
<hr>
<p></br><br></p>
<h1 id="3autowired">3.@Autowired</h1>
<p>@Autowired 애노테이션을 이용해서 필요한 의존 객체의 타입과 이름에 해당하는 빈을 찾아 주입한다.</p>
<p>required 옵션 : default가 true, false는 optional의 개념</p>
<p>사용할 수 있는 위치
</br></br></p>
<h4 id="constructor-spring-43-부터는-생략-가능">Constructor (Spring 4.3 부터는 생략 가능)</h4>
<pre><code class="language-java">@Autowired
    public BookService(BookRepository bookRepository){
        this.bookRepository = bookRepository;
    }</code></pre>
<h4 id="setter">Setter</h4>
<pre><code class="language-java">@Autowired(required = false)
    public void setBookRepository(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }</code></pre>
<h4 id="field">Field</h4>
<pre><code class="language-java">@Autowired(required = false)
    BookRepository bookRepository;</code></pre>
<hr>
<p></br></br></p>
<h3 id="같은-타입의-bean이-여러개-있다면-">같은 타입의 Bean이 여러개 있다면 ?</h3>
<p><code>Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed</code></p>
<ul>
<li><p>@Primary : Bean으로 만들고 싶은 클래스에 해당 애노테이션을 사용, (기본 값으로 사용하겠다는 의미)</p>
</li>
<li><p>@Qualifier : Bean으로 주입받는 곳에 해당 애노테이션과 Bean이름 주입 (첫글자가 소문자인 카멜 케이스 이름, Spring Container가 최초에 빈 등록할 때의 이름 형식임) → TypeSafe 하지 않음</p>
</li>
<li><p>해당 타입의 Bean 이름을 모두 주입 받기</p>
</li>
<li><p>인스턴스 이름으로 구분 (정말 비추)</p>
<pre><code class="language-java">  // BookRepository 인터페이스를 상속받는 여러 클래스 중
  // MyBookResitory.class를 사용하겠다.
  @Autowired
  BookRepository myBookResitory;    </code></pre>
<ul>
<li>Spring IoC의 최상단 인터페이스 BeanFactory의 LifeCycle을 보면 알 수 있다.</li>
<li>BookRepository가 myBookResitory를 사용할 것이라는 것은 Initialization 전에 해준다.
</br> ( ref. <code>postProcessBeforeInitialization</code> methods of BeanPostProcessors)</li>
</ul>
</li>
</ul>
<hr>
<p></br><br></p>
<h1 id="4-component와-컴포넌트-스캔">4. Component와 컴포넌트 스캔</h1>
<p>@ComponentScan은 Spring 3.1부터 적용이 되었다.
@ComponentScan은 <code>basePackages(String Type)</code> 패키지 이름으로 컴포넌트 스캔을 하는 데,
String값으로 패키지 이름을 받는 것은 TypeSafe하지 않다. <br>
그래서 TypeSafe한 방법으로 <code>basePackageClasses</code> 라는 옵션을 사용한다.
<code>basePackageClasses</code> 을 사용하면, 해당 옵션에 전달된 클래스가 있는 패키지를 기준으로 컴포넌트 스캔을 시작한다.
→ SpringBoot에서 @SpringBootApplication 애노테이션은 @ComponentScan을 포함하고 있다.<br>
따라서, @SpringBootApplication이 붙은 클래스의 Package와 다른 상속 Package에 있는 클래스들은 ComponetScan이 되지 않는다.<br></p>
<h4 id="컴포넌트-스캔-주요-기능">컴포넌트 스캔 주요 기능</h4>
<ul>
<li>스캔 위치를 설정한다.</li>
<li>filter : 어떤 애노테이션을 스캔할 지 or 스캔하지 않을 지<ul>
<li>excludeFilters → TypeExcludeFilter.class, AutoConfigurationExcludeFilter.class <br></li>
</ul>
</li>
</ul>
<h4 id="componentscan의-대상">ComponentScan의 대상</h4>
<ul>
<li>@Repository</li>
<li>@Service</li>
<li>@Controller</li>
<li>@Configuration <br></li>
</ul>
<h4 id="function을-이용한-bean-등록">function을 이용한 Bean 등록</h4>
<ul>
<li>Bean 등록 시, ComponentScan 에 해당되지 않는 빈들을 등록할 수 있다.</li>
<li>성능을 높일 수 있다. (하지만, 필요에 따라 사용해야 한다. 무조건 사용은 비추)</li>
</ul>
<pre><code class="language-java">
// MyBean.class / ComPonentScan 외에 있는 bean을 function을 이용해서 등록이 가능하다. 
public static void main(String[] args) {
    new SpringApplicationBuilder()
            .sources(Demospring51Application.class)
            .initializers((ApplicationContextInitializer&lt;GenericApplicationContext&gt;)
        applicationContext -&gt; {
            applicationContext.registerBean(MyBean.class);
    })
    .run(args);
}</code></pre>
<hr>
<p></br><br></p>
<h1 id="5-빈의-스코프">5. 빈의 스코프</h1>
<h3 id="scope">Scope</h3>
<h3 id="singleton">singleton</h3>
<ul>
<li>(Spring의 디폴트) 전체 application 내 해당 빈의 인스턴스가 오직 1개</li>
</ul>
<h3 id="prototype">prototype</h3>
<p>인스턴스를 여러개로 만들 수 있다.</p>
<ul>
<li>Request: request에 따라</li>
<li>Session : 세선에 따라</li>
<li>WebSocket : 웹 소켓에 따라</li>
</ul>
<h3 id="prototype의-bean이-singleton을-참조하면-">prototype의 bean이 singleton을 참조하면 ?</h3>
<p>참조하는 싱글톤은 늘 인스턴스가 1개이기 때문에 개발자의 의도대로 사용이 된다.</p>
<h3 id="singleton의-bean이-prototype의-bean을-참조하면-">Singleton의 bean이 prototype의 bean을 참조하면 ?</h3>
<ul>
<li>프로토타입의 빈이 변경이 안된다. → 싱글톤 빈이 최초 인스턴스화 될 때, 처음으로 만들어졌던 프로토타입의 인스턴스만을 참조하기 때문</li>
<li>업데이트 하려면 ?<ul>
<li><code>@Scope*(value = &quot;prototype&quot;, proxyMode = ScopedProxyMode.TARGET_CLASS)</code>  :  프로토타입 클래스의 선언부에 다음과 같이 설정한다.<ul>
<li>해당 클래스를 Proxy로 감싸라. → 싱글톤 객체가 프로토타입 객체를 접근할 때 proxy를 거쳐서 참조하도록</li>
<li>기존 JVM에 있는 proxy는 interface 기준으로만 proxy를 만들어 주는 데, 이 방법을 사용하면 CGLib을 사용하여 class도 proxy가 되도록 만들어준다.</li>
</ul>
</li>
<li><code>ObjectProvider</code></li>
<li><code>Provider</code></li>
</ul>
</li>
</ul>
<hr>
<p></br><br></p>
<p>출처 : <a href="https://www.inflearn.com/course/spring-framework_core">[인프런 강의] 백기선 - 스프링 프레임워크 핵심기술</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[15. 케이스 클래스와 패턴 매치]]></title>
            <link>https://velog.io/@dev_jhjhj/15.-%EC%BC%80%EC%9D%B4%EC%8A%A4-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%ED%8C%A8%ED%84%B4-%EB%A7%A4%EC%B9%98</link>
            <guid>https://velog.io/@dev_jhjhj/15.-%EC%BC%80%EC%9D%B4%EC%8A%A4-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%ED%8C%A8%ED%84%B4-%EB%A7%A4%EC%B9%98</guid>
            <pubDate>Tue, 31 Aug 2021 12:59:27 GMT</pubDate>
            <description><![CDATA[<h1 id="간단한-예">간단한 예</h1>
<p>도메인 특화 언어에 산술 표현식을 다루는 라이브러리가 필요하다고 가정해보다</p>
<p>예시의 첫단계는 입력 데이터를 정의하는 것
문제를 간단히 하기 위해 변수와 숫자, 단항/이항 연산자로 이뤄진 산술식</p>
<pre><code class="language-java">abstract class Expr        
case class Var (name: String) extends Expr
case class Number (name: Double) extends Expr
case class UnOp (operatot: String, arg : Expr) extends Expr
case class BinOp (operatot: String, left: Expr, right: Expr) extends Expr</code></pre>
<ul>
<li>추상 기본 클래스 하나</li>
<li>그 클래스를 상속한 4개의 서브 클래스, 각 서브클래스는 식의 한종류를 표현</li>
<li>모든 클래스의 내용은 비어있음</li>
</ul>
<h2 id="케이스-클래스">케이스 클래스</h2>
<p>class 선언 앞에 case가 붙어있으면 케이스 클래스라고 부른다.</p>
<h3 id="case-클래스의-편리한-기능">case 클래스의 편리한 기능</h3>
<ol>
<li>컴파일러는 클래스 이름과 같은 이름의 팩토리 메소드를 추가<ul>
<li>팩토리 메소드는 중첩해서 객체를 생성할 때 좋음<ul>
<li>ex ) new Var(&quot;x&quot;) 대신 Var(&quot;x&quot;)를 사용해서 Var 객체 생성 가능</li>
</ul>
</li>
</ul>
</li>
</ol>
<pre><code class="language-shell">scala&gt; val v = Var(&quot;x&quot;)
v: Var = Var(x)</code></pre>
<ol start="2">
<li><p>케이스 클래스의 파라미터 목록에 있는 모든 인자에 암시적으로 val 접두사를 붙인다. 각 파라미터가 클래스의 필드도 된다.</p>
</li>
<li><p>스칼라 컴파일러는 케이스 클래스에 toString, hashCode, equals 메소드의 일반적인 구현을 추가</p>
</li>
<li><p>스칼라 컴파일러는 케이스 클래스에서 일부를 변경한 복사본을 생성하는 copy 메소드를 추가</p>
<ul>
<li>기존 인스턴스에 하나 이상의 속성을 바꾼 새로운 인스턴스를 생성할 때 유용 (copy 메소드 8.8절 참조)</li>
</ul>
</li>
</ol>
<h2 id="패턴-매치">패턴 매치</h2>
<pre><code class="language-java">// 패턴 매치를 사용하는 simplifyTop 함수
def simplifyTop(expr: Expr) : Expr = expr match {
    case UnOp(&quot;-&quot;, UnOp(&quot;-&quot;, e)) =&gt; e       // 부호를 두 번 반전
    case BinOp(&quot;+&quot;, e, Number(0)) =&gt; e      // 0을 더함
    case BinOp(&quot;*&quot;, e, Number(1)) =&gt; e      // 1을 곱함
}

def simplifyTop(expr: Expr): Expr

scala&gt; simplifyTop(UnOp(&quot;-&quot;, UnOp(&quot;-&quot;, Var(&quot;x&quot;))))
val res0: Expr = Var(x)</code></pre>
<ul>
<li>스칼라는 다음과 같은 형식으로 패턴 매치를 사용
<code>셀렉터 match { 대안들 }</code>
참고) 자바의 스위치문 <code>switch (셀렉터) { 대안들 }</code></li>
</ul>
<ul>
<li>셀렉터(selector)는 match 표현식에서 사용이 되는 값</li>
<li>패턴 매치에는 case 키워드로 시작하는 여러 대안(alternative)들이 들어간다.</li>
<li>각각의 대안에서는 패턴과 셀렉터가 일치하였을 때 계산되는 하나 이상의 표현식을 포함한다.</li>
<li>패턴과 표현식을 분리하는 것은 화살표(=&gt;)이다.</li>
<li>따라서, match를 통해서 패턴을 하나씩 검사하게 되면 이 중 첫 번째로 일치한 패턴을 선택해서 화살표 뒤의 표현식을 실행하게 된다.</li>
</ul>
<h3 id="패턴-형식">패턴 형식</h3>
<ol>
<li>상수 패턴</li>
</ol>
<ul>
<li>&quot;+&quot; 문자열이나 1과 같은 상수 패턴은 ==을 적용해서 매치</li>
</ul>
<ol start="2">
<li>변수만 사용한 패턴</li>
</ol>
<ul>
<li>e와 같이 오른쪽에 있는 표현식에 변수가 있으면 그 변수는 매치된 값을 가르킨디ㅏ.</li>
</ul>
<ol start="3">
<li>와일드카드 패턴</li>
</ol>
<ul>
<li>case+ =&gt; expr와 같은 형태</li>
<li>언더스코어<code>(_)</code>를 활용하면 모든 값 매치 가능</li>
<li>but, 해당 값에 이름을 붙이지 않기 때문에 표현식에서는 사용 X</li>
</ul>
<ol start="4">
<li>생성자 패턴</li>
</ol>
<ul>
<li>case UnOp(&quot;-&quot;, UnOp(&quot;-&quot;, e)) =&gt; e 와 같은 형태</li>
<li>어떤 값의 타입이 UnOp이고, 첫 번째 인자가 &quot;-&quot;이며, 두 번째 인자를 e에 매치시킬 수 있다면 해당 생성자 패턴과 매치 가능</li>
</ul>
<h3 id="switch와-match-비교">switch와 match 비교</h3>
<ul>
<li><p>match식은 자바 스타일 switch를 일반화</p>
</li>
<li><p>하지만 switch와 다르게 주의할 점 3가지</p>
<ul>
<li><p>스칼라의 match는 표현식, 따라서 항상 결과값을 내놓는다.</p>
</li>
<li><p>스칼라의 대안 표현식은 다음 케이스로 빠지지 않는다.</p>
</li>
<li><blockquote>
<p>switch에서 각 case에 대해 break가 있다는 전제로 생각하면 된다.</p>
</blockquote>
</li>
<li><p>매치에 성공하지 못한 경우, MatchError 예외 발생
따라서 default 케이스는 반드시 추가해야한다.
<code>case _ =&gt; // default case</code></p>
</li>
</ul>
</li>
</ul>
<h1 id="패턴의-종류">패턴의 종류</h1>
<h3 id="와일드카드-패턴">와일드카드 패턴</h3>
<pre><code class="language-java">expr match {
    case BinOp(_, _, _) =&gt; println(expr + &quot; is a binary operation&quot;)
    case _ =&gt; println(&quot;It&#39;s something else&quot;)
}</code></pre>
<ul>
<li>와일드카드패턴은 <code>(_)</code> 어떤 객체라도 매치 가능</li>
<li>default case로 사용하거나 BinOp에 대한 인자로 사용 가능</li>
</ul>
<h3 id="상수-패턴">상수 패턴</h3>
<pre><code class="language-java">x match {
    case 5 =&gt; &quot;five&quot;
    case true =&gt; &quot;truth&quot;
    case &quot;hello&quot; =&gt; &quot;hi!&quot;
    case Nil =&gt; &quot;the empty list&quot;
    case _ =&gt; &quot;something else&quot;
}</code></pre>
<ul>
<li>상수 패턴은 자신과 똑같은 값과 매치, 어떤 리터럴이든 상수로 사용 가능</li>
<li>val이나 싱글톤 객체도 상수로 사용 가능. ex) Nil은 오직 빈 리스트에만 매치시킬 수 있는 패턴</li>
</ul>
<h3 id="변수-패턴">변수 패턴</h3>
<pre><code class="language-java">// 변수 패턴을 사용한 패턴 매치
expr match {
  case 0 =&gt; &quot;zero&quot;
  case somethingElse =&gt; &quot;not zero: &quot; + somethingElse
}</code></pre>
<ul>
<li>와일드카드 패턴처럼 변수 패턴은 어떤 객체와도 매치 됨</li>
<li>하지만 변수 패턴은 와일드카드 패턴과 아래와 같은 점들이 다르다.<ul>
<li>언더스코어 <code>(_)</code>가 아닌 변수에 객체를 바인딩한다.</li>
<li>변수를 표현식에서 활용할 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="변수-또는-상수-">변수 또는 상수 ?</h3>
<p>상수 패턴이 기호로 이뤄진 이름일 수도 있다. ex) Nil</p>
<pre><code class="language-java">scala&gt; import math.{E, Pi}
import math.{E, Pi}

scala&gt; E match {
         case Pi =&gt; &quot;strange math? Pi = &quot; + Pi
         case _ =&gt; &quot;OK&quot;
       }
res11: String = OK</code></pre>
<p>컴파일러는 Pi가 셀럭터와 매치시킬 변수가 아니라, scala.math 로 부터 import 한 상수인지 알 수 있을 까?</p>
<blockquote>
<p>소문자로 시작하는 간단한 이름은 패턴 변수로 취급하고 다른 모든 참조는 상수로 간주한다.</p>
</blockquote>
<pre><code class="language-java">
scala&gt; val pi = math.Pi
pi: Double = 3.141592653589793

scala&gt; E match {
         case pi =&gt; &quot;strange math? Pi = &quot; + pi
       }
res12: String = strange math? Pi = 2.718281828459045</code></pre>
<p>※ 소문자 이름을 상수 패턴으로 사용하고 싶으면 두 가지 방법</p>
<ul>
<li><p>상수가 어떤 객체의 필드인 경우에는 지정자(qualifier)를 앞에 붙일 수 있다.</p>
</li>
<li><p>역따옴표<code>(｀)</code>를 사용한다.</p>
</li>
</ul>
<h3 id="생성자-패턴">생성자 패턴</h3>
<pre><code class="language-java">expr match {
    case BinOp(&quot;+&quot;, e, Number(0)) =&gt; println(&quot;a deep match&quot;)
    case _ =&gt;
}</code></pre>
<ul>
<li>깊은 매치(deep match)를 지원</li>
<li>어떤 패턴이 제공받은 최상위 객체를 매치시킬 뿐만 아니라, 추가적인 패턴으로 객체의 내용에 대해서도 매치를 시도
ex)<ul>
<li>최상위 객체가 BinOP인지 검사</li>
<li>BinOP 객체의 세번째 생성자 인자가 Number 타입의 객체인지 확인</li>
<li>Number 타입 객체의 값 필드가 0인지 확인</li>
</ul>
</li>
<li>실제로는 객체 트리 구조를 세 단계 내려가면서 패턴을 매치</li>
</ul>
<h3 id="시퀀스-패턴">시퀀스 패턴</h3>
<p>배열이나 리스트 같은 시퀀스 타입에 대해서도 매치가 가능</p>
<pre><code class="language-java">expr match {
    case List(0, _, _) =&gt; println(&quot;found it&quot;)
    case _ =&gt;
}</code></pre>
<ul>
<li>0부터 시작하여 오직 세 원소만 가진 리스트 검사</li>
</ul>
<pre><code class="language-java">// 길이와 관계 없이 매치할 수 있는 시퀀스 패턴
expr match {
    case List(0, _*) =&gt; println(&quot;found it&quot;)
    case _ =&gt;
}</code></pre>
<ul>
<li>리스트 길이가 한정적이지 않을 떄는 마지막 원소를 <code>_*</code>로 표시</li>
</ul>
<h3 id="튜플-패턴">튜플 패턴</h3>
<pre><code class="language-java">expr match {
    case (a, b, c)  =&gt;  println(&quot;matched &quot; + a + b + c)
    case _ =&gt;
}</code></pre>
<ul>
<li>튜플도 패턴 매치 가능, 각 튜플에 맞는 원소들을 extract하여 표현식에서 활용 가능</li>
</ul>
<h3 id="타입-지정-패턴">타입 지정 패턴</h3>
<pre><code class="language-java">val x : Any = ...

x match {
    case s: String =&gt; s.length
    case m: Map[_, _] =&gt; m.size
    case _ =&gt; -1
}</code></pre>
<ul>
<li>타입 검사나 타입 변환을 간편하게 하기 위해 타입 지정 패턴 사용</li>
<li>s나 x 모두 동일한 값 참조하지만, x는 Any 타입, s는 String 타입 -&gt; 모든 String 인스턴스와 매치 가능</li>
<li><code>Map[_,_]</code>은 와일드 카드를 활용한 타입 지정 패턴<ul>
<li>키와 값의 타입과 관계없이 Map 타입의 값과 매치</li>
<li>m은 그 값을 가리킨다.</li>
</ul>
</li>
</ul>
<h3 id="변수-바인딩">변수 바인딩</h3>
<pre><code class="language-java">// (@ 기호를 사용한) 변수 바인딩이 있는 패턴
expr match {
    case UnOp(&quot;abs&quot;, e @ UnOp(&quot;abs&quot;, _)) =&gt; e
    case _ =&gt;
}</code></pre>
<ul>
<li>패턴 매칭에 성공하면 변수 e에는 <code>UnOp(&quot;abs&quot;, _))</code> 구문에서 생성된 객체가 할당</li>
</ul>
<h3 id="그-외-기타">그 외 기타</h3>
<h4 id="option-type">Option type</h4>
<pre><code class="language-java">def optionsPatternMatching(option: Option[String]): String = {
  option match {
    case Some(value) =&gt; s&quot;I&#39;m not an empty option. Value $value&quot;
    case None =&gt; &quot;I&#39;m an empty option&quot;
  }
}</code></pre>
<h4 id="정규식-패턴-사용">정규식 패턴 사용</h4>
<pre><code>def regexPatterns(toMatch: String): String = {
  val numeric = &quot;&quot;&quot;([0-9]+)&quot;&quot;&quot;.r
  val alphabetic = &quot;&quot;&quot;([a-zA-Z]+)&quot;&quot;&quot;.r
  val alphanumeric = &quot;&quot;&quot;([a-zA-Z0-9]+)&quot;&quot;&quot;.r

  toMatch match {
    case numeric(value) =&gt; s&quot;I&#39;m a numeric with value $value&quot;
    case alphabetic(value) =&gt; s&quot;I&#39;m an alphabetic with value $value&quot;
    case alphanumeric(value) =&gt; s&quot;I&#39;m an alphanumeric with value $value&quot;
    case _ =&gt; s&quot;I contain other characters than alphanumerics. My value $toMatch&quot;
  }
}</code></pre><h1 id="패턴-가드">패턴 가드</h1>
<p>문법적인 패턴 매치만으로 case에 대한 구분이 부족할 때, 패턴 가드를 사용
case 문을 좀 더 구체화하고 명확하게 하기 위한 boolean 표현식 이라고 생각</p>
<ul>
<li>패턴 가드는 패턴 뒤에 오고 if로 시작
  어떤 불리언 표현식이든 가드가 될 수 있음.<pre><code class="language-java">// 패턴 가드가 있는 match 표현식
scala&gt; def simplifyAdd(e: Expr) = e match {
       case BinOp(&quot;+&quot;, x, y) if x == y =&gt;
         BinOp(&quot;*&quot;, x, Number(2))
       case _ =&gt; e
     }
simplifyAdd: (e: Expr)Expr</code></pre>
</li>
</ul>
<h1 id="패턴-겹침">패턴 겹침</h1>
<p>패턴 매치는 코드에 있는 순서를 따른다. 그래서 case 문에 대한 순서가 굉장히 중요하다. 
아래 예시를 보면 알 수 있다.</p>
<pre><code class="language-java">// case의 순서가 중요함을 보여주는 match 표현식
def simplifyAll(expr: Expr): Expr = expr match {
    case UnOp(&quot;-&quot;, UnOp(&quot;-&quot;, e)) =&gt; simplifyAll (e)  // Double negation
    case BinOp(&quot;+&quot;, e, Number(0)) =&gt; simplifyAll (e)     // Adding zero
    case BinOp(&quot;*&quot;, e, Number(1)) =&gt; simplifyAll (e) // Multiplying by one
    case UnOp(op, e) =&gt; UnOp(op, simplifyAll(e))
    case BinOp(op, 1, r) =&gt; BinOp(op, simplifyAll(1), simplifyAll(r))
    case _ =&gt; expr
}

def simplifyTop(expr: Expr): Expr = expr match {
   case UnOp(&quot;-&quot;, UnOp(&quot;-&quot;, e))  =&gt; e   // Double negation
   case BinOp(&quot;+&quot;, e, Number(0)) =&gt; e   // Adding zero
   case BinOp(&quot;*&quot;, e, Number(1)) =&gt; e   // Multiplying by one
   case _ =&gt; expr
}</code></pre>
<ul>
<li><p>simplifyAll 함수는 이전에 정의된 simmplifyTop과 달리 산술식의 최상위 위치뿐 아니라 식의 모든 곳에 간소화 규칙을 적용</p>
</li>
<li><p>simplilfyTop 함수에서 2가지의 케이스를 추가하였다.</p>
<ul>
<li>case UnOp(op, e) =&gt; UnOp(op, simplifyAll(e))<ul>
<li>모든 단항 연산과 매치</li>
</ul>
</li>
<li>case BinOp(op, 1, r) =&gt; BinOp(op, simplifyAll(1), simplifyAll(r))<ul>
<li>모든 이항 연산과 매치</li>
</ul>
</li>
</ul>
</li>
<li><p>마지막으로 모든 경우를 처리하는 case 문이 더 구체적인 규칙 다음에 온다는 점이 중요</p>
</li>
<li><p>만약 순서를 바꾸게 되면 구체적인 매치는 시도 자체를 하지 않을 것</p>
<pre><code class="language-java">scala&gt; def simplifyBad(expr: Expr): Expr = expr match {
       case UnOp(op, e) =&gt; UnOp(op, simplifyBad(e))
       case UnOp(&quot;-&quot;, UnOp(&quot;-&quot;, e)) =&gt; e
     }
&lt;console&gt;:21: warning: unreachable code
       case UnOp(&quot;-&quot;, UnOp(&quot;-&quot;, e)) =&gt; e
                                       ^
simplifyBad: (expr: Expr)Expr</code></pre>
<h1 id="봉인된-클래스">봉인된 클래스</h1>
<p>패턴 매치를 작성할 때, 모든 경우를 다 다뤘는 지 확인할 필요가 있다. 그래서 default 케이스를 추가해서 할 수 도 있지만, 이는 합리적인 디폴트 케이스가 있을 때만 가능 -&gt; 만약 디폴트 동작이 없다면 ??</p>
</li>
<li><p>아무곳에서 케이스 클래스를 정의할 수 있기 때문에, 컴파일러에게 놓친 패턴 조합을 찾아달라고 요청할 수 없음...</p>
</li>
<li><p>패턴 매치를 작성할 때 match 식에서 놓친 패턴 조합을 찾을 수 있는 방법은 <strong>sealed 키워드</strong>를 활용</p>
</li>
<li><p>즉, 패턴 매치에서 <strong>셀렉터에 대한 타입을 sealed 클래스로 정의</strong>하고 <strong>매치를 하는 클래스는 모두 sealed 클래스를 상속받은 클래스로 정의</strong>하는 것이다.</p>
</li>
</ul>
<pre><code class="language-java">// 봉인된 케이스 클래스 계층
sealed abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr</code></pre>
<ul>
<li>sealed 클래스를 대상으로 패턴 매치를 하게 되면 상속받은 나머지 클래스에 대하여도 모두 case 구문을 만들어줘야 함</li>
<li>하지만 아래와 같이 패턴 매치를 하게 되면 warning이 발생한다.
컴파일러의 경고, 몇가지 가능한 패턴(UnOp, BinOp)을 처리하지 않았기 때문에 예외 발생</li>
</ul>
<ul>
<li>나머지 case 구문을 언더스코어(_)로 처리하여도 문제는 없지만 이상적이진 않으므로 추천은 하지 않는다.</li>
<li>또 다른 방법으로 <strong>셀렉터에 @unchecked 어노테이션을 추가</strong>
그러면 <strong>컴파일러는 그 match 문의 case 문이 모든 패턴을 다 다루는지 검사하는 일을 생략</strong></li>
</ul>
<pre><code class="language-java">def describe(e: Expr): String = e match {
    case Number(_) =&gt; &quot;a number&quot;
    case Var(_) =&gt; &quot;a variable&quot;
}

// 컴파일러의 경고
warning: match is not exhaustive!
missing combination UnOp
missing combination BinOp</code></pre>
<pre><code class="language-java">// 셀렉터에 @unchecked 어노테이션을 추가
// 컴파일러는 그 match 문의 case 문이 모든 패턴을 다 다루는지 검사하는 일을 생략
def describe(e: Expr): String = (e: @unchecked) match {
    case Number(_) =&gt; &quot;a number&quot;
    case Var(_) =&gt; &quot;a variable&quot;
}</code></pre>
<h1 id="option-타입">Option 타입</h1>
<ul>
<li>스칼라에서는 Option이라는 표준 타입 존재</li>
<li>이 타입은 선택적인 값을 표현하며, 두 가지 형태가 있다.<ul>
<li>x가 실제 값이라면 Some(x) 라는 형태로 값이 있음을 표현할 수 있다.</li>
<li>반대로 값이 없으면 None이라는 객체가 된다.</li>
</ul>
</li>
<li>스칼라 컬렉션의 몇몇 표준 연산에서는 Option이라는 선택적인 값을 생성한다. 그 중 Map의 get 메소드를 예시로 들 수 있다.</li>
</ul>
<pre><code class="language-bash">scala&gt; val capitals =  Map(&quot;France&quot;-&gt;&quot;Paris&quot;, &quot;Japan&quot;-&gt;&quot;Tokyo&quot;)
capitals: scala.collection.immutable.Map[String,String] = Map(France -&gt; Paris, Japan -&gt; Tokyo)

scala&gt; capitals get &quot;France&quot;
res23: Option[String] = Some(Paris)

scala&gt; capitals get &quot;North Pole&quot;
res24: Option[String] = None</code></pre>
<ul>
<li>java에서는 값이 없을 경우, null을 주로 사용하는 데, 스칼라에서 Option을 활용하면 다음과 같은 장점이 있다. <ul>
<li>Option[String] 타입의 변수가 null이 될 수도 있는 String 타입의 변수보다
선택적인 String이라는 사실을 더 명확하게 드러내준다</li>
</ul>
</li>
</ul>
<p>Option[String] 타입의 변수가 null이 될 수도 있는 String 타입의 변수보다 선택적인 String이라는 사실을 더 명확하게 드러내준다. </p>
<h1 id="패턴은-어디에나">패턴은 어디에나</h1>
<p>독립적인 match 표현식뿐 아니라, 스칼라의 여러 곳에서 패턴을 사용할 수 있다.</p>
<h2 id="변수정의에서-패턴-사용하기">변수정의에서 패턴 사용하기</h2>
<p>val이나 var 정의할 때, 단순 식별자 대신 패턴 사용 가능</p>
<pre><code class="language-java">scala&gt; val myTuple = (123, &quot;abc&quot;)
myTuple: (Int, String) = (123,abc)

scala&gt; val (number, string) = myTuple
number: Int = 123
string: String = abc</code></pre>
<h2 id="case를-나열해서-부분-함수-만들기">case를 나열해서 부분 함수 만들기</h2>
<p>함수 리터럴이 쓰일 수 있는 곳이라면, 중괄호 사이에 case를 나열한 표현식 작성 가능
본질적으로 case의 나열도 함수의 리터럴 값</p>
<ul>
<li>각각의 case가 함수의 진입점이고, 패턴은 파라미터를 명시한다.</li>
<li>각 진입점에 따른 함수의 본문은 case의 오른쪽 (화살표 =&gt;의 오른쪽)</li>
</ul>
<pre><code class="language-java">val withDefault: Option[Int] =&gt; Int = {
    case Some(x) =&gt; x
    case None =&gt; 0
}</code></pre>
<p>아래 예시는 아카 엑터(Akka actors) 라이브러리에서 사용하는 receive 메소드</p>
<pre><code class="language-java">var sum = 0

 // case의 나열은 부분 함수 이다.
def receive = {
    case Data(byte) =&gt;
        sum += byte
    case GetChecksum(requester) =&gt;
        val checksum = ~(sum &amp; OxFF) + 1
        requester ! checksum
}
</code></pre>
<ul>
<li>case 나열의 부분 함수에서 그 함수가 처리하지 않는 값을 전달해서 호출하면 실행 시점에서 예외가 발생</li>
</ul>
<pre><code class="language-shell">scala&gt; val second: List[Int] =&gt; Int = {
     |   case x :: y :: _ =&gt; y
     | }
                                      ^
       warning: match may not be exhaustive.
       It would fail on the following inputs: List(_), Nil
val second: List[Int] =&gt; Int = $Lambda$1061/638249818@4f6e663b

scala&gt; second(List(5,6,7))
val res3: Int = 6

scala&gt; second(List())
scala.MatchError: List() (of class scala.collection.immutable.Nil$)
  at $anonfun$second$1(&lt;console&gt;:1)
  at $anonfun$second$1$adapted(&lt;console&gt;:1)
  ... 32 elided</code></pre>
<ul>
<li><p>처리하지 않는 값을 전달하여 예외가 발생시에 처리 방법</p>
<ul>
<li><p>예외를 던져서 호출자에게 전달한다.</p>
</li>
<li><p>스칼라의 Option이나 Either 등을 사용하여 정상적인 값과 그렇지 않은 값을 처리할 수 있다.</p>
</li>
<li><p>부분 함수를 사용하여 처리하지 않는 값에 대하여는 작업을 하지 않도록 할 수 있다.</p>
</li>
<li><p>여기선, 처리하지 않는 값을 다루기 위해서 부분 함수를 포함하는 타입인 PartitionFunction을 이용한다.</p>
<ul>
<li>PartitionFunction 클래스는 isDefinedAt 함수를 사용함으로써 파라미터가 case 구문에 속해있는지 먼저 확인을 할 수 있다.</li>
<li>아래 예시에서 second는 PartitionFunction 타입의 객체로서 정수형 리스트를 받아서 정수 값을 반환한다.<pre><code class="language-shell">scala&gt; val second : PartialFunction[List[Int], Int] = {
|   case x :: y :: _ =&gt; y
| }
val second: PartialFunction[List[Int],Int] = &lt;function1&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>scala&gt; second.isDefinedAt(List(5,6,7))
val res5: Boolean = true</p>
<p>scala&gt; second.isDefinedAt(List())
val res6: Boolean = false</p>
<p>scala&gt; second(List(5,6,7))
res0: Int = 6</p>
<p>// 하지만, PartitionFunction에서도 예외가 발생하는 것은 여전하다.
scala&gt; second(List())
scala.MatchError: List() (of class scala.collection.immutable.Nil$)
  at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:259)
  at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:257)
  at $anonfun$1.applyOrElse(<console>:11)
  at $anonfun$1.applyOrElse(<console>:11)
  at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:38)
  ... 28 elided</p>
<pre><code>## for 표현식에서 패턴 사용하기
* for 표현식 안에 패턴을 사용할 수 있다.

*  아래 예시는 capitals 맵에서 모든 키/값 쌍을 가져온다.
* 각 튜플은 country와 city 변수가 있는 패턴과 매치된다.
* 두 번째 예시는 튜플 패턴과 정확히 매치하지 않을 경우에 해당 값을 버리는 경우에도 사용된다.

``` java
scala&gt; for ((country, city) &lt;- capitals)
         println(&quot;The capital of &quot; + country + &quot; is &quot; + city)
The capital of France is Paris
The capital of Japan is Tokyo</code></pre><ul>
<li>튜플 패턴과 정확히 매치하지 않을 경우에 해당 값을 버리는 경우에도 사용<pre><code class="language-java">scala&gt; val results = List(Some(&quot;apple&quot;), None, Some(&quot;orange&quot;))
results: List[Option[String]] = List(Some(apple), None, Some(orange))
</code></pre>
</li>
</ul>
<p>scala&gt; for (Some(fruit) &lt;- results) println(fruit)
apple
orange</p>
<p>```</p>
<h1 id="결론">결론</h1>
<ul>
<li>스칼라의 케이스 크랠스와 패턴 매치를 활용하면 보통의 객체지향 언어에서 지원하지 않는 간결한 표현법의 이점이 있다.</li>
<li>어떤 클래스에 대해 패턴 매치를 사용하고 싶지만, 케이스 클래스처럼 클래스 필드를 외부에 노출하고 싶지 않다면, 26장의 익스트렉터(Extractor)를 사용할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[11. 스칼라 계층구조]]></title>
            <link>https://velog.io/@dev_jhjhj/11.-%EC%8A%A4%EC%B9%BC%EB%9D%BC-%EA%B3%84%EC%B8%B5%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@dev_jhjhj/11.-%EC%8A%A4%EC%B9%BC%EB%9D%BC-%EA%B3%84%EC%B8%B5%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Tue, 31 Aug 2021 12:58:01 GMT</pubDate>
            <description><![CDATA[<h1 id="스칼라의-클래스-계층구조">스칼라의 클래스 계층구조</h1>
<h1 id="여러-기본-클래스를-어떻게-구현했는가">여러 기본 클래스를 어떻게 구현했는가?</h1>
<h1 id="바닥에-있는-타입">바닥에 있는 타입</h1>
<h1 id="자신만의-값-클래스-정의">자신만의 값 클래스 정의</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[10. 상속과 구성]]></title>
            <link>https://velog.io/@dev_jhjhj/10.-%EC%83%81%EC%86%8D%EA%B3%BC-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@dev_jhjhj/10.-%EC%83%81%EC%86%8D%EA%B3%BC-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Tue, 31 Aug 2021 12:56:39 GMT</pubDate>
            <description><![CDATA[<h1 id="2차원-레이아웃-라이브러리">2차원 레이아웃 라이브러리</h1>
<h1 id="추상클래스">추상클래스</h1>
<h1 id="파라미터-없는-메소드-정의">파라미터 없는 메소드 정의</h1>
<h1 id="클래스-확장">클래스 확장</h1>
<h1 id="메소드와-필드-오버라이드">메소드와 필드 오버라이드</h1>
<h1 id="파라미터-필드-정의">파라미터 필드 정의</h1>
<h1 id="슈퍼클래스의-생성자-호출">슈퍼클래스의 생성자 호출</h1>
<h1 id="override-수식자-사용">override 수식자 사용</h1>
<h1 id="다형성과-동적-바인딩">다형성과 동적 바인딩</h1>
<h1 id="final-멤버-선언">final 멤버 선언</h1>
<h1 id="상속과-구성-사용">상속과 구성 사용</h1>
<h1 id="above-beside-tostring-구현">above, beside, toString 구현</h1>
<h1 id="팩토리-객체-정의">팩토리 객체 정의</h1>
<h1 id="높이와-너비-조절">높이와 너비 조절</h1>
<h1 id="한데-모아-시험해보기">한데 모아 시험해보기</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[9. 흐름 제어 추상화]]></title>
            <link>https://velog.io/@dev_jhjhj/9.-%ED%9D%90%EB%A6%84-%EC%A0%9C%EC%96%B4-%EC%B6%94%EC%83%81%ED%99%94</link>
            <guid>https://velog.io/@dev_jhjhj/9.-%ED%9D%90%EB%A6%84-%EC%A0%9C%EC%96%B4-%EC%B6%94%EC%83%81%ED%99%94</guid>
            <pubDate>Tue, 31 Aug 2021 12:54:41 GMT</pubDate>
            <description><![CDATA[<h1 id="코드-중복-줄이기">코드 중복 줄이기</h1>
<h1 id="클라이언트-코드-단순하게">클라이언트 코드 단순하게</h1>
<h1 id="커링">커링</h1>
<h1 id="새로운-제어-구조-작성">새로운 제어 구조 작성</h1>
<h1 id="이름에-의한-호출-파라미터">이름에 의한 호출 파라미터</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[8. 함수와 클로저]]></title>
            <link>https://velog.io/@dev_jhjhj/8.-%ED%95%A8%EC%88%98%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@dev_jhjhj/8.-%ED%95%A8%EC%88%98%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Tue, 31 Aug 2021 12:53:56 GMT</pubDate>
            <description><![CDATA[<h1 id="메소드">메소드</h1>
<h1 id="지역-함수">지역 함수</h1>
<h1 id="1급-계층-함수">1급 계층 함수</h1>
<h1 id="간단한-형태의-함수-리터럴">간단한 형태의 함수 리터럴</h1>
<h1 id="위치-표시자-문법">위치 표시자 문법</h1>
<h1 id="부분-적용한-함수">부분 적용한 함수</h1>
<h1 id="클로저">클로저</h1>
<h1 id="특별한-형태의-함수-호출">특별한 형태의 함수 호출</h1>
<h1 id="꼬리-재귀">꼬리 재귀</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[7. 내장 제어 구문]]></title>
            <link>https://velog.io/@dev_jhjhj/7.-%EB%82%B4%EC%9E%A5-%EC%A0%9C%EC%96%B4-%EA%B5%AC%EB%AC%B8</link>
            <guid>https://velog.io/@dev_jhjhj/7.-%EB%82%B4%EC%9E%A5-%EC%A0%9C%EC%96%B4-%EA%B5%AC%EB%AC%B8</guid>
            <pubDate>Tue, 31 Aug 2021 12:52:56 GMT</pubDate>
            <description><![CDATA[<h1 id="if-표현식">if 표현식</h1>
<h2 id="명령형-언어-스타일">명령형 언어 스타일</h2>
<pre><code class="language-java">var filename = &quot;default.txt&quot;
  if (!args.isEmpty)
    filename = args(0)</code></pre>
<p>if 표현식에서 조건 결과가 참일 여부에 따라 분기 중 하나를 실행</p>
<h2 id="스칼라-스타일">스칼라 스타일</h2>
<pre><code class="language-javascript">val filename = if (!args.isEmpty) args(0) else &quot;default.txt&quot;</code></pre>
<p>스칼라 if 표현식은 결과로 값을 내놓는다.</p>
<p>var 대신 val로 선언하면 좋은 점</p>
<ul>
<li>변수가 사용되는 범위 내에서 값의 변화 여부를 판단하기 위해 모든 코드 살펴볼 필요 X</li>
<li>동일성 추론 : 해당 표현식에 사이드 이펙트가 존재하지 않기 때문에 값과 동일시하여 사용 가능</li>
</ul>
<pre><code class="language-javascript">// 예시
println(filename) → println(if (!args.isEmpty) args(0) else &quot;default.txt&quot;)</code></pre>
<h1 id="while-루프">while 루프</h1>
<ul>
<li>while과 do-while은 표현식이 아니라, loop라고 부름</li>
<li>loop의 결과 타입은 Unit이다. 그래서 어느정도의 side effect가 발생할 수 있음</li>
<li>Unit 타입에는 Unit value 밖에 없고, 이 값을 빈 괄호()로 표기<pre><code class="language-java">// 최대 공약수 구하기
def gcdLoop(x: Long, y: Long): Long = {
  var a = x
  var b = y
  while (a != 0) {
      val temp = a
      a = b % a
      b = temp
  }
  b
}
</code></pre>
</li>
</ul>
<p>// 결과
scala&gt; gcdLoop(10, 20)
val res0: Long = 10</p>
<p>scala&gt; gcdLoop(4, 6)
val res1: Long = 2</p>
<pre><code>

* ()은 값이 존재한다는 점에서 자바의 void와 다른 의미이다.
```java
scala&gt; def greet() = { println(&quot;hi&quot;) }
def greet(): Unit

scala&gt; () == greet()
hi
val res10: Boolean = true</code></pre><p>greet는 결과 타입이 Unit인 프로시저라고 한다. 
(결과 값이 있으면 함수, 결과 값이 없으면 프로시저라고 부른다.)</p>
<ul>
<li><p>()은 당연히 ()를 반환할 것이고, greet() 또한 Unit 함수이기 때문에 ()를 반환하므로 서로 동일하다.</p>
</li>
<li><p>다음과 같이 일반적인 형태로 while 루프를 스칼라에서 사용하면 문제가 생긴다.</p>
</li>
</ul>
<pre><code class="language-java">scala&gt; while ((line = scala.io.StdIn.readLine()) != &quot;&quot;)
     |   println(&quot;Read: &quot; + line)
                                                 ^
       warning: comparing values of types Unit and String using `!=` will always yield true
Read: ddd
Read: ddd
Read:
Read:</code></pre>
<ul>
<li><p>위의 readLine 함수는 Unit 타입으로 정의되어 있다.
=&gt; 결과 값은 ()일 것이고 해당 값은 빈 문자열인 &quot;&quot;와 같을 수 없을 것이다.</p>
</li>
<li><p>자바에서는 위와 관련된 I/O 함수가 실행되었을 때 line 이라는 변수에 값이 할당이 되겠지만 스칼라는 해당 함수가 Unit 타입이면 항상 ()를 반환한다. </p>
</li>
</ul>
<p>이를 해결 하기 위해서...</p>
<pre><code class="language-java">scala&gt; while({line = scala.io.StdIn.readLine(); line != &quot;&quot;})
     |   println(&quot;Read: &quot; + line)</code></pre>
<h1 id="for-표현식">for 표현식</h1>
<p>여러가지 방법으로 조합해서 다양한 반복 작업을 처리할 수 있다.
연속적인 정수에 대해 작업을 처리하는 것도 간략하게 가능하며, 여러 종류 컬렉션을 대상으로 조건에 맞는 요소를 가려내고 새로운 컬렉션을 만들 수 있다.</p>
<h3 id="컬렉션-이터레이션">컬렉션 이터레이션</h3>
<pre><code class="language-java">val filesHere = (new java.io.File(&quot;.&quot;)).listFiles
val filesHere: Array[java.io.File] = Array(.\fsc, .\fsc.bat, .\scala, .\scala.bat, .\scalac, .\scalac.bat, .\scaladoc, .\scaladoc.bat, .\scalap, .\scalap.bat)

 scala&gt; for (file &lt;- filesHere) {
     |   println(file)
     | }
.\fsc
.\fsc.bat
.\scala
.\scala.bat
.\scalac
.\scalac.bat
.\scaladoc
.\scaladoc.bat
.\scalap
.\scalap.bat</code></pre>
<ul>
<li>자바 API를 사용해서 디렉토리 내의 모든 파일을 출력하는 코드</li>
<li>file &lt;- filesHere 은 제너레이터(generator)라고 부른다. 각 단계마다 file 이라는 val 원소 값으로 초기화 한다.</li>
<li>컴파일러는 files가 Array[File] 타입이기 때문에 file의 타입도 File을 추론할 수 있다.</li>
</ul>
<h3 id="필터링">필터링</h3>
<p>컬렉션 모든 원소 접근 않고 일부만 사용하고 싶은 경우, for 문에 if 사용 가능하다.</p>
<pre><code class="language-java">var filesHere = (new java.io.File(&quot;.&quot;)).listFiles
var filesHere: Array[java.io.File] = Array(.\fsc, .\fsc.bat, .\scala, .\scala.bat, .\scalac, .\scalac.bat, .\scaladoc, .\scaladoc.bat, .\scalap, .\scalap.bat)


 for (file &lt;- filesHere if file.getName.endsWith(&quot;.bat&quot;)) {
     |   println(file)
     | }
.\fsc.bat
.\scala.bat
.\scalac.bat
.\scaladoc.bat
.\scalap.bat</code></pre>
<ul>
<li>위 예제는 if 문을 활용해서 조건 별로 file들을 대상으로 명령 수행 가능<blockquote>
<p>for 표현식은 사용하기 위한 값을 결과로 내놓는다.</p>
</blockquote>
</li>
</ul>
<h1 id="try-표현식">try 표현식</h1>
<p>Scala도 try 를 이용해서 예외를 처리하거나 종료할 수 있다.</p>
<h2 id="예외-발생시키기">예외 발생시키기</h2>
<p>스칼라에서 예외를 발생시키는 방법은 throw 키워드를 사용해 생성한 예외를 던지면 된다.</p>
<blockquote>
<p>스칼라에서는 throw가 결과 타입이 있는 표현식이다.</p>
</blockquote>
<pre><code class="language-javascript">val half =
    if (n%2==0)
        n/2
    else
        throw new RuntimeException(&quot;n must be even&quot;)</code></pre>
<p>n이 짝수이면, half에는 n의 반에 해당하는 값이 할당
n이 짝수가 아니면, 어떤 값으로도 초기화하지 않고 그 전에 예외를 발생
따라서, else 부분에서 어떤 예외를 던지더라도 이를 원하는 타입으로 다뤄도 문제 없다.
기술적으로 예외는 Nothing이라는 타입을 갖는다.</p>
<ul>
<li>Nothing 타입은 모든 타입의 서브타입, 값을 도출해내는 과정에서 예외가 있더라도 타입 에러에 관한 문제는 없다.
<img src="https://images.velog.io/images/dev_jhjhj/post/c99a6146-0def-4081-b709-df5ac5ddb355/image.png" alt=""></li>
</ul>
<h2 id="발생한-예외-잡기">발생한 예외 잡기</h2>
<p>try-catch 구문 사용, 다른 언어와 마찬가지로 예외가 발생하면 catch 구분에서 예외처리 시도
throws 선언하지 않고, 예외 메소드만 명시 가능 =&gt; @throws 어노테이션 명시 가능</p>
<pre><code class="language-javascript">import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException

try {
    val f = new FileReader(&quot;input.txt&quot;) // 파일을사용하고닫는다.
} catch {
    case ex: FileNotFoundException =&gt; // 파일을못찾는경우 처리
    case ex: IOException =&gt; // 그 밖의 10 오류 처리
}</code></pre>
<h2 id="finally-절">finally 절</h2>
<p>항상 실행하고 싶은 코드가 있을 경우에 finally로 감싸서 사용</p>
<pre><code class="language-javascript">import java.io.FileReader

val file = new FileReader(&quot;input.txt&quot;)
try {
  // 파일을사용한다.
} finally {
    file.close() // 파일을확실히 닫는다.
}</code></pre>
<h2 id="값-만들어내기">값 만들어내기</h2>
<p>try-catch-finally의 결과는 값이다.
예외가 발생하지 않을 경우 -&gt; 전체 결과는 try 절의 수행결과가 전체 결과
예외가 발생하여 예외를 잡은 경우 -&gt; catch절의 수행 결과가 전체 결과
finally 절에 결과 값이 있다면 버려진다. finally 절은 파일을 닫는 등 정리 작업을 수행하므로, try 절, catch 절의 결과를 수정하지 않아야한다.</p>
<pre><code class="language-javascript">import java.net.URL
import java.net.MaiformedURLException

def urlFor(path: String) = 
  try {
    new URL(path)
  } catch {
    case e: MaiformedURLException =&gt;
        new URL(&quot;http://www.scala-lang.org&quot;)
  }
</code></pre>
<h1 id="match-표현식">match 표현식</h1>
<p>스칼라의 match 표현식은 switch문과 유사하지만, 다음과 같은 차이점 존재</p>
<ul>
<li>java의 case 문에는 enum 값이나, 정수 type의 값 또는 문자열 값만 쓸 수 있지만, 스칼라는 어떤 종류의 상수도 사용 가능</li>
<li>break 구문이 없다. 따라서 코드가 짧고 실수로 break을 넣지 않았을 때의 발생하는 오류를 막을 수 있다.</li>
<li>match 표현식은 결과가 곧 값이다.</li>
<li>case <code>_</code> 는 switch 문에서 default 문과 비슷한 역할을 한다.</li>
<li><code>_</code>는 placeholder로 완전히 알려지지 않은 값을 표시하기 위해서 사용<pre><code class="language-javascript">val firstArg = if(!args.isEmpty)
                  args(0)
              else
                    &quot;&quot;
</code></pre>
</li>
</ul>
<p>val friend = {
    firstArg match {
        case &quot;salt&quot; =&gt; &quot;paper&quot;
        case &quot;chips&quot; =&gt; &quot;salsa&quot;
        case &quot;eggs&quot; =&gt; &quot;bacon&quot;
        case _ =&gt; &quot;huh?&quot;
    }</p>
<p>println(friend)
}</p>
<pre><code># break, continue 문 없이 살기
break, continue는 스칼라에서 자주 쓰이는 함수형 리터럴과 어울리지 않기 때문에 많이 사용되지 않는다.
break, continue를 사용하지 않고 프로그램을 제저할 수 있다.

* 모든 continue 문을 if로, 모든 break 문을 boolean 변수로 대체한다.
* 재귀함수를 이용한다.

```javascript
var i = 0
var foundIt = false

while(i&lt;args.length &amp;&amp; !foundIt) {
    if(!args(i).startsWith(&quot;-&quot;)) {
        if(args(i).endsWith(&quot;.scala&quot;))
            foundIt = true
    }
    i = i+1
}</code></pre><p>foundIt 변수가 boolean 변수의 역할을 하고 있다.
중첩 if문 내에서 continue로 인해 pass되는 코드들이 존재한다.</p>
<pre><code class="language-javascript">def searchFrom(i: Int): Int = {
    if(i&gt;=args.length) -1
    else if(args(i).startsWith(&quot;-&quot;)) searchFrom(i+1)
    else if(args(i).endsWith(&quot;.scala&quot;)) i
    else searchFrom(i+1)   
}

varl i = searcmFrom(0)</code></pre>
<ul>
<li>스칼라 컴파일러는 위 코드에 대해서 재귀함수를 만들지 않는다.</li>
<li>모든 재귀 호출은 꼬리 재귀 호출이기 떄문에 컴파일러는 while 루프와 비슷한 코드를 만들어내어 최적화를 한다.</li>
</ul>
<h1 id="변수-스코프">변수 스코프</h1>
<p>자바와 스칼라의 스코프 규칙이 자바와 거의 동일
한가지 차이점 -&gt; 스칼라의 내부 스코프에서 동일한 이름의 변수를 정의해도 된다.</p>
<p>자바는 바깥 스코프에 존재하는 변수와 동일한 이름의 변수를 안쪽 스코프에 선언하지 못한다.
하지만, 스칼라에서는 동일한 이름의 변수를 선언하면, 안쪽 스코프에서 바깥쪽 스코프에 있는 동일한 이름의 변수가 보이지 않는 것 처럼 동작 -&gt; 안쪽 변수가 바깥 스코프의 변수를 가렸다. (shadow)라고 표현</p>
<h1 id="명령형-스타일-코드-리펙토링">명령형 스타일 코드 리펙토링</h1>
<pre><code class="language-javascript">// 명령형 스타일로 코딩
def printMultiTable() = {
        var i = 1
        while(i&lt;=10) {
          var j = 1
          while(j&lt;=10) {
            val prod = (i*j).toString
            var k = prod.length
            while(k&lt;4) {
              print(&quot; &quot;)
              k += 1
            }
            print(prod)
            j += 1
          }
          println()
             i += 1
        }
      }


// 함수형 스타일로 코딩
def makeRowSeq(row:Int) = {
        for(col &lt;- 1 to 10) yield {
          val prod = (row * col).toString
          val padding = &quot; &quot; * (4 - prod.length)
          padding + prod
        }
      }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[6. 함수형 객체]]></title>
            <link>https://velog.io/@dev_jhjhj/6.-%ED%95%A8%EC%88%98%ED%98%95-%EA%B0%9D%EC%B2%B4</link>
            <guid>https://velog.io/@dev_jhjhj/6.-%ED%95%A8%EC%88%98%ED%98%95-%EA%B0%9D%EC%B2%B4</guid>
            <pubDate>Sun, 22 Aug 2021 15:00:57 GMT</pubDate>
            <description><![CDATA[<p>6장에서는 함수형 객체의 변경이 불가능한 상태 특징에 대해 설명한다. 이를 위해 책에서 분수(유리수)를 나타내는 클래스로 예를 든다.</p>
<h4 id="변경-불가능한-객체의-장단점">변경 불가능한 객체의 장단점</h4>
<h5 id="장점">장점</h5>
<ul>
<li>객체 생명주기 동안 상태가 변하지 않기 때문에 객체의 내부 값을 추론하기 쉽다.</li>
<li>내부 상태 변경이 불가능하기 때문에 메소드 인자로 전달을 자유롭게 할 수 있다.</li>
<li>내부 상태 변경이 불가능하기 때문에 여러 스레드에서 접근할 수 있다. (동기화 문제를 고려X)</li>
<li>변경 불가능한 객체는 안전한 해시 테이블 키로 사용될 수 있다. ( HashSet에 변경 가능한 객체를 키로 설정하고, 상태를 변경하게 되면 HashSet에서 해당 객체를 찾을 수 없는 경우가 발생한다. )</li>
</ul>
<h5 id="단점">단점</h5>
<ul>
<li>상태를 변경하기 위해서는 각 상태마다 새로운 객체를 만들어야 된다. 만약 객체 생성 비용이 높다면 성능상의 병목이 발생할 수 있다.</li>
</ul>
<h1 id="분수-클래스-명세">분수 클래스 명세</h1>
<ul>
<li>분수(rational number)는 n과 d가 정수, d&gt;0 일 때, n/d로 표시</li>
<li>n은 분자, d는 분모</li>
<li>분수의 사칙연산을 보면, 수학의 분수에는 변경 가능한 상태가 없음을 알 수 있다. (각각의 피연산자들을 연산하면, 새로운 분수가 결과값으로 나온다.) 원래 있던 두 분수 자체를 <strong>변경한 것은 아니다.</strong></li>
</ul>
<p>따라서 피연산자가 되는 각 분수는 서로 다른 Rational 객체이다. 어떤 두 Rational 객체를 더하면, 그 합에 해당하는 새로운 Rational 객체가 생긴다.</p>
<p>이번 장을 마치면, Rational 클래스를 사용해 다음과 같은 일을 할 수 있다.</p>
<pre><code class="language-scala">scala〉 val oneHalf = new Rational(l, 2)
oneHalf: Rational = 1/2

scala〉 val twoThirds = new Rational(2, 3)
twoThirds: Rational = 2/3

scala&gt; (oneHalf / 7) + (1 - twoThirds)
resO: Rational = 17/42
</code></pre>
<h1 id="rational-생성">Rational 생성</h1>
<p>Rational 객체는 변경 불가능한 객체라고 결정하였다. 
변경 불가능한 객체는 인스턴스에 필요한 정보(분수 객체라면 분자,분모 값)를 생성 시점에 모두 제공하여 설계한다.
→ 생성한 다음에 값을 수정할 수 있게 되면 변경 가능한 객체가 된다.</p>
<pre><code class="language-scala">class Rational(n: Int, d:Int)    // n, d는 클래스 파라미터</code></pre>
<p>◼️ 스칼라 컴파일러는 내부적으로 두 클래스 파라미터를 종합해서 클래스 파라미터와 같은 두 인자를 받는 주 생성자 (Primary Constructor)를 만든다.</p>
<p>◼️  Java에서는 클래스에 인자를 받는 생성자가 있지만, 스칼라에서는 클래스가 바로 인자를 받는다. 이러한 스칼라의 표기는 클래스 내부에서 파라미터를 바로 사용할 수 있어서 좀 더 간결하다.</p>
<p>필드를 정의하고 생성자의 인자를 필드로 복사하는 할당문 없어도 됨(자바와 다르게 따로 생성자를 만들지 않아도 된다. )</p>
<p>◼️  스칼라 컴파일러는 클래스 내부에 있으면서 필드나 메소드 정의에 들어 있지 않은 코드를 주 생성자 내부로 밀어 넣는다.
예를 들면, 디버깅용 메시지를 출력하는 코드로 작성할 수 있다.</p>
<pre><code class="language-scala">class Rational(n: Int, d:Int){
    println(&quot;Created &quot; + n + &quot;/&quot; + d)
}

====================
scala &gt; new Rational(1, 2)
Created 1/2
res0: Rational = Rational02591e0c9</code></pre>
<ul>
<li>스칼라 컴파일러는 println을 호출하는 이 코드를 Rational 클래스의 주 생성자에 넣는다. 따라서 Rational 인스턴스를 새로 생성할 때 마다 println을 통해 디버그 메시지 출력</li>
</ul>
<h1 id="tostring-메소드-다시-구현하기">toString 메소드 다시 구현하기</h1>
<p>기본적으로 Class는 java.lang.Object 클래스에 있는 toString 메소드를 호출하게 되는데, toString은 클래스 이름, </p>
<p>@ 표시, 16진수 숫자를 출력한다. ex) Rational@2591e0c9</p>
<p>이러한 메소드들은 override 수식자를 통해 재정의 할 수 있다.</p>
<pre><code class="language-scala">class Rational(n: Int, d: Int) {
    override def toString = n + &quot;/&quot; + d
}</code></pre>
<h1 id="선결-조건-확인">선결 조건 확인</h1>
<p>분수의 분모는 0일 수 없다. 
따라서 객체를 생성할 때 정보가 유효한지 확인이 반드시 필요
<strong>선결 조건을 정의해서 해결 가능</strong></p>
<ul>
<li>선결조건이란 ?
메소드나 생성자가 전달받은 값에 대한 제약
호출하는 이가 지켜야할 요구 조건</li>
</ul>
<blockquote>
<p>require 문을 통해 선결 조건을 만들 수 있다.</p>
</blockquote>
<pre><code class="language-scala">class Rational(n: Int, d: Int) {
    require(d != 0)     // d == 0 이라면 IllegalArgumentException 예외 발생
    override def toString = n + &quot;/&quot; + d
}</code></pre>
<h1 id="필드-추가">필드 추가</h1>
<h3 id="덧셈기능">덧셈기능</h3>
<p>Rational이 변경 불가능한 객체이기 때문에 add 메소드가 객체 자체의 값을ㄹ 변경해서는 안된다.
<strong>따라서 더한 결과값을 담는 새로운 Rational 객체를 반환해야 한다.</strong></p>
<pre><code class="language-scala">// 아래 코드는 컴파일 에러가 발생한다.
class Rational(n: Int, d: Int) {
    require(d != 0)
    override def toString = n + &quot;/&quot; + d

    def add(that: Rational): Rational =
        new Rational( n * that.d + that.n * d, d * that.d )
}</code></pre>
<ul>
<li>클래스 파라미터 n,d는 자신의 클래스 내부에서만 접근 가능</li>
<li>add 메소드와 같이 다른 객체의 클래스 파라미터에서는 접근 불가</li>
</ul>
<blockquote>
<p>따라서 변경 불가능한 객체의 클래스 파라미터에 접근하기 위해서는 필드를 따로 선언해줘야한다.</p>
</blockquote>
<pre><code class="language-scala">class Rational(n: Int, d: Int) {
    require(d != 0)
    val numer: Int = n
    val denom: Int = d
    override def toString = numer + &quot;/&quot; + denom

    def add(that: Rational): Rational =
        new Rational( numer * that.denom + that.numer * denom, denom * that.denom )
}</code></pre>
<h1 id="자기-참조">자기 참조</h1>
<p>현재 실행중인 메소드의 호출 대상 인스턴스에 대한 참조를 자기 참조(self reference) 라고 한다.
생성</p>
<pre><code class="language-java">class Rational(n: Int, d: Int) {
    require(d != 0)
    val numer: Int = n    // 분자
    val denom: Int = d    // 분모
    override def toString = numer + &quot;/&quot; + denom

    def add(that: Rational): Rational =
        new Rational( numer * that.denom + that.numer * denom, denom * that.denom )

     // 인자로 받은 Rational과 비교해 더 작은지 여부를 확인
     // 자기 스스로를 참조한다.
    def lessThan(that: Rational) =
        this.numer * that.denom &lt; that.numer * this.denom

    def max(that: Rational) =
        if (this.lessThan(that)) that else this
}</code></pre>
<ul>
<li><p>this.numer는 lessThan 메소드가 속한 객체의 분자를 나타낸다. </p>
</li>
<li><p>this를 생략할 수 없는 경우...</p>
<ul>
<li>max 함수에서 첫번째 this는 불필요한 중복 
→ this.lessThan(that) 을 lessThan(that)으로 변경해도 무방</li>
<li>하지만 두번째 this는 if 수행 결과가 false 일 경우 수행 결과로 반환할 값이다. 이건 생략하면 안됨(반환값이 사라짐)</li>
</ul>
</li>
</ul>
<h1 id="보조-생성자">보조 생성자</h1>
<p>하나의 클래스에 여러 생성자가 필요한 경우 → 보조생성자 사용
ex ) 분모는 1로 미리 정해져 있고, 분자만을 인자로 받는 생성자 </p>
<ul>
<li>스칼라의 보조 생성자는 <strong>def this(...)로 시작</strong></li>
</ul>
<pre><code class="language-java">class Rational(n: Int, d: Int){

    require(d!=0)
    val numer : Int = n
    val denom: Int = denom


    def this(n: Int) = this(n, 1)       // 보조 생성자

    override def toString = numer + &quot;/&quot; + denom
    def add(that: Rational) : Rational = 
        new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom
        )
}</code></pre>
<pre><code class="language-shell">scala&gt; val y = new Rational(3)
y: Rational = 3/1</code></pre>
<h1 id="비공개-필드와-메소드">비공개 필드와 메소드</h1>
<p>비공개 필드와 메소드가 있는 Rational 클래스</p>
<ul>
<li>비공개 변수 g</li>
<li>비공개 메소드 gcd</li>
</ul>
<blockquote>
<p>스칼라 컴파일러는 Rational 클래스의 필드 3개와 관련된 초기화 코드를 소스코드에 나온 순서대로 주 생성자에 위치 시킴 → g의 초기화 코드 gcd(n.abs, d,abs)는 다른 두 초기화 코드보다 먼저 수행됨</p>
</blockquote>
<pre><code class="language-java">class Rational(n: Int, d: Int){

    require(d!=0)
    private val g = gcd(n.abs, d,abs)       // 비공개 변수
    val numer : Int = n / g
    val denom: Int = denom / g


    def this(n: Int) = this(n, 1)       // 보조 생성자

    override def toString = numer + &quot;/&quot; + denom
    def add(that: Rational) : Rational = 
        new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom
        )

    override def toString = numer + &quot;/&quot; + denom

    private def gcd(a: Int, b:Int): Int =       // 비공개 메소드
        if(b == 0) a else gcd(b, a%b)
}

</code></pre>
<h1 id="연산자-정의">연산자 정의</h1>
<p>수학 기호로 기존의 add 메소드를 대체할 수 있다. 
즉, 연산자를 재정의할 수 있다.</p>
<pre><code class="language-java">class Rational(n: Int, d: Int){

    require(d!=0)
    private val g = gcd(n.abs, d,abs)       // 비공개 변수
    val numer : Int = n / g
    val denom: Int = denom / g


    def this(n: Int) = this(n, 1)       // 보조 생성자

    override def toString = numer + &quot;/&quot; + denom

    def + (that: Rational) : Rational = 
        new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom
        )        // + 연산자 정의

    def * (that: Rational) : Rational = 
        new Rational(numer * that.numer, denom * that.denom)        // * 연산자 정의

    override def toString = numer + &quot;/&quot; + denom

    private def gcd(a: Int, b:Int): Int =       // 비공개 메소드
        if(b == 0) a else gcd(b, a%b)
}
</code></pre>
<pre><code class="language-shell">scala&gt; val x = new Rat onal(l, 2)
x: Rational = 1/2

scala&gt; val y = new Rat onal(2, 3)
y: Rational = 2/3

scala&gt; x + y
res7: Rational = 7/6</code></pre>
<h1 id="스칼라의-식별자">스칼라의 식별자</h1>
<ul>
<li>영숫자 식별자<ul>
<li>문자나 밑줄<code>(_)</code>로 시작, 두 번째 글자부터는 문자, 숫자, 밑줄 모두 사용 가능</li>
<li>특수문자 <code>$</code> 도 문자로 취급, 하지만 예약문자이기 떄문에 사용 X</li>
<li>자바의 관례에 따라 Camel-Case로 표기한다. ( ex) toString, HashSet )</li>
<li>필드, 메소드 인자, 지역 변수, 함수는 소문자로 시작</li>
<li>클래스는 대문자로 시작</li>
<li>상수는 첫 글자만 대문자로 표기 (자바의 관례와 다르다)</li>
<li>스칼라에서는 상수를 첫 글자만 대문자로 표기한다.
<code>X_OFFSET</code> 같은 자바 스타일의 상수 표기는 스칼라에서는 XOffset처럼 Camel-Case를 사용하는 것이 관례</li>
</ul>
</li>
</ul>
<ul>
<li><p>연산자 식별자</p>
<ul>
<li>하나 이상의 연산자 문자로 이루어짐</li>
<li>스칼라 컴파일러는 내부적으로 <code>$</code>를 사용해 연산자 식별자를 해체하여 적합한 자바 식별자로 다시 만드는 작업을 수행</li>
<li>식별자 <code>:-&gt;</code> 는 내부적으로 <code>$colon$minus$greater</code>로 바뀐다.</li>
</ul>
</li>
<li><p>혼합 식별자</p>
<ul>
<li>영문자와 숫자로 이뤄진 식별자의 뒤에 밑줄이 오고, 그 뒤에 연산자 식별자가 온다.</li>
<li><code>unary_+</code>는 단항 연산자인 +를 정의하는 메소드의 이름</li>
</ul>
</li>
<li><p>리터럴 식별자</p>
<ul>
<li>역따옴표<code>｀...｀</code>로 둘러싼 임의의 문자열</li>
<li>런타임이 인식할 수 있는 어떤 문자열이라도 역따옴표 사이에 넣을 수 있다.</li>
<li>스칼라 예약어조차도 리터럴 식별자로 정의할 수 있다.
ex )  yield는 스칼라의 예약어이므로 자바의 Thread 클래스에 있는 정적 메소드 yield에 접근하고 싶을 때, Thread.yield()와 같이 사용할 수 없다.
Thread.<code>｀yield()｀</code> 처럼 메소드 이름을 지정하면 사용할 수 있다.</li>
</ul>
</li>
</ul>
<h1 id="메소드-오버로드">메소드 오버로드</h1>
<p><strong>오버로드한 여러 메소드를 추가한 Rational 클래스</strong>
메소드 호출 시점에 컴파일러는 오버로드한 메소드 중 인자의 타입이 일치하는 메소드를 선택 (자바와 유사)</p>
<pre><code class="language-java">class Rational(n: Int, d: Int){

    require(d!=0)
    private val g = gcd(n.abs, d,abs)       // 비공개 변수
    val numer : Int = n / g
    val denom: Int = denom / g


    def this(n: Int) = this(n, 1)       // 보조 생성자

    override def toString = numer + &quot;/&quot; + denom

    def + (that: Rationa): Rational = 
    new Rational(numer * that.denom + that.numer * denom, denom * that.denom)

    def + ( : Int): Rational =
    new Rational(numer + i * denom, denom)

    def - (that: Rational): Rational =
     new Rational(numer * that.denom - that.numer * denom, denom * that.denom)

    def - ( : Int): Rational =
    new Rational(numer - i * denom, denom)

    def * (that: Rational): Rational =
    new Rational(numer * that.numer, denom * that.denom)

    def * ( : Int): Rational =
    new Rat onal(numer * i, denom)

    def / (that: Rational): Rational =
    new Rational(numer * that.denom, denom * that.numer)

    def / (i: Int): Rational =
    new Rational(numer, denom * i)
    override def toString = numer + &quot;/&quot; + denom

    private def gcd(a: Int, b:Int): Int =       // 비공개 메소드
        if(b == 0) a else gcd(b, a%b)
}
</code></pre>
<h1 id="암시적-타입-변환">암시적 타입 변환</h1>
<pre><code class="language-java">val x = new Rational(2, 3)
x * 2   // Int를 인자로 받는 * 메소드를 정의하였기 때문에 사용가능
2 * x   // Error 발생</code></pre>
<ul>
<li>2 * x는 <code>2.*(x)</code>와 같기 때문에 결국 Int의 메소드를 호출해 문제가 발생</li>
<li>스칼라는 필요할 경우 암시적 타입 변환을 지정해 문제를 해결 가능</li>
</ul>
<pre><code class="language-java">// int를 Rational로 변환하는 메소드 정의
// implicit 수식자는 컴파일러에서 몇몇 상황에 해당 메소드를 활용해 변환을 수행하라고 알려줌
implicit def inToRational(x: Int) = new Rational(x)

val r = new Rational(2,3)
println(2 * r)
===========================================
4/3</code></pre>
<ul>
<li><p>암시적 타입 변환이 동작하기 위해서는 해당 스코프 안네 변환 메소드가 존재해야함</p>
</li>
<li><p>만약, 암시적 타입 변환 메소드를 Rational 클래스 내부에 정의하였다면, 메소드를 호출한 스코프에는 존재하지 않으므로 변환이 이뤄지지 않음</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[5. 기본 타입과 연산]]></title>
            <link>https://velog.io/@dev_jhjhj/5.-%EA%B8%B0%EB%B3%B8-%ED%83%80%EC%9E%85%EA%B3%BC-%EC%97%B0%EC%82%B0</link>
            <guid>https://velog.io/@dev_jhjhj/5.-%EA%B8%B0%EB%B3%B8-%ED%83%80%EC%9E%85%EA%B3%BC-%EC%97%B0%EC%82%B0</guid>
            <pubDate>Wed, 18 Aug 2021 15:21:48 GMT</pubDate>
            <description><![CDATA[<h1 id="51-기본타입">5.1 기본타입</h1>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/4696126e-22aa-4489-8583-edcaab6434a7/image.png" alt=""></p>
<ul>
<li>java.lang 패키지에 있는 String 을 제외하면 모든 타입은 scala 패키지의 멤버<h1 id="52-리터럴">5.2 리터럴</h1>
</li>
<li>기본 타입은 모두 리터럴(literal)로 적을 수 있다.<h5 id="정수-리터럴">정수 리터럴</h5>
</li>
<li>Int, Long, Short, Byte에 사용</li>
<li>10진수, 16진수 리터럴이 있다.</li>
</ul>
<h5 id="부동소수점-리터럴">부동소수점 리터럴</h5>
<ul>
<li>0~9 로 이루어져있다.</li>
<li>소수점이 있을 수 있으며, 마지막에 E나 e 다음에 지수 부분(exponent)이 있을 수 있다.
ex ) 1.2345e1은 1.234에 10^1 을 곱한 12.345 이다.</li>
</ul>
<h5 id="문자-리터럴">문자 리터럴</h5>
<ul>
<li>작음따옴표 안에 유니코드 문자를 넣어서 만든다.<pre><code>scala&gt; val a = &#39;A&#39;
a: Char = A
</code></pre></li>
</ul>
<p>scala&gt; val d = &#39;\u0041&#39;
d: Char = A
scala〉 val f = &#39;\u0044&#39;
f: Char = D</p>
<pre><code>
![](https://images.velog.io/images/dev_jhjhj/post/634f7274-4850-43c2-8911-b4d5831ab53d/image.png)

##### 문자열리터럴
문자열 리터럴은 큰따옴표 두개(&quot;&quot;)로 둘러싼 문자</code></pre><p>scala&gt; val escapes = &quot;\&quot;&#39;&quot;
escapes: String = V&quot;</p>
<p>println(&quot;&quot;&quot;Welcome to Ultamix 3000.
        Type &quot;HELP&quot; for help.&quot;&quot;&quot;)</p>
<p> Welcome to Ultamix 3000.
        Type &quot;HELP&quot; for help.  </p>
<p>// stripMargin을 문자열에 넣으면
println(&quot;&quot;&quot;|Welcome to Ultamix 3000.
        |Type &quot;HELP&quot; for help.&quot;&quot;&quot;)</p>
<p>// 다음과 같이 출력된다.
Welcome to Ultamix 3000.
Type &quot;HELP&quot; for help. </p>
<pre><code>
##### 심볼 리터럴
* 심볼(Symbol)은 작은 따옴표 + 문자열로 표현한다. ex) &#39;ident
* 작음 따옴표 뒤에 오는 식별자는 알파벳, 숫자 혼합 아무거나 가능
* 심볼 리터럴을 scala.Symbol이라는 클래스의 인스턴스로 매핑

* 스칼라에서는 선언하지 않은 필드 식별자를 메소드에 전달해 연산을 수행할 수 없다.
* 대신에, 심볼 리터럴을 넘기면 거의 비슷하게 표현 가능하다.

```scala
def updateRecordByName(r: Symbol, value: Any) = {
    // 코드
    println(r.name)
}

// Symbol을 사용하지 않은 경우 Error
// updateRecordByName(favoriteAlbum, &quot;OK Computer&quot;)

// Symbol을 사용하는 경우
updateRecordByName(&#39;favoriteAlbum, &quot;OK Computer&quot;)

===================================================
favoriteAlbum

// 같은 Symbol 리터럴을 2번 이상 사용하면, 사용된 Symbol은 서로 완전 동일한 객체를 참조한다.(Intern)

val a = &#39;hi
val b = &#39;hi
println(a eq b)
================
true</code></pre><h5 id="불리언-리터럴">불리언 리터럴</h5>
<p>Boolean 타입의 리터럴에는 true와 false</p>
<h1 id="53-문자열-인터폴레이션">5.3 문자열 인터폴레이션</h1>
<p>스칼라는 문자열 인터폴레이션을 위한 유연한 메커니즘을 포함</p>
<ul>
<li>문자열 리터럴 내부에 표현식을 내장시켜 간결하고 읽기 쉬운 코드 작성 가능</li>
</ul>
<pre><code class="language-javascript">val name = &quot;reader&quot;
println(s&quot;Hello, $name!&quot;)           // &quot;Hello, &quot; + name + &quot;!&quot;
println(s&quot;The answer is ${6*7}.&quot;    // &quot;The answer is &quot; + &quot;42&quot; + &quot;.&quot;
===============================
Hello, reader!
The answer is 42.</code></pre>
<ul>
<li>문자열 인터폴레이터 사용 방법 : 문자열 시작하는 따옴표 직전 s를 붙인다.</li>
<li>s 인터폴레이터는 내장된 각 표현식( ${}  내부식 )을 평가하고 내장된 표현식을 toString의 결과로 대치해준다.</li>
</ul>
<p>스칼라는 raw와 f라는 두 가지 인터폴레이터 추가 제공</p>
<ul>
<li>raw 문자열 인터폴레이터<pre><code>  * s처럼 작동하지만, 문자열 이스케이프 시퀀스 인식 못함</code></pre></li>
<li>f 문자열 인터폴레이터<pre><code>  * 내장된 표현식에 대해 printf 함수 스타일의 형식 지정을 사용 가능
  ex) 내장시킨 식 바로 다음에 %를 넣고, 그 뒤에 형식 지정 명령 추가</code></pre></li>
</ul>
<pre><code class="language-javascript">println(raw&quot;No\\\\escape!&quot;)
println(f&quot;${math.Pi}%.5f&quot;)
===============================
NO\\\\escape!
3.14159</code></pre>
<h1 id="54-연산자는-메소드">5.4 연산자는 메소드</h1>
<p>기본 타입의 풍부한 연산자 제공
예를들어, <em>1+2는 1.</em>(2)와 같다. 다시 말해, Int 클래스에는 Int를 인자로 받아 Int를 결과로 돌려주는 +라는 이름의 메소드가 있다.</p>
<pre><code class="language-javascript">// + 연산자
val sum = 1+2
val sumMore = 1.+(2)
=======================
3
3</code></pre>
<p>오버로드를 통해 파라미터 타입을 다르게 한 메소드가 다소 존재
ex ) Int에는 Long을 받고 Long 타입을 반환하는 + 메소드가 존재</p>
<pre><code class="language-javascript">// 연산자 오버로드
def +(x: Byte): Int
def +(x: Short): Int
def +(x: Char): Int
def +(x: Int): Int
def +(x: Long): Long
def +(x: Float): Float
def +(x: Double): Double</code></pre>
<blockquote>
<p>모든 메소드는 연산자가 될 수 잇다.
스칼라에서 연산자는 문법적으로 특별한 것이 아니다. 어떤 메소드든 연산자가 될 수 있다. 메소드가 연산자 역할을 할지 여부는 프로그래머가 메소드를 사용하는 방법에 따라 결정된다.</p>
</blockquote>
<blockquote>
<p>스칼라는 중위(infix) 연산자, 전위(prefix), 후위(postfix) 연산자 존재
전위와 후위는 중위에 다르게 피연산자가 하나이다. 이는 단항(unary) 연산자를 뜻한다.
단항 연산자도 실제로 메소드 호출을 간략하게 한 것</p>
</blockquote>
<pre><code class="language-javascript">// 단항연산자
print(-7)   // 스칼라는 7.unary_-를 호출한다.
=======================
-7</code></pre>
<ul>
<li><p>전위 연산자로 쓰일 수 있는 식별자는 (+, -, !, ~)     4가지 뿐</p>
<pre><code class="language-javascript">// 전위 연산자
def main(args: Aarray[String]): Unit = {
  val test = new Test(5)
  println(+test)
  println(-test)
  println(*test)  // not found Error
}
class Test(val num: Int) {
  def unary_+(): Int = if(num &gt; 0) num else -num
  def unary_-(): Int = if(num &gt; 0) -num else num
  def unary_*(): Int = num * num
}</code></pre>
</li>
<li><p>후위 연산자는 인자를 취하지않는 메소드를 &#39;.&#39;이나 괄호 없이 호출하는 경우
스칼라에서는 메소드 호출 시 빈 괄호 생략</p>
</li>
</ul>
<pre><code class="language-javascript">val s = &quot;Hello, world!!&quot;
println(s)
println(s.toLowerCase)  // 괄호 생략
println(s toLowerCase)  // (.) 생략
=================================
Hello, world!!
hello, world!!
hello, world!!</code></pre>
<h1 id="55-산술-연산">5.5 산술 연산</h1>
<ul>
<li>모든 수 타입에 대해 더하기(1), 빼기(-)，곱하기(★)，나누기(/)，나머지(%)를 중위 연산자 를 사용 가능</li>
</ul>
<h1 id="56-관계-연산과-논리-연산">5.6 관계 연산과 논리 연산</h1>
<ul>
<li><p>수 타입을 크다(〉)，작다(&lt;)，크거나 같다(&gt;=)，작거나 같다(이라는 관계 연산자를 사용 해 비교 가능, 각 연산자는 Boolean 값을 결과로 내놓고, &#39;!&#39; 연산자를 사용해 Boolean 값을 반전시킬 수 있다.</p>
</li>
<li><p>논리 연산으로는 논리곱 ( &amp;&amp;, &amp; ), 논리합( ||, | )이 있다. 각각은 두 Boolean 피연산자를 취하며 Boolean 결과값을 반환한다.</p>
<ul>
<li>논리연산은 Java와 마찬가지로 숏 서킷 연산이다. 논리연산자로 구성된 표현식은 결과를 결정하기 위해 필요부분만 값을 계싼</li>
</ul>
<p>ex ) &amp;&amp; 연산에서 왼쪽의 표현식이 false이면, 리턴값을 false이기 때문에 오른쪽 표현식은 계산 X</p>
</li>
</ul>
<pre><code class="language-scala">def main(args: Aarray[String]): Unit = {
    left() &amp;&amp; right()   // 이미 left()에서 false라는 결과가 도출되었기 때문에 right() 메소드는 호출되지 않는다.
    left() &amp; right()    // 좌항의 값과 관계없이 우항의 표현식을 항상 평가하고 싶다면, &amp;와 |를 대신 사용하면 된다.
}
def left() = {
    println(&quot;left&quot;)
    false
}
def right() = {
    println(&quot;right&quot;)
    true
}
=======================================
left
left
right</code></pre>
<p>참고 ) 스칼라 메소드에는 인자 계산을 미루는 기능이 있어 쇼트 서킷 연산을 수행할 수 있다. 심지어 사용할 필요가 없으면 인자를 전혀 계산하지 않을 수도 있다. 이런 기능을 이름에 의한 호출 ( call by name ) 파라미터라 부르며, 9.5절에서 설명한다.</p>
<h1 id="57-비트-연산">5.7 비트 연산</h1>
<ul>
<li>비트 연산자는 비트곱(&amp;), 비트합(|), 비트 배타합(^)이 있다.</li>
<li>단항 비트 반전 연산자(~)는 피연산자의 각 비트의 0과 1을 반대로 바꾼다.</br></li>
<li>스칼라의 shift 메소드 3가지 제공
(왼쪽 시프트나 부호 없는 오른쪽 시프트는 시프트를 하면서 비트가 밀려난 빈 자리에 0을 채워 넣음)<pre><code>  -  왼쪽 쉬프트  &lt;&lt;
  -  오른쪽 쉬프트  &gt;&gt;
  -  부호없는 오른쪽 쉬프트 &gt;&gt;&gt;</code></pre></li>
</ul>
<pre><code class="language-scala">println(1&amp;2)    // 1(0001) 2(0010) -&gt; 0(0000)
println(1|2)    // 1(0001) 2(0010) -&gt; 3(0011)
println(1^3)    // 1(0001) 3(0011) -&gt; 2(0010)
println(~1)     // 1(0001) -&gt; 11111111111111111111111111111110
println(-1 &gt;&gt; 31) // -1을 오른쪽으로 31비트만큼 이동한다.
println(-1 &gt;&gt;&gt; 31) // -1을 오른쪽으로 시프트하면서 0을 채워넣는다.
=======================================
0
3
2
-2
-1
1</code></pre>
<h1 id="58-객체-동일성">5.8 객체 동일성</h1>
<p>두 객체가 같은 지 비교 ==, !=
스칼라는 == 를 사용하면, 먼저 좌항이 null인지 검사, 좌항이 null이 아니라면 해당 객체의 equals 메소드를 호출</p>
<pre><code class="language-scala">println(1==2)
println(1!=2)
println(List(1,2,3) == List(1,2,3))     // 모든 객체에 적용할 수 있다.
println(List(1,2,3) == List(4,5,6))
println(1==1.0)     // 타입이 각기 다른 두 객체도 비교할 수 있다.
println(List(1,2,3) == &quot;hello&quot;)
println(List(1,2,3) == null)    // null 객제와도 비교할 수 있다.
=======================================
false
true
true
false
true
false</code></pre>
<blockquote>
<p>스칼라의 ==는 자바와 어떻게 다른가 ?</p>
<blockquote>
<p>자바에서 ==는 참조타입과 원시타입 비교 가능
    - 자바의 ==은 원시타입에서 값이 같은지 비교, 참조 타입은 참조가 같은지(즉, heap) 메소리에서 같은 객체를 가리키고 있는지 비교
스칼라의 참조 동일성 검사 기능은 eq, 그 역은 ne 이다. 하지만 eq와 ne는 자바 객체에 직접 맵핑한 객체에만 사용 가능</p>
</blockquote>
</blockquote>
<h1 id="59-연산자-우선순위와-결합-법칙">5.9 연산자 우선순위와 결합 법칙</h1>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/c3e6cce4-ab77-4e18-8e03-a6072bd100cd/image.png" alt="">
위의 표는 메소드 첫 글자에 따른 연산자의 우선순위를 높은 쪽부터 낮은 쪽으로 보여준다.
(같은 줄에 있는 연산자는 우선순위가 같다.)</p>
<pre><code class="language-scala">print(2 &lt;&lt; 2 + 2)  // 2 &lt;&lt; (2+2)
print(2 + 2 &lt;&lt; 2)  // (2+2) &lt;&lt; 2
===============================
32
16</code></pre>
<blockquote>
<p>우선순위 규칙의 한가지 예외는 할당 연산자(=)이다.
어떤 연산자가 등호로 끝나고, 그 연산자가 비교 연산자( &lt;=, &gt;=, ==, != )가 아니면 해당 연산자의 우선순위는 다른 모든 연산자보다 우선순위가 낮다.</p>
</blockquote>
<pre><code class="language-scala">val x *= y+1    // x *= (y+1)</code></pre>
<ul>
<li><p>우선순위가 같은 연산자가 표현식에서 나란히 나올 경우 결합법칙을 통해 어떻게 연산자를 묶을지 결정한다. </p>
</li>
<li><p>메소드 이름이 &#39;:&#39;으로 끝나면 오른쪽부터 왼쪽으로, &#39;:&#39;이 아닌 글자로 끝나면 왼쪽에서 오른쪽으로 짝을 이어간다.
a ::: b ::: c → a ::: ( b ::: c )
a * b * c → ( a * b ) * c</p>
</li>
<li><p>괄호를 사용하여 명확하게 해주는 것이 가장 좋다.</p>
</li>
</ul>
<h1 id="510-풍부한-래퍼">5.10 풍부한 래퍼</h1>
<p><img src="https://images.velog.io/images/dev_jhjhj/post/cd667f42-4f4b-4232-b4bb-26486c4adc36/image.png" alt="">
<img src="https://images.velog.io/images/dev_jhjhj/post/239ae886-311c-4d5f-a6fe-d0a62f7540ef/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>