<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ju-ei8ht.log</title>
        <link>https://velog.io/</link>
        <description>웹퍼블리셔의 백엔드 개발자 도전기</description>
        <lastBuildDate>Tue, 25 Apr 2023 05:05:34 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ju-ei8ht.log</title>
            <url>https://velog.velcdn.com/images/ju-ei8ht/profile/856064e7-e115-4822-813a-536682650b54/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ju-ei8ht.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ju-ei8ht" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Spring(SpringSecurity, BlogProject)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring12</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring12</guid>
            <pubDate>Tue, 25 Apr 2023 05:05:34 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/4/17 ~ 2023/4/21</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-28스프링-시큐리티">Day 28(스프링 시큐리티)</h3>
<p>Session의 Security ContextHolder의 Authentication 객체에 UserDetails(유저 오브젝트), Credential(비번), Authority(권한) 저장</p>
<ol>
<li>/login Post 요청(form)<ol>
<li>usernamePasswordAuthenticationFilter 발동<ol>
<li>usernamePasswordToken 발행</li>
<li>userDetailsService의 loadByUsername() 호출<ol>
<li>내부구현<ol>
<li>DB에 username 존재 확인</li>
<li>user 오브젝트(영속화) 리턴 받고</li>
<li>userDetails를 생성</li>
<li>Authentication 객체 생성</li>
<li>sch에 담기</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li>JWT를 쓰려면<ol>
<li>upafilter 무력화(form x)</li>
<li>시큐리티 필터<ol>
<li>jwt 필터(인가) 검증 추가<ol>
<li>토큰 검증</li>
<li>강제 세션 만들기(Security ContextHolder의 Authentication 객체 생성)</li>
</ol>
</li>
</ol>
</li>
<li>인증<ol>
<li>/login (컨트롤러에서 처리)</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>UserDetailsImpl에 오버라이드한 getAuthorities()메소드의 <code>authorities.add(()-&gt; &quot;ROLE_&quot;+user.getRole());</code> 는 문법 ROLE_ADMIN 이런 식</p>
<p>근데 SecurityConfig의 hasRole에는 <code>SecurityExpressionRoot</code> 의 <code>defaultRolePrefix</code> 에 “ROLE_”로 되어 있어서 hasRole(”ADMIN”)이런 식으로 넣어야 함……</p>
<hr>
<h3 id="day-29블로그-프로젝트">Day 29(블로그 프로젝트)</h3>
<p>intellij에서 .http로 api 테스트할 수 있음</p>
<hr>
<h3 id="day-30">Day 30</h3>
<p>@EntityGraph()쓰지 말자
웬만하면 Lazy Loading하고 N+1은 fetch join으로 해결</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring(ToyProject, Service)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring11</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring11</guid>
            <pubDate>Mon, 17 Apr 2023 09:42:53 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/4/10 ~ 2023/4/14</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-25토이프로젝트-검증1">Day 25(토이프로젝트 검증1)</h3>
<p>setter 쓰지 말고 상태 변경 메소드는 메소드명을 명확히!</p>
<p>@Pattern(regexp=”정규식”)
@Email쓰면 email에 쓰이는 정규식으로 validation 됨</p>
<hr>
<h3 id="day-26토이프로젝트-검증2">Day 26(토이프로젝트 검증2)</h3>
<p>dto에서 toEntity 메소드로 entity 리턴하자</p>
<pre><code class="language-java">public class RequestDTO{
    @Getter
    public static class JoinDTO{
        //field...

        public User toEntity(){
            retur User.builder()
                        .username(username)
                        ...
                        .build();
        }
    }
}</code></pre>
<p><strong>HibernateLazyInitializer Exception</strong>
출력해야 할 객체가 지연로딩이 발생하면 생김
→ 출력할 때 select 쿼리가 발생하는 문제…
⇒ eager 즉시로딩하거나 fetch join…</p>
<p>update시에 @Transactional을 붙이면 트랜잭션 종료 후 변경감지 → flush
@Transactional을 안 쓰면 repository.save(entity); → merge 실행</p>
<p><strong>flatMap</strong>
대괄호[]가 없어진다고 생각하면 될 듯…
근데 flatMap으로 Entity를 그대로 출력하기보단 ResponseDTO를 만드는 게 무조건 좋다.</p>
<p>DeleteMapping을 안 하면 PostMapping으로 api에 new, delete로 구분지어서
그럼 엔티티에 status를 넣어야겠네…</p>
<hr>
<h3 id="day-27service">Day 27(Service)</h3>
<p><strong>Controller</strong></p>
<ol>
<li>유효성 검사</li>
<li>인증 검사</li>
<li>권한 체크(DB조회 없는 것)</li>
<li>서비스 요청</li>
<li>MessageConverter Json 응답</li>
</ol>
<p>query 공부!</p>
<p><strong>Service</strong></p>
<ol>
<li>비즈니스 로직</li>
<li>트랜잭션 관리</li>
<li>권한 체크(자원에 대한)</li>
<li>DTO 변환 - Lazy Loading</li>
<li>더티 체킹</li>
</ol>
<p><strong>dto</strong>
request → in
response → out</p>
<p><strong>트랜잭션 ACID</strong>
@Transactional 처리를 하지 않으면 메소드 내에서 throw Exception을 날려도 아래 코드가 실행 될까?
chatGPT 대답)
→ 일반적으로, Java에서 메소드 내에서 Exception이 발생하면 해당 메소드의 나머지 코드는 실행되지 않고, 예외가 적절한 catch 블록이나 호출자로 전달됩니다. 이를 &quot;예외 전파&quot;라고 합니다. 따라서 해당 메소드의 후속 코드는 실행되지 않습니다.
하지만 예외가 발생한 메소드가 트랜잭션 내에서 실행되는 중이고, 트랜잭션 관리가 되지 않은 상태라면, 예외 발생 시 트랜잭션이 롤백되지 않고, 데이터베이스 등의 리소스에 대한 변경이 커밋될 수 있습니다. 이는 데이터의 일관성과 무결성을 해치는 원인이 될 수 있습니다.
따라서, 트랜잭션 처리를 하지 않고 메소드 내에서 예외가 발생한다면, 예외가 발생한 시점 이후의 코드가 실행될 수 있고, 데이터의 무결성을 보장하기 어려울 수 있습니다. 트랜잭션을 사용하여 예외 발생 시 롤백하고, 데이터의 일관성을 보장하도록 하는 것이 안전한 프로그래밍 방법입니다.</p>
<p>@ColumnDefault(”value”)로 기본값을 넣을 수 있음 →추천하진 않음</p>
<p>인증이 필요 없는 api는 앞에 엔티티 붙이지 않는다.
→ 예를들어 login, join을 /user/login, /user/join으로 하지 않는다.</p>
<p>OOP
상태체크(validate), 상태변경(setter), 상태확인(getter) → 해당 객체에서</p>
<p>dto는 동일해도 공유해서 쓰지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring(JWT, AOP)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring10</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring10</guid>
            <pubDate>Tue, 11 Apr 2023 12:01:03 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/4/3 ~ 2023/4/5</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-23jwt">Day 23(JWT)</h3>
<p>단방향 암호화: 인코딩, 동일성체크만 됨 → password(복호화할 필요가 없음)</p>
<p>양방향 암호화: 인코딩, 디코딩</p>
<p>동형암호</p>
<p>Base64: 바이너리 데이터를 문자열로 보내기 위해서 암호화→복호화
⇒ 보안적인 의미에서 쓰는 건 아님</p>
<hr>
<h3 id="day-24aop">Day 24(AOP)</h3>
<p>OOP는 행위로 상태를 변경
→ 행위는 stack 메모리에, 상태는 heap 메모리에 저장</p>
<p>스레드는 heap을 공유
→ 하나의 자원을 두고 경쟁(race condition)
⇒ 해결책: 상태를 안 쓰는 것 → functional programming</p>
<p>그래서 불변 객체(Immutable Object)가 나오고…</p>
<p>AOP는 리플렉션을 해주는 것</p>
<p>JoinPoint
→ 해당 어노테이션을 쓰는 곳의 정보를 가져옴(리플렉션)</p>
<p><strong>AOP 사용하는 법</strong></p>
<ol>
<li>깃발(어노테이션)생성</li>
<li>깃발에 별칭(Pointcut)주기</li>
<li>공통모듈 생성</li>
<li>공통모듈에 별칭등록하기</li>
</ol>
<p>이미 만들어져 있는 깃발(어노테이션)으로 등록하기</p>
<p>AOP로 validation하면 편하곘다…</p>
<p>valid에서 @Pattern으로 정규식 쓸 수도 있음</p>
<p>valid에서 에러 생기면 BindingResult(parameter로 RequestBody(dto) 바로 뒤에서) 받음</p>
<p>@Around의 경우 ProceedingJoinPoint를 파라미터로 받으면</p>
<p>공통로직</p>
<p>jp.proceed(); → 어노테이션 단 메소드 실행</p>
<p>공통로직으로 실행할 수 있음</p>
<pre><code class="language-java">System.out.println(&quot;실행 전&quot;);
Object result = jp.proceed();
System.out.println(&quot;실행 후&quot;);
return result;

//실행 전
//result 참조값
//실행 후</code></pre>
<p>할 때 proceed()가 실행할 때 result가 mesageConverter로 동작하는 건 아님</p>
<p>messageConverter를 테스트하려면 @afterReturning 써야 함</p>
<p>interceptor랑 AOP랑 동작할 때는 interceptor가 먼저 동작</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring(세션과 쿠키, OAuth)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring9</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring9</guid>
            <pubDate>Mon, 03 Apr 2023 07:06:44 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/3/27 ~ 2023/3/31</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-20연관관계">Day 20(연관관계)</h3>
<p><code>@OneToMany(mappedBy = &quot;board&quot;)</code>
→ mappedBy에는 연관관계의 주인(ManyToOne)의 변수명을 적음</p>
<pre><code class="language-java">@ManyToOne
private **Board board;</code></pre>
<p>변수명인 board를 적는 것(board2면 mappedBy = ”board2”)</p>
<p><code>@OneToMany(mappedBy = &quot;board&quot;, fetch = FetchType.***LAZY***, cascade = CascadeType.***PERSIST***, orphanRemoval = *true*)</code>
OneToMany의 fetch 기본 전략은 Lazy</p>
<p>cascade: 영속성 전이 (해당 객체가 영속성 상태일 때 전이됨)</p>
<p>orphanRemoval: 고아 객체 삭제</p>
<p><strong>양방향 매핑 장단점</strong></p>
<p>영속성 전이나 고아 객체에 대한 개념의 이해가 필요</p>
<p>양방향 때문에 생길 오류(N+1 문제 등?)</p>
<p>대신 심플해짐…</p>
<pre><code class="language-sql">select * from board inner join user</code></pre>
<p>board를 driving, user를 driven</p>
<p>만약 둘의 위치가 바뀌면 pk로 찾아지지 않기 때문에(중복될 수 있어서) full scan…</p>
<p>inner join</p>
<p>만약 게시글과 댓글을 inner join으로 찾으면 댓글 없는 게시글은 조회가 안 됨</p>
<p>left outer join을 하면</p>
<p>없으면 null로</p>
<p>NoSQL은 중복이 됨. join을 안 함. 모든 연관관계를 한 테이블에 넣어버림.
→ insert, select만 된다면 유리</p>
<p>@Transient
→ DB에 컬럼 만들지 않게</p>
<p>예를 들어 Board에 연관관계로 user, reply를 가지고 있다면 json으로 parsing될 때 무한참조가 일어남
→ @JsonIgnore하면 되겠지만 그렇게 하지 않고 service 단계에서 controller로 넘어오기 전에 OSIV 설정을 하면 됨</p>
<hr>
<h3 id="day-21세션과-쿠키">Day 21(세션과 쿠키)</h3>
<p>stateless → stateful</p>
<p><strong>구 버전</strong>
자원에 접근할 수 있는 상태가 되었다 = 세션이 생성됨</p>
<p>클라이언트가 서버에 get요청 → 연결을 끊음(끊지 않으면 서버 과부화)</p>
<p>서버 = 갑 = stateless
→ 클라이언트 상태를 기억하지 않는다(트래픽 낮음).</p>
<p><strong>최신(디폴트)</strong>
서버에 세션저장소에 세션키와 데이터를 저장 = stateful</p>
<p><strong>서버</strong></p>
<ol>
<li>클라이언트의 요청 헤더 cookie 값을 검사</li>
<li>JSessionID값이 있는지 확인</li>
<li>임의의 중복되지 않는 JSessionID를 생성</li>
<li>JSessionID를 세션 메모리 영역에 저장</li>
<li>컨트롤러-서비스-레파지토리 일을 하고</li>
<li>응답 헤더 set-cookie에 세션 키(JSessionID=세션 키)를 담아서 응답</li>
</ol>
<p><strong>브라우저</strong>
쿠키 저장소에 JSessionID를 키값으로 세션 키를 저장
쿠키 값을 헤더에 담아서 요청</p>
<p><strong>정책</strong>
javascript로 cookie에 접근하는 걸 막음(HttpOnly로 브라우저만 접근 가능)</p>
<p>멀티 쓰레드 환경에서 싱글톤 객체에 상태 변수(인스턴스 변수) 만들지 말도록
→ 지역변수는 괜찮음(스택에 따라 메모리가 다르니까)</p>
<hr>
<h3 id="day-22oauth">Day 22(OAuth)</h3>
<p><strong>XSS 공격</strong>
백엔드에서 xss 필터를 할 필요가 있다(프론트에서 리액트를 써서 막아지더라도).</p>
<p>궁금증)
xssFilter를 사용해서 escape로 html 태그로 변환했을 때(&lt; → &lt;) html 태그가 그대로 text로 출력되는데 데이터를 다시 원래 값(&lt; → &lt;)으로 변환해서 보내줘야 하는지?</p>
<p><strong>CSRF 공격</strong>
사실 Spring Security를 쓰면 filter로 쉽게 설정해줄 수 있다.</p>
<p><strong>OAuth</strong>
OAuth 쓸 때 내부 서버 데이터베이스에 OAuth에서 받은 회원정보를 저장할 필요가 있는지 아니면 매번 OAuth 서버에서 회원정보를 가져와야 하는지</p>
<p>궁금증)</p>
<ol>
<li>회원정보를 저장할 필요가 있다면 해당 회원이 OAuth로 가입한 회원인지 체크하고(예를 들면 boolean column을 만들어서 → OAuth로만 로그인 할 수 있게), 비밀번호는 임의로 한 후 내부 서버의 RefreshToken, AccessToken을 발급받아서 로그인 상태를 유지하게 해도 괜찮은지</li>
<li>저장할 필요가 없다면 매번 OAuth 서버에서 회원정보와 토큰을 받아서 인증과 로그인 상태 유지를 해야 하는지</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring(Hibernate)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring8</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring8</guid>
            <pubDate>Mon, 27 Mar 2023 11:36:02 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/3/20 ~ 2023/3/24</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-17hibernate">Day 17(Hibernate)</h3>
<p>하드웨어에 저장하는 persistence(영속성)</p>
<p>@Entity 하이버네이트가 관리해줌</p>
<p>hibernate가 관리하는 entity를 repository에 save된 후 persistence context에 관리됨
→ 바로 조회하면 캐싱돼서 persistence context에서 가져오기 때문에 DB 조회 쿼리가 발생하지 않음
⇒ 영속화 시킨 객체의 디폴트 생성자를 리플렉션해서 호출함</p>
<p>영속화된 걸 update → flush() 변경</p>
<p>영속화되지 않은 걸 update → 변경 안 됨</p>
<hr>
<h3 id="day-18repository">Day 18(Repository)</h3>
<p>영속 객체: @Entity, PC 안에 존재, PK 존재, 기본 생성자</p>
<p>준영속 객체: 영속 객체를 detach로 꺼낸 상태 → flush가 이뤄지지 않음: 더 이상 영속 객체가 아니라서 변경 감지가 이뤄지지 않음</p>
<p>entitiManager의 merge()</p>
<ol>
<li>PK 존재 확인 →PK가 존재하지 않으면 insert(transaction 종료 시점에)</li>
<li>PK DB 조회</li>
<li>transaction 종료 시점에 flush(update)
⇒ 쓰지 않음</li>
</ol>
<p>JpaRepository를 상속한 interface를 어떻게 new 할까?
→ 서버 실행 시에 동적으로 클래스를 만듦: 리플렉션으로
⇒ Ioc 컨테이너에 동적으로 구현된 클래스가 빈으로 등록하고 컴포넌트 스캔(부모를 찾는다)</p>
<hr>
<h3 id="day-19연관관계">Day 19(연관관계)</h3>
<p>insert, select만 하는 시스템
→ NoSQL</p>
<p>scalar
→ 한 건</p>
<p>vector
→ list [1, 2, 3, 4]</p>
<p>matrix
→ n차원</p>
<p><strong>연관관계의 주인 조회</strong></p>
<ol>
<li>주인이 아닌 애들이 있는지 찾는다. (user)</li>
<li>연관관계에 있는 애들을 포함해서 가져온다. (join)</li>
<li>flat → ORM(Object Relational Mapping)</li>
</ol>
<p><strong>spring batch</strong></p>
<pre><code class="language-yaml">jpa:
    hibernate:
      ddl-auto: create
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        default_batch_fetch_size: 100</code></pre>
<p>100건까지 쿼리 한번만 날림(join 안 하면 연관관계에 있는 테이블 조회가 따로 되겠지만 그것도 한 번씩만 됨)</p>
<p>N+1 문제 → in query → default_batch_size(or fetch join)</p>
<ol>
<li>주인만 select 한다.</li>
<li>주인이 아닌 애들은 각각 select 한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring(Annotation, ExceptionHandler,  Repository)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring7</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring7</guid>
            <pubDate>Tue, 21 Mar 2023 05:15:34 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/3/13 ~ 2023/3/17</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-14annotation">Day 14(Annotation)</h3>
<pre><code class="language-java">@GetMapping(value = &quot;/req/get&quot;, produces = MediaType.TEXT_HTML_VALUE)</code></pre>
<p>@RestController
→ 가끔 Object가 리턴될 때 JSON 파싱 → MessageConverter
→ response.getWriter().println()</p>
<p>@Controller
→ ViewResolver
→ request.setAttribute(); → model.addAttribute(name,value);
→ request.getRequestDispatcher();
→ view를 리턴하지만 리턴타입을 ResponseEntity로 하면 @ResponseBody가 발동</p>
<p>return에 “redirect:~”을 하면 dispatcherServlet이 send.redirect함</p>
<p><strong>Test 코드 작성</strong>
@WebMvcTest
→ Controller 이하 즉 Filter, DispatcherServlet, Controller, MockMvc를 로드</p>
<p>@SpringbootTest
→ Springboot에 관련된 모든 걸 로드</p>
<hr>
<h3 id="day-15exception-handler">Day 15(Exception Handler)</h3>
<p>DS는 RuntimeException(하위 예외 포함)만 처리함</p>
<p>델리게이트패턴, 프록시패턴</p>
<hr>
<h3 id="day-16repository">Day 16(Repository)</h3>
<p>bean change
→ 상속해서 덮어씌우기</p>
<p>IoC 컨테이너가 빈을 관리
→ Controller 등의 빈을 직접 new해서 관리하는 게 아니라 스프링에서 리플렉션으로 관리
⇒ 참조값을 모른다? → DI로 관리</p>
<p>@Bean
해당 메소드의 리턴타입을 빈에 등록</p>
<p>DI를 할 때 필드주입으로 @Autowired하는 것과 생성자 주입(@RequiredArgsConstructor: final 필드 자동 주입)하는 것의 차이
→ 필드로 주입하면 리플렉션으로 찾아야 하니까 부하가 걸림</p>
<p>DB LOCK
: insert가 일어날 때 다른 쓰레드에서 update, delete, insert이뤄지지 않고(DML) select(DQL)만</p>
<p>데이터 변경: ACID
→ I(고립성)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring(스프링부트)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring6</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring6</guid>
            <pubDate>Mon, 13 Mar 2023 10:12:52 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/3/06 ~ 2023/3/08</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-12스프링부트">Day 12(스프링부트)</h3>
<p>resouses/application.properties
application-dev.properties
spring-profiles-active=dev</p>
<p>단일 쓰레드 지원
→  빨라짐
Non-BLocking I/O(NIO)</p>
<p>Component Scan
artifect 아래만 scan함
스프링이 지정해둔 어노테이션(8가지)이 new 됨</p>
<hr>
<h3 id="day-13스프링부트-컨트롤러">Day 13(스프링부트 컨트롤러)</h3>
<p><code>@RequestParam(value = &quot;keyword&quot;, required = false) String keyword</code>
getParameter가 없을 수 있음(없으면 null)
default는 true (근데 requestParam을 안 쓰면 default false)</p>
<p>만약 parameter의 default 값을 넣어야 하면
<code>@RequestParam(defaultValue = &quot;cos&quot;) String keyword</code></p>
<p>WebFlux(spring 5.0)</p>
<p><code>@RequestBody</code> 을 쓰면 버퍼 발동 model로 받아지면 json으로 받아지고 아니면 그냥 받은 타입으로…</p>
<p>filter는 dispatcherServlet 직전에 발동</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring(자바, 자바스크립트)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring5</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring5</guid>
            <pubDate>Mon, 06 Mar 2023 09:20:07 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/2/28 ~ 2023/3/02</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-10자바">Day 10(자바)</h3>
<p><strong>static</strong>
컴파일 시에 정적으로 메모리 생성</p>
<p><strong>heap</strong>
인스턴스의 멤버변수(static이 아닌 것) 및 메소드 저장</p>
<p><strong>stack</strong>
메소드 실행되면 쌓임 FILO
→ 메소드의 지역변수, 매개변수도 stack에 저장됨</p>
<p><strong>상속</strong>
재사용하기 위해 쓰는 게 아님. 재사용은 부가적인 것
is a가 아니면 상속관계 X
추상화하기 위해 쓴다.</p>
<pre><code class="language-java">Car car = new Car(new Engine());</code></pre>
<p>DI 주입
→ 컴포짓 패턴</p>
<pre><code class="language-java">아시아인 n1 = new 한국인(); //한국인-(아시아인)-지구인
n1.name //아시아인

지구인 n2 = new 한국인(); //한국인-아시아인-(지구인)
n2.name //지구인</code></pre>
<p>구체적 행위가 다르다.
메소드에 파라미터로 주입돼도 의존성 주입</p>
<p>부모의 메서드가 오버라이드됨
→ 오버라이드: 무효화시킴</p>
<p>자식이 부모의 메서드를 오버라이딩(x) 재정의(0)</p>
<p>어노테이션은 JVM의 주석이다.</p>
<p>@Override를 달면 JVM이 해당 클래스를 조회할 때 해당 어노테이션으로 바로 감</p>
<p>싱글톤패턴 설계</p>
<pre><code class="language-java">class Doorman{
    //불변: setter가 없기 때문에
    private static instance = new Doorman();

    public static Doorman getInstance(){
        return instance;
    }

    private Doorman(){}
}

public main(String[] args){
    Doorman d = Doorman.getInsatance();
    //Doorman.getInstance = null; 할 수 없음
}</code></pre>
<hr>
<h3 id="day-11자바-스크립트">Day 11(자바 스크립트)</h3>
<p>인터프리터 언어</p>
<p>타입 안 적는 이유: 런타임에 메모리 결정</p>
<p>null 대신 undefined</p>
<pre><code class="language-java">class User{
    private int id;
    private String username;
    private String password;

    //getter, setter, constructor
}</code></pre>
<pre><code class="language-jsx">let user = {
    id: 1,
    username: &quot;test&quot;,
    password: &quot;1234&quot;
}

console.log(&quot;id&quot;. user.id)</code></pre>
<p>serializable 직렬화 및 역직렬화를 통해 통신 → json으로</p>
<p>json: javascript object notation</p>
<p>heap에 저장됨</p>
<p>원할 때 new 해서 띄우는 자바와는 다르게 자바스크립트는 다 뜸</p>
<p>그리고 자바스크립트는 타입을 보지 않기 때문에 배열에 여러 타입이 들어갈 수 있어서 느리다.</p>
<p>파이썬은 인터페이스 언어
→ 내부적으로 C언어로 실행됨</p>
<p>자바스크립트는 자바(객체 지향: 클래스를 1급 객체)와 다르게 모든 것을(메소드도) 1급 객체로 생각
→ 1급 객체: 최상단에 위치할 수 있는 코드
⇒ 1급 객체는 변수에 담을 수 있다.</p>
<p>자바에서는 클래스 안에 구현해야 쓸 수 있음
하지만 자바스크립트는 바로 호출 가능</p>
<pre><code class="language-java">class A{ //VO
    int num = 10;
}</code></pre>
<p>호이스팅
→ 코드가 실행하기 전 해당 스코프의 최상단으로 끌어 올려진 것 같은 현상을 말한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring(MVC패턴, Session)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring4</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring4</guid>
            <pubDate>Mon, 27 Feb 2023 10:31:54 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/2/21 ~ 2023/2/23</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-8mvc패턴">Day 8(MVC패턴)</h3>
<p><strong>FrontController 문제점</strong></p>
<ol>
<li>다이렉트한 접근을 막아야 한다.</li>
<li>jsp파일에 쓸데없는 java파일이 많다.</li>
<li>request body 데이터를 직접 전달하지 못 한다.</li>
</ol>
<p><strong>FrontController의 책임</strong>
다른 controller를 찾아 라우팅해주는 책임을 가진다.
⇒ Dispatcher Servlet</p>
<p><strong>세션 데이터 만료</strong></p>
<ol>
<li>브라우저를 다 종료하면</li>
<li>시간이 만료되면</li>
<li>서버가 꺼졌다가 다시 켜지면</li>
<li>세션을 강제로 제거</li>
</ol>
<p><strong>리퀘스트 데이터 만료</strong>
요청하고 응답이 끝나면 종료</p>
<p><strong>스택</strong>
메서드 요청시에 열리고 메서드가 종료되면 사라진다.</p>
<p>⇒ 배우는 이유 FrontController를 쓰기 위해
: request가 두 번 들어감(원래는 http 프로토콜상 request는 한 번만 요청할 수 있음)</p>
<p>RequestDispatcher</p>
<p>변경사항 계속 요청하는 것(새로고침 F5)
: polling기법</p>
<p>능동적 push
: hook</p>
<hr>
<h3 id="day-9실습">Day 9(실습)</h3>
<p>DispatcherServlet으로 request에서 getMethod로 get 혹은 post요청이 정상적으로 들어왔는지 확인하고, getParameter로 post요청 받고, getRequestDispatcher로 request, response forward하는 걸 실습함
sendRedirect로는 페이지가 이동되지만 getRequestDispatcher로 forwarding을 하면 uri는 그대로, WEB-INF 보안 디렉토리로 해당 request/response의 포워딩이 가능하다.</p>
<p>+과제로 request.setAttribute했던 걸 session에 해보고 로그인,회원가입을 post로 보내기, session에 user가 없으면(로그인한 기록) 다시 로그인 페이지로 redirect하기(아직 spring security는 없이)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring(서블릿)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring3</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring3</guid>
            <pubDate>Mon, 20 Feb 2023 04:49:43 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/2/13 ~ 2023/2/17</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-5서버">Day 5(서버)</h3>
<p>서버는 갑. 클라이언트로부터 request를 받으면 response를 하고 socket을 닫음.
⇒ stateless</p>
<p>요즘 http통신은 상태를 저장하는데 이 경우는 stateful</p>
<p>브라우저가 url 요청을 하면 html로 응답하는데 브라우저는 html 외는 해석을 못 함
따라서 jsp파일의 경우 아파치에 톰캣을 달아서 자바코드를 해석(컴파일 실행)</p>
<hr>
<h3 id="day-6서블릿-기본">Day 6(서블릿 기본)</h3>
<p>API(Application Interface)</p>
<hr>
<h3 id="day-7서블릿-고급">Day 7(서블릿 고급)</h3>
<p>서블릿은 단 한 번만 서비스가 만들어짐
서블릿의 호출로 쓰레드가 만들어짐
쓰레드를 통해서 서비스가 실행됨</p>
<p>서버는 클라이언트의 요청(request)을 받지만 해당 request를 저장하지 않음
response할 때 request를 삭제 → stateless 무상태성
⇒ 정보가 너무 많아지면 서버에 부담이 되므로</p>
<p>하지만 클라이언트의 정보를 저장할 필요가 있음
→ 처음 접근한 클라이언트에 대해 세션 스토어에 세션 key를 담고 response 헤더에 session key를 보냄
→ 브라우저에 저장(쿠키)
→ 두 번째부터 요청할 때 헤더에 session key를 보냄
: 이전에 왔던 사용자인지 알 수 있음</p>
<p><strong>브라우저</strong></p>
<ol>
<li>최초 요청 - 브라우저(쿠키 저장소 확인)</li>
<li>없음</li>
<li>요청</li>
</ol>
<p><strong>서버</strong></p>
<ol>
<li>요청 받음 - 헤더 쿠키값 확인</li>
<li>없음</li>
<li>세션키 생성</li>
<li>세션키 → 세션 스토어에 저장</li>
<li>response header에 세션키를 저장</li>
</ol>
<p><strong>브라우저</strong></p>
<ol>
<li>응답 받음 - response header에 세션키 확인</li>
<li>있음</li>
<li>브라우저 쿠키 저장소에 저장</li>
</ol>
<p>index.jsp 요청
→ 톰캣</p>
<ol>
<li>index_jsp.java (서블릿 파일 변환)</li>
<li>컴파일 실행</li>
</ol>
<p><strong>서블릿 스코프</strong></p>
<ol>
<li>Page 스코프</li>
<li>Request 스코프(*중요)<ul>
<li>HttpServletRequest API 사용</li>
<li>요청 ~ 응답까지의 Life Cycle을 가진다.</li>
</ul>
</li>
<li>Session 스코프</li>
<li>Application 스코프</li>
</ol>
<p>EL표현식</p>
<pre><code class="language-java">${pageScope.pageData}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring (통신2)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring2</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring2</guid>
            <pubDate>Mon, 13 Feb 2023 06:11:23 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/2/6 ~ 2023/2/10</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-2소켓통신1">Day 2(소켓통신(1))</h3>
