<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>메가승한</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 01 Dec 2022 14:13:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. 메가승한. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/seunghan-baek" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[자료구조] 힙과 트리]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%9E%99%EA%B3%BC-%ED%8A%B8%EB%A6%AC</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%9E%99%EA%B3%BC-%ED%8A%B8%EB%A6%AC</guid>
            <pubDate>Thu, 01 Dec 2022 14:13:54 GMT</pubDate>
            <description><![CDATA[<h1 id="힙과-트리">힙과 트리</h1>
<blockquote>
<p>참고</p>
<ul>
<li><a href="https://yoongrammer.tistory.com/68">https://yoongrammer.tistory.com/68</a></li>
<li><a href="https://www.boostcourse.org/cs204/joinLectures/212812?isDesc=false">https://www.boostcourse.org/cs204/joinLectures/212812?isDesc=false</a></li>
</ul>
</blockquote>
<h2 id="트리">트리</h2>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212012257672.png" alt="image"></p>
<h3 id="트리란">트리란</h3>
<p>노드들이 나무 가지처럼 연결된 비선형 계층적 자료구조이다.</p>
<p>트리는 트리 내의 다른 하위 트리가 있고 그 하위 트리 안에는 또 다른 트리가 있는 재귀적 자료구조이다.</p>
<h3 id="트리의-용어들">트리의 용어들</h3>
<ul>
<li>루트 노드(root node) - 부모가 없는 노드이다. 트리는 하나의 루트 노드를 가지고 있다. 최상위 레벨에 있다.</li>
<li>간선(edge, branch) - 각 노드를 잇는 연결 고리</li>
<li>단말 노드(leaf node) - 자식이 없는 노드이다. 최하위 레벨에 있다.</li>
<li>형제(sibling) - 같은 부모를 가지고 있는 노드들의 집합을 형제 관계라고 한다. 예를 들어 [A - B] [A -C]라는 관계에서 A가 부모 노드라면 B와 C는 형제 관계이다.</li>
<li>레벨(level) - 트리의 특정 깊이를 가지는 노드의 집합이다.</li>
<li>깊이(depth) - 루트에서 어떤 노드까지의 간선 수이다.</li>
<li>넓이(WIdth) - 어떠한 레벨에 있는 노드의 개수이다. 레벨 2의 넓이는 4이다. </li>
<li>크기(size) - 자신을 포함한 모든 자식 노드의 개수이다.</li>
<li>높이(height) - 루트 노드부터 가장 먼 단말 노드까지 가는데 거치게 되는 간선의 개수이다. 높이는  전체 노드의 개수를 알고 있으면 log2(n+1) - 1의 공식으로 구할 수 있다.<ul>
<li>레벨 0에서의 노드는 1개이다. 위 식에 대입하면 log2(0 + 1) - 1 = 0이 된다. 따라서 높이는 0이다.</li>
<li>레벨 1에서의 노드는 3개이다. 위 식에 대입하면 log2(3 + 1) - 1 = 1이 된다. 따라서 높이는 1이다.</li>
<li>..</li>
<li>레벨 3에서의 노드는 15개이다. 위 식에 대입하면 log2(15 + 1) -1 = 3이 된다. 따라서 높이는 3이다.</li>
</ul>
</li>
<li>노드 차수(degree) - 각 노드의 자식 수(하위 트리 개수 / 간선 수)이다.</li>
<li>트리의 차수(degree of tree) - 트리의 최대 차수이다. </li>
</ul>
<h3 id="트리의-특징">트리의 특징</h3>
<ul>
<li>하나의 루트 노드와 0개 이상의 하위 트리로 구성된다.</li>
<li>데이터를 순차적으로 저장하지 않기 때문에 비선형 자료구조이다.</li>
<li>트리 내에 또 다른 트리가 있는 재귀적 자료구조이다.</li>
<li>단순 순환을 갖지 않고 연결된 무방향 그래프 구조이다.<ul>
<li>그래프의 한 종류로 &#39;최소 연결 트리&#39;라고도 부른다.</li>
</ul>
</li>
<li>노드 간에 부모 자식 관계를 갖는 계층형 자료구조이다. 모든 자식은 하나의 부모 노드를 가진다.</li>
<li>노드가 n개인 트리는 항상 n -1개의 간선을 가진다.</li>
<li>순회는 Pre-order, In-order, Post-order로 이루어진다.<ul>
<li>전위 순회(Pre-order) - 루트 노드에서 시작하여 왼쪽 자식 노드에 갔다가 오른쪽 자식 노드로 가는 순회 방법이다. 다른 모든 노드를 지나기 전에 루트 노드를 방문하기 때문에 이름에 전(Pre)이 들어간다.</li>
<li>중위 순회(In-order) - 왼쪽 자식 노드에서 시작하여 루트 노드에 갔다가 오른쪽 자식 노드로 가는 순회 방법이다.</li>
<li>후위 순회(Post-order) - 왼쪽 자식 노드에서 시작하여 오른쪽 자식 노드에 갔다가 루트 노드로 가는 순회 방법이다. </li>
</ul>
</li>
</ul>
<h3 id="트리의-종류">트리의 종류</h3>
<ul>
<li><p>이진 트리</p>
<ul>
<li>각 노드가 최대 두 개의 자식을 갖는 트리</li>
</ul>
</li>
<li><p>이진 탐색 트리 (Binary Search Tree)</p>
<ul>
<li>순서화된 이진 트리 </li>
<li>노드의 왼쪽 자식은 부모의 값보다 작은 값을 가지고, 오른쪽 자식 노드는 부모의 값보다 커야 한다.</li>
</ul>
</li>
<li><p>편향 트리(Skew tree)</p>
<ul>
<li>모든 노드들이 자식을 하나만 가진다.</li>
<li>왼쪽 방향으로만 하나씩만 가질 때 left skew tree, 마찬가지로 오른쪽으로만 하나씩 가질 때는 right skew tree이다.</li>
</ul>
</li>
<li><p>균형 트리(Balanced Tree, B-Tree)</p>
<ul>
<li>m원 탐색 트리에서 높이 균형을 유지하는 트리</li>
<li>height-balanced m-way tree라고도 한다.</li>
</ul>
</li>
<li><p>원 탐색 트리(M-way Search Tree)</p>
<ul>
<li>최대 m개의 서브 트리를 가지는 탐색 트리</li>
<li>이진 탐색 트리의 확장된 형태. 높이를 줄이기 위해 사용한다.</li>
</ul>
</li>
<li><p>완전 이진 트리(Full Binary Tree)</p>
<ul>
<li>트리의 모든 높이에서 노드가 꽉 차 있는 이진 트리, 마지막 레벨을 제외하고 모든 레벨이 완전히 채워져 있다.</li>
<li>마지막 레벨은 꽉 차 있지 않아도 되지만, 노드가 왼쪽에서 오른쪽으로 채워져야 한다.</li>
<li>마지막 레벨 h에서 (1 ~ 2h-1)개의 노드를 가질 수 있다. </li>
</ul>
</li>
<li><p>정 트리(Full Binary Tree) </p>
<ul>
<li>모든 트리가 0개 또는 2개의 자식 노드를 갖는 트리</li>
</ul>
</li>
<li><p>포화 이진 트리(Perfect Binary Tree)</p>
<ul>
<li>정 트리면서 완전 이지 트리인 경우</li>
<li>모든 말단 노드는 같은 높이에 있어야 하며 마지막 레벨에서 노드 개수는 최대 개수여야 한다.</li>
<li>리프를 제외한 모든 노드가 두개의 또 다른 자식 노드를 가져야 한다.</li>
<li>모든 리프 노드는 같은 레벨에 있어야 한다.</li>
<li>노드의 개수 = 2^(height -1)개여야 한다. </li>
</ul>
</li>
</ul>
<h3 id="중위-표기법-후위-표기법">중위 표기법, 후위 표기법</h3>
<p>컴퓨터는 괄호, 연산자의 우선순위를 따로 처리하지 않고 왼쪽에서 오른쪽으로 표기된 순서대로 처리하면 결과가 올바르게 나오는 후위 표기법을 사용한다.</p>
<ul>
<li>중위 표기법 : 연산자를 피연산자의 가운데에 표기한다.<ul>
<li>예) 2 + 3</li>
</ul>
</li>
<li>후위 표기법 : 연산자를 피연산자의 뒤에 표기한다.<ul>
<li>예) 23 +</li>
</ul>
</li>
</ul>
<h3 id="트리의-구현">트리의 구현</h3>
<ul>
<li>트리 - 노드(부모 노드에 접근하는 구현은 제외함)</li>
</ul>
<pre><code class="language-java">class Node&lt;E&gt; {
  E data;
  Node&lt;E&gt; left, right;
  public Node(E obj) {
        this.data = obj;
    left = null;
    right =null;
  }
}</code></pre>
<ul>
<li>트리 - 새로운 노드 추가(재귀 구현)<ul>
<li>루트에서부터 시작한다.</li>
<li>트리의 규칙에 따라 내려간다</li>
<li>null인 부분에 새로운 노드를 추가한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">// 메서드 오버로딩 사용
public void add(E obj) {
  if(root == null) {
        root = new Node&lt;E&gt;(obj);
  } else {
    add(obj, root);
  }
}

private void add(E obj, Node&lt;E&gt; node) {
    if(((Comparable&lt;E&gt;)obj).compareTo(node.data) &gt;= 0) {
    // 오른쪽으로 이동
    if(node.right == null) {
      node.right = new Node&lt;E&gt;(obj);
      return;
    }
    return add(obj, node.right); 
  }
  // 왼쪽으로 이동
  if(node.left == null) {
    node.left = new Node&lt;E&gt;(obj);
    return;
  }
  return add(obj, node.left);
}</code></pre>
<ul>
<li>트리 - contains<ul>
<li>루트에서부터 찾는다.</li>
<li>트리의 규칙에 따라 내려간다</li>
<li>그 요소를 찾으면 true를 반환하고 null인 노드에 다다르면  false를 반환한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public boolean contains(E obj) {
  return contains(obj, root);
}

private boolean contains(E obj, Node&lt;E&gt; node) {
  // 트리의 끝이 null일 때
  if(node == null) {
    return false;
    }
    // 찾으려는 값과 일치할 때 
  if(((Comparable&lt;E&gt;)obj).compareTo(node.data) == 0) {
    return true;
  }
  if(((Comparable&lt;E&gt;)obj).compareTo(node.data) &gt; 0) {
    // 오른쪽으로 이동
    return contains(obj, node.right);
  }
  // 왼쪽으로 이동
     return contains(obj, node.left);
}</code></pre>
<ul>
<li><p>트리 - remove</p>
<p>자식 노드의 개수에 따라 트리의 특정 요소를 제거하는 방법은 다음과 같다.</p>
<ol>
<li><p>리프 노드를 제거할 경우</p>
<ul>
<li>제거할 노드의 부모 노드의 포인터를 null로 설정한다.</li>
</ul>
</li>
<li><p>자식 노드가 하나인 노드를 제거할 경우</p>
<ul>
<li>제거할 노드의 부모 노드의 포인터를 제거할 노드의 자식 노드로 향하게 한다. 주의할 점은 부모 노드에서 사용했던 포인터와 같은 포인터(left || right)를 사용해야 한다는 것이다.</li>
</ul>
</li>
<li><p>자식 노드가 2개인 노드를 제거할 경우</p>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041603846.png" alt="스크린샷 2022-12-04 16.02.55"></p>
<ul>
<li>중위 후속자 또는 중위 선임자 중 하나와 자리를 바꾼 후 그 리프 노드를 제거한다.</li>
</ul>
<blockquote>
<ul>
<li><p>중위 후속자(In order Successsor)</p>
<p>: 제거할 노드의 왼쪽(작은 값) 자식 노드 중 그 값이 제일 큰 노드이다. 중위 순회 방식으로 노드를 탐색할 때 루트 노드를 방문하기 직전에 만나게 되는 노드이기 때문에 중위 후속자라고 부른다.</p>
</li>
<li><p>중위 선임자(In order Prodessor)</p>
<p>: 제거할 노드의 오른쪽(큰 값)자식 노드 중 그 값이 가장 작은 노드이다.</p>
</li>
</ul>
</blockquote>
<ul>
<li><p>제거 흐름</p>
<ol>
<li><p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041632625.png" alt="스크린샷 2022-12-04 16.32.01"></p>
</li>
</ol>
<p>2.</p>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041633273.png" alt="스크린샷 2022-12-04 16.33.40"></p>
<ol start="3">
<li><p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041634091.png" alt=""></p>
</li>
</ol>
</li>
<li><p>삭제를 할 때 중위 후속자 또는 중위 선임자 노드가 제거 대상 노드의 자리와 스왑하는 이유는 다음과 같다. 트리는 부모 노드보다 작은 값을 부모의 왼쪽에, 부모 노드 보다 큰 값은 오른쪽에 위치 시킨다. 자식 노드가 2개인 부모 노드를 제거할 땐 위 트리의 규칙을 지켜주어야 하는데 이 규칙을 만족시키는 노드가 중위 후속자 또는 중위 후임자이다.</p>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<h3 id="트리의-회전">트리의 회전</h3>
<pre><code class="language-java">int arr[] = {2, 4, 6, 8, 10, 12, 14, 16, 18};</code></pre>
<p>위 배열을 트리로 표현해보자</p>
<ul>
<li>1번 트리</li>
</ul>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041721793.png" alt=""></p>
<ul>
<li><p>2번 트리</p>
<img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041724159.png" alt="스크린샷 2022-12-04 17.22.48" style="zoom:50%;" />

</li>
</ul>
<p>위 두 트리의 차이는 균형이다. 첫 번째 그림인 트리는 균형이 잡혀 있고 두 번째 트리는 그렇지 않다.</p>
<p>2번 트리는 연결 리스트처럼 한 방향으로 나열된 트리이고, 이런 트리를 균형이 잡히지 않은 트리라고 한다. 그리고 이렇게 균형 잡히지 않은 트리의 균형을 잡기 위해 노드 위치를 바꾸는 과정을 <strong>회전</strong>이라고 한다. </p>
<p>균형 잡힌 트리에서 특정 요소를 탐색하는 시간 복잡도는 O(log n)이다. 반면에 균형 잡히지 않은 트리에서는 연결리스트와 같은 O(n)이 된다. 따라서 데이터를 효율적으로 관리하려면 트리를 균형있게 만들어야 한다.</p>
<p>조부모, 부모, 자식 노드의 크기 관계에 따라 우측 회전 또는 좌측 회전을 한다. 트리를 재정렬하면 항상 중간 크기의 요소가 가장 위에 있는 노드가 된다.</p>
<ol>
<li><p>불균형이 왼쪽 서브트리에서 나타날 경우</p>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041740806.png" alt="스크린샷 2022-12-04 17.40.07"></p>
<p>크기 관계는 조부모 &gt; 부모 &gt; 자식 순이다. 우측 회전을 하여 조부모 노드를 부모의 우측 자식 위치로 옮긴다.</p>
<pre><code class="language-java">public Node&lt;E&gt; rightRotate(Node&lt;E&gt; node) {
  Node&lt;E&gt; temp = node.left;
  node.left = temp.right; null
  temp.right = node; 
  return tmp;
}</code></pre>
</li>
</ol>
<ol start="2">
<li><p>불균형이 오른쪽 서브트리에서 나타날 경우</p>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041744133.png" alt="스크린샷 2022-12-04 17.44.09"></p>
<p>크기 관계는 자식 &gt; 부모 &gt; 조부모이다. 좌측 회전을 하여 조부모 노드를 부모 노드의 왼쪽 자식 위치로 옮긴다.</p>
<pre><code class="language-java">public Node&lt;E&gt; leftRotate(Node&lt;E&gt; node) {
  Node&lt;E&gt; temp = node.right;
  node.right = temp.left;
  temp.left = node;
  return tmp;
}</code></pre>
</li>
</ol>
<ol start="3">
<li><p>불균형이 오른쪽 자식의 왼쪽 서브트리에서 나타날 경우</p>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212052208066.png" alt="스크린샷 2022-12-05 22.08.37"></p>
<p>크기 관계는 부모 &gt; 자식 &gt; 조부모이다. 자식 노드에 대해 부모 노드를 우측 회전 후, 좌측 회전을 하여 조부모 노드를 부모 노드의 왼쪽 자식 노드 위치로 옮긴다.</p>
<pre><code class="language-java">public Node&lt;E&gt; rightLeftRotate(Node&lt;E&gt; node) {
    node.right = rightRotate(node.right);
  return leftRotate(node);
}</code></pre>
</li>
</ol>
<ol start="4">
<li><p>불균형이 왼쪽 자식의 오른쪽 서브트리에서 나타날 경우</p>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212052212277.png" alt="스크린샷 2022-12-05 22.12.31"></p>
<p>크기 관계는 부모 &gt; 조부모 &gt; 자식이다. 자식 노드에 대해 부모 노드를 좌측 회전 후, 우측 회전을 하여 조부모 노드를 부모 노드의 오른쪽 자식 노드 위치로 옮긴다.</p>
<pre><code class="language-java">public Node&lt;E&gt; leftRightRotate(Node&lt;E&gt; node) {
  node.left = leftRotate(node.left);
  return rightRotate(node);
}</code></pre>
</li>
</ol>
<h2 id="힙heap">힙(Heap)</h2>
<h3 id="힙이란">힙이란</h3>
<p>힙은 최댓값 및 최솟값을 찾아내는 연산을 빠르게 하기 위해 고안된 완전이진트리 기반의 자료구조이다.</p>
<p>힙은 규칙에 의해 느슨한 정렬 상태인 반정렬 상태를 유지한다.</p>
<blockquote>
<p>완전 이진 트리란?</p>
<p>마지막 레벨을 제외하고 모든 레벨이 완전히 채워져 있으며 마지막 레벨의 모든 노드는 가능한 한 가장 왼쪽에 있다. 마지막 레벨 h에서 1부터 2h-1 개의 노드를 가질 수 있다. 출처 : <a href="https://ko.wikipedia.org/wiki">https://ko.wikipedia.org/wiki</a></p>
</blockquote>
<h3 id="힙의-종류">힙의 종류</h3>
<p>어떤 종류의 힙인지에 따라 두 가지의 다른 규칙이 존재한다.</p>
<ol>
<li>부모 노드가 자식 노드보다 크다. *MAX HEAP</li>
<li>부모 노드가 자식 노드보다 작다 *MIN HEAP</li>
</ol>
<p>가장 큰 숫자가 루트 노드가 되게 하려면 MAX HEAP의 규칙을, 가장 작은 숫자가 루트 노드가 되게 하려면 MIN HEAP의 규칙을 사용하면 된다.</p>
<h3 id="힙의-추가와-제거">힙의 추가와 제거</h3>
<p>힙에 새로운 데이터를 추가하거나 제거할 때는 힙의 규칙을 지켜야 한다. 최대 힙이면 부모 노드가 자식 노드보다 커야 하고 최소 힙일 때는 자식 노드가 부모 노드보다 커야 한다.</p>
<h4 id="노드-추가max-heap">노드 추가(MAX HEAP)</h4>
<ul>
<li><p>완전 이진 트리이기 때문에 노드의 위치는 다음과 같은 성질을 가진다.</p>
<ul>
<li>children : 2 * parent + 1 또는 2 * parent + 2</li>
<li>parent : floot((children-1)/2)</li>
</ul>
</li>
<li><p>삽입 과정</p>
<ol>
<li>비어있는 공간에 노드를 추가한다.</li>
<li>부모 노드보다 큰 숫자인지 확인하고 만약 그렇다면 두 노드를 바꾼다(TRICKLE UP)</li>
</ol>
</li>
<li><p>삽입 구현</p>
</li>
</ul>
<pre><code class="language-java">int lastposition; // 어디까지 요소를 넣었는지 기록
E[] array = (E[]) new Object[size]; // 힙이 구현될 배열
public void add(E obj){ //
    array[++lastposition] = obj; // 1. 노드 추가
    trickleup(lastposition); // 2. trickle up
}
public void swap(int from, int to){ // 위치 변경
    E tmp = array[from];
    array[from] = array[to];
    array[to] = tmp;
}
public void trickleup(int position){
    if (position == 0) // 루트 노드일 때는 이미 작업이 모두 끝난 상태
        return;
    int parent = (int) Math.floor((position-1)/2); // 매개변수 포지션으로 부모 노드의 위치를 구함
    if (((Comparable&lt;E&gt;) array[position]).compareTo(array.parent)&gt;0) { // 포지션 위치의 노드가 부모 노드 보다 값이 크다면 swap
        swap(position, parent);
        trickleup(parent); // 루트 값까지 비교할 수 있게 재귀 사용
    }
}</code></pre>
<h4 id="노드-제거max-heap">노드 제거(MAX HEAP)</h4>
<p>힙에서의 노드 제거는 무조건 루트를 제거해야 한다.</p>
<ol>
<li>루트를 제거한다.</li>
<li>트리의 마지막 요소를 루트에 넣는다.<ul>
<li>마지막 요소를 루트에 넣지 않고 루트의 자식 중 값이 큰 노드로 바꿔주게 되면 최하위 레벨의 단말 노드까지 노드의 위치를 바꿔야 하기 때문에 마지막 요소를 루트에 넣는 것이다.</li>
</ul>
</li>
<li>루트에서 시작하여 두 자식 중 큰 노드와 위치를 바꿔주며 힙의 규칙을 만족하게 한다.(TRICKLE DOWN)</li>
</ol>
<ul>
<li>삭제 구현</li>
</ul>
<pre><code class="language-java">public E remove(){
    E tmp = array[0];
    swap(0, lastposition--); // 루트와 마지막 노드를 바꿔주고 lastposition을 줄여 배열에서 제거합니다.
    trickleDown(0);
    return tmp;
}
public void trickleDown(int parent){
    int left = 2*parent + 1;
    int right = 2*parent + 2;
    // 마지막에 왼쪽 자식이 클 때
    if (left==lastposition &amp;&amp; (((Comparable&lt;E&gt;)array[parent]).compareTo(array[left])&lt;0){
        swap(parent, left)
        return;
    }
    // 마지막에 오른쪽 자식이 클 때
    if (right==lastposition &amp;&amp; (((Comparable&lt;E&gt;)array[parent]).compareTo(array[right])&lt;0){
        swap(parent, right)
        return;
    }
    // 마지막에 부모가 클 때
    if (left &gt;= lastposition || right &gt;= lastposition)
        return;
    // 왼쪽 자식이 클 때
    if (array[left] &gt; array[right] &amp;&amp; array[parent] &lt; array[left]) {
        swap(parent, left);
        trickleDown(left);
    }
    // 오른쪽 자식이 클 때
    else if (array[parent] &lt; array[right]){
        swap(parent, right);
        trickleDown(right);
    }
}</code></pre>
<h2 id="avl-트리">AVL 트리</h2>
<h3 id="avl-트리란">AVL 트리란</h3>
<p>위 트리의 회전에서 언급한 회전을 사용하여 스스로 균형을 잡는이진 탐색 트리의 속성을 가진 트리이다.</p>
<h3 id="avl-트리의-특징">AVL 트리의 특징</h3>
<ul>
<li>AVL 트리에서는 왼쪽과 오른쪽의 높이가 항상 1보다 작거나 같아야 한다. </li>
<li>높이 차이가 1보다 커지면 회전을 통해 균형을 맞춰 높이 차이를 줄인다.</li>
<li>추가, 삭제, 검색의 시간 복잡도가 O(log N)이다.</li>
</ul>
<h3 id="avl-트리-불균형-종류">AVL 트리 불균형 종류</h3>
<blockquote>
<p>헷갈려서 다시 정리했다.</p>
</blockquote>
<ul>
<li><p><strong>LL(Left Left)</strong> - 왼쪽 자식 노드의 왼쪽 서브트리에서 불균형 발생</p>
<img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041740806.png" alt="스크린샷 2022-12-04 17.40.07" style="zoom:50%;" />

<ul>
<li>우회전을 적용하면 불균형이 해소된다.</li>
</ul>
</li>
<li><p><strong>RR(Right Right)</strong> - 오른쪽 자식 노드의 오른쪽 서브트리에서 불균형 발생</p>
<img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212041744133.png" alt="스크린샷 2022-12-04 17.44.09" style="zoom:50%;" />

<ul>
<li>좌회전을 적용하면 불균형이 해소된다.</li>
</ul>
</li>
<li><p><strong>LR(Left Right)</strong> - 왼쪽 자식 노드의 오른쪽 서브트리에서 불균형 발생</p>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212052212277.png" alt="스크린샷 2022-12-05 22.12.31"></p>
<ul>
<li>좌회전 적용 후 우회전을 적용하면 불균형이 해소된다.</li>
</ul>
</li>
<li><p><strong>RL(Right Left)</strong> - 오른쪽 자식 노드의 왼쪽 서브트리에서 불균형 발생</p>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212052208066.png" alt="스크린샷 2022-12-05 22.08.37"></p>
<ul>
<li>우회전 적용 후 좌회전을 적용하면 불균형이 해소된다.</li>
</ul>
</li>
</ul>
<h3 id="avl-트리-구현">AVL 트리 구현</h3>
<ul>
<li>노드</li>
</ul>
<pre><code class="language-java">class Node&lt;T&gt; {
  T data;
    Node&lt;T&gt; left;
  Node&lt;T&gt; right;
  Node&lt;T&gt; parent;  
  public Node(T obj) {
    this.data = obj;
    left = right = parent = null;
  }
}</code></pre>
<ul>
<li>추가</li>
</ul>
<pre><code class="language-java">public AVLTree() {
  root = null;
  currentSize = 0;
}

public void add(E obj) {
  Node&lt;E&gt; node = new Node&lt;E&gt;(obj);
  // 트리가 비어있을 경우
  if(root = null) {
    root = node;
    currentSize++;
    return;
  }
  // 트리에 노드가 있을 경우 add메서드를 재귀로 호출
  add(root, node);
}

// 재귀 add
public void add(Node&lt;E&gt; parent, Node&lt;E&gt; newNode) {
  if(((Comparable&lt;E&gt;)newNode.data.compareTo(parent.data) &gt; 0) {
    if(parent.right == null) {
      parent.right = newNode;
      newNode.parent = parent;
      currentSize;
    } else {
      add(parent.right, newNode);
    }
  } else {
    if(parent.left == null) {
      if(parent.left == null) {
        parent.left = newNode;
        newNode.parent = parent;
        currentSize++;
      } else {
        add(parent.left, newNode);
      }
    }
  }
  checkBalance(newNode);
}</code></pre>
<ul>
<li>균형 확인</li>
</ul>
<pre><code class="language-java">public void checkBalance(Node&lt;E&gt; node) {
  // 높이 차이가 1 초과 또는 미만
  if(
    (height(node.left) - height(node.right) &gt; 1) ||
    (height(node.left) - height(node.right) &lt; -1)
  ) {
          rebalance(node); 
  }
  // 부모 노드를 계속 확인해서 루트까지 간다.
  if(node.parent == null) {
    return;
  }
  checkBalance(node.parent);
} </code></pre>
<ul>
<li>균형 맞추기</li>
</ul>
<pre><code class="language-java">public void rebalance(Node&lt;E&gt; node) {
    // 왼쪽 자식 &gt; 오른쪽 자식
  if(height(node.left) - height(node.right) &gt; 1) {
    // 노드의 왼쪽 자식의 왼쪽 서브트리가 오른쪽 서브 트리보다 크다면 우측 회전
    if(height(node.left.left) &gt; height(node.left.right)) {
      node = rightRotate(node); 
    // 노드의 왼쪽 자식의 오른쪽 서브트리가 왼쪽 서브 트리보다 크다면 좌측-우측 회전
    } else {
      node = leftRightRotate(node);
    }
  } else {
        // 노드의 오른쪽 자식의 왼쪽 서브트리가 오른쪽 서브 트리보다 크다면 우측-좌측 회전
    if(height(node.right.left) &gt; height(node.right.right)) {
      node = rightLeftRotate(node); 
    // 노드의 왼쪽 자식의 오른쪽 서브트리가 왼쪽 서브 트리보다 크다면 좌측 회전
    } else {
      node = leftRotate(node);
    }
  }
  if(node.parent == null) {
    root = node;
  }
}</code></pre>
<ul>
<li>추가 예제</li>
</ul>
<p>아래 GIF는 강의의 실습 예제이다.</p>
<blockquote>
<p><a href="https://www.cs.usfca.edu/~galles/visualization/AVLtree.html">AVL 시뮬레이터</a>를 통해 직접 추가하면서 그 과정을 GIF로 담았다. </p>
</blockquote>
<p><img src="https://raw.githubusercontent.com/back-seung/TIL/master/uPic/202212142346259.gif" alt="4EMhW76kr3i-z-0-y-6399e0c0fa683b7f081521c7"></p>
<p>AVL의 규칙에 따라 루트 노드인 43에 18, 22, 9, 21, 6, 8, 20, 63, 50, 62, 51을 순서대로 추가한 결과이다. 추가될 때 마다 균형이 깨졌는지 확인하고 회전을 하여 균형을 유지한다.</p>
<h2 id="레드-블랙-트리">레드-블랙 트리</h2>
<h3 id="레드-블랙-트리란">레드-블랙 트리란</h3>
<p>레드-블랙 트리는 <strong>자가 균형 이진 탐색 트리</strong>로써, 이진 탐색 트리(BST)의 한 종류이며, 스스로 균형을 잡는 트리이다.</p>
<p>BST의 worst case의 단점을 개선하게 된다.(최악의 경우에도 O(N)이 아닌 O(logN)의 시간복잡도를 가진다.)</p>
<h3 id="레드-블랙-트리의-규칙">레드-블랙 트리의 규칙</h3>
<blockquote>
<p><strong>우선 알아둬야 할 것</strong></p>
<ul>
<li>nil 노드<ul>
<li>존재하지 않음을 의미하는 노드</li>
<li>자녀가 없을 때 자녀를 nil 노드로 표기한다.</li>
<li>값이 있는 노드와 동등하게 취급한다.</li>
<li>RB트리에서 leaf노드는 nil노드이다</li>
</ul>
</li>
<li>black height<ul>
<li>아래 4번의 규칙을 만족해야 이 개념이 성립된다.</li>
<li>노드 X에서 임의의 자손 nil 노드까지 내려가는 경로에서의 black의 수(자기 자신은 카운트에서 제외한다.)</li>
</ul>
</li>
</ul>
</blockquote>
<p>레드-블랙 트리의 규칙은 다음과 같다.</p>
<ol>
<li>모든 노드는 <code>빨간색</code>이거나 <code>검은색</code>이다. 다른 색은 존재하지 않는다.</li>
<li>루트는 항상 검은색이다. 만약 루트가 검은색이 아니라면 강제로 검은색으로 만들어 주어야 한다.</li>
<li>새로 추가되는 노드는 항상 빨간색이다. 만약 새로 추가된 노드가 루트 노드라면 우선 빨간색으로 추가한 다음 <code>2</code>에서 언급한 내용처럼 강제로 검은색으로 만들어준다. <ul>
<li>새로 삽입하는 노드가 빨간색이어야 하는 이유는 삽입 후에도 <code>규칙 4번</code>을 만족해야 하기 때문이다.</li>
</ul>
</li>
<li>루트에서 리프 노드로 가는 모든 경로에는 <strong>같은 수의 검은색 노드가 있어야 한다.</strong></li>
<li>어떤 경로에서도 빨간색 노드 2개가 연속으로 있어선 안된다. 즉, 빨간색의 자식은 모두 검은색이여야 한다.</li>
<li>모든 비이었는 노드는 검은색이라고 가정한다. </li>
</ol>
<h3 id="레드-블랙-트리의-균형">레드-블랙 트리의 균형</h3>
<p>삽입 또는 삭제 시 주로 위 규칙의 <code>4</code>, <code>5</code>를  위반하고, 이것을 해결하기 위해 구조를 바꾸다보면 자연스럽게 트리의 균형이 잡히게 된다. 레드-블랙 트리에서 균형이 깨지면 회전, 색상 전환을 통해 이를 해결한다.</p>
<ol>
<li><p>이모 노드가 검은색일 경우 - 회전</p>
<p>회전을 하고 나면 부모 노드는 검은색, 두 자식 노드는 빨간색이 되어야 한다.</p>
</li>
<li><p>이모 노드가 빨간색일 경우 - 색상 전환</p>
<p>색상 전환을 하고 나면 부모 노드는 빨산색, 두 자식 노드는 검은색이 되어야 한다.</p>
</li>
</ol>
<h3 id="레드-블랙-트리-구현">레드-블랙 트리 구현</h3>
<ul>
<li>클래스</li>
</ul>
<pre><code class="language-java">public class RedBlackTree&lt;K,V&gt; implements RedBlackI&lt;K,V&gt; {
    Node&lt;K,V&gt; root;
    int size;
    class Node&lt;K,V&gt; {
        K key;
        V value;
        Node&lt;K,V&gt; left, right, parent;
        boolean isLeftChild, black;
        public Node (K key, V value) {
            this.key = key;
            this.value = value;
            left = right = parent = null;
            black = false;
            isLeftChild = false;
        }
    }
}</code></pre>
<ul>
<li><p>삽입 - add 메서드</p>
<ol>
<li>이진 탐색 트리와 삽입 방식은 동일하다.</li>
<li>삽입 후에 레드-블랙 트리 위반 여부를 확인한다.</li>
<li>트리 규칙을 위반했다면 재조정한다.</li>
<li>레드-블랙 트리의 규칙을 다시 만족한다.</li>
</ol>
<pre><code class="language-java">public void add(K key, V value){
    Node&lt;K,V&gt; node = new Node&lt;K,V&gt;(key, value); // black = false, isLeftChild = false
    // 트리가 비어있을 경우
    if (root == null) {
        root = node;
        root.black = true;
        size++;
        return;
    }
    // 트리에 노드가 있을 경우 재귀 메소드 사용
    add(root, node);
    size++;
}
// add 재귀함수, 내부클래스
private void add (Node&lt;K,V&gt; parent, Node&lt;K,V&gt; newNode){
    // newNode의 data가 parent의 data보다 크면 트리의 오른쪽에 추가하면 됩니다.
    if (((Comparable&lt;K&gt;) newNode.key).compareTo(parent.key) &gt; 0){
        if(parent.right == null){
            parent.right = newNode;
            newNode.parent = parent;
            newNode.isLeftChild=false;
            return;
        }
        return add(parent.right, newNode);
    // newNode의 data가 parent의 data보다 작거나 같으면 트리의 왼쪽에 추가하면 됩니다.    
  } else {
    if (parent.left == null){
      parent.left = newNode;
      newNode.parent = parent;
      newNode.isLeftChild=true;
      return;
    }
    return add(parent.left, newNode);
    // 레드 블랙 트리가 규칙에 맞게 잘 되어있는지 확인합니다.
    checkColor(newNode);
}</code></pre>
</li>
</ul>
<ul>
<li><p>색깔 검증 - checkColor 메서드</p>
<ul>
<li>레드-블랙 트리의 6가지 규칙을 만족하는지 확인해준다.</li>
</ul>
<pre><code class="language-java">public void checkColor(Node&lt;K,V&gt; node) {
  // 루트 노드는 항상 검은색이므로 확인할 필요가 없습니다.
  if(node == root) {
    return;
  }
  // 노드 2개가 연속적으로 빨간색이 나오는 경우
  if(!node.black &amp;&amp; !node.parent.black) {
    correctTree(node); 
  }
  // 부모 노드를 재귀적으로 확인합니다.
  checkColor(node.parent);
}</code></pre>
</li>
</ul>
<ul>
<li><p>연속된 빨간색 노드 수정 - correctTree 메서드</p>
<ul>
<li>어떤 경로에서도 빨간색 노드 2개가 연속으로 있어선 안된다. 즉, 빨간색의 자식은 모두 검은색이여야 한다.의 규칙이 만족되지 않았을 때 해당 노드의 이모 노드 색깔을 파악한 후, 회전 또는 색상 변환을 수행한다.</li>
</ul>
<pre><code class="language-java">public void correctTree(Node&lt;K,V&gt; node) {
    // node의 부모 노드가 왼쪽 자식이면 이모 노드는 조부모 노드의 오른쪽 자식입니다.
  if(node.parent.isLeftChild) {
    // 이모 노드가 검은색(이모 노드가 비어있는 경우 포함)
    if(node.parent.parent.right == null || node.parent.parent.right.black) {
      // 회전
      return rotate(node);
    }
    // 이모 노드가 빨간색인 경우
    if(node.parent.parent.right != null) {
      // 색상 변환
      node.parent.parent.right.black = true;
    }
    node.parent.parent.black = false;
    node.parent.black = true;
    return;
  }
  // node의 부모 노드가 오른쪽 자식이면 이모 노드는 조부모 노드의 왼쪽 자식입니다.
  // 위 코드와 동일하게 하되, 이모 노드를 node.parent.parent.left로 바꿔야 합니다.
   else {
    // 이모 노드가 검은색(이모 노드가 비어있는 경우 포함)
     if(node.parent.parent.left == null || node.parent.parent.left.black) {
       // 회전
       return rotate(node);
     } 
     // 이모 노드가 빨간색인 경우
     if(node.parent.parent.left != null) {
       // 색상 변환
       node.parent.parent.left.black = true;
     }
     node.parent.parent.black = false;
     node.parent.black = true; 
     return;
   }
}</code></pre>
</li>
</ul>
<ul>
<li><p>회전 - rotate 메서드</p>
<ul>
<li>현재 노드와 부모 노드가 각각 오른쪽 자식인지 왼쪽 자식인지에 따라 필요한 회전이 달라진다. 각각의 경우에 따라 수행이 다르다.</li>
<li>파라미터로 들어가는 node는 규칙을 어긋나게한 노드의 조부모 노드이다.</li>
</ul>
<pre><code class="language-java">public void rotate(Node&lt;K,V&gt; node){
    // 현재 노드가 왼쪽 자식
    if (node.isLeftChild) {
        // 부모 노드가 왼쪽 자식
        if (node.parent.isLeftChild) {
            // 조부모 노드를 우측회전
            rightRotate(node.parent.parent);
            node.black = false;
            node.parent.black = true;
            if(node.parent.right != null)
                node.parent.right.black = false;
            return;
        }
        // 부모 노드가 오른쪽 자식
        // 조부모 노드를 우측-좌측 회전
        rightLeftRotate(node.parent.parent);
        node.black = true;
        node.right.black = false;
        node.left.black = false;
        return;
  // 현재 노드가 오른쪽 자식
    } else {
      // 부모 노드가 오른쪽 자식
        if (!node.parent.isLeftChild) {
            // 조부모 노드를 좌측회전
            leftRotate(node.parent.parent);
            node.black = false;
            node.parent.black = true;
            if(node.parent.left != null)
                node.parent.left.black = false;
            return;
        }
        // 부모 노드가 왼쪽 자식
        // 조부모 노드를 좌측-우측 회전
        leftRightRotate(node.parent.parent);
        node.black = true;
        node.left.black = false;
        node.right.black = false;
        return;        
}</code></pre>
</li>
</ul>
<ul>
<li><p>좌측 회전 - leftRotate 메서드</p>
<ul>
<li>파라미터로 들어가는 node는 규칙을 어긋나게한 노드의 조부모 노드이다.</li>
</ul>
<pre><code class="language-java">// 좌측 회전: 조부모 노드를 부모 노드의 왼쪽 자식 노드 위치로 옮깁니다.
public void leftRotate (Node&lt;K,V&gt; node){
    Node&lt;K,V&gt; temp = node.right;
    node.right = temp.left;
    // 부모 노드 node.right가 temp가 되면서 조부모 노드가 없어집니다.
    if(node.right != null) {
        node.right.parent = node;  
        node.right.isLeftChild = false;
    }
    // node가 루트인 경우
    if(node.parent = = null) {
        root = temp;
        temp.parent = null;
    }
    // node가 루트가 아닌 경우
    else {
        temp.parent = node.parent;
        if(node.isLeftChild) {
            temp.isLeftChild = true;
            temp.parent.left = temp;
        } else {            
            temp.isLeftChild = false;
            temp.parent.right = temp;
        }
        temp.left = node;
        node.isLeftChild = true;
        node.parent = temp;
    }
}</code></pre>
</li>
<li><p>우측 회전 - rightRotate 메서드</p>
<ul>
<li>파라미터로 들어가는 node는 규칙을 어긋나게한 노드의 조부모 노드이다.</li>
</ul>
<pre><code class="language-java">// 우측 회전: 조부모 노드를 부모 노드의 오른쪽 자식 노드 위치로 옮깁니다.
public void rightRotate (Node&lt;K,V&gt; node){
    Node&lt;K,V&gt; temp = node.left;
    node.left = temp.right;
    // 부모 노드 node.left가 temp가 되면서 조부모 노드가 없어집니다.
    if(node.left != null) {
        node.left.parent = node;  
        node.left.isLeftChild = true;
    }
    // node가 루트인 경우
    if(node.parent == null) {
        root = temp;
        temp.parent = null;
    }
    // node가 루트가 아닌 경우
    else {
        temp.parent = node.parent;
        if(node.isLeftChild) {
            temp.isLeftChild = true;
            temp.parent.left = temp;
        } else {            
            temp.isLeftChild = false;
            temp.parent.right = temp;
        }
        temp.right = node;
        node.isLeftChild = false;
        node.parent = temp;
    }
}</code></pre>
</li>
</ul>
<ul>
<li><p>좌측-우측 회전 - leftRightRotate 메서드</p>
<ul>
<li>파라미터로 들어가는 node는 규칙을 어긋나게한 노드의 조부모 노드이다.</li>
</ul>
<pre><code class="language-java">public void leftRightRotate(Node&lt;K,V&gt; node) {
  leftRotate(node);
  rightRotate(node);
}</code></pre>
</li>
</ul>
<ul>
<li><p>우측-좌측 회전 - rightLeftRotate 메서드</p>
<ul>
<li>파라미터로 들어가는 node는 규칙을 어긋나게한 노드의 조부모 노드이다.</li>
</ul>
<pre><code class="language-java">public void rightLeftRotate(Node&lt;K,V&gt; node) {
  rightRotate(node);
  leftRotate(node);
}</code></pre>
</li>
</ul>
<ul>
<li><p>높이 - height 메서드</p>
<pre><code class="language-java">public int height() {
  if(root == null) {
    return 0;
  }
  return height(root) - 1;
}

private int heigth(Node&lt;K,V&gt; node) {
     if(node == null) {
    return 0;
  } 
  int leftHeight = height(node.left) + 1;
  int rightHeight = height(node.right) + 1;
  if(leftHeight &gt; rightHeight) {
    return leftHeight;
  } else {
    return rightHeight;
  }
}</code></pre>
</li>
<li><p>검은색 노드 개수 - blackNodes 메서드</p>
<pre><code class="language-java">public int blackNodes(Node&lt;K,V&gt; node) {
  if(node == null) {
    return 1;
  }
  int rightBlackNodes = blackNodes(node.right);
  int leftBlackNodes = blackNodes(node.left);
  // 오른쪽과 왼쪽의 검은색 노드 개수가 다르면 에러를 내거나 고쳐준다.
  if(rightBlackNodes != leftBlackNodes) {
    throws new Exception(&quot;양쪽 서브 트리의 검은색 노드의 개수가 일치하지 않습니다.&quot;);
    // 또는 고쳐준다.    
  }
  if(node.black) {
    leftBlackNodes++;
  }
  return leftBlackNodes++;
}</code></pre>
</li>
</ul>
<ul>
<li><p>삭제 - remove 메서드</p>
<ul>
<li>노드를 삭제하고 삭제된 후의 트리가 규칙에 어긋나는지를 확인해야 한다. 그 중 삭제되는 색이 규칙의 위배되는지를 확인하는 방법이 될 수 있다. 삭제되는 색은 다음과 같이 정의된다.<ol>
<li>삭제되는 노드의 자녀가 1개이거나 없는 경우<ul>
<li>삭제되는 색 : <strong>삭제되는 노드의 색깔</strong></li>
</ul>
</li>
<li>삭제되는 노드의 자녀가 2개인 경우<ul>
<li>삭제되는 색 : <strong>삭제되는 노드의 successor(중위 후속자)의 색</strong></li>
</ul>
</li>
</ol>
</li>
<li>삭제되는 색이 어떤 색이냐에 따라 바로 규칙을 위배하는지 알 수 있다.<ol>
<li>빨간색 - 6가지 중 어떠한 속성도 위배하지 않는다.</li>
<li>검은색 - <code>1. 루트 노드는 항상 검은색</code>, <code>2. 빨간색의 자식은 모두 검은색</code>, <code>3. 루트에서 리프 노드로 가는 모든 경로에는 같은 수의 검은색 노드가 있어야 함</code>의 3개 규칙을 경우에 따라 위배하게 된다. <ul>
<li>그 중 3번 규칙은 특수한 상황을 제외하면 항상 위배하게 된다. </li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[약 1년 회고, 내가 느낀 1일 1커밋의 장단점 ]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%95%BD-1%EB%85%84-%ED%9A%8C%EA%B3%A0-%EB%82%B4%EA%B0%80-%EB%8A%90%EB%82%80-1%EC%9D%BC-1%EC%BB%A4%EB%B0%8B%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90</link>
            <guid>https://velog.io/@seunghan-baek/%EC%95%BD-1%EB%85%84-%ED%9A%8C%EA%B3%A0-%EB%82%B4%EA%B0%80-%EB%8A%90%EB%82%80-1%EC%9D%BC-1%EC%BB%A4%EB%B0%8B%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90</guid>
            <pubDate>Tue, 29 Nov 2022 14:06:56 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요? 저는 곧 1년차가 되는 B2B 솔루션 회사의 주니어 개발자입니다. 취업준비 때부터 지금까지 1일 1커밋을 해오고 있으며 그에 대한 간단한 회고를 남기고 싶어 글을 썼습니다.</p>
<h2 id="깃허브-잔디밭">깃허브 잔디밭</h2>
<p><img src="https://velog.velcdn.com/images/seunghan-baek/post/129d0170-07ca-42e6-b36f-00ac6eff6031/image.png" alt="">22년 1월 2일 새해를 다짐하며 시작한 <code>1일 1커밋</code>이 벌써 <code>331</code>일 째가 되는 날입니다.</p>
<p>이 글은 쓰는 지금은 제가 1일 1커밋을 마무리 하고자 생각했던 1년보다는 34일 정도 앞선 시점인데요, 이렇게 미리 글을 쓰는 이유는 비유를 통해 설명드리겠습니다.</p>
<h3 id="비유글쓴이-시점---치킨을-시켰습니다">비유(글쓴이 시점) - 치킨을 시켰습니다.</h3>
<p><img src="https://velog.velcdn.com/images/seunghan-baek/post/266ac5b0-14ad-45ba-ab82-7569efffbd9e/image.png" alt=""></p>
<p>치킨을 시키며 제가 느낀 모든것을 리뷰 해주고 싶었습니다. 리뷰를 하는 시점을 <code>먹는 도중</code>, <code>먹고난 뒤</code>로 나눕니다.</p>
<blockquote>
<ul>
<li>** 먹고난 뒤**</li>
</ul>
<p>아 배불렁 리뷰 써주기 귀찮앙</p>
</blockquote>
<p>이제 쉬고 싶다는 게으름과 다 먹었다는 만족감에 의해 성실 리뷰의 의무를 포기하고야 말았습니다.</p>
<blockquote>
<ul>
<li><strong>먹는 도중</strong></li>
</ul>
<p>치킨리뷰 전용 스페셜 어시스턴스를 고용하고 닭의 상태를 하나하나 체크하며 목, 가슴, 다리, 날개, 봉, 바삭함의 정도 또는 간의 베기는 골고루 베어졌는가 등에 대한 개인적 견해를 시간별로 빠짐없이 기록합니다.</p>
</blockquote>
<p>정확하고 체계적인 접근으로 당시 느꼈던 감정을 오류 없이 솔직하게 서술할 수 있게 되었습니다.</p>
<h3 id="비유방문자-시점---님-이-게임-재밌나요">비유(방문자 시점) - 님, 이 게임 재밌나요?</h3>
<p> <img src="https://velog.velcdn.com/images/seunghan-baek/post/b36bfcbb-6072-4acb-bdb8-9a7db27d89c0/image.png" alt=""></p>
<p> 한 뉴비가 경험을 해본 사람에게 플레이하고자 하는 게임이 어떤지 묻습니다.</p>
<blockquote>
<ul>
<li>** 이미 끝까지 깸**
ㅇㅇ잼슴. 근데 막상 끝까지 해보면 별거 없어요 걍 할만함</li>
</ul>
</blockquote>
<p>툭 던진 성의없는 답변에 참담해지고 끝에 도달하면 별거 없다는 사실에 선뜻 시도해 볼 용기조차 나지 않습니다.</p>
<blockquote>
<ul>
<li>** 아직 깨고 있음**
아니 여기 깨려고 ㄱ을 샀는데 ㄱ가 ㄴ을 위해선 필수거든요 ㄴ얻으려고 ㄷ까지 얻어놨는데 ㄷ은 캐릭터 처음 생성하실 때가 제일 중요함. 캐릭터는 무조건 ㄹ로 하세요 ㄷ얻기 개편함 ㅁ도 쉬워짐</li>
</ul>
</blockquote>
<p>게임에 대한 이해도가 사전에 이미 매우 높아졌으며 자세한 설명에 힘입어 당찬 포부로 이 게임을 즐길 준비, 흥미가 생깁니다.</p>
<p>서론이 길었습니다. 시작하겠습니다.</p>
<h2 id="먼저">먼저</h2>
<p>이 <code>1일 1커밋</code>이라는 행위를 긍정적으로 말하는 사람들, 부정적으로 말하는 사람들 모두 있겠지만, 전 제가 느낀 긍정적인 면, 부정적인 면 모두를 시간별로 나누어 주관대로 이야기 해보려 합니다.</p>
<h3 id="시작하게-된-계기">시작하게 된 계기</h3>
<blockquote>
<p><a href="https://youtu.be/V9AGvwPmnZU">YOUTUBE : 지방대 개발 비전공자가 배달의민족 리드 개발자가 되기까지 - EO 이오</a></p>
</blockquote>
<p>위 영상을 보았습니다.</p>
<p>영상의 썸네일에 적혀있는 하루키의 법칙은 <code>어제의 자신이 지녔던 약점을 조금이라도 극복하는 것</code>이라고 합니다.</p>
<p>저는 이 영상을 보고 업무 외 시간에 성장을 위한 시간을 만들어 부족한 것을 채워나가고 더 나은 사람이 되고자 하는 것에 깊은 공감을 했습니다. 또 저보다 앞서간 사람들을 부러워 했습니다.</p>
<p>누구나 그렇겠지만요.</p>
<p>저는 꾸준히 발전하는 개발자가 되고 싶었고 앞으로도 그러고 싶습니다.
또한 스스로 느끼는 부족함과 열등감을 극복하고자 꾸준함을 몸에 습관화하는 연습을 하고 싶었습니다.
하지만 무턱대고 시작하기에 제가 정한 1년이라는 시간 동안 저는 절대 포기하고 싶지 않았습니다.
혹시나 제가 열정이 식어 포기하진 않을까 다른 사람들의 후기가 궁금해졌고, 구글에 <code>1일1커밋 하는 법</code>, <code>1일1커밋 장단점</code>, <code>1일 1커밋 후기</code> 등을 검색하였습니다.</p>
<p>종합적으로 검색해 본 결과, <code>1일 1커밋</code>은 제 스스로의 꾸준함과 배우고자 하는 의지를 증진 시키는 하나의 도구로써 저에게는 큰 동기부여가 되겠다는 확신을 얻었습니다.</p>
<p>그렇게 준비는 끝나고, 드디어 2022년 1월 2일에 첫 커밋을 푸쉬하며 1년 간의 <code>1일 1커밋</code>을 시작점을 찍었습니다. </p>
<h2 id="본론">본론</h2>
<blockquote>
<ul>
<li>들어가기에 앞서 장점과 단점은 계속 중복된 내용이 어지럽게 나열될 수 있습니다.</li>
<li>1분기에 장점이 2분기에도 여전히 그렇거나 1분기의 제가 느낀 단점이 해결이 안됐다면 2분기에도 똑같이 해결이 안될 때가 있었기 때문입니다.</li>
<li>정말 솔직하게 적었습니다. 저라는 사람을 소개하는 글일수도 있을 것 같습니다.
대외적으로 보이는 제 모습에도 가식을 섞지 않고 솔직한 사람이 되고자 노력했습니다. 양해 부탁드립니다.</li>
</ul>
</blockquote>
<h3 id="📆-1분기-2201--2203">📆 1분기 (22/01/ ~ 22/03)</h3>
<h4 id="주요-활동-내용">주요 활동 내용</h4>
<ul>
<li><code>스프링 부트와 AWS로 혼자 구현하는 웹 서비스</code> 책을 사고, 그 책을 따라서 스프링 부트 웹 프로젝트를 만들어보았습니다.</li>
<li><code>이것이 자바다</code>라는 책을 사고 그 책의 예제와 핵심 개념을 마크다운으로 정리하였습니다.</li>
<li><code>백기선님의 자바스터디</code>를 듣고 매 주차에 맞는 학습 목표를 공부하고 정리했습니다.</li>
<li>코로나에 걸렸습니다.</li>
<li>취업을 했습니다.</li>
<li>친구와 줌을 통해 1:1 모각코 스터디를 시작했습니다.</li>
<li>친구와 제가 악마의 커밋이라고 부르는 <code>README 따위에 링크 한 줄을 추가</code>하는 등을 커밋이라고 정신승리를 하며 스스로 하지 말아야 한다고 생각한 것을 하게 됐습니다.</li>
</ul>
<h4 id="장점">장점</h4>
<ul>
<li>남들처럼 TIL 레포지토리를 생성했고 제가 공부한 내용을 차곡차곡 정리하며 쌓여가는 .md 파일들에 괜시리 뿌듯함을 느꼈습니다.</li>
<li>멋진 개발자가 될 수 있을 것 같은 추상적이지만 긍정적인 느낌, 매일 무언가 얻어가고 있다는 저를 위한 동기부여를 확실히 얻을 수 있었습니다.</li>
<li>어떤 상황에서든 하루 중 <code>20:00 ~ 23:59</code> 사이에는 개발 공부를 위해 스스로 책상에 앉는 습관이 생겼습니다.</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>해야 하는 공부나 하고 싶은 공부를 <strong><u>찾는</u></strong> 시간에 더 시간을 쓴 적이 많았습니다. 이 때는 하고 싶은게 너무 많았고 해야 하는 공부도 너무 많았습니다. *<u>이건 지금도 그렇습니다..</u>*</li>
<li>공부하는 과목이 추가되면 그것에만 관심을 가지다가 나중에 <code>&quot;어 맞다 이것도 공부하려고 했었지&quot;</code>하며 이것저것 배우고 싶다는 들 뜬 감정이 앞서기만 했습니다. 제 공부는 체계적인 부분이 전혀 없었습니다.</li>
<li>의미없는 커밋이 많지는 않았으나 가끔 이런 행동을 하는 제 자신이 너무 보기가 싫어졌습니다. 시작한지 얼마 되지도 않고서 그러는 제 자신이 너무 한심했고 다시는 안 그러겠다고 생각하면서도 언젠가 절대적인 이유에서 오늘은 못했다고 결국 정신승리를 했습니다.</li>
</ul>
<h3 id="📆-2분기-2204--2206">📆 2분기 (22/04 ~ 22/06)</h3>
<h4 id="주요-활동-내용-1">주요 활동 내용</h4>
<ul>
<li>개발과 관련된 <code>온라인 세미나</code> 등을 들으며 저에게 필요한 것을 정리했습니다.</li>
<li><code>이것이 자바다</code> 정리 이후, 다시 처음부터 공부하고 싶은 마음에 <code>자바의 정석</code>을 구매하여 다시 공부하게 됩니다.</li>
<li>이 때 <code>ombok</code>, <code>JDBC</code>, <code>객체지향</code> 등이 무엇인지 책에 있는 내용만 보는게 아니라 더 넓은 영역을 스스로 구글링을 통해 더 많은 인풋을 얻고자 노력했습니다.</li>
<li><a href="https://www.inflearn.com/course/http-%EC%9B%B9-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC/dashboard">모든 개발자를 위한 HTTP 웹 기본 지식 - 김영한</a> 강의를 들으며 정리했습니다.</li>
</ul>
<h4 id="장점-1">장점</h4>
<ul>
<li>공부를 하고 싶어서 인강을 구매했습니다. 뿌듯했습니다. 저를 위해 무언가 투자하는 제 자신이 기특했습니다.</li>
<li>책도 더 샀습니다. 똑같은 내용을 다른 사람이 어떻게 다룰까?에 대한 흥미가 생겼고 똑같은 내용을 다시 보는게 무조건 지루하지는 않다는 것을 깨닫는 계기였습니다.</li>
<li>이쯤 장기적인 학습에 대한 거부감이 많이 사라졌습니다. 공부중 궁금한 내용이 생기면 일단 바로 찾아봤으며 이러한 지적 호기심을 바탕으로 판을 깔아두고 하나씩 제대로 공부하면 100년의 시간도 부족하겠다라는 생각이 들게 됩니다.</li>
<li>어떤 것을 공부할 때 먼저 해결해야 할 선수지식이 조금씩 덜어졌고 호기심에 한 공부도 나중엔 도움이 될 것을 강하게 느꼈습니다.</li>
</ul>
<h4 id="단점-1">단점</h4>
<ul>
<li>정리만을 위한 공부를 하고 있지는 않은가? 나 공부하는 사람이야~ 라고 남들에게 보이고 싶던게 아니였을까? 잠시 해왔던 것을 돌아보니 다 맞는 소리였고, 많이 부끄러워졌습니다. </li>
<li>위와 비슷한 이유로 제 깃허브가 참고한 글의 말투만 살짝 바꿔 다시 올리는 불필요한 공간이 되지는 않았는지 의심이 되었습니다. 포기할까 가장 많이 고민한 순간이였네요. 경험에 의한 배움이 중요하다는 생각이 들었지만 당장 방향을 돌리기엔 아직 정리 해야할 책, 인강, 파일 등이 너무 많아 압도적인 양에 일단 이 중요한 사안을 외면했습니다. </li>
<li>시간에 쫓기며 성격이 많이 급해졌습니다. 알아야 할 건 너무 많고 하나도 끝내지 못한 상태에서 불안감에 무언가 계속 필요하다고 생각되면 또 공부할 목록에 우선 추가했습니다. 일이 끝나면 바로 집으로 와서 공부를 하려고 했고, 여태까지 꾸준히 해오던 운동을 운동을 쉬면서까지 공부에 집착했습니다.</li>
<li>처음 체계적으로 잡지 못한 학습의 방법은 엉키고 엉켜 더 저를 복잡하게 만들었습니다.</li>
<li>가끔, 새벽까지 공부를 할 때 23시에 커밋을 한 번 하고, 자정이 지나 또 커밋을 하면 이게 이틀치를 미리 해놓은 기분이 들어서 크게 현타가 왔습니다.</li>
</ul>
<h3 id="📆-3분기-2207--2209">📆 3분기 (22/07 ~ 22/09)</h3>
<h4 id="주요-활동-내용-2">주요 활동 내용</h4>
<ul>
<li><code>스프링 공부</code>를 하기 시작했습니다.(강의 구매)</li>
<li>무리하게 쌓아놓은 것들이 조금은 마무리 되었습니다. <code>백기선님의 자바스터디</code>를 마무리했습니다.</li>
<li>잠시 머리를 식히기 위해서 <code>여름 휴가</code>를 갔습니다.</li>
<li>공부할 시간을 조금 줄였습니다(<code>20:00 ~ 24:00</code> -&gt; <code>22:00 ~ 01:00</code>)</li>
<li>정신 차리고 <code>운동을 다시 시작</code>했습니다.</li>
<li>이번엔 모각코가 아니라 친구와 함께 공부할 과목을 정하고 같은 시간에 화상통화로 마주보며 공부하였습니다.</li>
<li>공통 관심사의 친구가 있으니 힘들 때 정신적인 위로를 많이 받았습니다. 동료는 참 소중하고 중요합니다.</li>
</ul>
<h4 id="장점-2">장점</h4>
<ul>
<li>마무리 되가는 몇가지가 보이니 스스로 미뤄놨던 것의 순서가 조금은 눈에 보이기 시작합니다. 자바를 공부하고 스프링을 공부하고 어떤 가지로 뻗어 나가야 원하는 공부를 할 수 있는지 대략적인 감이 이제서야 잡혔습니다.</li>
<li>같이 공부하는 친구에게 설명을 할 때 완벽하게는 못하지만 적어도 그 친구의 이해를 도울 정도로는 설명이 가능해졌습니다. 또, &quot;너는 이렇게 해봐 난 그렇게 안해서 괜히 빙빙 돌았던 거 같아&quot;라는 말도 해줄 수 있었습니다.</li>
</ul>
<h4 id="단점-2">단점</h4>
<ul>
<li>공부에 집착하던 하루가 반복되며 일상은 엉망이 되었습니다.</li>
<li>커밋을 안하면 큰 불안이 생겼습니다. 여행 중 정해진 시간에 공부를 못하는 것이 불안하였고, 수시로 공부할 내용에 대해 생각하다보니 여행도, 공부도 제대로 집중하지 못했습니다.</li>
<li>번아웃이라고 하는 느낌을 비슷하게 받은 것 같습니다.</li>
<li>이 때 일부러 의미 없는 커밋을 23시 50분에 하나 푸쉬하고 정신승리를 자주 했습니다. 부정적인 말을 많이 했고, 현타가 더 크게 왔습니다.</li>
</ul>
<h3 id="📆-4분기-중-현재-2210--2211">📆 4분기 중 현재 (22/10 ~ 22/11)</h3>
<h4 id="주요-활동-내용-3">주요 활동 내용</h4>
<ul>
<li>공부할 걸 산더미 처럼 쌓아놓은 제 자신을 반성하고 지금 하는 공부에만 잠시 집중하기로 합니다.</li>
<li>회사, 운동, 공부라는 루틴을 제 생활에 완벽하게 녹였습니다. 공부를 하는데 필요한 시간대를 확실하게 정해놓고 지키니 바른생활 청년이 되었습니다.</li>
<li>공부를 하기 싫은 날에는 쉬는 시간이다 생각하고 할당량(30분) 외에 개발 공부를 하지 않았습니다.</li>
<li>아직 인강, 책, 다른 사람의 정리글을 보고 정리(따라 쓰기)만 하는 공부방법은 여전합니다.</li>
</ul>
<h4 id="장점-3">장점</h4>
<ul>
<li>지금 하는 공부를 빨리 끝내고 다음에 공부하기로 계획한 것들을 시작하고 싶은 마음이 여전합니다. 이 정도를 얻은 것만 해도 감사합니다.</li>
<li>제 스스로의 부족한 점을 너무 잘 알게 되고 있습니다. 올 해에는 부족한 내용과 행동으로 <code>1일 1커밋</code>을 도전하고 있지만 이제 <code>1일 1커밋</code>을 하던 안하던 이런 부족한 점을 상기시키며 어떻게 공부를 해야 더 효율적으로 학습할 수 있을지 몸소 느끼게 되었습니다.</li>
<li>약 1년이라는 시간은 사실 헛되지 않고 있었습니다. 가끔은 의미 없는 커밋을 했어도 가끔일 뿐이며 그 외에는 귀찮음을 잘 이겨냈다는 생각이 듭니다.</li>
</ul>
<h4 id="단점-3">단점</h4>
<ul>
<li>생각보다 공부로 얻은게 크게 없는 것 같습니다. 인풋은 많았지만 &quot;그래서 어떤 공부를 집중적으로 하신거에요?&quot; 물으시면 &quot;그냥 이것저것,, 많이 찾아본 것 같아요&quot; 정도의 대답입니다.</li>
<li></li>
</ul>
<h2 id="느낌-감정-후기">느낌, 감정, 후기</h2>
<ul>
<li>집에서 공부하는 것도 질릴 때가 존재했습니다. 저는 방의 구조를 바꾸거나 밖에서 공부하는 등 다양한 환경에서 공부했습니다.</li>
<li>한 줄의 코드 작성, 또는 한 문장의 정리를 한다고 해도 고심해서 적은 한 줄이 더 뿌듯합니다.</li>
<li>저에게는 <code>1일 1커밋</code>이 <code>매일 커밋을 한다</code>가 아닌 <code>매일 나에게 맞는 학습법을 찾는다</code>라는 과정이였다고 생각이 됩니다.</li>
<li>1일 1커밋이 싫다면 하지 않아도 됩니다. 본인에게 맞는 방법을 고민하시고, 실제로 적용해보세요.</li>
<li>스스로 공부를 하는 방법을 모르거나 바꾸고 싶다면 적극 추천합니다.</li>
<li>스스로에게 스트레스를 주는 행동이 어떤 것인지를 아는 것은 중요합니다.</li>
<li>어떤 것이든 꾸준히 하는 것은 의외의 부분에서 긍정적인 결과를 얻습니다.</li>
<li>포기하고 싶을 때 한번 더 해보는 것은 정말정말 커다란 용기이며 포기하는 것은 부끄러운 것이 아닙니다. 전 되려 포기를 못해서 부끄럽습니다. 그래도 30일만 채우면 1년인데 아깝잖아요.</li>
<li>허리가 아플 정도로 앉는 것은 건강에 좋지 않습니다.</li>
<li>루틴은 엄격히 지키지 않으면 소용 없습니다. 스스로를 위한 루틴을 만들고 이를 지키는건 아주 중요하다고 생각합니다</li>
<li>가장 많이 느낀 감정은 기쁨, 슬픔, 집착, 포기, 후련함, 쓸쓸함, 성취감, 허탈함입니다. 마냥 좋기만 할 때는 전혀 없었습니다.</li>
<li>저는 이제 공부할 항목을 주간계획표로 설계할 것이고, 일일계획에는 더 구체적인 학습 목표를 적을 것이며, 한 달에 걸쳐 무엇이 부족했는지 체크할 것입니다.</li>
</ul>
<p>어리숙하고 못난 글솜씨를 수정하느라 포스팅하기 까지 하루가 더 걸렸네요. 끝까지 읽어주셨다면 너무 감사합니다. 제가 더 잘 성장할 수 있는 방법에 대해서 댓글로 조언을 주시면 더욱 감사할 것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자료구조] 해시 테이블]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%95%B4%EC%8B%9C-%ED%85%8C%EC%9D%B4%EB%B8%94</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%95%B4%EC%8B%9C-%ED%85%8C%EC%9D%B4%EB%B8%94</guid>
            <pubDate>Tue, 29 Nov 2022 13:21:26 GMT</pubDate>
            <description><![CDATA[<h2 id="해시-테이블">해시 테이블</h2>
<h3 id="해시-테이블-">해시 테이블 ?</h3>
<ul>
<li>Key, Value를 통해 데이터를 1:1로 저장하는 자료구조 중 하나이다. key를 통해 값(Value)을 검색하는데 이 때 시간 복잡도는 상수값인 O(1)이다.</li>
<li>해시 테이블의 index는 키 값을 해시 함수(단방향 수식)에 대입하여 나온 결과값에 의해 결정된다.</li>
<li>해시 함수를 사용해 키 값을 인덱스로 변환하는 과정을 해싱이라고 표현한다.</li>
</ul>
<h3 id="해싱-">해싱 ?</h3>
<p>키값을 해시 함수에 대입시켜 계산한 결과를 인덱스로 사용하여 값에 접근할 수 있게 하는 방법이다.</p>
<h3 id="해시-함수-">해시 함수 ?</h3>
<p>키 값을 값이 저장되는 인덱스로 바꾸기 위한 함수. 이 때 결과값은 중복 없이 충돌이 일어나지 않게 고유한 값으로 리턴해야 한다. 충돌이 적게 일어날 수록 좋은 해시 알고리즘이다.</p>
<p>고유한 값을 생성하는 해시 알고리즘은 다양하다. 대표적으로</p>
<ol>
<li><code>Division Method</code> - 나눗셈 법으로, 숫자 key를 테이블의 크기로 나눈 나머지 값을 인덱스로 사용한다.</li>
<li><code>Digit Folding</code> - key의 문자열을 ASCII 코드로 변환하여 그 값을 합한 뒤 인덱스로 사용한다.</li>
<li><code>Multiplication Method</code> - 곱셉법으로, 숫자로 된 Key값 K와 0과 1사이의 실수 A, 보통 2의 제곱수인 m을 사용하여 다음과 같은 계산을 해준다. h(k)=(kAmod1) × m</li>
</ol>
<h3 id="해시">해시</h3>
<p>해시 테이블의 키를 해시 함수에 넣게 되면 나오는 결과가 해시이다.</p>
<h3 id="해시의-충돌-">해시의 충돌 ?</h3>
<p>고유 값을 만들어 인덱스에 값을 넣으려고 하는데 그 인덱스에는 이미 값이 존재하여 충돌이 일어나는 것을<strong><em>해시 충돌(Hash Collision)</em></strong>이라고 한다. 이를 해결하는 방법은 여러가지인데, 크게는 <code>분리 연결법</code>, <code>개방 주소법</code>이 존재하고 그 중 가장 대표적으로<code>체이닝</code>,<code>선형 탐색</code>이 있다. 물론 더 많은 방법도 존재하는데 일단 아는 것만 정리하고 나중에 추가할 예정이다.</p>
<ul>
<li><code>체이닝</code> - 저장하고자 하는 저장소인 버킷(또는 슬롯)에 값이 존재한다면 기존 값과 새로운 값을 연결 리스트(Linked List)로 연결하는 방법이다.</li>
<li><code>개방 주소법(선형 탐색)</code> - 충돌이 일어났을 때 체이닝과 달리 1:1 매핑을 지키기 위해서 테이블의 비어있는 공간에 데이터를 저장하는 방법이다. 만약 테이블의 공간이 비어있지 않다면 테이블의 공간을 늘려 놓는다.</li>
</ul>
<h4 id="체이닝의-리해싱rehashing">체이닝의 리해싱(ReHashing)</h4>
<p>체이닝은 자료 구조에 배열의 크기보다 더 많은 값을 담을 수 있다. 추가를 하면 링크드 리스트에 추가되기 때문이다. 따라서 체이닝을 사용하면 적재율이 1을 넘어갈 수 있다.</p>
<p>하지만 적재율이 1을 넘어간다는 것은 각 배열에 존재하는 링크드 리스트에 요소들이 많아졌다는 것을 의미한다. 만약 무언가를 배열에서 찾아야 한다면 해당 인덱스의 배열이 가진 링크드 리스트에 포함된 각 요소들을 탐색하며 찾아야 하기 때문에 효율적이지 않다.</p>
<p>이 때는 배열의 크기보다 큰 새로운 배열을 생성해 기존 배열의 요소들을 리해싱하여 새 배열에 옮기는 작업을 통해 적재율을 낮춘다.</p>
<h3 id="정리">정리</h3>
<ul>
<li>해시 테이블이란 키 - 값 형태로 데이터를 다루는 자료구조이다.</li>
<li>해시 테이블의 삽입, 삭제, 검색의 시간 복잡도는 O(N)이다. 하지만 체이닝 방식에서 최악의 경우 O(N)의 복잡도가 나올 수 있다.</li>
<li>해시 테이블에서 특정 키를 구분하기 위한 함수를 해시 함수라고 한다.</li>
<li>해시 테이블에서는 키를 hashCode 함수에 넣고 반환된 정수를 버킷 또는 슬롯의 인덱스로 사용한다.</li>
<li>해시 함수의 특징은 대략적으로 다음과 같다.<ul>
<li>빨라야 한다.</li>
<li>같은 객체에 대해 같은 값을 반환해야 한다.</li>
<li>데이터의 충돌을 최대한 피해야 한다.</li>
<li>일방향성을 갖는다.</li>
</ul>
</li>
<li>해시 함수가 충돌을 피하기 위해 다음과 같은 방법이 있다.<ul>
<li>체이닝</li>
<li>개방 주소법<ul>
<li>선형 조사법</li>
<li>2차원 조사법</li>
<li>더블 해싱</li>
</ul>
</li>
</ul>
</li>
<li>자바에서 구현된 해시 테이블의 최대 적재율은 3/4이다. 3/4가 넘으면 테이블의 크기를 2배로 늘려 리해싱한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 스터디 - 15주차]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%84%B0%EB%94%94-15%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%84%B0%EB%94%94-15%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Wed, 24 Aug 2022 04:07:45 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p>자바의 람다식에 대해 학습하세요.</p>
<h1 id="학습할-것-필수">학습할 것 (필수)</h1>
<ul>
<li>람다식 사용법</li>
<li>함수형 인터페이스</li>
<li>Variable Capture</li>
<li>메소드, 생성자 레퍼런스</li>
</ul>
<h2 id="람다">람다</h2>
<p>JDK 1.8 부터 등장하였다. 람다가 등장하면서 자바는 객체 지향인 동시에 함수형 프로그래밍 언어가 되었다.</p>
<ul>
<li>람다식은 익명함수라고도 한다 -&gt; 메서드명과 리턴값이 없기 때문이다.</li>
<li>람다식은 메서드를 위해 클래스를 새로 만들거나 메서드를 위한 객체를 생성하는 일련의 과정이 필요하지 않다.</li>
<li>람다식은 메서드의 매개변수로 전달되는 것이 가능하며 메서드의 결과로도 반환될 수 있다.</li>
</ul>
<h2 id="람다식-사용법">람다식 사용법</h2>
<blockquote>
<p>반환 타입, 메서드 이름을 제거하고 매개변수 선언, 구현 사이에 <code>-&gt;</code>를 추가한다.</p>
</blockquote>
<ul>
<li>사용법</li>
</ul>
<pre><code class="language-java">(매개변수 선언) -&gt; {
    구현부 문장
}</code></pre>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">(int a, int b) -&gt; {
    a &gt; b ? a : b;
}</code></pre>
<h3 id="매개변수가-하나라면">매개변수가 하나라면</h3>
<ul>
<li><p>()를 생략할 수 있다. 단, 매개변수에 타입이 있으면 생략이 불가능하다.</p>
</li>
<li><p>구현부에 문장이 한 줄 이면, {}를 생략할 수 있다(<code>;</code>을 붙히면 안된다). 단, 리턴문이 있는 경우에는 생략할 수 없다.</p>
</li>
</ul>
<pre><code class="language-java">a -&gt; a * a // O
int a -&gt; a * a // X
(int a) -&gt; a * a // O


// BEFORE
(String name, int i ) -&gt; {
    System.out.println(name + &quot; = &quot; + i)
}    

// AFTER
(String name, int i ) -&gt; System.out.println(name + &quot; = &quot; + i)

// ERROR
(int a, int b) -&gt; return a + b; // X
(int a, int b) -&gt; { return a + b; } // O</code></pre>
<h2 id="함수형-인터페이스">함수형 인터페이스</h2>
<p>자바에서 모든 메서드는 클래스 내에 포함 되어야 하는데 람다식은 <strong>익명 클래스 객체와 동등하다.</strong></p>
<ul>
<li>람다식</li>
</ul>
<pre><code class="language-java">(int a, int b) -&gt; a &gt; b ? a : b;</code></pre>
<ul>
<li>람다는 다음 객체처럼 취급된다.</li>
</ul>
<pre><code class="language-java">new Object() {
    int max(int a, int b) {
        return a &gt; b ? a : b;
    }
}</code></pre>
<p>참조 변수가 있어야 정의된 익명 객체의 메서드를 호출할 수 있다. 참조 변수의 타입은 참조형이기 때문에 클래스, 인터페이스가 되어야 한다.</p>
<ul>
<li>max() 메서드를 정의한 인터페이스</li>
</ul>
<pre><code class="language-java">public interface MyFunction() {
    int max(int a, int b);
}</code></pre>
<ul>
<li>max()를 구현한 객체</li>
</ul>
<pre><code class="language-java">MyFunction f = new MyFunction() {
    public int max(int a, int b) {
        return a &gt; b ? a : b;
    }
};</code></pre>
<ul>
<li>max()를 람다식으로 구현</li>
</ul>
<pre><code class="language-java">MyFunction f = (int a, int b) -&gt; a &gt; b ? a : b; 
int max = f.max(3, 5); // 5</code></pre>
<p>인터페이스를 구현한 익명 객체를 람다식으로 표현 가능한 이유가 람다식도 하나의 익명 객체이고 MyFunction 인터페이스를 구현한 람다식의 매개변수의 타입 개수, 반환값이 일치하기 때문이다.</p>
<ul>
<li>함수형 인터페이스</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
interface MyFunction {
    public abstract int max(int a, int b);
}</code></pre>
<p>함수형 인터페이스는 추상 메서드를 단 하나만 가진다. 그래야 람다식과 메서드가 1:1 관계로 연결된다. 하나의 메서드가 선언된 인터페이스를 정의하여 람다식을 다루면 기존의 자바 규칙을 어기지 않는다. 따라서 인터페이스를 통해 람다식을 다루게 되었고 람다식을 다루기 위한 인터페이스를 함수형 인터페이스라고 부른다. 단, static, default 메서드는 개수의 제한이 없다.</p>
<h3 id="함수형-인터페이스-타입의-매개변수와-반환-타입">함수형 인터페이스 타입의 매개변수와 반환 타입</h3>
<p>메서드의 매개변수가 <code>함수형 인터페이스 타입</code>이면 이 메서드를 호출할 때 람다식을 참조하는 참조변수를 매개변수로 지정해야 한다는 뜻이다.</p>
<pre><code class="language-java">@FuncitonalInterface
interface MyFunction {
    void myMethod();
}</code></pre>
<pre><code class="language-java">void aMethod(MyFunction f) {
    f.myMethod();
}

MyFunction f = () -&gt; System.out.println(&quot;myMethod()&quot;);

aMethod(f);</code></pre>
<ul>
<li>참조변수 없이 직접 람다식을 매개변수로 지정</li>
</ul>
<pre><code class="language-java">aMethod(() -&gt; System.out.println(&quot;myMethod()&quot;));</code></pre>
<p>aMethod()의 반환타입이 void가 아닌 함수형 인터페이스라면 람다식을 직접 반환할 수 있다.</p>
<pre><code class="language-java">MyFunction myMethod() {
    MyFunction f = () -&gt; {};
    return f;
}
// 위 코드를 다음과 같이 반환할 수 있다.
MyFunction myMethod() {
    return () -&gt; {};
}</code></pre>
<h4 id="예제">예제</h4>
<ul>
<li>함수형 인터페이스 MyFunction</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
interface MyFunction {
    void run();
}</code></pre>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">public class Example {
    static void execute(MyFunction f) {
        f.run();
    }

    static MyFunction getMyFunction() {
        return () -&gt; System.out.println(&quot;f3.run()&quot;);
    }

    public static void main(String[] args) {
        MyFunction f1 = () -&gt; System.out.println(&quot;f1.run()&quot;);

        MyFucntion f2 = new MyFunction() {
            @Override
            public void run() {
                System.out.pritnln(&quot;f2.run()&quot;);
            }
        }

        MyFunction f3 = getMyFunction();

        f1.run();
        f2.run();
        f3.run();
    }
}</code></pre>
<ul>
<li>실행 결과</li>
</ul>
<pre><code>f1.run()
f2.run()
f3.run()</code></pre><h2 id="variable-capture">Variable Capture</h2>
<p>람다식의 바디 내에서 외부에 정의된 변수를 사용할 수 있는 것을 말한다. 이렇게 람다식의 함수 파라미터로 전달되는 변수가 아닌 외부 정의 변수를 <strong>자유 변수(Free Variable)</strong>라고 한다.</p>
<p>람다식 내부에서 자유변수를 참조하는 것을 람다 캡처링이라고 한다.</p>
<h3 id="람다-캡처링의-제약-조건">람다 캡처링의 제약 조건</h3>
<p>람다 캡처링이 일어나기 위해서는 외부에 정의된 지역 변수가 final 키워드로 선언되어야 한다는 조건이 있다.</p>
<p>final로 선언되지 않은 지역 변수는 final처럼 동작하게 해야한다(값이 변경되면 안된다.)</p>
<h4 id="조건-정리">조건 정리</h4>
<ol>
<li>외부 지역 변수가 final 키워드로 선언</li>
<li>외부 지역 변수가 final로 선언되지 않았다면 final처럼 동작하게 해야함</li>
</ol>
<p><strong>외부 지역 변수가 값이 바뀌거나 다시 할당되면 안된다.</strong></p>
<h3 id="지역-변수-캡처local-variable-capture">지역 변수 캡처(Local Variable Capture)</h3>
<p>지역변수 캡쳐는 람다식 바디 외부에 선언된 지역변수에 접근할 수 있다.</p>
<pre><code class="language-java">String localVariable = &quot;hello&quot;;

new Thread(() -&gt; {
   System.out.println(localVariable + &quot;, new Thread&quot;); 
});</code></pre>
<p>람다식 내부에서 지역변수를 참조할 수 있는 이유는 변수에 대한 참조가 effectively final이기 때문이다.</p>
<p>람다식에서 지역변수를 참조하려면 위에서 언급한 것처럼 final이거나 final이 아니지만 값이 변경되지 않아야 한다.(만약 람다식 이전, 이후로 변수가 변경된다면 컴파일러가 람다식 내부에 에러를 띄운다.)</p>
<h3 id="인스턴스-변수-캡처instance-variable-capture">인스턴스 변수 캡처(Instance Variable Capture)</h3>
<p>람다식은 람다를 생성하는 객체의 인스턴스 변수를 캡쳐할 수 있다. 박스를 생성하는 BoxFactory를 통해 알아보자.</p>
<ul>
<li>BoxFactory</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
public interface BoxFactory {
    public void createBox();
}</code></pre>
<ul>
<li>Creator</li>
</ul>
<pre><code class="language-java">public class Creator {
    private int boxCount = 0;

    void createOneBox() {
        BoxFactory factory = () -&gt; this.boxCount++;
        factory.createBox();
    }
    public static void main(String[] args) {
        Creator creator = new Creator();
        creator.createOneBox();
        System.out.println(creator.boxCount);   
    }
}</code></pre>
<p>Creator 클래스의 createOneBox() 메서드에서 람다식으로 BoxFactory의 추상메서드를 구현하며 메소드를 실행한다. 메소드는 Creator의 boxCount를 1씩 추가한다.</p>
<p>예제의 결과로 boxCount는 1이 되는 것을 확인할 수 있다.</p>
<blockquote>
<p>하지만 지역변수 캡쳐와는 다르게 외부 변수의 값을 변경할 수 있다.</p>
</blockquote>
<h3 id="정적-변수-캡쳐static-variable-capture">정적 변수 캡쳐(Static Variable Capture)</h3>
<p>람다식은 정적 변수를 캡처할 수 있는데, 정적 변수는 원래 자바 애플리케이션 내부 어디에서든지 접근이 가능하다. 인스턴스 변수와 마찬가지로 정적 변수의 값이 변경되어도 계속 캡쳐할 수 있다.</p>
<h3 id="변수-종류별로-캡처의-제약조건이-생기는-이유">변수 종류별로 캡처의 제약조건이 생기는 이유</h3>
<blockquote>
<p>출처 : <a href="https://bugoverdose.github.io/development/lambda-capturing-and-free-variable/">bugoverdose님 블로그</a></p>
</blockquote>
<p>람다식 내부에서 클래스의 static 필드 또는 인스턴스 필드를 캡처하는 데에는 아무런 제약이 존재하지 않았다.</p>
<p>하지만 지역변수를 람다식에서 참조하려고 하는 경우는 <strong>값이 변경되지 않는 final || effectively final인 경우에만 참조가 가능</strong>했다.</p>
<p>지역변수에 이런 제약이 생기는 이유는 JVM의 메모리 구조와 관련이 있다. 인스턴스나 클래스 변수는 JVM의 힙, 메서드 영역에 저장되지만 지역변수는 스택에 저장되기 때문이다.</p>
<p>만약 지역변수의 값을 캡처한 람다를 반환하는 메서드가 있다면 해당 메서드가 종료될 때 메서드에서 선언되고 사용된 모든 지역변수는 할당이 해제된다.</p>
<p>그럼에도 불구하고 람다는 계속 지역변수를 아무런 문제없이 참조하여 사용할 수 있는데, 이는 <strong>람다 내부에서 사용되는 지역변수는 원본 지역변수를 복제한 데이터</strong>이기 때문이다. 그렇기 때문에 지역변수의 할당이 해제되어도 람다에선 유지가 되는 것이며 값의 변경이 이뤄지지 않아야 한다는 제약이 생겨난 것이다.</p>
<h4 id="주의-사항">주의 사항</h4>
<p>static, instance 필드의 경우는 제약이 존재하지 않는다. 값의 변경이 자유롭게 이뤄진다는 것이다. 위에서 언급했듯 이런 필드는 스택이 아닌 힙, 메서드(데이터) 영역에 생성되기 때문이다. </p>
<p>그런데 이처럼 값을 자유자재로 바꿀수 있게 되면 무분별한 참조로 인해 애플리케이션 전체에서 공유되는 가변적 자원의 값이 결국 특정 시점에서 에상치 못했던 값으로 나오는 등 여러 문제가 생기기 마련이다.</p>
<h2 id="메소드-생성자-레퍼런스">메소드, 생성자 레퍼런스</h2>
<p>람다식을 사용하면 메서드를 아주 간결하게 표현할 수 있다. 하지만 람다식이 하나의 메서드만 호출하는 경우 더 간결하게 표현할 수 있는 방법인 <strong>메서드 참조(레퍼런스)를 사용할 수 있다.</strong></p>
<h3 id="예제-1">예제 1</h3>
<ul>
<li>람다식을 사용하는 경우</li>
</ul>
<pre><code class="language-java">Function&lt;String, Integer&gt; f = (String s) -&gt; Integer.parseInt(s);</code></pre>
<ul>
<li>메서드 레퍼런스</li>
</ul>
<pre><code class="language-java">Function&lt;String, Integer&gt; f = Integer::parseInt;</code></pre>
<p>람다식의 일부는 생략되었지만 컴파일러는 parseInt()메서드의 선언부에서, 또는 좌변의 지네릭타입으로부터 쉽게 알아낼 수 있다.</p>
<h3 id="예제-2">예제 2</h3>
<ul>
<li>람다식을 사용하는 경우</li>
</ul>
<pre><code class="language-java">BiFunction&lt;String, String, Boolean&gt; f = (s1, s2) -&gt; s1.equals(s2);</code></pre>
<ul>
<li>메서드 레퍼런스</li>
</ul>
<pre><code class="language-java">BiFunction&lt;String, String, Boolean&gt; f = String::equals;</code></pre>
<p>f의 타입인 BiFunction을 봐도 String 타입이 2개오는 것을 알 수 있기 때문에 s1, s2는 생략되어도 된다. 그럼 두 개의 String 값을 서로 equals()로 비교하고 그 값을 Boolean으로 반환하면 좋겠지만 같은 메서드가 다른 클래스에 존재할 수도 있기 때문에 equals앞에는 클래스 이름이 반드시 필요하다.</p>
<h3 id="예제-3">예제 3</h3>
<p>이미 생성된 객체의 메서드를 람다식에서 사용한 경우, 클래스 이름대신 참조 변수를 적어줘야 한다.</p>
<ul>
<li>람다식</li>
</ul>
<pre><code class="language-java">MyClass obj = new MyClass();

Function&lt;String, Boolean&gt; f = (x) -&gt; obj.equals(x);</code></pre>
<ul>
<li>메서드 레퍼런스</li>
</ul>
<pre><code class="language-java">MyClass obj = new MyClass();

Function&lt;String, Boolean&gt; f = MyClass::equals;</code></pre>
<h3 id="생성자-레퍼런스">생성자 레퍼런스</h3>
<p>생성자를 호출하는 람다 표현식도 앞서 정리한 메서드 레퍼런스와 다를 것 없다. 단순히 객체를 생성하고 반환하는 람다 표현식은 생성자 레퍼런스로 변환할 수 있다. 당연한 얘기지만 생성자가 없으면 컴파일 에러가 발생한다.</p>
<ul>
<li>람다식</li>
</ul>
<pre><code class="language-java">Supplier&lt;MyClass&gt; s = () -&gt; new MyClass();</code></pre>
<ul>
<li>생성자 레퍼런스</li>
</ul>
<pre><code class="language-java">Supplier&lt;MyClass&gt; s = MyClass::new;</code></pre>
<h3 id="정리">정리</h3>
<p>메서드 레퍼런스를 만드는 유형은 세가지가 있다.</p>
<ol>
<li>정적 메서드 참조 - Integer의 parseInt()를 Integer::parseInt로 사용가능</li>
<li>다양한 형식의 인스턴스 메서드 참조 - String의 length(), equals() 등 메서드를 String::length, String::equals로 사용가능</li>
<li>기존 객체의 인스턴스 메서드 참조 - MyClass의 equals()메서드를 MyClass::equals로 사용 가능</li>
</ol>
<p>생성자 레퍼런스를 만들 수 있다. 단, 생성자가 있어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스터디 - 14주차]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-14%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-14%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Fri, 19 Aug 2022 04:27:28 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p>자바의 제네릭에 대해 학습하세요.</p>
<h1 id="학습할-것-필수">학습할 것 (필수)</h1>
<ul>
<li>제네릭 사용법</li>
<li>제네릭 주요 개념 (바운디드 타입, 와일드 카드)</li>
<li>제네릭 메소드 만들기</li>
<li>Erasure</li>
</ul>
<h2 id="제네릭이란">제네릭이란</h2>
<ul>
<li>JAVA 5부터 새로 추가된 기능이다.</li>
<li>객체의 타입을 파라미터화 해서 컴파일 시에 구체적인 타입이 결정되도록 한다.</li>
</ul>
<h3 id="장점">장점</h3>
<ol>
<li>컴파일시 강한 타입 체크 :  컴파일 과정에서 타입이 결정되기 때문에 의도하지 않은 타입이 잘못 형변환되어 사용될 수 있는 문제를 막는다.</li>
<li>타입 변환을 제거 : 제네릭을 사용하지 않으면 불필요한 타입 변환을 하는데, 이는 성능에 악영향을 끼친다. 제네릭을 사용함으로써 이 과정을 생략할 수 있다. 또, 코드가 간결해진다.</li>
</ol>
<h2 id="제네릭-사용법">제네릭 사용법</h2>
<p>제네릭 타입은 클래스와 메서드에 사용할 수 있다. 선언시 클래스 또는 메서드 뒤에 <code>&lt;&gt;</code>이 붙고 <code>&lt;&gt;</code>사이에는 타입 파라미터가 위치한다.</p>
<h3 id="선언">선언</h3>
<ul>
<li>제네릭을 사용하지 않았을 때</li>
</ul>
<pre><code class="language-java">public class NonGenericsClass {
    Object item;

    void setItem(Object item) {
        this.item = item;
    }

    Object getItem() {
        return item;
    }
}</code></pre>
<ul>
<li>제네릭을 사용할 때</li>
</ul>
<pre><code class="language-java">public class GenericsClass&lt;T&gt; {
    T item;

    void setItem(T item) {
        this.item = item;
    }

    T getItem() {
        return item;
    }
}</code></pre>
<ul>
<li>사용 예제</li>
</ul>
<pre><code class="language-java">public class GenericsClass&lt;T&gt; {
    ...
  public static void main(String[] args) {
        GenericsClass&lt;Integer&gt; integerGenerics = new GenericsClass&lt;&gt;();
        integerGenerics.setItem(3);
        Integer item = integerGenerics.getItem();
        System.out.println(item);

        GenericsClass&lt;String&gt; stringGenerics = new GenericsClass&lt;&gt;();
        stringGenerics.setItem(&quot;hi&quot;);
        String item = stringGenerics.getItem();
        System.out.println(item);
    }
}</code></pre>
<p>타입 파라미터에 Integer, String을 넣어봤다. 컴파일 시 클래스 타입이 달라지는 것을 확인할 수 있다.</p>
<h2 id="제네릭-주요-개념바운디드-타입-와일드-카드">제네릭 주요 개념(바운디드 타입, 와일드 카드)</h2>
<h3 id="바운디드-타입">바운디드 타입</h3>
<p>제네릭으로 사용되는 파라미터 타입을 제한할 수 있는 것을 말한다.</p>
<p>예를 들어 숫자를 연산하는 제네릭 메소에는 Number타입과 그 하위 타입의 인스턴스만 가져야 한다. 이것이 바운디드 타입이 필요한 이유다. </p>
<p>바운디드 타입은 extends 키워드를 붙이고 상위 타입을 명시하면 되는데 이 때 extends는 상속이 아닌 종류의 의미로 사용된다.</p>
<pre><code class="language-java">public &lt;T extends 상위타입&gt; 리턴타입 메소드명(매개변수, ...) {
    ...
}</code></pre>
<ul>
<li>타입 파라미터에 저장되는 구체적인 타입은 <strong>상위 타입이거나 상위 타입의 하위 || 구현 클래스만 가능하다.</strong></li>
<li>메서드의 중괄호 안에서 타입 파라미터의 변수로 사용 가능한 것은 상위 타입의 멤버로 제한된다.</li>
</ul>
<h3 id="와일드-카드">와일드 카드</h3>
<p>일반적으로 코드에서의 ?를 와일드카드라고 부른다. 제네릭 타입을 매개변수나 리턴타입으로 사용할 때 바운디드 타입과 마찬가지로 타입 파라미터를 제한할 목적으로 사용된다. </p>
<p>와일드 카드를 사용하는 경우는 상위, 하위 타입으로의 변환이 가능한 것을 제한하기 위함이다.</p>
<h4 id="와일드-카드-형태">와일드 카드 형태</h4>
<ol>
<li><p>제네릭타입&lt;?&gt; : 제한 없음</p>
<ul>
<li>타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스가 대입될 수 있다.</li>
</ul>
</li>
<li><p>제네릭타입&lt;? extends 상위타입&gt; : UpperBounded WildCards</p>
<ul>
<li>타입 파라미터를 대치하는 구체적인 타입으로 하위 타입만 올 수 있다.</li>
</ul>
</li>
<li><p>제네릭타입&lt;? super 하위타입&gt; : LowerBounded WildCards</p>
<ul>
<li>타입 파라미터를 대치하는 구체적인 타입으로 상위 타입만 올 수 있다.</li>
</ul>
</li>
</ol>
<h2 id="제네릭-메서드-만들기">제네릭 메서드 만들기</h2>
<h3 id="제네릭-메서드란">제네릭 메서드란</h3>
<p>매개변수 타입과 리턴 타입으로 타입 파라미터를 갖는 메서드를 말한다.</p>
<h3 id="선언-방법">선언 방법</h3>
<ul>
<li>리턴 타입 앞에 <code>&lt;&gt;</code>를 추가하고 타입 파라미터를 기술한다.</li>
<li>타입 파라미터를 리턴타입과 매개변수에 사용한다.</li>
</ul>
<pre><code class="language-java">public &lt;타입 파라미터&gt; 리턴타입 메소드명(매개변수, ...) {
    ...
}</code></pre>
<ul>
<li>클래스의 타입 파라미터와 메소드의 타입 파라미터의 이름이 같다면?<pre><code>- 메소드에 대입되는 타입 파라미터는 클래스에 정의된 타입 파라미터와는 별개이다. 같은 문자 T를 대입해도 서로 다르다.</code></pre></li>
</ul>
<h2 id="erasure">Erasure</h2>
<blockquote>
<p>참조 </p>
<ul>
<li><p><a href="https://xxxelppa.tistory.com/206?category=858435">https://xxxelppa.tistory.com/206?category=858435</a></p>
</li>
<li><p><a href="https://azurealstn.tistory.com/111?category=972027">https://azurealstn.tistory.com/111?category=972027</a></p>
</li>
<li><p><a href="https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html">https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html</a></p>
</li>
</ul>
</blockquote>
<p>제네릭은 제네릭 도입 전 코드와의 호환성 유지를 위한 작업을 동시에 진행했다. 따라서 코드의 호환성을 위해 erasure 방식을 사용한다.</p>
<p>제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 Raw type이라고 한다. Raw type을 사용하는 것</p>
<p>erasure 방식은 컴파일 타입에만 타입 제약 조건을 정의하고, 런타임에서는 타입을 제거한다.</p>
<ul>
<li>타입 소거 전</li>
</ul>
<pre><code class="language-java">public class Example&lt;T&gt; {
    public void method(T type) {
        System.out.println(type.toString());
    }   
}</code></pre>
<ul>
<li>타입 소거 후</li>
</ul>
<pre><code class="language-java">public class Example {
    public void method(Object type) {
        System.out.println(type.toString());
    }
}</code></pre>
<p>위처럼 unbounded type에서는 Object로 바뀌게 된다.</p>
<p>그럼 bounded type은 어떻게 바뀔까?</p>
<ul>
<li>타입 소거 전</li>
</ul>
<pre><code class="language-java">public class Example&lt;T extends Comparable&lt;T&gt;&gt; {
    public void method(T type) {
        System.out.println(type.toString());
    }
}</code></pre>
<ul>
<li>타입 소거 후</li>
</ul>
<pre><code class="language-java">public class Example {
    public void method(Comparable type) {
        System.out.println(type.toString());
    }
}</code></pre>
<p>&lt;T extends Comparable<T>&gt;와 같이 제한된 제네릭을 걸어두면 제한시킨 타입인 Comparable 타입으로 변환이 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스터디 - 13주차]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-13%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-13%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Fri, 19 Aug 2022 04:26:39 GMT</pubDate>
            <description><![CDATA[<h2 id="목표">목표</h2>
<p>자바의 Input과 Ontput에 대해 학습하세요.</p>
<h2 id="학습할-것-필수">학습할 것 (필수)</h2>
<ul>
<li>스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O</li>
<li>InputStream과 OutputStream</li>
<li>Byte와 Character 스트림</li>
<li>표준 스트림 (System.in, System.out, System.err)</li>
<li>파일 읽고 쓰기</li>
</ul>
<h2 id="스트림-stream--버퍼-buffer--채널-channel-기반의-io">스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O</h2>
<h3 id="javaio와-javanio"><code>java.io</code>와 <code>java.nio</code></h3>
<blockquote>
<p> 각 기반의 I/O를 살펴보기에 앞서 자바에서의 I/O는 <code>java.io</code>, <code>java.nio</code>의 두 패키지로 나눠진다. 나는 이 둘을 큰 틀로 잡고 비교하여 정리하였다.</p>
</blockquote>
<h3 id="javaio와-javanio의-차이점"><code>java.io</code>와 <code>java.nio</code>의 차이점</h3>
<p>io, nio는 입출력, 버퍼, 비동기, 블로킹/넌블로킹의 방식이 서로 다르다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>IO</th>
<th>NIO</th>
</tr>
</thead>
<tbody><tr>
<td>입출력 방식</td>
<td>스트림 방식</td>
<td>채널 방식</td>
</tr>
<tr>
<td>버퍼 방식</td>
<td>넌버퍼(non-buffer)</td>
<td>버퍼(buffer)</td>
</tr>
<tr>
<td>비동기 방식</td>
<td>지원 안 함</td>
<td>지원</td>
</tr>
<tr>
<td>블로킹 / 넌블로킹 방식</td>
<td>블로킹 방식만 지원</td>
<td>블로킹 / 넌블로킹 방식 모두 지원</td>
</tr>
</tbody></table>
<p>위 표를 보고 각 방식의 차이점에 대해 알아보자</p>
<h4 id="스트림-vs-채널">스트림 vs 채널</h4>
<ul>
<li>IO는<ul>
<li>스트림 기반이다. 스트림은 입력 스트림과 출력 스트림이 따로 구분되어 있으며 예를 들어 파일을 읽고 쓴다면 각자의 스트림(입, 출력)이 필요하다. 스트림은 FIFO(First In First Out)구조이다.</li>
</ul>
</li>
<li>NIO는<ul>
<li>채널기반이며 채널은 양방향이다. 파일을 읽고 쓴다면 하나의 채널로 입, 출력이 가능하다.</li>
</ul>
</li>
</ul>
<h4 id="버퍼-vs-넌버퍼">버퍼 vs 넌버퍼</h4>
<ul>
<li><p>IO는 </p>
<ul>
<li>출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를 읽는다.(넌버퍼는 느리다)</li>
<li>보조 스트림(BufferedInputStream, BufferedOutputStream)을 연결하여 사용하기도 한다. </li>
<li>스트림에서 읽은 데이터를 즉시 처리한다. 그렇기 때문에 스트림에서 입력된 전체 데이터를 별도로 지정하지 않으면 데이터의 위치 이동이 자유롭지 않다.</li>
</ul>
</li>
<li><p>NIO는</p>
<ul>
<li>버퍼를 사용해서 입출력을 한다(IO보다 성능이 좋다). 채널은 버퍼에 저장된 데이터를 출력하고 입력된 데이터를 버퍼에 저장한다.</li>
<li>읽은 데이터를 무조건 버퍼에 저장하기 때문에 버퍼 내에서 데이터의 위치를 이동해가면서 필요한 부분만 읽고 쓸 수 있다.</li>
</ul>
</li>
<li><p>버퍼란</p>
<ul>
<li>데이터를 전송하는 상호간의 장치에서 고속의 장치와 저속의 장치간의 속도 차이로 인한 저속 장치를 고속 장치가 기다려주는 현상을 줄여주는 기술이다. 데이터를 임시로 저장한다.</li>
</ul>
</li>
<li><p>버퍼를 쓰면 빨라지는 이유</p>
<ul>
<li>OS레벨의 콜 시스템 횟수가 줄어들기 때문에 빨라지는 것이다.</li>
<li></li>
</ul>
</li>
</ul>
<h4 id="블로킹-vs-넌블로킹">블로킹 vs 넌블로킹</h4>
<ul>
<li>IO는<ul>
<li>블로킹이 가능하다.</li>
<li>read()를 호출하면 입력되기 전까지 Thread가 블로킹 된다. 반대로 write()를 호출해도 마찬가지로 Thread가 블로킹 된다.   빠져 나오는 유일한 방법은 스트림을 닫는 것이다.</li>
</ul>
</li>
<li>NIO는<ul>
<li>블로킹, 넌블로킹 특징을 모두 가진다.</li>
<li>NIO 블로킹은 인터럽트를 사용해서 빠져나올 수 있다.</li>
<li>NIO 넌블로킹은 데이터가 바로 읽고 쓸 수 있는 상태의 채널만 선택해서 작업 스레드가 처리하기 때문에 블로킹되지 않는다(Selector를 연결한다).</li>
</ul>
</li>
</ul>
<h4 id="io는-왜-느릴까">IO는 왜 느릴까?</h4>
<p>자바에서 I/O를 처리하는 영역은 유저영역 / 커널영역으로 구분할 수있다.</p>
<ul>
<li>유저영역 - 실행 중인 프로세스 제어 가능</li>
<li>커널영역 - 하드웨어까지 제어 가능</li>
</ul>
<h2 id="inputstream과-outputstream">InputStream과 OutputStream</h2>
<table>
<thead>
<tr>
<th>InputStream</th>
<th>OutputStream</th>
</tr>
</thead>
<tbody><tr>
<td>abstract int read( )</td>
<td>abstract void write(int b)</td>
</tr>
<tr>
<td>int read(byte[ ] b)</td>
<td>void write(byte[ ] b)</td>
</tr>
<tr>
<td>int read(byte[ ] b, int off, int len)</td>
<td>void write(byte[ ] b, int off, int len)</td>
</tr>
</tbody></table>
<h3 id="inputstream">InputStream</h3>
<p><img src="https://t1.daumcdn.net/cfile/tistory/9961443C5C1E016C2B" alt="inputStream"></p>
<blockquote>
<p>출처 코딩팩토리  : <a href="https://coding-factory.tistory.com/281">https://coding-factory.tistory.com/281</a></p>
</blockquote>
<p>InputStream은 <strong>바이트 기반 입력 스트림의 최상위 추상클래스이다.</strong> 읽기에 대한 다양한 추상 메서드가 있으며 목적에 따라 데이터를 입력 받을 수 있다.</p>
<h3 id="outputstream">OutputStream</h3>
<p><img src="https://t1.daumcdn.net/cfile/tistory/99C0C7335C1E049323" alt="outputStream"></p>
<p>​    출처 코딩 팩토리 : <a href="https://coding-factory.tistory.com/281">https://coding-factory.tistory.com/281</a></p>
<p>OutputStream은 <strong>바이트 기반 출력 스트림의 최상위 추상클래스이다.</strong></p>
<h2 id="byte와-character-스트림">Byte와 Character 스트림</h2>
<h3 id="바이트-기반-스트림---inputstream-outputstream">바이트 기반 스트림 - InputStream, OutputStream</h3>
<p>스트림은 바이트 단위로 데이터를 전송하며 입출력 대상에 따라 다음과 같은 입출력 스트림이 있다.</p>
<p>바이트기반은 입출력의 단위가 1byte라는 것이다. </p>
<table>
<thead>
<tr>
<th>입력 스트림</th>
<th>출력 스트림</th>
<th>입출력 대상의 종류</th>
</tr>
</thead>
<tbody><tr>
<td><strong>File</strong>InputStream</td>
<td><strong>File</strong>OutputStream</td>
<td>파일</td>
</tr>
<tr>
<td><strong>ByteArray</strong>InputStream</td>
<td><strong>ByteArray</strong>OutputStream</td>
<td>메모리(byte배열)</td>
</tr>
<tr>
<td><strong>Piped</strong>InputStream</td>
<td><strong>Piped</strong>OutputStream</td>
<td>프로세스(프로세스간의 통신)</td>
</tr>
<tr>
<td><strong>Audio</strong>InputStream</td>
<td><strong>Audio</strong>OutputStream</td>
<td>오디오장치</td>
</tr>
</tbody></table>
<p>위와 같이 여러 종류의 입출력 스트림이 있으며 필요에 따라 원하는 스트림을 사용하면 된다.</p>
<h3 id="문자-기반-스트림---reader-writer">문자 기반 스트림 - Reader, Writer</h3>
<p>앞서 바이트 기반 스트림은 1byte를 입출력의 단위로 사용한다고 하였다. 하지만 자바에서 한 문자는 2byte를 차지하기 때문에 바이트 기반 스트림으로 문자를 처리하는데에는 어려움이 있다. 이 점을 보완하기 위해 문자기반의 스트림이 제공된다.</p>
<blockquote>
<ul>
<li>InputStream -&gt; Reader</li>
<li>OutputStream -&gt; Writer</li>
</ul>
</blockquote>
<h2 id="표준-스트림-systemin-systemout-systemerr">표준 스트림 (System.in, System.out, System.err)</h2>
<blockquote>
<p>자바에서는 표준 입출력을 위해 3가지의 입출력 스트림을 제공한다. 이 스트림들은 자바 애플리케이션의 실행과 동시에 사용할 수 있게 자동적으로 생성되기 때문에 개발자가 별도로 스트림을 생성하는 코드를 작성하지 않고도 사용이 가능하다.</p>
</blockquote>
<p>아래 3가지는 모두 System 클래스에 속해 있는 클래스(static) 변수이다. 선언부분에는 InputStream, PrintStream이라고 적혀있지만 실제로는                                                          BufferedInputStream, BufferedOutputStream의 인스턴스를 사용한다.</p>
<pre><code class="language-java">public final class System {
    public static final InputStream in;
    public static final PrintStream out;
    public static final PrintStream err;
    ...
}</code></pre>
<ul>
<li><code>System.in</code> - 콘솔로부터 데이터를 입력받는데 사용</li>
<li><code>System.out</code> - 콘솔로 데이터를 출력하는데 사용</li>
<li><code>System.err</code> - 콘솔로 데이터를 출력하는데 사용</li>
</ul>
<h2 id="파일-읽고-쓰기">파일 읽고 쓰기</h2>
<blockquote>
<p>자바에서는 io패키지의 File 클래스를 통해서 파일과 디렉토리를 다룰 수 있도록 하고 있다. 더불어 nio 패키지에서는 좀 더 다양한 파일의 속성 정보를 제공해주는 클래스와 인터페이스를 <code>java.nio.file</code>, <code>java.nio.attribute</code>패키지에서 제공한다.</p>
</blockquote>
<h3 id="io패키지-file-클래스-특징">IO패키지 File 클래스 특징</h3>
<ul>
<li><p>File 클래스는 파일크기, 속성, 이름 등의 정보를 얻어내는 기능, 파일 생성, 삭제의 기능을 제공한다.</p>
</li>
<li><p>그러나 파일의 데이터를 읽고 쓰는 기능은 지원하지 않는다.</p>
</li>
<li><p>파일의 입출력은 Stream을 사용한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스터디 - 12주차]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-12%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-12%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Fri, 19 Aug 2022 04:26:17 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p>자바의 애노테이션에 대해 학습하세요.</p>
<h1 id="학습할-것-필수">학습할 것 (필수)</h1>
<ul>
<li>애노테이션 정의하는 방법</li>
<li><a href="https://github.com/retention">@retention</a></li>
<li><a href="https://github.com/target">@target</a></li>
<li><a href="https://github.com/documented">@documented</a></li>
<li>애노테이션 프로세서</li>
</ul>
<h2 id="애노테이션이란">애노테이션이란</h2>
<blockquote>
<p>자바를 개발한 사람은 소스코드에 대한 문서를 만들기 보다, 소스 코드의 문서를 하나의 파일로 만드는 것이 더 낫다고 생각했다. 이로 인해 개발된 것이 JavaDoc - <code>/** ~ */</code>이다.  </p>
</blockquote>
<ul>
<li><p>이 기능을 응용하여 프로그램의 소스코드안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 애노테이션이다.</p>
</li>
<li><p>애노테이션은 사전적으로 주석이라는 의미를 가지고 있으며, 프로그램에 대한 데이터를 제공하는 메타 데이터의 한 형태이다.</p>
</li>
</ul>
<h3 id="javalangannotationannotation">java.lang.annotation.Annotation</h3>
<p>모든 애노테이션의 조상은 Annotation이다. 그러나 애노테이션은 상속이 허용되지 않으므로 아래와 같이 명시적으로 Annotation을 조상으로 지정할 수 없다.</p>
<pre><code class="language-java">@interface TestCode extends Annotation { // 에러. 허용되지 않는 표현
    int count();
    String testedBy();
}</code></pre>
<p>게다가 Annotation은 애노테이션이 아니라 일반적인 인터페이스로 정의되어 있다.</p>
<pre><code class="language-java">package java.lang.annotation;

public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    String toString();
    Class&lt;? extends Annotation&gt; annotationType();
}</code></pre>
<p>모든 애노테이션 객체에 대해 equals(), hashCode(), toString() 과 같은 메서드를 호출하는 것이 가능하다.</p>
<h3 id="애노테이션의-용도">애노테이션의 용도</h3>
<ul>
<li>컴파일러에게 코드 작성 문법을 에러를 체크하고자, 에러 메세지를 억제하고자 사용</li>
<li>소프트웨어 개발툴의 빌드, 배치 시점에서 자동으로 코드를 생성하도록 사용</li>
<li>런타임 시점에서 특정 기능을 실행할 수 있도록 사용(Java Reflection)</li>
</ul>
<h2 id="애노테이션-종류">애노테이션 종류</h2>
<h3 id="표준-애노테이션">표준 애노테이션</h3>
<table>
<thead>
<tr>
<th>애노테이션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>@Override</td>
<td>컴파일러에게 오버라이딩하는 메서드라는 것을 알린다.</td>
</tr>
<tr>
<td>@Deprecated</td>
<td>앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.</td>
</tr>
<tr>
<td>@SuppressWarnings</td>
<td>컴파일러의 특정 경고메시지가 나타나지 않게 해준다.</td>
</tr>
<tr>
<td>@SafeVarargs</td>
<td>지네릭스 타입의 가변인자에 사용한다.(JDK1.7)</td>
</tr>
<tr>
<td>@FunctionalInterface</td>
<td>함수형 인터페이스라는 것을 알린다.</td>
</tr>
<tr>
<td>@Native</td>
<td>native 메서드에서 참조되는 상수 앞에 붙힌다.(JDK1.8)</td>
</tr>
</tbody></table>
<h3 id="메타-애노테이션">메타 애노테이션</h3>
<p>애노테이션을 위한 애노테이션이다. 애노테이션을 정의할 때 애노테이션의 적용대상이나 유지기간 등을 지정하는데 사용된다.</p>
<table>
<thead>
<tr>
<th>애노테이션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>@Target</td>
<td>애노테이션이 적용가능한 대상을 지정하는데 사용한다.</td>
</tr>
<tr>
<td>@Documented</td>
<td>애노테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다.</td>
</tr>
<tr>
<td>@Inherited</td>
<td>애노테이션이 자손 클래스에 상속되도록 한다.</td>
</tr>
<tr>
<td>@Retention</td>
<td>애노테이션이 유지되는 범위를 지정하는데 사용한다.</td>
</tr>
<tr>
<td>@Repeatable</td>
<td>애노테이션을 반복해서 적용할 수 있게 한다.(JDK1.8)</td>
</tr>
</tbody></table>
<h3 id="마커-애노테이션">마커 애노테이션</h3>
<p>값을 지정할 필요가 없는 경우 애노테이션의 요소를 하나도 정의하지 않을 수 있다.</p>
<p>Serializable이나 Cloneable과 같은 요소가 하나도 정의되지 않은 애노테이션을 마커 애노테이션이라고 한다. </p>
<h2 id="애노테이션-정의하는-방법">애노테이션 정의하는 방법</h2>
<pre><code class="language-java">public @interface 애노테이션 이름 {
    타입 요소이름();
    ...
}</code></pre>
<p><code>@interface</code> 를 사용하여 애노테이션 타입을 선언한다. </p>
<h3 id="애노테이션-타입">애노테이션 타입</h3>
<p>기호 <code>@</code>와 <code>interface</code>는 각자 별개이다. 그래서 둘 사이에 공백을 주어도 문제없이 작동된다. 근데 보기가 안좋다.</p>
<pre><code class="language-java">public @            interface 애노테이션 이름 {
    타입 요소이름();
    ...
}</code></pre>
<h3 id="애노테이션의-요소">애노테이션의 요소</h3>
<blockquote>
<p>애노테이션 내에 선언된 메서드를 <code>애노테이션의 요소</code>라고 한다.</p>
<ul>
<li>애노테이션의 요소는 반환값이 있고, 매개변수는 없는 추상메서드의 형태를 가진다.</li>
<li>상속을 통해 구현하지 않아도 된다. 하지만 적용시 요소들의 값을 빠짐없이 적어야한다(순서는 상관하지 않는다.)</li>
<li>예외를 선언할 수 없다.</li>
<li>default값을 지정할 수 있다. <code>int count() default 3</code></li>
<li>요소를 타입 매개변수(지네릭)로 정의할 수 없다.</li>
<li>요소의 타입은 int, String, enum, 애노테이션, Class만 가질 수 있다.</li>
<li>요소는 배열로 여러개를 가질 수 있으며, 만약 요소가 하나라면 요소명은 제외하고 값만 적어도 된다.(배열이여도 요소명이 value면 값만 적을 수 있다.)</li>
</ul>
</blockquote>
<p>아래 예제를 보며 요소의 특징을 활용한다.</p>
<ul>
<li>myAnnotation</li>
</ul>
<pre><code class="language-java">public @interface TestCheck {
    int count();    // Primitive Type 원시형
       String testedBy();    // String
    String[] testTools();    // 배열
    TestType testType; // enum TestType { FIRST, SECOND }
    DateTime testTime(); // 자신이 아닌 애노테이션을(@DateTime) 포함시킬 수 있다.
}

// 애노테이션 적용 예시

@TestCheck(
    count = 3, testedBy = &quot;seunghan&quot;,
    testTools = {&quot;JUnit&quot;, &quot;AutoTester&quot;},
    testType = TestType.FIRST,
    testTime = @DateTime(yyyymmdd = &quot;220202&quot;, hhmmss = &quot;001234&quot;)
)
public class TestClass {
    ...
}</code></pre>
<h2 id="override">@Override</h2>
<blockquote>
<p>메서드 앞에만 붙일 수있는 애너테이션이다. 조상의 메서드를 오버라이딩하는 것이라는걸 컴파일러에게 알려주는 역할을 한다.</p>
</blockquote>
<ul>
<li>예시</li>
</ul>
<pre><code class="language-java">// 조상 클래스의 doOverriding 메서드
class Parent {
    public void doOverriding() {
        ...
    }
}
// 자손 클래스의 dooverriding 메서드 -&gt; 메서드 명에 오타가 있다.
class Child extends Parent {
    public void dooverriding {
        ...
    }
}</code></pre>
<p>위 코드처럼 Child 클래스가 부모인 Parent클래스의 doOverriding을 오버라이딩하려고 했으나 메서드명을 잘못 기입할 때, <code>@Override</code>가 없다면 컴파일러가 자손의 메서드명이 잘못된 것을 인식하지 못한다(그냥 자손 클래스에 새로운 메서드가 생겨나는 것이다). </p>
<p>@Override를 사용하면 다음과 같이 잘못된 것을 인식한다. 알아내기 어려운 실수를 미연에 방지할 수 있기 떄문에 반드시 붙히는게 좋다.</p>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h3n3ivuhogj21g60d4752.jpg" alt="Screen Shot 2022-06-27 at 21.54.59"></p>
<h2 id="deprecated">@Deprecated</h2>
<p>새로운 버전의 JDK가 소개될 때, 기존의 부족했던 기능들을 개선하면서 그 기능을 새롭게 대체할 것이 추가된다. 하지만 어디서 쓰일지도 모르는 기존의 기능을 삭제할 수는 없다. 따라서 사용되지 않는 필드, 메서드에 <code>@Deprecatede</code>를 붙혀 더 이상 사용하지 않을 것을 권고한다. </p>
<p>만일 @Deprecated가 붙은 필드 || 메서드를 적용하고 컴파일 하면 다음과 같은 메시지가 나타난다.</p>
<pre><code class="language-java">Note: AnnotationXXX.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.</code></pre>
<p><code>-Xlint:deprecation</code> : 다시 컴파일하면 상세한 내용을 알 수 있다.</p>
<h2 id="suppresswarnings">@SuppressWarnings</h2>
<blockquote>
<p>컴파일러가 보여주는 경고메시지를 보이지 않게 억제한다. 경고 메시지에는 여러 종류가 있는데 JDK 버전이 올라갈 수록 계속 추가될 것이다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>억제 가능한 경고 메시지</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>deprecation</td>
<td>@Deprecated가 사용된 필드 || 메서드를 사용시 발생하는 경고를 억제함</td>
</tr>
<tr>
<td>unchecked</td>
<td>지네릭스로 타입을 지정하지 않았을 때 발생하는 경고를 억제함</td>
</tr>
<tr>
<td>rawtype</td>
<td>지네릭스를 사용하지 않아서 발생하는 경고 억제함</td>
</tr>
<tr>
<td>varargs</td>
<td>가변인자의 타입이 지네릭 타입일 때 발생하는 경고를 억제함</td>
</tr>
</tbody></table>
<h2 id="safevarargs">@SafeVarargs</h2>
<blockquote>
<ul>
<li>reifiable - 컴파일 이후에 제거되지 않는 타입</li>
<li>non-reifiable - 컴파일 이후에 제거되는 타입 (지네릭 타입은 대부분 이에 해당함)</li>
</ul>
</blockquote>
<p>메서드에 선언된 가변인자의 타입이 non - reifiable 타입인 경우 해당 메서드를 선언 &amp; 호출하는 부분에서 unchecked 경고가 발생한다. 해당 코드에 문제가 없다면 이 경고를 억제하기 위해 붙힌다.</p>
<p><strong>이 애노테이션은 static, final이 붙은 메서드와 생성자에만 사용이 가능하다. 오버라이딩에는 사용이 불가능하다는 뜻이다.</strong></p>
<p>메서드를 선언할 때 이 애노테이션을 붙히면 호출할 때도 경고 메시지가 억제된다.</p>
<p>추가로 @SafeVarargs로 unchecked 경고를 억제할 수 있지만 varargs 경고는 억제할 수 없다. 따라서 @SuppressWarnings(&quot;varargs&quot;)를 습관적으로 같이 사용한다.</p>
<h2 id="retention">@Retention</h2>
<p>애너테이션이 유지되는 기간을 지정하는데 사용된다. 애너테이션의 유지 정책의 종류는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>유지 정책</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>SORUCE</td>
<td>소스 파일에만 존재. 클래스파일에는 존재하지 않는다.</td>
</tr>
<tr>
<td>CLASS</td>
<td>클래스 파일에 존재. 실행시에 사용불가. 기본값</td>
</tr>
<tr>
<td>RUNTIME</td>
<td>클래스 파일에 존재. 실행시에 사용가능</td>
</tr>
</tbody></table>
<ul>
<li>SOURCE - @Override, @SuppressWarnings처럼 컴파일러가 사용하는 애너테이션은 SOURCE 정책을 사용한다. 컴파일러를 직접 작성할 것이 아니라면 이 유지정책은 필요없다.</li>
<li>CLASS - 컴파일러가 애너테이션의 정보를 클래스 파일에 저장할 수 있게는 하지만 JVM에 로딩될 떄는 애너테이션 정보가 무시되어 실행시 정보를 얻을 수 없다. 그래서 기본값임에도 잘 사용되지 않는다</li>
<li>RUNTIME - 실행시 리플렉션(reflection)을 통해 클래스 파일에 저장된 애너테이션의 정보를 읽어서 처리할 수 있다. </li>
</ul>
<h2 id="target">@Target</h2>
<p>애너테이션이 적용가능한 대상을 지정하는데 사용된다. 여러 개의 값을 지정할 때는 배열처럼 <code>{}</code>을 사용한다.</p>
<ul>
<li>@Target</li>
</ul>
<pre><code class="language-java">@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}</code></pre>
<p>ElementType을 배열로 적용가능한 대상으로 여러값을 줄 수 있다.</p>
<pre><code class="language-java">@Target(ElmentType.FIELD, ElementType.METHOD, ElementType.TYPE)</code></pre>
<h4 id="elementtype-종류">ElementType 종류</h4>
<table>
<thead>
<tr>
<th>대상 타입</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>ANNOTATION_TYPE</td>
<td>애너테이션</td>
</tr>
<tr>
<td>CONSTRUCTOR</td>
<td>생성자</td>
</tr>
<tr>
<td>FIELD</td>
<td>필드(멤버변수, enum 상수)</td>
</tr>
<tr>
<td>LOCAL_VARIABLE</td>
<td>지역변수</td>
</tr>
<tr>
<td>METHOD</td>
<td>메서드</td>
</tr>
<tr>
<td>PACKAGE</td>
<td>패키지</td>
</tr>
<tr>
<td>PARAMETER</td>
<td>매개변수</td>
</tr>
<tr>
<td>TYPE</td>
<td>타입(클래스, 인터페이스, enum)</td>
</tr>
<tr>
<td>TYPE_PARAMETER</td>
<td>타입 매개변수(JDK1.8)</td>
</tr>
<tr>
<td>TYPE_USE</td>
<td>타입이 사용되는 모든 곳</td>
</tr>
</tbody></table>
<h2 id="documented">@Documented</h2>
<p>애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다. 자바에서 제공하는 기본 애너테이션 중 <code>@Override</code>, <code>@SuppressWarnings</code>을 제외한 모두 이 메타 애너테이션이 붙어있다(<code>@Documented</code>본인까지도).</p>
<pre><code class="language-java">package java.lang.annotation;

/**
 * Indicates that annotations with a type are to be documented by javadoc
 * and similar tools by default.  This type should be used to annotate the
 * declarations of types whose annotations affect the use of annotated
 * elements by their clients.  If a type declaration is annotated with
 * Documented, its annotations become part of the public API
 * of the annotated elements.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}</code></pre>
<h2 id="애노테이션-프로세서">애노테이션 프로세서</h2>
<h3 id="애노테이션-프로세서란">애노테이션 프로세서란?</h3>
<blockquote>
<p>애노테이션을 프로세싱하는 기술로 자바 컴파일러 플러그인의 일종으로 애노테이션에 대한 코드베이스를 검사, 수정, 생성하는 역할을 가진다.</p>
</blockquote>
<p><code>애노테이션 프로세서는</code> 컴파일 시점에 끼어들어 특정한 애노테이션이 붙어있는 소스코드를 참조해서 새로운 소스코드를 만들어 낼 수 있는 기능이다. 이 때 새로 생성되는 소스코드는 자바일수도 있고 다른 코드일수도 있다.</p>
<h3 id="애노테이션-프로세서-사용-예">애노테이션 프로세서 사용 예</h3>
<ul>
<li>롬복(Lombok)</li>
<li>AutoService : java.util.ServiceLoader용 파일 생성 유틸리티</li>
<li>@Override</li>
<li>Dagger2 : 컴파일 타임 DI 제공</li>
</ul>
<h3 id="애노테이션-프로세서-장단점">애노테이션 프로세서 장/단점</h3>
<h4 id="장점">장점</h4>
<ul>
<li>런타임 비용이 없다.</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>기존 클래스 코드를 변경할 때 약갼의 해킹이 필요하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스터디 - 11주차]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-11%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-11%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 23 Jun 2022 01:44:37 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p>자바의 열거형에 대해 학습하세요.</p>
<h1 id="학습할-것-필수">학습할 것 (필수)</h1>
<ul>
<li>enum 정의하는 방법</li>
<li>enum이 제공하는 메소드 (values()와 valueOf())</li>
<li>java.lang.Enum</li>
<li>EnumSet</li>
</ul>
<h2 id="enum-정의하는-방법">Enum 정의하는 방법</h2>
<h3 id="열거형enum이란-">열거형(Enum)이란 ?</h3>
<blockquote>
<p>  서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용하다.</p>
</blockquote>
<ul>
<li>JDK 1.5부터 새로 추가되었다.</li>
<li>자바의 열거형은 C언어의 열겨형보다 향샹되었다. 값뿐만 아니라 타입도 관리하기 때문인데, 이로써 논리적인 오류를 줄일 수 있다.</li>
</ul>
<h3 id="열거형-적용-전-후">열거형 적용 전, 후</h3>
<ul>
<li>적용전</li>
</ul>
<pre><code class="language-java">class Card {
    static final CLOVER = 0;
    static final DIAMOND = 1;
    static final HEART = 2;
    static final SPADE = 3;

    static final int TWO = 0;
    static final int THREE = 1;
    static final int FOUR = 2;

    int kind;
    int num;
}</code></pre>
<ul>
<li>적용후</li>
</ul>
<pre><code class="language-java">class Card {
    enum Kind { CLOVER, DIAMOND, HEART, SPADE }
    enum Value { TWO, THREE, FOUR }

    final Kind kind;
    final Value value;
}</code></pre>
<ul>
<li>Enum을 사용한다면 타입에 안전함</li>
</ul>
<pre><code class="language-java">if(Card.CLOVER == Card.TWO) // True지만 원래는 false가 맞다.
if(Card.Kind.CLOVER == Card.Value.TWO) // 컴파일 에러. 값은 같지만 타입이 다름</code></pre>
<p>또 중요한 것은 상수의 값이 바뀌면 상수를 참조하는 모든 소스를 재 컴파일 해야하지만, Enum을 사용한다면 기존의 소스를 다시 재컴파일 하지 않아도 된다.</p>
<h3 id="열거형의-정의와-사용">열거형의 정의와 사용</h3>
<ul>
<li>정의</li>
</ul>
<pre><code class="language-java">enum 열거형이름 { 상수명1, 상수명2, ... 상수명n }</code></pre>
<ul>
<li>정의 예제 ( 동서남북 )</li>
</ul>
<pre><code class="language-java">enum Direction { EAST, WEST, SOUTH, NORTH }</code></pre>
<ul>
<li>정의 예제를 통한 사용 예제</li>
</ul>
<pre><code class="language-java">class Unit {
    int x, int y;    // 유닛의 위치
    Direction dir; // 열거형을 인스턴스 변수로 선언

    // 메서드
    void init() {
        dir = Direction.EAST;    // 유닛의 방향을 동쪽으로 지정
    }
}</code></pre>
<ul>
<li><p>열거형의 비교</p>
<blockquote>
<p>  열거형은 <code>equals()</code>가 아닌  <code>==</code> 를 통해 비교를 할 수 있다.  그만큼 빠른 성능을 제공한다. 하지만 <code>&gt;</code>, <code>&lt;</code>등과 같은 비교연산은 할 수 없고, <code>compareTo()</code>는 사용이 가능하다.</p>
</blockquote>
</li>
<li><p>switch에서의 Enum 사용</p>
</li>
</ul>
<pre><code class="language-java">void move() {
    switch(dir) {
        case EAST : x++;
            break;
        case WEST : x--;
            break;
        case SOUTH : y--;
            break;
        case NORTH : y++;
            break;
    }
}</code></pre>
<p><em>주의할 점 : <code>switch</code>문에 열겨형을 입력할 때는 <code>열거형이름.상수이름</code>이 아닌  <code>상수이름</code>만을 넣어야 한다.</em></p>
<h2 id="values-valueof-메서드">values(), valueOf() 메서드</h2>
<h3 id="values-메서드">values() 메서드</h3>
<ul>
<li>선언된 순서로 Enum값들을 모두 담은 배열을 return하는 static 메서드이다.</li>
<li>forEach 문에서 Enum 값들을 반복하는데 사용된다.</li>
</ul>
<pre><code class="language-java">enum Direction { EAST, WEST, SOUTH, NORTH }

Direction[] dirArr = Direction.values();

for (Direction dir : dirArr) {
    System.out.printf(&quot;%s = %d%n&quot;, dir.name(), dir.ordinal());
}</code></pre>
<h3 id="valueofstring-name">valueOf(String name)</h3>
<ul>
<li>지정된 열거형에서 name과 일치하는 열거형 상수를 리턴한다.</li>
</ul>
<pre><code class="language-java">enum Direction { EAST, WEST, SOUTH, NORTH }

Direction dir = Direction.valueOf(&quot;EAST&quot;);
System.out.println(dir);    // EAST
System.out.println(dir.name());        // EAST
System.out.println(Direction.EAST == dir);    // true
System.out.println(Direction.EAST.equals(dir)); // true</code></pre>
<h2 id="javalangenum"><code>java.lang.Enum</code></h2>
<blockquote>
<p>모든 열거형의 조상, 모든 enum들은 <code>java.lang.Enum</code>을 상속하기 때문에 enum은 다른 클래스를 상속하지 못한다. </p>
</blockquote>
<h3 id="enum-클래스-메서드"><strong>Enum 클래스 메서드</strong></h3>
<ul>
<li><p><code>int ordinal()</code> : 열거형의 정의된 순서대로 상수를 0부터 정수로 리턴한다.</p>
</li>
<li><p><code>Class&lt;E&gt; getDeclaringClass()</code>  : 열거형의 Class 객체를 리턴한다.</p>
</li>
<li><p><code>String name()</code> : 열거형 상수의 이름을 문자열로 리턴한다.</p>
</li>
<li><p><code>T valueOf(Class&lt;T&gt; enumType, String name)</code> : 지정된 열거형에서 name과 일치하는 열거형 상수를 리턴한다.</p>
</li>
<li><p>예제</p>
</li>
</ul>
<pre><code class="language-java">public class EnumEx1 {
    public static void main(String[] args) {
        Direction d1 = Direction.EAST;
        Direction d2 = Direction.valueOf(&quot;WEST&quot;);
        Direction d3 = Direction.valueOf(&quot;EAST&quot;);

        System.out.println(&quot;d1 : &quot; + d1);
        System.out.println(&quot;d2 : &quot; + d2);
        System.out.println(&quot;d3 : &quot; + d3);

        System.out.println(&quot;d1 == d2 ? &quot; + (d1 == d2));
        System.out.println(&quot;d1 == d3 ? &quot; + (d1 == d3));
//        System.out.println(&quot;d1 &gt; d2 ? &quot; + (d1 &gt; d2)); 에러.
        System.out.println(&quot;d1.equals(d2)&quot; + (d1.equals(d2)));
        System.out.println(&quot;d1.equals(d3)&quot; + (d1.equals(d3)));

        System.out.println(&quot;d1.compareTo(d2)&quot; + d1.compareTo(d2));
        System.out.println(&quot;d1.compareTo(d3)&quot; + d1.compareTo(d3));

        switch (d1) {
            case EAST :
                System.out.println(&quot;Direction is EAST&quot;);
                break;
            case WEST :
                System.out.println(&quot;Direction is WEST&quot;);
                break;
            case SOUTH :
                System.out.println(&quot;Direction is SOUTH&quot;);
                break;
            case NORTH :
                System.out.println(&quot;Direction is NORTH&quot;);
                break;
            default :
                System.out.println(&quot;Invalid Direction&quot;);
                break;
        }

        Direction[] dirArr = Direction.values();

        for (Direction direction : dirArr) {
            System.out.printf(&quot;%s = %d%n&quot;, direction.name(),direction.ordinal());
        }
    }
}</code></pre>
<ul>
<li>실행결과</li>
</ul>
<pre><code class="language-java">d1 : EAST
d2 : WEST
d3 : EAST
d1 == d2 ? false
d1 == d3 ? true
d1.equals(d2)false
d1.equals(d3)true
d1.compareTo(d2)-1
d1.compareTo(d3)0
Direction is EAST
EAST = 0
WEST = 1
SOUTH = 2
NORTH = 3</code></pre>
<h3 id="열거형에-멤버-추가하기">열거형에 멤버 추가하기</h3>
<p><code>ordinal()</code>이 열거형 상수가 정의된 순서를 반환하지만, 이는 내부적 용도로 사용하는 것이기 때문에 사용하지 않는 것이 좋다.</p>
<p>열거형 상수의 값이 불연속적인 경우 상수의 이름과 함께 괄호 <code>()</code>를 추가하여 값을 지정해주면 된다.</p>
<pre><code class="language-java">enum Direction { EAST(1), WEST(3), SOUTH(10), NORTH(-10) }</code></pre>
<p>그리고 지정된 값을 사용할 수 있는 <code>인스턴스변수</code>와 <code>생성자</code>를 추가해 주어야 한다. </p>
<p><em>주의할 점 : 열거형 상수를 먼저 입력한 뒤 그 뒤에 멤버들을 추가한다.</em></p>
<pre><code class="language-java">public enum Direction {
    EAST(1), WEST(3), SOUTH(10), NORTH(-10);

    private final int value;

    Direction(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}</code></pre>
<p>하지만 생성자를 추가했다고 해서 외부에서 생성자를 통해 enum 인스턴스를 직접 생성할 수는 없다. 제어자가 묵시적으로 <code>private</code>이기 때문이다.</p>
<p>그리고 필요하다면 값을 더 추가해주어도 된다. 인스턴스 변수 또한 추가해주어야 한다.</p>
<pre><code class="language-java">public enum Direction {
    EAST(1, &quot;E&quot;), WEST(3, &quot;W&quot;), SOUTH(10, &quot;S&quot;), NORTH(-10, &quot;N&quot;);

    private final int value;
    private final String symbol;

    Direction(int value, String symbol) {
        this.value = value;
        this.symbol = symbol;
    }

    public int getValue() {
        return value;
    }

    public String getSymbol() {
        return symbol;
    }
}</code></pre>
<h3 id="열거형에-추상-메서드-추가">열거형에 추상 메서드 추가</h3>
<ul>
<li>예제 enum</li>
</ul>
<pre><code class="language-java">public enum Transportation {
    BUS(100), TRAIN(150), SHIP(100), AIRPLANE(300);

    private final int BASIC_FARE;

    Transportation(int BASIC_FARE) {
        this.BASIC_FARE = BASIC_FARE;
    }

    public int fare() {
        return BASIC_FARE;
    }
}</code></pre>
<p>기본요금이 책정되어 있는 열거형을 생성했다. 하지만 이것만으로는 부족하다. 거리에 따라 요금을 계산하는 방식이 운송수단마다 다를 것이고, 이럴 때 추상메서드를 선언하면 각 열거형 상수가 이 추상 메서드를 반드시 구현해야 한다.</p>
<ul>
<li>추상 메서드 적용 예제</li>
</ul>
<pre><code class="language-java">package week_11;

public enum NewTransportation {

    BUS(100) {
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    }, TRAIN(150) {
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    }, SHIP(100) {
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    }, AIRPLANE(300) {
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    };

    protected final int BASIC_FARE;

    NewTransportation(int BASIC_FARE) {
        this.BASIC_FARE = BASIC_FARE;
    }

    abstract int fare(int distance);

    public int getBASIC_FARE() {
        return BASIC_FARE;
    }

}

class Enumex3 {
    public static void main(String[] args) {
        System.out.println(&quot;Bus fare : &quot; + NewTransportation.BUS.fare(100));
        System.out.println(&quot;Train fare : &quot; + NewTransportation.TRAIN.fare(100));
        System.out.println(&quot;Ship fare : &quot; + NewTransportation.SHIP.fare(100));
        System.out.println(&quot;Airplane fare : &quot; + NewTransportation.AIRPLANE.fare(100));
    }
}</code></pre>
<ul>
<li>실행 결과</li>
</ul>
<pre><code class="language-java">Bus fare : 10000
Train fare : 15000
Ship fare : 10000
Airplane fare : 30000</code></pre>
<blockquote>
<p>  <em>열거형에 추상 메서드를 선언할 일은 그리 많지 않으므로 참고만 하자</em></p>
</blockquote>
<h3 id="열거형의-이해">열거형의 이해</h3>
<p>열거형이 내부적으로 어떻게 구현되었는지에 대해 알아보자. 다음과 같이 열거형 Direction이 있을 때,</p>
<pre><code class="language-java">enum Direction { EAST, WEST, SOUTH, NORTH }</code></pre>
<p>열거형 상수 하나하나가 Direction 객체이다. 위 문장을 클래스로 표현하면 다음과 같다.</p>
<pre><code class="language-java">class Direction {
    static final Direction EAST = new Direction(&quot;EAST&quot;);
    static final Direction WEST = new Direction(&quot;WEST&quot;);
    static final Direction SOUTH = new Direction(&quot;SOUTH&quot;);
    static final Direction NORTH = new Direction(&quot;NORTH&quot;);

    private String name;

    private Direction (String name) {
        this.name = name;    
    }
}</code></pre>
<p>static 상수의 값은 객체의 주소이고, 이 값은 바뀌지 않는 값이므로 <code>==</code>로 비교가 가능한 것이다.</p>
<ul>
<li>Enum을 흉내 내어 클래스를 만들어보면 다음과 같다.</li>
</ul>
<pre><code class="language-java">abstract class MyEnum&lt;T extends MyEnum&lt;T&gt;&gt; implements Comparable&lt;T&gt; {
    static int id = 0; // 객체에 붙힐 일련번호

    int ordinal;
    String name = &quot;&quot;;

    public int ordinal() {
        return ordinal;
    }

    MyEnum(String name) {
        this.name = name;
        ordinal = id++; // 객체를 생성할 때 마다 id의 값을 증가시킨다.
    }

    public int compareTo(T t) {
        return ordinal - t.ordinal; // 에러. 타입 T에 ordinal이 있나?
    }
}</code></pre>
<p>compareTo를 보면 타입 T가 ordinal 변수를 가지고 있는ㄴ지 확실하지 않아 에러가 날 수 있다. 따라서 </p>
<p><code>&lt;T extends MyEnum&lt;T&gt;&gt;</code> 와 같이 선언한 것이다. 타입 T가 MyEnum<T>의 자손이어야 한다는 의미다. </p>
<p>그리고 추상 메서드를 새로 추가하면 클래스 앞에도 abstract를 붙혀줘야 하고, 각 static 상수들도 추상 메서드를 구현해주어야 한다. </p>
<ul>
<li>익명 클래스의 형태로 추상메서드를 구현</li>
</ul>
<pre><code class="language-java">absract class Direction extends MyEnum {
    static final Direction EAST = new Direction(&quot;EAST&quot;) {
        Point move(Point p) {
            // 내용 생략
        }
    };
    static final Direction WEST = new Direction(&quot;WEST&quot;) {
        Point move(Point p) {
            // 내용 생략
        }
    };
    static final Direction SOUTH = new Direction(&quot;SOUTH&quot;) {
        Point move(Point p) {
            // 내용 생략
        }
    };
    static final Direction NORTH = new Direction(&quot;NORTH&quot;) {
        Point move(Point p) {
            // 내용 생략
        }        
    };  
    private String name;

    private Direction(String name) {
        this.name = name;
    }

    abstract Point move(Point p);
}</code></pre>
<ul>
<li>총예제</li>
</ul>
<pre><code class="language-java">abstract class MyEnum&lt;T extends MyEnum&lt;T&gt;&gt; implements Comparable&lt;T&gt; {
    static int id = 0; // 객체에 붙힐 일련번호

    int ordinal;
    String name = &quot;&quot;;

    public int ordinal() {
        return ordinal;
    }

    MyEnum(String name) {
        this.name = name;
        ordinal = id++; // 객체를 생성할 때 마다 id의 값을 증가시킨다.
    }

    public int compareTo(T t) {
        return ordinal - t.ordinal(); // 에러. 타입 T에 ordinal이 있나?
    }
}

abstract class MyTranspotation extends MyEnum {
    static final MyTranspotation BUS = new MyTranspotation(&quot;BUS&quot;, 100) {
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    };
    static final MyTranspotation TRAIN = new MyTranspotation(&quot;TRAIN&quot;, 150) {
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    };
    static final MyTranspotation SHIP = new MyTranspotation(&quot;SHIP&quot;, 100) {
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    };
    static final MyTranspotation AIRPLANE = new MyTranspotation(&quot;AIRPLANE&quot;, 300) {
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    };

    @Override
    public int compareTo(Object o) {
        return 0;
    }


    abstract int fare(int distance);

    protected final int BASIC_FARE;

    private MyTranspotation(String name, int basicFare) {
        super(name);
        BASIC_FARE = basicFare;
    }


    public String name;

    @Override
    public String toString() {
        return &quot;MyTranspotation{&quot; +
                &quot;BASIC_FARE=&quot; + BASIC_FARE +
                &quot;, name=&#39;&quot; + name + &#39;\&#39;&#39; +
                &#39;}&#39;;
    }
}

class EnumEx4 {
    public static void main(String[] args) {
        MyTranspotation t1 = MyTranspotation.BUS;
        MyTranspotation t2 = MyTranspotation.BUS;
        MyTranspotation t3 = MyTranspotation.TRAIN;
        MyTranspotation t4 = MyTranspotation.SHIP;
        MyTranspotation t5 = MyTranspotation.AIRPLANE;

        System.out.printf(&quot;t1=%s, %d%n&quot;, t1.name, t1.ordinal());
        System.out.printf(&quot;t2=%s, %d%n&quot;, t2.name, t2.ordinal());
        System.out.printf(&quot;t3=%s, %d%n&quot;, t3.name, t3.ordinal());
        System.out.printf(&quot;t4=%s, %d%n&quot;, t4.name, t4.ordinal());
        System.out.printf(&quot;t5=%s, %d%n&quot;, t5.name, t5.ordinal());
        System.out.println(&quot;t1 == t2 ? &quot; + (t1 == t2));
        System.out.println(&quot;t1.compareTo(t3) = &quot; + t1.compareTo(t3));

    }
}</code></pre>
<h2 id="enumset">EnumSet</h2>
<blockquote>
<ul>
<li><p>EnumSet은 추상클래스로 Set 인터페이스를 구현하고, AbstractSet을 상속한다. 내부는 비트 벡터로 구현되었다.</p>
</li>
<li><p>비트벡터란 ? </p>
<p>: 중복되지 않는 정수 집합을 비트로 나타내는 방식이다. 8bit가 1byte이므로 0 ~ 7까지의 8개의 정수 집합은 고작 1byte 공간만 사용하여 데이터를 저장할 수 있다.</p>
</li>
</ul>
</blockquote>
<pre><code class="language-java">public abstract Class EnumSet&lt;E extends Enum&lt;E&gt;&gt; extends AbstractSet&lt;E&gt; implements Cloneable, Serializable</code></pre>
<h3 id="enumset-생성">EnumSet 생성</h3>
<pre><code class="language-java">EnumSet&lt;Direction&gt; dirSet = EnumSet.allOf(Direction.class); // 열거형 Direction에 담긴 모든 요소를 가진 EnumSet 생성.(저장된 순서는 열거형에 정의한 순서와 같다.
EnumSet&lt;Direction&gt; dirSet = EnumSet.of(Direction.EAST, Direction.SOUTH); // 열거형 Direction에 담긴 요소 중 부분집합으로 EnumSet 생성. argument의 갯수는 제한이 없다.
EnumSet&lt;Direction&gt; dirSet = EnumSet.range(Direction.EAST, Direction.SOUTH); // 열거형에 정의된 EAST 부터 SOUTH까지 범위를 지정하여 범위 내의 요소들을 Set으로 생성 
EnumSet&lt;Direction&gt; dirSet = EnumSet.noneOf(Direction.class); // 비어있는 EnumSet 생성</code></pre>
<h3 id="enumset-사용시-고려사항">EnumSet 사용시 고려사항</h3>
<ul>
<li><p>enum만 포함할 수 있으며 모든 값은 동일한 enum에 포함되어야 한다.</p>
</li>
<li><p>null을 추가할 수 없다. <code>NullPointerException</code>이 발생한다.</p>
</li>
<li><p>스레드에 안전하지 않으므로, 필요할 경우 외부에서 동기화한다.</p>
</li>
<li><p>복사본에 fail-safe iterator를 사용하여 컬렉션을 순회할 때 컬렉션이 수정되어도 ConcurrentModificationException이 발생하지 않는다.</p>
</li>
</ul>
<h3 id="enumset의-장점">EnumSet의 장점</h3>
<ul>
<li><p>EnumSet의 모든 메서드는 산술 비트 연산을 사용하여 구현되므로 일반적인 연산이 매우 빠르게 계산된다.</p>
</li>
<li><p>EnumSet은 HashSet 같은 다른 Set 구현체와 비교했을 때, 데이터가 예상 가능한 순서로 저장되어 있고, 각 계산을 하는데 하나의 비트만이 필요하므로 더 빠르다고 할 수 있다. 또한 HashSet처럼 데이터를 저장할 버킷을 찾는데 Hashcode를 계산할 필요가 없다.</p>
</li>
<li><p>비트 벡터의 특성상 적은 메모리를 사용한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[크론식]]></title>
            <link>https://velog.io/@seunghan-baek/%ED%81%AC%EB%A1%A0%EC%8B%9D</link>
            <guid>https://velog.io/@seunghan-baek/%ED%81%AC%EB%A1%A0%EC%8B%9D</guid>
            <pubDate>Tue, 07 Jun 2022 06:35:03 GMT</pubDate>
            <description><![CDATA[<h1 id="cron">Cron</h1>
<blockquote>
<p>시스템을 운용하다보면 정기적으로 수행해야하는 일이 생기는데 이 때 사용하는 것이 Cron이라는 프로그램이다.</p>
</blockquote>
<h2 id="어떻게-사용할까">어떻게 사용할까?</h2>
<pre><code>crontab- e</code></pre><p>위 명령어를 linux 쉘에 입력하면 에디터가 뜨는데 여기에 정기적으로 하고자 하는 일을 정의할 수 있다.</p>
<p>또한 다음과 같은 라인을 볼 수 있다. 이는 어떻게 적으면 되는지 간단하게 설명한 것이다.</p>
<pre><code>m    h    dom    mon    dow    command</code></pre><ul>
<li><p>m : 분</p>
</li>
<li><p>h : 시</p>
</li>
<li><p>dom : 월 기준 일</p>
</li>
<li><p>mon : 월</p>
</li>
<li><p>dow : 월 기준 요일</p>
</li>
<li><p>command : 명령어 (수행할 작업을 이곳에 적는다)</p>
</li>
</ul>
<h3 id="크론-표현식-cronexpression">크론 표현식 (CronExpression)</h3>
<p>크론 표현식에는 총 6항목이 있는데 항목은 왼쪽부터 순서대로 다음과 같다.</p>
<ol>
<li>초</li>
<li>분</li>
<li>시</li>
<li>일</li>
<li>월</li>
<li>요일(0-7)</li>
</ol>
<p>위 내용을 알맞게 적기 위해서는 간단한 이해가 필요하다. 먼저 표를 살펴보자</p>
<ul>
<li>필드에 허용되는 값과 특수문자, - * /</li>
</ul>
<table>
<thead>
<tr>
<th>필드</th>
<th>허용값</th>
<th>허용 특수문자</th>
</tr>
</thead>
<tbody><tr>
<td>초</td>
<td>0 ~ 59</td>
<td>, - * /</td>
</tr>
<tr>
<td>분</td>
<td>0 ~ 59</td>
<td>, - * /</td>
</tr>
<tr>
<td>시</td>
<td>0 ~ 23</td>
<td>, - * /</td>
</tr>
<tr>
<td>일(Day Of Month</td>
<td>1 ~ 31</td>
<td>, - * / L W</td>
</tr>
<tr>
<td>월</td>
<td>1 ~ 12 || JAN ~ DEC</td>
<td>, - * /</td>
</tr>
<tr>
<td>요일</td>
<td>0 ~ 6 또는 SUN ~ SAT</td>
<td>, - * / L #</td>
</tr>
<tr>
<td>년</td>
<td>1070 ~ 2099</td>
<td>, - * /</td>
</tr>
</tbody></table>
<ul>
<li>특수문자 별 의미</li>
</ul>
<table>
<thead>
<tr>
<th>특수문자</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>*</td>
<td>모든 값</td>
</tr>
<tr>
<td>?</td>
<td>특정값 없음</td>
</tr>
<tr>
<td>-</td>
<td>범위를 지정함</td>
</tr>
<tr>
<td>,</td>
<td>여러 값</td>
</tr>
<tr>
<td>/</td>
<td>증가 값을 지정(초기 / 증가)</td>
</tr>
<tr>
<td>L</td>
<td>마지막 값 지정</td>
</tr>
<tr>
<td>W</td>
<td>가장 가까운 평일</td>
</tr>
<tr>
<td>#</td>
<td>몇 번째 무슨 요일인지 지정</td>
</tr>
</tbody></table>
<h2 id="예제">예제</h2>
<p>강의 내에서는 다음과 같은 Cron식을 추가했다.</p>
<p><code>*/1 * * * * date &gt;&gt; date.log</code></p>
<p>위 내용은 매 분마다 date를 date.log에 저장한다는 의미이다. 위 내용을</p>
<pre><code>crontab- e</code></pre><p>에 적으니 쉘에서는 아래와 같이 crontab을 설치중이라는 문장을 출력해준다.</p>
<pre><code>installing new crontab</code></pre><p>예제에서는 -e 옵션을 주었지만 다른 옵션도 추가로 설명해보겠다.</p>
<pre><code>Options:
 -u &lt;user&gt;  define user
 유저를 정의한다.
 -e         edit user&#39;s crontab
 유저의 crontab을 수정한다.
 -l         list user&#39;s crontab
 유저의 crontab 리스트를 보여준다.
 -r         delete user&#39;s crontab
 유저의 crontab을 삭제한다.
 -i         prompt before deleting
 삭제전 확인을 한다.
 -n &lt;host&gt;  set host in cluster to run users&#39; crontabs
 클러스터의 호스트가 크론탭을 실행하도록 설정
 -c         get host in cluster to run users&#39; crontabs
 크론탭을 실행하는 클러스터의 호스트를 불러온다</code></pre><h2 id="크론으로-할-수-있는-일들-사례">크론으로 할 수 있는 일들 (사례)</h2>
<p>정기적으로 해야되는 일들은 많이 있는데 강의에서는 하나의 사례를 소개해주었다.</p>
<p>웹페이지에 사용자가 입력하는 양식이 있다. 그리고 전송을 눌러 서버로 양식을 전송을 하면 서버가 사용자가 보낸 정보를 받아 <code>정보를 보낸이가 10만명이 될 때까지 기다렸다가 10만명이 다 찰 때, 새로운 글이 등록되었다는 회신과 함께 글을 등록한 모든 사용자들에게 이메일을 보내는</code>프로그램이 있다라고 하자. 1명 당 회신 + 이메일을 보내는데0.1s가 걸린다면 이 프로그램은 좋은 프로그램이 아닐 것이다.</p>
<h3 id="크론을-사용하면">크론을 사용하면</h3>
<p>사용자가 정보를 전송하면 서버쪽 컴퓨터에는 전송이 되었다(<code>SAVED</code>)상태를 저장하고 이를 곧바로 회신해준다. 그리고 서버에 설치된 CRON이 정기적으로 컴퓨터의 <code>SAVED</code>정보가 추가되었다면 그 정보를 주기에 맞춰 BACKGROUD로 100,000개의 이메일을 보내는 작업을 사용자에게 보내는 작업을 진행하면 사용자는 오래 기다릴 필요가 없어진다.</p>
<h2 id="reference">Reference</h2>
<p>생활코딩, 리눅스(정기적 실행 1, 2) - <a href="https://opentutorials.org/module/2538/14218">https://opentutorials.org/module/2538/14218</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스터디 - 10주차]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-10%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-10%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Wed, 11 May 2022 13:40:45 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p>자바의 멀티쓰레드 프로그래밍에 대해 학습하세요.</p>
<h1 id="학습할-것-필수">학습할 것 (필수)</h1>
<ul>
<li>Thread 클래스와 Runnable 인터페이스</li>
<li>쓰레드의 상태</li>
<li>쓰레드의 우선순위</li>
<li>Main 쓰레드</li>
<li>동기화</li>
<li>데드락</li>
</ul>
<h2 id="thread-클래스와-runnable-인터페이스">Thread 클래스와 Runnable 인터페이스</h2>
<h3 id="프로세스란">프로세스란</h3>
<ul>
<li><p>프로그램은 하나의 프로세스이다.</p>
</li>
<li><p>프로그램을 실행하면 운영체제로부터 자원을 할당받아 프로세스가 된다. </p>
</li>
<li><p>프로세스는 프로그램을 실행하는데 필요한 <strong>자원</strong>과 <strong>스레드</strong>로 구성되어 있다.</p>
</li>
</ul>
<h3 id="쓰레드란">쓰레드란</h3>
<ul>
<li>프로세스의 자원을 이용해서 실제 작업을 수행하는 것</li>
<li>모든 프로세스에는 하나 이상의 스레드가 존재한다. 둘 이상의 쓰레드를 가진 프로세스를 <code>멀티쓰레드 프로세스</code>라고 한다.</li>
<li>가장 작은 실행 단위이다.</li>
</ul>
<p>현재 우리가 사용하고 있는 대부분의 OS는 멀티 태스킹을 지원하기 떄문에 여럭 개의 프로세스가 동시에 실행될 수 있다. 마찬가지로 멀티쓰레딩은 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것이다. </p>
<p>멀티쓰레딩의 장점은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>멀티 스레딩의 장점</th>
</tr>
</thead>
<tbody><tr>
<td>CPU의 사용률을 향상시킨다.</td>
</tr>
<tr>
<td>자원을 보다 효율적으로 사용할 수 있다.</td>
</tr>
<tr>
<td>사용자에 대한 응답성이 향상된다.</td>
</tr>
<tr>
<td>작업이 분리되어 코드가 더 간결해진다.</td>
</tr>
</tbody></table>
<h3 id="thread-클래스와-runnable-인터페이스-1">Thread 클래스와 Runnable 인터페이스</h3>
<p>쓰레드를 구현하는 방법은 <code>Thread 클래스를 상속</code>받는 방법과 <code>Runnable 인터페이스</code>를 구현하는 방법 모두 두 가지가 있다.</p>
<p>Thread 클래스를 상속 받으면 다른 클래스를 상속 받을 수 없기 때문에 Runnable 인터페이스를 구현하는 방법이 일반적이다.</p>
<p><strong>Runnable 인터페이스를 구현하는 방법은 재사용성이 높고, 코드의 일관성을 유지할 수 있기 때문에 보다 객체지향적인 방법이다.</strong></p>
<p>Thread 클래스를 상속받은 경우와 Runnable 인터페이스를 구현한 경우의 인스턴스 생성 방법이 다르다.</p>
<pre><code class="language-java">ThreadExample t1 = new ThreadExample();                // Thread의 자손 클래스의 인스턴스를 생성

Runnable r = new ThreadExample();                    // Runnable을 구현한 클래스의 인스턴스를 생성
Thread t2 = new Thread(r);                            // 생성자 Thread(Runnable Target)

Thraed t2 = new Thread(new ThreadExample());        // 위의 두 줄을 한 줄로 간단히</code></pre>
<ul>
<li><p><code>Runnable Example 예제</code></p>
<pre><code class="language-java">package week_10;

public class RunnableExample implements Runnable {
    @Override
    public void run() {
        System.out.println(&quot;This is Runnable Example run()&quot;);
    }
}</code></pre>
</li>
</ul>
<ul>
<li><p><code>Thread Example 예제</code></p>
<pre><code class="language-java">package week_10;

public class ThreadExample extends Thread {
    @Override
    public void run() {
        System.out.println(&quot;This is Thread Example run()&quot;);
    }
}</code></pre>
</li>
</ul>
<ul>
<li>실행 - <code>start()</code></li>
</ul>
<p>쓰레드를 생성헀다고 해서 자동으로 실행되는 것은 아니다. start() 메서드릃 호출해야만 쓰레드가 실행된다. </p>
<pre><code class="language-java">t1.start();    // 쓰레드 t1 실행
t2.start(); // 쓰레드 t2 실행</code></pre>
<p>사실은 start()가 호출되었다고 해서 바로 실행되는 것이 아니라 일단 실행대기 상태에 있다가 자신의 차례가 되어야 실행된다. 물론 실행대기 중인 쓰레드가 하나도 없으면 바로 실행상태가 된다. </p>
<p>또한 한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다. start()가 한 번만 호출될 수 있다는 뜻이다.  두 번 이상 호출하면 <code>IllegalThreadStateException</code>이 발생한다.</p>
<blockquote>
<p>  <em>쓰레드의 실행순서는 OS의 스케줄러가 작성한 스케줄에 의해 결정된다.</em></p>
</blockquote>
<ul>
<li>start()와 run()</li>
</ul>
<p>run() 아닌 start()를 호출하는 이유는 무엇일까? main 메서드에서 run()을 호출하는 것은 단순히 클래스에 선언된 메서드를 실행하는 것일 뿐이다.</p>
<p>반면에 start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출 스택을 생성한 다음에 run()을 호출해서 생성도니 호출 스택에 run()이 첫 번째로 올라가게 한다.</p>
<p>모든 쓰레드는 독립적인 작업을 수행하기 위해서 자신만의 호출스택을 필요로 하기 때문에 새로운 쓰레드를 생성하고 실행시킬 때마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.</p>
<ul>
<li><p><code>선언 및 실행 예제</code></p>
<pre><code class="language-java">package week_10;

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

        // Runnable을 사용하여 Thread 생성
        RunnableExample runnable = new RunnableExample();
        new Thread(runnable).start();

        // Runnable은 다음과 같이 생성이 가능하다
        Thread thread = new Thread(new Runnable() {
             @Override
            public void run() {
                System.out.println(&quot;This is Runnable Example run()&quot;);
            }
        });

        // Runnable은 Lambda를 통해서도 생성이 가능하다.
        Thread thread = new Thread( () -&gt; {
           System.out.println(&quot;This is Runnable Example run()&quot;)
        });

        // Thread를 사용하여 Thread 생성
        ThreadExample thread = new ThreadExample();
        thread.start();

    }
}</code></pre>
</li>
<li><p>실행 결과</p>
</li>
</ul>
<blockquote>
<p>  This is Runnable Example run()
  This is Thread Example run()</p>
<p>  Process finished with exit code 0</p>
</blockquote>
<ul>
<li>ThreadExameple 수정</li>
</ul>
<pre><code class="language-java">package week_10;

public class ThreadExample extends Thread {
    public ThreadExample(String valueOf) {
    }

    @Override
    public void run() {
        System.out.println(&quot;This is Thread Example run() \t And My Name is \t&quot; + this.getName());
    }
}</code></pre>
<ul>
<li>Thread 배열을 선언하고 순서대로 실행되는지 확인하는 예제</li>
</ul>
<pre><code class="language-java">package week_10;

public class ThreadOrderExample {
    public static void main(String[] args) {
        Thread[] tArr = new ThreadExample[50];

        for (int i = 0; i &lt; 50; i++) {
            Thread t = new ThreadExample(String.valueOf(i + 1));
            tArr[i] = t;
        }

        for (Thread thread : tArr) {
            thread.start();
        }
    }
}</code></pre>
<ul>
<li>출력결과</li>
</ul>
<pre><code>This is Thread Example run()      And My Name is     Thread-1
This is Thread Example run()      And My Name is     Thread-6
This is Thread Example run()      And My Name is     Thread-13
This is Thread Example run()      And My Name is     Thread-8
This is Thread Example run()      And My Name is     Thread-21
.
.

This is Thread Example run()      And My Name is     Thread-38
This is Thread Example run()      And My Name is     Thread-44
This is Thread Example run()      And My Name is     Thread-42
</code></pre><blockquote>
<p>  <strong>순서대로 실행되지 않는다는 것을 알 수 있다</strong>. Thread는 실행할 때 먼저 대기 상태로 진입하며 OS의 스케쥴링에 따라 실행되고, 컴퓨터의 성능에 따라 달리지기 때문이다.</p>
</blockquote>
<h2 id="쓰레드의-상태">쓰레드의 상태</h2>
<table>
<thead>
<tr>
<th>상태</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>NEW</td>
<td>쓰레드가 생성되고 아직  start() 가 호출되지 않은 상태</td>
</tr>
<tr>
<td>RUNNABLE</td>
<td>실행 중 또는 실행 가능한 상태</td>
</tr>
<tr>
<td>BLOCKED</td>
<td>동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)</td>
</tr>
<tr>
<td>WAITING, TIMED_WAITING</td>
<td>쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(<em>unrunnable</em>)일시정지 상태, TIMED_WAITING은 일시정지 시간이 지정된 경우를 의미한다.</td>
</tr>
<tr>
<td>TERMINATED</td>
<td>쓰레드가 종료된 상태</td>
</tr>
</tbody></table>
<blockquote>
<p>  스레드 객체를 생성하고 start() 메서드를 호출하면 곧바로 스레드가 실행되는 것처럼 보이지만 실행대기 상태가 된다.</p>
</blockquote>
<h3 id="실행대기란-">실행대기란 ?</h3>
<ul>
<li>아직 스케줄링이 되지 않아서 실행을 기다리는 상태이다.</li>
<li>실행대기 상태에 있는 스레드 중에서 스케줄링으로 선택된 스레드가 비로소 CPU를 점유하고 run() 메소드를 실행한다. 이때를 실행상태라고 한다.</li>
<li>실행 상태 스레드는 메서드를 모두 실행하기 전에 다시 실행 대기 상태로 돌아갈 수 있다. 그리고 실행 대기 상태에 있는 다른 실행 대기 스레드가 선택되어 실행 상태가 된다. 이런식으로 번갈아가면서 자신의 run() 메서드를 실행하고 모든  run()메서드가 종료되면 종료 상태가 되는 것이다.</li>
</ul>
<h3 id="sleeplong-mills---일정시간동안-쓰레드를-멈추게-한다">sleep(long mills) - 일정시간동안 쓰레드를 멈추게 한다.</h3>
<pre><code class="language-java">static void sleep(long millis)
static void sleep(long millis, int nanos)</code></pre>
<p>밀리세컨드와 나노세컨드의 시간단위로 세밀하게 값을 지정할 수 있지만 어느 정도의 오차가 발생할 수 있다는 것은 염두에 둬야한다.</p>
<p>sleep()에 의해 일시정지 상태(TIMED_WAITING)가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면 잠에서 깨어나 실행대기 상태(RUNNABLE)가 된다. 그래서 sleep()을 호출할 때는 항상 <code>try - catch</code>로 감싸 예외처리를 해야 한다.</p>
<p>sleep()은 현재 실행 중인 쓰레드에 대해 작동한다. 따라서 Thread.sleep()을 사용한다.</p>
<pre><code class="language-java">try {
    th1.sleep()
} catch(InterruptedException e) {

}</code></pre>
<p><code>th1.sleep()</code>을 호출한다고 하더라도 현재의 쓰레드인 main 쓰레드가 sleep 상태에 들어간다.</p>
<h3 id="interrupt와--interrupted---쓰레드의-작업을-취소한다">interrupt()와  interrupted() - 쓰레드의 작업을 취소한다.</h3>
<p>진행 중인 쓰레드의 작업이 끝나기 전에 취소시켜야할 때가 있다.</p>
<ul>
<li>interrupt() - 단지 멈추라고 요청하는 것일뿐, 쓰레드를 강제로 종료시키지는 못한다.<ul>
<li>쓰레드의 interrupted상태를  false에서 true로 변경</li>
</ul>
</li>
<li>interrupted() - 쓰레드에 대해  interrupt()가 호출되었는지 알려준다.<ul>
<li><code>boolean isInterrupted()</code> - 쓰레드의 interrupted 상태를 반환</li>
<li><code>static boolean isInterrupted()</code> - 현재 쓰레드의  interrupted 상태를 반환 후, false로 변경</li>
</ul>
</li>
</ul>
<pre><code class="language-java">Thread th = new Thread();
th.start();
...

th.interrupt();    // 쓰레드  th에 interrupt()를 호출한다.

class MyThread extends Thread {
    public void run() {
        while(!interrupted()) {    // interrupted() 결과가 false인 동안 반복
            ...
        }
    }
}</code></pre>
<p>쓰레드가 sleep(), wait(), join()에 의해 일시정지 상태에 있을 때, 해당 쓰레드에 대해 interrupt()를 호출하면 sleep(), wait(), join()에서 <code>InterruptedException</code>이 발생하고  쓰레드는 실행대기 상태(RUNNABLE)로 바뀐다. </p>
<h3 id="suspend-resume-stop---deprecated">suspend(), resume(), stop() - deprecated</h3>
<p>suspend() - 쓰레드를 멈추게 한다. suspend()에 의해 정지된 쓰레드는  resume()을 호출해야 다시 실행대기 상태가 된다. stop()은 호출되는 즉시 쓰레드가 종료된다. 위 3개의 메서드는 쓰레드의 실행을 제어하는 가장 손쉬운 방법이지만 suspend()와 stop()이 교축상태를 일으키기 쉽게 작성되어 있으므로 권장하지는 않는다.</p>
<h3 id="yield---다른-쓰레드에게-양보한다">yield() - 다른 쓰레드에게 양보한다.</h3>
<p>쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보한다.</p>
<p>예를 들어 스케쥴러에 의해 1초의 실행시간을 할당받은 쓰레드가 0.5초의 시간동안 작업한 상태에서 yield()가 호출되면 나머지 0.5초는 포기하고 다시 실행대기상태가 된다. </p>
<p>yield(), interrupt()를 적절히 사용하면 프로그램의 응답성을 높이고 보다 효율적인 실행이 가능하게 할 수 있다.</p>
<pre><code class="language-java">package week_10.status;

public class ThreadYieldExample implements Runnable {
    boolean suspended = false;
    boolean stopped = false;
    Thread th;

    public ThreadYieldExample(String name) {
        th = new Thread(this, name); // Thread(Runnable r, String name)
    }

    @Override
    public void run() {
        String name = th.getName();

        while (!stopped) {
            if (!suspended) {
                System.out.println(name);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(name + &quot; - interrupted&quot;);
                }
            } else {
                Thread.yield();
            }
        }
        System.out.println(name + &quot;- stopped&quot;);
    }

    public void suspend() {
        suspended = true;
        th.interrupt();
        System.out.println(th.getName() + &quot; - interrupted() by suspend()&quot;);
    }

    public void resume() {
        suspended = false;
    }

    public void stop() {
        stopped = true;
        th.interrupt();
        System.out.println(th.getName() + &quot; - interrupted by stop()&quot;);
    }

    public void start() {
        th.start();
    }


    public static void main(String[] args) {
        ThreadYieldExample th1 = new ThreadYieldExample(&quot;*&quot;);
        ThreadYieldExample th2 = new ThreadYieldExample(&quot;**&quot;);
        ThreadYieldExample th3 = new ThreadYieldExample(&quot;***&quot;);
        th1.start();
        th2.start();
        th3.start();

        try {
            Thread.sleep(2000);
            th1.suspend();
            Thread.sleep(2000);
            th2.suspend();
            Thread.sleep(3000);
            th1.resume();
            Thread.sleep(3000);
            th1.stop();
            th2.stop();
            Thread.sleep(2000);
            th3.stop();
        } catch (InterruptedException e) {
        }
    }
}
</code></pre>
<p>else문을 보면 yield()를 호출해서 남은 실행시간을 while문에서 낭비하지 않고 다른 쓰레드에게 양보하므로 더 효율적이다.</p>
<p>interrupt()를 호출하면 sleep()에서 InterruptedException이 발생하여 즉시 일시정지 상태에서 벗어나게 되므로 응답성이 좋아진다.</p>
<p>만일 stop()이 호출되었을 때 Thread.sleep(1000)에 의해 쓰레드가 일시정지 상태에 머물러 있는 상황이라면 stopped의 값이 true로 바뀌었어도 쓰레드가 정지될 때까지 1초의 시간지연이 생겼을 것이다. 하지만 같은 상황에서  interrupt()를 호출하면 sleep()에서 <code>InterruptedException</code>이 발생하여 즉시 일시정지 상태에서 벗어나게 되므로 응답성이 좋아진다. </p>
<h3 id="join---다른-쓰레드의-작업을-기다린다">join() - 다른 쓰레드의 작업을 기다린다.</h3>
<blockquote>
<p>  쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간 동안 작업을 수행하도록 할 때 join()을 사용한다.</p>
</blockquote>
<pre><code class="language-java">void join()
void join(long millis)
void join(long millis, int nanos)    </code></pre>
<p>시간을 지정하지 않으면 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다. 작업 중에 다른 쓰레드의 작업이 먼저 수행되어야할 필요가 있을 때  join()을 사용한다.</p>
<pre><code class="language-java">try {
    th1.join();    // 현재 실행중인 쓰레드가 쓰레드 th1의 작업이 끝날 때까지 기다린다.   
} catch(InterruptedExcepion e) {

}</code></pre>
<p>join()도 sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있으며, join()이 호출되는 부분을  <code>try-catch</code>로 감싸야한다. 허나 다른점은 join()은 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작하므로  static 메서드가 아니라는 것이다. </p>
<ul>
<li>join() 예제</li>
</ul>
<pre><code class="language-java">package week_10.status;

import java.sql.SQLOutput;

public class joinExample {
    static long startTime = 0;

    public static void main(String[] args) {
        joinExample_1 th1 = new joinExample_1();
        joinExample_2 th2 = new joinExample_2();
        th1.start();
        th2.start();
        startTime = System.currentTimeMillis();

        try {
            th1.join(); // main 쓰레드가 th1의 작업이 끝날 떄까지 기다린다.
            th2.join(); // main 쓰레드가 th2의 작업이 끝날 떄까지 기다린다.
        } catch (InterruptedException e) {
        }
        System.out.print(&quot;소요시간 : &quot; + (System.currentTimeMillis() - joinExample.startTime));
    }   // main
}

class joinExample_1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i &lt; 300; i++) {
            System.out.print(new String(&quot;-&quot;));
        }
    } // run
}

class joinExample_2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i &lt; 300; i++) {
            System.out.print(new String(&quot;|&quot;));
        }
    } // run
}</code></pre>
<ul>
<li>실행결과</li>
</ul>
<pre><code>|||||||||||||||||||---------------------------------------------------|||----------------------------------|||------||--------|||||----||-||||||--------------------|||||||||||||||||||||||||||||||||||||----||||||||||||||||||--------------------------------------------|||--||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-----------------------------------------------------------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-|||||-------|||||||||||||||||||||||||-----------------------------------------------------소요시간 : 11</code></pre><p>join()을 사용하여 main 쓰레드가 th1, th2의 작업이 끝날때까지 기다리도록 했다. </p>
<ul>
<li>더욱 실질적인 예제</li>
</ul>
<pre><code class="language-java">package week_10.status;

public class JoinDeeperExample {
    public static void main(String[] args) {
        JoinDeeperExample_1 gc = new JoinDeeperExample_1();
        gc.setDaemon(true);
        gc.start();

        int requiredMemory = 0;

        for (int i = 0; i &lt; 20; i++) {
            requiredMemory = (int) (Math.random() * 10) * 20;

            // 필요한 메모리가 사용할 수 있느 양보다 크거나 전체 메모리의 60% 이상을 사용했을 경우 gc를 깨운다.
            if (gc.freeMemory() &lt; requiredMemory
                    || gc.freeMemory() &lt; gc.totalMemory() * 0.4) {
                gc.interrupt();
            }
            gc.usedMemory += requiredMemory;
            System.out.println(&quot;userMemory : &quot; + gc.usedMemory);
        }
    }
}

class JoinDeeperExample_1 extends Thread {
    final static int MAX_MEMORY = 1000;
    int usedMemory = 0;

    public void run() {
        while (true) {
            try {
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                System.out.println(&quot;Awaken by interrupt()&quot;);
            }
            gc();
            System.out.println(&quot;Garbage Collected. Free Memory : &quot; + freeMemory());
        }
    }

    public void gc() {
        usedMemory -= 300;
        if (usedMemory &lt; 0) {
            usedMemory = 0;
        }
    }

    public int totalMemory() {
        return MAX_MEMORY;
    }

    public int freeMemory() {
        return MAX_MEMORY - usedMemory;
    }
}</code></pre>
<ul>
<li>실행결과</li>
</ul>
<pre><code>userMemory : 20
userMemory : 140
userMemory : 320
userMemory : 340
userMemory : 380
userMemory : 520
userMemory : 680
userMemory : 680
userMemory : 680
Awaken by interrupt()
userMemory : 700
userMemory : 540
userMemory : 700
userMemory : 820
userMemory : 840
userMemory : 1000
userMemory : 1160
userMemory : 1340
Garbage Collected. Free Memory : -340
userMemory : 1400
Awaken by interrupt()
Garbage Collected. Free Memory : -100
userMemory : 1240
userMemory : 1260
Awaken by interrupt()
Garbage Collected. Free Memory : 40
Awaken by interrupt()
Garbage Collected. Free Memory : 340</code></pre><h2 id="쓰레드의-우선순위">쓰레드의 우선순위</h2>
<p>쓰레드는 우선순위라는 속성을 가지고 있는데 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다. 쓰레드가 수행하는 작업의 주요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다.</p>
<h3 id="쓰레드의-우선순위-지정하기">쓰레드의 우선순위 지정하기</h3>
<p>쓰레드의 우선순위와 관련된 메서드와 상수는 다음과 같다.</p>
<pre><code class="language-java">void setPriority(int newPriority) // 쓰레드의 우선순위를 지정한 값으로 변경한다.
int getPriority()                  // 쓰레드의 우선순위를 반환한다.

public static final int MAX_PRIORITY = 10     // 최대 우선 순위
public static final int MIN_PRIORITY = 1    // 최소 우선 순위
public static final int NORM_PRIORITY = 5    // 보통 우선 순위</code></pre>
<p>쓰레드가 가질 수 있는 우선순위의 범위는 1 ~ 10 사이이며 숫자가 높을수록 우선순위가 높다. 쓰레드의 우선 순위는 생성한 쓰레드로부터 상속 받는다. main 메서드를 수행하는 쓰레드는 우선순위가 5이므로 main 메서드 내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가 된다.</p>
<pre><code class="language-java">public class ThreadPriorityExample {
    public static void main(String[] args) {
        ThreadPriorityExample_1 t1 = new ThreadPriorityExample_1();
        ThreadPriorityExample_2 t2 = new ThreadPriorityExample_2();

        t2.setPriority(7);

        System.out.println(&quot;Priority of t1 (-) : &quot; + t1.getPriority());
        System.out.println(&quot;Priority of t2 (|) : &quot; + t2.getPriority());
        t1.start();
        t2.start();
    }
}

class ThreadPriorityExample_1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i &lt; 300; i++) {
            System.out.print(&quot;-&quot;);
            for (int x = 0; x &lt; 10_000_000; x++) ;
        }
    }
}

class ThreadPriorityExample_2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i &lt; 300; i++) {
            System.out.print(&quot;|&quot;);
            for (int x = 0; x &lt; 10_000_000; x++) ;
        }
    }
}</code></pre>
<blockquote>
<p>  t1과 t2 모두 main 메서드에서 생성하였기 때문에 우선순위 5를 상속 받는다. 그 다음에 t2의 우선순위를 7로 지정하여 각 start()를 호출하여 쓰레드를 실행시켰다. </p>
<p>  우선순위가 같은 경우 각 쓰레드에게 거의 같은 양의 실행시간이 주어지지만 우선순위가 다르다면 우선수위가 높은 쓰레드에게 상대적으로 더 많은 양의 실행시간이 주어지고 결과적으로 더 빨리 작업을 완료할 수 있다. 하지만 <strong>멀티코어에서는 쓰레드의 우선순위에 따른 차이가 전혀 없었다.</strong></p>
<p>  <em>그저 쓰레드에 높은 우선순위를 주면 더 많은 실행시간과 실행기회를 갖게 될 것이라고 기대할 수 없는 것이다.</em></p>
<p>  멀티코어라 해도 OS마다 다른 방식으로 스케줄링하기 때문에 어떤 OS에서 실행하느냐에 따라 다른 결과를 얻을 수 있다. 굳이 우선순위에 차등을 두어 쓰레드를 실행하려면 특정 OS의 스케줄링 정책과 JVM의 구현을 직접 확인해봐야 한다. 자바는 쓰레드가 우선순위에 따라 어떻게 다르게 처리되어야 하는지 강제하지 않으므로 쓰레드의 우선순위와 관련된 구현이 JVM마다 다를 수 있기 때문이다.</p>
<p>  만일 확인하나 하더라도 OS의 스케줄러에 종속적이라서 어느 정도 예측만 가능한 정도일 뿐 정확히 알 수는 없다.</p>
</blockquote>
<h3 id="쓰레드-그룹-thread-group">쓰레드 그룹 (Thread Group)</h3>
<p>스레드 그룹은 서로 관련된 쓰레드를 그룹으로 다루기 위한 것으로, 폴더를 생성해서 관련된 파일들을 함께 넣어서 관리하는 것처럼 쓰레드 그룹을 생성해서 쓰레드를 그룹으로 묶어서 관리할 수 있다.</p>
<p>사실 쓰레드 그룹은 보안상의 이유로 도입된 개념으로 자신이 속한 쓰레드 그룹이나 하위 쓰레드 그룹은 변경할 수 있지만 다른 쓰레드 그룹의 쓰레드는 변경할 수는 없다.</p>
<p><code>ThreadGroup</code> 을 사용해서 생성할 수 있으며 주요 생성자와 메서드는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>생성자 / 메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>ThreadGroup(String name)</td>
<td>지정된 이름의 새로운 쓰레드 그룹을 생성</td>
</tr>
<tr>
<td>ThreadGroup(ThreadGroup parent, String name)</td>
<td>지정된 쓰레드 그룹에 포함되는 새로운 쓰레드 그룹을 생성</td>
</tr>
<tr>
<td>int activeCount()</td>
<td>쓰레드 그룹에 포함된 활성상태에 있는 쓰레드의 수를 반환</td>
</tr>
<tr>
<td>int activeGroupCount()</td>
<td>쓰레드 그룹에 포함된 활성상태에 있는 쓰레드 그룹의 수를 반환</td>
</tr>
<tr>
<td>void checkAccess()</td>
<td>현재 실행중인 쓰레드가 쓰레드 그룹을 변경할 권하니 있는지 체크. 만일 권한이 없다면 SecurityException을 발생시킨다.</td>
</tr>
<tr>
<td>void destroy()</td>
<td>쓰레드 그룹과 하위 쓰레드 그룹까지 모두 삭제한다. 단, 쓰레드 그룹이나 하위 쓰레드 그룹이 비어져 있어야한다.</td>
</tr>
<tr>
<td>int enumerate(Thread[] list)<br />int enumerate(Thread[] list, boolean recurse)<br />int enumerate(ThreadGroup[] list)<br />int enumerate(ThreadGroup[] list, boolean recurse)</td>
<td>쓰레드 그룹에 속한 쓰레드 또는 하위 쓰레드 그룹의 목록을 지정된 배열에 담고 그 개수를 반환.<br />두 번째 매개변수인 recurse의 값을 true로 하면 쓰레드 그룹에 속한 하위 쓰레드 그룹에 쓰레드 또는 쓰레드 그룹까지 배열에 담는다.</td>
</tr>
<tr>
<td>int getMaxPriority()</td>
<td>쓰레드 그룹의 최대우선순위를 반환</td>
</tr>
<tr>
<td>String getName()</td>
<td>쓰레드 그룹의 이름을 반환</td>
</tr>
<tr>
<td>ThreadGroup getParent()</td>
<td>쓰레드 그룹의 상위 쓰레드그룹을 반환</td>
</tr>
<tr>
<td>void interrupt()</td>
<td>쓰레드 그룹에 속한 모든 쓰레드를 interrupt()</td>
</tr>
<tr>
<td>boolean isDaemon()</td>
<td>쓰레드 그룹이 데몬 쓰레드그룹인지 확인</td>
</tr>
<tr>
<td>boolean isDestroyed()</td>
<td>쓰레드 그룹이 삭제 되었는지 확인</td>
</tr>
<tr>
<td>void list()</td>
<td>쓰레드 그룹에 속한 쓰레드와 하위 쓰레드그룹에 대한 정보를 출력</td>
</tr>
<tr>
<td>boolean parentOf(ThreadGroup g)</td>
<td>지정된 쓰레드 그룹의 상위 쓰레드그룹인지 확인</td>
</tr>
<tr>
<td>void setDaemon(boolean daemon)</td>
<td>쓰레드 그룹을 데몬 쓰레드 그룹으로 설정 / 해제</td>
</tr>
<tr>
<td>void setMaxPriority(int pri)</td>
<td>쓰레드 그룹의 최대 우선 순위를 설정</td>
</tr>
</tbody></table>
<p>모든 쓰레드는 반드시 쓰레드 그룹에 포함되어야 하기 때문에 쓰레드 그룹을 지정하지 않은 쓰레드는 기본적으로 자신을 생성한 쓰레드와 같은 쓰레드 그룹에 속하게 된다. </p>
<ul>
<li>예제 - 쓰레드 그룹과 쓰레드를 생성하고 main.list()를 호출해서 main 쓰레드 그룹의 정보를 출력</li>
</ul>
<pre><code class="language-java">package week_10.threadGroup;

public class ThreadGroupExample {
    public static void main(String[] args) {
        ThreadGroup main = Thread.currentThread().getThreadGroup();
        ThreadGroup grp1 = new ThreadGroup(&quot;Group1&quot;);
        ThreadGroup grp2 = new ThreadGroup(&quot;Group2&quot;);

        // ThreadGroup(ThreadGroup parent, String name)
        ThreadGroup subGrp1 = new ThreadGroup(grp1, &quot;SubGroup1&quot;);

        grp1.setMaxPriority(3);     // 쓰레드 그룹 grp1의 최대 우선순위를 3으로 지정

        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
        };

        // Thread(ThreadGroup tg, Runnable r, String name)
        new Thread(grp1, r, &quot;th1&quot;).start();
        new Thread(subGrp1, r, &quot;th2&quot;).start();
        new Thread(grp2, r, &quot;th3&quot;).start();

        System.out.println(&quot;&gt;&gt; List of ThreadGroup : &quot; + main.getName() + &quot;, Active ThreadGroup : &quot; + main.activeGroupCount() + &quot;, Active Thread : &quot; + main.activeCount());
        main.list();
    }
}</code></pre>
<pre><code>&gt; 실행 결과

&gt;&gt; List of ThreadGroup : main, Active ThreadGroup : 3, Active Thread : 5
java.lang.ThreadGroup[name=main,maxpri=10]
    Thread[main,5,main]
    Thread[Monitor Ctrl-Break,5,main]
    java.lang.ThreadGroup[name=Group1,maxpri=3]
        Thread[th1,3,Group1]
        java.lang.ThreadGroup[name=SubGroup1,maxpri=3]
            Thread[th2,3,SubGroup1]
    java.lang.ThreadGroup[name=Group2,maxpri=10]
        Thread[th3,5,Group2]</code></pre><h2 id="main-쓰레드">Main 쓰레드</h2>
<p>프로그램이 실행되기 위해서는 작업을 수행하는 쓰레드가 최소한 하나가 필요하다. 그래서 JVM은 실행시 쓰레드를 생성하는데 이 쓰레드가 main()을 호출하면 작업이 수행되도록 하는 것이다.</p>
<pre><code class="language-java">package week_10.main;

class ThreadEx2 {
    public static void main(String[] args) throws Exception {
        ThreadEx2_1 t1 = new ThreadEx2_1();
        t1.start();
    }
}

class ThreadEx2_1 extends Thread {
    @Override
    public void run() {
        throwException();
    }

    public void throwException() {
        try {
            throw new Exception();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}</code></pre>
<pre><code>&gt;&gt; 실행결과

java.lang.Exception
    at week_10.main.ThreadEx2_1.throwException(ThreadEx2.java:18)
    at week_10.main.ThreadEx2_1.run(ThreadEx2.java:13)

Process finished with exit code 0</code></pre><p>호출스택의 첫 번째 메서드가 main 메서드가 아니라 run 메서드이다. main 쓰레드의 호출스택이 없는 것은 main쓰레드가 이미 종료되었기 때문이다.</p>
<blockquote>
<p><strong>한 쓰레드가 예외가 발생해서 종료되어도 다른 쓰레드의 실행에는 영향을 미치지 않는다.</strong></p>
</blockquote>
<pre><code class="language-java">package week_10.main;

class ThreadEx3 {
    public static void main(String[] args) throws Exception{
        ThreadEx3_1 t1 = new ThreadEx3_1();
        t1.run();
    }
}

class ThreadEx3_1 extends Thread {
    @Override
    public void run() {
        throwException();
    }

    public void throwException() {
        try {
            throw new Exception();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}</code></pre>
<pre><code>&gt;&gt; 실행결과

java.lang.Exception
    at week_10.main.ThreadEx3_1.throwException(ThreadEx3.java:18)
    at week_10.main.ThreadEx3_1.run(ThreadEx3.java:13)
    at week_10.main.ThreadEx3.main(ThreadEx3.java:6)

Process finished with exit code 0</code></pre><p>단순히 run()이 호출되었기 때문에 쓰레드가 생성되지 않았다. </p>
<h3 id="데몬-쓰레드">데몬 쓰레드</h3>
<p>데몬 쓰레드는 다른 일반 쓰레드의 작업을 보조하는 역할을 가진 쓰레드이다. 일반 쓰레드가 모두 종료된다면 데몬 쓰레드는 강제적으로 작업을 종료한다. 일반 쓰레드가 종료되는 순간 데몬 쓰레드 또한 쓸모없어지기 때문에 종료시킨다. </p>
<ul>
<li><p>데몬 쓰레드 생성</p>
<blockquote>
<p>먼저 쓰레드를 생성한 뒤, 해당 쓰레드를 실행하기 전에 <code>setDaemon(true)</code>를 호출하여 데몬 쓰레드로 세팅한다.</p>
</blockquote>
</li>
</ul>
<pre><code class="language-java">boolean isDaemon();        // 쓰레드가 데몬 쓰레드인지 확인한다.
void setDaemon(boolean on);     // 쓰레드를 데몬 쓰레드 또는 일반 쓰레드로 변경, true면 데몬 쓰레드</code></pre>
<ul>
<li>활용예시</li>
</ul>
<p>자바 실행 시 JVM은 GC, 이벤트 처리, 그래픽 처리와 같은 프로그램이 실행되는데, 필요한 보조 작업을 수행하는 쓰레드들은 데몬 쓰레드이며 자동적으로 생성되어서 실행되는 구조이다.</p>
<h2 id="동기화">동기화</h2>
<blockquote>
<p>  <strong>한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 쓰레드의 동기화라고 한다.</strong></p>
</blockquote>
<p>멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 된다.  이러한 일이 발생하는 것을 막기 위해서 한 쓰레드가 특정 작업을 마치기 전까지 다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요한데, 이를 위해 도입된 개념이 임계 영역(critical section)과 잠금(lock)이다. </p>
<h3 id="synchronized-를-이용한-동기화">synchronized 를 이용한 동기화</h3>
<ul>
<li>이 키워드(synchronized)는 임게 영역을 설정하는데 필요하다. 방법은 두가지 방식이 있다.</li>
</ul>
<pre><code class="language-java">// 1. 메서드 전체를 임계 영역으로 설정
public synchronized void calcSum() {        // 임계영역
    // ...
}

// 2. 특정한 영역을 임계 영역으로 지정 
synchronized(객체의 참조변수) {                // 임계 영역
    // ...
}</code></pre>
<ol>
<li>은 메서드 앞에 synchronized를 붙이는 것인데, synchronized를 붙이면 메서드 전체가 임계 영역으로 설정된다. 쓰레드는 synchronized 메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 lock을 얻어 작업을 수행하다가 메서드가 종료되면 lock을 반환한다.</li>
<li>는 메서드 내의 코드 일부를 블럭 <code>{}</code>으로 감싸고 블럭 앞에 synchronized(참조변수)를 붙이는 것인데, 이때 참조변수는 락을 걸고자하는 객체를 참조하는 것이어야 한다. 이 블럭을 synchronized 블럭이라고 부르며, 이 블럭의 영역 안으로 들어가면서부터 쓰레드는 지정된 객체의 lock을 얻게 되고 이 블럭을 벗어나면 lock을 반납한다.</li>
</ol>
<blockquote>
<p>  모든 객체는  lock을 하나씩 가지고 있으며 해당 객체의 lock을 가지고 있는 쓰레드만 임계 영역의 코드를 수행할 수 있다. 그리고 다른 쓰레드들은 lock을 얻을 때까지 기다리게 된다. 임계 영역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에 전체보단 임계영역을 최소화해서 효율적인 프로그램이 되도록 노력해야 한다. </p>
<p>  <strong>또한 <code>synchronized</code>가 붙은 임계영역 내에서 사용되는 인스턴스 변수의 접근 제어자는 <code>private</code>이 아니면 안된다. 외부에서 접근이 가능하기 때문에 동기화로 막아놓는다고 해도 값이 변하기 때문이다.</strong></p>
</blockquote>
<h3 id="wait과-notify">wait()과 notify()</h3>
<p><code>synchronized</code>를 사용해서 공유 데이터를 보호하는 것 까지는 좋은데 특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내지 않도록 하는 것도 중요하다.</p>
<p><em>이러한 상황을 개선하기 위해 고안된 것이  wait(), notify()이다.</em> 동기화된 임계영역이 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면 일단 wait()을 호출하여 쓰레드가 락을 반납하고 기다리게 한다. 그러면 다른 쓰레드가 락을 얻어 해당 객체에 대한 작업을 수행할 수 있게 된다. </p>
<p>나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출하여 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행할 수 있게 된다.</p>
<p>다만 작업을 오래 기다린 쓰레드가 락을 얻는다는 보장이 없다는 것이다. </p>
<p>wait()은 notify() || notifyAll()이 호출될 때까지 기다리지만, 매개변수가 있는 wait()은 지정된 시간 동안만 기다린다. notifyAll()은 호출되면 waiting pool에 모든 객체가 깨어나는 것이 아니라 waiting pool에 대기중인 쓰레드만 깨워진다는 것을 기억하자.</p>
<blockquote>
<ul>
<li><strong>wait(), notify(), notifyAll()</strong><ol>
<li>Object에 정의되어 있다.</li>
<li>동기화 블록내에서만 사용할 수 있다.</li>
<li>보다 효율적인 동기화를 가능하게 한다.</li>
</ol>
</li>
</ul>
</blockquote>
<ul>
<li>음식점 예제 - 소비자</li>
</ul>
<pre><code class="language-java">package week_10.waitAndNotify;

public class Customer implements Runnable {
    private Table table;
    private String food;

    public Customer(Table table, String food) {
        this.table = table;
        this.food = food;
    }
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            String name = Thread.currentThread().getName();

            if(eatFood()) {
                System.out.println(name + &quot; : ate a &quot; + food);
            } else {
                System.out.println(name + &quot; failed to eat. :(&quot;);
            }
        }
    }

    private boolean eatFood() {
        return table.remove(food);
    }
}</code></pre>
<ul>
<li>음식점 예제 - 요리사</li>
</ul>
<pre><code class="language-java">package week_10.waitAndNotify;

public class Cook implements Runnable {
    private Table table;

    public Cook(Table table) {
        this.table = table;
    }

    @Override
    public void run() {
        while (true) {
            int idx = (int) (Math.random() * table.dishNum());
            table.add(table.dishNames[idx]);

            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
            }
        }
    }
}</code></pre>
<ul>
<li>음식점 예제 - 테이블</li>
</ul>
<pre><code class="language-java">package week_10.waitAndNotify;

import java.util.ArrayList;

public class Table {
    String[] dishNames = {&quot;donut&quot;, &quot;donut&quot;, &quot;burger&quot;};
    final int MAX_FOOD = 6;

    private ArrayList&lt;String&gt; dishes = new ArrayList&lt;&gt;();

    public void add(String dish) {
        if (dishes.size() &gt;= MAX_FOOD) {
            return;
        }
        dishes.add(dish);
        System.out.println(&quot;Dishes : &quot; + dishes.toString());

    }

    public boolean remove(String dishName) {
        for (int i = 0; i &lt; dishNames.length; i++) {
            if (dishName.equals(dishes.get(i))) {
                dishes.remove(i);
                return true;
            }
        }
        return false;
    }
    public int dishNum() {
        return dishNames.length;
    }
}</code></pre>
<ul>
<li>음식점 예제 - 실행 (예외 발생)</li>
</ul>
<pre><code class="language-java">package week_10.waitAndNotify;

public class ThreadWaitExample_1 {
    public static void main(String[] args) {
        Table table = new Table();

        new Thread(new Cook(table), &quot;COOK1&quot;);
        new Thread(new Customer(table, &quot;donut&quot;), &quot;CUST1&quot;).start();
        new Thread(new Customer(table, &quot;burger&quot;), &quot;CUST2&quot;).start();

        try {
            Thread.sleep(100);
            System.exit(0);
        } catch (Exception e) {
        }
    }
}</code></pre>
<ul>
<li>실행결과</li>
</ul>
<pre><code class="language-java">Exception in thread &quot;CUST1&quot; Exception in thread &quot;CUST2&quot; java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:659)
    at java.util.ArrayList.get(ArrayList.java:435)
    at week_10.waitAndNotify.Table.remove(Table.java:22)
    at week_10.waitAndNotify.Customer.eatFood(Customer.java:29)
    at week_10.waitAndNotify.Customer.run(Customer.java:20)
    at java.lang.Thread.run(Thread.java:750)
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:659)
    at java.util.ArrayList.get(ArrayList.java:435)
    at week_10.waitAndNotify.Table.remove(Table.java:22)
    at week_10.waitAndNotify.Customer.eatFood(Customer.java:29)
    at week_10.waitAndNotify.Customer.run(Customer.java:20)
    at java.lang.Thread.run(Thread.java:750)</code></pre>
<blockquote>
<p>  테이블의 마지막 음식을 가져가는 도중에 다른 손님이 낚아채갔기 때문에 <code>IndexOutOfBoundsException</code>이 발생한다.</p>
<p>  또한 위 실행결과에는 없지만, 요리사가 음식을 만들기도 전에 음식을 가져가려고 하기 때문에 <code>ConcurrentModificationException</code>이 발생한다.</p>
</blockquote>
<p>위 문제를 해결하기 위해서 <code>synchronized</code> 를 통해 동기화를 추가한다.</p>
<ul>
<li>음식점 예제 - 테이블(동기화 추가)</li>
</ul>
<pre><code class="language-java">package week_10.waitAndNotify;

import java.util.ArrayList;

public class Table {
    String[] dishNames = {&quot;donut&quot;, &quot;donut&quot;, &quot;burger&quot;};
    final int MAX_FOOD = 6;

    private ArrayList&lt;String&gt; dishes = new ArrayList&lt;&gt;();

    public synchronized void add(String dish) {
        if (dishes.size() &gt;= MAX_FOOD) {
            return;
        }
        dishes.add(dish);
        System.out.println(&quot;Dishes : &quot; + dishes.toString());

    }

    public boolean remove(String dishName) {
        synchronized (this) {
            while (dishes.size() == 0) {
                String name = Thread.currentThread().getName();
                System.out.println(name + &quot; is waiting&quot;);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {

                }
            }
            for (int i = 0; i &lt; dishNames.length; i++) {
                if (dishName.equals(dishes.get(i))) {
                    dishes.remove(i);
                    return true;
                }
            }
            return false;
        }

    }

    public int dishNum() {
        return dishNames.length;
    }
}</code></pre>
<ul>
<li>실행결과</li>
</ul>
<pre><code class="language-java">CUST2 is waiting
CUST2 is waiting
CUST2 is waiting
CUST2 is waiting
CUST2 is waiting
CUST2 is waiting
CUST2 is waiting
CUST2 is waiting
CUST2 is waiting
CUST2 is waiting</code></pre>
<blockquote>
<p>  예외는 발생하지 않지만, 결과가 원활하지 않다.</p>
<p>  요리사 쓰레드가 음식을 추가하지 않고 손님 쓰레드를 기다리는 이유는 손님 쓰레드가 테이블 객체의 lock을 쥐고 있기 때문이다. 이럴때 사용하는 것이 wait() &amp; notify() 이다.</p>
</blockquote>
<ul>
<li>음식점 예제 - 테이블(wait(), notify()) 추가</li>
</ul>
<pre><code class="language-java">package week_10.waitAndNotify;

import java.util.ArrayList;

public class Table {
    String[] dishNames = {&quot;donut&quot;, &quot;donut&quot;, &quot;burger&quot;};
    final int MAX_FOOD = 6;

    private ArrayList&lt;String&gt; dishes = new ArrayList&lt;&gt;();

    public synchronized void add(String dish) {
        String name = Thread.currentThread().getName();
        System.out.println(name + &quot; is waiting&quot;);

        try {
            wait();
            Thread.sleep(500);
        } catch (InterruptedException e) {
        }
        dishes.add(dish);
        notify();
        System.out.println(&quot;Dishes : &quot; + dishes.toString());

    }

    public boolean remove(String dishName) {
        synchronized (this) {
            String name = Thread.currentThread().getName();
            while (dishes.size() == 0) {
                System.out.println(name + &quot; is waiting&quot;);
                try {
                    wait();
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
            }
            while (true) {
                for (int i = 0; i &lt; dishNames.length; i++) {
                    if (dishName.equals(dishes.get(i))) {
                        dishes.remove(i);
                        notify();
                    }
                }
                try {
                    System.out.println(name + &quot; is waiting&quot;);
                    wait();
                    Thread.sleep(500);

                } catch (InterruptedException e) {

                }
            }
        }
    }

    public int dishNum() {
        return dishNames.length;
    }
}</code></pre>
<blockquote>
<p>  이전 예제에 wait(), notify()를 추가하였다.</p>
<p>  하지만 아직 한가지 문제가 있다. 요리사와 손님 쓰레드가 같이 기다다는 것이다. 그래서 notify()가 호출 되었을 때, 요리사 쓰레드와 손님 쓰레드 중에서 누가 통지 받을지 알 수 없다.</p>
</blockquote>
<h3 id="기아-현상과-경쟁-상태">기아 현상과 경쟁 상태</h3>
<ul>
<li><p>기아 현상</p>
<p>: 쓰레드가 계속 통지를 받지 못하고 오랫동안 기다리게 되는 것을 말한다. 이럴 때는 모든 쓰레드에게 통지를 할 수 있게끔 notify대신 notifyAll()을 사용한다.</p>
</li>
<li><p>경쟁 상태 </p>
<p>: lock을 얻기 위해 서로 경쟁하는 것.</p>
</li>
</ul>
<blockquote>
<p>  <em>이럴 때, 선별적인 통지를 가능하게끔 lock과 Condition을 사용한다.</em></p>
</blockquote>
<h3 id="lock과-condition을-이용한-동기화">Lock과 Condition을 이용한 동기화</h3>
<blockquote>
<p>  JDK1.5에 와서 <code>java.util.concurrent.locks</code>패키지가 제공하는 lock클래스들을 사용할 수 있다.</p>
</blockquote>
<p>synchronized블럭으로 동기화를 하면 자ㅓ동적으로 lock이 잠기고 풀리기 때문에 편리하다. 심지어 synchronized 블럭 내에서 예외가 발생해도 lock은 자동으로 풀린다. 그러나 떄로는 <strong>같은 메서드 내에서만 lock을 걸 수 있다는 제약이 불편</strong>하기도 하다. 그럴 때 이 lock 클래스를 사용한다.</p>
<p><em>Reentrant - 재진입할 수 있는</em></p>
<ul>
<li><p>ReentrantLock - 재진입이 가능한 lock, 가장 일반적인 배타 lock</p>
<p>reentrant라는 단어가 붙은 이유는 특정 조건에서 lock을 풀고 나중에 다시 lock을 얻고 임계영역으로 들어와서 이후의 작업을 수행할 수 있기 떄문이다.</p>
</li>
<li><p>ReentrantReadWriteLock - 읽기에는 공유적이고, 쓰기에는 배타적인 lock</p>
<p>읽기를 위한 lock과 쓰기를 위한 lock을 제공한다. 읽기 lock이 걸려있으면 다른 쓰레드가 읽기 lock을 중복해서 걸고 읽기를 수행할 수 있다. 읽기는 내용을 변경하지 않으므로 동시에 여러 쓰레드가 읽어도 문제가 되지 않는다. 그러나 읽기 lock이 걸린 상태에서  쓰기 lock을 거는 것은 허용되지 않는다. 반대의 경우 또한 동일하다.</p>
</li>
<li><p>StampedLock - ReentrantReadWriteLock에 낙관적인 lock의 기능을 추가</p>
<p>lock을 걸거나 해지할 때 stamp(long 타입의 정수값)를 사용하며, 읽기와 쓰기를 위한 lock외에 낙관적 읽기(optimistic reading lock)이 추가된 것이다. 읽기 lock이 걸려 있으면 쓰기 lock을 얻기 위해서는 읽기 lock이 풀릴 때까지 기다려야 하는데 비해 <code>낙관적 읽기  lock</code>은 쓰기 lock에 의해 바로 풀린다. </p>
<p><strong>낙관적 읽기에 실패하면 다시 읽기 lock을 얻어서 다시 읽어 와야 한다. 무조건 읽기  lock을 걸지 않고 쓰기와 읽기가 충돌할 때만 쓰기가 끝난 후에 읽기 lock을 거는 것이다.</strong></p>
</li>
<li><p>예제 - StampedLock</p>
</li>
</ul>
<pre><code class="language-java">int getBalance() {
    long stamp = lock.tryOptimisticRead(); // 낙관적 읽기 lock을 건다. 

    int curBalance = this.balance; // 공유 데이터인 balance를 읽어온다.

    if(!lock.validate(stamp)) {    // 쓰기 lock에 의해 낙관적 읽기 lock이 풀렸는지 확인
        stamp = lock.readLock();    // lock이 풀렸으면, 읽기 lock을 얻으려고 기다린다.

        try {
            curBalance = this.balance;     // 공유 데이터를 다시 읽어온다.
        } finally {
            lock.unlockRead(stamp);    // 읽기 lock을 푼다.
        }
    }
    return curBalance;    // 낙관적 읽기 lock이 풀리지 않았으면 곧바로 읽어온 값을 반환
}</code></pre>
<ul>
<li>ReentrantLock의 생성자</li>
</ul>
<pre><code class="language-java">ReentrantLock()
ReentrantLock(boolean fair)</code></pre>
<p>생성자의 매개변수를 true로 주면, lock이 풀렸을 때 가장 오래 기다린 쓰레드가 lock을 획득할 수 있게, 즉 공정하게 처리한다. 어떤 쓰레드가 가장 오래기다렸는지 확인해야하므로 성능은 떨어진다.</p>
<pre><code class="language-java">void lock();
void unlock();
boolean isLocked();</code></pre>
<p>ReentrantLock과 같은 lock 클래스들은 수동으로 lock을 잠그고 해제해야 한다. 방법은 간단하다. 메서드를 호출하면 된다. 임계 영역 내에서 예외가 발생하거나 return문으로 빠져나가게 되면 lock이 풀리지 않을 수 있으므로 <code>try-finally</code>문으로 감싸는게 일반적이다.</p>
<p>이외에도 tryLock()이라는 메서드가 있는데, 이 메서드는 lock()과 달리, 다른 쓰레드에 의해 lock이 걸려있으면 lock을 얻으려고 기다리지 않는다. 또는 지정된 시간만큼만 기다린다. lock을 얻으면 true, 아니면 false를 리턴한다.</p>
<pre><code class="language-java">boolean tryLock();
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException</code></pre>
<p>응답성이 중요한 경우 tryLock()을 사용하여 지정된 시간 동안 lock을 얻지 못하면 다시 작업을 시도할지, 포기할지를 사용자가 결정하게 하는 것이 좋다.</p>
<ul>
<li>ReentrantLock과 Condition</li>
</ul>
<blockquote>
<p>  wait() &amp; notify() 의 단점을 해결하기 위한 것.</p>
</blockquote>
<p>Condition은 이미 생성된 lock으로부터 newCondition()을 호출해서 생성한다.</p>
<pre><code class="language-java">private ReentrantLock lock = new ReentrantLock();    // lock을 생성
private Condition forCook = new lock.Condition();
private Condition forCust = new lock.Condition();</code></pre>
<ul>
<li>wait() &amp; notify()와 await() &amp; signal()의 비교</li>
</ul>
<table>
<thead>
<tr>
<th>Object</th>
<th>Condition</th>
</tr>
</thead>
<tbody><tr>
<td>void wait()</td>
<td>void await()<br />void awaitUninterruptibly()</td>
</tr>
<tr>
<td>void wait(long timeout)</td>
<td>boolean await(long time, TimeUnit unit)<br />long awaitNanos(long nanosTimeout)</td>
</tr>
<tr>
<td>void notify()</td>
<td>void signal()</td>
</tr>
<tr>
<td>void notifyAll()</td>
<td>void signalAll()</td>
</tr>
</tbody></table>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">package week_10.status;

import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class AwaitConditionExample {
}

class Customer implements Runnable {

    private Table table;
    private String food;

    Customer(Table table, String food) {
        this.table = table;
        this.food = food;
    }

    @Override
    public void run() {
        while (true) {
            int idx = (int) (Math.random() * table.dishNum());
            table.add(table.dishNames[idx]);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
        }
    }
}

class Table {
    String[] dishNames = {&quot;donut&quot;, &quot;donut&quot;, &quot;burger&quot;};
    final int MAX_FOOD = 6;
    private ArrayList&lt;String&gt; dishes = new ArrayList&lt;&gt;();

    private ReentrantLock lock = new ReentrantLock();
    private Condition forCook = lock.newCondition();
    private Condition forCust = lock.newCondition();

    public void add(String dish) {
        lock.lock();
        try {
            while (dishes.size() &gt;= MAX_FOOD) {
                String name = Thread.currentThread().getName();
                System.out.println(name + &quot; is waiting.&quot;);
                try {
                    forCook.await();
                    Thread.sleep(500);

                } catch (InterruptedException e) {
                }
            }
            dishes.add(dish);
            forCust.signal();
            System.out.println(&quot;Dishes:&quot; + dishes.toString());
        } finally {
            lock.unlock();
        }
    }

    public void remove(String dishName) {
        lock.lock();    // synchronized(this) {
        String name = Thread.currentThread().getName();

        try {
            while (dishes.size() == 0) {
                System.out.println(name + &quot;is waiting.&quot;);
                try {
                    forCust.await();    // wait(); CUST 쓰레드를 기다리게 한다.
                } catch (InterruptedException e) {
                }
            }
            while (true) {
                for (int i = 0; i &lt; dishes.size(); i++) {
                    if (dishName.equals(dishes.get(i))) {
                        dishes.remove(i);
                        forCook.signal();       // notify(); 잠자고 있는 COOK을 깨움
                        return;
                    }
                }

                try {
                    System.out.println(name + &quot;is waiting.&quot;);
                    forCust.await();
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
            }
        } finally {
            lock.unlock();
        }
    }

    public int dishNum() {
        return dishNames.length;
    }
}

class Cook implements Runnable {
    private Table table;

    public Cook(Table table) {
        this.table = table;
    }

    @Override
    public void run() {
        while (true) {
            int idx = (int) (Math.random() * table.dishNum());
            table.add(table.dishNames[idx]);

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
    }
}

class TreadWaitEx4 {
    public static void main(String[] args) throws InterruptedException {
        Table table = new Table();
        new Thread(new Cook(table), &quot;COOK1&quot;).start();
        new Thread(new Customer(table, &quot;donut&quot;), &quot;CUST1&quot;).start();
        new Thread(new Customer(table, &quot;burger&quot;), &quot;CUST2&quot;).start();

        Thread.sleep(2000);
        System.exit(0);
    }
}
</code></pre>
<h3 id="volatile">volatile</h3>
<blockquote>
<p>Java변수를 Main Memory에 저장하겠다라는 것을 명시하는 것이다. 매번 변수의 값을 읽어올 때 Cache Memory가 아닌 Main Memory에서 읽는다. 또한 변수의 값을 Write할 때 Main Memory에 까지 작성한다.</p>
</blockquote>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h23nw8jde4j20q70ep74x.jpg" alt="스크린샷 2022-05-10 23.08.33"></p>
<p>코어는 메모리에서 읽어온 값을 캐시에 저장하고 캐시에서 값을 읽어서 작업한다. 다시 같은 값을 읽어올 때는 먼저 캐시에 있는지 확인하고 없을 때만 메모리에서 읽어온다. 그러다보니 도중에 메모리에 저장된 변수의 값이 변경되었는데도 캐시에 저장된 값이 갱신되지 않아서 메모리에 저장된 값이 다른 경우가 발생한다. 그래서 변수 stopped의 값이 바뀌었는데도 쓰레드가 멈추지 않고 계속 실행되는 것이다.</p>
<pre><code class="language-java">boolean suspend = false;
boolean stopped = false;
// -&gt;
volatile boolean suspend = false;
volatile boolean stopped = false;</code></pre>
<blockquote>
<p>  <code>volatile</code>을 붙히면 코어의 변수가 값을 읽어올 때 캐시가 아닌 메모리에서 읽어온다. 따라서 캐시와 메모리간의 값의 불일치가 해결된다.</p>
<p>  <em>실제로 캐시와 메모리에서 값을 읽어오는 과정은 더 복잡하다.</em></p>
</blockquote>
<p><code>synchronized</code>블럭을 사용하는 것도 같은 효과를 낼 수 있다. 쓰레드가 synchronized 블록으로 들어갈 때와 나올 때, 캐시와 메모리간의 동기화가 이뤄지기 때문이다.</p>
<ul>
<li>volatile로 long과 double을 원자화</li>
</ul>
<blockquote>
<p>  <code>volatile</code>은 해당 변수에 대한 읽거나 쓰기가 원자화 된다.</p>
<p>  <em>원자화란 작업을 더 이상 나눌 수 없다는 뜻이다, volatile로 처리하는 원자화는 synchronized처럼 동기화하는 것은 아니다.</em></p>
</blockquote>
<p>JVM이 데이터를 처리하는 단위는 4Byte이기 때문에 int형과 그보다 작은 타입들은 한 번에 읽거나 쓰는 것이 가능하다. 반면에 크기가 8Byte인 long과 double 타입의 변수는 하나의 명령어로 값을 읽거나 쓸 수 없기 때문에 변수의 값을 읽는 과정에 다른 쓰레드가 끼어들 여지가 있다. 다른 쓰레드가 끼어들지 못하게 하려고 변수를 읽고 쓰는 모든 과정을 volatile로 해결할 수 있다.</p>
<pre><code class="language-java">volatile long sharedVal;    // long타입의 변수(8byte)를 원자화
volatile double sharedVal;    // double타입의 변수(8byte)를 원자화

// 예제

volatile long balance;     // 인스턴스 변수 balance를 원자화한다.

synchronized long getBalance() {    // balance의 값을 반환한다.
    return balance;
}

synchronized void withDraw(int money) {
    if(balance &gt; money) {
        balance -= money;
    }
}</code></pre>
<p>balance를 volatile로 원자화 했으니까 getBalance를 동기화 할 필요가 없다고 생각할 수 있다. 그러나 getBalance를 동기화 하지 않으면 withDraw()가 호출되어 객체에 lock을 걸어도 getBalance()가 호출되는 것이 가능해진다. 따라서 getBalance()는 synchronized로 동기화를 해야한다.</p>
<h3 id="fork--join-프레임워크">fork &amp; join 프레임워크</h3>
<blockquote>
<p>  시대가 빠르게 변하면서 멀티 코어를 잘 활용할 수 있는 멀티 쓰레드 프로그래밍이 점점 더 중요해지고 있다. 하지만 그리 쉽지는 않다.</p>
<p>  <em>JDK1.7부터는 fork&amp;join 프레임워크가 추가 되었고 이 프로그램은 하나의 작업은 작게 나누어 여러 쓰레드가 동시에 처리하는 것을 쉽게 만들어준다.</em></p>
</blockquote>
<ul>
<li>수행할 작업에 따라 두 클래스 중에서 하나를 상속받아 구현해야 한다.</li>
</ul>
<pre><code class="language-java">RecusiveAction // 반환값이 없는 작업을 구현할 때 사용
RecursiveTask // 반환값이 있는 작업을 구현할 때 사용</code></pre>
<p>두 클래스 모두 compute()라는 추상 메서드를 가지고 있는데, 우리는 상속을 통해 이 추상 메서드를 구현하기만 하면 된다. </p>
<pre><code class="language-java">public abstract class RecusiveAction extends ForkJoinTask&lt;Void&gt; {
    ...
    protected abstract void compute(); // 상속을 통해 이 메서드를 구현해야 한다.
    ...
}

public abstract class RecursiveTask&lt;V&gt; extends ForkJoinTask&lt;V&gt; {
    ...
    V result;
    protected abstract V compute();    // 상속을 통해 이 메서드를 구현해야 한다.
}</code></pre>
<p>1부터 n까지의 합을 계산한 결과를 돌려주는 작업의 구현은 다음과 같이 한다.</p>
<pre><code class="language-java">public class SumTask extends RecursiveTask&lt;Long&gt; {  // RecursiveTask 를 상속 받는다.
    long from, to;

    SumTask(long from, long to) {
        this.from = from;
        this.to = to;
    }

    @Override
    protected Long compute() {
        long result = 0;
        // 처리할 작업을 수행하기 위한 문장을 넣는다.
        for (from = 1; from &lt; to; from++) {
            result += from;
        }
        return result;
    }
}</code></pre>
<p>그 다음에는 쓰레드풀과 수행할 작업을 생성하고 invoke()로 작업을 시작한다. 쓰레드를 시작할 때 run()이 아니라 start()를 호출하는 것처럼 fork&amp;join프레임워크로 수행할 작업도 compute()가 아닌 invoke()로 시작한다.</p>
<pre><code class="language-java">ForkJoinPool pool = new ForkJoinPool();
SumTask task = new SumTask(from, to);
long result = pool.invoke(task);</code></pre>
<ul>
<li><p>ForkJoinPool 장점</p>
<ol>
<li>프레임워크에서 제공하는 쓰레드 풀로 지정된 수의 쓰레드를 미리 만들어 놓고 재사용할 수 있게 해준다.</li>
<li>쓰레드를 반복해서 생성하지 않아도 된다</li>
<li>너무 많은 쓰레드가 생성되어 성능이 저하되는 것을 막아준다</li>
</ol>
</li>
<li><p>쓰레드 풀은 쓰레드가 수행해야하는 작업이 담긴 큐를 제공하며, 각 쓰레드는 자신의 작업 큐에 담긴 작업을 순서대로 처리한다.</p>
</li>
<li><p>compute() 구현</p>
</li>
</ul>
<blockquote>
<p>  <code>compute()</code>를 구현할 때는 수행할 작업 외에도, 작업을 어떻게 나눌  것인가에 대해서도 알려줘야 한다.</p>
</blockquote>
<pre><code class="language-java">public Long compute() {
    long size = to - from + 1; // from &lt;= 1 &lt;= to

    if(size &lt;= 5) {    // 더할 숫자가 5 이하면
        return sum(); // 숫자의 합을 반환. sum()은 from 부터 to 까지의 수를 더해서 반환        
    }
    // 범위를 반으로 나눠서 두 개의 작업을 생성
    long half = (from+to) / 2;

    SumTask leftSum = new SumTask(from, half);
    SumTask rightSum = new SumTask(half + 1, to);

    leftSum.fork(); // 작업을 큐에 넣는다.

    return rightSum.compute() + leftSum.join();
}</code></pre>
<p>실제 수행한 작업은 sum() 뿐이고, 나머지는 수행할 작업의 범위를 반으로 나눠서 새로운 작업을 생성해서 실행시키기 위한 것이다. 이 과정은 작업이 더 이상 나눠질 수 없을 때까지 size의 값이 5보다 작거나 같을 때까지 반복된다.</p>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h24pp3ts56j20yw0sqmy6.jpg" alt="스크린샷 2022-05-11 20.56.32"></p>
<p>이 그림에서는 작업의 size가 2가 될 때까지 나눈다. compute()가 처음 호출되면 더할 숫자의 범위를 반으로 나눠서 한 쪽에는 fork()를 호출해서 작업 큐에 저장한다. 하나의 쓰레드는 compute()를 재귀호출하면서 작업을 계속해서 반으로 나누고 다른 쓰레드는 fork()에 의해 작업 큐에 추가된 작업을 수행한다.</p>
<ul>
<li>다른 쓰레드의 작업 훔쳐오기 </li>
</ul>
<p>fork()가 호출되어 작업 큐에 추가된 작업 역시 compute()에 의해 더 이상 나눌 수 없을 때까지 반복해서 나뉘고, 자신의 작업 큐가 비어있는 쓰레드는 다른 쓰레드의 작업 큐에서 작업을 가져와서 수행한다. 이것을 <strong>작업 훔쳐오기</strong>라고 한다. 이 과정은 모두 쓰레드풀에 의해 자동적으로 이루어진다. </p>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h24pzm1vg8j218s0js0tx.jpg" alt="스크린샷 2022-05-11 21.06.41"></p>
<p>작업 큐가 비어있는 쓰레드가 다른 쓰레드의 작업을 가져와서 수행하는 것을 그런 것이다. 이런 과정을 통해 한 쓰레드에 작업이 몰리지 않고 여러 쓰레드가 골고루 작업을 나누어 처리하게 된다.</p>
<ul>
<li>fork() 와 join()</li>
</ul>
<p>fork()는 작업을 쓰레드의 작업 큐에 넣는 것이고, 작업 큐에 들어간 작업은 더 이상 나눌 수 없을 때까지 나뉜다. 즉 compute()로 나누고 fork()로 작업 큐에 넣는 작업이 계속해서 반복된다. 그리고 나눠진 작업은 각 쓰레드가 골고루 나눠서 처리하고 작업의 결과는 join()을 호출해서 얻을 수 있다.</p>
<blockquote>
<p>  fork()와 join()의 차이점은 동기 / 비동기이다.</p>
<ul>
<li>fork() - 해당 작업을 쓰레드 풀의 작업 큐에 넣는다. <em>비동기메서드</em></li>
<li>join() - 해당 작업의 수행이 끝날 때까지 기다렸다가, 수행이 끝나면 그 결과를 반환한다. <em>동기 메서드</em></li>
</ul>
</blockquote>
<p>비동기 메서드는 일반적인 메서드와 달리 메서드를 호출만 할 뿐, 그 결과를 기다리지 않는다. 그래서 fork()를 호출하면 결과를 기다리지 않고 return문으로 넘어간다. return문에서 compute()가 재귀호출될 때, join()은 호출되지 않는다. 그러다가 작업을 더 이상 나눌 수 없게 됐을 때, compute()의 재귀호출은 끝나고 join()의 결과를 기다렸다가 더해서 결과를 리턴한다. 재귀호출된 compute()가 모두 종료될 때 최종 결과를 얻는다.</p>
<pre><code class="language-java">public Long compute() {
    ...
    SumTask leftSum = new SumTask(from, half); 
    SumTask rightSum = new SumTask(half + 1, to);
    leftSum.fork();        // 비동기 메서드, 호출 후 결과를 기다리지 않는다.

    return rightSum.compute() + leftSum.join(); // 동기 메서드. 호출 결과를 기다린다.
}</code></pre>
<h2 id="데드락">데드락</h2>
<blockquote>
<p><em>2개 이상의 쓰레드가 서로를 기다리며 영원히 blocked된 상태</em></p>
</blockquote>
<p> 데드락(<code>교착상태</code>) 은 둘 이상의 쓰레드가 Lock을 획득하기 위해 대기하는데, 이 Lock을 잡고 있는 자원에 서로 다른 쓰레드들이 Lock을 동시에 획득하려고 할 때 발생할 수 있다.</p>
<h3 id="데드락-발생-조건">데드락 발생 조건</h3>
<ul>
<li>상호 배제 (Mutual Exclusion) - 한 자원에 대해 여러 쓰레드 동시 접근 불가</li>
<li>점유와 대기(Hold and Wait) - 자원을 가지고 있는 상태에서 다른 쓰레드가 사용하고 있는 자원 반납을 기다리는 것</li>
<li>비선점(Non Preemptive) - 다른 쓰레드의 자원을 실행 중간에 강제로 가져올 수 없음</li>
<li>환형대기(Circle Wait) - 각 쓰레드가 순환적으로 다음 쓰레드가 요구하는 자원을 가지고 있는 것</li>
</ul>
<blockquote>
<p>위 4가지 조건을 모두 충족할 경우 데드락이 발생하게 된다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스터디 - 9주차]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-9%EC%A3%BC%EC%B0%A8-x4jhawbq</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-9%EC%A3%BC%EC%B0%A8-x4jhawbq</guid>
            <pubDate>Sat, 16 Apr 2022 07:03:00 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p>자바의 예외 처리에 대해 학습하세요.</p>
<h1 id="학습할-것-필수">학습할 것 (필수)</h1>
<ul>
<li>자바에서 예외 처리 방법 (try, catch, throw, throws, finally)</li>
<li>자바가 제공하는 예외 계층 구조</li>
<li>Exception과 Error의 차이는?</li>
<li>RuntimeException과 RE가 아닌 것의 차이는?</li>
<li>커스텀한 예외 만드는 방법</li>
</ul>
<h2 id="자바에서-예외-처리-방법-try-catch--throw-throws-finally">자바에서 예외 처리 방법 (try, catch , throw, throws, finally)</h2>
<h3 id="try---catch-finally-문">try - catch-finally 문</h3>
<blockquote>
<p>자바에서는 프로그램이 실행되는 도중 발생하는 예외를 처리하기 위해 try / catch / finally 문을 사용한다.</p>
</blockquote>
<pre><code class="language-java">try {
    // 예외 발생시 throw 하고자 하는 코드
    catch(XXXException e1) {
        // e1 예외가 발생할 경우에 실행될 코드
    }
    catch(XXXException e2) {
        // e2 예외가 발생할 경우에 실행될 코드
    }
    ...

    finally {
        // 예외 발생과 상관없이 무조건 실행할 코드
    }
}</code></pre>
<ul>
<li><code>try</code> 블럭에는 여러 개의 <code>catch</code> 블록이 올 수 있으며, 발생하는 예외의 종류와 일치하는 단 한 개의 <code>catch</code> 블록만 수행된다.</li>
<li><code>try</code>블록에서 예외가 발생하면 <code>throw</code> 키워드를 사용하여 예외가 던져진다. 던져진 예외는 catch 블록에 의해 잡힐 수 있다. </li>
<li><code>catch</code> 블록에서는 발생한 예외 코드나 예외 객체를 전달 받아 그 처리를 담당한다. 다중 catch문으로 작성할 때의 주의할 점은 범위가 낮은 예외들부터 작성을 해야 한다는 것이다(<strong>예외는 상속 관계를 가지고 있기 때문이다.</strong>).</li>
<li><code>finally</code> 키워드는 예외와 별개로 반드시 실행시키고 싶은 소스들을 처리하고 싶을 때 사용한다. </li>
</ul>
<h3 id="try-catch-try-catch-문">try-catch-try-catch 문</h3>
<pre><code class="language-java">try {
    // 예외 발생시 trhow 하고자 하는 코드
} catch(XXXException e1) {
    // 예외 발생시 실행될 코드
    try {
        // 예외 발생시 throw 하고자 하는 코드
        catch(XXXException e2) {
            // 예외 발생시 실행될 코드
        }
    }
}</code></pre>
<p>catch 블록 내에 다시 try-catch문을 사용할 수 있는데 이 때 주의할 점은 참조되는 변수의 이름이 중복되면 안된다.</p>
<h3 id="catch---printstacktrace--getmessage">catch - <code>printStackTrace()</code> , <code>getMessage()</code></h3>
<blockquote>
<p> 예외가 발생하였을 때 해당 인스턴스에는 발생한 예외의 정보들이 담겨져 있다. 이에 대해 알고 싶을 때  <code>printStackTrace()</code> , <code>getMessage()</code>를 사용하여 <strong>예외의 정보를 얻는다.</strong></p>
</blockquote>
<h4 id="printstacktrace"><code>printStackTrace()</code></h4>
<ul>
<li>에러의 발생 근원지를 찾아서 단계별로 에러를 출력한다.</li>
<li>매우 자세하게 나오며, 개발할 때에만 사용해야 한다. 운영할 시스템에 적용하면 엄청난 양의 로그가 쌓인다. 적재적소에만 사용해야 한다</li>
</ul>
<h4 id="getmessage"><code>getMessage()</code></h4>
<ul>
<li>에러의 원인을 간단하게 출력한다.</li>
</ul>
<h3 id="throw">throw</h3>
<p>throw 키워드를 사용하면 고의로 예외를 발생시킬 수 있다.</p>
<pre><code class="language-java">class User {
   int age; 
}
class UserException {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
         User user = new User();

        System.out.println(&quot;나이를 입력하세요&quot;);
        user.age = sc.nextInt();

        try {
            if(user.age &lt; 0 || user.age &gt; 120) {
                throw new IllegalArgumentException(&quot;정확한 값을 입력해주세요&quot;);
            }
        } catch(IllegalException e) {
            System.out.println(e.getMessage());
        }
    }
}</code></pre>
<blockquote>
<p>IllegalArgumentException 인스턴스를 생성하여 throw로 예외를 발생시켰다.</p>
</blockquote>
<h3 id="throws">throws</h3>
<p>throws 키워드를 통해 메서드에 예외를 선언할 수도 있다. </p>
<pre><code class="language-java">public void 예외_던져줭() throws Exception {
    // 메서드 내용
}</code></pre>
<p><code>예외_던져줭()</code> 메서드에 예외를 선언해두고 해당 메서드를 호출할 때 어떤 예외를 처리해야 되는지 알려준다.</p>
<p><code>,</code>를 기준으로 여러개 선언도 가능하다.</p>
<pre><code class="language-java">public void 예외_많이_던져줭 throws Exception1, Exception2, Exception3 {
    // 메서드 내용
}</code></pre>
<p>throws 자체는 예외의 처리와는 관계가 없다. 메서드를 호출한 호출자가 이 예외를 처리해야 하기 떄문이다. </p>
<h3 id="try-with-resources">try-with-resources</h3>
<p>1.7부터 자원의 해제를 자동으로 해주는 <code>try-with-resources</code> 구문이 추가되었다. <code>resources</code>는  DataSource, Connection, FileInpuStream 등 사용이 끝날 때 닫아줘야 하는 자원을 말한다.</p>
<p><code>try-with-resources</code>는 try 키워드 <code>{}</code>이전에 <code>()</code>를 열어 소괄호에 사용하게 될 자원을 입력하여 사용한다. 모든 자원이 <code>()</code>에 들어갈 수 있는 것은 아니고 <code>AutoCloseable</code> 인터페이스를 구현한 객체만 사용 가능하다. </p>
<ul>
<li>1.7 이전</li>
</ul>
<pre><code class="language-java"> public static void main(String[] args) {

        Connection con = null;
        PreparedStatement pst = null;
        ResultSet rs = null;

        MysqlDataSource ds = getMySQLDataSource();

        try {

            con = ds.getConnection();
            pst = con.prepareStatement(&quot;SELECT VERSION()&quot;);
            rs = pst.executeQuery();

            if (rs.next()) {

                String version = rs.getString(1);
                System.out.println(version);
            }
        } catch (IOException e) {
            ...
        } catch (SQLException e) {
            ...
        } finally {

            if (rs != null) {
                rs.close();
            }

            if (pst != null) {
                pst.close();
            }

            if (con != null) {
                con.close();
            }
        }
    }
}</code></pre>
<ul>
<li>1.7 이후</li>
</ul>
<pre><code class="language-java"> public static void main(String[] args) {

        Connection con = null;
        PreparedStatement pst = null;
        ResultSet rs = null;

        MysqlDataSource ds = getMySQLDataSource();

        try(con = ds.getConnection();
            pst = con.prepareStatement(&quot;SELECT VERSION()&quot;);
            rs = pst.executeQuery()) {

            if (rs.next()) {

                String version = rs.getString(1);
                System.out.println(version);
            }

        } catch (IOException e) {
            ...
        } catch (SQLException e) {
            ...
        }
    }
}</code></pre>
<h2 id="자바에서-제공하는-예외-계층-구조">자바에서 제공하는 예외 계층 구조</h2>
<p><img src="https://lh5.googleusercontent.com/WqqNoyFEkZXfmZBBQjgIutY72_BUV6_By_BAe7Ih9u36HfelS3nTWQEYtdRUkQS32Tuhg9P9CUXo-jgvOpkO84vLm2viI4Od0BNustwONdMm7DKZnKC6kyVHyRJbsESLIPV4uBU" alt="img"></p>
<blockquote>
<p>출처 : <a href="https://www.javamadesoeasy.com/2015/05/exception-handling-exception-hierarchy.html">https://www.javamadesoeasy.com/2015/05/exception-handling-exception-hierarchy.html</a></p>
</blockquote>
<h2 id="runtimeexception과-re가-아닌-것의-차이는">RuntimeException과 RE가 아닌 것의 차이는?</h2>
<ul>
<li><h4 id="checked-exception"><code>Checked Exception</code></h4>
<p>컴파일 시점에서 확인될 수 있는 예외. 만약 코드 내에서 이 예외를 발생시킨다면 해당 예외는 반드시 처리되거나 해당 코드가 속한 메서드 선언부에 예외를 선언해줘야 한다.</p>
<p>치명적인 예외 상황을 발생시키기 때문에 try-catch문으로 감싸줘야 한다. 예외가 발생할 가능성이 있는 구문을 예외처리하지 않았다면 컴파일 에러가 발생한다.</p>
</li>
</ul>
<ul>
<li><h4 id="unchecked-exceptionruntime-exception"><code>Unchecked Exception(Runtime Exception)</code></h4>
<p>컴파일 단계에서 확인되지 않는 예외이다. Java에서는 Runtime Exception과 그 하위 클래스 ,그리고 Error와 그 하위 클래스가 이에 속한다. 이 예외들은 개발자가 알아서 처리 해야한다.</p>
<p>try / catch 보다는 해당 예외가 발생하지 않도록 주의하여 코드를 짜는 것이 좋다.</p>
</li>
</ul>
<h2 id="예외와-에러의-차이">예외와 에러의 차이</h2>
<blockquote>
<p>예외와 에러의 가장 큰 차이는 System Level의 문제와 Application Level의 문제의 차이다.</p>
</blockquote>
<h3 id="에러란">에러란</h3>
<blockquote>
<p>프로그램을 사용하다가 프로그램이 비정상적으로 종료될 때 이러한 결과를 초래하는 원인</p>
</blockquote>
<p>Error는 빌트인 클래스 Throwable의 서브 클래스이다. 에러는 근본적으로 JVM에서 생성되거나 나타나는 예외이다. 오류는 일반적으로 프로그램에서 처리할 수 없는 치명적인 오류로 인해 발생한다. 프로그램에서 처리할 수 없다는 뜻은 사용자가 제어할 수 없다는 뜻이다.</p>
<p>컴파일러는 발생에 대한 지식이 없으므로 오류는 항상 검사되지 않은 유형이다. 오류는 항상 런타임 환경에서 발생한다. 예를 들면 스택 오버플로우, 메모리 부족, 시스템 충돌 등이 해당한다. 이러한 종류의 오류는 시스템으로 인한 것이다. 오류가 발생하면 프로그램이 비정상적으로 종료된다.</p>
<p>따라서 에러는 기본적으로 <code>Unchecked Exception</code>이다.</p>
<h3 id="예외란">예외란</h3>
<blockquote>
<p>발생하더라도 프로그래머가 미리 적절한 코드를 사용해서 프로그램이 비정상적으로 종료되지 않도록 핸들링 해줄 수 있다.</p>
</blockquote>
<p>예외는 내장 클래스 Throwable의 하위 클래스이기도 하다. 예외는 런타임 환경에서 발생하는 예외적인 조건이다. 예외의 대부분은 프로그램의 코드로 인해 발생하지만 예외는 복구할 수 있으므로 프로그램 자체에서 예외를 처리할 수 있다. 예외는 3개의 키워드 <code>try</code>, <code>catch</code> , <code>throw</code> 를 사용하여 처리한다.</p>
<h3 id="예외와-에러의-차이는-">예외와 에러의 차이는 ?</h3>
<table>
<thead>
<tr>
<th>비교</th>
<th>오류</th>
<th>예외</th>
</tr>
</thead>
<tbody><tr>
<td>기본</td>
<td>시스템 자원이 부족하여 오류가 발생함</td>
<td>코드로 인해 예외가 발생함</td>
</tr>
<tr>
<td>회복</td>
<td>오류는 복구할 수 없다.</td>
<td>예외는 복구할 수 있다.</td>
</tr>
<tr>
<td>키워드</td>
<td>프로그램 코드에서 오류를 처리할 수 있는 방법은 없다.</td>
<td>예외는 3개의 키워드 <code>try</code>, <code>catch</code>, <code>throw</code>를 사용하여 처리된다.</td>
</tr>
<tr>
<td>결과</td>
<td>오류가 감지되면 프로그램이 비정상적으로 종료된다.</td>
<td>예외가 감지되면 throw 및 catch 키워드에 따라 예외가 발생한다.</td>
</tr>
<tr>
<td>유형</td>
<td>오류는 검사되지 않은 유형으로 분류된다.</td>
<td>예외는 체크되거나 확인되지 않은 유형으로 분류 된다.</td>
</tr>
<tr>
<td>패키지</td>
<td>Java에서 오류는 <code>java.lang.Error</code>패키지로 정의된다.</td>
<td>Java에서 예외는 <code>java.lang.Exception</code> 패키지로 정의된다.</td>
</tr>
</tbody></table>
<blockquote>
<ul>
<li>시스템 자원이 부족한 경우에만 오류가 발생하지만 코드에 문제가 있는 경우 예외가 발생한다.</li>
<li>오류는 복구할 수 없지만 예외를 처리할 코드를 준비하면 예외를 복구할 수 있다.</li>
<li>오류는 결코 처리할 수 없지만 예외를 throw하는 코드가 try 및 catch 블록 내에 작성된 경우 예외가 코드에 의해 처리될 수 있다.</li>
<li>오류가 발생하면 프로그램이 비정상적으로 종료된다. 반면에 예외가 발생하면 프로그램은 예외를 throw하고 try &amp; catch 블록을 사용하여 처리된다.</li>
<li>오류는 검사되지 않은 유형이다. 오류는 컴파일러에 대한 지식이 아니지만 예외는 검사된 것과 검사되지 않은 것으로 분류된다.</li>
</ul>
</blockquote>
<h2 id="커스텀한-예외-만드는-방법">커스텀한 예외 만드는 방법</h2>
<blockquote>
<ul>
<li>일반 예외로 선언할 경우 Exception 을 상속받아 구현한다.</li>
<li>실행 예외로 선언할 경우 RuntimeException을 상속받아 구현한다.</li>
</ul>
</blockquote>
<ul>
<li>사용자 정의 예외 클래스는 컴파일러가 체크하는 일반 예외(<code>Checked Exception</code>)와 컴파일러가 체크하지 않는(<code>Unchecked Exception</code>)로 선언할 수 있다.</li>
<li>커스텀으로 만든 예외는 Exception으로 끝나는 것을 권장한다.</li>
<li>생성자는 기본 생성자, 예외 발생 원인을 전달하기 위해 String 타입의 매개변수를 갖는 생성자 두개를 선언하는 것이 일반적이다. 예외 메세지는 catch의 <code>{}</code> 블록의 예외 처리에서 이용하기 위함이다.</li>
</ul>
<h3 id="커스텀-예외-생성">커스텀 예외 생성</h3>
<pre><code class="language-java">public class CustomException extends RuntimeException {
    // 1. 매개 변수가 없는 생성자
    public CustomException() {

    }

    // 2. 예외 발생 원인을 전달하기 위해 String 타입의 매개변수를 갖는 생성자
    public CustomException(String message) {
        super(message); // RuntimeException 클래스의 생성자를 호출한다.
    }
}</code></pre>
<h3 id="커스텀-예외-사용">커스텀 예외 사용</h3>
<pre><code class="language-java">public static void main(String[] args) {
    try {
        test();
    } catch(CustomException e) {
        System.out.println(&quot;아ㅋㅋ 예외라고&quot;);
        System.out.println(e.getMessage());
    }
}

public static void test() throws CustomException {
    throw new CustomException(&quot;ㅋㅋ아 예외라고&quot;);
}</code></pre>
<h1 id="출처">출처</h1>
<blockquote>
<ul>
<li><a href="https://ko.gadget-info.com/difference-between-error">https://ko.gadget-info.com/difference-between-error</a></li>
<li><a href="https://wisdom-and-record.tistory.com/46">https://wisdom-and-record.tistory.com/46</a></li>
<li><a href="https://veneas.tistory.com/entry/Java-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%98%88%EC%99%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0Custom-Exception">https://veneas.tistory.com/entry/Java-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%98%88%EC%99%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0Custom-Exception</a></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[NIO]]></title>
            <link>https://velog.io/@seunghan-baek/NIO</link>
            <guid>https://velog.io/@seunghan-baek/NIO</guid>
            <pubDate>Thu, 31 Mar 2022 01:27:36 GMT</pubDate>
            <description><![CDATA[<h2 id="nio">NIO</h2>
<blockquote>
<p>자바 4부터 새로운 입출력이라는 뜻에서 <code>java.nio</code>패키지가 포함되었다.</p>
<p>자바 7로 버전업하면서 자바 IO와 NIO 사이의 일관성 없는 클래스 설계를 바로잡고 비동기 채널 등의 네트워크 지원을 대폭 강화한 NIO.2 API가 추가되었다. NIO.2는 java.nio2 패키지로 제공되지 않고 기존 java.nio의 하위 패키지 (<code>java.nio.channels</code> , <code>java.nio.charset</code>, <code>java.nio.file</code>)에 통합되어 있다.</p>
</blockquote>
<h3 id="nio에서-제공하는-패키지-간략-소개">NIO에서 제공하는 패키지 간략 소개</h3>
<table>
<thead>
<tr>
<th>NIO 패키지</th>
<th>포함되어 있는 내용</th>
</tr>
</thead>
<tbody><tr>
<td>java.nio</td>
<td>다양한 버퍼 클래스</td>
</tr>
<tr>
<td>java.nio.channels</td>
<td>파일 채널, TCP 채널, UDP 채널 등의 클래스</td>
</tr>
<tr>
<td>java.nio.channels.spi</td>
<td>java.nio.channels 패키지를 위한 서비스 제공자 클래스</td>
</tr>
<tr>
<td>java.nio.charset</td>
<td>문자셋, 인코더, 디코더 API</td>
</tr>
<tr>
<td>java.nio.charset.spi</td>
<td>java.nio.charset 패키지를 위한 서비스 제공자 클래스</td>
</tr>
<tr>
<td>java.nio.file</td>
<td>파일 및 파일 시스템에 접근하기 위한 클래스</td>
</tr>
<tr>
<td>java.nio.file.attribute</td>
<td>파일 및 파일 시스템의 속성에 접근하기 위한 클래스</td>
</tr>
<tr>
<td>java.nio.file.spi</td>
<td>java.nio.file 패키지를 위한 서비스 제공자 클래스</td>
</tr>
</tbody></table>
<h2 id="io와-nio의-차이점">IO와 NIO의 차이점</h2>
<p>IO와 NIO는 데이터를 입출력한다는 목적은 동일하지만, 방식에 있어서 크게 차이가 난다. 아래 표는 IO와 NIO의 차이점을 정리한 것이다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>IO</th>
<th>NIO</th>
</tr>
</thead>
<tbody><tr>
<td>입출력 방식</td>
<td>스트림 방식</td>
<td>채널 방식</td>
</tr>
<tr>
<td>버퍼 방식</td>
<td>넌버퍼(non-buffer)</td>
<td>버퍼(buffer)</td>
</tr>
<tr>
<td>비동기 방식</td>
<td>지원 안 함</td>
<td>지원</td>
</tr>
<tr>
<td>블로킹 / 넌블로킹 방식</td>
<td>블로킹 방식만 지원</td>
<td>블로킹 / 넌블로킹 방식 모두 지원</td>
</tr>
</tbody></table>
<h3 id="스트림-vs-채널">스트림 vs 채널</h3>
<p><strong>IO는 스트림 기반이다.</strong> 스트림은 입력 스트림과 출력 스트림으로 구분되어 있기 때문에 데이터를 읽기 위해서는 입력 스트림을 생성해야 하고, 데이터를 출력하기 위해서는 출력 스트림을 생성해야 한다.  </p>
<p>예를 들어 하나의 파일을 읽고 저장하는 작업을 모두해야 한다고 했을 때 <code>fileInputStream</code>,<code>FileOutputStream</code>을 별도로 생성해야 한다.  </p>
<p><strong>NIO는 채널 기반이다</strong>. 채널은 스트림과 달리 양방향으로 입력과 출력이 가능하다. 그렇기 때뭔에 입력과 출력을 위한 별도의 채널을 만들 필요가 없다.  </p>
<p>예를 들어 하나의 파일을 읽고 저장하는 작업을 모두해야 한다고 했을 때 <code>FileChannel</code>만 있으면 된다.</p>
<h3 id="넌버퍼-vs-버퍼">넌버퍼 vs 버퍼</h3>
<ul>
<li><p><strong>IO에서는 출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를  읽는다.</strong> 이런 시스템은 대체로 느리다.  이것보다는 버퍼를 사용해서 복수 개의 바이트를 한꺼번에 입력받고 출력하는 것이 빠른 성능을 낸다. 그래서 IO는 버퍼를 제공해주는 보조 스트림은 <code>BufferedInputStream</code>, <code>BufferedOutputStream</code>을 연결해서 사용하기도 한다.</p>
</li>
<li><p><strong>IO는 스트림에서 읽은 데이터를 즉시 처리한다.</strong> 그렇기 때문에 스트림에서 입력된 전체 데이터를 별도로 저장하지 않으면, 입력된 데이터의 위치를 이동해가면서 자유롭게 이용할 수 없다.</p>
</li>
</ul>
<ul>
<li><p><strong>NIO는 기본적으로 버퍼를 사용해서 입출력을 하기 때문에 IO보다는 입출력 성능이 좋다.</strong> 채널은 버퍼에 저장된 데이터를 출력하고 입력된 데이터를 버퍼에 저장한다.</p>
</li>
<li><p><strong>NIO는 읽은 데이터를 무조건 버퍼에 저장하기 때문에</strong> 버퍼 내에서 데이터의 위치를  이동해 가면서 필요한 부분만 읽고 쓸 수 있다.</p>
</li>
</ul>
<h3 id="블로킹-vs-넌블로킹">블로킹 vs 넌블로킹</h3>
<ul>
<li><strong>IO는 블로킹 된다.</strong> 입력 스트림의 read() 메소드를 호출하면 데이터가 입력되기 전까지 스레드는 블로킹 된다. 마찬가지로 출력 스트림의 write()메소드를 호출하면 데이터가 출력되기 전까지 스레드는 블로킹된다. IO 스레드가 블로킹되면 다른 일을 할 수 없고, 블로킹을 빠져 나오기 위해 인터럽트도 할 수 없다. 빠져나오는 유일한 방법은 스트림을 닫는 것이다.</li>
</ul>
<ul>
<li><strong>NIO는 블로킹과 넌블로킹 특징을 모두 가지고 있다.</strong> IO와 차이점은 NIO 블로킹은 스레드를 인터럽트함으로써 빠져나올 수가 있다는 것이다. 블로킹의 반대 개념이 넌블로킹인데, 입출력 작업 시 스레드가 블로킹되지 않는 것을 말한다. NOI의 넌블로킹은 입출력 작업 준비가 완료된 채널만 선택해서 작업 스레드가 처리하기 때문에 작업 스레드가 블로킹되지 않는다. 여기서 작업 준비가 완료되었다는 뜻은 지금 바로 읽고 쓸 수 있는 상태를 말한다. <strong>NIO 넌블로킹의 핵심 객체는 멀티플렉서인 셀렉터(Selector)이다.</strong> 셀렉터는 복수 개의 채널 중에서 준비 완료된 채널을 선택하는 방법을 제공해준다.</li>
</ul>
<h3 id="io와-nio의-선택">IO와 NIO의 선택</h3>
<ul>
<li><strong>NIO 선택</strong>
불특정 다수의 클라이언트 연결 또는 멀티 파일들을 넌블로킹이나 비동기로 처리할 수 있기 때문에  과도한 스레드 생성을 피하고 스레드를 효과적으로 재사용한다는 점이 있다. 또한 운영체제의 버퍼를 이용한 입출력이 가능하기 때문에 입출력 성능이 향상된다.<br>연결 클라이언트 수가 많고, 하나의 입출력 처리 작업이 오래 걸리지 않는 경우에 사용하는 것이 좋다. </li>
<li><strong>IO 선택</strong>
스레드에서 입출력 처리가 오래 걸린다면 대기하는 작업의 수가 늘어나기 때문에 제한된 스레드로 처리하는 것이 오히려 불편할 수 있다. 대용량 데이터를 처리할 경우에는 IO가 더 유리한데 NIO 버퍼의 할당 크기도 문제가 되고, 모든 입출력 작업에 버퍼를 무조건 사용하므로 받은 즉시 처리하는 IO보다 좀 더 복잡하다. 연결 클라이언트 수가 적고, 전송되는 데이터가 대용량 &amp;&amp; 순차적 처리 필요성이 있을 경우 IO로 구현하는 것이 좋다.</li>
</ul>
<h2 id="파일과-디렉토리">파일과 디렉토리</h2>
<blockquote>
<p>IO는 파일의 속성 정보를 읽기 위해 File 클래스만 제공하지만, NIO는 좀 더 다양한 파일의 속성 정보를 제공해주는 클래스와 인터페이스를 <code>java.nio.file</code>, <code>java.nio.attribute</code> 패키지에서 제공하고 있다.</p>
</blockquote>
<h3 id="경로-정의-path">경로 정의 (Path)</h3>
<p><strong>NIO에서 제일 먼저 살펴봐야 할 API는 <code>java.nio.file.Path</code>인터페이스다.</strong> Path는 IO의 <code>java.io.File</code> 클래스에 대응되는 NIO 인터페이스이다. NIO의 API에서 파일의 경로를 지정하기 위해 Path를 사용하기 때문에 Path 사용 방법을 잘 익혀두어야 한다.  Path 구현 객체를 얻기 위해서는 <code>java.nio.file.Paths</code> 클래스의 정적 메소드인 get() 메소드를 호출하면 된다.</p>
<pre><code class="language-java">Path path = Paths.get(String first, String ... more);
Path path = Paths.get(URI uri);</code></pre>
<ul>
<li>get() : 매개값은 파일의 경로이다. 문자열로 지정할 수도 있고, URI 객체를 지정할 수도 있다. 문자열로 지정할 경우 전체 경로를 한꺼번에 저장해도 좋고, 상위와 하위 디렉토리를 따로 나열해도 된다. </li>
</ul>
<pre><code class="language-java">Path path = Paths.get(&quot;C:/Temp/dir/file.txt&quot;);
Path path = Paths.get(&quot;C:/Temp/dir/&quot;, &quot;file.txt&quot;);
Path path = Paths.get(&quot;C:&quot;, &quot;Temp/dir&quot;, &quot;file.txt&quot;);</code></pre>
<ul>
<li>Path 인터페이스 파일 경로 정보 제공 메소드</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드 (매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>int</td>
<td>compareTo(Path other)</td>
<td>파일 경로가 동일하면 0을 리턴,<br />상위 경로면 음수,<br />하위 경로면 양수를 리턴,<br />음수와 양수 값의 차이나는 문자열의 수</td>
</tr>
<tr>
<td>Path</td>
<td>getFileName()</td>
<td>부모 경로를 제외한 파일 또는 디렉토리 이름만 가진 Path 리턴</td>
</tr>
<tr>
<td>FileSystem</td>
<td>getFileSystem()</td>
<td>FileSystem 객체 리턴</td>
</tr>
<tr>
<td>Path</td>
<td>getName(int index)</td>
<td>C:/Temp/dir/file.txt일 경우<br />index가 0이면 &quot;Temp&quot;의 Path 객체 리턴<br />index가 1이면 &quot;dir&quot;의 Path 객체 리턴<br />index가 2이면 &quot;file.txt&quot;의 Path 객체 리턴</td>
</tr>
<tr>
<td>int</td>
<td>getNameCount()</td>
<td>중첩 경로 수, C:/Temp/dir/file.txt일 경우 3을 리턴</td>
</tr>
<tr>
<td>Path</td>
<td>getParent()</td>
<td>바로 위 부모 폴더의 Path 리턴</td>
</tr>
<tr>
<td>Path</td>
<td>getRoot()</td>
<td>루트 디렉토리의 Path 리턴</td>
</tr>
<tr>
<td>Iterator<Path></td>
<td>iterator()</td>
<td>경로에 있는 모든 디렉토리와 파일을 Path객체로 생성하고 반복자를 리턴</td>
</tr>
<tr>
<td>Path</td>
<td>normalize()</td>
<td>상대 경로로 표기할 때 불필요한 요소를 제거</td>
</tr>
<tr>
<td>WatchKey</td>
<td>register(...)</td>
<td>WatchService를 등록</td>
</tr>
<tr>
<td>File</td>
<td>toFile()</td>
<td>java.io.File 객체로 리턴</td>
</tr>
<tr>
<td>String</td>
<td>toString()</td>
<td>파일 경로를 문자열로 리턴</td>
</tr>
<tr>
<td>URI</td>
<td>toUri()</td>
<td>파일 경로를 URI 객체로 리턴</td>
</tr>
</tbody></table>
<h3 id="파일-시스템-정보filesystem">파일 시스템 정보(FileSystem)</h3>
<p>운영체제의 파일 시스템은 FileSystem 인터페이스를 통해서 접근할 수 있다. FileSystem 구현 객체는 FileSystem의 정적 메소드인 getDefault()를 통해 얻을 수 있다.</p>
<pre><code class="language-java">FileSystem fileSystem = FileSystems.getDefault();</code></pre>
<ul>
<li>제공 메소드</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Iterable<FileStore></td>
<td>getFileStores()</td>
<td>드라이버 정보를 가진 FileStore 객체들을 리턴</td>
</tr>
<tr>
<td>Iterable<Path></td>
<td>getRootDirectories()</td>
<td>루트 디렉토리 정보를 가진 Path 객체들을 리턴</td>
</tr>
<tr>
<td>String</td>
<td>getSeparator()</td>
<td>디렉토리 구분자 리턴</td>
</tr>
</tbody></table>
<p>FileStore는 드라이버를 표현한 객체로 다음과 같은 메소드를 제공한다.</p>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드 (매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>long</td>
<td>getTotlaSpace()</td>
<td>드라이버 전체 공간 크리(단위: 바이트) 리턴</td>
</tr>
<tr>
<td>long</td>
<td>getUnallocatedSpace()</td>
<td>할당되지 않은 공간 크기(단위 : 바이트) 리턴</td>
</tr>
<tr>
<td>long</td>
<td>getUsableSpace()</td>
<td>사용 가능한 공간 크기, getUnallocatedSpace()와 동일한 값</td>
</tr>
<tr>
<td>boolean</td>
<td>isReadOnly()</td>
<td>읽기 전용 여부</td>
</tr>
<tr>
<td>String</td>
<td>name()</td>
<td>드라이버명 리턴</td>
</tr>
<tr>
<td>String</td>
<td>type()</td>
<td>파일 시스템 종류</td>
</tr>
</tbody></table>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;

public class FileSystemExample {
    public static void main(String[] args) throws Exception {
        FileSystem fileSystem = FileSystems.getDefault();
        for (FileStore store : fileSystem.getFileStores()) {
            System.out.println(&quot;드라이버 명 : &quot; + store.name());
            System.out.println(&quot;파일시스템 : &quot; + store.type());
            System.out.println(&quot;전체 공간 : &quot; + store.getTotalSpace() + &quot;바이트&quot;);
            System.out.println(&quot;사용 중인 공간 : &quot; + (store.getTotalSpace() - store.getUnallocatedSpace()) + &quot;바이트&quot;);
            System.out.println(&quot;사용 가능한 공간 : &quot; + store.getUsableSpace() + &quot;바이트&quot;);
            System.out.println();
        }

        System.out.println(&quot;파일 구분자 : &quot; + fileSystem.getSeparator());
        System.out.println();

        for (Path path : fileSystem.getRootDirectories()) {
            System.out.println(path.toString());
        }
    }
}</code></pre>
<h3 id="파일-속성-읽기-및-파일-디렉토리-생성삭제">파일 속성 읽기 및 파일, 디렉토리 생성/삭제</h3>
<blockquote>
<p> <code>java.nio.fiole.Files</code> 클래스는 파일과 디렉토리의 생성 및 삭제, 그리고 이들의 속성을 읽는 메소드를 제공한다. 여기서 속성이란 파일이나 디렉토리가 숨김인지, 디렉토리인지, 크기가 어떻게 되는지, 소유자가 누구인지에 대한 정보를 말한다.</p>
</blockquote>
<ul>
<li>Files 정적 메소드</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>long || Path</td>
<td>copy(..)</td>
<td>복사</td>
</tr>
<tr>
<td>Path</td>
<td>createDirectories(...)</td>
<td>모든 부모 디렉토리 생성</td>
</tr>
<tr>
<td>Path</td>
<td>createDirectory(...)</td>
<td>경로의 마지막 디렉토리만 생성</td>
</tr>
<tr>
<td>Path</td>
<td>createFile(...)</td>
<td>파일 생성</td>
</tr>
<tr>
<td>void</td>
<td>delete(...)</td>
<td>삭제</td>
</tr>
<tr>
<td>boolean</td>
<td>deleteifExists(...)</td>
<td>존재한다면 삭제</td>
</tr>
<tr>
<td>boolean</td>
<td>exists(...)</td>
<td>존재 여부</td>
</tr>
<tr>
<td>FilesStore</td>
<td>getFileStore(...)</td>
<td>파일이 위치한 FileStore(드라이브) 리턴</td>
</tr>
<tr>
<td>FileTime</td>
<td>getLastModifiedTime(...)</td>
<td>마지막 수정 시간을 리턴</td>
</tr>
<tr>
<td>UserPrincipal</td>
<td>getOwner(...)</td>
<td>소유자 정보를 리턴</td>
</tr>
<tr>
<td>boolean</td>
<td>isDirectory(...)</td>
<td>디렉토리인지 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>isExecutable(...)</td>
<td>실행 가능 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>isHidden(...)</td>
<td>숨김 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>isReadable(...)</td>
<td>읽기 가능 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>isRegularFile(...)</td>
<td>일반 파일인지 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>isSameFile(...)</td>
<td>같은 파일인지 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>isWritable(...)</td>
<td>쓰기 가능 여부</td>
</tr>
<tr>
<td>Path</td>
<td>move(...)</td>
<td>파일 이동</td>
</tr>
<tr>
<td>BufferedReader</td>
<td>newBufferedReader(...)</td>
<td>텍스트 파일을 읽는 BufferedReader 리턴</td>
</tr>
<tr>
<td>BufferedWriter</td>
<td>newBufferedWrtier(...)</td>
<td>텍스트 파일에 쓰는 BufferedWriter 리턴</td>
</tr>
<tr>
<td>SeekableByteChannel</td>
<td>newByteChannel(...)</td>
<td>디렉토리의 모든 내용을 스트림으로 리턴</td>
</tr>
<tr>
<td>DirectoryStream<Path></td>
<td>newDirectoryStream...)</td>
<td>디렉토리의 모든 내용을 스트림으로 리턴</td>
</tr>
<tr>
<td>InputStream</td>
<td>newInputStream(...)</td>
<td>파일의 InputStream 리턴</td>
</tr>
<tr>
<td>OutputStream</td>
<td>newOutputStream(...)</td>
<td>파일의 OutputStream 리턴</td>
</tr>
<tr>
<td>boolean</td>
<td>notExists(...)</td>
<td>존재하지 않는지 여부</td>
</tr>
<tr>
<td>String</td>
<td>probeContentType(...)</td>
<td>파일의 MIME 타입을 리턴</td>
</tr>
<tr>
<td>byte[]</td>
<td>readAllBytes(...)</td>
<td>파일의 모든 바이트를 읽고 배열로 리턴</td>
</tr>
<tr>
<td>List<String></td>
<td>readAllLines(...)</td>
<td>텍스트 파일의 모든 라인을 읽고 리턴</td>
</tr>
<tr>
<td>long</td>
<td>size(...)</td>
<td>파일의 크기 리턴</td>
</tr>
<tr>
<td>Path</td>
<td>write(...)</td>
<td>파일에 바이트나 문자열을 저장</td>
</tr>
</tbody></table>
<ul>
<li><p>파일의 속성을 읽고 출력하는 예제</p>
<pre><code class="language-java">public class FileExample {
  public static void main(String[] args) throws Exception {
    Path path = Paths.get(&quot;src/sec02/exam03_file_directory/FileExample.java&quot;);
    System.out.println(&quot;디렉토리 여부 : &quot; + Files.isDirectory(path));
    System.out.println(&quot;파일 여부 : &quot; + Files.isRegularFile(path));
    System.out.println(&quot;마지막 수정 시간 : &quot; + Files.getLastModifiedTime(path));
    System.out.println(&quot;파일 크기 : &quot; + Files.size(path));
    System.out.println(&quot;소유자 : &quot; + Files.getOwner(path).getName());
    System.out.println(&quot;숨김 파일 여부 : &quot; + Files.isHidden(path));
    System.out.println(&quot;읽기 가능 여부 : &quot; + Files.isReadable(path));
    System.out.println(&quot;쓰기 가능 여부 : &quot; + Files.isWritable(path));
  }
}</code></pre>
</li>
</ul>
<h3 id="와치-서비스-watchservice">와치 서비스 (WatchService)</h3>
<p>자바 7에서 처음 소개된 것으로 디렉토리 내부에서 파일 생성, 삭제, 수정 등의 내용 변화를 감시하는데 사용된다. 흔하게 볼 수 있는 와치 서비스의 적용 예는 에디터에서 파일을 편집하고 있을 때, 에디터 바깥에서 파일 내용을 수정하게 되면 파일 내용이 변경되었으니 파일을 다시 불러올 것인지를 묻는 대화상자를 띄우는 것이다. <strong>와치 서비스는 일반적으로 파일 변경 통지 메커니즘으로 알려져 있다.</strong> WatchService를 생성하려면 다음과 같이 FileSystem의 newWatchService()를 호출한다.</p>
<pre><code class="language-java">WatchService watchService = FileSystem.getDefault().newWatchService();</code></pre>
<p>생성했다면 감시가 필요한 디렉토리의 Path 객체에서 register() 메소드로 등록하면 된다. 이때 어떤 변화(생성 ,삭제 ,수정)를 감시할 것인지를 StandardWatchEventKinds 상수로 지정할 수 있다. </p>
<pre><code class="language-java">path.register(watchService.StandardWatchEventKinds.ENTRY_CREATE,
                                       StandardWatchEventKinds.ENTRY_MODIFY,
                                          StandarsWatchEventKinds.ENTRY_DELETE);</code></pre>
<p>Path에 WatchService를 등록한 순간부터 디렉토리 내부에서 변경이 발생되면 와치 이벤트가 발생하고 WatchService는 해당이벤트 정보를 가진 WatchKey를 생성하여 큐에 넣어준다. 프로그램은 무한 루프를 돌면서 WatchService의 take() 메소드를 호출하여 WatchKey가 큐에 들어올 때 까지 대기하고 있다가 WatchKey가 큐에 들어오면 WatchKey를 얻어 처리하면 된다.</p>
<pre><code class="language-java">while(true) {
    WatchKey watchKey = watchService.take();
}</code></pre>
<p>Key를 얻었다면 pollEvents() 메소드를 호출해서 WatchEvent 리스트를 얻어낸다. 한개의 WatchEvent가 아니라 List&lt;WatchEvent&lt;?&gt;&gt;로 리턴하는 이유는 여러개의 파일이 동시에 삭제, 수정, 생성될 수 있기 때문이다. 참고로 WatchEvent는 파일당 하나씩 발생한다.</p>
<pre><code class="language-java">List&lt;WatchEvnet&lt;?&gt;&gt; list = watchKey.pollEvents();</code></pre>
<p>프로그램은 WatchEvent리스트에서 WatchEvent를 하나씩 꺼내어 이벤트의 종류와 Path 객체를 얻어낸 다음 적절히 처리하면 된다.</p>
<pre><code class="language-java">for(WatchEvent watchEvent : list) {
  //이벤트 종류 얻기
  Kind kind = watchEvent.kind();
  // 감지된 Path 얻기 
  Path path = (Path) watchEvent.context();
  // 이벤트 종류별로 처리
  if(kind == StandardWatchEventKinds.ENTRY_CREATE) {
    // 생성되었을 경우, 실행 코드
  } else if(kind == StandardWatchEventKinds.ENTRY_MODIFY) {
    // 수정되었을 경우, 실행 코드
  } else if(kind == StandardWatchEventKinds.ENTRY_DELETE) {
    // 삭제 되었을 경우 실행 코드
  } else if(kind == StandardWatchEventKinds.OVERFLOW) {
    ...
  }
}</code></pre>
<p><code>OVERFLOW</code> 이벤트는 운영체제에서 이벤트가 소실됐거나 버려진 경우에 발생하므로 별도의 처리 코드가 필요없다. 따라서 <code>CREATE</code>,<code>MODIFY</code>,<code>DELETE</code>이벤트만 처리하면 된다. 한 번 사용된  WatchKey는 reset() 메소드로 초기화해야 하는데, 새로운 WatchEvent가 발생하면 큐에 다시 들어가기 때문이다. 초기화 성공시 true를 리턴하지만 감시하는 디렉토리나 삭제 || 키가 유효하지 않을 경우 false를 리턴한다. WatchKey가 더 이상 유효하지 않게 되면 무한 루프를 빠져나와 WatchService의 close() 메소드를 호출하고 종료하면 된다.</p>
<pre><code class="language-java">while(true) {
  WatchKey watchKey = watchService.take();
  List&lt;WatchEvent&lt;?&gt;&gt; list = watchKey.pollEvents();

  for(WatchEvent watchEvent : list) {
    ...
  }
  boolean valid = watchKey.reset();
  if(valid) break;
}
watchService.close();</code></pre>
<h2 id="버퍼-buffer">버퍼 (Buffer)</h2>
<p>NIO에서는 데이터를 입출력하기 위해서 항상 버퍼를 사용한다.  </p>
<p>버퍼는 읽고 쓰기가 가능한 메모리 배열이다. 버퍼를 이해하고 잘 사용할 수 있어야 NIO에서 제공하는 API를 올바르게 활용할 수 있다.</p>
<h3 id="버퍼의-종류">버퍼의 종류</h3>
<p>Buffer는 저장되는 데이터 타입에 따라 분류될 수 있고, 어떤 메모리를 사용하느냐에 따라 다이렉트(Direct) 넌다이렉트(NonDirect)로 분류할 수도 있다.</p>
<h4 id="데이터-타입에-따른-버퍼">데이터 타입에 따른 버퍼</h4>
<p>![스크린샷 2022-03-19 12.13.57](/Users/mac/Library/Application Support/typora-user-images/스크린샷 2022-03-19 12.13.57.png)</p>
<blockquote>
<p>데이터 타입에 따라서 별도의 클래스로 제공된다. 이 버퍼 클래스들은 Buffer 추상 클래스를 상속한다.</p>
</blockquote>
<p>버퍼 클래스의 이름을 보면 어떤 데이터가 저장되는 버퍼인지 쉽게 알 수있다. 이 중에 MappedByteBuffer는 ByteBuffer의 하위 클래스로 파일의 내용에 랜덤하게 접근하기 위해서 파일의 내용을 메모리와 맵핑시킨 버퍼이다.</p>
<h4 id="넌다이렉트와-다이렉트-버퍼">넌다이렉트와 다이렉트 버퍼</h4>
<p>버퍼가 사용하는 메모리의 위치에 따라서 넌다이렉트 버퍼와 다이렉트 버퍼로 분류된다.</p>
<p>넌다이렉트 버퍼는 JVM이 관리하는 힙 메모리 공간을 이용하는 버퍼이고, 다이렉트 버퍼는 운영체제가 관리하는 메모리 공간을 사용하는 버퍼이다. 두 버퍼의 특징은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>넌다이렉트 버퍼</th>
<th>다이렉트 버퍼</th>
</tr>
</thead>
<tbody><tr>
<td>사용하는 메모리 공가</td>
<td>JVM 힙 메모리</td>
<td>운영체제의 메모리</td>
</tr>
<tr>
<td>버퍼 생성시간</td>
<td>버퍼 생성이 빠름</td>
<td>버퍼 생성이 느림</td>
</tr>
<tr>
<td>버퍼의 크기</td>
<td>작다</td>
<td>크다 (큰 데이터를 처리할 때 유리)</td>
</tr>
<tr>
<td>입출력 성능</td>
<td>낮다.</td>
<td>높다(입출력이 빈번할 때 유리)</td>
</tr>
</tbody></table>
<ul>
<li>넌다이렉트 버퍼<ul>
<li>JVM 힙 메모리를 사용하므로 버퍼 생성 시간이 빠르다.</li>
<li>JVM의 제한된 힙 메모리를 사용하므로 버퍼의 크기를 크게 잡을 수 없다.</li>
<li>입출력을 하기 위해 임식 다이렉트 버퍼를 생성하고 넌다이렉트 버퍼에 있는 내용을 임시 다이렉트 버퍼에 복사한다. 그리고 나서 임시 다이렉트 버퍼를 사용해서 운영체제의 nativeI/O 기능을 수행한다. 따라서 입출력 성능이 상대적으로 낮다.</li>
</ul>
</li>
<li>다이렉트 버퍼 <ul>
<li>운영체제의 메모리를 할당 받기 위해 운영체제의 네이티브(Native) C 함수를 호출해야 하고 여러가지 잡다한 처리를 해야하므로 상대적으로 버퍼 생성이 느리다. 따라서 <strong>한번 생성한 후 재사용하는 것이 적합하다</strong></li>
<li>운영체제가 관리하는 메모리를 사용하므로 운영체제가 허용하는 범위 내에서 대용량 버퍼를 생성시킬 수 있다. </li>
</ul>
</li>
</ul>
<ul>
<li>넌다이렉트 / 다이렉트 버퍼 성능 비교</li>
</ul>
<pre><code class="language-java">import java.io.File;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;

public class PerformanceExample {
    public static void main(String[] args) throws Exception{
        Path from = Paths.get(&quot;src/sec03/exma01_direct_Buffer/house.jpg&quot;);
        Path to1 = Paths.get(&quot;src/sec03/exma01_direct_Buffer/house2.jpg&quot;);
        Path to2 = Paths.get(&quot;src/sec03/exma01_direct_Buffer/house3.jpg&quot;);

        long size = Files.size(from);

        FileChannel fileChannel_from = FileChannel.open(from);
        FileChannel fileChannel_to1 = FileChannel.open(to1, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
        FileChannel fileChannel_to2 = FileChannel.open(to2, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));

        ByteBuffer nonDirectBuffer = ByteBuffer.allocate((int) size);
        ByteBuffer directBuffer = ByteBuffer.allocateDirect((int) size);

        long start, end;

        start = System.nanoTime();

        for (int i = 0; i &lt; 100; i++) {
            fileChannel_from.read(nonDirectBuffer);
            nonDirectBuffer.flip();
            fileChannel_to1.write(nonDirectBuffer);
            nonDirectBuffer.clear();
        }
        end = System.nanoTime();
        System.out.println(&quot;넌 다이렉트 :\t&quot; + (end - start) + &quot; ns&quot;);

        fileChannel_from.position(0);

        start = System.nanoTime();
        for (int i = 0; i &lt; 100; i++) {
            fileChannel_from.read(directBuffer);
            directBuffer.flip();
            fileChannel_to2.write(directBuffer);
            directBuffer.clear();
        }
        end = System.nanoTime();
        System.out.println(&quot;다이렉트 :\t&quot; + (end - start) + &quot; ns&quot;);

        fileChannel_from.close();
        fileChannel_to1.close();
        fileChannel_to2.close();
    }
}</code></pre>
<h3 id="buffer-생성">Buffer 생성</h3>
<blockquote>
<p>각 데이터 타입별로 넌 다이렉트 버퍼를 생성하기 위해서는 각 Buffer 클래스의 allocate()와 wrap() 메소드를 호출하면 되고, 다이렉트 버퍼는 ByteBuffer의 allocateDirect() 메소드를 호출하면 된다. </p>
</blockquote>
<ul>
<li><strong>allocate() 메소드</strong>
JVM 힙 메모리에 넌다이렉트 버퍼를 생성한다. 다음은 데이터 타입별로 Buffer를 생성하는 allocate() 메소드이다.  매개값은 해당 데이터 타입의 저장 개수를 의미한다.</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>ByteBuffer</td>
<td>ByteBuffer.allocate(int capacity)</td>
<td>capacity개만큼의 byte 값을 저장</td>
</tr>
<tr>
<td>CharBuffer</td>
<td>CharBuffer.allocate(int capacity)</td>
<td>capacity개만큼의 char 값을 저장</td>
</tr>
<tr>
<td>DoubleBuffer</td>
<td>DoubleBuffer.allocate(int capacity)</td>
<td>capacity개만큼의 double 값을 저장</td>
</tr>
<tr>
<td>FloatBuffer</td>
<td>FloatBuffer.allocate(int capacity)</td>
<td>capacity개만큼의 float 값을 저장</td>
</tr>
<tr>
<td>IntBuffer</td>
<td>IntBuffer.allocate(int capacity)</td>
<td>capacity개만큼의 int 값을 저장</td>
</tr>
<tr>
<td>LongBuffer</td>
<td>LongBuffer.allocate(int capacity)</td>
<td>capacity개만큼의 long 값을 저장</td>
</tr>
<tr>
<td>ShortBuffer</td>
<td>ShortBuffer.allocate(int capacity)</td>
<td>capacity개만큼의 short 값을 저장</td>
</tr>
</tbody></table>
<ul>
<li>100개의 바이트를 저장하는 ByteBuffer 생성 및 100개의 문자를 저장하는 CharBuffer 생성</li>
</ul>
<pre><code class="language-java">ByteBuffer byteBuffer = ByteBuffer.allocate(100);
CharBuffer charBuffer = CharBuffer.allocate(100);</code></pre>
<ul>
<li><strong>wrap() 메소드</strong>
각 타입별 Buffer 클래스는 모두 wrap() 메소드를 가지고 있는데, wrap() 메소드는 이미 생성되어 있는 자바 배열을 래핑해서 Buffer 객체를 생성한다. 자바배열은 JVM 힙 메모리에 생성되므로 wrap() 메소드는 넌다이렉트 버퍼를 생성한다. 다음은 길이가 100인 byte[] 를 이용해서 ByteBuffer를 생성하고, 길이가 100인 char[] 를 이용해서 CharBuffer를 생성한다.</li>
</ul>
<pre><code class="language-java">byte[] byteArr = new byte[100];
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArr);

char[] charArr = new char[100];
CharBuffer charBuffer = CharBuffer.wrap(charArr);</code></pre>
<pre><code> 일부 데이터만을 가지고 Buffer 객체를 생성할 수도 있다. 이 경우 시작 인덱스와 길이를 추가적으로 지정하면 된다. `0 ~ 50`개만 버퍼로 생성해보자.</code></pre><pre><code class="language-java">byte[] byteArr = new byte[100];
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArr, 0, 50);

char[] charArr = new char[100];
CharBuffer charBuffer = CharBuffer.wrap(charArr, 0, 50);</code></pre>
<ul>
<li><strong>allocateDirect() 메소드</strong>
ByteBuffer의 allocateDirect() 메소드는 JVM 힙 메모리 바깥쪽, 즉 운영체제가 관리하는 메모리에 다이렉트 버퍼를 생성한다. 이 메소드는 각 타입별 Buffer 클래스에는 없고, ByteBuffer에서만 제공된다. 각 타입별로 다이렉트 버퍼를 생성하고 싶다면 우선 ByteBuffer의 allocateDirect() 메소드로 버퍼를 생성한 다음 ByteBuffer의 asCharBuffer(), asFloatBuffer(), asDoubleBuffer() ... asIntBuffer() 메소드를 이용해서 해당 타입별 Buffer를 얻으면 된다.</li>
</ul>
<ul>
<li><p><strong>byte 해석 순서(ByteOrder)</strong>
데이터를 처리할 때 바이트 처리 순서는 운영체제마다 차이가 있따. 이러한 차이는 데이터를 다른 운영체제로 보내거나 받을 때 영향을 미치기 때문에 데잍러르 다루는 버퍼도 이를 고려해야 한다. 앞쪽 바이트부터 먼저 처리하는 것을 <strong>Big endian</strong>이라고 하고, 뒤쪽 바이트부터 먼저 처리하는 것을 <strong>Little endian</strong>이라고 한다.</p>
<ul>
<li>Big-endian</li>
</ul>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0fdq3yjh4j21hc0u0dgy.jpg" alt="image-20220319193207991"></p>
<ul>
<li>Little-endian</li>
</ul>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0fdpybkewj21hc0u0dgy.jpg" alt="image-20220319193252142"></p>
</li>
</ul>
<blockquote>
<p>Litte-endian으로 동작하는 운영체제에서 만든 데이터 파일을 Big-endian으로 동작하는 운영체제에서 읽는다면 ByteOrder 클래스로 데이터 순서를 맞춰야 한다. ByteOrder 클래스의 nativeOrder() 메소드는 현재 동작하고 있는 운영체제가 Big-endian인지 Little-endian인지 알려준다. <strong>JVM도 일종의 독립된 운영체제이기 때문에</strong> 이런 문제를 취급하는데, JRE가 설치된 어떤 환경이든 JVM은 무조건 Big-endian으로 동작하게 되어 있다. 다음 예제는 현재 컴퓨터의 운영체제 종류와 바이트를 해석하는 순서에 대해 출력한다.</p>
</blockquote>
<ul>
<li>현재 컴퓨터의 운영체제 종류와 바이트를 해석하는 순서에 대해 출력한다.</li>
</ul>
<pre><code class="language-java">import java.nio.ByteOrder;

public class ComputerByteOrderExample {
    public static void main(String[] args) {
        System.out.println(&quot;운영체제 종류 : &quot; + System.getProperty(&quot;os.name&quot;));
        System.out.println(&quot;네이티브의 바이트 해석 순서 : &quot; + ByteOrder.nativeOrder());
    }
}</code></pre>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0fdrtfchkj216k07kjs7.jpg" alt="스크린샷 2022-03-19 19.44.31"></p>
<p>운영체제와 JVM의 바이트 해석 순서가 다를 경우에는 JVM이 운영체제와 데이터를 교환할 때 자동적으로 처리해주기 때문에 문제는 없다. 하지만 다이렉트 버퍼일 경우 운영체제의 native I/O를 사용하므로 운영체제의 기본 해석 순서로 JVM의 해석 순서를 맞추는 것이 성능에 도움 된다. 다음과 같이 allocateDirect()로 버퍼를 생성한 후, order() 메소드를 호출해서 nativeOrder()의 리턴값으로 세팅해주면 된다.</p>
<pre><code class="language-java">ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100).order(ByteOrder.nativeOrder());</code></pre>
<h3 id="buffer의-위치-속성">Buffer의 위치 속성</h3>
<blockquote>
<ul>
<li>Buffer의 위치 속성 개념</li>
<li>위치 속성이 언제 변경되는지에 대해 알고 있어야 한다.</li>
</ul>
</blockquote>
<ul>
<li>Buffer의 네 가지 위치 속성</li>
</ul>
<table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>position</td>
<td>현재 읽거나 쓰는 위치값<br />인덱스 값이기 때문에 0부터 시작하며 limit보다 큰 값을 가질 수 없다. 만약 position과 limit의 값이 같아진다면 더 이상 데이터를 쓰거나 읽을 수 없다는 뜻이 된다.</td>
</tr>
<tr>
<td>limit</td>
<td>버퍼에서 읽거나 쓸 수 있는 위치의 한게를 나타낸다. 이 값은 capacity보다 작거나 같은 값을 가진다. 최초에 버퍼를 만들었을 때는 capacity와 같은 값을 가진다.</td>
</tr>
<tr>
<td>capacity</td>
<td>버퍼의 최대 데이터 개수(메모리 크기)를 나타낸다. 인덱스 값이 아니라 수량임</td>
</tr>
<tr>
<td>mark</td>
<td>reset() 메소드를 실행했을 때, 돌아오는 위치를 지정하는 인덱스로서 mark() 메소드로 지정할 수 있다. 주의할 점은 반드시 position 이하의 값으로 지정해주어야 한다. position이나 limit의 값이 mark 값보다 작은 경우, mark는 자동 제거된다. mark가 없는 상태에서 reset() 메소드를 호출하면 InvalidMarkException이 발생한다.</td>
</tr>
</tbody></table>
<p>position, limit, capacity, mark 속성의 크기 관계는 다음과 같다. mark는 position보다 클 수 없고, position은 limit보다 클 수 없으며, limit은 capacity보다 클 수 없다.</p>
<pre><code>0 &lt;= mark &lt;= position &lt;= limit &lt;= capacity</code></pre><h3 id="buffer-메소드">Buffer 메소드</h3>
<p>Buffer를 생성한 후 사용할 때에는 Buffer가 제공하는 메소드를 잘 활용해야 한다. Buffer마다 공통적으로 사용하는 메소드들도 있고, 데이터 타입별로 Buffer가 개별적으로 가지고 있는 메소드들도 있다. </p>
<ul>
<li><strong>공통 메소드</strong>
각 타입별 버퍼 클래스는 Buffer 추상 클래스를 상속하고 있다. Buffer 추상 클래스에는 모든 버퍼가 공통적으로 가져야 할 메소드들이 정의되어 있는데, 위치 속성을 변경하는 flip(), rewind(), clear(), mark(), reset()도 모두 Buffer 추상 클래스에 있다. 다음은 Buffer가 가지는 메소드들을 정리한 표이다.</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Object</td>
<td>array()</td>
<td>버퍼가 래핑(wrap)한 배열을 리턴</td>
</tr>
<tr>
<td>int</td>
<td>arrayOffset()</td>
<td>버퍼의 첫 번째 요소가 있는 내부 배열의 인덱스를 리턴</td>
</tr>
<tr>
<td>int</td>
<td>capacity()</td>
<td>버퍼의 전체 크기를 리턴</td>
</tr>
<tr>
<td>Buffer</td>
<td>clear()</td>
<td>버퍼의 위치 속성을 초기화(position = 0, limit = capacity)</td>
</tr>
<tr>
<td>Buffer</td>
<td>flip()</td>
<td>limit을 position으로, position을 0 인덱스로 이동</td>
</tr>
<tr>
<td>boolean</td>
<td>hasArray()</td>
<td>버퍼가 래핑한 배열을 가지고 있는지 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>hasRemaining()</td>
<td>position과 limit 사이에 요소가 있는지 여부(position &lt; limit)</td>
</tr>
<tr>
<td>boolean</td>
<td>isDirect()</td>
<td>운영체제의 버퍼를 사용하는지 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>isReadOnly()</td>
<td>버퍼가 읽기 전용인지 여부</td>
</tr>
<tr>
<td>int</td>
<td>limit()</td>
<td>limit 위치를 리턴</td>
</tr>
<tr>
<td>Buffer</td>
<td>limit(int newLimit)</td>
<td>newLimit으로 limit 위치를 설정</td>
</tr>
<tr>
<td>Buffer</td>
<td>mark()</td>
<td>현재 위치를 mark로 표시</td>
</tr>
<tr>
<td>int</td>
<td>position()</td>
<td>position 위치를 리턴</td>
</tr>
<tr>
<td>Buffer</td>
<td>position(int newPosition)</td>
<td>newPosition으로 position 위치를 설정</td>
</tr>
<tr>
<td>int</td>
<td>remaining()</td>
<td>position과 limit 사이의 요소 개수</td>
</tr>
<tr>
<td>Buffer</td>
<td>reset()</td>
<td>position을 mark위치로 이동</td>
</tr>
<tr>
<td>Buffer</td>
<td>rewind()</td>
<td>position을 0 인덱스로 이동</td>
</tr>
</tbody></table>
<h4 id="데이터를-읽고-저장하는-메소드"><strong>데이터를 읽고 저장하는 메소드</strong></h4>
<p>버퍼에 데이터를 저장하는 메소드는 put()이고 데이터를 읽는 메소드는 get()이다.</p>
<p>이 메소드들은 Buffer 추상 클래스에는 없고, 각 타입별 하위 Buffer 클래스가 가지고 있다. get(), put()은 상대적과 절대적으로 구분된다. <strong>버퍼 내의 현재 위치 속성인 position에서 데이터를 읽고 저장할 경우는 상대적</strong>이고 <strong>position과 관계없이 주어진 인덱스에서 데이터를 읽고, 저장할 경우는 절대적</strong>이다. 상대적 get()과 put() 메소드를 호출하면 position값은 증가하지만, 절대적 get(), put() 메소드를 호출하면 position의 값은 변하지 않는다. 만약 position값이 limit 값 까지 증가했는데도 상대적 get()을 사용하면 <strong><code>BufferUnderflowException</code></strong>이 발생하고, put()을 사용하면 <strong><code>BufferOverflowException</code></strong>이 발생한다.</p>
<ul>
<li>get() / put() 나열 표</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th></th>
<th>ByteBuffer</th>
<th>CharBuffer</th>
</tr>
</thead>
<tbody><tr>
<td>get()</td>
<td>상대적</td>
<td>get()<br />get(byte[] dst)<br />get(byte[] dst, int offset, int length)<br />getChar()<br />getDouble()<br />getFloat()<br />getInt()<br />getLong()<br />getShort()</td>
<td>get()<br />get(char[] dst)<br />get(char[] dst, int offset, int length)</td>
</tr>
<tr>
<td></td>
<td>절대적</td>
<td>get(int index)<br />getChar(int index)<br />getDouble(int index)<br />getFloat(int index)<br />getInt(int index)<br />getLong(int index)<br />getShort(int index)</td>
<td>get(int index)</td>
</tr>
<tr>
<td>put()</td>
<td>상대적</td>
<td>put(byte b)<br />put(byte[] src)<br />put(byte[] src, int offset, int length)<br />put(ByteBuffer src)<br />putChar(char value)<br />putDouble(double value)<br />putFloat(float value)<br />putInt(int value)<br />putLong(long value)<br />putShort(short value)</td>
<td>put(char c)<br />put(char[] src)<br />put(char[] src, int offset, int length)<br />put(CharBuffer src)<br />put(String src)<br />put(String src, int start, int end)</td>
</tr>
<tr>
<td></td>
<td>절대적</td>
<td>put(int index, byte b)<br />putChar(int index, char value)<br />putDouble(int index, double value) <br />putFloat(int index, float value)<br />putInt(int index, int value)<br />putLong(int index, long value)<br />putShort(int index, short value)</td>
<td>put(int index, char c)</td>
</tr>
</tbody></table>
<blockquote>
<p>상대적 메소드와 절대적 메소드를 쉽게 구분하는 방법은 index 매개 변수가 없으면 상대적이고, index 매개 변수가 있으면 절대적이다.</p>
</blockquote>
<ul>
<li><strong>버퍼 예외의 종류</strong>
버퍼 클래스에서 발생하는 예외를 살펴보자. 주로 버퍼가 다 찼을 때 데이터를 저장하려는 경우와 버퍼에서 더 이상 읽어올 데이터가 없을 때 데이터를 읽으려는 경우에 예외가 발생한다. 다음 표는 버퍼와 관련된 예외 클래스이다. 
가장 흔한 예외는 <strong><code>BufferOverflowException</code></strong>,<strong><code>BufferUnderflowException</code></strong>이다.</li>
</ul>
<table>
<thead>
<tr>
<th>예외</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>BufferOverflowException</td>
<td>position이 limit에 도달했을 때 put() 을 호출하면 발생</td>
</tr>
<tr>
<td>BufferUnderflowException</td>
<td>position이 limit에 도달했을 때 get()을 호출하면 발생</td>
</tr>
<tr>
<td>InvalidMarkException</td>
<td>mark가 없는 상태에서 reset() 메소드를 호출하면 발생</td>
</tr>
<tr>
<td>ReadOnlyBufferException</td>
<td>읽기 전용 버퍼에서 put() 또는 compact() 메소드를 호출하면 발생</td>
</tr>
</tbody></table>
<ul>
<li>데이터를 버퍼에 쓰고 읽을 때, 위치 속성을 변경하는 메소드를 호출할 때 버퍼의 위치 속성값의 변화를 보여주는 예제</li>
</ul>
<pre><code class="language-java">import java.nio.Buffer;
import java.nio.ByteBuffer;

public class BufferExample {
    public static void main(String[] args) throws Exception {
        System.out.println(&quot;[7바이트 크기로 버퍼 생성]&quot;);
        ByteBuffer buffer = ByteBuffer.allocateDirect(7);
        printState(buffer);

        buffer.put((byte) 10);
        buffer.put((byte) 11);
        System.out.println(&quot;[2바이트 저장 후]&quot;);
        printState(buffer);

        buffer.put((byte) 12);
        buffer.put((byte) 13);
        buffer.put((byte) 14);
        System.out.println(&quot;[3바이트 저장 후]&quot;);
        printState(buffer);

        buffer.flip();
        System.out.println(&quot;[flip() 실행 후]&quot;);
        printState(buffer);

        buffer.get(new byte[3]);
        System.out.println(&quot;[3바이트 읽은 후]&quot;);
        printState(buffer);

        buffer.mark();
        System.out.println(&quot;---------[현재 위치를 마크 해놓음]&quot;);

        buffer.get(new byte[2]);
        System.out.println(&quot;[2바이트 읽은 후]&quot;);
        printState(buffer);

        buffer.reset();
        System.out.println(&quot;---------[position을 마크 위치로 옮김]&quot;);
        printState(buffer);

        buffer.rewind();
        System.out.println(&quot;[rewind() 실행 후]&quot;);
        printState(buffer);

        buffer.clear();
        System.out.println(&quot;[clear() 실행 후]&quot;);
        printState(buffer);
    }

    public static void printState(Buffer buffer) {
        System.out.print(&quot;\tposition : &quot; + buffer.position() + &quot;, &quot;);
        System.out.println(&quot;\tlimit : &quot; + buffer.limit() + &quot;, &quot;);
        System.out.println(&quot;\tcapacity : &quot; + buffer.capacity());

    }
}</code></pre>
<blockquote>
<p>실행결과 :</p>
</blockquote>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0ficqw6gaj20iw13m76q.jpg" alt="스크린샷 2022-03-19 22.22.54"></p>
<ul>
<li>compact() 메소드 호출 후, 변경된 버퍼의 내용과 position, limit의 위치를 보여주는 예제</li>
</ul>
<pre><code class="language-java">import java.nio.ByteBuffer;

public class CompactExample {
    public static void main(String[] args) {
        System.out.println(&quot;[7바이트 크기로 버퍼 생성]&quot;);
        ByteBuffer buffer = ByteBuffer.allocateDirect(7);
        buffer.put((byte) 10);
        buffer.put((byte) 11);
        buffer.put((byte) 12);
        buffer.put((byte) 13);
        buffer.put((byte) 14);
        // 데이터를 읽기 위해 위치 속성값 변경
        buffer.flip();

        buffer.get(new byte[3]);
        System.out.println(&quot;[3바이트 읽음]&quot;);

        // 읽지 않은 데이터는 0 인덱스부터 복사
        buffer.compact();
        System.out.println(&quot;[compact 실행 후]&quot;);
        printState(buffer);
    }

    public static void printState(ByteBuffer buffer) {
        System.out.print(buffer.get(0) + &quot;, &quot;);
        System.out.print(buffer.get(1) + &quot;, &quot;);
        System.out.print(buffer.get(2) + &quot;, &quot;);
        System.out.print(buffer.get(3) + &quot;, &quot;);
        System.out.println(buffer.get(4));
        System.out.print(&quot;position : &quot; + buffer.position() + &quot;, &quot;);
        System.out.print(&quot;limit : &quot; + buffer.limit() + &quot;, &quot;);
        System.out.println(&quot;capacity : &quot; + buffer.capacity());
    }
}</code></pre>
<blockquote>
<p>실행 결과 : </p>
</blockquote>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0fiiatcocj20qs0bmgmf.jpg" alt="스크린샷 2022-03-19 22.28.21"></p>
<h3 id="buffer-변환">Buffer 변환</h3>
<p>채널이 데이터를 일고 쓰는 버퍼는 모두 ByteBuffer이다. 따라서 채널을 통해 읽은 데이터를 복원하려면 ByteBuffer를 문자열 또는 다른 타입 버퍼(CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer)로 변환해야 한다. 반대로 문자열 또는 다른 타입 버퍼의 내용을 채널을 통해 쓰고 싶다면 ByteBuffer로 변환해야 한다.</p>
<ul>
<li><p><strong>ByteBuffer &lt;-&gt; String</strong></p>
<p>프로그램에서 가장 많이 처리되는 데이터는 String, 문자열이다. 채널을 통해 문자열을 파일이나 네트워크로 전송하려 면 특정 문자셋(UTF-8, EUC-KR)으로 인코딩해서 ByteBuffer로 변환해야 한다. 먼저 문자셋을 표현하는 <code>java.nio.charset.Charset</code>객체가 필요한데 다음 두 가지 방법으로 얻을 수 있다.</p>
</li>
</ul>
<pre><code class="language-java">Charset charset = Charset.forName(&quot;UTF-8&quot;); // 매개값으로 주어진 문자셋
Charset charset = Charset.defaultCharset(); // 운영체제가 사용하는 디폴트 문자셋</code></pre>
<blockquote>
<p>Charset을 이용해서 문자열을 ByteBuffer로 변환하려면 다음과 같이 encode() 메소드를 호출하면 된다.</p>
</blockquote>
<pre><code class="language-java">String data = ...;
ByteBuffer byteBuffer = charset.encode(data);</code></pre>
<p>반대로 파일이나 네트워크로부터 읽은 ByteBuffer가 특정 문자셋(UTF-8, EUC-KR)으로 인코딩되어 있을 경우, 해당 문자셋으로 디코딩해야만 문자열로 복원할 수 있다. Charset은 ByteBuffer를 디코딩해서 CharBuffer로 변환시키는 decode() 메소드를 제공한다.</p>
<pre><code class="language-java">ByteBuffer byteBuffre = ...;
String data = charset.decode(byteBuffer).toString();</code></pre>
<ul>
<li>Encode / Decode 예제</li>
</ul>
<pre><code class="language-java">import java.nio.ByteBuffer;
import java.nio.charset.Charset;

public class ByteBufferToStringExample {
    public static void main(String[] args) {
        Charset charset = Charset.forName(&quot;UTF-8&quot;);

        // 문자열 -&gt; 인코딩 -&gt; ByteBuffer
        String data = &quot;안녕하세요&quot;;
        ByteBuffer buffer = charset.encode(data);

        // ByteBuffer -&gt; 디코딩 -&gt; String
        data = charset.decode(buffer).toString();
        System.out.println(&quot;[문자열 복원] : &quot; + data);
    }
}</code></pre>
<ul>
<li><strong>ByteBUffer &lt;-&gt; IntBuffer</strong>
int[] 배열을 생성하고 이것을 파일이나 네트워크로 출력하기 위해서는 int[] 배열 또는 IntBuffer로부터 ByteBuffer를 생성해야 한다. int 타입은 4byte 크기를 가지므로 int[] 배열 크기 또는 IntBuffer의 capacity보다 4배 큰 capacity를 가진 ByteBuffer를 생성하고, ByteBuffer의 putInt()메소드로 정수값을 하나씩 저장하면 된다. 다음은 int[] 배열을 IntBuffer로 래핑하고 4배 큰 ByteBuffer를 생성한 후 정수값을 저장한다.<br>putInt() 메소드는 position을 이동시키기 때문에 모두 저장한 후에 position을 0으로 되돌려 놓는 flip() 메소드를 호출해야 한다.</li>
</ul>
<pre><code class="language-java">int[] data = new int[] {10, 20};
IntBuffer intBuffer = IntBuffer.wrap(data);
ByteBuffer byteBuffer = ByteBuffer.allocate(intBuffer.capacity()*4);
for(int i = 0; i &lt; intBuffer.capacity(); i++) {
    byteBuffer.putInt(intBuffer.get(i));
}
byteBuffer.flip();</code></pre>
<p>반대로 파일이나 네트워크로부터 입력된 ByteBuffer에 4바이트씩 연속된 int 데이터가 저장되어 있을 경우 int[] 배열로 복원이 가능하다. ByteBuffer의 asIntBuffer() 메소드로 IntBuffer를 얻고, IntBuffer의 capacity와 동일한 크기의 int[] 배열을 생성한다. 그리고 IntBuffer의 get() 메소드로 int값들을 배열에 저장하면 된다.</p>
<pre><code class="language-java">ByteBuffer byteBuffer = ...;
IntBuffer = byteBuffer.asIntBuffer();

int[] data = new int[IntBuffer.capacity()];
intBuffer.get(data);</code></pre>
<blockquote>
<p>ByteBuffer에서 asIntBuffer()로 얻은 IntBuffer에서는 array() 메소드를 사용해서 int[] 배열을 얻을 수 없다. array() 메소드는 래핑한 배열만 리턴하기 때문에, int[] 배열을 wrap()으로 래핑한 IntBuffer에서만 사용할 수 있다.</p>
</blockquote>
<h2 id="파일-채널">파일 채널</h2>
<p><code>java.nio.channels.FileChannel</code>을 이용하면 파일 읽기와 쓰기를 할 수 있다. FileChannel은 동기화 처리가 되어 있기 때문에 멀티 스레드 환경에서 사용해도 안전하다.</p>
<h3 id="filechannel-생성과-닫기">FileChannel 생성과 닫기</h3>
<p>정적 메소드인 open() 을 호출해서 얻을 수도 있지만, IO의 FileInputStream, FileOutputStream의 getChannel()을 통해서도 얻을 수 있다.</p>
<pre><code class="language-java">FileChannel fileChannel = FileChannel.open(Paht path, OpenOption... options);</code></pre>
<blockquote>
<p>path 매개값은 열거나 생성하고자 하는 파일의 경로를 Path 객체로 생성해서 지정하면 되고, 두 번째 options 매개값은 열기 옵션 값인데 StandardOpenOption의 다음 열거 상수를 나열해주면 된다. </p>
</blockquote>
<table>
<thead>
<tr>
<th>열거 상수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>READ</td>
<td>읽기용으로 파일을 연다</td>
</tr>
<tr>
<td>WRITE</td>
<td>쓰기용으로 파일을 연다</td>
</tr>
<tr>
<td>CREATE</td>
<td>파일이 없다면 새 파일을 생성한다.</td>
</tr>
<tr>
<td>CREATE_NEW</td>
<td>새 파일을 만든다. 이미 파일이 있으면 예외와 함께 실패한다.</td>
</tr>
<tr>
<td>APPEND</td>
<td>파일 끝에 데이터를 추가한다. WRITE나 CREATE와 함께 사용된다.</td>
</tr>
<tr>
<td>DELETE_ON_CLOSE</td>
<td>채널을 닫을 때 파일을 삭제한다(임시 파일을 삭제할 때 사용)</td>
</tr>
<tr>
<td>TRUNCATE_EXISTING</td>
<td>파일을 0바이트로 잘라낸다. (WRITE 옵션과 함께 사용됨)</td>
</tr>
</tbody></table>
<pre><code class="language-java">// 파일을 생성하고 내용을 작성
FIleChannel fileChannel = FileChannel.open(
    Paths.get(&quot;C:/Temp/file.txt&quot;),
  StandardOpenOption.CREATE_NEW,
  StandardOpenOption.WRITE
);

// 파일을 읽고 작성
FIleChannel fileChannel = FileChannel.open(
    Paths.get(&quot;C:/Temp/file.txt&quot;),
  StandardOpenOption.READ,
  StandardOpenOption.WRITE
);

// 작업 종료
fileChannel.close();</code></pre>
<h3 id="파일-쓰기와-읽기">파일 쓰기와 읽기</h3>
<p>파일에 바이트를 쓰려면 다음과 같이 FileChannel의 write() 메소드를 호출하면 된다. 매개값으로 ByteBuffer 객체를 주면 되는데, 파일에 쓰여지는 바이트는 ByteBuffer의 position부터 limit까지이다. position이 0이고 limit이 capacity와 동일하다면 ByteBuffer의 모든 바이트가 파일에 쓰여진다. write() 메소드의 리턴값은 ByteBuffer에서 파일로 쓰여진 바이트 수이다. </p>
<pre><code class="language-java">int bytesCount = fileChannel.write(ByteBuffer src);</code></pre>
<ul>
<li>파일 쓰기 예제</li>
</ul>
<pre><code class="language-java">public class FileChannelWriteExample {
  public static void main(String[] args) throws IOException {
    Path path = Paths.get(&quot;C:/Temp/file.txt&quot;);
    Files.createDirectories(path.getParent());

    FileChannel fileChannel = FileChannel.open(
      path, StandardOpenOptions.CREATE, StandardOpenOption.WRITE);

    String data = &quot;안녕하세요&quot;;
    Charset charset = Charset.defaultCharset();
    ByteBuffer byteBuffer = charset.encode(data);

    int byteCount = fileChannel.write(byteBuffer);
    System.out.println(&quot;file.txt : &quot; + byteCount + &quot;bytes written&quot;);

    fileChannel.close();
  }
}</code></pre>
<p>파일로부터 바이트를 읽기 위해서는 다음과 같이 FileChannel의 read() 메소드를 호출하면 된다. 매개값으로 ByteBuffer 객체를 주면 된든데, 파일에서 읽혀지는 바이트는 ByteBuffer의 position부터 저장된다. position이 0이면 ByteBuffer의 첫 바이트부터 저장된다. read() 메소드의 리턴값은 파일에서 ByteBuffer로 읽혀진 바이트 수이다. 한 번 읽을 수 있는 최대 바이트 수는 ByteBuffer의 capacity까지므로 리턴되는 최대값은 capacity가 된다. 더 이상 읽을 바이트가 없다면 read()는 -1을 리턴한다.</p>
<p>버퍼에 한 바이트를 저장할 때마다 position이 1씩 증가하게 되는데, read() 메소드가 -1을 리턴할 때까지 버퍼에 저장한마지막 바이트의 위치는 position -1 인덱스이다.</p>
<ul>
<li>파일 읽기 예제</li>
</ul>
<pre><code class="language-java">public class FileChannelReadExample {
    public static void main(String[] args) {
    Path path = Paths.get(&quot;C:/Temp/file.txt&quot;);

    FileChannel fileChannel = FileChannel.open(
    path, StandardOpenOption.READ);

    ByteBuffer byteBuffer = ByteBuffer.allocate(100);

    Charset charset = Charset.defaultCharset();
    String data = &quot;&quot;;
    int byteCount;

    while(true) {
      // 최대 100바이트를 읽는다.
      byteCount = fileChannel.read(byteBuffer);
      if(byteCount == -1) break;
      // limit을 현재 position으로 설정하고 position을 0으로 설정
      byteBuffer.flip();
      // 문자열 변환
      data += charset.decode(byteBuffer).toString();
      // position을 0번 인덱스로, limit을 capacity로 설정해서 ByteBuffer를 초기화
      byteBuffer.clear();
    }

    fileChannel.close();

    System.out.println(&quot;file.txt : &quot; + data);
  }
}</code></pre>
<h3 id="파일-복사">파일 복사</h3>
<p>파일 복사를 구현하기 위해서는 하나의 ByteBuffer를 사이에 두고, 파일 읽기용 FileChannel과 파일 쓰기용 FileChannel이 읽기와 쓰기를 교대로 번갈아 수행하도록 하면 된다.</p>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0gghad1o5j21d80kijsp.jpg" alt="스크린샷 2022-03-20 18.03.42"></p>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FileCopyExample {
    public static void main(String[] args) throws Exception {

        Path from = Paths.get(&quot;src/exam02_file_copy/house1.jpg&quot;);
        Path to = Paths.get(&quot;src/exam04_file_copy/house2.jpg&quot;);

        FileChannel fileChannel_from = FileChannel.open(from, StandardOpenOption.READ);
        FileChannel fileChannel_to = FileChannel.open(to, StandardOpenOption.CREATE, StandardOpenOption.WRITE);

        ByteBuffer buffer = ByteBuffer.allocateDirect(100);
        int byteCount;
        while (true) {
            buffer.clear();
            byteCount = fileChannel_from.read(buffer);
            if (byteCount == -1) {
                break;
            }
            buffer.flip();
            fileChannel_to.write(buffer);

            fileChannel_from.close();
            fileChannel_to.close();
            System.out.println(&quot;복사 완료&quot;);

        }
    }
}</code></pre>
<blockquote>
<p>복사할 이미지가 존재하는 Path와 이미지를 복사할 Path를 생성하고, 각 Path의 FileChannel을 생성해준다. 그리고 다이렉트 버퍼를 생성하는데 채널에서 읽고 다시 채널로 쓰는 경우 다이렉트 버퍼가 좋은 성능을 내기 때문이다.</p>
</blockquote>
<p>단순히 파일을 복사할 목적이라면 NIO의 FIles 클래스의 copy() 메소드를 사용하는 것이 더 편리하다.</p>
<pre><code class="language-java">Path path = Files.copy(Path source, Path target, CopyOption ... options);</code></pre>
<blockquote>
<p>첫 번째 source 매개값에는 원본 파일의 Path 객체를 지정하고 두 번째 Path값에는 타겟 파일의 Path 객체를 지정하면 된다. 세 번째 매개값은 StandardCopyOption 열거 상수를 목적에 맞게 나열해주면 된다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>열거 상수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>REPLACE_EXISTRING</td>
<td>타겟 파일이 존재하면 대체한다.</td>
</tr>
<tr>
<td>COPY_ATTRIBUTES</td>
<td>파일의 속성까지도 복사한다.</td>
</tr>
<tr>
<td>NOFOLLOW_LINKS</td>
<td>링크 파일일 경우 링크 파일만 복사하고 링크된 파일은 복사하지 않는다.</td>
</tr>
</tbody></table>
<ul>
<li>예제 (Files.copy())</li>
</ul>
<pre><code class="language-java">import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class FilesCopyExample {
    public static void main(String[] args) throws Exception {
        Path from = Paths.get(&quot;src/exam02_file_copy/house1.jpg&quot;);
        Path to = Paths.get(&quot;src/exam04_file_copy/house2.jpg&quot;);

        Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
        System.out.println(&quot;파읿 복사 성공&quot;);

    }
}</code></pre>
<h2 id="파일-비동기-채널">파일 비동기 채널</h2>
<p>FileChannel의 read() 와 write() 메소드는 파일 입출력 작업 동안 블로킹된다. 만약 UI 및 이벤트를 처리하는 스레드에서 이 메소드들을 호출하면 블로킹되는 동안에 UI 갱신이나 이벤트 처리를 할 수 없다. 따라서 별도의 작업 스레드를 생성해서 이 메소드들을 호출해야 한다. 만약 동시에 처리해야 할 파일 수가 많다면 스레드의 수도 증가하기 때문에 문제가 될 수 있다. 그래서 자바 NIO는 불특정 다수의 파일 및 대용량 파일의 입출력 작업을 위해서 비동기 파일 채널을 별도로 제공하고 있다.  </p>
<p><code>AsyncronousFileChannel</code>의 특징은 파일의 데이터 입출력을 위해 read()와 write() 메소드를 호출하면 스레드풀에게 작업 처리를 요청하고 이 메소드를 즉시 리턴시킨다. 실질적인 입출력 작업 처리는 스레드풀의 작업 스레드가 담당한다. 작업 스레드가 파일 입출력을 완료하게 되면 콜백 메소드가 자동 호출되기 때문에 작업 완료 후 실행해야 할 코드가 있다면 콜백 메소드에 작성하면 된다.</p>
<h3 id="asyncronousfilechannel-생성과-닫기">AsyncronousFileChannel 생성과 닫기</h3>
<p>AsynchronousFileChannel은 두 가지 정적 메소드인 open()을 호출해서 얻을 수 있다. 첫 번째  open() 메소드는 다음과 같이 파일의 Path 객체와 열기 옵션 값을 매개값으로 받는다.</p>
<pre><code class="language-java">AsychronousFileChannel fileChannel = AsynchronousFileChannel.open(
    Path file,
    OpenOption... options);</code></pre>
<p>이렇게 생성된 AsynchronousFileChannel은 내부적으로 생성되는 기본 스레드풀을 사용해서 스레드를 관리한다.</p>
<p>기본 스레드풀의 최대 스레드 개수는 개발자가 지정할 수 없기 때문에 다음과 같이 두 번째 open() 메소드로 채널을 만들수도 있다.</p>
<pre><code class="language-java">AsychronousFileChannel fileChannel = AsychronousFileChannel.open(
  Path file,
  Set&lt;? extends OpenOptions&gt; options,
  ExecutorService executor,
  FileAttribute&lt;?&gt; ... attrs
  );</code></pre>
<blockquote>
<p>File 매개값은 파일의 Path 객체이고 options 매개값은 열기 옵션 값들이 저장도니 Set 객체이다. executor 매개값은 스레드풀인 ExecutorService 객체이다. attrs 매개값은 파일 생성 시 파일 속성값이 될 FileAttribute를 나열하면 된다. 예를 들어 C:/Temp/ifle.txt 파일에 대한 AsynchronousFileChannel은 다음과 같이 생성할 수 있다.</p>
</blockquote>
<pre><code class="language-java">ExecutorService service = ExecutorService.newFixedThreadPool(
  Runtime.getRuntime().availableProcessors()
);

AsychronousFileChannel fileChannel = AsychronousFileChannel.open(
    Paths.get(&quot;C:/Temp/file.txt&quot;),
  EnumSet.of.(StandardOpenOption.CREATE, StandardOpenOptions.WRITE),
  service
);
</code></pre>
<p>더 이상 채널을 사용하지 않을 경우 <code>fileChannel.close()</code>를 호출하여 채널을 닫아준다.</p>
<ul>
<li><p>availableProcessors() :  CPU의 코어수를 리턴한다. 쿼드 코어의 경우 4개를 리턴한다.</p>
</li>
<li><p>EnumSet.of() : 매개값으로 나열된 열거 상수를 Set 객체에 담아 리턴한다</p>
</li>
</ul>
<h3 id="파일-읽기와-쓰기">파일 읽기와 쓰기</h3>
<p>채널이 생성되었다면 read(), write() 메소드를 이용해서 입출력할 수 있다.</p>
<pre><code class="language-java">read(ByteBuffer dst, long position, A attachment, CompletionHandler&lt;Integer, A&gt; handler);
write(ByteBuffer src, long position, CompletionHandler&lt;Integer, A&gt; handler);</code></pre>
<p>이 메소드들을 호출하면 즉시 리턴되고 스레드풀의 스레드가 입출력 작업을 진행한다. </p>
<ul>
<li><p><code>drt</code>, <code>src</code> : 파일을 읽거나 쓰기 위한 ByteBuffer</p>
</li>
<li><p><code>position</code> : 파일을 읽거나 쓸 때의 위치</p>
</li>
<li><p><code>attachment</code> : 콜백 메소드로 전달할 첨부 객체이다. 콜백 메소드에서 결과값 외에 추가적인 정보를 얻기 위해 사용한다. 첨부 객체가 필요없다면 <code>null</code>을 리턴해도 된다. </p>
</li>
<li><p><code>handler</code> : CompletionHandler&lt;Integer, A&gt;의 구현 객체를 지정한다. <code>Integer</code>는 입출력의 결과 타입으로 read(), write() 메소드로 읽거나 쓴 바이트 수를 리턴한다. <code>A</code>는 첨부 객체 타입으로 개발자가 CompletionHandler 구현 객체를 작성할 때 임의로 지정이 가능하며 필요없다면 void가 된다.</p>
</li>
<li><p><code>CompletionHandler&lt;Integer, A&gt;</code> : 정상적 완료와 예외 발생시 호출할 두 가지 콜백 메소드를 가지고 있어야 한다.</p>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드명 (매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>void</td>
<td>completed(Integer result, A attachment)</td>
<td>작업이 정상적으로 완료된 경우 콜백</td>
</tr>
<tr>
<td>void</td>
<td>failed(Throwable exc, A attachment)</td>
<td>예외 때문에 작업이 실패된 경우 콜백</td>
</tr>
</tbody></table>
<ul>
<li><code>completed()</code> : result 매개값은 작업 결과가 대입되는데  read(), write() 메소드의 읽거나 쓴 바이트 수이다. attachment는 read(), write() 호출 시 제공된 첨부 객체이다.</li>
<li><code>failed()</code> : exc 매개값은 작업 처리 도중 발생한 예외이다. 주목할 점은 콜백 메소드를 실행하는 스레드는 read(), write()를 호출한 스레드가 아니고 스레드풀의 작업 스레드인 것이다.</li>
<li>CompletionHandler 구현 클래스 작성 방법</li>
</ul>
<pre><code class="language-java">new CompletionHandler&lt;Integer, A&gt;() {
  @Override
  public void completed(Integer result, A attachment) { ... }
  @Override
  public void failed(Throwable exc, A attachment) { ... }
}</code></pre>
</li>
</ul>
<h2 id="tcp-블로킹-채널">TCP 블로킹 채널</h2>
<blockquote>
<p>NIO를 이용해서 TCP 서버/클라이언트 애플리케이션을 개발하려면 블로킹, 넌블로킹, 비동기 구현 방식 중에서 하나를 결정해야 한다. 이 결정에 따라 구현이 완전히 달라진다.</p>
</blockquote>
<h3 id="서버소켓-채널과-소켓-채널의-용도">서버소켓 채널과 소켓 채널의 용도</h3>
<blockquote>
<p>NIO에서 TCP 네트워크 통신을 위해 사용하는 채널은 <code>java.nio.channels.ServerSocketChannel</code>과 <code>java.nio.channels.SocketChannel</code>이다. 이 두 채널은 IO의 ServerSocket과 Socket에 대응되는 클래스로 IO가 버퍼를 사용하지 않고 블로킹 입출력 방식만 지원한다면 ServerSocketChannel, SocketChannel은 버퍼를 이용하고 블로킹과 넌블로킹 방식을 모두 지원한다. 사용 방법은 IO와 큰 차이점이 없는데, ServerSocket은 클라이언트 SocketChannel의 연결 요청을 수락하고 통신용 SocketChannel을 생성한다.</p>
</blockquote>
<h3 id="서버소켓-채널-생성과-연결-수락">서버소켓 채널 생성과 연결 수락</h3>
<ul>
<li>서버를 개발하기 위해선 ServerSocketChannel 객체를 얻어야 함</li>
<li>ServerSockeChannel은 정적 메소드인 open()으로 생성하고, 블로킹 방식으로 동작시키기 위해 configureBlocking(true) 메소드를 호출함.</li>
<li>기본적으로 블로킹 방식으로 동작, 그러나 명시적으로 설정하는 이유는 넌블로킹과 구분하기 위함임.</li>
<li>포트에 바인딩 하기 위해서는  bind() 메소드가 호출되어야 함 :arrow_right: InetSocketAddress 객체를 매개값으로 주면 된다.</li>
</ul>
<pre><code class="language-java">ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(true);
serverSocketChannel.bind(new InetSocketAddress(5001));</code></pre>
<p>포트 바인딩까지 끝났다면 ServerSocketChannel은 클라이언트 연결 수락을 위해 accept() 메소드를 실행해야 한다.</p>
<ul>
<li>accept() : 클라이언트가 연결 요청을 하면 accept()는 클라이언트와 통신할 SocketChannel을 만들고 리턴함. 클라이언트가 요청을 하기 전까지 블로킹 됨, UI 및 이벤트를 처리하는 스레드에서  accept() 메소드를 호출하지 않도록 한다.</li>
</ul>
<pre><code class="language-java">SocketChannel socketChannel = serverSocketChannel.accept();</code></pre>
<p>연결된 클라이언트의 IP와 포트 정보를 얻고 싶다면 SocketChannel의 getRemoteAddress()를 통해 얻으면 된다. 실제 리턴되는 것은 InetSocketAddress 인스턴스이므로 다음과 같이 캐스팅이 가능하다.</p>
<pre><code class="language-java">InetSocketAddress socketAddress = (InetSocketAddress) serverSocket.getRemoteAddress();</code></pre>
<p><code>InetSocketAddress</code>에는 다음과 같이 IP, 포트 정보를 리턴하는 메소드들이 있다.</p>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드명 (매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>String</td>
<td>getHostName()</td>
<td>클라이언트 IP리턴</td>
</tr>
<tr>
<td>int</td>
<td>getPort()</td>
<td>PORT 번호 리턴</td>
</tr>
<tr>
<td>String</td>
<td>toString()</td>
<td>&quot;IP 포트번호&quot; 형태의 문자열 리턴</td>
</tr>
</tbody></table>
<blockquote>
<p>더 이상 클라이언트를 위해 연결 수락할 필요가 없다면 ServerSocketChannel의 close() 메소드를 호출시킨다. 이렇게 해야만 해당 포트를 재사용할 수 있다.</p>
</blockquote>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">public class ServerExample {
  public static void main(String[] args) { 
      ServerSocketChannel channel = null;
    try {
      channel = ServerSocketChannel.open();
      channel.configureBlocking(true);
      channel.bind(new InetSocketAddress(5001));

      while(true) {
        System.out.println(&quot;연결을 기다림&quot;);
        SocketChannel socketChannel = channel.aceept();
        InetSocketAddress isa = socketChanel.getRemoteAddress();
        System.out.println(&quot;연결을 수락함 : &quot; + isa.getHostName());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    if(channel.isOpne()) {
      try {
        channel.close();
      } catch (IOException e) {
        e.printStackTrace(); 
      }
    }
  }
}</code></pre>
<h3 id="소켓-채널-생성과-연결-요청">소켓 채널 생성과 연결 요청</h3>
<ul>
<li>클라이언트가 서버 연결 요청을 할 때에는 <code>java.nio.channels.SocketChannel</code>을 이용함.</li>
<li>SocketChannel은 정적 메소드인 open() 으로 생성하고 블로킹 방식으로 동작 시키기 위해 configureBlocking(true) 메소드를 호출함. 기본적으로 블로킹 방식으로 동작하지만, 명시적으로 설정하는 이유는 넌블로킹과 구분을 위해서임.</li>
<li>서버 연결 요청은 connect()를 호출하는데, IP와 Port를 가진  InetSocketAddress를 매개값으로 줌.</li>
</ul>
<pre><code class="language-java">SocketChannel channel = SocketChannel.open();
channel.configureBlocking(true);
channel.connect(new InetSocketAddress(5001));</code></pre>
<blockquote>
<p>connect() 메소드는 서버와 연결이 될 때까지 블로킹되므로 UI 및 이벤트를 처리하는 스레드에서는 사용하지 않도록 한다. 블로킹되면 UI 갱신, 이벤트 처리를 할 수 없기 때문이다.  </p>
<p>연결된 후 클라이언트 프로그램을 종료하거나 필요에 따라 연결을 끊고 싶다면 close() 메소드를 호출한다.</p>
</blockquote>
<pre><code class="language-java">channel.close();</code></pre>
<ul>
<li>연결 요청 예제</li>
</ul>
<pre><code class="language-java">public class ClientExample {
  public static void main(String[] args) {
    SocketChannel channel = null
    try {
        channel = SocketChannel.open();
        SocketChannel.configureBlocking(true);
        System.out.println(&quot;연결 요청&quot;);
      channel.connect(new InetSocketAddress(&quot;localhost&quot;, 5001));
      System.out.println(&quot;연결 성공&quot;);
    } catch(Exception e) {
      e.printStackTrace();
    } 
      if(channel.isOpen()) {
        try{
        channel.close();
          } catch(IOException e) {
        e.printStackTrace();
      }
  } 
}</code></pre>
<h3 id="소켓-채널-데이터-통신">소켓 채널 데이터 통신</h3>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0pzrka3e5j21400jp44g.jpg" alt="image-20220329000106876"></p>
<p>클라이언트가 연결 요청(connect())을 하고 서버가 연결 수락(accept())을 하면 양쪽 SocketChannel 객체의 read(), write() 메소드를 호출해서 데이터를 통신할 수 있다. 이 메소드들은 모두 버퍼를 사용하기 때문에 버퍼로 읽고 쓰는 작업을 해야 한다.</p>
<pre><code class="language-java">// write() 메소드 이용 코드
Charset charset = Charset.forName(&quot;UTF-8&quot;); 
ByteBuffer buffer = charset.encode(&quot;Hello world&quot;);
socketChannel.write(buffer);

// 다음은 SOcketChannel의 read() 를 이용하여 문자열을 받는 코드임
ByteBuffer readBuffer = ByteBuffer.allocate(100);
int byteCnt = socketChannel.reay(readBuffer);
readBuffer.flip();
Charset charset2 = Charset.forName(&quot;UTF-8&quot;);
String message = charset2.decode(readBuffer).toString();</code></pre>
<ul>
<li>예제 1 - 데이터 보내고 받기</li>
</ul>
<pre><code class="language-java">public class ClientExmaple {
  public static void main(String[] args) {
    SocketChannel socketCHannel = null;
    try {
      socketChannel = SocketChannel.open();
      socketChannel.configureBlocking(true);
      System.out.println(&quot;연결 요청&quot;);
      socketChannel.connect(new InetSocketAddress(&quot;localhost&quot;, 5001));
      System.out.println(&quot;연결 성공&quot;);

      ByteBuffer buffer = null;
      Charset charset = Charset.forName(&quot;UTF-8&quot;);

      buffer = charset.encode(&quot;Hello Server&quot;);
      socketChannel.write(buffer);
      System.out.println(&quot;데이터 전송 성공&quot;);

      buffer = ByteBuffer.allocate(100);
      int byteCount = socketChannel.read(buffer);
      buffer.flip();
      String message = charset.decode(buffer).toString();
      System.out.println(&quot;데이터 받기 성공&quot; + message);
    } catch(Exception e) {
      ...
    }

    if (socketChannel.isOpen()) {
      try {
        socketChannel.close();
      } catch(Exception e) {
        ...
      }
    }
  }
}</code></pre>
<blockquote>
<p>연결 성공 후에 클라이언트가 먼저 &quot;Hello Server&quot;를 보내고 서버가 이 데이터를 받고 &quot;Hello Client&quot;를 클라이언트로 보내면 클라이언트가 이 데이터를 받는다.</p>
</blockquote>
<ul>
<li>예제 2 - 데이터 받고 보내기</li>
</ul>
<pre><code class="language-java">public class ServerExmaple {
  public static void main(String[] args) {

    ServerSocketChannel serverSocketChannel = null;
    try {
      serverSocketChannel = ServerSocketChannel.open();
      serverSocketChannel.configureBlocking(true);
      serverSocketChannel.bind(new InetSocketAddress(5001));
      while(true) {
        System.out.println(&quot;연결 기다림&quot;);
        SocketChannel socketChannel = ServerSocketChannel.accept();
        InetSocketAddress isa = (InetSocketAddress) socketChannel.getRemoteAddress();
        System.out.println(&quot;연결 수락함&quot; + isa.getHostName);

        ByteBuffer buffer = null;
        Charset charset = Charset.forName(&quot;UTF-8&quot;);

        buffer = ByteBuffer.allocate(100);
        int byteCount = socketChannel.read(buffer);
        buffer.flip();
        String message = charset.decode(buffer).toString();
        System.out.println(&quot;데이터 받기 성공&quot; + message);

        buffer = charset.encode(&quot;Hello Client&quot;);
        socketChannel.write(buffer);
        System.out.println(&quot;데이터 보내기 성공&quot;);
            }
    } catch(Exception e) {
      ...
    }

    if(serverSocketChannel.isOpen()) {
      try {
        serverSocketChannel.close();
      } catch(Exception e) {
        ...
      }
    }
  }
}</code></pre>
<blockquote>
<p>데이터를 받기 위해 read() 메소드를 호출하면 상대방이 데이터를 보내기 전까지는 블로킹 되는데 read() 메소드가 블로킹 해제되고 리턴되는 경우는 다음 세가지이다. </p>
</blockquote>
<table>
<thead>
<tr>
<th>블로킹이 해제되는 경우</th>
<th>리턴값</th>
</tr>
</thead>
<tbody><tr>
<td>상대방이 데이터를 보냄</td>
<td>읽은 바이트 수</td>
</tr>
<tr>
<td>상대방이 정상적으로 SocketChannel.close()를 호출</td>
<td>-1</td>
</tr>
<tr>
<td>상대방이 비정상적으로 종료</td>
<td>IOException 발생</td>
</tr>
</tbody></table>
<blockquote>
<p>상대방이 정상적으로 close()를 호출하고 연결을 끊었을 경우와 상대방이 비정상적으로 종료된 경우에는 예외 처리를 해서 이쪽도 SocketChannel을 닫기 위해 close() 메소드를 호출해야 한다.</p>
</blockquote>
<pre><code class="language-java">try {

  // 상대방이 비정상적으로 종료했을 경우 IOException 발생
  int byteCount = socketChannel.read(buffer);

  // 상대방이 정상적으로 Socket의 close() 호출했을 경우
  if(readByteCount == -1) {
        throws new IOException(); // 강제로 IOException을 발생시킴
  }

} catch (Exception e) {
    try { socketChannel.close(); } catch(Exception e2) { ... }
}</code></pre>
<h3 id="스레드-병렬-처리">스레드 병렬 처리</h3>
<p>TCP 블로킹 방식은 데이터 입출력이 완료되기 전까지 read()와 write() 메소드가 블로킹된다. 만약 애플리케이션을 실행시키는 main 스레드가 직접 입출력을 작업을다망하게 되면 입출력이 완료될 때까지 다른 작업을 할 수 없는 상태가 된다. 예를 들어 서버는 지속적으로 클라이언트의 연결 수락 기능을 수행해야 하는데, 입출력에서 블로킹되면 이 작업을 할 수 없게 된다. 그렇기 때문에 클라이언트 연결 하나에 작업 스레드 하나를 할당해서 병렬 처리해야 한다.</p>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0s90yntcjj20lf0cvdhn.jpg" alt="KakaoTalk_Photo_2022-03-30-22-47-45 001"></p>
<blockquote>
<p>위와 같이 스레드 병렬 처리를 할 경우 수천 개의 클라이언트가 동시 연결되면 수천개의 스레드가 서버에 생성되기 때문에 <strong>서버 성능이 급격히 저하되어 다운될 수 있다.</strong> 클라이언트의 폭증으로 인해 서버의 과도한 스레드 생성을 방지하려면 <strong>스레드 풀을 사용하는 것이 바람직하다.</strong></p>
</blockquote>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0s928svgtj21400nmtff.jpg" alt="KakaoTalk_Photo_2022-03-30-22-47-46 002"></p>
<blockquote>
<p>스레드 풀은 스레드 수를 제한해서 사용하기 때문 갑작스런 클라이언트의 폭증은 큐의 작업량만 증가시킬 뿐 스레드 수에는 변함이 없어 서버 성능은 완만히 저하된다. 하지만 대기하는 작업량이 증가하기 때문에 개별 클라이언트에서 받게될 응답이 느릴 수 있다. 이 경우 하드웨어 사양에 맞게 스레드 수를 늘려준다.</p>
</blockquote>
<h3 id="끝">끝?</h3>
<blockquote>
<p>NIO에 대한 중요성을 아직은 느끼지 못해서 여기서 공부를 접고, 이제 아마 다른 공부를 할 것 같다.</p>
<p>다음에 시간이 생기면 더 공부하는걸로 하겠다 끗!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[IOPackage]]></title>
            <link>https://velog.io/@seunghan-baek/IOPackage</link>
            <guid>https://velog.io/@seunghan-baek/IOPackage</guid>
            <pubDate>Thu, 31 Mar 2022 01:25:52 GMT</pubDate>
            <description><![CDATA[<h1 id="io-패키지">IO 패키지</h1>
<blockquote>
<ul>
<li>목표 : 자바의 입력과 출력 패키지에 대해 공부한다.</li>
</ul>
</blockquote>
<h2 id="입력-스트림과-출력-스트림">입력 스트림과 출력 스트림</h2>
<blockquote>
<p>프로그램이 출발지냐 도착지냐에 따라 스트림의 종류가 결정되는데, 데이터를 입력 받을 땐 <strong>입력 스트림</strong>, 데이터를 보낼 때에는 <strong>출력 스트림</strong>이라고 부른다. 입력과 출력의 기준은 항상 프로그램이다. 또한 스트림은 단방향이기 때문에 입력과 출력 스트림은 각각 따로 필요하다. 하나의 스트림으로 입/출력을 할 수는 없다.  </p>
<p>JAVA의 기본적 데이터 입출력 API는 <code>java.io</code> 패키지에서 제공한다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>java.io 패키지 주요 클래스</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>File</td>
<td>파일 시스템의 파일 정보를 얻기 위한 클래스</td>
</tr>
<tr>
<td>Console</td>
<td>콘솔로부터 문자를 입출력하기 위한 클래스</td>
</tr>
<tr>
<td>InputStream / OutputStream</td>
<td>바이트 단위 입출력을 위한 최상위 입출력 스트림 클래스</td>
</tr>
<tr>
<td>FileInputStream / FileOutputStream<br />DataInputStream / DataOutputStream<br />ObjectInputStream / ObjectOutputStream<br />PrintStream<br />BufferedInputStream / BufferedWriter</td>
<td>바이트 단위 입출력을 위한 하위 스트림 클래스</td>
</tr>
<tr>
<td>Reader / Writer</td>
<td>문자 단위 입출력을 위한 최상위 입출력 스트림 클래스</td>
</tr>
<tr>
<td>FileReader / FileWriter<br />InputStreamReader / OutputStreamReader<br />PrintWriter<br />BufferedReader / BufferedWriter</td>
<td>문자 단위 입출력을 위한 하위 스트림 클래스</td>
</tr>
</tbody></table>
<p>스트림 클래스는 크게 두 종류로 구분된다.</p>
<ol>
<li>바이트 기반 스트림 : 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 받고 보낼 수 있다.</li>
<li>문자 기반 스트림 : 문자만 받고 보낼 수 있도록 특화 되었다.</li>
</ol>
<table>
<thead>
<tr>
<th>구분</th>
<th>바이트기반 스트림</th>
<th></th>
<th>문자 기반 스트림</th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td></td>
<td><strong>입력 스트림</strong></td>
<td><strong>출력 스트림</strong></td>
<td><strong>입력 스트림</strong></td>
<td><strong>출력 스트림</strong></td>
</tr>
<tr>
<td>최상위 클래스</td>
<td>InputStream</td>
<td>OutputStream</td>
<td>Reader</td>
<td>Writer</td>
</tr>
<tr>
<td>최하위 클래스</td>
<td>XXXInputStream<br />(FileInputStream)</td>
<td>XXXOutputStream<br />(FileOutputStream)</td>
<td>XXXReader<br />(FileReader)</td>
<td>XXXWriter<br />(FileWriter)</td>
</tr>
</tbody></table>
<ul>
<li>InputStream / OutputStream : 바이트 기반 입/출력 최상위 클래스. 이 클래스들을 상속받는 하위 클래스는 접미사로 InputStream || OutputStream이 붙는다.</li>
<li>Reader / Writer : 문자 기반 입/출력 최상위 클래스. 이 클래스들을 상속받는 하위 클래스는 접미사로 Reader || Writer가 붙는다.</li>
</ul>
<h3 id="inputstream">InputStream</h3>
<p><img src="https://t1.daumcdn.net/cfile/tistory/9961443C5C1E016C2B" alt="inputStream"></p>
<p>​    출처 코딩팩토리  : <a href="https://coding-factory.tistory.com/281">https://coding-factory.tistory.com/281</a></p>
<blockquote>
<p><code>InputStream</code>이란 <strong>바이트 기반 입력 스트림의 최상위 추상클래스이다.</strong> 모든 바이트 기반 입력 스트림은 이 클래스를 상속받는다.  </p>
<p>InputStream은 읽기에 대한 다양한 추상 메소드를 정의해 두었다. 이 추상메소드를 오버라이딩하여 목적에 따라 데이터를 입력 받을 수 있다. 주요 메소드를 보자</p>
</blockquote>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>int</td>
<td>read()</td>
<td>입력 스트림으로 부터 1 바이트를 읽고 읽은 바이트를 리턴한다.</td>
</tr>
<tr>
<td>int</td>
<td>read(byte[] b)</td>
<td>입력 스트림으로부터 읽은 바이트들을 매개값으로 주어진 바이트 배열 b에 저장하고 실제로 읽은 바이트 수를 리턴한다.</td>
</tr>
<tr>
<td>void</td>
<td>close()</td>
<td>사용한 시스템 자원을 반납하고 입력 스트림을 닫는다.</td>
</tr>
</tbody></table>
<h4 id="read-메소드">read() 메소드</h4>
<blockquote>
<p>입력 스트림으로부터 1바이트를 읽고, 4바이트 int타입으로 리턴한다. 따라서 리턴된 4바이트 중 끝의 1바이트에만 데이터가 들어있다. 입력 스트림에서 5개의 바이트가 들어온다면 다음과 같이 read() 메소드로 1바이트씩 5번(int) 읽을 수 있다.  </p>
<p>더 이상 입력 스트림으로부터 바이트를 읽을 수 없다면 read() 메소드는 -1을 리턴한다. 이것을 이용하면 읽을 수 있는 마지막 바이트까지 루프를 돌려 한 바이트씩 읽을 수 있다.</p>
</blockquote>
<pre><code class="language-java">InputStream is = new FileInputStream(&quot;C:/Users/test.jpg&quot;);
int readByte; 
while((readByte = is.read()) != -1) { ... }</code></pre>
<h4 id="readbyte-b-메소드">read(byte[] b) 메소드</h4>
<blockquote>
<p>입력 스트림으로부터 매개값으로 주어진 바이트 배열의 길이만큼 바이트를 읽고 배열에 저장한다. 그리고 읽은 바이트 수를 리턴한다. 실제로 읽은 바이트 수가 배열의 길이보다 작을 경우 읽은 수만큼만 리턴한다. 길이가 3인 바이트 배열에 5개의 스트림이 들어온다면 2번 읽을 수 있다. raed(byte[] b) 메소드 또한 읽을 값이 없다면 -1을 리턴한다. 이를 통해 루프를 만들 수 있다.</p>
</blockquote>
<pre><code class="language-java">InputStream is = new FileInputStream(&quot;C:/Users/test.jpg&quot;);
int readByteNo;
byte[] readBytes = new byte[100];
while((readByteNo = is.read(readBytes)) != -1) { ... } </code></pre>
<p>위 코드는 많은 양의 바이트를 읽을 때 사용하면 좋다. 기존 read() 메소드보다 현저하게 루핑하는 횟수가 줄기 때문이다.</p>
<h4 id="readbyteb-int-off-int-len-메소드">read(byte[]b, int off, int len) 메소드</h4>
<blockquote>
<p>입력 스트림으로부터 len개의 바이트만큼 읽고, 매개값으로 주어진 바이트 배열 b[off]부터 len개 까지 저장한다. 그리고 읽은 바이트 수인 len개를 리턴한다. 실제로 읽은 바이트 수가 len개 보다 작을 경우 읽은 수만큼 리턴한다.  </p>
<p>read(byte[] b) 메소드와 차이점은 한 번에 읽어들이는 바이트 수를 len 매개값으로 조절할 수 있고, 배열에서 저장이 시작되는 인덱스를 지정할 수 있다는 점이다.</p>
</blockquote>
<h4 id="close-메소드">close() 메소드</h4>
<blockquote>
<p>InputStream을 더이상 사용하지 않을 경우에는 close() 메소드를 통해 InputStream에서 사용했던 시스템 자원을 풀어준다.</p>
</blockquote>
<h3 id="outputstream">OutputStream</h3>
<p><img src="https://t1.daumcdn.net/cfile/tistory/99C0C7335C1E049323" alt="outputStream"></p>
<p>​    출처 코딩 팩토리 : <a href="https://coding-factory.tistory.com/281">https://coding-factory.tistory.com/281</a></p>
<blockquote>
<p>바이트 기반 출력 스트림의 최상위 추상클래스이다.  </p>
<p>모든 바이트 기반 출력 스트림 클래스는 이 클래스를 상속 받아 기능을 재정의 한다. 주요 메소드를 보자</p>
</blockquote>
<h4 id="writeint-b-메소드">write(int b) 메소드</h4>
<blockquote>
<p>매개 변수로 주어진 b 값에서 끝에 있는 1바이트만 출력 스트림으로 보낸다. 매개 변수가 int 타입이므로 4바이트 모두를 보내는 것으로 오해할 수 있다.</p>
</blockquote>
<pre><code class="language-java">OutputStream os = new FileOutputStream(&quot;C:/test.txt&quot;);
byte[] data = &quot;ABC&quot;.getBytes();
for (int i = 0; i &lt; data.length; i++) {
  os.write(data[i]); // A, B, C를 하나씩 출력
}</code></pre>
<h4 id="writebyte-b-메소드">write(byte[] b) 메소드</h4>
<blockquote>
<p>매개값으로 주어진 바이트 배열의 모든 바이트를 출력 스트림으로 내보낸다.</p>
</blockquote>
<pre><code class="language-java">OutputStream os = new FileOutputStream(&quot;C:/test.txt&quot;);
byte[] data = &quot;ABC&quot;.getBytes();
os.write(data);</code></pre>
<h4 id="writebyte-b-int-off-int-len-메소드">write(byte[] b, int off, int len) 메소드</h4>
<blockquote>
<p>b[off]부터 len개의 바이트를 출력 스트림으로 보낸다.</p>
</blockquote>
<pre><code class="language-java">OutputStream os = new FileOutputStream(&quot;C:/test.txt&quot;);
byte[] data = &quot;ABC&quot;.getBytes();
os.write(data, 1, 2); // B,C만 출력</code></pre>
<h4 id="flush와-close-메소드">flush()와 close() 메소드</h4>
<blockquote>
<p>출력 스트림은 내부에 작은 buffer가 있어서 데이터가 출력되기 전에 버퍼가 쌓여있다가 순서대로 출력된다. flush() 메소드는 버퍼에 잔류하고 있는 데이터를 모두 출력시키고 버퍼를 비우는 역할을 한다. 프로그램에서 더 이상 출력할 데이터가 없다면 flush() 메소드를 마지막으로 호출하여 버퍼에 잔류하는 모든 데이터가 출력되도록 해야 한다. OutputStream을 더 이상 사용하지 않을 경우에는 close() 메소드를 통해 OutputStream에서 사용했던 시스템 자원을 풀어준다.  </p>
</blockquote>
<h3 id="reader">Reader</h3>
<blockquote>
<p>문자 기반 입력 스트림의 최상위 클래스로 추상 클래스이다.  모든 문자 기반 입력 스트림은 이 클래스를 상속 받아서 만들어진다.  </p>
<p>Reader 클래스에는 문자 기반 입력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다. 주요 메소드를 보자</p>
</blockquote>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>int</td>
<td>read()</td>
<td>입력 스트림으로부터 한 개의 문자를 읽고 리턴한다.</td>
</tr>
<tr>
<td>int</td>
<td>read(char[] cbuf)</td>
<td>입력 스트림으로부터 읽은 문자들을 매개값으로 주어진 문자 배열 cbuf에 저장하고 실제로 읽은 문자 수를 리턴한다.</td>
</tr>
<tr>
<td>int</td>
<td>read(char[] cbuf, int off, int len)</td>
<td>입력 스트림으로부터 len개의 문자를 읽고 매개값으로 주어진 문자 배열 cbuf[off]부터 len개까지 저장한다. 그리고 실제로 읽은 문자 수인 len개를 리턴한다.</td>
</tr>
<tr>
<td>void</td>
<td>close()</td>
<td>사용한 시스템의 자원을 반납하고 입력스트림을 닫는다.</td>
</tr>
</tbody></table>
<h4 id="read-메소드-1">read() 메소드</h4>
<blockquote>
<p>입력 스트림으로부터 한 개의 문자(2byte)를 읽고 4바이트 int 타입으로 리턴한다. 따라서 리턴된 4바이트 중 끝에 있는 2바이트에 문자 데이터가 들어 있다. 더 이상 입력스트림으로부터 문자를 읽을 수 없다면 -1을 리턴한다.</p>
</blockquote>
<pre><code class="language-java">Reader reader = new FileReader(&quot;C:/test.txt&quot;);
int readData;

while((readData = reader.read()) != -1) { 
    char charData = (char) readData;
}</code></pre>
<h4 id="readchar-cbuf-메소드">read(char[] cbuf) 메소드</h4>
<blockquote>
<p>입력 스트림으로부터 매개값으로 주어진 문자 배열의 길이만큼 문자를 읽고 배열에 저장한다. 그리고 읽은 문자 수를 리턴한다. 실제로 읽은 문자 수가 배열의 길이보다 작을 경우 읽은 수만큼만 리턴한다. 더 이상 입력스트림으로부터 문자를 읽을 수 없다면 -1을 리턴한다.</p>
</blockquote>
<pre><code class="language-java">Reader reader = new FileReader(&quot;C:/test.txt&quot;);
int readCharNo;
char[] cbuf = new char[2];
while((readCharNo = reader.read(cbuf)) != -1 ) { ... }</code></pre>
<h4 id="readchar-cbuf-int-off-int-len-메소드">read(char[] cbuf, int off, int len) 메소드</h4>
<blockquote>
<p>입력 스트림으로부터 len개의 문자만큼 읽고 매개값으로 주어진 문자 배열 cbuf[off]부터 len개까지 저장한다. 그리고 읽은 문자 수인 len개를 리턴한다. 실제로 읽은 문자 수가 len개 보다 작을 경우 읽은 문자 수만큼 리턴한다.</p>
</blockquote>
<h4 id="close-메소드-1">close() 메소드</h4>
<blockquote>
<p>Reader를 사용하지 않을 경우에는 close() 메소드를 통해 시스템 자원을 풀어준다.</p>
</blockquote>
<h3 id="writer">Writer</h3>
<blockquote>
<p>문자 기반 출력 스트림의 최상위 클래스로 추상 클래스이다. 모든 문자 출력 스트림 클래스는 이 클래스를 상속받아서 만들어진다.  </p>
<p>주요 메소드를 보자.</p>
</blockquote>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>void</td>
<td>write(int c)</td>
<td>출력 스트림으로 주어진 한 문자를 보낸다.(c의 끝 2바이트)</td>
</tr>
<tr>
<td>void</td>
<td>write(char[] cbuf)</td>
<td>출력 스트림으로 주어진 문자 배열 cbuf의 모든 문자를 보낸다.</td>
</tr>
<tr>
<td>void</td>
<td>write(char[] cbuf, inf off, int len)</td>
<td>출력 스트림으로 주어진 문자 배열 cbuf[off]부터 len개의 문자를 보낸다.</td>
</tr>
<tr>
<td>void</td>
<td>write(String str)</td>
<td>출력 스트림으로 주어진 문자열을 전부 보낸다.</td>
</tr>
<tr>
<td>void</td>
<td>write(String str, int off, int len)</td>
<td>출력 스트림으로 주어진 문자열 off순번부터 len개까지의 문자를 보낸다.</td>
</tr>
<tr>
<td>void</td>
<td>flush()</td>
<td>버퍼에 잔류하는 모든 문자열을 출력한다.</td>
</tr>
<tr>
<td>void</td>
<td>close()</td>
<td>시스템 자원을 반납하고 출력 스트림을 닫는다.</td>
</tr>
</tbody></table>
<h4 id="writeint-c-메소드">write(int c) 메소드</h4>
<blockquote>
<p>int값을 제공을 하게되면 int에서 끝 2바이트에 있는 문자 정보를 출력 스트림으로 보낸다. (4바이트 전부가 보내지는 것이 아니다.)</p>
</blockquote>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">Writer writer = new FileWriter(&quot;C:/Temp/test.txt&quot;);
char[] data = &quot;홍길동&quot;.toCharArray();
for(int i = 0; i &lt; data.length; i++) {
  writer.write(data[i]); // 홍 길 동 출력
}
writer.flush();
writer.close();</code></pre>
<h4 id="writechar-cbuf-메소드">write(char[] cbuf) 메소드</h4>
<blockquote>
<p>매개값으로 주어진 char[] 배열의 모든 문자를 출력 스트림으로 보낸다.</p>
</blockquote>
<pre><code class="language-java">Writer writer = new FileWriter(&quot;C:/Temp/test.txt&quot;);
char[] data = &quot;홍길동&quot;.toCharArray();
writer.write(data); // 홍길동 모두 출력
writer.flush();
writer.close();</code></pre>
<h4 id="writechar-cbuf-int-off-int-len-메소드">write(char[]) cbuf, int off, int len) 메소드</h4>
<blockquote>
<p>cbuf[off]의 위치부터 len개 까지의 문자를 출력 스트림으로 보낸다.</p>
</blockquote>
<pre><code class="language-java">Writer writer = new FileWriter(&quot;C:/Temp/test.txt&quot;);
char[] data = &quot;홍길동&quot;.toCharArray();
writer.write(data, 1, 2); // 길 동 출력
writer.flush();
writer.close();</code></pre>
<h4 id="writestring-str과-writestring-str-int-off-int-len-메소드">write(String str)과 write(String str, int off, int len) 메소드</h4>
<blockquote>
<p>Writer는 좀 더 쉽게 문자열을 내보내기 위해서 매개값이 String타입인 write(String str)메소드와 write(String str, int off, int len) 메소드를 가진다. </p>
</blockquote>
<h2 id="콘솔-입출력">콘솔 입출력</h2>
<blockquote>
<p>콘솔은 시스템을 사용하기 위해 키보드로 입력을 받고 화면으로 출력하는 소프트웨어를 말한다. </p>
<p>unix/linux 운영체제에서는 terminal이 있고 windows 운영체제는 명령프롬프트가 있다.  </p>
<p>자바는 콘솔로부터 데이터를 입력받을 떄 <code>System.in</code>, 콘솔에 출력할 때 <code>System.out</code>을, 에러를 출력할 때 <code>System.err</code>을 사용한다.</p>
</blockquote>
<h3 id="systemin-필드">System.in 필드</h3>
<blockquote>
<p>System 클래스에는 정적 필드 <code>in</code>이 있다. <code>System.in</code>은 InputStream 타입의 필드이므로 다음과 같이 변수로 참조가 가능하다. <code>InputStream is = System.in;</code>  </p>
<p>키보드로부터 어떤 키가 입력되었는지 확인하려면 read() 메소드로 한 바이트를 읽으면 된다.  </p>
<p><code>int asciiCode = is.read();</code>  </p>
<p>컴퓨터는 0과 1만을 이해할 수 있다. 그리고 아스키코드는 1byte로 표현되는 256가지의 숫자에 영어 알파벳, 아라비아 숫자, 특수 기호를 매칭하고 있다. 이러한 숫자로 된 아스키코드 대신 문자를 직접 얻고 싶다면 read() 메소드로 얻은 아스키 코드를  char로 형변환 하면 된다.  </p>
<p><code>char inputChar = (char) is.read();</code></p>
</blockquote>
<h3 id="systemout-필드">System.out 필드</h3>
<blockquote>
<p>콘솔로 데이터를 출력하기 위해서는 System 클래스의  <code>out</code> 정적 필드를 사용한다. <code>out</code>은 PrintStream 타입의 필드이다.  </p>
<p><code>OutputStream os = System.out;</code>  </p>
<p>콘솔로 1개의 바이트를 출력하려면  OutputStream의 write(int b)메소드를 이용하면 된다. 이때 바이트 값은 아스키코드인데, write() 메소드는 아스키 코드를 문자로 콘솔에 출력한다. 예를 들어 아스키 코드 97번을 write(int b)로 출력하면 &#39;a&#39;가 출력된다.</p>
</blockquote>
<ul>
<li>System 클래스의 out 필드를 OutputStream으로 변환해서 사용하는 것은 그리 편하지 않다. 따라서 PrintStream의 print(), println() 등을 사용하여 좀 더 쉬운 방법으로 다양한 타입의 데이터를 콘솔에 출력할 수 있다.</li>
</ul>
<h3 id="console-클래스">Console 클래스</h3>
<blockquote>
<p>자바 6부터 콘솔에서 입력받은 문자열을 쉽게 읽을 수 있도록 <code>java.io.Console</code> 클래스를 제공하고 있다. Console객체는 <code>System.console()</code>로 호출하여 얻으면 된다.</p>
</blockquote>
<ul>
<li>이클립스에서 실행하면 null을 리턴하기 때문에 명령 프롬프트에서 실행해야 한다.</li>
<li>Console 클래스 읽기 메소드 정리</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>String</td>
<td>readLine()</td>
<td>Enter키를 입력하기 전의 모든 문자열을 읽음</td>
</tr>
<tr>
<td>char[]</td>
<td>readPassword()</td>
<td>키보드 입력 문자를 콘솔에 보여주지 않고 문자열을 읽음</td>
</tr>
</tbody></table>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">public class ConsoleExample {
  public static void main(String[] args) {
    Console console = System.console();

    System.out.println(&quot;아이디 : &quot;);
    String id = console.readLine();

    System.out.println(&quot;비밀번호 : &quot;);
    char[] charPass = console.readPassword();
    String pw = new String(charPass);

    System.out.println(id);
    System.out.println(pw);
  }
}</code></pre>
<h3 id="scanner-클래스">Scanner 클래스</h3>
<blockquote>
<p>Console 클래스는 문자열은 읽을 수 있지만, 정수, 실수 값은 바로 읽을 수 없다. <code>java.io</code> 패키지의 클래스는 아니지만 <code>java.util</code> 패키지의 Scanner 클래스를 이용하면 콘솔로부터 기본 타입의 값을 바로 읽을 수 있다. <strong>콘솔에서만 Scanner 클래스가 사용되는 것은 아니고 File, InputStream, Path 등과 같이 다양한 입력 소스를 지정할 수도 있다.</strong></p>
<p><code>Scanner sc = new Scanner(System.in);</code> </p>
</blockquote>
<h2 id="파일-입출력">파일 입출력</h2>
<h3 id="file-클래스">File 클래스</h3>
<blockquote>
<p>IO 패키지에서 제공하는 File 클래스는 파일크기, 속성, 이름 등의 정보를 얻어내는 기능과 파일 생성 및 삭제 기능을 제공하고 있다. 그리고 디렉토리를 생성하고 디렉토리에 존재하는 파일 리스트를 얻어내는 기능도 있다. 그러나 파일의 데이터를 읽고 쓰는 기능은 지원하지 않는다. 파일의 입출력은 Stream을 사용한다.</p>
<p><code>File file = new File(&quot;C:/file/test.txt&quot;);</code></p>
<p><code>File file = new File(&quot;C:\\file\\test.txt&quot;);</code></p>
<ul>
<li>구분자 : 디렉터리 구분자는 운영체제마다 조금씩 다르다. 윈도우의 경우 <code>/</code>, <code>\</code>를 사용할 수 있고, 유닉스나 리눅스는 <code>/</code>를 사용한다.  <code>File.seperator</code>를 출력하면 해당 운영체제에서 사용하는 구분자를 확인할 수 있다.</li>
</ul>
<p>또한 단지 File 객체를 생성 및 초기화 했다고 해서 파일 || 디렉토리가 생성된 것은 아니다. 해당 객체로 실제 파일이나 디렉토리가 있는지 확인하려면 boolean타입의 <code>exist()</code>를 호출할 수 있다.</p>
</blockquote>
<ul>
<li>exist()의 리턴값이 false라면 이러한 메소드를 사용할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>boolean</td>
<td>createNewFile()</td>
<td>새로운 파일을 생성</td>
</tr>
<tr>
<td>boolean</td>
<td>mkdir()</td>
<td>새로운 디렉토리를 생성</td>
</tr>
<tr>
<td>boolean</td>
<td>mkdirs()</td>
<td>경로상에 없는 디렉토리들을 생성</td>
</tr>
<tr>
<td>boolean</td>
<td>delete()</td>
<td>파일 또는 디렉토리 삭제</td>
</tr>
</tbody></table>
<ul>
<li>파일 또는 디렉토리가 존재할 경우에는 다음 메소드를 사용할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>boolean</td>
<td>canExcute()</td>
<td>실행할 수 있는 파일인지 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>canRead()</td>
<td>읽을 수 있는 파일인지 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>canWrite()</td>
<td>수정 및 저장할 수 있는 파일인지 여부</td>
</tr>
<tr>
<td>String</td>
<td>getName()</td>
<td>파일의 이름을 리턴</td>
</tr>
<tr>
<td>String</td>
<td>getParent()</td>
<td>파일 상위 디렉토리의 이름을 리턴</td>
</tr>
<tr>
<td>File</td>
<td>getParentFile()</td>
<td>부모 디렉토리를 File객체로 생성 후 리턴</td>
</tr>
<tr>
<td>String</td>
<td>getPath()</td>
<td>전체 경로를 리턴</td>
</tr>
<tr>
<td>boolean</td>
<td>isDirectory()</td>
<td>디렉토리인지 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>isFile()</td>
<td>파일인지 여부</td>
</tr>
<tr>
<td>boolean</td>
<td>isHidden()</td>
<td>숨겨진 파일인지 여부</td>
</tr>
<tr>
<td>long</td>
<td>lastModified()</td>
<td>마지막 수정 날짜 및 시간을 리턴</td>
</tr>
<tr>
<td>long</td>
<td>length()</td>
<td>파일의 크기를 리턴</td>
</tr>
<tr>
<td>String[]</td>
<td>list()</td>
<td>디렉토리에 포함된 파일 및 서브디렉토리 목록 전부를 String 배열로 리턴</td>
</tr>
<tr>
<td>String[]</td>
<td>list(FilenameFilter filter)</td>
<td>디렉토리에 포함된 파일 및 서브디렉토리 목록 중에 FilenameFileter에 맞는 것만 String 배열로 리턴</td>
</tr>
<tr>
<td>File[]</td>
<td>listFiles()</td>
<td>디렉토리에 포함된 파일 및 서브 디렉토리 목록 전부를 File 배열로 리턴</td>
</tr>
<tr>
<td>File[]</td>
<td>listFiles(FilnameFilter filter)</td>
<td>디렉토리에 포함된 파일 및 서브디렉토리 목록 중에 FilenameFileter에 맞는 것만 File 배열로 리턴</td>
</tr>
</tbody></table>
<h3 id="fileinputstream">FileInputStream</h3>
<blockquote>
<p>파일로부터 바이트 단위로 읽어들일 때 사용하는 바이트 기반 입력 스트림이다. 바이트 단위로 읽기 때문에 그림, 오디오, 비디오, 텍스트 파일 등 모든 종류의 파일을 읽을 수 있다. 다음은 FileInputStream을 생성하는 두 가지 방법을 보여준다.</p>
</blockquote>
<pre><code class="language-java">// # 1
FileInputStream fis = new FileInputStream(&quot;C:/temp/test.txt&quot;);

// # 2
File file = new File(&quot;C:/temp/test.txt&quot;)
FileInputStream fis = new FIleInputStream(file);</code></pre>
<blockquote>
<p>만약 파일이 존재하지 않을시  FileNotFoundException을 발생시킨다. 따라서 try-catch로 예외 처리를 해야한다.</p>
</blockquote>
<h3 id="fileoutputstream">FileOutputStream</h3>
<blockquote>
<p>바이트 단위로 데이터를 파일에 저장할 때 사용하는 바이트 기반의 출력 스트림이다. 바이트 단위로 저장하기 때문에 그림, 오디오, 비디오 등 모든 종류의 데이터를 파일로 저장할 수 있다. </p>
</blockquote>
<pre><code class="language-java">// # 1
FileOutputStream fis = new FileOutputStream(&quot;C:/temp/test.txt&quot;);

// # 2
File file = new File(&quot;C:/temp/test.txt&quot;)
FileOutputStream fis = new FileOutputStream(file);</code></pre>
<blockquote>
<p>파일이 이미 존재할 경우 데이터를 출력하면 파일을 덮어쓰게 되므로 기존의 파일 내용은 사라지게 된다. 기존의 파일 내용 끝에 데이터를 추가할 경우에는 생성자의 두 번째 매개값으로 <code>true</code>를 주면 된다.</p>
</blockquote>
<pre><code class="language-java">// # 1
FileOutputStream fis = new FileOutputStream(&quot;C:/temp/test.txt&quot;, true);

// # 2
File file = new File(&quot;C:/temp/test.txt&quot;)
FileOutputStream fis = new FileOutputStream(file, true);</code></pre>
<h3 id="filereader">FileReader</h3>
<blockquote>
<p>텍스트 파일을 프로그램으로 읽어들일 때 사용하는 문자 기반 스트림이다. 문자 단위로 읽기 때문에 텍스트가 아닌 그림, 오디오, 비디오 등은 읽을 수 없다. FileReader를 생성하는 두가지 방법을 보자</p>
</blockquote>
<pre><code class="language-java">// # 1
FileReader reader = new FileReader(&quot;C:/temp/test.txt&quot;);

// # 2
File file = new File(&quot;C:/temp/test.txt&quot;);
FileReader reader = new FileReader(file);</code></pre>
<blockquote>
<p>객체가 생성될 때 파일과 직접 연결이 되는데, 만약 파일이 존재하지 않으면 <code>FileNotFoundException</code>을 발생시키므로 try-catch로 감싸줘야 한다. </p>
</blockquote>
<h3 id="filewriter">FileWriter</h3>
<blockquote>
<p>텍스트 데이터를 파일에 저장할 때 사용하는 문자 기반 스트림이다. 문자 단위로 저장하기 때문에 텍스트 외의 다른 파일 등은 저장할 수 없다. FileWriter를 생성하는 두가지 방법을 보자</p>
</blockquote>
<pre><code class="language-java">// # 1
FileWriter writer = new FileWriter(&quot;C:/temp/test.txt&quot;);

// # 2
File file = new File(&quot;C:/temp/test.txt&quot;);
FileWriter writer = new FileWriter(file);</code></pre>
<blockquote>
<p>주의할 점으로 경로에 해당 파일이 이미 존재할 경우 덮어쓰여지므로 원래 파일의 내용은 사라지게 된다. 기존의 파일 내용 끝에 데이터를 추가하는 경우에는 생성자 두 번째 매개값으로 <code>true</code>를 주자.</p>
</blockquote>
<h2 id="보조-스트림">보조 스트림</h2>
<blockquote>
<p>다른 스트림과 연결되어 여러 가지 편리한 기능을 제공해주는 스트림을 말한다. 보조 스트림을 필터 스트림이라고도 하는데, 이는 보조 스트림의 일부가 FileInputStream, FileOutputStream의 하위 클래스이기 때문이다. 하지만 다른 보조 스트림은 이 클래스를 상속받지 않는다.  </p>
<p>보조 스트림은 자체적으로 입출력을 수행할 수 없기 때문에 입력 소스와 바로 연결되는 InputStream, FileInputStream, Reader, FileReader, 출력 소스와 바로 연결되는 OutputStream, FileOutputStream, Writer, FileWriter 등에 연결해서 입출력을 수행한다.  보조 스트림은 문자 변환, 입출력 성능 향상, 기본 데이터 타입 입출력, 객체 입출력 등의 기능을 제공한다.</p>
</blockquote>
<ul>
<li>보조 스트림을 생성할 떄에는 자신이 연결될 스트림을 다음과 같이 생성자의 매개값으로 받는다.</li>
</ul>
<pre><code class="language-java">보조스트림 변수 = new 보조스트림(연결스트림);</code></pre>
<ul>
<li>예를 들어 콘솔 입력 스트림을 문자 변환 보조 스트림인 InputStreamReader에 연결하는 코드는 다음과 같다.</li>
</ul>
<pre><code class="language-java">InputStream is = System.in;
InputStreamReader reader = new InputStreamReader(is);</code></pre>
<ul>
<li>문자 변환 보조 스트림인 InputStreamReader를 다시 성능 향상 보조 스트림인 BufferedReader에 연결하는 코드는 다음과 같다.</li>
</ul>
<pre><code class="language-java">InputStream is = System.in;
InputStreamReader reader = new InputStreamReader(is);
BufferedReader br = new BufferedReader(reader);</code></pre>
<h3 id="문자-변환-보조-스트림">문자 변환 보조 스트림</h3>
<blockquote>
<p>소스 스트림이 바이트 기반 스트림(InputStream, OutputStream, FileInputStream, FileOutputStream)이면서 입출력 데이터가 문자라면  Reader와 Writer로 변환해서 사용하는 것을 고려해야 한다.  </p>
<p>그 이유는 Reader와 Writer는 문자 단위로 입출력하기 때문에 바이트 기반 스트림보다는 편하고 문자셋의 종류를 지정할 수 있기 때문에 다양한 문자를 입출력할 수 있다.</p>
</blockquote>
<h4 id="inputstreamreader">InputStreamReader</h4>
<blockquote>
<p>바이트 입력 스트림에 연결되어 문자 입력 스트림인 Reader로 변환시키는 보조 스트림이다.</p>
</blockquote>
<pre><code class="language-java">Reader reader = new InputStreamReader(바이트입력스트림);</code></pre>
<ul>
<li>예를 들어 콘솔 입력을 위한 문자 변환 보조 스트림인 InputStream을 다음과 같이 Reader 타입으로 변환할 수 있다.</li>
</ul>
<pre><code class="language-java">InputSteream is = System.in;
Reader reader = new InputStream(is);</code></pre>
<ul>
<li>파일 입력을 위한 FileInputStream도 다음과 같이 Reader 타입으로 변환시킬 수 있다.</li>
</ul>
<pre><code class="language-java">FileInputStream fis = new FileInputStream(&quot;C:/Temp/file.txt&quot;);
Reader reader = new InputStreamReader(fis);</code></pre>
<blockquote>
<p>FileInputStream에 InputStreamReader를 연결하지 않고 FileReader를 직접 생성할 수도 있다. FileReader는  InputStreamReader의 하위 클래스이다. 이것은 FileReader가 내부적으로 FileInputStream에 InputStreamReader 보조 스트림을 연결한 것이라고 볼 수 있다.</p>
</blockquote>
<h4 id="outputstreamreader">OutputStreamReader</h4>
<blockquote>
<p>바이트 출력 스트림에 연결되어 문자 출력 스트림인  Writer로 변환시키는 보조 스트림이다. </p>
</blockquote>
<pre><code class="language-java">Writer writer = new OutputStreamWriter(바이트출력스트림);</code></pre>
<ul>
<li>예를 들어 파일 출력을 위한 FileOutputStream을 다음과 같이 Writer 타입으로 변환할 수 있다.</li>
</ul>
<pre><code class="language-java">FileOutputStream fos = new FileOutputStream(&quot;C:/Temp/test.txt&quot;);
Writer writer = new OutputStreamWriter(fos);</code></pre>
<blockquote>
<p>FileOutputStream에 OutputStreamWriter를 연결하지 않고 FileWriter를 직접 생성할 수도 있다.  </p>
<p>FileWriter는 OutputStreamWriter의 하위 클래스이다. 이것은 FileWriter가 내부적으로 FileOutputStream에 OutputStreamWriter 보조 스트림을 연결한 것으로 볼 수 있다. </p>
</blockquote>
<h3 id="성능-향상-보조-스트림">성능 향상 보조 스트림</h3>
<blockquote>
<p>프로그램의 실행 성능은 입출력이 가장 늦은 장치를 따라간다. CPU와 메모리가 아무리 뛰어나도 하드 디스크의 입출력이 늦어지면 프로그램의 실행 성능은 하드 디스크의 처리 속도에 맞춰진다. 네트워크로 데이터를 전송할 때도 마찬가지다. 이러한 문제에 대한 완전한 해결책은 될 수 없지만, 프로그램이 입출력 소스와 직접 작업하지 않고 중간에 메모리 버퍼와 작업함으로써 실행 성능을 향상시킬 수 있따. 예를 들어 프로그램은 직접 하드 디스크에 데이터를 보내지 않고 메모리 버퍼에 데이터를 보냄으로써 쓰기 속도가 향상된다. <strong>버퍼는 데이터가 쌓이길 기다렸다가 꽉 차게 되면 데이터를 한 번에 하드디스크로 보냄으로써 출력 횟수를 줄여준다.</strong>  </p>
<p>보조 스트림 중에서는 위와 같이 <strong>메모리 버퍼를 제공하여 프로그램의 성능을 향상시키는 것</strong>들이 있다.</p>
</blockquote>
<h4 id="bufferedinputstream과-bufferedreader">BufferedInputStream과 BufferedReader</h4>
<blockquote>
<p><code>BufferedInputStream</code> : 바이트 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다. <strong>최대 8192 바이트</strong></p>
<p><code>BufferedReader</code> : 문자 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다. <strong>최대 8192 문자</strong></p>
<p>둘 다 입력 소스로부터 자신의 내부 버퍼 크기만큼 데이터를 미리 읽고 버퍼에 저장해둔다. 프로그램은 외부의 입력 소스로부터 직접 읽는 대신 버퍼로부터 읽음으로써 읽기 성능이 향상된다.  </p>
<p><code>BufferedInputStream</code>과 <code>BufferedReader</code> 보조 스트림은 다음과 같이 생성자의 매개값으로 준 입력스트림과 연결되어 8192 내부 버퍼 사이즈를 가지게 된다. </p>
<p>데이터를 읽어들이는 방법은 InputStream || Reader와 동일하다.</p>
</blockquote>
<pre><code class="language-java">BufferedInputStream bis = new BufferedInputStream(바이트 입력 스트림);
BufferedReader br = new BufferedReader(문자 입력 스트림);</code></pre>
<h4 id="bufferedoutputstream과-bufferedwriter">BufferedOutputStream과 BufferedWriter</h4>
<blockquote>
<p><code>BufferedOutputStream</code> : 바이트 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다. <strong>최대 8192 바이트</strong></p>
<p><code>BufferedWriter</code> : 문자 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다. <strong>최대 8192 문자</strong></p>
<p>둘 다 프로그램에서 전송한 데이터를 내부 버퍼에 쌓아두었다가 꽉 차면 버퍼의 모든 데이터를 한꺼번에 보낸다. 프로그램 입장에서 보면 직접 데이터를 보내는 것이 아니라, 메모리 버퍼로 데이터를 고속 전송하기 때문에 성능이 향상되는 효과를 얻게 된다.  </p>
<p>데이터를 출력하는 방법은  OutputStream || Writer와 동일하다.</p>
</blockquote>
<pre><code class="language-java">BufferedOutputStream bos = new BufferedOutputStream(바이트 출력 스트림);
BufferedWriter bw = new BufferedWriter(문자 출력 스트림);</code></pre>
<h3 id="기본-타입-입출력-보조-스트림">기본 타입 입출력 보조 스트림</h3>
<blockquote>
<p>바이트 스트림은 바이트 단위로 입출력하기 때문에 자바의 기본 데이터 타입인 boolean, char, short, int, long, float, double 단위로 입출력할 수 없다. 그러나 <code>DataInputStream</code>과 <code>DataOutputStream</code> 보조 스트림을 연결하면 기본 데이터 타입으로 입출력이 가능하다. 객체 생성 방법에 대해 알아보자.</p>
</blockquote>
<pre><code class="language-java">DataInputStream dis = new DataInputStream(바이트 입력 스트림);
DataOutputStream dos = new DataOutputStream(바이트 출력 스트림);</code></pre>
<ul>
<li><code>DataInputStream</code>과 <code>DataOutputStream</code>이 가지는 메소드</li>
</ul>
<table>
<thead>
<tr>
<th>DataInputStream</th>
<th></th>
<th>DataOutputStream</th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>boolean</td>
<td>readBoolean()</td>
<td>void</td>
<td>writeBoolean(boolean v)</td>
</tr>
<tr>
<td>byte</td>
<td>readByte()</td>
<td>void</td>
<td>writeByte(int v)</td>
</tr>
<tr>
<td>char</td>
<td>readChar()</td>
<td>void</td>
<td>writeChar(char v)</td>
</tr>
<tr>
<td>double</td>
<td>readDouble()</td>
<td>void</td>
<td>writeDouble(double v)</td>
</tr>
<tr>
<td>float</td>
<td>readFloat()</td>
<td>void</td>
<td>writeFloat(float v)</td>
</tr>
<tr>
<td>int</td>
<td>readInt()</td>
<td>void</td>
<td>writeInt()</td>
</tr>
<tr>
<td>long</td>
<td>readLong()</td>
<td>void</td>
<td>writeLong()</td>
</tr>
<tr>
<td>short</td>
<td>readShort()</td>
<td>void</td>
<td>writeShort()</td>
</tr>
<tr>
<td>String</td>
<td>readString()</td>
<td>void</td>
<td>writeString()</td>
</tr>
</tbody></table>
<p>이 메소드들로 입출력할 떄의 주의점은 <strong>데이터 타입의 크기가 모두 다르므로 DataOutputStream으로 출력한 데이터를 다시  DataInputStream으로 읽어올 때는 출력한 순서와 동일한 순서로 읽어야 한다는 점</strong>이다.</p>
<p>예를 들어 int ▶️ boolean ▶️ double의 출력 순서를 가지고 있다면 입력 순서도 int ▶️ boolean ▶️ double의 순서를 지켜야 한다는 것이다.</p>
<h3 id="프린터-보조-스트림">프린터 보조 스트림</h3>
<blockquote>
<p><code>PrintStream</code>과 <code>PrintWriter</code>는 프린터와 유사하게 출력하는 print(), println() 메소드를 가지고 있는 보조 스트림이다. System.out이 바로 PrintStream 타입이기 때문에 print(), println() 메소드를 사용할 수 있었다. PrintStream은 바이트 출력 스트림과 연결되고, PrintWriter는 문자 출력 스트림으로 연결된다. 둘 다 거의 같은 기능을 가지고 있다.</p>
</blockquote>
<pre><code class="language-java">PrintStream ps = new PrintStream(바이트출력스트림);
PrintWriter pw = new PrintWriter(문자출력스트림);</code></pre>
<ul>
<li>print()와 println()은 다음과 같이 오버로딩이 되어 있다.(데이터 타입에 따라)</li>
</ul>
<table>
<thead>
<tr>
<th>PrintStream</th>
<th>PrintWriter</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>void</td>
<td>print(boolean b)</td>
<td>void</td>
<td>println(boolean b)</td>
</tr>
<tr>
<td>void</td>
<td>print(char c)</td>
<td>void</td>
<td>println(char c)</td>
</tr>
<tr>
<td>void</td>
<td>print(double d)</td>
<td>void</td>
<td>println(double d)</td>
</tr>
<tr>
<td>void</td>
<td>print(float f)</td>
<td>void</td>
<td>println(float f)</td>
</tr>
<tr>
<td>void</td>
<td>print(long l)</td>
<td>void</td>
<td>println(long l)</td>
</tr>
<tr>
<td>void</td>
<td>print(int i)</td>
<td>void</td>
<td>println(int i)</td>
</tr>
<tr>
<td>void</td>
<td>print(Object o)</td>
<td>void</td>
<td>println(Object o)</td>
</tr>
<tr>
<td>void</td>
<td>print(String s)</td>
<td>void</td>
<td>println(String s)</td>
</tr>
</tbody></table>
<blockquote>
<p>그 외에 printf()도 제공한다. printf()는 형식화된 문자열을 출력할 수 있도록 하기 위해 자바 5부터 추가된 메소드이다.  </p>
<p>첫 번째 매개값으로 형식화된 문자열을 지정하고, 두 번째 매개값부터 형식화된 문자열에 들어갈 값을 나열해주면 된다.  </p>
<p><code>printf(String format, Object ... args)</code> </p>
</blockquote>
<ul>
<li><code>format</code> :<ul>
<li>%[argument_index$] [flags] [width] [.precision] converison</li>
<li><code>매개값의 순번</code>, <code>-,0</code>, <code>전체 자릿수</code>, <code>소수 자릿수</code>, <code>변환 문자</code></li>
<li>형식화된 문자열에서 %와 conversion은 필수적으로 작성하고 그 이외의 항목은 생략할 수 있다. <code>argument_index$</code>는 <strong>적용할 매개값의 순번</strong>인데 <code>1$</code>는 첫 번째 매개값, <code>2$</code>는 두 번째 매개값을 말한다. <code>flags</code>는 빈공간을 채우는 방법인데, 생략되면 <strong>왼쪽이 공백으로 채워지고, <code>-</code>는 오른쪽이 공백으로 채워진다. *<em><code>width</code>는 *</em>전체 자릿수</strong>이며, <code>precision</code>은 <strong>소수자릿수</strong>를 뜻한다. 변환문자에는 정수(d), 실수(f), 문자열(s)과 시간과 관련된 문자가 와서 매개값을 해당 타입으로 출력한다. 형식화된 문자열에 대한 자세한 내용은  Java API 도큐먼트에서 <code>java.util.Formatter</code> 클래스의 Format String syntax 부분을 읽어보면 된다.</li>
<li>형식화된 문자열에서 자주 사용되는 것</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>형식화된 문자</th>
<th></th>
<th>설명</th>
<th>출력형태</th>
</tr>
</thead>
<tbody><tr>
<td>정수</td>
<td>%d<br />%6d<br />%-6d<br />%06d</td>
<td>정수<br />6자리 정수, 왼쪽 빈자리 공백<br />6자리 정수, 오른쪽 빈자리 공백<br />6자리 정수, 왼쪽 빈자리 0채움</td>
<td>123<br /><strong>___</strong>123<br />123_<strong>__</strong><br />000123</td>
</tr>
<tr>
<td>실수</td>
<td>%10.2f<br />%-10.2f<br />%010.2f</td>
<td>소수점 이상 7자리, 소수점 이하 2자리, 왼쪽 빈자리 공백<br />소주점 이상 7자리, 소수점 이하 2자리, 오른쪽 빈자리 공백<br />소수점이상 7자리, 소수점 이하 2자리, 왼쪽 빈자리 0으로 채움</td>
<td><strong>___</strong>123.45<br />123.45___<br />0000123.45</td>
</tr>
<tr>
<td>문자열</td>
<td>%s<br />%6s<br />%-6s</td>
<td>문자열<br />6자리 문자열 왼쪽 빈자리 공백<br />6자리 문자열 오른쪽 빈자리 공백</td>
<td>abc<br /><strong>___</strong>abc<br />abc_______</td>
</tr>
<tr>
<td>날짜</td>
<td>%tF<br />%tY<br />%ty<br />%tm<br />%td<br />%tH<br />%tl<br />%tM<br />%tS</td>
<td>%tY-%tm-%td<br />4자리 년<br />2자리 년<br />2자리 월<br />2자리 일<br />2자리 시(0 ~ 23)<br />시 (0 ~ 12)<br />2자리 분<br />2자리 초</td>
<td>2010-01-06<br />2010<br />10<br />01<br />06<br />08<br />8<br />06<br />24</td>
</tr>
<tr>
<td>특수문자</td>
<td>\t<br />\n<br />%%</td>
<td>탭 (tab)<br />줄바꿈<br />%</td>
<td><br /><br />%</td>
</tr>
</tbody></table>
<h3 id="printf-예제">printf() 예제</h3>
<pre><code class="language-java">import java.util.Date;

public class PrintExample {
    public static void main(String[] args) {
        System.out.printf(&quot;상품의 가격 : %d원\n&quot;, 123);
        System.out.printf(&quot;상품의 가격 : %6d원\n&quot;, 123);
        System.out.printf(&quot;상품의 가격 : %-6d원\n&quot;, 123);
        System.out.printf(&quot;상품의 가격 : %06d원\n&quot;, 123);

        System.out.printf(&quot;반지름이 %d인 원의 넓이 : %10.2f\n&quot;, 10, Math.PI * 10 * 10);

        System.out.printf(&quot;%6d | %-10s | %10s\n&quot;, 1, &quot;홍길동&quot;, &quot;도적&quot;);

        Date now = new Date();
        System.out.printf(&quot;오늘은 %tY년 %tm월 %td일 입니다\n&quot;, now, now, now);
        System.out.printf(&quot;오늘은 %1$tY년 %1$tm월 %1$td일 입니다\n&quot;, now);
        System.out.printf(&quot;현재 %1$tH시 %1$tM분 %1$tS초 입니다\n&quot;, now);
    }
}</code></pre>
<p>​    </p>
<h3 id="객체-입출력-보조-스트림">객체 입출력 보조 스트림</h3>
<blockquote>
<p>자바는 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수가 있다. 객체는 문자가 아니기 때문에 바이트 기반 스트림으로 출력해야 한다. 객체를 출력하기 위해서는 객체의 데이터를 일렬로 늘어선 연속적인 바이트로 변경해야 하는데, 이것을 <strong>객체 직렬화(Serialization)라고 한다.</strong> 반대로 파일에 저장되어 있거나 네트워크에서 전송된 객체를 읽을수도 있는데 입력 스트림으로부터 읽어들인 연속적인 바이트를 객체로 복원하는 것을 <strong>역직렬화(deserialization)</strong>이라고 한다.</p>
</blockquote>
<h4 id="objectinputstream-objectoutputstream">ObjectInputStream, ObjectOutputStream</h4>
<blockquote>
<ul>
<li>ObjectOutputStream : 바이트 출력 스트림과 연결되어 객체를 직렬화하는 역할을 한다.</li>
<li>ObjectInputStream : 바이트 입력 스트림과 연결되어 객체로 역직렬화 하는 역할을 한다.</li>
</ul>
</blockquote>
<pre><code class="language-java">// # 생성하는 방법
ObjectInputStream ois = new ObjectInputStream(바이트입력스트림);
ObjectOutputStream oos = new ObjectOutputStream(바이트출력스트림);

// ObjectOutputStream으로 객체를 직렬화하기 위해서는 writeObject() 메소드를 사용한다.
oss.writeObejct(객체);

// 반대로 ObjectInputStream의 readObject() 메소드는 입력 스트림에서 읽은 바이트를 역직렬화해서 객체로 생성한다. readObject()의 리턴 타입은 Object이기 때문에 객체 원래의 타입으로 캐스팅 해주어야 한다.
객체타입 변수 = (객체타입) ois.readObject();</code></pre>
<h4 id="예제">예제</h4>
<pre><code class="language-java">import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class ObjectInputOutputStreamExample {
    public static void main(String[] args) throws Exception {
        String path = &quot;/Object.dat&quot;;
        FileOutputStream fos = new FileOutputStream(path);
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        oos.writeObject(new Integer(10));
        oos.writeObject(new Double(3.14));
        oos.writeObject(new int[]{1, 2, 3});
        oos.writeObject(new String(&quot;홍길동&quot;));

        oos.flush();
        oos.close();
        fos.close();

        FileInputStream fis = new FileInputStream(path);
        ObjectInputStream ois = new ObjectInputStream(fis);

        Integer obj1 = (Integer) ois.readObject();
        Double obj2 = (Double) ois.readObject();
        int[] obj3 = (int[]) ois.readObject();
        String obj4 = (String) ois.readObject();

        ois.close();
        fis.close();

        System.out.println(obj1);
        System.out.println(obj2);
        System.out.println(obj3[0] + &quot;&quot; + obj3[1] + &quot;&quot; + obj3[2]);
        System.out.println(obj4);

    }
}</code></pre>
<h4 id="직렬화가-가능한-클래스seriallizable">직렬화가 가능한 클래스(Seriallizable)</h4>
<blockquote>
<p>자바는 Serializable 인터페이스를 구현한 클래스만 직렬화가 가능하도록 제한하고 있다. Serializable 인터페이스는 필드나 메소드가 없는 빈 인터페이스지만, 객체를 직렬화할 때 private 필드를 포함한 모든 필드를 바이트로 변환해도 좋다는 표시 역할을 한다.</p>
</blockquote>
<pre><code class="language-java">public class XXX implements Serializable { }</code></pre>
<blockquote>
<p>객체를 직렬화하면 바이트로 변환되는 것은 필드들이고 생성자 및 메소드는 직렬화에 포함되지 않는다. 따라서 역직렬화할 때에는 필드의 값만 복원된다. 하지만 모든 필드가 직렬화 대상이 되는 것은 아니다. 필드 선언에 static 또는 는  transient가 붙어 있을 경우에는 직렬화가 되지 않는다.</p>
</blockquote>
<pre><code class="language-java">public class XXX implements Serializable {
  public int field1;
  public int field2;
  int field3;
  private int field4;
  public static int field5; // static 키워드가 붙어 직렬화가 되지 않음
  transient int field6; // transient 키워드가 붙어 직렬화가 되지 않음
}</code></pre>
<h4 id="serialversionuid-필드">serialVersionUID 필드</h4>
<blockquote>
<p> 직렬화된 객체를 역직렬화 할 때는 직렬화했을 때와 같은 클래스를 사용해야 한다. 클래스의 이름이 같더라도 클래스의 내용이 변경되면 역직렬화는 실패하며 다음과 같은 예외가 발생한다.</p>
</blockquote>
<p><code>java.io.InvalidClassException: XXX; local class incompatible: stream classdesc
serialVersionUID = -975398275932579136, local class serialVersionUID = -12131123948237592845</code></p>
<p>: 직렬화 할 때와 역직렬화 할 때 사용된 클래스의  serialVersionUID가 다르다는 것이다. serialVersionUID는 같은 클래스임을 알려주는 식별자 역할을 하는데, Serializable 인터페이스를 구현한 클래스를 컴파일하면 자동적으로 이 정적 필드가 추가 된다. 문제는 재 컴파일하면 이 UID값이 달라진다는 것이다. 네트워크로 객체를 직렬화하여 전송하는 경우, 보내는 쪽과 받는 쪽이 모두 같은 serialVersionUID를 갖는 클래스를 가지고 있어야 하는데, 한 쪽에서 클래스를 변경해서 재컴파일하면 다른 serialVerisonUID를 가지게 되므로 역직렬화에 실패한다.</p>
<ul>
<li>SerialVersionUID 변경에 따른 예외 발생 예제</li>
</ul>
<ol>
<li>직렬화가 가능한 클래스</li>
</ol>
<pre><code class="language-java">public class ClassC implements Serializable {
    // 직렬화가 가능한 클래스
    int field1;
}</code></pre>
<ol start="2">
<li>객체 직렬화 수행</li>
</ol>
<pre><code class="language-java">import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class SerialVerionUIDExample1 {
    // 직렬화 수행
    public static void main(String[] args) throws Exception {
        FileOutputStream fos = new FileOutputStream(&quot;C:/Temp/Object.dat&quot;);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        ClassC classC = new ClassC();
        classC.field1 = 1;

        oos.writeObject(classC);
        oos.flush();
        oos.close();
        fos.close();
    }
}</code></pre>
<ol start="3">
<li>객체 역직렬화 수행</li>
</ol>
<pre><code class="language-java">import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class SerialVersionUIDExample2 {
    // 역직렬화 수행
    public static void main(String[] args) throws Exception {
        FileInputStream fis = new FileInputStream(&quot;C:/Temp/Object.dat&quot;);
        ObjectInputStream ois = new ObjectInputStream(fis);

        ClassC classC = (ClassC) ois.readObject();
        System.out.println(&quot;field1 : &quot; + classC.field1);
    }
}</code></pre>
<ol start="4">
<li>1.의 필드 수정 - serialVersionUID 변경됨</li>
</ol>
<pre><code class="language-java">import java.io.Serializable;

public class ClassC implements Serializable {
    // 직렬화가 가능한 클래스
    int field1;
    // 필드 수정 - serialVersionUID 변경됨
    int field2;
}</code></pre>
<blockquote>
<p>이후 파일에 저장된 ClassC 객체를 복원하기 위해 역직렬화를 수행하면  serialVersionUID가 다르기 때문에 예외가 발생한다.</p>
<p>만일 불가피하게 클래스의 수정이 필요할 때는 명시적으로 필드에 serialVersionUID를 선언하면 된다.</p>
</blockquote>
<pre><code class="language-java">import java.io.Serializable;

public class ClassC implements Serializable {
    static final long serialVersionUID = 정수값;
}</code></pre>
<h4 id="writeobject와-readobject-메소드">writeObject()와 readObject() 메소드</h4>
<blockquote>
<p>두 클래스가 상속 관계에 있을 때를 가정할 때, 부모 클래스가  Serializable 인터페이스를 구현하고 있으면 자식 클래스는 이를 구현하지 않아도 자식 객체를 직렬화하면 부모 필드 및 자식 필드가 모두 직렬화 된다. 하지만 그 반대로 부모 클래스가 Serializable을 구현하지 않고, 자식 클래스만 구현하고 있다면 자식 객체를 직렬화할 때 <strong>부모 클래스의 필드는 직렬화에서 제외된다.</strong></p>
<p>이럴 경우 부모 클래스의 필드를 직렬화하고 싶다면 다음 2가지 방법 중 택 1을 해야한다.</p>
<ul>
<li>부모 클래스가 Serializable 인터페이스를 구현하도록 한다.</li>
<li>자식 클래스에서 writeObject()와 readObject() 메소드를 선언해서 부모 객체의 필드를 직접 출력시킨다.  </li>
</ul>
<p>물론 부모 클래스를 수정하면 좋겠지만, 수정할 수 없을 경우에는 후자의 방법을 사용해야 한다.</p>
</blockquote>
<ul>
<li>writeObject()  : 직렬화될 때 자동적으로 호출된다.</li>
<li>readObject() : 역직렬화될 때 자동적으로 호출된다.</li>
<li></li>
</ul>
<pre><code class="language-java">// writeObject() 선언 방법
private void writeObject(ObjectOutputStream out) throws IOException {
  out.writeXXX(부모필드); // 부모 객체의 필드값을 출력함
  .
  .
     out.defaultWriteObject(); // 자식 객체의 필드값을 직렬화
}</code></pre>
<pre><code class="language-java">// readObject() 선언 방법
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
  부모필드 = in.readXXX(); // 부모 객체의 필드값을 읽어옴
  .
  .
  in.defaultReadObject(); // 자식 객체의 필드값을 역직렬화
}</code></pre>
<blockquote>
<p>주의할 점은 두 메소드의 접근 제한자가 <code>private</code>가 아니면 자동 호출되지 않기 때문에 반드시 private을 적어줘야 한다는 점이다.</p>
<p>메소드의 매개값인 <code>ObjectInputStream</code>, <code>ObjectOutputStream</code>은 다양한 종류의 <code>readXXX()</code>, <code>writeXXX()</code>를 제공하기 때문에 부모 필드 타입에 맞는 것을 선택해서 사용하면 된다.</p>
</blockquote>
<h2 id="네트워크-기초">네트워크 기초</h2>
<blockquote>
<p><strong>네트워크</strong> : 여러 대의 컴퓨터를 통신 회선으로 연결한 것을 말한다.</p>
<p><strong>지역 네트워크</strong> : 회사 건물, 특정 영역 에 존재하는 컴퓨터를 통신 회선으로 연결한 것을 말한다.</p>
<p><strong>홈 네트워크</strong> : 집에 방마다 컴퓨터가 있고, 이 컴퓨터들을 유/무선 등의 통신 회선으로 연결했다면 홈네트워크가 형성된 것.</p>
<p><strong>인터넷</strong> : 지역 네트워크를 통신 회선으로 연결한 것.</p>
</blockquote>
<h3 id="서버와-클라이언트">서버와 클라이언트</h3>
<blockquote>
<p>컴퓨터가 인터넷에 연결되어 있다면 실제로 데이터를 주곱다는 행위는 프로그램들이 한다. </p>
<p>서비스를 제공하는 프로그램을 <strong>서버</strong>라고 부르고, 서비스를 받는 프로그램을 <strong>클라이언트</strong>라고 부른다.  </p>
<p>두 프로그램이 통신하기 위해서는 연결을 요청하는 역할과 수락하는 역할이 필요하다. 클라이언트는 서비스를 받기 위한 연결을 요청하고, 서버는 연결을 수락하여 서비스를 제공해준다. </p>
<p>서버는 클라이언트가 <strong>요청(Request)</strong>하는 내용을 처리해주고, <strong>응답(Response)</strong>을 클라이언트로 보낸다.</p>
<p>클라이언트/서버 모델은 한 개의 서버와 다수의 클라이언트로 구성되는 것이 보통이나, <strong>두 개의 프로그램이 서버인 동시에 클라이언트 역할을 하는 P2P</strong> 모델도 있다. P2P는 먼저 접속을 시도한 컴퓨터가 클라이언트가 된다. </p>
</blockquote>
<h3 id="ip주소와-포트">IP주소와 포트</h3>
<blockquote>
<p>모든 컴퓨터에는 고유한 주소가 있다. 이것이 바로 <strong>IP(Internet Protocol)</strong>이다. IP 주소는 네트워크 어댑터(랜카드) 마다 할당되는데, 한 개의 컴퓨터에 두 개의 네트워크 어댑터가 장착되어 있다면 2개의 IP 주소를 할당할 수 있다. </p>
<p>IP주소는 xxx.xxx.xxx.xxx와 같은 형식으로 표현된다. 여기서 xxx는 부호가 없는 0~255 사이의 정수이다. 연결할 상대의 IP주소를 모르면 프로그램들은 통신할 수 없다. 대중에게 서비스를 제공하는 대부분의 서버는 도메인 이름을 가지고 있는데 다음과 같이  DNS에 도메인 이름으로 IP를 등록해 놓는다.</p>
</blockquote>
<pre><code class="language-tex">[DNS]                                도메인 이름        :        등록된 IP 주소
                                www.naver.com      :        222.122.195.5</code></pre>
<blockquote>
<p>숫자보다는 도메인 이름을 더 쉽게 기억하기 때문에 도메인 이름을 사용한다.  </p>
<p>한 대의 컴퓨터에는 다양한 서버 프로그램들이 실행될 수 있다. 예를 들어 웹서버, 데이터 베이스 관리시스템(DBMS), FTP 서버 등이 하나의 IP주소를 갖는 컴퓨터에서 동시 실행될 수 있다. 이 경우 클라이언트는 어떤 서버와 통신해야 할지 결정해야 한다. IP는 컴퓨터의 네트워크 어댑터까지만 갈 수 있는 정보이기 때문에 접근하고자하는 내부의 서버를 선택하기 위해서는 추가적인 정보가 필요한데 이를 <strong>포트(port)</strong>라고 한다.  </p>
<p>서버는 시작할 때 고유한 포트를 가지고 실행하는데, 이것을 <strong>포트 바인딩(Port Binding)</strong>이라고 한다.  </p>
<p>클라이언트도 서버에서 보낸 정보를 받기 위해 포트 번호가 필요한데, 서버와 같이 고정적인 포트번호가 아니라 운영체제가 자동으로 부여하는 동적 포트 번호를 사용한다. 이 동적 포트 번호는 클라이언트가 서버로 연결 요청을 할 때 전송되어 서버가 클라이언트로 데이터를 보낼 때 사용된다. 총 범위는 <code>0 ~ 65535</code>인데 다음과 같이 세 가지 범위로 구분된다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>구분명</th>
<th>범위</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Well Know Port Numbers</td>
<td>0 ~ 1023</td>
<td>국제 인터넷 주소 관리 기구가 특정 애플리케이션용으로 미리 예약한 포트</td>
</tr>
<tr>
<td>Registered Port Numbers</td>
<td>1024 ~ 49151</td>
<td>회사에서 등록해서 사용할 수 있는 포트</td>
</tr>
<tr>
<td>Dynamic Or Private Port Numbers</td>
<td>49152 ~ 65535</td>
<td>운영체제가 부여하는 동적 포트 또는 개인적인 목적으로 사용할 수 있는 포트</td>
</tr>
</tbody></table>
<h3 id="inetaddress로-ip주소-얻기">InetAddress로 IP주소 얻기</h3>
<blockquote>
<p>자바는  IP주소를 <code>java.net.InetAddress</code> 객체로 표현한다. InetAddress는 로컬 컴퓨터의 IP주소 뿐만 아니라 도메인 이름을 DNS에서 검색한 후 IP 주소를 가져오는 기능을 제공한다. 로컬 컴퓨터의 InetAddress를 얻고 싶다면 IntetAddress.getLocalHost() 메소드를 다음과 같이 호출한다.</p>
</blockquote>
<pre><code class="language-java">InetAddress ia = InetAddress.getLocalHost()

// 외부 컴퓨터의 도메인 이름을 알고 있다면 다음 두 개의 메소드를 사용하여 InetAddress객체를 얻으면된다.
InetAddress ia = InetAddress.getByName(String host);
InetAddress[] iaArr = InetAddress.getAllByName(String host);

// InetAddress 객체에서 IP 주소를 얻기 위해서는 다음과 같이 호출하면 된다. 리턴값은 문자열이다.
String IPAddress = InetAddress.getHostAddress();</code></pre>
<h2 id="tcp-네트워킹">TCP 네트워킹</h2>
<blockquote>
<p>TCP(Tranmission Control Protocol)는 연결 지향적 프로토콜이다. 연결 지향 프로토콜이란 클라이언트와 서버가 연결된 상태에서 데이터를 주고받는 프로토콜을 말한다. 클라이언트가 연결 요청을 하고, 서버가 연결을 수락하면 통신 선로가 고정되고, 모든 데이터는 고정된 통신 선로를 통해서 순차적으로 전달된다. 그렇기 때문에 TCP는 데이터를 정확하고 안정적으로 전달한다.  </p>
<ul>
<li>단점 :<ul>
<li>데이터를 보내기 전에 반드시 연결이 형성되어야 한다. (<strong>가장 시간이 많이 걸리는 작업이다.</strong>)</li>
<li>고정된 통신 선로가 최단선이 아닐경우 상대적으로 UDP보다 데이터 전송 속도가 느릴수 있다.  </li>
</ul>
</li>
<li>자바에서의 TCP<ul>
<li><code>java.net.ServerSocket</code></li>
<li><code>java.net.Socket</code></li>
</ul>
</li>
</ul>
</blockquote>
<h3 id="serversocket과-socket의-용도">ServerSocket과 Socket의 용도</h3>
<blockquote>
<p>TCP 서버의 역할은 두 가지로 볼 수 있다. 하나는 클라이언트가 연결 요청을 해오면 연결을 수락하는 것이고, 다른 하나는 연결된 클라이언트와 통신하는 것이다. 자바에서는 이 두 역할별로 별도의 클래스를 제공하고 있다.</p>
<ul>
<li><code>java.net.ServerSocket</code> : 클라이언트의 연결 요청을 기다리면서 연결 수락을 담당</li>
<li><code>java.net.Socket</code> : 연결된 클라이언트와 통신을 담당\</li>
</ul>
<p>클라이언트가 연결 요청을 해오면 ServerSocket은 연결을 수락하고 통신용 Socket을 만든다.  </p>
<p>서버는 클라이언트가 접속할 포트를 가지고 있어야 하는데 이 포트를 <strong>바인딩 포트(Binding Port)</strong> 라고 한다. 서버는 고정도니 포트 번호에 바인딩해서 실행하므로, ServerSocket을 생성할 때 포트 번호를 하나 지정해야 한다. 서버가 실행되면 클라이언트는 서버의 IP주소와 바인딩 포트 번호로 Socket을 생성해서 연결 요청을 할 수 있다. ServerSocket은 클라이언트가 연결 요청을 해오면 accept() 메소드로 연결 수락을 하고 통신용 Socket을 생성한다. 그 후 각각의 Socket을 이용해서 데이터를 주고 받게 된다.</p>
</blockquote>
<h3 id="serversocket-생성과-연결-수락">ServerSocket 생성과 연결 수락</h3>
<blockquote>
<p>ServerSocket은 서버를 개발하기 위한 객체이다. ServerSocket을 얻는 가장 간단한 방법은 생성자에 바인딩 포트를 대입하고 객체를 생성하는 것이다. </p>
</blockquote>
<pre><code class="language-java">ServerSocket serverSocket = new ServerSocket(5001); // 5001번의 바인딩 포트를 가지는 서버소켓 생성</code></pre>
<blockquote>
<p>다른 방법은 디폴트 생성자로 객체를 생성하고 포트 바인딩을 위해 bind() 메소드를 호출하는 것이다.  bind()  메소드의 매개값은 포트 정보를 가진 InetSocketAddress이다.</p>
</blockquote>
<pre><code class="language-java">ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(5001));</code></pre>
<blockquote>
<p>만약 서버 PC에 멀티 IP가 할당되어 있을 경우, 특정 IP로 접속할 때만 연결 수락을 하고 싶다면 다음과 같이 작성하고 <code>localhost</code>에 정확한 IP를 준다.</p>
</blockquote>
<pre><code class="language-java">ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(&quot;IP 주소&quot;, 5001));</code></pre>
<ul>
<li><code>BindException</code> : ServerSocket을 생성할 때 바인딩 포트가 사용 중이라면 발생한다.  이 경우에는 다른 포트로 바인딩하거나 다른 프로그램을 종료하고 다시 실행한다.</li>
<li>바인딩 수행이 끝났을 때 : ServerSocket은 클라이언트 연결 수락을 위해 <code>accept()</code> 메소드를 실행해야 된다 .<code>accept()</code>는  클라이언트가 연결 요청하기 전까지 블로킹 되는데, 블로킹이란 스레드가 대기상태가 된다는 뜻이다. 그렇기 때문에 UI를 생성하는 스레드나, 이벤트를 처리하는 스레드에서 accept(() 메소드르 호출하지 않도록 한다. 블로킹이 되면 UI갱신이나 이벤트 처리를 할 수 없기 떄문이다. 클라이언트가 연결 요청을 하면 accept()는 클라이언트와 통신할 Socket을 만들고 리턴한다. 이것이 <code>연결 수락</code>이다.  </li>
<li>accept()에서 블로킹 되었을 때 : ServerSocket을 닫기 위해 close() 메소드를 호출하면 SocketException이 발생한다. 예외 처리가 필요하다.</li>
</ul>
<pre><code class="language-java">try {
  Socket socket = serverSocket.accept();
} catch (SocketException e) {
  e.printStackTrace();
}</code></pre>
<blockquote>
<p>연결된 클라이언트 IP, PORT를 알고 싶다면 <code>getRemoteSocketAddress</code>를 호출해서 SocketAddress를 얻으면 된다.  </p>
<p>실제로 리턴되는 것은 InetSocketAddress이므로 다음과 같이 타입 변환을 할 수 있다.</p>
</blockquote>
<pre><code class="language-java">InetSocketAddress socketAddress = (InetSocketAddress) socket.getRemoteAddress();</code></pre>
<ul>
<li>InetSocketAddress IP, PORT 리턴 메소드</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드 (매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>String</td>
<td>getHostName()</td>
<td>클라이언트 IP리턴</td>
</tr>
<tr>
<td>int</td>
<td>getPort()</td>
<td>클라이언트 포트 번호 리턴</td>
</tr>
<tr>
<td>String</td>
<td>toString()</td>
<td>&quot;IP 포트번호&quot; 형태의 문자열 리턴</td>
</tr>
</tbody></table>
<ul>
<li>언바인딩 : 더 이상 클라이언트 연결 수락이 필요없을 떄는 ServerSocket의 close() 메소드를 호출해서 포트를 언바인딩 시켜야 한다. </li>
</ul>
<pre><code class="language-java">serverSocket.close();</code></pre>
<h4 id="accept-메소드-호출하여-다중-클라이언트-연결을-수락하는-예제">accept() 메소드 호출하여 다중 클라이언트 연결을 수락하는 예제</h4>
<pre><code class="language-java">package socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerExample {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;

        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(&quot;localhost&quot;, 5001));

            while (true) {
                System.out.println(&quot; [연결 기다림]&quot;);
                Socket socket = serverSocket.accept();
                InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
                System.out.println(&quot;[연결 수락]&quot; + isa.getHostName());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (!serverSocket.isClosed()) {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}</code></pre>
<h3 id="socket-생성과-연결-요청">Socket 생성과 연결 요청</h3>
<blockquote>
<p>클라이언트가 서버에 연결 요청을 하려면 <code>java.net.Socket</code>을 이용해야 한다. Socket 객체를 생성함과 동시에 연결 요청을 하려면 생성자의 매개값으로 서버의 IP주소와 바인딩 포트 번호를 제공하면 된다. </p>
</blockquote>
<pre><code class="language-java">try {
  Socket socket = new Socket(&quot;localhost&quot;, 5001); // 방법 1
  Socket socket = new Socket( new InetSocketAddress (&quot;localhost&quot;, 5001)); // 방법 2
} catch (UnknownHostException e) {
  // IP 표기 방법이 잘못되었을 경우
} catch (IOException e) {
  // 해당 포트의 서버에 연결할 수 없는 경우
}</code></pre>
<blockquote>
<p>외부 서버에 접속하려면 localhost 대신 정확한 IP를 입력하면 된다. 만약 IP대신 도메인만 알고 있다면, 도메인 이름을 IP주소로 번역해야 하므로 InetSocketAddress 객체를 이용하는 방법을 사용해야 한다.  </p>
<p>Socket 객체를 생성과 동시에 연결을 요청하지 않고 기본 생성자로 Socket을 생성한 후, connect() 메소드로 연결 요청을 할 수도 있다.</p>
</blockquote>
<pre><code class="language-java">socket = new Socekt();
socket.connect(new InetSocketAddress(&quot;localhost&quot;, 5001));</code></pre>
<ul>
<li><p>연결 요청시 발생 예외 2가지</p>
<ul>
<li><code>UnknownHostException</code> : 잘못 표기된 IP주소를 입력했을 경우 발생</li>
<li><code>IOException</code> : 주어진 포트로 접속할 수 없을 때 발생.</li>
</ul>
</li>
<li><p>두가지 예외를 처리하는 방법</p>
<ul>
<li>Socket 생성자 및 connect() 메소드는 서버와 연결이 될 때까지 블로킹되기 때문에 UI를 생성하는 스레드나 이벤트를 처리하는 스레드에서 Socket 생성자 및 connect()를 호출하지 않도록 한다. 블로킹시 UI 갱신 || 이벤트 처리를 할 수 없다.</li>
<li>연결된 후, 클라이언트 프로그램을 종료하거나 강제적으로 연결을 끊고 싶다면 Socket의 close() 메소드를 호출한다. close() 메소드 또한 IOException이 발생할 수 있기 때문에 예외 처리가 필요하다.</li>
</ul>
</li>
<li><p>localhost 5001 포트 요청 예제</p>
</li>
</ul>
<pre><code class="language-java">package socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

public class ClientExample {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            socket = new Socket(&quot;localhost&quot;,5001);
            System.out.println(&quot;[연결 요청]&quot;);
            socket.connect(new InetSocketAddress(&quot;localhost&quot;, 5001));
            System.out.println(&quot;[연결 성공]&quot;);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (!socket.isClosed()) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }
}</code></pre>
<h3 id="socket-데이터-통신">Socket 데이터 통신</h3>
<blockquote>
<p>클라이언트가 연결 요청(<code>connect()</code>)하고 서버가 연결 수락(<code>accept()</code>)했다면, 양쪽의 Socket 객체로부터 각각 입력 스트림(InputStream)과 출력 스트림(OutputStream)을 얻을 수 있다.</p>
</blockquote>
<pre><code class="language-java">InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();</code></pre>
<ul>
<li>상대에게 데이터를 보낼 때 : byte[] 배열로 생성하고 이것을 매개값으로 해서 <code>OutputStream</code>의 <code>write()</code>메소드를 호출하면 된다.</li>
</ul>
<pre><code class="language-java">String data = &quot;보낼 데이터&quot;;
byte[] byteArr = data.getBytes(&quot;UTF-8&quot;);
OutputStream os = socket.getOutputStream();
os.write(byteArr);
os.flush();</code></pre>
<ul>
<li>상대방이 보낸 데이터를 받을 떄 : byte[] 배열로 하나 생성하고, 이것을 매개값으로 해서 <code>InputStream</code>의  <code>read()</code> 메소드를 호출하면 된다. 읽은데이터를 byte[] 배열에 저장하고 읽은 바이트 수를 리턴한다. </li>
</ul>
<pre><code class="language-java">byte[] byteArr = new byte[100];
InputStream is = socket.getInputStream();
int readByteCount = is.read(byteArr);
String data = new String(byteArr, 0, readByteCount, &quot;UTF-8&quot;);</code></pre>
<ul>
<li>데이터 보내고 받기 예제</li>
</ul>
<pre><code class="language-java">package socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class ClientExample2 {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            socket = new Socket();
            System.out.println(&quot;[연결 요청]&quot;);
            socket.connect(new InetSocketAddress(&quot;localhost&quot;, 5001));
            System.out.println(&quot;[연결 성공]&quot;);

            byte[] bytes = null;
            String message = null;

            OutputStream os = socket.getOutputStream();
            message = &quot;Hello Server&quot;;
            bytes = message.getBytes(&quot;UTF-8&quot;);
            os.write(bytes);
            os.flush();
            System.out.println(&quot;[데이터 보내기 성공&quot;);

            InputStream is = socket.getInputStream();
            bytes = new byte[100];
            int readByteCount = is.read(bytes);
            message = new String(bytes, 0, readByteCount, &quot;UTF-8&quot;);
            System.out.println(&quot;[데이터 받기 성공]&quot;);

            os.close();
            is.close();

        } catch (Exception e) {

        }

        if (!socket.isClosed()) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}</code></pre>
<ul>
<li>데이터 받고 보내기</li>
</ul>
<pre><code class="language-java">package socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class ServerExample2 {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;

        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(&quot;localhost&quot;, 5001));
            while (true) {
                System.out.println(&quot;[연결 기다림]&quot;);
                Socket socket = serverSocket.accept();
                InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();

                byte[] bytes = null;
                String message = null;

                InputStream is = socket.getInputStream();
                bytes = new byte[100];
                int readByteCount = is.read(bytes);
                message = new String(bytes, 0, readByteCount, &quot;UTF-8&quot;);
                System.out.println(&quot;[데이터 받기 성공]&quot;);

                OutputStream os = socket.getOutputStream();
                message = &quot;Hello Server&quot;;
                bytes = message.getBytes(&quot;UTF-8&quot;);
                os.write(bytes);
                os.flush();

                is.close();
                os.close();

                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (!serverSocket.isClosed()) {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}</code></pre>
<blockquote>
<p>데이터를 받기 위해 InputStream의 <code>read()</code> 메소드를 호출하면 상대방이 데이터를 보내기 전까지는 블로킹 되는데, <code>read()</code> 메소드가 블로킹 해제되고 리턴되는 경우는 세가지이다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>블로킹이 해제되는 경우 3가지</th>
<th>리턴값</th>
</tr>
</thead>
<tbody><tr>
<td>상대방이 데이터를 보냄</td>
<td>읽은 바이트 수</td>
</tr>
<tr>
<td>상대방이 정상적으로 Socket의 close()를 호출</td>
<td>-1</td>
</tr>
<tr>
<td>상대방이 비정상적으로 종료</td>
<td>IOException 발생</td>
</tr>
</tbody></table>
<blockquote>
<p>상대방이 정상적으로 Socket의 close() 메소드를 호출하고 연결을 끊었을 경우와 비정상적으로 종료했을 경우 모두 예외 처리를 해서 이쪽도 Socket을 닫기 위해 close() 메소드를 호출해야 한다.</p>
</blockquote>
<h3 id="스레드-병렬-처리">스레드 병렬 처리</h3>
<blockquote>
<p>연결 수락을 위해 ServerSocket의 accept()를 실행하거나, 서버 연결 요엉을 위해 Socket을 생성자 또는 connect()를 실행할 경우에는 해당 작업이 완료되기 전까지 블로킹된다. </p>
<p>데이터 통신을 할 때에도 마찬가지인데 InputStream의 read() 메소드는 상대방이 데이터를 보내기 전까지 블로킹되고, OutputStream의 wirte() 메소드는 데이터를 완전하게 보내기 전까지 블로킹된다.  결론적으로 말해서 ServerSocket과 Socket은 동기 방식으로 구동된다. </p>
<p>만약 서버를 실행시키는 main 스레드가 직접 입출력 작업을 담당하게 되면 입출력이 완료될 때까지 다른 작업을 할 수 없는 상태가 된다. 서버 애플리케이션은 지속적으로 클라이언트의 연결 수락 기능을 수행해야 되는데, 입출력에서 이 작업을 할 수 없게 된다.  또한 클라이언트1과 입출력하는 동안에는 클라이언트2와 입출력을 할 수 없게 된다. 그렇기 때문에 accept(), connect(), read(), write()는 별도의 작업 스레드를 생성하고, 다중 클라이언트와 병렬적으로 통신하는 모습을 보여준다.</p>
</blockquote>
<ul>
<li><p>다중 클라이언트와 병렬적으로 통신하는 모습![KakaoTalk_Image_2022-03-14-23-40-25]<img src="https://user-images.githubusercontent.com/84169773/160957585-86c4d1e0-698d-4529-a7b4-3003b297b843.jpg" alt="1"></p>
<p>: 스레드로 병렬처리를 할 경우, 수천 개의 클라이언트가 동시에 연결되면 서버에서 수천 개의 스레드가 생성되기 때문에 서버 성능이 급격하게 저하된다. 클라이언트 폭증의 이슈를 방지하려면 스레드풀을 사용하는 것이 바람직하다.</p>
</li>
<li><p>스레드풀을 이용한 서버 구현 방식![KakaoTalk_Image_2022-03-14-23-40-31]<img src="https://user-images.githubusercontent.com/84169773/160957569-85b30e8e-3514-439d-8620-4c3bc48ef06a.jpg" alt="2"></p>
<p>: 클라이어늩가 연결 요청을 하면 서버의 스레드풀에서 연결 수락을 하고 Socket을 생성한다. 클라이언트가 작업 처리 요청을 하면 서버의 스레드 풀에서 요청을 처리하고 응답을 클라이언트로 보낸다. 스레드풀은 스레드 수를 제한해서 사용하기 때문에 갑작스런 클라이언트의 폭증은 작업 큐의 작업량만 증가시킬 뿐, 스레드의 수는 변함이 없다. 따라서 서버 성능은 완만히 저하되지만 클라이언트가 응답을 받는 시간이 조금 더 늦춰질 수는 있다.</p>
</li>
</ul>
<h2 id="udp-네트워킹">UDP 네트워킹</h2>
<blockquote>
<p><strong>UDP</strong> (User Datagram Protocol)는 비연결 지향적 프로토콜이다. 비연결 지향적이란 데이터를 주고받을 때 연결 절차를 거치지 않고, 발신자가 일방적으로 데이터를 발신하는 방식이다. <strong>연결 과정이 없기 때문에 TCP보다는 빠른 전송을 할 수 있지만, 데이터 전달의 신뢰성은 떨어진다.</strong>  </p>
<p>UDP는 발신자가 데이터 패킷을 순차적으로 보내더라도 이 패킷들은 서로 다른 통신 선로를 통해 전달될 수 있다. 먼저 보낸 패킷이 느린 선로를 통해 전송될 경우 나중에 보낸 패킷보다 늦게 도착할 수 있다. 또한 일부 패킷은 잘못된 선로로 전송되어 잃어버릴 수도 있다.</p>
<p>UDP는 편지에 비유할 수 있다. 발신자는 봉투(<strong>패킷</strong>)에 수신자의 주소(<strong>IP와 Port</strong>)와 발신자의 주소(<strong>로컬 IP와 Port</strong>)를 쓴다. 그리고 봉투 안에 편지(<strong>전송할 데이터</strong>)를 넣고 편지를 보낸다.  </p>
<p>발신자는 수신자가 편지를 받았는지의 여부는 모른다. 게다가 최근에 보낸 편지가 일찍 보내질 수도 있고, 보내지지 않았을 수도 있다. </p>
<p>일반적으로 데이터 전달의 신뢰성보다는 속도가 중요한 프로그램에서는 UDP를 사용하고 신뢰성이 중요한 프로그램에서는 TCP를 사용한다. 자바에서는 UDP 프로그래밍을 위해 <code>java.net.DatagramSocket</code>과 <code>java.net.DatagramPacket</code>을 제공한다. </p>
<ul>
<li><strong>DatagramSocket</strong> : 발신점과 수신점에 해당하는 클래스</li>
<li><strong>DatagramPacket</strong> : 주고 받는 패킷 클래스</li>
</ul>
</blockquote>
<h3 id="발신자-구현">발신자 구현</h3>
<pre><code class="language-java">DatagramSocket datagramSocket = new DatagramSocket();</code></pre>
<blockquote>
<p>보내고자 하는 데이터를 byte[] 배열로 생성하는데, 문자열인 경우 다음과 같이 UTF-8로 인코딩해서 byte[] 배열을 얻으면 된다.</p>
</blockquote>
<pre><code class="language-java">byte[] byteArr = data.getBytes(&quot;UTF-8&quot;);</code></pre>
<blockquote>
<p>데이터와 수신자 정보를 담고 있는 <code>DatagramPacket</code>을 생성해야 하는데, <code>DatagramPacket</code> 생성자의 <strong>첫 번째 매개값</strong>은 보낼 데이터 <code>byte[]</code>이고 <strong>두 번째 매개값</strong>은 byte[] 배열에서 보내고자하는 항목 수이다. 전체 항목을 보내려면 length 값으로 대입하면 된다. <strong>세 번째 매개값은</strong> 수신자 IP와 Port를 가지고 있는 <strong>SocketAddress</strong>이다. SocketAddress는 추상 클래스이므로 하위 클래스인 InetSocketAddress를 생성해서 대입한다. </p>
</blockquote>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">DatagramSocket datagramSocket = new DatagramSocket();

byte[] byteArr = data.getBytes(&quot;UTF-8&quot;);
DatagramPacket packet = new Datagram(
  byteArr,
  byteArr.length,
  new InetSocketAddress(&quot;localhost&quot;, 5001)
  );

datagramSocket.send(packet);
datagramSocket.close();</code></pre>
<h3 id="수신자-구현">수신자 구현</h3>
<pre><code class="language-java">DatagramSocket datagramSocket = new DatagramSocket();</code></pre>
<blockquote>
<p>`<strong>receive()</strong> 메소드를 사용해서 패킷을 읽을 준비를 한다. 패킷을 받을 때까지 블로킹되고, 도착하면 매개값으로 주어진 DatagramPacket에 패킷 내용을 저장한다.</p>
</blockquote>
<pre><code class="language-java">datagramSocket.receive(datagramPacket);</code></pre>
<blockquote>
<p>패킷의 내용을 저장할 DatagramPacket 객체는 다음과 같이 생성한다. <strong>첫 번째 매개값</strong>은 읽은 패킷 데이터를 저장할 바이트 배열이고, <strong>두번 째 매개값</strong>은 읽을 수 있는 최대 바이트 수로 첫 번째 바이트 배열의 크기와 같거나 작아야 한다. 일반적으로 첫 번째 바이트배열의 크기를 준다.</p>
</blockquote>
<pre><code class="language-java">DatagramPacket datagramPacket = new DatagramPacket(new byte[100], 100);</code></pre>
<blockquote>
<p>receive() 메소드가 패킷을 읽었다면 <code>DatagramPacket</code>의 getData()로 데이터가 저장된 바이트 배열을 얻어낼 수 있다. 그리고 getLength()를 호출해서 읽은 바이트 수를 얻을 수 있다. 받은 데이터가 인코딩된 문자열이라면 다음과 같이 디코딩해서 문자열을 얻는다.</p>
</blockquote>
<pre><code class="language-java">String data = new String(datagramPacket.getData(), 0, datagramPacket.getLength(), &quot;UTF-8&quot;);</code></pre>
<blockquote>
<p>수신자가 패킷을 받고나서 발신자에게 응답 패킷을 보내고 싶다면 발신자의 IP와 Port를 알아야 하는데 DatagramPacket의 getSocketAddress()를 통해 발신자의 SocketAddress를 얻을 수 있어 send()메소드에서 이용할 수 있다.  </p>
<p>수신자는 항상 데이터를 받을 준비를 해야 하므로 작업 스레드를 생성하여 receive() 메소드를 반복적으로 호출해야 한다. 작업 스레드를 종료 시키는 방법은 receive() 메소드가 블로킹 된 상태에서 DatagramSocket의 close()를 호출하면 된다. 이 경우 receive()에서 <code>SocketException</code>이 발생하고, 예외 처리 코드에서 작업 스레드를 종료시킨다.</p>
</blockquote>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">public class UdpReceiveExample extends Thread {

    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(5001);

        Thread thread = new Thread() {

            @Override
            public void run() {
                System.out.println(&quot;[수신 시작]&quot;);
                try {
                    while (true) {
                        DatagramPacket packet = new DatagramPacket(new byte[100], 100);
                        socket.receive(packet);

                        String data = new String(packet.getData(), 0, packet.getLength(), &quot;UTF-8&quot;);
                        System.out.println(&quot;[받은 내용 :&quot; + packet.getSocketAddress() + &quot; ] &quot; + data);

                    }
                } catch (Exception e) {
                    System.out.println(&quot;[수신 종료]&quot;);

                }
            }
        };
        thread.start();

        Thread.sleep(10000);
        socket.close();
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링_핵심_원리 -1 ]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%8A%A4%ED%94%84%EB%A7%81%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-1</link>
            <guid>https://velog.io/@seunghan-baek/%EC%8A%A4%ED%94%84%EB%A7%81%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-1</guid>
            <pubDate>Thu, 31 Mar 2022 01:20:35 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘-배운-것-테스트-코드-작성">오늘 배운 것. 테스트 코드 작성</h2>
<blockquote>
<p>테스트 코드는 선택이 아닌 필수, 개발자는 테스트 코드 작성법을 알아야 한다.</p>
</blockquote>
<h3 id="테스트-코드-미적용">테스트 코드 미적용</h3>
<pre><code class="language-java">import note.deadPerson.member.Grade;
import note.deadPerson.member.Member;
import note.deadPerson.member.MemberService;
import note.deadPerson.member.MemberServiceImpl;

public class MemberApp {


    public static void main(String[] args) {
        MemberService memberService = new MemberServiceImpl();

        Member memberA = new Member(1L, &quot;memberA&quot;, Grade.VIP);

        memberService.joinMember(memberA);

        Member findMember = memberService.findMember(1L);

        System.out.println(&quot;Member : &quot; + memberA.getName());
        System.out.println(&quot;Find Member : &quot; + findMember.getName());

    }
}</code></pre>
<blockquote>
<p>위 코드는 <code>memberA</code>와 <code>findMember</code>가 서로 같은 이름을 가지고 있는지를 확인하는 코드이다.</p>
<p>출력 결과를 보고 둘을 대조해야만 서로 같은지를 알 수 있다. </p>
<p>반면에 테스트 코드를 작성하면 다음과 같이 편리하게 동일한지 알 수 있다.</p>
<p>출력 결과를 보고 둘을 대조해야만 서로 같은지를 알 수 있다. 반면에 테스트 코드를 작성하면 다음과 같이 편리하게 동일한지 알 수 있다.</p>
</blockquote>
<h3 id="테스트-코드-작성">테스트 코드 작성</h3>
<pre><code class="language-java">import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public class MemberServiceTest {

    MemberService memberService = new MemberServiceImpl();

    @Test
    void join() {
        //given
        Member member = new Member(1L, &quot;memberA&quot;, Grade.VIP);

        //when
        memberService.joinMember(member);
        Member findMember = memberService.findMember(1L);

        //then
        Assertions.assertThat(member).isEqualTo(findMember);
    }

}</code></pre>
<blockquote>
<p>실행을 해보면 아래와 같이 테스트가 통과됨을 알 수 있다. 굳이 getter를 통해 출력된 문장을 서로 일일히 비교하지 않아도 된다.</p>
</blockquote>
<p>![스크린샷 2022-03-17 22.06.06](/Users/mac/Library/Application Support/typora-user-images/스크린샷 2022-03-17 22.06.06.png)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Linux 디렉토리 구조 - 기초]]></title>
            <link>https://velog.io/@seunghan-baek/Linux-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC-%EA%B5%AC%EC%A1%B0-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@seunghan-baek/Linux-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC-%EA%B5%AC%EC%A1%B0-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Thu, 31 Mar 2022 01:19:47 GMT</pubDate>
            <description><![CDATA[<h2 id="리눅스--유닉스-계열의-디렉토리-구조">리눅스 &amp; 유닉스 계열의 디렉토리 구조</h2>
<h3 id="---최상위-디렉토리"><code>/</code> - 최상위 디렉토리</h3>
<p>/는 최상위 디렉토리(루트)를 의미한다.</p>
<pre><code class="language-sh"># 최상위 디렉토리로 이동
cd /</code></pre>
<h3 id="최상위-디렉토리-기준-구조">최상위 디렉토리 기준 구조</h3>
<pre><code class="language-sh">/
 ㄴ /bin
 ㄴ /sbin
 ㄴ /etc
 ㄴ /dev
 ㄴ /proc
 ㄴ /var
 ㄴ /tmp
 ㄴ /usr
 ㄴ /home
 ㄴ /boot
 ㄴ /lib
 ㄴ /opt
 ㄴ /mnt
 ㄴ /media
 ㄴ /src
</code></pre>
<h3 id="bin"><code>/bin</code></h3>
<p><strong>User Binaries</strong>.  </p>
<p>실행 가능한 프로그램을 바이너리라고도 부른다. 이를 줄여서 <code>bin</code>으로 표현한 것. 사용자들이 사용하는 명령들이 위치하고 있다.</p>
<h3 id="sbin"><code>/sbin</code></h3>
<p><strong>System Binaries</strong></p>
<p>시스템 관리자(루트 유저)가 사용하는 프로그램이 위치하고 있다. </p>
<h3 id="etc"><code>/etc</code></h3>
<p><strong>Configuration Files</strong></p>
<p>대부분의 프로그램의 설정 파일이 위치하고 있다. 프로그램들은 이 설정 파일을 참고하여 어떻게 실행될 것인지 정해진다.</p>
<h3 id="var"><code>/var</code></h3>
<p><strong>Variable Files</strong></p>
<p>내용 또는 용량이 변경될 수 있는 파일들이 위치하고 있다. </p>
<h3 id="tmp"><code>/tmp</code></h3>
<p><strong>Temp Files</strong></p>
<p> 임시파일들이 위치하고 있다. 임시파일이므로 컴퓨터를 재부팅하면 내용이 사라진다. </p>
<h3 id="home"><code>/home</code></h3>
<p><strong>Home Directories</strong></p>
<p>사용자들의 디렉토리이다. 운영체제의 사용자(나 = <code>seung</code>)의 디렉토리가 위치하고 있다.</p>
<h3 id="lib"><code>/lib</code></h3>
<p><strong>System Libraries</strong></p>
<p>bin과 sbin이 공통으로 사용하는 라이브러리가 위치한다.</p>
<h3 id="opt"><code>/opt</code></h3>
<p><strong>Optional add-on Applications</strong></p>
<p> 어떠한 소프트웨어를 설치할 때 그 소프트웨어를 특정 디렉토리에 지정해야 될 때 opt에 설치하는 것이 좋은 방법이다.</p>
<h3 id="usr"><code>/usr</code></h3>
<p><strong>User Programs</strong></p>
<p>설치하는 프로그램들은  /usr 밑에 설치가 되고, 기본적으로 Unix 계열에 설치가 되어서 bundle의 형태로 제공되는 앱은 /bin /sbin에 설치 된다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Provisioning(프로비저닝)]]></title>
            <link>https://velog.io/@seunghan-baek/Provisioning%ED%94%84%EB%A1%9C%EB%B9%84%EC%A0%80%EB%8B%9D</link>
            <guid>https://velog.io/@seunghan-baek/Provisioning%ED%94%84%EB%A1%9C%EB%B9%84%EC%A0%80%EB%8B%9D</guid>
            <pubDate>Thu, 31 Mar 2022 01:18:43 GMT</pubDate>
            <description><![CDATA[<h2 id="프로비저닝-provisioning">프로비저닝 (Provisioning)</h2>
<h3 id="개념">개념</h3>
<blockquote>
<p>사용자의 요구에 맞게 시스템 자원을 할당, 배치, 배포해 두었다가 필요 시 시스템을 즉시 사용할 수 있는 상태로 미리 준비해 두는 것을 말한다.  </p>
<p>프로비저닝의 유형은</p>
<ol>
<li>서버 프로비저닝</li>
<li>OS 프로비저닝</li>
<li>소프트웨어 프로비저닝</li>
<li>스토리지 프로비저닝</li>
<li>계정 프로비저닝 등으로 나눌 수 있다.</li>
</ol>
</blockquote>
<h3 id="서버-프로비저닝">서버 프로비저닝</h3>
<blockquote>
<p>필요한 리소스를 기반으로 네트워크에서 사용될 서버를 설정하는 프로세스.</p>
<p>데이터 센터에 물리적 하드웨어 설치, 소프트웨어 설치 및 설정, 운영 체제 및 애플리케이션 포함 미들웨어와 네트워크 및 스토리지 연결로 이루어진다.</p>
</blockquote>
<h3 id="os-프로비저닝">OS 프로비저닝</h3>
<blockquote>
<p>OS를 서버에 설치하고, 구성 작업을 해서  OS가 동작 가능하도록 준비해두는 것.</p>
</blockquote>
<h3 id="소프트웨어-프로비저닝">소프트웨어 프로비저닝</h3>
<blockquote>
<p>WAS, DBMS, Application 등을 시스템에 설치 및 배포하고 필요한 구성 셋팅 작업을 해서 실행 가능하도록 준비하는 것</p>
</blockquote>
<h3 id="스토리지-프로비저닝">스토리지 프로비저닝</h3>
<blockquote>
<p>낭비되거나 사용되지 않는 스토리지를 식별하고 공통 풀에서 옮긴 후, 스토리지에 대한 요구가 접수 되면 관리자는 공통 풀에서 스토리지를 꺼내 사용 효율성을 높일 수 있는 인프라 구축을 가능하도록 하는 것.</p>
</blockquote>
<h3 id="계정-프로비저닝">계정 프로비저닝</h3>
<blockquote>
<p>기업의 자원 범주가 변경 되었을 때 각 담당자, IT관리자가 승인절차를 밟은 후 그룹웨어, e-mail 등 다양한 어플리케이션에 필요 계정을 생성 및  액세스 권한 변경을 해주는 일련의 과정</p>
</blockquote>
<h3 id=""></h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스터디 - 8주차]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-8%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-8%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 31 Mar 2022 01:16:48 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p>자바의 인터페이스에 대해 학습하세요.</p>
<h1 id="학습할-것-필수">학습할 것 (필수)</h1>
<ul>
<li>인터페이스 정의하는 방법</li>
<li>인터페이스 구현하는 방법</li>
<li>인터페이스 레퍼런스를 통해 구현체를 사용하는 방법</li>
<li>인터페이스 상속</li>
<li>인터페이스의 기본 메소드 (Default Method), 자바 8</li>
<li>인터페이스의 static 메소드, 자바 8</li>
<li>인터페이스의 private 메소드, 자바 9</li>
</ul>
<h2 id="인터페이스-정의하는-방법">인터페이스 정의하는 방법</h2>
<blockquote>
<p>인터페이스의 정의, 특징, 장점을 배운다.</p>
</blockquote>
<h3 id="인터페이스란-">인터페이스란 ?</h3>
<p>객체의 사용 방법을 정의한 타입으로, 모든 기능을 추상화하여 정의한 상태만 선언한다. </p>
<p>프로그래밍 관점에서 인터페이스는 <strong>추상 메소드의 집합</strong>, 상수도 존재하고 jdk1.8부터는 static 메소드, default 메소드도 추가 됐지만 핵심은 추상 메소드이다.</p>
<p>객체의 사용 설명서라고 이해하면 편하다.</p>
<h3 id="인터페이스의-특징-개인-공부">인터페이스의 특징 <code>개인 공부</code></h3>
<ul>
<li>구현된 것이 전혀 없다.(설계도의 개념)</li>
<li>모든 멤버가 <code>public</code></li>
</ul>
<h3 id="인터페이스의-장점--개인-공부">인터페이스의 장점  <code>개인 공부</code></h3>
<ul>
<li><p><strong>두 대상 간의 &#39;연결, 대화, 소통&#39;을 돕는 중간 역할을 한다.</strong></p>
<p>: 예를 들어 컴퓨터라는 하드웨어(<code>대상 1</code> )를 사람(<code>대상 2</code>)이 직접 제어하는 것은 큰 어려움이 있다(기계와 사람은 사용하는 언어가 서로 다르기 때문에). 이를 해결하기 위해 우리는 인터페이스(<code>Graphic Users Interface</code>)를 사용하는데 이를 통해 얻는 이점은 컴퓨터의 내부 하드웨어가 바뀌어도 사용법은 똑같기 때문에 변경에 유리해진다는 것이다.</p>
</li>
</ul>
<ul>
<li><p><strong>선언과 구현을 분리시킬 수 있게 한다.</strong></p>
<ul>
<li>클래스만 있을 경우</li>
</ul>
<pre><code class="language-java">class B {
    public void method() {
        System.out.println(&quot;MethodInB&quot;);
    }
}</code></pre>
<blockquote>
<p>유연하지 않고 변경에 불리하다.</p>
</blockquote>
<ul>
<li>인터페이스를 통해 분리하였을 경우</li>
</ul>
<pre><code class="language-java">// 인터페이스에 추상메소드 선언
interface I {
    public void method();
}

// 인터페이스를 구현한 클래스가 추상메소드를 구현
class B implements I {
    public void method() {
        System.out.println(&quot;MethodInB&quot;);
    }
}</code></pre>
<blockquote>
<p>선언부와 구현부가 나누어져 있기 때문에 유연하고 변경에 유리하다.</p>
</blockquote>
</li>
</ul>
<ul>
<li><p><strong>인터페이스를 사용하여 B가 변경되어도 A는 안바꿀 수 있게 된다(느슨한 결합)</strong></p>
<ul>
<li>직접적인 관계의 두 클래스 (A-B)</li>
</ul>
<pre><code class="language-java">class A {
    public void methodA(B b) {
        b.methodB();
    }
}

class B {
    public void methodB() {
        System.out.println(&quot;method B()&quot;);
    }
}

class InterfaceTest {
    public static void main(String[] args) {
        A a = new A();
        a.methodA(new B());
    }
}</code></pre>
<ul>
<li>간접적인 관계의 두 클래스(A-I-B)</li>
</ul>
<pre><code class="language-java">class A {
    public void methodA(I i) {
        i.methodB();
    }
}

interface I { void methodB(); }

// 1
class B implements I {
    public void methodB() {
        System.out.println(&quot;method B()&quot;);
    }
}
// 2
class C implements I {
    public void methodB() {
        System.out.println(&quot;methodB() in C&quot;);
    }
}

class InterfaceTest {
    public static void main(String[] args) {
        A a = new A();
        a.methodA(new B());
    }
}</code></pre>
<blockquote>
<p>직접 클래스를 참조하지 않고 인터페이스인 I를 사용하기 때문에 구현 객체가 변경되어도 A 클래스는 변경할 필요가 없다.</p>
</blockquote>
</li>
</ul>
<ul>
<li><p><strong>개발 시간을 단축할 수 있다.</strong></p>
<p>: A라는 클래스가 B라는 클래스에 의존하여 개발이 필요하다고 가정을 하자. 하지만 B도 구현이 지연된 상황이라 A의 개발이 늦춰진다. 이때 인터페이스 I를 생성후 추상 메소드를 선언한뒤, B가 I를 구현하게 만든다면 A는 I의 추상 메소드를 호출할 수 있으므로 개발의 시간을 단축시킬 수 있다.</p>
</li>
<li><p><strong>변경에 유리한 유연한 설계가 가능하다.</strong>
: 상단의 <strong>느슨한 결합</strong>을 보게되면 A 클래스는 <code>method()</code>를 호출할 때의 매개변수로 인터페이스인 I를 사용하는데, 이는 <strong>I를 구현한 클래스의 인스턴스만</strong> 매개변수로 올 수 있다는 것이다. 따라서 A의 인스턴스가 생성되고 <code>method()</code>의 매개변수로는 I를 구현한 B, C가 A의 직접적인 변경 없이 매개변수로 사용 가능하기 때문에 변경에 유리한 유연할 설계가 가능하다는 것이다.</p>
</li>
<li><p><strong>표준화가 가능하다.</strong></p>
<p>: JDBC를 예로 들어보자. 사용자는 데이터베이스로 현재 Oracle을 사용하고 있는데 Oracle의 가격 정책이 변경되면서 큰 부담이 느껴져 MySQL로 변경하게 되었다. 이 때 JDBC를 사용하지 않은 상태에서는 Oracle만의 표준을 따라 Application을 개발했기 때문에 MySQL로써의 변경 중 Application에 필요한 수정사항이 많을 것이다. Java는 JDBC라는 표준 인터페이스 집합을 제공하고 있다. 각 데이터베이스 회사는 JDBC의 표준에 따라 개발하고, 사용자는 이 JDBC를 사용해서 DB를 바꿔주면 된다.</p>
</li>
<li><p><strong>서로 관계없는 클래스들의 관계를 맺어줄 수 있다.</strong></p>
</li>
</ul>
<h3 id="정의-방법">정의 방법</h3>
<pre><code class="language-java">interface 인터페이스이름 {
      public static final 타입 상수이름 = 값;
    public abstract 메소드이름(매개변수목록);
}</code></pre>
<blockquote>
<p>모든 인터페이스의 멤버가 public인 것과 추상 메소드(abstract)로 이루어진 것을 확인할 수 있다.</p>
</blockquote>
<pre><code class="language-java">interface PlayingCard {
    public static final int SPADE = 4; // 생략 없음
    final int DIAMOND = 3; // public, static 생략
    static int HEARD = 2; // public, final 생략
    int CLOVER = 1; // public static final 생략

    public abstract String getCardNumber(); // 생략 없음
    String getCardKind(); // public, abstract 생략
}</code></pre>
<blockquote>
<p>인터페이스의 멤버(메소드 || 상수)는 항상 public, abstract, static, final이기 때문에  생략이 가능하다. </p>
</blockquote>
<h2 id="인터페이스-구현하는-방법">인터페이스 구현하는 방법</h2>
<blockquote>
<p>인터페이스의 구현이란 인터페이스에 정의된 추상 메소드를 완성하는 것이다. <code>implements</code> 키워드를 사용하여 구현한다.  구현 방법은 총 2가지로 <strong>구현 클래스를 통한 구현</strong>과, <strong>익명 객체를 통한 구현</strong>이 있다.</p>
</blockquote>
<ul>
<li>구현 클래스를 통한 구현</li>
</ul>
<pre><code class="language-java">class 클래스이름 implements 인터페이스이름 {
    // 인터페이스에 정의된 추상메소드를 모두 구현해야 한다.
}</code></pre>
<ul>
<li>익명 객체를 통한 구현</li>
</ul>
<pre><code class="language-java">public class A {
  public static void main(String[] args) {
    인터페이스 변수명 = new 인터페이스() {
      @Override
      public 메소드명() {
        /* 구현부 */
      }
      @Override
      public 메소드명2() {
        /* 구현부 */
      }
    }
  }
}</code></pre>
<ul>
<li>예시</li>
</ul>
<pre><code class="language-java">class Fighter implemments Fightable {
    public void move(int x, int y) {
        System.out.print(x + &quot;만큼 좌로 이동, &quot;);
           System.out.print(y + &quot;만큼 앞으로 이동&quot;);
    }

    public void Attack(Unit unit) {
        System.out.print(unit + &quot;에게 공격을 합니다&quot;);
    } 
}</code></pre>
<blockquote>
<p>Fighter 클래스는 FIghtable 인터페이스를 구현 했다. </p>
</blockquote>
<ul>
<li>일부만 구현할 경우에는 클래스 앞에 <code>abstact</code> 키워드를 붙혀야 한다.</li>
</ul>
<pre><code class="language-java">abstract class Fighter implemments Fightable {
    public void move(int x, int y) {
        System.out.print(x + &quot;만큼 좌로 이동, &quot;);
           System.out.print(y + &quot;만큼 앞으로 이동&quot;);
    }
}</code></pre>
<blockquote>
<p>attack 메소드는 구현하지 않았다. 따라서 추상 메소드가 존재하기 때문에 구현 클래스는 abstract 키워드를 가진 추상 클래스가 된다.</p>
</blockquote>
<h3 id="인터페이스를-이용한-다형성-개인-공부">인터페이스를 이용한 다형성 <code>개인 공부</code></h3>
<blockquote>
<p>인터페이스 또한 다형성을 구현하는 기술이 사용된다.</p>
<ul>
<li>다형성?
: 하나의 타입에 대입되는 객체에 따라서 실행 결과가 다양한 상태로 나오는 성질을 말함</li>
</ul>
<p>클래스에서 부모 타입에 어떤 자식 객체를 대입하냐에 따라서 실행 결과가 달라지듯, 인터페이스 타입에 따라 어떤 구현 객체를 대입하느냐에 따라 결과가 달라진다. 상속은 같은 종류의 하위 클래스를 만드는 기술이고, 인터페이스는 사용 방법이 동일한 클래스를 만드는 기술이지만 다형성을 구현하는 점은 틀림없다.</p>
</blockquote>
<pre><code class="language-java">class Fighter extends Unit implements Fightable {
    public void move(int x ,int y) { /* 내용 생략 */ };
    public void attack(Fightable f) { /* 내용 생략 */};
}</code></pre>
<blockquote>
<p>다중 상속의 문제는 각각의 다른 클래스가 둘 다 <code>attack()</code>이라는 메소드를 가지고 있을 때 어떤 메소드를 사용할지에 대한 충돌이 일어난다는 것이다.</p>
<p>인터페이스는 추상 메소드로 선언되어 구현부가 없기 때문에 선언부가 충돌해도 상관이 없다. 따라서 인터페이스를 통해 다중 상속의 문제를 해결하면서 다형성을 구현할 수 있게 된다.</p>
</blockquote>
<ul>
<li>인터페이스 타입의 객체 생성</li>
</ul>
<pre><code class="language-java">// 부모 클래스 상속
Unit u = new Fighter();
// 인터페이스 구현
Fightable f = new Fighter();

f.move(100, 200);
f.attack(new Fighter());</code></pre>
<blockquote>
<p>Fightable의 타입의 참조변수로 Fighter 클래스를 참조하는게 가능하다. 다만 구현된 객체는 Fightable에 정의된 멤버만 사용가능하다. Fightable은 move(), attack() 멤버만 가지고 있기 때문에 그 외의 멤버는 사용하지 못한다.</p>
</blockquote>
<ul>
<li>인터페이스 타입 매개변수는 인터페이스를 구현한 클래스의 객체만 가능하다.</li>
</ul>
<pre><code class="language-java">interface Fightable {
    void move(int x, int y);
    void attack(Fightable f);
}</code></pre>
<ul>
<li>인터페이스를 메소드의 리턴타입으로 지정할 수 있다.</li>
</ul>
<pre><code class="language-java">Fightable method() {
    //Fighter f = new Fighter();
    //return f;
    return new Fighter();
}

Fightable f = method();
// ==
Fightable f = new Fighter();</code></pre>
<blockquote>
<p>Fightable을 구현한 클래스 Fighter가 리턴 타입으로 대입 가능한 것을 확인할 수 있다.</p>
</blockquote>
<h2 id="인터페이스-레퍼런스를-통해-구현체를-사용하는-방법">인터페이스 레퍼런스를 통해 구현체를 사용하는 방법</h2>
<pre><code class="language-java">// 인터페이스 선언
interface Fightable {
  void move(int x, int y);
  void attack(Unit unit);
}

class Fighter implements Fightable {
  @Override
  public void move(int x, int y) {
    System.out.println(x + &quot;만큼 이동, &quot; + y + &quot;만큼 이동&quot;);
  }
     public void attack(Unit unit) {
       System.out.println(unit + &quot;에게 공격을 합니다.&quot;) 
  }
}

class Main {
  public static void main(String[] args) {
        Fighter fighter = new Fighter(); // Fighter 객체 생성 
    fighter.move(100, 200);

    /*
    * 인터페이스 타입의 객체를 선언하면서 인터페이스를 구현한 구현체를 참조하여 객체 초기화
    */
    Fightable fighter_2 = fighter;
    fighter_2.move(200, 400);
  }
}

/* 출력결과 : 
* 100만큼 이동, 200만큼 이동
*
* 200만큼 이동, 400만큼 이동
*/</code></pre>
<p>새로운 인터페이스를 정의했다면 참조 타입으로써 인터페이스를 사용할  수 있다. 해당 인터페이스 타입을 구현한 클래스가 인터페이스를 데이터 타입으로써 인스턴스가 될 수 있다. </p>
<h2 id="인터페이스-상속">인터페이스 상속</h2>
<blockquote>
<p>인터페이스의 조상은 인터페이스만 가능(Object가 최고 조상이 아니다.)</p>
<p>인터페이스는 다중 상속이 가능하다. 추상 메소드는 선언부만 있기 때문에 구현부에서 충돌이 나지 않기 떄문이다. </p>
</blockquote>
<pre><code class="language-java">interface Fightable extends Movable, Attackable { }

interface Movable {
    void move(int x, int y);
}

interface Attackable {
    void attack(Unit unit);
}</code></pre>
<h2 id="인터페이스의-기본-메소드-default-method-java-8">인터페이스의 기본 메소드 (Default Method), JAVA 8</h2>
<p>위에서 적었듯이, 인터페이스는 8버전이 나오면서부터 default 메소드와 static 메소드가 추가 되었다. default 메소드는 인터페이스에 선언되지만 사실은 객체가 가지고 있는 인스턴스 메소드라고 생각해야 한다. </p>
<ul>
<li><p><strong>디폴트 메소드의 필요성</strong></p>
<ul>
<li><p>자바 8에서 default 메소드를 허용하는 이유는 기존 인터페이스의 확장하여 새로운 기능을 추가하기 위함이다.</p>
</li>
<li><p>기존 인터페이스의 이름과 추상 메소드의 변경 없이 디폴트 메소드만 추가할 수 있기 때문에 <strong>이전에 개발한 구현 클래스를 그대로 사용</strong>하면서 <strong>새롭게 개발하는 클래스는 디폴트 메소드를 활용할 수 있다</strong>.</p>
</li>
</ul>
</li>
</ul>
<pre><code class="language-java">/*
* 기존 인터페이스
*/
interface Fightable {
  void move(int x, int y);
  void attack(Unit unit);
}

/*
* 기존 구현 클래스
*/
class Fighter implements Fightable {
  @Override
  public void move(int x, int y) {
    System.out.println(x + &quot;, &quot; + y + &quot;만큼 이동&quot;);
  }
  @Override
  // 생략
}

/*
* 수정 인터페이스
*/
interface Fightable {
  void move(int x, int y);
  void attack(Unit unit);
  // 디폴트 메소드 추가
  default void breath(int sec) {
    System.out.println(sec + &quot;초 만큼 숨을 고릅니다.&quot;);
  }
}

/*
* 새로운 구현 클래스
*/
class newFighter implements Fightable {
  @Override
  public void move(int x, int y) {
    System.out.println(x + &quot;, &quot; + y + &quot;만큼 이동&quot;);
  }
  @Override
  // 생략

  // 디폴트 메소드 재정의, 
  @Override
  public void breath(int sec) {
    System.out.println(sec + &quot;초 만큼 숨을 고릅니다.&quot;);
    System.out.println(&quot;스으으으으읍&quot;);
    System.out.println(&quot;후우우우우우&quot;);
  }
}</code></pre>
<ul>
<li><strong>디폴트 메소드 선언</strong></li>
</ul>
<pre><code class="language-java">[public] default 리턴타입 메소드명(매개변수 목록) { 구현부 }</code></pre>
<blockquote>
<p>형태는 클래스의 인스턴스 메소드와 동일하지만 <code>default</code> 키워드가 리턴 타입 앞에 붙는다. public 특성을 가지고 있기 때문에 public을 생략해도 컴파일 과정에서 자동적으로 추가 된다. 인터페이스에 디폴트 메소드를 추가해도 구현 객체에는 영향이 없으며 <strong>구현 객체에서 재정의가 가능하다는 특징이 있다.</strong></p>
</blockquote>
<ul>
<li><strong>디폴트 메소드 사용</strong></li>
</ul>
<p>디폴트 메소드는 객체의 구현 없이 사용할 수 없다. 따라서 모든 구현 객체가 가지고 있는 기본 메소드라고 생각하면 된다. 그러나 어떤 구현 객체는 디폴트 메소드의 내용이 맞지 않아 수정이 필요할 수도 있다. 구현 클래스를 작성할 때  디폴트 메소드를 재정의해서 자신에게 맞게 수정하면 디폴트 메소드가 호출될 때 재정의한 메소드가 호출된다. </p>
<h2 id="인터페이스의-static-메소드-java-8">인터페이스의 static 메소드, JAVA 8</h2>
<p>디폴트 메소드와 마찬가지로 8버전 부터 추가 되었으며, 객체가 없어도 인터페이스만으로 호출이 가능한 메소드이다. </p>
<ul>
<li>정적 메소드 선언</li>
</ul>
<pre><code class="language-java">[public] static 리턴타입 메소드명(매개변수 목록) { 구현부 }</code></pre>
<blockquote>
<p>형태는 클래스의 정적 메소드와 완전히 동일하다. 정적 메소드 또한 public의 특성을 가지고 있어 생략하더라도 컴파일 과정에서 자동으로 public이 붙는다. <strong>구현 객체에서는 재정의가 불가능하다.</strong></p>
</blockquote>
<h2 id="인터페이스의-private-메소드-java-9">인터페이스의 private 메소드, JAVA 9</h2>
<p>자바 9 버전에서는 private 메소드와 private static 메소드가 추가 되었는데, 캡슐화의 유지를 할 수 있게 되고, 인터페이스 내부에서 <strong>재사용성</strong>이 높아졌다.</p>
<ul>
<li>private 메소드는 <code>abstract</code>, 즉 추상화 될 수 없고 인터페이스 내부에서만 사용할 수 있다.</li>
<li>private static 메소드는 다른 static과 non-static 인터페이스 메소드 내에서 사용할 수 있다.</li>
<li>private non-static 메소드는 private static 메소드 내에서 사용할 수 없다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD_리팩토링]]></title>
            <link>https://velog.io/@seunghan-baek/TDD%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@seunghan-baek/TDD%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Thu, 31 Mar 2022 01:14:58 GMT</pubDate>
            <description><![CDATA[<h2 id="참조---유튜브---190425-tdd-리팩토링-by-자바지기-박재성님"><a href="https://www.youtube.com/watch?v=bIeqAlmNRrA&amp;t=84s">참조 - 유튜브 - 190425 TDD 리팩토링 by 자바지기 박재성님</a></h2>
<blockquote>
<p>TDD와 리팩토링을 잘하는 방법은 오직 연습이다. 하지만 무조건 연습을 많이 한다고 잘할 수 있을까?</p>
<p>무엇인가를 연습할 때는 의식적인 연습이 필요하다.</p>
</blockquote>
<h3 id="의식적인-연습의-7가지-원칙---1만-시간의-재발견">의식적인 연습의 7가지 원칙 - 1만 시간의 재발견</h3>
<ol>
<li>효과적인 훈련 기법이 수립되어 있는 기술 연마</li>
<li>개인의 컴포트 존을 벗어난 지점에서 진행, 자신의 현재 능력을 살짝 넘어가는 작업을 지속적으로 시도</li>
<li>명확하고 구체적인 목표를 가지고 진행</li>
<li>신중하고 계획적이다. 즉 개인이 온전히 집중하고 의식적으로 행동할 것을 요구</li>
<li>피드백과 피드백에 따른 행동 변경을 수반</li>
<li>효과적인 심적 표상을 만들어내는 한편으로 심적 표상에 의존</li>
<li>기존에 습득한 기술의 특정 부분을 집중적으로 개선함으로써 발전시키고, 수정하는 과정을 수반</li>
</ol>
<h3 id="1단계---단위-테스트-연습">1단계 - 단위 테스트 연습</h3>
<p>내가 사용하는 API 사용법을 익히기 위한 학습 테스트에서 시작</p>
<ul>
<li>자바 String 클래스의 다양한 메소드 사용법</li>
</ul>
<pre><code class="language-java">import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class StringTest {

    @Test
    public void split() {
        String[] values = &quot;1.&quot;.split(&quot;,&quot;);
        assertThat(values).contains(&quot;1&quot;);
        values = &quot;1,2&quot;.split(&quot;,&quot;);
        assertThat(values).containsExactly(&quot;1&quot;,&quot;2&quot;);
    }

    @Test
    public void subString() {
        String input = &quot;(1,2)&quot;;
        String result = input.subString(1, input.length() -1);
        assertThat(result).isEqualTo(&quot;1,2&quot;);
    }
}</code></pre>
<ul>
<li>자바 ArrayList에 데이터를 추가, 수정, 삭제하는 방법</li>
</ul>
<pre><code class="language-java">import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class CollectionTest {

    @Test
    public void arrayList() {
        ArrayList&lt;String&gt; values = new ArrayList&lt;&gt;();
        values.add(&quot;first&quot;);f
        values.add(&quot;second&quot;);
        assertThat(values.add(&quot;third&quot;)).isTrue();
        assertThat(values.size()).isEqualTo(3);
        assertThat(values.get(0)).isEqualTo(&quot;first&quot;);
        assertThat(values.contains(&quot;first&quot;)).isTrue();
        assertThat(values.remove(0)).isEqualTo(&quot;first&quot;);
        assertThat(values.size()).isEqualTo(2);
    }
}
</code></pre>
<h3 id="1단계-연습의-효과">1단계 연습의 효과</h3>
<ul>
<li>단위 테스트 방법을 학습할 수 있다.</li>
<li>단위테스트 도구(xUnit)의 사용법을 익힐 수 있다.</li>
<li>사용하는 API에 대한 학습 효과가 있다.</li>
</ul>
<h3 id="1단계-연습-종류">1단계 연습 종류</h3>
<ul>
<li><p>내가 구현하는 메소드 중 Input과 Output이 명확한 클래스 메소드(보통 Util 성격의 메소드)에 대한 단위 테스트를 연습한다.</p>
</li>
<li><p>알고리즘을 학습한다면 알고리즘 구현에 대한 검증을 단위테스트로 한다.</p>
</li>
</ul>
<h3 id="2단계-tdd-연습">2단계 TDD 연습</h3>
<p>어려운 문제를 해결하는 것이 목적이 아니라 TDD 연습이 목적. 난이도가 낮거나 자신에게 익숙한 문제로 시작하는 것을 추천함</p>
<p>웹, 모바일 UI나 DB에 의존관계를 가지지 않는 요구사항으로 연습하는게 좋다(토이 프로젝트)</p>
<h3 id="예시---문자열-덧셈-계산기-요구사항">예시 - 문자열 덧셈 계산기 요구사항</h3>
<table>
<thead>
<tr>
<th>입력(input)</th>
<th>출력(output)</th>
</tr>
</thead>
<tbody><tr>
<td>null || &quot;&quot;</td>
<td>0</td>
</tr>
<tr>
<td>&quot;1&quot;</td>
<td>1</td>
</tr>
<tr>
<td>&quot;1,2&quot;</td>
<td>3</td>
</tr>
<tr>
<td>&quot;1, 2:3&quot;</td>
<td>6</td>
</tr>
</tbody></table>
<h3 id="tdd-circle-of-life">TDD Circle of life</h3>
<blockquote>
<p>   Test Passes :arrow_right: Test Fails :arrow_right: Refactor </p>
</blockquote>
<p>많은 개발자가 놓치는 것이 TDD연습을 실패하는 테스트를 만들고, 패스하고를 반복한다는 것이다. 이 중에서 가장 중요한 것은 리팩토링에 있다고 한다.</p>
<ul>
<li>테스트 코드</li>
</ul>
<pre><code class="language-java">public class StringCalculatorTest {
  @Test
  public void null_또는_빈값() {
    assertThat(StringCalculator.splitAndSum(null)).isEqualTo(0);
    assertThat(StringCalculator.splitAndSum(&quot;&quot;)).isEqualTo(0);    
  }

  @Test
  public void 값_하나() {
        assertThat(StringCalculator.splitAndSum(&quot;1&quot;)).isEqualTo(3);
  }

  @Test
  public void 쉼표_구분자() {
    assertThat(StringCalculator.splitAndSum(&quot;1,2&quot;)).isEqualTo(3);    
  }

  @Test
  public void 쉼표_콜론_구분자() {
    assertThat(StringCalculator.splitAndSum(&quot;1,2:3&quot;)).isEqualTo(6);    
  }
}</code></pre>
<ul>
<li>StringCalculator 코드</li>
</ul>
<pre><code class="language-java">/*
* 리팩토링 전 코드
*/

public class StringCalculator {
  public static int splitAndSum(String text) {
    int result = 0;
    if (text == null || text.isEmpty()) {
      result = 0;
    } else {
      String[] values = text.split(&quot;,|:&quot;);
      for(String value : values) {
        result += Integer.parseInt(value);
      }
    }
    return result;
  }
}</code></pre>
<h2 id="리팩토링-연습---메소드-분리">리팩토링 연습 - 메소드 분리</h2>
<blockquote>
<p>  <strong>테스트 코드는 변경하지 말고, 테스트 대상 코드(프로덕션 코드)를 개선하는 연습을 의식적으로 집중하여 한다.</strong></p>
</blockquote>
<pre><code class="language-java">public class StringCalculator {
  public static int splitAndSum(String text) {
    int result = 0;
    if (text == null || text.isEmpty()) {
      result = 0;
    } else {
      String[] values = text.split(&quot;,|:&quot;);
      for(String value : values) {
        result += Integer.parseInt(value);
      }
    }
    return result;
  }
}</code></pre>
<ol>
<li>한 메서드에는 오직 한 단계의 indent(들여쓰기)를 유지하라.</li>
</ol>
<pre><code class="language-java">      for(String value : values) {
        result += Integer.parseInt(value);
      }</code></pre>
<blockquote>
<p>  현재 위 구문은 들여쓰기가 2인 곳이 있다. 이를 고쳐보자</p>
</blockquote>
<pre><code class="language-java">public class StringCalculator {
  public static int splitAndSum(String text) {
    int result = 0;
    if (text == null || text.isEmpty()) {
      result = 0;
    } else {
      String[] values = text.split(&quot;,|:&quot;);
      result = sum(values); // 들여쓰기가 1로 유지된다!
    }
    return result;
  }

    private static int sum(String[] values) {
      int result = 0;
      for(String value : values) {
        result += Integer.parseInt(value);
      }
      return result;
    }
}</code></pre>
<ol start="2">
<li>메소드가 단 한가지의 일만 하게하라.</li>
</ol>
<pre><code class="language-java">    private static int sum(String[] values) {
      int result = 0;
      for(String value : values) {
        result += Integer.parseInt(value);
      }
      return result;
    }</code></pre>
<blockquote>
<p>  위 메소드를 보면 현재 <strong>문자열을 숫자로 바꾼뒤 이를 result에 담는 두 가지의 일을 하고 있다.</strong></p>
</blockquote>
<pre><code class="language-java">public class StringCalculator {
  public static int splitAndSum(String text) {
    {...}
    }

  private static int[] toInts(String[] values) {
    int[] numbers = new int[values.length];
    for (int i =0; i &lt; values.length; i++) {
      numbers[i] = Integer.parseInt(values[i]);
    }
    return numbers;
  }

    private static int sum(int[] numbers) {
      int result = 0;
      for(String number : numbers) {
        result += number;
      }
      return result;
    }
}      </code></pre>
<blockquote>
<p>  이렇게 메소드를 분리한다면 재사용이 쉽다. for문을 두 번 돌기는 하지만 대부분 구현하는 반복문은 데이터 크기가 크지 않기 때문에 성능 차이에 큰 영향을 끼치지 않는다.</p>
</blockquote>
<ol start="3">
<li>else 예약어를 쓰지 않는다.</li>
</ol>
<pre><code class="language-java">public class StringCalculator {
  public static int splitAndSum(String text) {
    if (text == null || text.isEmpty()) {
        return 0; // 바로 return을 하여 로컬 변수를 만들 필요가 없다!
      }

//  String[] values = text.split(&quot;,|:&quot;); // + 로컬 변수가 필요한가?
//  return sum(values);
    return sum(toInt(text.split(&quot;,|:&quot;)));
    }

  private static int[] toInts(String[] values) {
    int[] numbers = new int[values.length];
    for (int i =0; i &lt; values.length; i++) {
      numbers[i] = Integer.parseInt(values[i]);
    }
    return numbers;
  }

    private static int sum(int[] numbers) {
      int result = 0;
      for(String number : numbers) {
        result += number;
      }
      return result;
    }
}      </code></pre>
<h3 id="compose-method-패턴-적용">compose method 패턴 적용</h3>
<pre><code class="language-java">public class StringCalculator {
  public static int splitAndSum(String text) {
    if(isBlank(text)) {
      return 0;
    }

    return sum(toInts(split(text)));
  }

  private static boolean isBlank(String text) {
    return text == null || text.isEmpty();
  }

  private static String[] split(String text) {
    return text.split(&quot;,|:&quot;);
  }

  private static int[] toInts(String[] values) {
    int[] numbers = new int[values.length];
    for (int i =0; i &lt; values.length; i++) {
      numbers[i] = Integer.parseInt(values[i]);
    }
    return numbers;
  }

    private static int sum(int[] numbers) {
      int result = 0;
      for(String number : numbers) {
        result += number;
      }
      return result;
    }
}</code></pre>
<blockquote>
<p>  1줄 단위의 메소드를 만들어 처음 코드를 파악하는 이에게도 알기 쉽게끔 구현이 된다.</p>
</blockquote>
<h3 id="연습할-때는">연습할 때는</h3>
<ul>
<li>한번에 모든 원칙을 지키면서 리팩토링하려고 연습하지 마라.</li>
<li>한번에 한 가지 명확하고 구체적인 목표를 가지고 연습하라.</li>
<li>연습은 극단적인 방법으로 하는 것도 좋은 방법이다. 예를 들어 메소드의 라인을 15줄 -&gt; 10줄로 줄여가면서 연습하는 것도 좋은 방법이다.</li>
</ul>
<h2 id="리팩토링-연습---클래스-분리">리팩토링 연습 - 클래스 분리</h2>
<h3 id="다시-문자열-덧셈-계산기-요구사항">다시 문자열 덧셈 계산기 요구사항</h3>
<blockquote>
<p>  쉼표(,) 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환.</p>
<p>  <strong>숫자 이외의 값 또는 음수를 전달하는 경우 RuntimeException 예외를 Thorw 한다.</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th>입력(input)</th>
<th>출력(output)</th>
</tr>
</thead>
<tbody><tr>
<td>null || &quot;&quot;</td>
<td>0</td>
</tr>
<tr>
<td>&quot;1&quot;</td>
<td>1</td>
</tr>
<tr>
<td>&quot;1,2&quot;</td>
<td>3</td>
</tr>
<tr>
<td>&quot;1, 2:3&quot;</td>
<td>6</td>
</tr>
<tr>
<td>&quot;-1, 2:3&quot;</td>
<td>RuntimeException</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class StringCalculatorTest {
  @Test
  public void null_또는_빈값() {
    assertThat(StringCalculator.splitAndSum(null)).isEqualTo(0);
    assertThat(StringCalculator.splitAndSum(&quot;&quot;)).isEqualTo(0);    
  }

  @Test
  public void 값_하나() {
        assertThat(StringCalculator.splitAndSum(&quot;1&quot;)).isEqualTo(3);
  }

  @Test
  public void 쉼표_구분자() {
    assertThat(StringCalculator.splitAndSum(&quot;1,2&quot;)).isEqualTo(3);    
  }

  @Test
  public void 쉼표_콜론_구분자() {
    assertThat(StringCalculator.splitAndSum(&quot;1,2:3&quot;)).isEqualTo(6);    
  }

  // +++ 음수값 테스트 추가 +++
  @Test(expected = RuntimeException.class) {
    public void 음수값() {
      StringCalculator.splitAndSum(&quot;-1,2:3&quot;);
    }
  }
}</code></pre>
<ul>
<li>요구사항(음수값 처리)에 따른 클래스 수정</li>
</ul>
<pre><code class="language-java">public class StringCalculator {
  public static int splitAndSum(String text) {
    {...}
    }

  private static int[] toInts(String[] values) {
    int[] numbers = new int[values.length];
    for (int i =0; i &lt; values.length; i++) {
      numbers[i] = toInt(values[i]);
    }
    return numbers;
  }

  private static int[] toInt(String value) {
    int number = Integer.parseInt(value);
    if (number &lt; 0) {
      throw new RuntimeException();
    }
    return number;
  }
}</code></pre>
<ul>
<li>모든 원시값과 문자열을 포장하라.</li>
</ul>
<pre><code class="language-java">  private static int[] toInt(String value) {
    int number = Integer.parseInt(value);
    if (number &lt; 0) {
      throw new RuntimeException();
    }
    return number;
  }</code></pre>
<blockquote>
<p>  위 메소드를 클래스로 분리한다.</p>
</blockquote>
<pre><code class="language-java">public class Positive {
  private int number;

  public Positive(String value) {
    this(Integer.parseInt(value));
  }

  public Positive(int number) {
    if (number &gt; 0) {
      throw new RuntimeException();
    }

    this.number = number;
  }
}</code></pre>
<blockquote>
<p>  Positive(양수)라는 클래스로 분리하므로써 string과 int로 생성자를 구현하였다. 이로 인해 Positive 클래스는 양수를 보장받을 수 있게된다. 따라서 다시 StringCalculator를 다음과 같이 수정할 수 있다.</p>
</blockquote>
<pre><code class="language-java">public class StringCalculator {
  {...}

  private static Positive[] toPositives(String[] values) {
    Postive[] numbers = new Positive[values.length];
    for(int i = 0; i &lt; values.length; i++) {
      numbers[i] = new Positive(values[i]);
    }
    return numbers;
  }

  private static int sum(Positive[] numbers) {
    Positive result = new Positive(0);
    for (Positive number : numbers) {
      result = result.add(number); // Positive.add(int number) 매소드 추가
    }
    return result.getNumber();
  }
}

/*
* Positive add Method
*/

public class Positive {
  {...}

  public Positive add(Positive other) {
    return new Positive(this.number + other.number);
  }

  public int getNumber() {
    return number;
  }
}</code></pre>
<ul>
<li>일급 컬렉션을 사용한다.</li>
</ul>
<blockquote>
<p>  클래스를 포장하는 클래스를 만든다.</p>
</blockquote>
<pre><code class="language-java">import java.util.Set;

public class Lotto {
  private static final int LOTTO_SIZE = 6;

  private final Set&lt;LottoNumber&gt; lotto;

  private Lotto(Set&lt;LottoNumber&gt; lotto) {
    if(lotto.size() != LOTTO_SIZE) {
      throw new IllegalException();
    }
    this.lotto = lotto;
  }
}</code></pre>
<ul>
<li>3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.</li>
</ul>
<pre><code class="language-java">public class WinningLotto {
  private final Lotto lotto;
  private final LottoNumber no;

  public WinningLotto(Lotto lotto, LottoNumber no) {
    if(lotto.contains(no)) {
      throw new IllegalArgumentException();
    }
    this.lotto = lotto;
    this.no = no;
  }

  public Rank match(Lotto userLotto) {
    int matchCount = lotto.match(userLotto);
    boolean matchBonus = userLotto.contains(no);
    return Rank.valueOf(matchCount, matchBonus);
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스터디 - 7주차]]></title>
            <link>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-7%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@seunghan-baek/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%84%B0%EB%94%94-7%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 20 Mar 2022 12:18:39 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p>자바의 패키지에 대해 학습하세요.</p>
<h1 id="학습할-것-필수">학습할 것 (필수)</h1>
<ul>
<li>package 키워드</li>
<li>import 키워드</li>
<li>클래스패스</li>
<li>CLASSPATH 환경변수</li>
<li>-classpath 옵션</li>
<li>접근지시자</li>
</ul>
<h2 id="package-키워드">package 키워드</h2>
<blockquote>
<p>package는 많은 클래스 || 인터페이스들을 체계적으로 관리하기 위해 사용한다. 자바의 클래스가 물리적인 하나의 파일이라면 패키지는 물리적인 하나의 디렉토리이다. 각 디렉토리의 계층 구조는 <code>.</code>으로 구분한다.</p>
</blockquote>
<ul>
<li>패키지를 선언하는 방법은 다음과 같다.</li>
</ul>
<pre><code class="language-java">package 패키지명;</code></pre>
<blockquote>
<p>위와 같은 명령문을 클래스나 인터페이스의 소스 파일에 추가하면 된다. 이때, 패키지 이름에는 패키지의 전체 경로를 포함해야만 한다.</p>
</blockquote>
<h3 id="unnamed-package">Unnamed package</h3>
<p>자바의 모든 클래스는 반드시 하나 이상의 패키지에 포함되어야 한다. 하지만 자바 컴파일러는 소스 파일에 어떤 패키지의 선언도 포함되지 않으면 기본적으로 이름 없는 패키지에 포함해 컴파일한다. 따라서 패키지를 명시하지 않은 모든 클래스와 인터페이스는 모두 같은 패키지(<code>default package</code>)에 포함된다.</p>
<h3 id="fqcnfully-qualified-class-name">FQCN(Fully Qualified Class Name)</h3>
<p>모든 클래스에는 정의된 클래스 이름과 패키지 이름이 있다. 이 둘을 합쳐야 완전하게 한 클래스를 표현할 수 있으며, FQCN이라 한다.</p>
<blockquote>
<p><code>Scanner</code> -&gt; <code>java.util(Scanner의 패키지).Scanner(FQCN)</code></p>
</blockquote>
<h2 id="import-키워드">import 키워드</h2>
<p>선언한 패키지에 속한 클래스를 다른 파일에서 사용하기 위해서는 클래스 이름 앞에 패키지의 경로까지 포함한 풀 네임을 명시해 사용해야 한다.</p>
<p>하지만 클래스를 사용할 때 마다 이렇게 긴 이름을 사용하는 것은 비효율적이므로 <code>import</code> 키워드를 클래스 최상단에 명시하여 자바 컴파일러에게 코드에 사용할 클래스의 패키지 정보를 미리 제공한다.</p>
<h3 id="import의-선언">import의 선언</h3>
<pre><code class="language-java">import 패키지이름.클래스이름;
import 패키지이름.*; </code></pre>
<blockquote>
<p><code>*</code>은 패키지에 속한 모든 클래스를 불러옴을 의미한다.</p>
</blockquote>
<h3 id="import-키워드-사용의-편리성-예제">import 키워드 사용의 편리성 예제</h3>
<pre><code class="language-java">// import 키워드를 사용하지 않았을 때
public class ImportExample {
  com.seung.pack.Import import = new com.seung.pack.Import();
}


// import 키워드를 사용했을 때
import com.seung.pack.Import

public class ImportExmaple {
  Import import = new Import();
}</code></pre>
<h3 id="import의-특징">import의 특징</h3>
<ul>
<li><code>*</code>를 사용하는 것이 해당 패키지에 포함된 다른 모든 하위 패키지의 클래스까지 포함해 주는 것은 아니다.</li>
<li><code>java.lang</code> 패키지에 대해서는 import문을 사용하지 않아도 클래스 이름만으로 사용할 수 있도록 한다.(<code>Built in Package</code>)</li>
</ul>
<h3 id="static-import">static import</h3>
<blockquote>
<p>static 키워드를 가진 메소드 및 변수는 패키지, 클래스를 import하지 않아도 사용이 가능하다.</p>
</blockquote>
<h3 id=""></h3>
<h2 id="클래스패스">클래스패스</h2>
<blockquote>
<p>클래스를 찾기 위한 경로. </p>
<p>JVM이 프로그램을 실행할 때 클래스 파일을 찾는 기준 파일 경로를 말한다. default는 현재 경로를 바라보게 된다. </p>
</blockquote>
<ul>
<li><p>자바 프로그램 실행 과정</p>
<p><img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h0gjpar88nj210g0rognr.jpg" alt="스크린샷 2022-03-20 19.54.48"></p>
</li>
</ul>
<blockquote>
<p>자바의 프로그램 실행 과정 중, .java 파일을 .class로 변환하는 과정 즉, 자바 파일이 바이트 코드로 변환되고 Class Loader가 이 .class 파일을 읽기 위해서는 해당 파일의 위치를 찾을 수 있어야 한다. 이 때 classpath에 지정된 경로를 사용한다. classpath는 .class 파일이 포함된 디렉토리와 파일을 <code>:</code>으로 구분한 목록이다.  </p>
</blockquote>
<h3 id="classpath에-사용할-수-있는-값">classpath에 사용할 수 있는 값</h3>
<ul>
<li>/Users/dir/ 등 디렉토리 형식</li>
<li>classes.zip 등의 zip 파일</li>
<li>.jar (자바 아카이브) 파일</li>
</ul>
<blockquote>
<p>세 가지 유형을 모두 사용한다면 다음과 같이 지정할 수 있다.</p>
</blockquote>
<pre><code class="language-java">/export/home/username/java/classes:/export/home/username/java/classes/util.zip:/export/home/username/java/classes/xxx.jar</code></pre>
<h3 id="classpath-사용법">classpath 사용법</h3>
<p>기본적으로 패키지에 포함되지 않은 java 소스 파일을 컴파일할 때, classpath를 설정하게 된다. <code>.java</code> 파일 이름을 abc.java로 지정했다고 가정하고, <code>javac abc.java</code> 명령을 사용하여 파일을 컴파일하면 javac 명령을 실행한 디렉토리에 컴파일된 .class 파일이 생성된다. 이 디렉토리를 /export/home/username이라고 가정할 때 /export/home/username/abc.class 파일이 생기는데 abc.java 파일에 포함된 프로그램을 실행하려면 classpath를 지정해주어야 한다.</p>
<p>클래스패스를 지정할 수 있는 방법은 두 가지이다.</p>
<ol>
<li><strong>시스템 환경변수 CLASSPATH를 사용하는 방법</strong></li>
<li><strong>java runtime에 -classpath 옵션을 사용하는 방법</strong></li>
</ol>
<h2 id="classpath-환경-변수">CLASSPATH 환경 변수</h2>
<p>환경변수는 운영체제에 지정하는 변수로 JVM과 같은 애플리케이션들은 환경변수의 값을 참고해서 동작하게 된다. 자바는 클래스패스로 환경변수 CLASSPATH를 사용하는데, 이 값을 지정하면 실행할 때마다 -classpath 옵션을 사용하지 않아도 돼 편리하지만 운영체제가 바뀐다면 path가 사라진다.</p>
<h2 id="-classpath-옵션">-classpath 옵션</h2>
<pre><code class="language-java">CLASSPATH=/export/home/username

// 현재 작업 디렉토리가 /export/home/username인 경우 간단하게 설정이 가능하다.
CLASSPATH=.
// 다른 디렉토리에 클래스 파일이 더 있으면, 다음과 같이 classpath를 설정해야 한다.
CLASSPATH=/export/home/username:/export/home/username/util</code></pre>
<h2 id="접근제어자">접근제어자</h2>
<blockquote>
<p>클래스, 메소드, 인스턴스 및 클래스 변수를 선언할 때 사용된다. 총 네 가지로 <code>public</code>, <code>private</code>, <code>default</code>, <code>protected</code>가 있다.</p>
</blockquote>
<h3 id="각-제어자의-접근-권한">각 제어자의 접근 권한</h3>
<ul>
<li><strong>public</strong> : 누구든 접근 가능</li>
<li><strong>protected</strong> : 같은 패키지에 있거나 상속 받는 경우 접근 가능</li>
<li><strong>private</strong> : 해당 클래스 내에서만 접근 가능</li>
<li><strong>default</strong> : 같은 패키지 내에서 접근 가능</li>
</ul>
<blockquote>
<ul>
<li><p>클래스패스 참조</p>
<p><a href="https://effectivesquid.tistory.com/entry/%EC%9E%90%EB%B0%94-%ED%81%B4%EB%9E%98%EC%8A%A4%ED%8C%A8%EC%8A%A4classpath%EB%9E%80#recentComments">https://effectivesquid.tistory.com/entry/%EC%9E%90%EB%B0%94-%ED%81%B4%EB%9E%98%EC%8A%A4%ED%8C%A8%EC%8A%A4classpath%EB%9E%80#recentComments</a></p>
<p><a href="https://jjunbbang.tistory.com/8">https://jjunbbang.tistory.com/8</a></p>
</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Stream]]></title>
            <link>https://velog.io/@seunghan-baek/Stream</link>
            <guid>https://velog.io/@seunghan-baek/Stream</guid>
            <pubDate>Tue, 08 Mar 2022 03:32:11 GMT</pubDate>
            <description><![CDATA[<h1 id="스트림">스트림</h1>
<blockquote>
<p>스트림은 자바 8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이다.</p>
</blockquote>
<h2 id="반복자-스트림">반복자 스트림</h2>
<blockquote>
<p>자바 7까지는 List<String> 컬렉션에서 요소를 순차적으로 처리하기 위해 <code>Iterator</code> 반복자를 사용했다.</p>
</blockquote>
<ul>
<li>Iterator 사용</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; list = Arrays.asList(&quot;홍길동&quot;, &quot;백승한&quot;, &quot;김자바&quot;);
Iterator&lt;String&gt; iterator = list.iterator();
while(iterator.hasNext()) {
    String name = iterator.next();
    System.out.println(name);
}</code></pre>
<ul>
<li>Stream 사용</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; list = Arrays.asList(&quot;홍길동&quot;, &quot;백승한&quot;, &quot;김자바&quot;);
Stream&lt;String&gt; stream = list.stream();
stream.forEach(name -&gt; System.out.println(name));</code></pre>
<p>컬렉션의 <code>stream()</code>메소드로 스트림 객체를 얻고 나서 <code>stream.forEach( name -&gt; System.out.println(name) );</code> 메소드를 통해 컬렉션의 요소를 하나씩 콘솔에 출력한다. <code>forEach()</code>  메소드는 다음과 같이 Consumer 함수적 인터페이스 타입의 매개값을 가지므로 컬렉션의 요소를 소비할 코드를 람다식으로 기술할 수 있다.</p>
<ul>
<li>forEach 문법</li>
</ul>
<pre><code class="language-java">void forEach( Consumer&lt;T&gt; action );</code></pre>
<h2 id="스트림의-특징">스트림의 특징</h2>
<blockquote>
<p>Stream은 Iterator와 비슷한 역할을 하는 반복자이지만, 람다식으로 요소 처리 코드를 제공하는 점과 내부 반복자를 사용하므로 병렬 처리가 쉽다는 점, 중간 처리와 최종 처리 작업을 수행하는 점에서 많은 차이를 가지고 있다. 자세하게 살펴보자</p>
</blockquote>
<h3 id="람다식으로-요소-처리-코드를-제공한다">람다식으로 요소 처리 코드를 제공한다.</h3>
<p>Stream이 제공하는 대부분의 요소 처리 메소드는 함수적 인터페이스 매개 타입을 가지기 때문에 람다식 또는 메소드 참조를 이용해서 요소 처리 내용을 매개값으로 전달할 수 있다. </p>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class LambdaExpressionExample {
    public static void main(String[] args) {
        List&lt;Student&gt; list = Arrays.asList(
                new Student(&quot;cho&quot;, 25),
                new Student(&quot;baek&quot;, 25),
                new Student(&quot;weon&quot;, 25)
        );

        Stream&lt;Student&gt; stream = list.stream();
        stream.forEach( s -&gt; {
            String name = s.getName();
            int score = s.getScore();
            System.out.println(&quot;이름 : &quot; + name + &quot;, 점수 : &quot; + score);
        });
    }
}</code></pre>
<h3 id="내부-반복자를-사용하므로-병렬처리가-쉽다">내부 반복자를 사용하므로 병렬처리가 쉽다.</h3>
<p><strong>외부 반복자</strong>란 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴을 말한다.  </p>
<p>내부 반복자는 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야 할 코드만 제공하는 코드 패턴을 말한다.  </p>
<p>내부 반복자를 사용해서 얻는 이점은 컬렉션 내부에서 어떻게 요소를 반복시킬 것인가는 컬렉션에게 맡겨두고, 개발자는 요소 처리 코드에만 집중할 수 있다고 집중할 수 있다. 내부 반복자는 요소들의 반복 순서를 변경하거나, 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있게 도와주기 때문에 하나씩 처리하는 순차적 외부 반복자 보다 효율적으로 요소를 반복시킬 수 있다.  </p>
<p>Iterator는 컬렉션 요소를 가져오는 것부터 처리까지 모두 작성해야 하지만, 스트림은 람다식으로 요소 처리 내용만 전달할 뿐, 반복은 컬렉션 내부에서 일어난다.  </p>
<ul>
<li>병렬처리 : 한가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것.</li>
</ul>
<p>병렬 처리 스트림을 이용하면 런타임 시 하나의 작업을 서브 작업으로 자동으로 나누고, 이 결과를 자동으로 결합하여 결과를 만든다. 즉, 여러개의 스레드가 요소들을 부분적으로 합하고 이 부분합을 최종 결합하여 전체 합을 생성한다.</p>
<pre><code class="language-java">package stream;

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;baek&quot;, &quot;cho&quot;, &quot;weon&quot;);

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


        // 병렬 처리
        Stream&lt;String&gt; parallelStream = list.parallelStream();
        parallelStream.forEach(ParallelExample::print);
    }


    public static void print(String str) {
        System.out.println(str + &quot; : &quot; + Thread.currentThread().getName());

    }
}</code></pre>
<h3 id="스트림은-중간-처리와-최종-처리를-할-수-있다">스트림은 중간 처리와 최종 처리를 할 수 있다.</h3>
<p>스트림은 컬렉션의 요소에 대해 중간 처리와 최종 처리를 수행할 수 있는데, 중간 처리에서는 <strong>매핑, 필터링, 정렬을 수행</strong>하고 최종 처리에서는 <strong>반복, 카운팅, 평균, 총합 등</strong>의 집계 처리를 수행한다.  </p>
<ul>
<li>List에 저장되어 있는 Student 객체를 중간 처리에서 score 필드값으로 매핑하여 최종 처리에서 평균값 산출하는 예제</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;

public class MapAndReduceExample {
    public static void main(String[] args) {
        List&lt;Student&gt; studentList = Arrays.asList(
                new Student(&quot;baek&quot;, 70),
                new Student(&quot;cho&quot;, 58),
                new Student(&quot;weon&quot;, 80)
        );

        double avg = studentList.stream()
                .mapToInt(Student::getScore)
                .average()
                .getAsDouble();

        System.out.println(&quot;평균 점수 : &quot; + avg);
    }
}</code></pre>
<h2 id="스트림의-종류">스트림의 종류</h2>
<blockquote>
<p>자바 8부터 새로 추가된 <code>java.util.stream</code> 패키지에는 부모 인터페이스인 BaseStream과 그 자식들로 이루어져 있다.  </p>
<p>BaseStream에는 공통 메소드가 있을 뿐, 직접적으로 사용은 하지 않는다.</p>
</blockquote>
<h3 id="자식-인터페이스">자식 인터페이스</h3>
<ol>
<li><p>Stream</p>
</li>
<li><p>IntStream</p>
</li>
<li><p>LongStream</p>
</li>
<li><p>DoublStream</p>
</li>
</ol>
<blockquote>
<p><code>Stream</code>은 객체 요소를 처리하는 스트림이고, 나머지는 기본 타입인 int, long, double을 처리하는 스트림이다.</p>
</blockquote>
<h3 id="스트림-구현-객체를-얻는-소스">스트림 구현 객체를 얻는 소스</h3>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드 (매개변수)</th>
<th>소스</th>
</tr>
</thead>
<tbody><tr>
<td>Stream&lt;T&gt;</td>
<td>java.util.Collection.stream()<br />java.util.Collection.parallelStream()</td>
<td>컬렉션</td>
</tr>
<tr>
<td>Stream<T><br />IntStream<br />LongStream<br />DoubleStream</td>
<td>Arrays.Stream(T[])          Stream.of(T[])<br />Arrays.stream(int[])          IntStream.of(int[])<br />Arrays.stream(long[])          LongStream.of(long[])<br />Arrays.stream(double[])          DoubleStream.of(double[])</td>
<td>배열</td>
</tr>
<tr>
<td>IntStream</td>
<td>IntStream.range(int, int)<br />IntStream.rangeClosed(int, int)</td>
<td>int 범위</td>
</tr>
<tr>
<td>LongStream</td>
<td>LongStream.range(long, long)<br />LongStream.rangeClosed(long, long)</td>
<td>long 범위</td>
</tr>
<tr>
<td>Stream<Path></td>
<td>Files.find(Path, int, BiPredicate, FileVisitOption)<br />Files.list(Path)</td>
<td>디렉토리</td>
</tr>
<tr>
<td>Stream<String></td>
<td>Files.lines(Path, Charset)<br />BufferedReader.lines()</td>
<td>파일</td>
</tr>
<tr>
<td>DoubleStream</td>
<td>Random.doubles(...)<br />Random.ints()<br />Random.longs()</td>
<td>랜덤 수</td>
</tr>
</tbody></table>
<h3 id="컬렉션으로부터-스트림-얻기">컬렉션으로부터 스트림 얻기</h3>
<blockquote>
<p>다음 예제는 List<Stream> 컬렉션에서 Stream<Student>를 얻어내고 요소를 콘솔에 출력한다.</p>
</blockquote>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class FromCollectionExample {
    public static void main(String[] args) {
        List&lt;Student&gt; studentList = Arrays.asList(new Student(&quot;baek&quot;, 25), new Student(&quot;weon&quot;, 25),
                new Student(&quot;cho&quot;, 25));

        Stream&lt;Student&gt; stream = studentList.stream();

        stream.forEach(s -&gt; {
            System.out.println(s.getName());
        });
    }
}</code></pre>
<h3 id="배열로부터-스트림-얻기">배열로부터 스트림 얻기</h3>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.stream.Stream;

public class FromArrayExample {
    public static void main(String[] args) {
        String[] strArray = {&quot;baek&quot;, &quot;weon&quot;, &quot;cho&quot;};
        Stream&lt;String&gt; strStream = Arrays.stream(strArray);

        strStream.forEach(a -&gt; System.out.print(a + &quot;, &quot;));
    }
}</code></pre>
<h3 id="숫자-범위로부터-스트림-얻기">숫자 범위로부터 스트림 얻기</h3>
<blockquote>
<p>1부터 100까지의 합을 구하기 위해 IntStream의 rangeClosed() 메소드를 사용한다.  </p>
<p><code>rangeClosed()</code>는 첫 번쨰 매개값에서부터 두 번째 매개값까지 순차적으로 제공하는 IntStream을 리턴한다. <code>range()</code>는 똑같이 IntStream을 리턴하는데, 두 번쨰 매개값은 포함하지 않는다.</p>
</blockquote>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class FromIntRangeExample {
    public static int sum;

    public static void main(String[] args) {

        IntStream stream = IntStream.rangeClosed(1, 100);
        stream.forEach(a -&gt; sum += a);
        System.out.println(&quot;총합 : &quot; + sum);
    }
}</code></pre>
<h3 id="파일로부터-스트림-얻기">파일로부터 스트림 얻기</h3>
<blockquote>
<p><code>Files</code>의 정적 메소드인 lines()와BufferedReader의 lines() 메소드를 이용하여 문자 파일의 내용을 스트림을 통해 행단위로 읽고 콘솔에 출력한다.</p>
</blockquote>
<pre><code class="language-java">package stream;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class FromFileContentExameple {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get(&quot;/Exam_Source/stream/linedata.txt&quot;);
        Stream&lt;String&gt; stream;

        // Files.lines() 메소드 ㅇ이용
        stream = Files.lines(path, Charset.defaultCharset());
        stream.forEach(System.out::print);
        System.out.println();

        // BufferedReader lines() 이용
        File file = path.toFile();
        FileReader fileReader = new FileReader(file);
        BufferedReader br = new BufferedReader(fileReader);
        stream = br.lines();
        stream.forEach(System.out::println);
    }
}</code></pre>
<h3 id="디렉토리로부터-스트림-얻기">디렉토리로부터 스트림 얻기</h3>
<h2 id="스트림-파이프라인">스트림 파이프라인</h2>
<blockquote>
<p>리덕션 : 대량의 데이터를 가공해서 축소하는 것. 합계, 평균값, 카운팅, 최대값 등이 대표적인 리덕션의 결과물이라고 볼 수있다. 그러나 컬렉션의 요소를 리덕션의 결과물로 바로 집계할 수 없을 경우에는 집계하기 좋도록 필터링, 매핑, 정렬, 그룹핑 등의 중간 처리가 필요하다.</p>
</blockquote>
<h3 id="중간-처리와-최종-처리">중간 처리와 최종 처리</h3>
<blockquote>
<p>스트림은 데이터의 중간처리(필터링, 매핑, 그룹핑)와 최종 처리(카운팅, 평균, 합계)를 파이프라인으로 해결한다. 파이프라인은 <strong>여러 개의 스트림이 연결되어 있는 구조를 말한다</strong>. 파이프라인에서 최종 처리를 제외하고는 모두 중간 처리 스트림이다.  </p>
<p>중간 스트림이 생성될 때 바로 중간 처리 되는 것이 아니라 최종 처리가 시작되기 전까지 중간처리는 지연된다. 이후 최종 처리가 시작되면 비로소 컬렉션의 요소가 하나씩 처리 되고, 최종 처리 된다.</p>
</blockquote>
<ul>
<li>파이프라인 예시</li>
</ul>
<pre><code class="language-java">// 로컬 변수 사용 시
Stream&lt;Member&gt; maleFemaleStream = list.stream();
Stream&lt;Member&gt; maleStream = maleFemaleStream.filter( m -&gt; m.getSex == Member.Male);
IntStream ageStream = maleStream.mapToInt(Member::getAge);
OptionalDouble optionalDouble = ageStream.average();
double ageAvg = optionalDouble.getAsDouble();

// 로컬 변수 사용 X
double ageAvg = list.stream()
  .filter( a -&gt; a.getSex == Member.MALE)
  .mapToInt(Member::getAge)
  .average()
  .getAsDouble();</code></pre>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamPipelinesExample {
    public static void main(String[] args) {
        List&lt;Member&gt; list = Arrays.asList(
                new Member(&quot;kildong&quot;, Member.MALE, 25),
                new Member(&quot;baek&quot;, Member.MALE, 45),
                new Member(&quot;weon&quot;, Member.FEMALE, 27)
        );

        double ageAvg = list.stream()
                .filter(a -&gt; a.getSex() == Member.MALE)
                .mapToInt(Member::getAge)
                .average()
                .getAsDouble();

        System.out.println(ageAvg);
    }
}</code></pre>
<h3 id="중간-처리-메소드와-최종-처리-메소드">중간 처리 메소드와 최종 처리 메소드</h3>
<blockquote>
<p>스트림 파이프라인에서 중간 처리를 하는 메소드와 최종 처리를 하는 메소드의 종류를 살펴본다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>종류</th>
<th>종류</th>
<th>리턴타입</th>
<th>메소드(매개변수)</th>
<th>소속된 언터페이스</th>
</tr>
</thead>
<tbody><tr>
<td>중간처리</td>
<td>필터링</td>
<td>Stream<br />IntStream<br />LongStream<br />DoubleStream</td>
<td>distinct()</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>filter(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td>매핑</td>
<td></td>
<td>flatMap(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>flatMapToDouble(...)</td>
<td>Stream</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>flatMapToInt(...)</td>
<td>Stream</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>flatMapToLong(...)</td>
<td>Stream</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>map(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>mapToInt(...)</td>
<td>Stream, LongStream, DoubleStream</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>mapToDouble(...)</td>
<td>Stream, LongStream, IntStream</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>mapToLong(...)</td>
<td>Stream, IntStream, DoubleStream</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>mapToObj(...)</td>
<td>IntStream, LongStream, DoubleStream</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>asDoubleStream()</td>
<td>IntStream, LongStream</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>asLongStream()</td>
<td>IntStream</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>boxed()</td>
<td>IntStream, LongStream, DoubleStream</td>
</tr>
<tr>
<td></td>
<td>정렬</td>
<td></td>
<td>sorted(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td>루핑</td>
<td></td>
<td>peek(...)</td>
<td>공통</td>
</tr>
<tr>
<td>최종처리</td>
<td>매칭</td>
<td>boolean</td>
<td>allMatch(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td>boolean</td>
<td>anyMatch(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td>boolean</td>
<td>noneMatch(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td>집계</td>
<td>long</td>
<td>count()</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td>OptionalXXX</td>
<td>findFirst()</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td>OptionalXXX</td>
<td>max(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td>OptionalXXX</td>
<td>min(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td>OptionalDouble</td>
<td>average()</td>
<td>IntStream, LongStream, DoubleStream</td>
</tr>
<tr>
<td></td>
<td></td>
<td>OptionalXXX</td>
<td>reduce(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td></td>
<td>int, long, double</td>
<td>sum()</td>
<td>IntStream, LongStream, DoubleStream</td>
</tr>
<tr>
<td></td>
<td>루핑</td>
<td>void</td>
<td>forEach(...)</td>
<td>공통</td>
</tr>
<tr>
<td></td>
<td>수집</td>
<td>R</td>
<td>collect(...)</td>
<td>공통</td>
</tr>
</tbody></table>
<blockquote>
<p>리턴 타입이 스트림이라면 중간 처리 타입이고, 기본 타입이거나 OptionalXXX라면 최종 처리 메소드이다.</p>
<p>공통의 의미는 Stream, IntStream, LongStream, DoubleStream에서 모두 제공된다는 것이다.  </p>
</blockquote>
<h2 id="필터링distinct-filter">필터링(distinct(), filter())</h2>
<blockquote>
<p>필터링은 중간 처리 기능으로 요소를 걸러내는 역할을 한다.ㄴㄴ</p>
</blockquote>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Stream<br />IntStream<br />LongStream<br />DoubleStream</td>
<td>distinct()</td>
<td>중복제거</td>
</tr>
<tr>
<td></td>
<td>filter(Predicate)</td>
<td>조건 필터링</td>
</tr>
<tr>
<td></td>
<td>filter(IntPredicate)</td>
<td></td>
</tr>
<tr>
<td></td>
<td>filter(LongPredicate)</td>
<td></td>
</tr>
<tr>
<td></td>
<td>filter(DoublePredicate)</td>
<td></td>
</tr>
</tbody></table>
<ul>
<li><p>distinct() 메소드는 중복을 제거하는데, Stream의 경우 Object.eqauls(Object)가 true이면 동일 객체로 판단하고 중복을 제거한다. 그 외 int, long, double Stream의 경우 동일 값이면 중복을 제거한다.</p>
</li>
<li><p>예제</p>
</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;

public class FilteringExample {
    public static void main(String[] args) {
        List&lt;String&gt; names = Arrays.asList(
                &quot;baek&quot;,
                &quot;weon&quot;,
                &quot;cho&quot;,
                &quot;weon&quot;,
                &quot;bung&quot;
        );

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


        // 필터링
        names.stream()
                .filter(n -&gt; n.startsWith(&quot;b&quot;))
                .forEach(n -&gt; System.out.println(n));
        System.out.println();

        // 중복 제거 후 필터링
        names.stream()
                .distinct()
                .filter(n -&gt; n.startsWith(&quot;b&quot;))
                .forEach(n -&gt; System.out.println(n));
    }
}</code></pre>
<h2 id="매핑-flatmapxxx-mapxxx-asxxxstream-boxed">매핑( flatMapXXX(), mapXXX(), asXXXStream(), boxed())</h2>
<blockquote>
<p>매핑은 중간처리 기능으로 스트림의 요소를 다른 요소로 대체하는 작업이다.</p>
</blockquote>
<h3 id="flatmapxxx">flatMapXXX()</h3>
<blockquote>
<p>요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴한다.</p>
</blockquote>
<img width="676" alt="스크린샷 2022-02-22 09 10 50" src="https://user-images.githubusercontent.com/84169773/155125882-3d2115b8-bfb4-4b91-b160-eae4cc290752.png">

<ul>
<li>flatMapXXX() 메소드의 종류는 다음과 같다</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
<th>요소 -&gt;대체 요소</th>
</tr>
</thead>
<tbody><tr>
<td>Stream<R></td>
<td>flatMap(Function&lt;T.Stream<R>&gt;)</td>
<td>T -&gt; Stream<R></td>
</tr>
<tr>
<td>DoubleStream</td>
<td>flatMap(DoubleFunction<DoubleStream>)</td>
<td>double -&gt; DoubleStream</td>
</tr>
<tr>
<td>IntStream</td>
<td>flatMap(IntFucntion<IntStream>)</td>
<td>int -&gt; IntStream</td>
</tr>
<tr>
<td>LongStream</td>
<td>flatMap(LongFunction<LongStream>)</td>
<td>long -&gt; LongStream</td>
</tr>
<tr>
<td>DoubleStream</td>
<td>flatMapToDouble(Function&lt;T, DoubleStream&gt;)</td>
<td>T -&gt; DoubleStream</td>
</tr>
<tr>
<td>IntStream</td>
<td>flatMapToInt(Function&lt;T, IntStream&gt;)</td>
<td>T -&gt; IntStream</td>
</tr>
<tr>
<td>LongStream</td>
<td>flatMapToLong(Function&lt;T, LongStream&gt;)</td>
<td>T -&gt; LongStream</td>
</tr>
</tbody></table>
<ul>
<li>List<String>에 저장된 요소별로 단어를 뽑아 단어 스트림으로 재생성하는 예제</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;

public class FlatMapExample {
    public static void main(String[] args) {
        List&lt;String&gt; inputList1 = Arrays.asList(
                &quot;java8 Lambda&quot;, &quot;stream mapping&quot;
        );

        inputList1.stream()
                .flatMap(data -&gt; Arrays.stream(data.split(&quot; &quot;)))
                .forEach(word -&gt; System.out.println(word));

        List&lt;String&gt; inputList2 = Arrays.asList(&quot;10, 20, 30, 40, 50, 60&quot;);

        inputList2.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(number));
    }
}</code></pre>
<h3 id="mapxxx-메소드">mapXXX() 메소드</h3>
<blockquote>
<p>요소를 대체하는 요소로 구성된 새로운 스트림을 리턴한다. A와 B스트림이 있을때 각각 A-&gt;C, B-&gt;D 요소로 대체된다고 할 경우 C, D 요소를 가지는 새로운 스트림이 생성된다.</p>
</blockquote>
<ul>
<li>mapXXX() 메소드 종류</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드 (매개 변수)</th>
<th>요소 -&gt; 대체요소</th>
</tr>
</thead>
<tbody><tr>
<td>Stream<R></td>
<td>map(Function&lt;T, R&gt;)</td>
<td>T -&gt; R</td>
</tr>
<tr>
<td>DoubleStream</td>
<td>mapToDouble(ToDoubleFunction<T>)</td>
<td>T -&gt; Double</td>
</tr>
<tr>
<td>IntStream</td>
<td>mapToInt(ToIntFunction<T>)</td>
<td>T -&gt; Int</td>
</tr>
<tr>
<td>LongStream</td>
<td>mapToLong(ToLongFunction<T>)</td>
<td>T -&gt; Long</td>
</tr>
<tr>
<td>DoubleStream</td>
<td>map(DoubleUnaryOperator)</td>
<td>double -&gt; double</td>
</tr>
<tr>
<td>IntStream</td>
<td>mapToInt(DoubleToIntFunction)</td>
<td>double -&gt; Int</td>
</tr>
<tr>
<td>LongStream</td>
<td>mapToLong(DoubleToLongFunction)</td>
<td>double -&gt; long</td>
</tr>
<tr>
<td>Stream<U></td>
<td>mapToObj(DoubleFunction<U>)</td>
<td>double -&gt; U</td>
</tr>
<tr>
<td>IntStream</td>
<td>map(IntUnaryOperator)</td>
<td>int -&gt; int</td>
</tr>
<tr>
<td>DoubleStream</td>
<td>maptoDouble(IntToDoubleFunction)</td>
<td>int -&gt; double</td>
</tr>
<tr>
<td>LongStream</td>
<td>mapToLong(IntToLongFunction)</td>
<td>int -&gt; long</td>
</tr>
<tr>
<td>Stream<U></td>
<td>maoToObj(IntFunction<U>)</td>
<td>int -&gt; U</td>
</tr>
<tr>
<td>LongStream</td>
<td>map(LongUnaryOperator)</td>
<td>long -&gt; long</td>
</tr>
<tr>
<td>DoubleStream</td>
<td>mapToDouble(LongToDoubleFunction)</td>
<td>long -&gt; double</td>
</tr>
<tr>
<td>IntStream</td>
<td>mapToInt(LongToIntFunction)</td>
<td>long -&gt; int</td>
</tr>
<tr>
<td>Stream<U></td>
<td>mapToObj(LongFunction<U>)</td>
<td>long -&gt; U</td>
</tr>
</tbody></table>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;

public class MapExample {
    public static void main(String[] args) {
        List&lt;Student&gt; studentList = Arrays.asList(
                new Student(&quot;홍길동&quot;, 10),
                new Student(&quot;백승한&quot;, 20),
                new Student(&quot;김말이&quot;, 30)
        );
        studentList.stream()
                .mapToInt(Student::getScore)
                .forEach(score -&gt; System.out.println(score));
    }
}</code></pre>
<h3 id="asdoublestream-aslongstream-boxed-메소드">asDoubleStream(), asLongStream(), boxed() 메소드</h3>
<blockquote>
<p>asDoubleStream 메소드는 IntStream의 int 요소 또는 LongStream의 long 요소를 double 요소로 변환해서 DoubleStream을 생성한다.  </p>
<p>asLongStream 메소드 또한 마찬가지로 IntStream의 int요소 또는 DoubleStream의 double 요소를 long으로 변환해서 LongStream을 생성한다.  </p>
<p>boxed() 메소드는 int, long, double 요소를 Wrapper 클래스로 박싱해서 Stream을 생성한다.</p>
</blockquote>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.stream.IntStream;

public class AsDoubleStreamAndBoxedExample {
    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));

        System.out.println();

        intStream = Arrays.stream(intArr);
        intStream
                .boxed()
                .forEach(obj -&gt; System.out.println(obj.intValue()));

    }
}</code></pre>
<h2 id="정렬-sorted">정렬 (sorted())</h2>
<blockquote>
<p>스트림은 요소가 최종 처리되기 전에 중간 단계에서 요소를 정렬해서 최종 처리 순서를 변경할 수 있다. 요소를 정렬하는 메소드는 다음과 같다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드( 매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Stream<T></td>
<td>sorted()</td>
<td>객체를 Comparable 구현 방법에 따라 정렬</td>
</tr>
<tr>
<td>Stream<T></td>
<td>sorted(Comparator<T>)</td>
<td>객체를 주어진 Comparator에 따라 정렬</td>
</tr>
<tr>
<td>DoubleStream</td>
<td>sorted()</td>
<td>double 요소를 오름차순 정렬</td>
</tr>
<tr>
<td>IntStream</td>
<td>sorted()</td>
<td>int 요소를 오름차순 정렬</td>
</tr>
<tr>
<td>LongStream</td>
<td>sorted()</td>
<td>long 요소를 오름차순 정렬</td>
</tr>
</tbody></table>
<p>객체 요소일 경우에는 클래스가 Comparable을 구현하지 않으면 sorted() 메소드를 호출했을 때 <code>ClassCastException</code>이 발생한다. 따라서 Comparable을 구현한 요소에서만 sorted() 메소드를 호출해야 한다.</p>
<p>Comparable을 구현한 객체이면 다음 중 하나의 방법으로 sorted()를 호출하면 된다.</p>
<pre><code class="language-java">sorted();
sorted( (a,b) -&gt; a.compareTo(b) );
sorted( Comaparator.naturalOrder() );</code></pre>
<p>만약 객체 요소가 Comparable을 구현하고 있지만 기본비교 방법과 정반대 방법으로 정렬하고 싶다면 다음과 같이 sorted()를 호출하면 된다.</p>
<pre><code class="language-java">sorted( (a,b) -&gt; b.compare(a) );
sorted( Comparator.reverseOrder() );</code></pre>
<p>만약 객체 요소가 Comaprable을 구현하지 않았다면 Comparable을 매개값으로 갖는 sorted() 메소드를 사용하면 된다. Comparator는 함수적 인터페이스이므로 다음과 같이 작성할 수 있다.</p>
<pre><code class="language-java">sorted( (a,b) -&gt; { . . . })</code></pre>
<blockquote>
<p>중괄호 안에는 a와 b 중 a가 작으면 음수, 같으면 0, 크면 1을 리턴하는 코드를 작성하면 된다.</p>
</blockquote>
<ul>
<li>예제 - 숫자 요소일 경우 오름차순 정렬, Student 요소일 경우 기본 비교(Comparable) 기준으로 오름차순 정렬 후 출력</li>
</ul>
<pre><code class="language-java">package stream;

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, 4, 3, 1});
        intStream
                .sorted()
                .forEach(n -&gt; System.out.print(n + &quot;,&quot;));
        System.out.println();

        //객체 요소일 경우
        List&lt;Student&gt; studentList = Arrays.asList(
                new Student(&quot;홍길동&quot;, 30),
                new Student(&quot;백승한&quot;, 40),
                new Student(&quot;신용권&quot;, 20)
        );

        studentList.stream()
                .sorted()   // 오름차순 정렬
                .forEach(s -&gt; System.out.print(s.getScore() + &quot;,&quot;));
        System.out.println();


        studentList.stream()
                .sorted(Comparator.reverseOrder()) // 내림차순 정렬
                .forEach(s -&gt; System.out.print(s.getScore() + &quot;,&quot;));

    }
}</code></pre>
<h2 id="루핑peek-foreach">루핑(peek(), forEach())</h2>
<blockquote>
<p>루핑은 요소 전체를 반복하는 것을 말한다. 루핑하는 메소드에는 peek(), forEach()가 있다. 각자 루핑의 기능은 같지만 동작 방식이 다르다. peek()은 중간 처리 메소드, forEach()는 최종 처리 메소드이다.  </p>
</blockquote>
<ul>
<li><p>peek()은 중간 처리 단계에서 전체 요소를 루핑하며 추가적 작업을 하기 위해 사용한다. 반드시 최종 처리 메소드가 호출되야 작동한다.  </p>
<pre><code class="language-java">intStream
  .filter( a -&gt; a % 2 == 0 )
  .peek( a -&gt; System.out.println(a)) // peek() 지연
  .sum() // 정상 작동</code></pre>
<blockquote>
<p>요소 처리의 최종 단계가 전체 합이라면 sum() 메소드를 호출해야만 정상적으로 동작한다.</p>
</blockquote>
</li>
</ul>
<ul>
<li>forEach()는 최종 처리 메소드이기 때문에 파이프라인 마지막에 루핑하면서 요소를 하나씩 처리한다. forEach()는 요소를 소비하는 최종 처리 메소드이므로 이후에 sum()처럼 다른 최종 처리 메소드를 호출하면 안된다.</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;

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

        System.out.println(&quot;[peek()를 마지막에 호출한 경우]&quot;);
        Arrays.stream(intArr)
                .filter(a -&gt; a % 2 == 0)
                .peek(n -&gt; System.out.println(n)); // 동작 X

        System.out.println(&quot;[최종 처리 메소드를 마지막에 호출한 경우]&quot;);
        int total = Arrays.stream(intArr)
                .filter(a -&gt; a % 2 == 0)
                .peek(n -&gt; System.out.println(n)) // 동작 O
                .sum(); // 최종 처리 메소드
        System.out.println(&quot;총합 : &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>
<h2 id="매칭allmatch-anymatch-nonematch">매칭(allMatch(), anyMatch(), noneMatch())</h2>
<blockquote>
<p>스트림 클래스는 최종 처리 단계에서 요소들이 특정 조건에 만족하는지 조사할 수 있도록 세가지 매칭 메소드를 제공하고 있다.  </p>
<ul>
<li><p>allMatch() : 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하는지 조사한다.</p>
</li>
<li><p>anyMatch() :  최소한 한 개의 요소가 매개값으로 주어진 Predicate의 조건을 만족하는지 조사한다.</p>
</li>
<li><p>noneMatch() : 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하지 않는지 조사한다.</p>
</li>
</ul>
</blockquote>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
<th>제공 인터페이스</th>
</tr>
</thead>
<tbody><tr>
<td>boolean</td>
<td>allMatch(Predicate<T> predicate)<br />anyMatch(Predicate<T> predicate)<br />noneMatch(Predicate<T> predicate)</td>
<td>Stream</td>
</tr>
<tr>
<td>boolean</td>
<td>allMatch(IntPredicate<T> predicate)<br />anyMatch(IntPredicate<T> predicate)<br />noneMatch(IntPredicate<T> predicate)</td>
<td>IntStream</td>
</tr>
<tr>
<td>boolean</td>
<td>allMatch(LongPredicate<T> predicate)<br />anyMatch(LongPredicate<T> predicate)<br />noneMatch(LongPredicate<T> predicate)</td>
<td>LongStream</td>
</tr>
<tr>
<td>boolean</td>
<td>allMatch(DoublePredicate<T> predicate)<br />anyMatch(DoublePredicate<T> predicate)<br />noneMatch(DoublePredicate<T> predicate)</td>
<td>DoubleStream</td>
</tr>
</tbody></table>
<ul>
<li>예제 - int 배열 스트림 생성(모든 요소가 2의 배수인지, 하나라도 3의 배수가 존재하는지, 모든 요소가 3의 배수가 아닌지를 조사)</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;

public class MatchExample {
    public static void main(String[] args) {
        int[] intArr = {2, 4, 6};

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

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

        result = Arrays.stream(intArr)
                .noneMatch(a -&gt; a % 3 == 0);
        System.out.println(&quot;3의 배수가 없는가? : &quot; + result);
    }
}</code></pre>
<h2 id="기본-집계sum-count-average-min-max">기본 집계(sum(), count(), average(), min(), max())</h2>
<blockquote>
<p>집계는 최종 처리 기능으로 요소들을 처리해서 카운팅, 합계, 평균값, 최대값, 최소값 등과 같이 하나의 값으로 산출하는 것을 말한다.  </p>
<p>집계 : 대량의 데이터를 가공해서 축소하는 리덕션</p>
</blockquote>
<h3 id="스트림이-제공하는-기본-집계">스트림이 제공하는 기본 집계</h3>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>long</td>
<td>count()</td>
<td>요소 개수</td>
</tr>
<tr>
<td>OptionalXXX</td>
<td>findFirst()</td>
<td>첫 번째 요소</td>
</tr>
<tr>
<td>Optional<T><br />OptionalXXX</td>
<td>max(Comparator<T>)<br />max()</td>
<td>최대 요소</td>
</tr>
<tr>
<td>Optional<T><br />OptionalXXX</td>
<td>min(Comparator<T>)<br />min()</td>
<td>최소 요소</td>
</tr>
<tr>
<td>OptionalDouble</td>
<td>average()</td>
<td>요소 평균</td>
</tr>
<tr>
<td>int, long, double</td>
<td>sum()</td>
<td>요소 합계</td>
</tr>
</tbody></table>
<blockquote>
<p><code>OptionalXXX</code> 는 자바 8에서 추가한 <code>java.util</code>패키지의 Optional, OptionalInt, OptionalLong, OptionalDouble을 말한다. 이들은 값을 저장하는 값 기반 클래스이다. </p>
</blockquote>
<h3 id="optional-클래스">Optional 클래스</h3>
<blockquote>
<p>이 클래스들은 저장하는 값의 타입만 다를 뿐 제공하는 기능은 거의 동일하다. Optional 클래스는 단순히 집계 값만 저장하는 것이 아니라, 집계 값이 존재하지 않을 경우 디폴트 값을 설정할 수 도 있고, 집계 값을 처리하는 Consumer도 등록이 가능하다.</p>
</blockquote>
<ul>
<li>메소드 종류</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>boolean</td>
<td>isPresent()</td>
<td>값이 저장되어 있는지 여부</td>
</tr>
<tr>
<td>T<br />double<br />int<br />long</td>
<td>orElse(T)<br />orElse(double)<br />orElse(int)<br />orElse&lt;long</td>
<td>값이 저장되어 있지 않을 경우 Default 값 지정</td>
</tr>
<tr>
<td>void</td>
<td>ifPresent(Consumer)<br />ifPresent(DoubleConsumer)<br />ifPresent(IntConsumer)<br />ifPresent(LongConsumer)</td>
<td>값이 저장되어 있을 경우 Consumer에서 처리</td>
</tr>
</tbody></table>
<ul>
<li>컬렉션은 주로 동적으로 요소가 추가된다. 만약 값이 없을 때 평균(average())를 구한다면NoSuchElementException이 발생하게 된다.</li>
<li>요소가 없을 경우 예외를 피하는 3가지 방법에 대해서 알아보자.</li>
</ul>
<pre><code class="language-java">// 1. Optional 객체 얻어 isPresent() 메소드로 평균 여부 확인
OptionalDouble optional = list.stream()
  .mapToInt(Integer :: intValue)
  .average();
if(optional.isPresent()) {
  System.out.println(&quot;평균 : &quot; + optional.getAsDouble());
} else {
  System.out.println(&quot;평균 : 0.0&quot;);
}

// 2. orElse() 사용
double avg = list.stream()
  .mapToInt(Integer :: intValue)
  .average()
  .orElse(0.0);
System.out.println(&quot;평균 : &quot; + avg);

// 3. ifPresent() 메소드 사용 - 평균값이 있을 경우에만 값 사용
list.stream()
  .mapToInt(Integer :: intValue)
  .average()
  .ifPresent(a -&gt; System.out.println(&quot;평균 : &quot; + a));</code></pre>
<ul>
<li>집계 예제</li>
</ul>
<pre><code class="language-java">package stream;

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

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

        // 예외 발생 NoSuchElementException
//        double avg = list.stream()
//                .mapToInt(Integer::intValue)
//                .average()
//                .getAsDouble();

        // 방법 1
        OptionalDouble optional = list.stream()
                .mapToInt(Integer::intValue)
                .average();
        if (optional.isPresent()) {
            System.out.println(&quot;평균 : &quot; + optional.getAsDouble());
        } else {
            System.out.println(&quot;방법1 평균 : 0.0&quot;);
        }

        // 방법 2
        double avg = list.stream()
                .mapToInt(Integer::intValue)
                .average()
                .orElse(0.0);
        System.out.println(&quot;방법2 평균 : &quot; + avg);

        // 방법 3
        list.stream()
                .mapToInt(Integer::intValue)
                .average()
                .ifPresent(a -&gt; System.out.println(&quot;방법3 평균 : &quot; + a));
    }
}</code></pre>
<h2 id="커스텀-집계reduce">커스텀 집계(reduce())</h2>
<blockquote>
<p>스트림은 기본 집계 메소드를 제공하지만, 프로그램화 해서 다양한 집계 결과물을 만들 수 있도록 reduce() 메소드도 제공한다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>인터페이스</th>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
</tr>
</thead>
<tbody><tr>
<td>Stream</td>
<td>Optional<T></td>
<td>reduce(BinaryOperator<T> accumulator)</td>
</tr>
<tr>
<td></td>
<td>T</td>
<td>reduce(T identity, BinaryOperator<T> accumulator)</td>
</tr>
<tr>
<td>IntStream</td>
<td>OptionalInt</td>
<td>reduce(IntBinaryOperator op)</td>
</tr>
<tr>
<td></td>
<td>int</td>
<td>reduce(int identity, IntBinaryOperator op)</td>
</tr>
<tr>
<td>LongStream</td>
<td>OptionalLong</td>
<td>reduce(LongBinaryOperator op)</td>
</tr>
<tr>
<td></td>
<td>long</td>
<td>reduce(long identity, LongBinaryOperator op)</td>
</tr>
<tr>
<td>DoubleStream</td>
<td>OptionalDouble</td>
<td>reduce(DoubleBinaryOperator op)</td>
</tr>
<tr>
<td></td>
<td>double</td>
<td>reduce(double identity, DoubleBinaryOperator op)</td>
</tr>
</tbody></table>
<blockquote>
<p>각 인터페이스에는 매개 타입으로 XXXOperator, 리턴 타입으로 OptionalXXX, int, long, double을 가지는 reduce() 메소드가 오버로딩 되어있다. 스트림에 요소가 전혀 없을 경우 identity(디폴트값)가 리턴된다.</p>
</blockquote>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;

public class ReductionExample {
    public static void main(String[] args) {
        List&lt;Student&gt; studentList = Arrays.asList(
                new Student(&quot;홍길동&quot;, 25),
                new Student(&quot;백승한&quot;, 30),
                new Student(&quot;신용권&quot;, 40)
        );
                // sum() 이용
        int sum1 = studentList.stream()
                .mapToInt(Student::getScore)
                .sum();
                // reduce(BinaryOperator&lt;Integer&gt;) 이용
        int sum2 = studentList.stream()
                .mapToInt(Student::getScore)
                .reduce((a, b) -&gt; a + b)
                .getAsInt();
                // reduce(int identity, IntBinaryOperator op) 이용
        int sum3 = studentList.stream()
                .mapToInt(Student::getScore)
                .reduce(0, (a, b) -&gt; a + b);

        System.out.println(&quot;Sum 1 : &quot; + sum1);
        System.out.println(&quot;Sum 2 : &quot; + sum2);
        System.out.println(&quot;Sum 3 : &quot; + sum3);

    }
}</code></pre>
<h2 id="수집-collect">수집 (collect())</h2>
<blockquote>
<p>스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공한다. 이 메소드를 통해 필요한 요소만 컬렉션으로 담을 수 있고, 요소들을 그룹핑한 후 집계할 수 있다.</p>
</blockquote>
<h3 id="필터링한-요소-수집">필터링한 요소 수집</h3>
<blockquote>
<p>Stream의 collect(Collector&lt;T,A,R&gt; collector) 메소드는 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고, 이 컬렉션을 리턴한다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드 (매개 변수)</th>
<th>인터페이스</th>
</tr>
</thead>
<tbody><tr>
<td>R</td>
<td>collect(Collector&lt;T,A,R&gt; collector)</td>
<td>Stream</td>
</tr>
</tbody></table>
<p>매개값인 Collector는 어떤 요소를 어떤 컬렉션에 수집할 것인지를 결정한다. Collector의 타입 파라미터 T는 요소이고, A는 누적기(accumulator)이다. 그리고 R은 요소가 저장될 컬렉션이다. 즉, T요소를 A에 누적하여 R에 저장한다.</p>
<h3 id="collectors-정적-메소드">Collectors 정적 메소드</h3>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>Collectors의 정적 메소드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Collector&lt;T, ?, List<T>&gt;</td>
<td>toList()</td>
<td>T를 list에 저장</td>
</tr>
<tr>
<td>Collector&lt;T, ?, Set<T>&gt;</td>
<td>toSet()</td>
<td>T를 set에 저장</td>
</tr>
<tr>
<td>Collector&lt;T, ?, Collection<T>&gt;</td>
<td>toCollection(Supplier&lt;Collection<T>&gt;)</td>
<td>T를 Supplier가 제공한 Collection에 저장</td>
</tr>
<tr>
<td>Collector&lt;T, ?, Map&lt;K,U&gt;&gt;</td>
<td>toMap(Function&lt;T,K&gt; keyMapper, Function&lt;T,U&gt; valueMapper)</td>
<td>T를 K와 U로 매핑해서 K를 키로 U를 값으로 Map에 저장</td>
</tr>
<tr>
<td>Collector&lt;T, ?, ConcurrentMap&lt;K,U&gt;&gt;</td>
<td>toCurrentMap(Function&lt;T,K&gt; keyMapper, Function&lt;T,U&gt; valueMapper)</td>
<td>T를 K와 U로 매핑해서 K를 키로, U를 값으로 ConcurrentMap에 저장</td>
</tr>
</tbody></table>
<blockquote>
<p>리턴값인 Collector를 보면 A가 ?로 되어있는데 이것은 Collector가 R에 T를 저장하는 방법을 알고 있어 A가 필요없기 떄문이다. </p>
</blockquote>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">// 1. 변수 사용
Stream&lt;Student&gt; totalStream = totalList.stream();
Stream&lt;Student&gt; maleStream = totalStream.filter(a -&gt; a.getSex() == Student.MALE);
Collector&lt;Student, ?, List&lt;Student&gt;&gt; collector = Collectors.toList();

List&lt;Student&gt; maleList = maleStream.collect(collector);

// 1. 변수 사용 X
List&lt;Student&gt; maleList = totalList.stream()
  .filter(a -&gt; a.getSex() == Student.MALE)
  .collect(Collectors.toList());

// 2. 변수 사용
Stream&lt;Student&gt; totalStream = totalList.stream();
Stream&lt;Student&gt; femaleStream = totalStream.filter(s -&gt; s.getSex() == Student.FEMALE);
Supplier&lt;HashSet&lt;Student&gt;&gt; supplier = HashSet :: new;
Collector&lt;Student, ?, HashSet&lt;Student&gt;&gt; collector = Collectors.toCollection(supplier);
Set&lt;Student&gt; femaleSet = femaleStream.collect(collector);

// 2. 변수 사용 X
Set&lt;Student&gt; femaleSet = totalList.stream()
  .filter(s -&gt; s.getSex() == Student.FEMALE)
    .collect(Collectors.toCollection(HashSet::new));</code></pre>
<h3 id="사용자-정의-컨테이너에-수집하기">사용자 정의 컨테이너에 수집하기</h3>
<blockquote>
<p>List, Map, Set같은 컬렉션이 아니라 사용자 정의 컨테이너 객체에 수집하는 방법에 대해 알아본다.  </p>
<p>스트림은 요소들을 필터링, 매핑해서 사용자 정의 컨테이너 객체에 수집할 수 있도록 collect() 메소드를 추가적으로 제공한다.  </p>
</blockquote>
<table>
<thead>
<tr>
<th>인터페이스</th>
<th>리턴 타입</th>
<th>메소드 (매개 변수)</th>
</tr>
</thead>
<tbody><tr>
<td>Stream</td>
<td>R</td>
<td>collect(Supplier<R>, BiConsumer&lt;R,? super T&gt;, BiConsumer&lt;R,R&gt;)</td>
</tr>
<tr>
<td>IntStream</td>
<td>R</td>
<td>collect(Supplier<R>, ObjIntConsumer<R>, BiConsumer&lt;R,R&gt;)</td>
</tr>
<tr>
<td>LongStream</td>
<td>R</td>
<td>collect(collect(Supplier<R>, ObjLongtConsumer<R>, BiConsumer&lt;R,R&gt;))</td>
</tr>
<tr>
<td>DoubleStream</td>
<td>R</td>
<td>collect(Supplier<R>, ObjDoubleConsumer<R>, BiConsumer&lt;R,R&gt;)</td>
</tr>
</tbody></table>
<ul>
<li>첫 번째 Supplier는 요소들이 수집될 컨테이너 객체(R)를 생성하는 역할을 한다. 순차 처리 스트림에서는 단 한 번 Supplier가 실행되고 하나의 컨테이너 객체를 생성한다. 병렬 처리 스트림에서는 여러 번 Supplier가 실행되고 여러 개의 컨테이너를 생성하지만 결국 하나로 결합된다.</li>
<li>두 번째 XXXConsumer는 컨테이너 객체(R)에 요소(T)를 수집하는 역할을한다. 스트림에서 요소를 컨테이너에 수집할 때마다 XXXConsumer가 실행된다.</li>
<li>세 번째 BiConsumer는 컨테이너 객체를 결합하는 역할을 한다. 순차 처리 스트림에서는 호출되지 않고, 병렬처리 스트림에서만 호출되어, 각 객체를 결합해서 하나의 최종 컨테이너를 완성한다.</li>
</ul>
<blockquote>
<p>리턴 타입 R은 요소들이 최종 수집된 컨테이너 객체이다. 순차 처리 스트림에서는 리턴 객체가 첫 번째 Supplier가 생성한 객체지만, 병렬 처리 스트림에서는 최종 결합된 컨테이너 객체가 된다.</p>
</blockquote>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">package stream;

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

public class MaleStudent {
    private List&lt;Student&gt; list; // 남학생들이 수집될 필드

    public MaleStudent() {
        list = new ArrayList&lt;&gt;();
        // 생성자가 몇 번 호출되었는지 확인
        System.out.println(&quot;[&quot; + Thread.currentThread().getName() + &quot;] MaleStudent()&quot;);
    }

    public void accumulate(Student student) {
        list.add(student); // 매개값으로 받은 Student를 list에 수집
        // accumulate() 실행 횟수
        System.out.println(&quot;[&quot; + Thread.currentThread().getName() + &quot;] accumulate()&quot;);
    }

    public void combine(MaleStudent other) { // 병렬 처리 스트림 사용시 다른 MaleStudent와 결합될 목적
        list.addAll(other.getList());
        System.out.println(&quot;[&quot; + Thread.currentThread().getName() + &quot;] combine()&quot;);
    }

    public List&lt;Student&gt; getList() {
        return list;
    }
}</code></pre>
<ul>
<li>남학생 MaleStudent에 누적하는 예제</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;

public class MaleStudentExample {
    public static void main(String[] args) {
        List&lt;Student&gt; totalList = Arrays.asList(
                new Student(&quot;홍길동&quot;, 25, Student.SEX.MALE),
                new Student(&quot;백승한&quot;, 6, Student.SEX.MALE),
                new Student(&quot;김수애&quot;, 10, Student.SEX.FEMALE),
                new Student(&quot;박수미&quot;, 6, Student.SEX.FEMALE)
        );

        MaleStudent maleStudent = totalList.stream()
                .filter(s -&gt; s.getSEX() == Student.SEX.MALE)
                .collect(MaleStudent::new, MaleStudent::accumulate, MaleStudent::combine);

        maleStudent.getList().stream()
                .forEach(s -&gt; System.out.println(s.getName()));
    }
}</code></pre>
<blockquote>
<p>순차 처리를 담당하는 스레드는 main인 것을 알 수 있다. 생성자가 한 번 호출되었기에 한 개의 MaleStudent가 생성 되었고, accumulate가 2번 호출 되었기 때문에 요소가 2번 호출됐다. 따라서 collect()가 리턴한 최종 남학생은 2명이다.</p>
</blockquote>
<h3 id="요소를-그룹핑해서-수집">요소를 그룹핑해서 수집</h3>
<blockquote>
<p>collect() 메소드는 단순히 요소를 수집하는 기능 이외에 컬렉션의 요소들을 그룹핑해서 Map객체를 생성하는 기능도 제공한다. collect() 호출시 Collectors의 <code>groupingBy()</code> || <code>groupingByConcurrent()</code>가 리턴하는 Collector를 매개값으로 대입하면 된다.  </p>
<ul>
<li><p>GroupingBy : 스레드에 안전하지 않은 Map 생성</p>
</li>
<li><p>GroupingByConcurrent : 스레드에 안전한 ConcurrentMap 생성</p>
</li>
</ul>
</blockquote>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>Collectors의 정적 메소드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Collector&lt;T,?,Map&lt;K,List<T>&gt;&gt;</td>
<td>groupingBy(Function&lt;T,K&gt; classifier)</td>
<td>T를 K로 매핑하고 K키에 저장된 List에 T를 저장한 Map 생성</td>
</tr>
<tr>
<td>Collector&lt;T,?,ConcurrentMap&lt;K,List<T>&gt;&gt;</td>
<td>groupingByConcurrent(Function&lt;T,K&gt; classfier)</td>
<td></td>
</tr>
<tr>
<td>Collector&lt;T,?,Map&lt;K,D&gt;&gt;</td>
<td>groupingBy(Function&lt;T,K&gt; classfier, Collector&lt;T,A,D&gt; collector)</td>
<td>T를 K로 매핑하고 K키에 저장된 D객체에 T를 누적한  Map 생성</td>
</tr>
<tr>
<td>Collector&lt;T,?,ConcurrentMap&lt;K,D&gt;&gt;</td>
<td>groupingByConcurrent(Function&lt;T,K&gt; classfier, Collector&lt;T,A,D&gt; collector)</td>
<td></td>
</tr>
<tr>
<td>Collector&lt;T,?,Map&lt;K,D&gt;&gt;</td>
<td>groupingBy(Function&lt;T,K&gt; classfier, Supplier&lt;Map&lt;K,D&gt;&gt; mapFactory, Collector&lt;T,A,D&gt; collector)</td>
<td>T를 K로 매핑하고 Supplier가 제공하는 Map에서 K 키에 저장된 D객체에 T를 누적</td>
</tr>
<tr>
<td>Collector&lt;T,?,ConcurrentMap&lt;K,D&gt;&gt;</td>
<td>groupingByConcurrent(Function&lt;T,K&gt; classfier, SupplierConcurrent&lt;Map&lt;K,D&gt;&gt; mapFactory, Collector&lt;T,A,D&gt; collector)</td>
<td></td>
</tr>
</tbody></table>
<ul>
<li>학생들을 성별로 그룹핑 ▶️ 같은 그룹에 속하는 학생 List생성  ▶️ 성별(K)-학생(List) Map 생성</li>
</ul>
<pre><code class="language-java">// 지역 변수 생략 X
Stream&lt;Student&gt; totalStream = totalList.stream();

Function&lt;Student, Student.SEX&gt; classfier = Student::getSEX;
Collector&lt;Student, ?, Map&lt;Student.SEX, List&lt;Student&gt;&gt;&gt; collector = Collectors.groupingBy(classfier);

Map&lt;Student.SEX, List&lt;Student&gt;&gt; mapBySex = totalStream.collect(collector);

// 지역 변수 생략
Map&lt;Student.SEX, List&lt;Student&gt;&gt; mapBySex = totalList.stream()
  .collect(Collectors.groupingBy(Student::getSEX));</code></pre>
<ul>
<li>학생들을 거주 도시별 그룹핑 ▶️ 같은 그룹에 속하는 학생 List 생성 ▶️ 거주도시(K)-이름(List) Map 생성</li>
</ul>
<pre><code class="language-java">// 지역 변수 생략 X
Stream&lt;Student&gt; totalStream = totalList.stream();
Function&lt;Student, Student.City&gt; classfier = Student::getCity;
Collector&lt;Student, String&gt; mapper = Student::getName;
Collector&lt;Student, ?, List&lt;String&gt;&gt; collector1 = Collectors.toList();
Collector&lt;Student, ?, List&lt;String&gt;&gt; collector2 = Collectors.mapping(mapper, collector1);

Collector&lt;Student, ?, Map&lt;Student.City, List&lt;String&gt;&gt;&gt; collector3 = Collectors.groupingBy(classfier, collector2);

Map&lt;Student.City, List&lt;String&gt;&gt; mapByCity = totalStream.collect(collector3);

// 지역 변수 생략 1
Map&lt;Student.City, List&lt;String&gt;&gt; mapByCity = totalList.stream()
  .collect(Collectors.groupingBy(Student::getCity, Collectors.mapping(Student::getName, Collectors.toList())
  )
);

// 지역 변수 생략 2 - groupingBy(Function&lt;T,K&gt; classfier, Supplier&lt;Map&lt;K,D&gt;&gt; mapFactory, Collector&lt;T,A,D&gt; collector) 사용
Map&lt;Student.City, List&lt;String&gt;&gt; mapByCity = totalList.stream()
      .collect()
              .Collectors.groupingBy(
                        Student::getCity,
                      TreeMap::new,
                      Collectors.mapping(Student::getName, Collectors.toList())
                )
);</code></pre>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">package stream;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupingByExample {
    public static void main(String[] args) {
        List&lt;Student&gt; totalList = Arrays.asList(
                new Student(&quot;홍길동&quot;, 10, Student.SEX.MALE, Student.City.Seoul),
                new Student(&quot;백승한&quot;, 5, Student.SEX.MALE, Student.City.Suwon),
                new Student(&quot;신용권&quot;, 10, Student.SEX.MALE, Student.City.Seoul),
                new Student(&quot;김애리&quot;, 5, Student.SEX.FEMALE, Student.City.Suwon)
        );

        Map&lt;Student.SEX, List&lt;Student&gt;&gt; mapBySex = totalList.stream()
                .collect(Collectors.groupingBy(Student::getSEX));
        System.out.println(&quot;남학생&quot;);
        mapBySex.get(Student.SEX.MALE).stream()
                .forEach(s -&gt; System.out.print(s.getName() + &quot; &quot;));

        System.out.println(&quot;여학생&quot;);
        mapBySex.get(Student.SEX.FEMALE).stream()
                .forEach(s -&gt; System.out.print(s.getName() + &quot; &quot;));

        System.out.println();

        Map&lt;Student.City, List&lt;String&gt;&gt; mapByCity = totalList.stream()
                .collect(
                        Collectors.groupingBy(
                                Student::getCity,
                                Collectors.mapping(Student::getNamem, Collectors.toList())
                        )
                );

        System.out.println(&quot;\n서울&quot;);
        mapByCity.get(Student.City.Seoul)
                .forEach(c -&gt; System.out.print(c + &quot; &quot;));

        System.out.println(&quot;\n수원&quot;);
        mapByCity.get(Student.City.Suwon)
                .forEach(c -&gt; System.out.print(c + &quot; &quot;));
    }
}
</code></pre>
<h3 id="그룹핑-후-매핑-및-집계">그룹핑 후 매핑 및 집계</h3>
<blockquote>
<p>Collectors.groupingBy() 메소드는 그룹핑 후 매핑 또는 집계를 할 수 있도록 두 번째 매개값으로 Collector를 가질 수 있다. Collectors는 mapping() 메소드 이외에도 다양한 Collector를 리턴하는 메소드를 가지고 있다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메소드(매개 변수)</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Collector&lt;T,?,R&gt;</td>
<td>mapping(Function&lt;T,U&gt; mapper, Collector&lt;U,A,R&gt; collector)</td>
<td>T를 U로 매핑한 후, U를 R에 수집</td>
</tr>
<tr>
<td>Collector&lt;T,?,Double&gt;</td>
<td>averagingDouble(ToDoubleFunctio<T> mapper)</td>
<td>T를 Double로 매핑한 후, Double의 평균값을 산출</td>
</tr>
<tr>
<td>Collector&lt;T,?,Long&gt;</td>
<td>counting()</td>
<td>T의 카운팅 수를 산출</td>
</tr>
<tr>
<td>Collector&lt;CharSequence,?,String&gt;</td>
<td>joining(CharSequence delimiter)</td>
<td>CharSequence를 구분자(delimiter)로 연결한 String을 산출</td>
</tr>
<tr>
<td>Collector&lt;T,?,Optional<T>&gt;</td>
<td>maxBy(Comparator<T> comparator)</td>
<td>Comparator를 통해 최대 T를 산출</td>
</tr>
<tr>
<td>Collector&lt;T,?,Optional<T>&gt;</td>
<td>minBy(Comparator<T> comparator)</td>
<td>Comparator를 통해 최소 T를 산출</td>
</tr>
<tr>
<td>Collector&lt;T,?,Integer&gt;</td>
<td>summingInt(ToIntFunction)<br />summingLong(ToLongFunction)<br />summingDouble(ToDoubleFunction)</td>
<td>Int,Long,Double 타입의 합계 산출</td>
</tr>
</tbody></table>
<h2 id="병렬처리">병렬처리</h2>
<blockquote>
<p>병렬 처리(Paralle Opertaion) : 멀티 코어 CPU 환경에서 하나의 작업을 분할하여 각각의 코어가 병렬적으로 처리하는 것. 자바 8부터 요소를 병렬 처리할 수 있도록 하기 위해 병렬 스트림을 제공 ▶️ 컬렉션의 전체 요소 처리 시간 단축  </p>
<ul>
<li>목적 : 작업 처리 시간 줄이기 위함</li>
</ul>
</blockquote>
<h3 id="동시성concerrency과-병렬성parallelism">동시성(Concerrency)과 병렬성(Parallelism)</h3>
<blockquote>
<p>멀티 스레드는 동시성 || 병렬성으로 실행된다.  </p>
<p>멀티 스레드 동작 방식이라는 점에서는 같지만, 목적이 다르다.  </p>
<ul>
<li>동시성 : 멀티 작업을 위해 멀티 스레드가 번갈아가며 실행</li>
<li>병렬성 : 멀티 작업을 위해 멀티 코어를 이용해 동시에 실행, 데이터 병렬성, 작업 병렬성으로 구분할 수 있다.</li>
</ul>
</blockquote>
<h4 id="데이터-병렬성">데이터 병렬성</h4>
<blockquote>
<p>전체 데이터를 쪼개어 서브 데이터로 만들고 서브 데이터를 병렬처리해서 작업을 빨리 끝내는 것.  </p>
<p>자바 8에서 지원하는 병렬 스트림은 데이터 병렬성을 구현한 것. 쿼드 코어(4Core)CPU의 경우 4개로 조개어 4개의 스레드가 각각 서브 요소를 병렬 처리한다. </p>
</blockquote>
<h4 id="작업-병렬성">작업 병렬성</h4>
<blockquote>
<p>서로 다른 작업을 병렬 처리하는 것.  웹서버가 대표적인 예이다. 각각의 브라우저가 요청한 내용을 개별 스레드에서 처리한다.</p>
</blockquote>
<h3 id="포크조인-프레임워크">포크조인 프레임워크</h3>
<blockquote>
<p>병렬 스트림을 이요하면 런타임 시에 포크조인 프레임워크가 동작한다.  </p>
<ol>
<li>포크 단계 : 전체 데이터를 서브 데이터로 분리 ▶️ 서브 데이터를 멀티 코어에서 병렬 처리</li>
<li>조인 단계 : 결합 과정을 거쳐 최종 결과를 산출한다.</li>
</ol>
<ul>
<li>포크 조인 프레임워크는 ForkJoinPool(스레드풀)을 제공한다. ▶️ <code>ExecutorService</code>의 <code>ForkJoinPool</code></li>
</ul>
</blockquote>
<h3 id="병렬-스트림-생성">병렬 스트림 생성</h3>
<blockquote>
<p>병렬 처리를 위해 포크조인 프레임워크를 직접 사용할 수 있지만, 병렬 스트림을 이용할 경우 백그라운드에서 포크조인 프레임워크가 사용되기 때문에 쉽게 병렬 처리를 할 수 있다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>인터페이스</th>
<th>리턴타입</th>
<th>메소드(매개 변수)</th>
</tr>
</thead>
<tbody><tr>
<td>java.util.Collection</td>
<td>Stream</td>
<td>parallelStream()</td>
</tr>
<tr>
<td>java.util.Stream.Stream<br />java.util.Stream.IntStream<br />java.util.Stream.LongStream<br />java.util.Stream.DoubleStream</td>
<td>Stream<br />IntStream<br />LongStream<br />DoubleStream</td>
<td>parallel()</td>
</tr>
</tbody></table>
<ul>
<li>parallelStream() : 컬렉션으로부터 병렬 스트림을 바로 리턴한다.</li>
<li>parallel() : 순차 처리 스트림을 병렬 처리 스트림으로 변환해서 리턴한다.</li>
</ul>
<ul>
<li>병렬 스트림 수정 전 코드</li>
</ul>
<pre><code class="language-java">MaleStudent maleStudent = totalList.stream()
  .filter(s-&gt; s.getSex() == Student.SEX.MALE)
  .collect(MaleStudent :: new, MaleStudent :: accumulate, MaleStudent :: combine);</code></pre>
<blockquote>
<p>stream() 메소드로 순차 처리 스트림을 얻어 MaleStudent 객체는 하나만 생성되고 남학생을 수집하기 위해 accumulate가 호출된다. combine() 메소드는 순차 처리 스트림이므로 결합할 서브작업이 없기 때문에 실행되지 않는다. </p>
</blockquote>
<ul>
<li>병렬 스트림 수정 후 코드</li>
</ul>
<pre><code class="language-java">MaleStudent maleStudent = totalList.parallelStream()
  .filter(s-&gt; s.getSex() == Student.SEX.MALE)
  .collect(MaleStudent :: new, MaleStudent :: accumulate, MaleStudent :: combine);</code></pre>
<blockquote>
<p>parallelStream()을 사용하여 전체 요소를 서브 요소로 나누어 각 스레드가 병렬 처리한다. n개의 MaleStudent 객체를 생성하기 위해 collect()의 첫 번째 메소드 참조인 <code>MaleStudent::new</code>를 n번 실행, 남학생 수집을 위한 <code>MaleStudent::accumulate</code>를 매번 실행시킨다. 이후, n개의 MaleStudent는 n-1번의 결합으로 최종 MaleStudent를 만들어질 수 있기에 <code>MaleStudent::combine</code>을 n-1번 실행 시킨다.</p>
</blockquote>
<h3 id="병렬-처리-성능">병렬 처리 성능</h3>
<blockquote>
<p>스트림 병렬 처리가 스트림 순차 처리보다 항상 실행 성능이 좋다고 말 할수는 없다. 영향을 미치는 3가지 요인을 잘 확인해야 한다.</p>
</blockquote>
<h4 id="요소의-수와-요소당-처리-시간">요소의 수와 요소당 처리 시간</h4>
<blockquote>
<p>컬렉션 요수의 수가 적고 요소당 처리 시간이 짧으면 순차 처리가 빠를 수 있다.  </p>
<p><code>!</code>병렬 처리는 스레드 풀 생성, 스레드 생성이라는 추가 비용이 발생하기 때문이다.</p>
</blockquote>
<h4 id="스트림-소스의-종류">스트림 소스의 종류</h4>
<blockquote>
<p>ArrayList, 배열은 인덱스로 요소를 관리하기 때문에 포크 단계에서 요소를 쉽게 분리할 수 있어 병렬 처리 시간이 절약되지만 HashSet, TreeSet은 분리가 쉽지 않고, LinkedList 역시 링크를 따라가기에 분리가 쉽지 않다.</p>
</blockquote>
<h4 id="코어의-수">코어의 수</h4>
<blockquote>
<p>싱글 코어CPU는 순차 처리가 빠르다. 병렬 스트림 사용시 스레드만 늘어나고 동시성 작업으로 처리되어 좋지 못한 결과를 준다. 코어의 수가 많을수록 병렬 처리 속도가 빨라진다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>