<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kkimdy_12.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sat, 30 May 2026 09:01:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>kkimdy_12.log</title>
            <url>https://velog.velcdn.com/images/kkimdy_12/profile/54e685bc-6fdf-4f82-8f49-9fb25410ccf2/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. kkimdy_12.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kkimdy_12" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[atomic design pattern ]]></title>
            <link>https://velog.io/@kkimdy_12/atomic-design-pattern</link>
            <guid>https://velog.io/@kkimdy_12/atomic-design-pattern</guid>
            <pubDate>Sat, 30 May 2026 09:01:27 GMT</pubDate>
            <description><![CDATA[<h1 id="atomic-design-pattern의-best-practice-여정기">Atomic Design Pattern의 Best Practice 여정기</h1>
<blockquote>
<p>출처: <a href="https://yozm.wishket.com/magazine/detail/1531/">https://yozm.wishket.com/magazine/detail/1531/</a></p>
</blockquote>
<hr>
<h2 id="📌-한-줄-요약">📌 한 줄 요약</h2>
<p>Atomic Design은 엄격한 규칙이 아닌 <strong>멘탈 모델</strong>이며, 프로젝트 특성에 맞게 하이브리드로 변형해서 쓰는 것이 Best Practice다.</p>
<hr>
<h2 id="1-atomic-design이란">1. Atomic Design이란?</h2>
<p><img src="https://velog.velcdn.com/images/kkimdy_12/post/e3ebd8f2-cdaf-4adc-aa2e-558f18219d49/image.png" alt=""></p>
<p>Brad Frost가 화학 개념을 UI 설계에 적용한 5단계 계층 구조</p>
<table>
<thead>
<tr>
<th>계층</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>Atoms</td>
<td>더 이상 쪼갤 수 없는 최소 단위</td>
<td>Button, Input, Icon</td>
</tr>
<tr>
<td>Molecules</td>
<td>Atoms 조합으로 하나의 기능 수행</td>
<td>SearchBar</td>
</tr>
<tr>
<td>Organisms</td>
<td>Molecules + Atoms 조합, 의미 있는 영역</td>
<td>Header, Footer</td>
</tr>
<tr>
<td>Templates</td>
<td>데이터 없는 최종 레이아웃 뼈대</td>
<td>HomeTemplate</td>
</tr>
<tr>
<td>Pages</td>
<td>실제 데이터가 결합된 최종 화면</td>
<td>HomePage</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>핵심</strong>: 선형 프로세스가 아니라, UI를 &quot;전체이자 부분의 모음&quot;으로 바라보게 해주는 사고 도구</p>
</blockquote>
<hr>
<h2 id="2-도입-후-마주친-문제들">2. 도입 후 마주친 문제들</h2>
<h3 id="❶-molecules-vs-organisms-경계-모호">❶ Molecules vs Organisms 경계 모호</h3>
<ul>
<li>실제 컴포넌트는 재사용 범위가 다양해서 분류가 애매함</li>
<li>사람마다 기준이 달라 팀 내 혼선 발생</li>
</ul>
<h3 id="❷-계층만으로는-부족">❷ 계층만으로는 부족</h3>
<ul>
<li>도메인/기능별 구분도 필요 (Header, Footer, 검색 영역 등)</li>
<li>5단계만으로는 현실의 복잡성을 담기 어려움</li>
</ul>
<h3 id="❸-template-vs-pages와-데이터-바인딩-불명확">❸ Template vs Pages와 데이터 바인딩 불명확</h3>
<ul>
<li>상태관리(Redux, Riverpod 등)와 함께 쓸 때 역할 경계가 흐려짐</li>
</ul>
<h3 id="❹-고민-유발-구조의-역설">❹ 고민 유발 구조의 역설</h3>
<blockquote>
<p>&quot;어디에 넣을지 고민하게 만드는 구조는 좋은 구조가 아니다&quot;</p>
</blockquote>
<hr>
<h2 id="3-best-practice-하이브리드-접근법">3. Best Practice: 하이브리드 접근법</h2>
<h3 id="기본-원칙">기본 원칙</h3>
<ul>
<li>Atomic 계층 구조 유지 + 의미론적(도메인) 구분 추가</li>
<li>기획/디자인 용어와 개발 폴더명 통일</li>
</ul>
<h3 id="폴더-구조-예시">폴더 구조 예시</h3>
<pre><code>/components
  ㄴ /공통            ← 뷰 컴포넌트 (다른 프로젝트에서도 재사용 가능)
      ㄴ /atoms
      ㄴ /molecules
      ㄴ /organisms
  ㄴ /일정관리         ← 비즈니스 컴포넌트 (이 프로젝트 전용)
      ㄴ /molecules
      ㄴ /organisms
      ㄴ /templates</code></pre><h3 id="뷰-vs-비즈니스-컴포넌트-구분">뷰 vs 비즈니스 컴포넌트 구분</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>특징</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>뷰 컴포넌트</td>
<td>Props 기반, 외부 의존 없음, 재사용 가능</td>
<td>Input, Checkbox, Modal</td>
</tr>
<tr>
<td>비즈니스 컴포넌트</td>
<td>상태관리와 결합, 프로젝트 전용</td>
<td>일정 상세 팝업</td>
</tr>
</tbody></table>
<h3 id="계층-판단-기준-뷰-컴포넌트">계층 판단 기준 (뷰 컴포넌트)</h3>
<pre><code>Atoms      → 외부 import 없음
Molecules  → 다른 컴포넌트를 import함 (예: SelectBox = Input + Dropdown)
Organisms  → 독립적으로 동작하는 단위 (예: Modal Popup)</code></pre><hr>
<h2 id="4-결론">4. 결론</h2>
<ol>
<li>Atomic Design은 <strong>엄격한 규칙이 아닌 이해 도구</strong>로 활용</li>
<li>팀의 기획·디자인·개발 <strong>용어를 통일</strong>해서 폴더명에 반영</li>
<li><strong>뷰/비즈니스 컴포넌트를 먼저 분리</strong>한 뒤 Atomic 계층 적용</li>
<li>폴더 구조는 단순 정리가 아닌 <strong>팀의 사고체계를 반영</strong>하는 것</li>
</ol>
<blockquote>
<p>&quot;분류 기준을 많이 만들어 두고, 좋은 이름을 붙이고, 명확한 규칙을 만드는 것&quot; — 좋은 폴더 구조의 조건</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 자바 12장) 직렬화]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-12%EC%9E%A5</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-12%EC%9E%A5</guid>
            <pubDate>Fri, 02 Jan 2026 11:18:19 GMT</pubDate>
            <description><![CDATA[<h1 id="item-85---자바-직렬화의-대안을-찾으라">Item 85 - 자바 직렬화의 대안을 찾으라</h1>
<h2 id="직렬화란">직렬화란?</h2>
<p>이펙티브 자바에서 말하는 <strong>직렬화(Serializable)</strong> 는
객체의 상태를 <strong>바이트 스트림</strong>으로 변환하는 것을 의미한다.
반대로 바이트 스트림을 다시 객체로 복원하는 과정은 <strong>역직렬화</strong>다.</p>
<p>바이트 스트림을 사용하는 이유는
네트워크, 파일, DB 등 <strong>출발지와 목적지 모두가 이해할 수 있는 공통 표현</strong>이기 때문이다.</p>
<h2 id="왜-직렬화를-사용할까">왜 직렬화를 사용할까?</h2>
<ul>
<li><p>자바 객체는 그대로 전송할 수 없음</p>
</li>
<li><p>전송을 위해 <strong>모두가 이해 가능한 형태(byte)</strong> 로 변환 필요</p>
</li>
<li><p>대표적인 예</p>
<ul>
<li>문자열 → 바이트 (소켓 통신)</li>
<li>객체 → JSON 문자열 (웹 API)</li>
</ul>
</li>
</ul>
<p>JSON 직렬화는 보통 Jackson 같은 라이브러리가 대신 처리해준다.</p>
<h2 id="자바-직렬화-방법">자바 직렬화 방법</h2>
<ul>
<li>객체가 <code>Serializable</code> 인터페이스를 구현하면 가능</li>
<li><code>ObjectOutputStream.writeObject()</code>로 직렬화</li>
<li><code>ObjectInputStream.readObject()</code>로 역직렬화</li>
<li><code>Serializable</code>은 <strong>마커 인터페이스</strong>로, 직렬화 가능 여부만 표시</li>
</ul>
<h2 id="자바-직렬화의-문제점">자바 직렬화의 문제점</h2>
<p>자바 직렬화의 핵심 문제는 <strong>보안과 성능</strong>이다.</p>
<h3 id="⚠️-보안-문제">⚠️ 보안 문제</h3>
<ul>
<li><code>readObject()</code>는 반환 타입이 <code>Object</code></li>
<li>클래스패스에 있는 거의 모든 객체를 생성 가능</li>
<li>역직렬화 과정에서 <strong>해당 객체의 모든 코드가 실행됨</strong></li>
<li>결과적으로 <strong>공격 표면이 매우 넓어짐</strong></li>
</ul>
<h3 id="⚠️-성능-문제">⚠️ 성능 문제</h3>
<ul>
<li>직렬화 데이터 크기가 큼</li>
<li>역직렬화 시 의도적으로 만든 구조로 인해
<strong>엄청난 연산 비용(역직렬화 폭탄)</strong> 이 발생할 수 있음</li>
</ul>
<p>➡️ 신뢰할 수 없는 데이터를 역직렬화하는 행위 자체가 위험하다.</p>
<h2 id="어떻게-대처해야-할까">어떻게 대처해야 할까?</h2>
<ul>
<li><p><strong>가장 좋은 방법: 자바 직렬화를 사용하지 않는 것</strong></p>
</li>
<li><p>대안</p>
<ul>
<li>JSON, protobuf 같은 명시적 포맷 사용</li>
</ul>
</li>
<li><p>불가피하게 역직렬화해야 한다면</p>
<ul>
<li>Java 9의 <code>ObjectInputFilter</code> 사용</li>
<li>허용된 클래스만 역직렬화하도록 제한</li>
</ul>
</li>
</ul>
<hr>
<h1 id="item-86--serializable을-구현할지는-신중히-결정하라">Item 86 -Serializable을 구현할지는 신중히 결정하라</h1>
<p><code>Serializable</code>을 구현하면 객체를 쉽게 직렬화할 수 있다.
하지만 이는 <strong>단기적으로는 편해 보이지만, 장기적으로 매우 큰 비용을 초래하는 결정</strong>이다.</p>
<h2 id="왜-위험한가">왜 위험한가?</h2>
<h3 id="1️⃣-직렬화-형태--공개-api">1️⃣ 직렬화 형태 = 공개 API</h3>
<ul>
<li><p><code>Serializable</code> 구현 순간</p>
<ul>
<li>클래스의 <strong>내부 구조가 외부에 고정</strong></li>
</ul>
</li>
<li><p><code>private</code> 필드도 직렬화 대상</p>
</li>
<li><p>한번 배포되면</p>
<ul>
<li><strong>과거 형태와의 호환성 유지 필수</strong></li>
</ul>
</li>
</ul>
<h2 id="2️⃣-클래스-수정이-거의-불가능해진다">2️⃣ 클래스 수정이 거의 불가능해진다</h2>
<h3 id="발생-가능한-문제">발생 가능한 문제</h3>
<ul>
<li><p>필드 추가 / 삭제 / 타입 변경</p>
</li>
<li><p>역직렬화 시</p>
<ul>
<li><code>InvalidClassException</code></li>
</ul>
</li>
</ul>
<h3 id="serialversionuid">serialVersionUID</h3>
<ul>
<li>직렬화 버전 식별자</li>
<li>반드시 명시</li>
</ul>
<pre><code class="language-java">private static final long serialVersionUID = 1L;</code></pre>
<p>단, UID를 고정해도</p>
<ul>
<li>구조 변경까지 안전해지는 것은 아님</li>
</ul>
<h2 id="3️⃣-버그와-보안-취약점의-원인">3️⃣ 버그와 보안 취약점의 원인</h2>
<ul>
<li><p>역직렬화는 <strong>생성자를 호출하지 않음</strong></p>
</li>
<li><p>생성자에서 보장하던 불변식 무력화</p>
</li>
<li><p>결과</p>
<ul>
<li>잘못된 상태의 객체 생성</li>
<li>보안 취약점 발생 가능</li>
</ul>
</li>
</ul>
<p>➡️ 역직렬화 = <strong>숨겨진 생성자</strong></p>
<h2 id="4️⃣-테스트-비용-폭증">4️⃣ 테스트 비용 폭증</h2>
<p>클래스를 수정할 때마다</p>
<ul>
<li>구버전 → 신버전</li>
<li>신버전 → 구버전</li>
</ul>
<p>Serializable 클래스 수 × 릴리스 횟수
→ 테스트 복잡도 급증</p>
<h2 id="언제-사용해야-할까">언제 사용해야 할까?</h2>
<p>자바 직렬화를 사용하는 프레임워크용 클래스이거나, Serializable을 반드시 요구하는 컴포넌트라면 선택의 여지가 없다.
하지만 그 외의 경우라면 이득과 비용을 반드시 비교해야 한다.
값 객체나 컬렉션은 직렬화를 지원하는 경우가 많지만, 스레드 풀처럼 “동작”을 표현하는 객체는 대부분 지원하지 않는다.</p>
<hr>
<h1 id="item-87---커스텀-직렬화-형태를-고려하라">Item 87 - 커스텀 직렬화 형태를 고려하라</h1>
<h2 id="기본-직렬화-형태가-괜찮은-경우">기본 직렬화 형태가 괜찮은 경우</h2>
<p>✔ 객체의 <strong>물리적 표현 = 논리적 내용</strong>일 때</p>
<ul>
<li><strong>물리적 표현</strong>: 코드 내부 구현 방식</li>
<li><strong>논리적 내용</strong>: 객체가 의미하는 실제 데이터</li>
</ul>
<pre><code class="language-java">public class Name implements Serializable {
    private final String lastName;
    private final String firstName;
    private final String middleName;
}</code></pre>
<ul>
<li>필드 자체가 의미를 직접 표현</li>
<li>내부 구현 변경 가능성 낮음
→ <strong>기본 직렬화 사용해도 문제 없음</strong></li>
</ul>
<h2 id="기본-직렬화-형태가-위험한-경우">기본 직렬화 형태가 위험한 경우</h2>
<p>객체의 물리적 표현과 논리적 내용이 다른 경우</p>
<pre><code class="language-java">public final class StringList implements Serializable {
    private int size;
    private Entry head;

    private static class Entry implements Serializable {
        String data;
        Entry next;
        Entry previous;
    }
}</code></pre>
<h3 id="문제점-정리">문제점 정리</h3>
<p>1️⃣ 내부 구현이 공개 API로 고정됨</p>
<ul>
<li>연결 리스트 → 배열로 변경 불가</li>
<li>과거 직렬화 형태를 영구 지원해야 함</li>
</ul>
<p>2️⃣ 불필요하게 큰 직렬화 데이터</p>
<ul>
<li>노드 연결 정보까지 전부 직렬화</li>
<li>네트워크/저장 비용 증가</li>
</ul>
<p>3️⃣ 직렬화 성능 저하</p>
<ul>
<li>객체 그래프를 직접 순회</li>
<li>O(n) 비용 발생</li>
</ul>
<p>4️⃣ 스택 오버플로 위험</p>
<ul>
<li>재귀 순회 구조</li>
<li>노드 수 많으면 위험</li>
</ul>
<hr>
<h1 id="item-88---readobject-메서드는-방어적으로-작성하라">Item 88 - readObject 메서드는 방어적으로 작성하라</h1>
<p><code>readObject</code>는 단순히 객체를 복원하는 메서드가 아니다.
<strong>역직렬화 과정에서 객체를 새로 만들어내는 또 하나의 생성자</strong>라고 봐야 한다.</p>
<p>직렬화된 데이터라고 해서 항상 정상적이라고 가정하면 안 된다.
잘못된 바이트 스트림이 전달되면, 생성자를 거치지 않고도 불변식이 깨진 객체가 만들어질 수 있다.</p>
<h2 id="readobject-작성-시-기본-원칙">readObject 작성 시 기본 원칙</h2>
<ul>
<li><code>readObject</code>는 public 생성자처럼 다룬다</li>
<li>어떤 바이트 스트림이 와도 유효한 객체만 생성해야 한다</li>
<li>입력 데이터는 절대 신뢰하지 않는다</li>
</ul>
<h2 id="불변-클래스도-안전하지-않다">불변 클래스도 안전하지 않다</h2>
<p>불변 클래스라도 내부에 <code>Date</code> 같은 <strong>가변 객체</strong>를 참조하고 있다면 위험하다.
역직렬화 과정에서 이 참조가 외부로 노출되면, final 필드라도 내부 상태가 변경될 수 있다.</p>
<p>생성자에서의 방어적 복사만으로는 충분하지 않으며,
<strong>readObject에서도 동일한 방어가 필요하다.</strong></p>
<h2 id="반드시-지켜야-할-작성-규칙">반드시 지켜야 할 작성 규칙</h2>
<ol>
<li><p><strong>불변식 검증</strong>
역직렬화 직후 객체 상태를 검사하고, 위반 시 <code>InvalidObjectException</code>을 던진다.</p>
</li>
<li><p><strong>방어적 복사</strong>
클래스 내부의 모든 가변 필드는 반드시 복사본으로 교체한다.</p>
</li>
<li><p><strong>재정의 가능 메서드 호출 금지</strong>
하위 클래스가 완전히 복원되기 전에 실행될 수 있다.</p>
</li>
</ol>
<hr>
<h1 id="item-89---인스턴스-수를-통제해야-한다면-readresolve보다는-열거-타입을-사용하라">Item 89 - 인스턴스 수를 통제해야 한다면 readResolve보다는 열거 타입을 사용하라</h1>
<p>싱글턴은 애플리케이션 전체에서 <strong>단 하나의 인스턴스만 존재함을 보장</strong>하는 패턴이다.
하지만 이 싱글턴이 <code>Serializable</code>을 구현하는 순간, 우리가 기대한 보장은 쉽게 깨진다.</p>
<h2 id="직렬화가-싱글턴을-깨뜨리는-이유">직렬화가 싱글턴을 깨뜨리는 이유</h2>
<p>클래스에 <code>implements Serializable</code>을 추가하면
역직렬화 과정에서 <strong>새로운 인스턴스가 생성</strong>된다.</p>
<ul>
<li>기본 직렬화를 쓰지 않아도</li>
<li><code>readObject</code>를 직접 구현해도</li>
<li>심지어 불변 클래스로 만들어도</li>
</ul>
<p><strong>역직렬화는 항상 새로운 객체를 만들어낸다.</strong>
즉, 직렬화 가능한 싱글턴은 더 이상 싱글턴이 아니다.</p>
<h2 id="readresolve로-해결할-수-있을까">readResolve로 해결할 수 있을까?</h2>
<p><code>readResolve</code>는 역직렬화로 만들어진 객체를
<strong>다른 객체로 교체</strong>할 수 있는 훅(hook)이다.</p>
<p>이를 이용하면:</p>
<ul>
<li>역직렬화된 객체는 버리고</li>
<li>클래스 초기화 시 생성된 기존 싱글턴 인스턴스를 반환해</li>
<li>겉보기엔 싱글턴처럼 동작하게 만들 수 있다</li>
</ul>
<p>이 방식이 바로 <strong>전통적인 직렬화 싱글턴 구현법</strong>이다.</p>
<h2 id="하지만-readresolve는-매우-취약하다">하지만 readResolve는 매우 취약하다</h2>
<p>문제는 <strong>readResolve가 실행되기 전</strong>이다.</p>
<ul>
<li><p>싱글턴이 <code>transient</code>가 아닌 <strong>참조 필드</strong>를 가지고 있다면</p>
</li>
<li><p>그 필드들은 readResolve 이전에 이미 역직렬화된다</p>
</li>
<li><p>이 틈을 이용하면 공격자가</p>
<ul>
<li>역직렬화 중인 싱글턴 객체의 참조를 훔쳐</li>
<li>진짜 싱글턴과 별도의 인스턴스를 확보할 수 있다</li>
</ul>
</li>
</ul>
<p>즉, <strong>순간적으로라도 싱글턴이 두 개 존재</strong>하게 된다.</p>
<p>이를 막으려면:</p>
<ul>
<li>모든 참조 타입 필드를 <code>transient</code>로 선언해야 하고</li>
<li>구현 난이도와 실수 가능성이 급격히 올라간다</li>
</ul>
<h2 id="가장-안전한-해법-열거-타입">가장 안전한 해법: 열거 타입</h2>
<p>이 모든 문제를 <strong>한 번에 해결하는 방법</strong>이 있다.
바로 싱글턴을 <strong>열거 타입(enum)</strong> 으로 구현하는 것이다.</p>
<ul>
<li>JVM 차원에서 인스턴스 개수가 보장된다</li>
<li>직렬화/역직렬화가 자동으로 안전하게 처리된다</li>
<li>리플렉션 공격에도 강하다</li>
</ul>
<pre><code class="language-java">public enum Elvis {
    INSTANCE;
}</code></pre>
<p>이 방식은:</p>
<ul>
<li>가장 간결하고</li>
<li>가장 안전하며</li>
<li>유지보수 비용도 가장 낮다</li>
</ul>
<h2 id="readresolve가-필요한-경우도-있다">readResolve가 필요한 경우도 있다</h2>
<p>열거 타입이 항상 가능한 것은 아니다.</p>
<ul>
<li>인스턴스의 개수가 <strong>컴파일 타임에 결정되지 않는 경우</strong></li>
<li>상속 구조를 반드시 유지해야 하는 경우</li>
</ul>
<p>이런 상황에서는 readResolve를 사용할 수밖에 없다.
단, <strong>모든 참조 필드를 transient로 선언</strong>해야 하며
보안과 불변식 유지에 각별히 신경 써야 한다.</p>
<h2 id="정리">정리</h2>
<ul>
<li>직렬화는 싱글턴을 쉽게 깨뜨린다</li>
<li>readResolve는 해결책이지만 매우 취약하다</li>
<li><strong>인스턴스 통제가 목적이라면 열거 타입이 최선의 선택이다</strong></li>
<li>불가피할 때만 readResolve를 사용하되, 모든 참조 필드는 transient로</li>
</ul>
<hr>
<h1 id="item-90---직렬화된-인스턴스-대신-직렬화-프록시-사용을-검토하라">Item 90 - 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라</h1>
<p><code>Serializable</code>을 구현하면 객체는 <strong>생성자를 거치지 않고 생성</strong>될 수 있다.
이로 인해 불변식이 깨지거나 보안 문제가 생길 수 있는데, 이를 해결하는 방법이 <strong>직렬화 프록시 패턴</strong>이다.</p>
<h2 id="직렬화의-문제점">직렬화의 문제점</h2>
<p>일반적인 직렬화는 다음과 같은 위험을 가진다.</p>
<ul>
<li>생성자와 유효성 검사를 우회한다</li>
<li>가짜 바이트 스트림 공격이 가능하다</li>
<li>내부 필드 탈취 위험이 있다</li>
<li>불변 클래스를 보장하기 어렵다</li>
</ul>
<h2 id="직렬화-프록시-패턴-개념">직렬화 프록시 패턴 개념</h2>
<p>직렬화 프록시 패턴은
<strong>실제 객체 대신 프록시 객체를 직렬화</strong>하는 방식이다.</p>
<ul>
<li>직렬화 시 <code>writeReplace</code>로 프록시 객체를 반환</li>
<li>바깥 클래스의 <code>readObject</code>는 예외를 던져 차단</li>
<li>프록시의 <code>readResolve</code>에서 생성자를 통해 객체 복원</li>
</ul>
<p>이를 통해 역직렬화가 <strong>항상 정상적인 생성 경로</strong>를 따르게 된다.</p>
<h2 id="장점">장점</h2>
<ul>
<li>가짜 바이트 스트림 및 필드 탈취 공격 차단</li>
<li>필드를 <code>final</code>로 선언 가능 → 진정한 불변 클래스</li>
<li>역직렬화 시 추가 검증 코드 불필요</li>
<li>직렬화 전후 클래스가 달라도 안전하게 동작</li>
</ul>
<h2 id="한계">한계</h2>
<ul>
<li>상속 가능한 클래스에는 적용 불가</li>
<li>객체 그래프에 순환 참조가 있으면 사용 불가</li>
<li>성능은 다소 떨어진다</li>
</ul>
<h2 id="핵심-정리">핵심 정리</h2>
<ul>
<li>직렬화는 객체 생성 규칙을 쉽게 무너뜨린다</li>
<li>직렬화 프록시 패턴은 이를 구조적으로 차단한다</li>
<li><strong>확장할 수 없는 클래스라면 직렬화 프록시를 우선 고려하자</strong></li>
</ul>
<hr>
<p>참고 블로그
<a href="https://github.com/Meet-Coder-Study/book-effective-java/blob/main/12%EC%9E%A5/90_%EC%A7%81%EB%A0%AC%ED%99%94%EB%90%9C_%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4_%EB%8C%80%EC%8B%A0_%EC%A7%81%EB%A0%AC%ED%99%94_%ED%94%84%EB%A1%9D%EC%8B%9C_%EC%82%AC%EC%9A%A9%EC%9D%84_%EA%B2%80%ED%86%A0%ED%95%98%EB%9D%BC_%ED%99%A9%EC%A4%80%ED%98%B8.md">https://github.com/Meet-Coder-Study/book-effective-java/blob/main/12%EC%9E%A5/90_%EC%A7%81%EB%A0%AC%ED%99%94%EB%90%9C_%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4_%EB%8C%80%EC%8B%A0_%EC%A7%81%EB%A0%AC%ED%99%94_%ED%94%84%EB%A1%9D%EC%8B%9C_%EC%82%AC%EC%9A%A9%EC%9D%84_%EA%B2%80%ED%86%A0%ED%95%98%EB%9D%BC_%ED%99%A9%EC%A4%80%ED%98%B8.md</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 자바 11장) 동시성]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-11%EC%9E%A5</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-11%EC%9E%A5</guid>
            <pubDate>Fri, 02 Jan 2026 09:24:20 GMT</pubDate>
            <description><![CDATA[<h1 id="item-78---공유-중인-가변-데이터는-동기화해-사용하라">Item 78 - 공유 중인 가변 데이터는 동기화해 사용하라</h1>
<h2 id="1-핵심-정리">(1) 핵심 정리</h2>
<ul>
<li>여러 스레드가 가변 데이터를 공유한다면 그 데이터를 읽고 쓰는 동작은 반드시 동기화 해야 한다.</li>
<li>동기화하지 않는다면 한 스레드가 수행한 변경을 다른 스레드가 보지 못할 수도 있다.</li>
<li>배타적 실행은 필요 없고 스레드끼리의 통신만 필요하다면 volatile 한정자만으로 동기화할 수 있다. 다만, 올바로 사용하기가 까다롭다.</li>
</ul>
<h2 id="2-동기화의-기능">(2) 동기화의 기능</h2>
<h4 id="21-배타적-실행">2.1 배타적 실행</h4>
<ul>
<li>한 스레드가 변경하는 중이라면 다른 스레드가 보지 못하게 막는 용도를 말한다.</li>
<li>즉, Lock 을 걸어 동시에 여러 스레드가 접근하는 것을 차단한다.</li>
</ul>
<h4 id="22-스레드-사이의-안정적-통신">2.2 스레드 사이의 안정적 통신</h4>
<ul>
<li>동기화 없이는 한 스레드가 만든 변화를 다른 스레드에서 확인하지 못할 수 있다.</li>
<li>즉, Lock 의 보호하에 수행된 모든 이전수정의 최종 결과를 보게 해준다.</li>
</ul>
<h2 id="3-원자적-연산">(3) 원자적 연산</h2>
<h4 id="31-원자적-연산">3.1 원자적 연산</h4>
<ul>
<li><strong>중단될 수 없는 연산</strong></li>
<li>여러 스레드가 동시에 접근해도 <strong>연산 결과가 깨지지 않음</strong></li>
</ul>
<p>Java에서의 특징:</p>
<ul>
<li><code>long</code>, <code>double</code>을 제외한 <strong>기본 타입의 읽기/쓰기는 원자적</strong></li>
</ul>
<blockquote>
<p>단, <strong>원자적 = 스레드 안전</strong> 은 아니다 ❌</p>
</blockquote>
<h4 id="32-왜-long--double-은-원자적이지-않을까-">3.2 왜 long / double 은 원자적이지 않을까 ?</h4>
<ul>
<li>자바 메모리 모델(JMM)은 <strong>32비트 단위 연산</strong>을 기준으로 설계됨</li>
<li><code>long</code>, <code>double</code>은 <strong>64비트</strong> → 32비트씩 <strong>두 번에 나눠 처리</strong>될 수 있음</li>
<li>이 과정에서 다른 스레드가 끼어들면 <strong>찢어진 값(torn read)</strong> 발생 가능</li>
</ul>
<h4 id="33-그렇다면-jvm-64-bit-machine-에서도-원자적이지-않을까">3.3 그렇다면 JVM 64-bit machine 에서도 원자적이지 않을까??</h4>
<ul>
<li>최신 64-bit JVM에서는 대부분 <strong>원자적으로 구현</strong>되어 있음</li>
<li>하지만 <strong>자바 언어 명세(JLS)는 이를 보장하지 않는다</strong></li>
</ul>
<blockquote>
<p>따라서 <strong>명세에 의존한 안전한 코드</strong>를 작성해야 하며,
<code>volatile</code> 또는 <code>synchronized</code> 사용이 정석이다.</p>
</blockquote>
<h2 id="4-synchronized">(4) Synchronized</h2>
<h4 id="41-synchronized-란-">4.1 Synchronized 란 ?</h4>
<ul>
<li>Java 에서는 synchronized 키워드를 통해 메서드나 블록을 한번에 한 스레드씩 수행하도록 보장한다.</li>
</ul>
<h4 id="42-예제">4.2 예제</h4>
<ul>
<li>기능 <ul>
<li>boolean 필드를 polling 하면서 값을 체크하는 로직으로 Thread 를 정지</li>
</ul>
</li>
</ul>
<ul>
<li><p>AS-IS: 무한 루프</p>
<pre><code class="language-java">  public class StopThread {

      private static boolean stopRequest;

      public static void main(String[] args) throws InterruptedException {

          Thread thread = new Thread(() -&gt; {
              int i = 0;
              while (!stopRequest) {
                  i++;

                  /**
                   *  print 를 하니까 정상 종료 됨 ??
                   *  System.out.println(&quot;i: &quot; + i + &quot;stopRequest: &quot; + stopRequest); 
                   */
              }
          });

          thread.start();

          TimeUnit.SECONDS.sleep(1);
          stopRequest = true;
      }
  }</code></pre>
</li>
<li><p>TO-BE: 1초 후 Stop</p>
<pre><code class="language-java">  public class StopThreadSync {

      private static boolean stopRequested;

      // write
      private static synchronized void requestStop() {
          stopRequested = true;
      }

      // read
      private static synchronized boolean stopRequest() {
          return stopRequested;
      }

      public static void main(String[] args) throws InterruptedException {

          Thread thread = new Thread(() -&gt; {
              int i = 0;

              while (!stopRequest()) {
                  i++;
              }
          });

          thread.start();

          TimeUnit.SECONDS.sleep(1);
          requestStop();
      }
  }</code></pre>
</li>
<li><p>위 예제 코드를 살펴보면 쓰기/읽기 모두 synchronized 키워드가 붙어 있음을 볼 수 있다.</p>
</li>
<li><p>즉, 쓰기/읽기 모두 동기화되지 않으면 동작을 보장하지 않는다.</p>
</li>
</ul>
<h4 id="43-threadstop을-사용하지-않은-이유">4.3 Thread.stop()을 사용하지 않은 이유</h4>
<ul>
<li>Thread.stop() 은 안전하지 않아, Java 11 에서 deprecated 되었다.</li>
<li>오라클 문서에서는 아래와 같이 설명하고 있다.</li>
</ul>
<br>

<img width="900" src="https://user-images.githubusercontent.com/60383031/126901361-ff028e57-6669-4450-92e1-d6fd8f6b667b.png">


<ul>
<li>Thread.stop() 을 호출하는 순간, 해당 스레드를 바라보고 있던 다른 스레드들에 대한 lock 이 해제가 된다.</li>
<li>stop() 메서드가 호출 ---&gt; ThreadDeath exception 전파 ---&gt;  바라보고 있는 스레드의 lock 해제 </li>
<li>따라서 객체의 일관성을 보장하기 어려울 뿐만 아니라, ThreadDeath 객체는 특별한 경고 없이 스레드를 죽이기 때문에 개발자가 캐치하기 어렵다.</li>
</ul>
<h2 id="5-volatile">(5) Volatile</h2>
<h4 id="51-volatile-">5.1 Volatile ?</h4>
<ul>
<li>Volatile 한정자는 배타적 수행과는 상관없지만 항상 가장 최근에 기록된 값을 읽게 됨을 보장한다.</li>
</ul>
<h4 id="52-예제">5.2 예제</h4>
<pre><code class="language-java">public class StopThreadVolatile {

    private static volatile boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -&gt; {
            int i = 0;

            while (!stopRequested) {
                i++;
            }
        });

        thread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}</code></pre>
<h4 id="53-volatile-원리">5.3 Volatile 원리</h4>
<ul>
<li><p>Volatile 키워드가 붙은 데이터는 메인 메모리에 저장이된다.</p>
</li>
<li><p>Multi-thread 환경</p>
<ul>
<li><p>task 를 수행하는 동안 성능 향상을 위해 CPU cache 에 저장하고 활용한다.</p>
</li>
<li><p>즉, 각각의 스레드의 CPU cache 에 저장된 값이 다르기 때문에 동시성 문제가 발생한다.</p>
<br>

<img width="400" src="https://user-images.githubusercontent.com/60383031/126903403-611cde38-45fc-46b7-b838-e6c3beff6b5a.png">


</li>
</ul>
</li>
</ul>
<ul>
<li>따라서 Volatile 키워드를 사용하면 해당 변수를 메인 메모리에 저장하고 읽기 때문에 항상 최근에 기록된 값을 읽을 수 있다.</li>
<li>하지만 CPU cache 보다 메인 메모리에 접근하면 비용이 더 발생한다.</li>
</ul>
<h4 id="54-volatile-한계">5.4 Volatile 한계</h4>
<ul>
<li>증가 연산자(++) 와 같이 필드를 두 번 접근하는 연산은 한다면 동시성을 보장할 수 없다.</li>
<li>따라서 Volatile 키워드는 하나 이상의 스레드가 write 하는 상황에서는 동시성을 보장한다고 볼 수 없다.</li>
</ul>
<blockquote>
<p>👉 실무에서는 volatile를 잘 쓰지 않는다?</p>
</blockquote>
<ul>
<li>volatile은 가시성만 보장하고 원자성은 보장하지 않는다</li>
<li>실무에서 동시성 이슈의 핵심은 대부분 쓰기의 원자성 문제
웹 애플리케이션 특성상
→ 최신 값이 잠시 보이지 않아도 치명적이지 않은 경우가 많음</li>
<li>그래서 현업에서는 volatile보다 Atomic / Lock / Concurrent 컬렉션을 주로 사용한다</li>
<li>volatile은 종료 플래그 같은 단순 상태 공유에만 제한적으로 사용됨</li>
</ul>
<hr>
<h1 id="item-79---과도한-동기화는-피하라">Item 79 - 과도한 동기화는 피하라</h1>
<p>동기화 안에서 외부 코드(콜백)를 호출하지 마라. → 성능 저하, 예외, 교착상태 위험</p>
<ul>
<li><p><strong>과도한 동기화 문제</strong></p>
<ul>
<li>성능 나빠짐</li>
<li>교착상태(서로 기다리다 멈춤)</li>
<li>예상 못 한 버그 발생</li>
</ul>
</li>
</ul>
<ul>
<li>synchronized 블록 안에서 클라이언트가 준 코드(람다, 콜백)를 실행하지 마라</li>
</ul>
<p>→ 이걸 제어권을 클라이언트에 넘긴다고 함</p>
<h3 id="왜-위험한가">왜 위험한가?</h3>
<h4 id="1️⃣-concurrentmodificationexception">1️⃣ ConcurrentModificationException</h4>
<ul>
<li>동기화된 상태에서</li>
<li>관찰자가 자기 자신을 제거</li>
<li>→ 순회 중 컬렉션 수정 → 예외 발생</li>
</ul>
<h4 id="2️⃣-교착상태-deadlock">2️⃣ 교착상태 (Deadlock)</h4>
<ul>
<li>메인 스레드가 락 잡고 있음</li>
<li>콜백에서 다른 스레드가 락 요청</li>
<li>서로 기다리다 멈춤</li>
</ul>
<h3 id="문제-원인">문제 원인</h3>
<ul>
<li>동기화 영역 안에서</li>
<li>언제 끝날지 모르는 외부 코드 실행</li>
<li>이런 메서드를 열린 호출(Open Call) 이라고 함</li>
</ul>
<h3 id="해결-방법">해결 방법</h3>
<h4 id="방법-1-스냅샷-떠서-밖에서-실행">방법 1. 스냅샷 떠서 밖에서 실행</h4>
<pre><code class="language-java">synchronized (observers) {
    snapshot = new ArrayList&lt;&gt;(observers);
}
for (SetObserver&lt;E&gt; observer : snapshot)
    observer.added(this, element);</code></pre>
<h4 id="방법-2-copyonwritearraylist-사용">방법 2. <code>CopyOnWriteArrayList</code> 사용</h4>
<ul>
<li>동기화 문제 자동 해결</li>
<li>관찰자 패턴에 특히 잘 맞음</li>
</ul>
<blockquote>
<p>정리</p>
</blockquote>
<ul>
<li>동기화 블록 안에서는 최소한의 일만</li>
<li>계산, 콜백, IO → ❌ 밖으로 빼기</li>
</ul>
<hr>
<h2 id="item-80---스레드보다는-실행자-태스크-스트림을-애용하라">Item 80 - 스레드보다는 실행자, 태스크, 스트림을 애용하라</h2>
<p>Effective Java 초판에서는 작업 큐(work queue)를 직접 구현하는 방법을 소개했지만, 이는 코드가 복잡하고 동시성 오류에 취약했다. 스레드 생성과 관리, 예외 처리까지 모두 개발자가 책임져야 했기 때문이다.</p>
<p>이후 자바에는 <code>java.util.concurrent</code> 패키지, 즉 <strong>Executor Framework</strong>가 도입되었다. Executor를 사용하면 스레드나 큐를 직접 다룰 필요 없이 작업 실행을 간단히 처리할 수 있다.</p>
<pre><code class="language-java">ExecutorService exec = Executors.newSingleThreadExecutor();
exec.execute(runnable);
exec.shutdown();</code></pre>
<p>ExecutorService는 작업 실행뿐 아니라 작업 완료 대기, 결과 수집, 주기적 실행 등 다양한 기능을 제공한다. 작은 규모의 프로그램에는 <code>newCachedThreadPool</code>이 적합하며, 기존 스레드를 재사용하고 필요 시 새 스레드를 생성한다. 반면 대규모 애플리케이션에서는 스레드 수가 고정된 <code>newFixedThreadPool</code>을 사용하는 것이 안전하다.</p>
<p>저자가 스레드보다 Executor 사용을 권장하는 이유는, <code>Thread</code>가 <strong>작업의 단위와 실행 메커니즘을 함께 가지고 있기 때문</strong>이다. Executor Framework에서는 이 둘이 분리된다. 작업의 단위는 태스크이며, <code>Runnable</code>과 <code>Callable</code>이 이에 해당한다. 덕분에 실행 정책을 바꾸더라도 작업 코드는 영향을 받지 않는다.</p>
<p>자바 7부터는 <code>ForkJoinPool</code>을 통해 fork-join 작업을 지원하며, 이를 기반으로 한 <code>parallel stream</code>은 병렬 처리를 더 쉽고 효율적으로 만들어준다.</p>
<hr>
<h1 id="item-81---wait와-notify보다는-동시성-유틸리티를-사용하라">Item 81 - wait와 notify보다는 동시성 유틸리티를 사용하라</h1>
<p>자바는 오래전부터 <code>wait</code>, <code>notify</code>, <code>notifyAll</code>을 통해 스레드 간 협력을 지원해왔다. 하지만 Java 5 이후 도입된 <strong>고수준 동시성 유틸리티</strong> 덕분에, 이 저수준 메서드들을 직접 사용할 이유는 점점 줄어들었다.</p>
<h2 id="wait--notify란">wait / notify란?</h2>
<ul>
<li><p><code>wait()</code></p>
<ul>
<li>고유 락을 반납하고 스레드를 대기 상태로 만듦</li>
<li>반드시 <code>synchronized</code> 블록 내부에서 호출해야 함</li>
</ul>
</li>
<li><p><code>notify()</code></p>
<ul>
<li>대기 중인 스레드 하나를 임의로 깨움</li>
</ul>
</li>
<li><p><code>notifyAll()</code></p>
<ul>
<li>대기 중인 모든 스레드를 깨움</li>
</ul>
</li>
</ul>
<p>👉 세 메서드 모두 <code>Object</code>에 정의되어 있다.</p>
<h2 id="wait-사용-시-반드시-지켜야-할-규칙">wait 사용 시 반드시 지켜야 할 규칙</h2>
<pre><code class="language-java">synchronized (obj) {
    while (condition) {
        obj.wait();
    }
}</code></pre>
<ul>
<li><code>wait</code>는 <strong>조건 검사 반복문(wait loop)</strong> 안에서만 사용</li>
<li>깨어난 뒤에도 조건을 다시 검사해야 함</li>
<li>그렇지 않으면 락이 보호하는 불변식이 깨질 수 있음</li>
</ul>
<h3 id="조건이-충족되지-않았는데-깨어나는-경우">조건이 충족되지 않았는데 깨어나는 경우</h3>
<ul>
<li>다른 스레드가 상태를 변경한 경우</li>
<li>실수 또는 악의적인 <code>notify</code> 호출</li>
<li><code>notifyAll</code>로 모든 스레드가 깨어난 경우</li>
<li>허위 각성(spurious wakeup)</li>
</ul>
<p>➡️ 이 모든 상황을 고려해야 하므로 구현 난이도가 매우 높다.</p>
<h2 id="synchronized의-한계">synchronized의 한계</h2>
<ul>
<li>메서드 진입·탈출 시 오버헤드 발생</li>
<li>읽기 전용 작업도 락 때문에 대기</li>
<li>락 경쟁이 심해질수록 성능 저하</li>
</ul>
<p>👉 단순히 <code>synchronized</code> 키워드 하나 차이로도 성능이 크게 달라질 수 있다.</p>
<h2 id="동시성-유틸리티가-필요한-이유">동시성 유틸리티가 필요한 이유</h2>
<p>Java 5부터 제공되는 <code>java.util.concurrent</code>는
<strong>안전성 + 성능 + 가독성</strong>을 모두 개선한다.</p>
<h2 id="주요-동시성-유틸리티-분류">주요 동시성 유틸리티 분류</h2>
<h3 id="1️⃣-실행자-프레임워크">1️⃣ 실행자 프레임워크</h3>
<ul>
<li><code>ExecutorService</code>, <code>Executors</code></li>
<li>스레드 생성과 관리 책임을 프레임워크에 위임</li>
</ul>
<h3 id="2️⃣-동시성-컬렉션">2️⃣ 동시성 컬렉션</h3>
<ul>
<li><code>ConcurrentHashMap</code>, <code>ConcurrentLinkedQueue</code></li>
<li>외부 락 없이 높은 동시성 제공</li>
<li><code>Collections.synchronizedMap</code>보다 성능 우수</li>
</ul>
<h3 id="3️⃣-동기화-장치synchronizer">3️⃣ 동기화 장치(Synchronizer)</h3>
<ul>
<li><code>CountDownLatch</code> : 특정 조건까지 대기</li>
<li><code>Semaphore</code> : 자원 접근 개수 제한</li>
<li>의도가 명확하고 사용이 안전함</li>
</ul>
<blockquote>
<p>결론</p>
</blockquote>
<ul>
<li><code>wait</code>와 <code>notify</code>는 <strong>저수준 도구</strong>로 사용이 어렵고 위험하다</li>
<li><code>synchronized</code> 기반 설계는 성능과 확장성에 한계가 있다</li>
<li>특별한 이유가 없다면
👉 <strong>동시성 유틸리티를 우선적으로 사용하자</strong></li>
</ul>
<hr>
<h1 id="item-82-스레드-안전성-수준을-문서화하라">Item 82. 스레드 안전성 수준을 문서화하라</h1>
<p>멀티스레드 환경에서 여러 스레드가 동시에 메서드를 호출할 때의 동작 방식은 <strong>클래스와 클라이언트 사이의 중요한 계약</strong>이다. 하지만 API 문서에 스레드 안전성에 대한 설명이 없다면, 사용자는 스스로 가정을 할 수밖에 없고 이는 심각한 오류로 이어질 수 있다. 동기화가 부족하면 버그가 발생하고, 과도하면 성능이 급격히 저하된다.</p>
<p>따라서 멀티스레드 환경에서도 API를 안전하게 사용하려면, <strong>클래스가 지원하는 스레드 안전성 수준을 반드시 문서로 명확히 밝혀야 한다.</strong> 단순히 메서드에 <code>synchronized</code>를 붙였다고 해서 스레드 안전하다고 판단해서는 안 된다.</p>
<h2 id="스레드-안전성-수준-분류">스레드 안전성 수준 분류</h2>
<h3 id="불변-immutable">불변 (Immutable)</h3>
<ul>
<li>인스턴스 상태가 절대 변하지 않음</li>
<li>외부 동기화 불필요</li>
<li>예: <code>String</code>, <code>Long</code></li>
</ul>
<h3 id="무조건적-스레드-안전">무조건적 스레드 안전</h3>
<ul>
<li>내부 동기화로 항상 안전</li>
<li>외부 동기화 필요 없음</li>
<li>예: <code>AtomicLong</code>, <code>ConcurrentHashMap</code></li>
</ul>
<h3 id="조건부-스레드-안전">조건부 스레드 안전</h3>
<ul>
<li>기본적으로 안전</li>
<li>일부 메서드는 외부 동기화 필요</li>
<li>예: <code>Collections.synchronizedList</code></li>
</ul>
<h3 id="스레드-안전하지-않음">스레드 안전하지 않음</h3>
<ul>
<li>동시 사용 시 클라이언트가 직접 동기화</li>
<li>예: <code>ArrayList</code>, <code>HashMap</code></li>
</ul>
<h3 id="스레드-적대적">스레드 적대적</h3>
<ul>
<li>외부 동기화로도 안전하지 않음</li>
<li>주로 정적 필드를 동기화 없이 수정</li>
<li>예: <code>nextSerialNumber++</code> 같은 구현</li>
</ul>
<blockquote>
<p>정리</p>
</blockquote>
<ul>
<li>모든 클래스는 <strong>자신의 스레드 안전성 수준을 문서화해야 한다</strong></li>
<li>스레드 안전성은 암묵적인 추측에 맡기면 안 된다</li>
<li><code>synchronized</code>는 문서화를 대신할 수 없다</li>
</ul>
<hr>
<h1 id="item-83---지연-초기화는-신중히-사용하라">Item 83 - 지연 초기화는 신중히 사용하라</h1>
<h2 id="1-지연-초기화란">1. 지연 초기화란?</h2>
<ul>
<li>값이 처음 필요할 때 초기화</li>
<li>안 쓰이면 아예 초기화 안 함</li>
</ul>
<h2 id="2-언제-쓰나">2. 언제 쓰나?</h2>
<ul>
<li>해당 클래스의 인스턴스 중 그 필드를 사용하는 인스턴스의 비율이 낮고, 그 필드를 초기화하는 비용이 클 때 (hashCode)</li>
<li>지연 초기화 적용 전후의 성능을 측정</li>
</ul>
<h2 id="3-단점">3. 단점</h2>
<ul>
<li>생성은 빨라짐</li>
<li>대신 접근할 때 비용 증가</li>
<li>멀티 스레드면 동기화 필수</li>
</ul>
<h2 id="4-초기화-방법-한눈에-보기">4. 초기화 방법 한눈에 보기</h2>
<h3 id="a-일반-초기화">a. 일반 초기화</h3>
<pre><code class="language-java">private final FieldType field = compute();</code></pre>
<ul>
<li>가장 안전하고 단순</li>
</ul>
<h3 id="b-synchronized-접근자">b. synchronized 접근자</h3>
<pre><code class="language-java">private FieldType field;
private synchronized FieldType get() {
  if (field == null) field = compute();
  return field;
}</code></pre>
<ul>
<li>초기화 순환 문제 해결</li>
<li><strong>느림</strong><h3 id="c-정적-필드-홀더-클래스-베스트">c. 정적 필드: 홀더 클래스 (베스트)</h3>
</li>
</ul>
<pre><code class="language-java">private static class Holder {
  static final FieldType field = compute();
}
static FieldType get() { return Holder.field; }</code></pre>
<ul>
<li><strong>빠르고 안전</strong></li>
<li>정적 필드 지연 초기화의 정석</li>
</ul>
<h3 id="d-인스턴스-필드--성능-중요-→-이중-검사">d. 인스턴스 필드 + 성능 중요 → 이중 검사</h3>
<pre><code class="language-java">private volatile FieldType field;
FieldType get() {
  FieldType r = field;
  if (r != null) return r;
  synchronized(this) {
    if (field == null) field = compute();
    return field;
  }
}</code></pre>
<ul>
<li>초기화 후엔 <strong>락 안 씀</strong></li>
<li>반드시 <code>volatile</code><h3 id="e-단일-검사-중복-초기화-ok">e. 단일 검사 (중복 초기화 OK)</h3>
</li>
</ul>
<pre><code class="language-java">private volatile FieldType field;
FieldType get() {
  if (field == null)
    field = compute();
  return field;
}</code></pre>
<ul>
<li>여러 번 초기화돼도 상관없을 때</li>
<li>드문 케이스</li>
</ul>
<hr>
<h2 id="item-84---프로그램의-동작을-스레드-스케줄러에-기대지-말라">Item 84 - 프로그램의 동작을 스레드 스케줄러에 기대지 말라</h2>
<ul>
<li><p>멀티스레드 환경에서 어떤 스레드를 얼마나 실행할지는 OS 스레드 스케줄러가 결정한다.</p>
</li>
<li><p>스케줄링 정책은 운영체제마다 다르며,
정확성이나 성능이 스케줄러에 의존하는 프로그램은 이식성이 떨어진다.</p>
</li>
<li><p>좋은 멀티스레드 프로그램의 핵심은
실행 가능한 스레드 수를 프로세서 수보다 과도하게 늘리지 않는 것이다.</p>
</li>
<li><p>이를 위해 스레드는</p>
<ul>
<li>할 일이 없으면 대기 상태로 전환되어야 하며</li>
<li>당장 처리할 작업이 없으면 실행되면 안 된다.</li>
</ul>
</li>
</ul>
<h2 id="바쁜-대기busy-waiting">바쁜 대기(Busy Waiting)</h2>
<ul>
<li><p>바쁜 대기란</p>
<ul>
<li>공유 자원의 상태가 바뀌었는지 <strong>쉬지 않고 반복 검사하는 방식</strong></li>
</ul>
</li>
<li><p>문제점</p>
<ul>
<li>CPU를 소모하면서 아무 일도 하지 않음</li>
<li>시스템 전체 성능 저하</li>
</ul>
</li>
<li><p>스레드는 <strong>조건이 만족될 때까지 기다려야지, 확인해서는 안 된다.</strong></p>
</li>
</ul>
<h2 id="threadyield">Thread.yield</h2>
<ul>
<li><p><code>Thread.yield()</code></p>
<ul>
<li>현재 스레드가 <strong>다른 스레드에게 실행 기회를 양보</strong></li>
<li>단, 실제 동작은 <strong>전적으로 스케줄러 판단에 달림</strong></li>
</ul>
</li>
<li><p>문제점</p>
<ul>
<li>효과가 플랫폼·환경마다 다름</li>
<li>테스트 불가능</li>
<li>증상이 완화될 수는 있으나 <strong>근본 해결책이 아님</strong></li>
</ul>
</li>
</ul>
<p>➡️ 스레드가 제대로 실행되지 않는다고
<strong>yield로 고치려는 유혹을 반드시 피해야 한다.</strong></p>
<h2 id="올바른-접근-방식">올바른 접근 방식</h2>
<ul>
<li><p>스케줄러에 기대지 말고</p>
<ul>
<li>프로그램 구조를 개선해</li>
<li><strong>동시에 실행 가능한 스레드 수 자체를 줄여라</strong></li>
</ul>
</li>
<li><p>스레드는</p>
<ul>
<li>작업이 있을 때만 실행</li>
<li>없으면 <strong>대기(wait / blocking)</strong> 상태 유지</li>
</ul>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바] BOJ_16234_인구이동_G4]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ16234%EC%9D%B8%EA%B5%AC%EC%9D%B4%EB%8F%99G4</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ16234%EC%9D%B8%EA%B5%AC%EC%9D%B4%EB%8F%99G4</guid>
            <pubDate>Thu, 01 Jan 2026 12:33:07 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크"><a href="https://www.acmicpc.net/problem/16234">문제 링크</a></h1>
<h1 id="접근법">접근법</h1>
<p>처음에 봤을 때 </p>
<ol>
<li>모든 칸을 돌면서 동시에 visited[][] 갱신하면서, 국경선 열지 판단하는 것</li>
<li>한번 열면 다 합쳐서 sum / N 해서 갱신하는 것</li>
<li>차이가 L~R일 때까지 위 과정을 반복하는 것..
이라고 생각하고 코드를 짰다.</li>
</ol>
<h1 id="잘못된-점">잘못된 점</h1>
<p>처음에 코드를 구현했을 때 잘못된 점은 </p>
<ul>
<li>visited를 한 번만 만들고 끝냈다는 거였다
👉 이 문제는 하루마다 다시 방문 가능하기 때문에 매 day마다 초기화해야한다.</li>
</ul>
<h1 id="정리해보자면">정리해보자면</h1>
<ol>
<li>main에서 모든 칸을 순회한다. 방문하지 않은 칸이라면 isOpen을 통해 bfs 4방향 탐색을 진행한다. 
1-1. isOpen이 true을 반환하면 인구이동이 있었다는 것, false면 없었다는 것이기 때문에 해당 반복문을 종료한다. </li>
<li>isOpen에서는 기존에 했었던 bfs 4방향 탐색을 돌리는데 <strong><code>ArrayList&lt;int[]&gt; union = new ArrayList&lt;&gt;();</code>를 선언해서 연합 지역을 관리 및 계산해야한다는 게 핵심이다.</strong>
2-1. 또한 sum을 통해서 국경선이 열렸을 때 해당 좌표의 값을 sum에 계속해서 더해야 한다. </li>
<li>4방향 탐색을 돌리면서 Math.abs()를 통해 차이를 구하고 이 차이가 L 이상 R 이하라면 visited, q, union, sum을 갱신한다.</li>
<li>모든 탐색이 끝났다면 union을 통해 인구 이동을 시키면 된다. 
4-1. 연합이 2 이상이면 avg를 만들어서 모두 평균만큼 인구를 이동시킨다.</li>
</ol>
<h1 id="정답-코드">정답 코드</h1>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class BOJ_16234_인구이동_G4 {
    static int N, L, R;
    static int[][] arr;
    static boolean[][] visited;
    static int[] dx = {1, -1, 0, 0};
    static int[] dy = {0, 0, 1, -1};

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        L = Integer.parseInt(st.nextToken());
        R = Integer.parseInt(st.nextToken());
        arr = new int[N][N];
        visited = new boolean[N][N];

        for (int i = 0; i &lt; N; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; N; j++) {
                arr[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        int day = 0;

        while (true) {
            visited = new boolean[N][N];
            boolean moved = false;

            for (int i = 0; i &lt; N; i++) {
                for (int j = 0; j &lt; N; j++) {
                    if (!visited[i][j]) {
                        if (isOpen(i, j)) moved = true;
                    }
                }
            }

            if (!moved) break;
            day++;
        }

        System.out.println(day);
    }

    public static boolean isOpen(int x, int y) {
        // 여기선 bfs로 4방향을 탐색해야함
        Queue&lt;int[]&gt; q = new LinkedList&lt;&gt;();
        ArrayList&lt;int[]&gt; union = new ArrayList&lt;&gt;();

        q.add(new int[]{x, y});
        union.add(new int[]{x, y});
        visited[x][y] = true;

        int sum = arr[x][y];

        while (!q.isEmpty()) {
            int[] cur = q.poll();
            int cx = cur[0];
            int cy = cur[1];

            for (int d = 0; d &lt; 4; d++) {
                int nx = cx + dx[d];
                int ny = cy + dy[d];

                if (nx &lt; 0 || ny &lt; 0 || nx &gt;= N || ny &gt;= N) continue;
                if (visited[nx][ny]) continue;

                int diff = Math.abs(arr[cx][cy] - arr[nx][ny]);
                if (diff &gt;= L &amp;&amp; diff &lt;= R) {
                    visited[nx][ny] = true;
                    q.add(new int[]{nx, ny});
                    union.add(new int[]{nx, ny});
                    sum += arr[nx][ny];
                }
            }
        }

        // 연합이 2개 이상이면 인구 이동 발생
        if (union.size() &gt; 1) {
            int avg = sum / union.size();
            for (int[] p : union) {
                arr[p[0]][p[1]] = avg;
            }
            return true;
        }
        return false;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 자바 10장) 예외]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-10%EC%9E%A5-%EC%98%88%EC%99%B8</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-10%EC%9E%A5-%EC%98%88%EC%99%B8</guid>
            <pubDate>Fri, 26 Dec 2025 08:30:50 GMT</pubDate>
            <description><![CDATA[<h1 id="아이템-69---예외는-진짜-예외-상황에만-사용하라">아이템 69 - 예외는 진짜 예외 상황에만 사용하라</h1>
<p>예외는 오직 예외 상황에서만 써야 한다. 절대로 일상적인 제어 흐름용으로 쓰여선 안 된다. 잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 한다. </p>
<pre><code class="language-java">    try {
        int i = 0;
        while(true) {
            range[i++].climb()
        }
    } catch (ArrayIndexOutOfBoundsException e) {
}</code></pre>
<p>해당 코드의 문제점</p>
<ul>
<li>해당 코드는 무슨일을 하는 코드인지 알 수가 없다.</li>
<li>무한루프를 돌다가 배열의 끝에 도달해 ArrayIndexOutOfBoundsException이 발생해야 끝이난다.</li>
</ul>
<blockquote>
<p>첫 번째 예시에서는 왜 Exception을 던져줬을까? 바로 JVM에서 배열에 접근할 때마다 경계를 넘지 않는지 검사하는데, 일반적인 반복문도 배열 경계에 도달하면 종료된다는 희망에 던져졌을 것이라고, 책에서 말해준다. 하지만, 책에서는 세가지 면에서 잘못된 추론이라고 한다.</p>
</blockquote>
<ul>
<li>예외는 예외 상황에 쓸 용도로 설계되었으므로 예외에서는 최적화에 별로 신경 쓰지 않았을 가능성이 크다.</li>
<li>코드를 try-catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한 된다.</li>
<li>배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는다. JVM이 알아서 최적화해 없애준다.</li>
</ul>
<p>해당 코드를 다시 리팩토링 한다면,</p>
<pre><code class="language-java">for (Mountain m : range) {
    m.climb();
}</code></pre>
<p>아래와 같은 장점을 가지게 된다.</p>
<ul>
<li>무슨일을 하는 코드인지 직관적이게 확인할 수 있다.</li>
<li>forLoop가 끝나면 해당 코드는 끝나기 때문에 불필요한 Exception을 던질 필요가 없다.</li>
</ul>
<p>요번 아이템은 바로 장점 중에 두번째에 해당하는 Exception에 올바른 쓰임에 대해 2가지 항목으로 정리하며 끝내고 있다.</p>
<ul>
<li>예외는 오직 예외 상황에서만 써야 한다. 절대로 첫번째 예시처럼, 일상적인 제어 흐름용으로 쓰여선 안된다.</li>
<li>잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야한다.</li>
</ul>
<h1 id="아이템-70---복구할-수-있는-상황에는-검사-예외를-프로그래밍-오류에는-런타임-예외를-사용해라">아이템 70 - 복구할 수 있는 상황에는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용해라</h1>
<h3 id="1-검사-예외checked-exception">1. 검사 예외(Checked Exception)</h3>
<ul>
<li>반드시 예외처리를 해야한다.<ul>
<li><code>throw</code>, <code>try-catch</code>를 이용하여 예외처리를 강제화한다.</li>
<li>호출하는 쪽에서 복구하리라 여겨지는 상황이라면 검사 예외를 사용해야한다.</li>
</ul>
</li>
<li>복구에 필요한 정보를 알려주는 메소드를 정의하는게 좋다.<ul>
<li>예를 들어 물건을 구입하는데 잔고가 부족하다면, 잔고가 얼마나 부족한지 알려줘야한다.</li>
</ul>
</li>
</ul>
<h3 id="2-비검사-예외-unchecked-exception">2. 비검사 예외 (Unchecked Exception)</h3>
<ul>
<li>종류<ul>
<li>런타임 예외</li>
<li>에러</li>
</ul>
</li>
<li>프로그램에서 잡을 필요가 없다.<ul>
<li>복구가 불가능하거나 더 실행해봤자 의미가 없기 때문 → 예외처리를 강제화하지 않는다.</li>
<li>프로그래밍 오류를 나타낼 때에는 비검사 예외를 사용해야한다.</li>
</ul>
</li>
<li>우리가 구현하는 비검사 예외는 모두 <code>RuntimeException</code>을 상속 받아야한다.<ul>
<li><code>Error</code>는 상속 받지 말아야할 뿐만아니라, 직접 <code>throw</code> 하는 일도 없어야한다. (<code>AssertionError</code> 제외)</li>
<li><code>Exception</code>, <code>RuntimeException</code>, <code>Error</code>를 상속하지 않은 <code>throwble</code>은 만들지 말자<ul>
<li>이로운 것이 없다.</li>
<li>API 사용자에게 혼동을 줄 수도 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>뭘 사용할지 모르겠다면 아마 비검사 예외를 사용하는 것이 더 나을 것이다 (아이템 71 참고)</p>
<h3 id="3-결론">3. 결론</h3>
<ul>
<li>복구해야하는 상황이면 → <strong>검사 예외</strong></li>
<li>프로그래밍 시 나타나는 오류라면 → <strong>런타임 예외</strong></li>
<li>확실하지 않은 예외라면 → <strong>비검사 예외</strong></li>
<li><code>Error</code> 클래스를 상속해 하위 클래스를 만드는 일 → <strong>하지말자</strong></li>
<li><code>Exception</code>, <code>RuntimeException</code>, <code>Error</code>를 상속하지 않은 <code>throwble</code> → <strong>만들지 말자</strong></li>
</ul>
<h1 id="아이템-71---필요없는-검사-예외-사용은-피하라">아이템 71 - 필요없는 검사 예외 사용은 피하라</h1>
<h3 id="1-검사-예외-checked-exception">1. 검사 예외 (Checked Exception)</h3>
<ul>
<li><p>검사 예외는 발생한 문제를 프로그래머가 처리하여 안정성을 높힐 수 있다.</p>
</li>
<li><p>물론, 과하게 사용하면 오히려 쓰기 불편한 APi 가 될 수 있다.</p>
<ul>
<li><p>(1) try/catch 블록, throws</p>
<pre><code class="language-java">  public class Example {

      public void example() {

          try {

          } catch (IOException e) {

          } 

          try {

          } catch (IOException e) {

          } 

          try {

          } catch (IOException e) {

          } 

          ...
      }
  }     </code></pre>
</li>
<li><p>(2) Java 8, Stream</p>
<pre><code class="language-java">  public class Test {

      public static void main(String[] args) {
          String[] names = {&quot;자바&quot;, &quot;파이썬&quot;, &quot;고&quot;, &quot;Trigger&quot;};

          Arrays.stream(names)
                  .map(Example::verifyName)   // &lt;--- compile error
                  .collect(toSet());           
      }

      public static class Example {

          public String verifyName(String name) throws Exception {
              if (name == &quot;trigger&quot;) {
                  throw new Exception(&quot;fail&quot;);
              }
              return name;
          }
      }    

  }</code></pre>
</li>
</ul>
</li>
</ul>
<br>


<h3 id="2--검사-예외를-회피하는-방법">2.  검사 예외를 회피하는 방법</h3>
<p>(1) Optional</p>
<ul>
<li><p>검사 예외를 던지는 대신 단순히 빈 옵셔널을 반환하면 된다.</p>
</li>
<li><p>예시</p>
<ul>
<li><p>AS-IS</p>
<pre><code class="language-java">  Long userId = 1L;
  try {
     User user = userService.findUserById(userId); 
  } catch (Exception e) {
      log.error(&quot;fail to find user, userId: {}&quot;, userId)
      user = new User(userId);
  }</code></pre>
</li>
<li><p>TO-BE</p>
<pre><code class="language-java">  Long userId = 1L;

  User user = repository.findUserById(userId)
                  .orElseGet(() -&gt; new User(userId)); </code></pre>
</li>
</ul>
</li>
</ul>
<br>

<p>(2) 메서드를 두 개로 분할하여 비검사 예외로 바꾼다.</p>
<ul>
<li><p>이 방식에서 첫 번째 메서드는 예외가 던져질지 여부를 boolean 값으로 반환한다.</p>
</li>
<li><p>예시</p>
<ul>
<li>AS-IS<pre><code class="language-java">  try {
      obj.action(args);
  } catch (TheCheckedException e) {
      ... // 예외 상황에 대처한다.
  }</code></pre>
</li>
<li>TO-BE<pre><code class="language-java">  if(obj.actionPermitted(args)){
      obj.action(args);
  } else {
      ... // 예외 상황에 대처한다.
  }</code></pre>
</li>
</ul>
</li>
<li><p>하지만 외부 요안에 의하여 상태가 변할 수 있다면, 이 방법은 적절치 않다.</p>
</li>
</ul>
<blockquote>
<ul>
<li>꼭 필요한 곳에만 사용한다면, 검사 예외는 프로그램의 안정성을 높여준다. 하지만 남용하면 쓰기 어려운 API 를 낳는다.</li>
</ul>
</blockquote>
<ul>
<li>예외 상황에서 복구할 방법이 없다면 비검사 예외를 던지자.</li>
<li>복구가 가능하고 호출자가 그 처리를 해주길 바란다면, 우선 옵셔널을 반환해도 될지 고민하자.</li>
<li>옵셔널만으로는 상황을 처리하기에 충분한 정보를 제공할 수 없을 때만 검사 예외를 던지자.</li>
</ul>
<h1 id="아이템-72---표준-예외를-사용하라">아이템 72 - 표준 예외를 사용하라.</h1>
<p>자바 라이브러리는 대부분 API 에서 쓰기에 충분한 수의 예외를 제공한다.
표준 예외를 재사용하면 얻는 게 많다.</p>
<ol>
<li>API가 다른 사람이 익히고 사용하기 쉬워진다는 것</li>
<li>API를 사용한  프로그램도 낯선 예외를 사용하지 않게 되어 읽기 쉽게 된다는 장점</li>
<li>예외 클래스 수가 적을수록 메모리 사용량도 줄고 클래스를 적재하는 시간도 적게 걸린다.</li>
</ol>
<h2 id="자주-재사용하는-예외">자주 재사용하는 예외</h2>
<ul>
<li><h4 id="illegalargumentexception">IllegalArgumentException</h4>
<p>호출자가 인수로 부적절한 값을 넘길 때 던지는 예외</p>
</li>
</ul>
<p>  예시</p>
<pre><code class="language-java">  public class MainRunner {

      List&lt;Integer&gt; list = new ArrayList&lt;&gt;();

      public void positiveAdd(Integer number) {
          if (number &lt; 0) {
              throw new IllegalArgumentException(&quot;음수값은 허용하지 않음&quot;);
          }
          list.add(number);
      }

      public static void main(String[] args) {
          MainRunner mainRunner = new MainRunner();
          mainRunner.positiveAdd(-1);
      }
  }</code></pre>
<ul>
<li><h4 id="illegalstateexception">IllegalStateException</h4>
<p>대상 객체의 상태가 호출된 메서드를 수행하기에 적합하지 않을 때 주로 사용한다.</p>
</li>
</ul>
<p>  예시</p>
<pre><code class="language-java">  public class MainRunner {

      List&lt;Integer&gt; list = new ArrayList&lt;&gt;();

      public void evenIndexAdd(Integer number){
          if(list.size()%2 != 0){
              throw new IllegalStateException(&quot;짝수 인덱스에 들어갈 준비가 되지 않음&quot;);
          }
          list.add(number);
      }

      public static void main(String[] args) {
          MainRunner mainRunner = new MainRunner();
          mainRunner.evenIndexAdd(0);
          mainRunner.evenIndexAdd(1);
      }
  }</code></pre>
<ul>
<li><h4 id="nullpointerexception">NullPointerException</h4>
<p>null 값을 허용하지 않는 메서드에 null 을 건네는 경우 사용한다.</p>
</li>
</ul>
<p>  예시</p>
<pre><code class="language-java">  public class MainRunner {

      List&lt;Integer&gt; list = new ArrayList&lt;&gt;();

      public void add(Integer number){
          if(Objects.isNull(number)){
              throw new NullPointerException(&quot;값이 null 입니다.&quot;);
          }
          list.add(number);
      }

      public static void main(String[] args) {
          MainRunner mainRunner = new MainRunner();
          mainRunner.add(null);
      }
  }</code></pre>
<ul>
<li><h4 id="indexoutofboundsexception">IndexOutOfBoundsException</h4>
<p>시퀀스의 허용범위를 넘는 값을 건널때 사용한다.</p>
</li>
</ul>
<p>  예시</p>
<pre><code class="language-java">  public class MainRunner {

      List&lt;Integer&gt; list = new ArrayList&lt;&gt;();

      public void add(Integer number){
          if(list.size()&gt;1){
              throw new IndexOutOfBoundsException(&quot;요소를 3개 이상 설정 할 수 없습니다.&quot;);
          }
          list.add(number);
      }

      public static void main(String[] args) {
          MainRunner mainRunner = new MainRunner();
          mainRunner.add(1);
          mainRunner.add(2);
          mainRunner.add(3);
      }
  }</code></pre>
<ul>
<li><h4 id="concurrentmodificationexception">ConcurrentModificationException</h4>
<p>단일 스레드에서 사용하려고 설계한 객체를 여러 스레드가 동시에 수정하려 할 때 사용함.</p>
</li>
</ul>
<ul>
<li><h4 id="unsupportedoperationexception">UnsupportedOperationException</h4>
<p>클라이언트가 요청한 동작을 대상 객체가 지원하지 않을 때 던진다. 대부분 객체는 자신이 정의한 메서드를 모두 지원하니 흔히 쓰이는 예외는 아니다.</p>
</li>
</ul>
<h1 id="아이템-73---추상화-수준에-맞는-예외를-던져라">아이템 73 - 추상화 수준에 맞는 예외를 던져라</h1>
<h3 id="예외-변역이란">예외 변역이란?</h3>
<ul>
<li><p>상위 계층에서 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던지는것</p>
</li>
<li><pre><code class="language-java">try {
  ...
} catch (LowerLevelException e) {
  throw new HigherLevelException(...);
}</code></pre>
</li>
<li><pre><code class="language-java">/**
* ...
* @throws IndexOutOfBoundsException ...
*/
public E get(int index) {
    try {
        return listIterator(index).next();
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException(&quot;Index: &quot; + index);
    }
}</code></pre>
</li>
</ul>
<h3 id="예외-번역을-하지-않으면">예외 번역을 하지 않으면?</h3>
<ul>
<li>수행하려는 일과 관련없어 보이는 예외가 튀어나와 프로그래머를 당황시킨다</li>
<li>내부 구현 방식을 드러내어 윗 레벨 API를 오염시킨다</li>
<li>다음 릴리즈에서 구현 방식을 바꾸면 다른 예외가 튀어나와 기존 클라이언트 프로그램을 깨지게 할 수 있다</li>
</ul>
<p>예외 연쇄를 사용해서 번역할 수도 있다.</p>
<ul>
<li><p>예외 연쇄 : 문제의 근본 원인인 저수준 예외를 고수준 예외에 실어 보내는 방식</p>
</li>
<li><p>Throwable.getCause()를 통해 언제든 저수준 예외를 꺼내볼 수 있다</p>
</li>
<li><p>저수준 예외가 디버깅에 도움을 줄 수 있다</p>
</li>
<li><pre><code class="language-java">try {
  ...
} catch (LowerLevelException cause) {
  throw new HigherLevelException(cause);
}</code></pre>
<pre><code class="language-java">class HigherLevelException extends Exception {
  HigherLevelException(Throwable cause) {
    super(cause);
  }
}</code></pre>
</li>
</ul>
<p>하지만 예외 번역을 남용해서는 안된다.</p>
<ul>
<li><p>가능하다면 저수준 메서드가 반드시 성공하도록 하여 아래 계층에서는 예외가 발생하지 않도록 하는 것이 최선이다</p>
<ul>
<li><p>어떻게? 상위 계층 메서드의 매개변수 값을 하위 계층 메서드로 건네기 전에 미리 검사해서 예외가 발생할 만한 매개변수를 하위 계층 메서드로 전달되지 않게끔 해서.</p>
</li>
<li><pre><code class="language-java">public void function1(...) {
  //미리 검사
  function2(...);
}</code></pre>
</li>
</ul>
</li>
<li><p>하위 계층에서의 예외를 피할 수 없다면?</p>
<ul>
<li><p>상위 계층에서 조용히 처리하여 예외를 API 클라이언트에까지 전파하지 않고 로그를 남길 수도 있다</p>
</li>
<li><p>이렇게 하면 클라이언트 코드에 문제를 전파하지 않음 + 로그분석을 통해 조치를 취할 수 있음</p>
</li>
<li><pre><code class="language-java">public void function1(...) {
  ...
  try {
    function2(...);
  } catch(...) {
    //로그 기록
  }
}</code></pre>
</li>
</ul>
</li>
</ul>
<h1 id="아이템-74---메서드가-던지는-모든-예외를-문서화하라">아이템 74 - 메서드가 던지는 모든 예외를 문서화하라</h1>
<p><strong>체크 예외는 항상 개별적으로 선언하라, 또한 어떠한 조건에서 예외가 발생하는지 기재하라</strong> </p>
<ul>
<li>javaDoc의 <code>@throws</code> 태그를 달아서 예외가 발생하는 조건을 설명하면 된다.</li>
<li>여러 예외를 하나의 슈퍼 클래스 예외로 선언하지 마라. (<code>Exception</code>, <code>Throwable</code> 이 가장 대표적임)</li>
<li>단 하나의 예외 사항이 있다면, main 함수에서 던지는 <code>Exception</code> 이나 <code>Throwable</code> 이 있다.</li>
</ul>
<p>언체크 예외는 다룰 수가 없다. 어떻게 문서화 해야할까?</p>
<ul>
<li>잘 문서화된 언체크 예외는 사전조건으로 설명하라.(아이템 56번)</li>
<li>아이템 56번에 따르면, 모든 public 메서드는 사전조건(precondtions)를 설명해야 한다.</li>
<li>특히, 인터페이스의 메서드가 던질 수 있는 언체크 예외를 문서화하는 것이 중요하다.</li>
</ul>
<p><strong>Javadoc의 @throws 태그를 사용하여 체크 예외에 대한 설명을 하라, 다만 언체크 예외에 throws 키워드를 사용하면 안된다.</strong></p>
<ul>
<li>만약 언체크 예외를 Javadoc의 <code>@throws</code> 태그로 명시하였다면, 예외가 체크되지 않았다는 강력한 시각적 암시를 나타낸다.</li>
</ul>
<p><strong>하나의 클래스의 여러 메서드가 던지는 예외가 같은 이유로 설명된다면, 클래스 수준의 문서화 주석을 남겨라</strong></p>
<ul>
<li>가장 대표적인 예는 <code>NullpointerException</code> 일 것이다. </li>
<li>예를 들어, &quot;이 클래스의 모든 메서드는 NullPointerException을 던질 수 있음&quot; 이라고 설명하면 된다.</li>
</ul>
<h1 id="아이템-75---예외의-상세-메시지에-실패-관련-정보를-담으라">아이템 75 - 예외의 상세 메시지에 실패 관련 정보를 담으라</h1>
<ul>
<li>실패 순간을 정확히 포착하기위해선 발생한 예외에 관여된 모든 매개변수와 필드의 값을 실패 메세지에 담아야 한다.</li>
</ul>
<p>예시를 살펴보자.</p>
<h2 id="잘못된-예외-메세지">잘못된 예외 메세지</h2>
<pre><code class="language-java">public static void main(String[] args) {
    Member member = Member.create(&quot;kjj&quot;, &quot;재준&quot;, 99);
}</code></pre>
<p>누군가 만든 <code>Member.create()</code> 호출하여 새로운 회원을 생성하고자 했을때 </p>
<p><img src="https://velog.velcdn.com/images/kkimdy_12/post/192b30f4-5b69-4c44-ac5e-1dc336f377ca/image.png" alt=""></p>
<p>만약 다음과 같은  메세지를 만나게 된다면 해당 메서드를 사용한 클라이언트는 왜 오류가나는지 전혀 알수없다.</p>
<p>결국에는 소스코드를 타고들어가서 예외를 발생시키는 원인을 분석하게된다.</p>
<p>이러한 행동은 결국 나의 리소스를 낭비하게 됩니다.</p>
<h2 id="잘-설계된-예외-메세지">잘 설계된 예외 메세지</h2>
<pre><code class="language-java">public static void main(String[] args) {
    Member member = Member.create(&quot;kjj&quot;, &quot;재준&quot;, 99);
}</code></pre>
<p><code>Member.create()</code> 을 호출했을때 예외가 발생한다면</p>
<p><img src="https://velog.velcdn.com/images/kkimdy_12/post/41185990-764c-4e0a-b81b-302c36fdd53b/image.png" alt=""></p>
<p>이처럼 <strong>메세지, 대상필드, 입력값</strong> 등을 포함하여 예외를 발생시킨다면 다음과 같은 장점이 존재합니다.</p>
<ol>
<li>왜 예외가 발생하는지 코드를 살펴보지 않아도 알 수 있음</li>
<li>비지니스 로직 파악이 쉬움(객체의 불변식을 한눈에 볼 수 있음)</li>
</ol>
<h1 id="아이템-76---가능한-한-실패-원자적으로-만들라">아이템 76 - 가능한 한 실패 원자적으로 만들라</h1>
<h3 id="실패-원자적-failure-atomic인-메서드">실패 원자적 (failure-atomic)인 메서드</h3>
<ul>
<li><code>호출된 메서드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지해야 하는 메서드</code><h4 id="어떻게-실패-원자적인-메서드를-만들까">어떻게 실패 원자적인 메서드를 만들까?</h4>
<h5 id="1-불변-객체로-만들기">1. <code>불변 객체</code>로 만들기</h5>
</li>
<li>불변 객체는 태생적으로 실패 원자적이다. </li>
<li>메서드가 실패하면 새로운 객체가 만들어지지는 않을 수 있으나 기존 객체가 불완전한 상태에 빠지는 일은 결코 없다. 불변 객체의 상태는 생성 시점에 고정되어 절대 변하지 않기 때문이다!</li>
</ul>
<h5 id="2-가변-객체의-메서드의-경우-작업-수행-전-매개변수의-유효성을-검사하기">2. 가변 객체의 메서드의 경우 작업 수행 전 매개변수의 <code>유효성을 검사</code>하기</h5>
<ul>
<li><p>객체의 내부 상태를 변경하기 전에 잠재적 예외의 가능성 대부분을 걸러낼 수 있는 방법이다.</p>
<pre><code class="language-java">public Object pop() {
  if (size == 0)
      throw new EmptyStackException();
  Object result = elements[--size];
  elements[size] = null; // 다 쓴 참조 해제
  return result;
}</code></pre>
</li>
<li><p><code>실패 가능성이 있는 모든 코드를, 객체의 상태를 바꾸는 코드보다 앞에 배치하기</code></p>
<ul>
<li>계산을 수행해보기 전에는 인수의 유효성을 검사해볼 수 없을 때 앞의 방식에서 덧붙여 쓸 수 있는 기법</li>
</ul>
</li>
</ul>
<h5 id="3-객체의-임시-복사본에서-작업을-수행한-다음-작업이-성공적으로-완료되면-원래-객체와-교체하는-것">3. 객체의 <code>임시 복사본에서 작업을 수행</code>한 다음, 작업이 성공적으로 완료되면 <code>원래 객체와 교체</code>하는 것</h5>
<ul>
<li>데이터를 임시 자료구조에 저장해 작업하는 게 더 빠를 때 적용하기 좋은 방식<ul>
<li>예를 들어, 어떤 정렬 메서드에서는 정렬을 수행하기 전에 입력 리스트의 원소들을 배열로 옮겨 담는다. 배열을 사용하면 정렬 알고리즘의 반복문에서 원소들에 훨씬 빠르게 접근할 수 있기 때문이다.</li>
<li>물론, 정렬에 실패하더라도 입력 리스트는 변하지 않는 효과를 덤으로 얻게 된다.</li>
</ul>
</li>
</ul>
<h5 id="4-작업-도중-발생하는-실패를-가로채는-복구-코드를-작성하여-작업-전-상태로-되돌리는-방법">4. 작업 도중 발생하는 <code>실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 되돌리는 방법</code></h5>
<ul>
<li>주로 (디스크 기반의) 내구성(durability)을 보장해야 하는 자료구조에 쓰이는데, 자주 쓰이는 방법은 아니다.</li>
</ul>
<h4 id="실패-원자성은-항상-달성할-수-있을까">실패 원자성은 항상 달성할 수 있을까?</h4>
<p>실패 원자성은 일반적으로 권장되는 덕목이지만 항상 달성할 수 있는 것은 아니다. 예를 들어 <code>두 스레드가 동기화 없이 같은 객체를 동시에 수정한다면 그 객체의 일관성이 깨질 수 있다.</code> </p>
<p>따라서 ConcurrentModificationException을 잡아냈다고 해서 그 객체가 여전히 쓸 수 있는 상태라고 가정해서는 안 된다. </p>
<p>한편, Error는 복구할 수 없으므로 AssertionError에 대해서는 실패 원자적으로 만들려는 시도조차 할 필요가 없다.</p>
<h4 id="실패-원자성을-항상-보장해야-할까">실패 원자성을 항상 보장해야 할까?</h4>
<p>실패 원자적으로 만들 수 있더라도 항상 그리 해야 하는건 아니다. 실패 원자성을 달성하기 위한 비용이나 복잡도가 아주 큰 연산도 있기 때문이다. 그래도 문제가 무엇인지 알고 나면 실패 원자성을 공짜로 얻을 수 있는 경우가 더 많다.</p>
<p><code>메서드 명세에 기술한 예외라면 설혹 예외가 발생하더라도 객체의 상태는 메서드 호출 전과 똑같이 유지돼야 하는 것이 기본 규칙</code>이다. 이 규칙을 지키지 못한다면 실패 시의 객체 상태를 API 설명에 명시해야 한다. 이것이 이상적이나, 아쉽게도 지금의 API 문서 상당 부분이 잘 지키지 않고 있다.</p>
<h1 id="아이템-77---예외를-무시하지-말라">아이템 77 - 예외를 무시하지 말라</h1>
<h3 id="예외를-무시하지-말라">예외를 무시하지 말라</h3>
<p>API 설계자가 메서드 선언에 예외를 명시하는 까닭은 그 메서드를 사용할 때 적절한 조치를 취해달라고 말하는 것
→ 예외를 무시하는 것은 화재 경보를 무시하는 수준을 넘어 아예 꺼버리는 것고 같은 것이다
→ 예외를 절!대! 무시하지 말자 = try-catch의 catch 블럭을 비워두지 말자!</p>
<h3 id="예외를-무시하는-하면">예외를 무시하는 하면?</h3>
<p>예외를 무시하면 그 프로그램은 오류를 내재한 채 동작하게 된다
어느 순간 문제의 원인과 관계 없는 곳에서 프로그램이 죽을 수도 있다</p>
<h3 id="예외를-무시할-수-있는-경우">예외를 무시할 수 있는 경우</h3>
<p>FileInputStream은 읽기 전용 스트림이고, 이를 닫을 때 예외가 발생하면 어떤 조치를 해줄 필요가 없다
이런 경우 catch 블록안에 주석으로 이유를 남기고, 예외 변수를 ignored로 표기한다</p>
<pre><code class="language-java">Future&lt;Integer&gt; f = exec.submit(planarMap::chromaticNumber);
int numColors = 4;  // 기본값
try {
    numColors = f.get(1L, TimeUnit.SECONDS);
} catch(TimeoutException | ExecutionException ignored) {
    // 기본값을 사용한다(색상 수를 최소화하면 좋지만, 필수는 아니다)
}</code></pre>
<h3 id="정리">정리</h3>
<p>검사 예외든, 비검사 예외든 예외를 무시하지 말자!
예외를 적절히 처리해서 오류를 피하거나, 최소한 무시하지 않고 바깥으로 전파되게라도 두어 디버깅 정보를 남긴 채 프로그램이 신속히 중단되게 하라.</p>
<hr>
<p><a href="https://github.com/Meet-Coder-Study/book-effective-java">https://github.com/Meet-Coder-Study/book-effective-java</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Garbage Collection]]></title>
            <link>https://velog.io/@kkimdy_12/Garbage-Collection</link>
            <guid>https://velog.io/@kkimdy_12/Garbage-Collection</guid>
            <pubDate>Thu, 18 Dec 2025 10:08:27 GMT</pubDate>
            <description><![CDATA[<p><strong>Garbage Collection</strong></p>
<p><em>Assembled by GimunLee (2019-10-28)</em></p>
<p><strong>Goal</strong></p>
<ul>
<li>Garbage Collection의 역할에 대해 설명할 수 있다.</li>
<li>Garbage Collection의 메모리 해제 과정을 3단계로 설명할 수 있다.</li>
<li>Generational Gabage Collection에 대해 설명할 수 있다.</li>
<li>Generational Garbage Collection 과정에 대해 설명할 수 있다.</li>
<li>Minor GC와 Major GC의 차이점에 대해 설명할 수 있다.</li>
</ul>
<p><strong>Abstract</strong></p>
<p>C/C++ 프로그래밍을 할 때 메모리 누수(Memory Leak)를 막기 위해 객체를 생성한 후 사용자하지 않는 객체의 메모리를 프로그래머가 직접 해제 해주어야 했습니다. 하지만, JAVA에서는 JVM(Java Virtual Machine)이 구성된 JRE(Java Runtime Environment)가 제공되며, 그 구성 요소 중 하나인 Garbage Collection(이하 GC)이 자동으로 사용하지 않는 객체를 파괴합니다.</p>
<p>GC에 대해서 알아보기 전에 &#39;stop-the-world&#39;라는 용어를 알아야합니다. &#39;stop-the-world&#39;란, GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것입니다. 어떤 GC 알고리즘을 사용하더라도 &#39;stop-the-world&#39;는 발생하게 되는데, 대개의 경우 GC 튜닝은 이 &#39;stop-the-world&#39; 시간을 줄이는 것이라고 합니다.</p>
<p>GC를 해도 더이상 사용 가능한 메모리 영역이 없는데 계속 메모리를 할당하려고 하면, OutOfMemoryError가 발생하여 WAS가 다운될 수도 있습니다. 행(Hang) 즉, 서버가 요청을 처리 못하고 있는 상태가 됩니다.</p>
<p>따라서 규모 있는 JAVA 애플리케이션을 효율적으로 개발하기 위해서는 GC에 대해 잘 알아야한다고 합니다. 이번에는 GC에 대해 간단하게 알아보겠습니다.</p>
<p><strong>Garbage Collection</strong></p>
<p>C/C++ 언어와 달리 자바는 개발자가 명시적으로 객체를 해제할 필요가 없습니다. 자바 언어의 큰 장점이기도 합니다. 사용하지 않는 객체는 메모리에서 삭제하는 작업을 GC라고 부르며 JVM에서 GC를 수행합니다.</p>
<p>기본적으로 JVM의 메모리는 총 5가지 영역(class, stack, heap, native method, PC)으로 나뉘는데, GC는 힙 메모리만 다룹니다.</p>
<p>일반적으로 다음과 같은 경우에 GC의 대상이 됩니다.</p>
<ol>
<li>객체가 NULL인 경우 (ex. String str = null)</li>
<li>블럭 실행 종료 후, 블럭 안에서 생성된 객체</li>
<li>부모 객체가 NULL인 경우, 포함하는 자식 객체</li>
</ol>
<p>GC는 <code>Weak Generational Hypothesis</code> 에 기반합니다. 우선 GC의 메모리 해제 과정에 대해 살펴보겠습니다.</p>
<p><strong>GC의 메모리 해제 과정</strong></p>
<ol>
<li><p>Marking</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-001.png" alt=""></p>
<ul>
<li>프로세스는 마킹을 호출합니다. 이것은 GC가 메모리가 사용되는지 아닌지를 찾아냅니다. 참조되는 객체는 파란색으로, 참조되지 않는 객체는 주황색으로 보여집니다. 모든 오브젝트는 마킹 단계에서 결정을 위해 스캔되어집니다. 모든 오브젝트를 스캔하기 때문에 매우 많은 시간을 소모하게 됩니다.</li>
</ul>
</li>
<li><p>Normal Deletion</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-002.png" alt=""></p>
<ul>
<li>참조되지 않는 객체를 제거하고, 메모리를 반환합니다. 메모리 Allocator는 반환되어 비어진 블럭의 참조 위치를 저장해 두었다고 새로운 오브젝트가 선언되면 할당되도록 합니다.</li>
</ul>
</li>
<li><p>Compacting</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-003.png" alt=""></p>
<ul>
<li>퍼포먼스를 향상시키기 위해, 참조되지 않는 객체를 제거하고 또한 남은 참조되어지는 객체들을 묶습니다. 이들을 묶음으로서 공간이 생기므로 새로운 메모리 할당 시에 더 쉽고 빠르게 진행 할 수 있습니다.</li>
</ul>
</li>
</ol>
<p><strong>Generational Garbage Collection 배경</strong></p>
<p>위와 같이 모든 객체를 <code>Mark &amp; Compact</code> 하는 JVM은 비효율적입니다. 다음과 같은 그래프를 보시겠습니다.</p>
<p><img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-004.png" alt=""></p>
<p>Y축은 할당된 바이트의 수이고 X축은 바이트가 할당될 때의 시간입니다. 보시다시피 시간이 갈수록 적은 객체만이 남습니다. 위와 같은 그래프에 기반한 것이 Weak Generational Hypothesis 입니다.</p>
<p><strong>Weak Generational Hypothesis</strong></p>
<p>신규로 생성한 객체의 대부분은 금방 사용하지 않는 상태가 되고, 오래된 객체에서 신규 객체로의 참조는 매우 적게 존재한다는 가설입니다.</p>
<p>이 가설에 기반하여 자바는 Young 영역과 Old 영역으로 메모리를 분할하고, 신규로 생성되는 객체는 Young 영역에 보관하고, 오랫동안 살아남은 객체는 Old 영역에 보관합니다.</p>
<p><strong>Generational Gabage Collection</strong></p>
<p><img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-006.png" alt=""></p>
<ol>
<li><p>Young 영역(Yong Generation 영역)</p>
<p> 새롭게 생성한 객체의 대부분이 여기에 위치합니다. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라집니다. 이 영역에서 객체가 사라질때 <strong>Minor GC</strong> 가 발생한다고 말합니다.</p>
</li>
<li><p>Old 영역(Old Generation 영역)</p>
<p> 접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사됩니다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생합니다. 이 영역에서 객체가 사라질 때 <strong>Major GC(혹은 Full GC)</strong> 가 발생한다고 말합니다.</p>
</li>
<li><p>Permanet 영역</p>
<p> Method Area라고도 합니다. JVM이 클래스들과 메소드들을 설명하기 위해 필요한 메타데이터들을 포함하고 있습니다. JDK8부터는 PermGen은 Metaspace로 교체됩니다.</p>
</li>
</ol>
<p><strong>Generational Garbage Collection 과정</strong></p>
<ol>
<li><p>어떠한 새로운 객체가 들어오면 Eden Space에 할당합니다.</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-007.png" alt=""></p>
<hr>
</li>
<li><p>Eden space가 가득차게 되면, minor garbage collection이 시작됩니다.</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-008.png" alt=""></p>
<hr>
</li>
<li><p>참조되는 객체들은 첫 번째 survivor(S0)로 이동되어지고, 비 참조 객체는 Eden space가 clear 될 때 반환됩니다.</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-009.png" alt=""></p>
<hr>
</li>
<li><p>다음 minor GC 때, Eden space에서는 같은 일이 일어납니다. 비 참조 객체는 삭제되고 참조 객체는 survivor space로 이동하는 것 입니다. 그러나 이 케이스에서 참조 객체는 두 번째 survivor space로 이동하게 됩니다. 게다가 최근 minor GC에서 첫 번째 survivor space로 이동된 객체들도 age가 증가하고 S1 공간으로 이동하게 됩니다. 한번 모든 surviving 객체들이 S1으로 이동하게 되면 S0와 Eden 공간은 Clear 됩니다. 주의해야할 점은 이제 우리는 다른 aged 객체들을 서바이버 공간에 가지게 되었다는 것입니다.</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-010.png" alt=""></p>
<hr>
</li>
<li><p>다음 minor GC 때, 같은 과정이 반복 됩니다. 그러나 이 번엔 survivor space들은 switch 됩니다. 참조되는 객체들은 S0로 이동합니다. 살아남은 객체들은 aged되죠. 그리고 Eden과 S1 공간은 Clear 됩니다.</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-011.png" alt=""></p>
<hr>
</li>
<li><p>아래 그램은 promotion을 보여줍니다. minor GC 후 aged 오브젝트들이 일정한 age threshold(문지방)을 넘게 되면 그들은 young generation에서 old로 promotion 되어집니다. 여기서는 8을 예로 들었습니다.</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-012.png" alt=""></p>
<hr>
</li>
<li><p>minor GC가 계속되고 계속해서 객체들이 Old Generation으로 이동됩니다.</p>
<p> <img src="https://github.com/GimunLee/tech-refrigerator/raw/master/Language/JAVA/resources/java-gc-013.png" alt=""></p>
<hr>
</li>
<li><p>아래 그림은 전 과정을 보여주고 있습니다. 결국 major GC가 old Generation에 시행되고, old Generation은 Clear 되고, 공간이 Compact 되어집니다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 자바 9장) 일반적인 프로그래밍 원칙]]></title>
            <link>https://velog.io/@kkimdy_12/9%EC%9E%A5-%EC%9D%BC%EB%B0%98%EC%A0%81%EC%9D%B8-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@kkimdy_12/9%EC%9E%A5-%EC%9D%BC%EB%B0%98%EC%A0%81%EC%9D%B8-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Wed, 17 Dec 2025 16:59:46 GMT</pubDate>
            <description><![CDATA[<h1 id="아이템-57---지역변수의-범위를-최소화하라">아이템 57 - 지역변수의 범위를 최소화하라</h1>
<p>지역변수의 유효 범위를 최소로 줄이면 코드의 가독성과 유지보수성이 높아지고, 오류가 발생할 가능성은 낮아진다.
지역변수의 범위를 줄이는 가장 강력한 기법은 <strong>‘가장 처음 쓰일 때 선언하기’</strong>다. 거의 모든 지역변수는 선언과 동시에 초기화하는 것이 바람직하다.</p>
<ul>
<li><p>반복문은 변수 범위를 자연스럽게 최소화해주는 대표적인 구조다.
  반복 변수의 값을 반복문이 끝난 뒤에 사용할 필요가 없다면, while문보다는 for문을 사용하는 편이 낫다.</p>
</li>
<li><p>for문이 유리한 이유는 반복 변수의 유효 범위가 for문 블록 안으로 제한되기 때문이다.
이 덕분에 같은 이름의 변수를 여러 반복문에서 사용하더라도 서로 영향을 주지 않으며, 코드의 안정성이 높아진다.</p>
<h1 id="아이템-58---전통적인-for문보다는-for-each문을-사용하라">아이템 58 - 전통적인 for문보다는 for-each문을 사용하라</h1>
<p>반복자와 인덱스 변수는 모두 코드를 지저분하게 할 뿐 우리에게 진짜 필요한 건 원소들뿐이다. 혹시라도 잘못된 변수를 사용했을 때 컴파일러가 잡아준다는 보장도 없고... 등 문제가 많은데 이걸 for-each문을 사용하면 모두 해결이 된다!</p>
<pre><code class="language-java">for (Element e : elements) {
... // e로 무언가를 한다.
}</code></pre>
</li>
<li><p>for-each문은 중첩 순회할 때 이점이 더 커진다. 정말 운이 나빠서 바깥 컬렉션의 크기가 안쪽 컬렉션 크기의 배수라면 이 반복문은 예외를 던지지 않고 종료한다. </p>
</li>
<li><p>for-each문은 컬렉션과 배열은 물론 Iterable 인터페이스를 구현한 객체라면 무엇이든 순회할 수 있다!</p>
</li>
</ul>
<p>하지만 for-each문을 사용할 수 없는 상황 세 가지 존재한다!</p>
<p>1) 파괴적인 필터링 - 컬렉션을 순회하면서 선택된 원소를 제거해야 한다면 반복자의 remove 메서드를 호출해야 한다. 자바 8부터는 Collection의 removeIf 메서드를 사용해 컬렉션을 명시적으로 순회하는 일을 피할 수 있다. 
2) 변형 - 리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용해야한다. 
3) 병렬 반복 - 여러 컬렉션을 병렬로 순회해야 한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 한다.</p>
<h1 id="아이템-59---라이브러리를-익히고-사용하라">아이템 59 - 라이브러리를 익히고 사용하라</h1>
<pre><code class="language-java">static Random rnd = new Random();

static int random(int n) {
  return Math.abs(rnd.nextInt()) % n;
}</code></pre>
<p><strong>문제점</strong></p>
<p>1) n이 그리 크지 않은 2의 제곱수라면 얼마 지나지 않아 같은 수열이 반복된다. 
2) n이 2의 제곱수가 아니라면 몇몇 숫자가 평균적으로 더 자주 반환된다. 
3) 지정한 범위 &#39;바깥&#39;의 수가 종종 튀어나올 수 있다. rnd.nextInt()가 반환한 값을 Math.abs를 이용해 음수가 아닌 정수로 매핑하기 때문이다. </p>
<ul>
<li>이 문제점을 해결하려면 의사난수 생성기, 정수론, 2의 보수 계산 등에 조예가 깊어야한다. 다행히 Random.nextInt(int)가 다 해결해준다. </li>
<li>자바 7부터는 Random을 더 이상 사용하지 않는 게 좋다. ThreadLocalRandom으로 대체하면 대부분 잘 작동한다. 더 고품질의 무작위 수 생성 &amp; 속도도 빠름</li>
</ul>
<p>*<em>표준 라이브러리를 사용하면 좋은 점 *</em></p>
<ul>
<li>그 코드를 작성한 전문가의 지식과 여러분보다 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있다.</li>
<li>핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비하지 않아도 된다. </li>
<li>따로 노력하지 않아도 성능이 지속해서 개선된다. </li>
<li>기능이 점점 많아진다. </li>
<li>자연스럽게 다른 개발자들이 더 읽기 좋고, 유지보수하기 좋고, 재활용하기 쉬운 코드가 된다. </li>
</ul>
<blockquote>
<p><code>java.util.concurrent</code>를 알아두면.. 동시성 기능에 좋다?
하여튼간 자바 표준 라이브러리를 잘 이용해보자. 근데 원하는 기능이 거기 없다면 다음 선택지는 고품질의 서드파티 라이브러리에서 찾으면 되고.... (ex. 구글의 구아바 라이브러리) 그것도 없으면 직접 구현해야한다. </p>
</blockquote>
<h1 id="아이템-60---정확한-답이-필요하다면-float와-double은-피하라">아이템 60 - 정확한 답이 필요하다면 float와 double은 피하라</h1>
<p>float와 double은 넓은 범위의 수를 빠르게 정밀한 &#39;근사치&#39;로 계산하도록 설계되어 정확한 결과가 필요할 때는 사용하면 안 된다. 특히 금융 관련 계산과는 맞지 않는다!!</p>
<p>그래서 금융 계산에는 BigDecimal, int, long을 사용해야한다. 
하지만 BigDecimal에는 단점이 두가지가 있다. 기본 타입보다 쓰기가 훨씬 불편하고 훨씬 느리다. 
그래서 대안으로 int/long을 쓸 수도 있다. 
이 경우에는 다룰 수 있는 값의 크기가 제한되고 소수점을 직접 관리해야 한다. </p>
<h1 id="아이템-61---박싱된-기본-타입보다는-기본-타입을-사용하라">아이템 61 - 박싱된 기본 타입보다는 기본 타입을 사용하라</h1>
<p>자바의 데이터 타입은 크게 두 가지로 나눌 수 있다. </p>
<p>1) int, double, boolean 같은 기본 타입
2) String, List 같은 참조 타입
int, double, boolean에 대응하는 박싱된 기본 타입은 Integer, Double, Boolean이다.</p>
<p>기본 타입과 박싱된 기본 타입의 주된 차이</p>
<ol>
<li>기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성이란 속성을 갖는다. </li>
<li>기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 유효하지 않은 값, 즉 null을 가질 수 있다. </li>
<li>기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.</li>
</ol>
<p>박싱된 기본 타입 사용 시 발생할 수 있는 문제</p>
<ul>
<li>박싱된 기본 타입에 == 연산자를 사용하면 값 비교가 아니라 객체 참조 비교가 이루어진다.
이로 인해 의도하지 않은 오류가 발생할 수 있다.</li>
</ul>
<p>해결 방법</p>
<ul>
<li>비교가 필요하다면 Comparator나 naturalOrder()를 사용한다.</li>
<li>박싱된 타입의 값을 기본 타입 변수에 담아 비교 연산을 수행한다.</li>
</ul>
<p>기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 대부분 자동 언박싱이 일어난다.
하지만 실수로 지역변수를 박싱된 타입으로 선언하면, 눈치채지 못한 채 박싱·언박싱이 반복되어 성능 저하가 발생할 수 있다.</p>
<p>박싱된 기본 타입을 사용해야 하는 경우</p>
<ul>
<li>컬렉션의 원소, 키, 값</li>
<li>매개변수화 타입이나 매개변수화 메서드 (<code>ThreadLocal&lt;Integer&gt;</code> 등)</li>
<li>리플렉션을 통한 메서드 호출</li>
</ul>
<h1 id="아이템-62---다른-타입이-적절하다면-문자열-사용을-피하라">아이템 62 - 다른 타입이 적절하다면 문자열 사용을 피하라</h1>
<p>문자열은 만능 타입처럼 보이지만, 다음과 같은 경우에는 적절하지 않다.</p>
<ol>
<li>다른 값 타입을 문자열로 대신하는 경우
수치 데이터는 숫자 타입으로, 예/아니오 값은 boolean이나 열거 타입으로 표현하는 것이 좋다.</li>
<li>열거 타입을 문자열로 표현하는 경우
열거 타입을 사용하면 가독성과 타입 안정성이 크게 향상된다.</li>
<li>혼합 타입을 문자열로 표현하는 경우<pre><code>String compoundKey = className + &quot;#&quot; + i.next();</code></pre></li>
</ol>
<p>이 방식은 문자열 파싱이 필요해 번거롭고 오류 가능성이 크며,
equals, toString, compareTo 같은 메서드를 제대로 활용할 수 없다.</p>
<p>전용 클래스를 만드는 편이 훨씬 낫다.</p>
<ol start="4">
<li>문자열로 권한을 표현하는 경우
스레드 구분용 키를 문자열로 사용하면 전역에서 키가 충돌해 값이 공유되는 문제가 발생할 수 있다.</li>
</ol>
<h1 id="아이템-63---문자열-연결은-느리니-주의하라">아이템 63 - 문자열 연결은 느리니 주의하라</h1>
<p>문자열 연결 연산자 +는 간편하지만, 문자열을 많이 연결하는 경우 성능 문제가 심각해진다.
문자열 n개를 +로 연결하는 시간은 n²에 비례한다.</p>
<p>성능을 고려해야 한다면 StringBuilder를 사용하자.
StringBuilder는 문자열 개수에 따라 선형적으로 성능이 증가한다.</p>
<pre><code>public String statement2() {
    StringBuilder b = new StringBuilder(numItems() * LINEWIDTH);
    for (int i = 0; i &lt; numItems(); i++)
        b.append(lineForItem(i));
    return b.toString();
}
</code></pre><p>저자의 실험 기준으로 StringBuilder를 사용한 코드가 약 6.5배 더 빨랐다고 한다.</p>
<blockquote>
<p>성능을 신경써야 한다면 많은 문자열을 연결할 때는 문자열 연결 연산(+)를 피하자.
대신 StringBuilder의 append 메서드를 사용하라.
문자 배열을 사용하거나, 문자열을 (연결하지 않고) 하나씩 처리하는 방법도 있다.</p>
</blockquote>
<h1 id="아이템-64---객체는-인터페이스를-사용해-참조하라">아이템 64 - 객체는 인터페이스를 사용해 참조하라</h1>
<p>적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라. </p>
<p><strong>인터페이스 사용 장점</strong></p>
<ul>
<li>인터페이스를 타입으로 사용하는 습관을 길러두면 프로그램이 훨씬 유연해진다.</li>
<li>나중에 구현 크래스를 교체하고자 한다면, 그저 새 클래스의 생성자(혹은 다른 정적 팩터리)를 호출해주기만 하면 된다.</li>
</ul>
<p>인터페이스 대신 클래스 타입을 사용해도 되는 경우
적합한 인터페이스가 없다면 당연히 클래스로 참조해야 한다.</p>
<ol>
<li><p>String과 BigInteger 같은 값 클래스
값 클래스를 여러 가지로 구현될 수 있다고 생각하고 설계하는 일은 거의 없다.
값 클래스는 매개변수, 변수, 필드, 반환 타입으로 사용해도 무방하다.</p>
</li>
<li><p>클래스 기반으로 작성된 프레임워크가 제공하는 객체들
이런 경우라도 특정 구현 클래스보다는 (보통은 추상 클래스인) 기반 클래스를 사용해 참조하는게 좋다. OutStream 등 java.io 패키지의 여러 클래스가 이 부류에 속한다.</p>
</li>
<li><p>인터페이스에는 없는 특별한 메서드를 제공하는 클래스
예시) PriorityQueue 클래스는 Queue 인터페이스에는 없는 comparator 메서드를 제공한다.</p>
</li>
</ol>
<p>이러한 클래스 타입을 직접 사용하는 경우, 클래스 타입에서 제공하는 추가 메서드를 꼭 사용해야 하는 경우로 최소화해야 하며, 절대 남발하지 말아야 한다.</p>
<h1 id="아이템-65---리플렉션보다는-인터페이스를-사용하라">아이템 65 - 리플렉션보다는 인터페이스를 사용하라</h1>
<ol>
<li>리플렉션이란?<pre><code>java.lang.reflect API</code></pre></li>
</ol>
<ul>
<li>실행 중에 임의의 클래스에 접근할 수 있다.</li>
<li>클래스, 인터페이스, 메서드를 찾을 수 있다.</li>
<li>객체를 생성하거나 변수를 변경할 수 있고 메서드를 호출 할 수 있다.</li>
<li>나아가 Constructor, Method, Field 인스턴스를 이용해 실제 생성자, 메서드, 필드를 조작할 수 있다.</li>
</ul>
<ol start="2">
<li>리플렉션 단점</li>
</ol>
<ul>
<li>컴파일타임 타입 검사가 주는 이점을 하나도 누릴 수 없다.</li>
<li>예외 검사도 마찬가지다. 프로그램이 리플렉션 기능을 써서 존재하지 않는 혹은 접근할 수 없는 메서드를 호출하려고 하면 런타임 오류가 발생한다.</li>
<li>리플렉션을 이용하면 코드가 지저분하고 장황해진다.</li>
<li>성능이 떨어진다.</li>
<li>리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다.</li>
</ul>
<ol start="3">
<li>정리
리플렉션은 런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다. 하지만 단점도 많기 때문에 되도록 객체 생성에만 사용하고, 적절한 인터페이스나 상위 클래스로 형변환해 사용하자.</li>
</ol>
<h1 id="아이템-66---네이티브-메서드는-신중히-사용하라">아이템 66 - 네이티브 메서드는 신중히 사용하라</h1>
<p><strong>네이티브 메서드</strong></p>
<ul>
<li>자바 네이티브 인터페이스(Java Native Interface, JNI)는 자바 프로그램이 네이티브 메서드 를 호출하는 기술을 말한다.</li>
<li>네이티브 메서드 란 C나 C++ 같은 네이티브 프로그래밍 언어로 작성한 메서드를 말한다.</li>
</ul>
<p>네이티브 메서드는 주로 다음과 같은 용도로 사용된다.</p>
<ol>
<li>플랫폼 특화 기능 사용</li>
<li>네이티브 코드로 작성된 기존 라이브러리 (레거시 라이브러리가 그 예다)</li>
<li>성능 개선을 목적으로 성능에 결정적인 영향을 주는 영역만 따로 네이티브 언어로 작성</li>
</ol>
<p>그러나 네이티브 메서드를 성능을 개선할 목적으로 사용하는 것은 권장하지 않는다.</p>
<p><strong>네이티브 메서드의 단점</strong></p>
<ol>
<li>네이티브 언어가 안전하지 않으므로 네이티브 메서드를 사용하는 애플리케이션도 메모리 훼손 오류로부터 더 이상 안전하지 않다.</li>
<li>네이티브 언어는 자바보다 플랫폼을 많이 타기 때문에 이식성이 낮다.</li>
<li>디버깅이 더 어렵다.</li>
<li>주의하지 않으면 속도가 오히려 느려질 수도 있다.</li>
<li>가비지 컬렉터가 네이티브 메모리는 자동 회수하지 못한다. 심지어 추적조차 할 수 없다.</li>
<li>네이티브 메서드와 자바 코드 사이의 접착 코드(glue code)를 작성해야 한다. 이는 작업하기 귀찮을 뿐만 아니라 가독성도 떨어진다.</li>
</ol>
<h1 id="아이템-67---최적화는-신중히-하라">아이템 67 - 최적화는 신중히 하라</h1>
<p>모든 사람이 마음 깊이 새겨야 할 최적화 격언 세 개를 소개 한다.</p>
<blockquote>
<p>(맹목적인 어리석음을 포함해) 그 어떤 핑계보다 효율성이라는 이름 아래 행해진 컴퓨팅 죄악이 더 많다. (심지어 효율을 높이지도 못하면서) - 윌리엄 울프</p>
</blockquote>
<blockquote>
<p>(전체의 97% 정도인) 자그마한 효율성은 모두 잊자. 섣부른 최적화가 만악의 근원이다. - 도널드 크누스</p>
</blockquote>
<blockquote>
<p>최적화를 할 때는 다음 두 규칙을 따르라. 첫 번째, 하지 마라. 두 번째, (전문가 한정) 아직 하지 마라. 다시 말해, 완전히 명백하고 최적화되지 않은 해법을 찾을 때까지는 하지 마라.</p>
</blockquote>
<blockquote>
<p>M. A 잭슨
최적화는 좋은 결과보다는 해로운 결과로 이어지기 쉽고, 섣불리 진행하면 특히 더 그렇다. 빠르지도 않고 제대로 동작하지도 않으면서 수정하기는 어려운 소프트웨어를 탄생시키는 것이다.</p>
</blockquote>
<ul>
<li>빠른 프로그램보다 좋은 프로그램을 작성하라.</li>
<li>설계 단계에서 성능을 반드시 염두에 두어야 한다.<ul>
<li>성능을 제한하는 설계를 피하라.</li>
<li>API를 설계할 때 성능에 주는 영향을 고려하라.</li>
<li>&quot;최적화 시도 전후로 성능을 측정하라&quot;</li>
</ul>
</li>
</ul>
<h1 id="아이템-68---일반적으로-통용되는-명명-규칙을-따르라">아이템 68 - 일반적으로 통용되는 명명 규칙을 따르라</h1>
<p>자바 플랫폼은 명명 규칙이 잘 정립되어 있으며, 그중 많은 것이 자바 언어 명세[JLS, 6.1]에 기술되어 있다. 자바의 명명 규칙은 크게 철자 규칙과 문법 규칙, 두 범주로 나뉜다.</p>
<h3 id="1-철자-규칙-lexical-rules">1. 철자 규칙 (Lexical Rules)</h3>
<table>
<thead>
<tr>
<th>대상</th>
<th>명명 규칙</th>
<th>예시 / 설명</th>
</tr>
</thead>
<tbody><tr>
<td>패키지 / 모듈</td>
<td>점(<code>.</code>)으로 계층 구분, 모두 소문자</td>
<td><code>com.google.common</code>, <code>org.eff</code></td>
</tr>
<tr>
<td></td>
<td>조직 외부 공개용 패키지는 도메인 역순</td>
<td><code>edu.cmu</code>, <code>com.google</code></td>
</tr>
<tr>
<td></td>
<td>표준 라이브러리는 <code>java</code>, <code>javax</code>로 시작</td>
<td><code>java.util</code>, <code>javax.servlet</code></td>
</tr>
<tr>
<td></td>
<td>요소는 짧은 단어(보통 8글자 이하)</td>
<td><code>utilities</code> → <code>util</code>, <code>awt</code></td>
</tr>
<tr>
<td>클래스 / 인터페이스</td>
<td>각 단어의 첫 글자를 대문자로</td>
<td><code>Thread</code>, <code>PriorityQueue</code></td>
</tr>
<tr>
<td></td>
<td>불필요한 축약은 피함</td>
<td><code>HttpUrl</code> (권장), <code>HTTPURL</code> (비권장)</td>
</tr>
<tr>
<td>메서드 / 필드</td>
<td>첫 글자 소문자, 나머지는 클래스와 동일</td>
<td><code>remove</code>, <code>ensureCapacity</code></td>
</tr>
<tr>
<td>상수 필드</td>
<td>모두 대문자 + 밑줄</td>
<td><code>VALUES</code>, <code>NEGATIVE_INFINITY</code></td>
</tr>
<tr>
<td></td>
<td><code>static final</code> + 불변 값</td>
<td>기본 타입 또는 불변 참조</td>
</tr>
<tr>
<td>지역변수</td>
<td>메서드/필드와 유사, 약어 허용</td>
<td>문맥상 의미 유추 가능</td>
</tr>
<tr>
<td>매개변수</td>
<td>지역변수이지만 더 신중하게 작성</td>
<td>문서에 노출되기 때문</td>
</tr>
<tr>
<td>타입 매개변수</td>
<td>보통 한 글자</td>
<td><code>T</code>, <code>E</code>, <code>K</code>, <code>V</code>, <code>R</code>, <code>X</code></td>
</tr>
<tr>
<td></td>
<td>여러 개일 경우 순서 사용</td>
<td><code>T</code>, <code>U</code>, <code>V</code>, <code>T1</code>, <code>T2</code></td>
</tr>
</tbody></table>
<h3 id="2-문법-규칙-grammatical-rules">2. 문법 규칙 (Grammatical Rules)</h3>
<table>
<thead>
<tr>
<th>대상</th>
<th>규칙</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>패키지</td>
<td>별도 문법 규칙 없음</td>
<td>—</td>
</tr>
<tr>
<td>클래스 (인스턴스 생성 가능)</td>
<td>단수 명사 또는 명사구</td>
<td><code>Thread</code>, <code>PriorityQueue</code></td>
</tr>
<tr>
<td>클래스 (인스턴스 생성 불가)</td>
<td>복수형 명사</td>
<td><code>Collections</code>, <code>Collectors</code></td>
</tr>
<tr>
<td>인터페이스</td>
<td>클래스와 동일하거나 <code>-able</code>, <code>-ible</code> 형용사</td>
<td><code>Runnable</code>, <code>Iterable</code></td>
</tr>
<tr>
<td>애너테이션</td>
<td>명확한 규칙 없음</td>
<td>명사, 동사, 형용사 등</td>
</tr>
<tr>
<td>메서드 (동작 수행)</td>
<td>동사 또는 동사구</td>
<td><code>add</code>, <code>remove</code>, <code>calculateTotal</code></td>
</tr>
<tr>
<td>메서드 (boolean 반환)</td>
<td><code>is</code>, 드물게 <code>has</code>로 시작</td>
<td><code>isEmpty</code>, <code>isEnabled</code>, <code>hasNext</code></td>
</tr>
<tr>
<td>메서드 (속성 반환)</td>
<td>명사 / 명사구 또는 <code>get</code></td>
<td><code>size</code>, <code>hashCode</code>, <code>getTime</code></td>
</tr>
<tr>
<td>타입 변환 메서드</td>
<td><code>toType</code> 형태</td>
<td><code>toString</code>, <code>toArray</code></td>
</tr>
<tr>
<td>뷰 반환 메서드</td>
<td><code>asType</code> 형태</td>
<td><code>asList</code></td>
</tr>
<tr>
<td>기본 타입 값 반환</td>
<td><code>typeValue</code> 형태</td>
<td><code>intValue</code></td>
</tr>
<tr>
<td>정적 팩터리</td>
<td>관례적인 이름 사용</td>
<td><code>from</code>, <code>of</code>, <code>valueOf</code>, <code>getInstance</code></td>
</tr>
<tr>
<td>필드 (boolean)</td>
<td>접근자에서 접두어 제거한 이름</td>
<td><code>initialized</code>, <code>composite</code></td>
</tr>
<tr>
<td>필드 (그 외)</td>
<td>명사 또는 명사구</td>
<td><code>size</code>, <code>count</code></td>
</tr>
<tr>
<td>지역변수</td>
<td>필드와 유사하나 더 자유로움</td>
<td>짧고 명확하면 OK</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>명명 규칙은 단순한 스타일 문제가 아니라, 가독성·일관성·API 품질을 좌우하는 설계 요소다.</strong>
자바의 관례를 따르는 것만으로도 코드의 신뢰도가 크게 올라간다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바] BOJ_1182_부분수열의합_S2]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ1182%EB%B6%80%EB%B6%84%EC%88%98%EC%97%B4%EC%9D%98%ED%95%A9S2</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ1182%EB%B6%80%EB%B6%84%EC%88%98%EC%97%B4%EC%9D%98%ED%95%A9S2</guid>
            <pubDate>Tue, 16 Dec 2025 11:15:58 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크"><a href="https://www.acmicpc.net/problem/1182">문제 링크</a></h1>
