<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>suyeon-jin.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 12 Jun 2022 07:26:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. suyeon-jin.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/suyeon-jin" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[JAVA 비동기 프로그래밍: CompletableFuture]]></title>
            <link>https://velog.io/@suyeon-jin/JAVA-CompletableFuture</link>
            <guid>https://velog.io/@suyeon-jin/JAVA-CompletableFuture</guid>
            <pubDate>Sun, 12 Jun 2022 07:26:26 GMT</pubDate>
            <description><![CDATA[<p>CompletableFuture를 이해하기 위해서 자바의 Concurrent 프로그래밍부터 짚어볼 필요가 있다.</p>
<h2 id="1-concurrent-programming">1. Concurrent Programming</h2>
<p>Concurrent 소프트웨어는 동시에 여러 작업을 할 수 있는 소프트웨어를 의미한다. 예를 들면, 크롬으로 음악을 틀어두고 문서 작업을 할 수 있는 것처럼..~</p>
<p>자바에서 지원하는 Concurrent 프로그래밍에는 <strong>멀티 프로세싱</strong>과 <strong>멀티스레드</strong>가 있는데, CompletableFuture는 멀티스레드와 관련있으므로 이번에는 멀티스레드에 대해서만 정리하였다.
(cf 스프링 프레임워크는 자바의 멀티쓰레드를 사용한다.)</p>
<h3 id="1-1-thread">1-1. Thread</h3>
<p><strong>Thread를 상속하면</strong> 새로운 Thread 클래스를 새롭게 정의할 수 있다. </p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        YourThread yourThread = new YourThread();
        yourThread.start();

        MyThread myThread = new MyThread();
        myThread.start();

        System.out.println(&quot;메인 쓰레드 : &quot; + Thread.currentThread().getName());
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(&quot;마이 쓰레드 : &quot; + Thread.currentThread().getName());
        }
    }

    static class YourThread extends Thread {
        @Override
        public void run() {
            System.out.println(&quot;유어 쓰레드 : &quot; + Thread.currentThread().getName());
        }
    }
}</code></pre>
<pre><code>메인 쓰레드 : main
마이 쓰레드 : Thread-1
유어 쓰레드 : Thread-0

Process finished with exit code 0</code></pre><p>여러 개의 Thread를 정의할 수 있다. 또한, Thread는 순서가 보장되지 않는다.
코드에서는 YourThread -&gt; MyThread -&gt; main Thread 순으로 호출했지만, 결과는 예상한대로 나오지 않았다. 실행을 할 때마다 매번 호출 순서가 바뀌어 출력된다.</p>
<h3 id="1-2-runnable">1-2. Runnable</h3>
<p>Thread 클래스를 따로 정의하지 않아도, <strong>Runnable 이라는 익명 객체를 통해</strong> 새로운 Thread 객체를 사용할 수 있다.</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(&quot;뉴 쓰레드 : &quot; + Thread.currentThread().getName());
            }
        });
        thread.start();

        System.out.println(&quot;메인 쓰레드 : &quot; + Thread.currentThread().getName());
    }
}</code></pre>
<p>Thread를 상속하는 방법보다 간단하긴 하다. 자바8부터는 람다식을 사용하여 위의 코드를 간추릴 수 있다.</p>
<pre><code class="language-java">        Thread thread = new Thread(() -&gt; {
            System.out.println(&quot;뉴 쓰레드 : &quot; + Thread.currentThread().getName())
        });
        thread.start();</code></pre>
<h2 id="2-executor">2. Executor</h2>
<p>Thread, Runnable과 같은 low level API를 프로그래머가 직접 다루는 것은 현실적으로 어렵다. (수십,수백 개 쓰레드의 자원을 관리하려면..머리가 꽤 아플 것이다.)
그래서 쓰레드를 만들고 관리하는 작업을 애플리케이션에서 분리하여 high level API인 &#39;Executor&#39;에 위임하였다.</p>
<p>주요 인터페이스로는, 1) Executor, 2) ExecutorService, 3) ScheduledExecutorService 가 있다.</p>
<h3 id="2-1-executor">2-1. Executor</h3>
<pre><code class="language-java">Executor executor = Executors.newSingleThreadExecutor(); // single thread
executor.execute(() -&gt; System.out.println(&quot;Thread: &quot; + Thread.currentThread().getName()));</code></pre>
<p>Executor는 쓰레드를 생성하고 처리하는 인터페이스이다. 
execute()라는 메서드만을 가지고 있는데, 쓰레드를 처리할 수는 있지만 종료할 수는 없다. ide에서 실행시켜보면 강제종료하기 전까지 계속 실행되는 것을 확인할 수 있다.</p>
<h3 id="2-2-executorservice">2-2. ExecutorService</h3>
<pre><code class="language-java">ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -&gt; System.out.println(&quot;Thread : &quot; + Thread.currentThread().getName()));
executorService.shutdown();


ExecutorService executorService2 = Executors.newSingleThreadExecutor();
executorService2.submit(() -&gt; {
    try {
        Thread.sleep(30000);
        System.out.println(&quot;Thread2 : &quot; + Thread.currentThread().getName());
        } catch (InterruptedException e) {
              System.out.println(&quot;Call shutdownNow!&quot;);
              e.printStackTrace();
        }
    });
executorService2.shutdownNow();</code></pre>
<p>ExecutorService는 Executor를 상속 받은 인터페이스이다. 쓰레드를 생성하고 처리하고 종료하는 등의 작업을 할 수 있다.
submit()을 사용하면 작업을 ExecutorService가 만든 쓰레드풀에서 처리하고, shutdown()으로 쓰레드 풀을 종료할 수 있다. 
shutdown()을 사용하면 작업이 다 종료될 때까지 기다리고 종료하지만, 작업이 끝나든 말든 지금 당장 종료하고 싶다면 shutdownNow()를 사용하면 된다.</p>
<p>ExecutorService는 Executor를 상속 받았기 때문에 execute()와 submit() 모두 호출이 가능하다.
다만, 두 메서드는 받을 수 있는 인자 타입에 차이가 있다. 
execute()는 Runnable 인터페이스만 인자로 받을 수 있지만, submit()은 Runnable과 Callable 인터페이스 모두 인자로 받을 수 있다.
또한, 두 메서드는 리턴 타입도 다르다.
execute()는 반환 값이 없는 반면, submit()은 Future 객체를 반환한다.
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbts6vD%2FbtrdqiVGLbn%2FmVwuBF30kK5SDnT2NXcZr0%2Fimg.png" alt=""></p>
<blockquote>
<p>출처 <a href="https://www.geeksforgeeks.org/difference-between-executorservice-execute-and-submit-method-in-java/">https://www.geeksforgeeks.org/difference-between-executorservice-execute-and-submit-method-in-java/</a></p>
</blockquote>
<h3 id="2-3-scheduledexecutorservice">2-3. ScheduledExecutorService</h3>
<p>ScheduledExecutorService는 ExecutorService를 상속 받은 인터페이스로 특정 시간 이후에 또는 주기적으로 작업을 실행할 수 있다.</p>
<pre><code class="language-java">ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.schedule(() -&gt; System.out.println(&quot;After 3 seconds...&quot;), 3, TimeUnit.SECONDS);
scheduledExecutorService.shutdown();

ScheduledExecutorService scheduledExecutorService2 = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService2.scheduleAtFixedRate(() -&gt; System.out.println(&quot;Period is 2 seconds...&quot;), 3, 2, TimeUnit.SECONDS);
Thread.sleep(5000);
scheduledExecutorService2.shutdown();</code></pre>
<h2 id="3-callable">3. Callable</h2>
<p>Runnable은 반환 값이 void 이기 때문에, 작업을 처리하는 것 뿐 작업 결과를 리턴할 수 없었다.
Callable은 Runnable과 유사하지만, 작업의 결과(Future 객체)를 받을 수 있다는 차이가 있다. </p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Callable&lt;String&gt; hello = () -&gt; {
          Thread.sleep(2000);
          return &quot;hello&quot;;
        };

        Future&lt;String&gt; result = executorService.submit(hello);

        System.out.println(&quot;Started!&quot;);
        result.get(); // 블로킹
        System.out.println(&quot;End!&quot;);
        executorService.shutdown();
    }
}</code></pre>
<p>Future의 get()은 작업 결과를 리턴하는 함수인데, 블로킹 콜이라 리턴될 때까지 리소스를 잡고 있는다. 결과를 최대한으로 기다릴 시간(타임아웃)을 설정할 수 있다.</p>
<p>Future의 isDone()함수로는 작업 상태를 확인할 수 있다. 작업이 완료되었으면 true, 아니면 false를 리턴한다.</p>
<pre><code class="language-java">future.isDone();</code></pre>
<p>Future의 cancel()함수를 사용하면 해당 스레드의 작업을 취소할 수 있다.
인자로는 true/false를 줄 수 있다. true는 현재 진행중인 작업을 interrupt하면서 종료하는 것이고, false는 현재 진행중인 작업이 끝날때까지 기다렸다가 종료하는 것이다.
false를 줬다고 해서 결과를 얻을 수 있는건 아니다. 단지 작업을 기다려주는 것 뿐이다.</p>
<pre><code class="language-java">future.cancel(true);</code></pre>
<p>executorService.invokeAll()을 사용하면 여러 작업을 Collections으로 넘겨 동시에 실행할 수 있다. 다만, 동시에 실행한 작업 중 제일 오래 걸리는 작업 만큼 시간이 걸린다.
Thread pool에 있는 Callable 동작들을 각 Thread가 처리하고 모든 작업이 끝나면, 즉 마지막 작업까지 종료된 4초 뒤에야 작업의 결과를 가져올 수 있다.</p>
<pre><code class="language-java">public class StudyCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Callable&lt;String&gt; hello = () -&gt; {
          Thread.sleep(2000);
          return &quot;hello&quot;;
        };

        Callable&lt;String&gt; so = () -&gt; {
            Thread.sleep(3000);
            return &quot;so&quot;;
        };

        Callable&lt;String&gt; bye = () -&gt; {
            Thread.sleep(4000);
            return &quot;bye&quot;;
        };

        List&lt;Future&lt;String&gt;&gt; futures = executorService.invokeAll(Arrays.asList(hello, so, bye));
        for (Future&lt;String&gt; f : futures) {
            System.out.println(f.get());
        }

        executorService.shutdown();
    }</code></pre>
