네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'전체 글'에 해당되는 글 194건


반응형

1. ResponseEntity

- Spring 프레임워크에는 HTTP Request 혹은 Response을 나타내기 위해 HttpEntity 클래스가 존재한다.

- HttpEntity 클래스는 HttpHeader, HttpBody가 있다.

- HttpEntity를 상속하여 추가적으로 HttpStatus 속성이 있는 클래스가 RequestEntity, ResponseEntity 클래스가 있다.

 

[사용법]

1) static method를 사용하여 ResponseEntity 반환

@GetMapping("/v1")
public ResponseEntity<String> getV1() {
    return ResponseEntity.ok("hello world!");
}
// response

HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 12
Data: Wed, 29 Mar 2023 16:17:22 GMT
Keep-Alive: timeout=60
Connection: keep-alive

hello world!

- ResponseEntity에는 자주 쓰이는 HttpStatus로 쉽게 인스턴스를 생성할 수 있는 static 메서드를 제공한다.

- OK(200), CREATED(201), NO_CONTENT(204), NOT_FOUND(404) 등

 

2) 생성자를 이용한 ResponseEntity 반환

@GetMapping("/v2")
public ResponseEntity<String> getV2() {
    return new ResponseEntity("hello world!", HttpStatus.OK);
}
// response

HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 12
Data: Wed, 29 Mar 2023 16:17:22 GMT
Keep-Alive: timeout=60
Connection: keep-alive

hello world!

- ResponseEntity는 여러 생성자를 지원하여 HttpBody, HttpHeader, HttpStatus 케이스를 유연하게 오버로딩 되어있다.

 

3) HttpHeader 추가하기

@GetMapping("/v3")
public ResponseEntity<String> getV3() {
    MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
    headers.add("test", "header-value");
    
    return new ResponseEntity("hello world!", headers, HttpStatus.OK);
}
// response

HTTP/1.1 200
test: header-value
Content-Type: text/plain;charset=UTF-8
Content-Length: 12
Data: Wed, 29 Mar 2023 16:17:22 GMT
Keep-Alive: timeout=60
Connection: keep-alive

hello world!

- MultiValueMap 타입을 가지는 Header를 정의할 수 있다.

 

2. @ResponseStatus

- HttpStatus를 다른 방식으로 표현한 어노테이션이다.

- 컨트롤러단에서 반환하는 Body를 감싸 @ResponseStatus에 정의된 HttpStatus를 추가할 수 있지만 주로 예외처리에서 쓰인다.

 

[사용법]

1) Controller 에서 사용

@GetMapping("/v4")
@ResponseStatus(value = HttpStatus.OK)
public String getV4() {
    return "hello world!";
}
// response

HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 12
Data: Wed, 29 Mar 2023 16:17:22 GMT
Keep-Alive: timeout=60
Connection: keep-alive

hello world!

- ModelAndView로 반환하는 @Controller가 아닌 텍스트 Body를 반환하는 @RestController 기준으로 작성되었다.

- @ResponseStatus에 정의된 HttpStatus로 반환된다.

- ResponseEntity 클래스처럼 유연하지 않다. 커스텀 header 정의도 불가능하며 반환값에 추가적인 작업도 불가능하다.

- default 값은 HttpStatus.INTENAL_SERVER_ERROR(500) 이다.

 

2) Exception 으로 활용

@ReponseStatus(value = HttpStatus.NOT_FOUND)
@Slf4j
public class MyResponseCustomException extends RuntimeException {
    
    public MyResponseCustomException(String message) {
        super(message);
        log.error(message);
    }
}

- 기본적인 Spring Web MVC는 로직상 Exception이 발생하면 HttpStatus.INTERNAL_SERVER_ERROR를 반환한다.

- Exception에 @ResponseStatus를 정의하면 해당 HttpStatus로 반환할 수 있다.

 

3) @ControllerAdvice로 활용