<h4 id="서킷-스위칭과-패킷-스위칭">서킷 스위칭과 패킷 스위칭</h4>
<p>서킷 스위칭은 직렬화해서 통신마다 전용선을 두는 것. 단점으로 비용이 올라간다.
패킷 스위칭은 전용선x 공유선o 회선이 짧아서 비용을 아낄 수 있음 하지만 A가 데이터를 보내는 동안 다른 곳(보내는 쪽)은 동기화 상태
→ RR알고리즘(라운드로빈) 방식처럼 데이터를 쪼개서 전송(잘개 쪼개진 데이터: 패킷)</p>
<p>패킷은 전송 데이터와 목적지 주소(IP)를 함께 전송
→ 그렇지 않으면 데이터가 몰린다.</p>
<p>데이터 하나하나는 segment(payload에 담김), IP는 heder에 담김
⇒ segement와 header가 패킷</p>
<p>라우터는 전송받은 패킷을 최단경로(트래픽이 낮은)를 찾아서 보냄
:패킷 포워딩</p>
<p>라우터의 용량(RAM)이 꽉 차면 도착한 패킷은 유실됨</p>
<p>보낸 순서는 segment에 붙어서 보내짐(도착 순서 보장이 안 되기 때문에 재조립을 위해서)</p>
<p>받은 세그먼트를 보내진 순서에 맞게 조립(하나가 안 도착하면 다시 요청 후 받음: 신뢰성있는 데이터)
⇒ 패킷 스위칭</p>
<h4 id="동기화">동기화</h4>
<p>데이터 관점에서의 동기화: 데이터 일치(아이폰과 아이클라우드를 생각하면)
프로그래밍 관점에서의 동기화: 대기 상태. 일의 순서가 있다.</p>
<h4 id="인터페이스와-프로토콜">인터페이스와 프로토콜</h4>
<p>인터페이스: 상하관계가 있는 규약
프로토콜: 동등한 관계의 규약</p>
<p>라우터를 인터페이스라고 부르는 이유는 경로가 한정되어 있기 때문</p>
<hr>
<h3 id="day-3소켓통신2">Day 3(소켓통신(2))</h3>
<h4 id="소켓통신의-기본">소켓통신의 기본</h4>
<p><strong>TCP/IP 이해</strong>
통신하는 프로세스에 번호(Port 번호)를 붙임(범위는 0~65,535 → 2의 16제곱 개 → 2Byte개</p>
<p><strong>BufferedWrite</strong>
TCP에서 Socket으로 BufferedRead Segment(세그먼트에는 포트번호와 순번이 들어감 ⇒ 헤더)</p>
<p>바이너리코드가 packet으로 바뀌고 socket을 통해 segment로 바껴 프로세스(애플리케이션)에 도착
→ 역직렬화</p>
<p>직렬화: 기계가 이해하는 데이터로 변환하는 것
역직렬화: 인간이 이해하는 데이터로 변화하는 것</p>
<p>소켓이 직접 하는 건 없기 때문에 애플리케이션이 소켓에서 Buffered Read함</p>
<p><strong>Buffer</strong>
버퍼가 꽉 차서 데이터가 도착하지 못함 →버퍼링
애플리케이션이 read해서 데이터를 소비해야 함</p>
<h4 id="반이중과-전이중">반이중과 전이중</h4>
<ol>
<li>반이중</li>
</ol>
<ul>
<li>두 디바이스간 통신선이 하나(선은 stream)</li>
<li>한쪽 디바이스에서 송신과 수신 모두 가능</li>
<li>하나의 통신선으로 송신과 수신을 함. 때문에 동시에 송신과 수신을 할 수 없음</li>
</ul>
<p>양방향이지만 동시에 송수신 x</p>
<ul>
<li>동기적(일의 순서가 있음)</li>
<li>데몬이 아니다.</li>
</ul>
<p><strong>통신</strong></p>
<ul>
<li>반이중</li>
<li>서버 데몬(죽으면 안 됨)</li>
<li>동시접속 허용 - 멀티쓰레드</li>
</ul>
<ol start="2">
<li>전이중</li>
</ol>
<ul>
<li>두 디바이스간 통신선이 두 개(송신선, 수신선)</li>
<li>송신선과 수신선이 각각 존재하므로 데이터 송신과 동시에 수신이 가능</li>
</ul>
<p>Hello.java → 컴파일 Hello.class → 실행 JVM 동작</p>
<p><strong>JVM</strong></p>
<ol>
<li>클래스 로드(메모리)</li>
<li>static 찾기</li>
<li>static 메모리 공간에서 main을 찾음</li>
<li>main 실행</li>
<li>main의 스택과 큐가 열린다.</li>
</ol>
<p><strong>static</strong>
단 한 번만 메모리에 할당된다.
두 번x, 메모리 관리를 할 수가 없다. 메인 종류시까지 살아있다.</p>
<p><strong>heap(오브젝트)</strong>
내가 원할 때 new 해서 할당한다(인스턴스).
그래서 동적 메모리 공간이라고 한다. →제어할 수 있다. 단, 자바는 가비지컬렉션을 한다.</p>
<p><strong>stack</strong>
메서드가 호출될 때 잠깐 메모리에 뜬다.
메모리 관리가 필요없다. 왜냐하면 메서드 종료시 다 사라지기 때문</p>
<hr>
<h3 id="day-4mime-타입">Day 4(MIME 타입)</h3>
<p><strong>jar(패키지 모음 압축 파일)</strong></p>
<ul>
<li>메인메서드x → 라이브러리</li>
<li>메인메서드o → 실행파일</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring (통신)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-spring1</link>
            <guid>https://velog.io/@ju-ei8ht/wil-spring1</guid>
            <pubDate>Mon, 06 Feb 2023 06:27:33 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/2/2</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-1통신">Day 1(통신)</h3>
<p>판서로 진행되는 강의는 처음이라...
그래서 강의 들을 땐 재밌었는데 이걸 어떻게 정리하면 좋을지 모르겠다...</p>
<h4 id="cia보안의-3요소">CIA(보안의 3요소)</h4>
<ul>
<li>Confidentiality(기밀성)<ul>
<li>메시지를 public하게 보냄 → 기밀성x</li>
</ul>
</li>
<li>Integrity(무결성)<ul>
<li>메시지가 탈취+위조당함 → 무결성x</li>
</ul>
</li>
<li>Availability(가용성)<ul>
<li>메시지가 탈취당함 → 가용성x</li>
<li>암호화한 메시지의 프로토콜이 탈취당해서 복호화할 수 없음 → 가용성 x</li>
</ul>
</li>
</ul>
<br/>

<h4 id="대칭키-공개키">대칭키, 공개키</h4>
<br/>


<h4 id="rsa">RSA</h4>
<p>공개키로 잠그면 개인키로 열 수 있다.</p>
<p>개인키로 잠그면 공개키로 열 수 있다.</p>
<ul>
<li>기밀성은 가능<ul>
<li>상대의 공개키로 보내면 상대의 개인키로만 데이터를 확인할 수 있음</li>
</ul>
</li>
<li>무결성은 깨질 수 있음<ul>
<li>탈취 및 위조가 가능 → 데이터를 확인하지 못하지만 데이터 삭제 후 위조된 데이터를 보낼 수 있음</li>
</ul>
</li>
</ul>
<p>⇒ 따라서, 데이터(payload: body 데이터)를 상대의 공개키로 잠군 박스 안에 넣고(캡슐화), 그걸 또 개인키로 잠금</p>
<br/>

<p>중간에 탈취하더라도 위조하려면 밥의 개인키를 가지고 있지 않기 때문에 밥의 개인키로 잠글 수 없어 위조 확인 가능 → 무결성 확인 가능, 단 기밀성 x</p>
<ol>
<li>암호화 → 수신자의 공개키로 잠근다. (수신자만 열어볼 수 있다.)</li>
<li>데이터 신뢰성 → 본인의 개인키로 잠근다. (전자서명, 부인방지)</li>
</ol>
<br/>

<h4 id="대칭키">대칭키</h4>
<p>키 교환이 필요 없으면 대칭키를 쓰면 된다.</p>
<p>대표적인 대칭키</p>
<p>→ <strong>HS512</strong></p>
<p>발행주체와 검증주체가 다르면 대칭키x RSA를 써야 함</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java (람다와 스트림, 입출력(I/O), 토이프로젝트)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-java6</link>
            <guid>https://velog.io/@ju-ei8ht/wil-java6</guid>
            <pubDate>Wed, 01 Feb 2023 12:51:59 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/1/25 ~ 2023/1/30</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-18람다와-스트림">Day 18(람다와 스트림)</h3>
<p><a href="https://velog.io/@ju-ei8ht/lambda-stream">람다와 스트림</a></p>
<p>람다식이랑 스트림만 3개월 가르쳐도 부족하다는 말씀이었다. 근데 하루만에 이해하려니...ㅎ
그래서 람다랑 스트림은 좀 더 공부를 해야겠다고 느꼈다. 아직 익숙치 않기도 하고... 가끔 알고리즘 할 때 sorted나 map을 쓸 일은 있었지만 알고 썼다기보다 구글에서 찾아서 이렇게도 쓸 수 있네? 하고 일단 쓰자고 썼던 느낌이라 ㅎㅎ 배우니 새롭다.</p>
<hr>
<h3 id="day-19입출력io">Day 19(입출력(I/O))</h3>
<p><a href="https://velog.io/@ju-ei8ht/IO">입출력(I/O)</a></p>
<p>무지성 sout만 쓰다가 ㅋㅋ 이렇게 다양한 메소드가 있었을 줄이야... 또 공부할 게 늘었다...</p>
<hr>
<h3 id="day-20토이프로젝트--만화책대여시스템">Day 20(토이프로젝트 : 만화책대여시스템)</h3>
<p><a href="https://velog.io/@ju-ei8ht/toy-project1">ToyProject 1. 만화책 대여 시스템</a></p>
<p>ㅎㅎ... 나를 믿지 말자...
그리고 스프링의 소중함... 데이터베이스가 있어서 다행이야... JPA야 고맙다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ToyProject 1. 만화책 대여 시스템]]></title>
            <link>https://velog.io/@ju-ei8ht/toy-project1</link>
            <guid>https://velog.io/@ju-ei8ht/toy-project1</guid>
            <pubDate>Wed, 01 Feb 2023 12:47:20 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/1/19 ~ 2023/1/30</p>
<p><strong>개요</strong>
자바로 만화책 대여 시스템을 제작하는 프로젝트(커맨드 라인으로 입/출력)</p>
<hr>
<p>스프링 경험도 있고, 커맨드로만 입출력하는 거라 쉽겠다고 생각했음. 하지만 그건은 경기도 오산...</p>
<p>아무튼 쉽고 재밌겠다, 시간도 넉넉하겠다 싶어서 설 연휴 때 펑펑 놀고(ㅋㅋ...) 마지막 주말에 시작함 ㅋㅋㅋ 그래서 사실상 28,29,30로 3일동안 했는데......</p>
<p>네... 진짜...... 와... 시간을 돌리고 싶다...</p>
<p>쉽고 x 재밌고 XXX</p>
<p>오히려 스프링 없이, 데이터베이스 없이(!!!) 자바로만 구현하는 게 진짜... 오히려 제약이 됐다ㅜㅜ 진심 스프링하고 싶고 JPA는 고사하고 하다못해 데이터베이스 쿼리 쓰고 싶었음.....
특히 데이터베이스 대신 Array에 값을 입출력해야 하는 게 진짜 힘듦ㅜㅜ 생각없이 ArrayList로 썼다가 출력해야 할 때 id값을 어떻게 구하지;; -&gt; 다시 코드짜기의 무한 반복ㅋㅋㅋ 결국 HashMap으로 전부 바꾸고(그마저도 id값도 직접 넣어야 해서 .lastEntry() 쓰고 싶어서 TreeMap으로 다시 바꿈ㅋㅋㅋ)</p>
<p>그리고 트랜잭션 락이 없으니까ㅜㅜ Exception 에러를 throw해도 다른 코드 실행 됨 ㅎㅎ... 그래서 if else문 떡칠 ㅎㅎ......</p>
<p>Entity 계획도 스프링에서 하던 것처럼 만들었는데 생각해보니까 N:1 어떻게 함?ㅋㅋㅋㅋㅋ oneToMany고 나발이고 ㅋㅋㅋ 그래서 또 수정수정... FK 개념이 없으니까 그냥 HashMap의 key값을 참조해야 하는 객체의 key값으로 넣고... 아무튼...... 코드 드러워지고... 복잡해지고... 시간 촉박해서 정신 나가서 자잘한 실수 반복하고... 눈도 침침해져서(ㅋㅋㅋ) 파일도 못 찾고... 암튼 참... 기간이 넉넉한 데엔 이유가 있는 것을...</p>
<p>너무 날 과대평가했나 나도 모르게?...</p>
<p>처음엔 주어진 조건보다 더 디테일하게 만들어야지~ 하고 신났는데 막상 코드 짜다보니까 주어진 것도 다 할 수 있을까 싶었음 ㅋㅋ... 아니 왜 이런 시련을......</p>
<p>암튼 거두절미하고 본론으로 들어가면</p>
<h3 id="tree">Tree</h3>
<pre><code>.
├── Main.java
├── app
│   └── ComicsRentalApp.java
├── comm
│   ├── Menu.java
│   ├── Table.java
│   ├── Validate.java
│   └── exception
│       ├── GlobalExceptionHandler.java
│       ├── NotFoundException.java
│       ├── UnworkableException.java
│       └── WrongNumberException.java
├── controller
│   ├── ComicsController.java
│   ├── ComicsControllerImpl.java
│   ├── Controller.java
│   ├── CustomerController.java
│   ├── CustomerControllerImpl.java
│   ├── RentalController.java
│   └── RentalControllerImpl.java
├── domain
│   ├── Comics.java
│   ├── Customer.java
│   ├── Entity.java
│   ├── Rental.java
│   └── RentalItems.java
├── dto
│   ├── ComicsDTO.java
│   ├── CustomerDTO.java
│   ├── DTO.java
│   └── RentalDTO.java
├── factory
│   ├── Factory.java
│   └── Switch.java
└── repository
    ├── ComicsRepository.java
    ├── ComicsRepositoryImpl.java
    ├── CustomerRepository.java
    ├── CustomerRepositoryImpl.java
    ├── DB.java
    ├── RentalRepository.java
    ├── RentalRepositoryImpl.java
    └── Repository.java</code></pre><hr>
<h3 id="회고">회고</h3>
<p>domain은 안 만들고 dto만 써도 됐지만... 그냥 entity를 따로 두고 싶었다... dto로 사용자 값만 받아서 entity 타입으로 repository에 저장하는 걸로 만들었고
factory에서 controller나 repository를 enum으로 구분해서 각각 맞는 객체를 보내줬고... 사실 repository나 controller도 만들고 보니 중복되는 코드가 많았어서 ㅎㅎ 상위 클래스로 만들고 하위클래스를 개별로 만들어서 넘기거나... 통합하는 방향으로 짰으면 좋았을텐데 맘이 급해서 거기까지 머리가 따라가지 못했다 ㅋㅋ...
Rental도 Comics, Customer과 oneToMany, ManyToOne 관계를 생각해서 RentalItems 객체를 따로 만들어서 각각 객체를 멤버변수로 뒀는데... 생각해보니까 어짜피 JPA 쓰는 것도 아니고 ㅋㅋ 참조가 안 될텐데 싶었는데 이미 만든 거 또 엎기 힘들어서 그냥 Rental의 id를 CustomerId 값으로 하고(어짜피 시스템 기획상 1명의 고객은 1건의 대여만 발생함) RentalItems의 id는 ComicsId로 했다.</p>
<p>암튼 여차저차 구현은 했는데 아쉬움이 많이 남고 ㅎㅎ...
아니 무엇보다 view 구현하기가 힘들었다ㅜㅜ 메뉴 넘어가고 메소드 호출하고하는 게 헷갈려서...
view를 하나에서 만든 게 아니라 나눠서 구현해가지고...</p>
<p>+강사님이 만드신 코드를 보여주셨는데 와... 진짜...ㅋㅋㅋ
나 뭐했나 싶음ㅋㅋㅋ 람다... 스트림... 진짜 공부해야겠다는 생각을 했다...
다른 걸 떠나서 view에서 구현하는 메뉴도 중복되는 건 변수로 선언해서 재사용하니까 코드가 진짜 깔끔해지는구나 나는 아직 한참 멀었구나 싶었다ㅜㅜ 코드의 재사용성을 높이고, 멤버변수도 spring의 autowired 생각하고 무지성하게 controller며 repository며 static 멤버변수로 썼는데 그렇게 되면 GC의 영향을 받지 않아서 프로그램 종료될 때까지 사용하지 않는 데이터가 쌓인다는 지적이ㅜㅜ 아 맞네;; 하고 바로 각 메소드에서 필요할 때마다 호출해서 쓰도록 급 수정......ㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[입출력(I/O)]]></title>
            <link>https://velog.io/@ju-ei8ht/IO</link>
            <guid>https://velog.io/@ju-ei8ht/IO</guid>
            <pubDate>Wed, 01 Feb 2023 12:21:51 GMT</pubDate>
            <description><![CDATA[<ul>
<li>I/O란 입력(Input)과 출력(Output)의 줄임말로 두 대상간의 데이터를 주고 받는 것을 의미한다.</li>
<li>Java는 스트림(Stream)의 개념을 사용하여 I/O 작업을 빠르게 만든다.</li>
<li>java.io 패키지에는 입력 및 출력 작업에 필요한 모든 클래스와 인터페이스가 포함되어 있다.<ul>
<li>파일, 네트워크, 스레드간 통신(Pipe), 버퍼링, 필터링, 파싱(Parsing), 테스트 데이터, 기본형 데이터, 객체</li>
</ul>
</li>
<li>java I/O API를 사용하여 Java에서 파일 처리를 수행할 수 있다.</li>
<li>Stream: 스트림은 데이터의 시퀀스이다. Java에서 스트임은 바이트로 구성된다.</li>
<li>Java에서는 3개의 스트림이 자동으로 생성되며 이 모든 스트림은 콘솔에 연결된다.<ul>
<li>System.out: 표준 출력 스트림</li>
<li>System.in: 표준 입력 스트림</li>
<li>System.err: 표준 오류 스트림</li>
</ul>
</li>
</ul>
<br/>

<h2 id="io-클래스-구성">I/O 클래스 구성</h2>
<ul>
<li>입력, 출력, 바이트 기반, 문자 기반 및 버퍼링, 구문 분석 등과 같이 구체적인 목적으로 구분된 Java IO 클래스의 표</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>Byte 기반</th>
<th></th>
<th>Character 기반</th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td></td>
<td>Input</td>
<td>Output</td>
<td>Input</td>
<td>Output</td>
</tr>
<tr>
<td>Basic</td>
<td>InputStream</td>
<td>OutputStream</td>
<td>Reader<br/>InputStreamReader</td>
<td>Writer</td>
</tr>
<tr>
<td>OutputStreamReader</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Arrays</td>
<td>ByteArrayInputStream</td>
<td>ByteArrayOutputStream</td>
<td>CharArrayReader</td>
<td>CharArrayWriter</td>
</tr>
<tr>
<td>Files</td>
<td>FileInputStream<br/>RandomAccessFile</td>
<td>FileOutputStream<br/>RandomAccessFile</td>
<td>FileReader</td>
<td>FileWriter</td>
</tr>
<tr>
<td>Pipes</td>
<td>PipedInputStream</td>
<td>PipedOutputStream</td>
<td>PipedReader</td>
<td>PipiedWriter</td>
</tr>
<tr>
<td>Buffering</td>
<td>BufferedInputStream</td>
<td>BufferedOutputStream</td>
<td>BufferedReader</td>
<td>BufferedWriter</td>
</tr>
<tr>
<td>Filtering</td>
<td>FilterInputStream</td>
<td>FilterOutputStream</td>
<td>FilterReader</td>
<td>FilterWriter</td>
</tr>
<tr>
<td>Parsing</td>
<td>PushbackInputStream</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>StreamTokenizer</td>
<td></td>
<td>PushbackReader</td>
<td></td>
<td></td>
</tr>
<tr>
<td>LineNumberReader</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Strings</td>
<td></td>
<td></td>
<td>StringReader</td>
<td>StringWriter</td>
</tr>
<tr>
<td>Data</td>
<td>DataInputStream</td>
<td>DataOutputStream</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Data-Formatted</td>
<td></td>
<td>PrintStream</td>
<td></td>
<td>PrintWriter</td>
</tr>
<tr>
<td>Objects</td>
<td>ObjectInputStream</td>
<td>ObjectOutputStream</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Utilities</td>
<td>SequenceInputStream</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<hr>
<h2 id="stream">Stream</h2>
<ul>
<li>데이터를 운반하는데 사용되는 연결통로(데이터가 흘러갈 수 있는 길을 만들어주는 역할)</li>
<li>연속적인 데이터의 흐름을 물에 비유해서 붙여진 이름</li>
<li>입력스트림(Input Stream)과 출력스트림(Output Stream) 2개의 스트림으로 나뉜다.</li>
<li>자바에서 사용되는 모든 I/O 클래스들은 스트림 방식으로 데이터를 Read/Write 한다.</li>
<li>스트림은 데이터가 있는 소스(Source)와 데이터가 전달되어지는 목적지(Destination)로 데이터를 연속적으로 흘려보낸다.</li>
<li>Input/Output source를 갖는 순서화된 일련의 자료를 나타내는 추상적 입출력 모델이다.</li>
<li>대부분의 자바 I/O 관련 메소드는 IOException을 throw하도록 되어 있다.</li>
<li>기반 스트림: 대상에 직접 자료를 Read/Write하는 기능의 스트림</li>
<li>보조 스트림: 직접 Read/Write하는 기능은 없으며, 추가적인 기능을 제공해 주는 스트림으로 기반  스트림이나 다른 보조 스트림을 생성자의 매개변수로 포함한다.</li>
<li>파일 경로: 윈도우는 , Mac은 / 사용</li>
<li>예) File.separator를 사용하면 OS를 신경쓰지 않아도 된다.<ul>
<li>윈도우 - C:\Users\jongkwonkim\Documents\streamtest\testme.txt</li>
<li>Mac(리눅스) - /Users/jongkwonkim/Documents/streamtest/testme.txt</li>
<li>File.separator 사용 :
String separator = File.separator;
File targetFile = new File(separator + &quot;Users&quot; + separator + &quot;jongkwonkim&quot; + separator + &quot;Documents&quot; + separator + “streamtest&quot;);</li>
</ul>
</li>
</ul>
<br/>

<ul>
<li>Stream은 byte 단위 처리와 Character 단위의 Stream으로 나뉜다.<ul>
<li>Byte Stream은 1 Byte 바이너리 데이터(파일, 이미지로딩 등) 처리를 위해 사용</li>
<li>Character Stream은 주로 2 Byte 문자데이터(Char 단위) 처리를 위해 사용</li>
<li>InputStream 또는 Reader는 데이터 소스에 연결되며, OutputStream 또는 Writer는 데이터 destination에 연결된다.</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>Stream</th>
<th>Byte Stream</th>
<th align="center">Character Stream</th>
</tr>
</thead>
<tbody><tr>
<td>Source streams (입력 데이터의 시작)</td>
<td>InputStream</td>
<td align="center">Reader</td>
</tr>
<tr>
<td>Sink streams (데이터가 처리되어 출력되는 끝)</td>
<td>OutputStream</td>
<td align="center">Writer</td>
</tr>
</tbody></table>
<br/>

<h3 id="byte-stream">Byte Stream</h3>
<ul>
<li>바이트 단위로 데이터를 주고 받으며 입출력 대상에 맞는 Stream 클래스가 지원된다.</li>
<li>InputStream 또는 OutputStream의 하위클래스들이다.</li>
</ul>
<br/>

<table>
<thead>
<tr>
<th>입력스트림</th>
<th>출력스트림</th>
<th>대상</th>
</tr>
</thead>
<tbody><tr>
<td>FileInputStream</td>
<td>FileOutputStream</td>
<td>파일</td>
</tr>
<tr>
<td>ByteArrayInputStream</td>
<td>ByteArrayOutputStream</td>
<td>메모리</td>
</tr>
<tr>
<td>PipedInputStream</td>
<td>PipedOutputStream</td>
<td>프로세스</td>
</tr>
<tr>
<td>AudioInputStream</td>
<td>AudioOutputStream</td>
<td>오디오장치</td>
</tr>
</tbody></table>
<br/>

<table>
<thead>
<tr>
<th>InputStream</th>
<th>OutputStream</th>
</tr>
</thead>
<tbody><tr>
<td>abstract int read()</td>
<td>abstract void write(int b)</td>
</tr>
<tr>
<td>int read(byte[] b)</td>
<td>void write(byte[] b)</td>
</tr>
<tr>
<td>int read(byte[] b, int off, int len)</td>
<td>void write(byte[] b, int off, int len)</td>
</tr>
</tbody></table>
<br/>

<table>
<thead>
<tr>
<th>InputStream</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>int read()</td>
<td>입력 스트림에서 데이터의 다음 바이트를 read한다. 파일 끝일 경우 -1을 반환한다.</td>
</tr>
<tr>
<td>int read(byte[] b)</td>
<td>byte[] b만큼의 데이터를 읽어서 b에 저장하고 읽은 바이트 수를 반환한다.</td>
</tr>
<tr>
<td>int available()</td>
<td>현재 입력스트림에서 읽을 수 있는 바이트 수의 추정치를 반환한다.</td>
</tr>
<tr>
<td>mark()</td>
<td>데이터가 읽혀진 InputStream의 위치를 표시한다.</td>
</tr>
<tr>
<td>void close()</td>
<td>현재 입력 스트림을 close 하는데 사용된다.</td>
</tr>
</tbody></table>
<br/>

<table>
<thead>
<tr>
<th>OutputStream</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>void write(int b)</td>
<td>현재 출력 스트림에 바이트를 write하는데 사용된다.</td>
</tr>
<tr>
<td>void write(byte[] b)</td>
<td>현재 출력 스트림에 바이트 배열을 write하는데 사용된다.</td>
</tr>
<tr>
<td>void flush()</td>
<td>현재 출력 스트림을 flush한다.</td>
</tr>
<tr>
<td>void close()</td>
<td>현재 출력 스트림을 닫는데 사용된다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>InputStream과 OutputStream은 추상클래스이며, 하위 클래스들이 각 특성에 맞게 정의한다.<ul>
<li>Byte stream read()의 반환타입이 int인 이유는 8 bit I/O를 수행하고 반환값이 0~255(영문 대,소문자 등의 ASCII 값)와 -1이기 때문이다. 즉 byte형으로 반환할 경우 파일의 끝을 나타내는 기호로 사용할 마땅한 값이 없기 때문이다.</li>
<li>리턴타입이 int형이어서 1byte(8bit)인 바이너리코드를 int형으로 변환하여 숫자값을 리턴</li>
</ul>
</li>
<li>Stream의 사용이 종료되면 반드시 Stream의 close() 메서드를 호출해서 리소스 누수를 막아야 한다.</li>
</ul>
<br/>

<ul>
<li>InputStream의 read()와 OutputStream의 write() 메서드를 이용한 파일 Copy</li>
</ul>
<pre><code class="language-java">public class FileCopyByteEx1 {
    public static void main(String[] args) throws IOException {
        FileInputStream in = null; FileOutputStream out = null; 
        try {
            in = new FileInputStream( &quot;/Users/jongkwonkim/Documents/streamtest/testme.txt&quot;);
            out = new FileOutputStream( &quot;/Users/jongkwonkim/Documents/streamtest/copy_testme.txt&quot;);
            int c;
            while ((c = in.read()) != -1) {
                out.write(c); 
            }
        } finally {
            if (in != null) {
                in.close(); 
            }
            if (out != null) { 
                out.close();
            } 
        }
    } 
}</code></pre>
<br/>

<h3 id="character-stream">Character Stream</h3>
<ul>
<li>자바에서 2byte 단위 Unicode를 처리하기 때문에 바이트스트림으로 2Byte인 문자를 처리하는데 어려움이 있어 보완하기 위해 제공(1.1부터)되며, 활용방법은 거의 비슷하다. (한글의 경우 Unicode 2 byte로 처리되므로 한글 처리를 하려면 Character stream을 사용해야 한다.)</li>
<li>InputStream &gt; Reader, OutputStream &gt; Writer로 사용</li>
</ul>
<table>
<thead>
<tr>
<th>바이트기반 스트림</th>
<th>문자기반 스트림</th>
<th align="center">대상</th>
</tr>
</thead>
<tbody><tr>
<td>FileInputStream<br/>FileOutputStream</td>
<td>FileReader<br/>FileWriter</td>
<td align="center">파일</td>
</tr>
<tr>
<td>ByteArrayInputStream<br/>ByteArrayOutputStream</td>
<td>CharArrayReader<br/>CharArrayWriter</td>
<td align="center">메모리</td>
</tr>
<tr>
<td>PipedInputStream<br/>PipedOutputStream</td>
<td>PipedReader<br/>PipedWriter</td>
<td align="center">프로세스</td>
</tr>
<tr>
<td>StringBufferedInputStream<br/>StringBufferedOutputStream</td>
<td>StringReader<br/>StringWriter</td>
<td align="center">메모리</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>Abstract method가 Input/OutputStream과 다르다.</li>
</ul>
<table>
<thead>
<tr>
<th>InputStream</th>
<th>Reader</th>
</tr>
</thead>
<tbody><tr>
<td>abstract int read()<br/>int read(byte[] b)<br/>int read(byte[] b, int off, int len)</td>
<td>int read()<br/>int read(char[] char)<br/>abstract int read(char[] cbuf, int off, int len)</td>
</tr>
</tbody></table>
<br/>

<table>
<thead>
<tr>
<th>OutputStream</th>
<th>Writer</th>
</tr>
</thead>
<tbody><tr>
<td>abstract void write(int b)<br/>void write(byte[] b)<br/>void write(byte[] b, int off, int len)</td>
<td>void write(int c)<br/>void write(char[] cbuf)<br/>abstract void write(char[] cbuf, int off, int len)<br/>void write(String str)<br/>void write(String str, int off, int len)</td>
</tr>
</tbody></table>
<br/>

<h3 id="processingchaining-stream">Processing(chaining) Stream</h3>
<ul>
<li>Precess stream은 IO에서 Chaining(연쇄화)를 통해 데이터 타입별로 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있다. (Decorator Pattern)</li>
<li>각 class는 정해진 일을 하고 연결된 class에 forward한다.</li>
<li>예) text.txt 파일의 읽기를 향상시키기 위해 FileInputStream에 BufferedInputStream을 chaining한다.</li>
</ul>
<br/>

<p>예제</p>
<pre><code class="language-java">//기반 스트림을 생성
FileInputStream fin = new FileInputStream(&quot;test.txt&quot;);
//기반 스트림을 이용한 보조 스트림 생성
BufferedInputStream bin = new BufferedInputStream(fin);
...
//보조 스트림인 BufferedInputStream으로 데이터를 읽는다
bin.read();</code></pre>
<br/>

<table>
<thead>
<tr>
<th>입력</th>
<th>출력</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>FileInputStream</td>
<td>FileOutputStream</td>
<td>필터를 이용한 입출력 처리</td>
</tr>
<tr>
<td>BufferedInputStream</td>
<td>BufferedOutputStream</td>
<td>버퍼를 이용한 입출력 성능향상</td>
</tr>
<tr>
<td>DataInputStream</td>
<td>DataOutputStream</td>
<td>int, float과 같은 기본형 단위(primitive type)로 데이터를 처리하는 기능</td>
</tr>
<tr>
<td>SequenceInputStream</td>
<td>SequenceOutputStream</td>
<td>두 개의 스트림을 하나로 연결</td>
</tr>
<tr>
<td>ObjectInputStream</td>
<td>ObjectOutputStream</td>
<td>데이터를 객체단위로 read/write하는데 사용한다.</td>
</tr>
</tbody></table>
<br/>

<h3 id="byte-stream-1">Byte Stream</h3>
<ul>
<li>InputStream과 OutputStream은 모든 Byte Stream의 추상 슈퍼클래스이다. InputStream 메서드는 아래와 같다.</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>int available()</td>
<td>스트림으로부터 read 할 수 있는 데이터의 크기를 반환</td>
</tr>
<tr>
<td>void close()</td>
<td>스트림을 close로써 사용하고 있던 자원 반환</td>
</tr>
<tr>
<td>void mark(int readlimit)</td>
<td>현재위치를 표시한다. 필요시에 reset()에 의해서 표시해놓은 위치로 되돌아갈 수 있다. readlimit은 되돌아갈 수 있는 byte의 수이다.</td>
</tr>
<tr>
<td>boolean markSupported()</td>
<td>mark()와 reset()을 지원하는지를 알려준다. mark()와 reset()을 사용하기 전에 markSupported()를 호출해서 지원여부를 확인해야 한다.</td>
</tr>
<tr>
<td>abstract int read()</td>
<td>1byte를 read한다(0~255 사이 값). read할 데이터가 없으면 -1을 반환한다.<br/>abstract 메서드로 InputStream을 상속하는 클래스들의 특성에 맞게 구현한다.</td>
</tr>
<tr>
<td>int read(byte[] b)</td>
<td>byte 배열 b의 사이즈만큼 read한 후 데이터의 수를 반환하며 반환 값은 배열의 크기보다 작거나 같다.</td>
</tr>
<tr>
<td>int read(byte[] b, int off, int len)</td>
<td>최대 len개의 byte를 read해서, 배열 b의 off부터 저장하며, read할 수 있는 데이터가 len보다 적을 수 있다.</td>
</tr>
<tr>
<td>void reset()</td>
<td>스트림에서 위치를 마지막으로 mark() 메서드가 호출된 위치로 되돌아간다.</td>
</tr>
<tr>
<td>long skip(long n)</td>
<td>스트림에서 길이(n) 만큼 skip한다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>OutputStream 메서드</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>void close()</td>
<td>사용하던 자원을 반환한다.</td>
</tr>
<tr>
<td>void flush()</td>
<td>스트림의 버퍼에 있는 모든 것을 출력소스에 write한다.</td>
</tr>
<tr>
<td>abstract void write(int b)</td>
<td>int b 값을 출력소스에 write한다.</td>
</tr>
<tr>
<td>void write(byte[] b)</td>
<td>byte 배열 b를 출력소스에 write한다.</td>
</tr>
<tr>
<td>void write(byte[] b, int off, int len)</td>
<td>byte 배열 b에 저장된 내용 중에서 off 위치부터 len 만큼 read해서 출력소스에 write한다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>InputStream과 OutputStream 메소드 사용시 주의사항<ul>
<li>mark()와 reset()을 사용하여 이미 읽은 데이터를 되돌려서 다시 읽을 수 있으나 이 기능을 지원하는 클래스인지 markSupported()를 통해서 알 수 있다.</li>
<li>flush()는 버퍼를 비우는 OutputStream의 기능이며, Stream close 전에 비우도록 한다.</li>
<li>Stream을 사용하고 나서는 close()를 호출해서 반드시 닫아주어야 한다. (JVM이 자동적으로 닫아주기는 한다.)</li>
</ul>
</li>
</ul>
<br/>

<h3 id="bytearrayinputstream-bytearrayoutputstream">ByteArrayInputStream, ByteArrayOutputStream</h3>
<ul>
<li>메모리에 바이트배열을 입/출력할 때 사용하는 스트림이다. (GC에 자원반납이 되므로 close()는 무의미하다)</li>
<li>주로 다른 곳에 입출력하기 전에 임시로 담아서 변환 등의 작업시 사용한다.</li>
</ul>
<pre><code class="language-java">byte[] buf = {35,36,37,38};
//새로운 바이트 배열 입력 스트림 생성
ByteArrayInputStream byt = new ByteArrayInputStream(buf);
int k = 0;
while((k = byt.read()) != -1){
    //byte 값을 char 문자로 변환
    char ch = (char) k;
    System.out.println(&quot;문자의 ASCII 값: &quot; + k + &quot;; 특수문자: &quot; + ch);
}

[결과]
문자의 ASCII 값: 35; 특수문자: #
문자의 ASCII 값: 36; 특수문자: $
문자의 ASCII 값: 37; 특수문자: %
문자의 ASCII 값: 38; 특수문자: &amp;</code></pre>
<pre><code class="language-java">FileOutputStream fout =
    new FileOutputStream(&quot;/Users/jongkwonkim/Documents/streamtest/fout.txt&quot;);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
bout.write(68);
bout.writeTo(fout);

bout.flush();
bout.close();

[결과]
/Users/jongkwonkim/Documents/streamtest/fout.txt 파일이 생성됨
파일을 열어보면 char타입 문자 &#39;D&#39;가 저장되어 있다</code></pre>
<br/>

<h3 id="fileinputstream-fileoutputstream">FileInputStream, FileOutputStream</h3>
<ul>
<li>파일 입출력을 위한 스트림이다.</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>FileInputStream(String name)</td>
<td>지정 파일이름의 실제 파일과 연결된 FileInputStream을 생성한다.</td>
</tr>
<tr>
<td>FileInputStream(File file)</td>
<td>지정한 File 인스턴스로 FileInputStream을 생성한다.</td>
</tr>
<tr>
<td>FileOutputStream(String name)</td>
<td>지정 파일이름의 실제 파일과 연결된 FileOutputStream을 생성한다.</td>
</tr>
<tr>
<td>FileOutputStream(String name, boolean append)</td>
<td>지정 파일이름의 실제 파일과 연결된 FileOutputStream을 생성함에 있다.<br/>두번째 인자인 append를 true로 하면 파일의 마지막 내용에 덧붙이며, false면 덮어쓰게 된다.</td>
</tr>
<tr>
<td>FileOutputStream(File file)</td>
<td>지정한 File 인스턴스로 FileOutputStream을 생성한다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>파일 입출력을 위한 스트림이다.</li>
</ul>
<pre><code class="language-java">class FileViewer{
    public static void main(String[] args) throws IOException{
        FileInputStream fis = new FileInputStream(args[0]);
        int data = 0;
        while((data=fis.read())!=-1){ 
            //read return type이 int인 것은 
            //1byte씩 파일로부터 데이터를 읽어 들이지만 더 이상 값이 없음을 알리는 -1을 제외하고는
            //0~255범위의 정수값 int가 가장 효율적이고 빠르다
            char c = (char)data;
            System.out.println(c);
        }
    }
}</code></pre>
<br/>

<ul>
<li>텍스트 파일을 처리하는 경우에는 문자기반 스트림인 FileReader/Writer를 사용하는 것이 좋다.</li>
<li>예제(파일 스트림을 열어서 read하여 OutputStream 파일에 write하여 내용을 copy한다.)</li>
</ul>
<pre><code class="language-java">try{
    FileInputStream fin =
    new FileInputStream(&quot;/testme.txt&quot;);
    FileOutputStream fos =
    new FileOutputStream(&quot;/copytestme.txt&quot;);
    int data = 0;
    while((data=fin.read())!=-1){
        fos.write(data):
    }
    fin.close();
    fos.close();
}catch(Exception e){
    e.printStackTrace();
}</code></pre>
<br/>

<h3 id="filterinputstream-filteroutputstream">FilterInputStream, FilterOutputStream</h3>
<ul>
<li>모든 바이트기반 보조스트림(Chaining Stream)의 최상위 클래스</li>
<li>Chaining Stream은 자체 입출력 기능이 없기 때문에 기반 스트림이 필요하다.</li>
<li>상속을 통해 FilterInputStream, FilterOutputStream의 read(), write()를 기능에 맞게 오버라이딩해야 한다.</li>
<li>상속관계<ul>
<li>java.io.FilterInputStream<ul>
<li>java.io.BufferedInputStream</li>
<li>java.io.DataInputStream (implements java.io.DataInput)</li>
<li>java.io.LineNumberInputStream</li>
<li>java.io.PushbackInputStream</li>
</ul>
</li>
<li>java.io.FilterOutputStream<ul>
<li>java.io.BufferedOutputStream</li>
<li>java.io.DataOutputStream (implements java.io.DataOutput)</li>
<li>java.io.PrintStream (implements java.lang.Appendable, java.io.Closeable)</li>
</ul>
</li>
</ul>
</li>
</ul>
<br/>