<h1 id="접근-방법">접근 방법</h1>
<ul>
<li>깊이가 N이면 그때 합이 S인지 확인</li>
<li>현재 arr[idx]를 합한 경우도 고려해야되고 합하지 않은 경우도 고려해야 함</li>
<li>S == 0일 때, 공집합이 생기므로 정답-1 해줘야 함</li>
</ul>
<h3 id="공집합이-생기는-경우">공집합이 생기는 경우</h3>
<pre><code>idx=0, total=0
 → 선택X
idx=1, total=0
 → 선택X
idx=2, total=0
 → 선택X
idx=3, total=0
 → 선택X
idx=4, total=0
 → 선택X
idx=5 == N → total == S(0)
</code></pre><h1 id="정답-코드">정답 코드</h1>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class BOJ_1182_부분수열의합_S2 {
    static int N, S, answer;
    static int[] arr;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        S = Integer.parseInt(st.nextToken());
        arr = new int[N];
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; N; i++)
            arr[i] = Integer.parseInt(st.nextToken());

        dfs(0, 0);

        if (S == 0)
            answer--;
        System.out.println(answer);
    }

    public static void dfs(int idx, int total) {
        if (idx == N) {
            if (total == S)
                answer++;
            return;
        }

        // 현재 원소를 선택하는 경우
        dfs(idx + 1, total + arr[idx]);

        // 현재 원소를 선택하지 않는 경우
        dfs(idx + 1, total);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바] BOJ_15686_치킨배달_G5]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ15686%EC%B9%98%ED%82%A8%EB%B0%B0%EB%8B%ACG5</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ15686%EC%B9%98%ED%82%A8%EB%B0%B0%EB%8B%ACG5</guid>
            <pubDate>Mon, 15 Dec 2025 13:24:25 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크"><a href="https://www.acmicpc.net/problem/15686">문제 링크</a></h1>