@RestControllerAdvice
public class MyResponseGlobalExceptionHandler {

    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
    @ExceptionHandler(MyResponseCustomException.class)
    public String myResponseGlobalExceptionHandler(MyResponseCustomException ex) {
        return ex.getMessage();
    }
}

- @ControllerAdvice 혹은 @ExceptionHandler에서도 활용할 수 있다.

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

환경: Spring JPA

문제: PK가 같은 엔티티 2개가 각각 준영속, 영속상태일 때 비교가 되지 않음

해결: equals, hashCode 메서드를 재정의

Object 클래스의 equals 특성

1. reflexive (반사성)
x.equals(x)는 항상 참이어야 한다.

2. symmetric (대칭성)
x.equals(y)가 참이라면 y.equals(x) 역시 참이어야 한다.

3. transitive (추이성)
x.equals(y)가 참이고 y.equals(z)가 참일 때, x.equals(z) 역시 참이어야 한다.

4. consistent (일관성)
x.equals(y)가 참일 때 equals 메서드에 사용된 값이 변하지 않는 이상 몇 번을 호출해도 같은 결과가 나와야 한다.

5. x가 null이 아닐 때 x.equals(null)은 항상 거짓이어야 한다.

 

JPA (Hibernate)의 Entity 특징

@Entity가 붙은 클래스의 instance A와 B가 있다고 하자.

이 둘의 instance는 동일한 데이터베이스, 테이블의 같은 row를 instance로 만들었다. 즉, 같은 데이터를 기반하여 만들어진 instance이다.

이 경우 A == B 란 코드에서 true를 반환한다.

이 동작이 가능한 이유는 Persistence Context (영속성 컨텍스트)의 기능 때문이다.

 

재정의해야 하는가?

다음과 같은 엔티티가 있다고 하자.

@Entity
public class Item {
    @Id @GeneratedValue(strategy = GeneratedType.IDENTITY)
    private Long id;

    private String name;
    private Long price;
    private Long stockQuantity;

    ...
}

이 객체는 별다른 equals, hashCode 메서드를 재정의하지 않았다.

다음과 같은 테스트 코드를 작성하여 두 Item을 비교해 보았다.

@Test
void equalsTest() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();
    transaction.begin();
    
    Item item1 = Item.builder()
                    .namer("Cake")
                    .price(6000L)
                    .stockQuantity(10L).build();
    
    entityManager.persist(item1);
    transaction.commit();
    entityManager.clear();
    
    Item item2 = entityManager.find(Item.class, item1.getId()); // PK로 찾는다.
    assertEquals(item1, item2); // org.opentest4j.AssertionFailedError 발생!!!
}

결과는 위와 같이 실패한다.

 

일반적으로 한 엔티티 매니저의 영속성 컨텍스트에서 1차 캐시를 이용해 같은 ID의 엔티티를 항상 같은 객체로 가지고 올 수 있다. 하지만 위처럼 1차 캐시를 초기화한 후, 다시 데이터베이스에서 동일한 엔티티를 읽어오는 경우 초기화 전에 얻었던 item1과 item2 객체가 서로 다르다.

 

이는 위에서 언급한 equals 메서드의 consistent 원칙을 위반하게 된다. 엔티티는 그 본질이 자바 객체라기보단 데이터베이스 테이블의 레코드에 가깝기 때문에 이 Item 엔티티 객체의 필드(id, name, price, stockQuantity)가 동일하다면 같은 레코드, 즉 객체라고 판단해야 하는 것이다. 이 경우 Object의 equals 메서드로는 해결할 수 없기 때문에 equals 메서드 그리고 관례에 따라 hashCode 메서드를 재정의해야 한다.

 

어떻게 재정의해야 하는가?

1. 기본키로 구현하기

2. PK를 제외하고 구현하기

3. 비즈니스 키를 사용하여 구현하기

 

1. 기본키로 구현하기

모든 데이터베이스 레코드, 즉 엔티티는 각자 고유한 기본키를 가진다. 이는 데이터베이스에 의해 유일성이 보장되기 때문에 equals 메서드를 작성할 수 있다.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
    Item item = (Item) o;
    return id != null && Objects.equals(id, item.id);
}

