java 8 정리4

Java 정리 2021. 5. 19. 18:55

인프런 강의 6일차.

 - 더 자바, Java 8 (백기선 강사님)

 

1. Optional

 - 람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현가능

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

 

 - 자바 프로그래밍에서 NullPointException을 종종 보는 이유

   > null을 리턴하니까!

   > && 체크를 깜빡했으니까!

 

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

   > 예외를 던진다. (스택 트레이스를 찍게되므로 비싸다..)

   > null을 리턴한다. (비용 문제가 없지만 그 코드를 사용하는 클라이언트가 주의해야한다)

   > Optional을 리턴한다. (클라이언트 코드에게 명시적으로 빈 값일 수 있다는 걸 알려주고, 빈 값인 경우에 대한 처리를 강제한다.)

 

2. Optional 주의사항

 - 리턴 값으로만 쓰기를 권장한다. (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.)

   > 맵의 key는 비어있을 수가 없다라는 것의 맵의 기본 요건!(key가 빈 값이라는 것은 설계의 문제..)

 - Optional을 리턴하는 메소드에서 null을 리턴하지 말자

 - 프리미티브 타입용 Optional은 따로 있다. OptionalInt, OptionalLong, ...

   > Optional.of(10) 처럼 사용이 가능하긴하나, boxing, unboxing이 일어나는 과정에서 cost가 소모되기 때문에 OptionalInt 를 사용하는 것이 바람직하다.

 - Collection, Map, Stream, Array, Optional은  Optional로 감싸지 말것!

package me.whiteship.java8to11;

import java.util.Optional;

public class OnlineClass {

    private Integer id;

    private String title;

    private boolean closed;

    private Progress progress;

    public OnlineClass(Integer id, String title, boolean closed) {
        this.id = id;
        this.title = title;
        this.closed = closed;
    }

    public Integer getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public boolean isClosed() {
        return closed;
    }

    public void setClosed(boolean closed) {
        this.closed = closed;
    }

    public Optional<Progress> getProgress() {
        //Optional.of(param)의 경우 param이 null이 아니어야 한다. null일 경우 NullException이 발생한다.
        //Optional.ofNullable(param)의 경우 param이 null이면 empty로 취급해준다.
        //리턴타입이 Optional인데 return null같은 코드는 사용하지말자.. 정 리턴할게 없다면 Optional.empty를 사용1
        return Optional.ofNullable(progress);       //Optional 은 return type으로 쓰는 것이 권장사항.
    }

    /*
    Optional을 파라미터에 선언하는 것은 문법적으로 문제가 없으나 쓸 수 없다!!
    public void setProgress(Optional<Progress> progress) {
        //만약 Optional<Progress> progess 처럼 메소드 매개변수 타입으로 사용하고자 한다면 해당 함수 내에서 체크를 해줘야한다.
        progress.ifPresent(p -> this.progress = p); //해당 메소드를 호출할 때 파라미터가 null이 올 수 있기 때문에 위험하다.
        //this.progress = progress;
    }
    */
    public void setProgress(Progress progress) {
        this.progress = progress;
    }
}

 

3. Optional 만들기

 - Optional.of()

 - Optional.ofNullable()

 - Optional.empty()

 

4. Optional에 값이 있는지 없는지 확인

 - ifPresent()

 - isEmpty() (Java 11부터 제공)

 

5. Optional에 있는 값 가져오기

 - get()

 - 만약에 비어있는 Optional에서 무언가를 꺼낸다면?

 

6. Optional에 값이 있는 경우에 그 값을 가지고 ~~를 하라.

 - ifPresent(Consumer)

 

7. Optional에 값이 있으면 가져오고 없는 경우에 ~~를 리턴하라.

 - orElse(T)

    > 이미 만들어져 있는 인스턴스를 참고할 때는 orElse가 적합

 

8. Optional에 값이 있으면 가져오고 없는 경우에 ~~를 하라.

 - orElseGet(Supplier)

    > 동적으로 추가 작업을 해야하는 상황이라면 orElse 대신 orElseGet이 적합하다.

 

