728x90

객체 지향 쿼리 심화

한번에 여러 데이터를 수정할 수 있는 벌크 연산이 있다.

벌크 연산

엔티티를 수정하려면 영속성 컨텍스트 변경 감지 기능이나 병합을 사용하고, 삭제하려면 em.remove() 를 사용한다. 데이터를 하나씩 처리하기엔 너무 오래걸려서 여러개를 동시에 처리할 수 있는 벌크연산이 있다.

벌크 연산은 executeUpdate() 를 사용한다. 이 메소드는 벌크 연산으로 영향을 받은 엔티티 건수를 반환한다.

주의사항

벌크 연산은 영속성 컨텍스트를 통하지 않고 바로 DB에 직접 쿼리를 날린다. 그래서 영속성 컨텍스트에 있는 엔티티와 DB에 있는 테이블의 칼럼 값이 다를 수 있는데, 그래서 이 부분을 주의하여야 한다.

  • em.refresh 사용
    • 엔티티를 사용해야 할 경우엔 DB에서 다시 조회한다.
  • 벌크 연산 먼저 실행
    • 벌크연산을 가장 먼저 실행하여 실행한 후에 조회쿼리를 하면 변경된 것으로 조회가 된다.
  • 벌크연산 수행 후 영속성 컨텍스트 초기화
    • 수행 직후 컨텍스트를 초기화하여 엔티티를 제거했다가 벌크연산이 적용된 DB에서 조회하는 방법

영속성 컨텍스트와 JPQL

쿼리 후 영속 상태인것과 아닌 것

select m from Member m //엔티티 조회 (영속성 O)
select o.address from Order o //임베디드 타입 조회 (영속성 X)
select m.id, m.name from Member m // 필드 조회 (영속성 X)

엔티티 전체를 조회해야만이 영속성 컨텍스트가 관리한다❗️

JPQL로 조회한 엔티티와 영속성 컨텍스트

앞선 포스팅에서 JPQL로 DB에서 조회한 엔티티가 영속성 컨텍스트에 있다면 JPQL로 DB에서 조회한 값은 버리고 영속성 컨텍스트에 있던것을 꺼내온다고 했다.

덮어쓰거나 하게 된다면 컨텍스트안에서 수정 중이었던 데이터가 사라질 수 있어 위험하다.
그래서 영속성 컨텍스트는 엔티티의 동일성을 보장하기 때문에 em.find로 조회를 하던, JPQL을 사용하던 영속성 컨텍스트가 같으면 동일한 엔티티를 반환해준다.

JPQL의 특징

  • JPQL은 항상 DB를 조회한다.
  • JPQL로 조회한 엔티티는 영속 상태이다.
  • 영속성 컨텍스트에 이미 존재하는 엔티티가 있으면 기존 엔티티를 반환한다.

그래서 영속성 컨텍스트의 1차 캐시를 되도록이면 관리하여 많이 이용하는 것이 DB에 부하를 적게 주는 것이고 그게 바람직한 사용방법인것 같다는 나의 견해? 🤔

JPQL과 플러시 모드

flush는 영속성 컨텍스트의 변경 내역을 DB에 동기화 해주는 것이다.
그래서 JPA는 flush가 발생했을 때 쓰기지연 SQL 저장소 라고 했던 저장소에 있던 쿼리들을 쭉 만들어 DB에 반영해준다. flush를 호출하려면 em.flush()를 하거나 flush 모드에 따라 커밋 직전이나 쿼리 실행 직전에 자동 호출된다.

쿼리와 플러시 모드

JPQL은 영속성 컨텍스트 데이터를 고려하지 않고 DB에서 조회하기 때문에 사용할때는 반드시 영속성 컨텍스트의 변경사항을 flush해주어야 한다. 그렇지 않으면 데이터가 섞일 수 있다.

@Test
@DisplayName("쿼리와 플러시 모드")
void queryAndFlushTest() {
    em.setFlushMode(FlushModeType.COMMIT);
    Item item = em.find(Item.class, 1L);
    item.setPrice(2000);

    Object item2 = em.createQuery("select i from Item i where i.price = 2000").getSingleResult();
    System.out.println(item2.toString());
}

flush모드를 commit시에만 플러시로 설정해놓으면

이러한 select쿼리문 후에 에러를 발생한다.