<h2 id="4-completablefuture">4. CompletableFuture</h2>
<p>Future를 사용해서도 비동기 프로그래밍이 가능했지만, Future만으로 힘든 작업들도 많았다.</p>
<ul>
<li>Future는 취소하거나 get()에 타임아웃을 설정할 수는 있지만, 외부에서 완료시킬 수는 없다.</li>
<li>블로킹 코드(get())를 사용하지 않고는 작업이 끝났을 때 콜백을 실행할 수 없다.</li>
<li>여러 Future를 조합할 수 없다. (ex. 유튜브 영상 정보를 가져오고 해당 영상의 댓글 목록 가져오기)</li>
<li>예외 처리용 API를 제공하지 않는다.</li>
</ul>
<p>CompletableFuture는 Future만으로는 힘들었던 비동기 작업을 가능하게 하는 인터페이스이다. CompletableFuture를 사용하면 ExecutorService 객체와 Future 객체를 따로 만들지 않아도 된다.
<br></p>
<p>CompletableFuture를 사용하여 비동기 작업을 실행해보자.
Runnable처럼 리턴값 없이 사용할 때는 runAsync()를 사용한다.</p>
<pre><code class="language-java">CompletableFuture&lt;Void&gt; future = CompletableFuture.runAsync(() -&gt; System.out.println(&quot;No Return&quot;));</code></pre>
<p>리턴값이 있는 경우에는 supplyAsync()를 사용한다.</p>
<pre><code class="language-java">CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; {
            System.out.println(&quot;Return&quot;);
            return &quot;ParamReturn&quot;;
});
System.out.println(future.get()); // 결과 가져오기</code></pre>
<br>

<p>CompletableFuture를 사용하여 Asynchronous하게 콜백을 실행시킬수도 있다. 리턴값을 받아서 다른 값으로 바꾸는 콜백을 구현하기 위해 thenApply()를 사용할 수 있다.</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; {
            System.out.println(&quot;Return: &quot; + Thread.currentThread().getName());
            return &quot;ParamReturn&quot;;
        }).thenApply((s) -&gt; {
            System.out.println(&quot;CallBack: &quot; + Thread.currentThread().getName());
            return s.toUpperCase();
        });
        System.out.println(future.get()); // 결과 가져오기
    }
}</code></pre>
<p>thenApply() 말고도 콜백을 구현할 수 있는 함수들이 있다.
thenAccept()을 사용하면 리턴값을 받아 리턴 없이 또 다른 작업을 처리하는 콜백을 구현할 수 있고,
thenRun()을 사용하면 리턴값을 받지 않고 또 다른 작업을 처리하는 콜백을 구현할 수 있다.
<br></p>
<p>Executor를 해당 스레드에서 사용하게 할 수도 있다. 
runAsync() OR supplyAsync()의 두 번째 인자로 ExecutorService를 주면 해당 스레드 풀 내에서 작업을 할당해서 처리한다.</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        CompletableFuture&lt;Void&gt; future = CompletableFuture.supplyAsync(() -&gt; {
            System.out.println(&quot;Hello: &quot; + Thread.currentThread().getName());
            return &quot;HelloReturn&quot;;
        }, executorService).thenAccept((s) -&gt; {
            System.out.println(&quot;HelloCallBack: &quot; + Thread.currentThread().getName());
            System.out.println(s.toUpperCase());
        });

        future.get();
        executorService.shutdown();
    }
}</code></pre>
<p>스레드의 콜백작업을 또 다른 스레드에서 처리할 수 있는데! then<del>() 에 Async를 붙인 then</del>Async()함수를 사용하고 두 번째 인자로 ExecutorService를 주면 된다.</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        CompletableFuture&lt;Void&gt; future = CompletableFuture.supplyAsync(() -&gt; {
            System.out.println(&quot;Bye: &quot; + Thread.currentThread().getName());
            return &quot;ByeReturn&quot;;
        }, executorService).thenAcceptAsync((s) -&gt; {
            System.out.println(&quot;ByeCallBack: &quot; + Thread.currentThread().getName());
            System.out.println(s.toUpperCase());
        }, executorService);

        future.get();
        executorService.shutdown();
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM , JRE , JDK]]></title>
            <link>https://velog.io/@suyeon-jin/JVM</link>
            <guid>https://velog.io/@suyeon-jin/JVM</guid>
            <pubDate>Sun, 24 Apr 2022 12:47:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>업무 중 엔진을 패치하는 작업을 하게 되면, 자바 언어 자체의 기본적인 내용을 모르면 이해하지 못하는 경우가 종종 있었다... 그럴때마다 자바에 대한 기초적인 구조부터 공부할 필요가 있다고 생각하여 정리하는 시간을 가져보고자 한다!</p>
