<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>eta_kyung.log</title>
        <link>https://velog.io/</link>
        <description>https://xoruddl.github.io/ 로 이사</description>
        <lastBuildDate>Sun, 09 Aug 2026 13:27:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>eta_kyung.log</title>
            <url>https://velog.velcdn.com/images/eta_kyung/profile/6cccc7e7-d93d-4b01-a2ce-ab81bda66670/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. eta_kyung.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/eta_kyung" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[JAVA (27) - Executor 기초]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-27-Executor-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@eta_kyung/JAVA-27-Executor-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Sun, 09 Aug 2026 13:27:23 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바에서 멀티스레드 프로그래밍을 할 때 <code>new Thread()</code>를 통해 직접 스레드를 생성하고 관리하는 방식은 여러 한계를 지닌다. 스레드 생성과 파괴에 소요되는 오버헤드가 크고, 무분별한 스레드 생성은 시스템 자원 고갈로 이어질 수 있다.</p>
<p>자바 5부터 도입된 <strong>Executor 프레임워크</strong>는 이러한 스레드 생성 및 관리 작업을 캡슐화하여 개발자가 스레드를 직접 다루지 않고도 작업을 효율적으로 비동기 처리할 수 있도록 지원하는 표준 인프라다.</p>
<hr>
<h2 id="2-기존-스레드-직접-관리의-문제점">2. 기존 스레드 직접 관리의 문제점</h2>
<p><code>new Thread(() -&gt; { ... }).start()</code> 방식을 사용할 때 발생하는 대표적인 문제점은 다음과 같다.</p>
<ul>
<li><strong>높은 생성 비용</strong>: OS 스레드를 생성하고 메모리를 할당하는 작업은 비용이 크다. 요청마다 스레드를 생성하면 성능 저하가 발생한다.</li>
<li><strong>자원 고갈 위험</strong>: 사용자 요청이 급증하면 스레드가 무제한으로 생성되어 <code>OutOfMemoryError</code>가 발생하거나 CPU 컨텍스트 스위칭 오버헤드가 극대화된다.</li>
<li><strong>작업과 스레드의 강한 결합</strong>: 수행할 작업(Task)의 정의와 스레드의 생명주기 관리가 분리되지 않아 관리가 복잡해진다.</li>
</ul>
<hr>
<h2 id="3-executor-프레임워크의-개념과-구조">3. Executor 프레임워크의 개념과 구조</h2>
<p>Executor 프레임워크는 작업 제출(Task Submission)과 작업 실행(Task Execution)을 분리하는 구조를 가진다. 개발자는 &#39;수행할 작업&#39;만 정의하여 제출하고, 작업의 실제 스레드 할당 및 실행은 프레임워크가 담당한다.</p>
<p>이름이 유사하여 헷갈리기 쉬운 <strong><code>Executor</code></strong>, <strong><code>ExecutorService</code></strong>, <strong><code>Executors</code></strong>의 관계와 역할을 정리하면 다음과 같다.</p>
<h3 id="3종-비교-요약표">3종 비교 요약표</h3>
<table>
<thead>
<tr>
<th>이름</th>
<th>종류</th>
<th>주요 역할</th>
</tr>
</thead>
<tbody><tr>
<td><strong><code>Executor</code></strong></td>
<td>인터페이스</td>
<td>최상위 인터페이스. <code>execute()</code> 메서드 하나만 갖고 있으며, 작업 실행의 기본 규격을 정의한다.</td>
</tr>
<tr>
<td><strong><code>ExecutorService</code></strong></td>
<td>인터페이스</td>
<td><code>Executor</code>를 상속받은 확장 인터페이스. 작업 제출(<code>submit</code>), 스레드 풀 종료(<code>shutdown</code>) 등 실제 스레드 풀 관리와 제어를 담당한다.</td>
</tr>
<tr>
<td><strong><code>Executors</code></strong></td>
<td>팩토리 클래스 (유틸리티)</td>
<td>인터페이스가 아닌 <strong>일반 클래스</strong>다. <code>ExecutorService</code> 구현체 객체(<code>ThreadPoolExecutor</code> 등)를 쉽게 생성해 주는 <strong>공장(Factory)</strong> 역할을 한다.</td>
</tr>
</tbody></table>
<h3 id="상속-및-관계도">상속 및 관계도</h3>
<pre><code class="language-text">Executor (인터페이스)
   ▲ 상속(extends)
   │
ExecutorService (인터페이스)
   ▲ 구현(implements)
   │
ThreadPoolExecutor (구체 클래스) ◄── 생성(Factory) ── Executors (유틸리티 클래스)
</code></pre>
<ol>
<li><strong><code>Executor</code></strong>: 가장 기본이 되는 작업 실행 규격이다.</li>
<li><strong><code>ExecutorService</code></strong>: <code>Executor</code>를 상속받아 완성된 스레드 풀 관리용 인터페이스다.</li>
<li><strong><code>Executors</code></strong>: <code>ExecutorService</code>를 직접 구현한 것이 아니라, 실제 구현체인 <code>ThreadPoolExecutor</code> 같은 객체를 간편하게 만들어서 반환해 주는 팩토리 클래스다.</li>
</ol>
<hr>
<h2 id="4-executors를-통한-스레드-풀-생성">4. Executors를 통한 스레드 풀 생성</h2>
<p><code>Executors</code> 유틸리티 클래스가 제공하는 주요 팩토리 메서드는 다음과 같다.</p>
<pre><code class="language-java">// 1. 고정 크기 스레드 풀 생성
ExecutorService fixedPool = Executors.newFixedThreadPool(4);

// 2. 가변 크기 스레드 풀 생성
ExecutorService cachedPool = Executors.newCachedThreadPool();

// 3. 단일 스레드 풀 생성
ExecutorService singlePool = Executors.newSingleThreadExecutor();
</code></pre>
<ul>
<li><strong><code>newFixedThreadPool(int nThreads)</code></strong>: 지정한 개수만큼의 스레드를 고정으로 생성하여 재사용한다. 작업이 몰리면 큐(Queue)에 쌓아두고 순차 처리한다.</li>
<li><strong><code>newCachedThreadPool()</code></strong>: 필요에 따라 스레드를 동적으로 생성한다. 놀고 있는 스레드가 있다면 재사용하고, 60초 동안 사용되지 않은 스레드는 제거한다.</li>
<li><strong><code>newSingleThreadExecutor()</code></strong>: 단 하나의 스레드로 모든 작업을 순차적으로(FIFO) 처리한다.</li>
</ul>
<hr>
<h2 id="5-executorservice-사용-예시-및-콘솔-출력-결과">5. ExecutorService 사용 예시 및 콘솔 출력 결과</h2>
<h3 id="예시-코드">예시 코드</h3>
<pre><code class="language-java">import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorExample {
    public static void main(String[] args) {
        // 스레드 2개를 가진 고정 스레드 풀 생성
        ExecutorService executor = Executors.newFixedThreadPool(2);

        // 5개의 작업 제출
        for (int i = 1; i &lt;= 5; i++) {
            final int taskId = i;
            executor.execute(() -&gt; {
                System.out.println(Thread.currentThread().getName() + &quot; - 작업 &quot; + taskId + &quot; 수행 중&quot;);
            });
        }

        // 작업 완료 후 ExecutorService 종료
        executor.shutdown();
    }
}
</code></pre>
<h3 id="실행-콘솔-출력-예시">실행 콘솔 출력 예시</h3>
<pre><code class="language-text">pool-1-thread-1 - 작업 1 수행 중
pool-1-thread-2 - 작업 2 수행 중
pool-1-thread-1 - 작업 3 수행 중
pool-1-thread-2 - 작업 4 수행 중
pool-1-thread-1 - 작업 5 수행 중
</code></pre>
<h3 id="출력-결과의-중요한-특징">출력 결과의 중요한 특징</h3>
<ol>
<li><strong>스레드의 재사용</strong>: 스레드 이름(<code>pool-1-thread-1</code>, <code>pool-1-thread-2</code>)을 확인해 보면 새로 스레드를 계속 만드는 대신, 스레드 풀에 등록된 <strong>단 2개의 스레드가 5개의 작업을 나누어 교대로 처리</strong>한 것을 볼 수 있다.</li>
<li><strong>실행 순서의 비결정성 (비동기)</strong>: 동일한 코드를 실행하더라도 <strong>작업이 출력되는 순서가 매번 달라질 수 있다.</strong> OS의 스레드 스케줄링 및 동시성 작업 특성상 작업 제출 순서대로 완료된다는 보장이 없기 때문이다.</li>
</ol>
<blockquote>
<p>참고: <strong>execute() vs submit()</strong>
위 예시에서 사용한 execute()는 반환값이 없는(Runnable) 작업 전달에 사용한다. 만약 작업 실행 후 결과값을 반환받아야 한다면 submit() 메서드를 사용하며, 이는 Callable과 Future 를 알아야한다.</p>
</blockquote>
<hr>
<h2 id="6-executorservice의-종료">6. ExecutorService의 종료</h2>
<p>스레드 풀 내부의 스레드는 작업이 끝나도 기본적으로 대기 상태로 유지되므로, 애플리케이션이 종료되지 않고 계속 대기할 수 있다. 따라서 사용이 끝나면 반드시 종료 메서드를 호출해야 한다.</p>
<ul>
<li><strong><code>shutdown()</code></strong>: 현재 진행 중인 작업 및 큐에 대기 중인 작업까지 모두 마친 후 스레드 풀을 안전하게 종료한다.</li>
<li><strong><code>shutdownNow()</code></strong>: 즉시 종료를 시도한다. 대기 중인 작업 목록을 반환하고 진행 중인 작업에는 <code>interrupt()</code>를 발생시킨다.</li>
</ul>
<h3 id="실무-필수-패턴-awaittermination">실무 필수 패턴: awaitTermination()</h3>
<p>shutdown()은 &quot;종료 명령&quot;만 내리고 즉시 다음 코드로 넘어가기 때문에, 진행 중인 작업들이 실제로 모두 끝날 때까지 메인 스레드가 기다리게 하려면 <strong>awaitTermination()</strong>을 함께 사용하는 패턴이 자주 쓰인다.</p>
<pre><code class="language-java">executor.shutdown(); // 추가 작업 받기를 중단하고 완료 후 종료 요청

// 최대 60초 동안 기존 작업들이 완수되기를 기다림
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
    executor.shutdownNow(); // 시간 초과 시 강제 종료
}</code></pre>
<hr>
<h2 id="7-정리">7. 정리</h2>
<ol>
<li><strong>스레드 관리의 효율화</strong>: Executor 프레임워크는 스레드 직접 생성의 오버헤드를 줄이고 자원을 효율적으로 관리할 수 있게 한다.</li>
<li><strong>역할의 분리</strong>: 작업 내용과 스레드 생명주기 관리 로직을 분리하여 코드의 가독성과 유지보수성을 높인다.</li>
<li><strong>자원 보호</strong>: 스레드 풀의 크기를 제한함으로써 과도한 동시 요청으로 인한 시스템 마비를 방지한다.</li>
<li><strong>명시적 종료 필수</strong>: 스레드 풀을 사용한 후에는 프로세스가 종료되지 않는 현상을 막기 위해 반드시 <code>shutdown()</code>을 호출해야 한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (26) - synchronized와 동기화]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-26-synchronized%EC%99%80-%EB%8F%99%EA%B8%B0%ED%99%94</link>
            <guid>https://velog.io/@eta_kyung/JAVA-26-synchronized%EC%99%80-%EB%8F%99%EA%B8%B0%ED%99%94</guid>
            <pubDate>Sun, 09 Aug 2026 13:05:37 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>멀티스레드(Multi-thread) 환경에서는 여러 스레드가 하나의 공유 자원에 동시에 접근하여 값을 읽거나 수정할 수 있다. 이때 적절한 제어가 이루어지지 않으면 데이터의 일관성이 깨지는 신뢰성 문제가 발생한다.</p>
<p>자바에서는 이러한 멀티스레드 환경에서 발생할 수 있는 스레드 간의 충돌을 방지하고, 하나의 스레드 작업이 끝나기 전까지 다른 스레드가 제어권을 빼앗지 못하도록 방어하는 기술을 동기화(Synchronization)라고 한다. 자바는 이를 구현하기 위해 <code>synchronized</code> 키워드와 Monitor(모니터) 메커니즘을 제공한다.</p>
<hr>
<h2 id="2-프로세스-메모리와-임계-영역critical-section">2. 프로세스 메모리와 임계 영역(Critical Section)</h2>
<p>동기화를 이해하려면 먼저 멀티스레드 환경에서 메모리가 어떻게 공유되는지 이해해야 한다.</p>
<ul>
<li><strong>공유 영역</strong>: Heap(힙) 영역과 Static(메서드) 영역은 모든 스레드가 공유한다.</li>
<li><strong>독립 영역</strong>: Stack(스택) 영역은 스레드마다 독립적으로 할당된다.</li>
</ul>
<p>따라서 Heap 영역에 위치한 객체의 인스턴스 변수나 Static 변수는 모든 스레드가 동시에 접근할 수 있는 대상이다. 이때 <strong>동시 접근 시 문제가 발생할 수 있는 코드 영역</strong>을 임계 영역(Critical Section)이라고 부르며, 자바는 이 임계 영역에 Lock(락)을 걸어 동기화를 보장한다.</p>
<hr>
<h2 id="3-synchronized-키워드의-사용법">3. synchronized 키워드의 사용법</h2>
<p>자바에서 <code>synchronized</code> 키워드는 크게 두 가지 방식으로 사용된다.</p>
<h3 id="①-메서드-전체-동기화">① 메서드 전체 동기화</h3>
<p>메서드 선언부에 <code>synchronized</code>를 붙이는 방식이다. 해당 메서드가 호출되면, 메서드가 속한 객체(Instance)의 Lock을 획득하게 된다.</p>
<pre><code class="language-java">public synchronized void withdraw(int money) {
    if (balance &gt;= money) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}
        balance -= money;
    }
}
</code></pre>
<h3 id="②-동기화-블록synchronized-block">② 동기화 블록(Synchronized Block)</h3>
<p>메서드 전체에 Lock을 걸면 성능 저하가 발생할 수 있다. 특정 코드 구간만 동기화가 필요한 경우 블록 단위로 지정할 수 있다.</p>
<pre><code class="language-java">public void withdraw(int money) {
    // 동기화가 필요 없는 로직...

    synchronized(this) { // Lock을 걸 객체 지정
        if (balance &gt;= money) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            balance -= money;
        }
    }
}
</code></pre>
<hr>
<h2 id="4-모니터monitor와-락lock의-작동-원리">4. 모니터(Monitor)와 락(Lock)의 작동 원리</h2>
<p>자바의 모든 객체는 내부적으로 하나의 Lock(intrinsic lock 또는 monitor lock)을 가지고 있다.</p>
<ol>
<li>스레드가 <code>synchronized</code> 영역에 진입하려고 하면 해당 객체의 Lock을 요청한다.</li>
<li>획득에 성공한 스레드만 임계 영역을 실행할 수 있다.</li>
<li>다른 스레드가 이미 Lock을 선점하고 있다면, 나머지 스레드들은 Lock이 반환될 때까지 <strong>BLOCKED(대기)</strong> 상태로 전환된다.</li>
<li>임계 영역의 코드가 끝난 스레드는 Lock을 반환하고 대기 중인 스레드 중 하나가 Lock을 가져간다.</li>
</ol>
<hr>
<h2 id="5-wait와-notify를-이용한-스레드-제어">5. wait()와 notify()를 이용한 스레드 제어</h2>
<p>동기화 영역 내부에서 스레드 간의 효율적인 순서 제어를 위해 <code>Object</code> 클래스의 <code>wait()</code>, <code>notify()</code>, <code>notifyAll()</code> 메서드를 사용한다.</p>
<ul>
<li><strong><code>wait()</code></strong>: 현재 스레드가 획득한 Lock을 내려놓고 대기 상태(WAITING)로 들어간다.</li>
<li><strong><code>notify()</code></strong>: 대기 중인 스레드 중 하나를 깨워 실행 가능 상태(RUNNABLE)로 만든다.</li>
<li><strong><code>notifyAll()</code></strong>: 대기 중인 모든 스레드를 깨운다.</li>
</ul>
<blockquote>
<p><strong>주의</strong>: <code>wait()</code>와 <code>notify()</code>는 반드시 <code>synchronized</code> 블록 내부에서만 호출할 수 있다.</p>
</blockquote>
<hr>
<h2 id="6-예시">6. 예시</h2>
<h3 id="1-동기화가-필요한-이유-동시성-문제-예시">1. 동기화가 필요한 이유 (동시성 문제 예시)</h3>
<p>동기화를 적용하지 않았을 때 어떤 문제가 발생하는지 은행 계좌 출금 예제로 확인한다.</p>
<h4 id="동기화-미적용-코드">동기화 미적용 코드</h4>
<pre><code class="language-java">class Account {
    private int balance = 1000;

    public int getBalance() {
        return balance;
    }

    // 동기화가 처리되지 않은 출금 메서드
    public void withdraw(int money) {
        if (balance &gt;= money) {
            try {
                // 스레드 작업 지연을 가상으로 구현 (문제 발생 가능성 극대화)
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            balance -= money;
        }
    }
}

class ThreadExample implements Runnable {
    Account acc = new Account();

    @Override
    public void run() {
        while (acc.getBalance() &gt; 0) {
            // 100원, 200원, 300원 중 랜덤으로 출금 시도
            int money = (int) (Math.random() * 3 + 1) * 100;
            acc.withdraw(money);
            System.out.println(&quot;현재 잔액: &quot; + acc.getBalance());
        }
    }
}
</code></pre>
<p>위 코드를 두 개 이상의 스레드가 동시에 실행하면 잔액 검사(<code>if (balance &gt;= money)</code>) 조건문을 통과한 후 다른 스레드가 먼저 출금을 진행하여 <strong>잔액이 마이너스(<code>-</code>)가 되는 문제</strong>가 발생한다.</p>
<hr>
<h3 id="2-synchronized-키워드-사용-예시">2. synchronized 키워드 사용 예시</h3>
<p><code>synchronized</code> 키워드는 메서드에 적용하거나 코드 블록 단위로 적용하여 위의 문제를 해결한다.</p>
<h4 id="①-메서드-동기화-method-synchronization">① 메서드 동기화 (Method Synchronization)</h4>
<p>메서드 선언부에 <code>synchronized</code>를 작성하여 메서드 전체를 임계 영역으로 지정한다.</p>
<pre><code class="language-java">public synchronized void withdraw(int money) {
    if (balance &gt;= money) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        balance -= money;
    }
}
</code></pre>
<h4 id="②-동기화-블록-synchronized-block">② 동기화 블록 (Synchronized Block)</h4>
<p>메서드 전체에 Lock을 걸지 않고, 최소한의 공유 자원 접근 영역만 블록으로 감싸 성능 저하를 줄인다.</p>
<pre><code class="language-java">public void withdraw(int money) {
    // 동기화가 필요 없는 일반 검증 로직
    System.out.println(&quot;출금 요청 금액: &quot; + money);

    synchronized (this) { // this 객체의 Lock을 획득
        if (balance &gt;= money) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            balance -= money;
        }
    }
}
</code></pre>
<hr>
<h3 id="3-wait와-notify를-이용한-생산자-소비자-패턴-예시">3. wait()와 notify()를 이용한 생산자-소비자 패턴 예시</h3>
<p>스레드가 조건이 맞지 않을 때 락을 계속 잡고 기다리는 대신, <code>wait()</code>로 락을 반납하고 <code>notify()</code>로 제어권을 넘겨주는 대표적인 사용예시다.</p>
<pre><code class="language-java">class Table {
    private String dish;

    // 음식을 추가하는 메서드 (생산자)
    public synchronized void addDish(String dishName) {
        while (this.dish != null) {
            System.out.println(&quot;테이블에 음식이 이미 있습니다. 대기합니다.&quot;);
            try {
                wait(); // 음식이 비워질 때까지 대기 (Lock 반납)
            } catch (InterruptedException e) {}
        }

        this.dish = dishName;
        System.out.println(&quot;요리사가 음식을 올렸습니다: &quot; + dishName);
        notify(); // 대기 중인 손님 스레드를 깨움
    }

    // 음식을 먹는 메서드 (소비자)
    public synchronized void removeDish() {
        while (this.dish == null) {
            System.out.println(&quot;음식이 아직 없습니다. 대기합니다.&quot;);
            try {
                wait(); // 음식이 나올 때까지 대기 (Lock 반납)
            } catch (InterruptedException e) {}
        }

        System.out.println(&quot;손님이 음식을 먹었습니다: &quot; + this.dish);
        this.dish = null;
        notify(); // 대기 중인 요리사 스레드를 깨움
    }
}
</code></pre>
<h2 id="7-정리">7. 정리</h2>
<ol>
<li>동기화(Synchronization)는 멀티스레드 환경에서 데이터 일관성을 지키기 위해 필수적인 기술이다.</li>
<li><strong><code>synchronized</code></strong> 키워드를 사용하면 객체당 1개만 존재하는 Lock을 기반으로 임계 영역을 보호할 수 있다.
synchronized의 기준은 &#39;클래스&#39;가 아닌 &#39;객체 인스턴스&#39;다.</li>
<li>무분별한 메서드 동기화는 프로그램의 전체적인 성능 저하(병목 현상)를 유발하므로, 가능한 한 <strong>동기화 블록</strong>을 이용해 최적의 범위만 지정하는 것이 권장된다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (25) - Thread 와 Runnable]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-25-Thread-%EC%99%80-Runnable</link>
            <guid>https://velog.io/@eta_kyung/JAVA-25-Thread-%EC%99%80-Runnable</guid>
            <pubDate>Sun, 09 Aug 2026 12:20:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바(Java) 애플리케이션은 기본적으로 하나의 메인 스레드(Main Thread)에서 코드를 순차적으로 실행한다. 하지만 네트워크 통신, 파일 입출력, 복잡한 연산 등 시간이 오래 걸리는 작업을 처리할 때는 작업이 끝날 때까지 프로그램 전체가 대기하는 현상이 발생한다.</p>