<h1 id="접근-방식">접근 방식</h1>
<ul>
<li>집이랑 치킨집을 따로 <code>ArrayList</code>로 관리</li>
<li>dfs로 깊이가 M이면 치킨거리 계산해서 최소치킨거리 계산하게!</li>
<li>한번 selected에 들어가서 조합 처리됐으면 백트래킹으로 복구시켜놓기</li>
</ul>
<p>👉 <strong>치킨집 M개 조합을 전부 탐색해서 최솟값을 구하는 문제</strong></p>
<h1 id="코드-설명">코드 설명</h1>
<h3 id="조합-생성-combi">조합 생성 (<code>combi</code>)</h3>
<pre><code class="language-java">static void combi(List&lt;int[]&gt; selected, int start, int depth)</code></pre>
<ul>
<li>치킨집 중 <strong>M개를 선택하는 모든 조합 생성</strong></li>
</ul>
<p>파라미터</p>
<table>
<thead>
<tr>
<th>파라미터</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>selected</code></td>
<td>현재 선택된 치킨집 목록</td>
</tr>
<tr>
<td><code>start</code></td>
<td>다음에 고를 치킨집 시작 인덱스</td>
</tr>
<tr>
<td><code>depth</code></td>
<td>현재 선택한 개수</td>
</tr>
</tbody></table>
<h3 id="조합-로직">조합 로직</h3>
<pre><code class="language-java">for (int i = start; i &lt; chickens.size(); i++) {
    selected.add(chickens.get(i));
    combi(selected, i + 1, depth + 1);
    selected.remove(selected.size() - 1);
}</code></pre>
<ul>
<li><strong>중복 없는 조합</strong></li>
<li><code>i + 1</code>부터 탐색 → 이미 선택한 치킨집 재사용 X</li>
<li>DFS + 백트래킹 구조</li>
</ul>
<p>📌 예시
치킨집 5개 중 M=3이면
→ <code>(0,1,2), (0,1,3), (0,1,4), ...</code></p>
<h3 id="도시-치킨-거리-계산">도시 치킨 거리 계산</h3>
<pre><code class="language-java">static int getCityChickenDistance(List&lt;int[]&gt; selected)</code></pre>
<pre><code class="language-java">for (int[] home : homes) {
    int minDist = Integer.MAX_VALUE;
    for (int[] chicken : selected) {
        int dist = Math.abs(home[0]- chicken[0]) 
                 + Math.abs(home[1] - chicken[1]);
        minDist = Math.min(minDist, dist);
    }
    sum += minDist;
}</code></pre>
<ul>
<li><p>각 집마다:</p>
<ul>
<li>선택된 치킨집들과의 거리 중 <strong>최솟값</strong> 계산</li>
</ul>
</li>
<li><p>모든 집의 최소 거리 합 → 도시 치킨 거리</p>
</li>
</ul>
<h1 id="정답-코드">정답 코드</h1>
<pre><code class="language-java">

