15 - 9. Java - Functor, Monad, Optional, Stream
Functor
Mapping 함수 (값 → 값) 적용 방식을 가지는 “데이터 구조”이다
- Functor의 예시로는
List
가 있다 - “값의 데이터 구조에서 → 새 값의 데이터 구조가 나오는것”을 Functor라고 한다
- 예시
const new_array = array.map((each) => [each.name](http://each.name))
- 여기서
((each) => each.name)
이 Functor 부분이다
- 여기서
Monad
Mapping 함수 (값 → 값 + 상태) 적용 방식을 가지는 “데이터 구조”이다
- Monad의 예시로는
Optional
이 있다 - “값의 데이터 구조에서 → 오류 상태를 포함한 값의 데이터 구조가 나오는것”
- Promise와 비슷하지만 다른놈이다!
- Monad의 장점
- NPE (Null Pointer Exception) 방지
- 실패 상태를 가진 “값” 이기에 어떤 함수에서도 문제를 발생시키지 않는다
- 지연 연산 (Lazy Evaluation)
- 중간에 Exception이 발생해도, 실패상태를 가지고 이어져 나중에 처리한다
- NPE (Null Pointer Exception) 방지
Optional
상태(값이 없는 / 값이 있는)를 가진 객체
내부에서 Null 처리를 하기 위함
Optional의 등장 이유: Null을 외부가 아닌 내부에서 처리하자는 마인드이다
- 외부에서 Null을 처리하게 된다면
- NPE 발생에 대한 처리도 외부에 위치하게 되어 유지보수성과 가독성이 떨어진다
- 외부에서 Null을 처리하게 된다면
상태를 갖는 객체로서, 값이 없을 수도, 있을 수 도 있다
변수 선언:
Optional<Member>
: Generic을 통해 어떤 타입의 값을 가질 수 있는지 명시가 가능하다객체 생성:
Optional.ofNullable()
: 값이 없을 수도 있는 = 값이 없는 / 값을 가진 Optional이다Optional.empty()
: 값이 없는 Optional이다Optional<Member> optionalMember = Optional.of(member); // = 성공(값이 있다) 상태 + Member 객체 Optional<Member> optionalMember = Optional.empty(); // = 실패(값이 없다) 상태 + Null 객체 Optional<Member> optionalMember = Optional.ofNullable(member); // = 값이 있을수도, 없을수도 상태 + 객체
내부에서 Null을 처리했을때의 장점:
- 외부에서 Null을 직접 다루지 않아도 된다
- 특정 값이 Null인지 여부 판단을 할 수 있다
- 특정 값이 Null인 경우 별개 처리가 가능하다
- 명시적으로 Null인지 여부를 코드로서 표기 할 수 있다
- ex.)
Optional.ofNullable()
- ex.)
- 외부에서 Null을 직접 다루지 않아도 된다
아직까지 이해가 되지 않는다면 아래 예시를 보고 이해해보자
ex.) ID 기반 Member 데이터 조회 메서드 만들기
public User findById(Integer id)
- id가 Member에 존재하지 않을 시 어떻게 처리할것인가?
Objects.isNull(user)
일 경우,throw new RuntimeException("유저가 없습니다")
Optional<User> optional = Optional.ofNullable(users.get(id))
optional.orElseThrow(() → new RuntimeException(”유저가 없습니다)
- exception을 발생시킨다
optional.orElseGet(() -> new User(0, ""))
- 기본값을 가져오는 함수
optional.orElse(new User(0, “”))
- 기본값을 가져온다
- id가 Member에 존재하지 않을 시 어떻게 처리할것인가?
ex.) Optional을 정상적인 방법으로 사용하는 사례:
Retrieves the text from getText() and calculates the length of it, if it is not
null
, if it is null, return0
.int length = Optional.ofNullable(getText()).map((string) -> string.length()).orElse(0); // String::length로 치환이 가능하다 int length = Optional.ofNullable(getText()).map(String::length).orElse(0);
ex.)
.map()
을 활용해 Optional 안에 있는 값을 바꿀 수 있다public String getCityOfMemberFromOrder(Order order) { return Optional.ofNullable(order) **.map((order) -> order.getMember()) .map((member) -> member.getAddress()) .map((address) -> address.getCity())** .orElse("Seoul"); } // -> 이런식으로 바꿀 수 있다는거 알지? return Optional.ofNullable(order) **.map(Order::getMember) .map(Member::getAddress) .map(Address::getCity)** .orElse("Seoul"); // 만약 Optional을 사용하지 않았다면, public String getCityOfMemberFromOrder(Order order) { if (Objects.nonNull(order)) { Member member = order.member; if (Objects.nonNull(member)) { Address address = member.address; if (Objects.nonNull(address)) { return address.city; } else { return "Seoul"; } } } } // 이런식으로 꽤나 길어진다!
.map()
chaining을 사용해 Optional 객체를 3번 변환했다- 3번이나 변환을 마친결과에 Optional이 비어있다면, Failover 값으로
"Seoul"
로 처리하도록 한다
- 3번이나 변환을 마친결과에 Optional이 비어있다면, Failover 값으로
ex.)
.filter()
를 통해 Optional 안의 값을 검사/분류가 가능하다public Optional<Member> getMemberIfOrderWithin(Order order, int min) { return Optional.ofNullable(order) .filter(o -> o.getDate().getTime() > System.currentTimeMillis() - min * 1000) .map(Order::getMember); }
ex.) Null 상황 처리
Map<Integer, String> cities = new HashMap<>(); cities.put(1, "Seoul"); cities.put(2, "Busan"); cities.put(3, "Daejeon"); Optional<String> maybeCity = Optional.ofNullable(cities.get(4)); // Optional int length = maybeCity.map(String::length).orElse(0); // null-safe System.out.println("length: " + length); // Optional을 사용하지 않았을때, Null이 외부에서 처리된다 String city = cities.get(4); // returns null int length = city == null ? 0 : city.length(); // null check System.out.println("length: " + length);
ex.) 예외 처리
List<String> cities = Arrays.asList("Seoul", "Busan", "Daejeon"); 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-safe System.out.println("length: " + length);
- Exception 처리와 메인 로직을 분리하였으며, Failover처리까지 같이해줘서 좋은 코드라고 볼 수 있다
ex.)
.ifPresent()
: Optional 안에 있는 값이 존재할 경우 함수를 실행한다ifPresent(Cosnumser <? super T> consumer)
: Consumer 함수형 인자를 받을 수 있다.isPresent()
: Optional 안에 있는 값이 존재하는지 여부만 판단한다Optional<String> maybeCity = getAsOptional(cities, 3); // Optional maybeCity.ifPresent(city -> { System.out.println("length: " + city.length()); });
Stream
Collection 내 요소에 대한 내부 접근을 위함
등장 이유
- Collection 각각 요소에 대한 연산을 위해
for
,while
로 외부에서 처리하기보단 내부에서 처리하기 위함- Collection 밖에서 Element 요소 처리: Loop를 통해 외부에서 처리 (External Iteration)
- Collection 안에서 Element 요소 처리: Stream을 통해 내부에서 처리 (Internal Iteration)
- Collection 각각 요소에 대한 연산을 위해
비교해보기
// 외부 반복: for (String string : list) { if(string.contains("a")) { return true; } } // 내부 반복: boolean isExist = list.stream().anyMatch(element -> element.contains("a"));
변수 선언: Stream
: Generic 을 통해 어떤 타입의 요소를 가질 수 있는지 명시해줘야한다 객체 생성: 3가지 방법이 있다
Array로부터 Stream을 생성하는 방법
String[] arr = new String[]{"a","b","c"}; Stream<String> stream = Arrays.stream(arr);
List로부터 Stream을 생성하는 방법
List<String> lists = Arrays.asList("a", "b", "c"); Stream<String> stream = lists.stream();
Stream을 직접 생성하는 방법
Strean<String> stream = Stream.of("a", "b", "c");
장점:
- 선언형이다
- 더 간결하고 가독성이 좋다
- 조립할 수 있다
- 유연성이 좋다
- 병렬화
- 성능이 좋다
- 선언형이다
예시
List<Member> members = Arrays.asList( new Member(1L, "Aaron"), new Member(2L, "Baron"), new Member(3L, "Caron") ); // Stream을 사용해 ID만 뽑아와보는 예시 List<Long> ids = members.stream() .map((each) -> each.id) .collect(Collectors.toList());
- 이런식으로 원하는 결과만 뽑아올 수 있다