</blockquote>
<h1 id="jvm-java-virtual-machine">JVM (Java Virtual Machine)</h1>
<p><img src="https://velog.velcdn.com/images/suyeon-jin/post/a9ada479-bfa7-4808-8e07-71974871e66c/image.png" alt=""></p>
<p>JVM은 자바 바이트 코드(.class 파일)를 OS에 특화된 코드로 변환하여 실행하는 역할을 한다.</p>
<pre><code class="language-java">public class HelloWorld {
    public static void main(String[] args) {
       System.out.println(&quot;Hello, World!&quot;);
    }
}</code></pre>
<p>위의 자바 코드를 컴파일을 하면 클래스 파일이 나오는데, 클래스 파일을 보면 바이트 코드로 변환된 것을 확인할 수 있다.</p>
<pre><code class="language-java">Compiled from &quot;HelloWorld.java&quot;
public class HelloWorld {
  public HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object.&quot;&lt;init&gt;&quot;:()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Hello, World!
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}</code></pre>
<p>그리고 &#39;<strong>인터프리터</strong>와 <strong>JIT(Just In Time) 컴파일러</strong>&#39;가 native OS에 맞춰서 바이트 코드(.class)를 실행해준다. 현재 나의 컴퓨터는 Mac OS를 사용하고 있으므로, Mac에 맞춰 machine 코드로 변경한 다음, machine이 이해할 수 있는 코드로 변환하여 실행해준다. </p>
<p>즉, JVM은 바이트 코드를 어떻게 실행할 수 있는지에 대한 스펙이라고 할 수 있다. 또한, JVM은 machine 코드로 바꿔서 실행해야 하는데 machine 코드라는게 OS에 맞춰서 실행해야 하기 때문에 특정 플랫폼에 종속적이라는 특징을 가지고 있다.</p>
<h1 id="jre-java-runtime-environment">JRE (Java Runtime Environment)</h1>
<p><img src="https://velog.velcdn.com/images/suyeon-jin/post/570cc821-e272-4be6-b356-90293967f467/image.png" alt=""></p>
<p>JVM은 홀로 제공되지 않고, 최소한의 배포 단위가 JRE이다. 
JRE는 JVM과 함께 라이브러리를 제공하는 형태인데, 자바 어플리케이션을 실행하는데 필요한 것들만 들어있다. 자바 바이트 코드를 실행해야하므로 JVM이 들어있고, 자바의 핵심 라이브러리 및 자바 런타임 환경에서 사용하는 프로퍼티 세팅이나 리소스 파일을 가지고 있다.</p>
<p>즉, JRE는 <strong>자바 어플리케이션이 실행될 수 있도록</strong> 구성되어 있는 배포판이라고 생각하면 된다. 하지만 자바를 개발하는 도구는 포함되어 있지 않다!
(jre를 다운받아보면 java는 들어있지만, 자바를 컴파일할 때 사용하는 javac는 들어있지 않다)</p>
<h1 id="jdk-java-development-kit">JDK (Java Development Kit)</h1>
<p><img src="https://velog.velcdn.com/images/suyeon-jin/post/ed93393b-7029-4f02-ac6a-74a4711ecebc/image.png" alt=""></p>
<p>JDK는 JRE와 함께 자바 개발에 필요한 툴을 제공하는 형태이다. 
우리같은 개발자들은 JRE를 설치하여 사용할 일보다 JDK를 설치할 일이 많다 !</p>
<p>하지만, 자바 11부터는 JRE를 따로 제공하지 않으며 JDK만 제공하고 있다. 그러므로 더 이상 헷갈릴 일은 없지만 ! 업무에서 사용하고 있는 자바 버전이 11보다 낮을 수도 있기 때문에 ^^.. ㅜ 잘 구분해두도록 하자.
(엔진 패치 작업을 진행할 때, &#39;JDK가 어쩌고 저쩌고~&#39; 하는데 JRE랑 헷갈려서 머리가 어질어질했던 경험이 있다 ㅋ_ㅋ)</p>
<h1 id="jvm-구조">JVM 구조</h1>
<p><img src="https://velog.velcdn.com/images/suyeon-jin/post/c126d044-d1c4-4b20-97e9-33b0cbc906a6/image.png" alt=""></p>
<blockquote>
<p>참고: <a href="https://www.geeksforgeeks.org/jvm-works-jvm-architecture/">https://www.geeksforgeeks.org/jvm-works-jvm-architecture/</a></p>
</blockquote>
<p>JVM의 구조 크게 4가지 요소로 구성되어 있다. 공부할 때마다 매번 헷갈려 하는 개념이기 때문에(개념이 너무 생소하고 어렵다 ;_;) 정리해보고자 한다.</p>
<h3 id="1-클래스-로더">1. 클래스 로더</h3>
<p>클래스 로더는 .class 파일에서 바이트 코드를 읽고 메모리에 적절하게 배치하는 역할을 한다.
클래스 로더는 크게 3가지 과정으로 나눌 수 있다.</p>
<ul>
<li>로딩: 클래스 파일에서 바이트 코드를 읽어오는 과정</li>
<li>링크: 그 안의 레퍼런스를 연결하는 과정</li>
<li>초기화: static 값들 초기화 및 변수 할당</li>
</ul>
<h3 id="2-메모리">2. 메모리</h3>
<p>메모리는 크게 5가지 영역으로 나누어져 있다. 
&#39;힙, 메소드&#39; 영역은 전체 쓰레드가 참조할 수 있도록 공유되는 영역이다. 반면, &#39;스택, PC, 네이티브 메소드 스택&#39; 영역은 쓰레드 별로 공유되는 영역이다.</p>
<ul>
<li><p>스택    </p>
<ul>
<li>쓰레드마다 런타임 스택을 만들고, 그 안에 메소드 호출을 &#39;스택 프레임&#39;이라는 블럭으로 쌓는다. 
(스택 프레임 = 메소드 call)</li>
<li>그래서, 쓰레드가 종료되면 런타임 스택도 함께 사라진다.</li>
</ul>
</li>
<li><p>PC(Program Counter): 쓰레드마다 쓰레드 내 현재 실행할 명령어의 위치를 가리키는 포인터</p>
</li>
<li><p>네이티브 메소드 스택: 자바 이외의 언어로 작성된 native 코드를 실행할 때 할당되는 영역이며, 타 언어의 스택 정보를 바이트 코드로 저장한다.</p>
</li>
<li><p>힙: 객체를 저장하는 영역</p>
</li>
<li><p><strong><em>메소드</em></strong>: 클래스 수준의 정보를 저장하고 있는 영역
(클래스 수준의 정보? 클래스 이름, 부모 클래스 이름, 메소드, 변수)</p>
</li>
</ul>
<h3 id="3-실행-엔진">3. 실행 엔진</h3>
<p>바이트 코드를 이해하는 영역이다.</p>
<ul>
<li><p>인터프리터: 바이트 코드를 native 코드로 바꿔서 machine이 이해할 수 있도록 한다. 한줄 씩 실행하면서 컴파일하기 때문에 효율적이지는 않다.</p>
</li>
<li><p>JIT(Just In Time) 컴파일러: 인터프리터의 효율을 높이기 위해, 인터프리터가 반복되는 바이트 코드를 발견하면 JIT 컴파일러로 반복되는 바이트 코드를 모두 native 코드로 바꿔둔다. 그 다음부터 인터프리터는 native 코드로 컴파일된 코드를 바로 사용한다.</p>
</li>
<li><p><strong><em>GC(Garbage Collector)</em></strong>: 메모리 최적화를 위해, 더 이상 참조되지 않는 객체를 모아서 정리한다.</p>
</li>
</ul>
<h3 id="4-jni-java-native-interface">4. JNI (Java Native Interface)</h3>
<p>자바 어플리케이션에서 자바 언어가 아닌 C, C++, 어셈블리 등으로 작성된 함수를 사용할 수 있는 방법을 제공하는 인터페이스를 말한다.
<code>native</code> 키워드를 사용한 메소드를 호출한다.
ex) <code>Thread</code>의 <code>currentThread()</code> 메소드 - C로 구현되어 있으며, 소스코드를 확인하면 <code>native</code>키워드가 붙어있다.</p>
<h3 id="5-네이티브-메소드-라이브러리">5. 네이티브 메소드 라이브러리</h3>
<p>C, C++로 작성된 라이브러리이다. 해당 라이브러리는 꼭 JNI를 통해서 사용되어야 한다.</p>
<h1 id="클래스-로더">클래스 로더</h1>
<p>실제로 우리가 개발을 하게 되면 클래스 로더와 관련되어 있는 라이브러리와 툴을 경험할 기회가 많다. 그렇기에 클래스 로더 시스템에 구체적인 동작 방식에 대해 정리해보게 되었다.
<img src="https://velog.velcdn.com/images/suyeon-jin/post/60675464-2be2-4c9b-9653-0541005c902c/image.png" alt=""></p>
<p>클래스 로더는 로딩, 링크, 초기화 순으로 진행된다.</p>
<h3 id="1-로딩">1. 로딩</h3>
<p>로딩 단계에서는 클래스 로더가 바이트 코드를 읽고 그 내용에 따라 적절한 바이너리 데이터를 만든다. 그리고 클래스 정보를 &#39;메소드&#39; 메모리 영역에 저장한다.</p>
<p>이때, 메소드 영역에 저장되는 데이터는 다음과 같다.</p>
<p>1) FQCN(Fully Qualified Class Name): 클래스가 속한 패키지명을 모두 포함한 이름 정보 -&gt; 패키지 경로, 클래스 이름
2) 클래스인지/인터페이스인지/enum인지 구분하는 정보
3) 메소드와 변수 정보</p>
<p>로딩이 끝난 클래스는 해당 클래스 타입의 <code>Class</code> 객체를 생성하여 &#39;힙&#39; 메모리 영역에 저장한다.</p>
<br>

<p>클래스 로더는 계층 구조로 이뤄져 있으며 기본적으로 3가지 클래스 로더가 제공되고 있다.</p>
<ul>
<li>Bootstrap 클래스 로더
  : 최상위 우선순위를 가진 클래스 로더로써, <code>JAVA_HOME₩lib</code> 에 있는 코어 자바 API를 제공한다.</li>
<li>Platform(Extension) 클래스 로더
  : <code>JAVA_HOME₩lib₩ext</code> 폴더 또는 <code>java.ext.dirs 시스템 변수</code>에 해당하는 위치에 있는 클래스를 읽는다.</li>
<li>Application 클래스 로더
  : 어플리케이션 클래스패스(어플리케이션을 실행할 때 주는 <code>-classpath</code> 옵션 또는 <code>java.class.path 환경 변수의 값</code>에 해당하는 위치)에서 클래스를 읽는다.</li>
</ul>
<p>로딩을 할 때, 최상위 부모 클래스 로더(Bootstrap)부터 바이트 코드를 읽어오려고 한다. 만약 부모 클래스 로더가 코드를 읽어오지 못하면, 자식 클래스 로드로 내려가면서 계층적으로 클래스 파일을 읽어오려고 한다.
만약 Application 클래스 로더까지 읽어오지 못한다면 <code>Class Not Found Exception</code>이 발생하게 된다.</p>
<h3 id="2-링크">2. 링크</h3>
<p>링크 단계에서는 크게 3가지 단계로 나누어져 있다.</p>
<ul>
<li>Verify
  : .class 파일 형식이 유효한지 체크하는 단계이다.
  유효하지 않으면, 에러가 나고 JVM 단에서 에러를 발생시켜 자바 어플리케이션이 죽게 된다.</li>
<li>Prepare
  : 메모리를 준비하는 단계이다. 
  클래스 변수(static 변수)와 기본 값에 필요한 메모리를 준비한다.</li>
<li>Resolve (Optional)
  : 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체하는 단계이다.
  나중에 레퍼런스를 실제로 사용할 때, 해당 단계가 발생할 수도 있다.</li>