<p>이러한 문제를 해결하고 프로그램의 처리 효율을 높이기 위해 <strong>다중 스레드(Multi-Thread)</strong> 환경을 사용한다. 자바에서 스레드를 생성하고 관리하는 가장 대표적인 방법은 <code>Thread</code> 클래스를 상속받는 방식과 <code>Runnable</code> 인터페이스를 구현하는 방식 두 가지가 있다.</p>
<hr>
<h2 id="2-thread-클래스를-상속받는-방법">2. Thread 클래스를 상속받는 방법</h2>
<p>자바에서 제공하는 <code>java.lang.Thread</code> 클래스를 직접 상속(extends)받아 스레드를 구현할 수 있다.</p>
<pre><code class="language-java">class MyThread extends Thread {
    @Override
    public void run() {
        // 스레드가 수행할 작업 작성
        System.out.println(&quot;Thread 상속 방식 실행 중: &quot; + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 새로운 스레드 생성 및 run() 실행
    }
}
</code></pre>
<ul>
<li><code>Thread</code> 클래스를 상속받은 뒤 <code>run()</code> 메서드를 재정의(Override)하여 스레드가 실행할 로직을 작성한다.</li>
<li>실행 시에는 <code>run()</code>을 직접 호출하지 않고 <code>start()</code> 메서드를 호출한다. <code>start()</code>는 새로운 스레드를 위한 호출 스택(Call Stack)을 생성한 뒤 <code>run()</code>을 실행시킨다.</li>
</ul>
<hr>
<h2 id="3-runnable-인터페이스를-구현하는-방법">3. Runnable 인터페이스를 구현하는 방법</h2>
<p><code>java.lang.Runnable</code> 인터페이스를 구현(implements)하여 스레드 작업 로직을 정의할 수 있다.</p>
<pre><code class="language-java">class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 스레드가 수행할 작업 작성
        System.out.println(&quot;Runnable 구현 방식 실행 중: &quot; + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable); // Runnable 객체를 Thread 생성자에 전달
        thread.start();
    }
}
</code></pre>
<ul>
<li><code>Runnable</code> 인터페이스는 <code>run()</code> 추상 메서드 하나만 가지는 함수형 인터페이스다.</li>
<li>구현체 자체는 스레드가 아니므로, 작업 로직을 담은 <code>Runnable</code> 인스턴스를 <code>Thread</code> 객체의 생성자 매개변수로 전달한 뒤 <code>start()</code>를 호출해야 한다.</li>
<li>람다식(Lambda Expression)이나 익명 객체를 활용해 간결하게 작성할 수도 있다.</li>
</ul>
<pre><code class="language-java">Thread thread = new Thread(() -&gt; {
    System.out.println(&quot;람다 표현식을 활용한 Runnable 실행&quot;);
});
thread.start();
</code></pre>
<hr>
<h2 id="4-thread-상속-vs-runnable-구현-비교">4. Thread 상속 vs Runnable 구현 비교</h2>
<p>두 방식 모두 멀티스레딩을 구현하는 목적은 동일하지만, 설계 측면에서 명확한 차이가 존재한다.</p>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th>Thread 상속</th>
<th>Runnable 구현</th>
</tr>
</thead>
<tbody><tr>
<td><strong>자바 상속 제약</strong></td>
<td>단일 상속만 지원되므로 다른 클래스를 상속받을 수 없음</td>
<td>인터페이스 구현이므로 다른 클래스 상속 가능</td>
</tr>
<tr>
<td><strong>코드 재사용성</strong></td>
<td>스레드 클래스와 작업 로직이 결합됨</td>
<td>작업 로직과 스레드 객체가 분리되어 재사용성이 높음</td>
</tr>
<tr>
<td><strong>유연성</strong></td>
<td>낮은 유연성</td>
<td>객체 지향적 설계 및 람다식 활용에 유용</td>
</tr>
</tbody></table>
<p>자바는 클래스의 단일 상속만 허용하므로 <code>Thread</code> 클래스를 상속받으면 다른 클래스를 상속받을 수 없게 된다. 반면 <code>Runnable</code> 인터페이스를 구현하면 필요한 다른 클래스를 자유롭게 상속받으면서도 스레드 기능을 사용할 수 있다.</p>
<p>또한 <code>Runnable</code> 방식은 수행할 작업(Task)과 작업을 실행하는 주체(Thread)를 분리하는 객체 지향적 구조를 가지므로 <code>ExecutorService</code>와 같은 스레드 풀(Thread Pool) 기술과 연동하기도 훨씬 용이하다.</p>
<hr>
<h2 id="5-정리">5. 정리</h2>
<ul>
<li>자바에서 멀티스레드를 구현하는 방법은 <code>Thread</code> 클래스 상속과 <code>Runnable</code> 인터페이스 구현 두 가지가 있다.</li>
<li>두 방식 모두 실제 스레드를 동작시킬 때는 <code>run()</code>이 아닌 <code>start()</code> 메서드를 호출해야 한다.</li>
<li><code>Thread</code> 상속 방식은 다중 상속이 불가능하다는 제약이 있어 구조가 경직되는 단점이 있다.</li>
<li>따라서 실무에서는 코드의 재사용성과 유연성이 높고, 스레드 풀 등 고도화된 동기화 프레임워크와 쉽게 결합할 수 있는 <strong><code>Runnable</code> 인터페이스 구현 방식</strong>을 주로 권장한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (24) - Optional]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-24-Optional</link>
            <guid>https://velog.io/@eta_kyung/JAVA-24-Optional</guid>
            <pubDate>Sun, 09 Aug 2026 12:11:52 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바 프로그래밍에서 가장 흔하게 발생하는 예외 중 하나는 <code>NullPointerException</code> (NPE)이다. NPE를 방지하기 위해 과거에는 코드 곳곳에 <code>if (obj != null)</code>과 같은 조건문을 가득 채워 넣어야 했고, 이는 코드의 가독성을 크게 떨어뜨렸다. Java 8에서는 이러한 문제를 해결하고 null 처리를 보다 안전하고 간결하게 다루기 위해 <code>java.util.Optional&lt;T&gt;</code> 클래스를 도입했다. </p>
<h2 id="2-optional이란">2. Optional이란?</h2>
<p><code>Optional&lt;T&gt;</code>은 null이 될 수도 있는 객체를 감싸는 단일 값 래퍼(Wrapper) 클래스다. 객체가 null이더라도 직접 참조하는 대신 Optional이라는 상자에 담아 다룸으로써 <code>NullPointerException</code> 발생 가능성을 대폭 줄여준다.</p>
<ul>
<li><strong>NPE 방지</strong>: 객체에 직접 접근하기 전 값이 존재하는지 명시적으로 확인하거나 대체값을 지정하도록 유도한다.</li>
<li><strong>코드 가독성 향상</strong>: 메서드의 반환 타입으로 Optional을 사용하면, 호출하는 쪽에 &quot;이 메서드는 null을 반환할 수 있다&quot;는 의도를 명확하게 전달한다.</li>
</ul>
<h2 id="3-optional-객체-생성-방법">3. Optional 객체 생성 방법</h2>
<p>Optional 객체는 주로 다음 3가지 정적 팩토리 메서드를 사용하여 생성한다.</p>
<ol>
<li><strong><code>Optional.empty()</code></strong>: 빈 Optional 객체를 생성한다.<pre><code class="language-java">Optional&lt;User&gt; emptyOpt = Optional.empty();
</code></pre>
</li>
</ol>
<pre><code>

2. **`Optional.of(value)`**: null이 아닌 객체를 담은 Optional 객체를 생성한다. 만약 `value`가 null이면 즉시 `NullPointerException`이 발생한다.
```java
Optional&lt;User&gt; userOpt = Optional.of(user);
</code></pre><ol start="3">
<li><strong><code>Optional.ofNullable(value)</code></strong>: null일 가능성이 있는 객체를 담은 Optional 객체를 생성한다. <code>value</code>가 null이면 빈 Optional 객체를 반환한다.<pre><code class="language-java">Optional&lt;User&gt; nullableOpt = Optional.ofNullable(user);
</code></pre>
</li>
</ol>
<pre><code>


## 4. Optional 값 꺼내기 및 활용 메서드

Optional 내부에 저장된 값에 접근하거나, 값이 없을 때의 동작을 정의하기 위해 다양한 메서드를 제공한다.

* **`orElse(T other)`**: 값이 존재하면 그 값을 반환하고, 없으면 기본값(`other`)을 반환한다. (값이 있어도 `other` 생성 인스턴스는 평가됨)
* **`orElseGet(Supplier&lt;? extends T&gt; supplier)`**: 값이 존재하면 그 값을 반환하고, 없으면 `Supplier`를 실행하여 결과를 반환한다. (값이 없을 때만 로직이 수행되어 성능상 유리)
* **`orElseThrow(Supplier&lt;? extends X&gt; exceptionSupplier)`**: 값이 존재하면 반환하고, 없으면 지정한 예외를 던진다.
* **`ifPresent(Consumer&lt;? super T&gt; action)`**: 값이 존재할 때만 전달된 람다식을 실행한다.
* **`map()` / `flatMap()` / `filter()**`: 스트림과 유사하게 Optional 내부 값을 변환하거나 조건에 맞는지 필터링한다.

```java
// 예시: 값이 없으면 기본 객체를 생성하거나 예외를 던짐
User user = userOpt.orElseGet(() -&gt; new User(&quot;Guest&quot;));
User validUser = userOpt.orElseThrow(() -&gt; new IllegalArgumentException(&quot;유저가 존재하지 않습니다.&quot;));
</code></pre><h2 id="예시-1-조건문-중첩npe-체이닝-해결하기">예시 1: 조건문 중첩(NPE 체이닝) 해결하기</h2>
<p>사용자 객체에서 주소 정보를 거쳐 도시(City) 이름을 가져오는 상황이다. 중간에 하나라도 null이 있으면 바로 <code>NullPointerException</code>이 발생하기 때문에 기존 방식에서는 복잡한 if 문 중첩이 필요했다.</p>
<h3 id="기존-방식-null-체크-조건문-중첩">기존 방식 (Null 체크 조건문 중첩)</h3>
<pre><code class="language-java">public String getCityOfUser(User user) {
    if (user != null) {
        Address address = user.getAddress();
        if (address != null) {
            String city = address.getCity();
            if (city != null) {
                return city;
            }
        }
    }
    return &quot;Unknown&quot;; // 중간에 하나라도 null이면 기본값 반환
}
</code></pre>
<h3 id="optional-적용">Optional 적용</h3>
<p><code>map()</code>과 <code>orElse()</code>를 체이닝하여 안전하게 들여쓰기 없이 단 한 줄로 처리할 수 있다.</p>
<pre><code class="language-java">public String getCityOfUser(User user) {
    return Optional.ofNullable(user)
            .map(User::getAddress)
            .map(Address::getCity)
            .orElse(&quot;Unknown&quot;); // 값이 없거나 중간에 null이 있으면 &quot;Unknown&quot; 반환
}
</code></pre>
<hr>
<h2 id="예시-2-데이터가-없을-때-기본값-처리-orelse-vs-orelseget">예시 2: 데이터가 없을 때 기본값 처리 (<code>orElse</code> vs <code>orElseGet</code>)</h2>
<p>DB에서 회원 정보를 조회할 때 검색 결과가 없으면 기본 회원을 반환하거나 새로 생성해야 하는 상황이다.</p>
<h3 id="기존-방식">기존 방식</h3>
<pre><code class="language-java">User user = userRepository.findByName(&quot;Hong&quot;);
if (user == null) {
    user = new User(&quot;Guest&quot;); // null일 때만 기본값 생성
}
</code></pre>
<h3 id="optional-적용-1">Optional 적용</h3>
<p><code>orElseGet()</code>을 사용하면 <strong>값이 존재하지 않을 때만</strong> 람다식이 실행되어 효율적으로 기본 객체를 생성할 수 있다.</p>
<pre><code class="language-java">// findByName이 Optional&lt;User&gt;를 반환한다고 가정
User user = userRepository.findByName(&quot;Hong&quot;)
        .orElseGet(() -&gt; new User(&quot;Guest&quot;)); // Hong이 없을 때만 Guest 객체 생성
</code></pre>
<hr>
<h2 id="예시-3-데이터가-없을-때-예외-던지기-orelsethrow">예시 3: 데이터가 없을 때 예외 던지기 (<code>orElseThrow</code>)</h2>
<p>회원 조회를 시도했을 때 사용자가 존재하지 않으면 즉시 에러를 발생시켜 비즈니스 로직을 중단해야 하는 케이스다.</p>
<h3 id="기존-방식-1">기존 방식</h3>
<pre><code class="language-java">User user = userRepository.findById(userId);
if (user == null) {
    throw new IllegalArgumentException(&quot;존재하지 않는 회원입니다. id: &quot; + userId);
}
</code></pre>
<h3 id="optional-적용-2">Optional 적용</h3>
<p>Optional의 <code>orElseThrow()</code>를 사용하면 의도가 명확하게 드러나는 한 줄의 코드로 작성할 수 있다.</p>
<pre><code class="language-java">User user = userRepository.findById(userId)
        .orElseThrow(() -&gt; new IllegalArgumentException(&quot;존재하지 않는 회원입니다. id: &quot; + userId));