맞는 엔티티를 찾을 수 없다고 나오게 된다.

정리

  • JPQL은 SQL을 추상화하여 특정 DB에 의존하지 않는다.
  • QueryDSL은 JPQL을 만드는 빌더 역할만 하므로 JPQL을 잘 알아야 함!
  • QueryDSL을 사용하면 동적 쿼리를 생성하기가 편리하다.
  • QueryDSL은 JPA가 공식 지원하는 것은 아니지만 직관적이고 편리하다.
  • JPA도 네이티브 쿼리를 지원하지만, 종속적인 SQL을 사용하게 되면 특정 DB에만 한정적인게 된다.
    • JPQL을 최대한 활용 해보고 안되면 그 때 네이티브 SQL을 사용하자😊
  • JPQL은 대용량 수정, 삭제를 할 수 있는 벌크 연산을 지원한다.
728x90

'Java' 카테고리의 다른 글

TDD Clean Code with Java 12기 2주차 피드백  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차  (0) 2022.08.05
Effective Java 1장  (0) 2022.08.05
MockMvc  (0) 2022.08.04
728x90

이펙티브 자바를 읽으면서 내 기술을 확장하고 싶었다. 그래서 부족한 부분은 채우고 앞으로의 개발에 적용해보려고 한다.

1. 객체의 생성과 파괴

생성자 대신 정적 팩토리 메서드를 고려하라

클래스는 클라이언트에 public 생성자 대신 정적 팩토리 메서드를 제공할 수 있다.

정적 팩토리 메소드가 생성자보다 좋은 장점

  • 이름을 가질 수 있다.
  • 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
  • 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
  • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
  • 정적 펙토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

단점

  • 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.
  • 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.

정적 팩토리 메서드의 명명 방식

  • from: 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
    • Date d = Date.from(instant);
  • of: 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
    • Set<Rank> faceCards = EnumSet.of(JACK, KING, QUEEN);
  • valueOf: from과 of의 자세한 버전
  • instacne 혹은 getInstance: (매개변수를 받는다면)매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
  • create 혹은 newInstance: instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
  • getType: getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용한다. Type은 팩토리 메서드가 반환할 객체의 타입이다.
  • newType: newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용한다. Type은 팩토리 메서드가 반환할 객체의 타입이다.
  • type: getType과 newType의 간결한 버전

핵심정리

정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇지만 정적 팩토리를 사용하는게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하는 습관은 고치자😊

생성자에 매개변수가 많다면 빌더를 고려해라

생성자를 오버로딩하여 만든 점층적 생성자 패턴도 사용은 가능하지만, 매개변수가 엄청 많아지게 된다면 클라이언트 코드를 작성하거나 읽기가 어렵다.
이럴 때 활용하는 대안이 바로 자바빈즈 패턴이다.
기본 생성자로 객체를 초기화하고, setter 메소드를 사용하여 매개변수 값을 설정해주는 방식이다.

public class Apple {
    private int price;
    private String taste;

    public Apple() {
        //기본값이 이것을 생성
    }

    private getPrice() {
        return price;
    }

    private getTaste() {
        return taste;
    }

    private setPrice(int price) {
        this.price = price;
    }

    private setTaste(String taste) {
        this.taste = taste;
    }
}

class Main {
    public static void main(String[] args) {
        Apple apple = new Apple();
        apple.setPrice(1000);
        apple.setTaste("맛있다");
    }
}

이런식으로 구성된 것이 바로 자바빈즈 패턴이다.
그런데 이것도 단점인 이유는 객체 하나를 만드려면 지금은 인자가 2개라서 별로 못느낄 수 있겠지만, 메서드를 여러개 호출해야하고, 완전히 생성된 것이 아니라면 일관성이 무너진 상태에 놓인다.

점층적 생성자 패턴에서는 매개변수들이 유효한지는 생성자에서 확인하면 되었지만, 이 패턴은 그런 것이 없다. 그래서 이 패턴에서는 클래스를 불변으로 만들 수 없다.

빌더 패턴 - 점층적 생성자, 자바빈즈 패턴의 장점만 취함

필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자(혹은 정적 팩토리)를 호출해 빌더 객체를 얻는다. 그 다음 세터 메서드로 원하는 선택적 매개변수 세팅을 한다. 그 후 build 메서드를 호출하여 객체를 생성한다.

