<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-taewon-kim.log</title>
        <link>https://velog.io/</link>
        <description>개발이 재밌어서 하는 Junior Backend Developer</description>
        <lastBuildDate>Tue, 23 May 2023 03:33:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. dev-taewon-kim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-taewon-kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[작성중] 오라클 클라우드 가입(오류 해결법 포함)]]></title>
            <link>https://velog.io/@dev-taewon-kim/%EC%98%A4%EB%9D%BC%ED%81%B4-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EA%B0%80%EC%9E%85%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%EB%B2%95-%ED%8F%AC%ED%95%A8</link>
            <guid>https://velog.io/@dev-taewon-kim/%EC%98%A4%EB%9D%BC%ED%81%B4-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EA%B0%80%EC%9E%85%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%EB%B2%95-%ED%8F%AC%ED%95%A8</guid>
            <pubDate>Tue, 23 May 2023 03:33:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>오라클 클라우드는 현존하는 클라우드 서비스 중 가장 파격적인 무료 인스턴스를 제공한다.
이번 포스팅에서는 오라클 클라우드 가입 방법, 가입 중 발생하는 오류 해결법에 대해 다룰 예정이다.</p>
</blockquote>
<h1 id="개요">개요</h1>
<p>오라클 클라우드는 4 OCPU(1 OCPU == 2 vCPU), 24GB RAM, 200GB Block Storage, Inbound Traffic 무제한, Outbound Traffic 월 10TB(개인 사용 용도로는 무제한에 가까움)라는 파격적인 스펙의 무료 인스턴스를 제공한다.</p>
<p>심지어 무료 인스턴스를 2개까지 허용하기 때문에 2 OCPU, 12GB RAM, 100GB Block Storage의 스펙으로 인스턴스를 2개 생성해서 사용할 수도 있다.</p>
<p>하지만 고정 IP는 계정당 1개만 무료이기 때문에 만약 인스턴스를 2개 사용한다면 하나의 인스턴스는 임시 IP를 사용해야 한다.</p>
<p>임시 IP는 서버를 종료했다가 다시 실행하거나(재부팅시에는 유지됨) 인스턴스를 삭제하면 변경된다.</p>
<p>하지만 고정 IP는 계정에 귀속되기 때문에 서버를 종료했다가 다시 실행하거나 인스턴스를 삭제한다고 하더라도 계속 유지된다.</p>
<p><strong>중요한 점은 서버를 종료(shutdown)하고 다시 실행하는 것과 재부팅(reboot)하는 작업이 임시 IP 삭제 및 할당에 있어서 다르게 동작한다는 점이다.</strong></p>
<h1 id="가입">가입</h1>
<p>구글에 &quot;오라클 클라우드 트랜잭션을 처리하는 중 오류 발생&quot; 또는 &quot;oracle cloud error processing transaction&quot;를 검색하면 상당히 많은 사용자가 회원가입 중 오류를 겪는 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/a0d04298-33c4-4eba-a2f4-feaf2deb24f8/image.png" alt=""></p>
<p>필자 또한 위 오류로 인해 상당한 삽질을 시도했다.</p>
<ol>
<li><a href="https://store-oracle.custhelp.com/app/chat/chat_launch">https://store-oracle.custhelp.com/app/chat/chat_launch</a> 라이브 채팅</li>
<li><a href="mailto:oraclehelp_ww@oracle.com">oraclehelp_ww@oracle.com</a> 메일</li>
<li>시간이 지난 후 다시 시도</li>
</ol>
<p>필자는 위 3가지 방법을 모두 시도했지만 실패했다.</p>
<p>모두가 성공할 수는 없겠지만 혹시나 도움을 받는 사람이 존재하길 바라며 필자가 결국 성공한 방법에 대해 설명하겠다.</p>
<p>가입에 실패했을 때 입력한 정보와 성공했을 때 입력한 정보의 차이는 다음과 같다.</p>
<ol>
<li>카드 변경 : NH농협 체크카드(VISA 가능) -&gt; 카카오뱅크 체크카드 사용(Master 가능)</li>
<li>메일 변경 : Gmail(<del>@gmail.com) -&gt; 카카오 이메일(</del>@kakao.com)</li>
<li>영문 이름 형식 변경 : GilDong Hong -&gt; GILDONG HONG</li>
<li>영문 주소 입력 방법 변경 : Address1, 2, 3에 나눠서 입력 -&gt; Address1에 모든 주소 입력 후 2와 3은 공란으로 둠</li>
<li>IP 변경(가입 실패 후 재시도하는 경우에만 해당)</li>
</ol>
<p>구글링을 해보던 중 동일한 IP에서 여러 차례 가입에 실패하게 되면 해당 IP로는 가입이 불가할 수 있다는 글을 읽고 IP를 변경했다.</p>
<p>통신사 유동 IP를 사용하기 때문에 공유기의 공인 IP를 변경할 수 도 있었지만, 집에서 서버를 돌리고 있는 관계로 해당 방법은 제외했다.</p>
<p>따라서 필자는 핸드폰의 모바일 데이터를 이용해 가입을 진행했다.</p>
<p>&quot;트랜잭션을 처리하는 중 오류 발생&quot;을 해결하는 다른 방법은 모두 실패해서 결국 이 게시글을 찾아온 독자들을 위해 이 가이드는 카카오뱅크의 체크카드를 이용한다고 가정하고 진행한다.</p>
<p>카카오뱅크의 경우 어플로 손쉽게 체크카드를 발급받을 수 있으니 다른 방법을 모두 실패했다면 카카오뱅크 체크카드를 발급받아 시도해보는 것을 추천한다.</p>
<p>가입 전 확인해야 할 정보는 다음과 같다.</p>
<p>카카오뱅크 어플 -&gt; 내 정보 탭으로 접속한다.</p>
<ol>
<li>영문 이름을 확인한다.</li>
<li>집정보에서 주소가 올바르게 설정되어 있는지 확인한다. 주소를 확인했으면 <a href="https://search.naver.com/search.naver?sm=tab_hty.top&amp;where=nexearch&amp;query=%EC%98%81%EB%AC%B8%EC%A3%BC%EC%86%8C%EB%B3%80%ED%99%98">네이버 영문 주소 변환</a> 링크에 접속해 주소를 영문으로 변환한다.</li>
</ol>
<p>추가로, 카카오 이메일 계정(~@kakao.com)을 사용할 예정이니 카카오 이메일 계정이 없다면 새로 생성하자.</p>
<p>모든 준비가 되었다면 <a href="https://signup.cloud.oracle.com/?sourceType=_ref_coc-asset-opcSignIn&amp;language=en_US">오라클 클라우드 회원가입</a> 링크를 클릭한다.</p>
<p>&quot;국가/지역&quot;은 &quot;대한민국&quot;으로 설정한다. 본인이 인스턴스를 생성할 Region과 관계 없이 반드시 &quot;대한민국&quot;으로 설정해야 한다.</p>
<p>만약 카카오뱅크 어플에서 확인한 영문 이름이 &quot;HONG GILDONG&quot;이라면 이름은 &quot;GILDONG&quot;, 성은 &quot;HONG&quot;을 입력한다.</p>
<p><strong>반드시 모든 글자를 영문 대문자로 입력하고, 이름에는 띄어쓰기를 하지 않도록 한다.</strong></p>
<p>Captcha를 확인하고 &quot;내 전자메일 확인&quot;을 클릭하면 이메일로 추가 정보를 입력할 수 있는 회원가입 링크가 전송된다.</p>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/026552d1-9f01-4f82-9bec-86f33787fb7d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 20 - 데이터베이스 입출력 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-20-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9E%85%EC%B6%9C%EB%A0%A5-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-20-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9E%85%EC%B6%9C%EB%A0%A5-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Wed, 01 Feb 2023 02:36:41 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-20---데이터베이스-입출력-확인문제">Chapter 20 - 데이터베이스 입출력 확인문제</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/26a09649-da3d-4fd7-8def-a27f369dd78d/image.png" alt=""></p>
<h2 id="정답-4">정답: 4</h2>
<p>JDBC Driver는 DBMS 종류에 맞게 사용해야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/832d99b6-206e-4f0a-849d-39f673eb4c6c/image.png" alt=""></p>
<h2 id="정답-3">정답: 3</h2>
<p>테이블 이름이 아닌 DB의 이름을 알아야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/60ceda3c-1649-408c-82a2-e2a032fae1ec/image.png" alt=""></p>
<h2 id="정답">정답:</h2>
<pre><code>2 -&gt; 1 -&gt; 4 -&gt; 3</code></pre><p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/45e3ff3c-e0d1-44bb-b3b6-8cc1286c220a/image.png" alt=""></p>
<h2 id="정답-4-1">정답: 4</h2>
<p>매개변수화된 SQL 문의 ? 순번은 1번부터 시작한다
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/9abe729d-4cc3-461e-9e98-ed8d4b7f8fa0/image.png" alt=""></p>
<h2 id="정답-2">정답: 2</h2>
<p>next() 메소드는 커서를 다음 행으로 이동시키는데, 이동한 행에 데이터가 있으면 true를, 없으면 false를 리턴한다.
last 행까지는 true를 리턴하고 afterLast 행으로 이동하면 false를 리턴한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/43d63168-c8d4-406a-868f-7d257166923d/image.png" alt=""></p>
<h2 id="정답-3-1">정답: 3</h2>
<p>함수 호출 문자열로 &quot;{ ? = call 함수명(?, ?, …) }&quot;을 사용한다
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f356202f-667b-4fa3-a542-6741f2154a9e/image.png" alt=""></p>
<h2 id="정답-3-2">정답: 3</h2>
<p>롤백은 모든 작업을 취소하고 실행 전으로 되돌아간다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/2b6dc5fb-91ec-482c-b27a-7f491109ff72/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/a1707bd9-2ab9-45db-98b2-e9feeec3bb12/image.png" alt=""></p>
<h2 id="정답-1">정답:</h2>
<pre><code>Board.java</code></pre><pre><code class="language-java">import java.util.Date;
import java.util.Objects;

public class Board {
    private int bno;

    @Override
    public String toString() {
        return &quot;Board{&quot; +
                &quot;bno=&quot; + bno +
                &quot;, btitle=&#39;&quot; + btitle + &#39;\&#39;&#39; +
                &quot;, bcontent=&#39;&quot; + bcontent + &#39;\&#39;&#39; +
                &quot;, bwriter=&#39;&quot; + bwriter + &#39;\&#39;&#39; +
                &quot;, bdate=&quot; + bdate +
                &#39;}&#39;;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Board board = (Board) o;
        return bno == board.bno &amp;&amp; btitle.equals(board.btitle) &amp;&amp; bcontent.equals(board.bcontent) &amp;&amp; bwriter.equals(board.bwriter) &amp;&amp; bdate.equals(board.bdate);
    }

    @Override
    public int hashCode() {
        return Objects.hash(bno, btitle, bcontent, bwriter, bdate);
    }

    public int getBno() {
        return bno;
    }

    public void setBno(int bno) {
        this.bno = bno;
    }

    public String getBtitle() {
        return btitle;
    }

    public void setBtitle(String btitle) {
        this.btitle = btitle;
    }

    public String getBcontent() {
        return bcontent;
    }

    public void setBcontent(String bcontent) {
        this.bcontent = bcontent;
    }

    public String getBwriter() {
        return bwriter;
    }

    public void setBwriter(String bwriter) {
        this.bwriter = bwriter;
    }

    public Date getBdate() {
        return bdate;
    }

    public void setBdate(Date bdate) {
        this.bdate = bdate;
    }

    public Board(int bno, String btitle, String bcontent, String bwriter, Date bdate) {
        this.bno = bno;
        this.btitle = btitle;
        this.bcontent = bcontent;
        this.bwriter = bwriter;
        this.bdate = bdate;
    }

    public Board() {
    }

    private String btitle; private String bcontent; private String bwriter; private Date bdate;
}</code></pre>
<pre><code>User.java</code></pre><pre><code class="language-java">import java.util.Objects;

public class User {
    @Override
    public String toString() {
        return &quot;User{&quot; +
                &quot;userId=&#39;&quot; + userId + &#39;\&#39;&#39; +
                &quot;, userName=&#39;&quot; + userName + &#39;\&#39;&#39; +
                &quot;, userPassword=&#39;&quot; + userPassword + &#39;\&#39;&#39; +
                &quot;, userAge=&quot; + userAge +
                &quot;, userEmail=&#39;&quot; + userEmail + &#39;\&#39;&#39; +
                &#39;}&#39;;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return userAge == user.userAge &amp;&amp; userId.equals(user.userId) &amp;&amp; userName.equals(user.userName) &amp;&amp; userPassword.equals(user.userPassword) &amp;&amp; userEmail.equals(user.userEmail);
    }

    @Override
    public int hashCode() {
        return Objects.hash(userId, userName, userPassword, userAge, userEmail);
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }

    public int getUserAge() {
        return userAge;
    }

    public void setUserAge(int userAge) {
        this.userAge = userAge;
    }

    public String getUserEmail() {
        return userEmail;
    }

    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }

    public User(String userId, String userName, String userPassword, int userAge, String userEmail) {
        this.userId = userId;
        this.userName = userName;
        this.userPassword = userPassword;
        this.userAge = userAge;
        this.userEmail = userEmail;
    }

    public User() {
    }

    private String userId;
    private String userName;
    private String userPassword;
    private int userAge;
    private String userEmail;
}</code></pre>
<pre><code>BoardExample.java</code></pre><pre><code class="language-java">import java.sql.*;
import java.util.Scanner;

public class BoardExample {
    private Scanner scanner = new Scanner(System.in);
    private Connection conn;

    public BoardExample() {
        try {
            Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;);
            conn = DriverManager.getConnection(&quot;jdbc:mysql://localhost:3306/thisisjava&quot;, &quot;root&quot;, &quot;lovesickgirl&quot;);
        } catch (Exception e) {
            e.printStackTrace();
            exit();
        }
    }

    public void list() {
        System.out.println();
        System.out.println(&quot;[게시물 목록]&quot;);
        System.out.println(&quot;-------------------------------------------------------------------&quot;);
        System.out.printf(&quot;%-6s%-12s%-16s%-40s\n&quot;, &quot;no&quot;, &quot;writer&quot;, &quot;date&quot;, &quot;title&quot;);
        System.out.println(&quot;-------------------------------------------------------------------&quot;);

        try {
            String sql = &quot;&quot; + &quot;SELECT bno, btitle, bcontent, bwriter, bdate &quot; + &quot;FROM boards &quot; + &quot;ORDER BY bno DESC&quot;;

            PreparedStatement pstmt = conn.prepareStatement(sql);
            ResultSet rs = pstmt.executeQuery();

            while (rs.next()) {
                Board board = new Board();
                board.setBno(rs.getInt(&quot;bno&quot;));
                board.setBtitle(rs.getString(&quot;btitle&quot;));
                board.setBcontent(rs.getString(&quot;bcontent&quot;));
                board.setBwriter(rs.getString(&quot;bwriter&quot;));
                board.setBdate(rs.getDate(&quot;bdate&quot;));
                System.out.printf(&quot;%-6s%-12s%-16s%-40s \n&quot;, board.getBno(), board.getBwriter(), board.getBdate(), board.getBtitle());
            }
            rs.close();
            pstmt.close();

        } catch (SQLException e) {
            e.printStackTrace();
            exit();
        }
        mainMenu();
    }

    public void mainMenu() {
        System.out.println();
        System.out.println(&quot;-------------------------------------------------------------------&quot;);
        System.out.println(&quot;메인 메뉴: 1.Create | 2.Read | 3.Clear | 4.Join | 5.Exit&quot;);
        System.out.print(&quot;메뉴 선택: &quot;);
        String menuNo = scanner.nextLine();
        System.out.println();
        switch (menuNo) {
            case &quot;1&quot; -&gt; create();
            case &quot;2&quot; -&gt; read();
            case &quot;3&quot; -&gt; clear();
            case &quot;4&quot; -&gt; join();
            case &quot;5&quot; -&gt; exit();
        }
    }

    public void create() {
        Board board = new Board();
        System.out.println(&quot;[새 게시물 입력]&quot;);
        System.out.print(&quot;제목: &quot;);
        board.setBtitle(scanner.nextLine());
        System.out.print(&quot;내용: &quot;);
        board.setBcontent(scanner.nextLine());
        System.out.print(&quot;작성자: &quot;);
        board.setBwriter(scanner.nextLine());
        System.out.println(&quot;-------------------------------------------------------------------&quot;);
        System.out.println(&quot;보조 메뉴: 1.Ok | 2.Cancel&quot;);
        System.out.print(&quot;메뉴 선택: &quot;);
        String menuNo = scanner.nextLine();
        if (menuNo.equals(&quot;1&quot;)) {
            try {
                String sql = &quot;&quot; + &quot;INSERT INTO boards (btitle, bcontent, bwriter, bdate) &quot; + &quot;VALUES (?, ?, ?, now())&quot;;

                PreparedStatement pstmt = conn.prepareStatement(sql);
                pstmt.setString(1, board.getBtitle());
                pstmt.setString(2, board.getBcontent());
                pstmt.setString(3, board.getBwriter());
                pstmt.executeUpdate();
                pstmt.close();
            } catch (Exception e) {
                e.printStackTrace();
                exit();
            }
        }
        list();
    }

    public void read() {
        System.out.println(&quot;[게시물 읽기]&quot;);
        System.out.print(&quot;bno: &quot;);
        int bno = Integer.parseInt(scanner.nextLine());
        try {
            String sql = &quot;&quot; + &quot;SELECT bno, btitle, bcontent, bwriter, bdate &quot; + &quot;FROM boards &quot; + &quot;WHERE bno=?&quot;;

            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, bno);
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                Board board = new Board();
                board.setBno(rs.getInt(&quot;bno&quot;));
                board.setBtitle(rs.getString(&quot;btitle&quot;));
                board.setBcontent(rs.getString(&quot;bcontent&quot;));
                board.setBwriter(rs.getString(&quot;bwriter&quot;));
                board.setBdate(rs.getDate(&quot;bdate&quot;));
                System.out.println(&quot;#############&quot;);
                System.out.println(&quot;번호: &quot; + board.getBno());
                System.out.println(&quot;제목: &quot; + board.getBtitle());
                System.out.println(&quot;내용: &quot; + board.getBcontent());
                System.out.println(&quot;작성자: &quot; + board.getBwriter());
                System.out.println(&quot;날짜: &quot; + board.getBdate());
                System.out.println(&quot;--------------------------------------------------------------&quot;);
                System.out.println(&quot;보조 메뉴: 1.Update | 2.Delete | 3.List&quot;);
                System.out.print(&quot;메뉴 선택: &quot;);
                String menuNo = scanner.nextLine();
                if (menuNo.equals(&quot;1&quot;)) {
                    update(board);
                } else if (menuNo.equals(&quot;2&quot;)) {
                    delete(board);
                }
            }
            rs.close();
            pstmt.close();
        } catch (Exception e) {
            e.printStackTrace();
            exit();
        }
        System.out.println();
        list();
    }

    public void update(Board board) {
        System.out.println(&quot;[수정 내용 입력]&quot;);
        System.out.print(&quot;제목: &quot;);
        board.setBtitle(scanner.nextLine());
        System.out.print(&quot;내용: &quot;);
        board.setBcontent(scanner.nextLine());
        System.out.print(&quot;작성자: &quot;);
        board.setBwriter(scanner.nextLine());

        System.out.println(&quot;-------------------------------------------------------------------&quot;);
        System.out.println(&quot;보조 메뉴: 1.Ok | 2.Cancel&quot;);
        System.out.print(&quot;메뉴 선택: &quot;);
        String menuNo = scanner.nextLine();
        if (menuNo.equals(&quot;1&quot;)) {
            try {
                String sql = &quot;&quot; + &quot;UPDATE boards SET btitle=?, bcontent=?, bwriter=? &quot; + &quot;WHERE bno=?&quot;;
                PreparedStatement pstmt = conn.prepareStatement(sql);
                pstmt.setString(1, board.getBtitle());
                pstmt.setString(2, board.getBcontent());
                pstmt.setString(3, board.getBwriter());
                pstmt.setInt(4, board.getBno());
                pstmt.executeUpdate();
                pstmt.close();
            } catch (Exception e) {
                e.printStackTrace();
                exit();
            }
        }
        list();
    }

