<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>나의 구름낀 조각들</title>
    <link>https://roadj.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 4 Jul 2026 14:34:23 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>J-Mandu</managingEditor>
    <image>
      <title>나의 구름낀 조각들</title>
      <url>https://tistory1.daumcdn.net/tistory/5313336/attach/26177dbe996e4fd1800cf90cc048a64d</url>
      <link>https://roadj.tistory.com</link>
    </image>
    <item>
      <title>스프링 부트 AOP(Aspect-Oriented Programming) 완벽 가이드: 개념, 프록시 동작 원리 및 실무 예제</title>
      <link>https://roadj.tistory.com/17</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_99938a99938a9993.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qC5K9/dJMcajpa9g3/EChYNOtAxrfkK6TC2q9Mrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qC5K9/dJMcajpa9g3/EChYNOtAxrfkK6TC2q9Mrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qC5K9/dJMcajpa9g3/EChYNOtAxrfkK6TC2q9Mrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqC5K9%2FdJMcajpa9g3%2FEChYNOtAxrfkK6TC2q9Mrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;관점지향프로그래밍 요약을 설명한 이미지&quot; loading=&quot;lazy&quot; width=&quot;2816&quot; height=&quot;1536&quot; data-filename=&quot;Gemini_Generated_Image_99938a99938a9993.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;1. AOP(관점 지향 프로그래밍)의 등장 배경과 필요성&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;자바(Java) 웹 백엔드 개발을 위해 스프링(Spring) 프레임워크를 학습하다 보면 DI(의존성 주입)와 함께 가장 중요하게 다루어지는 개념이 바로 AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;객체 지향 프로그래밍(OOP)은 애플리케이션을 여러 개의 독립적인 객체로 나누고, 이들의 상호작용으로 로직을 구성합니다. 덕분에 비즈니스 로직의 모듈화와 재사용성이 크게 향상되었습니다. 하지만 애플리케이션의 규모가 커지면서 OOP만으로는 해결하기 어려운 문제에 직면하게 됩니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;바로 흩어진 관심사(Cross-cutting Concerns)의 문제입니다. 게시판 서비스를 예로 들어보겠습니다. '게시글 작성', '게시글 수정', '회원 가입'이라는 핵심 비즈니스 로직(Core Concerns)이 있습니다. 그런데 이 모든 로직 전후에 &lt;b data-index-in-node=&quot;144&quot; data-path-to-node=&quot;8&quot;&gt;실행 시간을 측정하는 로깅(Logging) 처리, 데이터베이스 트랜잭션(Transaction) 처리, 보안 인가(Security)&lt;/b&gt; 과정이 공통으로 필요하다면 어떻게 될까요?&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;모든 클래스의 메서드마다 동일한 로깅 코드와 트랜잭션 코드를 복사해서 붙여넣어야 합니다. 이는 코드의 중복을 낳고, 훗날 수정이 필요할 때 수백 개의 파일을 일일이 고쳐야 하는 유지보수의 지옥을 만들어냅니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10&quot;&gt;AOP는 바로 이 지점에서 구원투수로 등장합니다.&lt;/b&gt; 핵심 비즈니스 로직에서 공통으로 사용되는 부가 기능(관심사)을 별도의 모듈(Aspect)로 분리해 내고, 원하는 곳에 동적으로 적용하는 기술입니다. 즉, AOP는 OOP를 배척하는 것이 아니라, OOP가 더욱 객체 지향적인 원칙(특히 단일 책임 원칙)을 잘 지킬 수 있도록 돕는 완벽한 파트너입니다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;2. AOP를 정복하기 위한 핵심 용어 (Terminology)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;AOP 관련 공식 문서나 기술 블로그를 읽다 보면 난해한 용어들 때문에 좌절하기 쉽습니다. 실무에 AOP를 적용하기 위해 반드시 알아야 할 핵심 용어 6가지를 쉽게 풀어 설명해 드립니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;2.1. Aspect (애스펙트)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;정의:&lt;/b&gt; 공통으로 적용될 부가 기능(Advice)과 그 기능이 어디에 적용될지(Pointcut)를 하나로 묶은 모듈입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;비유:&lt;/b&gt; '로깅', '트랜잭션 관리' 등 흩어진 관심사를 하나의 클래스로 깔끔하게 모아둔 상자라고 생각하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;2.2. Target (타겟)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;정의:&lt;/b&gt; Aspect가 적용되는 실제 비즈니스 로직을 가진 객체입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;설명:&lt;/b&gt; 우리가 작성한 UserService, BoardService 같은 클래스들이 타겟이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;2.3. Advice (어드바이스)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;정의:&lt;/b&gt; 타겟에 제공할 실질적인 부가 기능 구현체(코드)입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;종류:&lt;/b&gt; 언제 실행될 것인지에 따라 @Before(메서드 실행 전), @After(실행 후), @Around(실행 전후 모두, 가장 강력함), @AfterReturning(정상 반환 후), @AfterThrowing(예외 발생 시)으로 나뉩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;2.4. JoinPoint (조인포인트)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;정의:&lt;/b&gt; Advice가 적용될 수 있는 애플리케이션 실행 흐름 상의 합류 지점입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;특징:&lt;/b&gt; 이론적으로는 생성자 호출 시, 필드 값 변경 시 등 다양하지만, &lt;b data-index-in-node=&quot;40&quot; data-path-to-node=&quot;20,1,0&quot;&gt;스프링 AOP는 오직 '메서드 실행 시점'만을 JoinPoint로 허용&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;2.5. Pointcut (포인트컷)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;정의:&lt;/b&gt; 무수히 많은 JoinPoint 중에서, 실제로 Advice를 적용할 타겟 메서드를 선별하는 정규 표현식 또는 조건입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0&quot;&gt;비유:&lt;/b&gt; &quot;com.example.service 패키지 밑에 있는 클래스 중 이름이 'get'으로 시작하는 메서드에만 적용해라!&quot;라고 타겟팅을 하는 필터 역할입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size23&quot;&gt;2.6. Weaving (위빙)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,0,0&quot;&gt;정의:&lt;/b&gt; Pointcut으로 결정된 타겟의 JoinPoint에 실제 Advice(부가 기능)를 끼워 넣는 과정입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size26&quot;&gt;3. 스프링 AOP의 심장: 프록시(Proxy) 동작 원리&lt;/h2&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;AOP를 구현하는 방법은 컴파일 타임에 코드를 삽입하거나, 클래스가 로드될 때 바이트코드를 조작하는 등 여러 방식(AspectJ)이 있습니다. 하지만 &lt;b data-index-in-node=&quot;84&quot; data-path-to-node=&quot;26&quot;&gt;스프링 AOP는 '프록시(Proxy) 패턴'을 기반으로 동작하는 런타임 위빙(Runtime Weaving) 방식&lt;/b&gt;을 채택하고 있습니다. 이 동작 원리를 이해하는 것은 기술 면접에서도 매우 중요합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;스프링 프레임워크가 부팅되면서 빈(Bean)들을 생성할 때, 대상 빈이 AOP 적용 대상(Target)이라면 스프링은 실제 객체를 그대로 스프링 컨테이너에 올리지 않습니다. 대신, &lt;b data-index-in-node=&quot;101&quot; data-path-to-node=&quot;27&quot;&gt;실제 객체를 감싸고 있는 가짜 객체(Proxy 객체)를 동적으로 생성하여 빈으로 등록&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트(컨트롤러 등)가 서비스의 메서드를 호출하면, 실제로는 다음의 흐름이 발생합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트가 프록시 객체의 메서드를 호출합니다.&lt;/li&gt;
&lt;li&gt;프록시 객체는 AOP에 정의된 부가 기능(Advice, 예: 로깅 시작)을 먼저 실행합니다.&lt;/li&gt;
&lt;li&gt;프록시가 실제 객체(Target)의 비즈니스 로직을 대신 호출(위임)합니다.&lt;/li&gt;
&lt;li&gt;실제 로직 실행이 끝나면, 프록시가 다시 나머지 부가 기능(예: 실행 시간 계산 후 로깅 끝)을 마무리하고 클라이언트에게 결과를 반환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;이러한 &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;30&quot;&gt;프록시 기반 동작 덕분에 우리는 기존 비즈니스 로직 코드를 단 한 줄도 수정하지 않고 부가 기능을 마음껏 추가하고 뺄 수 있는 것&lt;/b&gt;입니다. (참고로 스프링 부트 환경에서는 기본적으로 CGLIB라는 라이브러리를 사용하여 클래스 기반의 프록시를 생성합니다.)&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;31&quot; data-ke-size=&quot;size26&quot;&gt;4. 실무 완벽 적용: 커스텀 어노테이션 기반 AOP 구현 예제&lt;/h2&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;단순히 패키지 경로를 기준으로 AOP를 적용(Pointcut 표현식)하면, 원치 않는 메서드까지 적용되거나 패키지 구조가 변경되었을 때 에러가 발생하기 쉽습니다. 따라서 &lt;b data-index-in-node=&quot;95&quot; data-path-to-node=&quot;32&quot;&gt;실무에서는 개발자가 직접 만든 커스텀 어노테이션을 부착한 메서드에만 AOP가 동작하도록 구현하는 방식을 널리 사용&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 예제인 'API 실행 시간 측정 로직'을 만들어 보겠습니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;34&quot; data-ke-size=&quot;size23&quot;&gt;4.1. 의존성 (Dependency) 추가&lt;/h3&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;스프링 부트 프로젝트의 build.gradle에 AOP 스타터 의존성을 추가합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj6-6SZqbWVAxUAAAAAHQAAAAAQbA&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;Gradle&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;37&quot; data-ke-size=&quot;size23&quot;&gt;4.2. 커스텀 어노테이션 생성 (@Timer)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;시간 측정을 원하는 메서드에 붙일 용도로 활용할 어노테이션을 하나 생성합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj6-6SZqbWVAxUAAAAAHQAAAAAQbQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;Java&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD}) // 메서드에만 적용 가능
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 유지
public @interface Timer {
    // 내부 로직은 비워둡니다. 일종의 '마커(Marker)' 역할입니다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;40&quot; data-ke-size=&quot;size23&quot;&gt;4.3. Aspect 클래스 구현&lt;/h3&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;실제 시간을 측정하고 로그를 남기는 부가 기능(Advice) 클래스를 작성합니다. @Aspect와 빈 등록을 위한 @Component가 필수입니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj6-6SZqbWVAxUAAAAAHQAAAAAQbg&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;Java&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Aspect
@Component
public class TimerAspect {

    private static final Logger log = LoggerFactory.getLogger(TimerAspect.class);

    // 우리가 만든 @Timer 어노테이션이 붙은 메서드만 Pointcut으로 지정합니다.
    @Around(&quot;@annotation(com.example.demo.annotation.Timer)&quot;)
    public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 1. Target의 실제 비즈니스 로직 실행 (핵심)
        Object result = joinPoint.proceed();

        stopWatch.stop();
        