<h3 id="bufferedinputstream-bufferedoutputstream">BufferedInputStream, BufferedOutputStream</h3>
<ul>
<li>스트림의 입출력 효율을 높이기 위해 메모리 버퍼를 사용하는 chaining stream 클래스이다.</li>
<li>1byte씩 입출력하는 것보다 바이트배열(버퍼)를 이요해서 한번에 입출력하는 것이 빠르기 때문이다.</li>
<li>버퍼를 사용하지 않게되면, 입/출력시 OS와 I/O call을 직접적으로 하기 때문에 비용이 많이 드는 요청이다. 버퍼를 사용하게 되면 버퍼 메모리에 쌓아두고 읽어들이기 때문에 OS I/O를 콜하는 횟수가 줄어서 Overhead를 줄일 수 있다.</li>
<li>버퍼의 크기는 1024, 2048, 4096이 보통이다.</li>
<li>read() 호출 → 입력소스로부터 버퍼 크기만큼 읽어서 자신의 내부버퍼에 저장 → 버퍼에 저장된 데이터를 읽어서 처리 ⇒ 내부의 버퍼로부터 읽기 때문에 작업 효율이 높아진다.</li>
<li>write() 메소드의 경우 소스로부터 데이터를 읽어들여 버퍼가 차면, 버퍼의 모든 내용을 출렷소스에 write한다 ⇒ 다시 버퍼를 비우고 출력을 저장할 준비를 한다.</li>
<li>보조스트림을 close하면 기반스트림도 close된다.</li>
</ul>
<br/>

<ul>
<li>메서드와 생성자</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명/생성자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>BufferedInputStream(InputStream in, int size)</td>
<td>InputStream 인스턴스를 입력소스로 지정된 크기(byte 단위)의 버퍼를 갖는 BufferedInputStream 인스턴스를 생성한다.</td>
</tr>
<tr>
<td>BufferedInputStream(InputStream in)</td>
<td>InputStream 인스턴스를 입력소스로 버퍼의 크기를 지정해주지 않으면 Default buffer로 설정된 8,192byte 크기의 버퍼를 가지게 된다.</td>
</tr>
<tr>
<td>BufferedOutputStream(OutputStream out, int size)</td>
<td>OutputStream 인스턴스를 출력소스로 지정된 크기(byte 단위)의 버퍼를 갖는 BufferedOutputStream 인스턴스를 생성한다.</td>
</tr>
<tr>
<td>BufferedOutputStream(OutputStream out)</td>
<td>OutputStream 인스턴스를 출력소스로 버퍼의 크기를 지정해주지 않으면 Default buffer로 설정된 8,192byte 크기의 버퍼를 가지게 된다.</td>
</tr>
<tr>
<td>flush()</td>
<td>버퍼의 모든 내용을 출렷소스에 출력한 후 버퍼를 비운다.</td>
</tr>
<tr>
<td>close()</td>
<td>flush()를 호출해서 버퍼의 모든 내용을 출력소스에 출력하고, BufferedOutputStream 인스턴스가 사용하던 모든 자원을 반환한다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class BufferedInputStream extends FilterInputStream{
    private static int DEFAULT_BUFFER_SIZE = 8192;
}

public class BufferedOutputStream extends FilterOutputStream{
    public BufferedOutputStream(OutputStream out){
        this(out, 8192);
    }
}</code></pre>
<br/>

<ul>
<li>버퍼가 남은채로 종료될 수 있기 때문에 flush()나 close()를 실행해서 버퍼의 마지막 내용까지 write 되도록 한다.</li>
<li>버퍼가 가득차면 버퍼의 모든 데이터가 OutputStream으로 flush(push) 된다.</li>
<li>일시적으로 버퍼에 기록되는 데이터는 Disk의 지속적인 write를 최소화하여 프로그램 성능을 향상시킨다.</li>
</ul>
<pre><code class="language-java">try{
    FileOutputStream fos =
    new FileOutputStream(&quot;/bufFile.txt&quot;);
    BufferedOutputStream bos = new BufferedOutputStream(fos);
    String s = &quot;Welcome to stream!!!&quot;;
    byte[] b = s.getBytes();
    bos.write(b);
    bos.flush();
    bos.close();
    fos.close();
    System.out.println(&quot;Success!&quot;);
}catch(IOException e){
    e.printStackTrace();
}

[결과]
Success!</code></pre>
<br/>

<h3 id="datainputstream-dataoutputstream">DataInputStream, DataOutputStream</h3>
<ul>
<li>데이터를 read/write할 때 byte단위가 아닌, 8가지 Primitive type의 단위로 read/write 할 수 있는 장점이 있다.</li>
<li>DataOutputStream이 출력하는 형식은 각 기본 자료형 값을 16진수로 표현하여 저장한다.(예: int값을 출력하면, 4byte의 16진수로 출력)</li>
<li>각 자료형의 크기가 다르므로 출력할 때와 입력할 때 순서에 주의한다.</li>
</ul>
<br/>

<ul>
<li>DataInputStream</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명/생성자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>DataInputStream(read in)</td>
<td>지정한 InputStream 인스턴스를 기반스크림으로 하는 DataInputStream 인스턴스를 생성한다.</td>
</tr>
<tr>
<td>boolean readBoolean()<br/>byte readByte()<br/>char readChar()<br/>short readShort()<br/>int readInt()<br/>long readLong()<br/>float readFloat()<br/>double readDouble()</td>
<td>각 자료형에 맞는 값을 read한다. 더 이상 read할 값이 없으면 EOFException을 발생시킨다.</td>
</tr>
<tr>
<td>String readUTF()</td>
<td>UTF-8형식으로 write된 문자열을 read하는 데 사용된다.</td>
</tr>
<tr>
<td>int skipBytes(int n)</td>
<td>현재 read하고 있는 위치에서 지정된 n 만큼 skip한다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>DataOutputStream</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명/생성자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>DataOutputStream(OutputStream out)</td>
<td>지정한 OutputStream 인스턴스를 기반스트림으로 하는 DataOutputStream 인스턴스를 생성한다.</td>
</tr>
<tr>
<td>void writeBoolean(boolean b)<br/>void writeByte(int b)<br/>void writeChar(int c)<br/>void writeShort(int s)<br/>void writeInt(int i)<br/>void writeLong(long l)<br/>void writeFloat(float f)<br/>void writeDouble(double d)</td>
<td>각 자료형에 맞는 값을 write한다.</td>
</tr>
<tr>
<td>void writeUTF(String s)</td>
<td>UTF 형식으로 문자를 출력한다.</td>
</tr>
<tr>
<td>void writeChars(String s)</td>
<td>지정한 문자열을 write한다. writeChar(char c) 메서드를 여러번 호출한 결과와 같다.</td>
</tr>
<tr>
<td>int size()</td>
<td>지금까지 DataOutputStream에 write된 byte 수를 알려준다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">public class DataOutputStreamEx2 {
    public static void main(String[] args) throws IOException {

        String filename=&quot;data.txt&quot;;
        //노드 연결(노드 스트림 연결)
        FileOutputStream fos = new FileOutputStream(filename); 
        //필터 스트림과 노드 스트림을 연결
        DataOutputStream dos = new DataOutputStream(fos);

        byte b=65;
        byte[] buf={66,67,68}; 
        short s=100;
        char ch=&#39;가&#39;;
        boolean bool=true; 
        double d=123.45; 
        String str=&quot;홍길동&quot;;

        dos.write(b); 
        dos.write(buf,0,buf.length); 
        dos.writeShort(s); 
        dos.writeChar(ch); 
        dos.writeBoolean(bool); 
        dos.writeDouble(d); 
        dos.writeUTF(str);

        System.out.println(dos.size()+&quot;bytes 기록&quot;); 
        // 스트림에 남아있는 데이터가 있으면 flush 
        dos.flush();
        dos.close();
    }
}</code></pre>
<p>👇</p>
<pre><code class="language-java">public class DataInputStreamEx2 {
    public static void main(String[] args) throws IOException {

        DataInputStream dis = new DataInputStream( 
                        new FileInputStream(&quot;data.txt&quot;));
        System.out.println(dis.available()+&quot;바이트 읽어들이기&quot;); 
        // 파일에 쓴 순서대로 읽어야 함
        byte a=dis.readByte();
        System.out.println(a);

        byte[]ba=new byte[3]; 
        int n=dis.read(ba); 
        for(byte b:ba){
            System.out.println(b); 
        }
        short s=dis.readShort(); 
        System.out.println(s); 
        System.out.println(dis.readChar()); 
        System.out.println(dis.readBoolean()); 
        System.out.println(dis.readDouble()); 
        System.out.println(dis.readUTF());

        dis.close();
    }
}

[결과]
28바이트 읽어들이기 65
66
67
68
100
가
true
123.45
홍길동</code></pre>
<br/>

<h3 id="sequenceinputstream">SequenceInputStream</h3>
<ul>
<li>여러 입력스트림을 연속적으로 연결해서 하나의 스트림처럼 다룰 수 있게 해준다.</li>
<li>큰 파일을 여러 개의 작은 파일로 나누었다가 하나의 파일로 합치는 것과 같은 작업에 사용하면 좋다.</li>
<li>FilterInputStream 클래스가 아닌 InputStream을 상속받아 구현했다.</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명/생성자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>SequenceInputStream(Enumeration e)</td>
<td>Enumeration에 저장된 순서대로 입력스트림을 하나의 스트림으로 연결한다.</td>
</tr>
<tr>
<td>SequenceInputStream(InputStream s1, InputStream s2)</td>
<td>두 개의 입력스트림을 하나로 연결한다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">FileInputStream input1=
    new FileInputStream(&quot;/testme.txt&quot;);
/*
* testme.txt 파일 내용
* Welcome!
* test file
* Goodbye~
*/
FileInputStream input2=
    new FileInputStream(&quot;/bufFile.txt&quot;);
/*
* bufFile.txt 파일 내용
* Welcome to stream!!!
*/
SequenceInputStream inst=new SequenceInputStream(input1, input2);
int j;
while((j=inst.read())!=-1){
    System.out.print((char)j);
}
inst.close();
input1.close();
input2.close();

[결과]
Welcome!
test file
Goodbye~Welcome to stream!!!</code></pre>
<br/>

<h3 id="printstream">PrintStream</h3>
<ul>
<li>데이터를 다양한 형식의 문자로 출력하는 기능을 제공하는 Chaining Stream이다.</li>
<li>문자기반 스트림의 역할을 수행한다. JDK 1.1부터 향상된 문자기반 스트림인 PrintWriter가 추가됨</li>
<li>PrintStream보다 PrintWriter(다양한 언어의 문자를 처리하는데 적합)를 사용할 것을 권장한다.</li>
<li>printf()는 JDK 1.5부터 추가되었으며, c언어와 같이 Format 출력을 지원한다.</li>
</ul>
<table>
<thead>
<tr>
<th>format</th>
<th>설명</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td>%d</td>
<td>10진수</td>
<td>65</td>
</tr>
<tr>
<td>%o</td>
<td>8진수(octal integer)</td>
<td>101</td>
</tr>
<tr>
<td>%x</td>
<td>16진수(hexadecimal integer)</td>
<td>41</td>
</tr>
<tr>
<td>%c</td>
<td>문자</td>
<td>A</td>
</tr>
<tr>
<td>%s</td>
<td>문자열</td>
<td>65</td>
</tr>
<tr>
<td>%5d</td>
<td>5자리 숫자, 빈자리는 공백으로 채운다</td>
<td>65</td>
</tr>
<tr>
<td>%05d</td>
<td>5자리 숫자, 빈자리는 0으로 채운다</td>
<td>65</td>
</tr>
<tr>
<td>\t</td>
<td>탭(tab)</td>
<td></td>
</tr>
<tr>
<td>\n</td>
<td>줄바꿈 문자(new line)</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h2 id="reader-writer">Reader, Writer</h2>
<ul>
<li>문자기반 스트림 추상클래스이며, byte배열 대신 char배열을 사용한다.</li>
<li>메소드 형식은 Byte Stream과 다르지 않으며,  여러 종류의 인코딩을 지원해준다.</li>
<li>문자기반 입출력 스트림의 최상위 클래스다.</li>
</ul>
<br/>

<ul>
<li>Reader 메서드</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>abstract void close()</td>
<td>입력스트림을 close하면서 사용하던 자원을 반환한다.</td>
</tr>
<tr>
<td>void mark(int readlimit)</td>
<td>현재 위치를 표시해놓는다. 나중에 reset()에 의해서 표시해 놓은 위치로 다시 되돌아갈 수 있다.</td>
</tr>
<tr>
<td>boolean markSupported()</td>
<td>mark()와 reset() 지원여부를 확인해준다.</td>
</tr>
<tr>
<td>int read()</td>
<td>입력소스로부터 하나의 문자를 read한다. char의 범위인 0~65535범위의 정수를 반환하며, 입력스트림의 마지막 데이터에 도달하면 -1을 반환한다.</td>
</tr>
<tr>
<td>int read(char[] c)</td>
<td>입력소스로부터 매개변수로 주어진 배열 c의 크기만큼 읽어서 배열 c에 저장한다. read해 온 데이터의 개수 또는 -1을 반환한다.</td>
</tr>
<tr>
<td>boolean ready()</td>
<td>입력소스로부터 데이터를 읽을 준비가 되어있는지 확인한다.</td>
</tr>
<tr>
<td>void reset()</td>
<td>입력소스에서의 위치를 마지막으로 mark()가 호출되었던 위치로 되돌아간다.</td>
</tr>
<tr>
<td>void skip(long n)</td>
<td>현재 위치에서 전달된 n 만큼을 skip한다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>Writer 메서드</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>abstract void close()</td>
<td>출력스트림을 close하면서 사용하던 자원을 반환한다.</td>
</tr>
<tr>
<td>abstract void flush()</td>
<td>스트림의 버퍼에 있는 모든 내용을 출력소스에 write한다.</td>
</tr>
<tr>
<td>void write(int b)</td>
<td>전달된 값을 출력소스에 write한다.</td>
</tr>
<tr>
<td>void write(char[] c)</td>
<td>전달된 배열 c에 저장된 모든 내용을 출력소스에 write한다.</td>
</tr>
<tr>
<td>void write(String str)</td>
<td>전달된 문자열 str을 출력소스에 write한다.</td>
</tr>
<tr>
<td>void write(String str, int off, int len)</td>
<td>전달된 문자열 str을 off번째 문자부터 len개 만큼의 문자열을 출력소스에 write한다.</td>
</tr>
</tbody></table>
<br/>

<h3 id="filereader-filewriter">FileReader, FileWriter</h3>
<ul>
<li>문자기반의 파일 입출력. 텍스트 파일의 입출력에 사용한다.</li>
</ul>
<pre><code class="language-java">public class FileReaderEx01 {
    public static void main(String argsp[]){} }
        try {
            String fileName = &quot;/Users/jongkwonkim/Documents/streamtest/bufFile.txt&quot;; 
            FileReader fr = new FileReader(fileName);

            int data = 0; 
            while((data=fr.read()) != -1){
                System.out.print((char)data); 
            }
            System.out.println();
            fr.close(); 
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}</code></pre>
<br/>

<h3 id="pipedreader-pipedwriter">PipedReader, PipedWriter</h3>
<ul>
<li>프로세스(쓰레드)간의 통신(데이터를 주고 받음)에 사용한다.</li>
<li>입력과 출력 스트림을 하나의 스트림으로 연결해서 데이터를 주고 받는 게 특징이다.</li>
<li>두 쓰레드가 시작하기 전에 PipedReader와 PipedWriter를 연결해야 한다.</li>
</ul>
<br/>

<p>예제</p>
<pre><code class="language-java">try{
    final PipedReader read = new PipedReader();
    final PipedWriter write = new PipedWriter(read);

    Thread readerThread = new Thread(new Runnable(){
        public void run(){
            try{
                int data = read.read();
                while(date != -1){
                    System.out.println((char) data);
                    data = read.read();
                }
            }catch(Exception ex){}
        }
    });

    Thread writerThread = new Thread(new Runnable(){
        public void run(){
            try{
                write.write(&quot;writerThread write...!\n&quot;.toCharArray());
            }catch(Exception ex){}
        }
    });

    readerThread.start();
    writerThread.start();
}catch(Exception e){
    e.printStackTrace();
}

[결과]
writerThread write...!</code></pre>
<br/>

<h3 id="stringreader와-stringwriter">StringReader와 StringWriter</h3>
<ul>
<li>CharArrayReader, CharArrayWriter처럼 메모리의 입출력에 사용한다.</li>
<li>StringWriter에 출력되는 데이터는 내부의 StringBuffer에 저장된다. (char 배열이다)</li>
</ul>
<pre><code class="language-java">String inputData = &quot;String message!&quot;;
StringReader input = new StringReader(inputData);
StringWriter output = new StringWriter();

int data = 0;

try{
    while((data = input.read())!=-1){
        output.write(data);
    }
}catch(IOException e){}

System.out.println(&quot;Input Data: &quot; + inputData);
System.out.println(&quot;Output Data: &quot; + output.toString());

[결과]
Input Data: String message!
Output Data: String message!</code></pre>
<br/>

<h3 id="bufferedreader와-bufferedwriter">BufferedReader와 BufferedWriter</h3>
<ul>
<li>입출력 효율을 높이기 위해 버퍼(char[])를 사용하는 chaining stream이다.</li>
<li>readLine()을 사용하여 라인(line)단위로 읽어올 수 있는 장점이 있고 문자열 검색기능도 장점이다.</li>
<li>BufferedReader method: String readLine() 한 라인을 읽어온다.</li>
<li>BufferedWriter method: void newLine() 개행문자를 출력한다.</li>
</ul>
<pre><code class="language-java">public class BufferedReaderEx01 {
    public static void main(String[] args) {
        try {
            FileReader fr = new FileReader(&quot;/Users/jongkwonkim/Documents/streamtest/brtestfile.txt&quot;); 
            BufferedReader br = new BufferedReader(fr);

            String line = &quot;&quot;;
            for(int i=1;(line = br.readLine())!=null;i++) {
                System.out.println(i+&quot;:&quot;+line); 
            }
            br.close();
        } catch(IOException e) {
            e.printStackTrace(); 
        }
    } // main 
}

[결과]
1:안녕하세요!
2:테스트 파일입니다~</code></pre>
<br/>

<h3 id="character-chaining-stream">Character Chaining Stream</h3>
<ul>
<li>InputStreamReader와 OutputStreamReader</li>
<li>바이트기반 스트림을 문자기반 스트림으로 연결시켜주는 역할을 한다.</li>
<li>지정된 인코딩의 문자데이터로 변환하는 작업을 수행한다. (기본은 OS에서 사용하는 인코딩)</li>
</ul>
<br/>

<ul>
<li>InputStreamReader 메서드/생성자</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명/생성자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>InputStreamReader(InputStream in)</td>
<td>OS의 기본 인코딩의 문자로 변환하는 InputStreamReader를 생성한다.</td>
</tr>
<tr>
<td>InputStreamReader(InputStream in, String encoding)</td>
<td>지정 인코딩을 사용하는 InputStreamReader를 생성한다.</td>
</tr>
<tr>
<td>String getEncoding()</td>
<td>InputStreamReader의 인코딩을 알려준다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>OutputStreamReader 메서드/생성자</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명/생성자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>OutputStreamReader(OutputStream in)</td>
<td>OS의 기본 인코딩의 문자로 변환하는 OutputStreamReader를 생성한다.</td>
</tr>
<tr>
<td>OutputStreamReader(OutputStream in, String encoding)</td>
<td>지정 인코딩을 사용하는 OutputStreamReader를 생성한다.</td>
</tr>
<tr>
<td>String getEncoding()</td>
<td>OutputStreamReader의 인코딩을 알려준다.</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>시스템의 인코딩 정보는 sun.jnu.encoding의 값을 보면 알 수 있다.</li>
<li>Stream생성시 인코딩을 저장할 수 있다.</li>
</ul>
<pre><code class="language-java">FileInputStream fis = new FileInputStream(&quot;test.txt&quot;);
InputStreamReader isr = new InputStreamReader(fis, &quot;KSC5601&quot;);</code></pre>
<br/>

<h3 id="randomaccessfile">RandomAccessFile</h3>
<ul>
<li>하나의 스트림으로 파일에 입력과 출력을 모두 수행할 수 있는 스트림</li>
<li>다른 스트림들과 달리 Object의 하위 클래스이며, DataInput 인터페이스와 DataOutput 인터페이스를 모두 구현했기 때문에 read/write가 모두 가능하다. (기본 자료형 단위로 입출력을 처리할 수 있다)</li>
<li>가장 큰 장점은 파일의 어느 위치에나 입출력이 가능하며 내부적으로 파일포인터를 사용하는데 입출력시에 작업이 수행되는 곳이 바로 파일 포인터가 위치한 곳이다.</li>
</ul>
<table>
<thead>
<tr>
<th>메서드명/생성자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>RandomAccessFile(File file, String name)<br/>RandomAccessFile(String fileName, String mode)</td>
<td>주어진 file에 읽기 또는 읽기와 쓰기를 하기 위한 RandomAccessFile 인스턴스를 생성한다. mode는 “r(읽기)”과 “rw(읽기/쓰기)” 두 가지 값이 지정 가능하다.</td>
</tr>
<tr>
<td>long getFilePointer()</td>
<td>파일 포인터의 위치를 알려준다.</td>
</tr>
<tr>
<td>long length()</td>
<td>파일의 크기를 byte 단위로 얻을 수 있다.</td>
</tr>
<tr>
<td>void seek(long pos)</td>
<td>파일 포인터의 위치를 변경한다. 위치는 파일의 첫 부분부터 pos 크기만큼이다.</td>
</tr>
<tr>
<td>void setLength(long newLength)</td>
<td>파일 크기를 byte단위로 지정된 길이로 변경한다.</td>
</tr>
<tr>
<td>int skipBytes(int n)</td>
<td>지정 수 만큼의 byte를 skip한다.</td>
</tr>
</tbody></table>
<br/>

<p>예제와 파일 내용</p>
<pre><code class="language-java">static final String FILEPATH = &quot;/testme.txt&quot;;
public static void main(String[] args){
    try{
        System.out.println(new String(readFromFile(FILEPATH, 0, 13)));
        writeToFile(FILEPATH, &quot;RandomAccessFile Read Write test file!!!&quot;, 14);
    }catch(IOException e){
        e.printStackTrace();
    }
}

private static byte[] readFromFile(String filePath, int position, int size)
                throws IOException {
    RansomAccessFile file = new RandomAccessFile(filePath, &quot;r&quot;);
    file.seek(position);
    byte[] bytes = new byte[size];
    file.read(bytes);
    file.close();
    return bytes;
}

private static void writeToFile(String filePath, String data, int position)
                throws IOException {
    RandomAccessFile file = new RandomAccessFile(filePath, &quot;rw&quot;);
    file.seek(position);
    file.write(data.getBytes());
    file.close();
}

[결과]
Welcome!
test

[파일내용]
Welcome!
test
RandomAccessFile Read Write test file!!!</code></pre>
<br/>

<h3 id="file">File</h3>
<ul>
<li>파일과 디렉토리를 다루는데 사용되는 클래스</li>
<li>파일의 경로(path)와 디렉토리나 파일의 이름을 구분하는 데 사용되는 구분자가 OS마다 다를 수 있다.</li>
<li>File인스턴스를 생성해도 파일이나 디렉토리는 생성되지 않으며 유효하지 않은 파일명이나 디렉토리명이라도 컴파일 예외는 발생하지 않는다.</li>
</ul>
<table>
<thead>
<tr>
<th>멤버변수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>static String pathSeparator</td>
<td>편의를 위해 문자열로 표시되는 시스템 종속 경로 구분 문자로 윈도우 “;”, 유닉스 “:”</td>
</tr>
<tr>
<td>static char pathSeparatorChar</td>
<td>시스템 종속 경로 구분 문자로 윈도우 “;”, 유닉스 “:”</td>
</tr>
<tr>
<td>static String separator</td>
<td>편의를 위해 문자열로 표시되는 시스템 종속 default-name 구분 문자</td>
</tr>
<tr>
<td>static char separatorChar</td>
<td>시스템에 따라 달라지는 default-name 구분자로 윈도우 “;”, 유닉스 “:”</td>
</tr>
</tbody></table>
<br/>

<table>
<thead>
<tr>
<th>메서드명/생성자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>File(String fileName)</td>
<td>지정한 문자열의 파일명을 갖는 파일을 핸들링할 수 있는 FIle 인스턴스를 생성한다. 파일 뿐만 아니라 디렉토리도 같은 방법으로 다룬다.</td>
</tr>
<tr>
<td>File(String pathName, String fileName)</td>
<td>파일의 경로와 이름을 따로 분리해서 지정할 수 있도록 한 생성자이다.</td>
</tr>
<tr>
<td>String getName()</td>
<td>파일이름을 문자열로 반환한다.</td>
</tr>
<tr>
<td>String getPath()</td>
<td>파일의 경로를 문자열로 반환한다.</td>
</tr>
<tr>
<td>String getAbsolutePath()<br/>File getAbsolutePath()</td>
<td>파일의 절대경로를 문자열로 반환한다.<br/>파일의 절대경로를 File로 반환한다.</td>
</tr>
<tr>
<td>String getParent()<br/>File getParentFile()</td>
<td>파일의 상위 디렉토리를 문자열로 반환한다.<br/>파일의 상위 디렉토리를 File로 반환한다.</td>
</tr>
<tr>
<td>String getCanonicalPath()<br/>File getCanonicalFile()</td>
<td>파일의 정규경로를 String으로 반환한다.</td>
</tr>
<tr>
<td>파일의 정규경로를 File로 반환한다.</td>
<td></td>
</tr>
<tr>
<td>boolean canRead()</td>
<td>파일을 읽을 수 있는지 여부를 체크한다.</td>
</tr>
<tr>
<td>boolean canWrite()</td>
<td>파일을 수정할 수 있는지 여부를 체크한다.</td>
</tr>
<tr>
<td>boolean exists()</td>
<td>파일이 존재하는지 체크한다.</td>
</tr>
<tr>
<td>boolean isAbsolute()</td>
<td>파일 또는 디렉토리가 절대경로명으로 지정되었는지 확인한다.</td>
</tr>
<tr>
<td>boolean isDirectory()</td>
<td>디렉토리인지 확인한다.</td>
</tr>
<tr>
<td>boolean isFile()</td>
<td>파일인지 확인한다.</td>
</tr>
<tr>
<td>boolean isHidden()</td>
<td>파일의 속성이 hidden 파일인지 확인한다.</td>
</tr>
<tr>
<td>boolean createNewFile()</td>
<td>내용이 없는 새로운 파일을 생성한다. 파일이 이미 존재하면 생성되지 않는다.</td>
</tr>
<tr>
<td>static File createTempFile(String prefix, String suffix)</td>
<td>임시파일을 시스템의 임스 디렉토리에 생성한다.</td>
</tr>
<tr>
<td>String File createTempFile(String prefix, String suffix, File directory)</td>
<td>임시파일을 시스템의 지정 디렉토리에 생성한다.</td>
</tr>
<tr>
<td>boolean delete()</td>
<td>파일을 삭제한다.</td>
</tr>
<tr>
<td>void deleteOnExit()</td>
<td>응용 프로그램 종료시 파일을 삭제한다.</td>
</tr>
<tr>
<td>long length()</td>
<td>파일의 크기를 반환한다.</td>
</tr>
<tr>
<td>String[] list</td>
<td>디렉토리의 파일목록을 String 배열로 반환한다.</td>
</tr>
<tr>
<td>File[] listFiles()</td>
<td>디렉토리의 파일목록을 File 배열로 반환한다.</td>
</tr>
</tbody></table>
<br/>

<p>예제</p>
<pre><code class="language-java">try{
    //새로운 파일 생성
    File file =
                new File(&quot;/NewFileEx02.txt&quot;);
    file.createNewFile();
    System.out.println(file);

    //파일이 존재하는지 체크
    boolean bool = file.exists();
    //절대경로 반환
    String path = file.getAbsolutePath();
    if(bool){
        System.out.println(path + &quot; Exists? &quot; + bool);
    }
}catch(Exception e){
    e.printStackTrace();
}

[결과]
/NewFileEx02.txt
/NewFileEx02.txt Exists? true</code></pre>
<pre><code class="language-java">File targetFile = new File(&quot;/streamtest&quot;);
File[] files = targetFile.listFiles();

for(int i=0; i&lt;files.length; i++){
    File f = files[i];
    String attribute = &quot;&quot;;
    attribute = f.getName();
    if(files[i].isDirectory()){
        attribute += &quot; /Directory&quot;;
    }else{
        attribute += &quot; / size : &quot; + f.length() +
                                &quot; / canRead : &quot; + f.canRead() +
                                &quot; / canWrite : &quot; + f.canWrite() +
                                &quot; / isHidden : &quot; + f.isHidden();
    }
    System.out.println(attribute);
}</code></pre>
<hr>
<h2 id="객체-직렬화object-serialization">객체 직렬화(Object Serialization)</h2>
<ul>
<li>직렬화란 객체를 데이터 스트림으로 만드는 것을 의미한다.</li>
<li>객체를 ‘연속적인 데이터’로 변환하는 것. 반대과정은 ‘역직렬화’라고 한다.</li>
<li>객체의 인스턴스변수들의 값을 일렬로 나열하는 것</li>
<li>객체 직렬화를 통해 객체의 모든 인스턴스변수의 값을 저장하는 것(메소드는 포함되지 않는다)</li>
</ul>
<br/>

<ul>
<li>Serializable, transient</li>
<li>객체 직렬화 대상 객체는 java.io.Serializable을 implements로 선언해야만 직렬화 가능하다.</li>
</ul>
<pre><code class="language-java">public interface Serializable{}

public class Member implements java.io.Serializable{
    String name;
    String password;
    int age;
}</code></pre>
<br/>

<ul>
<li>제어자 transient가 붙은 인스턴스변수는 직렬화 대상에서 제외된다. (패스워드 등은 보안상 제외 필요)</li>
</ul>
<pre><code class="language-java">public class Member implements java.io.Serializable{
    String name;
    transient String password; //직렬화 대상 제외
    int age;
}</code></pre>
<br/>

<ul>
<li>Serializable을 구현하지 않은 클래스의 인스턴스, Serializable을 implements하지 않은 상위 멤버들은 직렬화 대상에서 제외</li>
</ul>
<pre><code class="language-java">public class Member implements java.io.Serializable{
    String name;
    transient String password; //직렬화 대상 제외
    int age;

    Object o = new Object(); //Object는 직렬화 제외
}</code></pre>
<pre><code class="language-java">public class ParentMember{
    String name; //직렬화 안 됨
    String password; //직렬화 안 됨
}

public class Member extends ParentMember implements Serializable{
    int age;
}</code></pre>
<br/>

<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">try{
    ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream(&quot;/obj.txt&quot;));
    Member m = new Member(&quot;GilDong&quot;, &quot;1234&quot;, 30);
    oos.writeObject(m);
    System.out.println(&quot;객체 저장 완료!&quot;);
    oos.close();

    System.out.println(&quot;저장된 객체 읽기!&quot;);
    ObjectInputStream ois = new ObjectInputStream()
        new FileInputStream(&quot;/obj.txt&quot;));
    Member im = (Member)ois.readObject();
    System.out.println(&quot;이름: &quot; + im.getName());
    System.out.println(&quot;비밀번호: &quot; + im.getPassword());
    System.out.println(&quot;나이: &quot; + im.getAge());
    ois.close();
}catch(Exception e){
    e.printStackTrace();
}

[결과]
객체 저장 완료!
저장된 객체 읽기!
이름: GilDong
비밀번호: 1234
나이: 30</code></pre>
<br/>

<h3 id="objectinputstream-objectoutputstream">ObjectInputStream, ObjectOutputStream</h3>
<ul>
<li>ObjectOutputStream은 직렬화(스트림에 객체를 출력) 시 ObjectInputStream은 역직렬화(스트림으로부터 객체를 입력) 시 사용한다. (Chaining Stream)</li>
</ul>
<pre><code class="language-java">ObjectInputStream(InputStream in)
ObjectOutputStream(OutputStream out)</code></pre>
<br/>

<ul>
<li>ObjectOutputStream의 writeObject()로 객체를 파일에 출력하여 직렬화</li>
</ul>
<pre><code class="language-java">FileOutputStream fos = new FileOutputStream(&quot;objectfile.ser&quot;);
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(new UserInfo());</code></pre>
<br/>

<ul>
<li>ObjectInputStream의 readObject()로 객체를 역직렬화</li>
</ul>
<pre><code class="language-java">FileInputStream fos = new FileInputStream(&quot;objectfile.ser&quot;);
ObjectInputStream in = new ObjectInputStream(fos);
UserInfo info = (UserInfo)in.readObject();</code></pre>
<br/>

