<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>se_ri.log</title>
        <link>https://velog.io/</link>
        <description>꾸준히 정진하며 나아가기</description>
        <lastBuildDate>Mon, 25 Aug 2025 02:53:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>se_ri.log</title>
            <url>https://velog.velcdn.com/images/se_ize/profile/426f756f-19b5-441f-86ec-9094d7510056/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. se_ri.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/se_ize" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Java&Spring 면접 준비] Day 7 - Spring 기초]]></title>
            <link>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-6-Spring-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-6-Spring-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Mon, 25 Aug 2025 02:53:23 GMT</pubDate>
            <description><![CDATA[<h3 id="1-dto와-vo의-차이와-각각-구현된-메소드가-무엇인가요">1. DTO와 VO의 차이와 각각 구현된 메소드가 무엇인가요?</h3>
<p>DTO는 말 그대로 데이터를 전달하기 위한 객체로, 주로 계층 간 데이터 교환에 사용됩니다. 그래서 가능하면 비즈니스 로직은 넣지 않고 getter, setter, toString 정도만 구현합니다.</p>
<p>반면에 VO는 값 자체를 표현하는 객체라서 불변성을 유지하는 것이 중요합니다. 그래서 생성자를 통해서만 값을 설정하고 변경 메서드는 두지 않으며, 값의 동등성을 보장하기 위해 equals와 hashCode를 구현하는 것이 일반적입니다. </p>
<p>즉, DTO는 단순히 데이터를 옮기기 위한 수단이고, VO는 값 자체를 의미 있게 다루기 위한 객체라는 차이가 있습니다.</p>
<h3 id="2-dto를-entity와-직접-매핑할-때-주의할-점은-무엇인가요">2. DTO를 Entity와 직접 매핑할 때 주의할 점은 무엇인가요?</h3>
<p>DTO와 엔티티를 직접 매핑할 때는 몇 가지 주의할 점이 있습니다. 
먼저, 엔티티를 외부에 직접 노출하면 영속성 정보나 내부 구조가 드러나기 때문에 보안상 위험할 수 있고, 영속성 컨텍스트에도 불필요하게 영향을 줄 수 있습니다.</p>
<p>그래서 반드시 DTO를 별도로 만들어 검증을 거친 뒤 매핑하는 게 좋습니다. </p>
<p>또 지연 로딩으로 인한 N+1 문제가 발생하지 않도록 fetch join이나 전용 조회 DTO를 사용하는 방식으로 최적화해야 합니다.</p>
<p>마지막으로 업데이트 시에는 허용된 필드만 매핑해서 불변 필드나 식별자가 바뀌지 않도록 주의하는 것이 중요합니다.</p>
<h3 id="3-di가-무엇이고-di는-어떤-방식으로-이루어지나요">3. DI가 무엇이고, DI는 어떤 방식으로 이루어지나요?</h3>
<p>의존성 주입은 객체가 필요한 의존성을 직접 생성하지 않고 외부에서 주입받는 개념입니다. 
이렇게 하면 객체 간 결합도가 낮아지고, 테스트나 확장성이 좋아집니다. </p>
<p>스프링에서는 보통 생성자 주입, 세터 주입, 그리고 필드 주입 세 가지 방법을 지원하는데, 일반적으로는 생성자 주입을 권장합니다. </p>
<p>왜냐하면 생성자 주입은 의존성을 강제로 주입하도록 강제할 수 있고, final 키워드로 불변성을 보장할 수 있기 때문입니다.</p>
<h3 id="4-생성자-주입이-권장되는-이유는-무엇인가요">4. 생성자 주입이 권장되는 이유는 무엇인가요?</h3>
<p>생성자 주입이 권장되는 이유는 여러 가지가 있습니다. 
우선 생성 시점에 필요한 의존성을 강제로 주입할 수 있어서 필수 의존성이 누락되는 상황을 막을 수 있고, final을 사용할 수 있어서 불변성이 보장됩니다.</p>
<p>또, 순환 참조가 발생하면 애플리케이션 구동 시점에 바로 오류가 나기 때문에 문제를 빨리 찾을 수 있고, 테스트 코드 작성도 더 수월합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java&Spring 면접 준비] Day 6 - 프로젝트 관련]]></title>
            <link>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-6-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B4%80%EB%A0%A8</link>
            <guid>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-6-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B4%80%EB%A0%A8</guid>
            <pubDate>Tue, 19 Aug 2025 01:20:32 GMT</pubDate>
            <description><![CDATA[<h3 id="1-oms-시스템-개발-시-프론트엔드와-백엔드-중-더-어려웠던-점은-무엇이었나요-어떻게-해결했나요">1. OMS 시스템 개발 시 프론트엔드와 백엔드 중 더 어려웠던 점은 무엇이었나요? 어떻게 해결했나요?</h3>
<p>저는 입사하고 React를 처음 접하고 공부하면서 개발했기때문에 프론트엔드가 더 어려웠습니다. 특히 <strong>컴포넌트가 불필요하게 리렌더링되면서 데이터 저장이 제대로 되지 않는 문제</strong>였습니다.</p>
<p>예를 들어, 주문 수정 화면에서 편집 후 저장하면, 상위 컴포넌트의 state 변경으로 리렌더링되면서 <strong>편집 중이던 데이터가 초기화</strong>되는 이슈가 있었습니다.</p>
<p>이를 해결하기 위해 <strong>React.memo와 useCallback</strong>을 적용했습니다.</p>
<ul>
<li><code>React.memo</code>를 통해 props가 실제로 변경된 컴포넌트만 리렌더링하도록 막았고,</li>
<li>이벤트 핸들러 함수는 <code>useCallback</code>으로 메모이제이션해 매번 새로운 함수 참조로 인한 리렌더링을 방지했습니다.</li>
</ul>
<p>결과적으로 편집 중 데이터가 사라지는 문제가 해결할 수 있었습니다.</p>
<h3 id="2-신규-이커머스-연동-과정에서-api-연동-시-발생했던-대표적인-이슈와-해결-방법은">2. 신규 이커머스 연동 과정에서 API 연동 시 발생했던 대표적인 이슈와 해결 방법은?</h3>
<p>대표적인 이슈는 API 스펙 불일치와 예외 데이터 처리였습니다.</p>
<p>일부 이커머스 플랫폼은 상태 코드나 응답 필드가 다르게 내려왔습니다.
예를 들어, 이커머스 플랫폼마다 주문 상태 코드가 달라 “결제 완료” 상태가 불일치했습니다. 
저는 StatusMapper라는 <strong>매핑 계층을 만들어 외부 상태 값을 내부 표준 상태 값으로 변환</strong>해 처리했습니다.</p>
<p>또, 일부 플랫폼에서 필수값(예: 수취인 연락처)이 누락된 주문 데이터가 내려오는 문제가 있었습니다. 
우선적으로 주문자 연락처를 수취인 연락처로 대체 저장하도록 처리했습니다.
이렇게 하면 데이터 유실 없이 정상 주문 프로세스를 이어갈 수 있고, 운영자는 이후 로그나 에러 큐 알림을 통해 해당 주문을 확인해 실제 수취인 연락처를 보정할 수 있었습니다.</p>
<p>즉, 시스템은 멈추지 않고 안정적으로 처리하되, 운영 단계에서 추후 정정할 수 있는 구조로 설계했습니다.
이런 방식으로 API 연동의 안정성을 확보할 수 있었습니다.</p>
<h3 id="3-대용량-엑셀-다운로드-기능-구현-시-비동기-처리와-스트리밍-처리를-함께-적용한-이유는-무엇인가요">3. 대용량 엑셀 다운로드 기능 구현 시, 비동기 처리와 스트리밍 처리를 함께 적용한 이유는 무엇인가요?</h3>
<p>두 가지 이유가 있었습니다.</p>
<ol>
<li><strong>비동기 처리</strong>는 사용자가 다운로드 요청 후 브라우저가 멈추지 않고, 작업이 완료되면 알림을 받을 수 있도록 하기 위함이었습니다.</li>
<li><strong>스트리밍 처리</strong>는 10만 건 이상 데이터를 한 번에 메모리에 올리면 OOM이 발생했기 때문에, 데이터를 청크 단위로 끊어서 처리하기 위함이었습니다.</li>
</ol>
<p>즉, 비동기로 사용자 경험을 개선하고, 스트리밍으로 시스템 안정성을 확보한 것이 핵심입니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-17만-건-처리-시-메모리-사용량을-70로-안정화했다고-했는데-메모리-최적화-과정에서-고려한-다른-방법은-있었나요">→ 17만 건 처리 시 메모리 사용량을 70%로 안정화했다고 했는데, 메모리 최적화 과정에서 고려한 다른 방법은 있었나요?</h4>
<p>네, 여러 가지 방법을 검토했습니다. 예를 들어,</p>
<ul>
<li><p><strong>쿼리 최적화</strong>: 필요한 컬럼만 select 하도록 projection을 최소화했습니다.</p>
</li>
<li><p><strong>파일 분할 다운로드</strong>: 65,000행 단위로 xlsx 파일을 분할하고, 여러 개일 경우 zip으로 묶어 제공했습니다.</p>
</li>
<li><p><strong>Heap 증설보다는 코드 최적화</strong>: 단순히 서버 메모리를 늘리는 대신, BlockingQueue와 생산자-소비자 패턴을 적용해 메모리 사용량을 제어했습니다.</p>
<p>  결과적으로 시스템 자원 증설보다 <strong>구조적인 최적화</strong>를 택해 안정성을 확보했습니다.</p>
<h3 id="4-wms와-연동된-주문-등록-기능-개발에서-시스템-간-연동-시-고려한-주요-포인트는-무엇이었나요">4. WMS와 연동된 주문 등록 기능 개발에서 시스템 간 연동 시 고려한 주요 포인트는 무엇이었나요?</h3>
<p>가장 중요하게 본 건 두 가지입니다.</p>
</li>
<li><p><strong>데이터 정합성</strong>: 주문이 OMS에서 WMS로 전달된 후, 정상적으로 등록/처리되었는지 이중 확인 로직을 넣었습니다.</p>
<ul>
<li>1차로 API 응답 코드를 확인하고, 2차로는 WMS DB나 조회 API를 호출해 실제 주문이 정상적으로 생성되었는지 검증했습니다. 
만약 응답은 성공이지만 DB에 데이터가 반영되지 않은 경우, 재시도 프로세스나 알림을 통해 운영자가 확인할 수 있도록 했습니다. 
덕분에 데이터 정합성을 보장하고, 주문 유실 가능성을 최소화했습니다.</li>
</ul>
</li>
<li><p><strong>성능</strong>: OMS에서 대량 주문을 일정 건수로 분할해 멀티스레드로 병렬 처리하도록 설계했습니다. 특정 청크가 실패하면 해당 부분만 재처리할 수 있어 운영 안정성도 높일 수 있었습니다.</p>
<ul>
<li>사실 당시에는 메시지 큐 도입을 검토했지만, WMS가 실시간 API 호출 기반으로만 동작하는 구조때문에 적용이 어려웠습니다.
대신 대량 주문 배치 트래픽이 몰리는 경우를 대비해 청크 단위 비동기 처리 방식을 도입했습니다. 예를 들어, 주문 데이터를 일정 건수(1000건)로 나누고, 각 청크를 별도의 스레드에서 병렬 처리하도록 구현했습니다.
또한 청크 단위 상태를 관리해서 실패한 청크만 재처리할 수 있도록 했습니다.
덕분에 단일 쓰레드 순차 처리 대비 처리 속도가 크게 개선됐고, 전체 배치가 중단되지 않고 안정적으로 마무리될 수 있었습니다.<h3 id="5-풀스택-개발-경험을-바탕으로-물류시스템-운영개발-업무에서-어떻게-기여할-수-있을지-구체적으로-말해보세요">5. 풀스택 개발 경험을 바탕으로, 물류시스템 운영/개발 업무에서 어떻게 기여할 수 있을지 구체적으로 말해보세요.</h3>
저는 프론트엔드와 백엔드 모두 경험했기 때문에, 단일 영역이 아닌 엔드투엔드 관점에서 문제를 해결할 수 있는 점이 강점입니다. 예를 들어, 물류시스템에서 주문 조회 속도가 느리다면, API 쿼리 튜닝부터 화면 최적화까지 전 과정에서 개선안을 제시할 수 있습니다.</li>
</ul>
</li>
</ul>
<p>또한 이커머스 API 연동 경험을 통해 <strong>외부 시스템과 안정적으로 연계하는 방법</strong>을 알고 있고, 대용량 엑셀 처리 경험을 통해 <strong>대규모 데이터 처리 및 성능 최적화 역량</strong>도 갖추었습니다.</p>
<p>단순 운영을 넘어 <strong>시스템 성능과 사용자 경험을 동시에 개선하는 개발자</strong>로 기여하고 싶습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java&Spring 면접 준비] Day 5 - 시스템 운영 및 최적화 ]]></title>
            <link>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-5-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%9A%B4%EC%98%81-%EB%B0%8F-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-5-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%9A%B4%EC%98%81-%EB%B0%8F-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Sat, 16 Aug 2025 09:58:59 GMT</pubDate>
            <description><![CDATA[<h3 id="1-oms-시스템에서-처리하신-주문-데이터는-어떤-방식으로-안정성을-확보했나요">1. OMS 시스템에서 처리하신 주문 데이터는 어떤 방식으로 안정성을 확보했나요?</h3>
<p>OMS는 일 평균 3만 건 이상의 주문 데이터를 처리하는 핵심 시스템이라, 안정성을 위해 API 요청·응답 로깅과 예외 처리 로직을 전 구간에 적용했습니다.</p>
<p>또한 Service 계층에 트랜잭션과 예외 처리 로직을 적용해 데이터 정합성을 보장했고, 주요 쿼리는 인덱싱과 쿼리 최적화를 통해 병목을 최소화했습니다.</p>
<p>추가로 AWS CloudWatch와 Datadog 모니터링을 적용해 장애 조기 감지와 대응이 가능하게 했습니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-api-요청·응답-로깅은-어떻게-설계했나요-성능-저하는-없었나요">→ API 요청·응답 로깅은 어떻게 설계했나요? 성능 저하는 없었나요?</h4>
<p>API 요청/응답 로깅은 AOP 기반 Interceptor로 구현해 전 구간에 적용했습니다.
실제 응답 바디 전체를 저장하는 대신, 핵심 필드와 오류 메시지만 로그 테이블에 저장해서 저장 공간과 성능 부하를 최소화했습니다.
또한 로그 보존 기간 정책을 둬 운영 환경에 부담이 가지 않도록 설계했습니다.</p>
<h3 id="2-실서비스-운영-중-발생한-트러블-상황과-해결-경험이-있나요">2. 실서비스 운영 중 발생한 트러블 상황과 해결 경험이 있나요?</h3>
<p>이커머스 플랫폼 연동 중 주문 데이터 필드에 이모티콘이 포함되어 파싱 과정에서 예외가 발생했고, 시스템이 멈춘 적이 있었습니다.
운영 모니터링 알람을 받고 즉시 로그를 분석해 원인을 확인했고, 해당 필드를 유니코드 호환 처리가 가능한 컬럼 타입으로 변경해 문제를 해결했습니다.</p>
<p>이후 사전 검증 로직을 Controller 레이어에서 추가해, 특수문자나 DB 호환 불가능한 문자가 들어오면 사전에 필터링했습니다.
추가로 DB 스키마 설계 시 utf8mb4를 적용해 유니코드 전체를 지원하도록 했고, API 연동 시에도 공통 Validation 모듈을 만들어 재사용성을 높였습니다.</p>
<h3 id="3-주문-목록-조회-속도를-0002초로-개선했다고-했는데-구체적으로-어떤-인덱스를-설정하셨나요">3. 주문 목록 조회 속도를 0.002초로 개선했다고 했는데, 구체적으로 어떤 인덱스를 설정하셨나요?</h3>
<p>기존 단일 인덱스로 주문일자로만 걸었었습니다. , 쿼리 실행 계획(EXPLAIN) 결과 full scan이 발생하던 걸 확인했습니다.</p>
<p>주문 목록 조회는 일자 + 상태 조건이 자주 사용됐기 때문에, <code>created_at</code>, <code>status</code> 컬럼에 복합 인덱스를 생성했습니다. 이후 인덱스 range scan으로 바뀐 걸 확인했고, 실제 응답 속도도 크게 개선됐습니다.</p>
<p>그 결과 주문 1건 당 응답속도를 기존 0.042초에서 0.002초로 단축했습니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-응답-속도-테스트는-배포-전에-어떻게-측정하셨나요">→ 응답 속도 테스트는 배포 전에 어떻게 측정하셨나요?</h4>
<p>응답 속도 테스트는 운영 환경과 최대한 동일한 조건에서 진행하기 위해 운영 DB 스키마와 동일하게 구성한 Stage 서버에서 진행했습니다.</p>
<p>운영 데이터를 그대로 가져오지는 않고, 주기적으로 익명화된 더미 데이터를 생성하거나, 운영 데이터에서 주요 패턴(주문 건수, 상태 분포, 기간 분포 등)을 반영한 샘플 데이터를 생성해습니다. 이후 브라우저 개발자 도구의 Network 탭을 활용해 응답 속도를 직접 측정했습니다.</p>
<p>특히 조회 API 호출 시 request start → response end까지 걸린 시간을 확인했고, 기존에는 평균 42ms 이상 소요되던 것이 인덱스 최적화 후 약 2ms 수준으로 단축된 것을 검증했습니다.</p>
<p>이후에는 동일 조건에서 여러 번 반복 호출해 평균 속도를 확인했고, 페이지네이션 조건(날짜, 상태 필터)을 바꿔도 일관된 성능 개선이 나타나는 것을 확인했습니다. 실제 운영 배포 후에도 성능 차이가 거의 없음을 확인했습니다.</p>
<h4 id="→-인덱스를-잘못-설정했을-때-생길-수-있는-문제는-무엇인가요">→ 인덱스를 잘못 설정했을 때 생길 수 있는 문제는 무엇인가요?</h4>
<p>불필요한 인덱스는 DML 성능을 저하시킬 수 있고, 옵티마이저가 잘못된 인덱스를 선택하면 오히려 Full Scan보다 느려질 수 있습니다.
그래서 인덱스 설계 후에는 항상 EXPLAIN으로 실행 계획을 확인했고, 주기적으로 Slow Query Log를 모니터링하며 실제 사용 빈도가 낮은 인덱스는 제거했습니다.</p>
<h3 id="4-대용량-엑셀-다운로드-구현-시-어떤-문제상황이-있었나요">4. 대용량 엑셀 다운로드 구현 시 어떤 문제상황이 있었나요?</h3>
<p>초기에 10만 건 이상 데이터를 한 번에 메모리에 로드하다 보니 OOM이 발생했습니다.</p>
<p>이를 해결하기 위해 JDBC Streaming을 적용해 데이터를 청크 단위로 불러오고, 65,000행 단위로 분할 생성 후 ZIP 압축해 제공했습니다.</p>
<p>또한 BlockingQueue 기반 생산자-소비자 패턴을 도입해 읽기와 쓰기 작업 속도를 균형 있게 맞춰 17만 건 데이터의 엑셀 다운로드 처리 시간을 3분 이내로 줄였습니다.
JDBC Streaming과 청크 단위 분할, BlockingQueue 기반 병렬 처리 덕분에 OOM 없이 안정적으로 동작하게 만들었습니다</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-왜-65000행-단위로-분할했나요">→ 왜 65,000행 단위로 분할했나요?</h4>
<p>기존 Excel 2003의 XLS 포맷은 65,536행 제한이 있었고, Apache POI 라이브러리에서도 이 제약이 남아 있었습니다.
그래서 65,000행 단위로 분할해 여러 개의 XLSX 파일을 생성하고, 이를 ZIP으로 묶어 제공했습니다.
이 방식 덕분에 사용자 측에서도 Excel에서 정상적으로 열 수 있는 구조를 유지할 수 있었습니다.</p>
<h4 id="→-3분이라는-처리-시간이-서비스-요구사항에-충분했나요-더-최적화할-방법이-있을까요">→ 3분이라는 처리 시간이 서비스 요구사항에 충분했나요? 더 최적화할 방법이 있을까요?</h4>
<p>운영 부서와 협의했을 때 3분 내에 다운로드 가능하면 충분하다고 판단했습니다.
추가 최적화를 한다면 파일 자체를 Excel 대신 CSV로 제공해 속도를 더 줄일 수 있을 거라 생각합니다. 다만 사용자 편의성을 고려해 Excel 방식을 유지했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java&Spring 면접 준비] Day 4 - Spring과 API 설계 및 통신]]></title>
            <link>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-4-Spring%EA%B3%BC-API-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%86%B5%EC%8B%A0</link>
            <guid>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-4-Spring%EA%B3%BC-API-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%86%B5%EC%8B%A0</guid>
            <pubDate>Fri, 08 Aug 2025 06:44:00 GMT</pubDate>
            <description><![CDATA[<h3 id="1-restful하게-api를-설계할-때-주의해야-할-점은-무엇인가요">1. RESTful하게 API를 설계할 때 주의해야 할 점은 무엇인가요?</h3>
<p>RESTful API를 설계할 때는 <strong>자원을 URI로 명확하게 표현</strong>하고, 행위를 HTTP 메서드로 구분해 <strong>의미에 맞게 사용하는 것</strong>이 중요합니다.</p>
<p>예를 들어, 사용자 정보를 가져올 때는 <code>GET /users/{id}</code>처럼 명사 기반의 URI를 사용하고, <code>POST</code>는 자원 생성, <code>PUT</code>은 전체 수정, <code>PUT</code>는 일부수정, <code>DELETE</code>는 삭제로 사용하는 것이 RESTful한 방식입니다.</p>
<p>또한, URI에는 <strong>동사를 사용하지 않는 것</strong>, 상태 코드는 <strong>의도에 맞게 응답하는 것</strong>, 응답 포맷은 <strong>일관성 있게 JSON 등으로 제공하는 것</strong>이 중요한 포인트입니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-현업에서-patch보다-put을-더-쓰는-이유가-뭘까요">→ 현업에서 PATCH보다 PUT을 더 쓰는 이유가 뭘까요?</h4>
<h4 id="1-구현의-복잡성">1. 구현의 복잡성</h4>
<ul>
<li><code>PATCH</code>는 <strong>부분 수정</strong>이기 때문에, 각 필드가 존재할 때만 업데이트해야 함</li>
<li>이를 위해서는 DTO에서 <strong>null 값을 구분</strong>하거나, <strong>Optional 필드 처리</strong>, <strong>dirty checking</strong> 등을 별도로 구현해야 함</li>
<li>즉, <code>PUT</code>보다 <strong>코드량과 로직 분기가 많아짐</strong></li>
</ul>
<h4 id="2-클라이언트가-불완전한-요청을-보낼-가능성">2. 클라이언트가 불완전한 요청을 보낼 가능성</h4>
<ul>
<li>클라이언트가 잘못된 요청으로 <strong>불필요한 필드를 누락하거나</strong> 이상한 조합으로 보낼 경우 예외 처리 복잡</li>
<li><code>PATCH</code>는 유연하지만, <strong>명확한 구조와 검증이 어려움</strong></li>
</ul>
<h3 id="2-spring에서-클라이언트-요청을-처리하는-방식은-어떻게-되나요">2. Spring에서 클라이언트 요청을 처리하는 방식은 어떻게 되나요?</h3>
<p>Spring MVC에서는 클라이언트 요청이 들어오면 <strong>DispatcherServlet</strong>이 가장 먼저 요청을 받아 처리 흐름을 시작합니다.</p>
<p>DispatcherServlet은 요청 URL에 따라 적절한 <strong>Controller</strong>를 찾고, 그 내부에서 <strong>Service, Repository</strong> 등을 거쳐 비즈니스 로직이 수행됩니다.
처리 결과는 다시 DispatcherServlet으로 전달되어, 필요시 View로 렌더링되거나 JSON 응답으로 클라이언트에 반환됩니다.</p>
<p>즉, 전체 흐름은 <strong>[DispatcherServlet → Controller → Service → Repository → DB] → 응답 처리</strong> 순서로 이루어집니다.</p>
<h3 id="3-spring에서-json-데이터를-주고받기-위해-어떤-방식이나-라이브러리를-사용했나요">3. Spring에서 JSON 데이터를 주고받기 위해 어떤 방식이나 라이브러리를 사용했나요?</h3>
<p>Spring에서는 JSON 데이터를 주고받기 위해 <strong><code>@RequestBody</code>와 <code>@ResponseBody</code></strong>를 주로 사용합니다.</p>
<p>클라이언트로부터 받은 JSON은 <code>@RequestBody</code>를 통해 자바 객체로 변환하고, 서버에서 응답할 객체는 <code>@ResponseBody</code>를 통해 JSON으로 직렬화됩니다.</p>
<p>이 과정은 내부적으로 <strong>Jackson 라이브러리</strong>가 자동으로 처리해주며, Spring Boot에서는 기본적으로 설정돼 있어서 별도의 설정 없이도 편리하게 사용할 수 있었습니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-캐시-저장-시-json-형태를-직접-커스터마이징하거나-직렬화-방식을-변경해본-경험이-있으신가요">→ 캐시 저장 시 JSON 형태를 직접 커스터마이징하거나 직렬화 방식을 변경해본 경험이 있으신가요?&quot;</h4>
<p>Jackson의 ObjectMapper를 활용한 JSON 커스터마이징을 진행했습니다.
<code>@JsonInclude(JsonInclude.Include.NON_NULL)</code>을 이용해 불필요한 null 필드를 제거해 캐시 공간 절약하고, JSON 응답을 간결하게 만들었습니다.
또한 <code>@JsonValue</code>와 <code>@JsonCreator</code>를 사용해 직렬화 방식을 변경해 enum 타입은 사용자 친화적인 값으로 JSON에 노출되도록 했습니다.</p>
<h3 id="4-http-상태-코드-중-2xx-4xx-5xx-은-각각-어떤-의미인가요">4. HTTP 상태 코드 중 2xx, 4xx, 5xx 은 각각 어떤 의미인가요?</h3>
<ul>
<li><strong>2xx (성공)</strong>: 클라이언트의 요청이 성공적으로 처리되었음을 의미합니다. 예를 들어 <code>200 OK</code>, <code>201 Created</code>가 있습니다.</li>
<li><strong>4xx (클라이언트 오류)</strong>: 클라이언트 요청에 문제가 있을 때 사용되며, 예를 들어 <code>400 Bad Request</code>, <code>401 Unauthorized</code>, <code>404 Not Found</code> 등이 있습니다.</li>
<li><strong>5xx (서버 오류)</strong>: 서버 내부에서 처리 중 예기치 못한 문제가 발생했을 때 사용됩니다. 대표적으로 <code>500 Internal Server Error</code>가 있습니다.</li>
</ul>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-상태-코드를-커스텀할때-어떤-식으로-구현하셨나요">→ 상태 코드를 커스텀할때 어떤 식으로 구현하셨나요?</h4>
<p>상태 코드를 enum으로 관리해서 코드 번호, 메시지, 대응하는 HTTP 상태 코드를 한 곳에서 정의했습니다.
예를 들어 <code>ErrorCode.INVALID_INPUT(400, &quot;입력값이 유효하지 않습니다&quot;)</code>처럼 구성해서 공통 응답 포맷에 적용했습니다.</p>
<h4 id="→-상태코드-커스텀할때-고민했던-부분이-있나요">→ 상태코드 커스텀할때 고민했던 부분이 있나요?</h4>
<p>가장 고민했던 부분은 <code>400 Bad Request</code>와 <code>401 Unauthorized</code>의 구분이었습니다.</p>
<p>HTTP 스펙상으로는 비밀번호가 틀렸을 때 401을 반환하는 것이 맞지만,
실무에서는 <strong>401을 사용하면 오히려 “아이디는 맞고 비밀번호만 틀렸다”는 식의 힌트를 공격자에게 줄 수 있다는 보안상 우려</strong>가 있었습니다.</p>
<p>그래서 &quot;아이디 불일치, 비밀번호 불일치, 입력값 오류” 같은 로그인 실패 상황을 모두 400 Bad Request로 통일해서 처리하기로 했습니다.</p>
<p>대신, 내부적으로는 AUTH_001, AUTH_002처럼 에러 코드와 메시지를 분리한 <strong>커스텀 응답 구조를 설계</strong>해서
프론트엔드가 실패 원인을 구분해서 처리할 수 있도록 했습니다.
사용자에게는 항상 <strong>“아이디 또는 비밀번호가 일치하지 않습니다.”</strong>처럼 안전하고 일반적인 메시지만 노출되도록 조정했습니다.</p>
<h4 id="→-refresh-token이-탈취됐을때-어떻게-처리하나요">→ Refresh token이 탈취됐을때 어떻게 처리하나요?</h4>
<ol>
<li><p>Refresh Token 서버 저장 (예: Redis)
Refresh Token은 클라이언트에만 보관하지 않고, 서버(Redis)에 사용자 ID 기준으로 저장합니다.
토큰 재발급 요청 시, 서버에 저장된 토큰과 비교해 일치할 경우에만 발급 처리합니다.</p>
</li>
<li><p>비밀번호 변경 시 Refresh Token 삭제
사용자가 비밀번호를 변경하면, 해당 계정의 모든 Refresh Token을 서버에서 삭제합니다.
이렇게 하면 다른 디바이스에서 로그인 상태라도, 이후 재발급이 불가능해집니다.</p>
</li>
<li><p>Access Token 블랙리스트 처리
Access Token은 stateless해서 원래는 서버에서 제어가 불가능하지만,
탈취가 의심되는 경우에는 해당 토큰을 Redis 블랙리스트에 저장해 차단합니다.</p>
</li>
<li><p>요청 처리 시, 블랙리스트 확인
클라이언트가 Access Token으로 요청할 때, 먼저 Redis에 블랙리스트 여부를 확인하고, 등록된 경우 401 Unauthorized 응답을 반환합니다.</p>
</li>
</ol>
<h3 id="5-spring에서-외부-api-호출이-필요한-경우-어떻게-처리하나요">5. Spring에서 외부 API 호출이 필요한 경우 어떻게 처리하나요?</h3>
<p>Spring에서는 외부 API 호출 시 주로 <strong><code>RestTemplate</code> 또는 <code>WebClient</code></strong>를 사용합니다.</p>
<p>전통적으로는 <strong><code>RestTemplate</code></strong>이 많이 사용되며, 동기 방식으로 간단한 호출에 적합합니다.
반면, <strong><code>WebClient</code></strong>는 Spring 5부터 등장한 비동기 방식의 클라이언트로, <strong>비동기 처리나 리액티브 프로그래밍</strong>이 필요할 때 적합합니다.</p>
<p>실제로 제가 작업했던 프로젝트에서는 <code>RestTemplate</code>을 사용해 API를 호출하고, 응답을 파싱해 클라이언트에 전달하는 기능을 구현한 경험이 있습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java&Spring 면접 준비] Day 3 - Spring 기초 및 JPA
]]></title>
            <link>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-3-Spring-%EA%B8%B0%EC%B4%88-%EB%B0%8F-JPA</link>
            <guid>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-3-Spring-%EA%B8%B0%EC%B4%88-%EB%B0%8F-JPA</guid>
            <pubDate>Tue, 05 Aug 2025 02:41:56 GMT</pubDate>
            <description><![CDATA[<h3 id="1-spring에서-dto와-entity를-분리해서-사용하는-이유는-무엇인가요">1. Spring에서 DTO와 Entity를 분리해서 사용하는 이유는 무엇인가요?</h3>
<p>실무에서 REST API를 개발하면서 항상 DTO와 Entity를 분리해서 사용했습니다. 그 이유는 먼저 Entity는 데이터베이스와 직접 매핑되기 때문에 불필요한 정보가 외부로 노출될 수 있고, 변경 시 의도치 않게 DB 구조까지 영향을 줄 수 있습니다.</p>
<p>반면 DTO는 요청/응답에 맞게 필요한 필드만 포함시키고, 보안적으로도 민감 정보는 제외할 수 있어서 안정적입니다.
예를 들어, 유저 정보를 조회할 때 비밀번호나 권한은 숨기고 이름, 이메일만 반환하는 <code>UserResponseDto</code>를 따로 만들어 사용했었습니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-그럼-dto를-entity로-변환할-땐-어떤-방식-쓰셨어요">→ 그럼 DTO를 Entity로 변환할 땐 어떤 방식 쓰셨어요?</h4>
<p>네, 작은 프로젝트에서는 수동 매핑을 선호했어요. 명시적으로 어떤 필드가 매핑되는지 알 수 있어서요. 대규모 프로젝트에서는 <code>MapStruct</code>도 일부 도입해서 자동화한 적 있습니다.</p>
<hr>
<h3 id="2-spring-data-jpa에서-pageable을-어떻게-사용해보셨나요">2. Spring Data JPA에서 Pageable을 어떻게 사용해보셨나요?</h3>
<p>게시글 목록이나 사용자 활동 이력 같은 리스트 조회 기능에서 자주 사용했습니다.</p>
<p>컨트롤러에서는 <code>@RequestParam</code>으로 page, size, sort 정보를 받고, 서비스에서 <code>PageRequest.of()</code>로 <code>Pageable</code> 객체를 생성해서 Repository에 넘겼습니다.</p>
<p>예를 들어 <code>Page&lt;Post&gt;</code> 형태로 결과를 받아서 <code>getContent()</code>, <code>getTotalPages()</code> 등을 추출해 API 응답 포맷에 맞게 DTO로 가공했습니다.</p>
<h3 id="3-transactional-어노테이션의-역할은-무엇인가요">3. @Transactional 어노테이션의 역할은 무엇인가요?</h3>
<p><code>@Transactional</code>은 트랜잭션 범위를 지정하는 어노테이션으로, 메서드 실행 중 예외가 발생하면 자동으로 롤백시켜줍니다.</p>
<p>실무에선 서비스 계층에 주로 붙여서 사용했고, 특히 여러 Repository가 동시에 변경되는 작업에서 유용했습니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-readonly--true-옵션은-써보셨나요">→ <code>readOnly = true</code> 옵션은 써보셨나요?</h4>
<p>네, 조회 전용 메서드에는 <code>@Transactional(readOnly = true)</code>를 붙이면 성능이 조금 향상되는 걸로 알고 있고, JPA가 더 이상 dirty checking을 하지 않아서 불필요한 리소스를 아낄 수 있었습니다.</p>
<h4 id="→-트랜잭션을-왜-서비스-계층에-걸었나요-컨트롤러나-repository에-걸면-안-되나요">→ 트랜잭션을 왜 서비스 계층에 걸었나요? 컨트롤러나 Repository에 걸면 안 되나요?</h4>
<p>네, 트랜잭션은 <strong>서비스 계층에 걸어야 한다고 생각합니다.</strong> 
왜냐하면 서비스 계층이 실제 비즈니스 로직을 처리하고 여러 DAO나 외부 호출을 조합하는 곳이기 때문입니다.
컨트롤러는 단순히 요청을 전달하는 역할이고, Repository는 DB 단위의 세부작업만 담당하니까, 트랜잭션 책임을 맡기기엔 적절하지 않다고 봅니다.
또한 서비스 계층에 트랜잭션을 설정하면 <strong>하위의 여러 Repository 호출이 하나의 트랜잭션으로 묶여서 처리</strong>되기 때문에, 정합성 유지에도 유리합니다.</p>
<h4 id="→--전체-주문을-하나의-트랜잭션으로-묶었는데-개별-주문마다-트랜잭션을-나누는-방식은-고려-안-해보셨나요">→  전체 주문을 하나의 트랜잭션으로 묶었는데, 개별 주문마다 트랜잭션을 나누는 방식은 고려 안 해보셨나요?</h4>
<p>네, 사실 두 가지 방식을 비교해봤습니다. 
하지만 당시 업무 요구사항에서는 <strong>한 번의 수집 배치에서 모든 주문이 가공·저장돼야 &#39;완전한 단위&#39;로 간주</strong>됐습니다.
그래서 일부 주문만 저장되고, 나머지가 빠지는 상황은 피해야 했습니다.
예를 들어 수집한 50건 중 48건만 저장되면, 다시 수집하거나 보정하는 작업이 더 복잡해집니다.
다만 예외 처리나 재시도 정책이 있다면 개별 트랜잭션도 고려해볼 수 있을 것 같습니다.</p>
<h4 id="→-이-작업은-배치로-동작했는데-트랜잭션이-너무-커지면-성능-문제는-없었나요">→ 이 작업은 배치로 동작했는데, 트랜잭션이 너무 커지면 성능 문제는 없었나요?</h4>
<p>맞습니다. 그래서 한 번에 수백 건씩 처리하지 않고, <strong>청크 단위로 잘라서 트랜잭션을 관리</strong>했습니다.
1000건 단위로 나눠서 수집하고, 청크별로 실패 시 별도 재처리 큐로 넘기는 방식으로 구현했어요.
또한 배치 특성상 사용자와 실시간 인터랙션이 없기 때문에, 어느 정도 트랜잭션 시간이 길어져도 문제는 없었고, DB 연결 시간이나 커넥션 풀 제한만 조심해서 튜닝했습니다.</p>
<h4 id="→-트랜잭션-적용-시-성능-이슈는-없었나요">→ 트랜잭션 적용 시 성능 이슈는 없었나요?</h4>
<p>초기에는 트랜잭션이 너무 길어져서 락이 오래 잡히는 문제가 있었습니다.
그래서 가공이 오래 걸리는 부분은 트랜잭션 외부로 분리했고, DB 저장이 필요한 로직만 트랜잭션 내부에 최소화해서 넣는 식으로 개선했습니다.
또한 가능하면 <code>readOnly = true</code>를 명시하거나, 조회 로직은 분리해서 처리하면서 성능 이슈를 최소화했습니다.</p>
<h4 id="→-propagationrequired-vs-requires_new">→ Propagation.REQUIRED vs REQUIRES_NEW</h4>
<p><code>REQUIRED</code>는 기존 트랜잭션이 있으면 그 안에 합류하는 방식이고, <code>REQUIRES_NEW</code>는 기존 트랜잭션을 <strong>잠시 보류</strong>하고 <strong>새로운 트랜잭션을 시작</strong>합니다.
그래서 주문 처리 중 <strong>실패하더라도 로그는 반드시 DB에 남겨야 하는 경우라면</strong>, <code>REQUIRES_NEW</code>를 사용해야 합니다.
실무에서는 로그, 이력, 알림 전송 같은 <strong>비핵심 부가 로직을 REQUIRES_NEW로 분리</strong>해놓고 트러블슈팅에 활용하는 경우가 많았습니다.</p>
<hr>
<h3 id="4-controller-service-repository의-역할을-구분해서-설명해주실-수-있나요">4. Controller, Service, Repository의 역할을 구분해서 설명해주실 수 있나요?</h3>
<p>네, 실무에서도 이 구조를 명확히 구분해서 작업했습니다.</p>
<ul>
<li><strong>Controller</strong>는 요청을 받아서 DTO로 파싱하고, 응답을 만드는 역할만 담당했습니다.</li>
<li><strong>Service</strong>는 실제 비즈니스 로직이 구현되는 곳으로, 트랜잭션 관리도 여기서 담당했습니다.</li>
<li><strong>Repository</strong>는 JPA를 사용해 DB와 직접 통신하는 역할을 했고, 주로 CRUD 또는 JPQL 쿼리를 정의했습니다.</li>
</ul>
<p>예를 들어 주문 수집 기능을 만들 때, Controller에서는 주문 수집 요청을 받고, Service에서는 API에서 받아온 주문 데이터를 가공해 처리했고, Repository에서는 최종 주문 수집 데이터를 저장했습니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-controller에서-바로-repository-호출하면-안-되나요">→ Controller에서 바로 Repository 호출하면 안 되나요?</h4>
<p>단기적인 개발에서는 가능할 수도 있지만, 그렇게 되면 재사용성이나 유지보수성이 떨어집니다.
특히 서비스 로직이 복잡해질 경우 컨트롤러가 비대해지고, 트랜잭션 처리도 어려워지기 때문에 분리를 유지하는 게 좋다고 생각합니다.</p>
<h4 id="→-valid-어노테이션을-사용해보신-적-있나요">→ @Valid 어노테이션을 사용해보신 적 있나요?</h4>
<p>네, 사용해봤습니다.
주로 컨트롤러에서 클라이언트로부터 전달받은 DTO 객체의 유효성 검사를 할 때 사용했습니다.
예를 들어, 사용자 생성 API에서 @RequestBody로 받은 DTO에 @NotBlank, @Size 같은 제약을 지정해두고,
컨트롤러 메서드 파라미터에 @Valid를 붙여서 요청값 검증이 자동으로 수행되도록 했습니다.</p>
<hr>
<h3 id="5-jpa에서-양방향-연관관계를-설정할-때-주의할-점은">5. JPA에서 양방향 연관관계를 설정할 때 주의할 점은?</h3>
<p>가장 중요한 건 <strong>연관관계의 주인</strong>을 명확히 설정하는 거라고 생각합니다. 주인은 외래키를 관리하는 쪽이고, 보통 <code>@ManyToOne</code> 쪽이 주인이 되죠.
그리고 <strong>양쪽 필드 값을 동시에 관리</strong>해줘야 데이터 정합성이 깨지지 않기 때문에, <code>addXXX()</code> 같은 편의 메서드를 만들어서 두 객체에 모두 값을 넣어주는 식으로 처리했습니다.</p>
<p>실무에서는 사실 양방향 연관관계를 자주 사용하지 않았습니다.
왜냐하면 연관관계를 잘못 설정하면 <code>무한 루프</code>, <code>데이터 정합성 문제</code>, <code>예상치 못한 N+1 문제</code> 등 다양한 이슈가 발생하기 쉽습니다.
대신 단방향 연관관계를 기본으로 두고, 필요한 경우에만 명확한 주체를 정해서 양방향으로 설정했습니다.
그리고 연관된 데이터를 조회할 필요가 있을 땐 <strong>JPQL, QueryDSL, DTO projection</strong>을 활용하는 식으로 설계를 했습니다.
양방향보다는 단방향이 <strong>설계가 단순하고 유지보수도 쉬워서</strong>, 가능하면 단방향으로 구성하는 걸 선호했습니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-cascade-옵션도-사용해보셨나요">→ Cascade 옵션도 사용해보셨나요?</h4>
<p>네, 부모-자식 관계에서 자식 엔티티를 함께 저장하거나 삭제할 때 <code>CascadeType.ALL</code> 또는 <code>REMOVE</code>를 사용했습니다.
단, 무분별하게 설정하면 예기치 않은 삭제가 발생할 수 있어서 꼭 필요한 경우에만 적용했습니다.</p>
<h4 id="→-jpql-vs-querydsl">→ JPQL vs QueryDSL</h4>
<table border="1" cellpadding="8" cellspacing="0">
  <thead>
    <tr>
      <th>구분</th>
      <th>JPQL</th>
      <th>QueryDSL</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>작성 방식</strong></td>
      <td>문자열 기반</td>
      <td>Java 코드 기반 (타입 안전)</td>
    </tr>
    <tr>
      <td><strong>적합한 상황</strong></td>
      <td>단순한 쿼리, 정적 쿼리, 복잡하지 않은 join</td>
      <td>복잡한 조건, 동적 쿼리, 조건이 많은 검색</td>
    </tr>
    <tr>
      <td><strong>가독성</strong></td>
      <td>짧은 쿼리는 오히려 보기 쉬움</td>
      <td>쿼리가 길어지면 유지보수 편함</td>
    </tr>
    <tr>
      <td><strong>재사용성</strong></td>
      <td>재사용 어려움 (하드코딩된 문자열)</td>
      <td>메서드로 분리해 재사용 용이</td>
    </tr>
    <tr>
      <td><strong>런타임 오류</strong></td>
      <td>존재 가능 (문자열 파싱 실패 등)</td>
      <td>컴파일 타임 오류로 사전 방지 가능</td>
    </tr>
    <tr>
      <td><strong>DTO 매핑</strong></td>
      <td><code>new</code> 키워드 사용, 오타 위험</td>
      <td><code>Projections.bean</code> 등으로 안정적 매핑 가능</td>
    </tr>
    <tr>
      <td><strong>단점</strong></td>
      <td>유지보수 어려움, IDE 지원 약함</td>
      <td>초기 세팅 번거롭고 문법 장벽 있음</td>
    </tr>
  </tbody>
</table>



<p>단순한 정적 쿼리의 경우에는 JPQL을 사용했습니다. 
예를 들어, 특정 상태값을 가진 리스트를 조회하거나 단순한 정렬이 필요한 경우 등입니다.</p>
<p>반면에, 조건이 유동적으로 바뀌는 검색 화면이나 필터 조건이 많은 쿼리의 경우에는 QueryDSL을 사용했습니다.
예를 들어, 검색 조건이 5~6개 이상이고 where 절이 동적으로 조합되어야 하는 경우,
BooleanBuilder 또는 where(...).and(...) 구조를 쓰는 QueryDSL이 훨씬 유지보수에 유리했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java&Spring 면접 준비] Day 2 - Java 심화 및 JPA]]></title>
            <link>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-1-Java-%EC%8B%AC%ED%99%94-%EB%B0%8F-JPA</link>
            <guid>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-1-Java-%EC%8B%AC%ED%99%94-%EB%B0%8F-JPA</guid>
            <pubDate>Sun, 03 Aug 2025 06:29:43 GMT</pubDate>
            <description><![CDATA[<hr>
<h3 id="1-java에서-optional-클래스가-등장한-배경과-적절한-사용법에-대해-알려주세요">1. Java에서 Optional 클래스가 등장한 배경과 적절한 사용법에 대해 알려주세요.</h3>
<p>Optional은 <strong>Java 8</strong>에서 도입된 클래스이고, 가장 큰 목적은 <strong>NullPointerException을 줄이고 null 처리를 더 명시적*</strong>으로 하기 위해서입니다.
예전에는 null인지 직접 조건문으로 확인해야 했는데, Optional을 사용하면 <strong>값이 없을 수도 있다는 걸 타입 레벨에서 표현할 수 있어서</strong> 의도를 더 잘 드러낼 수 있어요.</p>
<p>저는 주로 메서드 반환값에 Optional<T>를 사용하는데요, 예를 들어 DB 조회 시 값이 없을 수도 있는 경우에 <strong>Optional.empty()</strong>를 반환하면, 호출 측에서 <strong>orElse()나 ifPresent()</strong>로 안전하게 처리할 수 있어서 좋았습니다.
다만, 파라미터나 필드로 사용하는 건 코드 복잡성이 커질 수 있어서 지양하고 있습니다.</p>
<hr>
<h3 id="2-jpa에서-n1-문제는-어떻게-발생하며-이를-해결하기-위한-전략은-무엇인가요">2. JPA에서 N+1 문제는 어떻게 발생하며, 이를 해결하기 위한 전략은 무엇인가요?</h3>
<p> N+1 문제는 <strong>지연 로딩(LAZY) 설정된 연관 엔티티를 반복해서 접근</strong>할 때 발생합니다.
예를 들어 게시글 목록을 가져온 다음, 각 게시글마다 작성자 이름을 출력하려 하면, 처음에 게시글 1번만 조회된 후에 각 작성자의 정보를 가져오기 위해 추가 쿼리가 N번 실행돼서 총 N+1 쿼리가 됩니다.
저는 <strong>연관 객체를 Entity로 가져오지 않고, 필요한 필드만 JPQL 또는 QueryDSL로 직접 DTO로 매핑해서 가져와</strong> 해결했습니다. 이 방법은 성능 최적화에는 유리하지만, 쿼리 복잡도가 증가합니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-dto로-조회할-때-쿼리가-너무-많아지면-어떻게-관리하시나요">→ DTO로 조회할 때 쿼리가 너무 많아지면 어떻게 관리하시나요?</h4>
<p>실무에서 화면이 많아지고, 그에 따른 DTO 기반 쿼리도 많아지면 쿼리 관리 이슈가 생깁니다.</p>
<ul>
<li><p>QueryDSL을 사용할 경우: 
Repository 레이어에서 복잡한 쿼리는 Custom Repository로 분리해서 관리했습니다.
예: UserRepositoryCustom, UserRepositoryImpl
이렇게 하면 repository 인터페이스와 구현을 명확히 구분할 수 있어서 유지보수가 쉬워집니다.</p>
</li>
<li><p>JPQL이나 네이티브 쿼리를 사용하는 경우:
@NamedQuery 보다는 JPA의 @Query를 사용하고,
쿼리 복잡도가 높아지면 .sql 파일로 분리하거나, 쿼리를 별도 클래스로 추출하는 방식을 고려했습니다.</p>
</li>
</ul>
<p>추가로, 화면 단위로 DTO 쿼리를 분리하거나, 쿼리 작성 시 공통 조건은 메서드 분리로 재사용하여 중복을 줄였습니다.</p>
<h4 id="→-그럼-즉시로딩을-사용-했을때는-n1이-발생하지-않나요">→ 그럼 즉시로딩을 사용 했을때는 N+1이 발생하지 않나요?</h4>
<p>EAGER 로딩을 사용해도 N+1 문제는 발생할 수 있습니다.
왜냐하면 EAGER는 단지 &quot;즉시&quot; 로딩한다는 의미이지, &quot;어떻게(fetch 방식)&quot; 로딩하는지는 별도로 설정되지 않으면 기본은 select 쿼리입니다.
예를 들어, EAGER 연관 필드가 있는 리스트를 조회하면, 첫 쿼리 1번 + 연관 객체 조회 N번 쿼리가 발생할 수 있습니다.</p>
<p>이를 해결하기 위해서는 fetch join을 사용하거나 EntityGraph를 활용해 함께 로딩하거나 <strong>DTO 직접 조회(QueryDSL)</strong>로 쿼리를 명시적으로 작성하는 방법을 사용했습니다.</p>
<hr>
<h3 id="3-실무에서-fetchtypelazy를-사용할-때-주의할-점은-무엇인가요">3. 실무에서 FetchType.LAZY를 사용할 때 주의할 점은 무엇인가요?</h3>
<p>LAZY는 성능 최적화를 위해 기본적으로 많이 사용하는데요, 주의할 점은 <strong>영속성 컨텍스트가 닫힌 상태에서 지연 로딩 대상에 접근하면 LazyInitializationException</strong>이 발생합니다.</p>
<p>실제로 저도 처음에는 컨트롤러에서 Entity를 그대로 반환해서 문제가 생긴 적이 있었습니다. 그 후에는 <strong>Service 단에서 DTO로 변환한 후에만 컨트롤러로 전달하는 구조</strong>로 바꿨습니다.
특히, 양방향 연관관계에서는 순환 참조 위험도 있어서, <strong>DTO 분리 전략</strong>을 사용해 필요한 필드만 전달하도록 구성했습니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-open-session-in-view-패턴에-대해서는-어떻게-생각하시나요-이-패턴을-사용하면-컨트롤러-단에서-lazyinitializationexception을-피할-수-있지만-어떤-단점들이-있을까요">→ Open Session in View 패턴에 대해서는 어떻게 생각하시나요? 이 패턴을 사용하면 컨트롤러 단에서 LazyInitializationException을 피할 수 있지만, 어떤 단점들이 있을까요?</h4>
<p>OSIV 패턴은 컨트롤러까지 영속성 컨텍스트를 열어두기 때문에,
Lazy 로딩으로 인한 LazyInitializationException을 방지할 수 있는 장점이 있습니다.
하지만 트랜잭션 범위가 비즈니스 로직을 넘어 컨트롤러까지 확장되기 때문에, DB 커넥션 점유 시간이 길어지고, 성능 저하나 커넥션 부족 문제가 발생할 수 있습니다.</p>
<p>서비스 계층을 벗어난 지점에서 엔티티를 로딩하는 건 도메인 계층 설계 원칙에 어긋날 수 있습니다.
그래서 실무에서는 OSIV를 비활성화하고, 필요한 데이터는 서비스 계층에서 미리 fetch join 또는 DTO로 모두 가져온 뒤 컨트롤러로 넘기는 구조로 설계했습니다.</p>
<hr>
<h3 id="4-java-스트림-api와-루프for-each의-성능-차이나-장단점을-설명해-주세요">4. Java 스트림 API와 루프(for-each)의 성능 차이나 장단점을 설명해 주세요.</h3>
<p><strong>Stream API는 선언형으로 데이터를 처리할 수 있어서 코드가 간결하고 가독성이 좋다</strong>는 게 가장 큰 장점입니다.
예를 들어 <strong>필터링 → 정렬 → 매핑 같은 연산을 체이닝</strong>으로 처리할 수 있고, <strong>병렬 스트림을 사용하면 멀티코어 환경</strong>에서도 효율적입니다.</p>
<p>반면 <strong>for-each 루프는 좀 더 직관적이고 디버깅이 쉬운 구조</strong>라서, 단순한 반복문에는 오히려 더 좋을 때도 있습니다.
실무에서 <strong>복잡한 데이터 가공이 필요한 경우엔 Stream을, 로직이 단순하거나 가독성이 중요한 경우엔 for-each를 쓰는 편</strong>입니다.</p>
<hr>
<h3 id="5-java에서-자주-발생하는-deadlock-문제를-발견하고-해결하기-위한-전략과-도구는-무엇이-있나요">5. Java에서 자주 발생하는 Deadlock 문제를 발견하고 해결하기 위한 전략과 도구는 무엇이 있나요?</h3>
<p>Deadlock은 주로 여러 스레드가 서로 자원을 점유하고, 상대방 자원을 기다리면서 락이 풀리지 않는 상황에서 발생합니다.
해결하려면 먼저** 락 획득 순서를 고정하거나, tryLock() 같은 방식으로 시간 제한을 설정**해서 대기하지 않게 하는 게 중요하다고 생각합니다.</p>
<p>문제 상황을 재현하거나 의심될 때는 <strong>jstack으로 스레드 덤프를 분석
*<em>하거나 *</em>VisualVM, jconsole 같은 도구를 써서 어느 스레드가 멈춰 있는지 확인</strong>했습니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-락-획득-순서를-고정한다는건-어떤-방식으로-구현하셨었나요">→ 락 획득 순서를 고정한다는건 어떤 방식으로 구현하셨었나요?</h4>
<p>예를 들어 두 개 이상의 자원(또는 DB row)을 동시에 락을 걸어야 하는 상황이 있으면, 
  항상 동일한 순서(예: ID 오름차순)로 락을 획득하도록 강제했습니다.</p>
<p>구현 방식은 다음과 같습니다. </p>
<ol>
<li>락을 획득할 대상들을 미리 정렬 (예: List<Entity> 정렬)</li>
<li>그 순서대로 순차적으로 락을 획득 (예: pessimistic lock 또는 메모리 락)</li>
</ol>
<p>이렇게 하면 쓰레드 간 충돌이 발생해도 락 획득 순서가 항상 일치하므로 데드락을 방지할 수 있었습니다.</p>
<h4 id="→-trylock-을-사용하는-것의-장단점은-무엇인가요">→ tryLock() 을 사용하는 것의 장단점은 무엇인가요?</h4>
<ul>
<li>장점<ul>
<li>락 대기 없이 빠르게 실패할 수 있음</li>
<li>데드락 회피에 유리함</li>
<li>타임아웃 설정으로 유연한 처리 가능</li>
</ul>
</li>
<li>단점    <ul>
<li>락을 못 잡을 경우 처리가 복잡해질 수 있음 (retry, fallback 등)</li>
<li>락 경쟁이 심할 경우 낙오 많음</li>
<li>병렬성이나 안정성 낮아질 수 있음</li>
</ul>
</li>
</ul>
<hr>
<h3 id="6-스레드-풀-크기는-어떻게-정해야하고-스레드-풀에서-데드락이나-자원-경합이-발생하면-어떻게-진단하고-해결하나요">6. 스레드 풀 크기는 어떻게 정해야하고, 스레드 풀에서 데드락이나 자원 경합이 발생하면 어떻게 진단하고 해결하나요?</h3>
<p>CPU 바운드 작업: 병렬로 실행되는 CPU 연산이 많은 작업
공식: <strong>스레드 수 ≈ CPU 코어 수 + 1</strong>
이유: 너무 많은 스레드는 컨텍스트 스위칭 비용만 늘려 성능이 오히려 저하됩니다.</p>
<p>IO 바운드 작업: 네트워크/디스크 IO로 블로킹이 많은 작업.
공식: <strong>스레드 수 ≈ CPU 코어 수 × (1 + (대기 시간 / 처리 시간))</strong></p>
<p>데드락이나 자원 경합이 발생하면 먼저 <strong>스레드 수와 큐 길이를 조절해서 병목을 줄이고,</strong>
<strong>jstack, VisualVM 같은 도구로 스레드 상태나 락 점유 상태</strong>를 추적합니다.
또, 스레드 풀에 <strong>백프레셔(backpressure)나 타임아웃</strong>을 걸어 문제 상황에서도 빠르게 실패하고 회복 가능한 구조 설계하는 것도 중요합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java&Spring 면접 준비] Day 1 - Java 기초 및 JVM 구조]]></title>
            <link>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-1-Java-%EA%B8%B0%EC%B4%88-%EB%B0%8F-JVM-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@se_ize/JavaSpring-%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84-Day-1-Java-%EA%B8%B0%EC%B4%88-%EB%B0%8F-JVM-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Tue, 29 Jul 2025 02:25:55 GMT</pubDate>
            <description><![CDATA[<hr>
<h3 id="1-java는-플랫폼-독립적인-언어인-이유가-뭔가요">1. <strong>Java는 플랫폼 독립적인 언어인 이유가 뭔가요?</strong></h3>
<p>Java는 소스 코드를 컴파일하면 <strong>바이트코드(.class)</strong>로 변환되며, 이 바이트코드는 <strong>JVM(Java Virtual Machine)</strong> 위에서 실행됩니다. 각 운영체제별로 JVM이 구현되어 있기 때문에, 바이트코드만 있으면 <strong>어떤 운영체제에서도 동일한 Java 프로그램을 실행할 수 있어 플랫폼 독립적</strong>이라고 합니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-그럼-jvm-없이-java-프로그램을-실행할-수-있을까요">→ 그럼 JVM 없이 Java 프로그램을 실행할 수 있을까요?</h4>
<p>AOT (Ahead-of-Time) 컴파일을 사용하면 가능합니다. <strong>GraalVM의 Native Image</strong> 도구를 사용하면 Java 애플리케이션을 <strong>기계어로 직접 컴파일</strong>하여, JVM이 없이도 실행 가능한 <strong>자체 실행 파일(native binary)</strong>로 만들 수 있습니다.</p>
<hr>
<h3 id="2-java에서-클래스-로딩-과정class-loading-process은-어떻게-되나요">2. <strong>Java에서 클래스 로딩 과정(Class Loading Process)은 어떻게 되나요?</strong></h3>
<p>Java에서 클래스 로딩은 JVM이 클래스를 메모리에 올리는 과정이며, <strong>로딩(Loading), 링크(Linking), 초기화(Initialization)</strong>의 3단계로 구성됩니다.</p>
<ul>
<li><strong>로딩:</strong> 클래스 파일을 읽어 메모리에 로드</li>
<li><strong>링크:</strong> 클래스의 메타 정보를 검증하고 참조를 준비</li>
<li><strong>초기화:</strong> static 필드 초기화, static 블록 실행</li>
</ul>
<p>이 모든 과정은 클래스가 <strong>처음 참조될 때</strong> <strong>지연 로딩(lazy loading)</strong> 방식으로 실행됩니다.</p>
<p>지연 로딩(Lazy Loading)은 <strong>객체나 클래스, 데이터를 실제로 사용할 때까지 로딩을 지연시키는 기법</strong>입니다. 메모리 사용량을 줄이고, 초기 로딩 속도를 높이는 데 도움이 됩니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-클래스를-명시적으로-로딩하는-방법에는-어떤-게-있나요">→ 클래스를 명시적으로 로딩하는 방법에는 어떤 게 있나요?</h4>
<p><strong>Class.forName(String className)</strong>을 사용해 클래스 이름(패키지 포함)을 문자열로 전달하거나, <strong>ClassLoader.loadClass(String name)</strong>를 사용해 클래스를 로딩합니다.</p>
<hr>
<h3 id="3-java에서-컴파일은-언제-실행은-언제-이뤄지나요">3. <strong>Java에서 컴파일은 언제, 실행은 언제 이뤄지나요?</strong></h3>
<p>개발 단계에서는 컴파일, 실행 단계에서는 JVM이 해석하거나 JIT 컴파일을 통해 실행합니다.</p>
<ul>
<li><strong>컴파일 단계:</strong> 개발자가 작성한 <code>.java</code> 파일을 <code>javac</code> 컴파일러를 사용해 바이트 코드로 변환</li>
<li><strong>실행 단계:</strong> 바이트코드는 JVM에서 해석되거나 JIT(Just-In-Time) 컴파일러에 의해 런타임 중 기계어로 변환되어 실행</li>
</ul>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-jit-컴파일러가-하는-역할은-무엇인가요">→ JIT 컴파일러가 하는 역할은 무엇인가요?</h4>
<p><strong>JIT(Just-In-Time) 컴파일러</strong>는 Java 프로그램이 실행되는 동안, <strong>자주 실행되는 바이트코드</strong>를 <strong>기계어(native code)로 변환</strong>해 실행속도를 향상시킵니다.</p>
<hr>
<h3 id="4-java에서-heap-영역의-메모리-누수가-발생하는-경우는-어떤-상황인가요">4. <strong>Java에서 Heap 영역의 메모리 누수가 발생하는 경우는 어떤 상황인가요?</strong></h3>
<p><strong>참조가 계속 남아 있는 객체</strong>는 GC가 수거하지 못해 메모리 누수가 발생할 수 있습니다.</p>
<ul>
<li><strong>static 컬렉션에 객체를 계속 추가해놓고 제거하지 않는 경우</strong></li>
<li><strong>이벤트 리스너나 콜백에 객체를 등록해놓고 해제하지 않는 경우</strong></li>
<li><strong>캐시 구현 시 강한 참조로 유지하면서 정리 로직이 없을 경우</strong></li>
</ul>
<p>이런 상황이 반복되면 Heap 메모리를 점점 차지하여 <strong>OOM(OutOfMemoryError)</strong>로 이어집니다.</p>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-이런-문제를-방지하기-위해-어떤-도구나-기법을-활용할-수-있나요">→ 이런 문제를 방지하기 위해 어떤 도구나 기법을 활용할 수 있나요?</h4>
<ul>
<li><strong>WeakReference 사용:</strong> 캐시처럼 임시로만 참조가 필요한 객체에는 <code>WeakReference</code>를 사용합니다.</li>
<li><strong>콜렉션 정리:</strong> static이나 singleton에 담긴 콜렉션은 명시적으로 clear() 등으로 정리한합니다.</li>
<li><strong>불필요한 참조 제거:</strong> 예를 들어, 루프에서 생성된 객체를 외부에서 참조하지 않도록 스코프 관리에 신경 씁니다.<br>

</li>
</ul>
<h4 id="→--현업에서-메모리-누수가-발생했을-때-어떻게-대처하셨나요">→  현업에서 메모리 누수가 발생했을 때 어떻게 대처하셨나요?</h4>
<p>먼저, Datadog을 사용하여 애플리케이션의 메모리 사용량을 실시간으로 모니터링하고 있습니다. 임계값을 초과하는 순간 <strong>슬랙(Slack)</strong>으로 알림을 받아 즉시 문제를 인지할 수 있도록 설정해 두었습니다. 이를 통해 메모리 사용량이 급격히 증가하는 문제를 빠르게 감지하고 대응할 수 있었습니다.</p>
<p>메모리 사용량이 비정상적으로 늘어난 경우, ** JVM GC 로그를 통해 GC가 잘 동작하는지 추적하고, VisualVM과 같은 Heap 분석 도구를 사용하여 메모리 덤프 ** 를 분석했습니다. 이를 통해 누수의 의심 지점을 추적하고, 로그를 추가하여 어떤 객체가 계속해서 메모리를 차지하고 있는지 확인했습니다. 예를 들어, 특정 메서드에서 객체들이 명시적으로 수거되지 않거나, 자원 관리가 잘못된 부분을 발견할 수 있었습니다.</p>
<p>그런 다음, 메모리 누수가 발생한 부분에서 <strong>명시적으로 콜렉션을 정리하고, 참조를 끊어주기 위해 null 처리나 clear() 메서드</strong>를 활용했습니다. 이로써 메모리 사용량을 효율적으로 관리할 수 있었습니다. 또한, 메모리 누수가 다시 발생하지 않도록 <strong>객체의 라이프사이클을 명확히 관리하고, 불필요한 객체를 참조하지 않도록 코드를 최적화하는 작업</strong>을 진행했습니다.</p>
<p>추가적으로, <strong>JUnit을 이용해 자동화된 테스트를 구현하고, CI/CD 파이프라인에서 메모리 테스트를 자동화</strong>해 정기적으로 메모리 사용량을 점검하도록 했습니다. 이를 통해 새로운 기능이 추가될 때마다 메모리 관리가 제대로 이루어지는지 지속적으로 모니터링할 수 있었습니다.</p>
<h3 id=""></h3>
<hr>
<h3 id="5-컬렉션-프레임워크에서-list-set-map의-차이는-무엇인가요">5. <strong>컬렉션 프레임워크에서 List, Set, Map의 차이는 무엇인가요?</strong></h3>
<ul>
<li><strong>List:</strong> 순서가 있으며, <strong>중복을 허용</strong>합니다. 대표적으로 <code>ArrayList</code>, <code>LinkedList</code>가 있습니다.</li>
<li><strong>Set:</strong> 보통 순서가 없고, <strong>중복을 허용하지 않습니다</strong>. 대표적으로 <code>HashSet</code>, <code>TreeSet</code>(정렬된 순서로 저장) 등이 있습니다.</li>
<li><strong>Map:</strong> 키-값(key-value) 쌍으로 구성되며, <strong>키는 중복 불가</strong>, 값은 중복 가능. <code>HashMap</code>, <code>TreeMap</code>, <code>LinkedHashMap</code> 등이 있습니다.</li>
</ul>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-hashset은-내부적으로-어떤-자료구조를-이용해-중복을-방지하나요">→ HashSet은 내부적으로 어떤 자료구조를 이용해 중복을 방지하나요?</h4>
<p><code>HashSet</code>은 내부적으로 <strong>HashMap</strong>을 기반으로 동작합니다. 객체의 <code>hashCode()</code> 값으로 해시 버킷을 찾고, 같은 버킷 내에서는 <code>equals()</code> 메서드를 사용해 중복 여부를 판단합니다.</p>
<hr>
<h3 id="6-동기화synchronization는-무엇이고-java에서-멀티스레드를-안전하게-처리하려면-어떤-방법들이-있나요">6. <strong>동기화(synchronization)는 무엇이고, Java에서 멀티스레드를 안전하게 처리하려면 어떤 방법들이 있나요?</strong></h3>
<p><strong>동기화(Synchronization)</strong>는 여러 스레드가 동시에 공유 자원에 접근할 때 <strong>데이터의 일관성과 무결성을 보장하기 위한 기법</strong>입니다. Java에서는 <code>synchronized</code> 키워드를 통해 임계영역(critical section)을 지정할 수 있습니다.</p>
<p>멀티스레드 환경에서 안전하게 처리하기 위해 다음과 같은 방법들을 사용할 수 있습니다:</p>
<ul>
<li><code>synchronized</code> 메서드나 블록</li>
<li><code>java.util.concurrent</code> 컬렉션 - ConcurrentHashMap</li>
<li>불변 객체 사용으로 상태 변경을 줄이기</li>
<li>스레드 풀(ThreadPoolExecutor) 활용 - 미리 만들어진 스레드를 재사용해서 작업 처리</li>
</ul>
<p><strong>꼬리질문:</strong></p>
<h4 id="→-synchronized를-사용하면-어떤-성능-이슈가-발생할-수-있나요">→ synchronized를 사용하면 어떤 성능 이슈가 발생할 수 있나요?</h4>
<ul>
<li><strong>스레드 블로킹 증가:</strong> 한 스레드가 락을 획득하고 있을 때 다른 스레드는 대기 상태가 되어 CPU 자원을 효율적으로 사용하지 못합니다.</li>
<li><strong>응답 지연:</strong> 특히 임계 영역이 길거나 복잡한 경우, 스레드들이 락을 오래 기다리면서 시스템 전체 응답 시간이 느려질 수 있습니다.</li>
<li><strong>교착 상태(Deadlock) 위험:</strong> 여러 락이 걸려있을 때 순서에 따라 스레드가 서로를 기다리는 상태가 발생할 수 있습니다.</li>
</ul>
<h4 id="→-concurrenthashmap의-락-방식은-무엇인가요">→ ConcurrentHashMap의 락 방식은 무엇인가요?</h4>
<p>ConcurrentHashMap은 전체 맵에 대해 락을 거는 방식 대신, <strong>조각화된 락(Segment Locking) 방식</strong>을 사용합니다. 데이터를 <strong>여러 세그먼트(segment)로 나누고, 각 세그먼트에 대해 독립적인 락</strong>을 적용합니다. 이렇게 함으로써, 하나의 세그먼트에 대한 락을 걸어도 다른 세그먼트에는 영향을 미치지 않게 됩니다. 따라서 여러 스레드가 다양한 세그먼트에 동시에 접근할 수 있어 병목 현상을 줄일 수 있습니다.</p>
<h4 id="→-concurrent-컬렉션을-현업에서-어떻게-사용하셨나요">→ Concurrent 컬렉션을 현업에서 어떻게 사용하셨나요?</h4>
<p>여러 스레드가 동시에 put() 메서드를 호출하여 데이터를 병렬로 갱신할 때, <strong>ConcurrentHashMap</strong>을 사용하여 락 경합을 최소화하면서 동시성 문제를 해결했습니다. <strong>HashMap</strong>과 동일하게 키는 유일해야 하며, 값은 중복을 허용하지만, 여러 스레드가 동시에 값을 변경해도 일관된 상태를 보장해줍니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 40일차 TIL - [LeetCode] Unique Paths (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-40%EC%9D%BC%EC%B0%A8-TIL-LeetCode-Unique-Paths-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-40%EC%9D%BC%EC%B0%A8-TIL-LeetCode-Unique-Paths-Java</guid>
            <pubDate>Sat, 31 Aug 2024 01:56:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[LeetCode] Unique Paths  Java)
<a href="https://leetcode.com/problems/unique-paths/description/">https://leetcode.com/problems/unique-paths/description/</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 첫번째 줄-부등호 문자의 개수 k
두번째 줄-k개의 부등호 기호 (공백으로 구분)
출력 : 첫번째 줄-부등호 관계를 만족하는 k+1 자리의 최대, 최소 정수
두번째 줄-부등호 관계를 만족하는 k+1 자리의 최소 정수</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n!)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>dp</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>먼저 k를 입력받고, k개의 부등호를 배열 operators에 저장한다.</li>
<li>dfs 함수는 현재까지의 숫자 조합 num과 깊이 depth를 매개변수로 받아 백트래킹을 진행한다.</li>
<li>숫자 i를 선택하여 조건에 맞으면 다음 단계로 이동하고, k 길이에 도달하면 해당 숫자 조합을 results 리스트에 추가한다.</li>
<li>check 함수는 부등호 조건을 확인하는 함수로, 현재 숫자와 다음에 선택할 숫자가 주어진 부등호를 만족하는지 확인한다.</li>
<li>모든 가능한 조합을 results 리스트에 추가한 후, 이를 정렬하여 최대값과 최소값을 출력한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>class Solution {
    public int uniquePaths(int m, int n) {
        // dp 배열 생성
        int[][] dp = new int[m][n];

        // 첫 번째 열의 모든 위치에 1을 설정 (오직 아래로 이동 가능)
        for (int i = 0; i &lt; m; i++) {
            dp[i][0] = 1;
        }

        // 첫 번째 행의 모든 위치에 1을 설정 (오직 오른쪽으로 이동 가능)
        for (int j = 0; j &lt; n; j++) {
            dp[0][j] = 1;
        }

        // dp 배열을 채움
        for (int i = 1; i &lt; m; i++) {
            for (int j = 1; j &lt; n; j++) {
                // 위에서 오는 경로 수 + 왼쪽에서 오는 경로 수
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }

        // 오른쪽 하단의 경로 수 반환
        return dp[m - 1][n - 1];
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 39일차 TIL - [프로그래머스] 광물 캐기 (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-39%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B4%91%EB%AC%BC-%EC%BA%90%EA%B8%B0-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-39%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B4%91%EB%AC%BC-%EC%BA%90%EA%B8%B0-Java</guid>
            <pubDate>Fri, 30 Aug 2024 01:54:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[프로그래머스] 광물 캐기 (Java)
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/172927">https://school.programmers.co.kr/learn/courses/30/lessons/172927</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 송전탑의 개수 n, 전선 정보가 담긴 정수 2차원 배열 wires 
출력 : 두 전력망이 가지고 있는 송전탑 개수의 차이(절대값)</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n^2)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>그리디</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>인접 리스트를 이용해 그래프를 생성하고, 간접 정보를 인접 리스트에 추가한다.</li>
<li>간선을 하나씩 제거하면서 그래프가 두 부분으로 나뉘었을 때의 노드 수를 계산한다.</li>
<li>BFS를 사용해 한쪽 서브트리의 노드 수를 계산한다.</li>
<li>두 서브트리의 노드 수 차이를 계산하고, 최소값을 업데이트한다.</li>
<li>다음 계산을 위해 제거했던 간선을 다시 복구한다.</li>
<li>모든 간선에 대해 위의 작업을 반복한 후, 가장 작은 크기 차이를 반환한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

class Solution {
    public int solution(int[] picks, String[] minerals) {
        int answer = 0;
        int totalPicks = picks[0] + picks[1] + picks[2];  // 전체 곡괭이 개수
        int size = Math.min((minerals.length + 4) / 5, totalPicks); // 광물의 그룹 수 (5개씩 자르기 때문에 길이를 5로 나눈 값)

        // 그룹화하여 피로도를 저장하는 배열
        int[][] fatigue = new int[size][3];

        // 광물을 5개씩 그룹화
        for (int i = 0; i &lt; size * 5 &amp;&amp; i &lt; minerals.length; i++) {
            int group = i / 5;  // 현재 그룹
            String mineral = minerals[i];

            // 다이아 곡괭이 피로도
            if (mineral.equals(&quot;diamond&quot;)) {
                fatigue[group][0] += 1;  // 다이아몬드 곡괭이는 다이아몬드를 캐는 데 피로도가 1
                fatigue[group][1] += 5;  // 철 곡괭이는 다이아몬드를 캐는 데 피로도가 5
                fatigue[group][2] += 25; // 돌 곡괭이는 다이아몬드를 캐는 데 피로도가 25
            } else if (mineral.equals(&quot;iron&quot;)) {
                fatigue[group][0] += 1;  // 다이아몬드 곡괭이는 철을 캐는 데 피로도가 1
                fatigue[group][1] += 1;  // 철 곡괭이는 철을 캐는 데 피로도가 1
                fatigue[group][2] += 5;  // 돌 곡괭이는 철을 캐는 데 피로도가 5
            } else if (mineral.equals(&quot;stone&quot;)) {
                fatigue[group][0] += 1;  // 다이아몬드 곡괭이는 돌을 캐는 데 피로도가 1
                fatigue[group][1] += 1;  // 철 곡괭이는 돌을 캐는 데 피로도가 1
                fatigue[group][2] += 1;  // 돌 곡괭이는 돌을 캐는 데 피로도가 1
            }
        }

        // 피로도를 기준으로 내림차순 정렬
        Arrays.sort(fatigue, (a, b) -&gt; b[2] - a[2]);

        // 최소 피로도 계산
        for (int i = 0; i &lt; size; i++) {
            if (picks[0] &gt; 0) { // 다이아몬드 곡괭이가 남아있을 경우
                answer += fatigue[i][0];
                picks[0]--;
            } else if (picks[1] &gt; 0) { // 철 곡괭이가 남아있을 경우
                answer += fatigue[i][1];
                picks[1]--;
            } else if (picks[2] &gt; 0) { // 돌 곡괭이가 남아있을 경우
                answer += fatigue[i][2];
                picks[2]--;
            }
        }

        return answer;
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 37일차 TIL - [백준] 2529번: 부등호 (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-37%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%B1%EC%A4%80-2529%EB%B2%88-%EB%B6%80%EB%93%B1%ED%98%B8-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-37%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%B1%EC%A4%80-2529%EB%B2%88-%EB%B6%80%EB%93%B1%ED%98%B8-Java</guid>
            <pubDate>Tue, 27 Aug 2024 15:16:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[백준] 2529번: 부등호 (Java)
<a href="https://www.acmicpc.net/problem/2529">https://www.acmicpc.net/problem/2529</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 첫번째 줄-부등호 문자의 개수 k
두번째 줄-k개의 부등호 기호 (공백으로 구분)
출력 : 첫번째 줄-부등호 관계를 만족하는 k+1 자리의 최대, 최소 정수
두번째 줄-부등호 관계를 만족하는 k+1 자리의 최소 정수</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n!)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>dfs</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>먼저 k를 입력받고, k개의 부등호를 배열 operators에 저장한다.</li>
<li>dfs 함수는 현재까지의 숫자 조합 num과 깊이 depth를 매개변수로 받아 백트래킹을 진행한다.</li>
<li>숫자 i를 선택하여 조건에 맞으면 다음 단계로 이동하고, k 길이에 도달하면 해당 숫자 조합을 results 리스트에 추가한다.</li>
<li>check 함수는 부등호 조건을 확인하는 함수로, 현재 숫자와 다음에 선택할 숫자가 주어진 부등호를 만족하는지 확인한다.</li>
<li>모든 가능한 조합을 results 리스트에 추가한 후, 이를 정렬하여 최대값과 최소값을 출력한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

public class Main {

    static int k;
    static char[] operators;
    static boolean[] visited = new boolean[10];
    static ArrayList&lt;String&gt; results = new ArrayList&lt;&gt;();

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        k = scanner.nextInt();
        operators = new char[k];

        for (int i = 0; i &lt; k; i++) {
            operators[i] = scanner.next().charAt(0);
        }

        // 백트래킹을 사용하여 가능한 모든 숫자 조합을 만듦
        for (int i = 0; i &lt;= 9; i++) {
            visited[i] = true;
            dfs(&quot;&quot; + i, 0);
            visited[i] = false;
        }

        // 가능한 결과를 정렬하여 최소값과 최대값을 찾음
        Collections.sort(results);
        System.out.println(results.get(results.size() - 1)); // 최대값 출력
        System.out.println(results.get(0)); // 최소값 출력
    }

    static void dfs(String num, int depth) {
        if (depth == k) {
            results.add(num);
            return;
        }

        for (int i = 0; i &lt;= 9; i++) {
            if (!visited[i] &amp;&amp; check(num.charAt(num.length() - 1) - &#39;0&#39;, i, operators[depth])) {
                visited[i] = true;
                dfs(num + i, depth + 1);
                visited[i] = false;
            }
        }
    }

    static boolean check(int a, int b, char op) {
        if (op == &#39;&lt;&#39;) {
            return a &lt; b;
        } else {
            return a &gt; b;
        }
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 36일차 TIL - [프로그래머스] 전력망을 둘로 나누기 (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-36%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A0%84%EB%A0%A5%EB%A7%9D%EC%9D%84-%EB%91%98%EB%A1%9C-%EB%82%98%EB%88%84%EA%B8%B0-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-36%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A0%84%EB%A0%A5%EB%A7%9D%EC%9D%84-%EB%91%98%EB%A1%9C-%EB%82%98%EB%88%84%EA%B8%B0-Java</guid>
            <pubDate>Mon, 26 Aug 2024 16:18:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[프로그래머스] 전력망을 둘로 나누기 (Java)
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/86971">https://school.programmers.co.kr/learn/courses/30/lessons/86971</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 송전탑의 개수 n, 전선 정보가 담긴 정수 2차원 배열 wires 
출력 : 두 전력망이 가지고 있는 송전탑 개수의 차이(절대값)</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n^2)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>BFS</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>인접 리스트를 이용해 그래프를 생성하고, 간접 정보를 인접 리스트에 추가한다.</li>
<li>간선을 하나씩 제거하면서 그래프가 두 부분으로 나뉘었을 때의 노드 수를 계산한다.</li>
<li>BFS를 사용해 한쪽 서브트리의 노드 수를 계산한다.</li>
<li>두 서브트리의 노드 수 차이를 계산하고, 최소값을 업데이트한다.</li>
<li>다음 계산을 위해 제거했던 간선을 다시 복구한다.</li>
<li>모든 간선에 대해 위의 작업을 반복한 후, 가장 작은 크기 차이를 반환한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

class Solution {
    public int solution(int n, int[][] wires) {
        int answer = n;

        // 그래프를 인접 리스트로 표현
        List&lt;List&lt;Integer&gt;&gt; graph = new ArrayList&lt;&gt;();
        for (int i = 0; i &lt; n + 1; i++) {
            graph.add(new ArrayList&lt;&gt;());
        }

        // 간선 정보를 인접 리스트에 추가
        for (int[] wire : wires) {
            int u = wire[0];
            int v = wire[1];
            graph.get(u).add(v);
            graph.get(v).add(u);
        }

        // 각 간선을 하나씩 끊어보며 최소 차이를 계산
        for (int[] wire : wires) {
            int u = wire[0];
            int v = wire[1];

            // 간선을 끊음
            graph.get(u).remove((Integer) v);
            graph.get(v).remove((Integer) u);

            // BFS나 DFS로 한쪽 네트워크의 크기 계산
            int count = bfs(graph, n, u);

            // 두 네트워크 크기의 차이 계산
            int diff = Math.abs(n - 2 * count);
            answer = Math.min(answer, diff);

            // 끊었던 간선 다시 복구
            graph.get(u).add(v);
            graph.get(v).add(u);
        }

        return answer;
    }

    // BFS를 이용하여 한쪽 네트워크의 노드 수 계산
    private int bfs(List&lt;List&lt;Integer&gt;&gt; graph, int n, int start) {
        boolean[] visited = new boolean[n + 1];
        Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();
        queue.add(start);
        visited[start] = true;
        int count = 0;

        while (!queue.isEmpty()) {
            int node = queue.poll();
            count++;

            for (int neighbor : graph.get(node)) {
                if (!visited[neighbor]) {
                    visited[neighbor] = true;
                    queue.add(neighbor);
                }
            }
        }

        return count;
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 35일차 TIL - [프로그래머스] 게임 맵 최단거리 (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-35%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B2%8C%EC%9E%84-%EB%A7%B5-%EC%B5%9C%EB%8B%A8%EA%B1%B0%EB%A6%AC-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-35%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B2%8C%EC%9E%84-%EB%A7%B5-%EC%B5%9C%EB%8B%A8%EA%B1%B0%EB%A6%AC-Java</guid>
            <pubDate>Mon, 26 Aug 2024 01:51:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[프로그래머스] 타겟 넘버 (Java)
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/43165">https://school.programmers.co.kr/learn/courses/30/lessons/43165</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 게임 맵의 상태 maps
출력 : 캐릭터가 상대 팀 진영에 도착하기 위해서 지나가야 하는 칸의 개수의 최솟값. 단, 상대 팀 진영에 도착할 수 없다면 -1 리턴</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>bfs</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>미로의 크기를 파악하고, BFS 탐색을 위한 큐(Queue)를 생성한다.</li>
<li>시작점(0,0)에서 출발하며, 이 위치를 큐에 추가하고 방문 기록을 남긴다.</li>
<li>큐가 빌 때까지 반복하며, 현재 위치를 꺼내어 4개의 방향(상하좌우)으로 이동 가능한지 검사한다.</li>
<li>미로의 범위 내에서 이동 가능하고, 아직 방문하지 않은 칸이면 큐에 추가하고 방문으로 기록한다.</li>
<li>목표 지점(미로의 끝)에 도달하면 현재까지 이동한 거리를 반환한다. 큐가 비어도 목표에 도달하지 못하면 -1을 반환한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<p>Queue에서 add는 요소를 추가할 수 없을 때 예외를 던지는 반면, offer는 예외를 던지지 않고 단순히 false를 반환함</p>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

class Solution {
 private int[] dx = new int[]{-1, 1, 0, 0};
    private int[] dy = new int[]{0, 0, -1, 1};
    private int n;
    private int m;

    public int solution(int[][] maze) {
        n = maze.length;
        m = maze[0].length;
        // BFS 로직을 활용하는데,
        // 다음에 접근할 수 있는 칸을 maze 의 가로 세로 크기 이내의 인접한 칸
        // 을 기준으로 판단

        // int[]를 담는 Queue, {x, y, 여태까지 이동거리}
        Queue&lt;int[]&gt; visitNext = new LinkedList&lt;&gt;();
        boolean[][] visited = new boolean[n][m];
        visitNext.offer(new int[]{0, 0, 1});
        int answer = -1;
        // BFS 시작
        // Queue가 비어있지 않은 동안
        while (!visitNext.isEmpty()) {
            // 다음에 갈곳을 Queue에서 꺼낸다.
            int[] next = visitNext.poll();
            int nowX = next[0];
            int nowY = next[1];
            int steps = next[2];
            // 현재 칸이 n, m 이라면,
            if (nowX == n - 1 &amp;&amp; nowY == m - 1){
                answer = steps;
                break;
            }

            // 4개의 방향을 확인한다.
            for (int i = 0; i &lt; 4; i++) {
                int nextX = nowX + dx[i];
                int nextY = nowY + dy[i];
                if (
                        // 1. 미로의 범위를 벗어나진 않는지
                        checkBounds(nextX, nextY) &amp;&amp;
                        // 2. 미로에서 진행 가능한 칸인지 (0 또는 3)
                        maze[nextX][nextY] == 1 &amp;&amp;
                        // 3. 아직 방문한적 없는지
                        !visited[nextX][nextY]
                ) {
                    // 큐에 방문 대상으로 기록
                    visitNext.offer(new int[]{nextX, nextY, steps + 1});
                    // 효율성 검사 통과를 위해 방문 전에 기록한다.
                    visited[nextX][nextY] = true;
                }
            }
        }
        return answer;
    }

    // 미로의 범위 내에 있는지 검사하는 메소드
    private boolean checkBounds(int x, int y) {
        return -1 &lt; x &amp;&amp; x &lt; n &amp;&amp; -1 &lt; y &amp;&amp; y &lt; m;
    }

}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 34일차 TIL - [프로그래머스] 타겟 넘버 (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-34%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%83%80%EA%B2%9F-%EB%84%98%EB%B2%84-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-34%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%83%80%EA%B2%9F-%EB%84%98%EB%B2%84-Java</guid>
            <pubDate>Sun, 25 Aug 2024 01:49:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[프로그래머스] 타겟 넘버 (Java)
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/43165">https://school.programmers.co.kr/learn/courses/30/lessons/43165</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 사용할 수 있는 숫자가 담긴 배열 numbers, 타겟 넘버 target
출력 : 숫자를 적절히 더하고 빼서 타겟 넘버를 만드는 방법의 수</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>dfs</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>dfs 메서드를 호출하여 탐색을 시작한다. 초기 인덱스는 0이고, 초기 합계는 0으로 설정한다.</li>
<li>dfs 메서드는 현재까지 합계 sum이 목표 값 target과 일치하는지 확인한다.</li>
<li>숫자의 끝에 도달했을 때(next == numbers.length), 합계가 목표 값과 일치하면 answer 값을 증가시킨다</li>
<li>현재 숫자를 더하는 경우와 빼는 경우에 대해 각각 재귀적으로 dfs를 호출한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>class Solution {
    public int solution(int[] numbers, int target) {
        dfs(numbers, 0, target, 0);
        return answer;
    }

    // 재귀함수 DFS를 할 예정이라, 정답을 클래스 단위에서 관리
    private int answer = 0;

    // 재귀함수 DFS
    public void dfs(
            // 내가 사용할 숫자들
            int[] numbers,
            // 내가 다음에 사용할 차례의 숫자
            // 이번 DFS 호출에서 더하거나 빼거나 할 숫자는 numbers[next]
            int next,
            // 목표 값
            int target,
            // 이번 재귀까지 합한 값
            int sum
    ) {
        // 마지막 숫자를 쓴 시점에 next는 배열의 크기와 동일해진다.
        if (next == numbers.length) {
            // target 과 일치하는지 확인
            if (sum == target) this.answer++;

        }
        else {
            // 더한 다음 다음 숫자 결정
            dfs(numbers, next + 1, target, sum + numbers[next]);
            // 뺀 다음 다음 숫자 결정
            dfs(numbers, next + 1, target, sum - numbers[next]);
        }
    }


}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 33일차 TIL - [프로그래머스] 리코쳇 로봇 (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-33%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%A6%AC%EC%BD%94%EC%B3%87-%EB%A1%9C%EB%B4%87-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-33%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%A6%AC%EC%BD%94%EC%B3%87-%EB%A1%9C%EB%B4%87-Java</guid>
            <pubDate>Sat, 24 Aug 2024 01:55:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[프로그래머스] 리코쳇 로봇 (Java)
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/169199">https://school.programmers.co.kr/learn/courses/30/lessons/169199</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 게임판의 상태를 나타내는 문자열 배열 board
출력 : 말이 목표위치에 도달하는데 이동해야하는 최소 거리. 단, 목표 위치에 도달할 수 없다면 -1 리턴</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>bfs</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>주어진 보드에서 시작점(&#39;R&#39;)과 목표점(&#39;G&#39;)의 위치를 탐색하고, 각각의 위치를 start와 goal 변수에 저장한다.</li>
<li>BFS를 위한 큐를 생성하고, 시작점을 큐에 추가한다. 방문 배열(visited)을 만들어 이미 방문한 위치를 기록한다.</li>
<li>큐에서 현재 위치를 꺼내고, 목표점에 도달했는지 확인한다.</li>
<li>네 방향(위, 아래, 왼쪽, 오른쪽)으로 이동하면서 장애물(&#39;D&#39;)이나 보드의 경계에 도달할 때까지 계속 이동한다.</li>
<li>새로운 위치를 찾으면 큐에 추가하고, 방문 배열을 업데이트한다.</li>
<li>목표점에 도달하면 이동 횟수를 반환하고, 도달할 수 없다면 -1을 반환한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

class Solution {
    private int[] dx = {0, 0, -1, 1}; // Direction vectors for row movement
    private int[] dy = {-1, 1, 0, 0}; // Direction vectors for column movement

    static class Point {
        int x, y, depth;
        Point(int x, int y, int depth) {
            this.x = x;
            this.y = y;
            this.depth = depth;
        }
    }

    public int solution(String[] board) {
        int n = board.length;
        int m = board[0].length();
        boolean[][] visited = new boolean[n][m];

        Queue&lt;Point&gt; queue = new LinkedList&lt;&gt;();
        Point start = null, goal = null;

        for (int i = 0; i &lt; n; i++) {
            for (int j = 0; j &lt; m; j++) {
                if (board[i].charAt(j) == &#39;R&#39;) {
                    start = new Point(i, j, 0);
                }
                if (board[i].charAt(j) == &#39;G&#39;) {
                    goal = new Point(i, j, 0);
                }
            }
        }

        queue.add(start);
        visited[start.x][start.y] = true;

        while (!queue.isEmpty()) {
            Point current = queue.poll();

            if (current.x == goal.x &amp;&amp; current.y == goal.y) {
                return current.depth;
            }

            for (int i = 0; i &lt; 4; i++) {
                int nx = current.x;
                int ny = current.y;

                while (nx + dx[i] &gt;= 0 &amp;&amp; nx + dx[i] &lt; n &amp;&amp; ny + dy[i] &gt;= 0 &amp;&amp; ny + dy[i] &lt; m
                        &amp;&amp; board[nx + dx[i]].charAt(ny + dy[i]) != &#39;D&#39;) {
                    nx += dx[i];
                    ny += dy[i];
                }

                if (!visited[nx][ny]) {
                    visited[nx][ny] = true;
                    queue.add(new Point(nx, ny, current.depth + 1));
                }
            }
        }

        return -1;
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 32일차 TIL - [프로그래머스] 무인도 여행(Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-32%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B4%EC%9D%B8%EB%8F%84-%EC%97%AC%ED%96%89Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-32%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B4%EC%9D%B8%EB%8F%84-%EC%97%AC%ED%96%89Java</guid>
            <pubDate>Fri, 23 Aug 2024 01:51:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[프로그래머스] 무인도 여행 (Java)
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/154540">https://school.programmers.co.kr/learn/courses/30/lessons/154540</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 지도를 나타내는 문자열 배열 maps[] (3 ≤ maps.length ≤ 100)
출력 : 각 섬에서 최대 머무를 수 있는 정수 배열 result[]. 단, 지낼 수 있는 무인도가 없다면 -1 리턴</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>dfs</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>n, m, visited, maps, islands를 초기화한다.</li>
<li>맵의 모든 위치를 순회한다. 만약 해당 위치가 방문되지 않았고, 바다가 아니면 DFS를 사용해 해당 섬의 크기를 계산하고 islands 리스트에 추가한다.</li>
<li>dfs에서는 현재 위치를 방문한 것으로 표기하고, 현재 위치의 숫자를 sum에 더한다.</li>
<li>네 방향(상하좌우)에 대해 다음 위치(nx, ny)가 맵 범위 내에 있고, 방문하지 않았으며, 바다가 아니면 재귀적으로 dfs를 호출하여 섬의 크기를 계속 합산한다.</li>
<li>모든 연결된 땅을 탐색 후 최종 합산된 섬의 크기를 반환한다.</li>
<li>섬이 없으면 -1을 반환하고, 섬이 있으면 섬의 크기를 오름차순 정렬 후 배열로 변환해 반환한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

class Solution {
    private int n, m; //맵의 행과 열의 크기
    private boolean[][] visited;
    private String[] maps; //입력으로 받은 맵을 저장
    private int[] dx = {0, 0, -1, 1};
    private int[] dy = {-1, 1, 0, 0};

    public int[] solution(String[] maps) {
        this.n = maps.length;
        this.m = maps[0].length();
        this.visited = new boolean[n][m];
        this.maps = maps;
        List&lt;Integer&gt; islands = new ArrayList&lt;&gt;(); //섬의 크기를 저장할 리스트

        for (int i = 0; i &lt; n; i++) {
            for (int j = 0; j &lt; m; j++) {
                if (!visited[i][j] &amp;&amp; maps[i].charAt(j) != &#39;X&#39;) {
                    islands.add(dfs(i, j));
                }
            }
        }

        if (islands.isEmpty()) {
            return new int[]{-1};
        }

        Collections.sort(islands);
        return islands.stream().mapToInt(i -&gt; i).toArray();
    }

    private int dfs(int x, int y) {
        visited[x][y] = true;
        int sum = maps[x].charAt(y) - &#39;0&#39;; //현재 위치에 있는 문자를 숫자로 변환

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

            if (nx &gt;= 0 &amp;&amp; ny &gt;= 0 &amp;&amp; nx &lt; n &amp;&amp; ny &lt; m &amp;&amp; !visited[nx][ny] &amp;&amp; maps[nx].charAt(ny) != &#39;X&#39;) {
                sum += dfs(nx, ny);
            }
        }

        return sum;
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 31일차 TIL - [백준] 점프 점프 (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-31%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%B1%EC%A4%80-%EC%A0%90%ED%94%84-%EC%A0%90%ED%94%84-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-31%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%B1%EC%A4%80-%EC%A0%90%ED%94%84-%EC%A0%90%ED%94%84-Java</guid>
            <pubDate>Thu, 22 Aug 2024 01:56:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[백준] 점프 점프 (Java)
<a href="https://www.acmicpc.net/problem/14248">https://www.acmicpc.net/problem/14248</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 첫째 줄 - 돌다리의 돌 개수 n (1 ≤ n ≤ 100,000)
둘째 줄 - 그 위치에서 점프할 수 있는 거리 Ai (1 ≤ Ai ≤ 100,000)
셋째 줄 - 출발점 s (1 ≤ s ≤ n)
출력 : 영우가 방문 가능한 돌들의 개수</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>DFS</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>n을 입력받아 돌들의 개수를 설정한다.</li>
<li>돌들의 점프 거리를 stones 배열에 입력받는다.</li>
<li>방문 여부를 확인하기 위해 visited 배열을 사용한다.</li>
<li>현재 위치에서 시작해 가능한 모든 방향으로 이동하며, 각 위치를 방문한다.</li>
<li>각 이동 시 이미 방문한 곳은 다시 방문하지 않도록 처리한다.</li>
<li>방문할 수 있는 모든 위치를 탐색해 그 수를 반환한다.</li>
<li>DFS 탐색 결과, 방문할 수 있는 위치의 개수를 출력한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

public class Main {
    static int n;
    static int[] stones;
    static boolean[] visited;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // n 입력
        n = sc.nextInt();

        // 돌들의 배열 입력
        stones = new int[n];
        for (int i = 0; i &lt; n; i++) {
            stones[i] = sc.nextInt();
        }

        // 방문 확인 배열 초기화
        visited = new boolean[n];

        // 시작 지점 입력 및 인덱스 조정
        int start = sc.nextInt() - 1;

        // DFS 실행
        int result = dfs(start);

        // 결과 출력
        System.out.println(result);

        sc.close();
    }

    // DFS 함수 정의
    static int dfs(int index) {
        // 이미 방문한 곳이면 0 리턴
        if (visited[index]) {
            return 0;
        }

        // 현재 위치 방문 처리
        visited[index] = true;

        int count = 1;  // 현재 위치를 방문했으므로 1 카운트

        // 왼쪽 점프
        int left = index - stones[index];
        if (left &gt;= 0) {
            count += dfs(left);
        }

        // 오른쪽 점프
        int right = index + stones[index];
        if (right &lt; n) {
            count += dfs(right);
        }

        return count;
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 30일차 TIL - [LeetCode]  Find Right Interval (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-30%EC%9D%BC%EC%B0%A8-TIL-LeetCode-Find-Right-Interval-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-30%EC%9D%BC%EC%B0%A8-TIL-LeetCode-Find-Right-Interval-Java</guid>
            <pubDate>Tue, 20 Aug 2024 15:44:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[LeetCode] Find Right Interval (Java)
<a href="https://leetcode.com/problems/find-right-interval/description/">https://leetcode.com/problems/find-right-interval/description/</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : int[][] intervals - [start, end]로 구성된 하나의 구간(Interval)을 나타냄
출력 : 각 구간에 대해 해당 구간의 끝점보다 크거나 같은 시작점을 가진 가장 작은 구간의 인덱스 반환. 없으면 -1 반환</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n log n)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>이진탐색</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>TreeMap&lt;Integer, Integer&gt;를 사용하여 각 인터벌의 시작점을 키로, 해당 인터벌의 인덱스를 값으로 저장한다. TreeMap은 자동으로 키를 정렬하므로 이진 탐색을 쉽게 할 수 있다.</li>
<li>각 인터벌에 대해 intervals[i][1] (인터벌의 끝점)보다 크거나 같은 최소 시작점을 찾기 위해 TreeMap의 ceilingKey 메서드를 사용한다. 이 메서드는 주어진 키보다 크거나 같은 최소의 키를 반환한다.</li>
<li>만약 ceilingKey가 null을 반환한다면, 해당 인터벌에 대해 오른쪽 인터벌이 없다는 의미이므로 결과 배열에 -1을 저장한다. 그렇지 않으면, 해당 시작점에 대응하는 인덱스를 결과 배열에 저장한다.</li>
<li>결과 배열을 출력한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

class Solution {
    public int[] findRightInterval(int[][] intervals) {
        int n = intervals.length;
        int[] result = new int[n];
        TreeMap&lt;Integer, Integer&gt; map = new TreeMap&lt;&gt;();

        // Intervals의 시작점을 TreeMap에 저장, key는 시작점, value는 인덱스
        for (int i = 0; i &lt; n; i++) {
            map.put(intervals[i][0], i);
        }

        // 각 interval에 대해 right interval 찾기
        for (int i = 0; i &lt; n; i++) {
            Integer key = map.ceilingKey(intervals[i][1]);
            result[i] = key == null ? -1 : map.get(key);
        }

        return result;
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 29일차 TIL - [LeetCode] Longest Increasing Subsequence (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-29%EC%9D%BC%EC%B0%A8-TIL-LeetCode-Longest-Increasing-Subsequence-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-29%EC%9D%BC%EC%B0%A8-TIL-LeetCode-Longest-Increasing-Subsequence-Java</guid>
            <pubDate>Tue, 20 Aug 2024 00:59:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[LeetCode] Longest Increasing Subsequence (Java)
<a href="https://leetcode.com/problems/longest-increasing-subsequence/description/">https://leetcode.com/problems/longest-increasing-subsequence/description/</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 정수 배열 nums[]
출력 : 가장 긴 증가하는 수열의 길이</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n^2)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>dp</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>dp 배열을 생성해 각 위치에서 끝나는 가장 긴 증가하는 부분 수열의 길이를 저장한다. 각 요소는 최소 자기 자신만 포함하는 LIS를 가질 수 있으므로 초기값은 1이다.</li>
<li>중첩 반복문을 통해 dp를 구현한다. </li>
<li>nums[i] &gt; nums[j]라면, nums[i]는 nums[j] 뒤에 올 수 있으므로 dp[i]를 갱신한다. 이때 dp[i]는 dp[j] + 1과 현재 dp[i] 중 최대값으로 설정한다.</li>
<li>외부 루프가 실행될 때마다 maxLength를 dp[i]와 비교하여 갱신해 가장 긴 LIS의 길이를 구한다.</li>
<li>maxLength를 반환한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.Arrays;

public class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }

        int[] dp = new int[nums.length];
        Arrays.fill(dp, 1);  // 각 요소는 최소 자기 자신만 포함하는 LIS를 가지므로 초기값은 1

        int maxLength = 1;

        for (int i = 1; i &lt; nums.length; i++) {
            for (int j = 0; j &lt; i; j++) {
                if (nums[i] &gt; nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            maxLength = Math.max(maxLength, dp[i]);
        }

        return maxLength;
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 28일차 TIL - [프로그래머스] 괄호 회전하기 (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-28%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B4%84%ED%98%B8-%ED%9A%8C%EC%A0%84%ED%95%98%EA%B8%B0-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-28%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B4%84%ED%98%B8-%ED%9A%8C%EC%A0%84%ED%95%98%EA%B8%B0-Java</guid>
            <pubDate>Mon, 19 Aug 2024 01:57:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[프로그래머스] 괄호 회전하기 (Java)
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/76502">https://school.programmers.co.kr/learn/courses/30/lessons/76502</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 : 대괄호, 중괄호, 소괄호로 이루어진 문자열 s (1 ≤ s의 길이 ≤ 1,000)
출력 : s가 올바른 괄호 문자열이 되게 하는 x의 개수 (0 ≤ x &lt; (s의 길이))</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>해시맵, 스택</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>문자열을 왼쪽으로 한 칸씩 회전시키며 반복한다.</li>
<li>회전된 문자열이 올바른 괄호 문자열인지 확인한다.</li>
<li>문자를 하나씩 읽어가며 여는 괄호는 스택에 넣는다.</li>
<li>닫는 괄호가 나오면 HashMap을 사용해 매칭되는 여는 괄호를 찾고, 스택에서 꺼낸다.</li>
<li>매칭되지 않으면 올바르지 않은 문자열로 간주해 false를 반환한다.</li>
<li>올바른 괄호 문자열인 경우의 수를 리턴한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

class Solution {
    public int solution(String s) {
        int count = 0;
        int n = s.length();

        for (int i = 0; i &lt; n; i++) {
            if (isValid(s)) {
                count++;
            }
            // 문자열 회전
            s = s.substring(1) + s.charAt(0);
        }

        return count;
    }

    private boolean isValid(String s) {
        Stack&lt;Character&gt; stack = new Stack&lt;&gt;();
        HashMap&lt;Character, Character&gt; map = new HashMap&lt;&gt;();
        map.put(&#39;)&#39;, &#39;(&#39;);
        map.put(&#39;}&#39;, &#39;{&#39;);
        map.put(&#39;]&#39;, &#39;[&#39;);

        for (char c : s.toCharArray()) {
            if (map.containsKey(c)) {
                // 닫는 괄호가 나왔을 때 스택이 비어있거나 매칭되지 않으면 false
                if (stack.isEmpty() || stack.pop() != map.get(c)) {
                    return false;
                }
            } else {
                // 여는 괄호는 스택에 넣기
                stack.push(c);
            }
        }

        return stack.isEmpty();
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 27일차 TIL - [프로그래머스] 할인 행사 (Java)]]></title>
            <link>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-27%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%95%A0%EC%9D%B8-%ED%96%89%EC%82%AC-Java</link>
            <guid>https://velog.io/@se_ize/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-27%EC%9D%BC%EC%B0%A8-TIL-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%95%A0%EC%9D%B8-%ED%96%89%EC%82%AC-Java</guid>
            <pubDate>Sat, 17 Aug 2024 11:46:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/se_ize/post/4b3ad340-ef11-4a7d-b234-9a99a04d5256/image.png" alt=""></p>
<h2 id="📌-오늘의-학습-키워드">📌 오늘의 학습 키워드</h2>
<p>[프로그래머스] 할인 행사 (Java)
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/131127">https://school.programmers.co.kr/learn/courses/30/lessons/131127</a></p>
<h2 id="📌-공부한-내용-본인의-언어로-정리하기">📌 공부한 내용 본인의 언어로 정리하기</h2>
<h3 id="문제-탐색하기">문제 탐색하기</h3>
<p>입력 :  원하는 제품을 나타내는 문자열 배열 want[], 원하는 제품의 수량을 나타내는 정수 배열 number[], 마트에서 할인하는 제품을 나타내는 문자열 배열 discount[] (1 ≤ want.length = number.length ≤ 10, 10 ≤ discount.length ≤ 100,000)
출력 : 원하는 제품을 모두 할인 받을 수 있는 회원등록 날짜의 총 일수. 단, 가능한 날이 없으면 0을 리턴</p>
<h3 id="가능한-시간복잡도">가능한 시간복잡도</h3>
<p>O(n)</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>슬라이딩 윈도우</p>
<h2 id="📌-코드-설계하기">📌 코드 설계하기</h2>
<ol>
<li>Map을 사용하여 want 리스트의 각 제품과 number 리스트에서 필요한 수량을 매핑한다.</li>
<li>discount 배열에서 연속된 10일간의 구간을 확인하기 위해 슬라이딩 윈도우를 사용한다.</li>
<li>시작 인덱스를 i로 하여, i부터 i+9까지의 10일간의 제품들을 discountMap에 저장한다.</li>
<li>현재 구간에서 필요한 모든 제품을 원하는 수량만큼 구매할 수 있는지 확인한다.</li>
<li>wantMap에 저장된 각 제품과 그 수량이 discountMap에 존재하는지, 그리고 할인된 제품(discountMap)의 수량이 원하는 수량(wantMap)보다 적은지 확인한다.</li>
<li>모든 제품을 구매할 수 있는 경우 count를 증가시킨다.</li>
<li>count를 반환한다.</li>
</ol>
<h2 id="📌-오늘의-회고">📌 오늘의 회고</h2>
<h4 id="어떤-문제가-있었고-나는-어떤-시도를-했는지">어떤 문제가 있었고, 나는 어떤 시도를 했는지</h4>
<h4 id="어떻게-해결했는지">어떻게 해결했는지</h4>
<h4 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h4>
<p>getOrDefault(Object key, V defaultValue)
key: 맵에서 찾고자 하는 키
defaultValue: 지정한 키가 맵에 존재하지 않을 때 반환할 기본값</p>
<h4 id="내일-학습할-것은-무엇인지">내일 학습할 것은 무엇인지</h4>
<p>구현</p>
<h2 id="📌-정답-코드">📌 정답 코드</h2>
<pre><code>import java.util.*;

class Solution {
    public int solution(String[] want, int[] number, String[] discount) {
        int count = 0;

        // want와 number를 Map에 저장
        Map&lt;String, Integer&gt; wantMap = new HashMap&lt;&gt;();
        for (int i = 0; i &lt; want.length; i++) {
            wantMap.put(want[i], number[i]);
        }

        // 10일간의 구간을 확인하기 위해 슬라이딩 윈도우 기법을 사용
        for (int i = 0; i &lt;= discount.length - 10; i++) {
            Map&lt;String, Integer&gt; discountMap = new HashMap&lt;&gt;();

            // 현재 10일간의 제품을 맵에 저장
            for (int j = 0; j &lt; 10; j++) {
                String product = discount[i + j];
                discountMap.put(product, discountMap.getOrDefault(product, 0) + 1);
            }

            // 현재 구간에서 원하는 제품을 모두 구매할 수 있는지 확인
            boolean canBuyAll = true;
            for (String product : wantMap.keySet()) {
                if (discountMap.getOrDefault(product, 0) &lt; wantMap.get(product)) {
                    canBuyAll = false;
                    break;
                }
            }

            // 만약 모두 구매할 수 있다면 카운트 증가
            if (canBuyAll) {
                count++;
            }
        }

        return count;
    }
}

</code></pre>]]></description>
        </item>
    </channel>
</rss>