</code></pre>
<h2 id="5-optional-사용-시-주의사항-best-practices">5. Optional 사용 시 주의사항 (Best Practices)</h2>
<p>Optional은 오용할 경우 오히려 코드가 복잡해지거나 성능이 저하될 수 있으므로 주의해서 사용해야 한다.</p>
<ul>
<li><strong>반환 타입으로만 사용</strong>: Optional은 주로 메서드의 반환 타입으로 사용하도록 설계되었다. 필드(Field) 타입, 메서드 매개변수(Parameter), 컬렉션의 요소로 사용하는 것은 권장되지 않는다.</li>
<li><strong><code>get()</code> 직접 호출 자제</strong>: 값이 없는 상태에서 <code>get()</code>을 호출하면 <code>NoSuchElementException</code>이 발생하므로 <code>orElseGet()</code>이나 <code>orElseThrow()</code>를 사용하는 것이 안전하다.</li>
<li><strong>컬렉션이나 배열은 Optional로 감싸지 않기</strong>: <code>List</code>, <code>Set</code>, <code>Map</code> 등은 그 자체로 비어있는 상태(Empty Collection)를 표현할 수 있으므로 Optional을 사용하지 않고 빈 컬렉션을 반환하는 것이 올바른 방식이다.</li>
</ul>
<h2 id="6-정리">6. 정리</h2>
<p>Java의 Optional은 null 다루기를 안전하게 만들고 <code>NullPointerException</code>을 효과적으로 예방하는 유용한 도구다. 단순한 조건문 중첩을 줄이고 메서드 반환 값에 대한 명시적인 의도를 표현할 수 있게 도와준다. 하지만 모든 곳에 남용하기보다는 주로 메서드의 반환 타입으로 제한하고, <code>orElseGet</code>이나 <code>orElseThrow</code> 등 적절한 대체 메서드와 함께 활용하는 것이 바람직하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (23) - 람다와 스트림]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-23-%EB%9E%8C%EB%8B%A4%EC%99%80-%EC%8A%A4%ED%8A%B8%EB%A6%BC</link>
            <guid>https://velog.io/@eta_kyung/JAVA-23-%EB%9E%8C%EB%8B%A4%EC%99%80-%EC%8A%A4%ED%8A%B8%EB%A6%BC</guid>
            <pubDate>Sun, 09 Aug 2026 09:01:55 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>Java 8에서 도입된 가장 큰 변화 중 하나는 함수형 프로그래밍 스타일의 도입이다. 그 중심에는 람다식(Lambda Expression)과 스트림 API(Stream API)가 존재한다. 람다식을 통해 코드를 더욱 간결하고 직관적으로 작성할 수 있게 되었으며, 스트림을 사용하면 컬렉션이나 배열 데이터를 효율적이고 통일된 방식으로 처리할 수 있다.</p>
<h2 id="2-람다식lambda-expression의-개념과-특징">2. 람다식(Lambda Expression)의 개념과 특징</h2>
<p>람다식은 익명 함수(Anonymous Function)를 생성하기 위한 식이다. 메서드를 하나의 간결한 &#39;식(Expression)&#39;으로 표현하여 코드를 줄이고 가독성을 높여준다.</p>
<ul>
<li><strong>기본 문법</strong>: <code>(매개변수) -&gt; { 실행문; }</code></li>
<li><strong>주요 특징</strong>:</li>
<li>메서드의 이름과 반환 타입을 생략할 수 있어 코드가 매우 간결해진다.</li>
<li>람다식은 함수형 인터페이스(단 하나의 추상 메서드만 가지는 인터페이스)의 구현체로 취급된다.</li>
<li>대표적인 함수형 인터페이스로는 <code>Runnable</code>, <code>Supplier</code>, <code>Consumer</code>, <code>Function</code>, <code>Predicate</code> 등이 있다.</li>
</ul>
<pre><code class="language-java">// 기존 익명 클래스 방식
Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.println(&quot;Hello World!&quot;);
    }
};

// 람다식 적용
Runnable runnable2 = () -&gt; System.out.println(&quot;Hello World!&quot;);
</code></pre>
<h2 id="3-스트림-apistream-api의-개념과-특징">3. 스트림 API(Stream API)의 개념과 특징</h2>
<p>스트림은 데이터 소스(컬렉션, 배열 등)를 추상화하고, 데이터를 다루는 데 자주 사용되는 메서드들을 정의해 둔 객체다.</p>
<p><strong>주요 특징</strong>:</p>
<ul>
<li><strong>원본 데이터 비변경</strong>: 스트림은 데이터 소스로부터 읽기만 할 뿐, 원본 데이터를 직접 수정하지 않는다.</li>
<li><strong>일회용</strong>: 스트림은 한 번 사용하고 닫히면 재사용할 수 없다.</li>
<li><strong>지연 연산(Lazy Evaluation)</strong>: 최종 연산이 수행되기 전까지 중간 연산은 실행되지 않는다.</li>
<li><strong>병렬 처리 지원</strong>: <code>parallelStream()</code>을 활용하면 다중 스레드를 통한 병렬 처리를 손쉽게 구현할 수 있다.</li>
</ul>
<h2 id="4-스트림의-연산-구조-및-주요-연산">4. 스트림의 연산 구조 및 주요 연산</h2>
<p>스트림을 이용한 데이터 처리는 <strong>생성 -&gt; 중간 연산 -&gt; 최종 연산</strong>의 흐름(파이프라인)을 거친다.</p>
<h3 id="1-스트림-생성">(1) 스트림 생성</h3>
<p>컬렉션이나 배열 등을 스트림 객체로 변환하는 단계다.</p>
<ul>
<li><code>list.stream()</code> / <code>Arrays.stream(array)</code> / <code>Stream.of(...)</code></li>
</ul>
<h3 id="2-중간-연산-intermediate-operations">(2) 중간 연산 (Intermediate Operations)</h3>
<p>데이터를 가공·필터링하는 단계로, 연산 결과로 새로운 스트림을 반환하므로 체이닝(Chaining)이 가능하다. 최종 연산이 호출되기 전까지는 실행되지 않는 지연 연산(Lazy Evaluation)의 특징을 갖는다.</p>
<ul>
<li><strong>필터링 및 변환</strong><ul>
<li><code>filter(Predicate)</code>: 조건에 맞는 요소만 추출</li>
<li><code>map(Function)</code>: 요소를 원하는 형태나 값으로 변환</li>
<li><code>flatMap(Function)</code>: 중첩된 구조(리스트 안의 리스트 등)를 단일 스트림으로 평평하게 변환</li>
</ul>
</li>
<li><strong>정렬 및 중복 제거</strong><ul>
<li><code>distinct()</code>: 중복 요소 제거 (<code>equals</code>, <code>hashCode</code> 기준)</li>
<li><code>sorted()</code> / <code>sorted(Comparator)</code>: 요소 오름차순 또는 지정 기준 정렬</li>
</ul>
</li>
<li><strong>자르기 및 조회</strong><ul>
<li><code>limit(long maxSize)</code>: 지정한 개수만큼 요소 잘라내기</li>
<li><code>skip(long n)</code>: 지정한 개수만큼 요소 건너뛰기</li>
<li><code>peek(Consumer)</code>: 중간 결과 확인 (디버깅 용도)</li>
</ul>
</li>
</ul>
<h3 id="3-최종-연산-terminal-operations">(3) 최종 연산 (Terminal Operations)</h3>
<p>가공된 스트림의 요소를 소모하여 최종 결과를 도출한다. 최종 연산이 실행되면 스트림 파이프라인이 닫히며 재사용할 수 없다.</p>
<ul>
<li><strong>요소 수집 및 순회</strong><ul>
<li><code>collect(Collector)</code>: List, Set, Map 등 원하는 컬렉션 타입으로 결과 수집</li>
<li><code>forEach(Consumer)</code>: 각 요소를 순회하며 지정된 작업 수행</li>
<li><code>toArray()</code>: 스트림 요소를 배열로 변환</li>
</ul>
</li>
<li><strong>통계 및 누적 계산</strong><ul>
<li><code>count()</code>: 전체 요소 개수 반환</li>
<li><code>sum()</code> / <code>average()</code>: 숫자형 스트림의 합계 및 평균 계산</li>
<li><code>max(Comparator)</code> / <code>min(Comparator)</code>: 최댓값 및 최솟값 반환 (<code>Optional</code>)</li>
<li><code>reduce(BinaryOperator)</code>: 모든 요소를 누적하여 하나의 결과로 축소</li>
</ul>
</li>
<li><strong>조건 검사 및 검색 (Short-circuit)</strong><ul>
<li><code>anyMatch(Predicate)</code>: 조건에 맞는 요소가 하나라도 있는지 확인 (boolean)</li>
<li><code>allMatch(Predicate)</code>: 모든 요소가 조건을 만족하는지 확인 (boolean)</li>
<li><code>noneMatch(Predicate)</code>: 모든 요소가 조건을 만족하지 않는지 확인 (boolean)</li>
<li><code>findFirst()</code> / <code>findAny()</code>: 조건에 맞는 첫 번째 또는 임의의 요소 반환 (<code>Optional</code>)</li>
</ul>
</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; names = Arrays.asList(&quot;Kim&quot;, &quot;Lee&quot;, &quot;Park&quot;, &quot;Kang&quot;);

// 이름이 &#39;K&#39;로 시작하는 요소를 필터링하여 대문자로 변환 후 리스트로 수집
List&lt;String&gt; kNames = names.stream()
    .filter(name -&gt; name.startsWith(&quot;K&quot;)) // 중간 연산
    .map(String::toUpperCase)            // 중간 연산
    .collect(Collectors.toList());       // 최종 연산
</code></pre>
<h2 id="5-람다와-스트림을-함께-사용할-때의-장점">5. 람다와 스트림을 함께 사용할 때의 장점</h2>
<ul>
<li><strong>가독성과 생산성 향상</strong>: 반복문(<code>for</code>, <code>while</code>)과 조건문(<code>if</code>)이 복잡하게 얽힌 코드를 단 몇 줄의 직관적인 체이닝 코드로 대체할 수 있다.</li>
<li><strong>선언형 코드 작성</strong>: &#39;어떻게 처리할 것인가(How)&#39;에 집중하는 대신 &#39;무엇을 할 것인가(What)&#39;를 명확히 표현할 수 있다.</li>
<li><strong>유연한 병렬 처리</strong>: 대용량 데이터를 다룰 때 코드를 크게 변경하지 않고도 병렬 처리를 통해 성능을 향상시킬 수 있다.</li>
</ul>
<h2 id="6-정리">6. 정리</h2>
<p>자바의 람다와 스트림은 코드를 훨씬 간결하고 가독성 있게 만들어 주는 강력한 도구다. <strong>람다식</strong>은 함수형 프로그래밍의 기반을 제공하며, <strong>스트림</strong>은 데이터 처리 파이프라인(생성-중간연산-최종연산)을 통해 데이터의 가공 및 집계를 손쉽게 처리하도록 돕는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (22) - 가비지 컬렉션]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-22-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98</link>
            <guid>https://velog.io/@eta_kyung/JAVA-22-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98</guid>
            <pubDate>Sun, 09 Aug 2026 08:40:31 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바(Java)는 C나 C++과 달리 개발자가 직접 메모리를 할당하고 해제하지 않는다. JVM(Java Virtual Machine)의 가비지 컬렉터(Garbage Collector)가 더 이상 사용되지 않는 메모리를 알아서 정리해 주기 때문이다. 메모리 관리를 자동화함으로써 개발자는 비즈니스 로직 구현에 집중할 수 있지만, 성능 최적화와 메모리 누수 방지를 위해서는 가비지 컬렉션(GC)의 동작 방식과 원리를 정확히 이해해야 한다.</p>
<h2 id="2-가비지-컬렉션gc의-기본-개념">2. 가비지 컬렉션(GC)의 기본 개념</h2>
<p>가비지 컬렉션은 JVM의 <strong>Heap 영역</strong>에서 도달 불가능한(Unreachable) 객체를 찾아 메모리에서 해제하는 과정이다.</p>
<ul>
<li><strong>Reachable</strong>: 객체가 참조되고 있어 유효한 상태를 의미한다.</li>
<li><strong>Unreachable</strong>: 어떤 변수나 객체도 해당 객체를 참조하고 있지 않아 메모리에서 제거 대상이 된 상태를 의미한다.</li>
</ul>
<p>GC는 이러한 Unreachable 객체를 주기적으로 탐지하여 회수함으로써 <code>OutOfMemoryError</code>를 예방한다.</p>
<h2 id="3-stop-the-world와-mark-and-sweep">3. Stop-the-World와 Mark and Sweep</h2>
<p>GC가 실행되는 주요 알고리즘 및 메커니즘은 크게 두 가지 개념으로 설명된다.</p>
<ol>
<li><strong>Stop-the-World (STW)</strong></li>
</ol>
<ul>
<li>GC를 실행하기 위해 JVM이 애플리케이션 실행을 중단하는 현상이다.</li>
<li>STW가 발생하면 GC를 실행하는 스레드를 제외한 모든 스레드의 작업이 멈춘다. GC 튜닝은 주로 이 STW 시간을 최소화하는 것을 목표로 한다.</li>
</ul>
<ol start="2">
<li><strong>Mark and Sweep (and Compact)</strong></li>
</ol>
<ul>
<li><strong>Mark</strong>: 메모리에서 사용 중인 객체(Reachable)를 식별하는 단계다.</li>
<li><strong>Sweep</strong>: Mark 되지 않은 식별 불가능한 객체들을 메모리에서 제거하는 단계다.</li>
<li><strong>Compact</strong>: (선택적) 메모리 파편화를 방지하기 위해 남아있는 객체들을 한곳으로 모아 정돈하는 단계다.</li>
</ul>
<h2 id="4-jvm-heap-메모리의-세대별-구조">4. JVM Heap 메모리의 세대별 구조</h2>
<p>JVM은 객체의 생존 기간에 따라 Heap 영역을 구분하는 약한 세대 가설(Weak Generational Hypothesis)을 바탕으로 설계되었다.</p>
<ul>
<li><strong>Young 영역 (Young Generation)</strong></li>
<li>새롭게 생성된 객체 대부분이 위치하는 공간이다.</li>
<li>생존 주기가 짧은 객체가 많아 여기서 발생하는 GC를 <strong>Minor GC</strong>라고 부른다.</li>
<li>Eden, Survivor 0, Survivor 1 영역으로 세분화된다.</li>
</ul>
<ul>
<li><strong>Old 영역 (Old Generation)</strong></li>
<li>Young 영역에서 끝까지 살아남은 객체가 임계값(Age)을 넘어 이동(Promotion)하여 저장되는 공간이다.</li>
<li>덩치가 크고 오래 살아남는 객체가 모이며, 여기서 발생하는 GC를 <strong>Major GC</strong> (또는 Full GC)라고 부른다.</li>
</ul>
<h2 id="5-대표적인-gc-알고리즘-종류">5. 대표적인 GC 알고리즘 종류</h2>
<p>자바의 발전과 함께 GC 알고리즘도 성능 및 STW 시간 단축을 위해 계속 발전해 왔다.</p>
<ul>
<li><strong>Serial GC</strong>: 단일 스레드로 동작하는 간단한 GC로, 소규모 메모리와 CPU 코어가 적은 환경에 적합하다.</li>
<li><strong>Parallel GC</strong>: Young 영역의 Minor GC를 다중 스레드로 처리하여 속도를 향상시킨 방식이다. (Java 8 기본)</li>
<li><strong>CMS GC</strong>: STW 시간을 최소화하기 위해 애플리케이션 스레드와 GC 스레드가 동시에 실행되도록 설계된 방식이다. (현재는 Deprecated)</li>
<li><strong>G1 GC (Garbage First GC)</strong>: Heap을 바둑판 형태의 Region으로 나누어 가비지가 많은 영역을 우선적으로 정리한다. (Java 9 이상 기본)</li>
<li><strong>ZGC / Shenandoah GC</strong>: 대용량 메모리에서도 millisecond 단위의 매우 짧은 STW 시간을 보장하기 위해 도입된 고성능 GC이다.</li>
</ul>
<h2 id="6-정리">6. 정리</h2>
<p>자바의 가비지 컬렉션은 메모리 관리를 자동화하여 안정적인 개발 환경을 제공하는 핵심 메커니즘이다. Mark and Sweep 단계를 통해 도달 불가능한 객체를 정리하며, Heap 영역을 Young과 Old 세대로 나누어 효율성을 극대화한다. 서비스의 규모와 처리 특성에 맞는 적절한 GC 알고리즘을 선택하고 튜닝하면 Stop-the-World 시간을 단축하여 애플리케이션 성능을 크게 향상시킬 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (21) - 메모리 구조(Stack vs Heap)]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-21-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0Stack-vs-Heap</link>
            <guid>https://velog.io/@eta_kyung/JAVA-21-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0Stack-vs-Heap</guid>
            <pubDate>Sun, 09 Aug 2026 08:32:58 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바 애플리케이션을 효율적으로 개발하고 메모리 누수를 방지하기 위해서는 JVM(Java Virtual Machine)의 메모리 관리 방식을 이해해야 한다. JVM 메모리 영역은 크게 스태틱(Static), 스택(Stack), 힙(Heap) 영역 등으로 나뉜다. 그중에서도 프로그램 실행 시 데이터 저장과 할당이 가장 활발하게 일어나는 핵심 영역이 바로 <strong>Stack</strong>과 <strong>Heap</strong>이다.</p>