<ul>
<li>여러가지 타입의 값을 입출력할 수 있는 메소드를 제공</li>
</ul>
<table>
<thead>
<tr>
<th>ObjectOutputStream</th>
<th>ObjectInputStream</th>
</tr>
</thead>
<tbody><tr>
<td>void defaultWriteObject()<br/>void write(byte[] buf)<br/>void write(byte[] buf, int off, int len)<br/>void write(int val)<br/>void writeBoolean(boolean val)<br/>void writeByte(int val)<br/>void writeBytes(String val)<br/>void writeChar(int val)<br/>void writeChars(String str)<br/>void writeDouble(double val)<br/>void writeFloat(float val)<br/>void writeInt(int val)<br/>void writeLong(long val)<br/>void writeObject(Object obj)<br/>void writeShort(int val)<br/>void writeUTF(String str)</td>
<td>void defaultReadObject()<br/>int read()<br/>int read(byte[] buf, int off, int len)<br/>boolean readBoolean()<br/>byte readByte()<br/>char readChar()<br/>double readDouble()<br/>float readFloat()<br/>int readInt()<br/>long readLong()<br/>short readShort()<br/>Object readObject()<br/>String readUTF()</td>
</tr>
</tbody></table>
<hr>
<h2 id="io-vs-nio">I/O vs NIO</h2>
<ul>
<li>Java NIO(New Input/Output): Java 4 버전부터 java.nio 패키지가 포함되었고, Java 7 버전부터 java.io와 nio 사이의 일관성 없는 클래스 설계를 바로 잡고, 비동기 채널 등의 네트워크 지원을 대폭 강화한 NIO 2 API가 추가되었다.</li>
<li>스트림과 채널: 스트림은 입력과 출력 스트림으로 구분되어 있고, 데이터를 읽기 위한 InputStream, 출력하기 위한 OutputStream을 생성해야한다. NIO는 채널기반으로 양방향으로 입/출력이 가능해서 입력과 출력을 위한 별도의 채널을 생성할 필요가 없다.</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>java.io</th>
<th>java.nio</th>
</tr>
</thead>
<tbody><tr>
<td>입출력 방식</td>
<td>스트림 방식</td>
<td>채널 방식</td>
</tr>
<tr>
<td>비동기 방식</td>
<td>지원 안 함</td>
<td>지원</td>
</tr>
<tr>
<td>Buffer 방식</td>
<td>Non-buffer</td>
<td>Buffer</td>
</tr>
<tr>
<td>Blocking/non-blocking 지원</td>
<td>Blocking only</td>
<td>Blocking/Non-blocking 모두 지원</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[람다와 스트림]]></title>
            <link>https://velog.io/@ju-ei8ht/lambda-stream</link>
            <guid>https://velog.io/@ju-ei8ht/lambda-stream</guid>
            <pubDate>Tue, 31 Jan 2023 12:26:47 GMT</pubDate>
            <description><![CDATA[<h2 id="함수형-프로그래밍functional-programming">함수형 프로그래밍(Functional Programming)</h2>
<ul>
<li>소프트웨어 규모가 커짐에 따라 복잡한 코드를 유지보수 하기가 힘들어졌고, 이를 해결하기 위해 함수형 프로그램밍에 관심을 가지게 되었다. 선언형 프로그래밍이다.</li>
<li>계산을 수학적 함수의 평가로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임이다.</li>
<li>함수형 프로그래밍은 프로그램을 오직 순수 함수(pure function)들로만 작성되어진다.</li>
<li>순수 함수(Pure function)은 모든 입력이 이력으로만, 모든 출력이 출력으로만 사용된다.</li>
<li>함수형 프로그래밍은 대입문이 없는 프로그래밍이다.</li>
<li>객체지향은 동작하는 부분을 캡슐화해서 이해할 수 있게 하고, 함수형 프로그래밍은 동작하는 부분을 최소화해서 코드 이해를 돕는다.</li>
<li>함수 실행 동안 변수 할당 값은 불변: 변수에는 새로운 값만 설정할 수 있으며 값이 설정이 되면 변경할 수 없다.</li>
<li>1급 객체(First object, First class citizen): 1급 함수라고도 하며, 함수를 변수나 자료 구조 안에 할당할 수 있고, 인자 전달, 리턴 값으로 사용할 수 있는 객체를 의미한다.</li>
<li>고차 함수(Higher-order function): 함수의 인자로 함수를 넘기거나, 함수를 리턴하는 함수, 1급 함수를 지원한다는 의미는 고차함수를 사용할 수 있다는 의미이다.</li>
<li>익명함수(Anonymous function): Java에서 람다(Lambda)는 익명함수이며, 파라미터 리스트와 함수의 body만을 가지는 함수이다.</li>
<li>코드를 간결하게, 가독성을 높여 로직에 집중: 람다(Lambda), 스트림(Stream) API를 통해 보일러 플레이트(Boilerpalte code)를 제거하고, 내부에 직접적인 함수 호출을 통해 가독성을 높인다. 보일러 플레이트는 ‘별 수정 없이 반복적으로 사용되는 코드’를 의미한다.</li>
<li>동시성 작업을 보다 쉼고 안전하게 구현할 수 있다.</li>
<li>단일 추상 메서드를 가지는 인터페이스로 함수형 인터페이스 지정을 위해서 @FunctoinalInterface 어노테이션이 도입되었다.</li>
<li>단일 메서드 구현으로 람다 표현식을 이용해 함수로 구현할 수 있게 된다.</li>
<li>Default static method를 정의할 수 있지만 추상 메서드는 하나만 포함할 수 있다.</li>
<li>@FunctionalInterface 어노테이션이 아니더라도 추상 메서드가 하나인 인터페이스는 함수형 인터페이스로 처리된다.</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
public interface Runnable{
    public abstract void run();
}

@FUnctionalInterface
interface MyFunction{
    void run(); //public abstract void run();
}</code></pre>
<hr>
<h2 id="람다식lambda-expression-개요">람다식(lambda expression) 개요</h2>
<ul>
<li>Java 8에서의 가장 큰 변화는 람다식이며 람다는 원래는 수학기호로 함수형 언어의 특징에서 나온 것으로 한 번 이상 실행할 수 있는 코드블록을 말하며 실제 구현에는 익명 함수 형태로 사용된다.</li>
<li>식을 사용하여 하나의 메서드 인터페이스를 나타내는 명확하고 간결한 방법을 제공하며, 컬렉션 라이브러리에서 매우 유용하게 사용할 수 있다.</li>
<li>컬렉션에서 데이터를 반복, 필터링 및 추출하는 데 도움이 된다.</li>
<li>람다식은 함수를 간결하게 표현하며, 이미 많은 언어에서 지원하고 있다.</li>
<li>람다식은 함수형 인터페이스 구현을 제공하며, 람다식을 통해 인스턴스화 될 수 있고, 구현 코드만 작성하면 된다.</li>
</ul>
<pre><code class="language-java">Runnalbe r = () -&gt; {System.out.println(&quot;funmctional interface&quot;);}</code></pre>
<ul>
<li>함수의 구조로 → 와 같은 화살표 형태의 기호를 이용해서 매개변수를 함수 바디로 전달하는 형태를 취한다.<ul>
<li>예) (int x) → x + 1 → ‘x라는 인수로 호출하여 x+1을 반환’하는 동작을 수행하는 코드를 구현할 수 있다.</li>
</ul>
</li>
</ul>
<br/>

<h3 id="람다식-특성">람다식 특성</h3>
<ul>
<li>메서드처럼 특정 클래스에 종속되지 않고 parameter, body, return 값을 포함한다.</li>
<li>람다식을 메서드의 인자로 전달하거나 변수 값으로 저장할 수 있다.</li>
<li>적은 코딩으로 구현을 할 수 있으며, 일반적으로 다중 CPU를 활용하는 형태로 구현되어 병렬처리에 이득이다.</li>
<li>디버깅시 함수 콜스택 추적이 다소 어렵다.</li>
<li>람다식은 함수로 취급되며, 컴파일러는 .class 파일을 생성하지 않는다.</li>
<li>Syntax: (Argument list) → {body}<ul>
<li>Argument list: 매개변수는 비어 있거나 비어 있지 않을 수 있다.</li>
<li>→ (Arrow-token): 파라미터 리스트와 experssion 본문을 연결하는데 사용된다.</li>
<li>Body: 람다식을 위한 표현식과 명령문을 포함한다.</li>
</ul>
</li>
</ul>
<br/>

<h3 id="람다식lambda-experssion-개요">람다식(lambda experssion) 개요</h3>
<ul>
<li>기존 Comparator 코드</li>
</ul>
<pre><code class="language-java">Comparator&lt;Apple&gt; weight = new Comparator&lt;Apple&gt;(){
    @Override
    public int compare(Apple o1, Apple o2){
        return o1.getWeight().compareTo(o2.getWeight());
    }
}</code></pre>
<ul>
<li>람다를 이용한 Comparator 코드</li>
</ul>
<pre><code class="language-java">Comparator&lt;Apple&gt; weight =
            (Apple o1, Apple o2) -&gt; o1.getWeight().compareTo(o2.getWeight());</code></pre>
<br/>

<h3 id="람다식-예">람다식 예</h3>
<ul>
<li>No parameter return void: () → {}</li>
<li>No parameter, express body: () → 10, () → null</li>
<li>No parameter, body, return 값: () → { return 10; }</li>
<li>No parameter, return void: () → {System.out.println(”value”);}</li>
<li>One Parameter list: (int x) → x+1</li>
<li>Multiple Pamrameter list: (int x, int y) → x+y</li>
<li>Type 추론 parameter list: (x) → x+1, x → x+1</li>
<li>람다식의 Parameter, Return 타입 추론: 추상 메서드와 리턴타입에 대해서 타입 추론을 수행한다.</li>
<li>Boolean 표현식: (List&lt;String&gt; list) → list.isEmpty()</li>
<li>객체 생성: () → new Apple();</li>
<li>객체 소비: (Apple a) → {System.out.println(a.getWeight());}</li>
<li>객체 선택/추출: (String s) → s.length();</li>
<li>값의 조합: (int a, int b) → a*b</li>
<li>객체 비교: (Apple a, Apple b) → a.getWeight().compareTo(b.getWeight());</li>
</ul>
<br/>

<ul>
<li>람다식을 이용한 스레드 생성 예제</li>
</ul>
<pre><code class="language-java">public class LambdaCreateThreadEx1{
    public static void main(String[] args) {
        // 람다 없이 Thread 생성 
        Runnable r1=new Runnable(){
            public void run(){
                System.out.println(&quot;Thread1 is running...&quot;);
            } 
        };

        Thread t1=new Thread(r1); t1.start();
        // 람다를 사용한 Thread 생성 
        Runnable r2=()-&gt;{
            System.out.println(&quot;Thread2 is running...&quot;); 
        };
        Thread t2=new Thread(r2);
        t2.start(); 
    }
}</code></pre>
<ul>
<li>컬렉션 프레임워크에서 람다식을 사용할 수 있고, 데이터를 iteration, filtering 등의 간결한 방법을 제공한다.</li>
</ul>
<br/>

<ul>
<li>Comparator 예제</li>
</ul>
<pre><code class="language-java">public class LambdaProduct{ 
int id;
String name;
int price;
public LambdaProduct(int id, String name, int price) {
    this.id = id; 
    this.name = name; 
    this.price = price;
}

public int getId() {
    return id;
}
public String getName() {
    return name; 
}
public int getPrice() { 
    return price;
}</code></pre>
<pre><code class="language-java">public class LambdaCollectionComparator { 
    public static void main(String[] args) {
        List&lt;LambdaProduct&gt; list=new ArrayList&lt;&gt;();

        list.add(new LambdaProduct(1,&quot;노트북&quot;,25000)); 
        list.add(new LambdaProduct(3,&quot;키보드&quot;,300)); 
        list.add(new LambdaProduct(2,&quot;마우스&quot;,150));

        System.out.println(&quot;이름 기준 정렬&quot;);

        // lambda expression 구현 
        Collections.sort(list,(p1, p2)-&gt;{
            return p1.name.compareTo(p2.name);
        });

        for(LambdaProduct p:list){ 
            System.out.println(
                            p.id+&quot; &quot;+p.name+&quot; &quot;+p.price);
        }
    }
}

[결과]
이름 기준 정렬 
1 노트북 25000 
2 마우스 150 
3 키보드 300</code></pre>
<br/>

<h3 id="메서드-레퍼런스method-reference">메서드 레퍼런스(Method Reference)</h3>
<ul>
<li>Java 8부터 제공되는 기능으로 함수형 인터페이스의 메서드를 참조하는데 사용된다.</li>
<li>람다식으로 간결하게 사용할 수 있는 Syntax</li>
<li>클래스(객체)명과 메서드명 사이에 구분자(::)를 붙여 사용한다.</li>
<li>이 기능을 활용해 매개변수 정보 및 리턴 타입을 알아내어 람다식에서 불필요한 매개변수를 제거하는 것이 목적이다.</li>
<li>다음 유형의 메서드 레퍼런스가 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>종류</th>
<th>형식</th>
</tr>
</thead>
<tbody><tr>
<td>정적 메서드 참조</td>
<td>클래스명::정적메서드명</td>
</tr>
<tr>
<td>객체 메서드 참조</td>
<td>객체변수::메서드명</td>
</tr>
<tr>
<td>람다인자 객체 메서드 참조</td>
<td>클래스명::메서드명</td>
</tr>
<tr>
<td>생성자 참조</td>
<td>클래스명::New</td>
</tr>
</tbody></table>
<pre><code class="language-java">(apple) -&gt; apple.getWeight();
Apple::getWeight;</code></pre>
<br/>

<ul>
<li><p>정적 메서드 참조(Static method reference)</p>
<ul>
<li>클래스에 정의된 정적 메서드를 참조할 수 있다.</li>
<li>예) Integer::parseInt</li>
</ul>
</li>
<li><p>인스턴스 메서드 참조(Instance method reference)</p>
<ul>
<li>인스턴스 메서드를 참조할 수 있다. 클래스 객체 및 익명 객체로 메서드를 참조할 수 있다.</li>
<li>System.out::println</li>
</ul>
</li>
<li><p>생성자 참조(Constructor reference)</p>
<ul>
<li>new 키워드를 사용하여 클래스의 생성자를 간결하게 호출하여 참조할 수 있다. (String::new)</li>
<li>(인자) → new 클래스(인자)</li>
<li>() → new 클래스()</li>
<li>클래스::new</li>
</ul>
</li>
<li><p>정적 메서드 참조 예제: 미리 정의된 Runnable 인터페이스를 사용하여 정적 메서드 참조를 한다.</p>
</li>
</ul>
<pre><code class="language-java">public class StaticMethodReferenceEx2 { 
    public static void ThreadStatus(){
        System.out.println(&quot;Thread is running&quot;); 
    }
    public static void main(String[] args) {
        Thread t2=new Thread(StaticMethodReferenceEx2::ThreadStatus); 
        t2.start();
    } 
}

[결과]
Thread is running</code></pre>
<br/>

<ul>
<li>인스턴스 메서드 참조 예제</li>
</ul>
<pre><code class="language-java">interface SayableEx{ 
    void say();
}
public class InstanceMethodReferenceEx1 {
    public void saySomething(){ 
        System.out.println(&quot;안녕, 인스턴스 메서드&quot;);
    }
    public static void main(String[] args) {
        InstanceMethodReferenceEx1 methodReference = new InstanceMethodReferenceEx1(); 
        // 참조를 사용한 인스턴스 메서드 참조
        SayableEx sayable = methodReference::saySomething;
        // 인터페이스 메서드 호출
        sayable.say();
        // 익명 객체를 이용한 인스턴스 메서드 참조
        Sayable sayable2 = new InstanceMethodReferenceEx1()::saySomething; // 익명 객체를 사용할 수 있다
        sayable.say();
    }
}

[결과]
안녕, 인스턴스 메서드 
안녕, 인스턴스 메서드</code></pre>
<br/>

<ul>
<li>생성자 메서드 참조 예제</li>
</ul>
<pre><code class="language-java">interface Messageable{
    Message getMessage(String msg);
}
class Message{
    Message(String msg){ 
        System.out.print(msg);
    } 
}
public class ConstructorReferenceEx1 { 
    public static void main(String[] args) {
        Messageable hello = Message::new;
        hello.getMessage(&quot;안녕&quot;); 
    }
}

[결과] 
안녕</code></pre>
<hr>
<h2 id="함수형-인터페이스의-사용">함수형 인터페이스의 사용</h2>
<ul>
<li>함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 표현하며 추상 메서드 시그니처를 함수 디스크립터(function descriptor)라고 한다.</li>
<li>함수 디스트립터 == 람다 표현식의 시그니처</li>
<li>다양한 람다식을 사용하려면 공통의 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요하다.</li>
<li>자바 API는 Comparable, Runnable, Callable 등의 다양한 함수형 인터페이스를 포함하여 java.util.function 패키지로 제공한다.</li>
<li>Predefined-Functional interface<ul>
<li>람다 및 메서드 참조를 사용하여 함수형 프로그래밍을 처리하기 위해 미리 정의된 함수형 인터페이스를 제공한다.</li>
<li>사용자가 정의한 함수형 인터페이스를 사용할 수도 있다.</li>
</ul>
</li>
<li>java.util.function 패키지에 정의된 함수형 인터페이스</li>
<li>함수형 인터페이스의 타입 파라미터: T (첫번째 인자 Type) / U (두번째 인자 Type) / R (리턴타입)</li>
</ul>
<table>
<thead>
<tr>
<th>종류</th>
<th>추상 메서드 특징</th>
</tr>
</thead>
<tbody><tr>
<td>Function</td>
<td>인자 있고, 리턴값 있음, 주로 인자값을 연산하고 결과를 리턴</td>
</tr>
<tr>
<td>Consumer</td>
<td>인자 있고, 리턴값 없음</td>
</tr>
<tr>
<td>Supplier</td>
<td>인자 없고, 리턴값 있음</td>
</tr>
<tr>
<td>Operator</td>
<td>인자 있고, 리턴값 있음. 주로 인자값을 연산하고 결과를 리턴</td>
</tr>
<tr>
<td>Predicate</td>
<td>인자 있고, 리턴값은 boolean, 인자값을 조사하고 true/false를 리턴</td>
</tr>
<tr>
<td>- Descriptor</td>
<td></td>
</tr>
<tr>
<td>- Function: T → R</td>
<td></td>
</tr>
<tr>
<td>- Consumer: T → void</td>
<td></td>
</tr>
<tr>
<td>- Supplier: () → T</td>
<td></td>
</tr>
<tr>
<td>- Operator: T → T</td>
<td></td>
</tr>
<tr>
<td>- Predicate: T → boolean</td>
<td></td>
</tr>
</tbody></table>
<pre><code class="language-java">              //T          R
Function&lt;BufferedReader, String&gt; f = (BufferedReader b) -&gt; {
    //구현부
};</code></pre>
<pre><code class="language-java">            //T      U       R
BiFunction&lt;String, String, String&gt; func1 = (s1, s2) -&gt; {
    String s3 = s1 + s2;
    return s3;
};
String result = func1.apply(&quot;Hello&quot;, &quot;World&quot;);</code></pre>
<br/>

<ul>
<li>Function&lt;T, R&gt;은 대표적인 예이다. T 형식의 객체를 R 형식의 객체로 변환할 때 사용한다.<ul>
<li>예) Function&lt;Apple, Integer&gt;는 사과의 무게 정보가 Integer 객체로 변환된다.</li>
</ul>
</li>
<li>람다식에서 Checked Exception이 발생할 경우 람다식 내부에서 try~catch로 처리해주지 않으면 컴파일 에러가 발생한다.</li>
<li>아래 예제처럼 Checked Exception을 Runtime Exception으로 감싸서 throw하고 람다를 사용하는 곳에서 Runtime Exception을 핸들링한다.</li>
</ul>
<pre><code class="language-java">Function&lt;BufferedReader, String&gt; f = (BufferedReader b) -&gt; {
    try{
        return b.readLine();
    }catch(IOException e){
        throw new RuntimeException(e);
    }
}</code></pre>
<br/>

<h3 id="predicate-함수형-인터페이스">Predicate 함수형 인터페이스</h3>
<ul>
<li>Function Predicate<T> 인터페이스는 test 추상 메서드를 정의한다.</li>
<li>test 메서드는 제너릭 형식 T의 객체를 인수로 받아 boolean으로 리턴한다.</li>
<li>T 타입의 객체를 사용하는 boolean 표현식이 필요한 상황에서 Predicate 인터페이스를 사용하면 된다.</li>
<li>Argument type과 개수에 따라 인터페이스를 분류할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>인터페이스 명</th>
<th>추상 메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Predicate&lt;<span>T</span>&gt;</td>
<td>boolean test(T t)</td>
<td>객체 T를 조사</td>
</tr>
<tr>
<td>BiPredicate&lt;T, U&gt;</td>
<td>boolean test(T t, U u)</td>
<td>객체 T와 U를 비교 조사</td>
</tr>
<tr>
<td>DoublePredicate</td>
<td>boolean test(double value)</td>
<td>double 값을 조사</td>
</tr>
<tr>
<td>IntPredicate</td>
<td>boolean test(int value)</td>
<td>int 값을 조사</td>
</tr>
<tr>
<td>LongPredicate</td>
<td>boolean test(long value)</td>
<td>long 값을 조사</td>
</tr>
</tbody></table>
<pre><code class="language-java">@FunctionalInterface
public interface Predicate&lt;T&gt;{
    boolean test(T t);
    ...
}</code></pre>
<br/>

<ul>
<li>Predicate를 구현</li>
</ul>
<pre><code class="language-java">public &lt;T&gt; List&lt;T&gt; filter(List&lt;T&gt; list, Predicate&lt;T&gt; p) { 
    List&lt;T&gt; results = new ArrayList&lt;&gt;();
    for(T t: list){
        if(p.test(t)) { 
            results.add(t);
        } 
    }
    return results; 
}</code></pre>
<ul>
<li>구현한 코드를 람다식으로 호출</li>
</ul>
<pre><code class="language-java">Predicate&lt;String&gt; predicate = (String s) -&gt; !s.isEmpty(); 
List&lt;String&gt; nonEmpty = filter(lists, predicate);</code></pre>
<br/>

<h3 id="consumer-함수형-인터페이스">Consumer 함수형 인터페이스</h3>
<ul>
<li>Consumer&lt;<span>T</span>&gt; 인터페이스는 제네릭 타입 T 객체를 받고 리턴 타입이 없는 accept(T) 추상 메서드를 정의한다.</li>
<li>예를 들어 Integer 리스트를 인수로 받아서 각 항목에 어떤 동작을 수행하는 forEach 메서드를 정의할 때 Consumer를 활용할 수 있다.</li>
<li>Argument type과 개수에 따라 분류할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>인터페이스 명</th>
<th>추상 메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Consumer&lt;<span>T</span>&gt;</td>
<td>void accept(T t)</td>
<td>객체 T를 받아 소비</td>
</tr>
<tr>
<td>BiConsumer&lt;T,U&gt;</td>
<td>void accept(T t, U u)</td>
<td>객체 T와 U를 받아 소비</td>
</tr>
<tr>
<td>DoubleConsumer</td>
<td>void accept(double value)</td>
<td>double 값을 받아 소비</td>
</tr>
<tr>
<td>IntConsumer</td>
<td>void accept(int value)</td>
<td>int 값을 받아 소비</td>
</tr>
<tr>
<td>LongConsumer</td>
<td>void accept(long value)</td>
<td>long 값을 받아 소비</td>
</tr>
<tr>
<td>ObjDoubleConsumer&lt;<span>T</span>&gt;</td>
<td>void accept(T t, double value)</td>
<td>객체 T와 double 값을 받아 소비</td>
</tr>
<tr>
<td>ObjIntConsumer&lt;<span>T</span>&gt;</td>
<td>void accept(T t, int value)</td>
<td>객체 T와 int 값을 받아 소비</td>
</tr>
<tr>
<td>ObjLongConsumer&lt;<span>T</span>&gt;</td>
<td>void accept(T t, long value)</td>
<td>객체 T와 long 값을 받아 소비</td>
</tr>
</tbody></table>
<pre><code class="language-java">@FunctionalInterface
public interface Consumer&lt;T&gt; {
    void accept(T t);
    ...
}</code></pre>
<br/>

<ul>
<li>Consumer를 구현</li>
</ul>
<pre><code class="language-java">public &lt;T&gt; void forEach(List&lt;T&gt; list, Consumer&lt;T&gt; c){
    for(T t:list){
        c.accept(t);
    }
}</code></pre>
<ul>
<li>Consumer의 accept 메서드를 구현한 람다식</li>
</ul>
<pre><code class="language-java">forEach(
    Arrays.asList(1,2,3,4,5),
    (Integer i) -&gt; System.out.println(i)
);

[결과]
1
2
3
4
5</code></pre>
<br/>

<h3 id="function-함수형-인터페이스">Function 함수형 인터페이스</h3>
<ul>
<li>Function&lt;T, R&gt; 인터페이스는 제네릭 타입 T를 인수로 받아 R 객체로 리턴하는 추상 메서드 apply를 정의한다.</li>
<li>입/출력을 매핑하는 람다식을 정의시 Function 인터페이스를 활용할 수 있다.</li>
<li>Argument type과 Return type에 따라 분류할 수 있다.</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
public interface Function&lt;T, R&gt;{
    R apply(T t);
    ...
}</code></pre>
<table>
<thead>
<tr>
<th>인터페이스명</th>
<th>추상메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Function&lt;T,R&gt;</td>
<td>R apply(T t)</td>
<td>객체 T를 객체 R로 매핑</td>
</tr>
<tr>
<td>BiFunction&lt;T,R&gt;</td>
<td>R apply(T t, U u)</td>
<td>객체 T와 U를 객체 R로 매핑</td>
</tr>
<tr>
<td>DoubleFunction&lt;<span>R</span>&gt;</td>
<td>R apply(double value)</td>
<td>double을 객체 R로 매핑</td>
</tr>
<tr>
<td>IntFunction&lt;<span>R</span>&gt;</td>
<td>R apply(int value)</td>
<td>int를 객체 R로 매핑</td>
</tr>
<tr>
<td>IntToDoubleFunction</td>
<td>double applyAsDouble(int value)</td>
<td>int를 double로 매핑</td>
</tr>
<tr>
<td>IntToLongFunction</td>
<td>long applyAsLong(int value)</td>
<td>int를 long으로 매핑</td>
</tr>
<tr>
<td>LongToDoubleFunction</td>
<td>double applyAsDouble(long value)</td>
<td>long을 double로 매핑</td>
</tr>
<tr>
<td>LongToIntFunction</td>
<td>int applyAsInt(long value)</td>
<td>long을 int로 매핑</td>
</tr>
<tr>
<td>ToDoubleBiFunction&lt;T,U&gt;</td>
<td>double applyAsDouble(T t, U u)</td>
<td>객체 T와 U를 double로 매핑</td>
</tr>
<tr>
<td>ToDoubleFunction&lt;<span>T</span>&gt;</td>
<td>double applyAsDouble(T value)</td>
<td>객체 T를 double로 매핑</td>
</tr>
<tr>
<td>ToIntBiFunction&lt;T,U&gt;</td>
<td>int applyAsInt(T t, U u)</td>
<td>객체 T와 U를 int로 매핑</td>
</tr>
<tr>
<td>ToIntFunction&lt;<span>T</span>&gt;</td>
<td>Int applyAsInt(T value)</td>
<td>객체 T를 int로 매핑</td>
</tr>
<tr>
<td>ToLongBiFunction&lt;T,U&gt;</td>
<td>long applyAsLong(T t, U u)</td>
<td>객체 T와 U를 long으로 매핑</td>
</tr>
<tr>
<td>ToLongFunction&lt;<span>T</span>&gt;</td>
<td>long applyAsLong(T value)</td>
<td>객체 T를 long으로 매핑</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>Function 인터페이스를 구현</li>
</ul>
<pre><code class="language-java">public &lt;T, R&gt; List&lt;R&gt; map(List&lt;T&gt; list, Function&lt;T, R&gt; f){ 
    List&lt;R&gt; result = new ArrayList&lt;&gt;();
    for(T t: list){
        result.add(f.apply(t)); 
    }
    return result; 
}</code></pre>
<ul>
<li>Function의 apply 메서드를 구현한 람다식</li>
</ul>
<pre><code class="language-java">List&lt;Integer&gt; resultlist = f.map( 
    Arrays.asList(&quot;람다&quot;, &quot;홍길동&quot;, &quot;함수형 인터페이스&quot;), 
    (String s) -&gt; s.length()
);

[결과] 
[2, 3, 9]</code></pre>
<br/>

<h3 id="operator-함수형-인터페이스">Operator 함수형 인터페이스</h3>
<ul>
<li>Argument와 Return 값이 모두 있는 추상 메서드를 가지며, 주로 Argument 값을 연산하고 그 결과를 리턴할 경우에 사용한다.</li>
</ul>
<p>Argument 값 → Operator → 리턴 값</p>
<pre><code class="language-java">UnaryOperator&lt;String&gt; u = str -&gt; str + &quot; operator&quot;; 
String result = u.apply(&quot;test&quot;); // test operator</code></pre>
<ul>
<li>Argument와 개수에 따라 분류할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>인터페이스명</th>
<th>추상메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>BinaryOperator&lt;<span>T</span>&gt;</td>
<td>BiFunction&lt;T,U,R&gt;의 하위 인터페이스</td>
<td>T와 U를 연산한 후 R 리턴</td>
</tr>
<tr>
<td>UnaryOperator&lt;<span>T</span>&gt;</td>
<td>Function&lt;T,R&gt;의 하위 인터페이스</td>
<td>T를 연산한 후 R 리턴</td>
</tr>
<tr>
<td>DoubleBinaryOperator</td>
<td>double applyAsDouble(double, double)</td>
<td>두 개의 double 연산</td>
</tr>
<tr>
<td>DoubleUnaryOperator</td>
<td>double applyAsDouble(double)</td>
<td>한 개의 double 연산</td>
</tr>
<tr>
<td>IntBinaryOperator</td>
<td>int applyAsInt(int, int)</td>
<td>두 개의 int 연산</td>
</tr>
<tr>
<td>IntUnaryOperator</td>
<td>int applyAsInt(int)</td>
<td>한 개의 int 연산</td>
</tr>
<tr>
<td>LongBinaryOperator</td>
<td>long applyAsLong(long, long)</td>
<td>두 개의 long 연산</td>
</tr>
<tr>
<td>LongUnaryOperator</td>
<td>long applyAsLong(long)</td>
<td>한 개의 long 연산</td>
</tr>
</tbody></table>
<br/>

<h3 id="supplier-함수형-인터페이스">Supplier 함수형 인터페이스</h3>
<ul>
<li>Supplier&lt;<span>T</span>&gt; 인터페이스는 추상 메서드 get()을 람다의 Body 영역에 정의하며, 인수 없이 T 객체를 리턴한다.</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
public interface Supplier&lt;T&gt;{
    T get();
}</code></pre>
<pre><code class="language-java">Supplier&lt;String&gt; s = () -&gt; &quot;supplier&quot;;
String result = s.get(); //supplier</code></pre>
<ul>
<li>리턴 타입에 따라 분류할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>인터페이스명</th>
<th>추상메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Supplier&lt;<span>T</span>&gt;</td>
<td>T get()</td>
<td>객체를 리턴</td>
</tr>
<tr>
<td>BooleanSupplier</td>
<td>boolean getAsBoolean()</td>
<td>boolean 값을 리턴</td>
</tr>
<tr>
<td>DoubleSupplier</td>
<td>double getAsDouble()</td>
<td>double 값을 리턴</td>
</tr>
<tr>
<td>IntSupplier</td>
<td>int getAsInt()</td>
<td>int 값을 리턴</td>
</tr>
<tr>
<td>LongSupplier</td>
<td>long getAsLong()</td>
<td>long 값을 리턴</td>
</tr>
</tbody></table>
<br/>

<h3 id="함수형-인터페이스-예제">함수형 인터페이스 예제</h3>
<pre><code class="language-java">static &lt;T&gt; List&lt;T&gt; doSomething(Function&lt;T, T&gt; f, List&lt;T&gt; list) { 
    System.out.print(&quot;Function : &quot;);
    List&lt;T&gt; newList = new ArrayList&lt;T&gt;(list.size());
    for(T i : list) {
        newList.add(f.apply(i));
    }
    return newList; 
}
static &lt;T, R&gt; List&lt;R&gt; doSomething2(Function&lt;T, R&gt; f, List&lt;T&gt; list) { 
    System.out.print(&quot;Function : &quot;);
    List&lt;R&gt; newList = new ArrayList&lt;R&gt;(list.size());
    for(T i : list) {
        newList.add(f.apply(i)); // i/10
    }
    return newList; 
}
static &lt;T&gt; void printEvenNum(Predicate&lt;T&gt; p, Consumer&lt;T&gt; c, List&lt;T&gt; list) { 
    System.out.print(&quot;Predicate, Consumer : [&quot;);
    for(T i : list) {
        // i%2==0
        if(p.test(i)) // boolean return (짝수 여부)
            c.accept(i); // T 객체 소비 System.out.print(i+&quot;, &quot;) 
    }
    System.out.println(&quot;]&quot;);
}
static &lt;T&gt; void makeRandomList(Supplier&lt;T&gt; s, List&lt;T&gt; list) { 
    for(int i=0;i&lt;10;i++) {
        list.add(s.get()); // T 객체 리턴
    }
}</code></pre>
<pre><code class="language-java">public static void main(String[] args) {
    // Supplier&lt;T&gt; -&gt; T get()
    Supplier&lt;Integer&gt; s = ()-&gt; (int)(Math.random()*100)+1; // 랜덤 값
    // Consumer&lt;T&gt; -&gt; void accept(T t)
    Consumer&lt;Integer&gt; c = i -&gt; System.out.print(i+&quot;, &quot;);
    // Predicate&lt;T&gt; -&gt; boolean test(T t)
    Predicate&lt;Integer&gt; p = i -&gt; i%2==0; // 짝수 구하기
    // Function&lt;T, R&gt; -&gt; R apply(T t)
    Function&lt;Integer, Integer&gt; f = i -&gt; i/10;
    // 문자열 표시
    Function&lt;Integer, String&gt; f2 = i -&gt; &quot;\&quot;&quot; + Integer.toString(i/10) + &quot;\&quot;&quot;;

    List&lt;Integer&gt; list = new ArrayList&lt;&gt;();
    makeRandomList(s, list); // Supplier 
    System.out.println(&quot;Supplier : &quot; + list);
    printEvenNum(p, c, list); // Predicate, Consumer 
    List&lt;Integer&gt; newList = doSomething(f, list); // function 
    System.out.println(newList);
    List&lt;String&gt; newList2 = doSomething2(f2, list);
    System.out.println(newList2); 
}

[결과]
Supplier : [86, 10, 92, 60, 32, 29, 3, 44, 29, 94] 
Predicate, Consumer : [86, 10, 92, 60, 32, 44, 94, ] 
Function:[8,1,9,6,3,2,0,4,2,9]
Function : [&quot;8&quot;, &quot;1&quot;, &quot;9&quot;, &quot;6&quot;, &quot;3&quot;, &quot;2&quot;, &quot;0&quot;, &quot;4&quot;, &quot;2&quot;, &quot;9&quot;]</code></pre>
<hr>
<h2 id="스트림stream-개요">스트림(Stream) 개요</h2>
<ul>
<li>스트림(Stream)은 Java 8 부터 java.util.stream이라는 추가 패키지를 통해 제공되는 API로 요소(Element)를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이며, 선언형(질의로 데이터를 처리)으로 컬렉션 데이터를 처리할 수 있다.</li>
<li>스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 병렬로 처리할 수 있다.</li>
<li>제공 기능<ul>
<li>스트림은 요소를 저장하지 않는다. 컴퓨팅 작업의 파이프라인(pipeline)을 통해 데이터 구조, 배열 또는 I/O 채널과 같은 데이터 소스이 요소를 전달한다.</li>
<li>스트림은 본질적으로 기능적이며, 스트림에서 수행되는 작업은 데이터 소스를 수정하지 않는다. 예를 들어 컬렉션에서 얻어온 스트림을 필터링하면 소스 컬렉션에서 요소를 제거하는 대신 필터링된 새 스트림이 생성된다.</li>
<li>스트림의 요소는 스트림의 실행동안 한번만 접근된다. Iterator와 마찬가지로 소스의 동일한 요소를 다시 접근하려면 새 스트림을 생성해야 한다.</li>
</ul>
</li>
<li>스트림을 사용하여 하나의 데이터 구조에서 다른 데이터 구조로 filter, collect, print 및 변환할 수 있다.</li>
</ul>
<pre><code class="language-java">//기존의 for문을 이용한 방식
int count = 0;
for(String w : words){
    if(w.length() &gt; 12) count++;
}

//Stream을 이용한 방식
long count = words.stream()
        .filter(w -&gt; w.length()&gt;12)
        .count();

//병렬처리를 수행하는 Stream을 이용한 방식
long count = words.parallelStram()
        .filter(w -&gt; w.length()&gt;12)
        .count();</code></pre>
<ul>
<li>Java 8 이전 코드에서 Stream API를 이용한 코드를 비교해보면 간결하고 짧은 코드로 쉽게 구현할 수 있다.</li>
<li>칼로리를 기준으로 요리를 정렬하는 자바 코드이다.</li>
</ul>
<pre><code class="language-java">List&lt;Dish&gt; lowCaloricDishes = new ArrayList&lt;&gt;(); 
for(Dish d: dishes){
    if(d.getCalories() &gt; 400){ 
        lowCaloricDishes.add(d);
    } 
}
List&lt;String&gt; lowCaloricDishesName = new ArrayList&lt;&gt;(); 
Collections.sort(lowCaloricDishes, new Comparator&lt;Dish&gt;() { //익명클래스compare로정렬
    public int compare(Dish d1, Dish d2){
        return Integer.compare(d1.getCalories(), d2.getCalories());
    } 
});
for(Dish d: lowCaloricDishes){ 
    lowCaloricDishesName.add(d.getName());
}</code></pre>
<pre><code class="language-java">List&lt;String&gt; lowCaloricDishesName = 
    dishes.stream()
                .filter(d -&gt; d.getCalories() &gt; 400) 
                .sorted(comparing(Dish::getCalories)) 
                .map(Dish::getName) 
                .collect(toList());</code></pre>