여기에 null값을 체크하는 유효성 검사를 넣어주면 완벽한 객체 생성기가 될것이다.

핵심정리

생성자나 정적 팩토리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는게 더 낫다.

Private 생성자나 열거 타입으로 싱글톤임을 보증하라

싱글톤(Singleton)은 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다.
클래스를 싱글톤으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다.

대부분 상황에서는 원소가 하나뿐인 Enum(열거) 타입이 싱글톤을 만드는 가장 좋은 방법이다.

❗ 단, 만드려는 싱글톤이 Enum 외의 클래스를 상속해야 한다면 사용할 수 없다.

인스턴스화를 막으려면 private 생성자 사용

추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다. 왜냐면 하위 클래스를 만들어 인스턴스화를 진행할 수 있기 때문이다. 이것으로 인해 사용하는 개발자가 상속해서 쓰라는 뜻으로 오해할 수 있는 우려가 있다.

컴파일러가 기본 생성자를 만드는 경우는 명시된 생성자가 없을 때밖에 없으니 private생성자를 추가하면 클래스의 인스턴스화를 방지할 수 있다.

728x90

'Java' 카테고리의 다른 글

TDD Clean Code with Java 12기 2주차  (0) 2022.08.05
[JPA] 객체 지향 쿼리 심화  (0) 2022.08.05
MockMvc  (0) 2022.08.04
Mock, Mockito  (0) 2022.08.04
728x90

MockMvc

MockMvc란 실제 객체와 비슷한데 Test를 할때 필요한 기능만 가지는 가짜 객체를 만들어 스프링MVC 동작을 재현할 수 있는 클래스이다.

build.gradle

testCompile('org.springframework.boot:spring-boot-starter-test')

의존성을 추가해준다.

가장 간단한 GET방식을 알아볼 것인데, Http Method의 post, put, patch, delete 들은 어디서 넣으면 테스트 할 수 있는지 별도의 주석으로 첨부하도록 하겠다.

Controller 추가

일단 Controller를 추가해준다.

@RestController
public class MockTestController{

    @GetMapping("/mockTest")
    public String mockTest(@RequestParam String name){
        return name + "님 안녕하세요.";
    }
}

요청 파라미터로 name값을 받아서 안녕하세요를 추가하여 반환해주는 컨트롤러를 작성했다.

Test Code 작성

MockTestController를 테스트하는 클래스를 만들어준다.

@ExtendWith(SpringExtension.class)
@WebMvcTest(MockTestController.class)
public class MockTestControllerTest{
    @Autowired
    private MockMvc mockMvc;

    @Test
    @DisplayName("mockMvc 테스트") //이 DisplayName은 임의로 준 것이기 때문에 안써도 무방하다.
    void 테스트_GET() throws Exception{
        MultiValueMap<String, String> initDatas = new LinkedMultiValueMap<>();

        initDatas.add("name", "홍길동");

        mockMvc.perform(get("/mockTest") // get, post, delete, put, patch를 여기서 매칭시킴
               .params(initDatas))
               .andExpect(status().isOk())
               .andExpect(content().string("홍길동님 안녕하세요"))
               .andDo(print());
    }
}

MockMvc 메소드 정리

  • perform() : 요청을 전송할 때 사용함.
    • 결과 값으로 ResultAction 객체를 받고, 이 객체는 반환값을 검증이나 확인을 하는 andExpect() 메소드 제공 perform(RequestBuilder requestBuilder) - 공식문서 참조
  • get("/mockTest") : Http 메소드를 결정하는 곳(get(), post(), delete(), put(), patch())
    • 괄호 안에는 url 경로를 매칭시킨다.
    • contentType(MediaType type) : json parse
    • content(ObjectMapper) : 요청하는 컨텐츠 contentType으로 변경되어 body에 들어감
  • params(initDatas) : 키,값 파라미터 전달함. 한개일때 param(), 여러개일때 params()
  • andExpect() : 응답을 검증
    • status() : 상태코드를 나타냄. 뒤에 메소드 체이닝으로 ResultMatcher 메소드를 잇는다. 여기선 자주 쓰는것만 정리하겠다.
      • isOk() : HttpStatus 200
      • isCreated() : 201
      • isNotFound() : 404
      • isInternalServerError() : 500
      • is(int status) : HttpStatus 직접 할당
      • view()
        • 뷰 이름 검증
        • view().name("aaa") : 뷰 이름이 aaa인가
      • content() : 응답정보에 대한 검증
        • string() : 괄호안 문자를 포함하는지
  • andDo(print()) : test 응답 결과에 대한 모든 것을 출력한다.