9. Optional에 값이 있으면 가져오고 없는 경우 에러를 던져라

 - orElseThrow()

    > orElseThrow(IllegralStateException::new) 처럼 발생시킬 익셉션을 정의할 수 있다.

 

10. Optional에 들어있는 값 걸러내기

 - Optional filter(Predicate)

    > 값이 있다는 가정 하게 동작하는 것.

 

11. Optional에 들어있는 값 변환하기

 - Optional map(Function)
 - Optional flatMap(Function): Optional 안에 들어있는 인스턴스가 Optional인 경우에 사용하면 편리하다.

 

package me.whiteship.java8to11;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class App {

    public static void main(String[] args) {
        List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(2, "spring data jpa", true));
        springClasses.add(new OnlineClass(3, "spring mvc", true));
        springClasses.add(new OnlineClass(4, "spring core", true));
        springClasses.add(new OnlineClass(5, "rest api development", false));

        OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);

        /* java8 이전의 null처리 방식
        Progress progress = spring_boot.getProgress();      //getProgress의 return은 null인 상태!
        if(progress != null){
            System.out.println(studyDuration);
        }
         */

        //spring으로 시작하는 oc 중 가장 첫번째 값 리턴 시 optional로 변수가 선언됨
        Optional<OnlineClass> optionalOnlineClass = springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .findFirst();

        //spring에 값이 있는지 없는지 검사
        boolean present = optionalOnlineClass.isPresent();
        System.out.println(present);

        //jpa로 시작하는 수업 중 첫번째 값 가져오기
        Optional<OnlineClass> optionalOnlineClass1 = springClasses.stream()
            .filter(oc -> oc.getTitle().startsWith("jpa"))
            .findFirst();

        boolean presentJpa = optionalOnlineClass1.isPresent();
        System.out.println(presentJpa);

        //값이 있으면 타이틀 출력
        optionalOnlineClass.ifPresent(oc -> System.out.println(oc.getTitle()));

        //jpa로 시작하는 수업이 있으면 가져오고, 없을 경우 createNewJpaClass를 새로 수행하여 클래스 생성
        //다만 optional에 값이 있더라도 createNewJpaClass메소드는 항상 실행된다(return을 하지 않을 뿐!)
        //이러한 현상을 방지하고자 orElseGet(Supplier)를 사용한다.orElse는 Supplier가 아닌 orElse(onlineClass)이다
        OnlineClass onlineClass = optionalOnlineClass.orElse(createNewJpaClass());
        System.out.println(onlineClass.getTitle());


        OnlineClass onlineClass1 = optionalOnlineClass1.orElseGet(() -> createNewJpaClass());//람다표현식 사용
//        optionalOnlineClass1.orElseGet(App::createNewJpaClass);     //메소드 레퍼런스 사용
        optionalOnlineClass1.orElseThrow(); //값이 없으면 NoSuchElementException인 RuntimeException을 던진다.
        optionalOnlineClass1.orElseThrow(IllegalStateException::new);   //발생시킬 exception을 정의할 수 있다.

        //spring으로 시작하는 클래스 중 closed가 안된 대상을 필터 : true값이 없으므로 empty가 리턴된다.
        Optional<OnlineClass> onlineClass2 = optionalOnlineClass.filter(oc -> !oc.isClosed());
        System.out.println(onlineClass2.isEmpty()); //비어있는 optional이 나오게 된다.

        //spring으로 시작하는 클래스의 id를 map으로 만듦
        Optional<Integer> integer = optionalOnlineClass.map(OnlineClass::getId);
        System.out.println(integer.isPresent());    //map 데이터가 있는지 확인

        //만약 map의 리턴 타입이 Optional이면 좀 복잡해진다.
        //map의 리턴타입은 Optional<Optional<Progress>> 임.
        Optional<Optional<Progress>> progress = optionalOnlineClass.map(OnlineClass::getProgress);  //progress의 리턴은 Optional
        Optional<Progress> progress1 = progress.orElseThrow();  //progress에서 한번 더 꺼내야만 실제 확인이 가능한 변수가 됨

        //위와 같은 형태를 줄이고자 할 때 사용하는 것이 flatMap
        //map과 다르게 리턴 타입이 Optional<Progress> 임.
        //orElseThrow를 하지는 않지만 구현이 비슷하게 된다고 알아두면 됨.(flatMap은 없을 때 optional.empty임)
        Optional<Progress> progress2 = optionalOnlineClass.flatMap(OnlineClass::getProgress);

        //Stream에서 쓰는 map은 Input:Output이 1:1 매핑, flatMap은 1:n 매핑이니 헷갈리지 말자!
        //get 보다는 가급적 다른 메소드를 사용하자.
    }

    private static OnlineClass createNewJpaClass() {
        System.out.println("creating new online class");
        return new OnlineClass(10, "New Class", false);
    }
}

 