<h2 id="2-stack-영역의-개념과-특징">2. Stack 영역의 개념과 특징</h2>
<p>Stack 영역은 메서드 호출 시 생성되는 스레드(Thread)별 전용 메모리 공간이다. 정적(Static) 메모리 할당 방식을 사용하며, LIFO(Last In, First Out; 후입선출) 구조로 동작한다.</p>
<ul>
<li><strong>저장되는 데이터</strong>: 원시 타입(Primitive Type) 변수(int, double, boolean 등)의 실제 값과 참조 타입(Reference Type) 변수의 주소값이 저장된다.</li>
<li><strong>생명주기</strong>: 메서드가 호출될 때 스택 프레임(Stack Frame)이 쌓이고, 메서드 실행이 완료되면 해당 프레임이 자동으로 제거된다.</li>
<li><strong>특징</strong>: 각 스레드마다 독립적인 Stack 영역을 가지므로 스레드 직간접 공유가 불가능하며, 접근 속도가 매우 빠르다. 메모리 공간을 넘어설 경우 <code>java.lang.StackOverflowError</code>가 발생한다.</li>
</ul>
<h2 id="3-heap-영역의-개념과-특징">3. Heap 영역의 개념과 특징</h2>
<p>Heap 영역은 JVM이 관리하는 동적 메모리 할당 공간이다. 모든 스레드가 공유하는 영역으로, 자바 프로그램 실행 중 동적으로 생성되는 객체들이 거주하는 공간이다.</p>
<ul>
<li><strong>저장되는 데이터</strong>: <code>new</code> 연산자를 통해 생성된 모든 객체(Object)와 배열(Array)이 실제 데이터로 저장된다.</li>
<li><strong>생명주기</strong>: 메서드 실행 종료와 관계없이 객체가 더 이상 참조되지 않을 때까지 유지된다.</li>
<li><strong>특징</strong>: 가비지 컬렉터(Garbage Collector, GC)의 관리 대상이 된다. 어떤 변수도 참조하지 않는 도달 불가능한(Unreachable) 객체는 GC에 의해 메모리에서 해제된다. 메모리가 부족하면 <code>java.lang.OutOfMemoryError</code>가 발생한다.</li>
</ul>
<h2 id="4-stack과-heap의-상호작용">4. Stack과 Heap의 상호작용</h2>
<p>실제 코드에서 두 영역은 서로 긴밀하게 연결되어 동작한다. 예를 들어 다음과 같은 코드가 있다고 가정해 보자.</p>
<pre><code class="language-java">public void example() {
    int age = 25;
    String name = new String(&quot;Java&quot;);
}
</code></pre>
<ul>
<li><code>int age = 25;</code>: 기본 타입 변수인 <code>age</code>와 그 값 <code>25</code>는 <strong>Stack</strong> 영역에 저장된다.</li>
<li><code>String name = new String(&quot;Java&quot;);</code>: 참조 변수인 <code>name</code>은 <strong>Stack</strong> 영역에 생성되지만, <code>new</code> 키워드로 생성된 실제 <code>&quot;Java&quot;</code> 문자열 객체는 <strong>Heap</strong> 영역에 할당된다. Stack의 <code>name</code> 변수는 Heap에 있는 객체의 <strong>메모리 주소</strong>를 가리킨다.</li>
</ul>
<h2 id="5-핵심-차이점-비교">5. 핵심 차이점 비교</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>Stack 영역</th>
<th>Heap 영역</th>
</tr>
</thead>
<tbody><tr>
<td><strong>저장 데이터</strong></td>
<td>원시 타입 값, 참조 변수(주소값), 지역 변수</td>
<td>실제 객체(Instance) 및 배열</td>
</tr>
<tr>
<td><strong>할당 방식</strong></td>
<td>컴파일 타임 크기 결정 (정적)</td>
<td>런타임 동적 할당</td>
</tr>
<tr>
<td><strong>스레드 공유</strong></td>
<td>각 스레드 독립 공간 (공유 X)</td>
<td>모든 스레드 공유</td>
</tr>
<tr>
<td><strong>해제 방식</strong></td>
<td>메서드 종료 시 자동 해제</td>
<td>가비지 컬렉터(GC)에 의해 해제</td>
</tr>
<tr>
<td><strong>속도 및 크기</strong></td>
<td>속도 매우 빠름 / 크기 제한적</td>
<td>Stack에 비해 속도 느림 / 크기 큼</td>
</tr>
</tbody></table>
<h2 id="6-정리">6. 정리</h2>
<p>자바의 Stack과 Heap은 데이터의 성격과 생명주기에 따라 역할을 분담한다. Stack은 빠른 접근과 자동 메모리 관리를 바탕으로 함수 실행 단위의 변수를 처리하고, Heap은 크기가 유동적인 객체를 저장하며 가비지 컬렉터를 통해 관리된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (20) - I/O 기초(스트림)]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-20-IO-%EA%B8%B0%EC%B4%88%EC%8A%A4%ED%8A%B8%EB%A6%BC</link>
            <guid>https://velog.io/@eta_kyung/JAVA-20-IO-%EA%B8%B0%EC%B4%88%EC%8A%A4%ED%8A%B8%EB%A6%BC</guid>
            <pubDate>Sun, 09 Aug 2026 08:24:54 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바에서 I/O(Input/Output)는 프로그램이 외부 장치(키보드, 파일, 네트워크, 모니터 등)와 데이터를 주고받는 입출력 작업을 의미한다. 자바는 모든 입출력을 <strong>스트림(Stream)</strong>이라는 일관된 개념을 통해 처리하며, 데이터의 단위에 따라 <strong>바이트 기반 스트림</strong>과 <strong>문자 기반 스트림</strong>으로 구분하여 지원한다.</p>
<h2 id="2-스트림stream의-개념과-특징">2. 스트림(Stream)의 개념과 특징</h2>
<p>자바에서의 스트림은 데이터가 이동하는 통로(흐름)를 의미한다. (※ Java 8의 Stream API와는 다른 개념이다.)</p>
<ul>
<li><strong>단방향성:</strong> 스트림은 단방향으로만 데이터가 흐른다. 따라서 입력(Input)과 출력(Output)을 동시에 수행하려면 입력 스트림과 출력 스트림을 각각 따로 생성해야 한다.</li>
<li><strong>FIFO (First In First Out):</strong> 먼저 들어간 데이터가 먼저 나오는 구조를 가진다.</li>
<li><strong>지연 가능성:</strong> 데이터가 전부 들어오거나 출력될 때까지 프로그램이 대기 상태(Blocking)에 빠질 수 있다.</li>
</ul>
<h2 id="3-바이트-기반-스트림-byte-stream">3. 바이트 기반 스트림 (Byte Stream)</h2>
<p>그림, 음악, 동영상, 실행 파일 등 모든 종류의 이진 데이터(Binary Data)를 1바이트(byte) 단위로 입출력하는 스트림이다.</p>
<ul>
<li><strong>최상위 클래스:</strong> <code>InputStream</code> (입력), <code>OutputStream</code> (출력) - 둘 다 추상 클래스다.</li>
<li><strong>주요 구현 클래스:</strong> <code>FileInputStream</code>, <code>FileOutputStream</code>, <code>ByteArrayInputStream</code>, <code>BufferedInputStream</code> 등</li>
</ul>
<pre><code class="language-java">// 파일 복사 예시 (바이트 단위)
try (FileInputStream fis = new FileInputStream(&quot;input.png&quot;);
     FileOutputStream fos = new FileOutputStream(&quot;output.png&quot;)) {

    int data;
    while ((data = fis.read()) != -1) { // 1바이트씩 읽기 (-1은 파일의 끝)
        fos.write(data);               // 1바이트씩 쓰기
    }
} catch (IOException e) {
    e.printStackTrace();
}
</code></pre>
<h2 id="4-문자-기반-스트림-character-stream">4. 문자 기반 스트림 (Character Stream)</h2>
<p>텍스트 문자를 입출력할 때 사용하는 스트림이다. 자바에서 <code>char</code>는 2바이트(Unicode)를 사용하므로, 1바이트 단위인 바이트 스트림으로 인코딩된 텍스트를 처리할 경우 깨짐 현상이 발생할 수 있다. 이를 방지하기 위해 2바이트(문자) 단위로 처리한다.</p>
<ul>
<li><strong>최상위 클래스:</strong> <code>Reader</code> (입력), <code>Writer</code> (출력) - 둘 다 추상 클래스다.</li>
<li><strong>주요 구현 클래스:</strong> <code>FileReader</code>, <code>FileWriter</code>, <code>BufferedReader</code>, <code>BufferedWriter</code> 등</li>
</ul>
<pre><code class="language-java">// 텍스트 파일 읽기 예시 (문자 단위)
try (FileReader fr = new FileReader(&quot;example.txt&quot;)) {
    int ch;
    while ((ch = fr.read()) != -1) {
        System.out.print((char) ch);
    }
} catch (IOException e) {
    e.printStackTrace();
}
</code></pre>
<hr>
<h2 id="헷갈리는-부분">헷갈리는 부분</h2>
<h4 id="한글은-3바이트를-차지하는-경우가-많은데-그렇다면-2바이트씩-모으면-깨져야하는-것이-아닌가"><em>한글은 3바이트를 차지하는 경우가 많은데 그렇다면 2바이트씩 모으면 깨져야하는 것이 아닌가?</em></h4>
<p>결론부터 말하면 <strong>전혀 잘리지 않는다.</strong></p>
<p>문자 기반 스트림이 &quot;2바이트 단위로 처리한다&quot;는 말은 <strong>파일을 2바이트씩 무작정 툭툭 잘라서 읽는다는 뜻이 아니기 때문이다.</strong></p>
<h3 id="1-2바이트-단위로-처리한다의-진짜-의미">(1). &quot;2바이트 단위로 처리한다&quot;의 진짜 의미</h3>
<p>여기서 &#39;2바이트&#39;는 파일의 저장 단위가 아니라, 자바 프로그램 내부(JVM 메모리)에서 문자를 다루는 기본 자료형인 <code>char</code>의 크기(2바이트, UTF-16)를 의미한다.</p>
<p>즉, &quot;문자 기반 스트림(<code>Reader</code>)은 2바이트 단위로 처리한다&quot;라는 말은:</p>
<blockquote>
<p>*&quot;외부 파일이 1바이트든, 2바이트든, 3바이트든 상관없이, 인코딩 규칙에 맞게 해석해서 <strong>자바의 온전한 1글자(<code>char</code>, 2바이트)</strong> 단위로 완성시켜서 반환해 준다&quot;*</p>
</blockquote>
<p>라는 뜻이다.</p>
<hr>
<h3 id="2-utf-8-한글3바이트을-읽을-때-일어나는-실제-과정">(2). UTF-8 한글(3바이트)을 읽을 때 일어나는 실제 과정</h3>
<p><code>FileReader</code>나 <code>InputStreamReader</code> 같은 문자 기반 스트림 내부에는 디코더(Decoder, 인코딩 해석기)가 탑재되어 있다.</p>
<p>파일에 UTF-8로 저장된 한글 <strong>&quot;가&quot;</strong> (<code>0xEA</code>, <code>0xB0</code>, <code>0x80</code> -&gt; 총 3바이트)가 있을 때 문자 기반 스트림은 다음과 같이 동작한다.</p>
<ol>
<li><strong>바이트 분석:</strong> 문자 스트림이 파일에서 첫 번째 바이트(<code>0xEA</code>)를 읽는다.</li>
<li><strong>인코딩 판별:</strong> UTF-8 디코더가 *&quot;아, 이 바이트의 표식을 보니 이건 3바이트짜리 한글 데이터의 시작이구나!&quot;* 하고 알아챈다.</li>
<li><strong>완전한 조합:</strong> 뒤이어 따라오는 2개 바이트(<code>0xB0</code>, <code>0x80</code>)까지 <strong>총 3바이트를 한꺼번에 묶어서 읽어 들인다.</strong> (여기서 데이터 손실이나 잘림이 발생하지 않는다.)</li>
<li><strong>자바 문자로 변환:</strong> 읽어 들인 3바이트를 자바 메모리에서 다룰 수 있는 1개의 문자(<code>char</code> &#39;가&#39;, Unicode <code>0xAC00</code>)로 변환하여 출력해 준다.</li>
</ol>
<hr>
<h3 id="3-비교해서-이해하기">(3). 비교해서 이해하기</h3>
<ul>
<li><strong>바이트 기반 스트림 (<code>InputStream</code>):</strong></li>
<li>인코딩 해석 기능이 <strong>없다.</strong></li>
<li>UTF-8 한글 &quot;가&quot; (3바이트)를 읽으면, 디코더가 없으므로 그냥 <strong>1바이트씩 3번에 걸쳐서</strong> 쪼개서 가져온다.</li>
<li>쪼개진 1바이트는 온전한 글자가 아니므로 <code>(char)</code>로 변환하면 글자가 깨진다.</li>
</ul>
<ul>
<li><strong>문자 기반 스트림 (<code>Reader</code>):</strong></li>
<li>인코딩 해석 기능이 <strong>있다.</strong></li>
<li>UTF-8 한글 &quot;가&quot; (3바이트)를 읽으면, 디코더가 <strong>3바이트를 올바르게 통째로 묶어서</strong> 읽은 뒤 자바의 <code>char</code>(2바이트) 변수에 온전한 한 글자로 담아준다.</li>
</ul>
<hr>
<h3 id="요약">요약</h3>
<p>UTF-8로 인코딩된 3바이트짜리 한글 데이터를 문자 기반 스트림으로 읽어도 <strong>잘리지 않는다.</strong></p>
<p>문자 기반 스트림은 기계적으로 2바이트씩 잘라오는 것이 아니라, <strong>파일의 인코딩(UTF-8)을 인식하여 3바이트를 올바르게 모은 후 자바의 문자인 <code>char</code>(2바이트) 단위로 완성해서 가져오기 때문</strong>이다.</p>
<hr>
<h2 id="5-보조-스트림-filter-stream">5. 보조 스트림 (Filter Stream)</h2>
<p>보조 스트림은 실제 데이터를 직접 읽거나 쓰는 기능은 없지만, 다른 주 스트림(Primary Stream)에 연결하여 <strong>성능을 향상시키거나 추가적인 편의 기능</strong>을 제공하는 스트림이다.</p>
<h3 id="1-성능-향상-보조-스트림-bufferedinputstream--bufferedreader-등">(1) 성능 향상 보조 스트림 (<code>BufferedInputStream</code> / <code>BufferedReader</code> 등)</h3>
<p>내부 버퍼(Buffer)를 사용하여 데이터를 한 번에 묶어서 처리함으로써 입출력 횟수를 대폭 줄인다.</p>
<pre><code class="language-java">// BufferedReader를 이용한 라인 단위 텍스트 읽기
try (BufferedReader br = new BufferedReader(new FileReader(&quot;example.txt&quot;))) {
    String line;
    while ((line = br.readLine()) != null) { // 줄 단위로 읽기
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}
</code></pre>
<h3 id="2-형변환-보조-스트림-inputstreamreader--outputstreamwriter">(2) 형변환 보조 스트림 (<code>InputStreamReader</code> / <code>OutputStreamWriter</code>)</h3>
<p>바이트 스트림을 문자 스트림으로 변환해 주는 역할을 한다. 표준 입력(<code>System.in</code>)처럼 바이트 단위로 들어오는 입력을 텍스트 데이터로 변환할 때 많이 쓰인다.</p>
<h2 id="6-정리">6. 정리</h2>
<p>자바의 I/O는 데이터를 주고받는 단방향 통로인 스트림(Stream)을 기반으로 동작한다. 이미지나 바이너리 파일은 1바이트 단위의 <code>InputStream</code>/<code>OutputStream</code>을 사용하고, 텍스트 파일은 2바이트(문자) 단위의 <code>Reader</code>/<code>Writer</code>를 사용하는 것이 기본이다. 입출력 성능을 극대화하기 위해서는 <code>BufferedReader</code>와 같은 버퍼 기반의 보조 스트림을 조합하여 사용하는 것이 바람직하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (19) - 리플렉션 기초]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-19-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@eta_kyung/JAVA-19-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Sun, 09 Aug 2026 07:56:43 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바의 <strong>리플렉션(Reflection)</strong>은 구체적인 클래스 타입을 알지 못하더라도 런타임 시점에 클래스의 메타데이터(클래스 구조, 메서드, 필드, 생성자 등)에 접근하고 이를 조작할 수 있게 해주는 기능이다. 작성 시점에는 알 수 없는 클래스를 동적으로 로딩하여 사용할 수 있게 해주며, Spring Framework, ORM(JPA/Hibernate), 테스트 프레임워크(JUnit) 등 자바 생태계의 다양한 프레임워크와 라이브러리에서 핵심적인 기반 기술로 활용된다.</p>
<h2 id="2-class-객체-획득하기">2. Class 객체 획득하기</h2>
<p>리플렉션을 사용하기 위해서는 가장 먼저 해당 클래스의 정보를 담고 있는 <code>Class</code> 객체를 얻어야 한다. 대표적으로 세 가지 방법이 존재한다.</p>
<ol>
<li><strong><code>클래스명.class</code>:</strong> 클래스 리터럴을 직접 사용하는 방식</li>
<li><strong><code>객체.getClass()</code>:</strong> 이미 생성된 객체 인스턴스를 통해 호출하는 방식</li>
<li><strong><code>Class.forName(&quot;패키지명을 포함한 클래스명&quot;)</code>:</strong> 문자열 형태의 클래스 이름으로 런타임에 로딩하는 방식 (클래스가 존재하지 않을 경우 <code>ClassNotFoundException</code> 발생)</li>
</ol>
<pre><code class="language-java">// 1. 클래스 리터럴
Class&lt;User&gt; clazz1 = User.class;

// 2. getClass()
User user = new User();
Class&lt;? extends User&gt; clazz2 = user.getClass();

// 3. Class.forName()
Class&lt;?&gt; clazz3 = Class.forName(&quot;com.example.User&quot;);
</code></pre>
<h2 id="3-메타데이터-조회-및-객체-조작">3. 메타데이터 조회 및 객체 조작</h2>
<p>획득한 <code>Class</code> 객체를 활용하면 클래스 내부에 정의된 요소들을 탐색하고 동적으로 제어할 수 있다.</p>
<h3 id="1-생성자-접근-및-인스턴스-생성">(1) 생성자 접근 및 인스턴스 생성</h3>
<pre><code class="language-java">// 파라미터가 없는 기본 생성자 획득
Constructor&lt;?&gt; constructor = clazz1.getDeclaredConstructor();
User userInstance = (User) constructor.newInstance();
</code></pre>
<h3 id="2-필드-접근-및-값-수정">(2) 필드 접근 및 값 수정</h3>
<p><code>getDeclaredField()</code>를 사용하면 <code>private</code>으로 선언된 필드에도 접근할 수 있다. 접근 제어자를 우회하기 위해서는 <code>setAccessible(true)</code>를 호출해야 한다.</p>
<pre><code class="language-java">Field nameField = clazz1.getDeclaredField(&quot;name&quot;);
nameField.setAccessible(true); // private 접근 허용

// 필드 값 변경
nameField.set(userInstance, &quot;홍길동&quot;);
</code></pre>
<h3 id="3-메서드-동적-호출">(3) 메서드 동적 호출</h3>
<pre><code class="language-java">// 매개변수가 없는 메서드 획득
Method printMethod = clazz1.getDeclaredMethod(&quot;printInfo&quot;);
printMethod.setAccessible(true);

// 메서드 실행 (invoke)
printMethod.invoke(userInstance);
</code></pre>
<h2 id="4-리플렉션의-주요-활용-사례">4. 리플렉션의 주요 활용 사례</h2>
<p>일반적인 비즈니스 로직 개발에서 리플렉션을 직접 사용하는 경우는 드물다. 하지만 프레임워크 단에서는 유연한 구조를 위해 활발히 쓰인다.</p>
<ul>
<li><strong>Spring Framework:</strong> <code>@Autowired</code>를 통한 의존성 주입(DI), <code>@Controller</code> 등 애노테이션 기반 컴포넌트 스캔 과정에서 동적으로 객체를 생성하고 결합할 때 사용한다.</li>
<li><strong>JSON 직렬화/역직렬화 (Jackson, Gson):</strong> JSON 데이터를 자바 객체로 변환할 때, 기본 생성자를 생성하고 필드 값을 리플렉션으로 매핑한다.</li>
<li><strong>JUnit 테스트 프레임워크:</strong> <code>@Test</code> 애노테이션이 붙은 메서드를 동적으로 탐색하여 실행한다.</li>
</ul>
<p>쉽고 확실하게 직관적으로 이해할 수 있는 <strong>현실 세계 비유 예시</strong>와 <strong>간단한 코드 예시</strong>로 설명해 준다.</p>
<hr>
<h3 id="코드-예시-리플렉션이-빛을-발하는-순간">코드 예시: 리플렉션이 빛을 발하는 순간</h3>
<p>우리가 웹 서버를 만들 때 <code>@AutoWired</code>나 <code>@Test</code> 같은 애노테이션을 붙여두면 알아서 실행되는 원리가 바로 이것이다.</p>
<h4 id="내가-만든-클래스-미래에-만들어질-코드">내가 만든 클래스 (미래에 만들어질 코드)</h4>
<pre><code class="language-java">public class Robot {
    private String secretKey = &quot;1234-5678&quot;; // private 변수

    public void powerOn() {
        System.out.println(&quot;로봇 전원 ON!&quot;);
    }
}
</code></pre>
<h4 id="프레임워크-개발자가-작성한-리플렉션-코드">프레임워크 개발자가 작성한 리플렉션 코드</h4>
<p>프레임워크를 만드는 사람은 내가 <code>Robot</code>이라는 클래스를 만들지, <code>Car</code>라는 클래스를 만들지 <strong>코드 작성 시점에는 전혀 모른다.</strong></p>
<pre><code class="language-java">public class FrameworkEngine {
    public void run(Object obj) throws Exception {
        // 1. 들어온 객체의 클래스 정보를 런타임에 엿본다 (X-ray 스캔)
        Class&lt;?&gt; clazz = obj.getClass();

        System.out.println(&quot;클래스 이름: &quot; + clazz.getName()); // &quot;Robot&quot; 출력

        // 2. private 필드에 접근해서 값을 강제로 꺼내온다
        Field field = clazz.getDeclaredField(&quot;secretKey&quot;);
        field.setAccessible(true); // private 접근 허용!
        String key = (String) field.get(obj);
        System.out.println(&quot;비밀키 추출: &quot; + key); // &quot;1234-5678&quot; 출력

        // 3. powerOn 메서드를 찾아서 실행시킨다
        Method method = clazz.getDeclaredMethod(&quot;powerOn&quot;);
        method.invoke(obj); // &quot;로봇 전원 ON!&quot; 출력
    }
}
</code></pre>
<h3 id="3줄-요약">3줄 요약</h3>
<ol>
<li><strong>원래 자바는:</strong> 코드를 짤 때 클래스 이름이나 메서드 이름을 <strong>미리 다 알고 적어야만</strong> 호출할 수 있다.</li>
<li><strong>리플렉션은:</strong> 코드를 짤 때는 몰랐더라도, <strong>프로그램이 실행 중일 때(Runtime)</strong> 객체를 스캔해서 &quot;너 무슨 변수 갖고 있어? 무슨 메서드 있어?&quot; 하고 물어보고 <strong>동적으로 실행</strong>하는 기술이다.</li>
<li><strong>어디서 쓰이냐면:</strong> 내가 무슨 클래스를 만들지 미리 알 수 없는 <strong>스프링(Spring) 같은 프레임워크</strong>가 내 코드를 대신 실행해 줄 때 핵심적으로 사용한다.</li>
</ol>
<h2 id="5-리플렉션-사용-시-주의점-및-단점">5. 리플렉션 사용 시 주의점 및 단점</h2>
<p>리플렉션은 강력한 유연성을 제공하지만 분명한 단점이 존재하므로 신중하게 사용해야 한다.</p>
<ol>
<li><strong>성능 오버헤드:</strong> 컴파일 타임 최적화를 받을 수 없고 런타임에 동적으로 분석하므로 일반적인 메서드 호출보다 실행 속도가 느리다.</li>
<li><strong>타입 안전성 상실:</strong> 컴파일 시점에 타입 검사를 할 수 없어, 잘못된 매개변수나 메서드 호출 시 런타임 예외(<code>NoSuchMethodException</code>, <code>IllegalAccessException</code> 등)가 발생한다.</li>
<li><strong>캡슐화 파괴:</strong> <code>setAccessible(true)</code>를 통해 <code>private</code> 멤버에 직접 접근이 가능해지므로, 객체 지향의 캡슐화 원칙을 저해할 수 있다.</li>
</ol>
<h2 id="6-정리">6. 정리</h2>
<p>자바 리플렉션은 런타임에 클래스의 정보에 접근하여 동적으로 객체를 생성하고 조작할 수 있는 강력한 기능이다. 프레임워크와 라이브러리 개발에 있어서 필수적인 유연성을 제공하지만, 성능 저하, 타입 안전성 결여, 캡슐화 파괴라는 위험 요소도 함께 동반된다. 따라서 일반적인 애플리케이션 개발 시에는 표준적인 정적 타입 명시 방식을 우선으로 하고, 리플렉션은 필요한 상황에 한해 제약적으로 활용하는 것이 바람직하다.</p>
<h2 id="7-요약">7. 요약</h2>
<ol>
<li><strong>정의:</strong> 코드 작성 시점에는 몰랐던 클래스의 정보(메서드, 변수 등)를 <strong>프로그램 실행 중에 동적으로 조사하고 다루는 기술</strong>이다.</li>
<li><strong>장점:</strong> 어떤 클래스가 들어올지 모르는 상황(프레임워크, 라이브러리)에서 엄청난 유연성을 준다.</li>
<li><strong>단점:</strong> 실행 중에 일일이 분석하므로 <strong>속도가 살짝 느리고</strong>, <code>private</code>까지 강제로 열 수 있어 <strong>안전성/캡슐화가 깨질 수 있다.</strong></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (18) - 애노테이션 기초]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-18-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@eta_kyung/JAVA-18-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Sun, 09 Aug 2026 07:37:25 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>애노테이션(Annotation)은 자바 5부터 도입된 기능으로, 코드에 대한 추가적인 정보(메타데이터, Metadata)를 제공하는 주석 형태의 표식이다. 기존의 주석이 사람을 위한 설명이었다면, 애노테이션은 컴파일러나 런타임 프레임워크가 코드를 어떻게 처리할지 알려주는 역할을 한다. 비즈니스 로직에 직접 영향을 주지는 않지만, 코드의 가독성을 높이고 반복되는 설정을 획기적으로 줄여 준다.</p>
<h2 id="2-애노테이션의-개념과-역할">2. 애노테이션의 개념과 역할</h2>
<p>애노테이션은 <code>@</code> 기호를 사용하여 클래스, 메서드, 필드, 매개변수 등에 붙여 작성한다. 주요 역할은 크게 세 가지로 나뉜다.</p>
<ul>
<li><strong>컴파일러 정보 제공:</strong> 에러를 체크하거나 경고를 억제하는 등 컴파일 시점에 정보를 활용한다. (예: <code>@Override</code>)</li>
<li><strong>빌드 및 배포 시 자동 생성:</strong> 개발 도구가 코드, XML 파일, 도큐먼트 등을 자동으로 생성하도록 돕는다.</li>
<li><strong>런타임 처리:</strong> 런타임 시점에 리플렉션(Reflection)을 통해 애노테이션 정보를 읽어와 해당 코드에 맞는 동작을 수행한다. (예: Spring Framework의 <code>@Autowired</code>, <code>@Controller</code> 등)</li>
</ul>
<h2 id="3-자바-표준-애노테이션">3. 자바 표준 애노테이션</h2>
<p>자바 언어 자체에서 기본으로 제공하는 대표적인 표준 애노테이션은 다음과 같다.</p>
<ul>
<li><strong><code>@Override</code>:</strong> 메서드가 상위 클래스나 인터페이스의 메서드를 오버라이딩했음을 컴파일러에게 명시한다. 오타나 매개변수 불일치 등의 실수를 컴파일 시점에 잡아준다.</li>
<li><strong><code>@Deprecated</code>:</strong> 더 이상 사용을 권장하지 않는 대상에 붙인다. 사용 시 컴파일 경고가 발생한다.</li>
<li><strong><code>@SuppressWarnings</code>:</strong> 컴파일러의 특정 경고 메시지를 나타나지 않도록 억제한다. (예: <code>@SuppressWarnings(&quot;unchecked&quot;)</code>)</li>
<li><strong><code>@FunctionalInterface</code>:</strong> 함수형 인터페이스임을 컴파일러에게 검증받는다. 추상 메서드가 1개가 아닐 경우 컴파일 에러를 발생시킨다.</li>
</ul>
<pre><code class="language-java">public class Parent {
    public void print() {
        System.out.println(&quot;Parent&quot;);
    }
}