</ul>
<h3 id="3-초기화">3. 초기화</h3>
<p>마지막 단계인 초기화 단계에서, Prepare 단계에서 준비해둔 메모리 영역에 Static 변수의 값을 할당하게 된다.
static 블럭은 이 단계에서 실행되게 된다.</p>
<br>

<p>이 포스팅은 백기선님의 <a href="https://www.inflearn.com/course/the-java-code-manipulation">더 자바, &quot;코드를 조작하는 다양한 방법&quot;</a>을 수강하고 정리한 내용입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리플렉션: 스프링의 DI는 어떻게 동작하는걸까?]]></title>
            <link>https://velog.io/@suyeon-jin/%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-DI%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94%EA%B1%B8%EA%B9%8C</link>
            <guid>https://velog.io/@suyeon-jin/%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-DI%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94%EA%B1%B8%EA%B9%8C</guid>
            <pubDate>Sun, 06 Mar 2022 10:20:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/suyeon-jin/post/becdc1b1-ad9a-46be-9d41-c458fc932ef1/image.png" alt=""></p>
<blockquote>
<p>이제까지 자바와 스프링으로 개발을 해왔지만, 한번도 의존성 주입이 어떻게 이루어지는지 궁금해하지 않고 당연한 것처럼 써왔다.
이번 기회를 통해, 스프링 내부 동작 방식에 대해 공부해보려고 한다.</p>
</blockquote>
<h1 id="di-내부-동작방식-">DI 내부 동작방식 ?</h1>
<p>스프링으로 개발을 할 때, 우리는 기본적으로 Service와 Repository라는 컴포넌트를 만들고
Service에 <em>@Autowired</em> 라는 어노테이션을 통해 Repository를 주입시킨다.</p>
<p>이러한 과정을 DI(Dependency Injection) 즉, 의존성 주입이라고 한다.
<br></p>
<p>실제로 테스트 클래스를 만들어보면,</p>
<pre><code class="language-java">package com.example.demo;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class BookServiceTest {

    @Autowired
    BookService bookService;

    @Test
    public void di() {
        // 스프링이 bookRepository를 생성하여, bookService에 DI 해줌
        Assert.assertNotNull(bookService);
        Assert.assertNotNull(bookService.bookRepository);
    }
}</code></pre>
<p>테스트가 정상 통과가 되고, bookService와 bookRepository가 null이 아님을 알 수 있다.</p>
<p>그렇다면, 스프링은 DI를 어떻게 하는 것일까? </p>
<h1 id="리플렉션reflection">리플렉션(Reflection)</h1>
<p>1) <strong>구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들을 접근할 수 있도록 해주는 자바 API</strong> 이며,
2) <strong>컴파일 시간이 아닌 실행 시간에 동적으로 특정 클래스의 정보를 추출해낼 수 있는 프로그램 기법</strong> 이다.
<br></p>
<p>자바는 컴파일 시점에 타입을 결정하는 정적 언어인데, 동적으로 클래스를 사용해야 할 때 리플렉션이 사용된다.
즉, 작성 시점에는 어떤 클래스를 사용해야 할지 모르지만 런타임 시점에서 클래스를 실행해야 할 경우 사용된다.</p>
<p>대표적인 예시로, 스프링이 리플렉션을 이용하여 <em>런타임 시에</em> 개발자가 등록한 빈을 어플리케이션에서 가져와 사용할 수 있도록 한다.</p>
<ul>
<li>스프링 프레임워크: DI에 사용</li>
<li>MVC: View에서 넘어오는 데이터를 객체에 바인딩할 때 사용</li>
<li>Hibernate: @Entity 클래스에 setter가 없으면 해당 필드에 값을 바로 주입</li>
<li>JUnit: 아예 ReflectionUtils 라는 클래스를 내부적으로 정의하여 사용</li>
</ul>
<p>그렇다면 이제 리플렉션 사용법에 대해 알아보자!</p>
<h2 id="리플렉션-api-1--클래스-정보-조회">리플렉션 API 1 : 클래스 정보 조회</h2>
<blockquote>
<p>자바에서 리플렉션은 Class&lt;?&gt; 인스턴스로 접근이 가능하다. (리플렉션이 제공하는 API)
<a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html">https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html</a></p>
</blockquote>
<p>아래의 예제를 통해, 리플렉션을 어떻게 사용하는지 알아보자.</p>
<pre><code class="language-java">
public class Book {

    private static String a = &quot;aBOOK&quot;;

    private static final String b = &quot;bBOOK&quot;;

    private String c = &quot;cBOOK&quot;;

    public String d = &quot;dBOOK&quot;;

    protected String e = &quot;eBOOK&quot;;

    public Book() {
    }

    public Book(String c) {
        this.c = c;
    }

    public Book(String c, String d, String e) {
        this.c = c;
        this.d = d;
        this.e = e;
    }

    private void privateMethod() {
        System.out.println(&quot;privateVoidMethod&quot;);
    }

    public void publicMethod() {
        System.out.println(&quot;publicVoidMethod&quot;);
    }

    public int sum(int l, int r) {
        return l + r;
    }
}</code></pre>
<ul>
<li>리플렉션을 사용하여, 클래스가 가지고 있는 &#39;필드, 메서드, 생성자, 접근 지시자&#39;와 같은 다양한 정보들에 접근할 수 있다.</li>
</ul>
<pre><code class="language-java">import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        // 생성한 객체에 접근하려면, class가 필요하다. 클래스 인스턴스에 접근하는 방법은 3가지다.

        // 1) 타입을 통해 클래스 객체를 가져오는 방법
        // 클래스 로딩이 끝나면, 클래스 타입의 인스턴스를 만들어서 heap에 저장하므로 인스턴스를 가져올 수 있음
        Class&lt;Book&gt; bookClass = Book.class;

        // 2) 인스턴스를 통해 클래스 객체를 가져오는 방법
        // 인스턴스가 이미 만들어져있는 경우, getClass() 를 사용하여 가져올 수 있음
        Book book = new Book();
        Class&lt;? extends Book&gt; aClass = book.getClass();

        // 3) 경로를 통해 클래스 객체를 가져오는 방법
        // 클래스가 없으면 ClassNotFoundException 이 발생함
        Class&lt;?&gt; aClass1 = Class.forName(&quot;com.example.demo.Book&quot;);

        ////////////////////////////////////////////////////////////////////////////////////
        // 필드 목록 가져오기
        System.out.println(&quot;-----모든 필드 목록 가져오기-----&quot;);
        Arrays.stream(bookClass.getDeclaredFields()).forEach(System.out::println);
        System.out.println();

        System.out.println(&quot;-----public 필드 목록만 가져오기-----&quot;);
        Arrays.stream(bookClass.getFields()).forEach(System.out::println);
        System.out.println();

        System.out.println(&quot;-----특정 이름을 가진 필드 목록만 가져오기-----&quot;);
        // 필드가 없으면 NoSuchFieldException 이 발생함
        Arrays.stream(new Field[]{bookClass.getDeclaredField(&quot;b&quot;)}).forEach(System.out::println);
        System.out.println();

        System.out.println(&quot;-----필드가 가지고 있는 값 목록 가져오기 (값을 가져올 때는 객체가 있어야함)-----&quot;);
        Arrays.stream(bookClass.getDeclaredFields()).forEach(f -&gt; {
            try {
                f.setAccessible(true); // reflection으로는 이렇게 접근 지시자를 무시할 수 있음
                System.out.printf(&quot;%s %s \n&quot;, f, f.get(book));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
        System.out.println();

        // 메서드 목록 가져오기
        System.out.println(&quot;-----모든 메서드 목록 가져오기-----&quot;);
        // 직접 정의한 public 메서드 외에도, Object로부터 상속받은 메서드도 출력됨
        // private 메서드도 출력하려면 getDeclaredMethods() 
        Arrays.stream(bookClass.getMethods()).forEach(System.out::println); 
        System.out.println();

        // 생성자 목록 가져오기
        System.out.println(&quot;-----모든 생성자 목록 가져오기-----&quot;);
        Arrays.stream(bookClass.getDeclaredConstructors()).forEach(System.out::println);
        System.out.println();

        // 접근 지시자 확인하기
        System.out.println(&quot;-----접근 지시자 확인하기-----&quot;);
        Arrays.stream(bookClass.getDeclaredFields()).forEach(f -&gt; {
            int modifiers = f.getModifiers();
            System.out.print(f + &quot;-&gt; &quot;);
            System.out.print(&quot;private? &quot; + Modifier.isPrivate(modifiers));
            System.out.println(&quot;, static? &quot; + Modifier.isStatic(modifiers));
        });
    }
}
</code></pre>
<pre><code>-----모든 필드 목록 가져오기-----
private static java.lang.String com.example.demo.Book.a
private static final java.lang.String com.example.demo.Book.b
private java.lang.String com.example.demo.Book.c
public java.lang.String com.example.demo.Book.d
protected java.lang.String com.example.demo.Book.e

-----public 필드 목록만 가져오기-----
public java.lang.String com.example.demo.Book.d

-----특정 이름을 가진 필드 목록만 가져오기-----
private static final java.lang.String com.example.demo.Book.b

-----필드가 가지고 있는 값 목록 가져오기 (값을 가져올 때는 객체가 있어야함)-----
private static java.lang.String com.example.demo.Book.a aBOOK 
private static final java.lang.String com.example.demo.Book.b bBOOK 
private java.lang.String com.example.demo.Book.c cBOOK 
public java.lang.String com.example.demo.Book.d dBOOK 
protected java.lang.String com.example.demo.Book.e eBOOK 

-----모든 메서드 목록 가져오기-----
public int com.example.demo.Book.sum(int,int)
public void com.example.demo.Book.publicMethod()
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

-----모든 생성자 목록 가져오기-----
public com.example.demo.Book(java.lang.String,java.lang.String,java.lang.String)
public com.example.demo.Book(java.lang.String)
public com.example.demo.Book()

-----접근 지시자 확인하기-----
private static java.lang.String com.example.demo.Book.a-&gt; private? true, static? true
private static final java.lang.String com.example.demo.Book.b-&gt; private? true, static? true
private java.lang.String com.example.demo.Book.c-&gt; private? true, static? false
public java.lang.String com.example.demo.Book.d-&gt; private? false, static? false
protected java.lang.String com.example.demo.Book.e-&gt; private? false, static? false</code></pre><ul>
<li>또한, 리플렉션으로 클래스가 가지고 있는 &#39;부모 클래스, 인터페이스&#39;와 같은 다양한 정보들에도 접근할 수 있다.</li>
</ul>
<pre><code class="language-java">public interface MyInterface {}
public interface BookInterface {}

public class MyBook extends Book implements MyInterface, BookInterface {}</code></pre>
<pre><code class="language-java">import java.util.Arrays;

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

        Class&lt;MyBook&gt; myBookClass = MyBook.class;

        // 상위 클래스 목록 가져오기
        System.out.println(&quot;-----부모클래스 목록 가져오기-----&quot;);
        System.out.println(myBookClass.getSuperclass());
        System.out.println();

        // 인터페이스 목록 가져오기
        System.out.println(&quot;-----모든 인터페이스 목록 가져오기-----&quot;);
        Arrays.stream(myBookClass.getInterfaces()).forEach(System.out::println);
        System.out.println();
    }
}</code></pre>
<pre><code>-----부모클래스 목록 가져오기-----
class com.example.demo.Book