    public void delete(Board board) {
        try {
            String sql = &quot;DELETE FROM boards WHERE bno=?&quot;;
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, board.getBno());
            pstmt.executeUpdate();
            pstmt.close();
        } catch (Exception e) {
            e.printStackTrace();
            exit();
        }
        list();
    }

    public void clear() {
        System.out.println(&quot;[게시물 전체 삭제]&quot;);
        System.out.println(&quot;-------------------------------------------------------------------&quot;);
        System.out.println(&quot;보조 메뉴: 1.Ok | 2.Cancel&quot;);
        System.out.print(&quot;메뉴 선택: &quot;);
        String menuNo = scanner.nextLine();
        if (menuNo.equals(&quot;1&quot;)) {
            try {
                String sql = &quot;TRUNCATE TABLE boards&quot;;
                PreparedStatement pstmt = conn.prepareStatement(sql);
                pstmt.executeUpdate();
                pstmt.close();
            } catch (Exception e) {
                e.printStackTrace();
                exit();
            }
        }
        list();
    }

    public void join() {
        User user = new User();
        System.out.println(&quot;[새 사용자 입력]&quot;);
        System.out.print(&quot;아이디: &quot;);
        user.setUserId(scanner.nextLine());
        System.out.print(&quot;이름: &quot;);
        user.setUserName(scanner.nextLine());
        System.out.print(&quot;비밀번호: &quot;);
        user.setUserPassword(scanner.nextLine());
        System.out.print(&quot;나이: &quot;);
        user.setUserAge(Integer.parseInt(scanner.nextLine()));
        System.out.print(&quot;이메일: &quot;);
        user.setUserEmail(scanner.nextLine());

        System.out.println(&quot;-------------------------------------------------------------------&quot;);
        System.out.println(&quot;보조 메뉴: 1.Ok | 2.Cancel&quot;);
        System.out.print(&quot;메뉴 선택: &quot;);
        String menuNo = scanner.nextLine();
        if (menuNo.equals(&quot;1&quot;)) {
            try {
                String sql = &quot;&quot; + &quot;INSERT INTO users (userid, username, userpassword, userage, useremail) &quot; + &quot;VALUES (?, ?, ?, ?, ?)&quot;;
                PreparedStatement pstmt = conn.prepareStatement(sql);
                pstmt.setString(1, user.getUserId());
                pstmt.setString(2, user.getUserName());
                pstmt.setString(3, user.getUserPassword());
                pstmt.setInt(4, user.getUserAge());
                pstmt.setString(5, user.getUserEmail());
                pstmt.executeUpdate();
                pstmt.close();
            } catch (Exception e) {
                e.printStackTrace();
                exit();
            }
        }
        list();
    }

    public void exit() {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
            }
        }
        System.out.println(&quot;** 게시판 종료 **&quot;);
        System.exit(0);
    }

    public static void main(String[] args) {
        BoardExample boardExample = new BoardExample();
        boardExample.list();
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 20 - 데이터베이스 입출력]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-20-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9E%85%EC%B6%9C%EB%A0%A5</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-20-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9E%85%EC%B6%9C%EB%A0%A5</guid>
            <pubDate>Wed, 01 Feb 2023 01:58:11 GMT</pubDate>
            <description><![CDATA[<h1 id="jdbc-개요">JDBC 개요</h1>
<blockquote>
<p>자바는 데이터베이스(DB)와 연결해서 데이터 입출력 작업을 할 수 있도록 JDBC(Java Database Connectivity) 라이브러리(java.sql 패키지)를 제공한다. JDBC는 데이터베이스 관리 시스템(DBMS)의 종류와 상관 없이 동일하게 사용할 수 있는 클래스와 인터페이스로 구성되어 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/1582e66b-e0a8-4733-8703-3fe6b80b5987/image.png" alt="">
JDBC 인터페이스를 통해 실제로 DB와 작업하는 것은 JDBC Driver이다. JDBC Driver는 JDBC 인터페이스를 구현한 것으로, DBMS마다 별도로 다운로드받아 사용해야 한다.
JDBC에 포함되어 있는 클래스와 인터페이스들의 연관 관계는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/62ec5b69-cad3-4e55-b6e2-089816e0801e/image.png" alt=""></p>
<h2 id="drivermanager">DriverManager</h2>
<p>DriverManager 클래스는 JDBC Driver를 관리하며 DB와 연결해서 Connection 구현 객체를 생성한다.</p>
<h2 id="connection">Connection</h2>
<p>Connection 인터페이스는 Statement, PreparedStatement, CallableStatement 구현 객체를 생성하며, 트랜잭션(Transaction) 처리 및 DB 연결을 끊을 때 사용한다.</p>
<h2 id="statement">Statement</h2>
<p>Statement 인터페이스는 SQL의 DDL(Data Definition Language)과 DML(Data Manipulation Language)을 실행할 때 사용한다. 주로 변경되지 않는 정적 SQL 문을 실행할 때 사용한다.</p>
<h2 id="preparedstatement">PreparedStatement</h2>
<p>PreparedStatement는 Statement와 동일하게 SQL의 DDL, DML 문을 실행할 때 사용한다. 차 이점은 매개변수화된 SQL 문을 사용할 수 있기 때문에 편리성과 보안성이 좋다. 그래서 Statement 보다는 PreparedStatement를 주로 사용한다.</p>
<h2 id="callablestatement">CallableStatement</h2>
<p>CallableStatement는 DB에 저장되어 있는 프로시저(procedure)와 함수(function)를 호출할 때 사용한다. </p>
<h2 id="resultset">ResultSet</h2>
<p>ResultSet은 DB에서 가져온 데이터를 읽을 때 사용한다.</p>
<h1 id="db-연결">DB 연결</h1>
<blockquote>
<p>클라이언트 프로그램에서 DB와 연결하려면 해당 DBMS의 JDBC Driver가 필요하다.
또한 연결에 필요한 다음 네 가지 정보가 있어야 한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/a6699988-31ad-4dfe-9236-9f391e0947c1/image.png" alt=""></p>
<ol>
<li>DBMS가 설치된 컴퓨터의 IP 주소</li>
<li>DBMS가 허용하는 포트(Port) 번호</li>
<li>사용자(DB 계정) 및 비밀번호</li>
<li>사용하고자 하는 DB 이름</li>
</ol>
<p>IP 주소는 컴퓨터를 찾아가기 위해, Port 번호는 DBMS로 연결하기 위해 필요하다.
DBMS는 여러 개의 DB를 관리하므로 실제로 사용할 DB 이름이 필요하며, 어떤 사용자인지 인증받기 위한 계정 및 비밀번호가 필요하다.</p>
<h1 id="데이터-저장">데이터 저장</h1>
<blockquote>
<p>JDBC를 이용해서 INSERT 문을 실행하는 방법을 알아보자.</p>
</blockquote>
<p>users 테이블에 새로운 사용자 정보를 저장하는 INSERT 문은 다음과 같다.</p>
<pre><code class="language-sql">INSERT INTO users (userid, username, userpassword, userage, useremail)
VALUES (&#39;winter&#39;, &#39;한겨울&#39;, &#39;12345&#39;, 25, &#39;winter@mycompany.com&#39;)</code></pre>
<p>값을 ?(물음표)로 대체한 매개변수화된 INSERT 문으로 변경하면 다음과 같다.</p>
<pre><code class="language-sql">INSERT INTO users (userid, username, userpassword, userage, useremail)
VALUES (?, ?, ?, ?, ?)</code></pre>
<p>그리고 INSERT 문을 String 타입 변수 sql에 문자열로 대입한다.</p>
<pre><code class="language-java">String sql = new StringBuilder()
    .append(&quot;INSERT INTO users (userid, username, userpassword, userage, useremail)&quot;)
    .append(&quot;VALUES (?, ?, ?, ?, ?)&quot;)
    .toString();
// 또는

String sql = &quot;&quot; +
    &quot;INSERT INTO users (userid, username, userpassword, userage, useremail) &quot; + 
    &quot;VALUES (?, ?, ?, ?, ?)&quot;;</code></pre>
<p>매개변수화된 SQL 문을 실행하려면 PreparedStatement가 필요하다.
다음과 같이 Connection의 prepareStatement() 메소드로부터 PreparedStatement를 얻는다.</p>
<pre><code class="language-java">PreparedStatement pstmt = conn.prepareStatement(sql);</code></pre>
<p>그리고 ?에 들어갈 값을 지정해주는데, ?는 순서에 따라 1번부터 번호가 부여된다.
값의 타입에 따라 Setter 메소드를 선택한 후 첫 번째에는 ? 순번, 두 번째에는 값을 지정해 준다.</p>
<pre><code class="language-java">pstmt.setString(1, &quot;winter&quot;);
pstmt.setString(2, &quot;한겨울&quot;);
pstmt.setString(3, &quot;12345&quot;);
pstmt.setInt(4, 25);
pstmt.setString(5, &quot;winter@mycompany.com&quot;);</code></pre>
<p>값을 지정한 후 executeUpdate() 메소드를 호출하면 SQL 문이 실행되면서 users 테이블에 1개 의 행이 저장된다.
executeUpdate() 메소드가 리턴하는 값은 저장된 행 수인데, 정상적으로 실행 되었을 경우 1을 리턴한다.</p>
<pre><code class="language-java">int rows = pstmt.executeUpdate();</code></pre>
<p>PreparedStatement를 더 이상 사용하지 않을 경우에는 close() 메소드를 호출해서 PreparedStatement가 사용했던 메모리를 해제시킨다.</p>
<pre><code class="language-java">pstmt.close();</code></pre>
<h1 id="데이터-수정">데이터 수정</h1>
<blockquote>
<p>JDBC를 이용해서 UPDATE 문을 실행하는 방법을 알아보자.</p>
</blockquote>
<p>boards 테이블에 저장된 게시물 중에서 bno가 3인 게시물의 btitle, bcontent, bfilename, bfiledata를 변경하는 SQL 문은 다음과 같다.</p>
<pre><code class="language-sql">UPDATE boards SET
    btitle = &#39;눈사람&#39;,
    bcontent=&#39;눈으로 만든 사람&#39;,
    bfilename = &#39;snowman.jpg&#39;,
    bfiledata = binaryData
WHERE bno=3</code></pre>
<p>값을 ?로 대체한 매개변수화된 UPDATE 문으로 변경한다.</p>
<pre><code class="language-sql">UPDATE boards SET
    btitle = ?,
    bcontent = ?,
    bfilename = ?,
    bfiledata = ?
WHERE bno=?</code></pre>
<p>String 타입 변수 sql에 매개변수화된 UPDATE 문을 저장한다.</p>
<pre><code class="language-java">String sql = new StringBuilder()
    .append(&quot;UPDATE boards SET &quot;)
    .append(&quot;btitle=?, &quot;)
    .append(&quot;bcontent=?, &quot;)
    .append(&quot;bfilename=?, &quot;)
    .append(&quot;bfiledata=? &quot;)
    .append(&quot;WHERE bno=?&quot;)
    .toString();</code></pre>
<p>매개변수화된 UPDATE 문을 실행하기 위해 다음과 같이 prepareStatement() 메소드로부터 PreparedStatement를 얻고, ?에 해당하는 값을 지정한다.</p>
<pre><code class="language-java">PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, &quot;눈사람&quot;);
pstmt.setString(2, &quot;눈으로 만든 사람&quot;);
pstmt.setString(3, &quot;snowman.jpg&quot;);
pstmt.setBlob(4, new FileInputStream(&quot;src/ch20/mysql/sec07/snowman.jpg&quot;));
pstmt.setInt(5, 3);</code></pre>
<p>값을 모두 지정하였다면 UPDATE 문을 실행하기 위해 executeUpdate() 메소드를 호출한다.
성공적으로 실행되면 수정된 행의 수가 리턴된다.
만약 0이 리턴되면 조건에 맞는 행이 없어 수정된 내용이 없음을 의미한다.</p>
<pre><code class="language-java">int rows = pstmt.executeUpdate();</code></pre>
<h1 id="데이터-삭제">데이터 삭제</h1>
<blockquote>
<p>JDBC를 이용해서 DELETE 문을 실행하는 방법을 알아보자.</p>
</blockquote>
<p>boards 테이블에서 bwriter가 winter인 모든 게시물을 삭제하는 DELETE 문은 다음과 같다.</p>
<pre><code class="language-sql">DELETE FROM boards WHERE bwriter=&#39;winter&#39;</code></pre>
<p>조건절의 값을 ?로 대체한 매개변수화된 DELETE 문으로 변경한다.</p>
<pre><code class="language-sql">DELETE FROM boards WHERE bwriter=?</code></pre>
<p>매개변수화된 DELETE 문을 String 타입 변수 sql에 대입한다.</p>
<pre><code class="language-java">String sql = &quot;DELETE FROM boards WHERE bwriter=?&quot;;</code></pre>
<p>매개변수화된 DELETE 문을 실행하기 위해 prepareStatement() 메소드로부터 PreparedStatement를 얻고 ?에 값을 지정한 후, executeUpdate로 SQL 문을 실행한다.
리턴 값은 삭제된 행 수이다.</p>
<pre><code class="language-java">String sql = &quot;DELETE FROM boards WHERE bwriter=?&quot;;
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, &quot;winter&quot;);
int rows = pstmt.executeUpdate();</code></pre>
<h1 id="데이터-읽기">데이터 읽기</h1>
<blockquote>
<p>PreparedStatement를 생성할 때 SQL 문이 INSERT, UPDATE, DELETE일 경우에는 executeUpdate() 메소드를 호출하지만, 데이터를 가져오는 SELECT 문일 경우에는 executeQuery() 메소드를 호출해야 한다. executeQuery() 메소드는 가져온 데이터를 ResultSet에 저장하고 리턴한다.</p>
</blockquote>
<pre><code class="language-java">ResultSet rs = pstmt.executeQuery();</code></pre>
<h2 id="resultset-구조">ResultSet 구조</h2>
<p>ResultSet은 SELECT 문에 기술된 컬럼으로 구성된 행(row)의 집합이다.
예를 들어 다음 SELECT 문은 userid, username, userage 컬럼으로 구성된 ResultSet을 리턴한다.</p>
<pre><code class="language-sql">SELECT userid, username, userage FROM users</code></pre>
<p>위의 SELECT 문이 가져온 데이터 행이 4개라면 ResultSet의 내부 구조는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b9b2aae3-bdf0-40b2-ac68-dfbcaf2f4752/image.png" alt="">
ResultSet의 특징은 커서(cursor)가 있는 행의 데이터만 읽을 수 있다는 것이다.
여기서 커서는 행을 가리키는 포인터를 말한다.</p>
<p>ResultSet은 실제 가져온 데이터 행의 앞과 뒤에 beforeFirst 행과 afterLast 행이 붙는데, 최초 커서는 beforeFirst를 가리킨다.
따라서 첫 번째 데이터 행인 first 행을 읽으려면 커서를 이동시켜야 한다. 이때 next() 메소드를 사용한다.</p>
<pre><code class="language-java">boolean result = rs.next();</code></pre>
<p>next() 메소드는 커서를 다음 행으로 이동시키는데, 이동한 행에 데이터가 있으면 true를, 없으면 false를 리턴한다.
앞의 그림을 보면 last 행까지는 true를 리턴하고 afterLast 행으로 이동하면 false를 리턴하는 것을 볼 수 있다.</p>
<p>만약 SELECT 문으로 가져온 데이터 행이 없다면 beforeFirst 행과 afterLast 행이 붙어있기 때문에 첫 번째 next() 결과는 false가 된다.</p>
<p>다음은 SELECT 문으로 가져온 행의 수에 따라서 커서를 이동시키는 코드이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f51ede87-48de-4ec0-85bc-bb91a410bba5/image.png" alt="">
1개의 데이터 행만 가져올 경우에는 if 조건식에서 next() 메소드를 1번 호출한다.
true일 경우(첫 번째 데이터 행이 있을 경우)와 false일 경우(afterLast 행으로 이동했을 경우)에 따라서 적절한 처리를 해야 한다.</p>
<p>주로 SELECT 문이 기본키(primary key)를 조건으로 데이터를 가져오는 경우에 해당한다.</p>
<p>n개의 데이터 행을 가져올 경우에는 while 문을 이용해서 next() 메소드를 반복 호출해 true가 리턴될 동안(last 행까지 이동할 때까지) 데이터 행을 처리하고, false가 리턴되면(afterLast 행으로 이동할 때) 반복을 종료시킨다.</p>
<p>SELECT 문에 따라 ResultSet에는 많은 데이터 행이 저장될 수 있기 때문에 ResultSet을 더 이상 사용하지 않는다면 close() 메소드를 호출해서 ResultSet이 사용한 메모리를 해제하는 것이 좋다.</p>
<pre><code class="language-java">rs.close();</code></pre>
<h2 id="데이터-행-읽기">데이터 행 읽기</h2>
<p>커서가 있는 데이터 행에서 각 컬럼의 값은 Getter 메소드로 읽을 수 있다.</p>
<p>컬럼의 데이터 타입에 따라서 getXxx() 메소드가 사용되며, 매개값으로 컬럼의 이름 또는 컬럼 순번을 줄 수 있다.
ResultSet에서 컬럼 순번은 1부터 시작하기 때문에 userid = 1, username = 2, userage = 3이 된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e60a9ef9-1ad9-4798-8a16-c7b4348d8463/image.png" alt="">
만약 SELECT 문에 연산식이나 함수 호출이 포함되어 있다면 컬럼 이름 대신에 컬럼 순번으로 읽어야 한다.
예를 들어 다음과 같은 SELECT 문에서 <code>userage - 1</code> 연산식이 사용되면 컬럼 순번으로만 읽을 수 있다.
<code>userage - 1</code>은 컬럼명이 아니기 때문이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/21745e13-9ec1-4eca-bada-4a76904a2aff/image.png" alt="">
<strong><code>(userage - 1) as userage</code>와 같이 별명(alias)이 있다면 별명이 컬럼 이름이 된다.</strong></p>
<h1 id="트랜잭션-처리">트랜잭션 처리</h1>
<blockquote>
<p>트랜잭션(transaction)은 기능 처리의 최소 단위를 말한다. 하나의 기능은 여러 가지 소작업들로 구성될 수 있다.
최소 단위란 것은 이 소작업들을 분리할 수 없으며, 전체를 하나로 본다는 개념이다
트랜잭션은 소작업들이 모두 성공하거나 모두 실패해야 한다.</p>
</blockquote>
<p>예를 들어 계좌 이체는 출금 작업과 입금 작업으로 구성된 트랜잭션이다. 출금과 입금 작업 중 하나만 성공할 수 없으며, 모두 성공하거나 모두 실패되어야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/0d6b8405-6e3b-43dc-9842-2ad4106a6c4a/image.png" alt="">
계좌 이체는 DB 입장에서 보면 두 개의 계좌 금액을 수정하는 작업이다.
출금 계좌에서 금액을 감소시키고, 입금 계좌에서 금액을 증가시킨다.</p>
<p>따라서 다음과 같이 두 개의 UPDATE 문이 필요하다. 두 UPDATE 문은 모두 성공하거나 모두 실패해야 하며, 하나만 성공할 수 없다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e859d345-1be0-40b5-9c89-87e15a401065/image.png" alt="">
DB는 트랜잭션을 처리하기 위해 커밋(commit)과 롤백(rollback)을 제공한다.</p>
<p>커밋은 내부 작업을 모두 성공 처리하고, 롤백은 실행 전으로 돌아간다는 의미에서 모두 실패 처리한다.</p>
<p>JDBC에서는 INSERT, UPDATE, DELETE 문을 실행할 때마다 자동 커밋이 일어난다.
이 기능은 계좌 이체와 같이 두 가지 UPDATE 문을 실행할 때 문제가 된다. 출금 작업이 성공되면 바로 커밋이 되기 때문에 입금 작업의 성공 여부와 상관없이 출금 작업만 별도 처리된다.</p>
<p>따라서 JDBC에서 트랜잭션을 코드로 제어하려면 자동 커밋 기능을 꺼야 한다.
자동 커밋 설정 여부는 Connection의 setAutoCommit() 메소드로 설정할 수 있다.</p>
<p>다음 코드는 자동 커밋 기능을 끈다.</p>
<pre><code class="language-java">conn.setAutoCommit(false);</code></pre>
<p>자동 커밋 기능이 꺼지면, 다음과 같은 코드로 커밋과 롤백을 제어할 수 있다.</p>
<pre><code class="language-java">conn.commit(); // 커밋하기
conn.rollback(); // 롤백하기</code></pre>
<p>트랜잭션을 위한 일반적인 코드 작성 패턴은 다음과 같다.</p>
<pre><code class="language-java">Connection conn = null;

try {
  // 트랜잭션 시작
    // 자동 커밋 기능 끄기
    conn.setAutoCommit(false);

    // 소작업 처리
    ...

    // 소작업 처리
    ...

    // 커밋 -&gt; 모두 성공 처리 conn.commit();
  // 트랜잭션 종료
} catch (Exception e) {
    try {
    //롤백 -&gt; 모두 실패 처리
    conn.rollback();
    } catch (SQLException e1) {}
} finally {
    if (conn != null) {
        try {
        // 원래대로 자동 커밋 기능 켜기
        conn.setAutoCommit(true);

        // 연결 끊기
        conn.close();
        } catch (SQLException e) {}
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 19 - 네트워크 입출력 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-19-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9E%85%EC%B6%9C%EB%A0%A5-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-19-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9E%85%EC%B6%9C%EB%A0%A5-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Tue, 31 Jan 2023 23:15:51 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-19---네트워크-입출력-확인문제">Chapter 19 - 네트워크 입출력 확인문제</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c78fd50b-3a5c-4e22-bff1-2e7560dda0e9/image.png" alt=""></p>
<h2 id="정답-2">정답: 2</h2>
<p>클라이언트가 서버에 연결하기 위해서는 IP 주소와 Port 번호가 있어야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c84611c2-0629-4f3e-b069-b26156c8e411/image.png" alt=""></p>
<h2 id="정답-2-4">정답: 2, 4</h2>
<p>2: TCP -&gt; UDP
4: UDP -&gt; TCP
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/09a73b67-d858-4098-afe0-b225180b7cb7/image.png" alt=""></p>
<h2 id="정답">정답:</h2>
<pre><code class="language-java">new Socket(&quot;localhost&quot;, 5001);</code></pre>
<pre><code class="language-java">serverSocket.accept()</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/dd2addc0-99d0-4b51-8579-553865d394fb/image.png" alt=""></p>
<h2 id="정답-1">정답:</h2>
<p>좌상단 -&gt; 우상단 -&gt; 좌하단 -&gt; 우하단</p>
<pre><code>InputStream</code></pre><pre><code>OutputStream</code></pre><pre><code>OutputStream</code></pre><pre><code>InputStream</code></pre><p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/9fe151de-789f-47d5-aba3-f30036ea8afd/image.png" alt=""></p>
<h2 id="정답-3">정답:</h2>
<ol>
<li>DatagramSocket</li>
<li>DatagramPacket</li>
<li>DatagramSocket</li>
<li>DatagramPacket</li>
<li>DatagramPacket
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/bb1c5229-2553-43cd-8530-25718134dfbd/image.png" alt=""><h2 id="정답-4">정답: 4</h2>
클라이언트로 DatagramPacket을 발신할 때 send() 메소드를 사용한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/69105e97-fd0a-49bc-84be-8b1199215a49/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/0ffcceff-75c8-4439-ac73-2ec97335feb9/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/2c720902-7dde-4731-ba5e-24b1c5360fc6/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/2aa1c84b-ff12-46b6-a8cf-802e52c529b6/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/013c324d-49fa-4a41-8c31-b0dbc311bf92/image.png" alt=""><pre><code class="language-java">import java.util.Objects;

</code></pre>
</li>
</ol>
<p>public class Product {
    public Product() {
    }</p>
<pre><code>public Product(int no, String name, int price, int stock) {
    this.no = no;
    this.name = name;
    this.price = price;
    this.stock = stock;
}

public int getNo() {
    return no;
}

public void setNo(int no) {
    this.no = no;
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Product product = (Product) o;
    return no == product.no &amp;&amp; price == product.price &amp;&amp; stock == product.stock &amp;&amp; name.equals(product.name);
}

@Override
public int hashCode() {
    return Objects.hash(no, name, price, stock);
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getPrice() {
    return price;
}

public void setPrice(int price) {
    this.price = price;
}

public int getStock() {
    return stock;
}

public void setStock(int stock) {
    this.stock = stock;
}

@Override
public String toString() {
    return &quot;Product{&quot; + &quot;no=&quot; + no + &quot;, name=&#39;&quot; + name + &#39;\&#39;&#39; + &quot;, price=&quot; + price + &quot;, stock=&quot; + stock + &#39;}&#39;;
}

private int no;
private String name;
private int price;
private int stock;</code></pre><p>}</p>
<pre><code>```java
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ProductServer {
    private int pk;
    private TreeMap&lt;Integer, Product&gt; products;
    private ExecutorService es;

    public static void main(String[] args) {
        ProductServer productServer = new ProductServer();
        try {
            productServer.start();
        } catch (IOException ignored) {
        }
    }


    public void start() throws IOException {
        try (ServerSocket ss = new ServerSocket(50001)) {
            es = Executors.newFixedThreadPool(100);
            products = new TreeMap&lt;&gt;();
            System.out.println(&quot;[서버] 시작됨&quot;);
            while (true) {
                try {
                    new SocketClient(ss.accept());
                } catch (IOException e) {
                    break;
                }
            }
        }
    }

    public class SocketClient {
        private Socket sc;
        private DataInputStream dis;
        private DataOutputStream dos;

        public SocketClient(Socket sc) {
            try {
                this.sc = sc;
                this.dis = new DataInputStream(sc.getInputStream());
                this.dos = new DataOutputStream(sc.getOutputStream());
                receive();
            } catch (IOException e) {
                try {
                    sc.close();
                } catch (IOException ignored) {
                }
            }
        }

        public void receive() {
            es.execute(() -&gt; {
                while (true) {
                    try {
                        JSONObject requestJson = new JSONObject(dis.readUTF());
                        int menu = requestJson.getInt(&quot;menu&quot;);
                        switch (menu) {
                            case 0 -&gt; getProductList();
                            case 1 -&gt; createProduct(requestJson);
                            case 2 -&gt; updateProduct(requestJson);
                            case 3 -&gt; deleteProduct(requestJson);
                        }
                    } catch (IOException e) {
                        break;
                    }
                }

                try {
                    sc.close();
                } catch (IOException ignored) {
                }
            });
        }

        public void getProductList() throws IOException {
            JSONArray productList = new JSONArray();
            JSONObject product;
            JSONObject jsonResponse = new JSONObject();

            for (Product p : products.values()) {
                product = new JSONObject();
                product.put(&quot;no&quot;, p.getNo());
                product.put(&quot;name&quot;, p.getName());
                product.put(&quot;price&quot;, p.getPrice());
                product.put(&quot;stock&quot;, p.getStock());
                productList.put(product);
            }

            jsonResponse.put(&quot;status&quot;, &quot;success&quot;);
            jsonResponse.put(&quot;data&quot;, productList);
            dos.writeUTF(jsonResponse.toString());
            dos.flush();
        }

        public void createProduct(JSONObject request) throws IOException {
            JSONObject jsonRequest = request.getJSONObject(&quot;data&quot;);
            JSONObject jsonResponse = new JSONObject();
            Product product = new Product();

            product.setNo(++pk);
            product.setName(jsonRequest.getString(&quot;name&quot;));
            product.setPrice(jsonRequest.getInt(&quot;price&quot;));
            product.setStock(jsonRequest.getInt(&quot;stock&quot;));
            products.put(pk, product);

            jsonResponse.put(&quot;status&quot;, &quot;success&quot;);
            jsonResponse.put(&quot;data&quot;, new JSONObject());
            dos.writeUTF(jsonResponse.toString());
            dos.flush();
        }

        public void updateProduct(JSONObject request) throws IOException {
            JSONObject jsonRequest = request.getJSONObject(&quot;data&quot;);
            JSONObject jsonResponse = new JSONObject();

            Product product = products.get(jsonRequest.getInt(&quot;no&quot;));
            product.setName(jsonRequest.getString(&quot;name&quot;));
            product.setPrice(jsonRequest.getInt(&quot;price&quot;));
            product.setStock(jsonRequest.getInt(&quot;stock&quot;));

            jsonResponse.put(&quot;status&quot;, &quot;success&quot;);
            jsonResponse.put(&quot;jsonRequest&quot;, new JSONObject());
            dos.writeUTF(jsonResponse.toString());
            dos.flush();
        }

        public void deleteProduct(JSONObject request) throws IOException {
            JSONObject jsonRequest = request.getJSONObject(&quot;data&quot;);
            JSONObject jsonResponse = new JSONObject();
            products.remove(jsonRequest.getInt(&quot;no&quot;));

            jsonResponse.put(&quot;status&quot;, &quot;success&quot;);
            jsonResponse.put(&quot;data&quot;, new JSONObject());
            dos.writeUTF(jsonResponse.toString());
            dos.flush();
        }
    }
}</code></pre><pre><code class="language-java">import org.json.JSONArray;
import org.json.JSONObject;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class ProductClient {
    private Socket sc;
    private DataInputStream dis;
    private DataOutputStream dos;
    private Scanner scanner;

    public static void main(String[] args) {
        ProductClient productClient = new ProductClient();
        try {
            productClient.start();
        } catch (IOException ignored) {
        }
    }

    public void start() throws IOException {
        scanner = new Scanner(System.in);
        sc = new Socket(&quot;localhost&quot;, 50001);
        dis = new DataInputStream(sc.getInputStream());
        dos = new DataOutputStream(sc.getOutputStream());

        System.out.println(&quot;[클라이언트] 서버에 연결됨&quot;);
        getProductList();
    }

    public void exit() {
        try {
            sc.close();
            scanner.close();
        } catch (Exception ignored) {
        }
        System.out.println(&quot;[클라이언트] 종료됨&quot;);
    }

    public void getResponse() throws IOException {
        JSONObject jsonResponse = new JSONObject(dis.readUTF());
        if (jsonResponse.getString(&quot;status&quot;).equals(&quot;success&quot;)) {
            getProductList();
        }
    }

    public void getMenuNumber() throws IOException {
        System.out.println();
        System.out.println(&quot;----------------------------------------------------------&quot;);
        System.out.println(&quot;메뉴: 1.Create | 2.Update | 3.Delete | 4.Exit&quot;);
        System.out.print(&quot;선택: &quot;);

        int menuNumber = Integer.parseInt(scanner.nextLine());
        System.out.println();

        switch (menuNumber) {
            case 1 -&gt; createProduct();
            case 2 -&gt; updateProduct();
            case 3 -&gt; deleteProduct();
            default -&gt; exit();
        }
    }

    public void getProductList() throws IOException {
        final int MENU_NUM = 1;
        JSONObject jsonRequest = new JSONObject();

        System.out.println();
        System.out.println(&quot;[상품 목록]&quot;);
        System.out.println(&quot;----------------------------------------------------------&quot;);
        System.out.println(&quot;no\t\tname\t\t\t\t\tprice\t\t\tstock&quot;);
        System.out.println(&quot;----------------------------------------------------------&quot;);

        jsonRequest.put(&quot;menu&quot;, MENU_NUM);
        jsonRequest.put(&quot;data&quot;, new JSONObject());

        dos.writeUTF(jsonRequest.toString());
        dos.flush();

        JSONObject jsonResponse = new JSONObject(dis.readUTF());

        if (jsonResponse.getString(&quot;status&quot;).equals(&quot;success&quot;)) {
            JSONArray jsonArray = jsonResponse.getJSONArray(&quot;data&quot;);

            for (int i = 0; i &lt; jsonArray.length(); i++) {
                JSONObject product = jsonArray.getJSONObject(i);
                System.out.printf(&quot;%d\t\t%s\t\t\t\t\t\t%d\t\t\t\t%d\n&quot;,
                        product.getInt(&quot;no&quot;),
                        product.getString(&quot;name&quot;),
                        product.getInt(&quot;price&quot;),
                        product.getInt(&quot;stock&quot;)
                );
            }
        }
        getMenuNumber();
    }

    public void createProduct() throws IOException {
        final int MENU_NUM = 1;
        Product product = new Product();
        JSONObject jsonProduct = new JSONObject();
        JSONObject jsonRequest = new JSONObject();

        System.out.println(&quot;[상품 생성]&quot;);

        System.out.print(&quot;상품 이름: &quot;);
        product.setName(scanner.nextLine());

        System.out.print(&quot;상품 가격: &quot;);
        product.setPrice(Integer.parseInt(scanner.nextLine()));

        System.out.print(&quot;상품 재고: &quot;);
        product.setStock(Integer.parseInt(scanner.nextLine()));

        jsonProduct.put(&quot;name&quot;, product.getName());
        jsonProduct.put(&quot;price&quot;, product.getPrice());
        jsonProduct.put(&quot;stock&quot;, product.getStock());

        jsonRequest.put(&quot;menu&quot;, MENU_NUM);
        jsonRequest.put(&quot;data&quot;, jsonProduct);

        dos.writeUTF(jsonRequest.toString());
        dos.flush();
        getResponse();
    }

    public void updateProduct() throws IOException {
        final int MENU_NUM = 2;
        Product product = new Product();
        JSONObject jsonProduct = new JSONObject();
        JSONObject jsonRequest = new JSONObject();

        System.out.println(&quot;[상품 수정]&quot;);

        System.out.print(&quot;상품 번호: &quot;);
        product.setNo(Integer.parseInt(scanner.nextLine()));

        System.out.print(&quot;이름 변경: &quot;);
        product.setName(scanner.nextLine());

        System.out.print(&quot;가격 변경: &quot;);
        product.setPrice(Integer.parseInt(scanner.nextLine()));

        System.out.print(&quot;재고 변경: &quot;);
        product.setStock(Integer.parseInt(scanner.nextLine()));

        jsonProduct.put(&quot;no&quot;, product.getNo());
        jsonProduct.put(&quot;name&quot;, product.getName());
        jsonProduct.put(&quot;price&quot;, product.getPrice());
        jsonProduct.put(&quot;stock&quot;, product.getStock());

        jsonRequest.put(&quot;menu&quot;, MENU_NUM);
        jsonRequest.put(&quot;data&quot;, jsonProduct);

        dos.writeUTF(jsonRequest.toString());
        dos.flush();
        getResponse();
    }


    public void deleteProduct() throws IOException {
        final int MENU_NUM = 3;
        JSONObject jsonProduct = new JSONObject();
        JSONObject jsonRequest = new JSONObject();

        System.out.println(&quot;[상품 삭제]&quot;);

        System.out.print(&quot;상품 번호: &quot;);
        int no = Integer.parseInt(scanner.nextLine());

        jsonProduct.put(&quot;no&quot;, no);

        jsonRequest.put(&quot;menu&quot;, MENU_NUM);
        jsonRequest.put(&quot;data&quot;, jsonProduct);

        dos.writeUTF(jsonRequest.toString());
        dos.flush();
        getResponse();
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 19 - 네트워크 입출력]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-19-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9E%85%EC%B6%9C%EB%A0%A5</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-19-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9E%85%EC%B6%9C%EB%A0%A5</guid>
            <pubDate>Tue, 31 Jan 2023 20:37:00 GMT</pubDate>
            <description><![CDATA[<h1 id="네트워크-기초">네트워크 기초</h1>
<blockquote>
<p>네트워크(Network)는 여러 컴퓨터들을 통신 회선으로 연결한 것을 말한다.</p>
</blockquote>
<p>LAN(Local Area Network)은 가정, 회사 건물, 특정 영역에 존재하는 컴퓨터를 연결한 것이고, WAN(Wide Area Network)은 LAN을 연결한 것이다.
WAN이 우리가 흔히 말하는 인터넷(Internet)이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c3f909cb-1714-4ce2-a072-96e8e4a66be3/image.png" alt=""></p>
<h2 id="서버와-클라이언트">서버와 클라이언트</h2>
<p>네트워크에서 유무선으로 컴퓨터가 연결되어 있다면 실제로 데이터를 주고받는 행위는 프로그램들이 한다.
서비스를 제공하는 프로그램을 일반적으로 서버(Server)라고 부르고, 서비스를 요청하는 프로그램을 클라이언트(Client)라고 부른다.</p>
<p>인터넷에서 두 프로그램이 통신하기 위해서는 먼저 클라이언트가 서비스를 요청하고, 서버는 처리 결과를 응답으로 제공해준다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5c2d73f6-a946-4f04-ad8a-6e4fb9438cdb/image.png" alt=""></p>
<h2 id="ip-주소">IP 주소</h2>
<p>IP(Internet Protocol) 주소는 네트워크 어댑터(LAN 카드)마다 할당된다.
만약 컴퓨터에 두 개의 네트워크 어댑터가 장착되어 있다면, 두 개의 IP 주소를 할당받을 수 있다.</p>
<h2 id="port-주소">Port 주소</h2>
<p>컴퓨터 내부에서 실행하는 서버를 선택하기 위해서는 Port 주소가 필요하다.
Port는 운영체제가 관리하는 서버 프로그램의 연결 번호이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/366f5962-3845-456f-af3a-4dc3a938a935/image.png" alt="">
클라이언트도 서버에서 보낸 정보를 받기 위해서는 Port 번호가 필요한데, 서버와 같이 고정적인 Port 번호에 바인딩하는 것이 아니라 운영체제가 자동으로 부여하는 번호를 사용한다.
이 번호는 클라이언트가 서버로 요청할 때 함께 전송되어 서버가 클라이언트로 데이터를 보낼 때 사용된다.</p>
<p>프로그램에서 사용할 수 있는 전체 Port 번호의 범위는 0~65535로, 다음과 같이 사용 목적에 따라 세 가지 범위를 가진다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5509c224-de82-4c8a-b104-112407610779/image.png" alt=""></p>
<h1 id="ip-주소-얻기">IP 주소 얻기</h1>
<blockquote>
<p>자바는 IP 주소를 java.net 패키지의 InetAddress로 표현한다. 이를 이용하면 로컬 컴퓨터의 IP 주소를 얻읋 수 있고, 도메인 이름으로 <a href="https://en.wikipedia.org/wiki/Domain_Name_System">DNS(Domain Name System)</a>에서 검색한 후 IP 주소를 가져올 수도 있다.</p>
</blockquote>
<p>로컬 컴퓨터의 InetAddress를 얻고 싶다면 InetAddress.getLocalHost() 메소드를 다음과 같이 호출하면 된다.</p>
<pre><code class="language-java">InetAddress ia = InetAddress.getLocalHost();</code></pre>
<p>만약 컴퓨터의 도메인 이름을 알고 있다면 다음 두 개의 메소드를 사용하여 InetAddress 객체를 얻을 수 있다.</p>
<pre><code class="language-java">InetAddress ia = InetAddress.getByName(String domainName);
InetAddress[] iaArr = InetAddress.getAllByName(String domainName);</code></pre>
<p>이 메소드들로부터 얻은 InetAddress 객체에서 IP 주소를 얻으려면 getHostAddress() 메소드를 다음과 같이 호출하면 된다.</p>
<p>리턴값은 문자열로 된 IP 주소이다.</p>
<pre><code class="language-java">String ip = InetAddress.getHostAddress();</code></pre>
<h1 id="tcp-네트워킹">TCP 네트워킹</h1>
<blockquote>
<p>IP 주소로 프로그램들이 통신할 때는 약속된 데이터 전송 규약이 있다. 이것을 <a href="https://en.wikipedia.org/wiki/Communication_protocol">전송용 프로토콜(Protocol)</a>이라고 부른다. 인터넷에서 전송용 프로토콜은 <a href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol">TCP(Transmission Control Protocol)</a>과 <a href="https://en.wikipedia.org/wiki/User_Datagram_Protocol">UDP(User Datagram Protocol)</a>가 있다.</p>
</blockquote>
<p>자바는 TCP 네트워킹을 위해 java.net 패키지에서 ServerSocket과 Socket 클래스를 제공하고 있다.
ServerSocket은 클라이언트의 연결을 수락하는 서버 쪽 클래스이고, Socket은 클라이언트에서 연결 요청할 때와 클라이언트와 서버 양쪽에서 데이터를 주고 받을 때 사용되는 클래스이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/8347d96b-72d5-4713-a5b7-a69f8b0dc1f8/image.png" alt=""></p>
<h2 id="tcp-서버">TCP 서버</h2>
<p>TCP 서버 프로그램을 개발하려면 우선 ServerSocket 객체를 생성해야 한다.
다음은 50001번 port에 바인딩하는 ServerSocket을 생성하는 코드이다.</p>
<pre><code class="language-java">ServerSocket serverSocket = new ServerSocket(50001);</code></pre>
<p>예제를 보자</p>
<pre><code class="language-java">package ch19.sec03.exam01;

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

public class ServerExample {
    private static ServerSocket serverSocket = null;

    public static void main(String[] args) {
        System.out.println(&quot;-----------------------------------------------&quot;);
        System.out.println(&quot;서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.&quot;);
        System.out.println(&quot;-----------------------------------------------&quot;);

        // TCP 서버 시작
        startServer();

        // 키보드 입력
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String key = scanner.nextLine();
            if (key.equalsIgnoreCase(&quot;q&quot;)) {
                break;
            }
        }
        scanner.close();

        // TCP 서버 종료
        stopServer();
    }

    public static void startServer() {
        // 작업 스레드 정의
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    // ServerSocket 생성 및 Port 바인딩
                    serverSocket = new ServerSocket(50001);
                    System.out.println(&quot;[서버] 시작됨&quot;);

                    while (true) {
                        System.out.println(&quot;\n[서버] 연결 요청을 기다림\n&quot;);
                        // 연결 수락
                        Socket socket = serverSocket.accept();

                        // 연결된 클라이언트 정보 얻기
                        InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
                        System.out.println(&quot;[서버] &quot; + isa.getHostName() + &quot;의 연결 요청을 수락함&quot;);

                        // 연결 끊기
                        socket.close();
                        System.out.println(&quot;[서버] &quot; + isa.getHostName() + &quot;의 연결을 끊음&quot;);
                    }
                } catch (IOException e) {
                    System.out.println(&quot;[서버] &quot; + e.getMessage());
                }
            }
        };
        // 스레드 시작
        thread.start();
    }

    public static void stopServer() {
        try {
            // ServerSocket을 닫고 Port 언바인딩
            serverSocket.close();
            System.out.println(&quot;[서버] 종료됨 &quot;);
        } catch (IOException e1) {
        }
    }
}</code></pre>
<h2 id="tcp-클라이언트">TCP 클라이언트</h2>
<p>클라이언트가 서버에 연결 요청을 하려면 Socket 객체를 생성할 때 생성자 매개값으로 서버 IP 주소와 Port 번호를 제공하면 된다.
로컬 컴퓨터에서 실행하는 서버로 연결 요청을 할 경우에는 IP 주소 대신 localhost를 사용할 수 있다.</p>
<p>예제를 보자.</p>
<pre><code class="language-java">package ch19.sec03.exam01;

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

public class ClientExample {
    public static void main(String[] args) {
        try {
            // Socket 생성과 동시에 localhost의 50001 Port로 연결 요청;
            Socket socket = new Socket(&quot;localhost&quot;, 50001);

            System.out.println(&quot;[클라이언트] 연결 성공&quot;);

            // Socket 닫기
            socket.close();
            System.out.println(&quot;[클라이언트] 연결 끊음&quot;);
        } catch (UnknownHostException e) {
            // IP 표기 방법이 잘못되었을 경우
        } catch (IOException e) {
            // 해당 포트의 서버에 연결할 수 없는 경우
        }
    }
}</code></pre>
<pre><code>-----------------------------------------------
서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.
-----------------------------------------------
[서버] 시작됨

[서버] 연결 요청을 기다림

[서버] localhost의 연결 요청을 수락함
[서버] localhost의 연결을 끊음

[서버] 연결 요청을 기다림

q
[서버] 종료됨 
[서버] Socket closed</code></pre><p>** 이전 예제인 ServerExample을 먼저 실행시키고 ClientExmaple을 실행하면 다음과 같은 내용이 Console 뷰에 출력된다. **</p>
<h2 id="입출력-스트림으로-데이터-주고-받기">입출력 스트림으로 데이터 주고 받기</h2>
<p>클라이언트가 연결 요쳥(connect())을 하고 서버가 연결 수락(accept())했다면, 다음 그림과 같이 양쪽의 Socket 객체로부터 각각 입력 스트림(InputStream)과 출력 스트림(OutputStream)을 얻을 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/a8cb6430-7e14-447e-941c-5f0b91f71716/image.png" alt="">
다음은 TCP 클라이언트가 보낸 메시지를 다시 돌려보내는 Echo(메아리) TCP 서버를 구현한 예제이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/88c18151-e5a2-4f4c-ad07-a5ee5b631220/image.png" alt=""></p>
<pre><code class="language-java">package ch19.sec03.exam02;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class EchoServer {
    private static ServerSocket serverSocket = null;

    public static void main(String[] args) {
        System.out.println(&quot;-----------------------------------------------&quot;);
        System.out.println(&quot;서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.&quot;);
        System.out.println(&quot;-----------------------------------------------&quot;);

        // TCP 서버 시작
        startServer();

        // 키보드 입력
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String key = scanner.nextLine();
            if (key.equalsIgnoreCase(&quot;q&quot;)) {
                break;
            }
        }
        scanner.close();

        // TCP 서버 종료
        stopServer();
    }

    public static void startServer() {
        // 작업 스레드 정의
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    // ServerSocket 생성 및 Port 바인딩
                    serverSocket = new ServerSocket(50001);
                    System.out.println(&quot;[서버] 시작됨&quot;);

                    // 연결 수락 및 데이터 통신
                    while (true) {
                        System.out.println(&quot;\n[서버] 연결 요청을 기다림\n&quot;);
                        // 연결 수락
                        Socket socket = serverSocket.accept();

                        // 연결된 클라이언트 정보 얻기
                        InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
                        System.out.println(&quot;[서버] &quot; + isa.getHostName() + &quot;의 연결 요청을 수락함&quot;);

                        // 데이터 받기
                        DataInputStream dis = new DataInputStream(socket.getInputStream());
                        String message = dis.readUTF();

                        // 데이터 보내기
                        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                        dos.writeUTF(message);
                        dos.flush();
                        System.out.println(&quot;[서버] 받은 데이터를 다시 보냄: &quot; + message);
                        // -------------------------------------------------------------------------------

                        // 연결 끊기
                        socket.close();
                        System.out.println(&quot;[서버] &quot; + isa.getHostName() + &quot;의 연결을 끊음&quot;);
                    }
                } catch (IOException e) {
                    System.out.println(&quot;[서버] &quot; + e.getMessage());
                }
            }
        };
        // 스레드 시작
        thread.start();
    }

    public static void stopServer() {
        try {
            // ServerSocket을 닫고 Port 언바인딩
            serverSocket.close();
            System.out.println(&quot;[서버] 종료됨 &quot;);
        } catch (IOException ignored) {
        }
    }
}</code></pre>
<pre><code class="language-java">package ch19.sec03.exam02;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;

public class EchoClient {
    public static void main(String[] args) {
        try {
            // Socket 생성과 동시에 localhost의 50001 포트로 연결 요청;
            Socket socket = new Socket(&quot;localhost&quot;, 50001);

            System.out.println(&quot;[클라이언트] 연결 성공&quot;);

            // 데이터 보내기
            String sendMessage = &quot;나는 자바가 좋아~~&quot;;
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            dos.writeUTF(sendMessage);
            dos.flush();
            System.out.println(&quot;[클라이언트] 데이터 보냄: &quot; + sendMessage);

            // 데이터 받기
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            String receiveMessage = dis.readUTF();
            System.out.println(&quot;[클라이언트] 데이터 받음: &quot; + receiveMessage);
            // ---------------------------------------------------------------------------

            // 연결 끊기
            socket.close();
            System.out.println(&quot;[클라이언트] 연결 끊음&quot;);
        } catch (Exception ignored) {
        }
    }
}</code></pre>
<pre><code>-----------------------------------------------
서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.
-----------------------------------------------
[서버] 시작됨

[서버] 연결 요청을 기다림

[서버] localhost의 연결 요청을 수락함
[서버] 받은 데이터를 다시 보냄: 나는 자바가 좋아~~
[서버] localhost의 연결을 끊음

[서버] 연결 요청을 기다림

q
[서버] 종료됨 
[서버] Socket closed</code></pre><pre><code>[클라이언트] 연결 성공
[클라이언트] 데이터 보냄: 나는 자바가 좋아~~
[클라이언트] 데이터 받음: 나는 자바가 좋아~~
[클라이언트] 연결 끊음</code></pre><h1 id="udp-네트워킹">UDP 네트워킹</h1>
<blockquote>
<p><a href="https://en.wikipedia.org/wiki/User_Datagram_Protocol">UDP(User Datagram Protocol)</a>는 발신자가 일방적으로 수신자에게 데이터를 보내는 방식으로, <a href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol">TCP(Transmission Control Protocol)</a> 처럼 연결 요청 및 수락 과정이 없기 때문에 TCP보다 데이터 전송 속도가 상대적으로 빠르다.</p>
</blockquote>
<p>자바는 UDP 네트워킹을 위해 java.net 패키지에서 DatagramSocket과 DatagramPacket 클래스를 제공하고 있다.
DatagramSocket은 발신점과 수신점에 해당하고 DatagramPacket은 주고 받는 데이터에 해당한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/7ad7b0fe-405f-4922-ac3a-d01f89b0a898/image.png" alt=""></p>
<h2 id="udp-서버">UDP 서버</h2>
<p>다음 예제는 UDP 클라이언트가 구독하고 싶은 뉴스 10개를 전송하는 UDP 서버이다.</p>
<pre><code class="language-java">package ch19.sec04;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

public class NewsServer {
    private static DatagramSocket datagramSocket = null;

    public static void main(String[] args) throws Exception {
        System.out.println(&quot;-----------------------------------------------&quot;);
        System.out.println(&quot;서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.&quot;);
        System.out.println(&quot;-----------------------------------------------&quot;);

        // UDP 서버 시작
        startServer();

        // 키보드 입력
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String key = scanner.nextLine();
            if (key.equalsIgnoreCase(&quot;q&quot;)) {
                break;
            }
        }
        scanner.close();

        // UDP 서버 종료
        stopServer();
    }

    public static void startServer() {
        // 작업 스레드 정의
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    // DatagramSocket 생성 및 Port 바인딩
                    datagramSocket = new DatagramSocket(50001);
                    System.out.println(&quot;[서버] 시작됨&quot;);

                    while (true) {
                        // 클라이언트가 구독하고 싶은 뉴스 주제 얻기
                        DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
                        datagramSocket.receive(receivePacket);
                        String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);

                        // 클라이언트의 IP와 Port 얻기
                        SocketAddress socketAddress = receivePacket.getSocketAddress();

                        // 10개의 뉴스를 클라이언트로 전송
                        for (int i = 1; i &lt;= 10; i++) {
                            String data = newsKind + &quot;: 뉴스&quot; + i;
                            byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
                            DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);
                            datagramSocket.send(sendPacket);
                        }
                    }
                } catch (Exception e) {
                    System.out.println(&quot;[서버] &quot; + e.getMessage());
                }
            }
        };
        // 스레드 시작
        thread.start();
    }

    public static void stopServer() {
        // DatagramSocket을 닫고 Port 언바인딩
        datagramSocket.close();
        System.out.println(&quot;[서버] 종료됨 &quot;);
    }
}</code></pre>
<pre><code>-----------------------------------------------
서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.
-----------------------------------------------
[서버] 시작됨
q
[서버] 종료됨 
[서버] Socket closed</code></pre><h2 id="udp-클라이언트">UDP 클라이언트</h2>
<p>UDP 클라이언트를 위한 DatagramSocket 객체는 기본 생성자로 생성한다.
Port 번호는 자동으로 부여되기 때문에 따로 지정할 필요가 없다.</p>
<p>다음은 이전 예제인 NewsServer로 구독하고 싶은 뉴스 주제를 보내고 관련 뉴스 10개를 받는 UDP 클라이언트이다.</p>
<pre><code class="language-java">package ch19.sec04;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

public class NewsClient {
    public static void main(String[] args) {
        try {
            // DatagramSocket 생성
            DatagramSocket datagramSocket = new DatagramSocket();

            // 구독하고 싶은 뉴스 주제 보내기
            String data = &quot;정치&quot;;
            byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
            DatagramPacket sendPacket = new DatagramPacket(bytes, bytes.length, new InetSocketAddress(&quot;localhost&quot;, 50001));
            datagramSocket.send(sendPacket);

            while (true) {
                // 뉴스 받기
                DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
                datagramSocket.receive(receivePacket);

                // 문자열로 변환
                String news = new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);
                System.out.println(news);

                // 10번째 뉴스를 받았을 경우 while 문 종료
                if (news.contains(&quot;뉴스10&quot;)) {
                    break;
                }
            }

            // DatagramSocket 닫기
            datagramSocket.close();
        } catch (Exception ignored) {
        }
    }
}</code></pre>
<pre><code>정치: 뉴스1
정치: 뉴스2
정치: 뉴스3
정치: 뉴스4
정치: 뉴스5
정치: 뉴스6
정치: 뉴스7
정치: 뉴스8
정치: 뉴스9
정치: 뉴스10</code></pre><h1 id="서버의-동시-요청-처리">서버의 동시 요청 처리</h1>
<blockquote>
<p>일반적으로 서버는 다수의 클라이언트와 통신을 한다. 서버는 클라이언트들로부터 동시에 요청을 받아서 처리하고, 처 결과를 개별 클라이언트로 보내줘야 한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/07784b06-4138-44ea-a3e7-db8efd18881e/image.png" alt=""></p>
<p>다수의 클라이언트와의 통신을 위해서는 accept()와 receive()를 제외한 요청 처리 코드를 별도의 스레드에서 작업하는 것이 좋다.</p>
<p><strong><code>스레드를 처리할 때 주의할 점은 클라이언트의 폭증으로 인한 서버의 과도한 스레드 생성을 방지해야 한다는 것이다.</code></strong></p>
<p>그래서 스레드풀을 사용하는 것이 바람직하다.
다음은 스레드풀을 이용해서 요청을 처리하는 방식이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/aa0ab9ff-b696-42f6-940f-b5dd3088e1e0/image.png" alt="">
스레드풀은 작업 처리 스레드 수를 제한해서 사용하기 때문에 갑작스런 클라이언트 폭증이 발생해도 크게 문제가 되지 않는다.
다만 작업 큐의 대기 작업이 증가되어 클라이언트에서 응답을 늦게 받을 수 있다.</p>
<h2 id="tcp-echoserver-동시-요청-처리">TCP EchoServer 동시 요청 처리</h2>
<p>다음은 스레드풀을 이용해서 클라리언트의 요청을 동시에 처리하는 EchoServer 예제이다.</p>
<pre><code class="language-java">package ch19.sec05.exam01;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EchoServer {
    private static ServerSocket serverSocket = null;
    private static final ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        System.out.println(&quot;-----------------------------------------------&quot;);
        System.out.println(&quot;서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.&quot;);
        System.out.println(&quot;-----------------------------------------------&quot;);

        // TCP 서버 시작
        startServer();

        // 키보드 입력
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String key = scanner.nextLine();
            if (key.equalsIgnoreCase(&quot;q&quot;)) {
                break;
            }
        }
        scanner.close();

        // TCP 서버 종료
        stopServer();
    }

    public static void startServer() {
        // 작업 스레드 정의
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    // ServerSocket 생성 및 Port 바인딩
                    serverSocket = new ServerSocket(50001);
                    System.out.println(&quot;[서버] 시작됨\n&quot;);

                    // 연결 수락 및 데이터 통신
                    while (true) {
                        // 연결 수락
                        Socket socket = serverSocket.accept();

                        executorService.execute(() -&gt; {
                            try {
                                // 연결된 클라이언트 정보 얻기
                                InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
                                System.out.println(&quot;[서버] &quot; + isa.getHostName() + &quot;의 연결 요청을 수락함&quot;);

                                // 데이터 받기
                                DataInputStream dis = new DataInputStream(socket.getInputStream());
                                String message = dis.readUTF();

                                // 데이터 보내기
                                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                                dos.writeUTF(message);
                                dos.flush();
                                System.out.println(&quot;[서버] 받은 데이터를 다시 보냄: &quot; + message);

                                // 연결 끊기
                                socket.close();
                                System.out.println(&quot;[서버] &quot; + isa.getHostName() + &quot;의 연결을 끊음\n&quot;);
                            } catch (IOException ignored) {
                            }
                        });
                    }
                } catch (IOException e) {
                    System.out.println(&quot;[서버] &quot; + e.getMessage());
                }
            }
        };
        // 스레드 시작
        thread.start();
    }

    public static void stopServer() {
        try {
            // ServerSocket을 닫고 Port 언바인딩
            serverSocket.close();
            executorService.shutdownNow();
            System.out.println(&quot;[서버] 종료됨 &quot;);
        } catch (IOException ignored) {
        }
    }
}</code></pre>
<h2 id="udp-echoserver-동시-요청-처리">UDP EchoServer 동시 요청 처리</h2>
<p>다음은 스레드풀을 이용해서 클라리언트의 요청을 동시에 처리하는 NewsServer 예제이다.</p>
<pre><code class="language-java">package ch19.sec05.exam02;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NewsServer {
    private static DatagramSocket datagramSocket = null;
    private static final ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws Exception {
        System.out.println(&quot;-----------------------------------------------&quot;);
        System.out.println(&quot;서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.&quot;);
        System.out.println(&quot;-----------------------------------------------&quot;);

        // UDP 서버 시작
        startServer();

        // 키보드 입력
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String key = scanner.nextLine();
            if (key.equalsIgnoreCase(&quot;q&quot;)) {
                break;
            }
        }
        scanner.close();

        // UDP 서버 종료
        stopServer();
    }

    public static void startServer() {
        // 작업 스레드 정의
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    // DatagramSocket 생성 및 Port 바인딩
                    datagramSocket = new DatagramSocket(50001);
                    System.out.println(&quot;[서버] 시작됨&quot;);

                    while (true) {
                        // 클라이언트가 구독하고 싶은 뉴스 종류 얻기
                        DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
                        datagramSocket.receive(receivePacket);

                        executorService.execute(() -&gt; {
                            try {
                                String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);

                                // 클라이언트의 IP와 Port 얻기
                                SocketAddress socketAddress = receivePacket.getSocketAddress();

                                // 10개의 뉴스를 클라이언트로 전송
                                for (int i = 1; i &lt;= 10; i++) {
                                    String data = newsKind + &quot;: 뉴스&quot; + i;
                                    byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
                                    DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);
                                    datagramSocket.send(sendPacket);
                                }
                            } catch (Exception ignored) {
                            }
                        });
                    }
                } catch (Exception e) {
                    System.out.println(&quot;[서버] &quot; + e.getMessage());
                }
            }
        };
        // 스레드 시작
        thread.start();
    }

    public static void stopServer() {
        // DatagramSocket을 닫고 Port 언바인딩
        datagramSocket.close();
        executorService.shutdownNow();
        System.out.println(&quot;[서버] 종료됨 &quot;);
    }
}</code></pre>
<h1 id="json-데이터-형식">JSON 데이터 형식</h1>
<blockquote>
<p>네트워크로 전달하는 데이터가 복잡할수록 구조화된 형식이 필요하다. 네트워크 통신에서 가장 많이 사용되는 데이터 형식은 <a href="https://en.wikipedia.org/wiki/JSON">JSON(Javascript Object Notation)</a>이다.</p>
</blockquote>
<p>JSON의 표기법은 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/ce713946-00f6-46d9-bb6a-a051a61f3620/image.png" alt=""></p>
<p>다음은 JSON 표기법과 관련된 클래스들이다.</p>
<table>
<thead>
<tr>
<th align="left">클래스</th>
<th align="left">용도</th>
</tr>
</thead>
<tbody><tr>
<td align="left">JSONObject</td>
<td align="left">JSON 객체 표기를 생성하거나 파싱할 때 사용</td>
</tr>
<tr>
<td align="left">JSONArray</td>
<td align="left">JSON 배열 표기를 생성하거나 파싱할 때 사용</td>
</tr>
<tr>
<td align="left">다음은 회원 정보를 JSON으로 만드는 예제이다.</td>
<td align="left"></td>
</tr>
<tr>
<td align="left">```java</td>
<td align="left"></td>
</tr>
<tr>
<td align="left">package ch19.sec06;</td>
<td align="left"></td>
</tr>
</tbody></table>
<p>import org.json.JSONArray;
import org.json.JSONObject;</p>
<p>import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;</p>
<p>public class CreateJsonExample {
    public static void main(String[] args) throws IOException {
        // JSON 객체 생성
        JSONObject root = new JSONObject();</p>
<pre><code>    // 속성 추가
    root.put(&quot;id&quot;, &quot;winter&quot;);
    root.put(&quot;name&quot;, &quot;한겨울&quot;);
    root.put(&quot;age&quot;, 25);
    root.put(&quot;student&quot;, true);

    // 객체 속성 추가
    JSONObject tel = new JSONObject();
    tel.put(&quot;home&quot;, &quot;02-123-1234&quot;);
    tel.put(&quot;mobile&quot;, &quot;010-123-1234&quot;);
    root.put(&quot;tel&quot;, tel);

    // 배열 속성 추가
    JSONArray skill = new JSONArray();
    skill.put(&quot;java&quot;);
    skill.put(&quot;c&quot;);
    skill.put(&quot;c++&quot;);
    root.put(&quot;skill&quot;, skill);

    // JSON 얻기
    String json = root.toString();

    // 콘솔에 출력
    System.out.println(json);

    // 파일로 저장
    Writer writer = new FileWriter(&quot;member.json&quot;, StandardCharsets.UTF_8);
    writer.write(json);
    writer.flush();
    writer.close();
}</code></pre><p>}</p>
<pre><code>```java
package ch19.sec06;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class ParseJsonExample {
    public static void main(String[] args) throws IOException {
        // 파일로부터 JSON 읽기
        BufferedReader br = new BufferedReader(new FileReader(&quot;member.json&quot;, StandardCharsets.UTF_8));
        String json = br.readLine();
        br.close();

        // JSON 파싱
        JSONObject root = new JSONObject(json);

        // 속성 정보 읽기
        System.out.println(&quot;id: &quot; + root.getString(&quot;id&quot;));
        System.out.println(&quot;name: &quot; + root.getString(&quot;id&quot;));
        System.out.println(&quot;age: &quot; + root.getInt(&quot;age&quot;));
        System.out.println(&quot;student: &quot; + root.getBoolean(&quot;student&quot;));

        // 객체 속성 정보 읽기
        JSONObject tel = root.getJSONObject(&quot;tel&quot;);
        System.out.println(&quot;home: &quot; + tel.getString(&quot;home&quot;));
        System.out.println(&quot;mobile: &quot; + tel.getString(&quot;mobile&quot;));

        // 배열 속성 정보 읽기
        JSONArray skill = root.getJSONArray(&quot;skill&quot;);
        System.out.print(&quot;skill: &quot;);
        for (int i = 0; i &lt; skill.length(); i++) {
            System.out.print(skill.get(i) + &quot;, &quot;);
        }
    }
}</code></pre><pre><code>id: winter
name: winter
age: 25
student: true
home: 02-123-1234
mobile: 010-123-1234
skill: java, c, c++, </code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 18 - 데이터 입출력 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-18-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9E%85%EC%B6%9C%EB%A0%A5-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-18-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9E%85%EC%B6%9C%EB%A0%A5-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Tue, 31 Jan 2023 19:20:27 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-18---데이터-입출력-확인문제">Chapter 18 - 데이터 입출력 확인문제</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b8aa5f77-b0e4-4e4a-91a8-3867c93e6e48/image.png" alt=""></p>
<h2 id="정답-1">정답: 1</h2>
<p>스트림은 입력 스트림과 출력 스트림으로 나뉘어 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/aa8f127b-afc8-43e4-bddd-e570b20db791/image.png" alt=""></p>
<h2 id="정답-1-1">정답: 1</h2>
<p>이미지 데이터는 InputStream으로만 읽을 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/3dd1d849-7626-4b3e-a45f-8641e168c606/image.png" alt=""></p>
<h2 id="정답-3">정답: 3</h2>
<p>read(byte[] b) 메소드는 입력 스트림으로부터 주어진 배열의 길이만큼만 읽을 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/86edd155-002d-4869-adab-020265a5dfbb/image.png" alt=""></p>
<h2 id="정답-1-2">정답: 1</h2>
<p>flush() 메소드는 출력 스트림의 버퍼에 있는 데이터를 모두 출력시키고 버퍼를 비운다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/7d35c007-6053-45b8-af7f-57b75a212955/image.png" alt=""></p>
<h2 id="정답-3-1">정답: 3</h2>
<p>DataInputStream은 기본 타입인 boolean, char, short, int, long, float, double 값을 입력할 수 있는 보조 스트림이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/8447e62e-0aaf-4dc6-82d5-4fc624061162/image.png" alt=""></p>
<h2 id="정답-3-2">정답: 3</h2>
<p>두 클래스가 동일한 SerialVersionUID 상수값을 가지고 있어야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/763c47a6-0e07-4f2b-923f-da37a12cfc6d/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/dc8894f0-ade2-4769-89fb-56624d54d218/image.png" alt=""></p>
<h2 id="정답">정답:</h2>
<pre><code class="language-java">new FileReader(filePath);</code></pre>
<pre><code class="language-java">new BufferedReader(fr);</code></pre>
<pre><code class="language-java">rowData = br.readLine();
if (rowData == null) break;
System.out.println(++rowNumber + &quot;: &quot; + rowData);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/0ce62a3e-939b-4753-b959-0a89145c67dd/image.png" alt=""></p>
<h2 id="정답-4">정답: 4</h2>
<p>PrintStream은 바이트 출력 스트림과 연결되고, PrintWriter는 문자 출력 스트림과 연결된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5ec922a3-e5d3-45b6-915a-0cb37d5f111c/image.png" alt=""></p>
<h2 id="정답-2">정답: 2</h2>
<p>File 객체는 getPath() 등의 메소드를 이용해서 디렉터리 정보를 제공한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/7dd5ca88-420b-4326-9b4c-82b5f9d9938a/image.png" alt=""></p>
<h2 id="정답-5">정답:</h2>
<pre><code class="language-java">import java.io.*;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print(&quot;원본 파일 경로: &quot;);
        String orgPath = sc.nextLine();

        System.out.print(&quot;복사 파일 경로: &quot;);
        String dstPath = sc.nextLine();

        File orgFile = new File(orgPath);
        if (!orgFile.exists()) {
            System.out.println(&quot;원본 파일이 존재하지 않습니다.&quot;);
            System.exit(0);
        }

        File dstFile = new File(dstPath);
        File parentFile = dstFile.getParentFile();
        if (!dstFile.getParentFile().exists()) {
            parentFile.mkdirs();
        }

        try {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(orgPath));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dstPath));

            bis.transferTo(bos);
            bis.close();
            bos.close();

            System.out.println(&quot;복사가 성공되었습니다.&quot;);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 18 - 데이터 입출력]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-18-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9E%85%EC%B6%9C%EB%A0%A5</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-18-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9E%85%EC%B6%9C%EB%A0%A5</guid>
            <pubDate>Tue, 31 Jan 2023 18:37:55 GMT</pubDate>
            <description><![CDATA[<h1 id="입출력-스트림">입출력 스트림</h1>
<blockquote>
<p>데이터는 키보드를 통해 입력될 수도 있고, 파일 또는 프로그램으로부터 입력될 수도 있다.
반대로 데이터는 모니터로 출력될 수도 있고, 파일에 저장되거나 다른 프로그램으로 전송될 수 있다.
이것을 총칭해서 데이터 입출력이라고 한다.</p>
</blockquote>
<p>자바는 입력 스트림과 출력 스트림을 통해 데이터를 입출력한다. 스트림(Stream)은 단방향으로 데이터가 흐르는 것을 말하는데, 다음 그림과 같이 데이터는 출발지에서 나와 도착지로 흘러들어간다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/7983a8d7-f354-4bdb-8eb5-a8ae1d672716/image.png" alt="">
프로그램을 기준으로 데이터가 들어오면 입력 스트림, 데이터가 나가면 출력 스트림이 된다.
프로그램이 다른 프로그램과 데이터를 교환하려면 양쪽 모두 입력 스트림과 출력 스트림이 필요하다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/49e0d77d-753a-4e37-9118-b6d0cab912b7/image.png" alt="">
어떤 데이터를 입출력하느냐에 따라 스트림은 두 종류로 구분할 수 있다.</p>
<ul>
<li><p>바이트 스트림
그림, 멀티미디어, 문자 등 모든 종류의 데이터를 입출력할 때 사용</p>
</li>
<li><p>문자 스트림
문자만 입출력할 때 사용</p>
</li>
</ul>
<p>자바는 데이터 입출력과 관련된 라이브러리를 java.io 패키지에서 제공하고 있다. java.io 패키지는 바이트 스트림과 문자 스트림을 다음과 같이 이름으로 구분해서 제공한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/11265954-cf76-4901-9f03-1c2b21fb275d/image.png" alt=""></p>
<p>바이트 입출력 스트림의 최상위 클래스는 InputStream과 OutputStream이다.
이 클래스를 상속받는 자식 클래스에는 접미사로 InputStream 또는 OutputStream이 붙는다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/3f14b605-9f03-4078-bd4a-ffc4069e094f/image.png" alt=""></p>
<h1 id="바이트-출력-스트림">바이트 출력 스트림</h1>
<blockquote>
<p>OutputStream은 바이트 출력 스트림의 최상위 클래스로 추상 클래스이다. 모든 바이트 출력 스트림 클래스는 이 OutputStream 클래스를 상속 받아서 만들어진다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/9c3e5f2a-d832-448c-aa47-ebabe6919ffc/image.png" alt=""></p>
<p>OutputStream 클래스에는 모든 바이트 출력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.
다음은 OutputStream 클래스의 주요 메소드이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/2eefb4b3-ee91-4e0c-bf98-5ce97d52cf21/image.png" alt=""></p>
<h2 id="1-바이트-출력">1 바이트 출력</h2>
<p>write(int b) 메소드는 매개값 int(4byte) 에서 끝 1byte만 출력한다.
매개변수가 int 타입으므로 4byte 모두를 보내는 것은 아니다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/8d2b7085-7cf8-4b6d-8d17-6132e5a6912a/image.png" alt=""></p>
<h2 id="바이트-배열-출력">바이트 배열 출력</h2>
<p>일반적으로 1바이트를 출력하는 경우는 드물고, 보통 바이트 배열을 통째로 출력하는 경우가 많다.
write(byte[] b) 메소드는 매개값으로 주어진 배열의 모든 바이트를 출력한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5fb95695-869a-40ed-88e6-14898416fe3e/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/3dffa06a-77ea-4b5a-8cf8-d9526e80b0dd/image.png" alt=""></p>
<h1 id="바이트-입력-스트림">바이트 입력 스트림</h1>
<blockquote>
<p>InputStream은 바이트 입력 스트림의 최상위 클래스로, 추상 클래스이다. 모든 바이트 입력 스트림은 InputStream 클래스를 상속받아 만들어진다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/8d700e94-0405-4c94-bd95-56a44ba59864/image.png" alt="">
InputStream 클래스에는 바이트 입력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.
다음은 InputStream 클래스의 주요 메소드이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/4217a5d3-11d6-4a6a-b14f-5fb95f877f61/image.png" alt=""></p>
<h2 id="1바이트-읽기">1바이트 읽기</h2>
<p>read() 메소드는 입력 스트림으로부터 1byte를 읽고 int(4byte) 타입으로 리턴한다. 따라서 리턴된 4byte 중 끝 1bye에만 데이터가 들어 있다. 예를 들어 입력 스트림에서 5개의 바이트가 들어온다면 다음과 같이 read() 메소드로 1byte씩 5번 읽을 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5eb3002f-0d7f-43a2-8764-4311b8b9592d/image.png" alt="">
더 이상 입력 스트림으로부터 바이트를 읽을 수 없다면 read()메소드는 -1을 리턴하는데, 이것을 이용하면 읽을 수 있는 마지막 바이트까지 반복해서 한 바이트씩 읽을 수 있다.</p>
<pre><code class="language-java">InputStream is = ...;

while (true) {
    int data = is.read(); // 1 바이트를 읽고 리턴
    if (data == -1) break; // -1을 리턴했을 경우 while 문 종료 
}</code></pre>
<h2 id="바이트-배열로-읽기">바이트 배열로 읽기</h2>
<p>read(byte[] b) 메소드는 입력 스트림으로부터 주어진 배열의 길이만큼 바이트를 읽고 배열에 저장한 다음 읽은 바이트 수를 리턴한다. 예를들어 입력 스트림에 5개의 바이트가 들어오면 다음과 같이 길이 3인 배열로 두 번 읽을 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/1a54646d-f444-407b-9f70-829162e103cc/image.png" alt="">
read(byte[] b) 역시 입력 스트림으로부터 바이트를 더 이상 읽을 수 없다면 -1을 리턴하는데, 이것을 이용하면 읽을 수 있는 마지막 바이트까지 반복해서 읽을 수 있다.</p>
<pre><code class="language-java">InputStream is = ...;
byte[] data = new byte[100];

while (true) {
    int num = is.read(data); //최대 100byte를 읽고, 읽은 바이트는 배열 data 저장, 읽은 수는 리턴
    if (num == -1) break; //-1을 리턴하면 while 문 종료
}</code></pre>
<h1 id="문자-입출력-스트림">문자 입출력 스트림</h1>
<blockquote>
<p>바이트 입출력 스트림인 InputStream과 OutputStream에 대응하는 문자 입출력 스트림으로 Reader와 Writer가 있다. 입출력되는 단위가 문자인 것을 제외하고는 바이트 입출력 스트림과 사용 방법은 동일하다.</p>
</blockquote>
<h2 id="문자-출력">문자 출력</h2>
<p>Writer는 문자 출력 스트림의 최상위 클래스로, 추상 클래스이다. 모든 문자 출력 스트림 클래스는 Writer 클래스를 상속받아서 만들어진다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/4a64ebfc-a748-4f9c-9372-67ef4b381beb/image.png" alt="">
Writer 클래스는 모든 문자 출력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.
Writer 클래스의 주요 메소드는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/ff89f03a-dccf-47ad-a4dc-2a290ac66929/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/69091e00-972c-44be-a095-84f31bdf6867/image.png" alt="">
Write는 OutPutStream과 사용 방법은 동일하지만, 출력 단위가 문자(char)이다.
그리고 문자열을 출력하는 write(String str) 메소드를 추가로 제공한다.</p>
<h2 id="문자-읽기">문자 읽기</h2>
<p>Reader는 문자 입력 스트림의 최상위 클래스로, 추상 클래스이다. 모든 문자 입력 스트림 클래스는 Reader 클래스를 상속받아서 만들어진다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b10e34c4-8180-4fb7-b104-411998d2699b/image.png" alt="">
Reader 클래스에는 문자 입력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.
다음은 Reader 클래스의 주요 메소드이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/72c1e6e5-1eb4-4b66-8d14-7ec0ea36e120/image.png" alt="">
Reader는 InputStream과 사용 방법은 동일하지만, 출력 단위가 문자(char)이다.</p>
<h1 id="보조-스트림">보조 스트림</h1>
<blockquote>
<p>보조 스트림이란 다른 스트림과 연결되어 여러 가지 편리한 기능을 제공해주는 스트림을 말한다.</p>
</blockquote>
<p>보조 스트림은 자체적으로 입출력을 수행할 수 없기 때문에 입출력 소스로부터 직접 생성된 입출력 스트림에 연결해서 사용해야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5606943a-44e2-4e20-b962-32ee5de9a123/image.png" alt="">
입출력 스트림에 보조 스트림을 연결하려면 보조 스트림을 생성할 때 생성자 매개값으로 입출력 스트림을 제공하면 된다.</p>
<pre><code class="language-java">보조스트림 변수 = new 보조스트림(입출력스트림);</code></pre>
<p>예를 들어 바이트 입력 스트림인 FileInputStream에 InputStreamReader 보조 스트림을 연결하는 코드는 다음과 같다.</p>
<pre><code class="language-java">InputStream is = new FileInputStream(&quot;...&quot;);
InputStreamReader reader = new InputStreamReader(is);</code></pre>
<p>보조 스트림은 또 다른 보조 스트림과 연결되어 스트림 체인으로 구성할 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b3db0bda-5a49-4fef-9019-62dde6ad28b6/image.png" alt="">
예를 들어 문자 변환 보조 스트림인 InputStreamReader에 BufferedReader 보조 스트림을 연결하는 코드는 다음과 같다.</p>
<pre><code class="language-java">InputStream is = new FileInputStream(&quot;...&quot;);
InputStreamReader reader = new InputStreamReader(is);
BufferedReader br = new BufferReader(reader);</code></pre>
<p>자주 사용되는 보조 스트림은 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/4ac6ed23-f8e7-43dd-b272-ad5ef6ad6564/image.png" alt=""></p>
<h1 id="문자-변환-스트림">문자 변환 스트림</h1>
<blockquote>
<p>바이트 스트림(InputStream, OutputStream)에서 입출력할 데이터가 문자라면 문자 스트림(Reader, Writer)으로 변환해서 사용하는 것이 좋다. 그 이유는 문자로 바로 입출력하는 편리함이 있고, 문자셋의 종류를 지정할 수 있기 때문이다.</p>
</blockquote>
<h2 id="inputstream을-reader로-변환">InputStream을 Reader로 변환</h2>
<p>InputStream을 Reader로 변환하려면 InputStreamReader 보조 스트림을 연결하면 된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/ccfdce50-f347-4e12-99d3-7ba1f5473216/image.png" alt="">
다음은 InputStream 을 Reader로 변환하는 예제이다.</p>
<pre><code class="language-java">InputStream is = new FileInputStream(&quot;C:/Temp/test.txt&quot;);
Reader reader = new InputStreamReader(is);</code></pre>
<h3 id="filereader의-원리">FileReader의 원리</h3>
<p>FileInputStream에 InputStreamReader를 연결하지 않고 FileReader를 직접 생성할 수 있다. FileReader는 InputStreamReader의 자식 클래스이다. 이것은 FileReader가 내부적으로 FileInputStream에 InputStreamReader 보조 스트림을 연결한 것이라고 볼 수 있다.</p>
<h2 id="outputstream을-writer로-변환">OutputStream을 Writer로 변환</h2>
<p>OutputStream을 Writer로 변환하려면 OutputStreamReader 보조 스트림을 연결하면 된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/aefc050e-0d47-4776-b204-3cc30a7b2fd7/image.png" alt=""></p>
<p>다음은 OutputStream을 Writer로 변환하는 예제이다.</p>
<pre><code class="language-java">OutputStream os = new FileOutputStream(&quot;C:/Temp/test.txt&quot;);
Writer writer = new OutputStreamWriter(os);</code></pre>
<h3 id="filewriter의-원리">FileWriter의 원리</h3>
<p>FIleOutputStream에 OutputStreamWriter를 연결하지 않고 FileWriter를 직접 생성할 수 있다. FileWriter는 OutputStreamWriter의 자식 클래스이다. 이것은 FileWriter가 내부적으로 FileOutputStream에 OutputStreamWriter 보조 스트림을 연결한 것이라고 볼 수 있다.</p>
<h1 id="성능-향상-스트림">성능 향상 스트림</h1>
<blockquote>
<p>프로그램이 입출력 소스와 직접 작업하지 않고 중간에 메모리 버퍼(Buffer)와 작업함으로써 실행 성능을 향상시킬 수 있다.</p>
</blockquote>
<p>출력 스트림과 입력 스트림은 하드 디스크가 아닌 메모리 버퍼에 데이터를 보내고, 받음으로써 입출력 속도를 향상시킬 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/fee0cf1d-c597-40bc-bf7a-4704d61493bb/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b676f851-26b0-49e3-9d0f-3d69f31801b5/image.png" alt=""></p>
<p>위와 같이 메모리 버퍼를 제공하여 프로그램의 실행 성능을 향상시키는 보조 스트림이 있다.</p>
<p>바이트 스트림에는 BufferedInputStream, BufferedOutputStream, 문자 스트림에는 BufferedReader, BufferedWriter 가 있다.</p>
<pre><code class="language-java">BufferedInputStream bis = new BufferedInputStream(바이트 입력 스트림);
BufferedOutputStream bos = new BufferedOutputStream(바이트 출력 스트림);</code></pre>
<pre><code class="language-java">BufferedReader br = new BufferedReader(문자 입력 스트림);
BufferedWriter bw = new BufferedWriter(문자 출력 스트림);</code></pre>
<h1 id="기본-타입-스트림">기본 타입 스트림</h1>
<blockquote>
<p>바이트 스트림에 DataInputStream과 DataOutputStream 보조 스트림을 연결하면 기본 타입인 boolean, char, short, int, long, float, double 값을 입출력할 수 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/0ae07312-499f-415a-a106-7096527a4cdf/image.png" alt="">
다음과 같이 사용한다.</p>
<pre><code class="language-java">DataInputStream dis = new DataInputStream(바이트 입력 스트림);
DataOutputStream dos = new DataOutputStream(바이트 출력 스트림);</code></pre>
<p>다음은 DataInputStream과 DataOutputStream이 제공하는 메소드이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f1fc9816-1938-4db9-9233-bfce7c1853b8/image.png" alt=""></p>
<p><strong><code>데이터 타입의 크기가 모두 다르므로 DataOutputStream으로 출력한 데이터를 DataInputStream으로 읽어 올 때에는 출력한 순서와 동일한 순서로 읽어야 한다는 점에 주의하자</code></strong></p>
<h1 id="프린트-스트림">프린트 스트림</h1>
<blockquote>
<p>프린트 스트림(PrintStream)과 프린트 라이터(PrintWriter)는 프린터와 유사하게 출력하는 print(), println(), printf() 메소드를 가지고 있는 보조 스트림이다.</p>
</blockquote>
<p>지금까지 우리는 콘솔에 출력하기 위해 System.out.println()을 사용하였는데, 그 이유는 out이 PrintStream 타입이기 때문이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/269cb965-931c-4dbd-94c0-d141866f8592/image.png" alt="">
PrintStream은 바이트 출력 스트림과 연결되고, PrintWriter는 문자 출력 스트림과 연결된다.</p>
<pre><code class="language-java">PrintStream ps = new PrintStream(바이트 출력 스트림);
PrintWriter pw = new PrintWriter(문자 출력 스트림);</code></pre>
<p>PrintStream과 PrintWriter는 거의 같은 메소드를 가지고 있다.
println() 메소드는 출력할 데이터 끝에 줄바꿈 문자인 <code>\n</code>을 더 추가시키기 때문에 콘솔이다 파일에 줄바꿈이 일어난다.
그러나 print() 메소드는 줄바꿈 없이 계속해서 문자를 출력시킨다.
println()과 print() 메소드는 출력할 데이터 타입에 따라 다음과 같이 재정의된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/2f16ae9a-efd9-4a07-939f-21710847732a/image.png" alt=""></p>
<h1 id="객체-스트림">객체 스트림</h1>
<blockquote>
<p>자바는 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수 있다. 객체를 출력하려면 필드값을 일렬로 늘어선 바이트로 변경해야 하는데, 이것을 <a href="https://en.wikipedia.org/wiki/Serialization">직렬화(Serialization)</a>라고 한다. 반대로 직렬화된 바이트를 객체의 필드값으로 복원하는 것을 역직렬화(Deserialization)라고 한다.</p>
</blockquote>
<p>ObjectInputStream과 ObjectOutputStream
은 객체를 입출력할 수 있는 보조 스트림이다. ObjectOutputStream은 바이트 출력 스트림과 연결되어 객체를 직렬화하고, ObjectInputStream은 바이트 입력 스트림과 연결되어 객체로 복원하는 역직렬화를 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/71e8f47f-8ffa-4934-b917-ee7f6318c4cf/image.png" alt=""></p>
<pre><code class="language-java">ObjectInputStream ois = new ObjectInputStream(바이트 입력 스트림);
ObjectOutputStream oos = new ObjectOutputStream(바이트 출력 스트림);</code></pre>
<p>ObjectOutputStream으로 객체를 직렬화하기 위해서는 writeObject() 메소드를 사용한다.</p>
<pre><code class="language-java">oos.writeObject(객체);</code></pre>
<p>반대로 ObjectInputStream 의 readObject()메소드는 읽은 바이트를 역직렬화해서 객체로 생성한다. readObject() 메소드의 리턴타입은 Object이므로 구체적인 타입으로 강제 타입 변환해야 한다.</p>
<pre><code class="language-java">객체타입 변수 = (객체타입) ois.readObject();</code></pre>
<h2 id="serializable-인터페이스">Serializable 인터페이스</h2>
<p>자바는 Serializable 인터페이스를 구현한 클래스만 직렬화할 수 있도록 제한한다.
Serializable 인터페이스는 멤버가 없는 빈 인터페이스이지만, 객체를 직렬화할 수 있다고 표시하는 역할을 한다. 
객체가 직렬화될 때 인스턴스 필드값은 직렬화 대상이지만 정적 필드값과 transient로 선언된 필드값은 직렬화에서 제외되므로 출력되지 않는다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/206ca7d6-461e-48dd-991e-8657021f1a4b/image.png" alt=""></p>
<h2 id="serialversionuid-필드">SerialVersionUID 필드</h2>
<p>직렬화할 때 사용된 클래스와 역직렬화할 때 사용된 클래스는 기본적으로 동일한 클래스여야 한다.
만약 클래스의 이름이 같더라도 클래스의 내용이 다르면 역직렬화에 실패한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/01fa8e23-4d8a-426e-89af-4c6bde032b17/image.png" alt="">
클래스 내용이 다르다 할지라고 직렬화된 필드를 공통으로 포함하고 있다면 역직렬화할 수 있는 방법이 있다.
두 클래스가 동일한 SerialVersionUID 상수값을 가지고 있으면 된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/7fda0e58-f2b8-4877-aac9-002ce618e75d/image.png" alt="">
SerialVersionUID의 값은 개발자가 임의로 줄 수있지만 가능한 클래스마다 다른 유일한 값을 갖도록 하는 것이 좋다.</p>
<h1 id="file과-files-클래스">File과 Files 클래스</h1>
<blockquote>
<p>java.io 패키지와 java.nio.file 패키지는 파일과 디렉터리 정보를 가지고 있는 File과 Files 클래스를 제공한다. Files는 File을 개선한 클래스로, 좀 더 많은 기능을 가지고 있다.</p>
</blockquote>
<h2 id="file-클래스">File 클래스</h2>
<p>File 클래스로부터 File 객체를 생성하는 방법은 다음과 같다.</p>
<pre><code class="language-java">File file = new File(&quot;경로&quot;);</code></pre>
<p>경로 구분자는 운영체제마다 조금씩 다르다.
윈도우에서는 <code>\\</code> 또는 <code>/</code> 둘다 사용할 수 있고 맥OS 및 리눅스에서는 <code>/</code>를 사용한다.</p>
<p>File 객체를 생성했다고 해서 파일이나 디렉토리가 생성되는 것은 아니다.
그리고 경로에 실제 파일이나 디렉토리가 없더라도 예외가 발생하지 않는다.
파일이나 디렉토리가 실제로 있는지 확인하고 싶다면 File객체를 생성하고 나서 exists() 메소드를 호출해보면 된다.</p>
<pre><code class="language-java">boolean isExist = file.exists(); // 파일이나 폴더가 존재한다면 true를 리턴</code></pre>
<p>exists() 메소드가 false를 리턴할 경우, 다음 메소드로 파일 또는 폴더를 생성할 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/070d3bf5-ae43-4781-8842-67441097961c/image.png" alt="">
exists() 메소드 리턴값이 true 라면 다음 메소드를 사용할 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/423d72dc-1fa9-48af-97e8-413162681f6f/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/9990f087-6c7f-4039-8a85-5c1e6b4daae0/image.png" alt=""></p>
<h2 id="files-클래스">Files 클래스</h2>
<p>Files 클래스는 정적 메소드로 구성되어 있기 때문에 File 클래스처럼 객체로 만들 필요가 없다.
Files의 정적 메소드는 운영체제의 파일 시스템에게 파일 작업을 수행하도록 위임한다.</p>
<p>다음은 Files 클래스가 가지고 있는 정적 메소드를 기능별로 정리한 표이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/90a2f0de-b28b-4de4-9890-200f3a9c1583/image.png" alt="">
이 메소드들은 매개값으로 Path 객체를 받는다. Path 객체는 파일이나 디렉토리를 찾기 위한 경로 정보를 가지고 있는데, 정적 메소드인 get() 메소드로 다음과 같이 얻을 수 있다.</p>
<pre><code class="language-java">Path path = Paths.get(String first, String ... more)</code></pre>
<p>get() 메소드의 매개값은 파일 경로인데, 전체 경로를 한꺼번에 지정해도 좋고 상위 디렉토리와 하위 디렉토리를 나열해서 지정해도 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 17 - 스트림 요소 처리 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-17-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%9A%94%EC%86%8C-%EC%B2%98%EB%A6%AC-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-17-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%9A%94%EC%86%8C-%EC%B2%98%EB%A6%AC-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Mon, 30 Jan 2023 15:58:47 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-17---스트림-요소-처리-확인문제">Chapter 17 - 스트림 요소 처리 확인문제</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/59c904e9-c7c6-4196-8d95-86ef5a0cc7b9/image.png" alt=""></p>
<h2 id="정답-4">정답: 4</h2>
<p>스트림은 요소를 모두 처리하고 나면 요소를 다시 반복시킬 수 없다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/8501b18b-4fc0-4422-8ecc-b5158b00a8d8/image.png" alt=""></p>
<h2 id="정답-2">정답: 2</h2>
<p>int, long, double 범위에서는 스트림을 얻을 수 없다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/7a82c298-0d57-4f14-ac70-521ba3fd72a7/image.png" alt=""></p>
<h2 id="정답-4-1">정답: 4</h2>
<p>최종 처리가 없으면 중간 처리를 할 수 없다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/21af8c6d-f8fb-482a-9362-e8839c89fd71/image.png" alt=""></p>
<h2 id="정답-3">정답: 3</h2>
<p>컬렉션에 전체 요소의 수가 적고 요소당 처리 시간이 짧으면 일반 스트림이 병렬 스트림보다 빠를 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/58f07472-05a7-460b-ab66-77b1cccb2aff/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b17c3975-1dbc-47bd-a67d-de6823fefc32/image.png" alt=""></p>
<h2 id="정답">정답:</h2>
<pre><code class="language-java">.filter(str -&gt; str.toLowerCase().contains(&quot;java&quot;))
.forEach(str -&gt; System.out.println(str));</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/2408e66f-33aa-4a9d-b2a7-2802b359d55b/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/a76b6f26-84f7-4c29-8dfe-9bb49b561598/image.png" alt=""></p>
<h2 id="정답-1">정답:</h2>
<pre><code class="language-java">.mapToInt(Member::getAge)
.average()
.getAsDouble();</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/72ff6443-af3a-486d-98c8-7477982897d0/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/a0d66863-3f17-40c8-832a-08af06441576/image.png" alt=""></p>
<h2 id="정답-5">정답:</h2>
<pre><code class="language-java">.filter(m -&gt; m.getJob().equals(&quot;개발자&quot;))
.collect(Collectors.toList());</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/51db6420-a4b9-4b08-90bd-a7856fe5ec72/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/01bf191d-e791-4e74-bce7-25236085476e/image.png" alt=""></p>
<h2 id="정답-6">정답:</h2>
<pre><code class="language-java">.collect(Collectors.groupingBy(m -&gt; m.getJob()));

groupingMap.get(&quot;개발자&quot;).stream()
    .forEach(m -&gt; System.out.println(m));

groupingMap.get(&quot;디자이너&quot;).stream()
    .forEach(m -&gt; System.out.println(m));</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 17 - 스트림 요소 처리]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-17-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%9A%94%EC%86%8C-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-17-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%9A%94%EC%86%8C-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Mon, 30 Jan 2023 13:31:28 GMT</pubDate>
            <description><![CDATA[<h1 id="스트림이란">스트림이란?</h1>
<blockquote>
<p>Java 8부터는 컬렉션 및 배열의 요소를 반복 처리하기 위해 스트림(Stream)을 사용할 수 있다.
스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다.</p>
</blockquote>
<p>List 컬렉션에서 요소를 반복 처리하기 위해 스트림을 사용하면 다음과 같다.</p>
<pre><code class="language-java">Stream&lt;String&gt; stream = list.stream();
stream.forEach(item -&gt; // item 처리);</code></pre>
<p>List 컬렉션의 stream() 메소드로 Stream 객체를 얻고, forEach() 메소드로 요소를 어떻게 처리할지를 람다식으로 제공한다.</p>
<p>Stream은 Iterator와 비슷한 반복자이지만, 다음과 같은 차이점을 가지고 있다.</p>
<ol>
<li>내부 반복자이므로 처리 속도가 빠르고 병럴 처리에 효율적이다.</li>
<li>람다식으로 다양한 요소 처리를 정의할 수 있다.</li>
<li>중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.</li>
</ol>
<h1 id="내부-반복자">내부 반복자</h1>
<blockquote>
<p>for 문과 Iterator는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리하는데, 이것을 외부 반복자라고 한다.
반면 스트림은 요소 처리 방법을 컬렉션 내부로 주입시켜서 요소를 반복 처리하는데, 이것을 내부 반복자라고 한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/d138feca-ada2-4bfa-b4c7-5356bd12729f/image.png" alt="">
외부 반복자일 경우에는 컬렉션의 요소를 외부로 가져오는 코드와 처리하는 코드를 모두 개발자 코드가 가지고 있어야 한다.
반면 내부 반복자일 경우는 개발자 코드에서 제공한 데이터 처리 코드(람다식)를 가지고 컬렉션 내부에서 요소를 반복 처리한다.</p>
<p>내부 반복자는 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있다.
하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있는 장점이 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f1f001f3-55c1-4eca-bf55-b34c0f61848d/image.png" alt=""></p>
<h1 id="중간-처리와-최종-처리">중간 처리와 최종 처리</h1>
<blockquote>
<p>스트림은 하나 이상 연결될 수 있다. 다음 그림을 보면 컬렉션의 오리지널 스트림 뒤에 중간 스트림이 연결될 수 있고, 그 뒤에 매핑 중간 스트림이 연결될 수 있다. 이와 같이 스트림이 연결되어 있는 것을 스트림 파이프라인(Pipeline)이라고 한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c6de03b2-4db3-4cc2-94a3-1f4d1b2b6589/image.png" alt="">
오리지널 스트림과 집계 처리 사이의 중간 스트림들은 최종 처리를 위해 요소를 걸러내거나(필터링), 요소를 변환시키거나(매핑), 정렬하는 작업을 수행한다. 최종 처리는 중간 처리에서 정제된 요소들을 반복하거나, 집계(카운팅, 총합, 평균) 작업을 수행한다.</p>
<p>예제를 보자.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/d812a60c-de9c-4dcc-bbcf-75d0fcf63a39/image.png" alt="">
이것을 코드로 표현하면 다음과 같다.</p>
<pre><code class="language-java">// Student 스트림
Stram&lt;Student&gt; studentStream = list.stream();

// score 스트림
IntStream scoreStream = studentStream.mapToInt(
    student -&gt; student.getScore() // Student 객체를 getScore() 메소드의 리턴값으로 매핑
);

// 평균 계산
double avg = scoreStream.average().getAsDouble();</code></pre>
<p>mapToInt() 메소드는 객체를 int 값으로 매핑해서 IntStream으로 변환시킨다.
어떤 객체를 어떤 int 값으로 매핑할 것인지는 람다식으로 제공해야 한다.
student -&gt; student.getScore()는 Student 객체를 getScore()의 리턴값으로 매핑한다.
IntStream은 최종 처리를 위해 다양한 메소드를 제공하는데, average() 메소드는 요소들의 평균 값을 계산한다.</p>
<p>메소드 체이닝 패턴을 이용하면 앞의 코드를 다음과 같이 더 간결하게 작성할 수 있다.</p>
<pre><code class="language-java">double avg = list.stream()
    .mapToInt(student -&gt; student.getScore())
    .average()
    .getAsDouble();</code></pre>
<p>스트림 파이프라인으로 구성할 때 주의할 점은 파이프라인의 맨 끝에는 반드시 최종 처리 부분이 있어야 한다는 것이다.
최종 처리가 없다면 오리지널 및 중간 처리 스트림은 동작하지 않는다.
즉, 위 코드에서 average() 이하를 생략하면 stream(), mapToInt()는 동작하지 않는다.</p>
<h1 id="리소스로부터-스트림-얻기">리소스로부터 스트림 얻기</h1>
<blockquote>
<p>java.util.stream 패키지에는 스트림 인터페이스들이 있다. BaseStream 인터페이스를 부모로 한 자식 인터페이스들은 다음과 같은 상속 관계를 이루고 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/36037b24-2b21-442b-92cb-07f570e1389c/image.png" alt=""></p>
<p>이 스트림 인터페이스들의 구현 객체는 다양한 리소스로부터 얻을 수 있다.
주로 컬렉션과 배열에서 얻지만, 다음과 같은 리소스로부터 스트림 구현 객체를 얻을 수도 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/bae003b8-04ac-4f90-bfd1-e496e9a97da0/image.png" alt=""></p>
<h2 id="컬렉션으로부터-스트림-얻기">컬렉션으로부터 스트림 얻기</h2>
<p>java.util.Collection 인터페이스는 스트림과 parallelStream() 메소드를 가지고 있기 때문에 자식 인터페이스인 List와 Set 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.
다음 예제는 List&lt;Product&gt; 컬렉션에서 Product 스트림을 얻는 방법을 보여준다.</p>
<pre><code class="language-java">package ch17.sec04.exam01;

public class Product {
    private final int pno;
    private final String name;
    private final String company;
    private final int price;

    public Product(int pno, String name, String company, int price) {
        this.pno = pno;
        this.name = name;
        this.company = company;
        this.price = price;
    }

    public int getPno() {
        return pno;
    }

    public String getName() {
        return name;
    }

    public String getCompany() {
        return company;
    }

    public int getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return &quot;{&quot; +
                &quot;pno:&quot; + pno + &quot;, &quot; +
                &quot;name:&quot; + name + &quot;, &quot; +
                &quot;company:&quot; + company + &quot;, &quot; +
                &quot;price:&quot; + price +
                &quot;}&quot;;
    }
}</code></pre>
<pre><code class="language-java">package ch17.sec04.exam01;

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

public class StreamExample {
    public static void main(String[] args) {
        // List 컬렉션 생성
        List&lt;Product&gt; list = new ArrayList&lt;&gt;();
        for (int i = 1; i &lt;= 5; i++) {
            Product product = new Product(i, &quot;상품&quot; + i, &quot;멋진회사&quot;, (int) (10000 * Math.random()));
            list.add(product);
        }

        // 객체 스트림 얻기
        Stream&lt;Product&gt; stream = list.stream();
        stream.forEach(p -&gt; System.out.println(p));
    }
}</code></pre>
<pre><code>{pno:1, name:상품1, company:멋진회사, price:6784}
{pno:2, name:상품2, company:멋진회사, price:3615}
{pno:3, name:상품3, company:멋진회사, price:7539}
{pno:4, name:상품4, company:멋진회사, price:5986}
{pno:5, name:상품5, company:멋진회사, price:3718}</code></pre><h2 id="배열로부터-스트림-얻기">배열로부터 스트림 얻기</h2>
<p>java.util.Arrays 클래스를 이용하면 다양한 종류의 배열로부터 스트림을 얻을 수 있다.
다음은 문자열 배열과 ㅈ어수 배열로부터 스트림을 얻는 방법을 보여준다.</p>
<pre><code class="language-java">package ch17.sec04.exam02;

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

public class StreamExample {
    public static void main(String[] args) {
        String[] strArray = {&quot;홍길동&quot;, &quot;신용권&quot;, &quot;김미나&quot;};
        Stream&lt;String&gt; strStream = Arrays.stream(strArray);
        strStream.forEach(item -&gt; System.out.print(item + &quot;,&quot;));
        System.out.println();

        int[] intArray = {1, 2, 3, 4, 5};
        IntStream intStream = Arrays.stream(intArray);
        intStream.forEach(item -&gt; System.out.print(item + &quot;,&quot;));
        System.out.println();
    }
}</code></pre>
<pre><code>홍길동,신용권,김미나,
1,2,3,4,5,</code></pre><h2 id="숫자-범위로부터-스트림-얻기">숫자 범위로부터 스트림 얻기</h2>
<p>IntStream 또는 LongStream의 정적 메소드인 range()와 rangeClosed() 메소드를 이용하면 특정 범위의 정수 스트림을 얻을 수 있다.
첫 번째 매개값은 시작 수이고 두 번째 매개값은 끝 수인데, 끝 수를 포함하지 않으면 range(), 포함하면 rangeClose()를 사용한다.</p>
<pre><code class="language-java">package ch17.sec04.exam03;

import java.util.stream.IntStream;

public class StreamExample {
    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>
<pre><code>총합: 5050</code></pre><h2 id="파일로부터-스트림-얻기">파일로부터 스트림 얻기</h2>
<p>java.nio.file.Files의 lines() 메소드를 이용하면 텍스트 파일의 행 단위 스트림을 얻을 수 있다.
이는 텍스트 파일에서 한 행씩 읽고 처리할 때 유용하게 사용할 수 있다.
다음 data.txt 파일은 한 행에 하나의 상품에 대한 정보를 담고 있다.</p>
<pre><code>{&quot;pno&quot;:1, &quot;name&quot;:&quot;상품1&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:1558}
{&quot;pno&quot;:2, &quot;name&quot;:&quot;상품2&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:4671}
{&quot;pno&quot;:3, &quot;name&quot;:&quot;상품3&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:470}
{&quot;pno&quot;:4, &quot;name&quot;:&quot;상품4&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:9584}
{&quot;pno&quot;:5, &quot;name&quot;:&quot;상품5&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:6868}</code></pre><pre><code class="language-java">package ch17.sec04.exam04;

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 StreamExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get(StreamExample.class.getResource(&quot;data.txt&quot;).toURI());
        Stream&lt;String&gt; stream = Files.lines(path, Charset.defaultCharset());
        stream.forEach(line -&gt; System.out.println(line));
        stream.close();
    }
}</code></pre>
<pre><code>{&quot;pno&quot;:1, &quot;name&quot;:&quot;상품1&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:1558}
{&quot;pno&quot;:2, &quot;name&quot;:&quot;상품2&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:4671}
{&quot;pno&quot;:3, &quot;name&quot;:&quot;상품3&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:470}
{&quot;pno&quot;:4, &quot;name&quot;:&quot;상품4&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:9584}
{&quot;pno&quot;:5, &quot;name&quot;:&quot;상품5&quot;, &quot;company&quot;:&quot;멋진회사&quot;, &quot;price&quot;:6868}</code></pre><h1 id="요소-걸러내기필터링">요소 걸러내기(필터링)</h1>
<blockquote>
<p>필터링은 요소를 걸러내는 중간 처리 기능이다. 필터링 메소드에는 다음과 같이 disctinct()와 filter()가 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/ec937c38-a9ed-44b0-a47c-faf6fa19fa03/image.png" alt="">
distinct() 메소드는 요소의 중복을 제거한다. 객체 스트림(Stream)일 경우, equals() 메소드의 리턴값이 true이면 동일한 요소로 판단한다.
IntStream, LongStream, DoubleStream은 같은 값일 경우 중복을 제거한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/a88f4fbe-fe25-4c02-8d45-a78040496a85/image.png" alt="">
filter() 메소드는 매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/eaff2332-964a-4d0b-a1a1-43158ad57dc5/image.png" alt="">
Predicate는 함수형 인터페이스로, 다음과 같은 종류가 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e5c533c0-4cd9-49f0-a072-5c2422ef9945/image.png" alt="">
모든 Predicate는 매개값을 조사한 후 boolean을 리턴하는 test() 메소드를 가지고 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/ebe71dc5-5f5b-4054-bbec-462f99589323/image.png" alt="">
Predicat&lt;T&gt;를 람다식으로 표현하면 다음과 같다.</p>
<pre><code class="language-java">T -&gt; { ... return true; }
또는
T -&gt; true; // return 문만 있을 경우 중괄호와 return 키워드 생략 가능</code></pre>
<p>예제를 보자.</p>
<pre><code class="language-java">package ch17.sec05;

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

public class FilteringExample {
    public static void main(String[] args) {
        // List 컬렉션 생성
        List&lt;String&gt; list = new ArrayList&lt;&gt;();
        list.add(&quot;홍길동&quot;);
        list.add(&quot;신용권&quot;);
        list.add(&quot;감자바&quot;);
        list.add(&quot;신용권&quot;);
        list.add(&quot;신민철&quot;);

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

        // 신으로 시작하는 요소만 필터링
        list.stream()
                .filter(n -&gt; n.startsWith(&quot;신&quot;))
                .forEach(n -&gt; System.out.println(n));
        System.out.println();

        // 중복 요소를 먼저 제거하고, 신으로 시작하는 요소만 필터링
        list.stream()
                .distinct()
                .filter(n -&gt; n.startsWith(&quot;신&quot;))
                .forEach(n -&gt; System.out.println(n));
    }
}</code></pre>
<pre><code>홍길동
신용권
감자바
신민철

신용권
신용권
신민철

신용권
신민철</code></pre><h1 id="요소-변환매핑">요소 변환(매핑)</h1>
<blockquote>
<p>매핑(Mapping)은 스트림의 요소를 다른 요소로 변환하는 중간 처리 기능이다.
매핑 메소드는 mapXxx(), asDoubleStream(), asLongStream(), boxed(), flatMapXxx() 등이 있다.</p>
</blockquote>
<h2 id="요소를-다른-요소로-변환">요소를 다른 요소로 변환</h2>
<p>mapXxx() 메소드는 요소를 다른 요소로 변환한 새로운 스트림을 리턴한다.
다음 그림처럼 원래 스트림의 A 요소는 C 요소로, B 요소는 D 요소로 변환해서 C, D 요소를 가지는 새로운 스트림이 생성된다.</p>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/0ff2eec9-4f36-4805-b762-64675d034874/image.png" alt="">
mapXxx() 메소드의 종류는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/997f9f3b-9d67-4f62-8b05-5210a8513f89/image.png" alt="">
매개타입인 Function은 함수형 인터페이스로, 다음과 같은 종류가 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/6b4f6ed8-5377-44db-81d0-70fe3ca32111/image.png" alt="">
모든 Function은 매개값을 리턴값으로 매핑(변환)하는 applyXxx() 메소드를 가지고 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/d13639fa-9a21-4b2e-a68b-0a9a09d556b7/image.jpeg" alt="">
Function&lt;T,R&gt;을 람다식으로 표현하면 다음과 같다.</p>
<pre><code class="language-java">T -&gt; { ... return R; }
또는
T -&gt; R; // return 문만 있을 경우 중괄호와 return 키워드 생략 가능</code></pre>
<p>기본 타입 간의 변환이거나 기본 타입 요소를 래퍼(Wrapper) 객체 요소로 변환하려면 다음과 같은 간편화 메소드를 사용할 수도 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/d3ba4309-5690-47d0-a401-7cfc2fe396c5/image.png" alt=""></p>
<h2 id="요소를-복수-개의-요소로-변환">요소를 복수 개의 요소로 변환</h2>
<p>flatMapXxx() 메소드는 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림을 리턴한다.
다음 그림처럼 원래 스트림의 A 요소를 A1, A2 요소로 변환하고 B 요소를 B1, B2로 변환하면 A1, A2, B1, B2 요소를 가지는 새로운 스트림이 생성된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e6a37ebf-c465-44d1-a87b-c08b05dcbe5b/image.png" alt="">
flatMap() 메소드의 종류는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/09c1e50a-cf2c-447a-af1a-a30827038615/image.png" alt=""></p>
<h1 id="요소-정렬">요소 정렬</h1>
<blockquote>
<p>정렬은 요소를 오름차순 또는 내림차순으로 정렬하는 중간 처리 기능이다.</p>
</blockquote>
<p>요소를 정렬하는 메소드는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f6d91c90-1e4e-4ae6-a6d2-2386506f5db4/image.png" alt=""></p>
<h2 id="comparable-구현-객체의-정렬">Comparable 구현 객체의 정렬</h2>
<p>스트림의 요소가 객체일 경우 Comparable을 구현하고 있어야먄 sorted() 메소드를 사용하여 정렬할 수 있다.
그렇지 않다면 ClassCastException이 발생한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/a2b52bae-ecfd-4d9a-af04-8f9ec3d956ba/image.png" alt="">
만약 내림차순으로 정렬하고 싶다면 다음과 같이 Comparator.reverseOrder() 메소드가 리턴하는 Comparator를 매개값으로 제공하면 된다.</p>
<pre><code class="language-java">Stream&lt;Xxx&gt; reverseOrderedStream = stream.sorted(Comparator.reverseOrder());</code></pre>
<h2 id="comparator를-이용한-정렬">Comparator를 이용한 정렬</h2>
<p>요소 객체가 Comparable을 구현하고 있지 않다면, 비교자를 제공하면 요소를 정렬시킬 수 있다.
비교자는 Comparator 인터페이스를 구현한 객체를 말하는데, 명시적인 클래스로 구현할 수도 있지만 다음과 같이 간단하게 람다식으로 작성할 수도 있다.</p>
<pre><code class="language-java">sorted((o1, 02) -&gt; { ... })</code></pre>
<p>중괄호 안에는 o1이 o2보다 작으면 음수, 같으면 0, 크면 양수를 리턴하도록 작성하면 된다.
o1과 o2가 정수일 경우에는 Integer.compare(o1, o2)를, 실수일 경우에는 Double.compare(o1, o2)를 호출해서 리턴값을 리턴해도 좋다.</p>
<h1 id="요소를-하나씩-처리루핑">요소를 하나씩 처리(루핑)</h1>
<blockquote>
<p>루핑(Looping)은 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것을 말한다.</p>
</blockquote>
<p>루핑 메소드에는 peek()과 forEach()가 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5e752aa3-7d90-4d7f-a08a-0d67d2db7e4c/image.png" alt="">
peek()과 forEach()는 동일하게 요소를 루핑하지만 peek()은 중간 처리 메소드이고, forEach()는 최종 처리 메소드이다.</p>
<p>따라서 peek()은 최종 처리가 뒤에 붙지 않으면 동작하지 않는다.
매개타입인 Consumer는 함수형 인터페이스로, 다음과 같은 종류가 있다.</p>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c9337694-d2d1-4144-ada3-0b1afcd731e0/image.png" alt="">
모든 Consumer는 매개값을 처리(소비)하는 accept() 메소드를 가지고 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/d7377a79-fc51-49b6-8e02-9c23b541be47/image.png" alt="">
<code>Consumer&lt;? super T&gt;</code>를 람다식으로 표현하면 다음과 같다.</p>
<pre><code class="language-java">T -&gt; { ... }
또는
T -&gt; 실행문; // 하나의 실행문만 있을 경우 중괄호 생략</code></pre>
<h1 id="요소-조건-만족-여부매칭">요소 조건 만족 여부(매칭)</h1>
<blockquote>
<p>매칭은 요소들이 특정 조건에 만족하는지 여부를 조사하는 최종 처리 기능이다.</p>
</blockquote>
<p>매칭과 관련된 메소드는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/949b957d-07bd-44fd-99e8-29ba53a24678/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c0b1bc72-d637-411a-a38b-e36c7d5ab6f8/image.png" alt="">
allMatch(), anyMatch(), noneMatch() 메소드는 매개값으로 주어진 Predicate가 리턴하는 값에 따라 true 또는 false를 리턴한다.</p>
<h1 id="요소-기본-집계">요소 기본 집계</h1>
<blockquote>
<p>집계(Aggregate)는 최종 처리 기능으로 요소들을 처리해서 카운팅, 합계, 평균값, 최대값, 최소값 등과 같이 하나의 값으로 산출하는 것을 말한다.</p>
</blockquote>
<p>즉, 대략의 데이터를 가공해서 하나의 값으로 축소하는 리덕션(Reduction)이라고 볼 수 있다.</p>
<h2 id="스트림이-제공하는-기본-집계">스트림이 제공하는 기본 집계</h2>
<p>스트림은 카운팅, 최대, 최소, 평균, 합계 등을 처리하는 다음과 같은 최종 처리 메소드를 제공한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/325343d2-bb4e-4ed3-b4ca-190afd854881/image.png" alt="">
집계 메소드가 리턴하는 OptionalXxx는 Optional, OptionalDouble, OptionalInt, OptionalLong 클래스를 말한다.</p>
<p>이들은최종값을 저장하는 객체로 get(), getAsDouble(), getAsInt(), getAsLong()을 호출하면 최종값을 얻을 수 있다.</p>
<h2 id="optional-클래스">Optional 클래스</h2>
<p>Optional, OptionalDouble, OptionalInt, OptionalLong 클래스는 단순하게 집계값만 저장하는 것이 아니라, 집계값이 존재하지 않을 경우 디폴트 값을 설정하거는 집계값을 처리하는 Consumer를 등록할 수 있다.</p>
<p>다음은 Optional 클래스가 제공하는 메소드이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/337f4c3e-c5ca-451c-b39a-68417bd8d094/image.png" alt=""></p>
<h1 id="요소-커스텀-집계">요소 커스텀 집계</h1>
<blockquote>
<p>스트림은 기본 집계 메소드인 sum(), average(), count(), max(), min()을 제공하지만, 다양한 집계 결과물을 만들 수 있도록 reduce() 메소드도 제공한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/0ed11161-9cc9-481a-86fd-cd459b75cc1e/image.png" alt="">
매개값인 BinaryOperator는 함수형 인터페이스이다.
BinaryOperator는 두 개의 매개값을 받아 하나의 값을 리턴하는 apply() 메소드를 가지고 있기 때문에 다음과 같이 람다식을 작성할 수 있다.</p>
<pre><code class="language-java">(a, b) -&gt; { ... return 값; }
또는
(a, b) -&gt; 값 // return 문만 있을 경우 중괄호와 return 키워드 생략 가능</code></pre>
<p>reduce()는 스트림에 요소가 없을 경우 예외가 발생하지만, identity 매개값이 주어지만 이 값을 디폴트 값으로 리턴한다.</p>
<p>다음 코드는 스트림에 요소가 없을 경우 NoSuchElementException을 발생시킨다.</p>
<pre><code class="language-java">int sum = stream
    .reduce((a, b) -&gt; a + b)
    .getAsInt();</code></pre>
<p>하지만 다음과 같이 작성할 경우 디폴트 값(identity)인 0을 리턴한다.</p>
<pre><code class="language-java">int sum = stream
    .reduce(0, (a, b) -&gt; a + b)</code></pre>
<h1 id="요소-수집">요소 수집</h1>
<blockquote>
<p>스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공한다.</p>
</blockquote>
<p>이 메소드를 이용하면 필요한 요소만 컬렉션에 담을 수 있고, 요소들을 그룹핑한 후에 집계도 할 수 있다.</p>
<h2 id="필터링한-요소-수집">필터링한 요소 수집</h2>
<p>Stream의 collect(Collector&lt;T, A, R&gt; collector) 메소드는 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고, 이 컬렉션을 리턴한다.
매개값인 Collector는 어떤 요소를 어떤 컬렉션에 수집할 것인지를 결정한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/ee7169e4-6008-4772-8727-293b55ab2faa/image.png" alt="">
타입 파라미터 T는 요소, A는 누적기(Accumlator) 그리고 R은 요소가 저장될 컬렉션이다.</p>
<p>Collector의 구현 객체는 다음과 같이 Collectors 클래스의 정적 메소드로 얻을 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/4a668246-a752-4bcc-981c-514183838eb2/image.png" alt=""></p>
<p>리턴값인 Collector를 보면 A(누적기)가 ?로 되어 있는데, 이것은 Collector가 List, Set, Map 컬렉션에 요소를 저장하는 방법을 알고 있어 별도의 누적기가 필요 없기 때문이다.</p>
<p>예제를 보자.</p>
<pre><code class="language-java">List&lt;Student&gt; maleList = totalList.stream()
    .filter(s -&gt; s.getSex().equals(&quot;남&quot;)) // 남학생만 필터링
    .collect(Collectors.toList());</code></pre>
<pre><code class="language-java">Map&lt;String, Integer&gt; map = totalList.stream()
    .collect(
        Collectors.toMap(
            s -&gt; s.getName(), // Student 객체에서 키가 될 부분 리턴
            s -&gt; s.getScore(), // Student 객체에서 값이 될 부분 리턴
        )
    );</code></pre>
<p>Java 16부터는 좀 더 편리하게 요소 스트림에서 List 컬렉션을 얻을 수 있다.
스트림에서 바로 toList() 메소드를 다음과 같이 사용하면 된다.</p>
<pre><code class="language-java">List&lt;Student&gt; maleList = totalList.stream()
    .filters(s -&gt; s.getSex().equlas(&quot;남&quot;))
    .toList();</code></pre>
<h2 id="요소-그룹핑">요소 그룹핑</h2>
<p>collect() 메소드는 단순히 요소를 수집하는 기능 이외에 컬렉션의 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공한다.</p>
<p>Collectors.groupingBy() 메소드에서 얻은 Collector를 collect() 메소드를 호출할 때 제공하면 된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f7f8ce45-b03d-4e04-93e8-a7022dcd0aa5/image.png" alt="">
groupingBy()는 Function을 이용해서 T를 K로 매핑하고, K를 키로 해 List&lt;T&gt;를 값으로 갖는 Map 컬렉션을 생성한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/d41da7d4-9f65-4e2d-84a5-4f866f93a4a4/image.png" alt="">
다음은 &quot;남&quot;, &quot;여&quot;를 키로 설정하고 List&lt;Stduent&gt;를 값으로 갖는 Map을 생성하는 코드이다.</p>
<pre><code class="language-java">Map&lt;String, List&lt;Student&gt;&gt; map = totalList.stream()
    .collect(
        Collectors.groupingBy(s -&gt; s.getSex()) // 그룹핑 키 리턴
    );</code></pre>
<p>Collectors.groupingBy() 메소드는 그룹핑 후 매핑 및 집계(평균, 카운팅, 연결, 최대, 최소, 합계)를 수행할 수 있도록 두 번째 매개값인 Collector를 가질 수 있다.
다음은 두 번째 매개값으로 사용될 Collector을 얻을 수 있는 Collectors의 정적 메소드들이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/41f732b2-bc40-4d8c-9b19-6d56572157d1/image.png" alt=""></p>
<h1 id="요소-병렬-처리">요소 병렬 처리</h1>
<blockquote>
<p>요소 병렬 처리(Parallel Operation)란 멀티 코어 CPU 환경에서 전체 요소를 분할해서 각각의 코어가 병렬적으로 처리하는 것을 말한다.</p>
</blockquote>
<p>요소 병렬 처리의 목적은 작업 처리 시간을 줄이는 것에 있다.
자바는 요소 병렬 처리를 위해 병렬 스트림을 제공한다.</p>
<h2 id="동시성과-병렬성">동시성과 병렬성</h2>
<p>멀티 스레드는 동시성(Concurrency) 또는 병렬성(Parallelism)으로 실행되기 때문에 이들 용어에 대해 정확히 이해하는 것이 좋다.</p>
<p>동시성은 멀티 작업을 위해 멀티 스레드가 하나의 코어에서 번갈아 가며 실행하는 것을 말하고,
병렬성은 멀티 작업을 위해 멀티 코어를 각각 이용해서 병렬로 실행하는 것을 말한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e9d641f3-3966-493a-b4ee-20dfa2c3bee3/image.png" alt="">
동시성은 한 시점에 하나의 작업만 실행한다. 번갈아 작업을 실행하는 것이 워낙 빠르다보니 동시에 처리되는 것처럼 보일 뿐이다.
병렬성은 한 시점에 여러 개의 작업을 병렬로 실행하기 때문에 동시성보다는 좋은 성능을 낸다.</p>
<p>병렬성은 데이터 병렬성(Data Parallelism)과 작업 병렬성(Task Parallelism)으로 구분할 수 있다.</p>
<h3 id="데이터-병렬성">데이터 병렬성</h3>
<p>데이터 병렬성은 전체 데이터를 분할해서 서브 데이터셋으로 만들고 이 서브 데이터셋들을 병렬 처리해서 작업을 빨리 끝내는 것을 말한다.
자바 병렬 스트림은 데이터 병렬성을 구현한 것이다.</p>
<h3 id="작업-병렬성">작업 병렬성</h3>
<p>작업 병렬성은 서로 다른 작업을 병렬 처리하는 것을 말한다. 작업 병렬성의 대표적인 예는 서버 프로그램이다.
서버는 각각의 클라이언트에서 요청한 내용을 개별 스레드에서 병렬로 처리한다.</p>
<h2 id="포크조인-프레임워크">포크조인 프레임워크</h2>
<p>자바 병렬 스트림은 요소들을 병렬 처리하기 위해 포크조인 프레임워크(Forkjoin Framework)를 사용한다.
포크조인 프레임워크는 포크 단계에서 전체 요소들을 서브 요소셋으로 분할하고, 각각의 서브 요소셋을 멀티코어에서 병렬로 처리한다.</p>
<p>조인 단계에서는 서브 결과를 결합해서 최종 결과를 만들어낸다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b91d5fc3-fc5a-4146-bb12-f48189444ba1/image.png" alt="">
병렬 처리 스트림은 포크 단계에서 요소를 순서대로 분할하지 않는다.
이해하기 쉽도록 위 그림에서는 차례대로 4등분 했지만, 내부적으로 요소들을 나누는 알고리즘이 있기 때문에 개발자는 신경 쓸 필요가 없다.</p>
<p>포크조인 프레임워크는 병럴 처리를 위해 스레드풀을 사용한다.
각각의 코어에서 서브 요소셋을 처리하는 것은 작업 스레드가 해야 하므로 스레드 관리가 필요하다.
포크조인 프레임워크는 ExecutorService의 구현 객체인 ForkJoinPool을 사용해서 작업 스레드를 관리한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e0ad93c2-e566-439d-b19d-2ce6b3ba810b/image.png" alt=""></p>
<h2 id="병렬-스트림-사용">병렬 스트림 사용</h2>
<p>자바 병렬 스트림을 이용할 경우에는 백그라운드에서 포크조인 프레임워크가 사용되기 때문에 개발자는 매우 쉽게 병렬 처리를 할 수 있다.
병렬 스트림은 다음 두 가지 메소드로 얻을 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/d114acef-45f6-4769-9e01-87bdc685c1b4/image.png" alt="">
parallelStream() 메소드는 컬렉션(List, Set)으로부터 병렬 스트림을 바로 리턴한다.
parallel() 메소드는 기존 스트림을 병렬 처리 스트림으로 변환한다.</p>
<h2 id="병렬-처리-성능">병렬 처리 성능</h2>
<p>스트림 병렬 처리가 스트림 순차 처리보다 항상 실행 성능이 좋다고 판단해서는 안 된다.
그 전에 먼저 병렬 처리에 영향을 미치는 다음 3가지 요인을 잘 살펴보아야 한다.</p>
<h3 id="요소의-수와-요소당-처리-시간">요소의 수와 요소당 처리 시간</h3>
<p>컬렉션에 전체 요소의 수가 적고 요소당 처리 시간이 짧으면 일반 스트림이 병렬 스트림보다 빠를 수 있다.
병렬 처리는 포크 및 조인 단계가 있고, 스레드 풀을 생성하는 추가적인 비용이 발생하기 때문이다.</p>
<h3 id="스트림-소스의-종류">스트림 소스의 종류</h3>
<p>ArrayList와 배열은 인덱스로 요소를 관리하기 때문에 포크 단계에서 요소를 쉽게 분리할 수 있어 병렬 처리 시간이 절약된다.
반면에 HashSet, TreeSet은 요소 분리가 쉽지 않고, LinkedList 역시 링크를 따라가야 하므로 요소 분리가 쉽지 않다. 따라서 이 소스들은 상대적으로 병렬 처리가 늦다.</p>
<h3 id="코어core의-수">코어(Core)의 수</h3>
<p>CPU 코어의 수가 많으면 많을수록 병렬 스트림의 성능은 좋아진다. 하지만 코어의 수가 적을 경우에는 일반 스트림이 더 빠를 수 있다. 병렬 스트림은 스레드 수가 증가하여 동시성이 많이 일어나므로 오히려 느려진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 16 - 람다식 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-16-%EB%9E%8C%EB%8B%A4%EC%8B%9D-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-16-%EB%9E%8C%EB%8B%A4%EC%8B%9D-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 26 Jan 2023 23:20:35 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-16---람다식-확인문제">Chapter 16 - 람다식 확인문제</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/9ac1d88c-4b5a-4215-b265-015aeed32e32/image.png" alt=""></p>
<h2 id="정답-4">정답: 4</h2>
<p>@FunctionalInterface를 붙이는 것은 선택사항이며 컴파일 과정에서 추상 메소드가 하나인지 검사하기 때문에 정확한 함수형 인터페이스를 작성할 수 있게 도와주는 역할을 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/2dfeb09f-83a5-4e5a-ba98-c2a885b4a428/image.png" alt=""></p>
<h2 id="정답-4-1">정답: 4</h2>
<p>디폴트 생성자만 호출되는 것은 아니다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/0430b413-3257-4ed3-97fd-b7c2637b9c61/image.png" alt=""></p>
<h2 id="정답-2">정답: 2</h2>
<pre><code class="language-java">(a, b) -&gt; a * b</code></pre>
<p>와 같이 작성해야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c191efae-1bb0-40cc-a6d6-7037dd149f69/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/41f9e34f-9175-49b0-b8c6-470abc1065b9/image.png" alt=""></p>
<h2 id="정답">정답:</h2>
<pre><code class="language-java">public class Example {
    public static void main(String[] args) {
        Thread thread = new Thread(() -&gt; {
            for (int i = 0; i &lt; 3; i++) {
                System.out.println(&quot;작업 스레드가 실행됩니다.&quot;);
            }
        });
        thread.start();
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/197d0bd0-b307-40f3-b1e4-d83d739e3df4/image.png" alt=""></p>
<h2 id="정답-1">정답:</h2>
<pre><code class="language-java">(() -&gt; {System.out.println(&quot;Ok 버튼을 클릭했습니다.&quot;);})</code></pre>
<pre><code class="language-java">(() -&gt; {System.out.println(&quot;Cancel 버튼을 클릭했습니다.&quot;);})</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/79483379-bca8-4fab-bf69-ce2bfe5d29f9/image.png" alt=""></p>
<h2 id="정답-3">정답:</h2>
<pre><code class="language-java">@FunctionalInterface
public interface Function {
    public double apply(double x, double y);
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b5853b4d-3c57-4f34-bf0b-b17d5fe3ef8b/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/2bf159cf-e302-40fb-963b-6d0b001d7bd9/image.png" alt=""></p>
<h2 id="정답-5">정답:</h2>
<p>방법 1</p>
<pre><code class="language-java">// 최대값 얻기
int max = maxOrMin((x, y) -&gt; (x &gt;= y) ? x : y);
System.out.println(&quot;최대값: &quot; + max);

// 최소값 얻기
int min = maxOrMin((x, y) -&gt; (x &lt;= y) ? x : y);
System.out.println(&quot;최소값: &quot; + min);</code></pre>
<p>방법 2</p>
<pre><code class="language-java">// 최대값 얻기
int max = maxOrMin((x, y) -&gt; Math.max(x, y));
System.out.println(&quot;최대값: &quot; + max);

// 최소값 얻기
int min = maxOrMin((x, y) -&gt; Math.min(x, y));
System.out.println(&quot;최소값: &quot; + min);</code></pre>
<p>방법 3(Best way)</p>
<pre><code class="language-java">// 최대값 얻기
int max = maxOrMin(Math::max);
System.out.println(&quot;최대값: &quot; + max);

// 최소값 얻기
int min = maxOrMin(Math::min);
System.out.println(&quot;최소값: &quot; + min);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f1113c71-2058-4a9e-a8ec-c0980ed444b2/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/51071682-98b9-48e8-bc79-c6b5ad2ff24d/image.png" alt=""></p>
<h2 id="정답-6">정답:</h2>
<pre><code class="language-java">public static double avg(Function&lt;Student&gt; function) {
    int sum = 0;

    for (Student student : students) {
        sum += function.apply(student);
    }

    return (double) sum / students.length;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b9f83518-416b-4bad-a624-0a5b584382e1/image.png" alt=""></p>
<h2 id="정답-7">정답:</h2>
<pre><code class="language-java">Student::getEnglishScore</code></pre>
<pre><code class="language-java">Student::getMathScore</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 16 - 람다식]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-16-%EB%9E%8C%EB%8B%A4%EC%8B%9D</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-16-%EB%9E%8C%EB%8B%A4%EC%8B%9D</guid>
            <pubDate>Thu, 26 Jan 2023 22:48:36 GMT</pubDate>
            <description><![CDATA[<h1 id="람다식이란">람다식이란?</h1>
<blockquote>
<p>자바는 함수형 프로그래밍을 위해 Java8부터 람다식(Lambda Expressions)을 지원한다. 람다식은 데이터 처리부에 제공되는 함수 역할을 하는 매개변수를 가진 중괄호 블록이다. 데이터 처리부는 람다식을 받아 매개변수에 데이터를 대입하고 중괄호를 실행시켜 처리한다.</p>
</blockquote>
<p>함수형 프로그래밍(Functional Programming)이란 함수를 정의하고 이 함수를 데이터 처리부로 보내 데이터를 처리하는 기법을 말한다. 데이터 처리부는 데이터만 가지고 있을 뿐, 처리 방법이 정해져 있지 않아 외부에서 제공된 함수에 의존한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/dd0a14b5-50f3-409b-b6df-e8ec6355b2f2/image.png" alt=""></p>
<pre><code class="language-java">람다식: (매개변수, ...) -&gt; { 처리 내용 }</code></pre>
<p>자바는 람다식을 <a href="https://velog.io/@dev-taewon-kim/Chapter-09-%EC%A4%91%EC%B2%A9-%EC%84%A0%EC%96%B8%EA%B3%BC-%EC%9D%B5%EB%AA%85-%EA%B0%9D%EC%B2%B4#%EC%9D%B5%EB%AA%85-%EA%B5%AC%ED%98%84-%EA%B0%9D%EC%B2%B4">익명 구현 객체</a>로 변환한다.다음과 같이 Calculable 인터페이스가 있다고 가정해보자.</p>
<pre><code class="language-java">public interface Calculable {
    // 추상 메소드
    void calculate(int x, int y);
}</code></pre>
<p>Calculable 인터페이스의 익명 구현 객체는 다음과 같이 생성할 수 있다.</p>
<pre><code class="language-java">new Calculable() {
    @Override
    public void calculate(int x, int y) { 처리 내용 }
}</code></pre>
<p>이것을 람다식으로 표현하면 다음과 같다. 추상 메소드인 calculate()는 두 개의 매개변수를 가지므로 (x, y)로 표현되었고, 화살표 뒤에 calculate()의 실행 블록이 온다.</p>
<pre><code class="language-java">(x, y) -&gt; { 처리 내용 };</code></pre>
<p>람다식은 인터페이스의 익명 구현 객체이므로 인터페이스 타입의 매개변수에 대입될 수 있다.
예를 들어 다음과 같이 Calculable 매개변수를 가지고 있는 action() 메소드가 있다고 가정해보자.</p>
<pre><code class="language-java">public void action(Calculabe calculable) {
    int x = 10;
    int y = 4;
    caculable.calculate(x, y);  // 데이터를 제공하고 추상 메소드를 호출
}</code></pre>
<p>action() 메소드를 호출할 때 매개값으로 다음과 같이 람다식을 제공할 수 있다.
action() 메소드에서 calculable.calculate(x, y)를 실행하면 람다식의 중괄호 블록이 실행되면서 데이터가 처리된다.
여기서 action() 메소드는 제공된 람다식을 이용해서 내부 데이터를 처리하는 처리부 역할을 한다.</p>
<pre><code class="language-java">action( (x, y) -&gt; {
    int result = x + y;
    System.out.println(result);
});</code></pre>
<p>다음 그림과 같이 람다식1과 2 중에서 어떤 람다식을 매개값으로 제공하느냐에 따라 계산 결과는 달라질 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/4ab4c4ad-f67f-4f2a-8ce0-7760547cb7b0/image.png" alt="">
인터페이스의 익명 구현 객체를 람다식으로 표현하려면 인터펭,스가 단 하나의 추상 메소드만 가져야 한다.</p>
<p><strong>인터페이스가 단 하나의 추상 메소드를 가질 때, 이를 함수영 인터페이스(Functional Interface)라고 한다.</strong></p>
<p>인터페이스가 함수형 인터페이스임을 보장하기 위해서는 <code>@FunctionalInterface</code> 어노테이션을 붙이면 된다.
<code>@FunctionalInterface</code>를 붙이는 것은 선택사항이지만, 컴파일 과정에서 추상 메소드가 하나인지 검사하기 때문에 정확한 함수형 인터페이스를 작성할 수 있게 도와주는 역할을 한다.</p>
<h1 id="매개변수가-없는-람다식">매개변수가 없는 람다식</h1>
<blockquote>
<p>함수형 인터페이스의 추상 메소드에 매개변수가 없을 경우 람다식은 다음과 같이 작성할 수 있다.
실행문이 두 개 이상일 경우에는 중괄호를 생략할 수 없고, 하나일 경우에만 생략할 수 있다.</p>
</blockquote>
<pre><code class="language-java">() -&gt; {
    실행문;
    실행문;
}</code></pre>
<pre><code class="language-java">() -&gt; 실행문</code></pre>
<h1 id="매개변수가-있는-람다식">매개변수가 있는 람다식</h1>
<blockquote>
<p>함수영 인터페이스의 추상 메소드에 매개변수가 잇을 경우 람다식은 다음과 같이 작성할 수 있다.
매개변수를 선언할 때 타입은 생략할 수 있고, 구체적인 타입 대신에 var를 사용할 수도 있다.
하지만 타입을 생략하고 작성하는 것이 일반적이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/1b56d570-17d2-4fb8-8c2d-6cff9fabaeda/image.png" alt="">
매개변수가 하나일 경우에는 괄호를 생략할 수도 있다. 이때는 타입 또는 var를 붙일 수 없다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/67b7737f-d370-46d4-90e8-a8a01fb5b6f3/image.png" alt="">
예제를 보자</p>
<pre><code class="language-java">package ch16.sec03;

@FunctionalInterface
public interface Workable {
    void work(String name, String job);
}</code></pre>
<pre><code class="language-java">package ch16.sec03;

@FunctionalInterface
public interface Speakable {
    void speak(String content);
}</code></pre>
<pre><code class="language-java">package ch16.sec03;

public class Person {
    public void action1(Workable workable) {
        workable.work(&quot;홍길동&quot;, &quot;프로그래밍&quot;);
    }

    public void action2(Speakable speakable) {
        speakable.speak(&quot;안녕하세요&quot;);
    }
}</code></pre>
<pre><code class="language-java">package ch16.sec03;

public class LambdaExample {
    public static void main(String[] args) {
        Person person = new Person();

        // 매개변수가 두 개일 경우
        person.action1((name, job) -&gt; {
            System.out.print(name + &quot;이 &quot;);
            System.out.println(job + &quot;을 합니다.&quot;);
        });
        person.action1((name, job) -&gt; System.out.println(name + &quot;이 &quot; + job + &quot;을 하지 않습니다.&quot;));

        // 매개변수가 한 개일 경우
        person.action2(word -&gt; {
            System.out.print(&quot;\&quot;&quot; + word + &quot;\&quot;&quot;);
            System.out.println(&quot;라고 말합니다.&quot;);
        });
        person.action2(word -&gt; System.out.println(&quot;\&quot;&quot; + word + &quot;\&quot;라고 외칩니다.&quot;));
    }
}</code></pre>
<h1 id="리턴값이-있는-람다식">리턴값이 있는 람다식</h1>
<blockquote>
<p>함수형 인터페이스의 추상 메소드에 리턴값이 있을 경우 람다식은 다음과 같이 작성할 수 있다.
return 문 하나만 있을 경우에는 중괄호와 함께 return 키워드를 생략할 수 있다. 리턴값은 연산식 또는 리턴값 있는 메소드 호출로 대체할 수 있다.</p>
</blockquote>
<pre><code class="language-java">(매개변수, ...) -&gt; {
    실행문;
    return 값;
}</code></pre>
<pre><code class="language-java">(매개변수, ...) -&gt; return 값;
(매개변수, ...) -&gt; 값;</code></pre>
<p>예제를 보자</p>
<pre><code class="language-java">package ch16.sec04;

@FunctionalInterface
public interface Calcuable {
    double calc(double x, double y);
}</code></pre>
<pre><code class="language-java">package ch16.sec04;

public class Person {
    public void action(Calcuable calcuable) {
        double result = calcuable.calc(10, 4);
        System.out.println(&quot;결과: &quot; + result);
    }
}</code></pre>
<pre><code class="language-java">package ch16.sec04;

public class LambdaExample {
    public static void main(String[] args) {
        Person person = new Person();

        // 실행문이 두 개 이상일 경우
        person.action((x, y) -&gt; {
            double result = x + y;
            return result;
        });

        // 리턴문이 하나만 있을 경우(연산식)
        // person.action((x, y) -&gt; {
        //  return (x + y);
        // });
        person.action((x, y) -&gt; (x + y));

        // 리턴문이 하나만 있을 경우(메소드 호출)
        // person.action((x, y) -&gt; {
        //  return sum(x, y);
        // });
        person.action((x, y) -&gt; sum(x, y));
    }

    public static double sum(double x, double y) {
        return (x + y);
    }
}</code></pre>
<h1 id="메소드-참조">메소드 참조</h1>
<blockquote>
<p>메소드 참조는 말 그대로 메소드를 참조해서 매개변수의 정보 및 리턴 타입을 알아내 람다식에서 불필요한 매개변수를 제거하는 것을 목적으로 한다.</p>
</blockquote>
<p>예를 들어 두 개의 값을 받아 큰 수를 리턴하는 Math 클래수의 max() 정적 메소드를 호출하는 람다식은 다음과 같다.</p>
<pre><code class="language-java">(left, right) -&gt; Math.max(left, right);</code></pre>
<p>람다식은 단순히 두 개의 값을 Math.max() 메소드의 매개값으로 전달하는 역할만 하기 떄문에 다소 불편해 보인다.
이 경우에는 다음과 같이 메소드 참조를 이용하면 매우 깔끔하게 처리할 수 있다.</p>
<pre><code class="language-java">Math :: max;</code></pre>
<h2 id="정적-메소드와-메소드-참조">정적 메소드와 메소드 참조</h2>
<p>정적 메소드를 참조할 경우에는 클래스 이름 뒤에 <code>::</code> 기호를 붙이고 정적 메소드 이름을 기술한다.</p>
<pre><code class="language-java">클래스 :: 메소드</code></pre>
<p>인스턴스 메소드일 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 <code>::</code> 기호를 붙이고 인스턴스 메소드 이름을 기술한다.</p>
<pre><code class="language-java">참조변수 :: 메소드</code></pre>
<p>예제를 보자</p>
<pre><code class="language-java">package ch16.sec05.exam01;

@FunctionalInterface
public interface Calcuable {
    double calc(double x, double y);
}</code></pre>
<pre><code class="language-java">package ch16.sec05.exam01;

public class Person {
    public void action(Calcuable calcuable) {
        double result = calcuable.calc(10, 4);
        System.out.println(&quot;결과: &quot; + result);
    }
}</code></pre>
<pre><code class="language-java">package ch16.sec05.exam01;

public class Computer {
    public static double staticMethod(double x, double y) {
        return x + y;
    }

    public double instanceMethod(double x, double y) {
        return x * y;
    }
}</code></pre>
<pre><code class="language-java">package ch16.sec05.exam01;

public class MethodReferenceExample {
    public static void main(String[] args) {
        Person person = new Person();

        // 정적 메소드일 경우
        // 람다식
        // person.action((x, y) -&gt; Computer.staticMethod(x, y));
        // 메소드 참조
        person.action(Computer::staticMethod);

        // 인스턴스 메소드일 경우
        Computer com = new Computer();
        // 람다식
        // person.action((x, y) -&gt; com.instanceMethod(x, y));
        // 메소드 참조
        person.action(com::instanceMethod);
    }
}</code></pre>
<pre><code>결과: 14.0
결과: 40.0</code></pre><h2 id="매개변수의-메소드-참조">매개변수의 메소드 참조</h2>
<p>다음과 같이 람다식에서 제공되는 a 매개변수의 메소드를 호출해서 b 매개변수를 매개값으로 사용하는 경우도 있다.</p>
<pre><code class="language-java">(a, b) -&gt; { a.instanceMethod(b); }</code></pre>
<p>이것을 메소드 참조로 표현하면 다음과 같다. a의 클래스 이름 뒤에 <code>::</code> 기호를 붙이고 메소드 이름을 기술한다.
작성 방법은 정적 메소드 참조와 동일하지만, a의 인스턴스 메소드가 사용된다는 점에서 다르다.</p>
<pre><code class="language-java">클래스 :: instanceMethod</code></pre>
<p>예제를 보자.</p>
<pre><code class="language-java">package ch16.sec05.exam02;

@FunctionalInterface
public interface Comparable {
    int compare(String a, String b);
}</code></pre>
<pre><code class="language-java">package ch16.sec05.exam02;

public class Person {
    public void ordering(Comparable comparable) {
        String a = &quot;홍길동&quot;;
        String b = &quot;김길동&quot;;

        int result = comparable.compare(a, b);

        if (result &lt; 0) {
            System.out.println(a + &quot;은 &quot; + b + &quot;보다 앞에 옵니다.&quot;);
        } else if (result == 0) {
            System.out.println(a + &quot;은 &quot; + b + &quot;과 같습니다.&quot;);
        } else {
            System.out.println(a + &quot;은 &quot; + b + &quot;보다 뒤에 옵니다.&quot;);
        }
    }
}</code></pre>
<pre><code class="language-java">package ch16.sec05.exam02;

public class MethodReferenceExample {
    public static void main(String[] args) {
        Person person = new Person();
        person.ordering(String::compareToIgnoreCase);
    }
}</code></pre>
<pre><code>홍길동은 김길동보다 뒤에 옵니다.</code></pre><h1 id="생성자-참조">생성자 참조</h1>
<blockquote>
<p>생성자를 참조한다는 것은 객체를 생성하는 것을 의미한다. 람다식이 단순히 객체를 생성하고 리턴하도록 구성된다면 람다식을 생성자 참조로 대치할 수 있다.</p>
</blockquote>
<p>다음 코드를 보면 람다식은 단순히 객체를 생성한 후 리턴만 한다.</p>
<pre><code class="language-java">(a, b) -&gt; { return new 클래스(a, b); }</code></pre>
<p>이것을 생성자 참조로 표현하면 다음과 같다. 클래스 이름 뒤에 <code>::</code> 기호를 붙이고 new 연산자를 기술하면 된다.</p>
<pre><code class="language-java">클래스 :: new</code></pre>
<p>생성자가 오버로딩되어 여러 개가 있을 경우, 컴파일러는 함수형 인터페이스의 추상 메소드와 동일한 매개변수 타입과 개수를 가지고 있는 생성자를 찾아 실행한다.
만약 해당 생성자가 존재하지 않으면 컴파일 오류가 발생한다.
예제를 보자.</p>
<pre><code class="language-java">package ch16.sec05.exam03;

@FunctionalInterface
public interface Creatable1 {
    Member create(String id);
}</code></pre>
<pre><code class="language-java">package ch16.sec05.exam03;

@FunctionalInterface
public interface Creatable2 {
    Member create(String id, String name);
}</code></pre>
<pre><code class="language-java">package ch16.sec05.exam03;

public class Member {
    private final String id;
    private String name;

    public Member(String id) {
        this.id = id;
        System.out.println(&quot;Member(String id)&quot;);
    }

    public Member(String id, String name) {
        this.id = id;
        this.name = name;
        System.out.println(&quot;Member(String id, String name)&quot;);
    }

    @Override
    public String toString() {
        String info = &quot;{ id: &quot; + id + &quot;, name: &quot; + name + &quot; }&quot;;
        return info;
    }
}</code></pre>
<pre><code class="language-java">package ch16.sec05.exam03;

public class Person {
    public Member getMember1(Creatable1 creatable) {
        String id = &quot;winter&quot;;
        Member member = creatable.create(id);
        return member;
    }

    public Member getMember2(Creatable2 creatable) {
        String id = &quot;winter&quot;;
        String name = &quot;한겨울&quot;;
        Member member = creatable.create(id, name);
        return member;
    }
}</code></pre>
<pre><code class="language-java">package ch16.sec05.exam03;

public class ConstructorReferenceExample {
    public static void main(String[] args) {
        Person person = new Person();

        Member m1 = person.getMember1(Member::new);
        System.out.println(m1);
        System.out.println();

        Member m2 = person.getMember2(Member::new);
        System.out.println(m2);
    }
}</code></pre>
<pre><code>Member(String id)
{ id: winter, name: null }

Member(String id, String name)
{ id: winter, name: 한겨울 }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 14 - 멀티 스레드 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-14-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-14-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Wed, 25 Jan 2023 15:07:39 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-14---멀티-스레드-확인문제">Chapter 14 - 멀티 스레드 확인문제</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/88d5c74b-6e80-4732-bfda-1b986b7cc97e/image.png" alt=""></p>
<h2 id="정답-4">정답: 4</h2>
<p>스레드 실행을 시작하려면 <strong><code>start()</code></strong> 메소드를 호출해야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c3a2f979-99e2-4614-a8fa-a2855d2128b4/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/37257a57-4828-4c85-b3ca-1c54994af307/image.png" alt=""></p>
<h2 id="정답">정답:</h2>
<pre><code class="language-java">new MusicRunnable()</code></pre>
<pre><code class="language-java">extends Thread</code></pre>
<pre><code class="language-java">implements Runnable</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/4de24aac-894b-4279-936d-9208f050f4ef/image.png" alt=""></p>
<h2 id="정답-2">정답: 2</h2>
<p>스레드가 동기화 메소드를 실행할 때 다른 스레드는 일반 메소드를 호출할 수 <strong><code>있다</code></strong>.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/767cbb8a-e4ab-4e4f-aa62-aaa8c43c0e89/image.png" alt=""></p>
<h2 id="정답-4-1">정답: 4</h2>
<p>yield()를 호출한 스레드는 실행 대기 상태로 돌아가고, 다른 스레드가 실행 상태가 된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/171b638e-4c99-40a9-ae1b-f01b13ae3da2/image.png" alt=""></p>
<h2 id="정답-2-1">정답: 2</h2>
<p>interrupt() 메소드는 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할을 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/314b0ec5-84d4-4680-82cb-387e8a38cd71/image.png" alt=""></p>
<h2 id="정답-1">정답:</h2>
<pre><code class="language-java">if (this.isInterrupted()) break;</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/ae5b2f00-1211-402d-84ba-36d3eb4f6c02/image.png" alt=""></p>
<h2 id="정답-3">정답: 3</h2>
<p>wait()와 notify() 메소드는 동기화 메소드 또는 동기화 블록 내에서만 사용할 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/aae1b637-0c50-45c7-8bd5-3b7234070ee8/image.png" alt=""></p>
<h2 id="정답-5">정답:</h2>
<pre><code class="language-java">thread.setDaemon(true);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/0db6db52-0f5f-4eda-971d-e6f74a30f694/image.png" alt=""></p>
<h2 id="정답-4-2">정답: 4</h2>
<p>stop() 메소드는 스레드를 갑자기 종료하게 되면 사용 중이던 리소스들이 불안전한 상태로 남겨지기 때문에 deprecated 되었다. 따라서 해당 메소드를 사용하는 것은 지양해야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/71a12331-e6c4-416f-9747-b15329a3ee54/image.png" alt=""></p>
<h2 id="정답-4-3">정답: 4</h2>
<p>execute() 메소드로 작업 처리 요청을 하면 Runnable 객체를 작업 큐에 저장하고 작업 처리 결과를 리턴하지 않는다. 즉, 작업 완료 여부를 확인하지 않고 Runnable 객체를 작업 큐에 저장하는 역할만 하기 때문에 블로킹되지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 14 - 멀티 스레드]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-14-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-14-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C</guid>
            <pubDate>Wed, 25 Jan 2023 14:05:49 GMT</pubDate>
            <description><![CDATA[<h1 id="멀티-스레드-개념">멀티 스레드 개념</h1>
<blockquote>
<p>운영체제는 실행 중인 프로그램을 프로세스(Process)로 관리한다. 멀티 태스킹(Multi tasking)은 두 가지 이상의 작업을 동시에 처리하는 것을 말하는데, 이때 운영체제는 멀티 프로세스를 생성해서 처리한다.</p>
</blockquote>
<p>하지만 멀티 태스킹이 꼭 멀티 프로세스를 뜻하지는 않는다.
하나의 프로세스가 두 가지 이상의 작업을 처리할 수 있는 이유는 멀티 스레드(Multi thread)가 있기 때문이다.
스레드(Thread)는 코드의 실행 흐름을 말하는데, 프로세스 내에 스레드가 두 개라면 두 개의 코드 실행 흐름이 생긴다는 의미이다.</p>
<p>멀티 프로세스가 프로그램 단위의 멀티 태스킹이라면 멀티 스레드는 프로그램 내부에서의 멀티 태스킹이라고 볼 수 있다.
다음 그림은 멀티 프로세스와 멀티 스레드의 차이점을 보여준다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5aebab33-c9bf-4174-9849-74be40efffe1/image.png" alt="">
멀티 프로세스들은 서로 독립적이므로 하나의 프로세스에서 오류가 발생해도 다른 프로세스에게 영향을 미치지 않는다.
하지만 멀티 스레드는 프로세스 내부에서 생성되기 때문에 하나의 스레드가 예외를 발생시키면 프로세스가 종료되므로 다른 스레드에게 영향을 미친다.</p>
<h1 id="메인-스레드">메인 스레드</h1>
<blockquote>
<p>모든 자바 프로그램은 메인 스레드(Main Thread)가 main() 메소드를 실행하면서 시작된다. 메인 스레드는 main() 메소드의 첫 코드부터 순차적으로 실행하고, main() 메소드의 마지막 코드를 실행하거나 return 문을 만나면 실행을 종료한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e8bdfd38-f524-4cfe-894e-d4e8f02767c3/image.png" alt="">
메인 스레드는 필요에 따라 추가 작업 스레드들을 만들어서 실행시킬 수 있다. 다음 그림에서 오른쪽의 멀티 스레드를 보면 메인 스레드가 작업 스레드1을 생성하고 실행시킨 다음, 곧이어 작업 스레드2를 생성하고 실행시키는 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f7af759b-3a16-46fb-826d-ad624e535e99/image.png" alt="">
싱글 스레드에서는 메인 스레드가 종료되면 프로세스도 종료된다. 하지만 멀티 스레드에서는 실행 중인 스레드가 하나라도 있다면 프로세스는 종료되지 않는다. 메인 스레드가 작업 스레드보다 먼저 종료되더라도 작업 스레드가 계속 실행중이라면 프로세스는 종료되지 않는다.</p>
<h1 id="작업-스레드-생성과-실행">작업 스레드 생성과 실행</h1>
<blockquote>
<p>멀티 스레드로 실행하는 프로그램을 개발하려면 먼저 몇 개의 작업을 병렬로 실행할지 결정하고 각 작업별로 스레드를 생성해야 한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/19a9915b-d907-4ee4-82a6-03e636bde51d/image.png" alt="">
자바 프로그램은 메인 스레드가 반드시 존재하기 때문에 메인 작업 이외에 추가적인 작업 수만큼 스레드를 생성하면 된다. 자바는 작업 스레드도 객체로 관리하므로 클래스가 필요하다. Thread 클래스로 직접 객체를 생성해도 되지만, 하위 클래스를 만들어 생성할 수도 있다.</p>
<h2 id="thread-클래스로-직접-생성">Thread 클래스로 직접 생성</h2>
<p>java.lang 패키지에 있는 Thread 클래스로부터 작업 스레드 객체를 직접 생성하려면 다음과 같이 Runnable 구현 객체를 매개값으로 갖는 생성자를 호출하면 된다.</p>
<pre><code class="language-java">Thread thread = new Thread(Runnable target);</code></pre>
<p>Runnable은 스레드가 작업을 실행할 때 사용하는 인터페이스이다. Runnable에는 run() 메소드가 정의되어 있는데, 구현 클래스는 run()을 재정의해서 스레드가 실행할 코드를 가지고 있어야 한다.
다음은 Runnable 구현 클래스를 작성하는 방법이다.</p>
<pre><code class="language-java">class Task implements Runnable {
    @Override
    public void run() {
        // 스레드가 실행할 코드
    }
}</code></pre>
<p>Runnable 구현 클래스는 작업 내용을 정의한 것이므로, 스레드에게 전달해야 한다.
Runnable 구현 객체를 생성한 후 Thread 생성자 매개값으로 Runnable 객체를 다음과 같이 전달하면 된다.</p>
<pre><code class="language-java">Runnable task = new Task();
Thread thread = new Thread(task);</code></pre>
<p>명시적인 Runnable 구현 클래스를 작성하지 않고 Thread 생성자를 호출할 때 Runnable 익명 구현 객체를 매개값으로 사용할 수 있다.
오하려 이 방법이 더 많이 사용된다.</p>
<pre><code class="language-java">Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        // 스레드가 실행할 코드
    }
});</code></pre>
<p>작업 스레드 객체가 생성되었다고 해서 바로 작업 스레드가 실행되지는 않는다. 작업 스레드를 실행하려면 스레드 객체의 start() 메소드를 다음과 같이 호출해야 한다.</p>
<pre><code class="language-java">thread.start();</code></pre>
<p>start() 메소드가 호출되면, 작업 스레드는 매개값으로 받은 Runnable의 run() 메소드를 실행하면서 작업을 처리한다.
다음은 작업 스레드가 생성되고 실행되기까지의 순서를 보여준다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c62ef751-413d-4528-85a3-36714f1d2e06/image.png" alt=""></p>
<h2 id="thread-자식-클래스로-생성">Thread 자식 클래스로 생성</h2>
<p>작업 스레드 객체를 생성하는 또 다른 방법은 Thread의 자식 객체를 만드는 것이다. Thread 클래스를 장속한 다음 run() 메소드를 재정의해서 스레드가 실행할 코드를 작성하고 객체를 생성하면 된다.</p>
<pre><code class="language-java">public class WorkerThread extends Thread {
    @Override
    public void run() {
        // 스레드가 실행할 코드
    }
}

Thread thread = new WorkerThread();</code></pre>
<p>작업 스레드를 실행하는 방법은 동일하다. start() 메소드를 호출하면 작업 스레드는 재정의된 run()을 실행시킨다.</p>
<pre><code class="language-java">thread.start();</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b2dd5c29-07b6-4b44-81df-68af714c40be/image.png" alt="">
명시적인 자식 클래스를 정의하지 않고, 다음과 같이 Thread 익명 객체를 사용할 수도 있다.
오히려 이 방법이 더 많이 사용된다.</p>
<pre><code class="language-java">Thread thread = new Thread() {
    @Override
    public void run() {
        // 스레드가 실행할 코드
    }
};</code></pre>
<h1 id="스레드-이름">스레드 이름</h1>
<blockquote>
<p>스레드는 자신의 이름을 가지고 있다. 메인 스레드는 <code>main</code>이라는 이름을 가지고 있고, 작업 스레드는 자동적으로 <code>Thread-n</code>이라는 이름을 가진다.</p>
</blockquote>
<p>작업 스레드의 이름을 <code>Thread-n</code> 대신 다른 이름으로 설정하고 싶다면 Thread 클래스의 setName() 메소드를 사용하면 된다.</p>
<pre><code class="language-java">thread.setName(&quot;스레드 이름&quot;);</code></pre>
<p>스레드 이름은 디버깅할 때 어떤 스레드가 작업을 하는지 조사할 목적으로 주로 사용된다.
현재 코드를 어떤 스레드가 실행하고 있는지 확인하려면 정적 메소드는 currentThread()로 스레드 객체의 참조를 얻은 다음 getName() 메소드로 이름을 출력해보면 된다.</p>
<pre><code class="language-java">Thread thread = Thread.currentThread();
System.out.println(thread.getName());</code></pre>
<h1 id="스레드-상태">스레드 상태</h1>
<blockquote>
<p>스레드는 생성(NEW), 실행 대기(RUNNABLE), 실행(RUNNING), 종료(TERMINATED) 상태를 가진다.</p>
</blockquote>
<p>스레드 객체를 생성(NEW)하고, start() 메소드를 호출하면 곧바로 스레드가 실행되는 것이 아니라 실행 대기 상태(RUNNABLE)가 된다. 실행 대기 상태란 실행을 기다리고 있는 상태를 말한다. 실행 대기하는 스레드는 CPU 스케줄링에 따라 CPU를 점유하고 run() 메소드를 실행한다. 이때를 실행(RUNNING) 상태라고 한다. 실행 스레드는 run() 메소드를 모두 실행하기 전에 스케줄링에 의해 다시 실행 대기 상태로 돌아갈 수 있다. 그리고 다른 스레드가 실행 상태가 된다.</p>
<p>이렇게 스레드는 실행 대기 상태와 실행 상태를 번갈아 가면서 자신의 run() 메소드를 조금씩 실행한다.
실행 상태에서 run() 메소드가 종료되면 더 이상 실행할 코드가 없기 때문에 스레드의 실행은 멈추게 된다.
이 상태를 종료 상태(TERMINATED)라고 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e0aaf491-1692-421b-9295-925201119616/image.png" alt="">
실행 상태에서 일시 정지 상태로 가기도 하는데, 일시 정지 상태는 스레드가 실행할 수 없는 상태를 말한다.
스레드가 다시 실행 상태로 가기 위해서는 일시 정지 상태에서 실행 대기 상태로 가야만 한다.
다음은 일시 정지로 가기 위한 메소드와 벗어나기 위한 메소드들을 보여준다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/da45e109-2fca-4b90-8451-f2e212b0dde9/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/23f1136b-b8a1-49c1-b590-088d3e738d8c/image.png" alt="">
위 표에서 wait()과 notify(), notifyAll()은 Object 클래스의 메소드이고 그 외는 Thread 클래스의 메소드이다.</p>
<h2 id="주어진-시간-동안-일시-정지">주어진 시간 동안 일시 정지</h2>
<p>실행 중인 스레드를 일정 시간 멈추게 하고 싶다면 Thread 클래스의 정적 메소드인 sleep()을 이용하면 된다.
매개값에는 얼마 동안 일시 정지 상태로 있을 것인지 밀리세컨드(1/1000) 단위로 시간을 주면 된다.</p>
<p>다음 코드는 1초 동안 일시 정지 상태를 만든다.</p>
<pre><code class="language-java">try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // interrupt() 메소드가 호출되면 실행
}</code></pre>
<h2 id="다른-스레드의-종료를-기다림">다른 스레드의 종료를 기다림</h2>
<p>스레드는 다른 스레드와 독립적으로 실행하지만 다른 스레드가 종료될 때까지 기다렸다가 실행을 해야 하는 경우도 있다.
예를 들어 계산 스레드의 작업이 종료된 후 그 결과값을 받아 처리하는 경우이다.
이를 위해 스레드는 join() 메소드를 제공한다.</p>
<p>다음 그림에서 ThreadA가 ThreadB의 join() 메소드를 호출하면 ThreadA는 ThreadB가 종료할 때까지 일시 정지 상태가 된다.
ThreadB의 run() 메소드가 종료되고 나서야 비로소 ThreadA는 일시 정지에서 풀려 다음 코드를 실행한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/fea6ecbb-f6b9-46ba-bfb9-a5c5b68bc2aa/image.png" alt=""></p>
<h2 id="다른-스레드에게-실행-양보">다른 스레드에게 실행 양보</h2>
<p>스레드가 처리하는 작업은 반복적인 실행을 위해 for 문이나 while 문을 포함하는 경우가 많은데, 가끔 반복문이 무의미한 반복을 하는 경우가 있다.</p>
<p>다음 코드를 보자. work의 값이 false라면 while 문은 어떠한 실행문도 실행하지 않고 무의미한 반복을 한다.</p>
<pre><code class="language-java">public void run() {
    while (true) {
        if (work) {
            System.out.println(&quot;ThreadA 작업 내용&quot;);
        }
    }
}</code></pre>
<p>이때는 다른 스레드에게 실행을 양보하고 자신은 실행 대기 상태로 가는 것이 프로그램 성능에 도움이 된다.
이런 기능을 위해 Thread는 yield() 메소드를 제공한다.
yield()를 호출한 스레드는 실행 대기 상태로 돌아가고, 다른 스레드가 실행 상태가 된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/09546091-1b4e-4d61-a20f-a496471c37fa/image.png" alt="">
다음은 무의미한 반복을 하지 않고 다른 스레드에게 실행을 양보하도록 이전 코드를 수정한 것이다.</p>
<pre><code class="language-java">public void run() {
    while (true) {
        if (work) {
            System.out.println(&quot;ThreadA 작업 내용&quot;);
        } else {
            Thread.yield();
        }
    }
}</code></pre>
<h1 id="스레드-동기화">스레드 동기화</h1>
<blockquote>
<p>멀티 스레드는 하나의 객체를 공유해서 작업할 수도 있다. 이 경우, 다른 스레드에 의해 객체 내부 데이터가 쉽게 변경될 수 있기 때문에 의도했던 것과는 다른 결과가 나올 수 있다. 다음 그림을 보자.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c9e7ae20-2d72-4c8b-8d51-860484b41d6d/image.png" alt="">
User1Thread는 Calculator 객체의 memory 필드에 100을 먼저 저장하고 2초간 일시 정지 상태가 된다.
그동안 User2Thread가 memory 필드값을 50으로 변경한다. 2초가 지나 User1Thread가 다시 실행 상태가 되어 memory 필드의 값을 출력하면 User2Thread가 저장한 50이 나온다.</p>
<p>그런데 이렇게 하면 User1Thread에 저장된 데이터가 날아가버린다. 스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 하려면 스레드 작업이 끝날 때까지 객체에 잠금을 걸면 된다. 이를 위해 자바는 동기화(Synchronized) 메소드와 블록을 제공한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c19ad95b-1cdd-4ddb-b656-c3dc6247cfa3/image.png" alt="">
객체 내부에 동기화 메소드와 동기화 블록이 여러 개가 있다면 스레드가 이들 중 하나를 실행할 때 다른 스레드는 해당 메소드는 물론이고 다른 동기화 메소드 및 블록도 실행할 수 없다. 하지만 일반 메소드는 실행이 가능하다.</p>
<h2 id="동기화-메소드-및-블록-선언">동기화 메소드 및 블록 선언</h2>
<p>동기화 메소드를 선언하는 방법은 다음과 같이 <code>synchronized</code> 키워드를 붙이면 된다.
<code>synchronized</code> 키워드는 인스턴스와 정적 메소드 어디든 붙일 수 있다.</p>
<pre><code class="language-java">public synchronized void method() {
    // 단 하나의 스레드만 실행하는 영역
}</code></pre>
<p>스레드가 동기화 메소드를 실행하는 즉시 객체는 잠금이 일어나고, 메소드 실행이 끝나면 잠금이 풀린다.
메소드 전체가 아닌 일부 영역을 실행할 때만 객체 잠금을 걸고 싶다면 다음과 같이 동기화 블록을 만들면 된다.</p>
<pre><code class="language-java">public void method() {
    // 여러 스레드가 실행할 수 있는 영역
    synchronized(공유객체) {
        // 단 하나의 스레드만 실행하는 영역
    }
    // 여러 스레드가 실행할 수 있는 영역
}</code></pre>
<p>예제를 보자</p>
<pre><code class="language-java">package ch14.sec06.exam01;

public class Calculator {
    private int memory;

    public int getMemory() {
        return memory;
    }

    public synchronized void setMemory1(int memory) {
        this.memory = memory;
        try {
            Thread.sleep(2000);
        } catch(InterruptedException e) {}
        System.out.println(Thread.currentThread().getName() + &quot;: &quot; + this.memory);
    }

    public void setMemory2(int memory) {
        synchronized(this) {
            this.memory = memory;
            try {
                Thread.sleep(2000);
            } catch(InterruptedException e) {}
            System.out.println(Thread.currentThread().getName() + &quot;: &quot; + this.memory);
        }
    }
}</code></pre>
<pre><code class="language-java">package ch14.sec06.exam01;

public class User1Thread extends Thread {
    private Calculator calculator;

    public User1Thread() {
        setName(&quot;User1Thread&quot;);
    }

    public void setCalculator(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public void run() {
        calculator.setMemory1(100);
    }
}</code></pre>
<pre><code class="language-java">package ch14.sec06.exam01;

public class User2Thread extends Thread {
    private Calculator calculator;

    public User2Thread() {
        setName(&quot;User2Thread&quot;);
    }

    public void setCalculator(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public void run() {
        calculator.setMemory2(50);
    }
}</code></pre>
<pre><code class="language-java">package ch14.sec06.exam01;

public class SynchronizedExample {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        User1Thread user1Thread = new User1Thread();
        user1Thread.setCalculator(calculator);
        user1Thread.start();

        User2Thread user2Thread = new User2Thread();
        user2Thread.setCalculator(calculator);
        user2Thread.start();
    }
}</code></pre>
<pre><code>User1Thread: 100
User2Thread: 50</code></pre><p>위 예제는 Calculator를 생성해서 User1Thread와 User2Thread에서 사용하도록 setCalculator() 메소드를 호출하고, 두 스레드를 시작시킨다.</p>
<p>정확히 User1Thread가 저장된 값 100이 출력되었고, User2Thread가 저장한 값 50이 출력되었다.</p>
<p>다음 그림을 보면 왜 이런 값이 나왔는지 이해할 수 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e27d75cd-3f79-4675-a976-8730a3c4e0b7/image.png" alt=""></p>
<h2 id="wait과-notify를-이용한-스레드-제어">wait()과 notify()를 이용한 스레드 제어</h2>
<p>경우에 따라서는 두 개의 스레드를 교대로 번갈아 가며 실행할 때도 있다. 정확한 교대 작업이 필요할 경우, 자신의 작업이 끝나면 상대방 스레드를 일시 정지 상태에서 푸렁주고 자신은 일시 정지 상태로 만들면 된다.</p>
<p>이 방법의 핵심은 공유 객체에 있다. 공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드로 정해 놓는다.
한 스레드가 작업을 완료하면 notify() 메소드를 호출해서 일시 정지 상태에 있는 다른 스레드를 실행 대기 상태로 만들고, 자신은 두 번 작업을 하지 않도록 wait() 메소드를 호출하여 일시 정지 상태로 만든다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/72981245-e312-4cb2-b1bd-0ebab442451c/image.png" alt="">
notify()는 wait()에 의해 일시 정지된 스레드 중 한 개를 실행 대기 상태로 만들고, notifyAll()은 wait()에 의해 일시 정지된 모든 스레드를 실행 대기 상태로 만든다.</p>
<p>주의할 점은 이 두 메소드는 동기화 메소드 또는 동기화 블록 내에서만 사용할 수 있다는 것이다.</p>
<h1 id="스레드-안전-종료">스레드 안전 종료</h1>
<blockquote>
<p>스레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료되지만, 경우에 따라서는 실행 중인 스레드를 즉시 종료할 필요가 있다. 스레드를 안전하게 종료하는 방법은 사용하던 리소스들을 정리하고 run() 메소드를 빨리 종료하는 것이다. 주로 조건 이용 방법과 interrupt() 메소드 이용 방법을 사용한다.</p>
</blockquote>
<h2 id="조건-이용">조건 이용</h2>
<p>스레드가 while 문으로 반복 실행할 경우, 조건을 이용해서 run() 메소드의 종료를 유도할 수 있다.</p>
<pre><code class="language-java">public class XXXThread extends Thread {
    private boolean stop;

    public void run() {
        while (!stop) {
            // 스레드가 반복 실행하는 코드;
        }
        // 스레드가 사용한 리소스 정리
    }
}</code></pre>
<h2 id="interrupt-메소드-이용">interrupt() 메소드 이용</h2>
<p>interrupt() 메소드는 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할을 한다.</p>
<p>이것을 이용하면 예외 처리를 통해 run() 메소드를 정상 종료시킬 수 있다.</p>
<p>다음 그림을 보자.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b267fcaf-427c-4ecd-b0c8-4a54815b0659/image.png" alt="">
XThread를 생성해서 start() 메소드를 실행한 후에 XThread의 interrupt() 메소드를 실행하면 XThread가 일시 정지 상태가 될 때 InterruptedException이 발생하여 예외 처리 블록으로 이동한다.</p>
<p>이것인 결국 while 문을 빠져나와 자원을 정리하고 스레드가 종료되는 효과를 가져온다.</p>
<h1 id="데몬-스레드">데몬 스레드</h1>
<blockquote>
<p>데몬(Daemon) 스레드는 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드이다. 주 스레드가 종료되면 데몬 스레드도 따라서 자동으로 종료된다.</p>
</blockquote>
<p>워드 프로세서의 자동 저장, 자바의 가바지 컬렉터 등이 데몬 스레드의 예시이다.</p>
<p>스레드를 데몬으로 만들기 위해서는 주 스레드가 데몬이 될 스레드의 setDaemon(true)를 호출하면 된다.</p>
<pre><code class="language-java">public static void main(String[] args) {
    AutoSaveThread thread = new AutoSaveThread();
    thread.setDaemon(true);
    thread.start();
}</code></pre>
<h1 id="스레드풀">스레드풀</h1>
<blockquote>
<p>병렬 작업 처리가 많아지면 스레드의 개수가 폭증하여 CPU 및 메모리 사용량이 늘어난다.
이에 따라 애플리케이션의 성능 또한 급격히 저하된다. 이렇게 병렬 작업 증가로 인한 스레드의 폭증을 막으려면 스레드풀(ThreadPool)을 사용하는 것이 좋다.</p>
</blockquote>
<p>스레드풀은 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해 놓고 작업 큐(Queue)에 들어오는 작업들을 스레드가 하나씩 맡아 처리하는 방식이다.</p>
<p>작업 처리가 끝난 스레드는 다시 작업 큐에서 새로운 작업을 가져와 처리한다.</p>
<p>이렇게 하면 작업량이 증가해도 스레드의 개수가 늘어나지 않아 애플리케이션의 성능이 급격히 저하되지 않는다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b76f3cdb-73c4-4520-8223-1ed335f62a9f/image.png" alt=""></p>
<h2 id="스레드풀-생성">스레드풀 생성</h2>
<p>자바는 스레드풀을 생성하고 사용할 수 있도록 java.util.concurrent 패키지에서 ExecutorService 인터페이스와 Executors 클래스를 제공하고 있다.</p>
<p>Executors의 다음 두 정적 메소드를 이용하면 간단하게 스레드풀인 ExecutorService 구현 객체를 만들 수 있다.</p>
<table>
<thead>
<tr>
<th align="left">메소드명(매개변수)</th>
<th align="left">초기 수</th>
<th align="left">코어 수</th>
<th align="left">최대 수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">newCachedThreadPool()</td>
<td align="left">0</td>
<td align="left">0</td>
<td align="left">Integer.MAX_VALUE</td>
</tr>
<tr>
<td align="left">newFixedThreadPool(int nThreads)</td>
<td align="left">0</td>
<td align="left">생성된 수</td>
<td align="left">nThreads</td>
</tr>
<tr>
<td align="left">초기 수는 스레드풀이 생성될 때 기본적으로 생성되는 스레드 수를 말하고, 코어 수는 스레드가 증가된 후 사용되지 않는 스레드를 제거할 때 최소한 풀에서 유지하는 스레드 수를 말한다.</td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left">그리고 최대 수는 증가되는 스레드의 한도 수이다.</td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
</tbody></table>
<p>다음과 같이 newCachedThreadPool() 메소드로 생성된 스레드풀의 초기 수와 코어 수는 0개이고, 작업 개수가 많아지면 새 스레드를 생성시켜 작업을 처리한다.
60초 동안 스레드가 아무 작업을 하지 않으면 스레드를 풀에서 제거한다.</p>
<pre><code class="language-java">ExecutorService executorService = Executors.newCachedThreadPool();</code></pre>
<p>다음과 같이 newFixedThreadPool()로 생성된 스레드풀의 초기 수는 0개이고, 작업 개수가 많아지면 최대 5개까지 스레드를 생성시켜 작업을 처리한다.
이 스레드풀의 특징은 생성된 스레드를 제거하지 않는다는 것이다.</p>
<pre><code class="language-java">ExecutorService executorService = Executors.newFixedThreadPool();</code></pre>
<p>위 두 메소드를 사용하지 않고 직접 ThreadPoolExecutor로 스레드풀을 생성할 수도 있다.
다음에서는 초기 수 0개, 코어 수 3개, 최대 수 100개인 스레드풀을 생성하는 코드이다.
그리고 추가도니 스레드가 120초 동안 놀고 있을 경우 해당 스레드를 풀에서 제거한다.</p>
<pre><code class="language-java">ExecutorService threadPool = new ThreadPoolExecutor {
    3,                               // 코어 스레드 개수
    100,                             // 최대 스레드 개수
    120L,                            // 놀고 있는 시간
    TimeUnit.SeCONDS,                // 놀고 있는 시간 단위
    new SynchronousQueue&lt;Runnable&gt;() // 작업 큐
}</code></pre>
<h2 id="스레드풀-종료">스레드풀 종료</h2>
<p>스레드풀의 스레드는 기본적으로 데몬 스레드가 아니기 때문에 main 스레드가 종료되더라도 작업을 처리하기 위해 계속 실행 상태로 남아 있다.</p>
<p>스레드풀의 모든 스레드를 종료하려면 ExecutorService의 다음 두 메소드 중 하나를 실행해야 한다.</p>
<table>
<thead>
<tr>
<th align="left">리턴 타입</th>
<th align="left">메소드명(매개변수)</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">void</td>
<td align="left">shutdown()</td>
<td align="left">현재 처리 중인 작업뿐만 아니라 작업 큐에 대기하고 있는 모든 작업을 처리한 뒤에<br>스레드풀을 종료시킨다.</td>
</tr>
<tr>
<td align="left">List&lt;Runnable&gt;</td>
<td align="left">shutdownNow()</td>
<td align="left">현재 작업 처리중인 스레드를 interrupt해서 작업을 중지시키고 스레드풀을 종료시킨다.<br>리턴값은 작업 큐에 있는 미처리된 작업(Runnable)의 목록이다.</td>
</tr>
<tr>
<td align="left">## 작업 생성과 처리 요청</td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left">하나의 작업은 Runnable 또는 Callable 구현 클래스로 표현한다. Runnable과 Callable의 차이점은 작업 처리 완료 후 리턴값이 있느냐 없느냐이다.</td>
<td align="left"></td>
<td align="left"></td>
</tr>
</tbody></table>
<p>다음은 Runnable과 Callable 구현 클래스를 작성하는 방법을 보여준다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/e0d45cea-7789-4beb-bf51-ee7fd61b6995/image.png" alt="">
Runnable의 run() 메소드는 리턴값이 없고, Callable의 call() 메소드는 리턴값이 없다.
call()의 리턴값은 Callable&lt;T&gt;에서 지정한 T 타입 파라미터와 동일한 타입이어야 한다.</p>
<p>작업 처리 요청이란 ExecutorService의 작업 큐에 Runnable 또는 Callable 객체를 넣는 행위를 말한다.
작업 처리 요청을 위해 ExecutorService는 당므 두 가지 메소드를 제공한다.</p>
<table>
<thead>
<tr>
<th align="left">리턴 타입</th>
<th align="left">메소드명(매개변수)</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">void</td>
<td align="left">execute(Runnable command)</td>
<td align="left">- Runnable을 작업 큐에 저장<br>- 작업 처리 결과를 리턴하지 않음</td>
</tr>
<tr>
<td align="left">Future&lt;T&gt;</td>
<td align="left">submit(Callable&lt;T&gt; task)</td>
<td align="left">- Callable을 작업 큐에 저장<br>- 작업 처리 결과를 얻을 수 있도록 Future를 리턴</td>
</tr>
</tbody></table>
<p>Runnable 또는 Callable 객체가 ExecutorService의 작업 큐에 들어가면 ExecutorService는 처리할 스레드가 있는지 보고, 없다면 스레드를 새로 생성시킨다.
스레드는 작업 큐에서 Runnable 또는 Callable 객체를 꺼내와 run() 또는 call() 메소드를 실행하면서 작업을 처리한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 13 - 제네릭 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-13-%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-13-%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Tue, 24 Jan 2023 08:43:10 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-13---제네릭-확인문제">Chapter 13 - 제네릭 확인문제</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/52138b19-b027-4183-b87a-42a92d77bdf8/image.png" alt=""></p>
<h2 id="정답-4">정답: 4</h2>
<p>제네릭 메소드는 리턴 타입으로 타입 파라미터를 가질 수 <strong><code>있다</code></strong>.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/614765ac-7600-4cea-a6e9-5635e05ed0ec/image.png" alt=""></p>
<h2 id="정답">정답:</h2>
<pre><code class="language-java">public class Container&lt;T&gt; {
    private T t;

    public T get() {
        return t;
    }

    public void set(T t) {
        this.t = t;
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/91762c81-b193-4be0-9772-3e12c0ead364/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f668db93-a89e-46f3-8bf3-a684850f50f3/image.png" alt=""></p>
<h2 id="정답-1">정답:</h2>
<pre><code class="language-java">public class Container&lt;K, V&gt; {
    private K key;
    private V value;

    public K getKey() {
        return this.key;
    }

    public V getValue() {
        return this.value;
    }

    public void set(K key, V value) {
        this.key = key;
        this.value = value;
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/57a8442c-74fc-4551-a2d9-be7897bf88c7/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/7b694dc2-a774-412e-8e92-87bf184e5a8e/image.png" alt=""></p>
<h2 id="정답-2">정답:</h2>
<pre><code class="language-java">public static &lt;P extends Pair&lt;K, V&gt;, K, V&gt; V getValue(P p, K k) {
    return p.getKey() == k ? p.getValue() : null;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 13 - 제네릭]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-13-%EC%A0%9C%EB%84%A4%EB%A6%AD</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-13-%EC%A0%9C%EB%84%A4%EB%A6%AD</guid>
            <pubDate>Tue, 24 Jan 2023 08:32:58 GMT</pubDate>
            <description><![CDATA[<h1 id="제네릭이란">제네릭이란?</h1>
<blockquote>
<p>제네릭(Generic)이란 결정되지 않은 타입을 파라미터로 처리하고 실제 사용할 때 파라미터를 구체적인 타입으로 대체시키는 기능을 의미한다.</p>
</blockquote>
<p>다음과 같이 Box 클래스를 선언하려고 한다. Box에 넣을 내용물로 content 필드를 선언하려고 할 때 타입을 무엇으로 해야 할까?</p>
<pre><code class="language-java">public class Box {
    public ? content;
}</code></pre>
<p>Box는 다양한 내용물을 저장해야 하므로 특정 클래스 타입으로 선언할 수 없다.
그래서 다음과 같이 Object 타입으로 선언한다.</p>
<pre><code class="language-java">public class Box {
    public Object content;
}</code></pre>
<p>Object 타입은 모든 클래스의 최상위 부모 클래스이다.
그렇기 때문에 모든 객체는 부모 타입인 Object 타입으로 자동 타입 변환이 되므로 content 필드에는 어떤 객체든 대입이 가능하다.</p>
<pre><code class="language-java">Box box = new Box();
box.content = 모든 객체;</code></pre>
<p>문제는 Box 안의 내용물을 얻을 때이다. content는 Object 타입이므로 어떤 객체가 대입되어 있는지 확실하지 않다.
이때 대입된 내용물의 타입을 안다면 강제 타입 변환을 거쳐 얻을 수 있다.
예를 들어 내용물이 String 타입이라면 (String)으로 강제 타입 변환해서 내용물을 얻는 식이다.</p>
<pre><code class="language-java">String content = (String) box.content;</code></pre>
<p>그러나 어떤 내용물이 저장되어 있는지 모른다면 instanceof 연산자로 타입을 조사할 수는 있지만 모든 종류의 클래스를 대상으로 조사할 수는 없다.
따라서 Object 타입으로 content 필드를 선언하는 것은 좋은 방법이 아니다.</p>
<p>Box를 생성하기 전엔 우리는 어떤 내용물을 넣을지 이미 알고 있다. 따라서 Box를 생성할 때 저장할 내용물의 타입을 미리 알려주면 Box는 content에 무엇이 대입되고, 읽을 때 어떤 타입으로 제공할지를 알게 된다. 이것이 제네릭이다.</p>
<p>다음은 Box 클래스에서 결정되지 않은 content의 타입을 T라는 타입 파라미터로 정의한 것이다.</p>
<pre><code class="language-java">public class Box &lt;T&gt; {
    public T content;
}</code></pre>
<p><code>&lt;T&gt;</code>는 T가 타입 파라미터임을 뜻하는 기호로, 타입이 필요한 자리에 T를 사용할 수 있음을 알려주는 역할을 한다.
여기에서 Box 클래스는 T를 content 필드의 타입으로 사용하였다.
즉, Box 클래스는 T가 무엇인지는 모르지만, Box 객체가 생성될 시점에 다른 타입으로 대체된다는 것을 알고 있다.</p>
<p>만약 Box의 내용물로 String을 저장하고 싶다면 다음과 같이 Box를 생성할 떄 타입 파라미터 T 대신 String으로 대체하면 된다.</p>
<pre><code class="language-java">Box&lt;String&gt; box = new Box&lt;String&gt;();
box.content = &quot;안녕하세요&quot;;
String content = box.content; // 강제 타입 변환 필요 없이 &quot;안녕하세요&quot;를 바로 얻을 수 있음.</code></pre>
<p><code>&lt;T&gt;</code>에서 타입 파라미터로 쓰이는 T는 단지 이름일 뿐이기 때문에 T 대신 A부터 Z까지 어떤 알파벳을 사용해도 좋다. 주의할 점은 타입 파라미터를 대체하는 타입은 클래스 및 인터페이스라는 것이다.</p>
<p>그리고 변수를 선언할 때와 동일한 타입으로 호출하고 싶다면 생성자 호출 시 생성자에는 타입을 명시하지 않고 &lt;&gt;만 붙일 수 있다.</p>
<pre><code class="language-java">Box&lt;String&gt; box = new Box&lt;String&gt;(); -&gt; Box&lt;String&gt; box = new Box&lt;&gt;();
Box&lt;Integer&gt; box = new Box&lt;Integer&gt;(); -&gt; Box&lt;Integer&gt; box = new Box&lt;&gt;();</code></pre>
<h1 id="제네릭-타입">제네릭 타입</h1>
<blockquote>
<p>제네릭 타입은 결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다. 제네릭 타입은 선언부에 &#39;&lt;&gt;&#39;부호가 붙고 그 사이에 타입 파라미터들이 위치한다.</p>
</blockquote>
<pre><code class="language-java">public class 클래스명&lt;A, B, ...&gt; {...}
public interface 인터페이스명&lt;A, B, ...&gt; {...}</code></pre>
<p>타입 파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만 일반적으로 대문자 알파벳 한 글자로 표현한다.
외부에서 제네릭 타입을 사용하려면 타입 파라미터에 구체적인 타입을 지정해야 한다.
만약 지정하지 않으면 암묵적으로(implicitly) Object 타입이 사용된다.</p>
<p>다음 예제는 Product 클래스를 제네릭 타입으로 선언한다.
kind와 model필드를 타입 파라미터로 선언하고, Getter의 매개변수와 Setter의 리턴 타입 역시 타입 파라미터로 선언한다.
이렇게 타입 파라미터를 사용하는 이유는 Product에 다양한 종류의 모델 제품을 저장하기 위해서이다.</p>
<pre><code class="language-java">package ch13.sec02.exam01;

// 제네릭 타입
public class Product&lt;K, M&gt; {
    // 필드
    private K kind;
    private M model;

    // 메소드
    public K getKind() { return this.kind; }
    public M getModel() { return this.model; }
    public void setKind(K kind) { this.kind = kind; }
    public void setModel(M model) { this.model = model; }
}</code></pre>
<pre><code class="language-java">package ch13.sec02.exam01;

public class Tv {
}</code></pre>
<pre><code class="language-java">package ch13.sec02.exam01;

public class Car {
}</code></pre>
<pre><code class="language-java">package ch13.sec02.exam01;

public class GenericExample {
    public static void main(String[] args) {
        // K는 Tv로 대체, M은 String으로 대체
        Product&lt;Tv, String&gt; product1 = new Product&lt;&gt;();

        // Setter 매개값은 반드시 Tv와 String을 제공
        product1.setKind(new Tv());
        product1.setModel(&quot;스마트Tv&quot;);

        // Getter 리턴값은 Tv와 String이 됨
        Tv tv = product1.getKind();
        String tvModel = product1.getModel();
        // -----------------------------------------------
        // K는 Car로 대체, M은 String으로 대체
        Product&lt;Car, String&gt; product2 = new Product&lt;&gt;();

        // Setter 매개값은 반드시 Car와 String을 제공
        product2.setKind(new Car());
        product2.setModel(&quot;SUV자동차&quot;);

        // Getter 리턴값은 Car와 String이 됨
        Car car = product2.getKind();
        String carModel = product2.getModel();
    }
}</code></pre>
<h1 id="제네릭-메소드">제네릭 메소드</h1>
<blockquote>
<p>제네릭 메소드는 타입 파라미터를 가지고 있는 메소드를 말한다.</p>
</blockquote>
<p>타입 파라미터가 메소드 선언부에 정의된다는 점에서 제네릭 타입과 차이가 있다. 제네릭 메소드는 리턴 타입 앞에 <code>&lt;&gt;</code>기호를 추가하고 타입 파라미터를 정의한 뒤, 리턴 타입과 매개변수 타입에서 사용한다.</p>
<pre><code class="language-java">public &lt;A, B, ...&gt; 리턴타입 메소드명(매개변수, ...)  {...}</code></pre>
<p>다음 boxing() 메소드는 타입 파라미터로 <code>&lt;T&gt;</code>를 정의하고 매개변수 타입과 리턴 타입에서 T를 사용한다.
정확한 리턴 타입은 T를 내용물로 갖는 Box 객체이다.</p>
<pre><code class="language-java">public &lt;T&gt; box&lt;T&gt; boxing(T t) {...}</code></pre>
<p>타입 파라미터 T는 매개값이 어떤 타입이냐에 따라 컴파일 과정에서 구체적인 타입으로 대체된다.</p>
<pre><code class="language-java">Box&lt;Integer&gt; box1 = boxing(100); // 1
Box&lt;String&gt; box2 = boxing(&quot;안녕하세요&quot;); // 2</code></pre>
<p>1은 100의 클래스 타입이 Integer이므로 타입 파라미터 T는 Integer로 대체되어 <code>Box&lt;Integer&gt;</code>가 리턴된다.
2는 &quot;안녕하세요&quot;의 클래스 타입이 String이므로 타입 파라미터 T는 String으로 대체되어 <code>Box&lt;String&gt;</code>이 리턴된다.</p>
<p>예제를 보자.</p>
<pre><code class="language-java">package ch13.sec03.exam01;

public class Box&lt;T&gt; {
    // 필드
    private T t;

    // Getter 메소드
    public T get() {
        return t;
    }

    // Setter 메소드
    public void set(T t) {
        this.t = t;
    }
}</code></pre>
<pre><code class="language-java">package ch13.sec03.exam01;

public class GenericExample {
    // 제네릭 메소드
    public static &lt;T&gt; Box&lt;T&gt; boxing(T t) {
        Box&lt;T&gt; box = new Box&lt;T&gt;();
        box.set(t);
        return box;
    }

    public static void main(String[] args) {
        // 제네릭 메소드 호출
        Box&lt;Integer&gt; box1 = boxing(100);
        int intValue = box1.get();
        System.out.println(intValue);

        // 제네릭 메소드 호출
        Box&lt;String&gt; box2 = boxing(&quot;홍길동&quot;);
        String strValue = box2.get();
        System.out.println(strValue);
    }
}</code></pre>
<pre><code>100
홍길동</code></pre><h1 id="제한된-타입-파라미터">제한된 타입 파라미터</h1>
<blockquote>
<p>모든 타입으로 대체할 수 없고, 특정 타입과 자식 또는 구현 관계에 있는 타입만 대체할 수 있는 타입 파라미터를 제한된 타입 파라미터(Bounded Type Parameter)라고 한다.</p>
</blockquote>
<p>경우에 따라서는 타입 파라미터를 대체하는 구체적인 타입을 제한할 필요가 있다.
예를 들어 숫자를 연산하는 제네릭 메소드는 대체 타입으로 Number또는 자식 클래스(Byte, Short, Integer, Long, Double)로 제한할 필요가 있다.</p>
<p>제한된 타입 파라미터는 다음과 같이 정의한다.</p>
<pre><code class="language-java">public &lt;T extends 상위타입&gt; 리턴 타입 메소드(매개변수, ...) {...}</code></pre>
<p>상위 타입은 클래스뿐만 아니라 인터페이스도 가능하다. 인터페이스라고 해서 implements를 사용하지는 않는다.
다음은 Number 타입과 자식 클래스(Byte, Short, Integer, Long, Double)에만 대체 가능한 타입 파라미터를 정의한 것이다.</p>
<pre><code class="language-java">public &lt;T extends Number&gt; boolean compare(T t1, T t2) {
    double v1 = t1.doubleValue() // Number의 doubleValue() 메소드 사용
    double v2 = t2.doubleValue() // Number의 doubleValue() 메소드 사용
    return (v1 == v2);
}</code></pre>
<p>타입 파라미터가 Number 타입으로 제한되면서 Object의 메소드뿐만 아니라 Number가 가지고 있는 메소드도 사용할 수 있다. 위 코드에서 doubleValue() 메소드는 Number 타입에 정의되어 있는 메소드로, double 타입 값을 리턴한다.</p>
<p>예제를 보자.</p>
<pre><code class="language-java">package ch13.sec04;

public class GenericExample {
    // 제한된 타입 파라미터를 갖는 제네릭 메소드
    public static &lt;T extends Number&gt; boolean compare(T t1, T t2) {
        // T의 타입을 출력
        System.out.println(&quot;compare(&quot; + t1.getClass().getSimpleName() + &quot;, &quot; +
                t2.getClass().getSimpleName() + &quot;)&quot;);

        // Number의 메소드 사용
        double v1 = t1.doubleValue();
        double v2 = t2.doubleValue();

        return (v1 == v2);
    }

    public static void main(String[] args) {
        // 제네릭 메소드 호출
        boolean result1 = compare(10, 20);
        System.out.println(result1);
        System.out.println();

        // 제네릭 메소드 호출
        boolean result2 = compare(4.5, 4.5);
        System.out.println(result2);
    }
}</code></pre>
<pre><code>compare(Integer, Integer)
false

compare(Double, Double)
true</code></pre><h1 id="와일드카드-타입-파라미터">와일드카드 타입 파라미터</h1>
<blockquote>
<p>제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터로 ?(와일드카드)를 사용할 수 있다.
?는 범위에 있는 모든 타입으로 대체될 수 있다는 표시이다.</p>
</blockquote>
<p>예를 들어 다음과 같은 상속 관계가 있다고 가정해보자
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5e6a92f2-a00b-49ce-8b54-c7dd7ac7b366/image.png" alt="">
타입 파라미터의 대체 타입으로 Student와 자식 클래스인 HighStudent와 MiddleStudent만 가능하도록 매개변수를 다음과 같이 선언할 수 있다.</p>
<pre><code class="language-java">리턴타입 메소드명(제네릭타입&lt;? extends Student&gt; 변수) {...}</code></pre>
<p>반대로 Worker와 부모 클래스인 Person만 가능하도록 매개변수를 다음과 같이 선언할 수 있다.</p>
<pre><code class="language-java">리턴타입 메소드명(제네릭타입&lt;? super Worker&gt; 변수) {...}</code></pre>
<p>다음과 같이 어떤 타입이든 가능하도록 매개변수를 선언할 수도 있다.</p>
<pre><code class="language-java">리턴타입 메소드명(제네릭타입&lt;?&gt; 변수) {...}</code></pre>
<p>다음 예제에서 Course 클래스의 메소드 registerCourse1()은 모든 사람이 들을 수 있는 과정을 등록하고, registerCourse2()는 학생만 들을 수 있는 과정을 등록한다. 그리고 registerCourse3()은 직장인과 일반인만 들을 수 있는 과정을 등록한다.</p>
<pre><code class="language-java">package ch13.sec05;

public class Person {
}

class Worker extends Person {
}

class Student extends Person {
}

class HighStudent extends Student {
}

class MiddleStudent extends Student{
}</code></pre>
<pre><code class="language-java">package ch13.sec05;

public class Applicant&lt;T&gt; {
    public T kind;

    public Applicant(T kind) {
        this.kind = kind;
    }
}</code></pre>
<pre><code class="language-java">package ch13.sec05;

public class Course {
    // 모든 사람이면 등록 가능
    public static void registerCourse1(Applicant&lt;?&gt; applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() +
                &quot;이(가) Course1을 등록함&quot;);
    }

    // 학생만 등록 가능
    public static void registerCourse2(Applicant&lt;? extends Student&gt; applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() + 
                &quot;이(가) Course2를 등록함&quot;);
    }

    // 직장인 및 일반인만 등록 가능
    public static void registerCourse3(Applicant&lt;? super Worker&gt; applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() +
                &quot;이(가) Course3을 등록함&quot;);
    }
}</code></pre>
<pre><code class="language-java">package ch13.sec05;

public class GenericExample {
    public static void main(String[] args) {
        // 모든 사람이 신청 가능
        Course.registerCourse1(new Applicant&lt;Person&gt;(new Person()));
        Course.registerCourse1(new Applicant&lt;Worker&gt;(new Worker()));
        Course.registerCourse1(new Applicant&lt;Student&gt;(new Student()));
        Course.registerCourse1(new Applicant&lt;HighStudent&gt;(new HighStudent()));
        Course.registerCourse1(new Applicant&lt;MiddleStudent&gt;(new MiddleStudent()));
        System.out.println();

        // 학생만 신청 가능
        // Course.registerCourse2(new Applicant&lt;Person&gt;(new Person())); (x)
        // Course.registerCourse2(new Applicant&lt;Worker&gt;(new Worker())); (x)
        Course.registerCourse2(new Applicant&lt;Student&gt;(new Student()));
        Course.registerCourse2(new Applicant&lt;HighStudent&gt;(new HighStudent()));
        Course.registerCourse2(new Applicant&lt;MiddleStudent&gt;(new MiddleStudent()));
        System.out.println();

        // 직장인 및 일반인만 신청 가능
        Course.registerCourse3(new Applicant&lt;Person&gt;(new Person()));
        Course.registerCourse3(new Applicant&lt;Worker&gt;(new Worker()));
        // Course.registerCourse3(new Applicant&lt;Student&gt;(new Student()));         (x)
        // Course.registerCourse3(new Applicant&lt;HighStudent&gt;(new HighStudent()));     (x)
        // Course.registerCourse3(new Applicant&lt;MiddleStudent&gt;(new MiddleStudent()));     (x)
    }
}</code></pre>
<pre><code>Person이(가) Course1을 등록함
Worker이(가) Course1을 등록함
Student이(가) Course1을 등록함
HighStudent이(가) Course1을 등록함
MiddleStudent이(가) Course1을 등록함

Student이(가) Course2를 등록함
HighStudent이(가) Course2를 등록함
MiddleStudent이(가) Course2를 등록함

Person이(가) Course3을 등록함
Worker이(가) Course3을 등록함</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 12 - java.base 모듈 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-12-java.base-%EB%AA%A8%EB%93%88-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-12-java.base-%EB%AA%A8%EB%93%88-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Fri, 20 Jan 2023 06:22:23 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-12---javabase-모듈">Chapter 12 - java.base 모듈</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/23be506c-a7d6-4e2b-8633-64855834244b/image.png" alt=""></p>
<h2 id="정답-4">정답: 4</h2>
<p>private 접근 제한을 가지는 멤버는 확인할 수 없다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/fdd5bc11-6403-4bac-9832-4aa7fff93f06/image.png" alt=""></p>
<h2 id="정답-3">정답: 3</h2>
<p>java.sql은 java.base의 패키지에 없다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/a5ad5b21-dc9d-42cf-b43e-373f32403b4b/image.png" alt=""></p>
<h2 id="정답-4-1">정답: 4</h2>
<p>Object의 toString() 메소드는 클래스명@16진수해시코드로 구성된 문자열을 리턴한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/dae48f22-523f-4d81-abf5-b97c3d6957cd/image.png" alt=""></p>
<h2 id="정답">정답:</h2>
<pre><code>hashCode</code></pre><pre><code>equals</code></pre><p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/65e54767-8a80-4f04-860c-be546799864a/image.png" alt=""></p>
<h2 id="정답-1">정답:</h2>
<pre><code class="language-java">@Override
public boolean equals(Object obj) {
    if (obj instanceof Student) {
        Student student = (Student) obj;
        if (studentNum.equals(student.getStudentNum())) {
            return true;
        }
    }
    return false;
}

@Override
public int hashCode() {
    return studentNum.hashCode();
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/cf655560-20a3-49c1-9dea-323b0dab1602/image.png" alt=""></p>
<h2 id="정답-2">정답:</h2>
<pre><code class="language-java">@Override
public String toString() {
    return id + &quot;: &quot; + name;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/fdf1249d-1468-4e22-996a-0726a755c8b4/image.png" alt=""></p>
<h2 id="정답-3-1">정답: 3</h2>
<p>milisTime -&gt; currentTimeMilis
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/1d70ac4f-a8c7-4216-9a8a-87c008b1903c/image.png" alt=""></p>
<h2 id="정답-5">정답:</h2>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        long startTime = System.nanoTime();

        int[] scores = new int[1000];
        for (int i = 0; i &lt; scores.length; i++) {
            scores[i] = i;
        }

        int sum = 0;
        for (int score : scores) {
            sum += score;
        }

        double avg = (double) sum / scores.length;
        System.out.println(avg);

        System.out.println((System.nanoTime() - startTime) + &quot; ns&quot;);
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/7240039a-037e-4948-8050-d451e8bbcdf2/image.png" alt=""></p>
<pre><code class="language-java">new String(bytes, &quot;UTF-8&quot;)</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/4e2cb399-13e8-43fd-acae-d6716bfca754/image.png" alt=""></p>
<pre><code class="language-java">public class StringBuilderExample {
    public static void main(String[] args) {
        String str = &quot;&quot;;
        StringBuilder sb = new StringBuilder();

        for (int i=1; i&lt;=100; i++) {
            sb.append(i);
        }

        str = sb.toString();
        System.out.println(str);
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/22539d9d-7e7e-4f97-b747-b32f86a30a2d/image.png" alt=""></p>
<pre><code class="language-java">import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) {
        String str = &quot;아이디,이름,패스워드&quot;;
        StringTokenizer st = new StringTokenizer(str, &quot;,&quot;);

        while(st.hasMoreTokens()) {
            String token = st.nextToken();
            System.out.println(token);
        }
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/55be981a-e725-45ad-b06b-b44a75d5b12e/image.png" alt=""></p>
<pre><code class="language-java">public class IntegerCompareExample {
    public static void main(String[] args) {
        Integer obj1 = 100;
        Integer obj2 = 100;
        Integer obj3 = 300;
        Integer obj4 = 300;

        System.out.println(obj1.equals(obj2));
        System.out.println(obj3.equals(obj4));
    }
}</code></pre>
<p>포장 객체의 효율적인 사용을 위해 다음 범위의 값을 갖는 포장 객체는 공유된다.
이 범위의 값을 갖는 포장 객체는 ==와 != 연산자로 비교할 수 있지만, 내부 값을 비교하는 것이 아니라 객체의 주소를 비교한다는 것을 알아야 한다.</p>
<table>
<thead>
<tr>
<th align="left">타입</th>
<th align="left">값의 범위</th>
</tr>
</thead>
<tbody><tr>
<td align="left">boolean</td>
<td align="left">true, false</td>
</tr>
<tr>
<td align="left">char</td>
<td align="left">\u0000 ~ \u007f</td>
</tr>
<tr>
<td align="left">byte, short, int</td>
<td align="left">-128 ~ 127</td>
</tr>
</tbody></table>
<p>따라서 obj1과 obj2는 같은 객체를 공유하기 때문에 == 연산자의 결과가 true이고, obj3과 obj4는 false인 것이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/92ae0d0f-e3f2-46f1-8dd9-dc20c47ca776/image.png" alt=""></p>
<h2 id="정답-4-2">정답: 4</h2>
<p>Math.round(5.7) -&gt; 6
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/7f72be46-53da-43c1-bfa7-8eb8d6e7bbca/image.png" alt=""></p>
<h2 id="정답-4-3">정답: 4</h2>
<p>Random의 nextInt(int n) 메소드는 0&lt;=...<code>&lt;</code> 사이의 정수 난수를 리턴한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/85388961-d045-40dd-a0a3-090f62c0fa74/image.png" alt=""></p>
<pre><code class="language-java">import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class Main {
    public static void main(String[] args) {
        System.out.println(LocalDateTime.now().until(LocalDateTime.of(LocalDateTime.now().getYear(), 12, 31, 0, 0, 0), ChronoUnit.DAYS));
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/9d686a8b-c3dc-4814-aea1-165888b8a930/image.png" alt=""></p>
<pre><code class="language-java">import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        System.out.println(new SimpleDateFormat(&quot;yyyy년 MM월 dd일 E요일 HH시 mm분&quot;).format(new Date()));
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/2670b768-df3c-45df-a395-8c4ccb990efb/image.png" alt=""></p>
<pre><code>&quot;[a-zA-Z][a-zA-Z0-9]{7,11}&quot;</code></pre><pre><code class="language-java">Pattern.matches(regExp, id)</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/07547a09-e58d-4434-b1b0-489f651c3d5b/image.png" alt=""></p>
<h2 id="정답-4-4">정답: 4</h2>
<p>getResource() 메소드는 class 파일의 경로를 기준으로 리소스의 URL을 리턴한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/1e7a7395-42af-4f7b-8247-7b589156971d/image.png" alt=""></p>
<h2 id="정답-4-5">정답: 4</h2>
<p>value 속성과 다른 속성의 값을 동시에 주고 싶다면 value 속성 이름은 반드시 언급해야 한다.</p>
<pre><code class="language-java">@AnnotationName(value=&quot;*&quot;, prop=3);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 12 - java.base 모듈]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-12-java.base-%EB%AA%A8%EB%93%88</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-12-java.base-%EB%AA%A8%EB%93%88</guid>
            <pubDate>Fri, 20 Jan 2023 05:35:40 GMT</pubDate>
            <description><![CDATA[<h1 id="api-도큐먼트">API 도큐먼트</h1>
<blockquote>
<p>자바 표준 모듈에서 제공하는 라이브러리는 방대하기 때문에 쉽게 사용할 수 있도록 도와주는 <a href="https://ko.wikipedia.org/wiki/API">API(Application Programming Interface)</a> 도큐먼트가 있다. 라이브러리가 클래스와 인터페이스의 집합이라면, API 도큐먼트는 이를 사용하기 위한 방법을 기술한 것이다.</p>
</blockquote>
<p>다음 URL을 방문하면 JDK 버전별로 사용할 수 있는 API 도큐먼트를 볼 수 있다.
<a href="https://docs.oracle.com/en/java/javase/index.html">https://docs.oracle.com/en/java/javase/index.html</a></p>
<h1 id="javabase-모듈">java.base 모듈</h1>
<blockquote>
<p>java.base는 모든 모듈이 의존하는 기본 모듈로, 모듈 중 유일하게 requires하지 않아도 사용할 수 있다.
이 모듈에 포함되어 있는 패키지는 대부분의 자바 프로그램에서 많이 사용하는 것들이다.</p>
</blockquote>
<p>다음은 java.base 모듈에 포함된 주요 패키지와 용도를 설명한 표이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f61c090d-41cd-4395-853c-f96e19ee0675/image.png" alt="">
우리가 지금까지 사용한 String, System, Integer, Exception 등의 클래스는 java.lang 패키지에 있고, 키보드 입력을 위해 사용한 Scanner는 java.util 패키지에 있다.</p>
<p>java.lang은 자바 언어의 기본적인 클래스를 담고 있는 패키지로, 이 패키지에 있는 클래스와 인터페이스는 import 없이 사용할 수 있다.</p>
<p>다음은 java.lang 패키지에 포함된 주요 클래스와 용도를 설명한 표이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/957359a6-51a8-4642-b216-fb62ec4f7121/image.png" alt=""></p>
<h1 id="object-클래스">Object 클래스</h1>
<blockquote>
<p>클래스를 선언할 때 extends 키워드로 다른 클래스를 상속하지 않으면 암시적으로 java.lang.Object 클래스를 상속하게 된다. 따라서 자바의 모든 클래스는 Object의 자식이거나 자손 클래스이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/42402d14-2469-4dcf-9746-c8d50dc89e61/image.png" alt="">
그렇기 때문에 Object가 가진 메소드는 모든 객체에서 사용할 수 있다.
다음은 Object가 가진 주요 메소드를 설명한 표이다.</p>
<table>
<thead>
<tr>
<th align="left">메소드</th>
<th align="left">용도</th>
</tr>
</thead>
<tbody><tr>
<td align="left">boolean equals(Object obj)</td>
<td align="left">객체의 주소를 비교하고 결과를 리턴</td>
</tr>
<tr>
<td align="left">int hashCode()</td>
<td align="left">객체의 해시코드를 리턴</td>
</tr>
<tr>
<td align="left">String toString()</td>
<td align="left">객체 문자 정보를 리턴</td>
</tr>
</tbody></table>
<h2 id="객체-동등-비교">객체 동등 비교</h2>
<p>Object의 euqals() 메소드는 객체의 주소를 비교하고 boolean 값을 리턴한다.</p>
<pre><code class="language-java">public boolean equals(Object obj)</code></pre>
<p>equals() 메소드의 매개변수 타입이 Object이므로 자동 타입 변환에 의해 모든 객체가 매개값으로 대입될 수 있다. equals() 메소드는 비교 연산자인 ==과 동일한 결과를 리턴한다.
두 객체가 동일한 객체라면 true를 리턴하고, 그렇지 않으면 false를 리턴한다.</p>
<p>일반적으로 Object의 equals() 메소드는 재정의해서 동등 비교용으로 사용된다.
동등 비교란 객체가 비록 달라도 내부의 데이터가 같은지를 비교하는 것을 말한다.</p>
<p>예를 들어 String은 equals() 메소드를 재정의해서 내부 문자열이 같은지를 비교한다.</p>
<h2 id="객체-해시코드">객체 해시코드</h2>
<p>객체 해시코드란 객체를 식별하는 정수를 말한다.
Object의 hashCode() 메소드는 객체의 메모리 주소를 이용해서 해시코드를 생성하기 때문에 객체마다 다른 정수값을 리턴한다.
hashCode() 메소드의 용도는 equals() 메소드와 비슷한데, 두 객체가 동등한지를 비교할 때 주로 사용한다.</p>
<pre><code class="language-java">public int hashCode()</code></pre>
<p>equals() 메소드와 마찬가지로 hasCode() 메소드 역시 객체의 데이터를 기준으로 재정의해서 새로운 정수값을 리턴하도록 하는 것이 일반적이다.</p>
<p>객체가 다르다 할지라도 내부 데이터가 동일하다면 같은 정수값을 리턴하기 위해서이다.</p>
<p>자바는 두 객체가 동등함을 비교할 때 hashCode()와 equals() 메소드를 같이 사용하는 경우가 많다.</p>
<p>우선 hashCode()가 리턴하는 정수값이 같은지를 확인하고,  그 다음 equals() 메소드가 true를 리턴하는지를 확인해서 동등 객체임을 판단한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/4ac92cca-10a4-4200-9f9c-f476575e4ef8/image.png" alt=""></p>
<h2 id="객체-문자-정보">객체 문자 정보</h2>
<p>Object의 toString() 메소드는 객체의 문자 정보를 리턴한다. 객체의 문자 정보란 객체를 문자열로 표현한 값을 말한다.</p>
<p>기본적으로 Object의 toString() 메소드는 <code>클래스명@16진수해시코드</code>로 구성된 문자열을 리턴한다.</p>
<pre><code class="language-java">Object obj = new Object();
System.out.println(obj.toString());</code></pre>
<pre><code>java.lang.Object@de6ced</code></pre><p>객체의 문자 정보가 중요한 경우에는 Object의 toString() 메소드를 재정의해서 간결하고 유익한 정보를 리턴하도록 해야 한다.</p>
<h2 id="레코드-선언">레코드 선언</h2>
<p>데이터 전달을 위한 <a href="https://ko.wikipedia.org/wiki/%EB%8D%B0%EC%9D%B4%ED%84%B0_%EC%A0%84%EC%86%A1_%EA%B0%9D%EC%B2%B4">DTO(Data Transfer Object)</a>를 작성할 때 반복적으로 사용되는 코드를 줄이기 위해 Java 14부터 레코드가 도입되었다.</p>
<p>class 키워드 대신에 record 키워드를 사용하고 클래스 이름 뒤에 괄호를 작성해서 저장할 데이터의 종류를 변수로 선언한다.</p>
<pre><code class="language-java">public record Person(String name, int age) {
}</code></pre>
<p>이렇게 선언된 레코드 소스를 컴파일하면 변수의 타입과 이름을 이용해서 private final 필드가 자동 생성되고, 생성자 및 Getter 메소드가 자동으로 추가된다. 그리고 hashCode(), equals(), toString() 메소드를 재정의한 코드도 자동으로 추가된다.</p>
<h1 id="system-클래스">System 클래스</h1>
<blockquote>
<p>자바 프로그램은 운영체제상에서 바로 실행되는 것이 아니라 자바 가상 머신(JVM) 위에서 실행된다. 따라서 운영체제의 모든 기능을 자바 코드로 직접 접근하기란 어렵다. 하지만 java.lang 패키지에 속하는 System 클래스를 이용하면 운영체제의 일부 기능을 이용할 수 있다.</p>
</blockquote>
<p>System 클래스의 정적 필드와 메소드를 이용하면 프로그램 종료, 키보드 입력, 콘솔 출력, 현재 시간 읽기, 시스템 프로퍼티 읽기 등이 가능하다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/d8386e8d-3443-4dcf-9ce4-54f82275c921/image.png" alt=""></p>
<h2 id="콘솔-출력">콘솔 출력</h2>
<p>out 필드를 이용하면 콘솔에 원하는 문자열을 출력할 수 있다.
err 필드도 out 필드와 동일한데, 차이점은 콘솔 종류에 따라 에러 내용이 빨간색으로 출력된다는 것이다.</p>
<h2 id="키보드-입력">키보드 입력</h2>
<p>자바는 키보드로부터 입력된 키를 읽기 위해 System 클래스에서 in 필드를 제공한다.
다음과 같이 in 필드를 이용해서 read() 메소드를 호출하면 입력된 키의 코드값을 얻을 수 있다.</p>
<pre><code class="language-java">int keyCode = System.in.read();</code></pre>
<p>키 코드는 각 키에 부여되어 있는 번호로, 다음과 같다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/9f01c919-e53d-49e1-8de4-fa0bba4ecbce/image.png" alt=""></p>
<p>read() 메소드는 호출과 동시에 키 코드를 읽는 것이 아니라, Enter 키를 누르기 전까지는 대기 상태이다가 Enter 키를 누르면 입력했던 키들을 하나씩 읽기 시작한다.</p>
<p>단, read() 메소드는 IOException을 발생시킬 수 있는 코드이므로 예외 처리가 필요하다.</p>
<h2 id="프로세스-종료">프로세스 종료</h2>
<p>운영체제는 실행 중인 프로그램을 프로세스로 관리한다. 자바 프로그램을 시작하면 JVM 프로세스가 생성되고, 이 프로세스가 main() 메소드를 호출한다. 프로세스를 강제 종료하고 싶다면 System.exit() 메소드를 사용한다.</p>
<p>exit() 메소드는 매개값이 필요한데, 이 값을 종료 상태값이라고 한다.
종료 상태값으로 어떤 값을 주더라도 프로세스는 종료되는데 정상 종료일 경우 0, 비 정상 종료는 1 또는 -1로 주는 것이 관례이다.</p>
<h2 id="진행-시간-읽기">진행 시간 읽기</h2>
<p>System 클래스의 currentTimeMillis() 메소드와 nanoTime() 메소드는 1970년 1월 1일 0시부터 시작해서 현재까지 진행된 시간을 리턴한다.</p>
<table>
<thead>
<tr>
<th align="left">메소드</th>
<th align="left">용도</th>
</tr>
</thead>
<tbody><tr>
<td align="left">long currentTimeMilis()</td>
<td align="left">1/1000 초 단위로 진행된 시간을 리턴</td>
</tr>
<tr>
<td align="left">long nanoTime()</td>
<td align="left">1/10<sup>9</sup> 초 단위로 진행된 시간을 리턴</td>
</tr>
</tbody></table>
<p>이 두 메소드는 프로그램 처리 시간을 측정하는 데 주로 사용된다.
프로그램 처리를 시작할 때 한 번, 끝날 때 한 번 읽어서 그 차이를 구하면 프로그램 처리 시간이 나온다.</p>
<h2 id="시스템-프로퍼티-읽기">시스템 프로퍼티 읽기</h2>
<p>시스템 프로퍼티(System Property)란 자바 프로그램이 시작될 때 자동 설정되는 시스템의 속성을 말한다.
예를 들어 운영체제 종류 및 사용자 정보, 자바 버전 등의 기본 사용 정보가 해당한다.</p>
<p>다음은 시스템 프로퍼티의 주요 속성 이름(Key)과 값(Value)에 대해 설명한 것이다.</p>
<table>
<thead>
<tr>
<th align="left">속성 이름(Key)</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">java.specification.version</td>
<td align="left">자바 스펙 버전</td>
</tr>
<tr>
<td align="left">java.home</td>
<td align="left">JDK 디렉토리 경로</td>
</tr>
<tr>
<td align="left">os.name</td>
<td align="left">운영체제</td>
</tr>
<tr>
<td align="left">user.name</td>
<td align="left">사용자 이름</td>
</tr>
<tr>
<td align="left">user.home</td>
<td align="left">사용자 홈 디렉토리 경로</td>
</tr>
<tr>
<td align="left">user.dir</td>
<td align="left">현재 디렉토리 경로</td>
</tr>
</tbody></table>
<h1 id="문자열-클래스">문자열 클래스</h1>
<blockquote>
<p>자바에서 문자열과 관련된 주요 클래스에 대해 알아보자</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">클래스</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">String</td>
<td align="left">문자열을 저장하고 조작할 때 사용</td>
</tr>
<tr>
<td align="left">StringBuilder</td>
<td align="left">효율적인 문자열 조작 기능이 필요할 때 사용</td>
</tr>
<tr>
<td align="left">StringTokenizer</td>
<td align="left">구분자로 연결된 문자열을 분리할 때 사용</td>
</tr>
</tbody></table>
<h2 id="string-클래스">String 클래스</h2>
<p>String 클래스는 문자열을 저장하고 조작할 떄 사용한다.
문자열 리터럴은 자동으로 String 객체로 생성되지만, String 클래스의 다양한 생성자를 이용해서 직접 객체를 생성할 수도 있다.</p>
<h2 id="stringbuilder-클래스">StringBuilder 클래스</h2>
<p>String은 내부 문자열을 수정할 수 없다. 다음 코드를 보면 다른 문자열을 결합해서 내부 문자열을 변경하는 것처럼 보이지만, 사실 &quot;ABCEDF&quot;라는 새로운 String 객체를 생성하는 것이다. 그리고 data 객체는 새로 생성된 String 객체를 참조하게 된다.</p>
<pre><code class="language-java">String data = &quot;ABC&quot;;
data += &quot;DEF&quot;;</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/897e7c2a-490f-485e-8bd3-f8536c4dd1c3/image.png" alt="">
문자열의 + 연산은 새로운 String 객체가 생성되고 이전 객체는 계속 버려지는 것이기 때문에 효율이 좋지 않다.</p>
<p>따라서 잦은 문자열 변경 작업을 해야 한다면 StringBuilder를 사용하는 것이 좋다.</p>
<p>StringBuilder는 내부 버퍼에 문자열을 저장해두고 그 안에서 추가, 수정, 삭제 작업을 하도록 설계되어 있다.
따라서 String처럼 새로운 객체를 만들지 않고도 문자열을 조작할 수 있다.</p>
<p>StringBuilder가 제공하는 조작 메소드는 다음과 같다.</p>
<table>
<thead>
<tr>
<th align="left">리턴 타입</th>
<th align="left">메소드(매개변수)</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">StringBuilder</td>
<td align="left">append(기본값</td>
<td align="left">문자열)</td>
</tr>
<tr>
<td align="left">StringBuilder</td>
<td align="left">insert(위치, 기본값</td>
<td align="left">문자열)</td>
</tr>
<tr>
<td align="left">StringBuilder</td>
<td align="left">delete(시작 위치, 끝 위치)</td>
<td align="left">문자열 일부를 삭제</td>
</tr>
<tr>
<td align="left">StringBuilder</td>
<td align="left">replace(시작 위치, 끝 위치, 문자열)</td>
<td align="left">문자열 일부를 대체</td>
</tr>
<tr>
<td align="left">String</td>
<td align="left">toString()</td>
<td align="left">완성된 문자열을 리턴</td>
</tr>
</tbody></table>
<h2 id="stringtokenizer-클래스">StringTokenizer 클래스</h2>
<p>문자열이 구분자(Delimter)로 연결되어 있을 경우, 구분자를 기준으로 문자열을 분리하려면 String의 split() 메소드를 이용하거나 java.util 패키지의 StringTokenizer 클래스를 이용할 수 있다.
split은 정규 표현식으로 구분하고, StringTokenizer는 문자로 구분한다는 차이점이 있다.</p>
<p>StringTokenizer가 제공하는 조작 메소드는 다음과 같다.</p>
<table>
<thead>
<tr>
<th align="left">리턴 타입</th>
<th align="left">메소드(매개변수)</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">int</td>
<td align="left">countTokens()</td>
<td align="left">분리할 수 있는 문자열의 총 수</td>
</tr>
<tr>
<td align="left">boolean</td>
<td align="left">hasMoreTokens()</td>
<td align="left">남아 있는 문자열이 있는지 여부</td>
</tr>
<tr>
<td align="left">String</td>
<td align="left">nextToken()</td>
<td align="left">문자열을 하나씩 가져옴</td>
</tr>
</tbody></table>
<h1 id="포장-클래스">포장 클래스</h1>
<blockquote>
<p>자바는 기본 타입(byte, short, int 등 총 8개의 타입)의 값을 갖는 객체의 생성할 수 있다. 이런 객체를 포장(Wrapper) 객체라고 한다. 값을 포장하고 있다고 해서 붙여진 이름이다.</p>
</blockquote>
<p>포장 객체를 생성하기 위한 클래스는 java.lang 패키지에 포함되어 있는데, char 타입과 int 타입이 각각 Character와 Integer인 것만 제외하고는 기본 타입의 첫 문자를 대문자로 바꾼 이름을 가지고 있다.
<code>e.g. byte -&gt; Byte, float -&gt; Float</code></p>
<p>포장 객체는 포장하고 있는 기본 타입의 값을 변경할 수 없고, 단지 객체로 생성하는 데 목적이 있다.
이런 객체가 필요한 이유는 컬렉션 객체 때문이다. 컬렉션 객체는 기본 타입의 값은 저장할 수 없고, 객체만 저장할 수 있다.</p>
<h2 id="박싱과-언박싱">박싱과 언박싱</h2>
<p>기본 타입의 값을 포장 객체로 만드는 과정을 박싱(Boxing)이라고 하고, 반대로 포장 객체에서 기본 타입의 값을 얻어내는 과정을 언박싱(Unboxing)이라고 한다.</p>
<pre><code class="language-java">Interger obj = 100; // 박싱
int value = obj; // 언박싱</code></pre>
<p>언박싱은 다음과 같이 연산 과정에서도 발생한다. obj는 50과 연산되기 전에 언박싱된다.</p>
<pre><code class="language-java">int value = obj + 50; 언박싱 후 연산</code></pre>
<h2 id="문자열을-기본-타입-값으로-변환">문자열을 기본 타입 값으로 변환</h2>
<p>대부분의 포장 클래스에는 parse + 기본타입 명으로 되어 있는 정적 메소드가 있다.
이 메소드는 문자열을 해당 기본 타입 값으로 변환한다.</p>
<p><code>e.g. Integer.parseInt()</code></p>
<h2 id="포장-값-비교">포장 값 비교</h2>
<p>포장 객체는 내부의 값을 비교하기 위해 ==와 != 연산자가 아닌 equals() 메소드를 사용한다.
포장 객체의 주소가 아닌 내부의 값을 비교해야 하기 때문이다.</p>
<h1 id="수학-클래스">수학 클래스</h1>
<blockquote>
<p>Math 클래스는 수학 계산에 사용할 수 있는 메소드를 제공한다. Math 클래스가 제공하는 메소드는 모두 정적(Static) 이므로 Math 클래스로 객체 생성 없이 바로 사용 가능하다.</p>
</blockquote>
<p>다음은 주요 메소드이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/455a04ad-3e44-4702-b817-a7e3d9717cf8/image.png" alt=""></p>
<h1 id="날짜와-시간-클래스">날짜와 시간 클래스</h1>
<blockquote>
<p>자바는 컴퓨터의 날짜 및 시각을 읽을 수 있도록 java.util 패키지에서 Date와 Calendar 클래스를 제공하고 있다. 또한 날짜와 시간을 조작할 수 있도록 java.time 패키지에서 LocalDateTime 등의 클래스를 제공한다.</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">클래스</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">Date</td>
<td align="left">날짜 정보를 전달하기 위해 사용</td>
</tr>
<tr>
<td align="left">Calendar</td>
<td align="left">다양한 시간대별로 날짜와 시간을 얻을 때 사용</td>
</tr>
<tr>
<td align="left">LocalDateTime</td>
<td align="left">날짜와 시간을 조작할 때 사용</td>
</tr>
</tbody></table>
<h1 id="형식-클래스">형식 클래스</h1>
<blockquote>
<p>Foramt(형식) 클래스는 숫자 또는 날짜를 원하는 형태의 문자열로 변환해주는 기능을 제공한다.
Format 클래스는 java.text 패키지에 포함되어 있는데, 주요 Format 클래스는 다음과 같다.</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">Format 클래스</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">DecimalFormat</td>
<td align="left">숫자를 형식화된 문자열로 변환</td>
</tr>
<tr>
<td align="left">SimpleDateFormat</td>
<td align="left">날짜를 형식화된 문자열로 변환</td>
</tr>
</tbody></table>
<h1 id="정규-표현식-클래스">정규 표현식 클래스</h1>
<blockquote>
<p>문자열리 정해져 있는 형식으로 구성되어 있는지 검증해야 하는 경우가 있다. 예를 들어 이메일이나 전화번호를 사용자가 제대로 입력했는지 검증할 때이다. 자바는 정규 표현식(Regular Expression)을 이용해서 문자열이 올바르게 구성되어 있는지 검증한다.</p>
</blockquote>
<h2 id="pattern-클래스로-검증">Pattern 클래스로 검증</h2>
<p>java.util.regex 패키지의 Pattern 클래스는 정규 표현식으로 문자열을 검증하는 matches() 메소드를 제공한다.</p>
<p>첫 번째 매개값은 정규 표현식이고, 두 번째 매개값은 검증할 문자열이다.
검증한 후의 결과는 boolean 타입으로 리턴된다.</p>
<pre><code class="language-java">boolean result = Pattern.matches(&quot;정규식&quot;, &quot;검증할 문자열&quot;);</code></pre>
<h1 id="리플렉션">리플렉션</h1>
<blockquote>
<p>자바는 클래스와 인터페이스의 메타 정보를 Class 객체로 관리한다. 여기서 메타 정보란 패키지 정보, 타입 정도, 멤버 정보 등을 말한다. 이러한 메타 정보를 프로그램에서 읽고 수정하는 행위를 리플렉션(Reflection)이라고 한다.</p>
</blockquote>
<p>프로그램에서 Class 객체를 얻으려면 다음 3가지 방법 중 하나를 이용하면 된다.</p>
<ol>
<li>Class clazz = 클래스이름.class;</li>
<li>Class clazz = Class.forName(&quot;패키지...클래스이름&quot;);</li>
<li>Class clazz = 객체참조변수.getClass();</li>
</ol>
<h1 id="어노테이션">어노테이션</h1>
<blockquote>
<p>코드에서 @으로 작성되는 요소를 어노테이션(Annotation)이라고 한다. 어노테이션은 클래스 또는 인터페이스를 컴파일하거나 실행할 때 어떻게 처리해야 할 것인지를 알려주는 설정 정보이다.</p>
</blockquote>
<p>어노테이션은 다음 세 가지 용도로 사용된다.</p>
<ol>
<li>컴파일 시 사용하는 정보 전달</li>
<li>빌드 툴이 코드를 자동으로 생성할 때 사용하는 정보 전달</li>
<li>실행 시 특정 기능을 처리할 때 사용하는 정보 전달</li>
</ol>
<p>컴파일 시 사용하는 정보 전달의 대표적인 예는 <code>@Override</code> 어노테이션이다.
<code>@Override</code>는 컴파일러가 메소드 재정의 검사를 하도록 설정한다. 정확히 재정의되지 않았다면 컴파일러는 에러를 발생시킨다.</p>
<p>어노테이션은 자바 프로그램을 개발할 때 필수 요소가 되었다. 웹 개발에 많이 사용되는 Spring Framework 또는 Spring Boot는 다양한 종류의 어노테이션을 사용해서 웹 애플리케이션을 설정한다.</p>
<p>따라서 자바 개발자라면 어노테이션의 사용 방법을 반드시 알아야 한다.</p>
<h2 id="어노테이션-타입의-정의와-적용">어노테이션 타입의 정의와 적용</h2>
<p>어노테이션도 하나의 타입이므로 어노테이션을 사용하기 위해서는 먼저 정의부터 해야 한다.
어노테이션을 정의하는 방법은 인터페이스를 정의하는 것과 유사하다.</p>
<p>다음과 같이 @interface 뒤에 사용할 어노테이션 이름이 온다.</p>
<pre><code class="language-java">public @interface AnnotationName {
}</code></pre>
<p>이렇게 정의한 어노테이션은 코드에서 다음과 같이 사용된다</p>
<pre><code class="language-java">@AnnotationName</code></pre>
<p>어노테이션은 속성을 가질 수 있다. 속성은 타입과 이름으로 구성되며, 이름 뒤에 괄호를 붙인다.
속성의 기본값은 default 키워드로 지정할 수 있다.</p>
<pre><code class="language-java">public @interface AnnotationName {
    String prop1();
    int prop2() default 1;
}</code></pre>
<p>이렇게 정의한 어노테이션은 코드에서 다음과 같이 사용할 수 있다.</p>
<pre><code class="language-java">@AnnotationName(prop1 = &quot;값&quot;, prop2 = 3);
@AnnotationName(prop1 = &quot;값&quot;); // default 값이 있는 prop2는 생략 가능</code></pre>
<p>어노테이션은 기본 속성인 value를 다음과 같이 가질 수 있다.</p>
<pre><code class="language-java">public @interface AnnotationName {
    String value();
    int prop2() default 1;
}</code></pre>
<p>value 속성을 가진 어노테이션을 코드에서 사용할 때에는 다음과 같이 값만 기술할 수 있다.
이 값은 value 속성에 자동으로 대입된다.</p>
<pre><code class="language-java">@AnnotationName(&quot;값&quot;);</code></pre>
<p>하지만 value 속성과 다른 속성의 값을 동시에 주고 싶다면 value 속성 이름은 반드시 언급해야 한다.</p>
<pre><code class="language-java">@AnnotationName(value = &quot;값&quot;, prop2 = 3);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 11 - 예외 처리 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-11-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-11-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 19 Jan 2023 09:59:43 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-11---예외-처리-확인문제">Chapter 11 - 예외 처리 확인문제</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/00317960-0d3b-4b8a-a84e-e0c851a28780/image.png" alt=""></p>
<h2 id="정답-4">정답: 4</h2>
<p>사용자 정의 예외를 구현해서 처리할 수도 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/c9f03bcd-887a-4346-9c68-283955cecac6/image.png" alt=""></p>
<h2 id="정답-3">정답: 3</h2>
<p>try 블록에서 return 문을 사용해서 finally 블록은 실행된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/573e7fff-80fe-45b0-9353-a35097be5f64/image.png" alt=""></p>
<h2 id="정답-4-1">정답: 4</h2>
<p><code>throws</code>는 예외를 떠넘기기 위해 사용된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/8a4aeb2e-a20d-4f95-a7c6-33910b1c1011/image.png" alt=""></p>
<h2 id="정답-2">정답: 2</h2>
<p>예외를 호출한 곳으로 떠넘기기 위해 메소드 선언부에 작성되는 것은 <code>throws</code>이다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/27568e4a-a9bd-410b-96d9-7106a126884c/image.png" alt=""></p>
<h2 id="정답-3-1">정답: 3</h2>
<p>상위 예외 객체(Exception)을 하위 예외 객체(ClassNotFoundException) 위에 작성하면 안 된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/eeee1f05-b149-4278-afd7-772e8117d9ae/image.png" alt=""></p>
<h2 id="정답">정답:</h2>
<pre><code>10
숫자로 변환할 수 없음
10
인덱스를 초과했음
10</code></pre><p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b47c7881-4af9-40bc-91ea-b9072689bdf7/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/b45d6954-a4bc-4d42-85ba-2bd6f8f6a371/image.png" alt=""></p>
<pre><code class="language-java">super(message);</code></pre>
<pre><code class="language-java">super(message);</code></pre>
<pre><code class="language-java">throws NotExistIDException, WrongPasswordException</code></pre>
<pre><code class="language-java">throw new NotExistIDException(&quot;아이디가 존재하지 않습니다.&quot;);</code></pre>
<pre><code class="language-java">throw new WrongPasswordException(&quot;패스워드가 틀립니다.&quot;);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/299b3125-3a1d-46d7-980d-376952bda84a/image.png" alt=""><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/0a2462a6-ff07-4618-a008-c3e5e9e08bd6/image.png" alt=""></p>
<h2 id="정답-1">정답:</h2>
<pre><code class="language-java">try(FileWriter fw = new FileWriter(&quot;file.txt&quot;)) {
    fw.write(&quot;Java&quot;);
} catch (IOException e) {
    e.printStackTrace();
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 11 - 예외 처리]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-11-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-11-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Thu, 19 Jan 2023 09:50:07 GMT</pubDate>
            <description><![CDATA[<h1 id="예외와-예외-클래스">예외와 예외 클래스</h1>
<blockquote>
<p>컴퓨터 하드웨어의 고장으로 인해 응용프로그램 실행 오류가 발생하는 것을 자바에서는 에러라고 한다. 프로그램을 아무리 견고하게 만들어도 개발자는 이런 에러에 대처할 방법이 전혀 없다.</p>
</blockquote>
<p>자바에서는 에러 이외에 예외(Exception)라고 부르는 오류가 있다. 예외란 잘못된 사용 또는 코딩으로 이한 오류를 말한다.
예외가 발생되면 프로그램은 곧바로 종료된다는 점에서는 에러와 동일하지만, 예외 처리를 통해 계속 실행 상태를 유지할 수 있다.</p>
<p>예외에는 다음 두 가지가 있다.</p>
<ul>
<li>일반 예외(Checked Exception)<ul>
<li>컴파일러가 예외 처리 코드 여부를 검사하는 예외</li>
</ul>
</li>
<li>실행 예외(Unchecked Exception or Runtime Exception)<ul>
<li>컴파일러가 예외 처리 코드 여부를 검사하지 않는 예외</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/8102a97f-e9e6-47a3-836c-ead07bb273bd/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f10b944e-124a-4612-a4cc-792072baf1d6/image.png" alt=""></p>
<p>자바는 예외가 발생하면 예외 클래스로부터 객체를 생성한다.
이 객체는 예외 처리 시 사용된다.
자바의 모든 에러와 예외 클래스는 <code>Throwable</code>을 상속 받아 만들어지고, 추가적으로 예외 클래스는 <code>java.lang.Exception</code> 클래스를 상속받는다.</p>
<p>실행 예외는 RuntimeException과 그 자식 클래스에 해당한다.</p>
<p>그 밖의 예외 클래스는 모두 일반 예외이다. 자바는 자주 사용되는 예외 클래스를 표준 라이브러리로 제공한다.
앞의 그림에서 언급한 모든 예외 클래스는 표준 라이브러리에서 제공하는 것들이다.</p>
<h1 id="예외-처리-코드">예외 처리 코드</h1>
<blockquote>
<p>예외가 발생했을 때 프로그램의 갑작스러운 종료를 막고 정상 실행을 유지할 수 있도록 처리하는 코드를 예외 처리 코드라고 한다.</p>
</blockquote>
<p>예외 처리 코드는 try-catch-finally 블록으로 구성된다.
try-catch-finally 블록은 생성자 내부와 메소드 내부에서 작성된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/fe28db90-4c01-463a-8c97-91637634b2b6/image.png" alt="">
try 블록에서 작성한 코드가 예외 없이 정상 실행되면 catch 블록은 실행되지 않고 finally 블록이 실행된다.</p>
<p>그러나 try 블록에서 예외가 발생하면 catch 블록이 실행되고 연이어 finally 블록이 실행된다.</p>
<p>예외 발생 여부와 상관없이 finally 블록은 항상 실행된다. 심지어 try 블록과 catch 블록에서 return 문(메소드 종료)을 사용하더라도 finally 블록은 항상 실행된다.</p>
<p>finally 블록은 옵션으로 생략 가능하다.</p>
<p>예제를 보자.</p>
<pre><code class="language-java">package ch11.sec02.exam01;

public class ExceptionHandlingExample1 {
    public static void printLength(String data) {
        int result = data.length();
        System.out.println(&quot;문자 수: &quot; + result);
    }

    public static void main(String[] args) {
        System.out.println(&quot;[프로그램 시작]\n&quot;);
        printLength(&quot;ThisIsJava&quot;);
        printLength(null);
        System.out.println(&quot;[프로그램 종료]&quot;);
    }
}</code></pre>
<p>12행에서 문자열 대신 null을 입력하면 <code>NullPointerException</code>이 발생한다.
<code>NullPointerException</code>은 참조 변수가 null인 상태에서 필드나 메소드에 접근할 경우 발생한다.</p>
<p>예외 처리 코드를 추가해보자</p>
<pre><code class="language-java">package ch11.sec02.exam01;

public class ExceptionHandlingExample2 {
    public static void printLength(String data) {
        try {
            int result = data.length();
            System.out.println(&quot;문자 수: &quot; + result);
        } catch (NullPointerException e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println(&quot;[마무리 실행]\n&quot;);
        }
    }

    public static void main(String[] args) {
        System.out.println(&quot;[프로그램 시작]\n&quot;);
        printLength(&quot;ThisIsJava&quot;);
        printLength(null);
        System.out.println(&quot;[프로그램 종료]&quot;);
    }
}</code></pre>
<p>예외가 발생하면 예외 객체가 catch 선언부의 예외 클래스 변수에 대입된다.
<code>e.getMessage()</code>는 예외가 발생한 이유만 리턴하지만, <code>e.toString()</code>은 다음과 같이 예외의 종류도 리턴한다.</p>
<pre><code class="language-java">java.lang.NullPointerException: Cannot invoke &quot;String.length()&quot; because &quot;data&quot; is null</code></pre>
<p><code>e.printStackTrace()</code>는 예외가 어디서 발생했는지 추적한 내용까지도 출력한다.</p>
<pre><code class="language-java">java.lang.NullPointerException: Cannot invoke &quot;String.length()&quot; because &quot;data&quot; is null
    at ch11.sec02.exam01.ExceptionHandlingExample2.printLength(ExceptionHandlingExample2.java:6)
    at ch11.sec02.exam01.ExceptionHandlingExample2.main(ExceptionHandlingExample2.java:19)</code></pre>
<h1 id="예외-종류에-따른-처리">예외 종류에 따른 처리</h1>
<blockquote>
<p>try 블록에는 다양한 종류의 예외가 발생할 수 있다. 이 경우, 다중 catch를 사용하면 발생하는 예외에 따라 예외 처리 코드를 다르게 작성할 수 있다.</p>
</blockquote>
<p>catch 블록의 예외 클래스는 try 블록에서 발생된 예외의 종류를 말하는데, 해당 타입의 예외가 발생하면 catch 블록이 선택되어 실행된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/5d046cf9-0097-416a-bea7-06f66b6c012f/image.png" alt="">
catch 블록이 여러 개라 할지라도 catch 블록은 단 하나만 실행된다. 그 이유는 try 블록에서 동시다발적으로 예외가 발생하지 않으며, 하나의 예외가 발생하면 즉시 실행을 멈추고 해당 catch 블록으로 이동하기 때문이다.</p>
<p>예제를 보자</p>
<pre><code class="language-java">package ch11.sec03.exam01;

public class ExceptionHandlingExample {
    public static void main(String[] args) {
        String[] array = {&quot;100&quot;, &quot;1oo&quot;};

        for (int i = 0; i &lt;= array.length; i++) {
            try {
                int value = Integer.parseInt(array[i]);
                System.out.println(&quot;array[&quot; + i + &quot;]: &quot; + value);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println(&quot;배열 인덱스가 초과됨: &quot; + e.getMessage());
            } catch (NumberFormatException e) {
                System.out.println(&quot;숫자로 변환할 수 없음: &quot; + e.getMessage());
            }
        }
    }
}</code></pre>
<p>처리해야 할 예외 클래스들이 상속 관계에 있을 때는 하위 클래스 catch 블록을 먼저 작성하고 상위 클래스 catch 블록을 나중에 작성해야 한다.</p>
<p>예외가 발생하면 catch 블록은 위에서부터 차례대로 검사 대상이 되는데, 하위 예외도 상위 클래스 타입이므로 상위 클래스 catch 블록이 먼저 검사 대상이 되면 안 된다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/95f3b2e4-9849-491e-823b-fc5064972506/image.png" alt=""></p>
<pre><code class="language-java">package ch11.sec03.exam02;

public class ExceptionHandlingExample {
    public static void main(String[] args) {
        String[] array = {&quot;100&quot;, &quot;1oo&quot;};

        for (int i = 0; i &lt;= array.length; i++) {
            try {
                int value = Integer.parseInt(array[i]);
                System.out.println(&quot;array[&quot; + i + &quot;]: &quot; + value);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println(&quot;배열 인덱스가 초과됨: &quot; + e.getMessage());
            } catch (Exception e) {
                System.out.println(&quot;실행에 문제가 있습니다.&quot;);
            }
        }
    }
}</code></pre>
<p>두 개 이상의 예외를 하나의 catch 블록으로 동일하게 처리하고 싶을 때가 있다.
이 경우에는 catch 블록에 예외 클래스를 기호 <code>|</code>로 연결하면 된다.</p>
<pre><code class="language-java">package ch11.sec03.exam03;

public class ExceptionHandlingExample {
    public static void main(String[] args) {
        String[] array = {&quot;100&quot;, &quot;1oo&quot;, null, &quot;200&quot;};

        for (int i = 0; i &lt;= array.length; i++) {
            try {
                int value = Integer.parseInt(array[i]);
                System.out.println(&quot;array[&quot; + i + &quot;]: &quot; + value);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println(&quot;배열 인덱스가 초과됨: &quot; + e.getMessage());
            } catch (NullPointerException | NumberFormatException e) {
                System.out.println(&quot;데이터에 문제가 있음: &quot; + e.getMessage());
            }
        }
    }
}    </code></pre>
<h1 id="리소스-자동-닫기">리소스 자동 닫기</h1>
<blockquote>
<p>리소스(Resource)란 데이터를 제공하는 객체를 말한다. 리소스는 사용하기 위해 열어야(open) 하며, 사용이 끝난 다음에는 닫아야(close) 한다.</p>
</blockquote>
<p>예를 들어 파일 내용을 읽기 위해서는 파일을 열어야 하며, 다 읽고 난 후에는 파일을 닫아야 다른 프로그램에서 사용할 수 있다.</p>
<p>리소스를 사용하다가 예외가 발생될 경우에도 안전하게 닫는 것이 중요하다. 그렇지 않으면 리소스가 불안정한 상태로 남아있게 된다.</p>
<p>다음 코드는 <code>file.txt</code> 파일의 내용을 읽기 위해 FileInputStream 리소스를 사용하는데, 예외 발생 여부와 상관없이 finally 블록에서 안전하게 close한다.</p>
<pre><code class="language-java">FileInputStream fis = null;
try {
    fis = new FileInputStream(&quot;file.txt&quot;);
    ...
} catch(IOException e) {
    ...
} fianlly {
    fis.close();
}</code></pre>
<p>좀 더 쉬운 방법이 있다. try-with-resource 블록을 사용하면 예외 발생 여부와 상관없이 리소스를 자동으로 닫아준다.</p>
<pre><code class="language-java">try (fis = new FileInputStream(&quot;file.txt&quot;)) {
    ...
} catch(IOException e) {
    ...
}</code></pre>
<p>try-with-resource 블록을 사용하기 위해서는 조건이 있다.</p>
<p>리소는 java.lang.AutoCloseable 인터페이스를 구현해서 close() 메소드를 오버라이딩해야 한다.
예를 들어 FileInputStream은 AutoCloseable 인터페이스를 구현해서 close() 메소드를 오버라이딩 하고 있다.
예제를 보자</p>
<pre><code class="language-java">package ch11.sec04;

public class MyResource implements AutoCloseable {
    private final String name;

    public MyResource(String name) {
        this.name = name;
        System.out.println(&quot;[MyResource(&quot; + name + &quot;) 열기]&quot;);
    }

    public String read1() {
        System.out.println(&quot;[MyResource(&quot; + name + &quot;) 읽기]&quot;);
        return &quot;100&quot;;
    }

    public String read2() {
        System.out.println(&quot;[MyResource(&quot; + name + &quot;) 읽기]&quot;);
        return &quot;abc&quot;;
    }

    @Override
    public void close() throws Exception {
        System.out.println(&quot;[MyResource(&quot; + name + &quot;) 닫기]&quot;);
    }
}</code></pre>
<pre><code class="language-java">package ch11.sec04;

public class TryWithResourceExample {
    public static void main(String[] args) {
        try (MyResource res = new MyResource(&quot;A&quot;)) {
            String data = res.read1();
            int value = Integer.parseInt(data);
        } catch (Exception e) {
            System.out.println(&quot;예외 처리: &quot; + e.getMessage());
        }

        System.out.println();

        try (MyResource res = new MyResource(&quot;A&quot;)) {
            String data = res.read2();
            // NumberFormatException 발생
            int value = Integer.parseInt(data);
        } catch (Exception e) {
            System.out.println(&quot;예외 처리: &quot; + e.getMessage());
        }

        System.out.println();

        /*try (
            MyResource res1 = new MyResource(&quot;A&quot;); 
            MyResource res2 = new MyResource(&quot;B&quot;)
        ) {
            String data1 = res1.read1();
            String data2 = res2.read1();
        } catch(Exception e) {
            System.out.println(&quot;예외 처리: &quot; + e.getMessage());
        }*/

        MyResource res1 = new MyResource(&quot;A&quot;);
        MyResource res2 = new MyResource(&quot;B&quot;);
        try (res1; res2) {
            String data1 = res1.read1();
            String data2 = res2.read1();
        } catch (Exception e) {
            System.out.println(&quot;예외 처리: &quot; + e.getMessage());
        }
    }
}</code></pre>
<pre><code>[MyResource(A) 열기]
[MyResource(A) 읽기]
[MyResource(A) 닫기]

[MyResource(A) 열기]
[MyResource(A) 읽기]
[MyResource(A) 닫기]
예외 처리: For input string: &quot;abc&quot;

[MyResource(A) 열기]
[MyResource(B) 열기]
[MyResource(A) 읽기]
[MyResource(B) 읽기]
[MyResource(B) 닫기]
[MyResource(A) 닫기]</code></pre><h1 id="예외-떠넘기기">예외 떠넘기기</h1>
<blockquote>
<p>메소드 내부에서 예외가 발생할 때 try-catch 블록으로 예외를 처리하는 것이 기본이지만, 메소드를 호출한 곳으로 예외를 떠넘길 수도 있다.</p>
</blockquote>
<p>이때 사용하는 키워드가 <code>throws</code>이다. <code>throws</code>는 메소드 선언부 끝에 작성하는데, 떠넘길 예외 클래스를 쉼표로 구분해서 나열하면 된다.</p>
<pre><code class="language-java">리턴타입 메소드명(매개변수, ...) throws 예외클래스1, 예외클래스2, ... {
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/bbee6fa5-b8b6-4dc9-af92-ea13c0d588a1/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/54200044-5d41-4c53-b97b-fdb7bf5c2091/image.png" alt="">
나열해야 할 예외 클래스가 많을 경우에는 throws Exception 또는 throws Throwable 만으로 모든 예외를 간단히 떠넘길 수도 있다.</p>
<pre><code class="language-java">리턴타입 메소드명(매개변수, ...) throws Exception {
}</code></pre>
<p>main() 메소드에서도 throws 키워드를 사용해서 예외를 떠넘길 수 있는데, 결국 JVM이 최종적으로 예외 처리를 하게 된다. JVM은 예외의 내용을 콘솔에 출력하는 것으로 예외 처리를 한다.</p>
<h1 id="사용자-정의-예외">사용자 정의 예외</h1>
<blockquote>
<p>은행의 뱅킹 프로그램에서 잔고보다 더 많은 출금 요청이 들어온 경우에는 잔고 부족 예외를 발생시킬 필요가 있다.
그러나 잔고 부족 예외는 표준 라이브러리에는 존재하지 않기 때문에 직접 예외 클래스를 정의해서 사용해야 한다.
이것을 사용자 정의 예외라고 한다.</p>
</blockquote>
<h2 id="사용자-정의-예외-1">사용자 정의 예외</h2>
<p>사용자 정의 예외는 일반 예외와 실행 예외로 선언할 수 있다.</p>
<p>통상적으로 일반 예외는 Exception의 자식 클래스로 선언하고, 실행 예외는 RuntimeException의 자식 클래스로 선언한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/f9bf3403-0ca0-4585-86e6-738f0860456a/image.png" alt="">
사용자 정의 예외 클래스에는 기본 생성자와 예외 메시지를 입력받는 생성자를 선언해준다.
예외 메시지는 부모 생성자 매개값으로 넘겨주는데 그 이유는 예외 객체의 공통 메소드인 getMessage()의 리턴값으로 사용하기 위해서이다.</p>
<p>예제를 보자
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/179b9019-e8e0-414e-bd92-89f2d25d81b9/image.png" alt=""></p>
<h2 id="예외-발생-시키기">예외 발생 시키기</h2>
<p>자바에서 제공하는 표준 예외뿐만 아니라 사용자 정의 예외를 직접 코드에서 발생시키려면 throw 키워드와 함께 예외 객체를 제공하면 된다.</p>
<p>예외의 원인에 해당하는 메시지를 제공하고 싶다면 생성자 매개값으로 전달한다.</p>
<pre><code class="language-java">throw new Exception()
throw new RuntimeException()
throw new Exception(&quot;예외메시지&quot;)
throw new RuntimeException(&quot;예외메시지&quot;)</code></pre>
<p>throw된 예외는 직접 try-catch 블록으로 예외를 처리할 수도 있지만(아래 왼쪽), 대부분 메소드를 호출한 곳에서 예외를 처리하도록 throws 키워드로 예외를 떠넘긴다(아래 오른쪽)
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/92e9e364-ae66-4877-b3b4-fa134627588c/image.png" alt=""></p>
<p>예제를 보자</p>
<pre><code class="language-java">package ch11.sec06;

public class Account {
    private long balance;

    public Account() {
    }

    public long getBalance() {
        return balance;
    }

    public void deposit(int money) {
        balance += money;
    }

    public void withdraw(int money) throws InsufficientException {
        if (balance &lt; money) {
            throw new InsufficientException(&quot;잔고 부족: &quot; + (money - balance) + &quot; 모자람&quot;);
        }
        balance -= money;
    }
}</code></pre>
<pre><code class="language-java">package ch11.sec06;

public class InsufficientException extends Exception {
    public InsufficientException() {
    }

    public InsufficientException(String message) {
        super(message);
    }
}</code></pre>
<pre><code class="language-java">package ch11.sec06;

public class AccountExample {
    public static void main(String[] args) {
        Account account = new Account();
        // 예금하기
        account.deposit(10000);
        System.out.println(&quot;예금액: &quot; + account.getBalance());

        // 출금하기
        try {
            account.withdraw(30000);
        } catch (InsufficientException e) {
            String message = e.getMessage();
            System.out.println(message);
        }
    }
}</code></pre>
<pre><code>예금액: 10000
잔고 부족: 20000 모자람</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 10 - 라이브러리와 모듈 확인문제]]></title>
            <link>https://velog.io/@dev-taewon-kim/Chapter-10-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EC%99%80-%EB%AA%A8%EB%93%88-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev-taewon-kim/Chapter-10-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EC%99%80-%EB%AA%A8%EB%93%88-%ED%99%95%EC%9D%B8%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Wed, 18 Jan 2023 09:55:42 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-10---라이브러리와-모듈-확인문제">Chapter 10 - 라이브러리와 모듈 확인문제</h2>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/370691c0-5c13-41dd-96b1-df14d7a760c5/image.png" alt=""></p>
<h2 id="정답-2">정답: 2</h2>
<p>JAR 파일 안에는 클래스 및 인터페이스의 <strong><code>바이트코드</code></strong> 파일이 있다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/9b447d34-3755-4577-9acf-026bb739d79b/image.png" alt=""></p>
<h2 id="정답-3">정답: 3</h2>
<p>모듈은 모듈 기술자가 반드시 존재해야 한다.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/aa18cc83-74c5-4dcf-b364-54dfdb2a6c2b/image.png" alt=""></p>
<h2 id="정답-3-1">정답: 3</h2>
<p>requires를 기술할 때에 exports도 기술할 수 있다.</p>
<pre><code class="language-java">// e.g.
module my_module_a {
    exports pack1;
    requries my_module_b;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-taewon-kim/post/73c3569f-6615-4ef8-bc9d-daad43bcc53a/image.png" alt=""></p>
<h2 id="정답-4">정답: 4</h2>
<p>집합 모듈을 의존 설정할 경우에도 다른 모듈을 의존 설정할 수 <strong><code>있다</code></strong>.
<img src="https://velog.velcdn.com/images/dev-taewon-kim/post/abc8bcd0-a9a2-4aca-883e-5287c42cda41/image.png" alt=""></p>
<h2 id="정답-2-1">정답: 2</h2>
<p><strong><code>java.lang</code></strong> 모듈에 속한 패키지는 import 없이도 사용할 수 있다.</p>
<p>java.lang 패키지 내 클래스들은 다른 패키지들보다 사용 빈도가 높기 때문에 모든 소스 파일에서 암시적으로(implicitly) 다음과 같은 import 문이 선언되어 있기 때문이다.</p>
<pre><code class="language-java">import java.lang.*;</code></pre>
]]></description>
        </item>
    </channel>
</rss>