public class Child extends Parent {
    @Override // 상위 메서드 재정의 검증
    public void print() {
        System.out.println(&quot;Child&quot;);
    }

    @Deprecated // 더 이상 사용을 권장하지 않음
    public void oldMethod() {
        System.out.println(&quot;Old version&quot;);
    }
}
</code></pre>
<h2 id="4-메타-애노테이션-meta-annotation">4. 메타 애노테이션 (Meta-Annotation)</h2>
<p>메타 애노테이션은 <strong>애노테이션을 정의할 때 사용하는 애노테이션</strong>으로, 적용 대상이나 유효 기간 등을 지정할 때 쓰인다.</p>
<ul>
<li><strong><code>@Target</code>:</strong> 애노테이션이 적용될 위치(클래스, 메서드, 필드, 매개변수 등)를 지정한다.</li>
<li><strong><code>@Retention</code>:</strong> 애노테이션의 유효 범위(생명주기)를 결정한다.</li>
<li><code>SOURCE</code>: 소스 코드에만 존재하고 컴파일 시 클래스 파일에서 제거된다.</li>
<li><code>CLASS</code>: 클래스 파일까지 남아있지만 런타임에는 참조할 수 없다. (기본값)</li>
<li><code>RUNTIME</code>: 런타임까지 유지되어 리플렉션을 통해 애노테이션 정보를 읽을 수 있다.</li>
</ul>
<ul>
<li><strong><code>@Documented</code>:</strong> Javadoc 문서에 해당 애노테이션 정보가 포함되도록 한다.</li>
<li><strong><code>@Inherited</code>:</strong> 자식 클래스가 부모 클래스의 애노테이션을 상속받도록 설정한다.</li>
</ul>
<h2 id="5-커스텀-애노테이션-정의">5. 커스텀 애노테이션 정의</h2>
<p>개발자가 직접 필요한 애노테이션을 선언하여 사용할 수 있다. <code>@interface</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.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default &quot;기본값&quot;; // 속성 정의 (기본값 지정 가능)
    int number() default 1;
}
</code></pre>
<p>이렇게 정의된 사용자 정의 애노테이션은 런타임 시점에 리플렉션 API를 활용하여 적용 유무를 판별하고 원하는 로직을 실행하도록 구현할 수 있다.</p>
<h2 id="6-정리">6. 정리</h2>
<p>애노테이션은 코드에 메타데이터를 부여하여 컴파일러 검증부터 런타임 프레임워크 동작까지 다양하게 활용되는 자바의 핵심 요소다. 표준 애노테이션을 통해 코딩 실수를 줄이고, 메타 애노테이션과 사용자 정의 애노테이션을 조합하여 프레임워크 수준의 유연한 구조를 설계할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (17) - 컬렉션 프레임워크]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-17-%EC%BB%AC%EB%A0%89%EC%85%98-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@eta_kyung/JAVA-17-%EC%BB%AC%EB%A0%89%EC%85%98-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Sun, 09 Aug 2026 07:29:31 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바의 <strong>컬렉션 프레임워크(Collection Framework)</strong>는 다수의 데이터를 효율적으로 저장, 관리, 조작할 수 있도록 표준화된 데이터 구조와 알고리즘을 제공하는 클래스와 인터페이스의 집합이다. 배열(Array)이 고정된 크기만을 지원하는 한계를 극복하기 위해 가변 크기의 데이터 구조를 제공하며, 표준화된 인터페이스를 통해 일관된 방식으로 데이터를 다룰 수 있게 해준다.</p>
<h2 id="2-컬렉션-프레임워크의-핵심-인터페이스-구조">2. 컬렉션 프레임워크의 핵심 인터페이스 구조</h2>
<p>자바 컬렉션 프레임워크는 크게 <code>Collection</code> 인터페이스와 <code>Map</code> 인터페이스 두 개의 주요 줄기로 나뉜다.</p>
<pre><code>                  Iterable
                     │
                 Collection
      ┌──────────────┼──────────────┐
     List           Set           Queue
      │              │              │
 ArrayList      HashSet        LinkedList
LinkedList      TreeSet         ArrayDeque
 Vector
</code></pre><p><em>(※ <code>Map</code> 인터페이스는 구조상의 이유로 <code>Collection</code>을 상속받지 않고 독립적으로 존재한다.)</em></p>
<h2 id="3-list-인터페이스">3. List 인터페이스</h2>
<p>순서가 있는 데이터의 집합이며, 데이터의 <strong>중복을 허용</strong>한다.</p>
<ul>
<li><strong>ArrayList:</strong> 내부적으로 가변 배열을 사용하여 데이터를 관리한다. 인덱스(Index)를 통한 데이터 조회(<code>get</code>) 속도가 빠르다($O(1)$). 반면, 중간에 데이터를 삽입하거나 삭제할 때는 요소들의 이동이 필요하여 성능이 저하된다.</li>
<li><strong>LinkedList:</strong> 양방향 연결 리스트 구조로 구현되어 있다. 요소의 삽입과 삭제가 빈번하게 발생하는 상황에서 뛰어난 성능을 보인다. 인덱스 조회의 경우 처음부터 순회해야 하므로 $O(N)$의 시간 복잡도를 갖는다.</li>
</ul>
<h2 id="4-set-인터페이스">4. Set 인터페이스</h2>
<p>순서를 유지하지 않는 데이터의 집합이며, <strong>중복을 허용하지 않는다.</strong></p>
<ul>
<li><strong>HashSet:</strong> 해시 테이블(Hash Table) 기반으로 구현되어 데이터를 저장하고 검색한다. 요소의 추가, 삭제, 검색 속도가 매우 빠르다($O(1)$). 중복 여부를 판단할 때 저장되는 객체의 <code>hashCode()</code>와 <code>equals()</code> 메서드를 활용한다.</li>
<li><strong>TreeSet:</strong> 이진 탐색 트리(Red-Black Tree) 구조를 기반으로 구성된다. 데이터가 자동으로 오름차순 또는 지정한 정렬 기준에 따라 정렬되어 저장된다. 검색 및 정렬 연산에 $O(\log N)$의 시간이 소요된다.</li>
</ul>
<h2 id="5-map-인터페이스">5. Map 인터페이스</h2>
<p>키(Key)와 값(Value)의 쌍으로 데이터를 관리한다. 키는 중복될 수 없으나, 값은 중복을 허용한다.</p>
<ul>
<li><strong>HashMap:</strong> 키와 값을 해시 테이블 구조로 저장한다. 키의 중복을 허용하지 않으며, 순서를 보장하지 않는다. 가장 흔하게 사용되는 Map 구현체다.</li>
<li><strong>TreeMap:</strong> 키를 기준으로 자동 정렬되는 Map 구조다. 정렬된 상태로 키-값 쌍을 조회하거나 범위 검색이 필요할 때 유용하다.</li>
<li><strong>ConcurrentHashMap:</strong> 다중 스레드(Multi-thread) 환경에서 스레드 안정성(Thread-safety)을 제공하면서도, 기존의 <code>Hashtable</code> 대비 높은 성능을 발휘하도록 분할 Lock 메커니즘을 사용한다.</li>
</ul>
<h2 id="6-stack과-queue-deque">6. Stack과 Queue, Deque</h2>
<ul>
<li><strong>Stack:</strong> LIFO(Last In First Out, 후입선출) 구조를 갖는 자료구조다. 현재는 <code>Stack</code> 클래스 대신 <code>Deque</code> 인터페이스를 사용하는 것이 권장된다.</li>
<li><strong>Queue:</strong> FIFO(First In First Out, 선입선출) 구조를 갖는다. 주로 <code>LinkedList</code>나 <code>ArrayDeque</code>로 구현하여 사용한다.</li>
<li><strong>Deque (Double Ended Queue):</strong> 양쪽 끝에서 요소의 삽입과 삭제가 모두 가능한 구조다. Stack과 Queue의 기능을 모두 수행할 수 있다.</li>
</ul>
<h2 id="7-정리">7. 정리</h2>
<p>자바 컬렉션 프레임워크는 상황에 맞는 최적의 데이터 구조를 선택할 수 있게 해준다. 순서가 중요하고 중복 조회가 빈번하다면 <code>List(ArrayList)</code>, 중복을 제거해야 한다면 <code>Set(HashSet)</code>, 키-값 형태의 매핑 데이터라면 <code>Map(HashMap)</code>을 선택하는 것이 기본 규칙이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (16) - 제네릭]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-16-%EC%A0%9C%EB%84%A4%EB%A6%AD</link>
            <guid>https://velog.io/@eta_kyung/JAVA-16-%EC%A0%9C%EB%84%A4%EB%A6%AD</guid>
            <pubDate>Sun, 09 Aug 2026 07:23:14 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바에서 제네릭(Generic)은 클래스, 인터페이스, 메서드를 정의할 때 사용할 데이터 타입을 파라미터로 지정할 수 있게 해주는 기능이다. Java 5에 도입되었으며, 데이터 타입(Data Type)의 안정성을 높이고 형변환의 번거로움을 줄이기 위해 사용된다. 다양한 타입의 객체들을 다루는 컬렉션 프레임워크 등에서 필수적으로 활용된다.</p>