public class BOJ_15686_치킨배달_G5 {
    static int N, M;
    static int[][] arr;
    static List&lt;int[]&gt; homes = new ArrayList&lt;&gt;();
    static List&lt;int[]&gt; chickens = new ArrayList&lt;&gt;();
    static int min = Integer.MAX_VALUE;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken()); // 치킨 집 수
        arr = new int[N][N];

        for (int i = 0; i &lt; N; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; N; j++) {
                arr[i][j] = Integer.parseInt(st.nextToken());
                if (arr[i][j] == 1)
                    homes.add(new int[]{i, j});
                if (arr[i][j] == 2)
                    chickens.add(new int[]{i, j});
            }
        }

        combi(new ArrayList&lt;&gt;(), 0, 0);
        System.out.println(min);
    }

    static void combi(List&lt;int[]&gt; selected, int start, int depth) {
        if (depth == M) {
            min = Math.min(min, getCityChickenDistance(selected));
            return;
        }

        for (int i = start; i&lt; chickens.size(); i++) {
            selected.add(chickens.get(i));
            combi(selected, i+1, depth+1);
            selected.remove(selected.size()-1);
        }
    }

    static int getCityChickenDistance(List&lt;int[]&gt; selected) {
        int sum = 0;
        for (int[] home : homes) {
            int minDist = Integer.MAX_VALUE;
            for (int[] chicken : selected) {
                int dist = Math.abs(home[0]- chicken[0]) + Math.abs(home[1] - chicken[1]);
                minDist = Math.min(minDist, dist);
            }
            sum+= minDist;
        }
        return sum;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[대규모 시스템 설계 기초] 10장 알림 시스템 설계]]></title>
            <link>https://velog.io/@kkimdy_12/%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%8A%A4%ED%84%B0%EB%94%94-10%EC%9E%A5-%EC%95%8C%EB%A6%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@kkimdy_12/%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%8A%A4%ED%84%B0%EB%94%94-10%EC%9E%A5-%EC%95%8C%EB%A6%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Thu, 11 Dec 2025 17:41:18 GMT</pubDate>
            <description><![CDATA[<p>알림(Notification) 시스템은 모바일 푸시 알림, SMS 메시지, 이메일의 세 가지 주요 채널로 구성된다. 이러한 시스템은 수많은 사용자에게 안정적으로 이벤트를 전달해야 하므로, 확장성과 안정성을 모두 고려한 아키텍처 설계가 필요하다.</p>
<h1 id="1단계-문제-이해-및-설계-범위-확정">1단계 문제 이해 및 설계 범위 확정</h1>
<h2 id="요구사항-정의">요구사항 정의</h2>
<ul>
<li>어떤 종류의 알림</li>
<li>실시간 시스템인지</li>
<li>어떤 종류의 단말을 지원해야하는지: iOS 단말, 안드로이드 단말, 랩톱/데스크톱 등</li>
<li>사용자에게 보낼 알림은 누가 만들 수 있나요? <ul>
<li>클라이언트 애플리케이션 프로그램이 만들 수도 있고, 서버 측에서 스케줄링도 가능</li>
</ul>
</li>
<li>사용자의 알림 설정 가능 유무</li>
<li>하루에 몇 건의 알림을 보낼 수 있는지</li>
</ul>
<h1 id="2단계-개략적-설계안-제시-및-동의-구하기">2단계 개략적 설계안 제시 및 동의 구하기</h1>
<h2 id="알림-유형별-지원-방안">알림 유형별 지원 방안</h2>
<p>iOS, 안드로이드, SMS 메시지, 이메일의 알림 메커니즘이 어떻게 동작하는지 알아보자.</p>
<h3 id="1-ios-푸시-알림">1. iOS 푸시 알림</h3>
<p>iOS에서 푸시 알림을 보내기 위해서는 세 가지 컴포넌트가 필요하다.
APNS 동작 방식에 대해 자세히 알고싶다면 <a href="https://babbab2.tistory.com/58">해당 사이트</a>를 참고하면 좋다.
<img src="https://velog.velcdn.com/images/kkimdy_12/post/c80a1aae-d863-4f82-94e3-608ba9969c60/image.png" alt=""></p>
<ul>
<li>구성 요소<ul>
<li>알림 제공자(provider) : 알림 요청을 만들어 애플 푸시 알림 서비스(APNS)로 보내는 주체이고 알림 요청을 만들려면 단말 토큰(device token), 페이 로드(payload) 가 필요하다.</li>
<li>APNS(Apple Push Notification Service) : 애플이 제공하는 원격 서비스로 푸시 알림을 iOS로 보내는 역할을 담당한다.</li>
<li>iOS 단말 : 푸시 알림을 수신하는 사용자 단말이다.</li>
</ul>
</li>
<li>요소: 단말 토큰(Device Token)과 페이로드(Payload)가 필요.</li>
</ul>
<h3 id="2-안드로이드-푸시-알림">2. 안드로이드 푸시 알림</h3>
<p>안드로이드에서 푸시 알림을 보내기 위해서는 세 가지 컴포넌트가 필요하다.
FCM 동작 방식에 대해 자세히 알고싶다면 <a href="https://firebase.google.com/docs/cloud-messaging?hl=ko">해당 사이트</a>를 참고하면 좋다.
<img src="https://velog.velcdn.com/images/kkimdy_12/post/c7598faf-65a2-4b39-8ec3-c74c81ada432/image.png" alt=""></p>
<p>알림 제공자(provider) : 알림 요청을 만들어 애플 푸시 알림 서비스(FCM)로 보내는 주체이고 알림 요청을 만들려면 단말 토큰(device token), 페이 로드(payload) 가 필요하다.
FCM(Firebase Cloud Messaging) : Google Firebase에서 제공하는 원격 서비스로 푸시 알림을 iOS로 보내는 역할을 담당한다.
안드로이드 단말 : 푸시 알림을 수신하는 사용자 단말이다.</p>
<h3 id="sms-메시지">SMS 메시지</h3>
<p>SMS 메시지를 보낼 때는 트윌리오, 넥스모 같은 제3사업자의 서비스를 많이 이용한다.
<img src="https://velog.velcdn.com/images/kkimdy_12/post/cc018e2a-22b6-4e85-a43e-72ec63eb00ea/image.png" alt=""></p>
<h3 id="이메일">이메일</h3>
<p>많은 회사가 상용 이메일 서비스를 이용한다. 센드그리드, 메일침프와 같은 서비스가 있다.
<img src="https://velog.velcdn.com/images/kkimdy_12/post/de330f60-e6ce-4f71-bdfb-a91bc1c130c6/image.png" alt=""></p>
<h3 id="연락처-정보-수집-절차">연락처 정보 수집 절차</h3>
<p>그렇다면 각 서비스는 알림을 보낼 단말을 어떻게 찾아서 알림을 보낼까? 알림을 보내려면 기기 단말 토큰, 전화번호, 이메일 등의 정보가 필요하다. 이 정보들은 사용자가 앱을 설치하거나, 처음으로 계정을 등록할 때 API서버에서 사용자의 정보를 수집하여 데이터베이스에 저장한다.</p>
<h3 id="개략적-설계안-초안">개략적 설계안 (초안)</h3>
<p><img src="https://velog.velcdn.com/images/kkimdy_12/post/136e6290-1811-4c0a-82c2-6d58c42a8074/image.png" alt=""></p>
<ul>
<li>각각의 서비스 : 마이크로서비스, 크론잡, 과금서비스와 같은 다양한 서비스다.</li>
<li>알림 시스템 : 알림 시스템은 알림 전송/수신 처리의 핵심으로 현재는 서버 1개만 사용하는 시스템이라고 가정했다.<ul>
<li>제3자 서비스에 전달할 알림 페이로드를 만들어 낼 수 있어야한다.</li>
<li>이 시스템은 서비스 1~N에 알림 전송을 위한 API를 제공해야한다.</li>
</ul>
</li>
<li>제3자 서비스(third party services) : 이 서비스들은 사용자에게 알림을 실제로 전달하는 역할을 한다.</li>
</ul>
<p>그러나 이 설계에는 몇가지 문제가 있다.</p>
<ul>
<li>SPOF(Single-Point-Of-Failure) : 알림 서비스 서버가 장애가 생기면 전체 서비스 장애로 이어진다.</li>
<li>규모 확장성 : 한대 서비스로 푸시 알림에 관계된 모든 것을 처리하므로, 데이터베이스 / 캐시 등 중요한 컴포넌트의 규모를 개별적으로 늘릴 방법이 없다.</li>
<li>성능 병목 : 알림을 처리하고 보내는 것은 자원을 많이 필요로 한다. 따라서 모든 것을 한 서버로 처리하면 사용자 트래픽이 많이 몰리는 시간에는 시스템 과부하 상태에 빠질 수 있다.</li>
</ul>
<h3 id="개략적-설계안-개선된-버전">개략적 설계안 (개선된 버전)</h3>
<p>주요 개선 방향</p>
<ul>
<li>데이터베이스와 캐시를 알림 시스템의 주 서버에서 분리한다.</li>
<li>알림 서버를 증설하고 자동으로 수평적 규모 확장이 이루어질 수 있도록 한다.</li>
<li>메시지 큐를 이용해 시스템 컴포넌트 사이의 강한 결합을 끊는다.
<img src="https://velog.velcdn.com/images/kkimdy_12/post/d4e81c40-e0bf-4e49-b47b-5bfd3ae73129/image.png" alt=""></li>
<li>각각의 서비스 : 알림 시스템 서버의 API를 통해 알림을 보낼 서비스이다.</li>
</ul>
<p><strong>구성 요소</strong></p>
<ul>
<li><p>알림 서버</p>
<ul>
<li>알림 전송 API : 스팸 방지를 위해 보통 사내 서비스 또는 인증된 클라이언트만 이용가능하다.</li>
<li>알림 검증 : 이메일 주소, 전화번호 등에 대한 기본적 검증을 수행한다.</li>
<li>데이터베이스 또는 캐시 질의 : 알림에 포함시킬 데이터를 가져오는 기능이다.</li>
<li>알림 전송 : 알림 데이터를 메시지 큐에 넣는다. 하나 이상의 메시지 큐를 사용하므로 알림을 병렬적으로 처리할 수 있다.</li>
</ul>
</li>
<li><p>캐시 : 사용자 정보, 단말 정보, 알림 템플릿 등을 캐시한다.</p>
</li>
<li><p>데이터 베이스 : 사용자, 알림, 설정 등 다양한 정보를 저장한다.</p>
</li>
<li><p>메시지 큐 : 시스템 컴포넌트 간 의존성을 제거하기 위해 사용한다. 다량의 알림이 전송되어야 하는 경우를 대비한 버퍼 역할도 한다.</p>
</li>
<li><p>작업 서버 : 메시지 큐에서 전송할 알림을 꺼내서 제3자 서비스로 전달하는 역할을 담당하는 서버이다.</p>
</li>
</ul>
<h1 id="3단계-상세-설계">3단계 상세 설계</h1>
<p>지금까지 개략적 설계를 진행해봤다. 몇가지 사항을 더 고려하여 최종 설계안을 도출해보자.</p>
<ul>
<li>안정성 : 데이터 손실 방지, 알림 중복 전송 방지</li>
<li>추가로 필요한 컴포넌트 및 고려사항 : 알림 템플릿, 알림 설정, 전송률 제한, 재시도 메커니즘, 보안</li>
</ul>
<h2 id="안정성">안정성</h2>
<h3 id="데이터-손실-방지">데이터 손실 방지</h3>
<p>알림 전송 시스템의 가장 중요한 요구사항 중 하나는 어떤 상황에서도 알림이 소실되면 안 된다는 것이다. (알림이 지연되거나 순서가 보장되지는 않아도 된다.)</p>
<h3 id="알림-중복-전송-방지">알림 중복 전송 방지</h3>
<p>같은 알림이 여러 번 반복되는 것을 완전히 막는 것은 가능하지 않다. 대부분 알림은 딱 한번 전송되지만 분산 시스템 특성상 가끔은 같은 알림이 중복되어 전송되기도 한다.</p>
<ul>
<li>보내야할 알림이 도착하면 그 이벤트 ID를 검사하여 이전에 본 적 있는 이벤트인지 살피고 중복이벤트라면 버린다. 그렇지 않으면 알림을 발송한다.</li>
<li>즉, 이벤트 ID 기반 중복 검증 후 이전 전송 기록을 단기 캐시에 저장하여 중복 수신 방지.</li>
</ul>
<h2 id="추가로-필요한-컴포넌트-및-고려사항">추가로 필요한 컴포넌트 및 고려사항</h2>
<h3 id="알림-템플릿">알림 템플릿</h3>
<p>대형 알림 시스템은 하루에도 수백만 건 이상의 알림을 처리하고 대부분 알림 메시지 형식이 비슷하다. 변수 치환 기반 템플릿 사용으로 효율적 메시지 생성하낟. </p>
<blockquote>
<p>본문:
인기 상품 [item_name]이 재입고 되었습니다. 현재 잔여 수량은 [item_cnt] 입니다.</p>
</blockquote>
<h3 id="알림-설정">알림 설정</h3>
<p>많은 웹사이트와 앱에서는 사용자가 알림 설정을 상세히 조정할 수 있도록 하고있다. 알림 설정 테이블에 보관되며 다음과 같은 필드들이 필요하다.</p>
<pre><code>user
channel(알림이 전송될 채널, 푸시 알림, 이메일 등)
opt_in(해당 채널로 알림을 받을 것인지 여부)</code></pre><h3 id="전송률-제한">전송률 제한</h3>
<ul>
<li>사용자별/서비스별 전송 빈도를 제어한다. </li>
<li>Redis 등 캐시 시스템을 활용한 제한 구현 가능하다.</li>
</ul>
<h3 id="재시도-방법">재시도 방법</h3>
<p>제3자 서비스가 알림 전송에 실패하면 해당 알림을 재시도 전용 큐에 넣는다. (만약 같은 문제가 계속 발생하면 개발자에게 통지해야함)</p>
<h3 id="푸시-알림과-보안">푸시 알림과 보안</h3>
<p>iOS와 안드로이드 앱의 경우, 알림 전송 API는 appKey, appSecret을 사용하여 보안을 유지한다. 인증되었거나 승인된 클라이언트만 해당 API를 사용하여 알림을 보낼 수 있도록 한다.</p>
<h3 id="수정된-설계안">수정된 설계안</h3>
<p>지금까지 설명한 내용을 모두 반영하여 수정한 설계안이다.
<img src="https://velog.velcdn.com/images/kkimdy_12/post/28f65e51-656c-4561-adb9-3cb183e99e7f/image.png" alt=""></p>
<h1 id="4단계-마무리">4단계 마무리</h1>
<ul>
<li>안정성(Reliability) : 메시지 전송 실패율을 낮추기 위해 안정적인 재시도 메커니즘을 도입했다.</li>
<li>보안(Security) : 인증된 클라이언트만 알림을 보낼 수 있도록 한다.</li>
<li>이벤트 추적 및 모니터링 : 알림이 만들어진 후 성공적으로 전송되기까지의 과정을 추적, 시스템 상태를 모니터링하기 위해 알림 전송의 각 단계마다 이벤트를 추적한다.</li>
<li>사용자 설정 : 사용자가 알림 수신 설정을 조정할 수 있도록 하였다. 알림을 보내기 전 반드시 해당 설정을 확인하고 알림을 보낼 수 있도록 해야한다.</li>
</ul>
<hr>
<p><a href="https://velog.io/@heehee/%EC%95%8C%EB%A6%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84">참고</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성 프로그래밍]]></title>
            <link>https://velog.io/@kkimdy_12/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@kkimdy_12/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Thu, 11 Dec 2025 09:36:29 GMT</pubDate>
            <description><![CDATA[<h2 id="동시성과-병렬성">동시성과 병렬성</h2>
<ul>
<li>동시성은 여러 작업이 “논리적으로 동시에 진행되는 것처럼 보이도록” 스케줄링하는 개념이고, 병렬성은 실제로 여러 작업이 같은 시점에 물리적으로 동시에 실행되는 상태를 말합니다.[1][3]</li>
<li>예를 들어 단일 코어 CPU에서 여러 스레드를 번갈아 실행해 사용자에게 동시에 돌고 있는 것처럼 보이면 동시성이고, 멀티 코어에서 서로 다른 코어가 각각의 스레드를 진짜 동시에 실행하면 병렬성입니다.[3][1]</li>
</ul>
<blockquote>
<p>“동시성은 시간 분할로 번갈아 실행해서 동시에처럼 보이게 하는 것이고, 병렬성은 멀티 코어 위에서 실제로 동시에 실행되는 것입니다.”  </p>
</blockquote>
<h2 id="thread-safe와-동시성-이슈">Thread-safe와 동시성 이슈</h2>
<ul>
<li>Thread-safe하다는 것은 여러 스레드가 동시에 같은 객체나 함수에 접근하더라도, 데이터가 꼬이거나 잘못된 결과가 발생하지 않도록 설계·구현되어 있다는 의미입니다.[7][9]</li>
<li>자바에서 대표적인 동시성 이슈는 레이스 컨디션, 가시성 문제(메모리 가시성), 원자성(atomicity) 깨짐, 데드락 등이 있으며, 공유 mutable 상태를 어떻게 보호하느냐가 핵심입니다.[9][7]</li>
</ul>
<p>면접용 포인트: “Thread-safe = 여러 스레드가 동시에 접근해도 상태 일관성이 깨지지 않는 설계/구현”이라고 짧게 말하고, 바로 레이스 컨디션 예시 하나를 붙여주면 좋습니다.  </p>
<h2 id="가시성-문제와-원자성-문제">가시성 문제와 원자성 문제</h2>
<ul>
<li>가시성 문제는 한 스레드가 공유 변수 값을 변경했는데, 다른 스레드가 그 최신 값을 보지 못하고 자신의 캐시/레지스터에 남은 옛 값으로 계속 동작하는 상황입니다.[7]</li>
<li>원자성 문제는 ++, += 같은 복합 연산이 실제로는 “읽기 → 계산 → 쓰기” 여러 단계로 나뉘는데, 이 사이에 다른 스레드가 끼어들어 연산이 중간에 섞여 버리는 문제를 말합니다.[9][7]</li>
</ul>
<blockquote>
<p>&quot;가시성은 ‘변경이 다른 스레드에 보이는가’ 문제, 원자성은 ‘연산이 쪼개지지 않고 한 번에 수행되는가’ 문제입니다.”  </p>
</blockquote>
<h2 id="자바-동시성-이슈-해결-방법">자바 동시성 이슈 해결 방법</h2>
<ul>
<li>전통적인 방법으로는 synchronized 블록/메서드, wait/notify, volatile, 그리고 java.util.concurrent의 락(ReentrantLock), Condition, ConcurrentHashMap 같은 고수준 동시성 컬렉션을 사용합니다.[7]</li>
<li>더 나아가 ExecutorService, 스레드 풀, Future/CompletableFuture 등으로 스레드 직접 생성 대신 태스크 기반으로 동시성을 관리하고, 공유 상태를 줄여서 문제를 근본적으로 완화하는 방향이 많이 사용됩니다.[7]</li>
</ul>
<h2 id="volatile-키워드">volatile 키워드</h2>
<ul>
<li>volatile은 변수에 대한 “메모리 가시성”을 보장해서, 한 스레드가 쓴 값을 다른 스레드가 즉시 메인 메모리에서 읽도록 강제하는 키워드입니다.[7]</li>
<li>다만 volatile은 읽기/쓰기 자체만 원자적으로 만들 뿐, 복합 연산(증가, 조건 검사 + 변경 등)에 대한 원자성을 보장해주지는 못하므로, 카운터 증가 같은 곳에는 적합하지 않습니다.[7]</li>
</ul>
<blockquote>
<p>“volatile은 가시성을 해결해 주지만, 동기화(mutex) 대신이 될 수는 없습니다.”  </p>
</blockquote>
<h2 id="synchronized-키워드와-구현·문제점">synchronized 키워드와 구현·문제점</h2>
<ul>
<li>synchronized는 한 번에 하나의 스레드만 진입할 수 있는 임계 구역(critical section)을 만들어서, 객체 모니터(모니터 락)를 기준으로 상호 배제를 보장합니다.[7]</li>
<li>내부적으로는 객체마다 모니터 락이 있고, 진입 시 락을 획득하고, 블록/메서드가 끝나면 락을 해제하며, 이 과정에서 OS의 락/뮤텍스 또는 JVM 레벨 최적화(경량 락, 편향 락 등)를 사용합니다.[7]</li>
</ul>
<p>문제점 관점에서 말할 것들:</p>
<ul>
<li>락 경합이 심해지면 컨텍스트 스위칭 비용이 커지고 성능이 떨어진다.[7]</li>
<li>블로킹 방식이라, 락을 기다리는 동안 스레드가 멈춰 있게 되고 데드락·기아(starvation) 위험도 있다.[7]</li>
</ul>
<blockquote>
<p>“synchronized는 모니터 락 기반 상호 배제라서 간단하지만, 블로킹과 락 경합으로 인한 성능 저하, 데드락 위험이 문제라서 고수준 동시성 API와 함께 적절히 써야 합니다.”  </p>
</blockquote>
<h2 id="atomic의-의미와-atomic-타입">atomic의 의미와 Atomic 타입</h2>
<ul>
<li>atomic하다는 것은 어떤 연산이 중간 상태를 외부에 노출하지 않고 “쪼개지지 않는 하나의 단위”로 실행된다는 뜻입니다.[7]</li>
<li>자바의 java.util.concurrent.atomic 패키지에는 AtomicInteger, AtomicLong, AtomicReference 등 CAS(Compare-And-Swap) 기반으로 원자적인 읽기·갱신을 제공하는 타입들이 있습니다.[7]</li>
</ul>
<blockquote>
<p>“AtomicInteger의 incrementAndGet은 내부적으로 CAS를 사용해 다른 스레드와 섞이지 않는 원자적 증가를 제공합니다.”  </p>
</blockquote>
<h2 id="가시성-문제-심화-질문-포인트">가시성 문제 심화 질문 포인트</h2>
<ul>
<li>여러 스레드가 같은 CPU의 캐시만 사용한다면 가시성 문제는 줄어들 수 있지만, 실제 환경에서는 여러 코어와 각각의 캐시, 재정렬, 컴파일러 최적화 등 요인 때문에 “항상 안전하다”고 볼 수 없습니다.[3][7]</li>
<li>그래서 자바 메모리 모델(JMM)은 volatile, synchronized, final 등으로 happens-before 관계를 정의하고, 이 규칙을 통해 스레드 사이의 가시성과 순서를 보장합니다.[7]</li>
</ul>
<blockquote>
<p>“하나의 CPU 캐시만 쓴다는 가정 자체가 비현실적이고, 재정렬까지 고려해야 해서 언어 차원의 메모리 모델 도움 없이 직접 가시성을 보장하기는 매우 어렵습니다.”  </p>
</blockquote>
<h2 id="cas-알고리즘-개념">CAS 알고리즘 개념</h2>
<ul>
<li>CAS(Compare-And-Swap)는 “현재 값이 기대한 값과 같을 때만 새로운 값으로 바꾸는” 원자적 연산으로, 실패하면 다시 읽고 재시도하는 방식입니다.[7]</li>
<li>이 방식은 락을 쓰지 않고도 경쟁 조건을 제어할 수 있어 lock-free 자료구조 구현에 많이 사용되지만, 충돌이 심하면 반복 재시도로 인해 비용이 커질 수 있습니다.[7]</li>
</ul>
<blockquote>
<p>“CAS는 기대값 비교 후 조건부 교체를 통해 락 없이 원자적 갱신을 보장하는 알고리즘이고, 자바 Atomic 클래스들이 이를 활용합니다.”  </p>
</blockquote>
<h2 id="synchronized-컬렉션-vs-동시성-컬렉션">Synchronized 컬렉션 vs 동시성 컬렉션</h2>
<ul>
<li>Vector, Hashtable, Collections.synchronizedXXX는 전체 메서드에 synchronized를 걸어 한 번에 하나의 스레드만 접근하게 하므로, 락 경합이 많고 병렬성이 떨어지는 것이 단점입니다.[7]</li>
<li>CopyOnWriteArrayList는 쓰기 시 새 배열을 복사하고, 읽기는 락 없이 기존 배열 스냅샷을 보는 구조라서 “읽기 위주” 환경에 적합하고, ConcurrentHashMap은 버킷/세그먼트 단위 락 또는 CAS로 동시성을 극대화합니다.[7]</li>
</ul>
<p>질문별 키워드 정리:</p>
<ul>
<li><p>SynchronizedList vs CopyOnWriteArrayList  </p>
<ul>
<li>SynchronizedList: 모든 접근에 락, 읽기·쓰기 모두 블로킹, 쓰기 적고 읽기 많을 때 비효율적.[7]</li>
<li>CopyOnWriteArrayList: 쓰기 시 전체 복사, 읽기는 락 없이 빠르지만 쓰기 비용이 크므로 “읽기 매우 많고 쓰기 적은 환경”에 적합.[7]</li>
</ul>
</li>
<li><p>ConcurrentHashMap vs SynchronizedMap  </p>
<ul>
<li>SynchronizedMap: 전체 맵에 단일 락, 동시성 낮음.[7]</li>
<li>ConcurrentHashMap: 내부를 분할하거나 CAS를 활용해 여러 스레드가 동시에 다른 버킷을 업데이트 가능, 읽기 대부분은 락 없이 처리해 높은 동시성 제공.[7]</li>
</ul>
</li>
</ul>
<hr>
<p>참고 블로그: </p>
<p><a href="https://josephcha.tistory.com/30">1</a>
<a href="https://jungwoo93.com/17">2</a>
<a href="https://leapcell.io/blog/ko/rust-tongsiyeokseong-peurogeuraeming-gidae-siseojak">3</a>
<a href="https://sunro1994.tistory.com/240">4</a>
<a href="https://explorer89.tistory.com/169">5</a>
<a href="https://velog.io/@ka0ka0ka/1-3d8vtbh5">6</a>
<a href="https://f-lab.kr/insight/threads-and-concurrency-programming-20251201">7</a>
<a href="https://wooniblo.com/entry/2025-%EC%B5%9C%EC%8B%A0-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91%EC%97%90%EC%84%9C-%EC%9E%90%EC%A3%BC-%EB%82%98%EC%98%A4%EB%8A%94-%EC%A7%88%EB%AC%B8-20%EA%B0%9C-%EB%8C%80%EB%8B%B5-%EC%98%88%EC%8B%9C-%ED%8F%AC%ED%95%A8">8</a>
<a href="https://markdj92.tistory.com/7">9</a>
<a href="https://blog.naver.com/goodee0205/223486912559">10</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 자바 8장) 메서드 ]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-8%EC%9E%A5-%EB%A9%94%EC%84%9C%EB%93%9C</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-8%EC%9E%A5-%EB%A9%94%EC%84%9C%EB%93%9C</guid>
            <pubDate>Thu, 11 Dec 2025 01:05:35 GMT</pubDate>
            <description><![CDATA[<h1 id="✅-item-49-매개변수가-유효한지-검사하라">✅ Item 49. 매개변수가 유효한지 검사하라</h1>
<h2 id="1-왜-매개변수-검사가-중요한가">1. 왜 매개변수 검사가 중요한가?</h2>
<p><strong>✔ 메서드는 잘못된 입력을 절대 받아들이면 안 된다.</strong></p>
<p>매개변수가 유효하지 않으면 즉시(fail-fast) 예외를 던져야 한다. 유효성 검사를 하지 않으면 다음 문제가 발생할 수 있다:</p>
<p>1.중간 실행 중 모호한 예외 발생
2. 잘못된 반환값 생성
3. 객체 상태(invariant)가 깨져 미래의 예측 불가한 시점에 오류 발생
   → 이런 오류는 실패 원자성(Item 76)까지 깨뜨릴 수 있다.</p>
<blockquote>
<p>따라서:
모든 public 메서드는 “메서드 본문이 실행되기 전에” 매개변수 유효성을 검사해야 한다.</p>
</blockquote>
<h2 id="2-매개변수-제약은-문서화해야-한다">2. 매개변수 제약은 문서화해야 한다</h2>
<ul>
<li>매개변수가 어떤 조건을 만족해야 하는지 반드시 명시해야 한다.</li>
<li>public/protected 메서드라면 <code>@throws</code> 태그로 어떤 조건 위반 시 어떤 예외가 발생하는지 문서화할 것.</li>
<li>예: <code>null</code>, 범위, 크기, 인덱스 등</li>
</ul>
<p>✔ 표준적으로 많이 쓰는 예외</p>
<ul>
<li><code>IllegalArgumentException</code></li>
<li><code>IndexOutOfBoundsException</code></li>
<li><code>NullPointerException</code></li>
</ul>
<h2 id="3-유효성-검사-방법">3. 유효성 검사 방법</h2>
<h3 id="3-1-null-검사--objectsrequirenonnull">3-1. null 검사 — <code>Objects.requireNonNull</code></h3>
<p>가장 간편하고 널리 쓰이는 null 체크 방식.</p>
<pre><code class="language-java">this.strategy = Objects.requireNonNull(strategy, &quot;strategy must not be null&quot;);</code></pre>
<h3 id="3-2-범위-검사--objectscheckindex-java-9">3-2. 범위 검사 — <code>Objects.checkIndex</code> (Java 9+)</h3>
<p>List/Array용 범위 체크 유틸리티.</p>
<pre><code class="language-java">int idx = Objects.checkIndex(index, list.size());</code></pre>
<p>(※ 메시지는 지정할 수 없고 List/Array 전용이라는 제약이 있음)</p>
<h3 id="3-3-assert-검사--private-메서드용">3-3. assert 검사 — private 메서드용</h3>
<ul>
<li>내부 메서드에서만 사용 (외부 입력이 불가능한 경우)</li>
<li>JVM 옵션 <code>-ea</code> 필요</li>
</ul>
<pre><code class="language-java">private void sort(int[] arr, int offset, int length) {
    assert arr != null;
    assert offset &gt;= 0 &amp;&amp; length &gt;= 0;
}</code></pre>
<h3 id="3-4-저장해두는-매개변수는-더-철저히-검사">3-4. 저장해두는 매개변수는 더 철저히 검사</h3>
<pre><code class="language-java">static List&lt;Integer&gt; intArrayAsList(int[] a) {
    Objects.requireNonNull(a);
    return new AbstractList&lt;&gt;() { ... };
}</code></pre>
<p>→ null 검사가 없으면 List 뷰 생성은 되지만, 이후 사용하는 시점에 NPE 발생 </p>
<h2 id="4-유효성-검사가-불필요한-경우">4. 유효성 검사가 불필요한 경우</h2>
<ul>
<li><strong>검사 비용이 지나치게 큰 경우</strong></li>
<li><strong>계산 과정에서 자동으로 유효성이 검증되는 경우</strong></li>
</ul>
<p>예: <code>Collections.sort(list)</code>
→ 비교 과정에서 자연스럽게 <code>ClassCastException</code> 발생하므로 사전 검증 불필요.</p>
<blockquote>
<p>정리</p>
</blockquote>
<ol>
<li>모든 public 메서드/생성자는 매개변수 유효성 검사를 메서드 시작 시점에 수행해야 한다.</li>
<li>제약 조건과 위반 시 예외를 문서화(@throws)하고, null·범위·인덱스 등은 반드시 검증한다.</li>
<li>private 메서드는 assert 활용 가능하지만, public API는 반드시 명시적 검사(requireNonNull 등)를 사용한다.</li>
</ol>
<hr>
<h1 id="✅-item-50-적시에-방어적-복사본을-만들라">✅ Item 50. 적시에 방어적 복사본을 만들라</h1>
<h2 id="1-왜-방어적-복사가-필요한가">1. 왜 방어적 복사가 필요한가?</h2>
<p>Period 같은 불변 클래스를 만들어도, 가변 객체(Date 등)를 참조로 받으면 내부 불변식이 깨진다.</p>
<pre><code class="language-java">Period p = new Period(start, end);
end.setYear(78);   // p 내부가 수정됨 → 불변 아님</code></pre>
<p>즉, 불변처럼 보이지만 실제로는 공격·버그에 취약하다.</p>
<h2 id="2-생성자에서-방어적-복사">2. 생성자에서 방어적 복사</h2>
<h3 id="✔-핵심-원칙">✔ 핵심 원칙</h3>
<blockquote>
<p>“매개변수 유효성 검사 전에 복사하고, 복사본을 기준으로 검증해라.”</p>
</blockquote>
<p>멀티스레드 환경에서</p>
<ol>
<li>원본 검증</li>
<li>복사
사이에 다른 스레드가 원본을 변경할 수 있기 때문.</li>
</ol>
<h3 id="올바른-작성-예">올바른 작성 예</h3>
<pre><code class="language-java">public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end   = new Date(end.getTime());

    if (this.start.compareTo(this.end) &gt; 0) {
        throw new IllegalArgumentException(start + &quot; after &quot; + end);
    }
}</code></pre>
<h3 id="⚠-clone을-쓰면-안-되는-이유">⚠ clone()을 쓰면 안 되는 이유</h3>
<ul>
<li>매개변수가 <strong>제3자가 만든 하위 클래스</strong>일 수 있음</li>
<li>clone이 악의적으로 재정의되어 있으면 공격자가 내부를 조작할 수 있음
→ 생성자/팩터리를 이용한 복사가 안전하다.</li>
</ul>
<h2 id="3-접근자getter에서도-방어적-복사-필요">3. 접근자(getter)에서도 방어적 복사 필요</h2>
<p>다시 “가변 필드”를 그대로 넘기면 내부가 수정된다.</p>
<pre><code class="language-java">p.getEnd().setYear(78);   // 내부 필드 수정 가능 → 불변 아님</code></pre>
<h3 id="올바른-접근자">올바른 접근자</h3>
<pre><code class="language-java">public Date getStart() { return new Date(start.getTime()); }
public Date getEnd()   { return new Date(end.getTime()); }</code></pre>
<h3 id="여기서는-clone-사용-가능">여기서는 clone 사용 가능</h3>
<p>Period가 내부적으로 보유한 Date는 <strong>java.util.Date로 확정적</strong>이므로 clone도 안전.
하지만 Item13에 따라 생성자를 쓰는 방식이 일반적으로 더 권장됨.</p>
<h2 id="4-방어적-복사의-목적--불변-클래스-만들기만이-아니다">4. 방어적 복사의 목적 = “불변 클래스 만들기”만이 아니다</h2>
<ul>
<li>클라이언트가 준 객체를 내부에서 저장해야 하는 경우</li>
<li>내부 객체를 외부로 전달해야 하는 경우
→ <strong>가변 객체라면 항상 복사본 사용</strong>이 기본 룰.</li>
</ul>
<h3 id="핵심-원칙">핵심 원칙</h3>
<blockquote>
<p><strong>“어떤 참조도 외부가 객체 내부를 바꿀 수 있게 해선 안 된다.”</strong></p>
</blockquote>
<h2 id="5-방어적-복사를-항상-할-수는-없다">5. 방어적 복사를 항상 할 수는 없다</h2>
<p>방어적 복사에는 비용이 존재한다.</p>
<h3 id="복사를-생략해도-되는-경우">복사를 생략해도 되는 경우</h3>
<ul>
<li>복사 비용이 매우 큰 경우</li>
<li>클라이언트를 신뢰할 수 있는 내부 시스템</li>
<li>불변식이 깨져도 오로지 호출자에게만 영향이 있는 경우</li>
</ul>
<blockquote>
<p>정리</p>
</blockquote>
<ol>
<li>가변 객체를 매개변수로 받거나 반환할 때는 반드시 방어적 복사를 해야 한다.</li>
<li>생성자에서는 유효성 검사보다 ‘복사’가 먼저이며, clone은 절대 쓰지 않는 것이 안전하다.</li>
<li>getter에서도 내부 가변 객체를 그대로 노출하지 말고 반드시 복사 후 반환한다.</li>
<li>방어적 복사를 하면 불변식이 깨지는 공격/버그를 완전히 차단할 수 있다.</li>
<li>비용이 크거나 신뢰 가능한 상황에서만 복사를 생략하며, 그 경우 반드시 문서화한다.</li>
</ol>
<hr>
<h1 id="✅-item-51-메서드-시그니처를-신중히-설계하라">✅ Item 51. 메서드 시그니처를 신중히 설계하라</h1>
<h2 id="1-메서드-이름은-신중하게">1. 메서드 이름은 신중하게</h2>
<ul>
<li>표준 명명 규칙(Item68)을 반드시 따르고 읽는 사람이 의도를 바로 이해할 수 있어야 한다.</li>
<li>같은 패키지/프로젝트 내 다른 이름들과 일관성을 유지하는 것이 가장 중요하다.</li>
<li>너무 긴 이름은 피하고, 애매하면 자바 표준 라이브러리의 이름 패턴을 참고하자.</li>
</ul>
<h2 id="2-편의-메서드를-너무-많이-만들지-말자">2. “편의 메서드”를 너무 많이 만들지 말자</h2>
<ul>
<li>편의 메서드(convenience method)는 사용자를 위해 복잡한 작업을 쉽게 해주는 메서드.</li>
<li>하지만 편의 메서드를 남발하면 클래스에 메서드가 너무 많아지고,
학습·사용·문서화·테스트·유지보수 모두 어려워진다.</li>
<li>각 기능을 완벽히 수행하는 메서드만 두고, 정말 자주 쓰이는 경우에만 약칭 메서드를 제공하라.</li>
</ul>
<h2 id="3-매개변수-목록은-짧게-유지하자">3. 매개변수 목록은 짧게 유지하자</h2>
<ul>
<li>일반적으로 <strong>4개 이하가 이상적</strong>이다.</li>
<li>특히 <strong>동일 타입의 매개변수 여러 개가 연속</strong>되는 경우는 매우 위험하다.
(순서를 바꿔 입력해도 컴파일되므로, 의도와 다르게 동작하기 쉬움)</li>
</ul>
<h2 id="4-매개변수를-줄이는-실전-기법-3가지">4. 매개변수를 줄이는 실전 기법 3가지</h2>
<h3 id="①-여러-메서드로-기능을-쪼갠다">① 여러 메서드로 기능을 쪼갠다</h3>
<ul>
<li>원래 긴 매개변수 목록이 들어가는 한 메서드를
→ 의미 있는 기능 단위로 쪼개서
→ 더 적은 파라미터를 받도록 만드는 방식.</li>
<li>이는 “직교성(orthogonality)”을 높여 오히려 메서드 수를 줄이는 효과도 있다.</li>
<li>예) <code>subList</code> + <code>indexOf</code> 조합으로 3개 매개변수가 필요한 작업을 해결.</li>
</ul>
<h3 id="②-도우미helper-클래스를-사용한다">② 도우미(helper) 클래스를 사용한다</h3>
<ul>
<li>두 개 이상의 매개변수가 <strong>하나의 개념</strong>을 이룬다면 이를 묶는 클래스를 만든다.</li>
<li>보통 정적 멤버 클래스(Item24)로 구현한다.</li>
<li>예) 카드의 숫자(rank) + 무늬(suit)는 항상 함께 다니므로 <code>Card</code>라는 클래스로 묶기.</li>
</ul>
<p>이렇게 하면:</p>
<ul>
<li>메서드는 Card 하나만 받으면 되고,</li>
<li>내부 구현도 훨씬 깔끔해지고,</li>
<li>의미도 명확해진다.</li>
</ul>
<h3 id="③-메서드-호출에-빌더-패턴을-응용한다">③ 메서드 호출에 ‘빌더 패턴’을 응용한다</h3>
<ul>
<li>매개변수 개수가 많거나 일부만 선택적으로 설정하면 되는 경우 유용.</li>
<li>“설정 객체(파라미터 객체)”를 만들어 setter 형태로 필요한 인자만 채운 뒤
마지막에 <code>execute()</code> 같은 메서드를 호출해서 실행.</li>
</ul>
<p>예)</p>
<pre><code class="language-java">QueryBuilder qb = new QueryBuilder()
        .limit(10)
        .orderBy(&quot;name&quot;)
        .filter(&quot;age &gt; 20&quot;);