이런식으로 mocking을 하여 테스트를 진행할 수 있다.
이것으로 http method를 다양하게 테스트 해볼 수 있게 되었다.

728x90

'Java' 카테고리의 다른 글

[JPA] 객체 지향 쿼리 심화  (0) 2022.08.05
Effective Java 1장  (0) 2022.08.05
Mock, Mockito  (0) 2022.08.04
[Java] Enum  (0) 2022.08.03
728x90

Mock

Mock이란 테스트 더블 이라고도 하며, 실제 사용되어야 하는 객체의 대역을 의미한다.
Mock객체는 대상의 행위를 검증하는데에 있어서 사용하기 때문에 객체가 가지고 있어야하는 기본 정보를 반드시 가지고 있다. 테스트를 해야하는 어떠한 객체를 검증해야 할 경우 그 객체가 가지고 있는 정보들을 미리 갖고 있어야 한다.

Mock객체를 직접 생성하는 경우에는 일일이 클래스를 만들기 어려울 것이다. 그러므로 우리는 Mockito라는 라이브러리를 이용한다.

Mockito란?

단위 테스트를 위한 Java Mocking Framework이다.
또한 Mockito는 Junit위에서 동작하며 Mocking과 Verification을 도와주는 Framework이다.

의존성 추가

build.gradle에 다음과 같이 추가해준다.

testImplementation org.mockito.mockito-all:1.9.5

기입 후에 build를 해주면 mockito 라이브러리가 추가되고 사용할 수 있게 된다.

개인적 견해

내가 생각할 때 Mockito는 if의 뜻과 비슷하다고 생각한다.
그래서 만약 XXX가 있다면~ XXX을 대입해달라 라는 느낌으로 생각하며 코드를 작성하니 이해하기가 되게 쉬웠다.

사람을 관리하는 저장소인 PersonRepository가 있다고 가정한다.

PersonRepository.java

public interface PersonRepository extends JpaRepository<Person, Long> {
    List<Person> findByName(String name);
}

PersonServiceTest.java

여기서 얘기했던 만약 ~가 있다면 방법으로 코드를 짜보았다.

@ExtendWith(MockitoExtension.class)
class PersonServiceTest {
    @Mock
    private PersonRepository personRepository;

    @Test
    void getPeopleByName(){
        when(personRepository.findByName("lsj")) // 만약 lsj라는 이름이 있다면 가정
                             .thenReturn(Lists.newArrayList(new Person("lsj"))); //이 객체를 돌려줘

        List<Person> result = personService.getPeopleByName("lsj");

        assertThat(result.size()).isEqualTo(1);
        assertThat(result.get(0)).isEqualTo("lsj");
    }
}

이런식으로 존재하면 돌려달라는 식으로 코드를 진행하여
객체가 반환되었으면 assertThat으로 size가 1인지 그리고 내가 달라했던 lsj라는 문자열 이름값이 들었는지 확인하는 검증을 진행하였다.

지금은 단순한 Mock과 Mockito에 대해서만 다루었지만 다음 포스팅에서는 MockMvc를 다뤄보는 포스팅을 올리도록 하겠다.

728x90

'Java' 카테고리의 다른 글

Effective Java 1장  (0) 2022.08.05
MockMvc  (0) 2022.08.04
[Java] Enum  (0) 2022.08.03
[Java] Optional  (0) 2022.08.03
728x90

Enum

소스코드를 분석을 하다 보니 너무 많은 if-else 가 엮여져 있는 코드들을 많이 봐서 너무 어지러웠다. 이것을 어떻게 할 수 없을까에 대한 고찰을 가지고 있다가 처음엔 switch조건을 생각해서 메소드로 따로 빼내자고만 생각했다.
근데 Enum을 찾아본 결과 너무 좋아서 포스팅하게 되었다.