<h2 id="2-제네릭의-필요성과-장점">2. 제네릭의 필요성과 장점</h2>
<p>제네릭이 도입되기 전에는 여러 타입을 다루기 위해 최상위 클래스인 <code>Object</code> 타입을 사용했다. 하지만 <code>Object</code> 타입을 사용하면 객체를 꺼낼 때마다 명시적 형변환(Casting)이 필요했고, 잘못된 타입이 들어갈 경우 런타임 에러가 발생할 위험이 컸다.</p>
<p>제네릭을 사용하면 다음과 같은 장점을 얻는다.</p>
<ul>
<li><strong>컴파일 타임 타입 검사:</strong> 런타임에 발생할 수 있는 <code>ClassCastException</code>을 컴파일 시점에 미리 찾아내어 방지한다.</li>
<li><strong>불필요한 형변환 제거:</strong> 객체를 꺼내올 때 명시적으로 타입 변환을 하지 않아도 되므로 코드가 간결해진다.</li>
<li><strong>코드 재사용성 증가:</strong> 하나의 클래스나 메서드로 다양한 데이터 타입을 처리할 수 있다.</li>
</ul>
<pre><code class="language-java">// 제네릭 사용 전
List list = new ArrayList();
list.add(&quot;hello&quot;);
String str = (String) list.get(0); // 명시적 형변환 필요

// 제네릭 사용 후
List&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;hello&quot;);
String str = list.get(0); // 형변환 생략 가능, 타입 안정성 확보
</code></pre>
<h2 id="3-제네릭의-기본-문법-및-관례">3. 제네릭의 기본 문법 및 관례</h2>
<p>제네릭 타입은 클래스나 메서드 이름 옆에 <code>&lt;T&gt;</code>와 같은 형태로 타입을 지정하여 선언한다.</p>
<h3 id="타입-파라미터-관례">타입 파라미터 관례</h3>
<p>개발자 간의 약속으로 통용되는 대표적인 타입 파라미터 기호는 다음과 같다.</p>
<ul>
<li><code>&lt;T&gt;</code> : Type (타입)</li>
<li><code>&lt;E&gt;</code> : Element (요소, 주로 컬렉션에서 사용)</li>
<li><code>&lt;K&gt;</code> : Key (키)</li>
<li><code>&lt;V&gt;</code> : Value (값)</li>
<li><code>&lt;N&gt;</code> : Number (숫자)</li>
</ul>
<pre><code class="language-java">// 제네릭 클래스 정의
public class Box&lt;T&gt; {
    private T item;

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

    public T getItem() {
        return item;
    }
}
</code></pre>
<h2 id="4-제네릭-메서드와-제한된-타입-파라미터">4. 제네릭 메서드와 제한된 타입 파라미터</h2>
<h3 id="1-제네릭-메서드">(1) 제네릭 메서드</h3>
<p>메서드의 리턴 타입 앞에 제네릭 타입을 선언하여 사용하는 메서드다. 클래스가 제네릭이 아니더라도 메서드 단위로 제네릭을 적용할 수 있다.</p>
<pre><code class="language-java">public &lt;T&gt; T printAndReturn(T item) {
    System.out.println(item);
    return item;
}
</code></pre>
<h3 id="2-제한된-타입-파라미터-bounded-type-parameter">(2) 제한된 타입 파라미터 (Bounded Type Parameter)</h3>
<p><code>extends</code> 키워드를 사용하면 특정 클래스의 하위 타입이나 특정 인터페이스를 구현한 타입으로만 제네릭을 제한할 수 있다.</p>
<pre><code class="language-java">// Number 클래스와 그 하위 클래스(Integer, Double 등)만 허용
public class NumberBox&lt;T extends Number&gt; {
    private T number;
}
</code></pre>
<h2 id="5-와일드카드-">5. 와일드카드 (<code>?</code>)</h2>
<p>제네릭 타입에서 지정할 타입을 정확히 알지 못하거나 범위를 다르게 지정해야 할 때 와일드카드 <code>?</code>를 사용한다.</p>
<ul>
<li><strong><code>&lt;?&gt;</code> (Unbounded Wildcard):</strong> 모든 타입을 허용한다. (<code>&lt;? extends Object&gt;</code>와 동일)</li>
<li><strong><code>&lt;? extends T&gt;</code> (Upper Bounded Wildcard):</strong> <code>T</code>와 그 하위 타입만 허용한다. (상한 제한, 읽기 전용으로 자주 사용)</li>
<li><strong><code>&lt;? super T&gt;</code> (Lower Bounded Wildcard):</strong> <code>T</code>와 그 상위 타입만 허용한다. (하한 제한, 쓰기 전용으로 자주 사용)</li>
</ul>
<h2 id="6-제네릭-타입-소거-type-erasure">6. 제네릭 타입 소거 (Type Erasure)</h2>
<p>자바의 제네릭은 하위 호환성을 유지하기 위해 컴파일 시점에만 타입을 검사하고 런타임에는 제네릭 타입 정보를 제거(Erasure)하는 방식으로 동작한다.</p>
<ul>
<li>컴파일러는 제네릭 타입을 바이트 코드로 변환할 때 상한 타입(기본적으로는 <code>Object</code>, <code>extends</code>가 있으면 제한된 타입)으로 변환한다.</li>
<li>이 때문에 런타임에는 제네릭 타입 정보가 존재하지 않아 <code>new T()</code>와 같은 인스턴스 직접 생성이나 제네릭 배열 생성(<code>new T[10]</code>)이 불가능하다.</li>
</ul>
<h2 id="7-정리">7. 정리</h2>
<p>제네릭은 자바에서 객체의 타입을 컴파일 시점에 지정하여 <strong>타입 안정성</strong>을 확보하고 <strong>불필요한 형변환</strong>을 줄이는 핵심 기능이다. 와일드카드와 제한된 타입 파라미터를 사용하면 유연한 다형성 설계를 구현할 수 있다. </p>
<p>단, 자바 내부적으로 컴파일 시 제네릭 타입이 제거되는 타입 소거(Type Erasure)가 일어난다는 특성을 숙지하고 사용하는 것이 필요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (15) - 예외 처리]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-15-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@eta_kyung/JAVA-15-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Sun, 09 Aug 2026 07:12:34 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>프로그램 실행 중에는 구문 오류(Syntax Error) 외에도 예측하지 못한 상황으로 인해 오류가 발생할 수 있다. 자바에서는 이러한 비정상 종료를 막고 프로그램이 정상적인 흐름을 유지할 수 있도록 <strong>예외 처리(Exception Handling)</strong> 메커니즘을 제공한다.</p>
<h2 id="2-에러error와-예외exception">2. 에러(Error)와 예외(Exception)</h2>
<p>자바에서는 실행 시점(Runtime)에 발생할 수 있는 오류를 크게 에러와 예외로 구분한다.</p>
<ul>
<li><strong>에러 (Error):</strong> 메모리 부족(<code>OutOfMemoryError</code>)이나 스택 오버플로우(<code>StackOverflowError</code>)와 같이 시스템 수준에서 발생하는 심각한 문제다. 복구가 불가능하며 프로그램이 종료된다.</li>
<li><strong>예외 (Exception):</strong> 프로그램 코드에 의해 수습될 수 있는 비교적 미약한 오류다. 개발자가 적절한 코드를 작성하여 비정상 종료를 방지할 수 있다.</li>
</ul>
<pre><code>java.lang.Throwable
 ├── java.lang.Error
 └── java.lang.Exception
</code></pre><h2 id="3-checked-exception-vs-unchecked-exception">3. Checked Exception vs Unchecked Exception</h2>
<p><code>Exception</code> 클래스의 하위 예외는 크게 두 가지 유형으로 나뉜다.</p>
<h3 id="1-checked-exception-체크-예외">(1) Checked Exception (체크 예외)</h3>
<ul>
<li><code>RuntimeException</code>을 상속받지 않은 예외 클래스다. (예: <code>IOException</code>, <code>SQLException</code>)</li>
<li>컴파일 시점에 예외 처리 여부를 검사한다.</li>
<li>예외 처리가 강제되므로 <code>try-catch</code>로 잡거나 <code>throws</code>로 던져야 컴파일이 가능하다.</li>
</ul>
<h3 id="2-unchecked-exception-언체크-예외--실행-예외">(2) Unchecked Exception (언체크 예외 / 실행 예외)</h3>
<ul>
<li><code>RuntimeException</code>을 상속받는 예외 클래스다. (예: <code>NullPointerException</code>, <code>IndexOutOfBoundsException</code>, <code>IllegalArgumentException</code>)</li>
<li>컴파일 시점에 예외 처리를 검사하지 않는다.</li>
<li>주로 개발자의 실수나 잘못된 로직으로 인해 발생하며, 예외 처리를 명시적으로 강제하지 않는다.</li>
</ul>
<h2 id="4-try---catch---finally-구문">4. try - catch - finally 구문</h2>
<p>예외를 직접 처리할 때 사용하는 가장 기본적인 구조다.</p>
<pre><code class="language-java">try {
    // 예외가 발생할 가능성이 있는 코드
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 해당 예외가 발생했을 때 처리할 코드
    System.out.println(&quot;0으로 나눌 수 없다: &quot; + e.getMessage());
} finally {
    // 예외 발생 여부와 상관없이 항상 실행되는 코드 (생략 가능)
    // 주로 자원 해제(Resource close) 등에 사용
}
</code></pre>
<ul>
<li><strong>Multi-catch:</strong> 자바 7부터는 하나의 <code>catch</code> 블록에서 <code>|</code> 기호를 사용하여 여러 예외를 동시에 잡을 수 있다.</li>
</ul>
<h2 id="5-try-with-resources">5. try-with-resources</h2>
<p>파일, DB 연결, 네트워크 소켓 등 자원(Resource)을 사용한 후 반납해야 할 때, 기존에는 <code>finally</code> 블록에서 일일이 <code>.close()</code>를 호출해야 했다. 자바 7에서 도입된 <code>try-with-resources</code> 구문을 사용하면 자원을 자동으로 해제해 준다.</p>
<blockquote>
<p>try 뒤에 괄호 ()를 붙여 자원을 선언해 주는 특수한 구문을 사용해야 자동으로 처리</p>
</blockquote>
<ul>
<li><code>AutoCloseable</code> 인터페이스를 구현한 클래스만 적용 가능하다.</li>
</ul>
<pre><code class="language-java">try (BufferedReader br = new BufferedReader(new FileReader(&quot;file.txt&quot;))) {
    String line = br.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
} // try 블록을 벗어나면 br.close()가 자동으로 호출됨
</code></pre>
<pre><code class="language-java">BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader(&quot;file.txt&quot;));
    System.out.println(br.readLine());
} catch (IOException e) {
    e.printStackTrace();
} finally {
    // 사용이 끝나면 무조건 실행되는 finally에서 직접 close()를 호출해야 함
    if (br != null) {
        try {
            br.close(); // close() 할 때도 예외 처리가 필요하여 코드가 매우 지저분해짐
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}</code></pre>
<h2 id="6-예외-던지기-throws-및-커스텀-예외">6. 예외 던지기 (throws) 및 커스텀 예외</h2>
<p>메서드 내부에서 예외를 직접 처리하지 않고, 자신을 호출한 상위 메서드로 예외 처리를 위임할 수 있다.</p>
<pre><code class="language-java">public void readFile(String filePath) throws IOException {
    FileReader fr = new FileReader(filePath); // 예외를 직접 처리하지 않고 상위로 던짐
}
</code></pre>
<p>비즈니스 로직에 맞춘 도메인 전용 예외가 필요하다면 <code>RuntimeException</code>을 상속받아 사용자 정의 예외(Custom Exception)를 만들어 활용할 수 있다.</p>
<pre><code class="language-java">public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}
</code></pre>
<h2 id="7-정리">7. 정리</h2>
<p>자바의 예외 처리는 프로그램의 비정상 종료를 예방하고 안정성을 높이는 핵심 기법이다. 컴파일 시점에 체크되는 Checked Exception과 명시적 처리를 강제하지 않는 Unchecked Exception의 차이를 구분하여 사용해야 한다. 자원 해제가 필요한 경우에는 <code>try-with-resources</code> 구문을 활용하여 코드를 간결하고 안전하게 유지하는 것이 바람직하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (14) - equals() 와 hashCode()]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-14-equals-%EC%99%80-hashCode</link>
            <guid>https://velog.io/@eta_kyung/JAVA-14-equals-%EC%99%80-hashCode</guid>
            <pubDate>Sun, 09 Aug 2026 07:03:01 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바의 최상위 클래스인 <code>Object</code>는 모든 클래스가 기본적으로 상속받는 메서드들을 제공한다. 그중 <code>equals()</code>와 <code>hashCode()</code>는 객체의 동일성(Identity)과 동등성(Equality)을 비교할 때 핵심적인 역할을 담당한다. 두 메서드는 올바른 객체 비교 및 컬렉션 프레임워크(특히 해시 기반 컬렉션)의 정상 동작을 위해 규칙에 맞게 함께 재정의(Override)해야 한다.</p>
<h2 id="2-동일성identity과-동등성equality">2. 동일성(Identity)과 동등성(Equality)</h2>
<p>두 메서드의 역할을 이해하려면 먼저 동일성과 동등성의 차이를 구분해야 한다.</p>
<ul>
<li><strong>동일성 (<code>==</code> 연산자):</strong> 두 객체가 메모리상에서 실제로 같은 인스턴스(주소)를 참조하고 있는지 비교한다.</li>
<li><strong>동등성 (<code>equals()</code> 메서드):</strong> 두 객체의 메모리 주소가 다르더라도, 내부적으로 가지고 있는 논리적 데이터가 같은지 비교한다.</li>
</ul>
<pre><code class="language-java">String str1 = new String(&quot;Java&quot;);
String str2 = new String(&quot;Java&quot;);

System.out.println(str1 == str2);      // false (주소값이 다름)
System.out.println(str1.equals(str2)); // true (논리적 문자열 내용이 같음)
</code></pre>
<h2 id="3-equals-메서드">3. equals() 메서드</h2>
<p><code>Object</code> 클래스에 정의된 기본 <code>equals()</code> 메서드는 내부적으로 <code>==</code> 연산자를 사용하여 객체의 주소값을 비교한다. 따라서 클래스 설계 시 논리적 동등성을 판단하려면 <code>equals()</code> 메서드를 재정의해야 한다.</p>
<h3 id="equals-재정의-시-지켜야-할-규약">equals() 재정의 시 지켜야 할 규약</h3>
<ol>
<li><strong>반사성 (Reflexivity):</strong> <code>x.equals(x)</code>는 항상 <code>true</code>여야 한다.</li>
<li><strong>대칭성 (Symmetry):</strong> <code>x.equals(y)</code>가 <code>true</code>이면 <code>y.equals(x)</code>도 <code>true</code>여야 한다.</li>
<li><strong>추이성 (Transitivity):</strong> <code>x.equals(y)</code>가 <code>true</code>이고 <code>y.equals(z)</code>가 <code>true</code>이면 <code>x.equals(z)</code>도 <code>true</code>여야 한다.</li>
<li><strong>일관성 (Consistency):</strong> 객체가 수정되지 않았다면 <code>x.equals(y)</code>를 몇 번 호출하든 항상 같은 결과를 반환해야 한다.</li>
<li><strong>null-비교:</strong> <code>x.equals(null)</code>은 항상 <code>false</code>여야 한다.</li>
</ol>
<h2 id="4-hashcode-메서드">4. hashCode() 메서드</h2>
<p><code>hashCode()</code> 메서드는 객체의 해시 코드(정수값)를 반환하는 메서드다. 해시 기반 컬렉션(<code>HashSet</code>, <code>HashMap</code>, <code>Hashtable</code> 등)에서 객체를 저장하고 검색할 때 위치를 결정하는 기준으로 사용된다.</p>
<h3 id="hashcode-규약">hashCode() 규약</h3>
<ul>
<li><code>equals()</code> 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션 실행 동안 <code>hashCode()</code>는 항상 같은 정수값을 반환해야 한다.</li>
<li><strong><code>equals(Object)</code>가 두 객체를 같다고 판단했다면(<code>true</code>), 두 객체의 <code>hashCode()</code> 값은 반드시 같아야 한다.</strong></li>
<li>두 객체의 <code>equals()</code>가 다를지라도 <code>hashCode()</code> 값이 반드시 다를 필요는 없다. 다만 서로 다른 정수값을 반환해야 해시 테이블의 성능이 향상된다.</li>
</ul>
<h2 id="5-equals와-hashcode를-함께-재정의해야-하는-이유">5. equals()와 hashCode()를 함께 재정의해야 하는 이유</h2>
<p><code>equals()</code>만 재정의하고 <code>hashCode()</code>를 재정의하지 않으면, 해시 기반 컬렉션에서 논리적으로 같은 객체가 서로 다른 객체로 취급되는 문제가 발생한다.</p>
<p>해시 컬렉션은 객체를 찾을 때 먼저 <code>hashCode()</code>를 비교한 후, 동일한 해시 값을 가진 객체들에 한해 <code>equals()</code>를 수행한다. 따라서 <code>equals()</code>가 <code>true</code>여도 <code>hashCode()</code>가 다르면 아예 <code>equals()</code> 비교조차 이루어지지 않는다.</p>
<pre><code class="language-java">class Person {
    private String name;

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name);
    }

    // hashCode()를 재정의하지 않으면 HashSet 등에서 정상 동작하지 않음
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}
</code></pre>
<h2 id="6-정리">6. 정리</h2>
<p><code>equals()</code>는 두 객체의 논리적 동등성을 판단하고, <code>hashCode()</code>는 객체를 빠르게 식별하기 위한 해시 정수값을 제공한다. 논리적으로 동등한 객체는 동일한 해시 코드를 가져야 하므로, <code>equals()</code>를 재정의할 때는 반드시 <code>hashCode()</code>도 함께 재정의해야 한다. 이를 지키지 않으면 <code>HashSet</code>이나 <code>HashMap</code> 같은 컬렉션에서 데이터 누락이나 중복 저장 문제가 발생할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (13) - String과 불변성]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-13-String%EA%B3%BC-%EB%B6%88%EB%B3%80%EC%84%B1</link>
            <guid>https://velog.io/@eta_kyung/JAVA-13-String%EA%B3%BC-%EB%B6%88%EB%B3%80%EC%84%B1</guid>
            <pubDate>Sun, 09 Aug 2026 06:52:06 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바에서 <code>String</code>은 가장 자주 사용되는 클래스 중 하나다. 다른 객체와 달리 <code>String</code>은 한번 생성되면 내부의 값을 변경할 수 없는 <strong>불변(Immutable) 객체</strong>로 설계되었다. 이러한 불변 특성은 자바의 메모리 관리 및 안정성 측면에서 매우 중요한 역할을 한다. </p>