-----모든 인터페이스 목록 가져오기-----
interface com.example.demo.MyInterface
interface com.example.demo.BookInterface</code></pre><h2 id="reflection-과-annotation">Reflection 과 Annotation</h2>
<ul>
<li>리플렉션으로 클래스가 가지고 있는 &#39;어노테이션&#39; 정보 또한 확인할 수 있다.</li>
</ul>
<p>하지만 Book 클래스가 가지고 있는 어노테이션 정보를 확인하였을 때, 실제 결과는 아무것도 출력되지 않는다. (?_?)</p>
<pre><code class="language-java">public @interface MyAnnotation {}

@MyAnnotation
public class Book {}</code></pre>
<pre><code class="language-java">import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        // 어노테이션 목록 가져오기
        System.out.println(&quot;-----모든 어노테이션 목록 가져오기-----&quot;);
        Arrays.stream(Book.class.getAnnotations()).forEach(System.out::println);
        System.out.println();

    }
}</code></pre>
<pre><code>-----모든 어노테이션 목록 가져오기-----

</code></pre><p>어노테이션은 근본적으로 주석과 같은 취급을 받기 때문에, 어노테이션 정보가 클래스, 소스까지는 남지만 바이트 코드를 로딩할 때 메모리 상에 남지 않게 된다. (즉, 어노테이션 정보는 빼고 읽어오게 된다.)</p>
<h3 id="retention">@Retention</h3>
<p>그래서 런타임까지 어노테이션 정보를 유지하고 싶다면, <code>@Retention</code> 어노테이션을 사용하면 된다.</p>
<ul>
<li>바이트 코드에 어노테이션 정보가 있는지 확인하고 싶다면, <code>java -c -v 클래스_절대경로.class</code> 를 사용하면 된다.</li>
</ul>
<pre><code class="language-java">import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

// Retention 기본값은 RetentionPolicy.CLASS
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션을 유지한다.
public @interface MyAnnotation {
}</code></pre>
<pre><code>-----모든 어노테이션 목록 가져오기-----
@com.example.demo.MyAnnotation()</code></pre><h3 id="target">@Target</h3>
<p>어노테이션을 적용할 수 있는 범위를 설정할 수도 있다.</p>
<p>아래 예제에서 <code>MyAnnotation</code>을 생성자나 메서드에 적용하려고 하면, 에러가 발생한다.</p>
<pre><code class="language-java">import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.FIELD}) // 어노테이션을 사용할 수 있는 곳은 클래스와 필드
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션을 유지한다.
public @interface MyAnnotation {
}</code></pre>
<h3 id="inherited">@Inherited</h3>
<p>어노테이션을 하위 클래스까지 전달할 것인지, 상속 여부를 결정할 수도 있다.</p>
<p>아래 예제를 보면, Book 클래스는 <code>MyAnnotation</code>이 적용되어 있으며,
MyBook 클래스는 <code>BookAnnotation</code>이 적용되어 있음과 동시에 Book 클래스를 상속받고 있다.</p>
<pre><code class="language-java">import java.lang.annotation.*;

@Inherited // 상속이 가능하도록 한다.
@Target({ElementType.TYPE, ElementType.FIELD}) // 어노테이션을 사용할 수 있는 곳은 클래스와 필드
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션을 유지한다.
public @interface MyAnnotation {
}

@Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션을 유지한다.
public @interface BookAnnotation {
}</code></pre>
<pre><code class="language-java">@MyAnnotation
public class Book {}

@BookAnnotation
public class MyBook extends Book implements MyInterface, BookInterface {}</code></pre>
<p>이 상태에서 MyBook 클래스의 어노테이션 목록을 출력하면, Book 클래스에 적용된 <code>MyAnnotation</code> 도 같이 하위 클래스인 MyBook 클래스에 적용되어 출력되게 된다.
부모 클래스가 아닌, 해당(자식) 클래스에 명시된 어노테이션 목록만 따로 출력할 수도 있다.</p>
<pre><code class="language-java">import java.util.Arrays;

public class Main {
        System.out.println(&quot;----모든 어노테이션 목록 가져오기-----&quot;);
        Arrays.stream(MyBook.class.getAnnotations()).forEach(System.out::println);
        System.out.println();

        System.out.println(&quot;----명시된 어노테이션 목록 가져오기-----&quot;);
        Arrays.stream(MyBook.class.getDeclaredAnnotations()).forEach(System.out::println);
        System.out.println();
    }
}</code></pre>
<pre><code>----모든 어노테이션 목록 가져오기-----
@com.example.demo.MyAnnotation()
@com.example.demo.BookAnnotation()