<ul>
<li>filet, sorted, map, collect와 같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들 수 있다.</li>
<li>fileter 메서드의 결과 → sorted 메서드 결과 → map 메서드 결과 → collection으로 연결되어 수행</li>
</ul>
<br/>

<h3 id="스트림-필터링-수집-예">스트림 필터링 수집 예</h3>
<ul>
<li>스트림을 사용하여 데이터를 필터링 할 수 있다. 코드가 최적화되고 빠른 실행을 제공한다.</li>
</ul>
<pre><code class="language-java">public class StreamFilterCollectEx1 { 
    public static void main(String[] args) {

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

        list.add(new LambdaProduct(1,&quot;삼성&quot;,17000)); 
        list.add(new LambdaProduct(3,&quot;아이폰&quot;,65000)); 
        list.add(new LambdaProduct(2,&quot;소니&quot;,25000)); 
        list.add(new LambdaProduct(4,&quot;노키아&quot;,15000)); 
        list.add(new LambdaProduct(6,&quot;레노바&quot;,19000));;

        List&lt;Integer&gt; productPriceList = list.stream() 
                        .filter(p -&gt; p.price &gt; 20000) // 데이터 필터링 
                        .map(p -&gt; p.price) // 가격 얻어오기 
                        .collect(Collectors.toList());// collecting list
        System.out.println(productPriceList); 
    }
}

[결과]
[65000, 25000]</code></pre>
<br/>

<h3 id="스트림-컬렉션의-reduce-메서드">스트림 컬렉션의 reduce() 메서드</h3>
<ul>
<li>일련의 입력 요소를 가져와 반복 작업을 통해 단일 요약 결과를 결합할 수 있다. 예를 들면 숫자의 합을 찾거나 목록에서 요소를 누적시킨다.</li>
</ul>
<pre><code class="language-java">public class StreamReduceEx01 {
    public static void main(String[] args) {
        List&lt;LambdaProduct&gt; productsList = new ArrayList&lt;&gt;(); 
        productsList.add(new LambdaProduct(1,&quot;삼성&quot;,17000)); 
        productsList.add(new LambdaProduct(3,&quot;아이폰&quot;,65000)); 
        productsList.add(new LambdaProduct(2,&quot;소니&quot;,25000)); 
        productsList.add(new LambdaProduct(4,&quot;노키아&quot;,15000)); 
        productsList.add(new LambdaProduct(6,&quot;레노바&quot;,19000));

        // 데이터 필터링의 간결한 접근 방식
        int totalPrice = productsList.stream()
                        .map(product-&gt;product.price)
                        .reduce(0,(sum, price)-&gt; sum + price); // 누적 가격 
        System.out.println(totalPrice);
    }
}

[결과] 
141000</code></pre>
<br/>

<h3 id="list를-map으로-변환">List를 Map으로 변환</h3>
<ul>
<li>예제</li>
</ul>
<pre><code class="language-java">public class StreamConverListToMapEx01{ 
    public static void main(String[] args) {
        List&lt;LambdaProduct&gt; productsList = new ArrayList&lt;&gt;(); 
        productsList.add(new LambdaProduct(1,&quot;삼성&quot;,17000)); 
        productsList.add(new LambdaProduct(3,&quot;아이폰&quot;,65000)); 
        productsList.add(new LambdaProduct(2,&quot;소니&quot;,25000)); 
        productsList.add(new LambdaProduct(4,&quot;노키아&quot;,15000)); 
        productsList.add(new LambdaProduct(6,&quot;레노바&quot;,19000));

        // Product List를 Map으로 변환 
        Map&lt;Integer,String&gt; productPriceMap =
                productsList.stream() 
                            .collect(Collectors.toMap(p-&gt;p.id, p-&gt;p.name));
        System.out.println(productPriceMap); 
    }
}

[결과]
{1=삼성, 2=소니, 3=아이폰, 4=노키아, 6=레노바}</code></pre>
<br/>

<h3 id="리덕션과-파이프라인">리덕션과 파이프라인</h3>
<ul>
<li>리덕션(Reduction)은 대량의 데이터를 가공해 축소 하는 것이며 데이터의 합계, 평균값, 카운팅, 최대값, 최소값을 구한다.</li>
<li>파이프라인(Pipeline)<ul>
<li>여러 개의 스트림이 연결되어 있는 구조이다.</li>
<li>파이프라인에서 최종 처리를 제외하고 모두 중간 처리 스트림이다.</li>
</ul>
</li>
<li>스트림의 처리는 중간 처리와 최종 처리로 구분된다.<ul>
<li>중간처리: 매핑, 필터링, 소팅을 수행</li>
<li>최종처리: 반복, 카운팅, 평균, 총합 등의 집계 처리를 수행</li>
</ul>
</li>
</ul>
<br/>

<h3 id="스트림-특징">스트림 특징</h3>
<ul>
<li>스트림은 Iterator와 비슷한 역할의 반복자이다.</li>
<li>스트림 API는 메서드들을 연경하여, 복잡한 연산을 처리하는 로직을 쉽고, 유연하게 작성할 수 있게 도와준다.</li>
<li>여러 연산을 파이프라인으로 연결해도 가독성이 유지되며 filter 같은 연산은 고수준 빌딩 블록으로 이루어져 있으므로 특정 스레딩 모델에 제한되지 않고 자유롭게 어떤 상황에서든 사용할 수 있어 (멀티코어 아키텍처를 최대한 투명하게 활용할 수 있게 구현되어 있다) 데이터 처리 과정을 병렬화하면 스레드와 락에 대해 걱정할 필요가 없다.</li>
</ul>
<pre><code class="language-java">//순차 필터링
long count = values.stream().filter().count();
//병렬 필터링
long count1 = values.parallelStream().filter().count();

1612104
순차 필터링 소요 시간: 168 ms

1612104
병렬 필터링 소요 시간: 36 ms</code></pre>
<br/>

<ul>
<li>스트림의 중간 연산</li>
</ul>
<table>
<thead>
<tr>
<th>연산</th>
<th>형식</th>
<th>반환 형식</th>
</tr>
</thead>
<tbody><tr>
<td>filter</td>
<td>중간 연산</td>
<td>Stream&lt;<span>T</span>&gt;</td>
</tr>
<tr>
<td>map</td>
<td>중간 연산</td>
<td>Stream&lt;<span>R</span>&gt;</td>
</tr>
<tr>
<td>limit</td>
<td>중간 연산</td>
<td>Stream&lt;<span>T</span>&gt;</td>
</tr>
<tr>
<td>sorted</td>
<td>중간 연산</td>
<td>Stream&lt;<span>T</span>&gt;</td>
</tr>
<tr>
<td>distinct</td>
<td>중간 연산</td>
<td>Stream&lt;<span>T</span>&gt;</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>스트림의 최종 연산</li>
</ul>
<table>
<thead>
<tr>
<th>연산</th>
<th>형식</th>
<th>반환 형식</th>
<th>목적</th>
</tr>
</thead>
<tbody><tr>
<td>forEach</td>
<td>최종 연산</td>
<td>void</td>
<td>스트림의 각 요소를 소비하면서 람다를 적용한다.</td>
</tr>
<tr>
<td>count</td>
<td>최종 연산</td>
<td>long (generic)</td>
<td>스트림의 요소 개수를 반환한다.</td>
</tr>
<tr>
<td>collect</td>
<td>최종 연산</td>
<td></td>
<td>스트림을 리듀스에서 리스트, 맵, 합수 형식의 컬렉션을 만든다.</td>
</tr>
</tbody></table>
<br/>

<h3 id="스트림-종류">스트림 종류</h3>
<ul>
<li>java.util.stream 패키지의 API로 BaseStream 인터페이스에 공통 메서드가 정의되어 있다.</li>
<li>Stream은 객체 요소를 처리하고 IntStream, DoubleStream 등은 기본형인 int, long, double 요소를 처리할 때 이용한다.</li>
<li>Stream은 컬렉션과 배열에서 주로 얻어낸다.</li>
</ul>
<table>
<thead>
<tr>
<th>리턴 타입</th>
<th>메서드(매개 변수)</th>
<th align="center">소스</th>
</tr>
</thead>
<tbody><tr>
<td>Stream&lt;<span>T</span>&gt;</td>
<td>java.util.Collection.stream(<br/>java.util.Collection.parallelStream()</td>
<td align="center"></td>
</tr>
<tr>
<td>Stream&lt;<span>T</span>&gt;<br/>IntStream<br/>LongStream<br/>DoubleStream</td>
<td>Arrays.stream(T[ ]), Stream.of(T[ ])<br/>Arrays.stream(int[ ]), IntStream.of(int[ ])<br/>Arrays.stream(long[ ]),<br/>LongStream.of(long[ ])<br/>Arrays.stream(double[ ]),<br/>DoubleStream.of(double[ ])</td>
<td align="center">배열</td>
</tr>
<tr>
<td>IntStream</td>
<td>IntStream.range(int, int)</td>
<td align="center"></td>
</tr>
<tr>
<td>IntStream.rangeClosed(int, int)</td>
<td>int 범위</td>
<td align="center"></td>
</tr>
<tr>
<td>LongStream</td>
<td>LongStream.range(long, long)<br/>LongStream.rangeClosed(long, long)</td>
<td align="center">long 범위</td>
</tr>
<tr>
<td>Stream&lt;Path&gt;</td>
<td>Files.find(Path, int, BiPredicate, FileVisitOption)<br/>Files.list(Path)</td>
<td align="center">디렉토리</td>
</tr>
<tr>
<td>Stream&lt;String&gt;</td>
<td>Files.lines(Path, Charset)<br/>BufferedReader.lines()</td>
<td align="center">파일</td>
</tr>
<tr>
<td>DoubleStream<br/>IntStream<br/>LongStream</td>
<td>Random.doubles(…)<br/>Random.ints()<br/>Random.longs()</td>
<td align="center">랜덤 수</td>
</tr>
</tbody></table>
<ul>
<li>다양한 방식의 스트림 생성 방법을 제공한다.</li>
<li>Collection: 콜렉션객체.stream(), parallelStream()</li>
<li>Files: Stream&lt;String&gt; Files.lines()</li>
<li>Arrays: Arrays.stream(*)</li>
<li>Stream.of(*)</li>
</ul>
<br/>

<h3 id="컬렉션과-스트림">컬렉션과 스트림</h3>
<ul>
<li>컬렉션과 스트림은 모두 연속된 요소 형식의 값을 저장하는 자료 구조의 인터페이스를 제공한다.</li>
<li>둘 다 순서에 따라 순차적으로 요소에 접근한다.</li>
<li>컬렉션<ul>
<li>각 계산식을 만날 때마다 데이터가 계산되며, 자료 구조이므로 데이터에 접근, 변경, 저장 같은 연산이 주 기능이다.</li>
<li>현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조로, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 하고 메모리 사용량이 늘어난다.</li>
</ul>
</li>
<li>스트림<ul>
<li>최종 연산이 실행될 때에 데이터가 계산되며, filter, sorted, map처럼 계산식(람다)를 표현하는 것이 주요 관심사로 계산을 JVM에 위임한다.</li>
<li>요청할 때만 요소를 계산하는 고정된 자료구조로 스트림에 요소를 추가하거나 제거할 수 없다.</li>
<li>스트림의 탐색된 요소는 소비되며, 한 번 탐색한 요소를 다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야 한다.</li>
</ul>
</li>
<li>데이터 계산이 언제냐에 따라 컬렉션과 스트림으로 나뉘는 가장 큰 차이가 있다.</li>
</ul>
<br/>

<h3 id="외부-반복과-내부-반복">외부 반복과 내부 반복</h3>
<ul>
<li>외부 반복(External Iterator): 개발자가 직접 컬렉션 요소를 반복해서 처리하는 방식</li>
<li>내부 반복(Internal Iterator): 컬렉션 내부에서 요소를 반복시키고, 개발자는 요소별로 처리해야 할 코드만 제공하는 방식</li>
</ul>
<br/>

<ul>
<li>외부 반복 코드를 스트림을 이용한 내부 반복 코드로 변경</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; highCaloricDish = new ArrayList&lt;&gt;();
Iterator&lt;String&gt; iterator = menu.iterator();
while(iterator.hasNext()){
    Dish dish = iterator.next();
    if(dish.getCalories() &gt; 300){
        highCaloricDish.add(dish.getName());
    }
}</code></pre>
<pre><code class="language-java">List&lt;String&gt; highCaloricDish =
        menu.stream()
                .flter(d -&gt; d.getCalories() &gt; 300)
                .collect(toList());</code></pre>
<br/>

<h3 id="스트림의-연산">스트림의 연산</h3>
<ul>
<li>연결할 수 있는 스트림 연산을 중간 연산(intermediate operation)</li>
<li>스트림을 닫는 연산을 최종 연산(terminal operation)</li>
<li>filter나 sorted와 같은 중간 연산은 다른 스트림을 반환하기 때문에 여러 중간 연산을 연결해서 질의를 만들 수 있다.</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; names =
                menu.stream() //객체 리스트에서 스트림 얻기
                        .filter(dish -&gt; dish.getCalories() &gt; 300)
                        .map(Dish::getName) //중간 연산
                        .limit(3) //중간 연산
                        .collect(toList()); //스트림을 리스트로 변환</code></pre>
<br/>

<h3 id="filter">Filter</h3>
<ul>
<li>중간 연산으로 Predicate(boolean을 반환하는 함수)를 인수로 받아서 Predicate와 일치하는 모든 요소를 포함한 스트림을 반환한다.</li>
<li>Predicate는 함수형 인터페이스라서 람다식을 전달할 수 있다.</li>
<li>아래 예와 같이 채식 요리인지 확인하는 메서드 참조를 통해 필터링해서 메뉴를 만들 수 있다.</li>
<li>Stream filter() method signature</li>
</ul>
<pre><code class="language-java">Stream&lt;T&gt; filter(Predicate&lt;? super T&gt; predicate);

List&lt;Dish&gt; vegetarianMenu =
                menu.stream()
                        .filter(Dish::isVegetarian)
                        .collect(toList());</code></pre>
<br/>

<h3 id="distinct">distinct</h3>
<ul>
<li>중간 연산으로 고유한 유일 값을 반환하며 유일한 값인지는 스트림에서 만든 객체의 hashCode, equals로 결정된다.</li>
</ul>
<pre><code class="language-java">List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 1, 3, 2, 4);
                numbers.stream()
                        .filter(i -&gt; i%2 == 0)
                        .distinct()
                        .forEach(System.out::println);</code></pre>
<br/>

<h3 id="limit">limit</h3>
<ul>
<li>중간 연산으로 지정된 값 이하의 크기를 갖는 새로운 스트림을 반환한다.</li>
<li>아래 예제는 filter predicate와 일치하는 처음 3개 요소를 반환한다.</li>
</ul>
<pre><code class="language-java">List&lt;Dish&gt; dishes =
    specialMenu.stream()
                        .filter(dish -&gt; dish.getCalories() &gt; 300)
                        .limit(3)
                        .collect(toList());</code></pre>
<br/>

<h3 id="map">map</h3>
<ul>
<li>중간 연산으로 특정 데이터를 선택하는 기능을 제공하며, 스트림은 함수를 인수로 받는 map 메서드를 지원한다.</li>
<li>함수를 적용한 결과가 새로운 요소로 매핑되는데 기존의 값이 아닌 새로운 버전을 만드는 매핑 과정과 같다.</li>
<li>예를 들면 아래의 예제에서 getName은 문자열을 반환하므로 map 메서드의 스트림은 Stream&lt;String&gt; 타입이 된다.</li>
<li>요리명의 길이를 추출하고 싶은 경우에는 아래 예제처럼 map 메서드를 chaining할 수 있다.</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; dishNames = menu.stream()
                        .map(Dish::getName)
                        .collect(toList());

List&lt;Integer&gt; dishNames = menu.stream()
                        .map(Dish::getName)
                        .map(String::length)
                        .collect(toList());</code></pre>
<br/>

<h3 id="flatmap">flatMap</h3>
<ul>
<li>map 메서드를 리스트에서 고유 문자로 이루어진 리스트로 반환하는 기능을 개발할 때 사용한다.</li>
<li>{”Hello”, “World”] 리스트가 있다면 결과로 [”H”, “e”, “l”, “o”, “W”, “r”, “d”] 리스트가 반환된다.</li>
<li>우리가 원하는 반환 스트림은 배열이 아닌 Stream&lt;String&gt; 타입이다.</li>
<li>map을 이용하면 배열 형태가 되는데 이런 중첩 구조를 한 단계 제거하기 위한 중간 연산이 필요하고, 이것이 flatmap이다.</li>
<li>flatMap은 스트림 평면화라는 이름으로도 불리며 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다. 즉 map(Arrays::Stream)과 달리 flatMap은 하나의 평면화된 스트림을 반환한다. (중복된 스트림을 1차원으로 평면화)</li>
<li>스트림의 각 값을 다른 스트림으로 생성 ⇒ 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행</li>
</ul>
<pre><code class="language-java">// [[a], [b]]
List&lt;List&lt;String&gt;&gt; list = Arrays.asList(Arrays.asList(&quot;a&quot;), Arrays.asList(&quot;b&quot;));

// [a, b]
List&lt;String&gt; flatList = list.stream() 
                    .flatMap(Collection::stream)
          .collect(Collectors.toList());</code></pre>
<ul>
<li>flatMap을 이용한 단어 리스트의 고유 문자 찾기 기능 구현</li>
<li>“Hello”와 “World”가 split 메서드에 의해 분리되고, map에 의해 [”H”,”e”,”l”,”l”,”o”]와 [”W”,”o”,”r”,”l”,”d”]로 변환된다.</li>
<li>Arrays:stream (Arrays.stream(T[] array))을 사용해 [”H”,”e”,”l”,”l”,”o”]와 [”W”,”o”,”r”,”l”,”d”]를 각각 Stream&lt;String&gt;으로 변환한다.</li>
<li>flatMap()을 사용하여 여러 개의 Stream&lt;String&gt;을 1개의 Stream&lt;String&gt;으로 평면화하여 ”H”,”e”,”l”,”l”,”o”,”W”,”o”,”r”,”l”,”d”]가 된다.</li>
<li>distinct()로 중복된 소스(”l”,”o”)가 제거된 후 [”H”,”e”,”l”,”o”,”W”,”r”,”d”]가 collect(toList())로 수집된다.</li>
<li>flatMap(Arrays::stream)은 String 리스트를 스트림으로 변환하는 것이 아닌 String을 감싸는 구성요소를 만들어주는 것이다.</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; words = Arrays.asList(&quot;Hello&quot;, &quot;World&quot;);
List&lt;String&gt; uniqueCharacters =
            words.stream()
                    .map(word -&gt; word.split(&quot;&quot;)) //단어를 개별 문자 배열로 반환
                    .flatMap(Arrays::stream) //생성된 스트림을 하나의 스트림으로 평면화
                    .distinct()
                    .collect(toList());

[결과]
[H, e, l, o, W, r, d]</code></pre>
<br/>

<h3 id="anymatch">anyMatch</h3>
<ul>
<li>최종 연산으로 Predicate가 적어도 한 요소와 일치하는지 확인한다.</li>
<li>아래 예제는 menu에 채식 메뉴가 있는지 확인하는 예이다.</li>
</ul>
<pre><code class="language-java">if(menu.stream().anyMatch(Dish:isVegetarian)){
    System.out.println(&quot;Vegetarian menu&quot;);
}</code></pre>
<br/>

<h3 id="allmatch">allMatch</h3>
<ul>
<li>최종 연산으로 Predicate가 모든 요소와 일치하는지 확인한다.</li>
<li>아래 예제는 모든 메뉴가 1000칼로리 이하인지 확인한다.</li>
</ul>
<pre><code class="language-java">boolean isHealthy = menu.stream()
    .allMatch(dish -&gt; dish.getCalories() &lt; 1000);</code></pre>
<br/>

<h3 id="nonematch">noneMatch</h3>
<ul>
<li>최종 연산으로 Predicate가 모든 요소와 불일치하는지 확인한다.</li>
<li>아래 예제는 모든 메뉴가 1000칼로리 이하가 아닌지 확인한다.</li>
</ul>
<pre><code class="language-java">boolean isHealthy = menu.stream()
    .noneMatch(dish -&gt; dish.getCalories() &gt;= 1000);</code></pre>
<br/>

<h3 id="reduce">reduce</h3>
<ul>
<li>최종 연산으로 모든 스트림 요소를 처리해서 값으로 반환하는 연산을 리듀싱 연산이라고 한다.</li>
<li>리듀스 연산을 이용해서 “메뉴의 모든 칼로리의 합계를 구하기”와 같은 복잡한 질의를 처리할 수 있다.</li>
<li>아래 연산 과정을 보면 스트림이 하나의 값으로 줄어들 때까지 람다는 각 요소를 반복해서 조합한다.</li>
</ul>
<br/>