'Java 정리' 카테고리의 다른 글

java 8 정리6  (0) 2021.05.21
java 8 정리5  (0) 2021.05.20
java 8 정리3  (0) 2021.05.17
java 8 정리2  (0) 2021.05.13
java 8 정리1  (0) 2021.05.12

스프링 입문 맛보기 2

Spring 정리 2021. 4. 29. 20:46

인프런 강의 2일차. 

 - 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 (김영한 강사님)


1. 백엔드 개발

 - 비지니스 요구사항 정리

   사전 제약 조건 : 데이터 (회원ID, 이름), 기능(회원 등록, 조회), 아직 데이터 저장소가 선정되지 않음

 

 * 일반적인 웹 어플리케이션 계층 구조

   - 컨트롤러 : 웹 MVC의 컨트롤러 역할

   - 서비스 : 핵심 비지니스 로직 구현

   - 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리

   - 도메인 : 비지니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨

 

 * 클래스 의존 관계

   - 아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계

   - 데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정

   - 개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반 저장소를 사용(추후 선정된 저장소로 변경)

 

2. 테스트케이스 작성(Test-Driven Developement: 테스트를 먼저 만들고 구현 클래스를 만들어서 확인)

 - 개발한 기능을 실행해서 테스트 할 때 자바의 main 메소드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다. 이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다. 자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.

 

 - JUnit Test 실행 시 어떤 테스트 메소드가 먼저 수행될지 보장되지 않는다. 그렇기 때문에 각 메소드별로 테스트가 끝나면 데이터를 클리어해주어야 한다. @AfterEach 태그를 달아 메소드를 구현하면 된다.

 

3. Repository 클래스의 메소드는 save, findById, findByName 등등 단순 데이터 CRUD에 가깝고, Service 클래스에 구현된 메소드는 join, findMembers 등 비지니스에 가깝다.

즉, Service는 비지니스에 가까운 용어를 사용해서 구현해야 커뮤니케이션이 원할하다.

 

 

MemoryMemberRepository.java

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        //Lambda 표현식
        //member 오브젝트에서 member.getName이 name과 하나라도 같은게 있는지(=findAny) 찾는 표현
        //하나라도 찾으면 바로 반환, 없는 경우 Optional에 의해 null 반환
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

    public void clearStore(){
        store.clear();
    }
}

MemoryMemberRepositoryTest.java

package hello.hellospring.Repository;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
//import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.*;