@Override
public int hashCode() {
    int result = id != null ? id.intValue() : 0;
    return result;
}

이 구현은 다음과 같은 특징이 있다.

1. id == null 인 경우 (비영속) 아예 동일하지 않다고 명시한다.

즉, repository.save()를 호출하지 않은 instance는 동등성을 아예 사용할 수 없다.

2. 객체가 영속화되기 전까지 Hibernate는 PK를 할당하지 않는다.

준영속 상태의 두 엔티티를 비교하게 되면, 준영속 상태이기 때문에 id 값이 모두 null이다. 두 엔티티가 다른 객체더라도 id 값이 null로 같아 같은 객체로 인식한다.

 

2. PK를 제외하고 구현하기

엔티티 클래스의 모든 필드에 Objects.equals 메서드를 적용하여 비교하는 방식으로 구현할 수 있다.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
    if (!(o instanceof User)) return false;

    Item item = (Item)o;
    return  Objects.equals(name, item.name) &&
            Objects.equals(price, item.price) &&
            Objects.equals(stockQuantity, item.stockQuantity);
}

@Override
public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + (price != null ? price.hashCode() : 0);
    result = 31 * result + (stockQuantity != null ? stockQuantity.hashCode() : 0);
    return result;
}

이 구현에서는 다음과 같은 문제가 발생할 수 있다.

1. 임의 영속성 컨텍스트의 instance를 수정한다면 다른 영속성 컨텍스트의 instance와 동일하지 않다. (변경되는 값이므로)

2. 속성의 조합이 유일성(unique)을 보장하지 않는다면, 전혀다른 row와 동일한 instance로 판단될 수 있다.

 

2. 비즈니스 키를 사용하여 구현하기

비즈니스 키는 다음과 같은 특성을 갖는다.

1. 변경할 수 없는 것은 아니지만, 변경할 일이 거의 없다.

2. Entity 클래스에는 반드시 비즈니스 키가 있어야 한다.

3. application에서 특정 record를 유일하게 식별하는데 사용한다.

 

비즈니스 키를 식별하는 기준

변경이 불가능한 필드는 아니지만, 변경의 횟수가 다소 적고, DB의 제약조건을 통해 유일성(UNIQUE)을 보장한다.

 

결론

1. 정의한 Entity를 Set과 같은 collection에 담아 관리할 가능성이 있는가? 없다면 application이 확정되는 과정에서도 계속해서 없음을 보장할 수 있는가?

=> PK를 사용하여 구현한다.

2. 영속, 비영속 상태의 entity를 비교할 가능성이 있는가? 있다면 각 속성의 조합으로 식별성을 갖출 수 있는가?

=> PK를 제외한 속성들로 구현한다.

3. 비즈니스 키를 사용하기 적합한 상황인가?

- 예를 들어 개인 식별 정보를 포함한다. 이름 + 핸드폰 / 이름 + 이메일

- 적은 개수의 속성으로도 식별성을 어느정도 보장할 수 있다.

 

 

 참고 

https://blog.yevgnenll.me/posts/jpa-entity-eqauls-and-hashcode-equality

 

Jpa Entity 의 Equals, 객체 동일성과 동등성, Lombok 을 써도 될까?

이전 면접에서 JPA entity 의 equals 와 hashCode 를 어떻게 구현했는지 묻는 질문이 나왔었다. 당시에는 Lombok 을 사용하고 있었고 자연스럽게 @EqualsAndHashCode 를 사용했기 때문에 이 부분에 대해 고민해

blog.yevgnenll.me

https://velog.io/@park2348190/JPA-Entity%EC%9D%98-equals%EC%99%80-hashCode

 

JPA Entity의 equals와 hashCode