<ul>
<li>reduct를 이용해서 최대값, 최소값을 구할 수 있다.</li>
</ul>
<pre><code class="language-java">Optional&lt;Integer&gt; max = numbers.stream().reduce(Integer::max);
Optional&lt;Integer&gt; min = numbers.stream().reduce(Integer::min);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java (Date와 정규식, Collection API, 제너릭스/열거형/어노테이션, Thread)]]></title>
            <link>https://velog.io/@ju-ei8ht/wil-java5</link>
            <guid>https://velog.io/@ju-ei8ht/wil-java5</guid>
            <pubDate>Thu, 26 Jan 2023 11:37:48 GMT</pubDate>
            <description><![CDATA[<p><strong>기간</strong>
2023/1/16 ~ 2023/1/20</p>
<p><strong>수업</strong>
실시간 ZOOM 수업</p>
<hr>
<h3 id="day-14date와-정규식">Day 14(Date와 정규식)</h3>
<p><a href="https://velog.io/@ju-ei8ht/date-regex">Date와 정규식</a></p>
<p>자바에 대한 지식이 2014년에 멈춰있었던 시절, 날짜 관련 알고리즘을 풀었을 때 당연하게도 Date date = new Date();해서 쓰고 formatter가 다 뭐야... 일일히 배열로 Monday -&gt; 월요일로 바꿔줬던 기억이...ㅎ
나중에 검색해보고 뭐야 한글 포맷이 지원이 된다고??? 순서대로 값 입력 안 해줘도 된다고?? 하면서 놀랐던 기억이 난다...ㅎㅎ 이제 Date 안 쓰고 LocalDate... 뭐가 다른 거지... 했었는데</p>
<p>특히 정규식은 진짜 처음 봤을 때 뭐야 이거 암혼가... 하고 해석할(ㅋㅋ) 엄두도 못 내고 그냥 필요하면 검색해서 가져다 썼었는데... 솔직히 validate 할 때 말곤 잘 쓰지 않기도 했고...</p>
<hr>
<h3 id="day-15collection-api">Day 15(Collection API)</h3>
<p><a href="https://velog.io/@ju-ei8ht/collection-api">Collection API</a></p>
<p>아 나왔네 컬렉션... 배열만 알았다가 해시 알고 놀랐던 기억이 난다.
알고리즘에서 중복 제거? HashMap 아니면 (value 따로 필요 없으면) HashSet
Set인데 입력 순서 그대로 출력해야 한다? LinkedHashSet...
Iterator는 솔직히 그냥 for문으로 쓰는 경우가 더 많았다. index를 하나하나 써야하면 Iterator도 괜찮지만 건너 뛰는 경우도 많고... 아니 사실 Iterator 써도 될 때도 for each 써버림...
그래서 강의 시간에 Iterator랑 for문 쓰는 거 차이가 있냐는 질문 나왔을 때 귀가 기울여졌는데 특별히 차이는 없지만 Iterator는 hasNext로 하나하나 넘어가는 반면에 for문은 건너뛰기가 가능하니까 필요에 따라 쓰면 된다는 대답을 들었다. 뭐 그거야 그렇겠지... 그래도 이왕 Iterator가 해시의 element를 탐색하는 용도로 만들어졌으니까 쓰는 게 좋겠다.</p>
<hr>
<h3 id="day-16제너릭스열거형어노테이션">Day 16(제너릭스/열거형/어노테이션)</h3>
<p><a href="https://velog.io/@ju-ei8ht/generics-enumeration-annotation">제너릭스/열거형/어노테이션</a></p>
<p>저번 부트캠프에서 자바 이론은 건너뛰고 스프링부트로 프로젝트 하고 면접 대비용으로 &#39;제네릭이 뭔가요?&#39;라는 질문을 듣고 처음 제네릭을 알았다... 제네릭은 말하자면 데이터 타입이다. 자바는 변수를 선언할 때 데이터 타입을 함께 선언하니까 컴파일 시에 해당 element에 대해서 일일히 데이터 타입을 지정해주지 않아도 데이터 타입을 읽을 수 있다. 예를 들면 javascript에서는 변수에 대해 데이터 타입을 지정해주지 않아도 돼서 데이터 타입이 다름에도 같은 변수명을 쓸 수 있다던지... 그래서 const가 나왔다던가(상수 같은 개념으로 지금 말하는 제네릭과는 좀 다른 얘기긴 하지만 아무튼)
처음에는 아 그럼 javascript가 더 편한 거 아닌가? 했지만 사실 사용하다 보면 일일히 데이터타입을 지정하는 게 귀찮다가도 지정하지 않았으면 되게 헷갈렸겠다 싶음. 특히 나는 변수명 지정할 때마다 고뇌를 하는 사람으로써... 데이터 타입마저 구분이 안 됐다면......ㅎ</p>
<hr>
<h3 id="day-17thread">Day 17(Thread)</h3>
<p><a href="https://velog.io/@ju-ei8ht/thread">Thread</a></p>
<p>아 쓰레드... 강의 들으면서도 뭔 말인지...
이건 시간 내서 다시 한 번 정리를 해야 할 것 같다.
결국 &#39;동기화&#39;가 문제란 건제... JPA에서도 동기화 문제... 그러니까 DB 동시성 문제를 듣긴 했었는데 예시로 든 게 관리자A와 관리자B가 하나의 게시글을 동시에 수정했을 경우였다. 그럼 어떤 글을 최종적으로 반영해야 하는가... 기본적으로는 마지막에 수정된 게시글이 반영되지만 경우에 따라선 처음 것에서 변경하지 않도록 할 필요가 있다, 뭐 그런 거였는데...... 사실 쓰레드는 현 단계에서 구체적으로 실습해 볼 기회가 없어서... 또 뭐 OS마다 쓰레드를 관리하는 스케쥴링이 다르기도 하고? 어렵고만...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스레드(Thread)]]></title>
            <link>https://velog.io/@ju-ei8ht/thread</link>
            <guid>https://velog.io/@ju-ei8ht/thread</guid>
            <pubDate>Thu, 26 Jan 2023 11:01:30 GMT</pubDate>
            <description><![CDATA[<h2 id="process공장--thread일꾼">Process(공장) &amp; Thread(일꾼)</h2>
<ul>
<li>프로세스는 프로그램 수행하는 데 필요한 데이터, 메모리 등의 자원 그리고 스레드로 구성된다.</li>
<li>프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 바로 스레드이다.</li>
<li>모든 프로세스에는 최소한 하나 이상의 스레드가 존재하며, 둘 이상의 스레드를 가진 프로세스를 멀티스레드 프로세스(multi-thread process)라고 한다. (하나일 경우 single-threaded process)</li>
<li>하나의 프로세스가 가질 수 있는 스레드의 개수는 제한 없으나 스레드가 작업을 수행하는데 개별적인 메모리공간(호출스택)을 필요로 하기 때문에 프로세스 메모리 한계(호출스택의 크기)에 따라 생성할 수 있는 스레드의 수가 결정된다.</li>
<li>실제로는 한 개의 CPU는 한 번에 단 한가지 작업만 수행할 수 있기 때문에 아주 짧은 시간동안 여러 작업을 번갈아 가며 수행함으로써 동시에 여러 작업이 수행되는 것처럼 보이게 하는 것이다.</li>
</ul>
<hr>
<h2 id="멀티프로세스-vs-멀티스레드">멀티프로세스 vs 멀티스레드</h2>
<ul>
<li>하나의 새로운 프로세스를 생성하는 것보다 하나의 새로운 스레드를 생성하는 것이 더 적은 비용이 든다.</li>
<li>스레드는 Virtual CPU다: 스레드의 데이터 구조는 인스트럭션들에 대한 순서를 기록하고 있다.<ul>
<li>현재 컴퓨터의 레지스터 내용과 현재 실행되는 인스트럭션의 위치, 메소드에 의해 사용되는 런타임 스택(지역변수와 인자들을 담고 있다)이 저장된다.</li>
</ul>
</li>
<li>자바 프로세서는 중앙처리장치 내에 스케쥴러에서 실행 시간을 배분받아 자바 프로그램을 실행시킨다.</li>
<li>멀티스레드의 장점<ul>
<li>CPU의 사용률을 향상시킨다.</li>
<li>CPU자원을 보다 효율적으로 사용할 수 있다.</li>
<li>사용자에 대한 응답성이 향상된다.</li>
<li>작업이 분리되어 코드가 간결해진다.</li>
</ul>
</li>
<li>멀티스레드의 예: 채팅의 경우 파일을 다운로드 하면서 음성대화를 나눌 수 있다.</li>
<li>서버 프로그램은 멀티스레드가 필수적이다.</li>
</ul>
<br/>

<h3 id="멀티스레드의-장단점">멀티스레드의 장/단점</h3>
<ul>
<li>프로세스를 생성하는 것이 스레드를 생성하는 것에 비해 훨씬 더 많은 시간과 메모리 공간을 필요하기 때문에 많은 수의 사용자 요청을 서비스하기 어렵다.<ul>
<li>스레드를 가벼운 프로세스, 즉 경량 프로세스(LWP, light-weight process)라고 부르기도 한다.</li>
<li>스레드는 공유 메모리 영역을 사용하기 때문에 멀티프로세싱보다 멀티쓰레딩을 사용한다. 별도의 메모리 영역을 할당하지 않으므로 메모리가 절약되고 스레드 간의 컨텍스트 전환이 프로세스보다 시간이 덜 걸린다.</li>
</ul>
</li>
<li>교착상태(deadlock): 두 스레드가 자원을 점유한 상태에서 서로 상대편이 점유한 자원을 사용하려고 대기하며 진행이 멈추어 있는 상태</li>
</ul>
<table>
<thead>
<tr>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>- 자원을 보도 효율적으로 사용할 수 있다.<br/>- 사용자에 대한 응답성(responseness)이 향상된다.<br/>- 작업이 분리되어 코드가 간결해진다.</td>
<td>- 개발시 고려할 사항들이 많다.<br/>- 동기화(synchronization)에 주의해야 한다.<br/>- 교착상태(dead-lock)가 발생하지 않도록 주의해야 한다.<br/>- 각 스레드가 효율적으로 고르게 실행될 수 있게 해야 한다.</td>
</tr>
</tbody></table>
<hr>
<h2 id="스레드의-구현과-실행">스레드의 구현과 실행</h2>
<ul>
<li>구현방법은 두 가지(어느 방법을 사용하더라도 별 차이는 없다)<ul>
<li>Thread 클래스를 상속: 이 클래스를 상속 받으면 다른 클래스를 상속 받을 수 없다.</li>
<li>Runnable 인터페이스를 구현: 일반적인 방법으로 재사용성(Resuability)이 높고 코드의 일관성(consistency)을 유지할 수 있다는 장점이 있기 때문에 보다 객체지향적인 방법이라고 할 수 있다.</li>
</ul>
</li>
<li>Thread 클래스를 상속: Thread 클래스의 run() 메서드를 오버라이딩 해서 Thread가 처리할 Task를 구현한다.</li>
</ul>
<pre><code class="language-java">class JobThread extends Thread{
    public void run(){/* Task 구현 */}
}</code></pre>
<ul>
<li>Runnable 인터페이스를 구현: Runnable 인터페이스의 run() 메서드를 구현한다.</li>
</ul>
<pre><code class="language-java">class JobThread implements Runnable{
    public void run(){/* Task 구현 */}
}</code></pre>
<ul>
<li>Runnable interface는 추상메서드 run()만 있다.</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
public interface Runnable{
    public abstract void run();
}</code></pre>
<ul>
<li>Runnable 인터페이스를 구현한 경우의 인스턴스 생성방법이 다르다.<ul>
<li>Runnable 인터페이스 구현 클래스의 인스턴스 생성</li>
<li>인스턴스를 Thread 클래스의 인스턴스 생성시 Thread(Runnable target) 생성자의 매개변수로 넘겨줘야 한다.</li>
</ul>
</li>
<li>Thread를 상속했을 때 현재의 스레드 이름 얻기 방법: String getName();</li>
</ul>
<pre><code class="language-java">System.out.println(getName());</code></pre>
<ul>
<li>Runnable을 구현했을 때 현재의 스레드 이름 얻기 방법: Thread.currentThread();</li>
</ul>
<pre><code class="language-java">System.out.prinln(Thread.currentThread.getName());
=&gt;Thread t = Thread.currentThread();
    String name = t.getName();</code></pre>
<pre><code class="language-java">Runnable r = new ThreadEx02();
//생성자 Thread(Runnable target)
Thread t2 = new Thread(r);</code></pre>
<ul>
<li>쓰레드를 생성 후 start() 메소드를 호출해야만 작업을 시작한다.</li>
<li>한 번 사용한 쓰레드는 다시 재사용할 수 없다. → start() 메소드는 한 번만 호출될 수 있다.</li>
<li>한 번 더 수행되기를 원한다면 새로운 쓰레드를 생성한 다음 start()를 호출한다.</li>
</ul>
<pre><code class="language-java">ThreadEx new ThreadEX();
t.start();
t.start(); //java.lang.IllegalThreadStateException 발생</code></pre>
<pre><code class="language-java">public class ThreadTester{
    public static void main(String[] args){
        HelloRunner r = new HelloRunner();
        Thread t = new Thread(r);
        t.start();
    }
}

class HelloRunner implments Runnable{
    int i;
    @Override
    public void run(){
        i = 0;
        while(true){
            System.out.println(&quot;Hello &quot; + i++);
            if(i == 30) break;
        }
    }
}</code></pre>
<br/>

<h3 id="start와-run">start()와 run()</h3>
<ul>
<li>run() 메서드를 호출하는 것은 생성된 스레드를 실행시키는 것이 아니라 단순히 클래스에 속한 메서드를 호출한 것이다.</li>
<li>아래의 순서대로 실행이 되었을 때 호출스택의 최상위에 있는 메소드일지라도 대기상태일 수 있다.
→ 스케줄러는 스레드들의 우선순위를 고려하여 실행순서와 실행시간을 결정한다. (OS 스케줄러의 영향을 받는다.)</li>
<li>run()의 작업이 종료된 스레드는 호출스택이 모두 비어지고 사라진다.</li>
</ul>
<ol>
<li>main()메서 스레드의 start() 호출</li>
<li>start()는 스레드가 작업을 수행하는데 필요한 Call stack 생성</li>
<li>생성된 Call stack에 run()메서드를 호출해서 스레드가 작업을 수행하도록 한다.</li>
<li>Call stack이 2개이기 때문에 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행된다.</li>
</ol>
<hr>
<h2 id="main-thread">main() Thread</h2>
<ul>
<li>main()의 작업을 수행하는 것도 스레드이다.</li>
<li>프로그램이 실행되기 위해서는 최소한 하나의 스레드가 필요하다.</li>
<li>main()의 작업이 완료되었어도 다른 스레드가 작업중이면 프로그램이 종료되지 않는다.</li>
<li>실행 중인 사용자 스레드가 하나도 없을 때 프로그램은 종료된다.</li>
<li>스레드는 사용자 스레드(user thread: non-daemon thread)와 데몬 스레드(daemon thread) 두 종류가 있다.</li>
</ul>
<hr>
<h2 id="single-thread와-multi-thread">Single thread와 Multi thread</h2>
<ul>
<li>Single thread: 하나의 작업이 완료된 후 다른 작업이 시작된다.</li>
<li>Multi thread: 짧은 시간동안 2개 이상의 스레드가 번갈아 가면서 작업을 수행</li>
<li>Context switching: single thread보다 multi thread가 작업시간 더 걸리게 되는 이유는 스레드간의 작업전환에 시간이 걸리기 때문이다. 프로세스간 또는 스레드간의 전환을 스위칭이라고 하는데 현재 진행중인 작업의 상태, 다음에 실행해야할 위치(프로그램 카운터:PC) 등의 정보를 저장하고 읽어오는 시간이 소요된다.</li>
<li>단순히 CPU만을 사용하는 계산작업: Single thread로 프로그래밍 하는 게 효율적이다.</li>
<li>CPU의 자원을 사용하는 작업: 멀티스레드 프로세스가 더 효율적이다.
예) 데이터를 입력받는 작업, 네트워크로부터 파일을 주고 받는 작업, 프린터 출력 작업과 같은 외부기기와의 입출력 작업 등</li>
</ul>
<p>예제</p>
<pre><code class="language-java">public class SingleThreadTest{
    public static void main(String[] args){
        for(int i=0; i&lt;50; i++){
            System.out.println(&quot;-&quot;);
        }
        for(int i=0; i&lt;50; i++){
            System.out.println(&quot;|&quot;);
        }
    }
}

[결과]
--------------------------------------------------||||||||||||||||||||||||||||||||||||||||||||||||||</code></pre>
<pre><code class="language-java">public class MultiThreadTest {
    public static void main(String args[]){
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();
        t1.start();
        t2.start();
    }
}
class MyThread1 extends Thread {
    @Override
    public void run() {
        for(int i=0; i&lt;50; i++) {
            System.out.print(&quot;-&quot;);
        }
    }
}
class MyThread2 extends Thread {
    @Override
    public void run() {
        for(int i=0; i&lt;50; i++) {
            System.out.print(&quot;|&quot;);
        }
    }
}

[결과]
————||||||||||||||||||||||||||||||||||||||||||||||||||-----------------------------------------
-||||||||-------------------------------------------------||||||||||||||||||||||||||||||||||||||||||</code></pre>
<hr>
<h2 id="os에-종속적인-스레드">OS에 종속적인 스레드</h2>
<ul>
<li>앞의 예제에서 실행시마다 다른 결과가 나온느 것은 OS의 스케줄러의 영향을 받기 때문이다.</li>
<li>JVM의 스레드 스케줄러에 의해서 어떤 스레드가 얼마동안 실행될 것인지 결정되는 것과 같이 프로세스도 프로세스 스케줄러에 의해서 실행순서와 실행시간이 결정되기 때문에 매 순간 상황에 따라 프로세스에게 할당되는 실행시간이 일정하지 않고 스레드에게 할당되는 시간 역시 일정하지 않게 된다.</li>
<li>자바 프로세서는 CPU 내에 스케줄러에서 실행 시간을 배분받아 자바 프로그램을 실행시킨다. - JVM의 종류에 따라 스레드 스케줄러의 구현방법이 다를 수 있기 때문에 멀티 스레드로 작성된 프로그램을 다른 종류의 OS에서도 충분히 테스트해 볼 필요가 있다.</li>
</ul>
<hr>
<h2 id="thread-priority-스레드-우선순위">Thread Priority (스레드 우선순위)</h2>
<ul>
<li>스레드는 우선순위(Priority)라는 멤버변수를 가지고 있다.</li>
<li>작업의 중요도에 따라 우선순위를 달리 지정하여 특정 스레드가 더 많은 작업시간을 갖도록 할 수 있다. (선점형 스케줄링-Preemptive scheduling이라고 한다.)</li>
<li>메신저의 경우 채팅내용 전송 보다 파일다운로드(I/O)를 처리하는 스레드의 우선순위가 높아야 한다.</li>
<li>우선순위의 범위는 1~10로 높은 숫자일수록 순위가 높다. I/O작업이 최우선 순위이다.</li>
<li>우선순위는 스레드를 생성한 스레드로부터 상속이므로 main()에서 생성한 스레드는 우선순위가 자동적으로 5이다.</li>
<li>우선순위 Setter, Getter 메서드를 통해 우선순위 값을 핸들링할 수 있다.</li>
<li>public final int getPriority(): 주어진 스레드의 우선 순위를 반환한다.</li>
<li>public final void setPriority(int newPriority): 스레드의 우선 순위를 newPriority에 업데이트하거나 할당한다.</li>
<li>newPriority 값이 1(최소)에서 10(최대) 사이의 범위를 벗어나면 메서드에서 illegalArgumentException이 발생한다.</li>
<li>Thread 클래스에 정의된 3개의 상수: MIN_PRIORITY(1), NORM_PRIORITY(5), MAX_PRIORITY(10)이다.</li>
<li>동일한 우선 순위를 가진 두 개의 스레드가 있는 경우 어떤 스레드가 먼저 실행할 기회를 얻을 것인지 예측할 수 없다.</li>
<li>다음 실행은 스레드 스케줄러의 알고리즘(선착순, 라운드 로빈 등)에 따라 달라진다.</li>
</ul>
<p>예제</p>
<pre><code class="language-java">public class ThreadPriorityEx extends Thread{
    public void run(){
        System.out.println(&quot;in run() method&quot;);
    }
    public static void main(String argvs[]){
        System.out.println(&quot;메인 스레드 현재 우선순위:&quot;+
                        Thread.currentThread().getPriority()); // 5
        // 메인 스레드의 우선순위를 7로 설정한다
        Thread.currentThread().setPriority(7);
        // currentThread() 메서드를 사용해서
        // 현재 스레드를 찾아서 getPriority()로 우선순위를 얻는다
        System.out.println(&quot;메인 스레드 변경된 우선순위:&quot;+
                        Thread.currentThread().getPriority());

        ThreadPriorityEx t = new ThreadPriorityEx();
        // t 스레드는 메인스레드의 child 스레드이기에 우선순위도 7이다
        System.out.println(&quot;스레드 t의 우선순위:&quot;+
                        t.getPriority());
        t.start();
    }
}

[결과]
메인 스레드 현재 우선순위: 5
메인 스레드의 우선순위: 7
스레드 t의 우선순위 : 7
in run() method</code></pre>
<pre><code class="language-java">public class ThreadPriorityTest extends Thread{
    public void run(){
        System.out.println(&quot;in run() method&quot;);
    }
    public static void main(String[] args){
        ThreadPriorityEx t1 = new ThreadPriorityEx();
        ThreadPriorityEx t2 = new ThreadPriorityEx();
        ThreadPriorityEx t3 = new ThreadPriorityEx();

        //스레드의 우선순위는 기본값인 5이다
        System.out.println(&quot;t1의 우선순위 : &quot; + t1.getPriority());
        System.out.println(&quot;t2의 우선순위 : &quot; + t2.getPriority());
        System.out.println(&quot;t3의 우선순위 : &quot; + t3.getPriority());

        //우선순위 변경
        t1.setPriority(6);
        t2.setPriority(3);
        t3.setPriority(11);

        System.out.println(&quot;t1 스레드 우선순위 : &quot; + t1.getPriority());
        System.out.println(&quot;t2 스레드 우선순위 : &quot; + t2.getPriority());
        System.out.println(&quot;t3 스레드 우선순위 : &quot; + t3.getPriority());
        System.out.println(&quot;현재 실행중인 스레드 : &quot; + Thread.currentThread().getName());
        System.out.println(&quot;메인 스레드의 우선순위 : &quot;
                    + Thread.currentThread().getPriority());
        //메인스레드 우선순위 10으로 변경
        Thread.currentThread().setPriority(10);
        System.out.println(&quot;메인 스레드의 우선순위 : &quot;
                    + Thread.currentThread().getPriority());
    }
}

[결과]
t1의 우선순위 : 5
t2의 우선순위 : 5
t3의 우선순위 : 5
t1 스레드 우선순위 : 6
t2 스레드 우선순위 : 3
t3 스레드 우선순위 : 9
현재 실행중인 스레드 : main
메인 스레드의 우선순위 : 5
메인 스레드의 우선순위 : 10</code></pre>
<hr>
<h2 id="thread-group">Thread Group</h2>
<ul>
<li>서로 관련된 스레드를 그룹으로 묶어서 다루기 위한 것</li>
<li>모든 스레드는 반드시 하나의 스레드 그룹에 포함되어 있어야 한다.</li>
<li>스레드 그룹을 지정하지 않고 생성한 스레드는 ‘main 스레드 그룹’에 속한다.</li>
<li>Garbage Collection을 수행하는 Finalizer 스레드는 System스레드 그룹에 속한다.</li>
<li>자신을 생성한 스레드(상위 스레드)의 그룹과 우선순위를 상속받는다.</li>
<li>보안상의 이유로 도입된 개념으로 자신이 속한 스레드 그룹 이하나 하위 스레드 그룹은 변경할 수 있지만 다른 스레드 그룹의 스레드를 변경할 수는 없다.</li>
<li>ThreadGroup 클래스로 생성할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>생성자/메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>ThreadGroup(String name)</td>
<td>주어진 이름으로 새로운 스레드 그룹 생성</td>
</tr>
<tr>
<td>ThreadGroup(ThreadGroup parent, String name)</td>
<td>주어진 부모그룹과 이름으로 새로운 스레드 그룹 생성</td>
</tr>
<tr>
<td>int activeCount()</td>
<td>스레드 그룹 및 하위 그룹의 활성 스레드 수 반환</td>
</tr>
<tr>
<td>void destroy()</td>
<td>스레드 그룹 및 하위 그룹까지 모두 삭제</td>
</tr>
<tr>
<td>int getMaxPriority()</td>
<td>스레드 그룹의 최대 우선 순위를 반환</td>
</tr>
<tr>
<td>void setMaxPriority(int pri)</td>
<td>스레드 그룹의 최대 우선 순위를 설정</td>
</tr>
<tr>
<td>String getName()</td>
<td>스레드 그룹의 이름을 반환</td>
</tr>
<tr>
<td>ThreadGroup getParent()</td>
<td>스레드 그룹의 상위 스레드 그룹을 반환</td>
</tr>
<tr>
<td>void list()</td>
<td>스레드 그룹, 하위 그룹 및 스레드에 대한 정보를 출력</td>
</tr>
<tr>
<td>boolean isDaemon()</td>
<td>스레드 그룹이 데몬 스레드 그룹인지 확인</td>
</tr>
<tr>
<td>void setDaemon(boolean daemon)</td>
<td>스레드 그룹을 데몬 스레드 그룹으로 설정 또는 해제</td>
</tr>
<tr>
<td>boolean isDestroyed()</td>
<td>스레드 그룹이 삭제되었는지 확인</td>
</tr>
<tr>
<td>void interrup()</td>
<td>스레드 그룹에 속한 모든 스레드를 중단</td>
</tr>
</tbody></table>
<br/>

<p>예제</p>
<pre><code class="language-java">public class ThreadGroupEx01 implements Runnable{
    public void run(){
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args){
        ThreadGroupEx01 runnable = new ThreadGroupEx01();
        ThreadGroup tg1 = new ThreadGroup(&quot;Parent ThreadGroup&quot;);

        Thread t1 = new Thread(tg1, runnable, &quot;one&quot;);
        t1.start();
        Thread t2 = new Thread(tg1, runnable, &quot;two&quot;);
        t2.start();
        Thread t3 = new Thread(tg1, runnable, &quot;three&quot;);
        t3.start();
        System.out.println(&quot;Thread Group Name: &quot;+tg1.getName());
        tg1.list();
    }
}

[결과]
one
two
three
Thread Group Name: Parent ThreadGroup
java.lang.ThreadGroup[name=Parent ThreadGroup,maxpri=10]</code></pre>
<hr>
<h2 id="daemon-thread데몬-스레드">Daemon Thread(데몬 스레드)</h2>
<ul>
<li>일반 스레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행</li>
<li>애플리케이션 실행되면 JVM은 Garbage Collection, 이벤트처리, 그래픽처리 등 프로그램이 실행되는데 필요한 보조작업을 수행하는 데몬 스레드들을 자동적으로 생성해서 실행시킨다.</li>
<li>일반 스레드가 모두 종료되면 자동적으로 종료되며, 우선 순위가 낮은 스레드이다.</li>
<li>boolean isDaemon(): 스레드가 데몬스레드인지 확인하여 데몬스레드일 경우 true 반환</li>
<li>void setDaemon(boolean on): 스레드를 데몬스레드(true)로 또는 사용자스레드(false)로 변경한다.</li>
<li>setDaemon(boolean on)은 반드시 start()를 호출하기 전에 실행되어야 한다. 그렇지 않으면 IllegalThreadStateException이 발생한다.</li>
</ul>
<pre><code class="language-java">public class DaemonThreadEx01 extends Thread{
    public void run(){
        if(Thread.currentThread().isDaemon()){
            System.out.println(&quot;daemon thread work&quot;);
        }else{
            System.out.println(&quot;user thread work&quot;);
        }
    }

    public static void main(String[] args){
        DaemonThreadEx01 t1 = new DaemonThreadEx01();
        DaemonThreadEx01 t2 = new DaemonThreadEx01();
        DaemonThreadEx01 t3 = new DaemonThreadEx01();

        //Daemon thread로 설정
        t1.setDaemon(true);

        t1.start();
        t2.start();
        t3.start();
    }
}

[결과]
daemon thread work
user thread work
user thread work</code></pre>
<hr>
<h2 id="thread-control">Thread Control</h2>
<ul>
<li>효율적인 멀티쓰레드 처리를 위해서는 정교한 스케쥴링을 통해 프로세스에게 주어진 자원과 시간을 여러 쓰레드가 낭비없이 잘 사용하도록 프로그래밍 해야 한다.</li>
<li>스케쥴링을 잘 하기 위해서는 쓰레드의 상태와 관련 메서드를 잘 알아야 한다.</li>
<li>resume(), stop(), suspend()는 쓰레드를 교착상태(dead-lock)로 만들기 쉽기 때문에 deprecated되었다.</li>
<li>stop()의 경우 동기화가 걸려있을 경우 lock을 반환하지 않고 종료시키는 치명적 오류가 발견되었다.</li>
</ul>
<table>
<thead>
<tr>
<th>생성자/메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>void interrupt()</td>
<td>sleep(), join(), wait()에 의해 일시정지 상태인 스레드르 꺠워서 실행대기 상태로 만든다. (Thread state가 WAITING → RUNNABLE) 해당 스레드에서는 interruptedException이 발생되면서 WAITING 상태를 벗어나게 된다.</td>
</tr>
<tr>
<td>void join()<br/>void join(long millis)</td>
<td>join()을 호출한 스레드가 종료될 때까지 기다리게 한다. 즉 스레드를 Running → Waiting 상태로 만든다.<br/>파라미터가 있는 join()은 대기 시간을 지정할 수 있고, 대기 시간이 끝나면 실행을 계속한다.</td>
</tr>
<tr>
<td>void resume()</td>
<td>suspend()에 의해 일시정지 상태에 있는 스레드를 실행대기 상태로 만든다.</td>
</tr>
<tr>
<td>static void sleep(long millis)<br/>static void sleep(long millis, int nanos)</td>
<td>지정된 시간(천분의 일초 단위)동안 스레드를 일시정지시킨다. 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기 상태가 된다.</td>
</tr>
<tr>
<td>void stop()</td>
<td>스레드를 즉시 종료시킨다. 교착상태(dead-lock)에 빠지기 쉽기 때문에 deprecated 되었다.</td>
</tr>
<tr>
<td>boolean isAlive()</td>
<td>스레드가 시작되었고 아직 끝나지 않았으면 true, 끝났으면 false를 반환한다.</td>
</tr>
<tr>
<td>void suspend()</td>
<td>스레드를 일시정지시킨다. resume()에 의해 다시 실행대기 상태가 된다.</td>
</tr>
<tr>
<td>static void yield()</td>
<td>다른 스레드에게 양보(yield)하고 자신은 실행대기 상태가 된다.</td>
</tr>
</tbody></table>
<hr>
<h2 id="thread-state">Thread State</h2>
<ul>
<li>스레드 상태는 Thread의 getStatus() 메서드를 호출해서 확인할 수 있다. (1.5부터 추가)</li>
<li>Java의 스레드는 아래의 상태 중 하나만 가질 수 있으며, JVM의 상태이다.</li>
</ul>
<table>
<thead>
<tr>
<th>상태</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>NEW</td>
<td>스레드가 생성되었지만 스레드가 아직 실행할 준비가 되지 않았다.</td>
</tr>
<tr>
<td>RUNNABLE</td>
<td>실행할 준비가 된 스레드가 실행 가능한 상태로 스케줄링을 기다리는 상태</td>
</tr>
<tr>
<td>BLOCKED</td>
<td>동기화블럭이나 I/O 작업에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)</td>
</tr>
<tr>
<td>WAITING</td>
<td>다른 스레드가 특정 작업을 완료하기를 기다리며, 다른 스레드가 notify(), notifyAll()을 호출하기를 기다리고 있는 상태</td>
</tr>
<tr>
<td>TIME_WAITING</td>
<td>TIME_WAITING은 일시정지 시간이 지정된 경우의 상태로 스레드가 sleep() 메서드 호출로 지정한 밀리초 동안 Sleep하고 있는 상태</td>
</tr>
<tr>
<td>TERMINATED</td>
<td>스레드가 종료된 상태</td>
</tr>
</tbody></table>
<br/>

<ul>
<li>New: 새 스레드가 생성될 때의 상태이다. New 상태 스레드의 경우 코드가 아직 실행 안 된 상태로 스레드가 생성되었지만 스레드가 아직 실행할 준비가 되지 않은 상태</li>
<li>Active: 스레드가 start() 메서드를 호출하면 Active 상태로 이동한다. Active 상태는 그 안에 두 가지(runnable, running) 상태를 포함한다.<ul>
<li>Runnable: 실행할 준비가 된 스레드가 실행 가능한 상태로 이동한다. 실행 가능한 상태에서 스레드는 실행 중이거나 주어진 시간에 실행할 준비가 될 수 있다. 스레드가 실행 중인 상태로 이동하는 것이 스레드 스케줄러의 역할이다.<ul>
<li>멀티스레딩을 구현하는 프로그램은 각 개별 스레드에 대해 고정된 시간의 부분(slice)을 얻는다.</li>
<li>각각의 모든 스레드는 짧은 시간 동안 실행되며 할당된 타임 슬라이스가 끝나면 스레드가 자발적으로 CPU를 다른 스레드에 양보하므로 다른 스레드도 해당 시간 동안 실행할 수 있다. 이러한 시나리오가 발생할 때마다 실행하려는 모든 스레드가 실행되기를 기자리는 실행 가능한 상태로 놓이며, 이 상태에는 스레드의 큐에 있다.</li>
</ul>
</li>
<li>Running: 스레드가 CPU를 가져오면 실행 가능한 상태에서 실행 중 상태로 이동한다. 일반적으로 스레드 상태의 가장 일반적인 변경은 실행 가능에서 실행 중으로, 다시 실행 가능으로 돌아가는 것이다.</li>
</ul>
</li>
<li>Blocked / Waiting: 스레드가 일정 시간 동안 비활성 상태일 때마다 스레드는 차단된 상태이거나 대기 중인 상태이다.<ul>
<li>예를 들어, 스레드 A가 프린터에서 일부 데이터를 인쇄하려고 할 수 있다. 그러나 동시에 다른 스레드 B가 프린터를 사용하여 일부 데이터를 인쇄하고 있다.</li>
<li>스레드 A는 스레드 B가 프린터를 사용할 때까지 기다려야 한다. 따라서 스레드 A는 blocked 상태이다. blocked 상태의 스레드는 실행할 수 없으므로 CPU의 사이클을 소비하지 않는다. 따라서 스레드 스케줄러가 waiting 또는 blocked 상태의 스레드 A를 다시 호라성화할 때까지 스레드 A가 idle 상태로 유지된다고 할 수 있다.</li>
<li>메인 스레드가 join() 메서드를 호출하며 메인 스레드가 대기 상태가 된다. 그런 다음 메인 스레드는 child 스레드가 작업을 완료할 때까지 기다린다. child 스레드가 작업을 완료하면 알림이 메인 스레드로 전송되어 스레드가 waiting 상태에서 active 상태로 다시 이동한다.</li>
<li>waiting 중이거나 blocked 상태의 스레드가 많은 경우 스레드 스케줄러는 선택할 스레드와 거부할 스레드를 결정하고 선택한 스레드에 실행할 기회가 주어진다.</li>
</ul>
</li>
<li>Timed Waiting: waiting 상태가 영원해지지 않도록 스레드에 time limit waiting 상태가 제공된다. 따라서 스레드는 특정 시간 동안 waiting 상태에 있게 된다.</li>
<li>Terminated: 다음과 같은 이유로 스레드가 terminated 상태가 된다. 종료된 스레드는 해당 스레드가 시스템에 더 이상 존재하지 않음을 의미한다.<ul>
<li>스레드가 작업을 완료하면 정상적으로 존재하거나 종료된다.</li>
<li>비정상 종료: 처리되지 않은 예외 또는 분할 오류과 같은 일부 비정상 이벤트가 생기는 경우 발생한다.</li>
</ul>
</li>
<li>Thread.getState() 메서드를 사용하면 스레드의 현재 상태를 얻을 수 있다. java.lang.Thread.State 클래스는 스레드 상태를 나타내는 상수 ENUM을 제공한다.</li>
<li>스레드는 특정 시점에 하나의 상태만 가질 수 있다.</li>
</ul>
<pre><code class="language-java">public enum State{
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}</code></pre>
<hr>
<h2 id="스레드-스케줄러thread-scheduler">스레드 스케줄러(Thread Scheduler)</h2>
<ul>
<li>실행하거나 실행할 스레드와 대기할 스레드를 결정하는 Java 구성요소를 스레드 스케줄러라고 한다.</li>
<li>스레드는 실행 가능한 상태인 경우에만 스레드 스케줄러에 의해 선택된다.</li>
<li>Time to Arrival(도착시간): 우선순위가 같은 두 스레드가 실행 가능한 상태에 들어간다고 가정하면 우선순위는 이 두 스레드의 선택 요소가 되는데, 이 경우 스케줄러는 스레드 도착 시간을 고려하여 먼저 도착한 스레드가 우선된다.</li>
<li>도착시간과 우선순위가 다른 5개의 스레드가 있다고 가정하면 어떤 스레드가 CPU를 먼저 차지할지 결정하는 것은 스레드 스케줄러의 책임이다.</li>
</ul>
<br/>

<h3 id="스레드-스케줄링">스레드 스케줄링</h3>
<ol>
<li>스레드 생성후 start() 호출 → 실행대기열에 저장되며 큐(queue)와 같은 구조 → FIFO구조로 먼저 들어온 스레드가 먼저 실행된다.</li>
<li>실행대기 상태에 있다가 차례가 되면 실행된다.</li>
<li>스케쥴러에 의해 할당된 실행시간이 다 되거나 yeild()를 만나면 다시 실행대기 상태가 되고, 다음 스레드가 실행된다.</li>
<li>실행 중에 suspend(), sleep(), wait(), join(), I/O block에 의해 일시정지 상태가 될 수 있다.
I/O block은 입출력 작업에 발생하는 지연상태이다.
예) 사용자의 입력을 기다리는 경우 → 일시정지 → 사용자 입력마침 → 다시 실행대기 상태가 실행된다.</li>
<li>지정된 일시정지 시간이 다 되거나(time-out), notify(), resume(), interrupt()가 호출되면 일시정지 상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다린다.</li>
<li>실행을 모두 완료하면 스레드가 소멸된다.</li>
</ol>
<br/>

<p>suspend(), resume(), stop()은 쓰레드를 교착상태(dead-lock)로 빠뜨릴 가능성이 있기 때문에 deprecated 되었으므로 사용하지 않는 것이 좋으며, 아래의 코드처럼 stopped와 suspended라는 boolean 타입의 변수를 선언하고 이 변수의 값을 변경함으로써 작업이 중지되고 종료되도록 변경할 수 있다.</p>
<pre><code class="language-java">public class ThreadSuspendResumeTest{
    public static void main(String[] args){
        ThreadSuspendResume ts1 = new ThreadSuspendResume();
        ThreadSuspendResume ts2 = new ThreadSuspendResume();
        t1.start();
        t2.start();

        try{
            Thread.sleep(2000);
            ts1.suspend();
            Thread.sleep(2000);
            ts2.suspend();
            Thread.sleep(2000);
            ts1.resume();
            Thread.sleep(2000);
            ts1.stop();
            ts2.stop();
        }catch(InterruptedException e){}
    }
}</code></pre>
<pre><code class="language-java">public class ThreadSuspendResumeTest implements Runnable{
    boolean suspended = false;
    boolean stopped = false;

    Thread t;

    @Override
    public void run(){
        while(!stopped){
            if(!suspended){
                //구현부
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException e){}
            }
        }
    }

    public void suspend(){
        suspended = true;
    }
    public void resume(){
        suspended = false;
    }
    public void stop(){
        stopped = true;
    }
    public void start(){
        t.start();
    }
}</code></pre>
<br/>

<ul>
<li>join()을 사용해서 main 스레드가 작업이 끝날 때까지 기다리도록 할 수 있다.</li>
<li>join()을 사용하지 않으면 main 스레드가 바로 종료된다.</li>
<li>여러 개의 서버나 Job의 실행 결과를 가지고 최종결과를 얻어야 할 경우 등에 사용한다.</li>
</ul>
<ul>
<li>yeild()는 양보를 통해 실행시간을 낭비시키지 않는다.</li>
<li>밑의 예제에서 suspended가 true여서 잠시 실행을 멈추게 된다면 쓰레드는 단순히 while문을 Looping 하면서 낭비를 하게 된다.</li>
<li>하지만 yield()를 호출해서 남은 실행시간을 while문에서 낭비하지 않고 다른 쓰레드에게 양보할 수 있도록 처리하여 효율적으로 쓰레드를 운영할 수 있다.</li>
</ul>
<pre><code class="language-java">while(!stopped){
    if(!suspended){
        System.out.println(Thread.currentThread().getName());

        try{
            Thread.sleep(1000);
        }catch(InterruptedException e){}
    }
}

while(!stopped){
    if(!suspended){
        System.out.println(name);

        try{
            Thread.sleep(1000);
        }catch(InterruptedException e){
            System.out.println(name + &quot; - interrupted&quot;);
        }
    }else{
        Thread.yield();
    }
}</code></pre>
<br/>

<ul>
<li>interrupt()는 InterruptedException을 발생시켜서 sleep(), join(), wait()에 의해 일시정지 상태인 쓰레드를 실행대기 상태(RUNNABLE)로 만든다.</li>
<li>그러나 interrupt()가 호출되었을 때 sleep(), join(), wait()에 의해 일시정지 상태가 아니라면 아무 일도 일어나지 않는다.</li>
</ul>
<hr>
<h2 id="스레드-풀thread-pool">스레드 풀(Thread Pool)</h2>
<ul>
<li>Java 스레드 풀은 작업을 기다리고 여러번 재사용되는 Worker Thread Group을 의미한다.</li>
<li>스레드 풀은 고정 사이즈 스레드 그룹이 생성된다. 그 스레드 풀에서 유휴 스레드(idle thread)를 가져와 서비스 작업에 할당한다.</li>
<li>작업이 완료되면 스레드는 다시 스레드 풀에 포함된다.</li>
<li>java.util.concurrent.Executors, ExecutorService를 이용하여 스레드풀을 생성하여 병렬처리를 할 수 있다.</li>
<li>Executors는 ExecutorService 객체를 생성하며, 스레드 풀 개수 및 종류를 생성할 수 있다.</li>
<li>Executors 메서드<ul>
<li>newFixedThreadPool(int s): 이 메서드는 고정 크기 s의 스레드 풀을 생성한다.
실제 생성되는 객체는 ThreadPoolExecutor이다.</li>
<li>newCachedThreadPool(): 필요할 때마다 스레드를 생성하는 스레드 풀을 생성한다. 이미 생성된 스레드의 경우 재사용된다.</li>
<li>newSingleThreadExecutor(): 하나의 스레드만 사용하는 ExecutorService를 생성하며 싱글 스레드에서 동작해야 하는 작업을 처리할 때 사용하며, 스레드가 1개이기 때문에 작업을 예약한 순서대로 처리하며 동시성(Concurrency)를 고려할 필요가 없다.</li>
</ul>
</li>
<li>ExecutorService 작업을 처리할 수 있다.<ul>
<li>submit(): 멀티스레드로 처리할 작업을 추가한다.</li>
<li>shutdown(): 더 이상 스레드풀에 작업을 추가하지 못하며, 처리 중인 task가 모두 완료되면 스레드 풀을 종료시킨다.</li>
<li>awaitTermination(): 이미 수행 중인 task가 지정된 시간동안 끝나기를 기다리며, 지정된 시간내에 종료되지 않으면 false를 리턴한다.
이 때 shutdownNow()를 호출하면 실행 중인 task를 모두 강제 종료시킬 수 있다.</li>
</ul>
</li>
<li>장점: 필요시에 새로운 스레드를 생성할 필요가 없기 때문에 스레드 생성 시간이 절약되고 바로 사용할 수 있어서 더 나은 성능을 기대할 수 있다.</li>
<li>Real-time usage: 컨테이너가 요청을 처리하기 위해 스레드 풀을 생성하는 서블릿 및 JSP에서 사용된다.</li>
</ul>
<br/>

<p>예제: ExecutorService 및 Executors를 사용하는 Java 스레드 풀의 사용 예</p>
<pre><code class="language-java">public class ThreadPoolWorkerThread implements Runnable{
 private String message;
 public ThreadPoolWorkerThread(String s){
     this.message=s;
 }
 public void run() {
        System.out.println(Thread.currentThread().getName() +
                        &quot; (Start) message = &quot;+message);
        // 스레드를 1초간 sleep하는 processmessage 메소드 호출
        processmessage();
        System.out.println(Thread.currentThread().getName()+&quot; (End)&quot;);
    }
    private void processmessage() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 스레드 풀을 생성 (5 스레드)
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i &lt; 10; i++) {
            Runnable worker = new ThreadPoolWorkerThread(&quot;&quot; + i);
            // ExecutorService의 execute 메소드 호출
            executor.execute(worker);
        }
        // shutdown한다. 이미 executor에 제공된 task는 실행되지만
        // 새로운 작업은 수용 안한다
        executor.shutdown();
        // shutdown후 모든 작업이 종료되었는지 여부를 확인한다
        while (!executor.isTerminated()) {}

        System.out.println(&quot;모든 소레드 종료&quot;);
    }
}

[결과]
pool-1-thread-4 (Start) message = 3
pool-1-thread-1 (Start) message = 0
pool-1-thread-2 (Start) message = 1
pool-1-thread-3 (Start) message = 2
pool-1-thread-5 (Start) message = 4
pool-1-thread-5 (End)
pool-1-thread-3 (End)
pool-1-thread-1 (End)
pool-1-thread-3 (Start) message = 6
pool-1-thread-2 (End)
pool-1-thread-2 (Start) message = 8
pool-1-thread-5 (Start) message = 5
pool-1-thread-4 (End)
pool-1-thread-4 (Start) message = 9
pool-1-thread-1 (Start) message = 7
pool-1-thread-3 (End)
pool-1-thread-4 (End)
pool-1-thread-2 (End)
pool-1-thread-1 (End)
pool-1-thread-5 (End)
모든 소레드 종료</code></pre>
<br/>

<ul>
<li>스레드 풀을 사용하면 비용적인 측면이나 컨텍스트 스위칭이 발생하는 상황에서 딜레이를 줄일 수 있는 장점이 있다.</li>
<li>단점<ul>
<li>스레드 풀에 너무 많은 양의 스레드를 만들어둔다면 메모리 낭비가 심해질 수 있다. 필요한 스레드 수를 예측하고 할당해서 사용해야 한다.</li>
<li>long task에 스레드를 사용할 때마다 주의해야 한다. 스레드가 영원히 대기하는 결과를 초래할 수 있으며 결국 리소스 누출로 이어진다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="스레드-동기화">스레드 동기화</h2>
<ul>
<li>클래스에 대한 객체가 생성될 때마다 Object lock이 생성되어 객체 내부에 저장된다.</li>
<li>한 번에 하나의 스레드만 객체에 접근할 수 있도록 객체에 락(lock)을 걸어서 데이터의 일관성을 유지하는 것으로 공유 리소스에 대한 여러 스레드의 접근 제어 기능을 한다.</li>
<li>Java 1.5부터는 java.util.concurrent.locks, java.util.concurrent.atomic 패키지를 통해서 다양한 방식의 동기화를 구현할 수 있도록 지원하고 있다.</li>
<li>사용 이유<ul>
<li>스레드의 간섭을 방지한다.</li>
<li>정볼르 처리하는데 있어 일관성 문제를 방지한다. (예를 들면 하나의 계좌에 입/출금을 동시에 여러 스레드가 접근하는 것을 방지해야 한다.)</li>
<li>사용법<ul>
<li>동기화 블럭(Synchronized Block): 특정 객체에 lock을 걸어서 사용하고자 할 경우
synchronized(객체 참조 변수){
  //구현 내용
}</li>
<li>메서드에 lock을 걸어 사용하고자 할 경우 (메서드 전체를 동기화하는 것보다 메서드의 일부 영역을 동기화하는 것이 성능 향상에 더 좋다)
public synchronized void CalcPay(){
  //구현 내용
}</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>만일 스레드 A가 작업하던 도중 다른 스레드 B에게 제어권이 넘어갔을 때 스레드 A가 작업하던 공유 데이터를 스레드 B가 임의로 변경하였다면 → 스레드 A는 의도하지 않은 다른 결과를 얻게 된다.</li>
<li>동기화는 lock 또는 Monitor로 알려진 내부 Entity를 중심으로 구축되며, 모든 객체에는 lock과 연결되어 있다.</li>
<li>규칙에 따라 객체의 필드에 대한 접근이 필요한 스레드는 접근 전에 객체의 lock을 획득해야 하고 작업이 완료되면 lock을 해제(반납)해야 한다.</li>
<li>java 5 버전의 java.util.concurrent.locks 패키지에 여러 lock 관련 구현이 포함되어 있다.</li>
<li>동기화는 공유 리소스에 대한 객체를 잠그는데 사용되기에 공유리소스에 접근하는 스레드들은 lock을 얻기 위해 대기하게 되며 그렇기 떄문에 시스템 성능이 저하될 수 있다.</li>
</ul>
<ul>
<li>교착상태(Dead lock): 두 스레드가 lock을 건 상태에서 서로 lock이 풀리기를 기다리는 상황으로 작업이 진행되지 않고 영원히 기다리는 상황으로 교착상태에 빠지지 않도록 개발해야 한다.</li>
<li>stop(), suspend(), resume()과 같이 스레드의 상태를 변경하는 메서드들은 교착상태를 일으킬 가능성이 높다는 이유로 deprecated 되었다.</li>
<li>인스턴스의 변수 Reference 변수인 this를 통해 Thread들에 의해 공유되지만 지역변수는 각 스레드의 스택 내에 생성되므로 같은 프로세스 내의 스레드일지라도 공유되지 않는다. (CASE 1)</li>
<li>아래 예제처럼 Thread 생성시 공유할 객체를 전달하지 않으면 각각의 객체로 Thread별로 처리된다. (CASE 2)</li>
</ul>
<pre><code class="language-java">//CASE 1) T1, T2에 객체가 공유됨
RunnableImpl r = new RunnableImpl();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);