Enum이란

  • Enum은 Eumeration로 열거형이라고 불리며, 서로 연관된 상수들의 집합을 의미한다.

  • 자바에서 final static String, int와 같이 문자열이나 숫자들을 나타내는 기본자료형의 값을 Enum으로 대체해서 사용할 수 있다.

  • 인터페이스나 클래스로 상수를 정의하는 것을 보완하여 IDE의 지원을 적극적으로 받고 타입 안정성도 갖출 수 있게 된다.

예를 들면 이런 코드였다.

    String ss = "A구역";

    if(ss.equals("A구역")) {
        System.out.println(ss);
    }else if(ss.equals("B구역")) {
        System.out.println(ss);
    }else if(ss.equals("C구역")) {
        System.out.println(ss);
    }else if(ss.equals("D구역")) {
        System.out.println(ss);
    }

이렇게되면 계속 else if(ss.equals("구역")이 지금은 적어도 가독성이 이렇게 좋지 않은데 엄청 많은 구역이 있다고 한다면 그만큼의 줄 수를 채워서 단지 if 조건 하나만을 수행한다. 참으로 효율이 떨어지는 코드이다.

여기서 Enum을 사용했다.

TestEnum.java

enum TestEnum{
    A("a", "A구역");
    B("b", "B구역");
    C("c", "C구역");
    D("d", "D구역");

    private String alpa;
    private String name;

    TestEnum(String alpa, String name){
        this.alpa = alpa;
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

이렇게 A,B,C,D라는 키에 각각 Value 값을 넣어준다. 값을 할당하는 갯수는 자유이다. 대신 생성자의 매개변수도 같이 늘어나야 한다.

그러면서 메인함수 실행 클래스에서 어떻게 설정하는지 이제 보도록 하자.

Main.java

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

        String str = TestEnum.valueOf(key).getName();
        String alpa = TestEnum.valueOf(key).getAlpa();    
    }
}

이렇게 설정을 해준 상태에서 위의 코드에서는 입력을 받아 key 변수에 값을 넣어준다. A,B,C,D 의 키값을 호출하게 되면 안에 있는 A구역이라면 alpa에는 a가, name에는 A구역이 들어가게 된다.
이렇게 해서 기존의 코드에서 상당히 많은 줄 수를 줄이면서 가독성도 높여 실용적으로 고칠 수 있는 방법을 터득하게 되었다.

앞으로 if-else구문이 나올때 반복되는게 많아진다면 Enum을 사용하는 것을 염두에 두고 코드를 짜 나갈 것이다.

그리고 Enum에 대해 추가적으로 더 공부가 필요해 보인다.

이상으로 Enum포스팅을 마친다.

728x90

'Java' 카테고리의 다른 글

MockMvc  (0) 2022.08.04
Mock, Mockito  (0) 2022.08.04
[Java] Optional  (0) 2022.08.03
Java Stream API  (0) 2022.08.03
728x90

오늘은 Java 8버전에 추가된 Optional을 알아보자.
프로그래밍을 함에 있어서 NullPointerException은 계속 만나게 되는데 이유는
null을 반환하거나, null 체크를 깜빡하고 진행하지 않았을때 두 가지로 나눌 수 있다.

메소드에서 작업 중에 특별한 상황에서 값을 제대로 반환할 수 없는 경우 선택 방법

  • 예외처리를 한다.(비싸다, stackTrace를 사용)
  • null을 그대로 반환. (비용에는 문제가 없지만, 그 코드를 사용하는 클라이언트에서 주의해서 사용해야한다)
  • Optional을 반환한다.(Java 8버전부터) 클라이언트의 코드에게 명시적으로 빈 값일 수도 있다는 것을 알려주고, 빈 값인 경우 처리를 강제함)

Optional이란?

오직 값 한 개가 들어있을 수도 없을 수도 있는 컨테이너이다.

주의할 점

  • 반환값으로만 쓰기를 권장한다. (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자)
  • Optional을 반환하는 메소드에서 null을 반환하지 말자.
  • primitive 타입용 Optional은 따로 있다. OptionalInt, OptionalLong, ...
  • Collection, Map, Stream, Array, Optional은 Optional로 감싸지 말 것

Optional 변수 선언하기

Optional은 제네릭을 제공하기 때문에, 변수를 선언할 때 명시한 타입 인자에 따라 감쌀 수 있는 객체의 타입이 결정된다.