<h2 id="2-string의-리터럴-생성과-new-연산자">2. String의 리터럴 생성과 new 연산자</h2>
<p>자바에서 문자열을 생성하는 방식은 크게 리터럴(Literal)을 사용하는 방식과 <code>new</code> 연산자를 사용하는 방식으로 나뉜다.</p>
<ul>
<li><strong>문자열 리터럴:</strong> <code>String str1 = &quot;hello&quot;;</code>와 같이 생성하면 힙 메모리 내의 String Constant Pool(문자열 상수 풀)에 객체가 생성된다. 이미 동일한 문자열이 풀에 존재한다면 기존 객체의 참조를 재사용한다.</li>
<li><strong><code>new</code> 연산자:</strong> <code>String str2 = new String(&quot;hello&quot;);</code>와 같이 생성하면 상수가 아닌 일반적인 힙 메모리 영역에 매번 새로운 <code>String</code> 객체를 할당한다.</li>
</ul>
<pre><code class="language-java">String a = &quot;hello&quot;;
String b = &quot;hello&quot;;
String c = new String(&quot;hello&quot;);

System.out.println(a == b); // true (동일한 String Pool 객체 참조)
System.out.println(a == c); // false (서로 다른 힙 메모리 객체 참조)
</code></pre>
<h2 id="3-string이-불변인-이유-immutable">3. String이 불변인 이유 (Immutable)</h2>
<p>자바에서 <code>String</code> 내부 데이터는 <code>private final char[]</code> (Java 9 이후부터는 <code>private final byte[]</code>) 형태로 관리된다. 일단 생성된 <code>String</code> 객체의 내부 값은 변경할 수 없다. 이러한 불변성을 선택한 주요 이유는 다음과 같다.</p>
<h3 id="1-string-constant-pool을-통한-메모리-절약">(1) String Constant Pool을 통한 메모리 절약</h3>
<p>문자열은 응용 프로그램 전체에서 빈번하게 사용된다. 문자열이 불변하기 때문에 자바는 동일한 내용의 문자열을 메모리상에 하나만 올려 두고 여러 변수가 이를 안전하게 공유할 수 있다.</p>
<h3 id="2-스레드-안정성-thread-safety">(2) 스레드 안정성 (Thread-Safety)</h3>
<p>불변 객체는 상태가 변경되지 않으므로 여러 스레드가 동시에 접근하더라도 값이 변할 위험이 없다. 별도의 동기화(Synchronization) 처리 없이도 안전하게 공유할 수 있다.</p>
<h3 id="3-보안성-security">(3) 보안성 (Security)</h3>
<p>네트워크 연결, 파일 경로, 데이터베이스 연결 URL 등 시스템 주요 정보가 <code>String</code> 형태로 전달되는 경우가 많다. 문자열이 가변(Mutable)하다면 매개변수로 전달받은 값이 중간에 악의적으로 변경될 수 있다. 불변성은 이러한 참조 변경 위험을 차단한다.</p>
<h3 id="4-hashcode-캐싱">(4) HashCode 캐싱</h3>
<p><code>String</code>은 불변하므로 생성 시점에 계산된 <code>hashCode</code> 값이 절대 바뀌지 않는다. 따라서 <code>HashMap</code>, <code>HashSet</code> 등 해시 기반 자료구조의 키(Key)로 사용하기에 매우 적합하며 캐싱을 통해 빠르게 동작한다.</p>
<h2 id="4-문자열-연산과-stringbuilder--stringbuffer">4. 문자열 연산과 StringBuilder / StringBuffer</h2>
<p><code>String</code> 객체의 값을 변경하는 것처럼 보이는 연산(예: <code>+</code> 연산자나 <code>.concat()</code>)을 수행하면, 기존 객체의 내용이 바뀌는 것이 아니라 <strong>새로운 <code>String</code> 객체가 생성</strong>된다.</p>
<pre><code class="language-java">String str = &quot;Hello&quot;;
str = str + &quot; World&quot;; // &quot;Hello World&quot;라는 새로운 String 객체를 생성하여 str에 할당
</code></pre>
<p>반복문 내부에서 문자열 연결 연산을 지속하면 수많은 불필요한 객체가 생성되어 메모리가 낭비되고 성능이 저하된다. 이러한 문제를 해결하기 위해 가변(Mutable) 특성을 지닌 <code>StringBuilder</code>와 <code>StringBuffer</code>를 사용한다.</p>
<ul>
<li><strong><code>StringBuilder</code>:</strong> 단일 스레드 환경에서 빠른 성능을 제공한다.</li>
<li><strong><code>StringBuffer</code>:</strong> 동기화를 지원하여 다중 스레드(Multi-thread) 환경에서 안전하다.</li>
</ul>
<pre><code class="language-java">StringBuilder sb = new StringBuilder(&quot;Hello&quot;);
sb.append(&quot; World&quot;); // 기존 객체 내부 데이터를 직접 변경
System.out.println(sb.toString());
</code></pre>
<h2 id="5-정리">5. 정리</h2>
<p>자바의 <code>String</code>은 불변 객체로 설계되어 <strong>메모리 절약, 스레드 안정성, 보안성, 해시 키 효율성</strong>을 제공한다. 문자열 재사용을 극대화하기 위해 String Constant Pool을 활용한다. 하지만 문자열 수정이 빈번하게 발생하는 로직에서는 문자열 연결 연산 대신 <code>StringBuilder</code>나 <code>StringBuffer</code>를 활용하여 메모리 낭비를 방지하는 것이 바람직하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (12) - 내부 클래스]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-12-%EB%82%B4%EB%B6%80-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@eta_kyung/JAVA-12-%EB%82%B4%EB%B6%80-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Sun, 09 Aug 2026 06:43:20 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>내부 클래스(Inner Class)는 클래스 내부에 선언된 또 다른 클래스를 의미한다. 두 클래스가 서로 긴밀한 관계에 있을 때 내부 클래스로 선언하면, 외부 클래스의 멤버들에 쉽게 접근할 수 있고 코드의 캡슐화를 높일 수 있다. 내부 클래스는 선언되는 위치와 성격에 따라 인스턴스 내부 클래스, 정적(static) 내부 클래스, 지역(local) 내부 클래스, 익명(anonymous) 내부 클래스로 나뉜다.</p>
<h2 id="2-내부-클래스의-장점과-구조">2. 내부 클래스의 장점과 구조</h2>
<p>클래스 내부에 다른 클래스를 정의하는 주된 이유는 다음과 같다.</p>
<ul>
<li><strong>외부 클래스 멤버 접근 용이:</strong> 내부 클래스는 외부 클래스의 <code>private</code> 멤버를 포함한 모든 멤버에 제약 없이 접근할 수 있다.</li>
<li><strong>캡슐화 증대:</strong> 서로 연관성이 높은 클래스를 묶어 외부로의 노출을 숨김으로써 코드의 복잡성을 줄인다.</li>
<li><strong>가독성과 유지보수성 향상:</strong> 특정 외부 클래스에서만 사용되는 클래스를 내부에 작성하여 코드 결합도를 명확히 표현한다.</li>
</ul>
<pre><code class="language-java">class Outer { // 외부 클래스
    private int outerValue = 10;

    class Inner { // 인스턴스 내부 클래스
        void printValue() {
            System.out.println(outerValue); // 외부 클래스의 private 변수에 직접 접근 가능
        }
    }
}
</code></pre>
<h2 id="3-내부-클래스의-종류">3. 내부 클래스의 종류</h2>
<p>내부 클래스는 변수가 선언되는 위치에 따라 구분되는 방식과 유사하게 분류된다.</p>
<h3 id="1-인스턴스-내부-클래스-instance-inner-class">(1) 인스턴스 내부 클래스 (Instance Inner Class)</h3>
<p>외부 클래스의 멤버변수 선언 위치에 선언하며, 외부 클래스의 인스턴스가 먼저 생성되어야 사용할 수 있다.</p>
<ul>
<li>외부 클래스의 인스턴스 멤버와 static 멤버 모두에 접근할 수 있다.</li>
<li>외부 클래스 인스턴스와의 숨은 참조를 유지하므로 메모리 누수(Memory Leak)에 주의해야 한다.</li>
</ul>
<pre><code class="language-java">Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 외부 인스턴스를 통해 생성
</code></pre>
<h3 id="2-정적-내부-클래스-static-nested-class">(2) 정적 내부 클래스 (Static Nested Class)</h3>
<p>내부 클래스에 <code>static</code> 키워드가 붙은 형태다. 외부 클래스의 인스턴스를 생성하지 않고도 단독으로 객체 생성이 가능하다.</p>
<ul>
<li>외부 클래스의 <code>static</code> 멤버만 접근할 수 있으며, 인스턴스 멤버에는 직접 접근할 수 없다.</li>
<li>외부 클래스 인스턴스에 대한 참조를 가지지 않으므로 메모리 관리에 효율적이다. 외부 클래스와 독립적으로 작동해야 하는 내부 클래스에 권장된다.</li>
</ul>
<pre><code class="language-java">class Outer {
    static class StaticInner {
        void msg() {
            System.out.println(&quot;Static Inner Class Call&quot;);
        }
    }
}

// 외부 클래스 인스턴스 생성 없이 직접 생성
Outer.StaticInner inner = new Outer.StaticInner();
</code></pre>
<h3 id="3-지역-내부-클래스-local-inner-class">(3) 지역 내부 클래스 (Local Inner Class)</h3>
<p>외부 클래스의 메서드 내부나 초기화 블록 내에 선언되어 사용되는 클래스다.</p>
<ul>
<li>메서드가 실행될 때만 유효하며, 메서드 호출이 끝나면 사용이 불가능하다.</li>
<li>메서드 내의 지역 변수를 사용할 때, 해당 지역 변수는 <code>final</code>이거나 상수에 준하는 상태(<code>effectively final</code>)여야 한다.</li>
</ul>
<h3 id="4-익명-내부-클래스-anonymous-inner-class">(4) 익명 내부 클래스 (Anonymous Inner Class)</h3>
<p>이름이 없는 클래스로, 클래스의 선언과 객체의 생성을 동시에 진행하는 형태다.</p>
<ul>
<li>단 한 번만 사용되는 일회성 객체를 생성할 때 주로 활용된다.</li>
<li>이벤트 처리기나 람다식(Lambda)의 기초 구조로 많이 쓰인다.</li>
</ul>
<pre><code class="language-java">Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println(&quot;Anonymous Inner Class&quot;);
    }
};
</code></pre>
<h2 id="4-정적static-내부-클래스-사용의-중요성">4. 정적(static) 내부 클래스 사용의 중요성</h2>
<p>내부 클래스를 작성할 때 특별히 외부 클래스의 인스턴스 변수를 참조할 이유가 없다면, <code>static</code> 내부 클래스로 선언하는 것이 바람직하다.</p>
<p>비정적(non-static) 내부 클래스는 자신을 감싸고 있는 외부 클래스 인스턴스에 대한 숨은 참조(outer reference)를 저장한다. 이로 인해 내부 클래스 객체가 살아 있는 동안 외부 클래스 객체도 GC(Garbage Collector)에 의해 수거되지 못하는 메모리 누수가 발생할 위험이 있다. 독립적인 유틸리티 클래스나 데이터 전달용 DTO 구조 등에서는 <code>static</code> 내부 클래스를 사용하는 것이 안전하다.</p>
<h2 id="5-정리">5. 정리</h2>
<p>내부 클래스는 코드의 논리적 그룹화와 캡슐화를 실현하는 강력한 도구다. 클래스 간의 관계가 밀접할 때 코드 구조를 더욱 깔끔하게 유지해 준다. 단, 인스턴스 내부 클래스는 외부 클래스와의 숨은 참조로 인해 메모리 누수를 유발할 수 있으므로, 외부 인스턴스를 참조할 필요가 없다면 정적(<code>static</code>) 내부 클래스로 정의하는 것이 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (11) - static 키워드]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-11-static-%ED%82%A4%EC%9B%8C%EB%93%9C</link>
            <guid>https://velog.io/@eta_kyung/JAVA-11-static-%ED%82%A4%EC%9B%8C%EB%93%9C</guid>
            <pubDate>Sun, 09 Aug 2026 06:25:19 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바(Java)에서 <code>static</code> 키워드는 클래스 수준의 멤버(변수, 메서드)를 정의할 때 사용한다. 일반적으로 객체 지향 프로그래밍에서는 클래스를 바탕으로 <code>new</code> 연산자를 통해 인스턴스를 생성해야 멤버에 접근할 수 있다. 하지만 <code>static</code> 키워드가 붙은 멤버는 인스턴스를 생성하지 않고도 클래스 이름을 통해 바로 접근할 수 있다. 프로그램이 실행될 때 메모리에 로드되어 공유된다는 특징을 지닌다.</p>
<h2 id="2-static-변수-정적-변수--클래스-변수">2. static 변수 (정적 변수 / 클래스 변수)</h2>
<p>클래스 내에서 <code>static</code>으로 선언된 변수는 인스턴스마다 별도로 생성되는 일반 변수(인스턴스 변수)와 달리, 해당 클래스의 모든 인스턴스가 하나의 메모리 공간을 공유한다.</p>
<ul>
<li><strong>메모리 할당 시점:</strong> JVM이 클래스를 로딩하는 시점에 메소드 영역(Method Area 또는 Metaspace)에 메모리가 할당된다.</li>
<li><strong>공유성:</strong> 모든 인스턴스가 동일한 값을 참조하고 변경 사항을 공유한다.</li>
<li><strong>사용 목적:</strong> 공통으로 관리해야 하는 데이터(예: 생성된 객체의 개수 카운트, 공통 설정값 등)를 다룰 때 적합하다.</li>
</ul>
<pre><code class="language-java">class Counter {
    static int count = 0; // static 변수

    Counter() {
        count++;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        System.out.println(Counter.count); // 출력: 2
    }
}
</code></pre>
<h2 id="3-static-메서드-정적-메서드">3. static 메서드 (정적 메서드)</h2>
<p><code>static</code> 메서드는 객체 생성 없이 <code>클래스명.메서드명()</code> 형태에 따라 직접 호출할 수 있는 메서드다.</p>
<ul>
<li><strong>특징:</strong> 메서드 내부에서 인스턴스 변수나 인스턴스 메서드를 직접 사용할 수 없다. 인스턴스가 생성되지 않은 시점에도 <code>static</code> 메서드는 호출될 수 있기 때문이다.</li>
<li><strong>사용 목적:</strong> Utility 성격의 함수(예: <code>Math.abs()</code>, <code>Math.max()</code> 등)를 만들 때 유용하게 쓰인다.</li>
</ul>
<pre><code class="language-java">public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }
}

// 사용 예시
int result = MathUtils.add(10, 20); // 객체 생성 없이 직접 호출
</code></pre>
<h2 id="4-static-블록-정적-초기화-블록">4. static 블록 (정적 초기화 블록)</h2>
<p><code>static</code> 변수가 복잡한 초기화 로직을 필요로 할 때 <code>static</code> 블록을 활용할 수 있다.</p>
<ul>
<li>클래스가 메모리에 처음 로딩될 때 딱 한 번만 실행된다.</li>
<li>생성자보다 먼저 실행되며, 주로 <code>static</code> 변수의 초기화 작업에 사용된다.</li>
</ul>
<pre><code class="language-java">public class Config {
    static String serverUrl;

    static {
        // 복잡한 초기화 로직 수행
        serverUrl = &quot;https://api.example.com&quot;;
    }
}
</code></pre>
<h2 id="5-static-사용-시-주의점">5. static 사용 시 주의점</h2>
<p><code>static</code> 키워드는 편리하지만 무분별한 사용은 지양해야 한다.</p>
<ol>
<li><strong>메모리 관리:</strong> <code>static</code> 멤버는 프로그램이 종료될 때까지 메모리에 유지되므로, 너무 큰 데이터를 남발하면 Garbage Collection의 대상이 되지 않아 메모리 효율이 떨어질 수 있다.</li>
<li><strong>객체 지향 원칙 저해:</strong> 상태를 가질 경우, 여러 쓰레드에서 동시에 변경을 시도할 때 동시성 문제가 발생할 수 있으며 객체 간의 결합도가 높아진다.</li>
<li><strong>테스트의 어려움:</strong> 공유 상태를 가지므로 단위 테스트 실행 시 독립성을 보장하기 어렵다.</li>
</ol>
<h2 id="6-정리">6. 정리</h2>
<p><code>static</code> 키워드는 메모리의 효율적 공유와 객체 생성 없는 접근성이라는 명확한 장점을 가진다. 공통 데이터 관리나 유틸리티 성격의 메서드 정의에 적합하다. </p>
<p>하지만 프로그램 전체 생명주기 동안 메모리를 차지하고 객체 지향적 설계를 저해할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (10) - 캡슐화와 접근 제어자]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-10-%EC%BA%A1%EC%8A%90%ED%99%94%EC%99%80-%EC%A0%91%EA%B7%BC-%EC%A0%9C%EC%96%B4%EC%9E%90</link>
            <guid>https://velog.io/@eta_kyung/JAVA-10-%EC%BA%A1%EC%8A%90%ED%99%94%EC%99%80-%EC%A0%91%EA%B7%BC-%EC%A0%9C%EC%96%B4%EC%9E%90</guid>
            <pubDate>Sun, 09 Aug 2026 03:56:06 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>객체지향 프로그래밍(OOP)의 핵심 특징 중 하나는 <strong>캡슐화(Encapsulation)</strong>다. 캡슐화는 연관된 데이터와 메서드를 하나의 단위로 묶고, 외부로부터 데이터를 보호하는 기법을 의미한다.</p>