//CASE 2) T1, T2가 공유하는 객체 없음
MyThread t1 = new MyThread();
MyTHread t2 = new MyThread();

t1.start();
t2.start();</code></pre>
<ul>
<li>Account의 출금메소드인 withdraw에 synchronized 키워드를 붙이기만 하면 한 스레드에 의해서 먼저 withdraw()가 호출되면, 종료될 때까지 다른 스레드는 호출하더라도 대기상태에 머물게 된다.</li>
</ul>
<pre><code class="language-java">public class SynchronizedTest{
    public static void main(String[] args){
        Runnable r = new RunnableSyncEx();
        new Thread(r).start();
        new Thread(r).start();
    }
}

class Account{
    private int balance = 1000;

    public int getBalance(){
        return balance;
    }

    public synchronized void withdraw(int money){
        if(balance &gt;= money){
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){}
            balance -= money;
        }
    } //withdraw
}

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

    @Override
    public void run(){
        while(acc.getBalance() &gt; 0){
            //임의의 값으로 출금(withdraw) 처리
            int money = (int)(Math.random() * 3 + 1) * 100;
            acc.withdraw(money);
            System.out.println(Thread.currentThread().getName()
                            +&quot; =&gt; 잔액 :&quot;+ acc.getBalance());
        }
    }
}

[결과]
//Synchronized 미사용 결과
Thread-1 =&gt; 잔액 :600
Thread-0 =&gt; 잔액 :800
Thread-1 =&gt; 잔액 :400
Thread-0 =&gt; 잔액 :400
Thread-1 =&gt; 잔액 :300
Thread-0 =&gt; 잔액 :0
Thread-1 =&gt; 잔액 :-200

//Synchronized 사용 결과
Thread-1 =&gt; 잔액 :700
Thread-0 =&gt; 잔액 :500
Thread-1 =&gt; 잔액 :300
Thread-1 =&gt; 잔액 :0
Thread-0 =&gt; 잔액 :0</code></pre>
<br/>

<ul>
<li>wait(), notify(), notifyAll() 메서드는 동기화의 효율을 높이기 위해 사용한다.</li>
<li>한 스레드가 무한정 객체에 lock을 걸고 점유하게 되면 다른 스레드들이 lock이 풀릴 때까지 기다려야 하는 상황이 발생한다. (비효율적)<ul>
<li>한 스레드가 객체에 lock을 걸고 오래 기다리는 대신 wait()을 호출해서 다른 스레드에게 제어권을 넘겨주고 대기상태로 기다린다.</li>
<li>다른 스레드에 의해 notify()가 호출되면 다시 실행대기열 상태가 되도록 하는 것이다.</li>
</ul>
</li>
<li>관련 메서드들은 Object클래스에 정의되어 있어서 모든 객체에서 호출가능하며 동기화 블록 내에서만 사용할 수 있다.</li>
<li>wait(): 객체의 lock을 풀고 해당 객체의 스레드를 waiting pool에 넣는다.</li>
<li>notify(): waiting pool에서 대기중인 스레드 중의 하나를 깨운다.</li>
<li>notifyAll(): waiting pool에서 대기중인 모든 스레드를 깨운다.</li>
<li>notify()에 의해 어떤 스레드가 깨워지게 될지 알 수 없고, 특정 스레드가 오랫동안 객체의 waiting pool에 머물 수 있기 때문에 다시 waiting pool에 들어가더라도 nofityAll()을 호출해서 모든 스레드를 깨워놓고 JVM의 스케쥴링에 의해서 처리되도록 하는 것이 안전하다.</li>
</ul>
<ul>
<li>잔고가 부족할 경우 wait()을 호출해서 스레드가 객체의 lock을 풀고 waiting pool에 들어가면서 다른 스레드에게 제어권을 넘긴다.</li>
<li>다른 스레드에 의해서 deposit() 메소드가 호출되고 잔고가 증가하면서 notify()를 호출하면 객체의 waiting pool에서 기다리고 있던 스레드를 깨우게 된다.</li>
</ul>
<pre><code class="language-java">class AccountEx{
    private int balance = 1000;

    public int getBalance(){
        return balance;
    }    

    public synchronized void withdraw(int money){
        while(balance &lt; money){
            try{
                //잔고 부족으로 출고 job 스레드는 lock 해제 후
                //waiting pool로 들어가며 제어권을 넘긴다
                wait();
            }catch(InterruptedException e){}
        }
        balance -= money;
    } //withdraw

    public synchronized void deposit(int money){
        balance += money;
        //잔고가 증가되면 notify()를 호출해서 waiting pool의
        //스레드를 깨워서 job을 이어서 처리하도록 한다
        notify();
    }
}</code></pre>
<hr>
<h2 id="실행-중인-모든-jvm-스레드-정보">실행 중인 모든 JVM 스레드 정보</h2>
<ul>
<li>Thread 클래스의 getAllStackTrace() 메서드는 실행 중인 모든 스레드의 스택 추적을 제공한다.</li>
<li>Key가 Thread 객체인 Map을 반환하므로 Key Set을 얻어와서 해당 요소를 반복하여 스레드에 대한 정보를 얻을 수 있다.</li>
<li>Main thread외에 다른 스레드가 있고, Java 버전에 따라 다를 수 있다.</li>
<li>Single Dispatcher: 이 스레드는 운영 체제에서 JVM으로 보내는 신호를 처리한다.</li>
<li>Finalizer: 이 스레드는 더 이상 시스템 리소스를 해제할 필요가 없는 객체에 대한 종료를 수행한다.</li>
<li>Reference Handler: 더 이상 필요하지 않는 객체를 Finalizer 스레드에서 처리할 대기열에 넣는다.</li>
<li>Spring boot tomcat thread 수: Minimum worker thread -10 / max - 200</li>
</ul>
<p><a href="https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.server.server.tomcat.threads.min-spare">Common Application Properties</a></p>
<pre><code class="language-java">Set&lt;Thread&gt; threads = Thread.getAllStackTraces().keySet();
System.out.printf(&quot;%-15s \t %-15s \t %-15s \t %s\n&quot;, &quot;Name&quot;, &quot;State&quot;, &quot;Priority&quot;, &quot;isDaemon&quot;);
for (Thread t : threads) {
    System.out.printf(&quot;%-15s \t %-15s \t %-15d \t %s\n&quot;, t.getName(), t.getState(), t.getPriority(), t.isDaemon());
}</code></pre>
<p>[결과]</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>State</th>
<th>Priority</th>
<th>isDeamon</th>
</tr>
</thead>
<tbody><tr>
<td>main</td>
<td>RUNNABLE</td>
<td>5</td>
<td>false</td>
</tr>
<tr>
<td>Thread-0</td>
<td>TERMINATED</td>
<td>5</td>
<td>false</td>
</tr>
<tr>
<td>Reference Handler</td>
<td>RUNNABLE</td>
<td>10</td>
<td>true</td>
</tr>
<tr>
<td>Monitor Ctrl-Break</td>
<td>RUNNABLE</td>
<td>5</td>
<td>true</td>
</tr>
<tr>
<td>Finalizer</td>
<td>WAITING</td>
<td>8</td>
<td>true</td>
</tr>
<tr>
<td>Thread-2</td>
<td>TERMINATED</td>
<td>5</td>
<td>false</td>
</tr>
<tr>
<td>Thread-1</td>
<td>BLOCKED</td>
<td>5</td>
<td>false</td>
</tr>
<tr>
<td>Common-Cleaner</td>
<td>TIMED_WAITING</td>
<td>8</td>
<td>true</td>
</tr>
</tbody></table>
<hr>
<h2 id="dead-lock교착-상태">Dead Lock(교착 상태)</h2>
<ul>
<li>두 스레드가 서로 lock 해제를 기다리고 있기 때문에 이 상태를 교착 상태라고 한다.</li>
<li>Deadlock을 피하는 방법: 교착 상태는 완전히 해결할 수 없다. 그러나 아래의 기본 규칙을 따르면 이러한 문제를 피할 수 있다.<ul>
<li>중첩된 lock 피하기: 여러 스레드에 lock을 제공하는 것을 피해야 하며 이것이 교착 상태의 주요 원인이다. 일반적으로 여러 스레드에 lock을 부여할 때 발생한다.</li>
<li>불필요한 lock 피하기: 중요한 스레드에 lock을 제공해야 한다.</li>
<li>스레드 조인 사용: 교착 상태는 일반적으로 한 스레드가 다른 스레드가 완료되기를 기다릴 때 발생한다. 이 경우 스레드가 소요되는 최대 시간으로 조인을 사용할 수 있다.</li>
<li>Lock Timeout: 스레드가 lock을 획득하는 시간을 지정하여 주어진 시간 내에 lock을 획득하지 못하면 lock 획득시도를 포기하고 일정 시간 후에 다시 시도하는 방식</li>
</ul>
</li>
</ul>
<br/>

<p>예제 - Dead Lock 발생 예제: Dead Lock에서는 resource1, 2에 접근한 패턴이 주요 문제이므로 해결하려면 공유 리소스의 접근 순서만 변경하면 된다.</p>
<pre><code class="language-java">public class TestDeadlockEx1{
    public static void main(String[] args){
        final String resource1 = &quot;테스트 리소스 1&quot;;
        final String resource2 = &quot;테스트 리소스 2&quot;;

        //t1은 resource1을 잠그고 resource2를 잠그려고 시도한다
        Thread t1 = new Thread(){
            public void run(){
                synchronized (resource1){
                    System.out.println(&quot;Thread 1: locked resource 1&quot;);
                    try{Thread.sleep(100);}catch(Exception e){}
                    synchronized(resource2){
                        System.out.println(&quot;Thread 1: locked resource 2&quot;);
                    }
                }
            }
        };

        //t2는 resource2를 잠그고 resource1을 잠그려고 시도한다.
        Thread t2 = enw Thread(){
            public void run(){
                synchronized (resource2){
                    System.out.println(&quot;Thread 2: locked resource 1&quot;);
                    try{Thread.sleep(100);}catch(Exception e){}
                    synchronized(resource1){
                        System.out.println(&quot;Thread 2: locked resource 2&quot;);
                    }
                }
            }
        };

        t1.start();
        t2.start();
    }
}

[결과]
//서로 리소스를 잠그고 기다리는 상황으로 스레드가 종료되지 않는다
//Thread t1이 resource1을 잠그고 resource2 사용을 위해 기다린다
//Thread t2가 resource2을 잠그고 resource1 사용을 위해 기다린다
Thread 2: locked resource 2
Thread 1: locked resource 1</code></pre>
<hr>
<h2 id="qa">Q&amp;A</h2>
<h3 id="동기화란-무엇인가">동기화란 무엇인가?</h3>
<p>동기화를 통해 스레드가 특정 메서드 또는 블록을 동시에 실행하지 않고 동기화하도록 만들 수 있다. 메서드 또는 블록이 동기화된 것으로 선언되면 하나의 스레드만 해당 메서드 또는 블록에 들어갈 수 있다. 한 스레드가 동기화된 메서드 또는 블록을 수행 중이면 해당 메서드 또는 블록을 실행하려는 다른 스레드는 첫 번째 스레드가 해당 메서드 또는 블록을 실행할 때까지 기다려야 하며 스레드 간섭을 피하고 스레드 안전성을 달성한다.</p>
<br/>

<h3 id="객체-잠금-또는-모니터란-무엇인가">객체 잠금 또는 모니터란 무엇인가?</h3>
<p>Java의 동기화는 객체 잠금 또는 모니터라는 엔티티를 중심으로 구축된다. 다음은 잠금 또는 모니터에 대한 간략한 설명이다.</p>
<ul>
<li>어떤 클래스에 대해 객체가 생성될 때마다 object lock이 생성되어 객체 내부에 저장된다.</li>
<li>하나의 객체에는 연결된 객체 잠금이 하나만 있다.</li>
<li>모든 스레드는 객체의 동기화된 메서드 또는 블록에 들어가고자 하며 해당 객체와 연결된 객체 잠금을 획득하고 실행이 완료된 후 잠금을 해제해야 한다.</li>
<li>해당 객체의 동기화된 메서드에 들어가려는 다른 스레드는 현재 실행 중인 스레드가 객체 잠금을 해제할 때까지 기다려야 한다.</li>
<li>정적 동기화 메서드 또는 블록에 들어가려면 정적 멤버가 클래스 메모리 내에 저장되므로 스레드는 해당 클래스와 관련된 클래스 잠금을 획득해야 한다.</li>
</ul>
<br/>

<h3 id="뮤텍스mutax란">뮤텍스(Mutax)란?</h3>
<p>동기화된 블록은 하나의 인수를 가지며 이를 뮤텍스라고 한다. 동기화된 블록이 비정적 메서드, 인스턴스 이니셜라이저 또는 생성자와 같은 비정적 정의 블록 내에서 정의된 경우 이 뮤텍스는 해당 클래스의 인스턴스여야 한다. 동기화된 블록이 정적 메서드 또는 정적 초기화 프로그램과 같은 정적 정의 블록 내부에 정의된 경우 이 뮤텍스는 ClassName.class와 같아야 한다.
동기화된 정적 메서드에는 클래스 수준 잠금이 필요하고 동기화된 비정적 메서드에는 객체 수준 잠금이 필요한데 이 두 가지 방법을 동시에 실행할 수 있다.</p>
<br/>

<h3 id="동기화된-메서드를-실행하는-동안-특정-스레드가-예외로-catch되면-실행-중인-스레드가-잠금을-해제하는가">동기화된 메서드를 실행하는 동안 특정 스레드가 예외로 catch되면 실행 중인 스레드가 잠금을 해제하는가?</h3>
<p>스레드는 실행이 정상적으로 완료되었는지 또는 예외로 포착되었는지 여부에 관계없이 잠금을 해제해야 한다.
동기화 메서드보다 동기화 블럭이 메서드의 일부만 동기화하기 때문에 성능이 향상된다.</p>
<br/>

<h3 id="java에서-스레드가-서로-통신하는-방법은-무엇인가">Java에서 스레드가 서로 통신하는 방법은 무엇인가?</h3>
<ul>
<li>wati(), notify() 및 notifyAll() 메소드를 사용하여 서로 통신한다.</li>
<li>wait(): 이 메서드는 현재 실행 중인 스레드에게 이 객체의 잠금을 해제하고 다른 스레드가 동일한 잠금을 획득할 때까지 기다렸다가 notify() 또는 notifyAll() 메서드를 사용하여 알릴 것을 지시한다.</li>
<li>notify(): 이 메서드는 이 객체에서 wait() 메서드를 호출한 스레드 하나를 임의로 깨운다.</li>
<li>notifyAll(): 이 메서드는 이 객체에서 wait() 메서드를 호출한 모든 스레드를 깨운다. 그러나 우선 순위에 따라 하나의 스레드만 이 객체의 잠금을 획득한다.</li>
</ul>
<br/>