Optional<Integer> number; //Integer 타입의 객체를 감쌀 수 있는 Optional 타입의 변수
Optional<String> ss;       //String 타입의 객체를 감쌀 수 있는 Optional타입 변수

Optional 객체 생성하기

  • Optional.of()
  • Optional.ofNullable()
  • Optional.empty()

Optional.empty()

null을 담고 있는(비어있는) Optional 객체를 생성한다. 이 비어있는 객체는 Optional 내부적으로 미리 생성해놓은 싱글톤 인스턴스이다.

Optional<Integer> number = Optional.empty();

Optional.of(value)

null이 아닌 객체를 담고 있는 Optional객체를 생성한다. null이 넘어올 경우, NullPointerException을 뱉기 때문에 주의하여야 한다.

Optional<Integer> number = Optional.of(1);

Optional.ofNullable()

null인지 아닌지 확신할 수 없는 객체를 담고 있는 Optional 객체 생성. empty()of()를 합쳐놓은 메소드라고 생각하면 편할 것 같다. null일 경우 NullPointerException을 뱉지 않고 empty()와 동일하게 비어 있는 Optional 객체를 얻어온다. 해당 객체가 null인지 아닌지 자신이 없다면 이 메소드를 사용하는 것이 좋다.

Optional<Integer> number = Optional.ofNullable(null);

Optional에 값이 있는지 없는지 확인하기

  • Optional.isPresent()
  • Optional.isEmpty() (Java 11부터 제공)

Optional이 담고 있는 객체 접근하기

.get()은 비어있는 Optional객체에 대해서, NoSuchElementException을 뱉는다.
.orElse()는 optional이 있던 없던 무조건 후자의 기능을 수행한다.
.orElseGet()은 있으면 그대로 없다면 뒤의 supplier를 수행한다. (람다식이나 메소드 레퍼런스 사용)

OnlineClass onlineClass = optional.orElseGet(App::createNewClass);
System.out.println(onlineClass.getTitle());

.orElseThrow()는 Optional객체가 값이 있으면 그대로 가져오고 없는 경우엔 에러를 던지는 작업을 수행한다.

들어있는 값을 걸러내려면 filter()를 사용하여 조건에 맞는 객체를 반환해준다.

map()은 Optional의 타입을 변환시킨다.
if 분기문 을 없애는 것에도 일가견이 있다.
예를들어 두개의 클래스가 있다.

class Person {
// constructor, getter, setter method 생략
    private String name;
}

class House {
// constructor, getter, setter method 생략
    private Person owner;
    private String address;
}

주인이 있는데 집안에 주인이 없거나, 집 주소가 없는 경우엔 콘솔에 노출되지 않게 해야한다는 조건이 붙는다면

public static void main(String[] args){
    House house = houseService.getHouse();
    if(house.getOwner() != null && house.getOwner().getName() != null){
        System.out.println("owner : " + house.getOwner().getName());
    }
    if (house.getAddress() != null) {
        System.out.println("address: " + house.getAddress());
    }
}

이렇게 if문이 많아져서 코드가 점점 지저분하게 된다. 이럴때 map을 사용하게 되면 아래와 같다.

public static void main(String[] args){
    House house = houseService.getHouse();
    Optional.of(house)
            .map(House::getOwner)
            .map(Person::getName)
            .ifPresent(name -> System.out.println("owner : " + name);

    Optional.of(house)
            .map(House::getAddress)
            .ifPresent(address -> System.out.println("address : " + address);
}

이렇게 가독성도 좋은 복잡한 if문이 빠진 코드를 작성할 수 있다.
ifPresent()는 있다면 그 다음의 람다식을 수행하고 그렇지 않으면 실행하지 않는다.
isPresent()와는 다르다!

flatmap()은 Optional 안에 들어있는 인스턴스가 Optional인 경우에 사용하면 편리하다.
-> 다시말해 Optional이 겹쳐졌을때를 대비하여 속껍질을 한번 까준다고 생각하면 될것이다.

728x90

'Java' 카테고리의 다른 글

Mock, Mockito  (0) 2022.08.04
[Java] Enum  (0) 2022.08.03
Java Stream API  (0) 2022.08.03
[Java] 디자인패턴 - 싱글톤 패턴  (0) 2022.08.03

+ Recent posts