public class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach(){
        repository.clearStore();
    }

    @Test
    public void save(){
        Member member = new Member();
        member.setName("Spring");
        repository.save(member);
        Member result = repository.findById(member.getId()).get();

        //실무에서는 build단계에서 테스트케이스를 통과하지 못하면 build를 수행하지 않도록 막는다.
        org.junit.jupiter.api.Assertions.assertEquals(member, result);    //기대값 : member, 실제 값 : reuslt (실제 값이 member가 맞는지 테스트)
        //org.junit.jupiter.api.Assertions.assertEquals(member, null);    //실패 케이스
        assertThat(member).isEqualTo(result);    //멤버가 result와 같은지 체크
        //Assertions.assertThat(member).isEqualTo(null);    //실패 케이스
    }

    @Test
    public void findByName(){
        Member member1 = new Member();
        member1.setName("Spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("Spring2");
        repository.save(member2);

        Member result = repository.findByName("Spring1").get();
        assertThat(member1).isEqualTo(result);
        //Assertions.assertThat(member2).isEqualTo(result);
    }

    @Test
    public void findAll(){
        Member member1 = new Member();
        member1.setName("Spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("Spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();

        assertThat(result.size()).isEqualTo(2);
    }
}

 

MemberService.java

package hello.hellospring.Service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;

import java.util.List;
import java.util.Optional;

public class MemberService {

    private final MemberRepository memberRepository = new MemoryMemberRepository() ;


    /**
     * 회원 가입
     */
    public Long join(Member member) {
        //같은 이름이 있는 중복 회원은 안된다는 제약조건 추가
        //Ctrl + Option + V (인텔리제이 단축키)

        /* throw exception 로직 1
        Optional<Member> result = memberRepository.findByName(member.getName());
        //이미 값이 존재하면 exception 발생(ifPresent)
        result.ifPresent(m ->{
            throw new IllegalStateException("이미 존재하는 회원입니다");
        });
        */

        /* throw exception 로직 2
        memberRepository.findByName(member.getName())
            .ifPresent(m ->{
                throw new IllegalStateException("이미 존재하는 회원입니다");
            });
        */

        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    /**
     * throw exception 로직 3
     * 이미 구현된 내용을 Alt + Shift + M을 입력하여 메소드로 추출
     * @param member
     */
    private void validateDuplicateMember(Member member) {
        //같은 이름이 있는 중복 회원안됨
        memberRepository.findByName(member.getName())
                .ifPresent(m ->{
                    throw new IllegalStateException("이미 존재하는 회원입니다");
                });
    }

    /**
     * 전체 회원 조회
     * @return
     */
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }
}

 

'Spring 정리' 카테고리의 다른 글

스프링 입문 맛보기 6  (0) 2021.08.04
스프링 입문 맛보기 5  (0) 2021.07.14
스프링 입문 맛보기 4  (0) 2021.07.06
스프링 입문 맛보기 3  (0) 2021.05.04
스프링 입문 맛보기  (0) 2021.04.28

NPE을 방지하기 위한 entity to dto 처리 방법

프로그래밍 2021. 1. 21. 16:17

1. Optional entity to dto

return Optional.ofNallable(entity)
       .map(Entity::convertToDto)	//Entity.convertToDo() 수행한 결과를 리턴
       .orElse(null);

Mapping

맵(map)은 스트림 내 요소들을 하나씩 특정 값으로 변환해줍니다. 이 때 값을 변환하기 위한 람다를 인자로 받습니다.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

스트림에 들어가 있는 값이 input 이 되어서 특정 로직을 거친 후 output 이 되어 (리턴되는) 새로운 스트림에 담기게 됩니다. 이러한 작업을 맵핑(mapping)이라고 합니다.

 

 

2. 삼항연산 entity to dto

return !ObjectUtils.isEmpty(entity) ? entity.convertToDto() : null;

 

3. if entity to dto

if(!ObjectUtils.isEmpty(entity)){
	return entity.convertToDto();
}else{
	return null;
}

 

p.s. IDE에서 제공하는 @Nullable, @NotNull 어노테이션도 있으니 필요하면 구글링 추가.

 

 

참고1 www.daleseo.com/java8-optional-before/

 

자바8 Optional 1부: 빠져나올 수 없는 null 처리의 늪

Engineering Blog by Dale Seo

www.daleseo.com

 

참고2 www.daleseo.com/java8-optional-after/

 

자바8 Optional 2부: null을 대하는 새로운 방법

Engineering Blog by Dale Seo

www.daleseo.com

참고3 www.daleseo.com/java8-optional-effective/

 

자바8 Optional 3부: Optional을 Optional답게

Engineering Blog by Dale Seo

www.daleseo.com