<h3 id="blocked-상태와-waiting-상태의-차이점은-무엇인가">BLOCKED 상태와 WAITING 상태의 차이점은 무엇인가?</h3>
<p>스레드는 다른 스레드의 알림을 기다리는 경우 WAITING 상태가 된다. 다른 스레드가 원하는 lock을 해제하기를 기다리는 경우 스레드는 BLOCKED 상태가 된다.
스레드가 객체에서 wait() 또는 join() 메서드를 호출할 때 WAITING 상태가 된다. 대기 상태로 전환하기 전에 스레드는 보유하고 있는 객체의 lock을 해제한다. 동일한 객체에서 다른 스레드 호출이 notify() 또는 notifyAll()될 때까지 WAITING 상태로 유지된다.
다른 스레드가 동일한 객체에 대하 notify() 또는 notifyAll()을 호출하면 해당 객체의  잠금을 기다리는 스레드 중 하나 또는 모든 것이 통지된다. 알림을 받은 모든 스레드는 객체를 즉시 잠그지 않는다. 현제 스레드가 lock을 해제하면 우선순위에 따라 객체 lock을 받는다. 그 때까지 그들은 차단된 상태에 있을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[제너릭스 / 열거형 / 어노테이션]]></title>
            <link>https://velog.io/@ju-ei8ht/generics-enumeration-annotation</link>
            <guid>https://velog.io/@ju-ei8ht/generics-enumeration-annotation</guid>
            <pubDate>Thu, 26 Jan 2023 06:58:30 GMT</pubDate>
            <description><![CDATA[<h2 id="제너릭스generics">제너릭스(Generics)</h2>
<ul>
<li>java 1.5 버전부터 지원되는 기능으로 컴파일시 타입을 체크해 주는 기능(compile-time type check)이다.</li>
<li>클래스나 메서드 선언시 사용할 내부 데이터 타입을 컴파일시 미리 지정하고, 컬렉션 선언시에도 저장할 특정 타입을 지정할 수 있게 되었다.</li>
<li>제너릭스 기능 제공으로 객체의 타입 안전성을 제공하고, 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해졌다(기본형 타입은 사용할 수 없다).</li>
<li>Java 1.5 이전에는 일반적으로 클래스, 메서드, 컬렉션에서 여러 타입을 핸들링 해야할 경우 Object 타입으로 사용했었고, Object 타입으로 객체를 전달받은 곳에서 원하는 타입으로 캐스팅을 해야 했었고, 그 과정에서 타입 캐스트 오류가 발생했었다.</li>
<li>컬렉션 클래스&lt;저장할 객체의 타입&gt; 변수명 = new 컬렉션클래스&lt;저장할 객체의 타입&gt;();<ul>
<li>ArrayList&lt;Integer&gt; intList = new ArrayList&lt;Integer&gt;();</li>
<li>&lt;&gt;안에 지정한 타입으로 저장되어야 하며, 그렇지 않을 경우에는 컴파일 에러가 발생한다.</li>
<li>객체를 꺼낼 때는 형변환할 필요가 없다.</li>
<li>만약 &lt;&gt; 안의 타입을 상위클래스 타입으로 하면 하위클래스를 저장할 수는 있으나 꺼낼 때 형변환이 필요하다.</li>
<li>저장된 객체에 다형성을 사용하려면 와일드 카드 ?를 사용하면 된다. 와일드 카드를 사용할 경우 하나 이상의 타입을 지정할 수 있다. 아래의 예제처럼 지정을 하게 되면 Product의 하위타입도 매개변수로 넘겨줄 수 있다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public static void printAll2(ArrayList&lt;? extends Product&gt; list){
    for(Product p : list){
        System.out.println(p);
    }
}</code></pre>
<br/>

<ul>
<li>타입 안정성: 제너릭스에서는 단일 유형의 객체만 보유할 수 있고, 다른 객체를 저장할 수 없다. 제너릭스가 없으면 모든 유형의 객체를 저장할 수 있다.</li>
<li>타입 캐스팅이 필요하지 않다. Generics를 사용하지 않을 경우 타입을 지정해야 하는데 컴파일 타임에 체크가 안 되며, 잘못 지정한 경우 ClassCastException이 발생한다.</li>
<li>Compile-Time 체크: 컴파일 타임에 체크하므로 런타임에 문제가 발생하지 않는다. 런타임보다 컴파일타임에 문제를 처리하는 것은 좋은 개발 전략이다.</li>
</ul>
<pre><code class="language-java">public class GenericsVSNonGenericsEx1{
    public static void main(String[] args){
        //Generics를 지정하지 않을 경우 모든 객체를 저장
        List list1 = new ArrayList();
        list1.add(10);
        list1.add(&quot;10&quot;);
        //컬렉션의 get() 메서드들은 리턴타입이 Object라서 캐스팅이 필요하다
        Integer i = (Integer)list1.get(0);
        System.out.println(i);

        //Generics를 사용했을 경우
        List&lt;Integer&gt; list2 = new ArrayList&lt;Integer&gt;();
        list2.add(10);
        //list2.add(&quot;10&quot;); //compile-time error
        Integer gi = list2.get(0); //형변환 필요 없음
        System.out.println(gi);
    }
}</code></pre>
<br/>

<h3 id="제너릭스-선언">제너릭스 선언</h3>
<ul>
<li>T는 타입변수(type variable) 또는 타입 매개변수라고 하며, 임의의 참조형 타입을 의미한다.</li>
<li>T 외에도 다른 문자를 사용해도 상관없다.</li>
<li>클래스를 작성할 때, Object 타입 대신 T와 같은 타입변수를 사용한다.</li>
<li>참조변수, 생성자에 T 대신 실제 타입을 지정하면 형변환이 생략 가능하다.</li>
<li>AbstractDAO&lt;T&gt;: Generic class로 ‘T의 AbstractDAO’ 또는 ‘T AbstractDAO’라고 한다.</li>
<li>T: 타입 변수 또는 타입 매개변수라고 하며, 배열 타입변수는 허용되지 않는다.</li>
<li>AbstractDAO: raw type</li>
<li>Java 7버전부터는 인스턴스 생성시 타입을 추정할 수 있는 경우 타입 생략 가능</li>
</ul>
<pre><code class="language-java">class GenericsClassEx2&lt;T&gt;{
    T element;
    void setElement(T element){
        this.element = element;
    }
    T getElement(){
        return element;
    }
}
...
GenericsClassEx2&lt;String&gt; gc = new GenericsClassEx2&lt;String&gt;();
GenericsClassEx2&lt;String&gt; gc = new GenericsClassEx2&lt;&gt;(); //java 7부터 가능</code></pre>
<br/>

<h3 id="제너릭스-다형성">제너릭스 다형성</h3>
<ul>
<li>타입변수의 다형성: 예제처럼 특정 클래스를 상속하고 있는 하위클래스는 컬렉션 객체 생성시 상위클래스 타입으로 선언하여 해당 타입외에 객체는 저장할 수 없다.</li>
</ul>
<pre><code class="language-java">class LandAnimal{
    public void crying(){
        System.out.println(&quot;육지동물&quot;);
    }
}

class Cat extends LandAnimal{
    public void crying(){
        System.out.println(&quot;냐옹냐옹&quot;);
    }
}

class Dog extends LandAnimal{
    public void crying(){
        System.out.println(&quot;멍멍&quot;);
    }
}

class Sparrow{
    public void crying(){
        System.out.println(&quot;짹짹&quot;);
    }
}

class AnimalList&lt;T&gt;{
    ArrayList&lt;T&gt; al = new ArrayList&lt;T&gt;();
    void add(T animal) {
        al.add(animal);
    }
    T get(int index) {
        return al.get(index);
    }
    boolean remove(T animal) {
        return al.remove(animal);
    }
    int size() {
        return al.size();
    }
}

public class GenericsPolymorphicEx1 {
    public static void main(String[] args) {
        AnimalList&lt;LandAnimal&gt; landAnimal = new AnimalList&lt;&gt;();
        landAnimal.add(new LandAnimal());
        landAnimal.add(new Cat());
        landAnimal.add(new Dog());
        // landAnimal.add(new Sparrow()); // 오류가 발생함.

        for (int i = 0; i &lt; landAnimal.size() ; i++) {
            landAnimal.get(i).crying();
        }
    }
}

[결과]
육지동물
냐옹냐옹
멍멍</code></pre>
<br/>

<h3 id="제너릭스-메서드generics-method">제너릭스 메서드(Generics Method)</h3>
<ul>
<li>제너릭스 클래스와 마찬가지로 모든 유형의 데이터에 사용할 수 있는 메서드를 개발할 수 있는데 이것을 제너릭스 메서드라고 한다.</li>
<li>메서드의 선언부에 타입 변수를 사용한 메서드를 의미한다.</li>
</ul>
<pre><code class="language-java">class GenericsMethodClass {
    // generics method 생성
    public &lt;T&gt; void genericsMethod(T data) {
        System.out.println(&quot;Generics Method Data Passed: &quot; + data);
        System.out.println(&quot;매개변수 타입 : &quot; + data.getClass().getName()+&quot;\n&quot;);
    }
}
public class GenericsMethodEx1 {
    public static void main(String[] args) {
        GenericsMethodClass demo = new GenericsMethodClass();
        // generics method의 매개변수가 String 타입으로 동작됨
        demo.&lt;String&gt;genericsMethod(&quot;제너릭스 메서드&quot;);
        // generics method의 매개변수가 Integer 타입으로 동작됨
        demo.&lt;Integer&gt;genericsMethod(25);
        // 이 경우 컴파일러는 메서드에 전달된 값을 기반으로 형식 매개 변수를 일치시킬 수 있습니다.
        demo.genericsMethod(50);
    }
}

[결과]
Generics Method Data Passed: 제너릭스 메서드
매개변수 타입 : java.lang.String
Generics Method Data Passed: 25
매개변수 타입 : java.lang.Integer
Generics Method Data Passed: 50
매개변수 타입 : java.lang.Integer</code></pre>
<br/>

<h3 id="generics-collections">Generics Collections</h3>
<ul>
<li>Iterator&lt;E&gt;: Iterator에도 제너릭스가 적용된다.</li>
<li>Comparable&lt;T&gt;과 Collections.sort()<ul>
<li>클래스의 기본 정렬기준을 구현하는 Comparable 인터페이스에도 제너릭스가 적용된다.</li>
<li>선수들의 총점을 내림차순으로 기본정렬해서 출력하는 간단한 소스</li>
</ul>
</li>
<li>HashMap&lt;K,V&gt;<ul>
<li>HashMap은 key와 value의 쌍 구조로 저장하는 컬렉션 클래스로, 지정해줘야 할 타입이 두 개다.</li>
<li>두 개의 타입을 ‘,’로 구분한다.</li>
<li>저장된 값을 가져올 때 형변환을 하지 않아도 된다.
StudentHash s1 = map.get(”1-1”);</li>
</ul>
</li>
</ul>
<br/>

<h3 id="와일드-카드-">와일드 카드 ‘?’</h3>
<ul>
<li>제너릭 타입에 와일드 카드를 사용하면 여러 타입을 대입해서 사용할 수 있다.</li>
<li>이름에 제한을 두지 않는다.</li>
<li><span>&lt;? extends T&gt;</span>: T와 그 자손 타입만 가능하다.</li>
<li><span>&lt;? super T&gt;</span>: T와 그 조상 타입만 가능하다.</li>
<li><span>&lt;?&gt;</span>: 제한이 없으며, 모든 타입이 가능하다. &lt;? extends Object&gt;와 동일하다.</li>
<li>Person을 상속하는 Man, Woman 이라는 클래스가 있다고 가정하면 아래와 같이 매개변수에 Person을 상속하는 타입을 매개변수로 전달할 수 있다.</li>
</ul>
<pre><code class="language-java">void add(List&lt;? extends Person&gt; list)</code></pre>
<ul>
<li>add() 메서드의 매개변수로 Person을 상속하고 있는 Man, Woman 타입으로 파라미터 전달 가능, 그 외의 타입은 불가능하다.</li>
</ul>
<pre><code class="language-java">List&lt;Man&gt; mlist = new ArrayList&lt;Man&gt;();
…
add(mlist);</code></pre>
<br/>

<h3 id="제너릭스-상속관계-선언">제너릭스 상속관계 선언</h3>
<ul>
<li>만일 매개변수가 여러개면서 extends 타입이 동일할 경우 아래와 같이 간략히 할 수 있다.
(만약 Product가 클래스가 아닌 interface라도 implements가 아닌 extends를 사용해야 한다)</li>
<li>클래스와 인터페이스를 동시에 상속받고 구현해야 한다면 &amp; 기호를 사용하면 된다.</li>
</ul>
<pre><code class="language-java">interface WarmBlood{...}
...
// implements 키워드를 사용해서는 안됨
class AnimalList&lt;T extends WarmBlood&gt; { ... }

// 클래스와 인터페이스를 동시에 상속받고 구현해야 한다면 &amp; 사용
class AnimalList&lt;T extends LandAnimal &amp; WarmBlood&gt; { ... }</code></pre>
<pre><code class="language-java">public static void printAll2(ArrayList&lt;? extends Product&gt; list, ArrayList&lt;? extends Product&gt; list2){
    for(Product p : list){
        System.out.println(p);
    }
}

↓

public static &lt;T extends Product&gt; void printAll2(ArrayList&lt;T&gt; list, ArrayList&lt;T&gt; list2){
    for(Product p : list){
        System.out.println(p);
    }
}</code></pre>
<hr>
<h2 id="열거체enumeration-type">열거체(Enumeration type)</h2>
<ul>
<li>Java 1.5버전부터 열거체를 정의한 Enum 클래스를 사용할 수 있다.</li>
<li>장점<ul>
<li>열거체를 비교할 때 실제 값 뿐만 아니라 타입까지도 체크한다.</li>
<li>열거체의 상수값이 재정의되더라도 재컴파일할 필요가 없다.</li>
</ul>
</li>
<li>관련된 상수들을 같이 묶어 놓은 것이다.</li>
<li>열거형 정의 방법: enum 열거형명 {상수명1, 상수명2, …}</li>
<li>열거체의 첫번째 상수값을 0부터 설정되어 옆의 정의된 값 순으로 1씩 증가된다.</li>
<li>선언 및 방법</li>
</ul>
<pre><code class="language-java">enum Colors{RED, ORANGE, YELLOW, GREEN, BLUE}
class EnumTest{
    Colors col; //열거형 인스턴스 변수
    void test(){
        col = Colors.BLUE;
    }
}</code></pre>
<ul>
<li>열거형은 값의 비교도 가능하다(==, compareTo() 사용 가능).</li>
</ul>
<pre><code class="language-java">if(col == Colors.BLUE)</code></pre>
<br/>

<p>예제</p>
<pre><code class="language-java">enum Colors{RED,ORANGE,YELLOW,GREEN,BLUE}

public class EnumEx01{
    public static void main(String[] args){
        Colors[] arr = Colors.values();
        for(Colors rb : arr){
            System.out.println(rb);
        }
        System.out.println(&quot;--------------&quot;);
        Colors.cols = Colors.valueOf(&quot;GREEN&quot;);
        System.out.println(cols);
    }
}

[결과]
RED
ORANGE
YELLOW
GREEN
BLUE
--------------
GREEN</code></pre>
<br/>

<ul>
<li>상수값을 별도 지정하고 싶을 경우 괄호{ }를 추가하고 지정하면 된다.</li>
<li>이때 인스턴스 변수와 생성자를 별도로 추가해야 한다.</li>
</ul>
<pre><code class="language-java">public enum Rainbow{
    RED(3), ORANGE(10), YELLOW(21), GREEN(5), BLUE(1), INDIGO(-1), VIOLET(-11);

    //인스턴스 변수 선언
    private final int value;
    //생성자 선어
    Rainbow(int value) {this.value = value;}
    public int getValue(){return value;}
}</code></pre>
<br/>

<ul>
<li>java.lang.Enum 클래스는 모든 자바 열거체의 공통 조상 클래스이다.</li>
<li>Enum 클래스는 열거체를 컨트롤하기 위한 다양한 메서드를 포함하고 있다.</li>
<li>values() 메서드<ul>
<li>해당 열거체의 모든 상수를 저장한 배열을 생성하여 반환한다.</li>
<li>자바의 모든 열거체를 컴파일러가 자동으로 추가해주는 메서드이다.</li>
</ul>
</li>
<li>valueOf() 메서드<ul>
<li>전달된 문자열과 일치하는 해당 열거체의 상수를 반환한다.</li>
</ul>
</li>
<li>ordinal() 메서드<ul>
<li>해당 열거체 상수가 열거체 정의에서 정의된 순서(0부터 시작)를 반환한다.</li>
<li>반환되는 값은 열거체 정의에서 해당 열거체 상수가 정의된 순서이지 상수값은 아니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }
public class EnumTestEx1 {
    public static void main(String[] args) {
        Color[] arr = Color.values();
        for (Color c : arr) {
            System.out.println(c);
        }

        Color cs = Color.valueOf(&quot;GREEN&quot;);
        System.out.println(cs);

        int idx = Color.YELLOW.ordinal();
        System.out.println(idx);
    }
}

[결과]
RED
ORANGE
YELLOW
GREEN
BLUE
INDIGO
VIOLET
GREEN
2</code></pre>
<hr>
<h2 id="어노테이션annotation">어노테이션(Annotation)</h2>
<ul>
<li>주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공한다. (Annotation은 interface이다)</li>
<li>어노테이션은 메타데이터를 나타내는 태그인데 클래스, 인터페이스, 메서드 또는 필드에 첨부되어 Java 컴파일러 및 JVM에서 사용할 수 있는 몇 가지 추가정보를 나타낸다.</li>
<li>이 어노테이션은 추가 정보를 제공하는 과거 XML의 대체 옵션이다.</li>
<li>어노테이션 사용 예</li>
</ul>
<pre><code class="language-java">@Test //해당 메소드가 단위 테스트임을 명시하는 어노테이션이다
private void runTest(){...}</code></pre>
<ul>
<li>*이 붙은 것은 메타 어노테이션 그 외는 Java 기본 제공 주석</li>
<li>Java 8버전부터는 클래스, 메서드, 인터페이스 필드 등의 요소 선언 위에 어노테이션을 배치(Annotation placement)할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>어노테이션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>@Override</td>
<td>컴파일러에게 오버라이딩하는 메서드라는 것을 알린다.</td>
</tr>
<tr>
<td>@Deprecated</td>
<td>앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.</td>
</tr>
<tr>
<td>@SuppressWarnings</td>
<td>컴파일러의 특정 경고메시지가 나타나지 않게 해준다.</td>
</tr>
<tr>
<td>@SafeVarargs</td>
<td>제너릭스 타입의 가변인자에 사용한다. (jdk 1.7)</td>
</tr>
<tr>
<td>@FunctionalInterface</td>
<td>함수형 인터페이스라는 것을 알린다. (jdk 1.8)</td>
</tr>
<tr>
<td>@Native</td>
<td>native 메서드에서 참조되는 상수 앞에 붙인다. (jdk 1.8)</td>
</tr>
<tr>
<td>@Target*</td>
<td>어노테이션이 적용가능한 대상을 지정하는데 사용한다.</td>
</tr>
<tr>
<td>@Documented*</td>
<td>어노테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다.</td>
</tr>
<tr>
<td>@Inherited*</td>
<td>어노테이션이 자손 클래스에 상속되도록 한다.</td>
</tr>
<tr>
<td>@Retention*</td>
<td>어노테이션이 유지되는 범위를 지정하는데 사용한다.</td>
</tr>
<tr>
<td>@Repeatable*</td>
<td>어노테이션을 반복해서 적용할 수 있게 한다. (jdk 1.8)</td>
</tr>
</tbody></table>
<ul>
<li>내장 어노테이션(Built-in Annotation): 기본으로 제공되는 주석이다.<ul>
<li>@Override, @SuppressWarnings, @Deprecated</li>
</ul>
</li>
</ul>
<br/>

<h3 id="커스텀-어노테이션custom-annotation-사용자-정의-어노테이션">커스텀 어노테이션(Custom Annotation): 사용자 정의 어노테이션</h3>
<ul>
<li>어노테이션 유형: Marker Annotation, Single-Value Annotation, Multi-Value Annotation</li>
<li>Marker Annotation: 메서드가 없는 주석을 마커 어노테이션이라고 한다. @Override, @Deprecated가 마커 어노테이션이다.</li>
</ul>
<pre><code class="language-java">@interface MyAnnotation()</code></pre>
<ul>
<li>Single-Value Annotation: 하나의 메서드가 있는 주석을 단일 값 주석이라고 한다.
기본값도 제공할 수 있다.</li>
</ul>
<pre><code class="language-java">@interface MyAnnotation(){
    int value() default 0;
}

//사용 예 @MyAnnotation(value=20)</code></pre>
<ul>
<li>Multi-Value Annotation: 두 개 이상의 메서드가 있는 주석을 다중 값 주석이라고 한다.</li>
</ul>
<pre><code class="language-java">@interface MyAnnotation{
    int value1() default 1;
    String value2() default &quot;&quot;;
    String value3() default &quot;i&quot;;
}

//사용 예 @MyAnnotation(value1=10,value2=&quot;gil Dong&quot;,value3=&quot;xxx&quot;)</code></pre>
<blockquote>
<p><strong>주의사항</strong></p>
<ul>
<li>메서드에서 throws 절이 없어야 한다.</li>
<li>메서드에 매개변수가 없어야 한다.</li>
<li>주석을 정의하려면 interface 키워드 바로 옆에 @을 붙여야 한다.</li>
<li>메서드에 기본값을 할당할 수 없다.</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Collection API]]></title>
            <link>https://velog.io/@ju-ei8ht/collection-api</link>
            <guid>https://velog.io/@ju-ei8ht/collection-api</guid>
            <pubDate>Thu, 26 Jan 2023 06:49:14 GMT</pubDate>
            <description><![CDATA[<ul>
<li>Java의 Collection은 객체 그룹을 저장하고 핸들링하기 위한 아키텍처를 제공하는 컬렉션 프레임워크(Collection framework)이다.<ul>
<li>컬렉션(다수의 객체)을 다루기 위한 표준화된 프로그래밍 방식</li>
<li>컬렉션을 쉽고 편리하게 다룰 수 있는 다양한 클래스를 제공</li>
<li>java.util 패키지에 포함되어 있다.</li>
</ul>
</li>
<li>검색, 정렬, 삽입, 조작, 삭제와 같은 데이터에 대해 수행하는 모든 작업을 처리할 수 있다.</li>
<li>Collection은 object elements들을 그룹화하여 저장한 single object이다.</li>
<li>성능을 높이면서 소프트웨어의 재사용을 높인다.</li>
<li>다양한 인터페이스(Set, List, Queue, Deque)와 클래스(ArrayList, LinkedList, HashSet, HashMap 등)를 제공한다.</li>
<li>Colleciton interface는 collection을 다루는 가장 기본적인 기능을 정의한다.</li>
</ul>
<br/>

<p><strong>Collection의 핵심 인터페이스</strong>
모든 컬렉션 클래스는 List, Set, Map 인터페이스 중 하나의 인터페이스를 구현하고 있다.</p>
<table>
<thead>
<tr>
<th>인터페이스</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>List</td>
<td>순서가 있는 데이터의 집합, 데이터의 중복을 허용한다. (예: 대기자 명단)<br/>구현 클래스: ArrayList, LinkedList, Stack, Vector 등</td>
</tr>
<tr>
<td>Set</td>
<td>순서가 없는 데이터의 집합, 데이터의 중복을 허용하지 않는다. (예: 양의 정수 집합, 소수의 집합)<br/>구현 클래스: HashSet, TreeSet 등</td>
</tr>
<tr>
<td>Map</td>
<td>키와 값의 한 쌍으로 이루어진 데이터의 집합으로 순서는 없고, 키는 중복을 허용하지 않으며, 값을 중복으로 허용한다. (예: 우편번호, 지역번호(전화번호))<br/>구현 클래스: HashMap, TreeMap, Properties 등</td>
</tr>
</tbody></table>
<br/>

<blockquote>
<p><strong>iterator(반복자) 인터페이스</strong>
컬렉션 요소에 접근하는데 사용할 수 있는 메서드를 제공한다. 모든 컬렉션에는 iterator() 메서드를 포함하고 있으며, 이 메서드로 iterator 인스턴스를 반환한다. 이 인터페이스는 모든 컬렉션의 Root 인터페이스이고, Collection interface는 iterable 인터페이스를 구현한다.
iterator&lt;T&gt; iterator(); iterable 인터페이스는 T 타입의 요소에 대한 iterator를 반환하는 하나의 추상 메서드만 선언되어 있다.  </p>
</blockquote>
<hr>
<h2 id="collection-인터페이스">Collection 인터페이스</h2>
<p>Collection 인터페이스에서 제공하는 주요 메서드</p>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>boolean add(E e)</td>
<td>해당 컬렉션에 전달된 요소를 추가한다.</td>
</tr>
<tr>
<td>void clear()</td>
<td>해당 컬렉션의 모든 요소를 제거한다.</td>
</tr>
<tr>
<td>boolean contains(Object o)</td>
<td>해당 컬렉션이 전달된 객체를 포함하고 있는지 확인한다.</td>
</tr>
<tr>
<td>boolean equals(Object o)</td>
<td>해당 컬렉션과 전달된 객체가 동일한지 확인한다.</td>
</tr>
<tr>
<td>boolean isEmpty()</td>
<td>해당 컬렉션이 비어있는지 확인한다.</td>
</tr>
<tr>
<td>Iterator&lt;E&gt; iterator()</td>
<td>해당 컬렉션의 반복자(iterator)를 반환한다.</td>
</tr>
<tr>
<td>boolean remove(Object o)</td>
<td>해당 컬렉션에 전달된 객체를 제거한다.</td>
</tr>
<tr>
<td>int size()</td>
<td>해당 컬렉션의 요소의 총 개수를 반환한다.</td>
</tr>
<tr>
<td>Object[] toArray()</td>
<td>해당 컬렉션의 모든 요소를 Object 타입의 배열로 반환한다.</td>
</tr>
</tbody></table>
<hr>
<h2 id="list-인터페이스">List 인터페이스</h2>
<ul>
<li>List 인터페이스는 요소의 저장 순서가 유지되고 같은 요소의 중복 저장을 허용한다.</li>
<li>대표적인 List 컬렉션 클래스는 아래와 같다.<ul>
<li>ArrayList, LinkedList, Stack, Vector</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class ListExample{
    public static void main(String[] args){
        List list = new ArrayList();
        list.add(&quot;one&quot;);
        list.add(&quot;second&quot;);
        list.add(&quot;3rd&quot;);
        list.add(Integer.valueOf(4));
        list.add(Float.valuof(5.0F));
        list.add(&quot;second&quot;); //중복 저장을 허용한다
        list.add(Integer.valueOf(4)); //중복 저장을 허용한다
        System.out.println(list);
    }
}

[결과]
[one, second, 3rd, 4, 5.0, second, 4]</code></pre>
<br/>

<p><strong>List 인터페이스 주요 메서드</strong></p>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>add()</td>
<td>List에 요소를 추가</td>
</tr>
<tr>
<td>addAll()</td>
<td>모든 요소를 다른 List에 추가</td>
</tr>
<tr>
<td>get()</td>
<td>List 요소에 접근하는데 이용</td>
</tr>
<tr>
<td>set()</td>
<td>List의 요소 변경</td>
</tr>
<tr>
<td>remove()</td>
<td>List의 요소를 제거</td>
</tr>
<tr>
<td>clear()</td>
<td>List에서 모든 요소를 제거</td>
</tr>
<tr>
<td>size()</td>
<td>List의 길이를 반환</td>
</tr>
<tr>
<td>toArray()</td>
<td>List를 배열로 반환</td>
</tr>
<tr>
<td>contains()</td>
<td>List에 지정된 요소가 포함된 경우 반환</td>
</tr>
<tr>
<td>iterator()</td>
<td>List 요소의 순차적 접근용 Iterator 객체를 반환</td>
</tr>
</tbody></table>
<br/>

<h3 id="arraylist">ArrayList</h3>
<ul>
<li>List인터페이스를 구현하므로, 저장순서가 유지되고 중복을 허용한다. (java 1,2부터 제공)</li>
<li>가장 많이 사용되는 컬렉션 클래스 중 하나이다.</li>
<li>데이터의 저장공간으로 배열을 사용하지만 요소가 추가되더라도 자동으로 수행된다.</li>
<li>배열을 이용하기 때문에 인덱스를 이용해서 배열 요소에 빠르게 접근할 수 있다.</li>
<li>Vector는 자체적을 동기화처리가 되어 있으나 ArrayList는 그렇지 않다.</li>
</ul>
<pre><code class="language-java">public class ArrayListEx1 {
    public static void main(String[] args){
        ArrayList&lt;Integer&gt; arrList = new ArrayList&lt;Integer&gt;();
        // add() 메소드를 이용한 요소의 저장
        arrList.add(40);
        arrList.add(20);
        arrList.add(30);
        arrList.add(10);
        // [40, 20, 30, 10]
        System.out.println(&quot;1-&gt;&quot; + arrList);
        // remove() 메소드를 이용한 요소의 제거
        arrList.remove(1);
        // [40, 30, 10]
        System.out.println(&quot;2-&gt;&quot; + arrList);
        // Collections.sort() 메소드를 이용한 요소의 정렬
        Collections.sort(arrList);
        // iterator() 메소드와 get() 메소드를 이용한 요소의 출력
        System.out.print(“3-&gt;&quot;);
        Iterator&lt;Integer&gt; iter = arrList.iterator();
        // 10 30 40 
        while (iter.hasNext()) {
            System.out.print(iter.next() + &quot; &quot;);
        }
        System.out.println();
        // set() 메소드를 이용한 요소의 변경
        arrList.set(0, 20);
        // [20, 30, 40]
        System.out.println(&quot;4-&gt;&quot; + arrList);
        // size() 메소드를 이용한 요소의 총 개수
        System.out.println(&quot;5-&gt; 리스트의 크기 : &quot; + arrList.size());
    }
}</code></pre>
<br/>

<p>예제</p>
<pre><code class="language-java">public static void main(String[] args){
    ArrayList list1 = new ArrayList(10);
    list1.add(Integer.valueOf(5));
    list1.add(Integer.valueOf(4));
    list1.add(Integer.valueOf(2));
    list1.add(Integer.valueOf(0));
    list1.add(Integer.valueOf(1));
    list1.add(Integer.valueOf(3));

    ArrayList list2 = new ArrayList(list1.subList(1,4));
    print(list1, list2);

    Collections.sort(list1); //list1과 list2를 정렬한다.
    Collections.sort(list2);
    print(list1, list2);

    System.out.println(&quot;list1.containsAll(list2):&quot;+list1.containsAll(list2));

    list2.add(&quot;B&quot;);
    list2.add(&quot;C&quot;);
    list2.add(3, &quot;A&quot;);
    print(list1, list2);

    list2.set(3, &quot;AA&quot;);
    print(list1, list2);

    //list1에서 list2와 겹치는 부분만 남기고 나머지는 삭제한다.
    System.out.println(&quot;list1.retainAll(list2):&quot;+ list1.retainAll(list2));
    print(list1, list2);
}

static void print(ArrayList list1, ArrayList list2){
    System.out.println(&quot;list1:&quot;+list1);
    System.out.println(&quot;list2:&quot;+list2);
    System.out.println();
}

[결과]
list1:[5, 4, 2, 0, 1, 3]
list2:[4, 2, 0]
list1:[0, 1, 2, 3, 4, 5]
list2:[0, 2, 4]
list1.containsAll(list2):true
list1:[0, 1, 2, 3, 4, 5]
list2:[0, 2, 4, A, B, C]
list1:[0, 1, 2, 3, 4, 5]
list2:[0, 2, 4, AA, B, C]
list1.retainAll(list2):true
list1:[0, 2, 4]
list2:[0, 2, 4, AA, B, C]</code></pre>
<br/>

<h3 id="linkedlist">LinkedList</h3>
<ul>
<li>LinkedList 클래스는 ArrayList 클래스가 배열을 이용하여 요소를 저장함으로써 발생하는 단점을 극복하기 위해 만들어졌으며(Java 1,2부터 제공) 내부적으로 연결 리스트(linked list)를 이용하여 요소를 저장한다.</li>
<li>배열은 저장된 요소가 순차적으로 저장되지만 연결 리스트는 저장된 요소가 비순차적으로 분포되며, 요소들 사이를 링크(link)로 연결하여 구성한다.</li>
<li>다음 요소를 가리키는 참조만을 가지는 연결리스트는 단일 연결 리스트(singly linked list)라고 한다.</li>
<li>단일 연결리스트는 요소의 저장과 삭제 작업이 다음 요소를 가리키는 참조만 변경하면 되므로, 아주 빠르게 처리될 수 있다. 하지만 단일 연결 리스트는 현재 요소에서 이전 요소로 접근하기가 어렵다.</li>
<li>따라서 이전 요소를 가리키는 참조도 가지는 이중 연결 리스트(doubly linked list)가 좀 더 많이 사용되며, LinkedList 클래스도 이중 연결 리스트를 구현한 것이다.</li>
</ul>
<br/>

<p>예제</p>
<pre><code class="language-java">public class LinkedListEx{
    public static void main(String[] args){
        LinkedList&lt;String&gt; lnkList = new LinkedList&lt;String&gt;();
        //add() 메소드를 이용한 요소의 저장
        lnkList.add(&quot;넷&quot;);
        lnkList.add(&quot;둘&quot;);
        lnkList.add(&quot;셋&quot;);
        lnkList.add(&quot;하나&quot;);
        System.out.println(lnkList);

        // remove() 메소드를 이용한 요소의 제거
        lnkList.remove(1);
        System.out.println(lnkList);

        // get() 메소드로 요소 접근
        String str = lnkList.get(2);
        System.out.println(&quot;요소 접근 : &quot; + str);

        // indexOf() 메소드로 요소의 값 index를 반환
        int index = lnkList.indexOf(&quot;셋&quot;);
        System.out.println(&quot;index는 &quot; + index);

        // set() 메소드를 이용한 요소의 변경
        lnkList.set(2, &quot;둘&quot;);
        System.out.println(lnkList);
    }
}

[결과]
[넷, 둘, 셋, 하나]
[넷, 셋, 하나]
요소 접근 : 하나
index는 1
[넷, 셋, 둘]</code></pre>
<br/>

<h3 id="arraylist-vs-linkedlist">ArrayList vs LinkedList</h3>
<ul>
<li>사용법에는 차이가 없으며, 내부적으로 요소를 처리하는 방법이 다르다.</li>
<li>순차적으로 데이터를 추가/삭제 - ArrayList가 빠르다</li>
<li>비순차적으로 데이터를 추가/삭제 - LinkedList가 빠르다</li>
<li>접근시간(access time) - ArrayList가 빠르다</li>
</ul>
<hr>
<h2 id="스택stack">스택(Stack)</h2>
<ul>
<li>Stack 클래스는 List 컬렉션 클래스의 Vector 클래스를 상속받아 스택 메모리 구조를 제공한다.</li>
<li>LIFO(후입선출: Last In First Out) 구조로 마지막에 저장된(push) 것을 제일 먼저 가져온다(pop).</li>
<li>스택 메모리 구조는 선형 메모리 공간에 데이터를 저장하면서 후입선출(LIFO)의 시멘틱을 따르는 자료구조이다.</li>
<li>Stack 클래스는 스택 메모리 구조를 표현하기 위해, Vector 클래스의 메서드를 5개만 상속받아 사용한다.</li>
</ul>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>boolean empty()</td>
<td>스택이 비어 있으면 true, 아니면 false를 반환</td>
</tr>
<tr>
<td>E peek()</td>
<td>스택의 제일 상단에 있는 (제일 마지막으로 저장된) 요소를 반환</td>
</tr>
<tr>
<td>E pop()</td>
<td>스택의 제일 상단에 있는 (제일 마지막으로 저장된) 요소를 반환하고, 해당 요소를 스택에서 제거</td>
</tr>
<tr>
<td>E push(E item)</td>
<td>스택의 제일 상단에 전달된 요소를 삽입</td>
</tr>
<tr>
<td>int search(Object o)</td>
<td>전달된 객체가 존재하는 위치의 인덱스를 반환. 요소 위치는 1부터 시작</td>
</tr>
</tbody></table>
<br/>

<p>예제</p>
<pre><code class="language-java">public class StackEx1{
    public static void main(String[] args){
        Stack&lt;Integer&gt; st = new Stack&lt;Integer&gt;(); //스택의 생성
        //Deque&lt;Integer&gt; st = new ArrayDeque&lt;Integer&gt;();
        //push() 메소드를 이용한 요소의 저장
        st.push(4);
        st.push(2);
        st.push(3);
        st.push(1);

        // peek() 메소드를 이용한 요소의 반환
        System.out.println(st.peek()); // 1
        System.out.println(st); // [4, 2, 3, 1]

        // pop() 메소드를 이용한 요소의 반환 및 제거
        System.out.println(st.pop()); // 1
        System.out.println(st); // [4, 2, 3]

        // search() 메소드를 이용한 요소의 위치 검색
        System.out.println(st.search(4)); // 3
        System.out.println(st.search(3)); // 1
    }
}

[결과]
1
[4, 2, 3, 1]
1
[4, 2, 3]
3
1</code></pre>
<hr>
<h2 id="큐queue">큐(Queue)</h2>
<ul>
<li>큐 메모리 구조는 별도의 인터페이스로 제공되며 인터페이스를 상속한 하위 인터페이스는 아래와 같다.<ul>
<li>Deque, BlockingDeque, BlockingQueue, TransferQueue</li>
</ul>
</li>
<li>Deque 인터페이스를 구현한 LinkedList 클래스가 큐 메모리 구조를 구현하는데 가장 많이 사용된다.</li>
<li>큐 메모리 구조는 선형 메모리 공간에 데이터를 저장하면서 선입선출(FIFO: First In First Out)의 시멘틱을 따르는 자료 구조이다.</li>
<li>가장 먼저 저장된(push) 데이터가 가장 먼저 인출(pop)되는 구조이다.</li>
<li>Java 6버전부터 지원되는 ArrayDeque 클래스는 스택과 큐 메모리 구조를 모두 구현하는데 가장 적합한 클래스이다.</li>
</ul>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>boolean add(E e)</td>
<td>큐의 맨 뒤에 전달된 요소를 삽입한다. 성공하면 true를 반환. 큐에 여유공간이 없으면 IllegalStateException이 발생한다.</td>
</tr>
<tr>
<td>E element()</td>
<td>큐의 맨 앞에 있는(제일 먼저 저장된) 요소를 반환한다.</td>
</tr>
<tr>
<td>boolean offer(E e)</td>
<td>큐의 맨 뒤에 전달된 요소를 삽입한다.</td>
</tr>
<tr>
<td>E peek()</td>
<td>큐의 맨 앞에 있는(제일 먼저 저장된) 요소를 반환한다. 큐가 비어있으면 null을 반환한다.</td>
</tr>
<tr>
<td>E poll()</td>
<td>큐의 맨 앞에 있는(제일 먼저 저장된) 요소를 반환하고, 해당 요소를 큐에서 제거한다. 만약 큐가 비어있으면 null을 반환한다.</td>
</tr>
<tr>
<td>E remove()</td>
<td>큐의 맨 앞에 있는(제일 먼저 저장된) 요소를 제거한다.</td>
</tr>
</tbody></table>
<br/>

<p>예제</p>
<pre><code class="language-java">public class QueueEx1 {
    public static void main(String args[]) {
        LinkedList&lt;String&gt; qu = new LinkedList&lt;String&gt;();// 큐의 생성
        // add() 메소드를 이용한 요소의 저장
        qu.add(&quot;넷&quot;);
        qu.add(&quot;둘&quot;);
        qu.add(&quot;셋&quot;);
        qu.add(&quot;하나&quot;);

        // peek() 메소드를 이용한 요소의 반환
        System.out.println(qu.peek());
        System.out.println(qu);

        // poll() 메소드를 이용한 요소의 반환 및 제거
        System.out.println(qu.poll());
        System.out.println(qu);

        // remove() 메소드를 이용한 요소의 제거
        qu.remove(&quot;하나&quot;);
        System.out.println(qu);
    }
}

[결과]
넷
[넷, 둘, 셋, 하나]
넷
[둘, 셋, 하나]
[둘, 셋]</code></pre>
<hr>
<h2 id="set-인터페이스">Set 인터페이스</h2>
<ul>
<li>List 인터페이스는 요소의 저장 순서가 유지되지 않고 같은 요소의 중복 저장을 허용 안 한다.</li>
<li>대표적인 Set 컬렉션 클래스들: HashSet, TreeSet</li>
<li>HashSet은 Set 컬렉션 클래스 중 가장 많이 사용되는 클래스 중 하나이다. (java 1.2부터 제공)</li>
<li>해시 알고리즘을 사용하여 검색 속도가 매우 빠르다.</li>
<li>HashSet 클래스는 내부적으로 HashMap 인스턴스를 이용하여 요소를 저장한다.</li>
<li>요소의 저장 순서를 유지해야 한다면 java 1.4부터 제공되는 LinkedHashSet 클래스를 사용하면 된다.</li>
</ul>
<pre><code class="language-java">public class HashSetEx1 {
    public static void main(String args[]) {
        HashSet&lt;String&gt; hs01 = new HashSet&lt;String&gt;();
        HashSet&lt;String&gt; hs02 = new HashSet&lt;String&gt;();

        // add() 메소드를 이용한 요소의 저장
        hs01.add(&quot;홍길동&quot;);
        hs01.add(&quot;이순신&quot;);
        System.out.println(hs01.add(&quot;임꺽정&quot;));
        System.out.println(hs01.add(&quot;임꺽정&quot;)); // 중복된 요소의 저장
        System.out.println(hs01);

        // add() 메소드를 이용한 요소의 저장
        hs02.add(&quot;임꺽정&quot;);
        hs02.add(&quot;홍길동&quot;);
        hs02.add(&quot;이순신&quot;);

        // iterator() 메소드를 이용한 요소의 출력
        Iterator&lt;String&gt; iter02 = hs02.iterator();
        while (iter02.hasNext()) {
            System.out.print(iter02.next() + &quot; &quot;);
        }
        System.out.println();

        // size() 메소드를 이용한 요소의 총 개수
        System.out.println(&quot;집합의 크기 : &quot; + hs02.size());
    }
}

[결과]
true
false
[홍길동, 이순신, 임꺽정]
홍길동 이순신 임꺽정
집합의 크기 : 3</code></pre>
<br/>

<h3 id="tree-set">Tree Set</h3>
<ul>
<li>TreeSet 클래스(java 1.2부터 제공)는 데이터가 정렬된 상태로 저장되는 이진 검색트리(binary search tree)의 형태로 요소를 저장한다.</li>
<li>이진 검색 트리는 데이터를 추가하거나 제거하는 등의 기본 동작 시간이 매우 빠르다.</li>
<li>TreeSet 인스턴스에 저장되는 요소들은 모두 정렬된 상태로 저장된다.</li>
<li>범위 검색과 정렬에 유리한 컬렉션 클래스로 HashSet보다 데이터 추가, 삭제에 시간이 더 걸린다.</li>
</ul>
<pre><code class="language-java">public class TreeSetEx1 {
    public static void main(String args[]) {
        TreeSet&lt;Integer&gt; ts = new TreeSet&lt;Integer&gt;();

        // add() 메소드를 이용한 요소의 저장 ts.add(30);
        ts.add(40);
        ts.add(20);
        ts.add(10); System.out.println(ts);

        // remove() 메소드를 이용한 요소의 제거
        ts.remove(40);
        // iterator() 메소드를 이용한 요소의 출력
        Iterator&lt;Integer&gt; iter = ts.iterator();
        while (iter.hasNext()) {
            System.out.print(iter.next() + &quot; &quot;); 
        }
        System.out.println();

        // size() 메소드를 이용한 요소의 총 개수
        System.out.println(&quot;이진 검색 트리의 크기 : &quot; + ts.size()); 
    }
}

[결과]
[10, 20, 30, 40]
10 20 30
이진 검색 트리의 크기:3</code></pre>
<hr>
<h2 id="iterators">Iterators</h2>
<ul>
<li>Iterator는 collection의 elements를 차례대로 스캔하여 작업을 처리해야 할 경우에 많이 사용한다.</li>
<li>ListIterator는 List를 forward(next()) 또는 backward(previous())로 스캔할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>hasNext()</td>
<td>element가 남아 있는지 확인하여 있으면 true, 없으면 false를 반환한다.</td>
</tr>
<tr>
<td>next()</td>
<td>다음 element를 읽어온다. next()를 호출하기 전에 hasNext()를 호출해서 읽어 올 요소가 있는지 확인하는 것이 안전하다.</td>
</tr>
<tr>
<td>remove()</td>
<td>next()로 읽어온 element를 삭제한다. next()를 호출한 다음에 remove()를 호출해야 한다. (선택적)</td>
</tr>
</tbody></table>
<br/>

<p>예제</p>
<pre><code class="language-java">List list = new ArrayList(10);
list.add(Integer.valueOf(5));
list.add(Integer.valueOf(4));
list.add(INteger.valueOf(2));

Iterator elements = list.iterator();
while(elements.hasNext()){
    System.out.println(element.next());
}

[결과]
5
4
2</code></pre>
<hr>
<h2 id="map">Map</h2>
<ul>
<li>Map은 key와 value가 한 쌍의 데이터로 저장되는 collection object이다.</li>
<li>key는 실질적인 값(value)을 찾기 위한 역할을 한다.</li>
<li>Map 인터페이스를 구현한 모든 Map 컬렉션 클래스는 다음과 같은 특징을 가진다.<ul>
<li>요소의 저장 순서를 유지하지 않는다.</li>
<li>키는 중복을 허용하지 않지만, 값의 중복을 허용한다.</li>
</ul>
</li>
<li>대표적인 Map 컬렉션 클래스들은 다음과 같다.<ul>
<li>HashMap, Hashtable, TreeMap</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>void clear()</td>
<td>맵의 모든 매핑을 제거한다.</td>
</tr>
<tr>
<td>boolean containsKey(Object key)</td>
<td>맵이 전달된 키를 포함하고 있는지 여부를 확인한다.</td>
</tr>
<tr>
<td>boolean containsValue(Object key)</td>
<td>맵이 전달된 값에 해당하는 하나 이상의 키를 포함하고 있는지를 확인한다.</td>
</tr>
<tr>
<td>get(Object key)</td>
<td>맵에서 전달된 키에 대응하는 값을 반환한다. 없으면 null을 반환한다.</td>
</tr>
<tr>
<td>boolean isEmpty()</td>
<td>맵이 비어있는지를 확인한다.</td>
</tr>
<tr>
<td>Set&lt;K&gt; keySet()</td>
<td>맵에 포함되어 있는 모든 키 목록을 Set 객체로 반환한다.</td>
</tr>
<tr>
<td>put(K key, V value)</td>
<td>맵에 전달된 키에 대응하는 값으로 특정 값을 매핑한다.</td>
</tr>
<tr>
<td>remove(Object key)</td>
<td>맵에서 전달된 키에 대응한 매핑을 제거한다.</td>
</tr>
<tr>
<td>int size()</td>
<td>맵의 매핑 총 개수를 반환한다.</td>
</tr>
</tbody></table>
<br/>

<h3 id="hashmap">HashMap</h3>
<ul>
<li>아래 예제처럼 HashMap은 입력된 순서대로 정렬되지 않는다.</li>
<li>HashMap은 동기화를 제공하지 않고 ConcurrentHashMap클래스는 동시성 성능을 개선하여 java 1.5버전부터 제공된다. (이전에는 Hashtable을 사용)</li>
<li>해싱 기법으로 데이터를 저장하여, 데이터가 많아도 검색이 빠르다.</li>
<li>만약 정렬 순서가 중요하다면 LinkedHashMap을 사용하면 된다.</li>
</ul>
<pre><code class="language-java">public class MapEx01{
    public static void main(String[] args){
        Map map = new HashMap();
        map.put(&quot;name&quot;, &quot;홍길동&quot;)
        map.put(&quot;address&quot;, &quot;서울시&quot;);
        System.out.println(&quot;map : &quot; + map);
    }
}

[결과]
map : {address=서울시, name=홍길동}</code></pre>
<pre><code class="language-java">public class HashMapEx3 {
    public static void main(String[] args) {
        HashMap&lt;String, Integer&gt; hm = new HashMap&lt;String, Integer&gt;();
        // put() 메소드를 이용한 요소의 저장
        hm.put(&quot;삼십&quot;, 30);
        hm.put(&quot;십&quot;, 10);
        hm.put(&quot;사십&quot;, 40);
        hm.put(&quot;이십&quot;, 20);

        System.out.println(&quot;맵에 저장된 키들의 집합 : &quot; + hm.keySet());
        for (String key : hm.keySet()) {
            System.out.println(String.format(
                            &quot;Key : %s, Value : %s&quot;, key, hm.get(key)));
        }

        // remove() 메소드를 이용한 요소의 제거
        hm.remove(&quot;사십&quot;);
        // iterator() 메소드와 get() 메소드를 이용한 요소의 출력
        Iterator&lt;String&gt; keys = hm.keySet().iterator();
        while (keys.hasNext()) {
            String key = keys.next();
            System.out.println(String.format(
                            &quot;Key : %s, Value : %s&quot;, key, hm.get(key)));
        }
        // replace() 메소드를 이용한 요소의 수정
        hm.replace(&quot;이십&quot;, 200);
        System.out.println(hm);

        // size() 메소드를 이용한 요소의 총 개수
        System.out.println(&quot;맵의 크기 : &quot; + hm.size());
    }
}</code></pre>
<br/>

<h3 id="treemap">TreeMap</h3>
<ul>
<li>키와 값을 한 쌍으로 하는 데이터를 이진 검색 트리(binary search tree)의 형태로 저장한다. (java 1.2부터 제공)</li>
<li>이진 검색 트리는 데이터를 추가하거나 제거하는 등의 기본 동작 시간이 빠르다.</li>
<li>NavigableMap 인터페이스를 기존의 이진 검색 트리의 성능을 향상시킨 레드-블랙 트리(Red-Black tree)로 구현한다.</li>
<li>중복된 키를 저장할 수 없으나 같은 값을 다른 키로 저장하는 것은 가능하다.</li>
<li>HashMap보다 데이터 추가, 삭제에 시간이 더 걸리며 이진 검색 트리의 구조로 키와 값의 쌍으로 이루어진 데이터를 저장한다.</li>
<li>Map이 필요할 때 주로 HashMap을 사용하고, 정렬이나 범위검색이 필요한 경우에 TreeMap을 사용하면 된다.</li>
</ul>
<pre><code class="language-java">public class TreeMapEx1 {
    public static void main(String[] args) {
        TreeMap&lt;Integer, String&gt; tm =
                new TreeMap&lt;Integer, String&gt;();

        // put() 메소드를 이용한 요소의 저장
        tm.put(30, &quot;삼십&quot;);
        tm.put(10, &quot;십&quot;);
        tm.put(40, &quot;사십&quot;);
        tm.put(20, &quot;이십&quot;);
        System.out.println(tm);

        // remove() 메소드를 이용한 요소의 제거
        tm.remove(40);
        System.out.println(tm);

        // replace() 메소드를 이용한 요소의 수정
        tm.replace(20, &quot;twenty&quot;);
        System.out.println(tm);
    }
}

[결과]
{10=십, 20=이십, 30=삼십, 40=사십}
{10=십, 20=이십, 30=삼십}
{10=십, 20=twenty, 30=삼십}</code></pre>
<hr>
<h2 id="comparable-인터페이스">Comparable 인터페이스</h2>
<ul>
<li>Java에서 같은 타입의 인스턴스를 서로 비교해야만 하는 클래스들은 모두 Comparable 인터페이스를 구현하고 있다.</li>
<li>compareTo() 메서드: 두 개의 값을 비교하여 int 값을 반환해주는 메서드다.<ul>
<li>기준 값과 비교대상이 동일한 값일 경우 0</li>
<li>기존 값이 비교대상 보다 작은 경우 -1</li>
<li>기준 값이 비교대상 보다 큰 경우 1</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class ComparableEx2 {
    public static void main(String argsp[]){
        String a = &quot;ABC&quot;;
        String b = &quot;ABC&quot;;
        String c = &quot;BCD&quot;;
        String d = &quot;ABCDEF&quot;;

        // 맨 첫자리부터 차례대로 비교
        // 문자열은 Unicode 값에 근거한다
        System.out.println(a.compareTo(b)); // 0
        // 인자로 전달받은 문자열보다 전에 있는 경우 결과는 음수가 된다
        // A vs B =&gt; feff0041-feff0042 = -1
        System.out.println(a.compareTo(c)); // -1
        System.out.println(a.compareTo(d)); // -3
    }
}

[결과]
0
-1
-3</code></pre>
<hr>
<h2 id="comparator-인터페이스">Comparator 인터페이스</h2>
<ul>
<li>Comparator 인터페이스는 Comparable 인터페이스와 같이 객체를 정렬하는데 사용되는 인터페이스다.</li>
<li>Comparable 인터페이스를 구현한 클래스는 기본적으로 오름차순으로 정렬된다.</li>
<li>Comparator 인터페이스는 내림차순이나 아니면 다른 기준으로 정렬하고 싶을 때 사용할 수 있다.</li>
<li>Comparator 인터페이스를 구현한 클래스는 compare() 메서드를 재정의하여 사용하게 된다.</li>
</ul>
<pre><code class="language-java">class DescendingOrder implements Comparator&lt;Integer&gt; {
    public int compare(Integer o1, Integer o2) {
        if(o1 instanceof Comparable &amp;&amp; o2 instanceof Comparable) {
            Integer c1 = (Integer)o1;
            Integer c2 = (Integer)o2;
            return c2.compareTo(c1);
        }
        return -1;
    }
}

public class ComparatorEx1 {
    public static void main(String[] args) {
        TreeSet&lt;Integer&gt; ts =
                new TreeSet&lt;Integer&gt;(new DescendingOrder());

        ts.add(30);
        ts.add(40);
        ts.add(20);
        ts.add(10);

        Iterator&lt;Integer&gt; iter = ts.iterator();
        while(iter.hasNext()) {
            System.out.println(iter.next());
        }
    }
}

[결과]
40
30
20
10</code></pre>
]]></description>
        </item>
    </channel>
</rss>