qb.execute();</code></pre>
<p>이는</p>
<ul>
<li>긴 시그니처를 피할 수 있고,</li>
<li>선택적 매개변수 처리가 쉬워지며,</li>
<li>최종 실행 전에 유효성 검사도 할 수 있다.</li>
</ul>
<h2 id="5-매개변수-타입은-구체-클래스보다-인터페이스로-받자">5. 매개변수 타입은 구체 클래스보다 인터페이스로 받자</h2>
<ul>
<li>매개변수로 사용할 인터페이스가 있다면 <strong>반드시 인터페이스로 받자</strong>.</li>
<li>예: <code>HashMap</code>을 받도록 설계하지 말고 <code>Map</code>을 받도록 하자.
→ 그럼 HashMap, TreeMap, ConcurrentHashMap 등 모든 Map 구현체 사용 가능
→ 클라이언트를 특정 구현체로 제한하지 않는다.</li>
<li>클래스 타입을 받으면 클라이언트가 “형 변환” 또는 “복사 비용”을 치르게 된다.</li>
</ul>
<h2 id="6-boolean-파라미터-대신-두-값짜리-enum을-사용하라">6. boolean 파라미터 대신 <strong>두 값짜리 enum</strong>을 사용하라</h2>
<ul>
<li>boolean을 쓰면 호출 시 의미가 명확하지 않다.</li>
<li>온도 단위 예시:</li>
</ul>
<pre><code class="language-java">Thermometer.newInstance(true); // 의미 모호
Thermometer.newInstance(TemperatureScale.CELSIUS); // 명확</code></pre>
<ul>
<li>확장성도 좋아서 이후 KELVIN 같은 값도 쉽게 추가할 수 있다.</li>
<li>또한 enum 상수에 각 단위를 처리하는 메서드를 포함시킬 수도 있다.</li>
</ul>
<blockquote>
<p>정리</p>
</blockquote>
<ol>
<li>메서드 이름은 일관성 있고 이해 가능하게 지어라.</li>
<li>편의 메서드 남발은 해롭다 — 각 메서드는 자신의 역할만 하게 하라.</li>
<li>매개변수는 4개 이하로, 특히 같은 타입 여러 개는 피하라.</li>
<li>매개변수는 쪼개기·도우미 클래스·빌더 패턴으로 줄여라.</li>
<li>매개변수 타입은 인터페이스로 받고, boolean보다 enum을 사용하라.</li>
</ol>
<hr>
<h1 id="✅-item-52-다중정의overloading는-신중히-사용하라">✅ Item 52. 다중정의(Overloading)는 신중히 사용하라</h1>
<h2 id="1-왜-다중정의가-위험한가">1. 왜 다중정의가 위험한가?</h2>
<h3 id="✔-다중정의는-정적으로compile-time-선택된다">✔ 다중정의는 <strong>정적으로(compile-time)</strong> 선택된다</h3>
<p>오버라이딩은 <strong>런타임 타입(runtime type)</strong> 이 기준이지만,
오버로딩은 <strong>컴파일 타입(compile-time type)</strong> 만 보고 어떤 메서드를 호출할지 결정한다.</p>
<p>즉:</p>
<pre><code class="language-java">whatIsThisClass(Collection&lt;?&gt; c);</code></pre>
<p>여기서 실제 런타임 객체가 <code>Set</code>, <code>List</code>여도
컴파일 타입이 <code>Collection</code>이면 <strong>Collection 버전이 항상 호출</strong>된다.</p>
<p>그래서 아래 예제에서 3번 모두 <code>&quot;컬렉션&quot;</code>이 출력된다:</p>
<h3 id="🧨-다중정의-실패-예시">🧨 다중정의 실패 예시</h3>
<pre><code class="language-java">public String whatIsThisClass(Set&lt;?&gt; set) { return &quot;셋&quot;; }
public String whatIsThisClass(List&lt;?&gt; list) { return &quot;리스트&quot;; }
public String whatIsThisClass(Collection&lt;?&gt; c) { return &quot;컬렉션&quot;; }