<p>Java에서는 이러한 캡슐화를 구현하고 객체의 내부 데이터에 대한 접근 범위를 지정하기 위해 <strong>접근 제어자(Access Modifier)</strong>를 제공한다.</p>
<h2 id="2-캡슐화encapsulation란">2. 캡슐화(Encapsulation)란?</h2>
<p>캡슐화는 알약 캡슐 안에 여러 성분을 넣어 내부를 보호하고 외부에는 알약 자체만 노출하는 것과 유사하다. 객체지향에서 캡슐화가 제공하는 주요 이점은 두 가지다.</p>
<ul>
<li><strong>데이터 보호 (Data Hiding):</strong> 외부에서 객체의 중요한 필드 값을 직접 변경하지 못하도록 막는다.</li>
<li><strong>자율성 확보 (정보 은닉):</strong> 내부 구현 로직을 외부에 숨기고, 필요한 인터페이스(메서드)만 외부에 제공하여 모듈 간의 독립성을 높인다.</li>
</ul>
<p>만약 데이터 보호가 이루어지지 않는다면 외부에서 잘못된 데이터를 주입하여 객체의 상태가 오염될 수 있다.</p>
<pre><code class="language-java">// 캡슐화가 적용되지 않은 예시
class BankAccount {
    public int balance; // 외부에서 직접 접근 가능
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount();
        account.balance = -10000; // 음수 잔액이라는 치명적인 오류 발생 가능
    }
}
</code></pre>
<h2 id="3-접근-제어자access-modifier의-종류와-범위">3. 접근 제어자(Access Modifier)의 종류와 범위</h2>
<p>Java는 클래스, 필드, 메서드, 생성자의 접근 범위를 지정하기 위해 4가지 접근 제어자를 제공한다.</p>
<ol>
<li><strong>private:</strong> 같은 클래스 내에서만 접근 가능 (가장 제한적)</li>
<li><strong>(default):</strong> 접근 제어자를 명시하지 않은 경우. 같은 패키지 내에서만 접근 가능</li>
<li><strong>protected:</strong> 같은 패키지 내, 또는 다른 패키지의 자식 클래스에서 접근 가능</li>
<li><strong>public:</strong> 접근 제한이 전혀 없음 (가장 넓은 범위)</li>
</ol>
<h3 id="접근-범위-요약">접근 범위 요약</h3>
<table>
<thead>
<tr>
<th>접근 제어자</th>
<th>같은 클래스</th>
<th>같은 패키지</th>
<th>자식 클래스 (다른 패키지)</th>
<th>전체 (다른 패키지)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>private</strong></td>
<td>O</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td><strong>(default)</strong></td>
<td>O</td>
<td>O</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td><strong>protected</strong></td>
<td>O</td>
<td>O</td>
<td>O</td>
<td>X</td>
</tr>
<tr>
<td><strong>public</strong></td>
<td>O</td>
<td>O</td>
<td>O</td>
<td>O</td>
</tr>
</tbody></table>
<h2 id="4-getter와-setter를-통한-안전한-데이터-접근">4. Getter와 Setter를 통한 안전한 데이터 접근</h2>
<p>캡슐화를 적용할 때는 멤버 변수를 <code>private</code>으로 선언하여 외부 직접 접근을 차단하고, <strong>Getter</strong>와 <strong>Setter</strong> 메서드를 통해 간접적으로 접근하도록 설계한다.</p>
<pre><code class="language-java">class BankAccount {
    // 1. 멤버 변수를 private으로 보호
    private int balance;

    // 2. Setter를 통해 유효성 검증 후 데이터 수정
    public void deposit(int amount) {
        if (amount &lt;= 0) {
            System.out.println(&quot;입금 금액은 0원보다 커야 합니다.&quot;);
            return;
        }
        this.balance += amount;
    }

    // 3. Getter를 통해 데이터 조회
    public int getBalance() {
        return this.balance;
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount();

        account.deposit(-5000); // 유효성 검증 실패: &quot;입금 금액은 0원보다 커야 합니다.&quot;
        account.deposit(10000);

        System.out.println(&quot;현재 잔액: &quot; + account.getBalance()); // 10000
    }
}
</code></pre>
<p>이처럼 <code>Setter</code> 내부에서 조건문 등을 통해 유효하지 않은 값이 입력되는 것을 사전 방지함으로써 객체 데이터의 무결성을 지킬 수 있다.</p>
<h2 id="5-정리">5. 정리</h2>
<ul>
<li><strong>캡슐화</strong>는 객체의 내부 필드와 메서드를 하나로 묶고, 불필요한 구현을 외부에 노출하지 않도록 보호하는 개념이다.</li>
<li>Java에서는 접근 제어자(<code>private</code>, <code>default</code>, <code>protected</code>, <code>public</code>)를 사용하여 클래스 요소의 접근 권한을 명확히 제어한다.</li>
<li>데이터를 안전하게 다루기 위해 <strong>필드는 <code>private</code>으로 설정</strong>하고, 필요한 경우 <strong>Getter/Setter 메서드를 제공</strong>하는 것이 올바른 객체지향 설계의 기본이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (9) - 추상 클래스 vs 인터페이스]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-9-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-vs-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@eta_kyung/JAVA-9-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-vs-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Sun, 09 Aug 2026 03:43:42 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>자바에서 <strong>추상 클래스(Abstract Class)</strong>와 <strong>인터페이스(Interface)</strong>는 모두 객체 지향 프로그래밍의 핵심 개념인 추상화(Abstraction)를 구현하기 위한 장치이다. 두 개념 모두 하위 클래스에서 반드시 구현해야 하는 메서드를 정의하여 코드의 규격을 강제하고 다형성을 확보하는 역할을 한다. 그러나 선언 방식과 설계 목적, 메모리 및 다중 상속의 허용 여부 등에서 명확한 차이가 존재한다.</p>
<hr>
<h2 id="2-추상-클래스-abstract-class">2. 추상 클래스 (Abstract Class)</h2>
<p>추상 클래스는 미완성된 설계도에 비유할 수 있으며, <code>abstract</code> 키워드를 사용해 선언한다. 하나 이상의 추상 메서드(구현부가 없는 메서드)를 포함할 수 있으며, 일반 클래스처럼 필드(변수)와 일반 메서드, 생성자도 가질 수 있다.</p>
<pre><code class="language-java">public abstract class Animal {
    private String name; // 일반 필드

    public Animal(String name) { // 생성자
        this.name = name;
    }

    public void sleep() { // 일반 메서드
        System.out.println(name + &quot;이(가) 잠을 잔다.&quot;);
    }

    public abstract void makeSound(); // 추상 메서드 (자식 클래스에서 반드시 구현)
}
</code></pre>
<ul>
<li><strong>인스턴스화 불가:</strong> 미완성된 클래스이므로 <code>new Animal()</code>과 같이 직접 객체를 생성할 수 없다.</li>
<li><strong>단일 상속:</strong> 자바의 클래스 상속 구조를 따르므로, 자식 클래스는 오직 하나의 추상 클래스만 <code>extends</code>할 수 있다.</li>
<li><strong>목적:</strong> 연관성이 높은 클래스들 간의 공통된 상태(필드)와 행위(메서드)를 공유하고 재사용할 때 사용한다.</li>
</ul>
<hr>
<h2 id="3-인터페이스-interface">3. 인터페이스 (Interface)</h2>
<p>인터페이스는 극단적으로 추상화된 대표적인 밑그림에 비유할 수 있으며, <code>interface</code> 키워드를 사용해 선언한다. 기본적으로 모든 필드는 <code>public static final</code>(상수)이며, 모든 메서드는 <code>public abstract</code>(추상 메서드)로 동작한다. (Java 8 이후부터는 <code>default</code> 메서드와 <code>static</code> 메서드 구현이 가능하다.)</p>
<pre><code class="language-java">public interface Swimmable {
    void swim(); // public abstract 생략 가능
}

public interface Flyable {
    void fly();
}
</code></pre>
<pre><code class="language-java">public class Duck implements Swimmable, Flyable {
    @Override
    public void swim() {
        System.out.println(&quot;오리가 수영을 한다.&quot;);
    }

    @Override
    public void fly() {
        System.out.println(&quot;오리가 날아간다.&quot;);
    }
}
</code></pre>
<ul>
<li><strong>다중 구현 가능:</strong> 클래스는 여러 개의 인터페이스를 동시에 <code>implements</code>할 수 있다.</li>
<li><strong>상태 미보유:</strong> 객체의 상태를 나타내는 일반 인스턴스 필드를 가질 수 없다.</li>
<li><strong>목적:</strong> 서로 다른 클래스 계층 구조를 가진 객체들에 공통된 행위 규격(접점)을 부여할 때 사용한다.</li>
</ul>
<hr>
<h2 id="4-추상-클래스-vs-인터페이스-주요-차점-비교">4. 추상 클래스 vs 인터페이스 주요 차점 비교</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>추상 클래스 (Abstract Class)</th>
<th>인터페이스 (Interface)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>선언 키워드</strong></td>
<td><code>abstract class</code></td>
<td><code>interface</code></td>
</tr>
<tr>
<td><strong>상속/구현 키워드</strong></td>
<td><code>extends</code></td>
<td><code>implements</code></td>
</tr>
<tr>
<td><strong>다중 지원 여부</strong></td>
<td>단일 상속만 가능</td>
<td>다중 구현 가능</td>
</tr>
<tr>
<td><strong>필드 (변수)</strong></td>
<td>일반 변수, 상수 모두 가능</td>
<td><code>public static final</code> (상수만 가능)</td>
</tr>
<tr>
<td><strong>생성자</strong></td>
<td>생성자 존재 가능</td>
<td>생성자 존재 불가</td>
</tr>
<tr>
<td><strong>메서드</strong></td>
<td>추상 메서드 + 일반 구현 메서드</td>
<td>추상 메서드 (Java 8 이후 default, static 메서드 가능)</td>
</tr>
<tr>
<td><strong>핵심 목적</strong></td>
<td>공통된 기능의 <strong>재사용 및 확장</strong> (<code>is-a</code> 관계)</td>
<td>동일한 행위의 <strong>규격 및 계약 강제</strong> (<code>can-do</code> 관계)</td>
</tr>
</tbody></table>
<hr>
<h2 id="5-상황별-선택-기준">5. 상황별 선택 기준</h2>
<p>두 방식 모두 하위 클래스의 메서드 구현을 강제하지만, 설계하는 목적에 따라 선택이 달라진다.</p>
<p><strong>추상 클래스를 사용하는 경우 (<code>is-a</code> 관계):</strong></p>
<ul>
<li>명확한 계층 구조가 형성되고, 부모-자식 간에 긴밀한 연관성이 있을 때 사용한다.</li>
<li>하위 클래스들이 공통으로 사용하는 상태(필드)나 구현된 일반 메서드가 많을 때 유용하다.</li>
</ul>
<p><strong>인터페이스를 사용하는 경우 (<code>can-do</code> 관계):</strong></p>
<ul>
<li>부모-자식 관계와 관계없이 특정 기능(행위)만을 여러 클래스에 적용하고 싶을 때 사용한다.</li>
<li>다중 상속 효과를 내어 객체 간 결합도를 낮추고 유연한 확장성(디자인 패턴, DI 등)을 확보하고자 할 때 유용하다.</li>
</ul>
<hr>
<h2 id="6-정리">6. 정리</h2>
<p>추상 클래스와 인터페이스는 모두 자바에서 추상화를 구현하는 중요한 도구이다. <strong>추상 클래스</strong>는 &#39;무엇인가(is-a)&#39;에 집중하여 공통 상태와 기능을 상속을 통해 확장 및 재사용하는 데 목적이 있다. </p>
<p><strong>인터페이스</strong>는 &#39;무엇을 할 수 있는가(can-do)&#39;에 집중하여 다중 구현을 통해 클래스의 행위 규격을 표준화하는 데 목적이 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA (8) - 상속과 다형성]]></title>
            <link>https://velog.io/@eta_kyung/JAVA-8-%EC%83%81%EC%86%8D%EA%B3%BC-%EB%8B%A4%ED%98%95%EC%84%B1</link>
            <guid>https://velog.io/@eta_kyung/JAVA-8-%EC%83%81%EC%86%8D%EA%B3%BC-%EB%8B%A4%ED%98%95%EC%84%B1</guid>
            <pubDate>Sun, 09 Aug 2026 03:34:09 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<p>상속(Inheritance)과 다형성(Polymorphism)은 객체 지향의 핵심이라 불리는 대표적인 특성이다. 상속을 통해 기존 클래스의 자원을 재사용하고, 다형성을 통해 하나의 타입으로 여러 형태의 객체를 다룸으로써 유연하고 유지보수가 쉬운 프로그램을 작성할 수 있다.</p>
<hr>
<h2 id="2-상속-inheritance">2. 상속 (Inheritance)</h2>
<p>상속은 기존의 클래스(부모 클래스 또는 상위 클래스)가 가진 필드와 메서드를 새로운 클래스(자식 클래스 또는 하위 클래스)가 물려받아 사용하는 것을 의미한다. 자바에서는 <code>extends</code> 키워드를 사용하여 상속을 구현한다.</p>
<pre><code class="language-java">// 부모 클래스
class Animal {
    String name;

    void eat() {
        System.out.println(&quot;음식을 먹는다.&quot;);
    }
}

// 자식 클래스
class Dog extends Animal {
    void bark() {
        System.out.println(&quot;멍멍 짖는다.&quot;);
    }
}
</code></pre>
<ul>
<li><strong>코드의 재사용:</strong> 부모 클래스에서 정의된 메서드와 필드를 재작성하지 않고 그대로 사용할 수 있다.</li>
<li><strong>단일 상속:</strong> 자바는 클래스 간 다중 상속을 허용하지 않는다. 즉, <code>extends</code> 뒤에는 하나의 부모 클래스만 지정할 수 있다.</li>
</ul>
<hr>
<h2 id="3-메서드-오버라이딩-method-overriding">3. 메서드 오버라이딩 (Method Overriding)</h2>
<p>부모 클래스로부터 물려받은 메서드의 동작을 자식 클래스에서 필요에 맞게 재정의하는 것을 <strong>메서드 오버라이딩</strong>이라고 한다.</p>
<p>오버라이딩을 할 때는 부모 클래스 메서드의 시그니처(메서드 이름, 매개변수 타입 및 개수, 반환 타입)가 동일해야 하며, <code>@Override</code> 애노테이션을 부여하여 컴파일 타임에 정확성을 검증하는 것이 권장된다.</p>
<pre><code class="language-java">class Cat extends Animal {
    @Override
    void eat() {
        System.out.println(&quot;생선을 맛있게 먹는다.&quot;); // 부모의 eat()을 재정의
    }
}
</code></pre>
<hr>
<h2 id="4-다형성-polymorphism">4. 다형성 (Polymorphism)</h2>
<p>다형성은 &#39;여러 가지 형태를 가질 수 있는 능력&#39;을 의미한다. 자바에서는 <strong>부모 클래스 타입의 참조 변수로 자식 클래스 인스턴스를 참조할 수 있는 성질</strong>로 나타난다.</p>
<pre><code class="language-java">Animal myPet = new Dog(); // 업캐스팅(Upcasting)
myPet.eat(); // Dog 객체의 오버라이딩된 메서드가 동작할 수 있음
</code></pre>
<ul>
<li><strong>업캐스팅(Upcasting):</strong> 자식 객체를 부모 타입의 변수에 할당하는 것으로, 별도의 형변환 연산자 없이 자동 변환된다.</li>
<li><strong>다운캐스팅(Downcasting):</strong> 부모 타입을 다시 원래의 자식 타입으로 변환하는 것이다. 이때는 명시적 형변환 연산자가 필요하며, 안전한 변환을 위해 <code>instanceof</code> 연산자를 활용하여 타입을 확인해야 한다.</li>
</ul>
<pre><code class="language-java">if (myPet instanceof Dog) {
    Dog myDog = (Dog) myPet; // 다운캐스팅
    myDog.bark();
}
</code></pre>
<hr>
<h2 id="5-다형성의-장점과-활용">5. 다형성의 장점과 활용</h2>
<p>다형성을 활용하면 하나의 배열이나 컬렉션에 서로 다른 자식 객체들을 담아 일괄적으로 처리할 수 있다. 또한 메서드의 매개변수 타입을 부모 타입으로 지정하면, 부모를 상속받은 모든 자식 객체를 전달받아 동작시킬 수 있어 확장성이 크게 향상된다.</p>
<pre><code class="language-java">public void makeAnimalEat(Animal animal) {
    animal.eat(); // 전달되는 객체(Dog, Cat 등)에 따라 다르게 동작
}
</code></pre>
<hr>
<h2 id="6-정리">6. 정리</h2>
<p><strong>상속</strong>은 기존 클래스의 속성과 기능을 물려받아 코드의 중복을 줄이고 계층 구조를 형성하는 메커니즘이다.</p>
<p>** 다형성**은 상속 관계와 메서드 오버라이딩을 바탕으로 부모 타입 하나로 여러 형태의 자식 객체를 유연하게 다룰 수 있게 해준다.</p>
]]></description>
        </item>
    </channel>
</rss>