스프링 프로젝트에서 ORM 기술로 JPA를 활용하던 도중 equals, hashCode 메서드를 재정의할 경우가 있었는데 이에 대한 고민 과정을 적어보고자 한다.일단 왜 재정의해야 하냐라는 의문이 들 수 있겠

velog.io

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

메시지 큐란

메시지 큐(Message Queue)는 프로세스 또는 프로그램 간에 데이터를 교환할 때 사용하는 통신 방법 중 하나로, 메시지 지향 미들웨어(Message Oriented Middleware:MOM)를 구현한 시스템을 의미한다. 메시지 지향 미들웨어란 비동기 메시지를 사용하는 응용 프로그램들 사이에서 데이터를 송수신하는 것을 의미한다. 여기서 메시지란, 요청/응답/오류 메시지 혹은 단순한 정보 등의 작은 데이터가 될 수 있다.

 

메시지 큐는 메시지를 임시로 저장하는 간단한 버퍼라고 생각하면 된다. 메시지를 전송 및 수신하기 위해 중간에 메시지 큐를 둔다.

메시지 전송 시 생산자(Producer)로 취급되는 컴포넌트가 메시지를 메시지 큐에 추가한다. 해당 메시지는 소비자(Consumer)로 취급되는 또 다른 컴포넌트가 메시지를 검색하고 이를 사용해 어떤 작업을 수행할 때까지 메시지 큐에 저장된다. 각 메시지는 하나의 소비자에 의해 한 번만 처리될 수 있는데, 이러한 이유로 메시지 큐를 이용한 방식을 일대일통신이라고 부른다.

 

메시지 큐를 사용하는 경우는?

일반적인 클라이언트-서버 구조에서는 사용자가 요청을 하면 서버는 그에 대한 처리를 한 후 클라이언트에게 응답을 한다. 간단한 서버 구조에서는 굳이 메시지 큐를 사용할 필요가 없다. 사용자가 응답을 기다려야하는 경우, HTTP 요청을 바로 처리하지 않고 중간에 메시지 큐를 두는 경우는 바람직하지 않다. 또한, 메시지 큐를 적용하려면 다양한 메시지 큐 중에서 시스템의 목적에 맞는 것을 선정해야 한다. 이후 선정된 메시지 큐의 사용방법을 익혀 지원하는 다양한 옵션 중에 시스템이 추구하는 옵션을 설정해야 한다. 

왜 메시지 큐를 사용하며, 어떤 경우에 사용하는 걸까?

메시지 큐는 소비자(Consumer)가 실제로 메시지를 어느 시점에 가져가서 처리하는지는 보장하지 않는다. 언젠가 큐에 넣어둔 메시지가 소비되어 처리될 것이라고 믿는 것이다. 이러한 비동기적 특성때문에 메시지 큐는 실패하면 치명적인 핵심 작업보다는 어플리케이션의 부가적인 기능에 사용하는 것이 적합하다.

 

이메일 전송

어떤 웹 사이트의 비밀번호를 잊어버려서 임시 비밀번호를 받거나, 새로운 회원가입을 위한 인증 코드를 보내는 이메일 서비스는 어느정도의 응답 지연이 허용되며, 어플리케이션의 핵심 기능이 아닌 경우이므로 메시지 큐를 사용할 수 있다.

- 비밀번호 재설정을 위해 이메일을 발급하는 서비스, 회원가입을 위해 이메일을 발급하는 서비스 등

- 이메일 전송 전용 서비스는 이메일이 어느 서비스로부터 생산되었는지와 관계없이, 메시지 큐의 메시지를 하나씩 소비하고, 그저 이메일이 전송되어야 할 곳으로 이메일을 전송한다.

- 메시지 큐에 들어오는 메시지 수가 많아지는 경우 이메일 전송 전용 서비스 인스턴스를 더 둠으로써 확장할 수 있다. (확장성이 뛰어남)

 

블로그 포스팅