----명시된 어노테이션 목록 가져오기-----
@com.example.demo.BookAnnotation()
</code></pre><h2 id="리플렉션-api-2--클래스-정보-수정-및-실행">리플렉션 API 2 : 클래스 정보 수정 및 실행</h2>
<p>리플렉션으로 클래스의 정보를 가져오는 것 뿐만 아니라, </p>
<p>1) 생성자로 인스턴스를 만들 수 있고 2) 필드 값을 가져오거나 수정할 수 있으며 3) 메소드를 실행할 수 있다.</p>
<pre><code class="language-java">import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
            InstantiationException, IllegalAccessException, NoSuchFieldException {

        Class&lt;?&gt; bookClass = Class.forName(&quot;com.example.demo.Book&quot;);

        // 1) 생성자를 사용하여 인스턴스 생성하기 Constructor.newInstance(params)
        Constructor&lt;?&gt; constructor = bookClass.getConstructor(null); // 기본 생성자를 사용하여 인스턴스를 만들겠다.
        Book book = (Book) constructor.newInstance();
        System.out.println(book);

        // 2) static 필드 값 가져오기 Field.get(object)
        Field a = Book.class.getDeclaredField(&quot;a&quot;);
        a.setAccessible(true); // private scope 이기 때문에 접근 지시자를 무시하도록 설정해줘야 함
        System.out.println(a.get(null)); // static 변수이므로, 특정 인스턴스를 넘겨줄 것이 없다. 그러므로 null을 넘긴다.

        // 2) static 필드 값 수정하기 Field.set(object, value)
        a.set(null, &quot;abook&quot;);
        System.out.println(a.get(null));

        // 2) 필드 값 가져오기 Field.get(object)
        // 특정 인스턴스가 가지고 있는 값을 가져오는 것이기 때문에, 인스턴스가 미리 만들어져 있어야한다.
        Field d = Book.class.getDeclaredField(&quot;d&quot;);
        System.out.println(d.get(book)); // 미리 만들어져 있던 Book 클래스 객체(book)를 사용하여 해당 객체의 필드 값을 가져옴

        // 2) 필드 값 수정하기 Field.set(object, value)
        d.set(book, &quot;dbook&quot;);
        System.out.println(d.get(book));

        // 3) 메소드 실행하기 Method.invoke(object, params)
        Method privateMethod = Book.class.getDeclaredMethod(&quot;privateMethod&quot;);
        privateMethod.setAccessible(true);
        privateMethod.invoke(book);

        // 3) 파라미터와 리턴 값이 있는 메서드 실행하기 Method.invoke(object, params)
        Method sum = Book.class.getDeclaredMethod(&quot;sum&quot;, int.class, int.class);
        int result = (int) sum.invoke(book, 10, 20);
        System.out.println(result);
    }
}</code></pre>
<pre><code>com.example.demo.Book@3f0ee7cb
aBOOK
abook
dBOOK
dbook
privateVoidMethod
30</code></pre><h1 id="리플렉션을-사용하여-di-프레임워크-만들기">리플렉션을 사용하여 DI 프레임워크 만들기</h1>
<ul>
<li>test/java<pre><code class="language-java">package com.example.di;
</code></pre>
</li>
</ul>
<p>public class BookRepository {}</p>
<p>public class BookService {
    @Inject // 필드(BookRepository) 주입
    BookRepository bookRepository;</p>
<pre><code>BookRepository bookRepositoryWithOutInject;</code></pre><p>}</p>
<pre><code>```java
package com.example.di;

import org.junit.Test;

import static org.junit.Assert.assertNotNull;

public class ContainerServiceTest {

    @Test
    public void getObject_BookRepository() {
        // @Inject 를 적용하지 않은 객체
        BookRepository bookRepository = ContainerService.getObject(BookRepository.class);
        assertNotNull(bookRepository);
    }

    @Test
    public void getObject_BookService() {
        // @Inject 를 적용하여 객체를 주입한 객체
        BookService bookService = ContainerService.getObject(BookService.class);
        assertNotNull(bookService);
        assertNotNull(bookService.bookRepository);
        assertNotNull(bookService.bookRepositoryWithOutInject); // FAIL 
    }
}</code></pre><ul>
<li>src/main/java
1) @Inject
필드 주입에 사용되는 객체인지 확인하기 위해, <code>@Inject</code> 라는 어노테이션을 만든다. 
런타임 시 참조해야하므로 <code>@Retention</code> 어노테이션도 적용해준다.<pre><code class="language-java">package com.example.di;
</code></pre>
</li>
</ul>
<p>import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;</p>
<p>@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
}</p>
<pre><code>2) ContainerService
`getObject`  메서드를 통해 classType 에 해당하는 타입의 객체를 만들어준다.
단, 해당 객체의 필드 중에 `@Inject` 어노테이션이 있다면 해당 필드도 같이 만들어 제공한다.
```java
package com.example.di;

import java.util.Arrays;

public class ContainerService {

    // 클래스 타입이 들어오면, 해당 클래스의 인스턴스를 리턴하도록 Generic 타입으로 정의함
    public static &lt;T&gt; T getObject(Class&lt;T&gt; classType) {
        T instance = createInstance(classType); // 리플렉션을 이용하여 인스턴스를 생성한다.
        Arrays.stream(classType.getDeclaredFields()).forEach(f -&gt; { // 클래스 타입의 필드들을 확인하면서,
            if (f.getAnnotation(Inject.class) != null) { // 필드에 적용된 어노테이션이 @Inject 이면
                Object fieldInstance = createInstance(f.getType()); // 필드 타입에 맞는 클래스 인스턴스를 생성하고
                f.setAccessible(true); // 접근 지시자를 무시하도록 설정하고
                try {
                    f.set(instance, fieldInstance); // 해당 필드에 객체를 주입한다.
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        return instance;
    }

    private static &lt;T&gt; T createInstance(Class&lt;T&gt; classType) { // 리플렉션 이용-&gt; 생성자를 만들어 리턴해준다.
        try {
            return classType.getConstructor(null).newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}</code></pre><p>위의 예제는 예외 처리, 캐싱과 같은 최적화를 위한 기법이 들어가지 않아 효율적인 IOC 컨테이너는 아니다. 실제로 스프링이 DI를 할 때는 훨씬 복잡하고 정교하게 이루어져 있다.</p>
<p>하지만, 스프링이 DI를 수행하는 방법은 위와 같은 리플렉션을 활용한 객체 생성 및 주입임을 기억하고 가자.</p>
<h2 id="정리">정리</h2>
<p>리플렉션은 강력한 기능인 만큼, 지나치게 사용하거나 잘못 사용한 경우에는 성능 이슈를 야기할 수 있다.</p>
<ul>
<li>인스턴스는 이미 생성되어 있고 그걸 계속 활용할 수 있으면, 리플렉션으로 접근하는 방법은 불필요하다. (리플렉션을 계속 사용하면, 새로운 객체를 계속 생성하는 것이기 때문에 비효율적임)</li>
<li>컴파일 시 확인하지 않고 런타임 시에만 발생하는 문제가 발생할 수 있다.</li>
<li>접근 지시자를 무시할 수 있다. (캡슐화를 무시하게 됨)</li>
</ul>
<p>하지만 잘못 사용했을 때 성능 이슈가 발생한다는 것이지, 잘만 사용한다면 리플렉션은 강력한 기능이다.
남발하지 말고 적재적소에 사용하도록 하자 ! (생각하는 개발자가 되자 @_ㅠ; )</p>
<br>

<p>이 포스팅은 백기선님의 <a href="https://www.inflearn.com/course/the-java-code-manipulation">더 자바, &quot;코드를 조작하는 다양한 방법&quot;</a>을 수강하고 정리한 내용입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Optional]]></title>
            <link>https://velog.io/@suyeon-jin/Optional</link>
            <guid>https://velog.io/@suyeon-jin/Optional</guid>
            <pubDate>Sun, 06 Feb 2022 09:21:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/suyeon-jin/post/6c227e24-ab22-406d-b474-607f4fd59eaf/image.png" alt=""></p>
<h3 id="값이-있을-수도-없을-수도-있는-컨테이너">&gt; 값이 있을 수도 없을 수도 있는 컨테이너</h3>
<ul>
<li><p>null이 리턴되어 발생하는 NullPonterException을 방지하고자 나오게 된 개념 </p>
<pre><code class="language-java">String str = null;
System.out.println(str.indexOf(&quot;a&quot;)); // NullPointerExecption 발생</code></pre>
</li>
<li><p>Method에서 작업 중 반환 값이 제대로 들어오지 않았을 때, 주로 3가지 처리 방법을 사용한다.</p>
<pre><code class="language-java">String str = null;
</code></pre>
</li>
</ul>
<p>// 방법1. 예외를 던진다 → 에러가 발생하면, stackTrace를 찍는다. 즉, 리소스 낭비이다
if(str == null) {
    throw new IllegalStateException(); // 에러 처리를 강제하게 됨
}
System.out.println(str.indexOf(&quot;a&quot;));</p>
<p>// 방법2. 클라이언트 코드에서 null을 확인한다 → 이는 클라이언트에서 놓친다면 에러가 발생할 수 있다
if(str != null) {
    System.out.println(str.indexOf(&quot;a&quot;));
}</p>
<p>// 방법3. Optional을 리턴한다 (자바 8부터) → 빈 값이 들어올수도 있음을 클라이언트에게 명시 후, 빈 값인 경우에 대한 처리를 강제한다.
Optional<String> strOptinal = Optional.ofNullable(str);</p>
<pre><code>
## 1. 주의할 점
### 1-1) 리턴 값으로만 사용하는 것을 권장한다.
#### [1] 파라미터로 Optional을 사용하지 않는다.
→ Optional을 써서 얻는 메리트가 사라짐!

- 파라미터로 Optional을 사용해도 문법적인 오류는 없다.
- 하지만, 파라미터 값으로 들어온 null에 ifPresent 함수를 호출하려고 하면 NullPointerException이 발생한다.
- 결국 Optional 객체 안에 null 이 아닌 값이 들어있는지 확인하는 코드가 필요하다.

```java
public void setParameter(Optional&lt;Object&gt; param){
    if(param != null) // NullPointerException 가능성 존재하므로 null 체크를 해줘야함
        param.ifPresent(p -&gt; this.param = p); 
}</code></pre><h4 id="2-map의-key-타입으로-optional을-사용하지-않는다">[2] Map의 Key 타입으로 Optional을 사용하지 않는다.</h4>
<ul>
<li>Map 인터페이스의 가장 큰 특징: <strong>&quot;Null 값을 Key 값으로 쓰지 않는다&quot;</strong></li>
<li>Optional을 Key 값으로 사용하여 null이 들어올 수 있다는 것이 Map의 특성을 깨버리는 것이 되버린다.</li>
</ul>
<h4 id="3-인스턴스-필드-타입으로-optional을-사용하지-않는다">[3] 인스턴스 필드 타입으로 Optional을 사용하지 않는다.</h4>
<ul>
<li>이는 도메인 클래스의 설계 문제이다.</li>
<li>차라리 상위/하위 클래스로 쪼개거나, delegation 사용을 권장한다.</li>
</ul>
<pre><code class="language-java">// Optional&lt;Node&gt; node; // 이렇게 사용하지 않는다.

/////////////////////////////////////////
// delegation을 사용한다.
// 참조 객체를 통해 메서드를 호출하는 방식을 사용한다.
Node node = node.getNode();

/////////////////////////////////////////

// Node 클래스에 정의되어 있는 메서드
public Optional&lt;Node&gt; getNode() {
    return Optional.empty();
}</code></pre>
<h3 id="1-2-primitive-type의-optional은-따로-존재한다">1-2) primitive type의 Optional은 따로 존재한다.</h3>
<pre><code class="language-java">Optional.of(10); // 내부에서 (un)boxing이 이루어진다 → 성능에 좋지 않다.
OptionalInt.of(10); // 각 primitive 타입에 맞는 클래스를 제공하므로 이를 사용하는 것을 권장한다.</code></pre>
<h3 id="1-3-optional을-리턴한다면-null을-반환하지-않는다">1-3) Optional을 리턴한다면 null을 반환하지 않는다.</h3>
<ul>
<li><strong>Optional.empty()</strong> 로 리턴한다.<pre><code class="language-java">public Optional&lt;Object&gt; getParam() {
  // return null; // 이렇게 사용하지 않는다. → getParam 이후 ifPresent() 실행 시, NullPointerException 발생 가능성이 있음
  return Optional.empty(); // null을 담고 있는 Optional 객체를 반환하자.
}</code></pre>
</li>
</ul>
<h3 id="1-4-collection-map-stream-array-optional은-optinal로-감싸지-않는다">1-4) Collection, Map, Stream Array, Optional은 Optinal로 감싸지 않는다.</h3>
<ul>
<li>컨테이너 성격의 객체들은 이미 &quot;값이 비어있을 수 있다&quot;는 의미를 포함하고 있다.</li>
<li>해당 객체들을 Optional로 감싸면 Optional을 2번 감싸는 형태가 되므로, 무의미하다</li>
</ul>
<h2 id="2-optional-api">2. Optional API</h2>
<h3 id="2-1-optional-만들기">2-1) Optional 만들기</h3>
<ul>
<li>Optional.of()<ul>
<li>인자로서 null 값을 받지 않는 Optional 객체를 만든다.</li>
<li>null 값을 of의 입력으로 받을 시, NullPointerException이 발생한다.</li>
</ul>
</li>
<li>Optional.ofNullable() <ul>
<li>인자로서 null 값을 허용하는 Optional 객체를 만든다.</li>
</ul>
</li>
<li>Optional.empty()<ul>
<li>null을 가지고 있는 비어있는 Optional 객체를 만든다.</li>
</ul>
</li>
</ul>
<h3 id="2-2-optional-값-여부-확인하기">2-2) Optional 값 여부 확인하기</h3>
<ul>
<li>boolean isPresent()</li>
<li>boolean isEmpty() : Java 11부터 제공</li>
</ul>
<h3 id="2-3-optional-값-가져오기">2-3) Optional 값 가져오기</h3>
<ul>
<li>get() <ul>
<li>값이 null일 경우 NoSuchElementException 발생</li>
<li><strong>가급적 사용하지 않는 것을 추천</strong></li>
</ul>
</li>
<li>ifPresent(Consumer) <ul>
<li>값이 있으면 그 값을 가지고 ~를 해라</li>
<li>값이 있는 경우만 함수가 동작한다</li>
</ul>
</li>
<li>orElse(T) <ul>
<li>값이 있으면 가져오고 없는 경우에 ~를 리턴해라</li>
<li>값이 있든 없든 orElse안에 T는 실행됨</li>
<li>값이 없을 때, 가져오는 객체가 이미 만들어져 있는 객체인 경우 적합</li>
</ul>
</li>
<li>orElseGet(Supplier) <ul>
<li>값이 있으면 가져오고 없는 경우에 ~를 해라</li>
<li>값이 있으면 orElseGet 안에 Supplier가 실행되지 않음</li>
<li>값이 없을 때, 동적으로 새로운 객체를 생성해 실행해야 하는 경우 적합</li>
</ul>
</li>
<li>orElseThrow()<ul>
<li>값이 있으면 가져오고 없는 경우에 에러를 던져라</li>
<li>기본적으로는 NoSuchElementException을 던진다</li>
<li>Supplier로 원하는 에러를 던지게 설정할 수 있다</li>
</ul>
</li>
</ul>
<h3 id="2-4-optional-필터">2-4) Optional 필터</h3>
<ul>
<li>Optional filter(Predicate) <ul>
<li>Optional에 들어있는 값을 조건에 따라 걸러라</li>
<li>값이 있다는 가정하에 동작</li>
<li>값이 없으면, 빈 optional 객체를 반환한다</li>
</ul>
</li>
</ul>
<h3 id="2-5-optional-변환">2-5) Optional 변환</h3>
<ul>
<li>Optional map(Function) <ul>
<li>Optional에 들어있는 값을 변환해라</li>
<li>optional이 담고 있는 타입이 달라진다</li>
</ul>
</li>
<li>Optional flatMap(Function) <ul>
<li>Optional 안에 들어있는 인스턴스가 Optional인 경우 사용하면 유용<pre><code class="language-java">// map을 사용한 경우
Optional&lt;Optional&lt;String&gt;&gt; str = optionalOnlineClass.map(OnlineClass::getOptionalString);
Optional&lt;String&gt; optionalString = str.orElse(Optional.empty());
</code></pre>
</li>
</ul>
</li>
</ul>
<p>// flatMap을 사용한 경우
Optional<String> optionalStringByFlatMap = optionalOnlineClass.flatMatp(OnlineClass::getOptionalString);</p>
<p>```</p>
<br>

<p>이 포스팅은 백기선님의 <a href="https://www.inflearn.com/course/the-java-java8">더 자바, Java 8</a>을 수강하고 정리한 내용입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Stream]]></title>
            <link>https://velog.io/@suyeon-jin/Stream</link>
            <guid>https://velog.io/@suyeon-jin/Stream</guid>
            <pubDate>Sun, 06 Feb 2022 07:45:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/suyeon-jin/post/0a50b36c-fd0b-4c49-a45e-22c01a57c3d9/image.png" alt=""></p>
<h5 id="컬렉션이-데이터를-가지고-있는-저장소라면-br-스트림은-이-데이터를-가지고-원하는-방식으로-처리해주는-컨베이어-벨트라고-할-수-있다">&gt; 컬렉션이 데이터를 가지고 있는 저장소라면, <br> <strong>스트림은 이 데이터를 가지고 원하는 방식으로 처리해주는 컨베이어 벨트</strong>라고 할 수 있다.</h5>
<ul>
<li><p>sequence of elements supporting sequential and parallel aggregate operations</p>
</li>
<li><p>Stream은 연속된 데이터를 처리하는 오퍼레이션들의 모임이다.</p>
</li>
<li><p>무제한으로 데이터가 들어와도 처리가 가능하다. </p>
<ul>
<li><strong>Short Circuit</strong> 메소드를 사용하여 들어오는 데이터를 제한할 수 있다.<br> 
</li>
</ul>
</li>
<li><p>Functional in nature, 스트림이 처리하는 데이터 소스는 변경하지 않는다. (원본 변경 x)</p>
</li>
<li><p>즉, 스트림으로 전달받은 데이터 자체를 변경하지 않는다.</p>
</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;apple&quot;);
list.add(&quot;banana&quot;);

// list에 저장된 데이터를 대문자로 바꾸는 처리를 하는 stream
Stream&lt;String&gt; stringStream = list.stream()
                                .map(String::toUpperCase); // stream을 리턴

// list 객체 안의 기존 데이터는 변경되지 않음
list.forEach(System.out::println); // apple banana</code></pre>
<h2 id="1-stream-pipeline">1. Stream pipeline</h2>
<ul>
<li>오퍼레이션들의 집합
<img src="https://images.velog.io/images/suyeon-jin/post/888769b5-a75b-4dd7-bfac-7bfbf8360a4f/image.png" alt=""></li>
<li><strong>0 또는 다수의 중개 오퍼레이션</strong>과 <strong>1개의 종료 오퍼레이션</strong>으로 구성되어 있다.</li>
<li>중개 오퍼레이션은 종료 오퍼레이션을만나지 않으면 실행되지 않는다.<ul>
<li>반드시 하나의 종료 오퍼레이션이 있어야 한다.</li>
</ul>
</li>
</ul>
<h3 id="1-1-intermediate-operation-중개-오퍼레이션">1-1) Intermediate operation (중개 오퍼레이션)</h3>
<ul>
<li>Stream을 리턴한다.</li>
<li>연산을 마치고 처리를 위한 다음 연산을 진행할 수 있다.</li>
<li>근본적으로 lazy하다.<ul>
<li>lazy? 종료 오퍼레이션과 만나기 전까지 실행하지 않는다.</li>
</ul>
</li>
<li>ex) filter, map, limit, sorted ...</li>
</ul>
<pre><code class="language-java">// 종료 오퍼레이션을 만나지 못했으므로, 코드가 실행되지 않음
// 단순히 스트림을 정의한 것뿐 실행은 안됨