Collection&lt;?&gt;[] arr = { new HashSet&lt;&gt;(), new ArrayList&lt;&gt;(), map.values() };
for (Collection&lt;?&gt; c : arr) {
    System.out.println(whatIsThisClass(c)); 
}</code></pre>
<h3 id="→-문제의-핵심">→ <strong>문제의 핵심</strong></h3>
<ul>
<li>다형성처럼 보이지만 <strong>다형성을 제공하지 않는다.</strong></li>
<li>타입 계층에 따라 메서드가 선택되는 것이 아니라
<strong>컴파일러가 정한 하나만 호출된다.</strong></li>
</ul>
<h2 id="2-반면-오버라이딩은-런타임에-동작이-결정된다">2. 반면 <strong>오버라이딩은 런타임에 동작이 결정된다</strong></h2>
<pre><code class="language-java">Burger -&gt; ChickenBurger -&gt; Whopper</code></pre>
<pre><code class="language-java">Burger b = new Whopper();
b.print();  // &quot;와퍼&quot;</code></pre>
<p>→ 여기서는 <strong>런타임 타입(실 객체 타입)</strong> 이 기준이므로 기대한 대로 동작한다.</p>
<p>✦ 결론:
<strong>오버라이딩은 다형성. 오버로딩은 단순한 이름 재사용 기능.</strong></p>
<h2 id="3-다중정의가-헷갈리는-이유">3. 다중정의가 헷갈리는 이유</h2>
<p>다중정의는 아래 상황에서 특히 혼란을 일으킨다:</p>
<ul>
<li>서로 관련된 타입 (예: Object, Collection, List)</li>
<li>매개변수 수가 같은 메서드</li>
<li>오토박싱, varargs, 형변환 등이 끼어 있을 때</li>
<li>컴파일러가 선택하는 메서드가 사람 기대와 다름</li>
</ul>
<h2 id="4-안전하게-설계하는-방법">4. 안전하게 설계하는 방법</h2>
<h3 id="✔-1-매개변수-수가-같은-오버로딩은-피하라">✔ 1) <strong>매개변수 수가 같은 오버로딩은 피하라</strong></h3>
<p>특히 타입 계층이 얽혀 있는 경우(Set, List, Collection 등)는 더욱 금지.</p>
<h3 id="✔-2-이름을-완전히-다르게-만들어라">✔ 2) 이름을 완전히 다르게 만들어라</h3>
<p>가장 안전한 해결책.</p>
<pre><code class="language-java">readInt(), readLong()  </code></pre>
<h3 id="✔-3-varargs가변인수와-오버로딩은-절대-같이-쓰지-마라">✔ 3) varargs(가변인수)와 오버로딩은 절대 같이 쓰지 마라</h3>
<p>컴파일러가 어떤 메서드에 매칭할지 극도로 모호함.</p>
<h3 id="✔-4-타입이-헷갈릴-수-있으면-아예-받지-마라">✔ 4) 타입이 헷갈릴 수 있으면 아예 받지 마라</h3>
<p>예: <code>Integer</code>용 오버로딩과 <code>Object</code> 오버로딩을 함께 두는 경우</p>
<h2 id="5-결론">5. 결론</h2>
<p><strong>다중정의(Overloading) — 정적 바인딩 → 컴파일타임 결정</strong>
<strong>재정의(Overriding) — 동적 바인딩 → 런타임 결정</strong></p>
<p>🚫 위험한 다중정의는 피하고</p>
<ul>
<li>매개변수 수가 같거나</li>
<li>타입 계층이 서로 관련되어 있고</li>
<li>varargs가 얽혀 있으면
→ <strong>메서드 이름을 다르게 만들어라.</strong></li>
</ul>
<p>✔ 안전한 다중정의만 허용</p>
<ul>
<li>서로 전혀 다른 타입</li>
<li>직관적으로 명확한 차이</li>
<li>자동 형변환/오토박싱/상속 계층이 얽히지 않는 경우</li>
</ul>
<hr>
<h1 id="✅-item-53-가변인수varargs는-신중히-사용하라">✅ Item 53. 가변인수(varargs)는 신중히 사용하라</h1>
<h2 id="1-가변인수varargs란">1. 가변인수(varargs)란?</h2>
<ul>
<li>개수가 정해지지 않은 인자를 받을 수 있는 메서드 기능
(<code>int... numbers</code>, <code>String... args</code>)</li>
<li>호출 시 <strong>인수 개수만큼 배열을 새로 만들어 전달</strong>한다.</li>
<li>인수를 하나도 넘기지 않아도 되고, 여러 개 넘겨도 된다.</li>
</ul>
<p>✔ 내부적으로는 <strong>배열 생성 → 값 복사 → 메서드 호출</strong>의 오버헤드가 항상 존재한다.</p>
<h1 id="2-기본-사용-예시">2. 기본 사용 예시</h1>
<h3 id="✔-간단한-가변인수-메서드-정상-예시">✔ 간단한 가변인수 메서드 (정상 예시)</h3>
<pre><code class="language-java">static int sum(int... args) {
    int sum = 0;
    for (int arg : args)
        sum += arg;
    return sum;
}

sum(1,2,3); // 6
sum();      // 0</code></pre>
<h2 id="3-문제-상황-인수가-1개-이상-필요한-경우">3. 문제 상황: 인수가 1개 이상 필요한 경우</h2>
<h3 id="❌-잘못된-구현">❌ 잘못된 구현</h3>
<pre><code class="language-java">static int min(int... args) {
    if(args.length == 0)
        throw new IllegalArgumentException();
    int min = args[0];
    for(int i = 1; i &lt; args.length; i++)
        if(args[i] &lt; min)
            min = args[i];
    return min;
}</code></pre>
<h3 id="문제점">문제점</h3>
<ul>
<li><code>min()</code> 호출 시 런타임 오류 발생</li>
<li>매개변수 유효성 검사를 매번 해야 함</li>
<li>첫 요소를 미리 뽑아 쓰므로 코드가 깔끔하지 않음</li>
</ul>
<h2 id="4-✔-올바른-설계-필수-매개변수--가변인수">4. ✔ 올바른 설계: &quot;필수 매개변수 + 가변인수&quot;</h2>
<pre><code class="language-java">static int min(int firstArg, int... remainingArgs) {
    int min = firstArg;
    for (int arg : remainingArgs)
        if (arg &lt; min)
            min = arg;
    return min;
}</code></pre>
<p>장점</p>
<ul>
<li>최소 하나는 반드시 전달됨</li>
<li>가독성 증가</li>
<li>유효성 체크 불필요</li>
</ul>
<h2 id="5-성능-최적화가-필요한-경우">5. 성능 최적화가 필요한 경우</h2>
<p>가변인수는 매 호출마다 배열 생성 비용이 든다 → 비용이 크다.</p>
<h3 id="✔-해결-전략-다중정의--마지막에만-varargs">✔ 해결 전략: <strong>다중정의 + 마지막에만 varargs</strong></h3>
<pre><code class="language-java">public void test() {}
public void test(int a1) {}
public void test(int a1, int a2) {}
public void test(int a1, int a2, int a3) {}
public void test(int a1, int a2, int a3, int... rest) {}</code></pre>
<p>효과:</p>
<ul>
<li>인수 0~3개 호출은 <strong>가변인수 배열을 생성하지 않음 → 빠름</strong></li>
<li>인수가 많을 때만 varargs 배열 생성</li>
<li>EnumSet의 정적 팩터리도 이 방식 사용(성능 최적화)</li>
</ul>
<h2 id="6-가변인수-메서드-호출-과정-중요">6. 가변인수 메서드 호출 과정 (중요)</h2>
<ol>
<li>인수 개수만큼 길이를 가진 배열 생성</li>
<li>전달된 인수들을 해당 배열에 복사</li>
<li>메서드가 이 배열을 매개변수로 받음</li>
</ol>
<p>→ 호출할 때마다 “배열 생성 + 복사” 오버헤드 발생</p>
<blockquote>
<p>정리</p>
</blockquote>
<ol>
<li>인수 개수가 정해져 있지 않다면 가변인수를 사용한다.</li>
<li><strong>필수 인수는 가변인수 앞에 두어야 한다.</strong></li>
<li>1개 이상 전달해야 하는 경우는 <code>firstArg + varargs</code> 형태로 만들 것.</li>
<li>성능 민감 코드라면 다중정의로 배열 생성을 최소화한다.</li>
<li>내부적으로 가변인수 호출 시 항상 배열을 새로 만든다.</li>
</ol>
<hr>
<h1 id="✅-item-54-null이-아닌-빈-컬렉션이나-배열을-반환하라">✅ Item 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라</h1>
<h2 id="1-절대-null-을-반환하지-마라">1) 절대 null 을 반환하지 마라</h2>
<p>다음과 같은 코드처럼, “값이 없으니 null 반환”은 흔하지만 <strong>나쁜 API 설계</strong>다.</p>
<pre><code class="language-java">return cheesesInStock.isEmpty() ? null : new ArrayList&lt;&gt;(cheesesInStock);</code></pre>
<p>이렇게 하면 <strong>클라이언트 코드가 매 호출마다 null 체크를 강제당한다.</strong></p>
<pre><code class="language-java">List&lt;Cheese&gt; cheeses = shop.getCheeses();
if (cheeses != null &amp;&amp; cheeses.contains(Cheese.Mozzarella)) {
    ...
}</code></pre>
<p>→ <strong>API 사용성이 떨어지고</strong>, null 체크가 누락되면 NPE 발생 위험이 커진다.</p>
<h2 id="2-빈-컬렉션을-반환하는-것이-더-안전--더-편리하다">2) <strong>빈 컬렉션을 반환하는 것이 더 안전 &amp; 더 편리하다</strong></h2>
<h3 id="✔-빈-컬렉션-할당-비용은-성능-문제를-일으키지-않는다">✔ 빈 컬렉션 할당 비용은 성능 문제를 일으키지 않는다</h3>
<ul>
<li>JVM 최적화 때문에 빈 리스트 생성 비용은 매우 낮다.</li>
<li>성능 프로파일링을 해보면 문제 원인은 대부분 다른 데 있음.</li>
<li>&quot;빈 컬렉션을 만든다고 성능이 떨어진다&quot;는 <strong>잘못된 걱정</strong>.</li>
</ul>
<h2 id="3-더-효율적인-방법--불변empty-컬렉션-반환">3) <strong>더 효율적인 방법 — 불변(empty) 컬렉션 반환</strong></h2>
<p>빈 컬렉션을 매번 새로 생성하지 않고 <strong>불변 싱글턴 컬렉션</strong>을 쓰면 된다.</p>
<pre><code class="language-java">return cheesesInStock.isEmpty() 
        ? Collections.emptyList() 
        : new ArrayList&lt;&gt;(cheesesInStock);</code></pre>