모든 블로그 사용자가 웹에 최적화되어 있거나, 용량이 작은 이미지만 업로드하는 것이 아니다. 블로그 사용자가 서비스의 응답 시간을 저해하지 않으면서 사용자들에게 유연성을 제공하는 방법으로, 사용자가 업로드한 모든 이미지를 게시하는 과정에서 즉각 처리가 아닌 사후 처리로 최적화한다. 

- 사용자가 고용량의 이미지가 포함된 블로그 포스팅을 한다.

- 이미지는 저장소에 전송된다.

- 업로드된 이미지에 대한 정보가 포함된 메시지를 이미지 최적화 서비스의 메시지 큐에 담는다.

- 이미지 최적화 서비스는 저장소에서 이미지를 가져와 최적화 후 이미지를 대체한다.

 

메시지 큐의 이점

1. 비동기(Asynchronous)

메시지 큐는 생산된 메시지의 저장, 전송에 대해 동기화 처리를 진행하지 않고, 큐에 넣어두기 때문에 나중에 처리할 수 있다. 여기서 기존 동기화 방식은 많은 메시지를 전송할 경우 병목 현상이 생길 수도 있고, 뒤에 들어오는 요청에 대한 응답이 지연될 것이다.

 

2. 낮은 결합도(Decoupling)

생산자 서비스와 소비자 서비스가 독립적으로 행하면서 서비스간 결합도가 낮아진다.

 

3. 확장성(Scalable)

생산자 서비스 혹은 소비자 서비스를 원하는대로 확장할 수 있기 때문에 확장성이 좋다.

 

4. 탄력성(Resilience)

소비자 서비스가 다운되더라도 어플리케이션이 중단되는 것이 아니라 메시지는 메시지 큐에 남아있다. 소비자 서비스가 다시 시작될 때마다 추가설정이나 작업을 수행하지 않아도 메시지 처리를 시작할 수 있다.

 

5. 보장성(Guarantees)

메시지 큐는 큐에 보관하는 모든 메시지가 결국 소비자 서비스에게 전달된다는 일반적인 보장을 제공한다.

 

메시지 큐의 오픈소스 시스템

- RabbitMQ

- Kafka

- ActiveMQ

 

 

 출처 

https://tecoble.techcourse.co.kr/post/2021-09-19-message-queue/

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

bool 변수 작명은 4가지로 한다.

  • is
  • has
  • 조동사
  • 동사원형

 

is 용법

가장 흔한 케이스이다. 보통 3가지 케이스로 나뉜다.

  • is + 명사
  • is + 현재진행형 (~ing)
  • is + 형용사

is + 명사

"(무엇)인가?" 라는 뜻으로 쓰인다.

boolean isStudent; // user는 학생인가?

is + 현재진행형 (~ing)

"~하는 중인가?" 라는 뜻으로 쓰인다.

boolean isExecuting; // 실행 중인가?
boolean isPending; // 대기 중인가?

is + 형용사 => 형용사 / ~ed(수동태)

boolean isSelected;
boolean isHidden;

 

has 용법

  • has + 명사 
  • has + 과거분사 (pp)

has + 명사

"~를 가지고 있는가?" 

boolean hasICloudAccount; // iCloud 계정을 가지고 있는가
boolean hasVideo; // video를 가지고 있는가?

has + 과거분사 => 현재완료

"과거에 완료된 것이 현재까지 유지가 되고 있다." "~가 유지되고 있는가?"

boolean hasConnected; // 연결되어 있는가?
boolean hasEnded; // 끝났는가?

 

조동사 + 동사원형

can / should / will 등이 있다.

can "~ 할 수 있는가?"

should, will "~ 해야하는 가?", "~할 것인가?"

boolean canEdit() { ... } // 편집할 수 있는가?

 

동사원형 => 3인칭단수로

예를 들어

  • supports : ~을 지원하는가?
  • includes : ~을 포함하는가?
  • shows : ~을 보여줄 것인가?
  • allows : ~을 허용할 것인가?
  • accepts : ~을 받아 주는가?
  • contains : ~을 포함하고 있는가?