list.stream().map((s) -&gt; {
        System.out.println(s); // 출력되지 않음
        return s.toUpperCase();
});</code></pre>
<h3 id="1-2-terminal-operation-종료-오퍼레이션">1-2) Terminal operation (종료 오퍼레이션)</h3>
<ul>
<li>Stream을 리턴하지 않는다.</li>
<li>연산을 마치고 해당 처리를 종료한다.</li>
<li>ex) collect, count, forEach, min, max ...</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; collect = list.stream().map((s) -&gt; {
        System.out.println(s); // 소문자로 출력됨
        return s.toUpperCase(); // 대문자로 변경됨
}).collect(Collectors.toList()); // list 객체로 반환됨

collect.forEach(System.out::println); // 대문자로 출력됨</code></pre>
<h2 id="2-parallelstream">2. parallelStream</h2>
<ul>
<li>스트림은 병렬처리를 쉽게 할 수 있다.</li>
<li>병렬처리가 항상 좋은 경우는 아니다.<ul>
<li>thread 생성 비용, 병렬처리 후 수집 비용, 컨텍스트 스위칭 비용 등등 ...</li>
</ul>
</li>
<li>데이터가 방대하게 큰 경우 사용하면 유용하다.</li>
<li>직접 성능을 측정해보고 결정하는 것이 좋다!</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; parallelCollect = list.parallelStream().map((s) -&gt; {
            System.out.println(s + &quot; , &lt;Thread name&gt; &quot; + Thread.currentThread().getName());
            return s.toUpperCase();
        }).collect(Collectors.toList());

        parallelCollect.forEach(System.out::println);</code></pre>
<h2 id="3-stream-api">3. Stream API</h2>
<h3 id="3-1-걸러내기">3-1) 걸러내기</h3>
<ul>
<li>Filter(Predicate)</li>
<li>스트림에서 특정 조건을 만족하는 데이터만 걸러서 만든 새로운 스트림으로 반환한다.</li>
<li>중개 오퍼레이션</li>
<li>ex) 수업 이름에 java가 들어가는 데이터만 스트림으로 만들기</li>
</ul>
<h3 id="3-2-변경하기">3-2) 변경하기</h3>
<ul>
<li>Map(Function) , FlatMap(Function)</li>
<li>하나의 데이터 안에서 특정 값만 꺼내거나 변경하여 새로운 스트림으로 반환한다.</li>
<li>flatMap은 여러 개의 값으로 감싸져있는 데이터(Array, List)를 세분화하여 하나로 합친 새로운 스트림을 생성한다.</li>
<li>하나의 input에 여러 개의 output이 나올 때, flatMap을 사용한다.</li>
<li>중개 오퍼레이션</li>
<li>ex) 수업 이름을 의미하는 String 객체만을 갖는 스트림으로 만들기</li>
</ul>
<h3 id="3-3-생성하기">3-3) 생성하기</h3>
<ul>
<li>generate(Supplier) , Iterator(T seed, UnaryOperator)</li>
<li>seed로부터 UnaryOperator를 무제한으로 반복하는 스트림으로 반환한다.</li>
<li>중개 오퍼레이션</li>
<li>ex) 10부터 1씩 증가하는 무제한 스트림 만들기</li>
</ul>
<h3 id="3-4-제한하기">3-4) 제한하기</h3>
<ul>
<li>limit(long) , skip(long)</li>
<li>중개 오퍼레이션</li>
<li>ex) 최대 10개의 데이터가 담긴 스트림 만들기</li>
<li>ex) 앞 5개를 제외한 나머지 데이터가 담긴 스트림 만들기</li>
</ul>
<h3 id="3-5-스트림에-있는-데이터가-특정-조건을-만족하는지-확인">3-5) 스트림에 있는 데이터가 특정 조건을 만족하는지 확인</h3>
<ul>
<li>anyMatch() , allMatch() , nonMatch()</li>
<li>스트림의 데이터들을 돌며 특정 조건을 만족하는지 확인한다.</li>
<li>종료 오퍼레이션</li>
<li>ex) 스트림에 있는 모든 데이터들이 5보다 큰지 확인한다. (boolean 값으로 반환된다.)</li>
</ul>
<h3 id="3-6-개수-세기">3-6) 개수 세기</h3>
<ul>
<li>count()</li>
<li>종료 오퍼레이션</li>
<li>ex) 10보다 큰 데이터의 개수를 반환한다.</li>
</ul>
<h3 id="3-7-스트림을-데이터-하나로-뭉치기">3-7) 스트림을 데이터 하나로 뭉치기</h3>
<ul>
<li>reduce(identity, ByFunction) , collect() , sum() , min() , max()</li>
<li>종료 오퍼레이션</li>
<li>ex) 모든 숫자의 합 구하기</li>
<li>ex) 모든 데이터를 하나의 리스트 혹은 Set에 옮겨 담기</li>
</ul>
<h3 id="예제">예제</h3>
<pre><code class="language-java">// setter, getter, 생성자는 만들어져있다고 가정
public class OnlineClass {
    private Integer id;
    private String title;
    private boolean closed;
}</code></pre>
<pre><code class="language-java">public class App {

    public static void main(String[] args) {
        List&lt;OnlineClass&gt; springClasses = new ArrayList&lt;&gt;();
        springClasses.add(new OnlineClass(1, &quot;spring boot&quot;, true));
        springClasses.add(new OnlineClass(2, &quot;spring data jpa&quot;, true));
        springClasses.add(new OnlineClass(3, &quot;spring mvc&quot;, false));
        springClasses.add(new OnlineClass(4, &quot;spring core&quot;, false));
        springClasses.add(new OnlineClass(5, &quot;rest api development&quot;, false));

        List&lt;OnlineClass&gt; javaClasses = new ArrayList&lt;&gt;();
        javaClasses.add(new OnlineClass(6, &quot;The Java, Test&quot;, true));
        javaClasses.add(new OnlineClass(7, &quot;The Java, Code manipulation&quot;, true));
        javaClasses.add(new OnlineClass(8, &quot;The Java, 8 to 11&quot;, false));

        List&lt;List&lt;OnlineClass&gt;&gt; events = new ArrayList&lt;&gt;();
        events.add(springClasses);
        events.add(javaClasses);

        System.out.println(&quot;spring 으로 시작하는 수업&quot;);
        springClasses.stream()
                .filter(onlineClass -&gt; onlineClass.getTitle().startsWith(&quot;spring&quot;)) // 중개 오퍼레이션
                .forEach(onlineClass -&gt; System.out.println(onlineClass.getTitle())); // 종료 오퍼레이션

        System.out.println(&quot;close 되지 않은 수업&quot;);
        springClasses.stream()
                .filter(onlineClass -&gt; !onlineClass.isClosed()) // 중개 오퍼레이션
                .forEach(onlineClass -&gt; System.out.println(onlineClass.getTitle())); // 종료 오퍼레이션

        System.out.println(&quot;수업 이름만 모아서 스트림 만들기&quot;);
        springClasses.stream()
                .map(OnlineClass::getTitle) // 중개 오퍼레이션 (OnlineClass -&gt; String)
                .forEach(System.out::println); // 종료 오퍼레이션 (String 출력)

        System.out.println(&quot;두 수업 목록에 들어있는 모든 수업 아이디 출력&quot;);
        events.stream()
                .flatMap(Collection::stream) // 스트림 input으로 들어온 List를 flatten시켜 OnlineClass 객체로 만든다.
                .forEach(onlineClass -&gt; System.out.println(onlineClass.getId())); // 종료 오퍼레이션

        System.out.println(&quot;10부터 1씩 증가하는 무제한 스트림 중에서 앞에 10개 빼고 최대 10개 까지만&quot;);
        Stream.iterate(10, i -&gt; i+1) // 증가 스트림 생성
                .skip(10) // 앞에 10개 제외
                .limit(10) // 최대 10개
                .forEach(System.out::println); // 종료 오퍼레이션

        System.out.println(&quot;자바 수업 중에 Test가 들어있는 수업이 있는지 확인&quot;);
        boolean result = javaClasses.stream()
                .anyMatch(onlineClass -&gt; onlineClass.getTitle().contains(&quot;Test&quot;)); // boolean으로 반환
        System.out.println(result);

        System.out.println(&quot;스프링 수업 중에 제목에 spring이 들어간 것만 모아서 List로 만들기&quot;);
        List&lt;String&gt; list = springClasses.stream()
                .map(OnlineClass::getTitle) // 제목만 모아서 새로운 stream 객체를 생성
                .filter(title -&gt; title.contains(&quot;spring&quot;)) // 조건에 맞게 거름
                .collect(Collectors.toList()); // 처리한 스트림을 List 객체로 반환
        list.forEach(System.out::println);

    }
}</code></pre>
<br>

<p>이 포스팅은 백기선님의 <a href="https://www.inflearn.com/course/the-java-java8">더 자바, Java 8</a>을 수강하고 정리한 내용입니다.</p>
]]></description>
        </item>
    </channel>
</rss>