<p><code>Collections.emptyList()</code>는</p>
<ul>
<li>불변 싱글턴</li>
<li>재사용 가능 (새 객체 생성 X)</li>
<li>안전하고 성능에도 유리</li>
</ul>
<p>Set/Map도 동일하게 <code>emptySet()</code>, <code>emptyMap()</code> 제공.</p>
<h2 id="4-배열도-null-대신-길이-0짜리-배열을-반환하라">4) <strong>배열도 null 대신 길이 0짜리 배열을 반환하라</strong></h2>
<h3 id="❌-잘못된-방식">❌ 잘못된 방식</h3>
<pre><code class="language-java">return null;</code></pre>
<h3 id="✔-올바른-방식">✔ 올바른 방식</h3>
<pre><code class="language-java">private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];

public Cheese[] getCheeses() {
    return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}</code></pre>
<p><code>new Cheese[0]</code> 배열은 불변이기 때문에 재사용이 가능하다.</p>
<blockquote>
<p>정리
✔ <strong>null 대신 빈 컬렉션/배열을 반환하라</strong>
✔ API 사용성 ↑, NPE ↓
✔ 성능 문제 거의 없음
✔ 필요하면 <code>Collections.emptyXxx()</code> 같은 불변 빈 컬렉션 사용
✔ 배열도 0길이 배열 싱글턴 사용</p>
</blockquote>
<hr>
<h1 id="✅-item-56-공개된-api-요소에는-항상-문서와-주석을-작성하라">✅ Item 56. 공개된 API 요소에는 항상 문서와 주석을 작성하라</h1>
<h2 id="1-공개-api에는-반드시-javadoc을-달아라">1. 공개 API에는 반드시 Javadoc을 달아라</h2>
<p><strong>Javadoc = Java 코드에서 API 문서를 생성하는 공식 주석 시스템</strong></p>
<ul>
<li>공개된 클래스, 인터페이스, 메서드, 필드에는 반드시 Javadoc 작성</li>
<li>특히 직렬화 가능한 클래스는 직렬화 형태까지 문서화해야 함</li>
<li>기본 생성자는 Javadoc을 달 수 없으므로 공개 API에서는 기본 생성자 사용 금지</li>
</ul>
<h2 id="2-문서-주석은-클라이언트와의-규약계약을-정확히-기술해야-한다">2. 문서 주석은 “클라이언트와의 규약(계약)”을 정확히 기술해야 한다</h2>
<p>메서드를 문서화할 때 반드시 아래 3개를 명시해야 한다.</p>
<p>✔ 1) <strong>전제조건(preconditions)</strong> – @param, @throws</p>
<p>메서드 호출자가 만족해야 하는 조건
예: <code>index &gt;= 0</code>, <code>object != null</code></p>
<p>✔ 2) <strong>사후조건(postconditions)</strong> – @return</p>
<p>메서드가 성공적으로 수행된 후 반드시 만족해야 하는 조건
예: “정렬된 리스트를 반환한다”</p>
<p>✔ 3) <strong>부작용(side effects)</strong></p>
<p>배경 스레드 생성, 전역 상태 변경 등
→ 문서화하지 않으면 API 오용이 발생할 수 있음
(예: <code>Thread.stop()</code> API 문서처럼 상세히 설명해야 함)</p>
<h2 id="📌-3-javadoc-태그-정리">📌 3. Javadoc 태그 정리</h2>
<p>✔ <strong>@param</strong></p>
<p>매개변수 설명</p>
<ul>
<li>관례상 문장 끝에 마침표 생략</li>
</ul>
<p>✔ <strong>@return</strong></p>
<p>반환 값 설명</p>
<ul>
<li>Java 16부터 <code>{@return}</code> 사용 가능 → 중복 설명 줄어듬</li>
</ul>
<p>✔ <strong>@throws</strong></p>
<p>체크/언체크 예외 기술</p>
<h2 id="4-중요한-javadoc-태그들">4. 중요한 Javadoc 태그들</h2>
<p>✔ <code>{@code ...}</code></p>
<ul>
<li>코드 폰트로 렌더링</li>
<li>HTML 태그/다른 Javadoc 태그 무시 → 안전함</li>
</ul>
<p>✔ <code>{@literal ...}</code></p>
<ul>
<li>HTML 메타문자(&lt;, &gt; 등)를 escape해서 그대로 출력</li>
<li>Mrs. 같은 문자열의 <strong>문장 종료 오해 문제 해결</strong></li>
</ul>
<p>✔ <code>@implSpec</code></p>
<ul>
<li>상속용 클래스의 “하위 클래스와의 계약(implementation requirements)”을 설명</li>
<li>활성화하려면 컴파일 옵션 필요
→ <code>-tag &quot;implSpec:a:Implementation Requirements:&quot;</code></li>
</ul>
<p>✔ <code>@summary</code> (JDK 10+)</p>
<p>문서 첫 문장 생성 시 마침표 문제 해결하는 용도</p>
<p>✔ <code>{@index ...}</code> (JDK 9+)</p>
<p>검색 색인 기능 추가</p>
<h2 id="5-제네릭--enum--annotation-문서화-규칙">5. 제네릭 / Enum / Annotation 문서화 규칙</h2>
<p>✔ 제네릭 타입</p>
<p>모든 타입 매개변수에 반드시 @param 문서 작성</p>
<pre><code class="language-java">@param &lt;K&gt; 키 타입
@param &lt;V&gt; 값 타입</code></pre>
<p>✔ Enum</p>
<p>각 상수까지 모두 문서화해야 하는 거의 유일한 타입</p>
<pre><code class="language-java">/** 플루트, 오보 등 목관악기 */
WOODWIND</code></pre>
<p>✔ 애너테이션</p>
<p>애너테이션 자체 + 멤버 모두 문서화해야 함</p>
<ul>
<li><strong>필드 설명 = 명사구</strong></li>
<li><strong>애너테이션을 적용하면 어떤 동작이 발생하는지 = 동사구</strong></li>
</ul>
<h2 id="6-api-단위-문서화-파일">6. API 단위 문서화 파일</h2>
<p>✔ package-info.java</p>
<p>패키지 설명을 여기에 작성 (package 선언 포함)</p>
<p>✔ module-info.java</p>
<p>모듈 설명을 여기에 작성</p>
<h2 id="7-스레드-안정성--직렬화-문서화는-필수">7. 스레드 안정성 / 직렬화 문서화는 필수</h2>
<ul>
<li><p>API에서 스레드 안전성을 반드시 명시
(&quot;불변&quot;, &quot;thread-safe&quot;, &quot;not thread-safe&quot;, &quot;조건부 thread-safe&quot; 등)</p>
</li>
<li><p>직렬화 가능 클래스는 <strong>직렬화 형태(documented serialized form)</strong> 명시
→ <code>@serial</code>, <code>@serialField</code>, <code>@serialData</code> 태그 사용 가능</p>
</li>
</ul>
<h2 id="8-javadoc-상속-규칙">8. Javadoc 상속 규칙</h2>
<p>Javadoc이 문서를 자동 상속하는 우선순위:</p>
<ol>
<li><strong>인터페이스 → 클래스</strong> 순으로 탐색</li>
<li>존재하지 않으면 상위 인터페이스/상위 클래스 체인 따라감</li>
<li>가장 먼저 찾은 문서를 사용</li>
</ol>
<p>→ 즉, <strong>인터페이스 문서가 가장 강력한 API 계약</strong>이 된다.</p>
<h2 id="9-링크-활용">9. 링크 활용</h2>
<p>복잡한 API라면 문서에 관련 클래스, 패키지 링크를 제공하는 것이 좋다.</p>
<pre><code class="language-java">See {@link java.util.Collections}</code></pre>
<h2 id="10-javadoc-품질-검사">10. Javadoc 품질 검사</h2>
<p>IDE와 JDK 자체에 Javadoc 검증 기능이 있음
(문서 누락, 태그 오류 등을 자동 검사)</p>
<blockquote>
<p>정리
✔ 공개된 API요소에는 반드시 Javadoc 작성
✔ 메서드는 &quot;계약(전제조건·사후조건·부작용)&quot;을 정확히 문서화
✔ 코드/HTML 안전성을 위해 <code>{@code}</code>, <code>{@literal}</code> 적극 활용
✔ 상속용 API는 반드시 <code>@implSpec</code> 사용
✔ 제네릭/Enum/Annotation 모두 문서화해야 함
✔ thread-safety / serialization 도 문서에서 필수
✔ Javadoc 상속 규칙 준수 (인터페이스 문서 = 최우선 계약)</p>
</blockquote>
<hr>
<p>참고 블로그: 
<a href="https://github.com/back-end-study/effective-java/tree/main/8%EC%9E%A5_%EB%A9%94%EC%84%9C%EB%93%9C">https://github.com/back-end-study/effective-java/tree/main/8%EC%9E%A5_%EB%A9%94%EC%84%9C%EB%93%9C</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[대규모 시스템 설계 기초] 9장) 웹 크롤러 설계]]></title>
            <link>https://velog.io/@kkimdy_12/%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-9%EC%9E%A5-%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%9F%AC-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@kkimdy_12/%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-9%EC%9E%A5-%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%9F%AC-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Wed, 10 Dec 2025 04:24:53 GMT</pubDate>
            <description><![CDATA[<p>웹 크롤러: 검색 엔진에서 널리 쓰는 기술, 웹에 새로 올라오거나 갱신된 콘텐츠를 찾아내는 것이 주된 목적</p>
<ul>
<li>검색 엔진 인덱싱: 크롤러는 웹 페이지를 모아 검색 엔진을 위한 로컬 인덱스 만듦</li>
<li>웹 아카이빙: 나중에 사용할 목적으로 장기보관하기 위해 웹에서 정보를 모음</li>
<li>웹 마이닝</li>
<li>웹 모니터링</li>
</ul>
<h2 id="1단계-문제-이해-및-설계-범위-확정">1단계 문제 이해 및 설계 범위 확정</h2>
<ul>
<li>웹 크롤러의 기본 알고리즘</li>
</ul>
<ol>
<li>초기 URL 집합을 입력으로 받는다.</li>
<li>각 URL에 연결된 웹 페이지를 다운로드한다.</li>
<li>페이지를 파싱하여 새로운 URL을 추출한다.</li>
<li>추출된 URL을 ‘다운로드할 URL 목록’에 추가한다.</li>
<li>위 과정을 반복하여 탐색을 확장한다.</li>
</ol>
<h2 id="2단계-개략적-설계안-제시-및-동의-구하기">2단계 개략적 설계안 제시 및 동의 구하기</h2>
<ul>
<li>시작 URL 집합: 탐색을 시작할 초기 URL 그룹.</li>
<li>미수집 URL 저장소 <ul>
<li>다운로드할 URL들을 FIFO 큐 방식으로 처리</li>
</ul>
</li>
<li>HTML 다운로더: HTTP 요청을 통해 페이지를 가져옴.</li>
<li>도메인 이름 변환기 (DNS Resolver):<ul>
<li>URL을 IP 주소로 매핑하며, 캐싱을 통해 성능 향상. </li>
</ul>
</li>
<li>콘텐츠 파서 <ul>
<li>이상한 웹페이지는 문제 발생 &amp; 저장공간 낭비, so 파싱과 검증 절차를 거쳐야 함. </li>
<li>추가로, 크롤링 서버 안에 콘텐츠 파서를 구현하면 크롤링 과정이 느려지게 될 수 있으므로 독립된 컨포넌트로 만듦</li>
</ul>
</li>
<li>중복된 컨텐츠인가? <ul>
<li>웹 페이지 해시값(예: MD5, SHA-1)을 활용해 동일 콘텐츠 판별.</li>
<li>실제 웹의 약 25~30%가 중복 콘텐츠로 추정됨.</li>
</ul>
</li>
<li>콘텐츠 저장소 <ul>
<li>HTML 문서를 보관하는 시스템</li>
<li>데이터 양이 너무 많을 땐, 대부분의 콘텐츠는 디스크에 저장</li>
<li>인기 있는 콘텐츠는 메모리에 두어 접근 지연시간 줄임</li>
</ul>
</li>
<li>URL 추출기<ul>
<li>HTML 페이지를 파싱하여 링크들을 골라내는 역할</li>
</ul>
</li>
<li>URL 필터<ul>
<li>특정 조건(정규 표현식, 도메인 제한, 파일 확장자 등)에 해당하는 URL들을 크롤링 대상에서 배제하는 역할</li>
</ul>
</li>
<li>이미 방문 URL?<ul>
<li>블룸 필터나 해시 테이블로 처리</li>
</ul>
</li>
<li>URL 저장소<ul>
<li>이미 방문한 URL 보관하는 저장소 </li>
</ul>
</li>
</ul>
<h2 id="3단계-상세-설계">3단계 상세 설계</h2>
<h3 id="dfs를-쓸-것인가-bfs를-쓸-것인가">DFS를 쓸 것인가, BFS를 쓸 것인가</h3>
<p>  웹은 유향 그래프나 같다. 페이지는 노드이고 하이퍼링크(url)은 edge라고 보면 된다. </p>
<ul>
<li><p>그래프 탐색에는 보통 DFS, BFS가 많이 쓰이는데 DFS는 그래프 크기가 클 경우 어느 정도로 깊숙이 가게 될지 가늠이 어렵기에 좋은 선택이 아닐 가능성이 높다. </p>
<p>그래서!! 웹 크롤러는 보통 BFS(너비 우선 탐색법)을 사용한다. 
BFS는 FIFO 큐를 사용한다. 큐의 한쪽으론는 탐색할 URL을 집어넣고, 다른 한쪽으로는 꺼내기만 한다. 하지만 이 구현법에는 두 가지 문제점이 있다. 
1) 한 페이지에서 나오는 링크의 상당수는 같은 서버로 되돌아간다. 위키피디아 페이지에서 추출한 링크가 내부 링크, 위키피디아 서버의 다른 페이지를 참조하게 되는...그럼 위키피디아 서버는 수많은 요청으로 과부하가 걸리게 된다. 
2) BFS는 URL 간에 우선순위를 두지 않는다. 하지만 페이지 순위, 사용자 트래픽의 양, 업데이트 빈도에 따라 우선순위 구별이 필요하다. </p>
<h3 id="미수집-url-저장소">미수집 URL 저장소</h3>
<p>미수집 URL 저장소로 위 문제 해결 가능!</p>
<p>1) 예의
예의 바른 크롤러를 만드는 데 있어서 지켜야 할 한 가지 원칙은, 동일 웹 사이트에 대해서는 한 번에 한 페이지만 요청한다는 것이다. 
2) 우선순위</p>
<ul>
<li>페이지랭크, 트래픽 양, 갱신 빈도</li>
<li>순위결정장치: URL을 입력으로 받아 우선순위를 계산한다. </li>
<li>전면 큐, 후면 큐
3) 신선도
모든 URL을 재수집하는 건 비효율 적. 최적화 전략: 웹 페이지의 변경 이력 활용, 우선순의를 활용하여 중요 페이지 더 자주 재수집</li>
</ul>
</li>
</ul>
<h3 id="html-다운로더">HTML 다운로더</h3>
<ul>
<li>Robots.txt</li>
<li>성능 최적화<ul>
<li>분산 크롤러 구조: 여러 서버 간 작업 분할.</li>
<li>DNS 캐싱: 동일 도메인 요청 시 이름 해석 비용 최소화.</li>
<li>네트워크 지역성(Locality): 지리적으로 가까운 서버 우선 요청.</li>
<li>짧은 타임아웃: 응답이 느린 서버로 인한 병목 방지.</li>
</ul>
</li>
<li>안정성<ul>
<li>크롤링 상태 및 수집 데이터 저장: 중단되었던 크롤링을 쉽게 재시작할 수 있게</li>
<li>장애 시 안정 해시(Consistent Hashing) 로 URL 재분배.</li>
<li>예외 처리 및 재시도 로직 존재.</li>
</ul>
</li>
<li>확장성</li>
<li>문제 있는 콘텐츠 감지 및 회피<ul>
<li>중복 콘텐츠 (해시나 체크섬 사용)</li>
<li>거미 덫: 크롤러를 무한 루프에 빠지게 함, 최대 길이를 제한하기</li>
<li>데이터 노이즈: 가치 없는 콘텐츠들 예외</li>
</ul>
</li>
</ul>
<h2 id="4단계-마무리">4단계 마무리</h2>
<p>시간이 허락한다면 다음과 같은 것을 추가로 논의해보면 좋다. </p>
<ul>
<li>서버 측 렌더링(동적 렌더링)</li>
<li>원치 않는 페이지 필터링</li>
<li>데이터베이스 다중화 및 샤딩</li>
<li>수평적 규모 확장성 </li>
<li>가용성, 일관성, 안정성</li>
<li>데이터 분석 솔루션</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바] BOJ_2636_치즈_G5]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ2636%EC%B9%98%EC%A6%88G5</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ2636%EC%B9%98%EC%A6%88G5</guid>
            <pubDate>Mon, 08 Dec 2025 16:33:26 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크"><a href="https://www.acmicpc.net/problem/2636">문제 링크</a></h1>
<h2 id="접근-방식">접근 방식</h2>
<ul>
<li>arr를 계속 갱신하면서 가장 자리 판별하면 될듯<ul>
<li>가장자리란? 상하좌우 중 한 곳이라도 치즈가 없으면 탈락!</li>
<li>가장자리인 곳은 2로 바꿔서.. 판별하면 되지 않을까</li>
</ul>
</li>
<li>답은 Queue로 만들어서 size 출력하고 poll()하기</li>
<li>처음엔 어떤 알고리즘을 적용해야될지 감이 안 잡혀서 일단 for문을 엄청 돌렸다...</li>
</ul>
<h2 id="잘못-구현한-부분">잘못 구현한 부분</h2>
<ul>
<li>메인 해결 방법이 <code>외부 공기를 매 턴 BFS로 다시 찾아야 한다</code>는 것이 핵심이었다. </li>
<li>가장자리인 곳을 2로 바꾸고 없애는 코드를 안 넣었었다..<pre><code>time = 0
lastMelt = 0
</code></pre></li>
</ul>
<p>while (true):
    외부공기 BFS()
    melt = 이번 턴에 녹일 치즈 리스트
    if melt.size == 0:
        break
    lastMelt = melt.size
    melt 치즈를 모두 arr에서 0으로 바꿈
    time++</p>
<pre><code>
## 왜 bfs? 
- dfs로도 가능하다고 함
- 외부 공기에 닿은 영역만 색출해내야 하니까, 내부 공기쪽은 XX
```java
// 빈 공간(0) → 계속 BFS 진행
if (arr[nx][ny] == 0) {
    visited[nx][ny] = true;
    q.add(new int[]{nx, ny});
}

// 치즈(1) → 외부공기와 닿았으므로 녹일 대상
else if (arr[nx][ny] == 1) {
    visited[nx][ny] = true;
    meltList.add(new int[]{nx, ny});
}</code></pre><ul>
<li>위 처럼 한번 닿았으면 visited=true 처리해서 더이상 깊게 안 들어가게 한다!</li>
</ul>
<h1 id="정답-코드">정답 코드</h1>
<pre><code class="language-java">package algo.ct.M12;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class BOJ_2636_치즈_G5 {
    static int n, m;
    static int[][] arr;
    static boolean[][] visited;
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1};

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        arr = new int[n][m];

        for (int i = 0; i &lt; n; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; m; j++) {
                arr[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        int time = 0;      // 총 시간
        int lastMelt = 0;  // 마지막에 녹은 치즈 개수

        while (true) {
            // 매 턴마다 외부 공기 범위가 달라지니까
            visited = new boolean[n][m];

            // 이번 턴에 녹을 치즈들
            List&lt;int[]&gt; meltList = bfs();  

            if (meltList.size() == 0) {
                // 녹일 치즈가 없으면 종료
                break;
            }

            lastMelt = meltList.size();
            time++;

            // 실제로 치즈 녹이기
            for (int[] cur : meltList) {
                arr[cur[0]][cur[1]] = 0;
            }
        }

        System.out.println(time);
        System.out.println(lastMelt);
    }

    // 외부 공기를 BFS로 탐색하면서, 외부 공기와 닿은 치즈를 meltList에 담아 반환
    public static List&lt;int[]&gt; bfs() {
        Queue&lt;int[]&gt; q = new LinkedList&lt;&gt;();
        List&lt;int[]&gt; meltList = new ArrayList&lt;&gt;();

        // 항상 외부 공기는 (0,0)에서 시작
        q.add(new int[]{0, 0});
        visited[0][0] = true;

        while (!q.isEmpty()) {
            int[] cur = q.poll();
            int x = cur[0];
            int y = cur[1];

            for (int i = 0; i &lt; 4; i++) {
                int nx = x + dx[i];
                int ny = y + dy[i];

                // 범위 밖
                if (nx &lt; 0 || ny &lt; 0 || nx &gt;= n || ny &gt;= m) continue;

                if (visited[nx][ny]) continue;

                // 빈 공간(0) → 계속 BFS 진행
                if (arr[nx][ny] == 0) {
                    visited[nx][ny] = true;
                    q.add(new int[]{nx, ny});
                }

                // 치즈(1) → 외부공기와 닿았으므로 녹일 대상
                else if (arr[nx][ny] == 1) {
                    visited[nx][ny] = true;
                    meltList.add(new int[]{nx, ny});
                }
            }
        }

        return meltList;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바] BOJ_1417_국회의원선거_S5]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ1417%EA%B5%AD%ED%9A%8C%EC%9D%98%EC%9B%90%EC%84%A0%EA%B1%B0S5</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ1417%EA%B5%AD%ED%9A%8C%EC%9D%98%EC%9B%90%EC%84%A0%EA%B1%B0S5</guid>
            <pubDate>Fri, 05 Dec 2025 07:15:10 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크"><a href="https://www.acmicpc.net/problem/1417">문제 링크</a></h1>
<h1 id="접근-방식">접근 방식</h1>
<ul>
<li>처음에는 어떤 방식으로 풀어야되지? 잘 모르겠어서 코드부터 써봤는데 알고보니 구현문제여서 그럴 수 밖에 없었다.</li>
<li>n, dasom까지 받고 나머지를 배열로 받아 계속 <code>Array.sorts</code>하면서 비교했다. </li>
<li><code>Array.sorts</code>를 하면 내림차순으로 되는 줄 알고.. 있었는데 출력해보니 오름차순으로 정렬된다는 점을 알았다.</li>
</ul>
<h1 id="내-코드--개선할-점">내 코드 &amp; 개선할 점</h1>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class BOJ_1417_국회의원선거_S5 {
    static int n, m;
    static int result = 0;
    static int dasom;
    static int[] arr;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        dasom = Integer.parseInt(br.readLine());

        if (n &gt; 1) {
            arr = new int[n - 1];
            for (int i = 0; i &lt; n - 1; i++)
                arr[i] = Integer.parseInt(br.readLine());

            while (true) {
                if (dasom &lt;= arr[n - 2]) {
                    arr[n - 2]--;
                    dasom++;
                    result++;
                } else if (dasom &gt; arr[n - 2])
                    break;
            }
        }

        System.out.println(result);
    }
}

</code></pre>
<ul>
<li>S5 정도는 혼자 10분 내로 수월하게 풀 수 있는 정도인 것 같다. 그래서 더 시간이 짧은 다른 코드들을 참고했고 매번 돌면서 Arrays.sort가 실행되는 게 느리다는 것을 알게 되었다.</li>
</ul>
<h1 id="개선된-코드">개선된 코드</h1>
<pre><code class="language-java">import java.io.*;
import java.util.*;
public class Main {


    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());

        int[] input = new int[N];
        for (int i = 0; i &lt; N; i++) {
            input[i] = Integer.parseInt(br.readLine());
        }

        int num = 0;
        int vote = input[0];
        int answer=0;

        while(true){

            for (int i = 0; i &lt; N; i++) {
                if(input[i] &gt;= vote){
                    num = i;
                    vote = input[i];
                }
            }

            if(num == 0) break;

            input[0]++;
            input[num]--;
            answer++;
ㅐ
            vote = -1;
        }

        System.out.println(answer);
    }
}</code></pre>
<hr>
<h4 id="가장-득표-많은-사람-찾기">가장 득표 많은 사람 찾기</h4>
<pre><code class="language-java">for (int i = 0; i &lt; N; i++) {
    if(input[i] &gt;= vote){
        num = i;
        vote = input[i];
    }
}</code></pre>
<ul>
<li><p>전체 후보의 득표를 훑어서(0 ~ N-1)</p>
</li>
<li><p>vote보다 크거나 같으면, ㅐ
이 루프가 끝나면:</p>
</li>
<li><p><code>num</code> → 가장 득표 높은 후보의 index</p>
</li>
<li><p><code>vote</code> → 그 후보의 득표수</p>
</li>
</ul>
<hr>
<h4 id="다솜이-1등이면-종료">다솜이 1등이면 종료</h4>
<pre><code class="language-java">if(num == 0) break;</code></pre>
<ul>
<li><code>num == 0</code> 이면 1등이 다솜이므로 더 이상 뺏을 필요 없음.</li>
</ul>
<hr>
<h4 id="다솜이-1등이-아니면-표-한-표-뺏어오기">다솜이 1등이 아니면 표 한 표 뺏어오기</h4>
<pre><code class="language-java">input[0]++;     // 다솜 표 +1
input[num]--;   // 가장 강한 경쟁자 표 -1
answer++;       // 매수 횟수 증가</code></pre>
<p>즉, 가장 위험한 경쟁자에게서 표를 뺏어서 다솜에게 줌*</p>
<hr>
<h4 id="다음-반복-준비">다음 반복 준비</h4>
<pre><code class="language-java">vote = -1;</code></pre>
<ul>
<li><code>vote = -1</code> 로 초기화해서 다음 for문에서 정상적으로 갱신되도록 함</li>
</ul>
<hr>
<blockquote>
<ol>
<li>매 반복마다 “현재 최고 득표자”를 찾고</li>
<li>그 사람에게서 표를 1개 가져와서 다솜에게 줘서</li>
<li>다솜이 가장 높은 득표를 얻을 때까지 반복</li>
</ol>
</blockquote>
<p>즉, <strong>다솜이 확실히 1등이 될 때까지 최소 횟수로 표를 훔치는 그리디 알고리즘</strong>.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스레드와 스레드 풀]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C</link>
            <guid>https://velog.io/@kkimdy_12/%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C</guid>
            <pubDate>Thu, 04 Dec 2025 13:39:27 GMT</pubDate>
            <description><![CDATA[<h1 id="✔-java에서-스레드를-만드는-방법-스레드-풀-개념-그리고-스프링이-스레드-풀을-많이-사용하는-이유">✔ Java에서 스레드를 만드는 방법, 스레드 풀 개념, 그리고 스프링이 스레드 풀을 많이 사용하는 이유</h1>
<p>멀티쓰레딩은 현대 애플리케이션에서 필수적인 개념이며, 특히 웹 서버·백엔드 개발에서는 요청을 병렬로 처리하기 위해 스레드 활용이 매우 중요합니다. 아래에서 Java의 스레드 생성 방식부터 스레드 풀과 스프링이 대규모 스레드 풀을 사용하는 이유까지 정리해보겠습니다.</p>
<hr>
<h2 id="✅-1-java에서-스레드를-만드는-방법">✅ 1. Java에서 스레드를 만드는 방법</h2>
<p>Java에서 스레드를 만드는 방법은 크게 <strong>세 가지</strong>입니다.</p>
<h3 id="1-thread-클래스를-상속"><strong>1) Thread 클래스를 상속</strong></h3>
<pre><code class="language-java">class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(&quot;Thread running&quot;);
    }
}

new MyThread().start();</code></pre>
<p><strong>특징</strong></p>
<ul>
<li>간단하지만, 이미 다른 클래스를 상속받고 있다면 사용 불가(단일 상속 제한)</li>
<li>재사용성이 낮음</li>
</ul>
<h3 id="2-runnable-인터페이스-구현-가장-일반적인-방식"><strong>2) Runnable 인터페이스 구현 (가장 일반적인 방식)</strong></h3>
<pre><code class="language-java">class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println(&quot;Task running&quot;);
    }
}

new Thread(new MyTask()).start();</code></pre>
<p><strong>특징</strong></p>
<ul>
<li>스레드 실행 로직(run)을 캡슐화</li>
<li>Thread와 작업(task)을 분리하여 더 유연하게 사용 가능</li>
<li>ThreadPoolExecutor 같은 스레드 풀에서 사용되는 기본 구조</li>
</ul>
<h3 id="3-callable--future-값-반환--예외-처리-가능"><strong>3) Callable + Future (값 반환 + 예외 처리 가능)</strong></h3>
<pre><code class="language-java">Callable&lt;Integer&gt; task = () -&gt; {
    return 42;
};

Future&lt;Integer&gt; result = Executors.newSingleThreadExecutor().submit(task);
System.out.println(result.get());</code></pre>
<p><strong>특징</strong></p>
<ul>
<li><code>Runnable</code>과 달리 <strong>결과 반환</strong> 및 <strong>예외 전파</strong>가 가능</li>
<li>비동기 작업에서 자주 사용</li>
</ul>
<h2 id="✔-정리하면">✔ 정리하면</h2>
<table>
<thead>
<tr>
<th>방식</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>Thread 상속</td>
<td>가장 단순</td>
<td>재사용성 낮음, 단일상속 제한</td>
</tr>
<tr>
<td>Runnable</td>
<td>유연함, 스레드 풀에서 사용</td>
<td>결과 반환 불가</td>
</tr>
<tr>
<td>Callable</td>
<td>결과 반환 가능, 예외 처리</td>
<td>구현 복잡도 ↑</td>
</tr>
</tbody></table>
<hr>
<h1 id="✅-2-스레드-풀thread-pool이란-무엇인가">✅ 2. 스레드 풀(Thread Pool)이란 무엇인가?</h1>
<p>스레드 풀은 <strong>미리 여러 개의 스레드를 만들어 놓고 필요할 때 가져다 쓰는 구조</strong>입니다.</p>
<p>Java에서는 <code>ExecutorService</code>, <code>ThreadPoolExecutor</code>로 구현되어 있습니다.</p>
<h3 id="장점"><strong>장점</strong></h3>
<h3 id="1-스레드-생성-비용-절감"><strong>1) 스레드 생성 비용 절감</strong></h3>
<ul>
<li><p>스레드를 새로 만드는 비용은 생각보다 매우 무겁다</p>
<ul>
<li>OS 레벨에서 메모리 할당(Stack)</li>
<li>커널에 스레드 등록</li>
<li>스케줄러 관리 대상 추가
→ 요청마다 새로 스레드를 만드는 것은 비효율적</li>
</ul>
</li>
</ul>
<h3 id="2-과도한-스레드-생성-방지"><strong>2) 과도한 스레드 생성 방지</strong></h3>
<ul>
<li><p>스레드를 계속 만들면?</p>
<ul>
<li>메모리 초과</li>
<li>CPU 문맥 교환(Context Switching) 폭증</li>
<li>성능 급락</li>
</ul>
</li>
</ul>
<p>스레드 풀은 <strong>동시에 실행 가능한 스레드 개수를 제한</strong>하여 이를 제어한다.</p>
<h3 id="3-안정적인-시스템-운영"><strong>3) 안정적인 시스템 운영</strong></h3>
<ul>
<li>요청이 몰려도 스레드 수가 일정하게 유지되므로 시스템 과부하 방지</li>
<li>Queue에 쌓아두고 순차적으로 처리 가능</li>
</ul>
<h2 id="✔-스레드-풀의-종류-executors">✔ 스레드 풀의 종류 (Executors)</h2>
<ul>
<li><code>newFixedThreadPool(n)</code> : 고정 개수 스레드</li>
<li><code>newCachedThreadPool()</code> : 필요할 때 늘렸다가 비면 제거</li>
<li><code>newSingleThreadExecutor()</code> : 1개 스레드</li>
<li><code>ThreadPoolExecutor</code> : 직접 정책 설정(실무에서 가장 많이 사용)</li>
</ul>
<hr>
<h1 id="✅-3-스프링서버-프레임워크은-왜-스레드-풀을-수백-개-이상으로-설정할까">✅ 3. 스프링(서버 프레임워크)은 왜 스레드 풀을 수백 개 이상으로 설정할까?</h1>
<p>스프링 기반 웹 서버(Tomcat, Netty 등)는 보통 <strong>200~300개 이상의 스레드 풀을 사용</strong>합니다.
이는 문맥 교환(Context Switching)이 발생함에도 불구하고 선택하는 구조인데, 이유는 다음과 같습니다.</p>
<h2 id="✔-이유-1-서버의-대기-시간이-cpu를-거의-사용하지-않기-때문">✔ 이유 1. 서버의 대기 시간이 CPU를 거의 사용하지 않기 때문</h2>
<p>웹 서버의 대부분 시간은 <strong>I/O 대기 시간</strong>입니다.</p>
<ul>
<li>DB 응답 기다림</li>
<li>Redis 대기</li>
<li>외부 API 응답 대기</li>
<li>파일/네트워크 I/O 대기</li>
</ul>
<p>➡ CPU를 쓰는 시간이 매우 적다.</p>
<p>즉, 스레드는 <strong>일하는 시간이 짧고 기다리는 시간이 길다</strong>.</p>
<p>따라서 스레드가 많다고 해서 CPU를 과도하게 사용하지 않는다.</p>
<h2 id="✔-이유-2-io-bound-작업은-많은-스레드를-둘수록-처리량throughput이-증가됨">✔ 이유 2. I/O Bound 작업은 많은 스레드를 둘수록 처리량(Throughput)이 증가됨</h2>
<p>CPU Bound 작업이면 스레드가 많으면 오히려 느려지지만,
웹 서버는 대부분 <strong>I/O Bound</strong>.</p>
<p>즉, 스레드 300개가 모두 일을 하는 것이 아니라</p>
<ul>
<li>대부분 <strong>대기 상태(Waiting)</strong></li>
<li>일부만 CPU를 점유</li>
</ul>
<p>그래서 문맥 교환 비용보다
<strong>동시성(concurrency) 증가로 얻는 이익이 훨씬 크다</strong>.</p>
<h2 id="✔-이유-3-웹-요청-자체가-동시성이-매우-높음">✔ 이유 3. 웹 요청 자체가 동시성이 매우 높음</h2>
<p>하나의 웹 서버에서 수백 개~수천 개의 요청이 동시에 들어온다.</p>
<p>만약 스레드가 50개뿐이라면?</p>
<ul>
<li>나머지 요청은 전부 대기</li>
<li>응답 지연 증가</li>
<li>서버 처리량 감소</li>
</ul>
<p>➡ 실무에서는 <strong>최소 200~300개 스레드</strong>를 둠</p>
<h2 id="✔-이유-4-스프링-mvc는-요청당-스레드-1개가-필요-tomcat-worker-thread">✔ 이유 4. 스프링 MVC는 요청당 스레드 1개가 필요 (Tomcat worker thread)</h2>
<p>스프링 MVC는 <strong>Blocking I/O 모델</strong>이다.</p>
<ul>
<li>요청 1개 → 스레드 1개 고정 점유</li>
<li>DB 응답 기다리는 동안에도 스레드는 점유됨</li>
</ul>
<p>따라서 처리량을 확보하려면 <strong>스레드 수를 많이 둘 수밖에 없다</strong>.</p>
<blockquote>
<p>스프링 WebFlux(Non-blocking)는 스레드 수가 매우 적게 필요하지만
전통적인 MVC는 request-per-thread 구조라 스레드 풀을 크게 잡아야 한다.</p>
</blockquote>
<hr>
<h1 id="✔-정리-문맥-교환-비용보다-더-큰-이득이-있기-때문">✔ 정리: 문맥 교환 비용보다 더 큰 이득이 있기 때문</h1>
<p>Context Switching 비용은 분명 존재한다.
하지만 서버 환경에서는 다음이 더 중요하다.</p>
<ul>
<li>I/O 대기 시간이 훨씬 크다</li>
<li>동시 접속 요청이 많다</li>
<li>스레드가 대부분 Waiting 상태다</li>
<li>스레드가 부족하면 처리량이 급격히 떨어진다</li>
</ul>
<p>➡ <strong>그래서 스레드 수백 개 운영이 훨씬 효율적</strong></p>
<hr>
<h1 id="📌-정리">📌 정리</h1>
<p>Java에서 스레드는 Thread 상속, Runnable 구현, Callable을 통해 생성할 수 있다. 하지만 요청마다 스레드를 새로 만드는 것은 비용이 커서, 보통 스레드 풀(Thread Pool)로 스레드를 재사용한다. 스프링을 포함한 대부분의 서버 프레임워크는 수백 개 이상의 스레드를 운영하는데, 이는 웹 서버의 대부분 로직이 CPU 작업이 아닌 DB·외부 API·네트워크 I/O 대기로 이루어져 있기 때문이다. 즉, 스레드는 CPU를 거의 사용하지 않고 대부분 대기 상태라 많은 스레드를 두어도 오버헤드보다 동시 처리량 증가의 이점이 훨씬 크다. 요청당 스레드를 하나씩 점유하는 스프링 MVC 구조에서는 특히 많은 스레드를 둘수록 안정적인 처리량을 확보할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바] BOJ_5567_결혼식_S2]]></title>
            <link>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ5567%EA%B2%B0%ED%98%BC%EC%8B%9DS2</link>
            <guid>https://velog.io/@kkimdy_12/%EC%9E%90%EB%B0%94-BOJ5567%EA%B2%B0%ED%98%BC%EC%8B%9DS2</guid>
            <pubDate>Thu, 04 Dec 2025 13:19:33 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크"><a href="https://www.acmicpc.net/problem/5567">문제 링크</a></h1>
<h2 id="처음-접근했을-때">처음 접근했을 때</h2>
<ul>
<li>bfs(int idx, int depth)로 한번만 main 메소드에서 실행시켜서 depth가 2를 초과되면 return하게 해야하나?라는 생각이 들어 그렇게 구현했는데<ul>
<li>언제 return해야되는지... 이게 한번만 쭉 갔다가 되돌아오면 되는 게 아니라 depth가 2이하인 모든 곳을 방문해야하는데 그걸 구현하는 부분에서 막혔다.</li>
</ul>
</li>
<li>일단 보자마자 BOJ 1260번 떠올라서 그거 코드를 생각하고 <code>ArrayList&lt;ArrayList&lt;Integer&gt;&gt;</code>로 구현한 건 잘했다...</li>
</ul>
<h2 id="잘못-판단한-부분">잘못 판단한 부분</h2>
<p>일단 너무 많은 걸 잘못했는데 ^^..</p>
<ul>
<li>처음에 visited가 필요없을 거라고 생각했다. 왜지? 좀더 침착히 설계하도록.....</li>
<li>아래를 arr.add가 아니라 매번 선언하고 있었다... (arr = new ArrayList&lt;&gt;(); -&gt; 뭐하세요?)<pre><code class="language-java">for (int i = 0; i &lt;= n; i++)
 arr.add(new ArrayList&lt;&gt;());</code></pre>
</li>
<li>bfs(idx, depth)가 아니라 bfs 안에서 depth는 따로 처리하면 된다.</li>
<li><code>if (dep == 2) continue;</code> 이 부분을 어디서 끊어줘야될지 헷갈렸다..<ul>
<li>결론적으로는 for문 전에 왜냐면 dep가 2면 더 들어가면 안되니까.. q.add()쪽을 못 가게 해야되니까..</li>
</ul>
</li>
<li><code>result++;</code> 이것도 어디서 해야될지 고민됐다..<ul>
<li>일단 저기까지 왔다는 거는 dep이 2 미만이라는 것. 2라면 그 전에 continue 처리됐을 꺼니까</li>
<li>그래서 방문하지 않았다면 방문 처리해주고 result++로 결혼식에 와라! 처리해주고 q.add() 처리해서 그 다음 친구도 볼 수 있게 하면 된다. </li>
</ul>
</li>
<li>for문의 역할 <code>for (int next : arr.get(cur))</code><ul>
<li>지정된 cur과 연결된 친구를 끝까지 보는 역할 (dep는 계속 체크해주니까 안심하고..)</li>
</ul>
</li>
</ul>
<h1 id="정답-코드">정답 코드</h1>
<pre><code class="language-java">package algo.ct.M12;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class BOJ_5567_결혼식_S2 {
    static int n, m;
    static ArrayList&lt;ArrayList&lt;Integer&gt;&gt; arr = new ArrayList&lt;&gt;();
    static int result = 0;
    static boolean[] visited;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine()); // 동기 수
        m = Integer.parseInt(br.readLine()); // 리스트 길이

        for (int i = 0; i &lt;= n; i++)
            arr.add(new ArrayList&lt;&gt;());
        visited = new boolean[n+1];

        for (int i = 0; i &lt; m; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            arr.get(a).add(b);
            arr.get(b).add(a);
        }

        bfs(1);
        System.out.println(result);
    }

    public static void bfs(int idx) {
        Queue&lt;int[]&gt; q = new LinkedList&lt;&gt;();
        q.add(new int[]{idx, 0});
        visited[idx] = true;

        while (!q.isEmpty()) {
            int[] now = q.poll();
            int cur = now[0];
            int dep = now[1];

            if (dep == 2) continue;

            for (int next : arr.get(cur)) {
                if (!visited[next]) {
                    visited[next] = true;
                    result++;
                    q.add(new int[]{next, dep + 1});
                }
            }
        }
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바] BOJ_14888_연산자끼워넣기_S1]]></title>
            <link>https://velog.io/@kkimdy_12/%EB%B0%B1%EC%A4%80-BOJ14888%EC%97%B0%EC%82%B0%EC%9E%90%EB%81%BC%EC%9B%8C%EB%84%A3%EA%B8%B0S1</link>
            <guid>https://velog.io/@kkimdy_12/%EB%B0%B1%EC%A4%80-BOJ14888%EC%97%B0%EC%82%B0%EC%9E%90%EB%81%BC%EC%9B%8C%EB%84%A3%EA%B8%B0S1</guid>
            <pubDate>Tue, 02 Dec 2025 04:41:01 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크"><a href="https://www.acmicpc.net/problem/14888">문제 링크</a></h1>
<h2 id="접근-방식">접근 방식</h2>
<ul>
<li>숫자 조합 + 연산자 조합을 일단 만들어서</li>
<li>그걸 계산한 후에</li>
<li>Math.max, Math.min으로 계산하기
이렇게 3단계로 나눠야겠다고 생각했다.</li>
</ul>
<h2 id="헷갈렸던-부분">헷갈렸던 부분</h2>
<h3 id="1-숫자-조합과-연산자-조합-따로인줄">1. 숫자 조합과 연산자 조합 따로인줄</h3>
<p>그래서 dfs 메소드를 두개로 나눠서 이걸 어떻게 조합하지..? StringBuilder? 이러고 헤맸는데 알고보니 dfs 안에서 백트래킹을 이용하면 다 해결가능했던 것..</p>
<ul>
<li><p>숫자 조합은 매번 계산해서 결과 값을 넘겨주면 되고 </p>
</li>
<li><p>연산자는 아래처럼 재귀를 사용하면 모든 경우 조합 가능하다</p>
<pre><code class="language-java">        for (int i = 0; i &lt; 4; i++) {
          if (operator[i] &gt; 0) {
              operator[i]--;

              int next = calculate(currentValue, arr[depth], i);

              dfs(depth + 1, next);
              operator[i]++; // 백트래킹
          }
      }</code></pre>
</li>
</ul>
<h3 id="2-넘겨주는-값-확인-잘하기">2. 넘겨주는 값 확인 잘하기</h3>
<ul>
<li>dfs(1, arr[0])<ul>
<li>위 부분을 dfs(0, 0)으로 잘못 넣고 있어서 매우 이상한 값이 답으로 나옴</li>
<li>애초에 처음부터 arr[0]을 결과값으로 넣으면 되기 때문에 <strong>arr[0]을 currentValue로 넣고</strong>  </li>
<li>depth도 1부터 시작해야 N까지 탐색 가능하다.<ul>
<li>0부터 N-1이면 N번 탐색은 가능하지만 arr[N-1] 탐색이 안되기 때문에 안됨!</li>
<li>아래 calculate 메소드 넘겨줄 때 <code>arr[depth]</code> 넘겨주니까~ 주의</li>
</ul>
</li>
</ul>
</li>
<li>calculate(int sum, int num, int op)<ul>
<li>여기도 처음에 op이 아니라 operator[i]를 넘기고 있었다... 걍 생각없이 매개변수 넘기는 듯.. 주의하기</li>
</ul>
</li>
</ul>
<h3 id="3-나눗셈-간단하게-구현하기">3. 나눗셈 간단하게 구현하기</h3>
<pre><code class="language-java">          if (op == 3) {
            if (sum &lt; 0 &amp;&amp; num &lt; 0) {
                sum *= -1;
                num *= -1;
                sum /= num;
            }
            else if (sum &lt; 0) {
                sum *= -1;
                sum /= num;
                sum *= -1;
            }
            else if (num &lt; 0) {
                num *= -1;
                sum /= num;
                sum *= -1;
            }
            else {
                sum /= num;
            }</code></pre>
<ul>
<li>음수일 경우를 판단하느라 노.가.다로 풀었는데 알고보니 아래 코드면 해결된다고 한다..</li>
<li>지금 보니 위 코드에서 num &lt; 0은 왜 확인한거지;;<pre><code class="language-java">      if (op == 3) {
        if (sum &lt; 0) return - ( Math.abs(sum) / num );
        else return sum / num;
    }</code></pre>
</li>
</ul>
<h1 id="정답-코드">정답 코드</h1>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class BOJ_14888_연산자끼워넣기_S1 {
    static int N;
    static int[] arr;
    static int[] operator = new int[4]; // +, -, x, ÷
    static int maxAnswer = Integer.MIN_VALUE;
    static int minAnswer = Integer.MAX_VALUE;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        N = Integer.parseInt(br.readLine());
        arr = new int[N];

        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; N; i++)
            arr[i] = Integer.parseInt(st.nextToken());
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; 4; i++)
            operator[i] = Integer.parseInt(st.nextToken());

        // 숫자조합
        // 연산자조합
        //index: arr에서 현재 몇 번째 숫자를 사용할지
        //currentValue: 앞까지 계산된 결과
        dfs(1, arr[0]);
        // 그 조합끼리 계산

        System.out.println(maxAnswer);
        System.out.println(minAnswer);

    }

    public static void dfs(int depth, int currentValue) {
        if (depth == N) {
            maxAnswer = Math.max(currentValue, maxAnswer);
            minAnswer = Math.min(currentValue, minAnswer);
            return;
        }

        for (int i = 0; i &lt; 4; i++) {
            if (operator[i] &gt; 0) {
                operator[i]--;

                int next = calculate(currentValue, arr[depth], i);

                dfs(depth + 1, next);
                operator[i]++; // 백트래킹
            }
        }

    }

    public static int calculate(int sum, int num, int op) {
        if (op == 0)
            sum += num;
        if (op == 1)
            sum -= num;
        if (op == 2)
            sum *= num;
        if (op == 3) {
            if (sum &lt; 0) return - ( Math.abs(sum) / num );
            else return sum / num;
        }

        return sum;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바] BOJ_15652_N과M(4)_S3]]></title>
            <link>https://velog.io/@kkimdy_12/%EB%B0%B1%EC%A4%80-BOJ15652N%EA%B3%BCM4S3</link>
            <guid>https://velog.io/@kkimdy_12/%EB%B0%B1%EC%A4%80-BOJ15652N%EA%B3%BCM4S3</guid>
            <pubDate>Mon, 01 Dec 2025 16:27:19 GMT</pubDate>
            <description><![CDATA[<h1 id="🧩-boj-15652--n과-m-4"><a href="https://www.acmicpc.net/problem/15652">🧩 BOJ 15652 — N과 M (4)</a></h1>
<p>중복 조합(Combination with Repetition)</p>
<hr>
<h2 id="📌-문제-요약">📌 문제 요약</h2>
<p>N과 M이 주어졌을 때,</p>
<ul>
<li>1부터 N까지 자연수 중에서</li>
<li>중복을 허용해서</li>
<li>비내림차순(오름차순 유지)으로</li>
<li>길이가 M인 수열을 모두 출력</li>
</ul>
<p>예:
N=4, M=2라면</p>
<pre><code>1 1
1 2
1 3
1 4
2 2
2 3
2 4
3 3
3 4
4 4</code></pre><p>이런 식의 &quot;중복 조합&quot; 형태가 나온다.</p>
<hr>
<h2 id="핵심-개념-정리">핵심 개념 정리</h2>
<p>✔ 왜 start 파라미터가 필요할까?</p>
<p>중복은 허용하지만
<strong>비내림차순(이전 값보다 작으면 안 됨)</strong> 을 유지해야 한다.</p>
<p>즉,</p>
<ul>
<li>이전에 선택한 값(i)을 다음 depth에서도 <strong>최소값으로 유지</strong>해야
오름차순이 유지됨</li>
<li>그래서 <code>for (int i = start; i &lt;= N; i++)</code> 로 시작</li>
</ul>
<p><code>start</code> 가 핵심이다.</p>
<hr>
<h2 id="흐름-시각화---헷갈렸음">흐름 시각화 - 헷갈렸음..</h2>
<p>예시: <code>N=3, M=2</code></p>
<h3 id="depth0-start1">depth=0, start=1</h3>
<pre><code>dfs(0,1)
 └ i=1 → arr[0]=1
       → dfs(1,1)
           └ i=1 → arr[1]=1 → dfs(2,1) → 출력: 1 1
           └ i=2 → arr[1]=2 → 출력: 1 2
           └ i=3 → arr[1]=3 → 출력: 1 3
           └ i=4 → arr[1]=4 → 출력: 1 4
 └ i=2 …</code></pre><hr>
<h1 id="시간-복잡도">시간 복잡도</h1>
<h3 id="전체-탐색-개수">전체 탐색 개수</h3>
<p>중복 조합의 개수는
<img src="https://velog.velcdn.com/images/kkimdy_12/post/954d0c61-2705-4d49-bb12-7d11905771fa/image.png" alt=""></p>
<p>N,M ≤ 8 이므로 최대 약 6435개 정도.</p>
<h3 id="한-수열을-만드는-비용">한 수열을 만드는 비용</h3>
<p>배열 8개 채우는 수준 → O(M)</p>
<h3 id="🔥-전체-시간">🔥 전체 시간</h3>
<p>6천 × 8
= 약 <strong>5만 연산</strong></p>
<p>➡ Java에서는 0.001초 수준
➡ 시간초과 걱정 전혀 없음</p>
<hr>
<h1 id="✅-최종-코드">✅ 최종 코드</h1>
<pre><code class="language-java">package algo.ct.M11;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class BOJ_15652_N과M4_S3 {
    static int[] arr;
    static int N, M;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        arr = new int[M];

        dfs(0, 1);
    }

    public static void dfs(int depth, int start) {
        if (depth == M) {
            for (int i = 0; i &lt; M; i++)
                System.out.print(arr[i] + &quot; &quot;);
            System.out.println();
            return;
        }

        for (int i = start; i &lt;= N; i++) {
            arr[depth] = i;
            dfs(depth + 1, i);
        }
    }
}</code></pre>
<hr>
<h1 id="헷갈렸던-판단-부분">헷갈렸던 판단 부분</h1>
<ul>
<li>start 값을 어디서부터 해야할지..</li>
<li>백트래킹인데 arr 되돌리는 코드 필요 없는 이유 -&gt; 덮어쓰기라서</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바] BOJ_2667_단지번호붙이기_S1]]></title>
            <link>https://velog.io/@kkimdy_12/%EB%B0%B1%EC%A4%80-BOJ2667%EB%8B%A8%EC%A7%80%EB%B2%88%ED%98%B8%EB%B6%99%EC%9D%B4%EA%B8%B0S1</link>
            <guid>https://velog.io/@kkimdy_12/%EB%B0%B1%EC%A4%80-BOJ2667%EB%8B%A8%EC%A7%80%EB%B2%88%ED%98%B8%EB%B6%99%EC%9D%B4%EA%B8%B0S1</guid>
            <pubDate>Mon, 01 Dec 2025 16:06:16 GMT</pubDate>
            <description><![CDATA[<h1 id="🏘️-boj-2667-단지번호붙이기"><a href="https://www.acmicpc.net/problem/2667">🏘️ BOJ 2667 단지번호붙이기</a></h1>
<h2 id="문제-요약">문제 요약</h2>
<p>N×N 지도에서</p>
<ul>
<li><strong>1 → 집이 있는 곳</strong></li>
<li><strong>0 → 집이 없는 곳</strong></li>
</ul>
<p>상하좌우로 연결된 집들은 하나의 <strong>단지</strong>를 이룬다.</p>
<p>해야 할 일:</p>
<ol>
<li>전체 단지 개수를 구하고</li>
<li>각 단지의 집 수를 오름차순으로 출력</li>
</ol>
<hr>
<h2 id="핵심-개념">핵심 개념</h2>
<p>이 문제의 구조는 전형적인 <strong>2차원 BFS/DFS 영역 탐색</strong> 문제이다.</p>
<h3 id="단지를-찾기-위해-필요한-조건">단지를 찾기 위해 필요한 조건</h3>
<ul>
<li>인접한 1들이 연결된 “덩어리” → <strong>영역 탐색 문제</strong></li>
<li>문제에서 “상하좌우&quot;만 연결이라고 명시 → 대각선 X</li>
<li>단지마다 개수를 세어야 함 → BFS(또는 DFS)에서 count 증가</li>
</ul>
<h3 id="bfs가-필요한-이유">BFS가 필요한 이유</h3>
<ul>
<li>큐를 이용해 <strong>현재 위치에서 갈 수 있는 모든 ‘연결된 집’을 탐색</strong></li>
<li>방문 체크로 중복 방문 방지</li>
<li>모든 단지를 탐색하기 위해 전체 map(N×N)을 훑으면서 BFS 시작 조건을 찾음</li>
</ul>
<hr>
<h2 id="시간-복잡도-분석">시간 복잡도 분석</h2>
<p>지도 크기</p>
<ul>
<li>N ≤ 25</li>
<li>최대 칸 수: 25×25 = 625</li>
</ul>
<p>모든 칸을 한 번씩만 방문한다.</p>
<p>💡 BFS 1회 시간</p>
<p>최대 625칸 → O(N²)</p>
<p>💡 BFS 시작점은 단지별로 다수일 수 있음</p>
<p>하지만 <strong>방문 체크</strong> 때문에
전체적으로 모든 칸은 단 한 번만 큐에 들어간다.</p>
<p>💡 전체 시간 복잡도</p>
<p>O(N²) = O(625)</p>
<p><strong>연산량이 매우 적어서 Java·Python 모두 충분히 여유롭다.</strong></p>
<hr>
<h2 id="풀이-흐름-정리">풀이 흐름 정리</h2>
<ol>
<li><p>입력을 받아 arr[][]에 저장</p>
</li>
<li><p>visited[][]로 방문 처리</p>
</li>
<li><p>(i, j)를 전체 순회하면서</p>
<ul>
<li>아직 방문하지 않고</li>
<li>arr[i][j] == 1인 지점이라면
→ 새 단지 시작 → BFS 실행</li>
</ul>
</li>
<li><p>BFS에서 연결된 모든 집 카운트</p>
</li>
<li><p>단지 수 + 단지 내 집 수 기록</p>
</li>
<li><p>오름차순 정렬 후 출력</p>
</li>
</ol>
<hr>
<h2 id="🔍-bfs-템플릿-핵심">🔍 BFS 템플릿 핵심</h2>
<pre><code class="language-java">Queue&lt;int[]&gt; q = new LinkedList&lt;&gt;();
q.add(new int[]{x, y});
visited[x][y] = true;

while (!q.isEmpty()) {
    int[] cur = q.poll();

    for (4방향) {
        좌표 유효성 검사
        1인지 검사
        방문했는지 검사
        → 조건 통과 시 q.add()
        → visited = true
        → count++
    }
}</code></pre>
<hr>
<h1 id="✅-최종-코드">✅ <strong>최종 코드</strong></h1>
<pre><code class="language-java">package algo.ct.M11;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class BOJ_2667_단지번호붙이기_S1 {
    static int N;
    static boolean[][] visited;
    static int[][] arr;
    static int[] nx = {-1, 1, 0, 0};   // 상하좌우
    static int[] ny = {0, 0, -1, 1};
    static ArrayList&lt;Integer&gt; answer = new ArrayList&lt;&gt;();

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        N = Integer.parseInt(br.readLine());
        arr = new int[N][N];
        visited = new boolean[N][N];

        // 지도 입력
        for (int i = 0; i &lt; N; i++) {
            String line = br.readLine();
            for (int j = 0; j &lt; N; j++) {
                arr[i][j] = line.charAt(j) - &#39;0&#39;;
            }
        }

        // 전체 순회하며 단지 시작점 찾기
        for (int i = 0; i &lt; N; i++) {
            for (int j = 0; j &lt; N; j++) {
                if (arr[i][j] == 1 &amp;&amp; !visited[i][j]) {
                    int cnt = bfs(i, j);
                    answer.add(cnt);
                }
            }
        }

        Collections.sort(answer);
        System.out.println(answer.size());
        for (int cnt : answer)
            System.out.println(cnt);
    }

    // BFS로 단지 하나의 집 개수 세기
    public static int bfs(int x, int y) {
        visited[x][y] = true;
        Queue&lt;int[]&gt; q = new LinkedList&lt;&gt;();
        q.add(new int[]{x, y});
        int count = 1;

        while (!q.isEmpty()) {
            int[] cur = q.poll();
            int curX = cur[0];
            int curY = cur[1];

            for (int i = 0; i &lt; 4; i++) {
                int nextX = curX + nx[i];
                int nextY = curY + ny[i];

                if (nextX &lt; 0 || nextY &lt; 0 || nextX &gt;= N || nextY &gt;= N) continue;
                if (visited[nextX][nextY]) continue;
                if (arr[nextX][nextY] == 0) continue;

                visited[nextX][nextY] = true;
                q.add(new int[]{nextX, nextY});
                count++;
            }
        }

        return count;
    }
}</code></pre>
<hr>
<h1 id="💡-헷갈렸던-판단-부분">💡 헷갈렸던 판단 부분</h1>
<ul>
<li><p>BFS 반복문 안에서 count 증가하는 타이밍은? → q에 추가될 때 (= 실제로 방문할 때)</p>
</li>
<li><p>4방향 탐색은 while 문 안에 !!</p>
</li>
<li><p>시간복잡도!!!
📌 “총 반복 횟수 × 한 반복에서의 처리 비용”
그 결과가,</p>
<blockquote>
<p>🟢 1천만(10⁷) 이하 → 매우 여유로움
🟡 1억(10⁸) 정도 → borderline (언어 따라 통과/시간초과 나뉨)
🔴 10억(10⁹) 이상 → 대부분 시간초과</p>
</blockquote>
<p>✔ <code>단지번호붙이기</code> 같은 경우..
N ≤ 25 → N² = 625
여기서 BFS·DFS는 각 칸을 최대 한 번 방문</p>
<p>➤ 전체 연산량: 최대 625번
O(N²) = 625</p>
<p>625는 자바 기준으로
1억 연산 중 0.0006% 정도밖에 안 됨.</p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>