        // 2. 부가 기능 수행 (실행 시간 로깅)
        String methodName = joinPoint.getSignature().getName();
        log.info(&quot;[Timer AOP] {} 메서드 실행 시간: {} ms&quot;, methodName, stopWatch.getTotalTimeMillis());

        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 @Around 내부의 joinPoint.proceed() 입니다. 이 코드를 기점으로 위쪽이 '메서드 실행 전(Before)', 아래쪽이 '메서드 실행 후(After)'에 동작할 코드가 됩니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;44&quot; data-ke-size=&quot;size23&quot;&gt;4.4. 서비스에 적용하기&lt;/h3&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;이제 핵심 비즈니스 로직이 있는 서비스 계층에서 앞서 만든 @Timer 어노테이션만 붙여주면 끝입니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj6-6SZqbWVAxUAAAAAHQAAAAAQbw&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;Java&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.example.demo.annotation.Timer;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Timer
    public void createUser() throws InterruptedException {
        // 복잡한 비즈니스 로직이 있다고 가정
        System.out.println(&quot;회원 가입 로직 실행 중...&quot;);
        Thread.sleep(1500); // 1.5초 소요 가정
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;이제 createUser() 메서드가 호출될 때마다 스프링 프록시가 개입하여 자동으로 실행 시간을 측정하고 콘솔에 로그를 남겨줍니다. 서비스 로직 안에는 로깅 코드가 전혀 섞이지 않은 깔끔한 상태를 유지하게 됩니다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;48&quot; data-ke-size=&quot;size26&quot;&gt;5. 스프링 AOP 사용 시 치명적인 주의사항: 내부 호출 (Self-Invocation)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;스프링 AOP를 실무에 적용할 때 주니어 개발자들이 가장 많이 겪는 장애 포인트가 있습니다. 바로 '같은 클래스 내부에서의 메서드 호출 시에는 AOP가 동작하지 않는다'는 점입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;앞서 스프링 AOP는 &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;50&quot;&gt;'프록시(Proxy)'&lt;/b&gt; 객체를 통해 동작한다고 설명했습니다. 외부 컨트롤러에서 서비스 메서드를 호출할 때는 프록시를 거치기 때문에 AOP가 정상 작동합니다. 하지만, &lt;b data-index-in-node=&quot;105&quot; data-path-to-node=&quot;50&quot;&gt;서비스 클래스 내부에서 자신의 다른 메서드를 직접 호출할 때(this.method())는 프록시를 거치지 않고 실제 객체의 내부 코드를 직접 실행해버리기 때문에 AOP(부가 기능)가 발동하지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;따라서 트랜잭션(@Transactional 역시 AOP 기반)이나 캐시 등을 적용한 메서드를 설계할 때는, 외부에서 직접 호출되도록 클래스를 분리하거나 구조를 재설계해야 한다는 점을 반드시 명심하시기 바랍니다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;52&quot; data-ke-size=&quot;size26&quot;&gt;6. 마치며&lt;/h2&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 자바 스프링의 핵심 철학인 AOP의 기본 개념부터 핵심 용어, 프록시 동작 원리, 그리고 실무에서 강력하게 활용되는 커스텀 어노테이션 기반의 AOP 적용법까지 심도 있게 알아보았습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;AOP는 무분별하게 남용하면 애플리케이션의 실행 흐름을 파악하기 힘들어지는 '유지보수의 적'이 될 수 있지만, 트랜잭션, 로깅, 권한 체크 등 명확한 '공통 인프라 로직'에 적절히 사용한다면 코드의 품질을 비약적으로 끌어올려 주는 최고의 무기입니다. 오늘 예제를 직접 타이핑해 보시면서 AOP의 매력을 꼭 느껴보시길 바랍니다.&lt;/p&gt;</description>
      <category>Spring</category>
      <category>aop</category>
      <category>java</category>
      <category>Spring</category>
      <category>관점지향프로그래밍</category>
      <category>백엔드</category>
      <category>스프링</category>
      <category>스프링 AOP</category>
      <category>스프링부트</category>
      <category>웹개발</category>
      <category>자바</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/17</guid>
      <comments>https://roadj.tistory.com/17#entry17comment</comments>
      <pubDate>Fri, 3 Jul 2026 10:15:17 +0900</pubDate>
    </item>
    <item>
      <title>서블릿 필터 vs 스프링 인터셉터</title>
      <link>https://roadj.tistory.com/15</link>
      <description>&lt;p&gt;&lt;img src=&quot;https://www.baeldung.com/wp-content/uploads/2021/05/filters_vs_interceptors-1024x382.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;서블릿 필터&lt;/h2&gt;
&lt;h3&gt;Filter란?&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 흐름
HTTP 요청 → WAS → 필터 → 서블릿 → 컨트롤러

// 제한
HTTP 요청 → WAS → 필터(서블릿 호출 x)

// 체인
HTTP 요청 → WAS → 필터 → 필터2 → 필터3 → 서블릿 → 컨트롤러&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;서블릿이 지원하는 기능으로 서블릿이 호출하기 전에 필터 로직이 실행되며,&lt;br&gt;특정 URL 패턴을 사용하여 특정 URL 요청에 대하여 적용할 수 있습니다.&lt;br&gt;필터는 로직에 의해서 적절하지 않은 요청이라고 판단할 경우 서블릿 호출을 하지 않습니다.&lt;br&gt;그리고 필터는 체인으로 구성되는데, 중간에 필터를 자유롭게 추가할 수 있습니다.&lt;br&gt;참고로 서블릿은 spring의 DispatcherServlet 입니다.&lt;/p&gt;
&lt;h3&gt;Filter 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public interface Filter {

    public default void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    public default void destroy() {}
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;init : 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출합니다.&lt;/li&gt;
&lt;li&gt;doFilter : 요청이 올 떄 마다 해당 메서드가 호출됩니다.&lt;/li&gt;
&lt;li&gt;destory : 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@Slf4j
public class LogFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        log.info(&amp;quot;REQUEST [{}]&amp;quot;, requestURI);
        chain.doFilter(request, response);
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean&amp;lt;Filter&amp;gt; filterRegistrationBean = new FilterRegistrationBean&amp;lt;&amp;gt;();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns(&amp;quot;/*&amp;quot;);
        return filterRegistrationBean;
    }

 /*
     필터 추가등록 방법
    @Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean&amp;lt;Filter&amp;gt; filterRegistrationBean = new FilterRegistrationBean&amp;lt;&amp;gt;();
        filterRegistrationBean.setFilter(new LoginCheckFilter());
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.addUrlPatterns(&amp;quot;/posts/*&amp;quot;);
        return filterRegistrationBean;
    }   
 */
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SpringBoot의 내장 톰캣을 사용하는 경우에만 쓸 수 있는 방법입니다.&lt;br&gt;그 이유는 FilterRegistrationBean은 스프링 부트에서 필터를 내장 톰캣의 서블릿 컨텍스트에 추가할 수 있도록 지원하는 빈입니다.&lt;br&gt;만약 다른 was를 사용하는 경우 해당 was의 서블릿 컨텍스트에 필터를 등록할 수 있는 방법을 찾으시면 됩니다.&lt;br&gt;ex) jeus를 was로 사용하는 경우 web.xml을 통해 필터를 등록합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;doFilter&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;chain.doFilter()를 호출하여 다음 필터를 호출합니다. (다음 호출할 필터가 없는 경우 서블릿이 호출됩니다.)&lt;/li&gt;
&lt;li&gt;ServletRequest, ServletResponse가 파라미터인 이유는 HTTP 요청이 아닌 경우까지 고려해서 만든 인터페이스이기 때문입니다.&lt;br&gt;그래서 HTTP를 사용하면 HTTPServletRequest, HTTPServletResponse로 인스턴스가 생성됩니다.&lt;br&gt;참고로 HTTPServletRequest, HTTPServletResponse는 ServletRequest, ServletResponse 상속합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;FilterRegistrationBean&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;setFilter : 필터를 등록합니다.&lt;/li&gt;
&lt;li&gt;setOrder : 필터 순번을 정합니다.&lt;/li&gt;
&lt;li&gt;addUrlPatterns : 필터를 적용시킬 url 패턴을 등록합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;스프링 인터셉터&lt;/h2&gt;
&lt;h3&gt;Interceptor란?&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 흐름
HTTP 요청 → WAS → 필터 → 서블릿 → 인터셉터 → 컨트롤러

// 제한
HTTP 요청 → WAS → 필터 → 서블릿 → 인터셉터(컨트롤러 x)

// 체인
HTTP 요청 → WAS → 필터 → 서블릿 → 인터셉터 → 인터셉터2 → 인터셉터3 → 컨트롤러&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;스프링 MVC가 컨트롤러가 호출되기 전에 인터셉터가 호출이 됩니다.&lt;br&gt;인터셉터도 URL 패턴을 사용하여 특정 URL 요청에 대하여 적용할 수 있습니다.&lt;br&gt;서블릿 URL패턴과 다르게 매우 정밀하게 설정할 수 있습니다.&lt;br&gt;인터셉터도 로직에 의해서 적절하지 않은 요청이라고 판단할 경우 컨트롤러을 호출하지 않습니다.&lt;br&gt;인터셉터도 체인으로 구성되어 중간에 인터셉터를 자유롭게 추가할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Interceptor 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public interface HandlerInterceptor {

    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return true;
    }


    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
    }


    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46990595/227729966-0d9d57f1-49d0-40de-a6fb-2e219964acf4.png&quot; alt=&quot;스크린샷 2023-03-26 오전 1 33 20&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;호출 흐름&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;preHandle : 핸들러 어댑터 호출전에 호출됩니다.&lt;/li&gt;
&lt;li&gt;postHandle : 핸들러 어댑터 호출 후에 호출됩니다.&lt;/li&gt;
&lt;li&gt;afterCompletion : 뷰 렌더링 이후에 호출됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/46990595/227731118-bf29b3d1-5a45-47cb-ab79-74269ecd69b7.png&quot; alt=&quot;스크린샷 2023-03-26 오전 1 57 14&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예외가 발생시&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;preHandle : 핸들러 어댑터 호출전에 호출됩니다.&lt;/li&gt;
&lt;li&gt;postHandle : 컨트롤러에서 예외가 발생할 경우 호출되지 않습니다.&lt;/li&gt;
&lt;li&gt;afterCompletion : 항상 호출 되고, 예외 발생시 파라미터로 예외를 받아서 확인 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@Sl4fj
public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
        Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        log.error(&amp;quot;preHandle {} {}&amp;quot;, requestURI, handler);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
        ModelAndView modelAndView) throws Exception {
        String requestURI = request.getRequestURI();
        log.error(&amp;quot;postHandle {} {}&amp;quot;, requestURI, handler);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
        Object handler, Exception ex) throws Exception {
        String requestURI = request.getRequestURI();
        log.error(&amp;quot;afterCompletion {} {}&amp;quot;, requestURI, handler);
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(new LogInterceptor())
            .order(1)
            .addPathPatterns(&amp;quot;/**&amp;quot;);
            .excludePathPatterns(&amp;quot;/&amp;quot;, &amp;quot;/auth/login&amp;quot;, &amp;quot;/auth/sign-up&amp;quot;, &amp;quot;/auth/logout&amp;quot;
                , &amp;quot;/posts&amp;quot;, &amp;quot;/css/**&amp;quot;, &amp;quot;/img/**&amp;quot;, &amp;quot;/error&amp;quot;, &amp;quot;/favicon.ico&amp;quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;preHandle&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리턴 타입이 true일 경우 다음 인터셉터를 호출합니다.(인터셉터가 없을경우 컨트롤러가 호출 됩니다.)&lt;/li&gt;
&lt;li&gt;리턴 타입이 false일 경우 인터셉터나 컨트롤러를 호출하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;postHandle&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컨트롤러가 호출 후에 호출이 되기때문에 ModelAndView를 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;afterCompletion&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예외가 발생하여도 호출되기 때문에 Exception을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;handler&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Controller, @RequestMapping인 경우 HandlerMethod 인스턴스가 생성됩니다.&lt;/li&gt;
&lt;li&gt;/resources/static와 같은 정적 리소스인 경우 ResourceHttpRequestHandler 인스턴스가 생성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;addInterceptors&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;order : 인터셉터의 호출 순서를 정합니다.&lt;/li&gt;
&lt;li&gt;addPathPatterns : 인터셉터를 적용할 URL 패턴을 지정합니다.&lt;/li&gt;
&lt;li&gt;excludePathPatterns : 인터셉터에서 제외할 패턴을 지정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Filter와 Interceptor의 차이점&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;관리하는 컨테이너&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Filter : spring과 상관없이 servlet에서 제공하는 기능이기 때문에 Servlet 컨테이너(was)에서 관리합니다.&lt;/p&gt;
&lt;p&gt;Intercptor : spring mvc에서 제공되는 기능으로 spring 컨테이너에서 관리합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;처리 순서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Filter : 등록된 순서대로 처리되며(지정한 순번이 있으면 순번대로 처리), 마지막 Filter에서 처리가 완료되면&lt;br&gt;실제 요청을 처리하는 Servlet이 호출됩니다.&lt;/p&gt;
&lt;p&gt;Interceptor : 지정한 순번대로 처리되며, 마지막 Interceptor에서 처리가 완료되면 실제 요청을 처리하는&lt;br&gt;HandlerApdator(controller)가 호출됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요청과 응답&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Filter : HTTP 요청과 응답의 처리 전/후로 조작합니다.&lt;/p&gt;
&lt;p&gt;Interceptor : HandlerApdaptor(controller) 요청과 응답의 처리 전/후로 조작합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;등록 방법&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Filter : was에 따라 등록 방법이 다릅니다.&lt;/p&gt;
&lt;p&gt;Interceptor : was에 상관없이 spring mvc에서 제공되는 방법으로 동일합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;구현 방법&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Filter : java.servlet 패키지의 Filter 인터페이스를 통해 구현합니다.&lt;/p&gt;
&lt;p&gt;Interceptor : org.springframework.web.servlet 패키지의 HandlerInterceptor 인터페이스를 통해 구현합니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.baeldung.com/spring-mvc-handlerinterceptor-vs-filter&quot;&gt;https://www.baeldung.com/spring-mvc-handlerinterceptor-vs-filter&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://mangkyu.tistory.com/173&quot;&gt;https://mangkyu.tistory.com/173&lt;/a&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Filter</category>
      <category>Interceptor</category>
      <category>만두</category>
      <category>인터셉터</category>
      <category>필터</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/15</guid>
      <comments>https://roadj.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 26 Mar 2023 17:08:50 +0900</pubDate>
    </item>
    <item>
      <title>Git 기초</title>
      <link>https://roadj.tistory.com/14</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;설정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;config&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ git config --global user.name &quot;{사용자 이름}&quot;
$ git config --global user.email {이메일}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git을 설치하고 나서 가장 먼저 해야 하는 것은 사용자이름과 이메일 주소를 설정해야 합니다.&lt;br /&gt;Git은 커밋할 때마다 이 정보를 사용하고 한 번 커밋한 후에는 정보를 변경할 수 없습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ git config --list
credential.helper=osxkeychain
core.excludesfile=/Users/{유저}/.gitignore_global
core.editor=vim
user.name={사용자 이름}
user.email={이메일}
init.defaultbranch=main
filter.lfs.process=git-lfs filter-process
filter.lfs.required=true
filter.lfs.clean=git-lfs clean -- %f
filter.lfs.smudge=git-lfs smudge -- %f
commit.template=/Users/minkyujin/.stCommitMsg
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=true

$ git config user.name
jin&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정한 정보를 확인하고 싶을 때 --list 옵션을 줍니다.&lt;br /&gt;git config 명령으로 특정 key에 대한 값을 확인할 수도 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;저장소 만들기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;init&lt;/h3&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt;$ cd {디렉토리}

$ git init
{디렉토리} 안의 빈 깃 저장소를 다시 초기화했습니다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 디렉토리를 Git 저장소로 만들때 쓰는 명령어이고 해당 디렉토리에 &lt;code&gt;.git&lt;/code&gt; 하위 디렉토리를 만듭니다.&lt;br /&gt;.git 디렉토리에는 저장소에 필요한 뼈대 파일이 들어있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;clone&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;// 디렉토리 명을 생략시 가져올 저장소 프로젝트 명으로 디렉토리가 생성됩니다.
$ git clone {저장소 주소}

$ git clone {저장소 주소} {디렉토리 명}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git 저장소를 복사하고 싶을 때 쓰는 명령어이고 프로젝트의 모든 데이터를 가져옵니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;저장소에 저장하기&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://git-scm.com/book/en/v2/images/lifecycle.png&quot; alt=&quot;git life cycle&quot; /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;status&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;$ git status 
현재 브랜치 main

아직 커밋이 없습니다

추적하지 않는 파일:
  (커밋할 사항에 포함하려면 &quot;git add &amp;lt;파일&amp;gt;...&quot;을 사용하십시오)
    f1.txt

커밋할 사항을 추가하지 않았지만 추적하지 않는 파일이 있습니다 (추적하려면 &quot;git add&quot;를 사용하십시오)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일의 상태를 확인하고 싶을 때 쓰는 명령어 입니다.&lt;br /&gt;Untracked, Modified, Staged 상태들의 파일들을 보여줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;status 내부 동작 원리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커밋할 사항이 없는 경우(clean) : Warking Directory, Staging Area(index), Local Repository(최신 Commit 개체의 Tree 개체)&lt;br /&gt;내용이 모두 같을 경우&lt;/li&gt;
&lt;li&gt;추적하지 않는 상태(Untracked) : StagingArea(index)에 Warking Directory의 파일들이 없는 경우&lt;/li&gt;
&lt;li&gt;커밋하도록 정하지 않은 변경사항(Modified) : Stagin gArea(index)과 Warking Directory의 파일 내용들이 다른 경우&lt;/li&gt;
&lt;li&gt;커밋할 변경 사항이 있는 경우(Staged) : Warking Directory, Staging Area(index)의 파일내용은 같지만&lt;br /&gt;Local Repository(최신 Commit 개체의 Tree 개체)의 파일 내용이 다를 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;add&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;$ git add {파일명}
현재 브랜치 main

아직 커밋이 없습니다

커밋할 변경 사항:
  (스테이지 해제하려면 &quot;git rm --cached &amp;lt;파일&amp;gt;...&quot;을 사용하십시오)
    새 파일:       f1.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 새로 추적하고 싶을 때 쓰는 명령이고 commit에 추가될 Staged 상태입니다.&lt;br /&gt;이미 Tracked 상태이지만 수정했을 경우 Staged 상태로 만들때도 사용합니다.&lt;br /&gt;상태를 확인하는 명령어(status)를 사용하여 추적 상태를 볼수 있습니다.&lt;br /&gt;Staged 상태 : Staging Area에 넣은 상태&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;add 내부 예상 동작 순서&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// object 파일들
$ find .git/objects -type f
.git/objects/b6/8025345d5301abad4d9ec9166f455243a0d746
.git/objects/78/981922613b2afb6025042ff6bd878ac1994e85

// object 내용
$ git cat-file -p 78981922613b2afb6025042ff6bd878ac1994e85
a

// 인덱스
$ git ls-files -s
100644 78981922613b2afb6025042ff6bd878ac1994e85 0    f1.txt
100644 b68025345d5301abad4d9ec9166f455243a0d746 0    f2.txt
100644 78981922613b2afb6025042ff6bd878ac1994e85 0    f3.txt&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 내용과 몇가지 부가적인 정보를 압축하여 그 결과를 해쉬 알고리즘(sha1) 통하여 해쉬 값을 얻습니다.&lt;/li&gt;
&lt;li&gt;.git/objects/{해쉬 값 앞자리 2개}/{나머지 해쉬 값} 디렉토리와 파일을 만들고 파일내용을 저장합니다.&lt;/li&gt;
&lt;li&gt;.git/index 파일에 object 파일명과 실제 파일명(add 한 파일명) 적어둡니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 f1.txt와 f3.txt는 파일 내용이 같아서 인덱스에서 같은 obejct를 가르킵니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;commit&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;// 쉘의 EDITOR 환경 변수에 등록된 편집기로 편집기가 실행되며 아래와 같은 메세지가 포함됩니다.
// git config --global core.editor 명령으로 어떤 편집기를 사용할지 설정할 수 있습니다.
$ git commit
# 변경 사항에 대한 커밋 메시지를 입력하십시오. '#' 문자로 시작하는
# 줄은 무시되고, 메시지를 입력하지 않으면 커밋이 중지됩니다.
#
# 현재 브랜치 main
# 커밋할 변경 사항:
#       수정함:        f1.txt
#

$ git commit -m {커밋 메세지}
[main (최상위-커밋) 0db3db0] 1
 1 file changed, 1 insertion(+)
 create mode 100644 f1.txt

$ git commit -am {커밋 메세지}

$ git commit --amend&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Staging Area에 있는 변경사항에 대하여 버전을 만들고 local 저장소에 저장하고 싶을 때 쓰는 명령입니다.&lt;br /&gt;커밋 메세지는 &lt;a href=&quot;https://velog.io/@outstandingboy/Git-%EC%BB%A4%EB%B0%8B-%EB%A9%94%EC%8B%9C%EC%A7%80-%EA%B7%9C%EC%95%BD-%EC%A0%95%EB%A6%AC-the-AngularJS-commit-conventions&quot;&gt;Angular 커밋 메세지 규약&lt;/a&gt;을 보고 작성합니다.&lt;br /&gt;-a 옵션을 추가하여 Tracked 상태인 파일들을 자동으로 Staging Area에 넣어줍니다.&lt;br /&gt;-m 옵션을 추가하여 인라인으로 메세지를 첨부할 수 있습니다.&lt;br /&gt;--amend 옵션을 추가하여 이전 커밋을 덮어쓸 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;commit 내부 예상 동작 순서&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://git-scm.com/book/en/v2/images/data-model-3.png&quot; alt=&quot;Git 저장소 내의 모든 개체.&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 현재 최신 커밋의 tree 개체 내용보기
$ git cat-file -p main^{tree}
100644 blob 78981922613b2afb6025042ff6bd878ac1994e85    f1.txt
100644 blob 2795c87096b42d4b7b8fda82a67dabded5c72c97    f2.txt
100644 blob 78981922613b2afb6025042ff6bd878ac1994e85    f3.txt
100644 blob 8baef1b4abc478178b004d62031cf7fe6db6f903    f4.txt

// 커밋 개체 내용보기
$ git cat-file -p 95d8893506c3c7c50d48b645e7a1f0bec74f1cb1
tree 1f1862a4f1875b673a186e10d99ac4b7044dc852
parent 527f5c509de3b26f3dee8eb432db618cbfa32cfb
author road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1677822895 +0900
committer road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1677822895 +0900

3

// 위의 커밋메시지에 있는 tree 개체 내용 보기
$ git cat-file -p 1f1862a4f1875b673a186e10d99ac4b7044dc852
100644 blob 78981922613b2afb6025042ff6bd878ac1994e85    f1.txt
100644 blob 2795c87096b42d4b7b8fda82a67dabded5c72c97    f2.txt
100644 blob 78981922613b2afb6025042ff6bd878ac1994e85    f3.txt
100644 blob 8baef1b4abc478178b004d62031cf7fe6db6f903    f4.txt&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.git/objects/ 디렉토리에 Staging Area(index)의 상태대로 Tree object를 만듭니다.&lt;/li&gt;
&lt;li&gt;.git/objects/ 디렉토리에 1번의 Tree 개체와 이전 Commit 개체와 Author/Committer 정보, 커밋 메시지를 토대로 Commit 개체를 만듭니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree 개체 : SHA-1 포인터, 파일 모드, 개체 타입, 파일 이름 순으로 작성되어 있고 Tree 개체를 통해서 커밋 시점의 파일들을 알 수 있습니다.&lt;br /&gt;그리고 해당 시점을 찍어서 나타낸다는 의미로 스냅샷이라고 하고 스냅샷을 가지고 있는 구조를 Tree 개체라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Commit 개체 : Tree 개체, parent(이전 Commit 개체이며 없으면 생략), Author/Committer 정보, 줄바꿈,&lt;br /&gt;commit message 순으로 작성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Author/Committer 정보 : Git 설정에서 했던 user.name과 user.email 정보를 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Object 파일 종류&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;blob : 파일의 내용을 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;Tree 개체&lt;/li&gt;
&lt;li&gt;Commit 개체&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;되돌리기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;reset&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 원본 파일 생성
$ vi f1.txt
init

$ git add f1.txt

$ git commit 1

// 로컬 저장소에 저장할 파일
$ vi f1.txt
repository

$ git commit -am 2

// Staging Area(index)에 저장할 파일
$ vi f1.txt
index

$ git add f1.txt

// working directory에 저장할 파일
$ vi f1.txt
working directory

// 로컬 저장소의 f1.txt 내용 보기
$ git cat-file -p HEAD                                           
tree 7b60d3bfdfa15b16c74154cd889a52fd089c5117
author road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1677997501 +0900
committer road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1677997501 +0900

1

$ git cat-file -p 7b60d3bfdfa15b16c74154cd889a52fd089c5117
100644 blob 09d06c8f2087539716887077f2c0f8df5ab0ee24    f1.txt

$ git cat-file -p 09d06c8f2087539716887077f2c0f8df5ab0ee24
repository

// Staging Area(index)의 f1.txt 내용 보기
$ git ls-files -s
100644 9015a7a32ca0681be64471d3ac2f8c1f24c1040d 0    f1.txt

$ git cat-file -p 9015a7a32ca0681be64471d3ac2f8c1f24c1040d 
index

// working directory의 f1.txt 파일 내용 보기
$ cat f1.txt
working directory

$ git log
commit 1290e14f1e6d56a93f3e3e0e7c403ad15b5c4ca2 (HEAD -&amp;gt; main)
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 15:38:06 2023 +0900

    2

commit 6df57e7dd878e6e0c08b93cda9ca733b4e882591
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 15:37:50 2023 +0900

        1

// HEAD가 참조하고 있는 브랜치 파일내용을 해당 커밋개체로 수정합니다.
$ git reset --soft 6df57e7dd878e6e0c08b93cda9ca733b4e882591

$ cat .git/refs/heads/main
6df57e7dd878e6e0c08b93cda9ca733b4e882591

$ git diff
diff --git a/f1.txt b/f1.txt
index 9015a7a..31e5f9a 100644
--- a/f1.txt
+++ b/f1.txt
@@ -1 +1 @@
-index
+working directory

// repository에 reset 하기 전의 커밋개체로 수정(원상 복구)
$ git reset --soft ORIG_HEAD

// soft 옵션 + index 파일내용을 해당 커밋개체의 tree 개체를 기반으로 수정합니다.
$ git reset --mixed 6df57e7dd878e6e0c08b93cda9ca733b4e882591

$ git diff
diff --git a/f1.txt b/f1.txt
index b1b7161..31e5f9a 100644
--- a/f1.txt
+++ b/f1.txt
@@ -1 +1 @@
-init
+working directory

// working directory, index, repository 전부 f1.txt 파일내용이 repository로 변경
git reset --hard ORIG_HEAD&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://s3-ap-northeast-2.amazonaws.com/opentutorials-user-file/module/2676/5131.png&quot; alt=&quot;reset mode&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전으로 돌아가고 싶을 때 쓰는 명령 입니다.&lt;br /&gt;reset을 할 경우 .git/ORIG_HEAD 파일이 생성되고 내용에 reset 하기전의 커밋개체가 작성 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Mode 옵션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값은 --mixed 입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;--soft {커밋 개체 해쉬 값} : HEAD가 참조하고 있는 브랜치 파일내용을 해당 커밋개체 해쉬 값으로 수정합니다.&lt;br /&gt;ex) working directory와 staging area에는 남기고 다시 작업하고 싶을경우(커밋 취소)&lt;/li&gt;
&lt;li&gt;-- mixed {커밋 개체 해쉬 값} : soft 옵션 + index 파일내용을 해당 커밋개체의 tree 개체를 기반으로 수정합니다.&lt;br /&gt;ex) working directory 남기고 다시 작업하고 싶을 경우(Add 취소)&lt;/li&gt;
&lt;li&gt;-- hard {커밋 개체 해쉬 값} : soft 옵션 + mixed 옵션 + 실제 파일 내용을 해당 커밋개체의 내용으로 수정합니다.&lt;br /&gt;ex) 커밋 후 작업했던 내용들을 전부 날리고 다시하고 싶을 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;revert&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ vi f1.txt
init

$ git add f1.txt

$ git commit -m 1

$ vi f1.txt
init
test

$ git commit -am 2

$ git log
commit 80d55e3a5c929264244ae89dfa5877f4c0289705
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 20:56:49 2023 +0900

    2

commit 47d63f089cf93d4218630191609372326cf06a6b
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 20:56:34 2023 +0900

    1

$ git revert 80d55e3a5c929264244ae89dfa5877f4c0289705
[main ebc07f9] Revert &quot;2&quot;
 1 file changed, 1 deletion(-)

$ cat f1.txt
init

$ git log
commit ebc07f90ef14f3c3a4218424e641e1b36d9b7bf3 (HEAD -&amp;gt; main)
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 21:09:19 2023 +0900

    Revert &quot;2&quot;

    This reverts commit 80d55e3a5c929264244ae89dfa5877f4c0289705.

commit 80d55e3a5c929264244ae89dfa5877f4c0289705
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 20:56:49 2023 +0900

    2

commit 47d63f089cf93d4218630191609372326cf06a6b
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 20:56:34 2023 +0900

    1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 커밋 개체의 변화만 되돌리고 싶을 때 쓰는 명령어 입니다.&lt;br /&gt;해당 커밋 개체의 변화만 되돌리고 커밋개체를 새로 생성합니다.&lt;br /&gt;만약 47d63f089 커밋 개체로 revert를 할 경우&lt;br /&gt;47d63f089 커밋 이후에 생긴 커밋 개체들의 변화들은 git이 처리하지 못하기 때문에 충돌현상 납니다.&lt;br /&gt;그래서 사용자가 직접 충돌현상을 작업해야 하기 때문에 만약에 47d63f089 revert를 하고 싶으면&lt;br /&gt;최상위 커밋 개체부터 차근차근 revert를 해야합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;브랜치 만들기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;branch&lt;/h3&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;// 브랜치 보기
$ git brach
* main

// 브랜치 생성
// 커밋 개체 해쉬 값이 없는 경우 최신 커밋 개체로 됨
$ git branch {브랜치 명} [{커밋 개체 해쉬 값}]

// 브랜치 보기
$ git branch
  exp
* main

// 브랜치 삭제
$ git branch -d {브랜치 명}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 통째로 복사하고 나서 원래 코드와는 상관없이 독립적으로 개발하는 것이 브랜치입니다.&lt;br /&gt;현재 브랜치를 모두 보여주는 명령어 입니다.&lt;br /&gt;{브랜치 명} 옵션을 추가하면 브랜치를 생성해 줍니다.&lt;br /&gt;-d {브랜치 명} 옵션을 추가하면 브랜치를 삭제합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;branch 내부 동작 원리&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 처음 깃 저장소를 만들시에 생성되는 HEAD
$ cat .git/HEAD
ref: refs/heads/main

// 파일 생성 후 내용 저장
$ vi f1.txt

// Staging Area 추가
$ git add f1.txt

// commit
$ git commit -m 1
[main (최상위-커밋) 07062c5] 1
 1 file changed, 1 insertion(+)
 create mode 100644 f1.txt

// main 브랜치가 어떤 커밋 개체를 가르키는지 확인
$ cat .git/refs/heads/main
07062c5e54b022fecdf6043226d73696f899527e&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HEAD 파일은 git 저장소를 만들시에 생성되며 현재 브랜치를 나타내는 파일입니다.&lt;/li&gt;
&lt;li&gt;브랜치들은 .git/refs/heads/{브랜치 명} 파일을 갖게 되고 커밋개체를 가르킵니다.&lt;br /&gt;main 브랜치는 커밋을 하고난 후에 파일이 생기며 그 후에 생성되는 브랜치들은&lt;br /&gt;main 커밋개체를 가르키기 때문에 바로 파일이 생성 됩니다.&lt;/li&gt;
&lt;li&gt;해당 브랜치를 이동할 때 HEAD 파일 내용에 이동할 Branch 파일 디렉토리로 수정됩니다.&lt;/li&gt;
&lt;li&gt;브랜치에서 커밋할 경우 Branch 파일 내용에 해당 커밋 개체 해쉬 값로 수정됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;switch&lt;/h3&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;// 브랜치 변경
$ git switch exp
'exp' 브랜치로 전환합니다

// 브랜치 생성 후 해당 브랜치로 변경
// 커밋 개체 해쉬 값이 없는 경우 최신 커밋 개체로 됨
$ git switch -c {브랜치 명} [{커밋 개체 해쉬 값}]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브랜치를 변경할 때 쓰는 명령어 입니다.&lt;br /&gt;-c {브랜치 명} 옵션을 추가하여 브랜치를 생성할 수 있고 {커밋 개체 해쉬 값} 옵션을 주어&lt;br /&gt;브랜치가 원하는 시점의 파일들을 가질 수 있게 해줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;브랜치 정보확인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;log&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 현재 브랜치의 커밋 목록
$ git log
commit b1a15fcf0dde684c19d8402c24316de2601e8b6f (HEAD -&amp;gt; main)
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sat Mar 4 00:05:06 2023 +0900

    4

commit 5a6fc468c7e6187d983e3a43a63dc337ea0aea67
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Fri Mar 3 19:54:02 2023 +0900

    2

commit 013c5f61c7d303eb3e8ee1f28035747c460099d0
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Fri Mar 3 19:53:43 2023 +0900

    1

// 모든 브랜치의 커밋 목록
$ git log --branches
commit b1a15fcf0dde684c19d8402c24316de2601e8b6f (HEAD -&amp;gt; main)
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sat Mar 4 00:05:06 2023 +0900

    4

commit 9f01c304fd6e1b7a9dafd296383ad1eeea54dd22 (exp)
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Fri Mar 3 23:26:15 2023 +0900

    3

commit 5a6fc468c7e6187d983e3a43a63dc337ea0aea67
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Fri Mar 3 19:54:02 2023 +0900

    2

commit 013c5f61c7d303eb3e8ee1f28035747c460099d0
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Fri Mar 3 19:53:43 2023 +0900

    1

// 모든 브랜치의 커밋 목록을 간략하게 그래프 표시    
$ git log --branches --graph --oneline
* b1a15fc (HEAD -&amp;gt; main) 4
| * 9f01c30 (exp) 3
|/
* 5a6fc46 2
* 013c5f6 1

// 브랜치1에 없고 브랜치2에만 있는 커밋 내용 표시
$git log -p {브랜치1}..{브랜치2}
commit 9f01c304fd6e1b7a9dafd296383ad1eeea54dd22 (exp)
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Fri Mar 3 23:26:15 2023 +0900

    3

diff --git a/f1.txt b/f1.txt
index 422c2b7..de98044 100644
--- a/f1.txt
+++ b/f1.txt
@@ -1,2 +1,3 @@
 a
 b
+c
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브랜치에 대한 커밋 목록을 보고 싶을 때 쓰는 명령어 입니다.&lt;br /&gt;--branches 옵션을 추가하면 모든 브랜치에 대한 커밋 목록을 보여줍니다.&lt;br /&gt;-- graph -- oneline 옵션을 추가하면 모든 브랜치에 대한 커밋 목록을 그래프로 간략하게 표시해줍니다.&lt;br /&gt;-p {브랜치1}..{브랜치2} 옵션을 추가하면 브랜치1에는 없고 브랜치2에는 있는 커밋한 파일들의 내용들을 비교해서 보여줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;diff&lt;/h3&gt;
&lt;pre class=&quot;diff&quot;&gt;&lt;code&gt;$ git diff {브랜치1}..{브랜치2}
diff --git a/f1.txt b/f1.txt
index 422c2b7..de98044 100644
--- a/f1.txt
+++ b/f1.txt
@@ -1,2 +1,3 @@
 a
 b
+c
diff --git a/f2.txt b/f2.txt
deleted file mode 100644
index b0f6d94..0000000
--- a/f2.txt
+++ /dev/null
@@ -1 +0,0 @@
-4444&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 브랜치에 대한 현재의 커밋 내용을 비교할 때 쓰는 명령어 입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;브랜치 합치기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;merge&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;$ git merge {브랜치}
Merge made by the 'ort' strategy.
 f1.txt | 1 +
 1 file changed, 1 insertion(+)

// Fast-forward 전략을 사용하지 않고 merge
$ git merge {브랜치} --no-ff&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 브랜치에서 {브랜치}의 내용들을 병합할 때 쓰는 명령어 입니다.&lt;br /&gt;merge 전략에 따라 새로운 커밋 개체가 생성되고 커밋 메시지는 자동적으로 쓰여집니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;merge 전략&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://git-scm.com/book/en/v2/images/basic-branching-4.png&quot; alt=&quot;&quot; /&gt;&lt;img src=&quot;https://git-scm.com/book/en/v2/images/basic-branching-5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Fast forward : 현재 브랜치 Head를 merge할 브랜치 Head로 변경하는 전략입니다.&lt;br /&gt;어떤 브랜치를 사용했고 언제 병합했는지 기록이 남지않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://git-scm.com/book/en/v2/images/basic-merging-1.png&quot; alt=&quot;&quot; /&gt;&lt;img src=&quot;https://git-scm.com/book/en/v2/images/basic-merging-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3-way merge : Fast forward로 merge를 못할 경우 두 브랜치의 공통 조상이 되는 커밋과 두 브랜치의 커밋들을 비교하여&lt;br /&gt;병합에 반영하고 충돌이 있는 내용은 사용자에게 해결을 맡기고 결과로 새로운 커밋을 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;충돌 해결&lt;/h4&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;$ git merge {브랜치}
자동 병합: common.txt
충돌 (내용): common.txt에 병합 충돌
자동 병합이 실패했습니다. 충돌을 바로잡고 결과물을 커밋하십시오.

$ git status
현재 브랜치 main
병합하지 않은 경로가 있습니다.
  (충돌을 바로잡고 &quot;git commit&quot;을 실행하십시오)
  (병합을 중단하려면 &quot;git merge --abort&quot;를 사용하십시오)

병합하지 않은 경로:
  (해결했다고 표시하려면 &quot;git add &amp;lt;파일&amp;gt;...&quot;을 사용하십시오)
    양쪽에서 수정:  common.txt

커밋할 변경 사항을 추가하지 않았습니다 (&quot;git add&quot; 및/또는 &quot;git commit -a&quot;를
사용하십시오)

$ vi common.txt
function b(){
}
&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
function a(master, exp){
=======
function exp(){
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; exp
}
function c(){
}

$ git add common.txt

$ git commit&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌된 파일에서 &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD 아래 코드가 현재 브랜치 코드입니다.&lt;br /&gt;======= 구분되어 있으며 구분 문자 바로 아래는 merge할 브랜치(exp)의 코드입니다.&lt;br /&gt;충돌된 파일을 사용자가 직접 수정한 다음 커밋하면 정상적으로 merge가 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;rebase&lt;/h3&gt;
&lt;pre class=&quot;puppet&quot;&gt;&lt;code&gt;$ git rebase {브랜치}
Successfully rebased and updated refs/heads/rb.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://git-scm.com/book/en/v2/images/basic-rebase-3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;C3&lt;/code&gt; 에서 변경된 사항을 Patch로 만들고 이를 다시 &lt;code&gt;C4&lt;/code&gt; 에 적용시키는 방법이 있다. Git에서는 이런 방식을 &lt;i&gt;Rebase&lt;/i&gt; 라고 합니다.&lt;br /&gt;커밋 히스토리를 나란히 만들면서 병합할 때 쓰는 명령어 입니다.&lt;br /&gt;Rebase 할 브랜치에서 명령어 실행하고 {브랜치}는 합칠 브랜치입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공개 저장소에 Push 한 커밋을 Rebase 하지말아야 합니다&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;충돌 해결&lt;/h4&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;$ git rebase {브랜치}
자동 병합: f1.txt
충돌 (내용): f1.txt에 병합 충돌
error: 다음을 적용할(apply) 수 없습니다: 82aebb4... R1
힌트: Resolve all conflicts manually, mark them as resolved with
힌트: &quot;git add/rm &amp;lt;conflicted_files&amp;gt;&quot;, then run &quot;git rebase --continue&quot;.
힌트: You can instead skip this commit: run &quot;git rebase --skip&quot;.
힌트: To abort and get back to the state before &quot;git rebase&quot;, run &quot;git rebase --abort&quot;.
Could not apply 82aebb4... R1

$ git status
대화형 리베이스 진행 중. 갈 위치는 bf1987c
Last command done (1 command done):
   pick 82aebb4 R1
Next commands to do (2 remaining commands):
   pick eabced7 R2
   pick 0469d04 R3
  (보고 편집하려면 &quot;git rebase --edit-todo&quot;를 사용하십시오)
현재 'rb' 브랜치를 'bf1987c' 위로 리베이스하는 중입니다.
  (충돌을 바로잡고 &quot;git rebase --continue&quot;를 사용하십시오)
  (이 패치를 건너뛰려면 &quot;git rebase --skip&quot;을 사용하십시오)
  (원본 브랜치를 가져오려면 &quot;git rebase --abort&quot;를 사용하십시오)

병합하지 않은 경로:
  (use &quot;git restore --staged &amp;lt;file&amp;gt;...&quot; to unstage)
  (해결했다고 표시하려면 &quot;git add &amp;lt;파일&amp;gt;...&quot;을 사용하십시오)
A
    양쪽에서 수정:  f1.txt

커밋할 변경 사항을 추가하지 않았습니다 (&quot;git add&quot; 및/또는 &quot;git commit -a&quot;를
사용하십시오)
R2

$ vi f1.txt
충돌 수정

$ git add f1.txt

$ git rebase --continue
[HEAD 분리됨 e2e7796] R3
 1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/rb.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌된 파일에서 &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD 아래 코드가 현재 브랜치 코드입니다.&lt;br /&gt;======= 구분되어 있으며 구분 문자 바로 아래는 rebase할 브랜치의 코드입니다.&lt;br /&gt;충돌된 파일을 사용자가 직접 수정한 다음 rebase --continue 명령어를 실행합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;브랜치 작업 도중 다른 브랜치 이동&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;stash&lt;/h3&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;// 임시 저장
$ git stash [push]
Saved working directory and index state WIP on exp: 3f440f9 1

// 최근 stash 적용
$ git stash apply
현재 브랜치 exp
커밋하도록 정하지 않은 변경 사항:
  (무엇을 커밋할지 바꾸려면 &quot;git add &amp;lt;파일&amp;gt;...&quot;을 사용하십시오)
  (use &quot;git restore &amp;lt;file&amp;gt;...&quot; to discard changes in working directory)
    수정함:        f1.txt

커밋할 변경 사항을 추가하지 않았습니다 (&quot;git add&quot; 및/또는 &quot;git commit -a&quot;를
사용하십시오)

// stash 조회
$ git stash list
stash@{0}: WIP on exp: 3f440f9 1

// 최근 stash 삭제
$ git stash drop
Dropped refs/stash@{0} (046135fef28ff0b48373d9421c570c3598a62768)

// apply와 drop을 합친 명령어
$ git stash pop
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 브랜치에서 작업을 하던 도중에 커밋하지 않고 다른 브랜치로 이동할 경우 작업한 데이터들이 이동한 브랜치까지 영향을 줍니다.&lt;br /&gt;현재 브랜치에서 작업을 임시로 저장할 때 쓰는 명령어 입니다.&lt;br /&gt;push 옵션을 추가하면 Modified이면서 Tracked 상태인 파일과 Staging Area에 있는 파일들을 보관하고&lt;br /&gt;Working Directory의 변경사항을 감춥니다.&lt;br /&gt;apply 옵션을 추가하면 최근 stash에 저장해 두었던 작업들을 다시 불러옵니다.&lt;br /&gt;list 옵션을 추가하면 stash들을 보여줍니다.&lt;br /&gt;drop 옵션을 추가하면 최근 stash를 삭제 합니다.&lt;br /&gt;pop 옵션을 추가하면 최근 stash에 저장해 두었던 작업들을 불러오고 해당 stash를 삭제합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원격 저장소 만들기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;remote&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;$ git remote add [-t {브랜치}] {원격 저장소 이름} {원격저장소 주소}

$ git remote -v

$ git remote remove {원격 저장소 이름}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 로컬 저장소와 원격 저장소를 연결할 때 쓴느 명령어 입니다.&lt;br /&gt;add -t {브랜치 명}] {원격저장소 이름} {원격저장소 주소} 옵션을 추가하면 원격저장소와 연결이 됩니다.&lt;br /&gt;-v 옵션을 추가하면 현재 연결된 원격 저장소들을 보여줍니다.&lt;br /&gt;remove {원격저장소 이름} 옵션을 추가하면 원격 저장소를 연결 해제합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;내부 동작 원리&lt;/h4&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;$ cat .git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = true
[remote &quot;origin&quot;]
        url = {원격 저장소 주소}
        fetch = +refs/heads/*:refs/remotes/origin/*
[remote &quot;upstream&quot;]
        url = {원격 저장소 주소}
        fetch = +refs/heads/만두:refs/remotes/upstream/만두&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;remote add시 .git/config 파일에 remote 정보가 추가됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;clone&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;$ git clone {원격 저장소 주소} [디렉토리 명]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어를 실행한 디렉토리에 원격 저장소의 작업들을 복사하여 로컬저장소로 만듭니다.&lt;br /&gt;기본적으로 remote 원격 저장소와 연결이 되어있으며 원격 저장소 이름은 origin으로 되어있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원격 저장소 동기화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;push&lt;/h3&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;$ git push {원격 저장소 이름} {브랜치}

// push 기본 원격 저장소 브랜치 설정
$ git push -u {원격 저장소 이름} {브랜치}

// 기본 원격 저장소 설정값으로 push하여 생략 가능합니다.
$ git push&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 저장소에 있는 작업들을 원격 저장소로 보낼때 쓰는 명령어 입니다.&lt;br /&gt;{원격저장소 이름} {브랜치} 옵션을 추가하여 해당 원격 저장소의 브랜치에 작업들을 보냅니다.&lt;br /&gt;-u {원격저장소 이름} {브랜치} 옵션을 추가하여 push 할 떄 기본 원격 저장소의 브랜치를 설정하여&lt;br /&gt;다음 push할 때 부터 원격 저장소와 브랜치를 생략가능합니다.&lt;br /&gt;설정 할때도 push 동작이 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;내부 동작 원리&lt;/h4&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;$ git push --set-updatream orgin 만두

$ cat .git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = true
[remote &quot;origin&quot;]
        url = {원격 저장소 주소}
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch &quot;만두&quot;]
        url = origin
        fetch = refs/heads/만두 
[remote &quot;upstream&quot;]
        url = {원격 저장소 주소}
        fetch = +refs/heads/만두:refs/remotes/upstream/만두

$ cat .git/refs/remotes/origin/만두
36db20f74f1130c97f412693e65cadcc2a48051a

$ cat .git/refs/heads/만두
36db20f74f1130c97f412693e65cadcc2a48051a&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push를 할 경우 .git/refs/remotes/{원격 저장소 이름}/{브랜치} 파일 내용에 push 할 커밋개체가 쓰여집니다.&lt;br /&gt;push --set-updatream {원격 저장소 이름} {브랜치} 옵션을 추가하면 .git/config에 해당 브랜치가 어떤 원격 저장소의 브랜치랑 연결되는지 추가됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;fetch&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;$ git fetch [{원격 저장소 이름} {브랜치}]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 저장소에 있는 변경된 작업들을 가져오는것 까지만 해주는 명령어 입니다.&lt;br /&gt;병합을 하기 전까지는 실제 로컬 저장소에는 영향을 미지치 않습니다.&lt;br /&gt;변경된 작업을 가져온 경우 {원격 저장소 이름 } /{브랜치}로 새로운 브랜치가 만들어 집니다.&lt;br /&gt;최신 커밋 개체들과 관련된 개체들을 다운받고 .git/refs/remotes/{원격 저장소 이름}/{브랜치} 파일 내용에 최신 커밋 개체 해쉬 값이 쓰여집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;pull&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;$ git pull [{원격 저장소 이름} {브랜치}]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 저장소에 있는 작업들을 가져오고 병합까지 해주는 명령어 입니다.&lt;br /&gt;fetch + merge 명령어의 기능을 합친 명령어 입니다.&lt;br /&gt;fetch 작업이 되고서 .git/refs/head/{브랜치} 파일 내용에 fast-forward인 경우&lt;br /&gt;.git/refs/remotes/{원격 저장소 이름}/{브랜치} 파일의 커밋 개체 해쉬 값이 쓰여지게 됩니다.&lt;br /&gt;3-way merge인 경우 충돌현상을 잡고서 새로운 커밋 개체가 생성이 되며 새로운 커밋 개체 해쉬 값이 쓰여지게 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;태그&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tag&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// Lightweight Tag
$ git tag {태그 이름} [{브랜치} | {커밋 개체 해쉬 값}]

// 태그 목록
$ git tag
1.0.0

// Annotated Tag
$ git tag -a {태그 이름} -m &quot;{태그 메시지}&quot; [{브랜치} | {커밋 개체 해쉬 값}]

$ git tag
1.0.0
1.1.0

// Annotated Tag 조회
$ git tag -v 1.1.0
object 5e4f4d28a075669c22e202b2ace4ec721f0ee19a
type commit
tag 1.1.0
tagger road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1678020977 +0900

bug fix
error: no signature found

// Lightweight Tag 조회
$ git tag -v 1.0.0
error: 1.0.0: cannot verify a non-tag object of type commit.


$ git log
commit 5e4f4d28a075669c22e202b2ace4ec721f0ee19a (HEAD -&amp;gt; main, tag: 1.1.0)
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 21:54:14 2023 +0900

    3

commit 392b8dd27c35a8e51a7cff1cfb81bbc98f179bea (tag: 1.0.0)
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 21:52:46 2023 +0900

    2

commit e11f17504379addef52a0168160c4935a692d20e
Author: road-jin &amp;lt;rjswmtk@gmail.com&amp;gt;
Date:   Sun Mar 5 21:52:22 2023 +0900

    1

// 태그 삭제    
$ git tag -d {태그 이름}    

// 태그들 원격 저장소에 push
$ git push --tags&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋 개체에 대한 태그를 붙일 때 쓰는 명령어 입니다.&lt;br /&gt;Lightweight Tag는 커밋 개체에 대한 기본적인 태그정보만 저장합니다.&lt;br /&gt;Annotated Tag는 태그 만든 사람, 언제 태그를 만들었는지, 태그 메시지를 저장합니다.&lt;br /&gt;옵션을 추가하지 않으면 태그 목록들을 보여줍니다.&lt;br /&gt;{태그 이름} [{브랜치} | {커밋 개체 해쉬 값}] 옵션을 추가하면 Lightweight Tag가 추가됩니다.&lt;br /&gt;-a {태그 이름} -m &quot;{태그 메시지}&quot; [{브랜치} | {커밋 개체 해쉬 값}] 옵션을 추가하면 Annotated Tag가 추가됩니다.&lt;br /&gt;-v {태그 이름} 옵션을 추가하면 해당 태그에 대한 정보를 보여줍니다.&lt;br /&gt;-d {태그이름} 옵션을 추가하면 해당 태그를 삭제합니다.&lt;br /&gt;push --tags 명령어를 사용해야만 원격 저장소에 태그들이 전송됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;내부 동작 원리&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ cat .git/refs/tags/1.0.0
392b8dd27c35a8e51a7cff1cfb81bbc98f179bea

$ git cat-file -p 392b8dd27c35a8e51a7cff1cfb81bbc98f179bea
tree 36f609fa71850be13f5f97e4346cdf2488e7057c
parent e11f17504379addef52a0168160c4935a692d20e
author road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1678020766 +0900
committer road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1678020766 +0900

2

$ cat .git/refs/tags/1.1.0
ecf7866ccf4366341a90adc23febac09af4a6ff9

$ git cat-file -p ecf7866ccf4366341a90adc23febac09af4a6ff9
object 5e4f4d28a075669c22e202b2ace4ec721f0ee19a
type commit
tag 1.1.0
tagger road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1678020977 +0900

bug fix

$ git cat-file -p 5e4f4d28a075669c22e202b2ace4ec721f0ee19a
tree 77aa31a93565e0c5809ff487b849af48ac7df624
parent 392b8dd27c35a8e51a7cff1cfb81bbc98f179bea
author road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1678020854 +0900
committer road-jin &amp;lt;rjswmtk@gmail.com&amp;gt; 1678020854 +0900

3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lightweight Tag인 경우 .git/refs/tags/{태그 이름}파일이 생성되며 내용에 커밋 개체 해쉬 값이 쓰여집니다.&lt;br /&gt;Annotated Tag인 경우 objects 파일을 생성하고 내용에 태그 정보들과 커밋 개체 해쉬 값이 쓰여집니다.&lt;br /&gt;그리고 .git/refs/tags/{태그 이름}파일이 생성되며 내용에 해당 objects 파일의 해쉬 값이 쓰여집니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLuHgQVnccGMA8iwZwrGyNXCGy2LAAsTXk&quot;&gt;https://www.youtube.com/playlist?list=PLuHgQVnccGMA8iwZwrGyNXCGy2LAAsTXk&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://git-scm.com/book/ko/v2&quot;&gt;https://git-scm.com/book/ko/v2&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%A0%9C%EB%8C%80%EB%A1%9C-%ED%8C%8C%EB%8A%94-%EA%B9%83&quot;&gt;https://www.inflearn.com/course/%EC%A0%9C%EB%8C%80%EB%A1%9C-%ED%8C%8C%EB%8A%94-%EA%B9%83&lt;/a&gt;&lt;/p&gt;</description>
      <category>Git</category>
      <category>git</category>
      <category>깃 기초</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/14</guid>
      <comments>https://roadj.tistory.com/14#entry14comment</comments>
      <pubDate>Mon, 6 Mar 2023 09:40:00 +0900</pubDate>
    </item>
    <item>
      <title>Java8 Optional API</title>
      <link>https://roadj.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목가.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2RZT0/btrBc1qhs4w/2imDKQOZuokB9dPC20Udck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2RZT0/btrBc1qhs4w/2imDKQOZuokB9dPC20Udck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2RZT0/btrBc1qhs4w/2imDKQOZuokB9dPC20Udck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2RZT0%2FbtrBc1qhs4w%2F2imDKQOZuokB9dPC20Udck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;343&quot; height=&quot;343&quot; data-filename=&quot;제목가.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Optional이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optional&amp;lt;T&amp;gt; Class는 Integer나 Double Class처럼 'T'타입의 객체를 포장해 주는 Wrapper Class이자 컨테이너 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 안에 값이 들어 있을 수도 있고, 값이 없어 비어있는 컨테이너 일수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 Optional은 Java 11 기준으로 설명하겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Optional 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값이 비어있을 수도 있다는 가정하에 사용하므로 직관성이 좋습니다.&lt;/li&gt;
&lt;li&gt;Null 값을 직접 사용하지 않아 NullPointerException 예외에 대하여 유연해집니다.&lt;/li&gt;
&lt;li&gt;Null 체크 기능을 직접 구현 안 해도 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Optional 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. static&amp;nbsp;&amp;lt;T&amp;gt;&amp;nbsp;Optional&amp;lt;T&amp;gt;&amp;nbsp;of(T&amp;nbsp;value)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명시된 값을 가지는 Optional 객체를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명시된 값이 아닌 null이 저장되는 경우에는 NullpointerException 예외가 발생합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651651578891&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optional = Optional.of(&quot;String&quot;);
// null 값이 들어가면 NullPointerException 예외가 발생합니다.
Optional&amp;lt;Object&amp;gt; optional2 = Optional.of(null);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. static&amp;nbsp;&amp;lt;T&amp;gt;&amp;nbsp;Optional&amp;lt;T&amp;gt;&amp;nbsp;ofNullable(T&amp;nbsp;value)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명시된 값이 null이 아니면 명시된 값을 가지는 Optional 객체를 반환하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명시된 값이 null일 경우 비어있는 Optional 객체를 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651724155226&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optional2 = Optional.ofNullable(&quot;4000&quot;);
Assertions.assertThat(optional2).isPresent();

Optional&amp;lt;String&amp;gt; optional = Optional.ofNullable(null);
Assertions.assertThat(optional).isEmpty();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. static&amp;nbsp;&amp;lt;T&amp;gt;&amp;nbsp;Optional&amp;lt;T&amp;gt;&amp;nbsp;empty()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;아무런 값도 가지지 않는 비어있는 Optional 객체를 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651724405922&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;Object&amp;gt; optional = Optional.empty();
Assertions.assertThat(optional).isEmpty();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Optional 메소드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;1. boolean isPresent()&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;저장된 값이 존재하면 true를 반환하고, 값이 존재하지 않으면 false를 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651725002600&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optional = Optional.ofNullable(&quot;1234&quot;);
Assertions.assertThat(optional.isPresent()).isTrue();

Optional&amp;lt;String&amp;gt; optional2 = Optional.ofNullable(null);
Assertions.assertThat(optional2.isPresent()).isFalse();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;2. &lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;boolean isEmpty&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;저장된 값이 존재하지 않으면 true를 반환하고, 값이 존재하면 false를 반환합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651725181866&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optional = Optional.ofNullable(null);
Assertions.assertThat(optional.isEmpty()).isTrue();

Optional&amp;lt;String&amp;gt; optional2 = Optional.ofNullable(&quot;1234&quot;);
Assertions.assertThat(optional2.isEmpty()).isFalse();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;3. &lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;T get()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;Optional 객체에 저장된 값을 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651726534885&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optional = Optional.ofNullable(&quot;Foo&quot;);
Assertions.assertThat(optional.get()).isEqualTo(&quot;Foo&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;&lt;b&gt;4. void ifPresent(Consumer&amp;lt;? super T&amp;gt; consumer)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;값이 존재하면 해당 값으로 Consumer interface에 의한 지정된 작업을 수행합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651726822447&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optional = Optional.ofNullable(&quot;Foo&quot;);
optional.ifPresent(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;5. void ifPresentOrElse​(Consumer&amp;lt;? super T&amp;gt; action, Runnable emptyAction)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 존재하면 해당 값으로 &lt;span style=&quot;background-color: #ffffff; color: #353833;&quot;&gt;Consumer interface에 의한 &lt;/span&gt;지정된 작업을 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 존재하지 않으면 Runnable interface에 의한 지정된 작업을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651727920916&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optional = Optional.ofNullable(&quot;Foo&quot;);
optional.ifPresentOrElse((string) -&amp;gt; Assertions.assertThat(string).isEqualTo(&quot;Foo&quot;)
                            , () -&amp;gt; Assertions.fail(&quot;NullPointerException&quot;, new NullPointerException()));

Assertions.assertThatThrownBy(() -&amp;gt; Optional.ofNullable(null)
                                            .ifPresentOrElse((string) -&amp;gt; Assertions.assertThat(string).isEqualTo(&quot;Foo&quot;)
                                                                , () -&amp;gt; Assertions.fail(&quot;NullPointerException&quot;
                                                                                            , new NullPointerException())))
        .isInstanceOf(AssertionError.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. Optional&amp;lt;T&amp;gt; or(Supplier&amp;lt;? extends Optional&amp;lt;? extends T&amp;gt;&amp;gt; supplier)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 존재하면 해당 값에 대한 Optional 객체가 반환되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 존재하지 않으면 Supplier interface에 의한 지정된 Optional&amp;lt;T&amp;gt; 객체가 반환됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651728555600&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String order = &quot;banana&quot;;
Optional&amp;lt;String&amp;gt; optional = Optional.ofNullable(order)
                                    .or(() -&amp;gt; Optional.of(&quot;apple&quot;));
Assertions.assertThat(optional.get()).isEqualTo(&quot;banana&quot;);

String order2 = null;
Optional&amp;lt;String&amp;gt; optional2 = Optional.ofNullable(order2)
                                     .or(() -&amp;gt; Optional.of(&quot;apple&quot;));
Assertions.assertThat(optional2.get()).isEqualTo(&quot;apple&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. T orElse(T other)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;저장된 값이 존재하면 해당 값을 반환하고, &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;값이 존재하지 않으면 매개변수로 전달된 값을 반환합니다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;orElse(T other)에 T other는 Optional에 값이 있든 없든 무조건 실행되기 때문에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;새로운 객체를 생성하거나, 새로운 연산을 수행하는 경우에는 orElseGet()을 써야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651735004432&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String order = &quot;apple&quot;;
String eat = Optional.ofNullable(order).orElse(&quot;bread&quot;);
Assertions.assertThat(eat).isEqualTo(&quot;apple&quot;);

String order2 = null;
String eat2 = Optional.ofNullable(order2).orElse(&quot;bread&quot;);
Assertions.assertThat(eat2).isEqualTo(&quot;bread&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8. T orElseGet​(Supplier&amp;lt;?&amp;nbsp;extends&amp;nbsp;T&amp;gt;&amp;nbsp;supplier)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;저장된 값이 존재하면 해당 값을 반환하고, &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;값이 존재하지 않으면 매개변수로 전달된  Supplier interface에 의한 객체를 반환합니다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651735259369&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String order = &quot;tomato&quot;;
String eat = Optional.ofNullable(order).orElseGet(() -&amp;gt; &quot;banana&quot;);
Assertions.assertThat(eat).isEqualTo(&quot;tomato&quot;);

String order2 = null;
String eat2 = Optional.ofNullable(order2)
                     .orElseGet(() -&amp;gt; {
                                           System.out.println(&quot;banana&quot;);
                                           return &quot;banana&quot;;
                                        });
Assertions.assertThat(eat2).isEqualTo(&quot;banana&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;9. T orElseThrow()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 &lt;span style=&quot;background-color: #ffffff; color: #474747;&quot;&gt;NoSuchElementException &lt;/span&gt;예외를 발생시킵니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651735933508&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Assertions.assertThat(Optional.ofNullable(&quot;짜잔&quot;).orElseThrow())
        .isEqualTo(&quot;짜잔&quot;);

Assertions.assertThatThrownBy(() -&amp;gt; Optional.ofNullable(null).orElseThrow())
        .isInstanceOf(NoSuchElementException.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;10. &amp;lt;X extends Throwable&amp;gt; T orElseThrow​(Supplier&amp;lt;?&amp;nbsp;extends&amp;nbsp;X&amp;gt;&amp;nbsp;exceptionSupplier)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 매개변수로 전달된 예외를 발생시킵니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651737755034&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Assertions.assertThat(Optional.ofNullable(&quot;짜잔&quot;).orElseThrow())
        .isEqualTo(&quot;짜잔&quot;);

Assertions.assertThatThrownBy(() -&amp;gt; Optional.ofNullable(null)
                                            .orElseThrow(() -&amp;gt; new NullPointerException()))
        .isInstanceOf(NullPointerException.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;11. Optional&amp;lt;T&amp;gt; filter​(Predicate&amp;lt;? super T&amp;gt; predicate)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장된 값을 Predicate interface에 의한 구현된 조건이 True일 경우만 Optional&amp;lt;T&amp;gt;로 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651740041041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Order {
    private String coffee;

    public String getCoffee() {
        return coffee;
    }

    public void setCoffee(String coffee) {
        this.coffee = coffee;
    }
}

Order order = new Order();
order.setCoffee(&quot;딸기라떼&quot;);

Optional&amp;lt;Order&amp;gt; optional = Optional.ofNullable(order)
                                .filter((coffee) -&amp;gt; coffee.getCoffee().equals(&quot;딸기라떼&quot;));
Assertions.assertThat(optional.isPresent()).isTrue();

Optional&amp;lt;Order&amp;gt; optional2 = Optional.ofNullable(order)
                                .filter((coffee) -&amp;gt; coffee.getCoffee().equals(&quot;초코라떼&quot;));
Assertions.assertThat(optional2.isPresent()).isFalse();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;12. &amp;lt;U&amp;gt;&amp;nbsp;Optional&amp;lt;U&amp;gt; map​(Function&amp;lt;?&amp;nbsp;super&amp;nbsp;T,​?&amp;nbsp;extends&amp;nbsp;U&amp;gt;&amp;nbsp;mapper)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장된 값을 매개변수로 Function interface에 의한 지정된 값을 Optional에 감싸서 Optional&amp;lt;T&amp;gt;을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651740489243&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Order {
    private String coffee;

    public String getCoffee() {
        return coffee;
    }

    public void setCoffee(String coffee) {
        this.coffee = coffee;
    }
}

Order order = new Order();

Optional&amp;lt;String&amp;gt; optional = Optional.ofNullable(order).map(Order::getCoffee);
Assertions.assertThat(optional.isPresent()).isFalse();

order.setCoffee(&quot;딸기라떼&quot;);

Optional&amp;lt;String&amp;gt; optional2 = Optional.ofNullable(order).map(Order::getCoffee);
Assertions.assertThat(optional2.isPresent()).isTrue();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;13. &amp;lt;U&amp;gt; Optional&amp;lt;U&amp;gt; flatMap​(Function&amp;lt;?&amp;nbsp;super&amp;nbsp;T,​?&amp;nbsp;extends&amp;nbsp;Optional&amp;lt;?&amp;nbsp;extends&amp;nbsp;U&amp;gt;&amp;gt;&amp;nbsp;mapper)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장된 값을 매개변수로 Function interface에 의한 지정된 Optional&amp;lt;T&amp;gt;을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651740969327&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Order {
    private String coffee;

    public Optional&amp;lt;String&amp;gt; getCoffee() {
        return Optional.ofNullable(coffee);
    }

    public void setCoffee(String coffee) {
        this.coffee = coffee;
    }
}

Order order = new Order();

Optional&amp;lt;String&amp;gt; optional = Optional.ofNullable(order).flatMap(Order::getCoffee);
Assertions.assertThat(optional.isPresent()).isFalse();

order.setCoffee(&quot;초코라떼&quot;);

Optional&amp;lt;String&amp;gt; optional2 = Optional.ofNullable(order).flatMap(Order::getCoffee);
Assertions.assertThat(optional2.isPresent()).isTrue();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;주의사&lt;/b&gt;항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;return 타입으로만 쓰기를 권장합니다.&lt;br /&gt;- 메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 사용하지 않습니다.&lt;/li&gt;
&lt;li&gt;Optional을 return 하는 메소드에서는 null을 리턴하지 않습니다.&lt;/li&gt;
&lt;li&gt;기본형 타입의 Optional은 따로 있습니다.&lt;br /&gt;- OptionalInt, OptionalLong 등..&lt;/li&gt;
&lt;li&gt;Collection, Map, Stream Array, Optional은 Optional로 감싸지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이로써 공부한 내용을 간략히 정리해보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-java8/dashboard&quot;&gt;https://www.inflearn.com/course/the-java-java8/dashboard&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.tcpschool.com/&quot;&gt;http://www.tcpschool.com/​&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html&lt;/a&gt;​&lt;/p&gt;</description>
      <category>Java</category>
      <category>java optional</category>
      <category>java11 optional</category>
      <category>optional</category>
      <category>optional api</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/13</guid>
      <comments>https://roadj.tistory.com/13#entry13comment</comments>
      <pubDate>Fri, 6 May 2022 09:00:03 +0900</pubDate>
    </item>
    <item>
      <title>Java8 Stream API</title>
      <link>https://roadj.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Stream이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다량의 데이터를 읽어온 다음, 사용자가 원하는 데이터로 가공하여 보여주는 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 데이터는 수정하지 않으며, Terminal Operation이 끝날 경우 소실됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Stream 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0 또는 다수의 Intermediate Operation(중개 연산)과 1개의 Terminal Operation&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;(최종 연산)으로 구성되어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Intermediate Operation은 새로운 Stream을 반환하고 항상 Lazy(게을러)하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terminal Operation이 실행될 때까지 시작되지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I7YHT/btrA0y9wOQC/KNClYiJJSLcnOnqKtITl2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I7YHT/btrA0y9wOQC/KNClYiJJSLcnOnqKtITl2k/img.png&quot; data-alt=&quot;출처 :http://www.tcpschool.com/java/java_stream_concept&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I7YHT/btrA0y9wOQC/KNClYiJJSLcnOnqKtITl2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI7YHT%2FbtrA0y9wOQC%2FKNClYiJJSLcnOnqKtITl2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;208&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :http://www.tcpschool.com/java/java_stream_concept&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Stream의 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 데이터를 수정하지 않습니다.&lt;/li&gt;
&lt;li&gt;손쉽게 병렬 처리를 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;함수형 인터페이스를 지원합니다.&lt;/li&gt;
&lt;li&gt;Stream은 일회성입니다.&lt;/li&gt;
&lt;li&gt;Stream 작업은 내부 반복문으로 인하여 코드가 간결합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Stream 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Collection 인터페이스를 구현한 모든 객체에&amp;nbsp; Stream() 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1651490014517&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
Stream&amp;lt;String&amp;gt; stream = list.stream();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 배열에 관한 Stream을 생성하기 위한 Arrays클래스의 stream() 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1651490388023&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String[] arr = new String[]{&quot;가&quot;,&quot;나&quot;,&quot;다&quot;,&quot;라&quot;};
Stream&amp;lt;String&amp;gt; stream = Arrays.stream(arr);		// 가, 나, 다, 라
Stream&amp;lt;String&amp;gt; stream2 = Arrays.stream(arr, 0, 2);	// 가, 나&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 매개변수를 받아 Stream을 생성하기 위한 Stream클래스의 of() 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1651490564468&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;Integer&amp;gt; stream = Stream.of(4, 2, 3, 1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 지정된 범위의 연속된 정수로 Stream을 생성하기 위한 IntStream클래스의 range() 및 rangeClosed() 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1651490731370&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IntStream stream1 = IntStream.range(1, 4);		// 1, 2, 3
IntStream stream2 = IntStream.rangeClosed(1, 4);	// 1, 2, 3, 4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 난수로 이루어진 Stream을 생성하기 위한 Random클래스의 ints(), longs(), doubles() 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1651491166113&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IntStream stream = new Random().ints(4);		// Int형의 랜덤한 숫자 4개
LongStream longs = new Random().longs(4);		// Long형의 랜덤한 숫자 4개
DoubleStream doubles = new Random().doubles(4);		// Double형의 랜덤한 숫자 4개&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 람다 표현식에 의한 반환되는 값을 요소로 무한 Stream을 생성하기 위한 Stream클래스의 iterate() 및 generate() 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1651491729351&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;Integer&amp;gt; stream = Stream.iterate(1, n -&amp;gt; n + 1);	// 1, 2, 3, 4 ...
Stream&amp;lt;Integer&amp;gt; stream2 = Stream.generate(() -&amp;gt; 1);	// 1, 1, 1, 1 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 파일의 한 행을 요소로 하는 Stream을 생성하기 위한 lines() 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1651491968388&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String&amp;lt;String&amp;gt; stream = Files.lines(Path path);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 아무 요소도 가지지 않는 빈 Stream을 생성하기 위한 Stream클래스의 empty() 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1651492015290&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;Object&amp;gt; stream = Stream.empty();&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Stream의 Intermediate Operation(중개 연산)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Stream 필터&amp;nbsp; : filter(), distinct()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;Stream&amp;lt;T&amp;gt; filter(Predicate&amp;lt;? super T&amp;gt; predicate)&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream에서 주어진 조건에 맞는 데이터만으로 구성된 새로운 Stream을 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;Stream&amp;lt;T&amp;gt; distinct()&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream에서 중복된 데이터를 제거 후 새로운 Stream을 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651493313022&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7, 1, 2, 3};
IntStream filter = Arrays.stream(arr).filter(number -&amp;gt; number &amp;gt; 5);	// 6, 7
IntStream distinct = Arrays.stream(arr).distinct();			// 1, 2, 3, 4, 5, 6, 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Stream 변경 : map(), flatMap()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&amp;lt;R&amp;gt; Stream&amp;lt;R&amp;gt; map(Functoin&amp;lt;? super T, ? extends R&amp;gt; mapper)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream의 데이터들을 Function 매개변수로 전달하여, 그 반환 값으로 이루어진 새로운 Stream을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&amp;lt;R&amp;gt; Stream&amp;lt;R&amp;gt; flatMap(Functoin&amp;lt;? super T, ? extends Stream&amp;lt;? extends R&amp;gt;&amp;gt; mapper)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&amp;nbsp; &amp;nbsp;해당 Stream의 데이터들을 Function 매개변수로 전달하여, Stream 반환 값으로 이루어진 새로운 Stream을 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651494186558&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String[] arr = {&quot;A-01&quot;, &quot;A-010&quot;, &quot;B-02&quot;, &quot;B-020&quot;};
// 4, 5, 4, 5
Stream&amp;lt;Integer&amp;gt; stream = Arrays.stream(arr).map(string -&amp;gt; string.length());
// A, 01, A , 010, B, 02, B , 020
Stream&amp;lt;String&amp;gt; stream2 = Arrays.stream(arr).flatMap(string -&amp;gt; Stream.of(string.split(&quot;-&quot;)));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Stream 제한 : limit(), skip()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;Stream&amp;lt;T&amp;gt; limit(long maxSize)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream에서 매개변수로 전달된 개수만큼의 데이터만으로 이루어진 새로운 Stream을 반환합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;Stream&amp;lt;T&amp;gt; skip(long n)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream에서 매개변수로 전달된 개수만큼을 제외한 나머지 데이터로 이루어진 새로운 Stream을 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651494742073&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;Integer&amp;gt; stream = Stream.iterate(1, number -&amp;gt; number + 1);	// 1, 2, 3, 4, ...
Stream&amp;lt;Integer&amp;gt; stream2 = stream.skip(10);				// 11, 12, 13, 14, ...
Stream&amp;lt;Integer&amp;gt; stream3 = stream2.limit(3);				// 11, 12, 13&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Stream 정렬 : sorted()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; Stream&amp;lt;T&amp;gt; sorted()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream의 데이터들을 오름차순으로 정렬합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; Stream&amp;lt;T&amp;gt; sorted(Comparator&amp;lt;? super T&amp;gt; comparator)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream의 데이터들을 Comparator를 이용하여 정렬합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651497085980&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer[] arr = {1, 5, 2, 35, 6, 9, 20, 12};
// 1, 2, 5, 6, 9, 12, 20, 35
Stream&amp;lt;Integer&amp;gt; sorted = Arrays.stream(arr).sorted();
// 1, 2, 5, 6, 9, 12, 20, 35
Stream&amp;lt;Integer&amp;gt; sorted1 = Arrays.stream(arr).sorted(Comparator.naturalOrder());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. Stream 연산 결과 확인 : peek()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;Stream&amp;lt;T&amp;gt; peek(Consumer&amp;lt;? super T&amp;gt; action)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 해당  Stream으로부터 각 데이터들을 소모하여 명시된 동작을 수행하여 새로운 Stream을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651497867174&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer[] arr = {1, 5, 2, 35, 6, 9, 20, 12};
Arrays.stream(arr).peek(number -&amp;gt; System.out.println(&quot;원본데이터 : &quot; + number))
         .sorted()
         .peek(number -&amp;gt; System.out.println(&quot;sorted(): &quot; + number))
         .limit(5)
         .peek(number -&amp;gt; System.out.println(&quot;limit(5) : &quot; + number))
         .forEach(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Stream의 Terminal Operation(최종 연산)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Stream 출력 : forEach()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;void forEach(Consumer&amp;lt;? super T&amp;gt; action)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #575757;&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream의 각 데이터들을 소모하여 명시된 동작을 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1651499897474&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer[] arr = {1, 5, 2, 35, 6, 9, 20, 12};
Arrays.stream(arr).sorted().forEach(System.out::println); // 1, 2, 5, 6, 9, 12, 20, 35&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Stream 소모 : reduce()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; Optional&amp;lt;T&amp;gt; reduce(BinaryOperator&amp;lt;T&amp;gt; accumulator)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 처음 두 데이터를 가지고 연산을 수행한 뒤, 그 결과와 다음 요소를 가지고 또다시 연산을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 이런 식으로 해당 Stream의 모든 데이터들을 소모하여 연산을 수행하고, 그 결과를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; T reduce(T identity, BinaryOperator&amp;lt;T&amp;gt; accumulator)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 위와 같으며, 첫 번째 매개변수가 초기값을 가지고 시작하므로 Optional이 아닌 T타입을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651500465011&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer[] arr = {1, 5, 2, 35, 6, 9, 20, 12};
Optional&amp;lt;Integer&amp;gt; reduce = Arrays.stream(arr).reduce((n, n2) -&amp;gt; n + n2); // 90
Integer reduce2 = Arrays.stream(arr).reduce(10, (n, n2) -&amp;gt; n + n2);	// 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Stream 검색 : findFirst(), findAny()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; Optional&amp;lt;T&amp;gt; findFirst()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream에서 첫 번째 데이터를 참조하는 Optional을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; Optional&amp;lt;T&amp;gt; findAny()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream에서 병렬 Stream일 경우 가장 먼저 찾은 데이터를 참조하는 Optional을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 병렬 Stream이 아닐 경우는 findFirst()와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651539422940&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String[] arr = {&quot;a&quot;, &quot;a1&quot;, &quot;b&quot;, &quot;b1&quot;, &quot;c&quot;, &quot;c1&quot;, &quot;b2&quot;, &quot;c2&quot;};
// b
Optional&amp;lt;String&amp;gt; findFirst = Arrays.stream(arr).filter(string -&amp;gt; string.startsWith(&quot;b&quot;)).findFirst();
// b
Optional&amp;lt;String&amp;gt; parallelFindFirst = Arrays.stream(arr).parallel().filter(string -&amp;gt; string.startsWith(&quot;b&quot;)).findFirst();
// b
Optional&amp;lt;String&amp;gt; findAny = Arrays.stream(arr).findAny();
// b, b1, b2 셋 중 하나
Optional&amp;lt;String&amp;gt; parallelFindAny = Arrays.stream(arr).parallel().filter(string -&amp;gt; string.startsWith(&quot;b&quot;)).findAny();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Stream 검사 : anyMatch(), allMatch(), noneMatch()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;boolean anyMatch(Predicate&amp;lt;? super T&amp;gt; predicate)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;해당 Stream의 일부 데이터가 특정 조건을 만족할 경우에 true를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;boolean allMatch(Predicate&amp;lt;? super T&amp;gt; predicate)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;해당 Stream의 모든 데이터가 특정 조건을 만족할 경우에 true를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;boolean noneMatch(Predicate&amp;lt;? super T&amp;gt; predicate)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;해당 Stream의 모든 데이터가 특정 조건을 만족하지 않을 경우에 true를 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651540914775&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String[] arr = {&quot;a1&quot;, &quot;b1&quot;, &quot;c1&quot;};
boolean anyMatch = Arrays.stream(arr).anyMatch(string -&amp;gt; string.startsWith(&quot;a&quot;));   //true
boolean allMatch = Arrays.stream(arr).allMatch(string -&amp;gt; string.contains(&quot;1&quot;));	    //true
boolean noneMatch = Arrays.stream(arr).noneMatch(string -&amp;gt; string.startsWith(&quot;d&quot;)); //true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. Stream 통계 : count(), min(), max()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;long count()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;해당 Stream의 데이터의 개수를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Optional&amp;lt;T&amp;gt; min(Comparator&amp;lt;? super T&amp;gt; comparator)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;해당 Stream의 데이터 중에서 가장 큰 값을 가지고 있는 데이터를 참조하는 Optional을 반환합니다.&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Optional&amp;lt;T&amp;gt; max(Comparator&amp;lt;? super T&amp;gt; comparator)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;해당 Stream의 데이터 중에서 가장 작은 값을 가지고 있는 데이터를 참조하는 Optional을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651542590368&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer[] arr = {100, 50, 200, 300, 420};
// 5
long count = Arrays.stream(arr).count();
// 420
Optional&amp;lt;Integer&amp;gt; max = Arrays.stream(arr).max(Integer::compareTo);
// 50
Optional&amp;lt;Integer&amp;gt; min = Arrays.stream(arr).min(Integer::compareTo);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. Stream 연산 : sum(), average()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; T sum()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream의 모든 데이터 합을 구하여 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; Optional&amp;lt;T&amp;gt;&amp;nbsp;average()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 해당 Stream의 모든 데이터 평균값을 구하여 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651543152703&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// sum() 및 average() 메소드는 IntStream or DoubleStream과 같은 기본 타입 스트림에서 사용할 수 있습니다.
// 1070
int sum = IntStream.of(100, 50, 200, 300, 420).sum();
// 214.0
OptionalDouble average = IntStream.of(100, 50, 200, 300, 420).average();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. Stream 수집 : collect()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;R,A&amp;gt;&amp;nbsp;R&amp;nbsp;collect(Collector&amp;lt;?&amp;nbsp;super&amp;nbsp;T,A,R&amp;gt;&amp;nbsp;collector)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 매개변수로 전달된 Collectors 객체에 구현된 방법대로 Stream 데이터를 수집하여 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651571506487&quot; class=&quot;reasonml&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer[] arr = {100, 50, 200, 300, 420, 300};
// [100, 50, 200, 300, 420, 300]
List&amp;lt;Integer&amp;gt; collect = Arrays.stream(arr).collect(Collectors.toList());
// [50, 100, 420, 200, 300]
Set&amp;lt;Integer&amp;gt; collect2 = Arrays.stream(arr).collect(Collectors.toSet());
// {Key100=100, Key420=420, Key200=200, Key300=300, Key50=50}
Map&amp;lt;String, Integer&amp;gt; collect3 = Arrays.stream(arr).collect(toMap((integer) -&amp;gt; &quot;Key&quot; + integer, (integer) -&amp;gt; integer, (oldVal, newVal) -&amp;gt; oldVal));
// 10050200300420300
String collect4 = Arrays.stream(arr).map(integer -&amp;gt; String.valueOf(integer)).collect(Collectors.joining());
// {50=[50], 420=[420], 100=[100], 200=[200], 300=[300, 300]}
Map&amp;lt;Integer, List&amp;lt;Integer&amp;gt;&amp;gt; collect5 = Stream.of(arr).collect(Collectors.groupingBy(Integer::intValue, toList()));
// [100, 50, 200, 300, 420, 300]
ArrayList&amp;lt;Integer&amp;gt; collect6 = Stream.of(arr).collect(Collectors.toCollection(ArrayList::new));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이로써 공부한 내용을 간략히 정리해보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-java8/dashboard&quot;&gt;https://www.inflearn.com/course/the-java-java8/dashboard&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.tcpschool.com/&quot;&gt;http://www.tcpschool.com/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <category>Java Stream</category>
      <category>Stream</category>
      <category>스트림</category>
      <category>자바 스트림</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/12</guid>
      <comments>https://roadj.tistory.com/12#entry12comment</comments>
      <pubDate>Wed, 4 May 2022 09:00:17 +0900</pubDate>
    </item>
    <item>
      <title>자바 함수형 인터페이스(Functional Interface)와 람다 표현식(Lambda Expressions)</title>
      <link>https://roadj.tistory.com/11</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-04-12 오후 5.07.58.png&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQ6UsB/btrzactzcqG/Pikb9bCSSA0MkYzeaJ9ef1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQ6UsB/btrzactzcqG/Pikb9bCSSA0MkYzeaJ9ef1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQ6UsB/btrzactzcqG/Pikb9bCSSA0MkYzeaJ9ef1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQ6UsB%2FbtrzactzcqG%2FPikb9bCSSA0MkYzeaJ9ef1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;147&quot; data-filename=&quot;스크린샷 2022-04-12 오후 5.07.58.png&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수형 인터페이스(Functional Interface)란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 메소드를 단 하나만 가지고 있는 SAM (Single Abstract Method) 인터페이스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@FuncationInterface 애노테이션을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수형 인터페이스를 더 알아보기 전에 우선 람다 표현식 및 메소드 레퍼런스부터 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다 표현식(Lambda Expressions)이란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수형 인터페이스의 인스턴스를 만드는 방법으로 쓰일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드 매개변수, 리턴 타입, 변수로 만들어 사용할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 코드를 확연히 줄일 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649750860698&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RunSomething runSomething = new RunSomething() {
    @Override
    public int doIt(int number) {
        return number + 10;
    }
};

RunSomething rambda = number -&amp;gt; number + 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현식 : ( 인자 리스트 ) -&amp;gt; { 바디 }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자 리스트&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인자가 없을 때 : ()&lt;/li&gt;
&lt;li&gt;인자가 한 개 일 때 : (one) 또는 one&lt;/li&gt;
&lt;li&gt;인자가 여러개 일 때 : (one, two)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;인자의 타입은 생략 가능하고 명시할 수도 있습니다. : (Integer one, Integer two)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바디&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화살표 오른쪽에 함수 본문을 정의합니다.&lt;/li&gt;
&lt;li&gt;여러 줄인 경우에 {}를 사용해서 묶습니다.&lt;/li&gt;
&lt;li&gt;한 줄인 경우에 생략 가능하며, return도 생략이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 캡처 (Variable Capture)&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-04-12 오후 9.04.15.png&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL4Fl3/btrzdgaUuLN/iCpD8eCvMNFK9HkZzzdFr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL4Fl3/btrzdgaUuLN/iCpD8eCvMNFK9HkZzzdFr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL4Fl3/btrzdgaUuLN/iCpD8eCvMNFK9HkZzzdFr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL4Fl3%2FbtrzdgaUuLN%2FiCpD8eCvMNFK9HkZzzdFr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;475&quot; height=&quot;218&quot; data-filename=&quot;스크린샷 2022-04-12 오후 9.04.15.png&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;로컬변수캡처 &lt;br /&gt;&lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt;이거나 &lt;/span&gt;&lt;span&gt;effective final &lt;/span&gt;&lt;span&gt;인 경우에만 참조할 수 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;span&gt;concurrency &lt;/span&gt;&lt;span&gt;&lt;span&gt;문제가 생길 수 있어서 컴파일에러가 발생합니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;pre id=&quot;code_1649764967915&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
        int baseNumber = 10;

        RunSomething rambda = number -&amp;gt;{
            return baseNumber;
        };
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;effective final &lt;br /&gt;&lt;/span&gt;&lt;span&gt;자바 &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;부터 지원하는 기능으로 &lt;/span&gt;&lt;span&gt;&amp;ldquo;&lt;/span&gt;&lt;span&gt;사실상&lt;/span&gt;&lt;span&gt;&quot; final&lt;/span&gt;&lt;span&gt;인 변수입니다.&lt;br /&gt;&lt;/span&gt;&lt;span&gt;final &lt;/span&gt;&lt;span&gt;키워드 사용하지 않은 변수를 익명 클래스 구현체 또는 람다에서 참조할 수 &lt;/span&gt;&lt;span&gt;있습니다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-04-12 오후 8.56.06.png&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n3aAI/btrzemodbU2/XUu5McMajAVSIK2Ki3z6E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n3aAI/btrzemodbU2/XUu5McMajAVSIK2Ki3z6E1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n3aAI/btrzemodbU2/XUu5McMajAVSIK2Ki3z6E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn3aAI%2FbtrzemodbU2%2FXUu5McMajAVSIK2Ki3z6E1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;454&quot; data-filename=&quot;스크린샷 2022-04-12 오후 8.56.06.png&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;익명 클래스 구현체와 달리 &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;lsquo;쉐도 윙(Shadowing)&amp;rsquo; 하지 않습니다.&lt;br /&gt;익명 클래스는 새로 스코프를 만들지만,&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;람다는 람다를 감싸고 있는 스코프와 같습니다.&lt;br /&gt;그래서 람다 스코프와 람다를 감싸고 있는 스코프(main Method)에 똑같은 변수명을 쓰지 못합니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;쉐도 윙이란 ?&lt;br /&gt;외부 클래스의 변수명과 내부 클래스의 변수명 그리고 내부 클래스 내의 메서드 파라미터명이 같을 경우&lt;br /&gt;외부 클래스의 변수가 가려지면서, 내부클래스의 변수나 내부 클래스 내의 파라미터가 쓰여집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드 레퍼런스란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용하면 람다와 다르게 간결하게 표현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1649765808956&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Greeting {

    private String name;

    public Greeting() {

    }

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

    public String getName() {
        return name;
    }

    public String hello(String name){
        return &quot;hello &quot; + name;
    }

    public static String hi (String name){
        return &quot;hi &quot; + name;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1649766217687&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 스태틱 메소드 참조
UnaryOperator&amp;lt;String&amp;gt; hi = Greeting::hi;

// 특정 객체의 인스턴스 메소드 참조
Greeting greeting = new Greeting();
UnaryOperator&amp;lt;String&amp;gt; hello = greeting::hello;

// 임의 객체의 인스턴스 메소드 참조
String [] name = {&quot;minkyu&quot;, &quot;com&quot;, &quot;example&quot;};
Arrays.sort(name, String::compareToIgnoreCase);

//생성자 참조
Supplier&amp;lt;Greeting&amp;gt; newGreeting = Greeting::new;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드 참조하는 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스태틱 메소드 참조 &amp;gt; 타입::스태틱 메소드&lt;/li&gt;
&lt;li&gt;특정 객체의 인스턴스 메소드 참조 &amp;gt; 객체 레퍼런스::인스턴스 메소드&lt;/li&gt;
&lt;li&gt;임의 객체의 인스턴스 메소드 참조 &amp;gt; 타입::인스턴스 메소드&lt;/li&gt;
&lt;li&gt;생성자 참조 &amp;gt; 타입::new&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드 또는 생성자의 매개변수로 람다의 입력값을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리턴 값 또는 생성한 객체는 람다의 리턴 값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 기본적으로 제공하는 함수형 인터페이스가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;java.util.function&lt;/a&gt; 패키지 중 자주 사용할만한 함수 인터페이스들을 자세히 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649767460588&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void functionTest(){
    Function&amp;lt;Integer, Integer&amp;gt; plus10 = (number) -&amp;gt; number + 10;
    Function&amp;lt;Integer, Integer&amp;gt; multiply2 = (number) -&amp;gt; number * 2;
    Function&amp;lt;Integer, Integer&amp;gt; functionCompose = plus10.compose(multiply2);
    Function&amp;lt;Integer, Integer&amp;gt; functionAndThen = plus10.andThen(multiply2);

    Assertions.assertThat(plus10.apply(2)).isEqualTo(12);
    Assertions.assertThat(multiply2.apply(2)).isEqualTo(4);
    Assertions.assertThat(functionCompose.apply(2)).isEqualTo(14);
    Assertions.assertThat(functionAndThen.apply(2)).isEqualTo(24);
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Function&amp;lt;T, R&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T타입을 받아서 R타입을 리턴하는 함수 인터페이스입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;R apply(T t)&lt;/li&gt;
&lt;li&gt;함수 조합용 메소드
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;andThen&lt;/li&gt;
&lt;li&gt;compose&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1649768279191&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void biFunctionTest(){
    Function&amp;lt;Integer, Integer&amp;gt; plus10 = (number) -&amp;gt; number + 10;
    BiFunction&amp;lt;Integer, Integer, Integer&amp;gt; biFunctionSum = (number, number2) -&amp;gt; number + number2;
    BiFunction&amp;lt;Integer, Integer, Integer&amp;gt; biFunctionAndThen = biFunctionSum.andThen(plus10);

    Assertions.assertThat(biFunctionSum.apply(4,8)).isEqualTo(12);
    Assertions.assertThat(biFunctionAndThen.apply(4,8)).isEqualTo(22);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BiFunction&amp;lt;T, U, R&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 값(T, U)을 받아서 R 타입을 리턴하는 함수 인터페이스입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;R apply(T t, U u)&lt;/li&gt;
&lt;li&gt;함수 조합용 메소드
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;andThen&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1649769300823&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void consumerTest(){
    ArrayList&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

    Consumer&amp;lt;Integer&amp;gt; addList = (age) -&amp;gt; list.add(age);
    Consumer&amp;lt;Integer&amp;gt; addListPlus10 = (name) -&amp;gt; list.add(name + 10 );
    Consumer&amp;lt;Integer&amp;gt; consumerAndThen = addList.andThen(addListPlus10);

    addList.accept(30);
    Assertions.assertThat(list.size()).isEqualTo(1);
    Assertions.assertThat(list.get(0)).isEqualTo(30);

    list.clear();
    consumerAndThen.accept(30);
    Assertions.assertThat(list.size()).isEqualTo(2);
    Assertions.assertThat(list.get(0)).isEqualTo(30);
    Assertions.assertThat(list.get(1)).isEqualTo(40);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Consumer&amp;lt;T&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T타입을 받아서 아무 값도 리턴하지 않는 함수 인터페이스입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;void accept(T t)&lt;/li&gt;
&lt;li&gt;함수 조합용 메소드
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;andThen&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1649769463883&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void supplierTest(){
    Supplier&amp;lt;Integer&amp;gt; getAge = () -&amp;gt; 30;
    Assertions.assertThat(getAge.get()).isEqualTo(30);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;Supplier&amp;lt;T&amp;gt;&lt;/div&gt;
&lt;div&gt;T타입의 값을 제공하는 함수 인터페이스입니다.&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T get()&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1649769769606&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void predicateTest(){
    Predicate&amp;lt;Integer&amp;gt; isEven = (number) -&amp;gt; number % 2 == 0;
    Predicate&amp;lt;Integer&amp;gt; isTen = (number) -&amp;gt; number.equals(10);

    Assertions.assertThat(isEven.test(10)).isEqualTo(true);
    Assertions.assertThat(isEven.and(isTen).test(10)).isEqualTo(true);
    Assertions.assertThat(isEven.or(isTen).test(4)).isEqualTo(true);
    Assertions.assertThat(isEven.negate().test(2)).isEqualTo(false);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Predicate&amp;lt;T&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T타입을 받아서 boolean을 리턴하는 함수 인터페이스입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;boolean test(T t)&lt;/li&gt;
&lt;li&gt;함수 조합용 메소드
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;and&lt;/li&gt;
&lt;li&gt;or&lt;/li&gt;
&lt;li&gt;negate&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1649770009566&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void unaryOperator(){
    UnaryOperator&amp;lt;Integer&amp;gt; plusTen = (number) -&amp;gt; number + 10;
    Assertions.assertThat(plusTen.apply(10)).isEqualTo(20);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UnaryOperator&amp;lt;T&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;Function&amp;lt;T, R&amp;gt;의 특수한 형태로, 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;R apply(T t)&lt;/li&gt;
&lt;li&gt;함수 조합용 메소드
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;andThen&lt;/li&gt;
&lt;li&gt;compose&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1649770021584&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void BinaryOpreator(){
    BinaryOperator&amp;lt;Integer&amp;gt; binarySum = (number, number2) -&amp;gt; number + number2;
    Assertions.assertThat(binarySum.apply(5,8)).isEqualTo(13);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;BinaryOperaor&amp;lt;T&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;BiFunction&amp;lt;T, U, R&amp;gt;의 특수한 형태로, 동일한 타입의 입력값 두 개를 받아 리턴하는 함수 인터페이스입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;R apply(T t, U u)&lt;/li&gt;
&lt;li&gt;함수 조합용 메소드
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;andThen&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝으로 자바에서 함수형 프로그래밍 대해 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수 함수(Pure function)란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 밖에 있는 값을 변경하거나 사용하지 않고, 동일한 입력에 대하여 항상 동일한 출력을 반환하는 함수입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Side Effect가 없다. (함수 밖에 있는 값을 변경하지 않습니다.)&lt;/li&gt;
&lt;li&gt;상태가 없다. (함수 밖에 있는 값을 사용하지 않습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고차 함수(Higher-Order Function)란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 함수를 매개변수로 받을 수 있고 함수를 리턴할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이로써 공부한 내용을 간략히 정리해보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-java8/dashboard&quot;&gt;https://www.inflearn.com/course/the-java-java8/dashboard&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <category>Functional Interface</category>
      <category>lambda</category>
      <category>Lambda Expressions</category>
      <category>람다</category>
      <category>람다표현식</category>
      <category>함수형</category>
      <category>함수형 인터페이스</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/11</guid>
      <comments>https://roadj.tistory.com/11#entry11comment</comments>
      <pubDate>Thu, 14 Apr 2022 09:00:47 +0900</pubDate>
    </item>
    <item>
      <title>자바 인터페이스(Interface)</title>
      <link>https://roadj.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스들이 구현해야 하는 동작들을 추상적으로 선언하는 추상 자료형입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interface라는 키워드를 사용하여 선언합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강제적으로 구현해야 하는 추상 메소드가 있으며, static과 final이 둘 다 선언되는 상수가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 8버전부터, default 메소드와 static 메소드를 인터페이스에 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 자바 7버전 이하에서는 추상화 클래스에서 공통코드를 구현체 메소드로 구현하여 상속 할 경우&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상화 클래스만 상속 받을 수 있기 때문에 더 이상 상속이 불가능하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 자바 8버전부터는 인터페이스에서 default 메소드와 static 메소드가 추가되어 상속에 대하여 자유가 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 메소드(Default Method)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스에 메소드를 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Object가 제공하는 기능(equals, hasCode 등)은 기본 메소드로 제공할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스태틱 메소드(Static Method)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 타입 관련 헬퍼 또는 유틸리티 메소드를 제공할 때 인터페이스에 스태틱 메소드를 제공할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649735901029&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface HandPhone {
    String getModelName();

    /**
     * @implSpec
     * 핸드폰 스펙(Model Name)을 리턴
     */
    default Map&amp;lt;String, String&amp;gt; getSpec(){
        Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        map.put(&quot;model&quot;, getModelName());
        return map;
    }

    /**
     * @implSpec
     * 핸드폰 스펙(Model Name)을 출력
     */
    default void printSpec() {
        System.out.println(&quot;Model Name : &quot; + getUpperName(getModelName()));
    }

    static String getUpperName(String name){
        return name.toUpperCase();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 메소드는 해당 인터페이스를 구현한 클래스가 있을 경우 기본 메소드를 추가하여도 구현체가 깨지지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 기본 메소드는 구현체 모르게 추가된 기능으로 반드시 문서화를 하는 게 좋습니다.(@implSpec 자바 독 태그)&lt;/p&gt;
&lt;pre id=&quot;code_1649741067504&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IPhone extends HandPhone {
    String SIZE = &quot;71.5 &amp;times; 146.7 &amp;times; 7.65 mm&quot;;

    void printSpec();

    /**
     * @implSpec
     * HandPhone Interface 재정의 함수 
     * 핸드폰 스펙(Size, Model Name)을 리턴
     */
    @Override
    default Map&amp;lt;String, String&amp;gt; getSpec() {
        Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        map.put(&quot;size&quot;, SIZE);
        map.put(&quot;model&quot;, HandPhone.getUpperName(getModelName()));
        return map;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 메소드는 인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 인터페이스 구현체가 기본 메소드를 재정의 할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649741097650&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class IPhone13 implements IPhone{

    @Override
    public String getModelName() {
        return &quot;IPhone13(A2633)&quot;;
    }

    @Override
    public void printSpec() {
        System.out.println(&quot;Size : &quot; + SIZE);
        System.out.println(&quot;Model Name : &quot; + HandPhone.getUpperName(getModelName()));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 메소드 getModelName()과 printSpec을 구현하였습니다.&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649741109720&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class HandPhoneTest {

    HandPhone iPhone13 = new IPhone13();

    @Test
    public void 모델명가져오기(){
        Assertions.assertThat(iPhone13.getModelName()).isEqualTo(&quot;IPhone13(A2633)&quot;);
    }

    @Test
    public void 스펙가져오기및출력(){
        Map&amp;lt;String, String&amp;gt; iPhone13Spec = iPhone13.getSpec();

        Assertions.assertThat(iPhone13Spec.size()).isEqualTo(2);
        Assertions.assertThat(iPhone13Spec.get(&quot;size&quot;)).isEqualTo(&quot;71.5 &amp;times; 146.7 &amp;times; 7.65 mm&quot;);
        Assertions.assertThat(iPhone13Spec.get(&quot;model&quot;)).isEqualTo(&quot;IPHONE13(A2633)&quot;);

        iPhone13.printSpec();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-04-12 오후 3.29.41.png&quot; data-origin-width=&quot;2244&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXC4cr/btrzdOxF02D/SV0O2S5u2Qw5k9foDpcBQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXC4cr/btrzdOxF02D/SV0O2S5u2Qw5k9foDpcBQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXC4cr/btrzdOxF02D/SV0O2S5u2Qw5k9foDpcBQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXC4cr%2FbtrzdOxF02D%2FSV0O2S5u2Qw5k9foDpcBQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2244&quot; height=&quot;516&quot; data-filename=&quot;스크린샷 2022-04-12 오후 3.29.41.png&quot; data-origin-width=&quot;2244&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;IPhone 인터페이스를 구현한 IPhone13 클래스를 테스트하기 위하여 테스트 케이스를 작성하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getModelName() 메소드로 모델명을 가져오고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getSpec() 메소드를 통하여 핸드폰 사이즈 및 모델명(대문자 변환)을 테스트해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이로써 공부한 내용을 간략히 정리해보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-java8/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.inflearn.com/course/the-java-java8/dashboard&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <category>interface</category>
      <category>인터페이스</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/10</guid>
      <comments>https://roadj.tistory.com/10#entry10comment</comments>
      <pubDate>Wed, 13 Apr 2022 09:00:50 +0900</pubDate>
    </item>
    <item>
      <title>애노테이션 프로세서(Annotation processor)</title>
      <link>https://roadj.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation Processor란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation Processor는 컴파일 단계에서 Annotation에 정의된 일렬의 프로세스를 동작하게 하는 것을 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 단계에서 실행되기 때문에, 빌드 단계에서 에러를 출력하게 할 수 있고, 소스코드 및 바이트 코드를 생성할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 예로 자바의 @Override가 있으며, Lombok(롬북)이라는 라이브러리도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lombok은 자주 사용하는 라이브러리로 한번 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lombok이란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Getter, @Seteer, @Builder 등의 Annotation과 Annotation Processor를 제공하여 표준적으로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성해야 할 코드를 개발자 대신 생성해주는 라이브러리 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 시점에 Annnotation Processor를 사용하여 abstract syntaxtree를 조작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면, 프로젝트를 생성하여 Lombok 라이브러리를 실습해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-20 오후 4.36.39.png&quot; data-origin-width=&quot;1321&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zl4mJ/btryvve88tA/oIWjJhnugYEj8UrbkqcUkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zl4mJ/btryvve88tA/oIWjJhnugYEj8UrbkqcUkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zl4mJ/btryvve88tA/oIWjJhnugYEj8UrbkqcUkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZl4mJ%2Fbtryvve88tA%2FoIWjJhnugYEj8UrbkqcUkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1321&quot; height=&quot;704&quot; data-filename=&quot;스크린샷 2022-03-20 오후 4.36.39.png&quot; data-origin-width=&quot;1321&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preferences -&amp;gt; Plugins설정에서 IntelliJ Lombok 플러그인을 설치합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-20 오후 4.35.33.png&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;705&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LUfFJ/btryxcTGEo9/WZEtVUaA1UxkEswAATEQpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LUfFJ/btryxcTGEo9/WZEtVUaA1UxkEswAATEQpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LUfFJ/btryxcTGEo9/WZEtVUaA1UxkEswAATEQpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLUfFJ%2FbtryxcTGEo9%2FWZEtVUaA1UxkEswAATEQpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1323&quot; height=&quot;705&quot; data-filename=&quot;스크린샷 2022-03-20 오후 4.35.33.png&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;705&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preferences -&amp;gt; Build,Execution,Deployment -&amp;gt; Compiler -&amp;gt; Annotation Processors설정에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Enable Anntation processing 체크박스를 활성화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lombok Annotation을 사용하는 클래스는 변경할 때마다 그대로 적용이 되어 Maven Complie을 안 해도 되는 설정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649134350627&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;1.18.8&amp;lt;/version&amp;gt;
      &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pom.xml에 lombok 라이브러리 dependency를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649134538644&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;


import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter @EqualsAndHashCode
public class Member {

    private String name;

    private int age;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Getter : 전역 변수에 대한 Getter를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Setter : 전역 변수에 대한 Setter를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EqualsAndHashCode : Equals 함수와 HashCode 함수를 재정의하여 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649134576021&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import org.junit.Assert;
import org.junit.Test;

import static org.junit.Assert.*;

public class MemberTest {

    @Test
    public void getterSetter(){
        Member member = new Member();
        member.setName(&quot;minkyu&quot;);

        Assert.assertEquals(member.getName(), &quot;minkyu&quot;);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드에 getter 및 setter 함수를 개발자가 직접 만들지 않아도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setName 및 getName 함수를 쓸 수 있으며, 테스트 코드도 통과하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Annotation Processor에 대해 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Processor Interface를 통하여 Annotation Processor를 구현하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Processor Interface는 여러 라운드(rounds)에 거쳐 Source 및 Compile 된 코드를 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 : &lt;a href=&quot;https://docs.oracle.com/en/java/javase/11/docs/api/java.compiler/javax/annotation/processing/Processor.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/en/java/javase/11/docs/api/java.compiler/javax/annotation/processing/Processor.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Abstract Pocessor라는 추상 클래스를 상속하여 Annotation Processor를 구현해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation Processor Jar 프로젝트를 생성해 보겠습니다.&lt;/p&gt;

&lt;pre id=&quot;code_1649137590406&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; &amp;lt;!-- https://mvnrepository.com/artifact/com.google.auto.service/auto-service --&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.google.auto.service&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;auto-service&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.0.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;!-- https://mvnrepository.com/artifact/com.squareup/javapoet --&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.squareup&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;javapoet&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.13.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AutoService 및&amp;nbsp; Javapoet 라이브러리를 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AutoService : Java SPI(서비스 프로바이더 인터페이스) 구성 파일을 생성하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되는 Annotation processor 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 시점에 Annotaion Processor를 사용하여&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;META-INF/services/javax.annotation.processor.Processor 파일 등 필요한 파일을 자동으로 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 : &lt;a href=&quot;https://www.baeldung.com/google-autoservice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.baeldung.com/google-autoservice&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Javapoet : Source Code(.java)파일을 생성해주는 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649138967413&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Magic {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation 범위는 Interface, Enum, Class, Annotation이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation 유지는 Annotation Processor의 컴파일 시점까지만 필요하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Source Code까지만 유지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649142239578&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.Set;

@AutoService(Processor.class)
public class MagicMojaProcessor extends AbstractProcessor {

    // 이 프로세서가 어떤 애노테이션을 처리 할 것 인지 정하는 메소드
    @Override
    public Set&amp;lt;String&amp;gt; getSupportedAnnotationTypes() {
        return Set.of(Magic.class.getName());
    }
    
    // 어떤 소스버전을 지원 할지 정하는 메소드 
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
    
    // 해당 애노테이션으로 작업을 처리하는 메소드
    @Override
    public boolean process(Set&amp;lt;? extends TypeElement&amp;gt; annotations, RoundEnvironment roundEnv) {      
        // 해당 애노테이션이 붙어 있는 엘리먼트들을 가져 온다.
        // Element : 클래스, 인터페이스, 메소드 등 애노테이션을 붙일 수 있는 target
        Set&amp;lt;? extends Element&amp;gt; elements = roundEnv.getElementsAnnotatedWith(Magic.class);
        for (Element element: elements) {
            // 해당 Element 이름
            Name simpleName = element.getSimpleName();

            // 해당 Element가 Interface가 아닌 경우 빌드에서 에러나게 동작
            if (element.getKind() != ElementKind.INTERFACE) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, &quot;Magic annotation can not be used on &quot; + simpleName);
            } else {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, &quot;Processing &quot; + simpleName);
            }

            TypeElement typeElement = (TypeElement) element;
            ClassName className = ClassName.get(typeElement);

            // MethodSpec: Method 만드는 객체
            MethodSpec pullout = MethodSpec.methodBuilder(&quot;pullOut&quot;)
                    .addModifiers(Modifier.PUBLIC)          // 접근 제한자 설정
                    .returns(String.class)                  // Method return Type 설정
                    .addStatement(&quot;return $S&quot;, &quot;Rabiit!&quot;)   // return 시 값 전달 설정
                    .build();

            // TypeSpec : Type 만드는 객체
            TypeSpec magicMoja = TypeSpec.classBuilder(&quot;MagicMoja&quot;)
                    .addModifiers(Modifier.PUBLIC)  // 접근 제한자 설정
                    .addMethod(pullout)             // 해당 클래스에 메소드 추가
                    .addSuperinterface(className)
                    .build();

            // Filer : 소스코드,클래스코드 및 리소스를 생성할 수 있는 인터페이스
            // processingEnv : AbstractProcessor 상속 받으면 쓸 수 있는 전역 변수
            Filer filer = processingEnv.getFiler();
            try {
                JavaFile.builder(className.packageName(), magicMoja)
                        .build()
                        .writeTo(filer);
            } catch (IOException e) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, &quot;FATAL ERROR : &quot; + e);
            }
        }

        /*
          true 일 경우 해당 애노테이션에 대한 다른 프로세서한테 더이상 처리하지 말라고 한다.
          false 일 경우 또 다른 프로세서가 처리 할 수 있음.
        */
        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Processing API를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;roundEnv.getElementsAnnotatedWith(Magic.class) : Magic Annotation이 붙어 있는 Element들을 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Element : 클래스, 인터페이스, 메소드 등 애노테이션을 붙일 수 있는 target&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getKind() : 해당 Type 종류를 반환합니다. ex) ElementKind.INTERFACE&lt;/li&gt;
&lt;li&gt;getSimpleName() : 해당 Type의 이름을 반환합니다. ex)  Interface 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Filer : Source Code, Class Code 및 리소스를 생성할 수 있는 인터페이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AbstractProcessor의 구현한 메소드들을 살펴보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getSupportedAnnotationTypes() : 이 Processor가 어떤 Annotation을 처리할 것인지를 정의하는 메소드입니다.&lt;/li&gt;
&lt;li&gt;getSupportedSourceVersion() : 어떤 자바버전을 지원할지 정의하는 메소드입니다.&lt;/li&gt;
&lt;li&gt;process(Set&amp;lt;? extends TypeElement&amp;gt; annotations, RoundEnvironment roundEnv)&lt;br /&gt;AbstractProcessor의 필수적으로 구현해야 하며, Processor가 어떻게 동작해야 하는지를 정의하는 메소드입니다.&lt;br /&gt;return 값이 true일 경우 해당 Annotation에 대한 다른 Processor한테 더 이상 처리하지 말라고 합니다.&lt;br /&gt;false일 경우 또 다른 Processor가 처리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Javapoet 라이브러리를 이용한 객체들을 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MethodSpec : Method 정보를 가지고(생성하는) 있는 객체입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MethodSpec.methodBuilder(&quot;pullOut&quot;) : 빌더 패턴 생성자로서,&amp;nbsp; pullOut이라는 메소드명을 정의합니다.&lt;/li&gt;
&lt;li&gt;addModifiers(Modifier.PUBLIC) :&amp;nbsp; 접근 제한자를 Public으로 정의합니다.&lt;/li&gt;
&lt;li&gt;returns(String.class)&amp;nbsp; :Method return Type을 String으로 정의합니다.&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/li&gt;
&lt;li&gt;addStatement(&quot;return $S&quot;, &quot;Rabiit!&quot;)&amp;nbsp;&amp;nbsp;: Method가 진행할 코드를 정의합니다.&lt;/li&gt;
&lt;li&gt;build() : 위의 정의한 내용으로 인스턴스를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeSpec : Type 정보를 가지고(생성하는) 있는 객체&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TypeSpec.classBuilder(&quot;MagicMoja&quot;) : 빌더 패턴 생성자로서,&amp;nbsp; MagicMoja라는 클래스 명을 정의합니다.&lt;/li&gt;
&lt;li&gt;addModifiers(Modifier.PUBLIC)&amp;nbsp;&amp;nbsp;: 접근 제한자를 Public으로 정의합니다.&lt;/li&gt;
&lt;li&gt;addMethod(pullout) : 클래스에 메소드를 추가합니다.&lt;/li&gt;
&lt;li&gt;addSuperinterface(className) : 상속할 Interface를 추가합니다.&lt;/li&gt;
&lt;li&gt;build()&amp;nbsp;: 위의 정의한 내용으로 인스턴스를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaFile : Source Code(.java)파일로 생성해주는 객체&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;builder(className.packageName(), magicMoja) : 빌더 패턴 생성자로서,&amp;nbsp; &lt;br /&gt;Source Code파일의 패키지명과 생성할 Type을 정의합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;build() : 위의 정의한 내용으로 인스턴스를 생성합니다.&lt;/li&gt;
&lt;li&gt;writeTo(filer) : 파일로 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 로컬 메이븐 저장소에 추가하여 사용하기 위해&amp;nbsp; maven clean install 명령어를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 저희가 만든 Annotation Processor가 어떻게 동작하는지 확인하기 위해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트할 프로젝트를 생성해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649145816540&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;we.whiteship&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;magicmoja&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 생성 후 pom.xml에 Annotation Processor 프로젝트의 groupId, artifactId, version 복사하여 dependency에 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649145961081&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package org.example;

import we.whiteship.Magic;

@Magic
public interface Moja {
    String pullOut();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희가 만든 Magic Annotation을 Moja Interface에 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-20 오후 9.30.57.png&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhHfXr/btryvvUtjEv/kI1A1z6uqXwOTxECoVE3F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhHfXr/btryvvUtjEv/kI1A1z6uqXwOTxECoVE3F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhHfXr/btryvvUtjEv/kI1A1z6uqXwOTxECoVE3F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhHfXr%2FbtryvvUtjEv%2FkI1A1z6uqXwOTxECoVE3F0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;985&quot; height=&quot;707&quot; data-filename=&quot;스크린샷 2022-03-20 오후 9.30.57.png&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preferences -&amp;gt; Build,Execution,Deployment -&amp;gt; Compiler &amp;gt; Annotation Processors설정에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Enable annotation processing 체크박스를 활성화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;annotation processing은 컴파일 시점에 Annotation을 스캔하고 처리하기 위해 자바 컴파일러에 삽입된 툴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 시점에서 annotation processing이 만든 클래스를 참조하기 위해 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven Compile 명령어를 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-20 오후 9.34.02.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BKUeI/btryu8SKEEX/YqYugKHqZNBSpYZEUIv7Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BKUeI/btryu8SKEEX/YqYugKHqZNBSpYZEUIv7Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BKUeI/btryu8SKEEX/YqYugKHqZNBSpYZEUIv7Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBKUeI%2Fbtryu8SKEEX%2FYqYugKHqZNBSpYZEUIv7Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1026&quot; height=&quot;837&quot; data-filename=&quot;스크린샷 2022-03-20 오후 9.34.02.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;837&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 우클릭 -&amp;gt; Open Module Settings -&amp;gt; Project Settings -&amp;gt; Modules설정에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;target -&amp;gt; generated-sources -&amp;gt; annotations폴더를 클릭하고 Sources폴더 그림을 클릭하여 활성화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation Processor가 만든 클래스를 다른 클래스가 참조할 수 있게 소스 경로로 인식하게 해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649146223479&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package org.example;

public class App 
{
    public static void main( String[] args ) {
        Moja moja = new MagicMoja();
        System.out.println(moja.pullOut());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하면&amp;nbsp; 빌드 화면에서는 Processing Moja가 출력이 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Run 화면에서는 Rabbit이 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이로써 공부한 내용을 간략히 정리해보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-code-manipulation&quot;&gt;https://www.inflearn.com/course/the-java-code-manipulation&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>Annotation Processor</category>
      <category>java abstractprocessor</category>
      <category>애노테이션 프로세서</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/9</guid>
      <comments>https://roadj.tistory.com/9#entry9comment</comments>
      <pubDate>Wed, 6 Apr 2022 09:00:58 +0900</pubDate>
    </item>
    <item>
      <title>프록시 패턴(Proxy Pattern)</title>
      <link>https://roadj.tistory.com/8</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-18 오후 4.10.15.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;1108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6BFcq/btryhOGOOrr/O64DkGvuJxg7kkJCgpAvLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6BFcq/btryhOGOOrr/O64DkGvuJxg7kkJCgpAvLk/img.png&quot; data-alt=&quot;출처 :https://www.inflearn.com/course/the-java-code-manipulation&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6BFcq/btryhOGOOrr/O64DkGvuJxg7kkJCgpAvLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6BFcq%2FbtryhOGOOrr%2FO64DkGvuJxg7kkJCgpAvLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1090&quot; height=&quot;1108&quot; data-filename=&quot;스크린샷 2022-03-18 오후 4.10.15.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;1108&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :https://www.inflearn.com/course/the-java-code-manipulation&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 패턴이란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리얼 서브젝트는 자신이 해야 할 일만 하면서 프록시를 사용하여 부가적인 기능을 제공할 때 쓰는 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시와 리얼 서브젝트가 공유하는 인터페이스(서브젝트)가 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 해당 인터페이스(서브젝트) 타입으로 프록시를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 프록시를 거쳐서 리얼 서브젝트를 사용하기 때문에 프록시는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리얼 서브젝트에 대한 접근을 관리하거나 부가기능을 제공하거나, 리턴값을 변경할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 프로젝트를 생성해서 간단히 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648982580233&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

public class Book {

    private Integer id;

    private String title;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648982594736&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

public interface BookService {
    void rent(Book book);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브젝트&lt;/p&gt;
&lt;pre id=&quot;code_1648982617930&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

public class BookServiceProxy implements  BookService {

    BookService bookService;

    public BookServiceProxy(BookService bookService) {
        this.bookService = bookService;
    }

    @Override
    public void rent(Book book) {
        System.out.println(&quot;aaaaa&quot;);
        bookService.rent(book);
        System.out.println(&quot;bbbbb&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시&lt;/p&gt;
&lt;pre id=&quot;code_1648982631802&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

public class DefaultBookService implements BookService {

    @Override
    public void rent(Book book) {
        System.out.println(&quot;rent : &quot; + book.getTitle());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리얼 서브젝트&lt;/p&gt;
&lt;pre id=&quot;code_1648982680439&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import org.junit.Test;
import static org.junit.Assert.*;

public class BookServiceTest {
    BookService bookService = new DefaultBookService();
    BookService bookServiceProxy = new BookServiceProxy(new DefaultBookService());

    @Test
    public void noProxy() {
        Book book = new Book();
        book.setTitle(&quot;spring&quot;);
        bookService.rent(book);
    }

    @Test
    public void proxy() {
        Book book = new Book();
        book.setTitle(&quot;spring&quot;);
        bookServiceProxy.rent(book);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;noProxy 메소드는 spring이 출력되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;proxy() 예상대로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aaaaa&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rent : spring&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bbbbb&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 프록시 패턴을 구현할 수도 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 방법으로 자바에서 지원하는 Dynamic Proxy를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dynamic Proxy란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Runtime에 특정 interface들을 구현하는 class 또는 instance를 만드는 기술입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션은 다이나믹 프록시를 사용하여 여러 임의의 이벤트 인터페이스를 구현하는 객체를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습을 위해 프로젝트를 생성하고 살펴보겠습니다.&lt;/p&gt;

&lt;pre id=&quot;code_1648983420989&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

public class Book {

    private Integer id;

    private String title;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648983432708&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

public interface BookService {

    void rent(Book book);

    void returnBook(Book book);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648983443835&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

public class DefaultBookService implements BookService {

    @Override
    public void rent(Book book) {

        System.out.println(&quot;rent : &quot; + book.getTitle());
    }

    @Override
    public void returnBook(Book book) {
        System.out.println(&quot;return : &quot;+ book.getTitle());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648983459046&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import static org.junit.Assert.*;

public class BookServiceTest {
    BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class}
            , new InvocationHandler() {
                BookService bookService = new DefaultBookService();
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if(method.getName().equals(&quot;rent&quot;)) {
                        System.out.println(&quot;aaaa&quot;);
                        Object invoke = method.invoke(bookService, args);
                        System.out.println(&quot;bbbb&quot;);
                        return invoke;
                    }
                    return method.invoke(bookService, args);
                }
            });

    @Test
    public void dynamicProxy() {
        Book book = new Book();
        book.setTitle(&quot;spring&quot;);
        bookService.rent(book);
        bookService.returnBook(book);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Proxy.newProxyInstance를 사용하여 이전의 실습한 BookServiceProxy 클래스를 구현한 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dynamicPorxy 메소드의 결과값은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aaaa&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rent : spring&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bbbb&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return : spring 이렇게 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BookService의 타입을 DefaultBookService로 바꾸면 에러가 납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 Interface 기반의 proxy는 생성할 수 있지만, class 기반의 proxy는 생성 할 수가 없기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &lt;span&gt;java proxy는 무조건 interface를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;써야 한다는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;제약 조건이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 class기반의 proxy가 필요할 경우 어떻게 해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class기반의 proxy는 서브 클래스를 만들 수 있는 라이브러리를 사용하여 proxy를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 클래스를 만드는 방법의 단점을 살펴보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상속을 사용하지 못하는 경우 프록시를 만들 수 없습니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;private 생성자만 있는 경우&lt;/li&gt;
&lt;li&gt;Final 클래스인 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;라이브러리 ByteBuddy를&lt;span&gt;&amp;nbsp;사용하여&amp;nbsp;&lt;/span&gt;&lt;/span&gt;실습해 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648985055748&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;

&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
  xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&amp;gt;
  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;

  &amp;lt;groupId&amp;gt;me.whiteship&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;javaproxy&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;

  &amp;lt;name&amp;gt;demospringdi&amp;lt;/name&amp;gt;
  &amp;lt;!-- FIXME change it to the project's website --&amp;gt;
  &amp;lt;url&amp;gt;http://www.example.com&amp;lt;/url&amp;gt;

  &amp;lt;properties&amp;gt;
    &amp;lt;project.build.sourceEncoding&amp;gt;UTF-8&amp;lt;/project.build.sourceEncoding&amp;gt;
    &amp;lt;maven.compiler.source&amp;gt;11&amp;lt;/maven.compiler.source&amp;gt;
    &amp;lt;maven.compiler.target&amp;gt;11&amp;lt;/maven.compiler.target&amp;gt;
  &amp;lt;/properties&amp;gt;

  &amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;4.11&amp;lt;/version&amp;gt;
      &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy --&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;net.bytebuddy&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;byte-buddy&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;1.12.8&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;
  &amp;lt;/dependencies&amp;gt;
&amp;lt;/project&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648985142996&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import org.junit.Test;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.junit.Assert.*;

public class BookServiceTest {

    @Test
    public void dynamicProxy() throws Exception{
        Class&amp;lt;? extends DefaultBookService&amp;gt; proxyClass = new ByteBuddy().subclass(DefaultBookService.class)
                .method(named(&quot;rent&quot;)).intercept(InvocationHandlerAdapter.of(new InvocationHandler() {
                    DefaultBookService defaultBookService = new DefaultBookService();

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(&quot;aaaa&quot;);
                        Object invoke = method.invoke(defaultBookService, args);
                        System.out.println(&quot;bbbb&quot;);
                        return invoke;
                    }
                }))
                .make().load(DefaultBookService.class.getClassLoader()).getLoaded();

        DefaultBookService bookService = proxyClass.getConstructor(null).newInstance();

        Book book = new Book();
        book.setTitle(&quot;spring&quot;);
        bookService.rent(book);
        bookService.returnBook(book);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 Dynamic Proxy 실습과 마찬가지로 똑같이 구현되게 테스트 코드를 작성하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dynamicProxy 메소드의 결과값은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aaaa&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rent : spring&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bbbb&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return : spring 이렇게 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dynamic Proxy는 런타임에 인터페이스 또는 클래스의 인스턴스 또는 클래스를 만들어 사용하는 프로그래밍 기법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용처는 Spring data JPA, Spring AOP, Mockito 등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이로써 공부한 내용을 간략히 정리해보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-code-manipulation&quot;&gt;https://www.inflearn.com/course/the-java-code-manipulation&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>java proxy pattern</category>
      <category>proxy pattern</category>
      <category>자바 프록시 패턴</category>
      <category>프록시패턴</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/8</guid>
      <comments>https://roadj.tistory.com/8#entry8comment</comments>
      <pubDate>Tue, 5 Apr 2022 09:00:01 +0900</pubDate>
    </item>
    <item>
      <title>자바 리플렉션(Reflection)2 - Annotation, Dependency Injection(DI)</title>
      <link>https://roadj.tistory.com/7</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 리플렉션 시간에 이어서&amp;nbsp;리플렉션을 활용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 DI(Dependency Injection)을 약간 비슷하게 따라 해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 DI를 쓸 때, 주로 Annotation을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 애노테이션을 활용하여, DI를 구현하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation에 관하여 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트는 저번 시간(리플렉션)에 이어서 진행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648945600075&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public @interface MyAnnotation {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648945710740&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

@MyAnnotation()
class Book {
    public static String A = &quot;A&quot;;

    private String B = &quot;B&quot;;

    public Book() {

    }

    public Book(String b) {
        B = b;
    }

    private void c () {
        System.out.println(&quot;C&quot;);
    }

    public int sum (int left, int right){
        return left + right;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 시간에 생성한 Book클래스에 Annotation을 적용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648945749686&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import java.util.Arrays;

public class App 
{
    public static void main( String[] args ) {
	// getAnnotations(): 상속받은 (@Inherit) 애노테이션까지 조회
	// getDeclaredAnnotations(): 자기 자신에만 붙어있는 애노테이션 조회
        Arrays.stream(Book.class.getAnnotations()).forEach(System.out::println);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플렉션을 이용하여 Book 클래스에 있는 Annotation 조회하여 출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 값은???&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무것도 출력되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Annotation은 주석(코멘트)이랑 같은 취급을 받으며, 클래스 파일까지 Annotation 정보가 유지됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금부터 Annotation에 대해 자세히 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648947957880&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Retention(RetentionPolicy.속성명) : Annotation이 어느 시점까지 유지할지 정하는 Annotation입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;속성명
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;SOURCE&amp;nbsp; &amp;nbsp;: Java Source File에서만 Annotation 정보가 유지됩니다.&lt;/li&gt;
&lt;li&gt;CLASS&amp;nbsp; &amp;nbsp; &amp;nbsp; : Class File까지 Annotation 정보가 유지되고, 리플렉션을 이용하여 Annotation 정보를 얻을 수는 없습니다.&lt;/li&gt;
&lt;li&gt;RUNTIME : Class File 및 RunTime까지 Annotation 정보가 유지되고, &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;리플렉션을 이용하여 Annotation 정보를 얻을 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - Annotation 생성 시 @Retention이 없는 경우에는 기본값으로 @Rentention(RetentionPolicy.CLASS) 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648948551541&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface MyAnnotation {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Target(ElementType.속성) : 해당 Annotation을 적용할 대상을 정하는 Annotation입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;속성명
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;TYPE : Class, Interface, Enum, Annotation&lt;/li&gt;
&lt;li&gt;ANNOTATION_TYPE : Annotation&lt;/li&gt;
&lt;li&gt;FIELD : FIELD(변수), Enum&lt;/li&gt;
&lt;li&gt;CONSTRUCTOR : Constructor(생성자)&lt;/li&gt;
&lt;li&gt;METHOD : Method&lt;/li&gt;
&lt;li&gt;LOCAL_VARIABLE : local variable(로컬 변수)&lt;/li&gt;
&lt;li&gt;PACKAGE : package&lt;/li&gt;
&lt;li&gt;PARAMETER : Parameter(매개변수)&lt;/li&gt;
&lt;li&gt;그 외 여러가지 속성명이 있습니다.&lt;br /&gt;참고 : &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/ElementType.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/ElementType.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1648962114033&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Inherited 
public @interface MyAnnotation {

    String value() default &quot;minkyu&quot;;

    //int number() default 100;
 
    //Class book() default Book.class;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Ingerited : 상속할 경우 서브 클래스에도 Annotation이 적용되게 할 경우 이 Annotation을 씁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648962125866&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship;

@MyAnnotation(&quot;minkyu&quot;)
//@MyAnnotation(value = &quot;minkyu&quot;, number = 200, book = MyBook.class)
public class Book {

....
..&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation에서 변수 선언할 때, default 값을 안 주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 Annotation을 사용한 대상에서 초기값을 선언을 안 하면 컴파일 에러가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수명이 value일 경우 @MyAnnotation(&quot;minkyu&quot;) 바로 초기값 선언이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 타입은 기본형 타입, String, Enum, Annotation, Class만 허용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 Annotation에 관하여 살펴보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Annotation을 적용하면, 리플렉션을 활용하여 인스턴스가 생성되는 간단한 프로젝트를 만들어 보겠습니다.&lt;/p&gt;

&lt;pre id=&quot;code_1648963200456&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship.di;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플렉션을 활용하기 때문에 Annotation이 Runtime까지 유지되어야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648963228715&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship.di;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;

public class ContainerService {

    public static &amp;lt;T&amp;gt; T getObject(Class&amp;lt;T&amp;gt; classType ) {
        T instance = createInstance(classType);
        Arrays.stream(classType.getDeclaredFields()).forEach(field -&amp;gt; {
            if (field.getAnnotation(Inject.class) != null) {
                Object fieldInstance = createInstance(field.getType());
                field.setAccessible(true);
                try {
                    field.set(instance, fieldInstance);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        return instance;
    }

    private static &amp;lt;T&amp;gt; T createInstance(Class&amp;lt;T&amp;gt; classType) {
        try {
            return classType.getConstructor(null).newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createInstance Method&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class&amp;lt;T&amp;gt; 타입에 대한 인스턴스를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getObject Method&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수 Class&amp;lt;T&amp;gt;에 대한 인스턴스를 생성(createInstance)하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 T 타입의 모든 변수에 Inject Annotation이 적용되어 있는지 확인하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용이 되어 있는 경우에 해당 변수에 변수 타입의 인스턴스를 생성하여 주입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 변수가 확인이 되었으면, 리턴 값으로 매개변수 타입에 대한 인스턴스를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제너릭 메소드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;T&amp;gt; T getObect(Class&amp;lt;T&amp;gt; classType)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭으로 매개변수 Class타입으로 return 한다는 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 클래스는 Class&amp;lt;T&amp;gt; Instance가 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 &lt;a href=&quot;https://devlog-wjdrbs96.tistory.com/201&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://devlog-wjdrbs96.tistory.com/201&lt;/a&gt; 참고 부탁드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648966516329&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship.di;

public class BookRepository {
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648966524728&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship.di;

public class BookService {

    @Inject
    BookRepository bookRepository;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648966591856&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package me.whiteship.di;

import org.junit.Test;

import static org.junit.Assert.assertNotNull;

public class ContainerServiceTest {

    @Test
    public void getObject_BookRepository() {
        BookRepository bookRepository = ContainerService.getObject(BookRepository.class);
        assertNotNull(bookRepository);
    }

    @Test
    public void getObject_BookService() {
        BookService bookService = ContainerService.getObject(BookService.class);
        assertNotNull(bookService);
        assertNotNull(bookService.bookRepository);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getObject_BookRepository 메소드는 ContainerService 클래스의 getOjbect를 활용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스를 생성하여 변수에 주입하고 해당 변수가 null값이 아닌지 확인하는 테스트 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getObject_BookService 메소드는 위와 비슷하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BookService에 전역 변수 bookRepository에 Inject Annotation이 적용되어 있어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bookRepository도 인스턴스가 생성되어 주입되어 있는지 확인하는 테스트 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 메소드 테스트 결과는 당연히 성공입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 리플렉션을 살펴보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플렉션의 기능들이 막강하지만, 주의할 점도 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지나친 사용은 성능 이슈를 야기할 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;컴파일 시점에 확인되지 않고 런타임 시에만 발생하는 문제를 만들 가능성이 있습니다.&lt;/li&gt;
&lt;li&gt;접근 지시자를 무시할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;리플렉션 사용처는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;스프링에서 의존성 주입, MVC View에서 넘어온 데이터를 객체에 바인딩 할때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;하이버네이트 @Entity 클래스에 Setter가 없다면 리플렉션을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이로써 공부한 내용을 간략히 정리해보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-code-manipulation&quot;&gt;https://www.inflearn.com/course/the-java-code-manipulation&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <category>annotation</category>
      <category>Java Reflection</category>
      <category>Reflection</category>
      <category>리플렉션</category>
      <category>애노테이션</category>
      <category>자바 리플렉션</category>
      <author>J-Mandu</author>
      <guid isPermaLink="true">https://roadj.tistory.com/7</guid>
      <comments>https://roadj.tistory.com/7#entry7comment</comments>
      <pubDate>Mon, 4 Apr 2022 09:00:55 +0900</pubDate>
    </item>
  </channel>
</rss>