boolean supportsVideo; // 비디오를 지원하는가?
boolean allowsEditing; // 편집을 허용하는가?

 

 

해당 사항은 Swift 언어의 bool 변수 작명(스위프트 디자인 가이드)으로 Java와 다른 부분이 있을수도 있지만, boolean 변수의 조동사를 넣어 자주 사용하는 필자로서, 그리고 Java와 비슷한 부분이 있어보여 정리할 필요가 있어보여 정리해보았다.

 

 출처 

https://soojin.ro/blog/naming-boolean-variables

 

Bool 변수 이름 제대로 짓기 위한 최소한의 영어 문법 · Soojin Ro

Background 프로그래머의 가장 어려운 업무가 이름 짓기라는 설문 결과도 있듯이 변수에 적절한 이름을 지어주는 것은 어렵고 오래걸리는 일이다. 영어가 모국어가 아닌 사람들에게는 더 어려울

soojin.ro

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

 이전 글 

https://frogand.tistory.com/209

 

[Java] Optional를 잘 사용하는 법 1 (NPE, if문으로 null 체크를 하지 말자)

Optional을 왜 사용하냐, 그럼 언제 사용해야할까, 에 대한 질문과 답을 적으려고 한다. 요점 - NPE을 방지하기 위해 사용하는 클래스. - null이 들어올 수 있는 값을 한 번 감싸는 Wrapper 클래스. NPE (Nul

frogand.tistory.com

Stream 처럼 사용하기

Optional을 제대로 사용하려면 최대 1개의 원소를 가지고 있는 특별한 Stream이라고 생각하는 것이 좋다. Optional 클래스와 Stream 클래스는 직접적인 구현, 상속과는 관련없지만 사용방식이 유사하다. Stream 클래스의 map(), flatMap(), filter() 와 같은 메서드가 Optional도 가지고 있기 때문에 Stream처럼 사용한다고 생각하면 될 것 같다.

 

map()

public String getCityOfMemberFromOrder(Order order) {
    if (order != null) {
        Member member = order.getMember();
        if (member != null) {
            Address address = member.getAddress();
            if (address != null) {
                String city = address.getCity();
                if (city != null) {
                    return city;
                }
            }
        }
    }

    return "Seoul"; // default
}

해당 코드를 Optional을 사용하여 변경하겠다.

public String getCityOfMemberFromOrder(Order order) {
    return Optional.ofNullable(order)
            .map(Order::getMember)
            .map(Member::getAddress)
            .map(Address::getCity)
            .orElse("Seoul");
}

이전 코드의 전통적인 NPE 방어 패턴에 비해 훨씬 간결하고 명확해진 코드를 볼 수 있다. 기존에 존재하던 조건문이 사라지고 Optional의 메서드 체이닝으로 대체되었다.

- ofNullable() 정적 팩토리 메서드를 활용하여 Order 객체를 Optional로 감쌌다. Order 객체가 null인 경우를 대비해 of() 메서드 대신에 ofNullable() 메서드를 사용했다.

- map() 메서드의 연쇄 호출을 통해 Optional 객체를 3번 변환하였다. 

Optional<Order> -> Optional<Member> -> Optional<Address> -> Optional<String>

- orElse() 메서드를 호출하여 기본값으로 사용할 값을 정해주었다.

 

filter()

null 체크로 시작하는 if 조건문 패턴이 많이 있다.

if (obj != null && obj.do() ...)

예를 들어, 주어진 시간(분) 내에 생성된 주문을 한 경우에만 해당 회원 정보를 구하는 메소드를 위 패턴으로 작성해보자.

public Member getMemberIfOrderWithin(Order order, int min) {
	if (order != null && order.getDate().getTime() > System.currentTimeMillis() - min * 1000) {
		return order.getMember();
	}
}

위 코드는 if 조건문에 null 체크와 비즈니스 로직이 같이 적혀있어서 가독성이 떨어진다.  

 

반면 filter() 메서드를 사용하면 if 조건문 없이 메서드 연쇄 호출만으로 가독성있는 코드를 작성할 수 있다. 또, 메서드의 리턴타입을 Optional로 사용함으로써 호출자에게 해당 메서드가 null을 담고있는 Optional로 반환할 수도 있다는 것을 명시적으로 알려주고 있다.

public Optional<Member> getMemberIfOrderWithin(Order order, int min) {
    return Optional.ofNullable(order)
            .filter(o -> o.gerDate().getTime() > System.currentTimeMillis() - min * 1000)
            .map(Order::getMember);
}

filter() 메서드는 넘어온 함수형 인자의 리턴 값이 false인 경우, Optional을 비우기 때문에 그 이후 메서드 호출이 의미가 없어진다.

 

Java8 이전 개발 코드를 Optional로 바꾸기

Java8 이전에 개발된 코드는 Optional이 없어 null-safe하지 않을 수 있다. JAVA 표준 API조차 하위 호환성을 보장하기 위해 기존 API에 Optional을 적용할 수 없었다.

 

메서드의 반환값이 존재하지 않을 때 전통적인 처리 패턴

이전 개발된 메서드들은 반환값이 존재하지 않을 경우 크게 2가지 패턴으로 처리하였다.

 

1. null 반환

Map 인터페이스의 get() 메서드는 주어진 인덱스에 해당하는 값이 없으면 null을 반환한다.

Map<Integer, String> cities = new HashMap<>();
cities.put(1, "Seoul");
cities.put(2, "Busan");
cities.put(3, "Daejeon");

보통은 null 체크 코드가 들어간다.

String city = cities.get(4); // returns null
int length = city == null ? 0 : city.length(); // null check
System.out.println("length: " + length);

get() 메서드의 반환값을 Optional로 감싸주면 null-safe하게 처리할 수 있다.

Optional<String> maybeCity = Optional.ofNullable(cities.get(4));
int length = maybeCity.map(String::length).orElse(0);
System.out.println("length: " + length);

map() 메서드로 결과를 얻어내고 orElse() 메서드로 디폴트 값을 설정해주었다.

 

2. 예외 발생

두번째 패턴은 예외를 던져버리는 경우이다. List 인터페이스의 get() 메서드는 주어진 인덱스에 해당하는 값이 없으면 ArrayIndexOutOfBoundsException 을 던진다.

List<String> cities = Arrays.asList("Seoul", "Busan", "Daejeon");

다음과 같이 try-catch 문을 사용하여 예외 처리를 해주고 예외 처리 이후에 null 체크도 진행해야 한다.

String city = null;
try {
    city = cities.get(3); // throws exception
} catch (ArrayIndexOutOfBoundsException e) {
    // 생략
}

int length = city == null ? 0 : city.length(); // null check
System.out.println("length: " + length);

이런 경우, 예외 처리부를 감싸서 정적 유틸리티 메서드로 분리해야 한다.

Optional 클래스의 정적 팩터리 메서드를 사용해서 정상처리와 예외 처리시에 반환할 Optional 객체를 각각 지정해주자.

public static <T> Optional<T> getAsOptional(List<T> list, int index) {
    try {
        return Optional.of(list.get(index));
    } catch (ArrayIndexOutOfBoundsException e) {
        return Optional.empty();
    }
}

Optional<String> maybeCity = getAsOptional(cities, 3); // Optional
int length = maybeCity.map(String::length).orElse(0); // null check
System.out.println("length: " + length);

 

ifPresent() 메서드 사용하기

ifPresent(Consumer<? super T> consumer

이 메서드는 특정 결과를 반환하는 대신에 Optional 객체가 감싸고 있는 값이 존재하는 경우에만 실행될 로직을 함수형 인자로 넘길 수 있다. 함수형 인자로 람다식이나 메서드 레퍼런스가 넘어올 수 있다.

Optional<String> maybeCity = getAsOptional(cities, 3); 
maybeCity.ifPresent(city -> {
	System.out.println("length: " + city.length());
});

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !