일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- spring security 인증
- Pessimistic Lock
- 암호화
- 서버개발캠프
- annotation
- JPA 동시성
- spring
- 낙관적락 비관적락 차이
- 스프링 log
- JPA 비관적락
- 디자인 패턴
- 서버
- bean
- JPA Lock
- JPA 낙관적락
- 캠프
- Transaction isolation level
- 개발
- Optimistic Lock
- Inno DB
- Android
- flask
- JPA
- Redis
- 스마일게이트
- 스프링
- 안드로이드
- component
- spring security
- 스프링 로그
- Today
- Total
모르는게 많은 개발자
[Java8] Stream, Optional 개념/예제 본문
저번 포스팅인 함수형 인터페이스, 람다 표현식 개념/예제에 이어서 Stream, Optional을 정리해보려 한다.
1. Stream
자바8 이전에서는 List에 담긴 데이터를 처리하려면 foreach, for를 이용해 순회하며 데이터를 처리했다. 하지만 이렇게 작성된 코드는 길이도 길고 가독성이 떨어졌다. 이러한 문제점을 해결하기 위해 stream기능이 추가됐다.
Stream은 Array, Collections와 같이 연속된 형태의 객체다. 그리고 2개의 연산 과정을 거쳐 데이터를 가공할 수 있다.
- 생성 연산 : Stream객체 생성
- 중개 연산 (filter, map) : List에 들어있는 데이터를 조건에 맞게 가공(ex: 특정 String만 골라내기)하는 연산들을 의미
Stream을 반환 즉, 중개 연산은 여러개 사용 가능 - 최종 연산 (count, collect) : 중개연산에서 가공된 데이터를 사용하는 연산(리스트 반환, 배열 반환, 개수 반환 등)을 의미
마지막에 딱 한번 사용 가능
list.stream().filter().map().count();
Stream의 특징은 다음과 같다.
- Stream은 원본 데이터를 변경하지 않음
- 데이터를 담고 있는 저장소(컬렉션)이 아님
- Stream은 재사용이 가능한 컬렉션과는 달리 한 번만 사용할 수 있음
- 중개 오퍼레이션은 근본적으로 lazy함
- 손쉽게 병렬처리 가능(parellel)
1.1 생성 연산(Create Operations)
먼저 Stream을 생성하는 방법을 보자.
배열
public static void main(String[] args)
String[] str = new String[] {"a", "b", "c"};
Stream < String > s1 = Arrays.stream(str);
List < String > list1 = new ArrayList < > ();
list1.add("a");
list1.add("b");
list1.add("c");
list1.add("d");
Stream < String > s2 = list1.stream();
Builder
public static void main(String[] args) {
Stream < String > builder = Stream. < String > builder()
.add("a")
.add("b")
.build();
}
1.2 중개 연산(intermediate Operations)
Filter
원하는 요소를 추출하는 연산이다. 인자로 Predicate를 받아 반환값 Boolean이 True인 데이터만 모은다.
Stream<T> filter(Predicate<? super T> predicate);
아래 예제의 경우 각 요소에서 "Test"인 것을 찾아 List로 반환하는 예제이다.
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("test");
list.add("b");
list.add("c");
list.add("d");
//list의 각 요소들중 'test'인값만 골라 List로 반환
List < String > filter = list.stream()
.filter(s - > s.equals("test")).collect(Collectors.toList());
//List 출력
filter.forEach(System.out::println);
}
결과
test
Map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Map은 Stream의 각 요소들을 가공한다. 각 요소 T라는 인자의 값을 활용해 R타입의 값을 반환한다.
public class Test {
private String testStr;
public Test() {
this.testStr = "new no has param test";
}
public Test(String str) {
this.testStr = str;
}
public String getTestStr() {
return testStr;
}
}
아래의 예제는 Test라는 객체가 든 list에서 map을 통해 tesetStr을 뽑아 List를 만드는 코드이다.
testStr은 String형태이므로 String List가 추출된다.
public static void main(String[] args) {
List < Test > list = new ArrayList < > ();
list.add(new Test("a"));
list.add(new Test("b"));
list.add(new Test("c"));
list.add(new Test("d"));
List < String > test = list.stream()
.map(Test::getTestStr)
.collect(Collectors.toList());
test.forEach(System.out::println);
}
결과
a
b
c
d
FlatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap은 중첩 구조를 한 단계 제거하고 하나의 Collection으로 풀어주는 역할을 한다.
아래 코드의 경우 List안에 List를 넣은 이중 list를 flatMap을 사용해 하나의 list로 풀어 처리하는 코드이다.
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
List < List < String >> flat = new ArrayList < > ();
flat.add(list);
flat.add(list);
flat.add(list);
flat.stream()
.flatMap(strings - > strings.stream())
.filter(s - > s.equals("a"))
.forEach(System.out::println);
//flatMap에 메소드 레퍼런스 사용
flat.stream()
.flatMap(Collection::stream)
.filter(s - > s.equals("a"))
.forEach(System.out::println);
}
결과
a
a
a
a
a
a
Sorted
Stream요소를 정렬하는 연산이다.
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("b");
list.add("a");
list.add("d");
list.add("c");
list.stream().sorted().forEach(System.out::println);
list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
}
결과
a
b
c
d
d
c
b
a
Distinct
중복 값 제거
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("a");
list.add("a");
list.add("b");
list.add("b");
list.stream().distinct().forEach(System.out::println);
}
결과
a
b
Limit
limit의 인자의 값만큼만 요소를 가져옴
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.stream().limit(2).forEach(System.out::println);
}
결과
a
b
Skip
limit와 반대로 인자의 값만큼 건너뛰고 나머지 요소를 가져옴
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.stream().skip(2).forEach(System.out::println);
}
결과
c
d
1.3 최종 연산(Terminal Operations)
Count
중개 오퍼레이션을 통해 가공된 값들의 개수를 출력한다.
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
//요소중 "b"인 것의 개수
System.out.println(list.stream().filter(s - > s.equals("b")).count());
}
출력
1
AnyMatch, AllMatch, NoneMatch
anyMatch()는 각 요소들 중에 특정 조건을 만족하는 요소가 하나라도 있으면 True반환
allMatch()는 모든 요소가 특정 조건을 만족해야 True반환
noneMatch()는 특정 조건을 하나라도 만족하면 False반환(모든 요소가 만족하지 않아야 True반환)
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
//요소중 "a"가 있으므로 true
System.out.println(list.stream().anyMatch(s - > s.equals("a")));
//요소중 "not"이 없으므로 false
System.out.println(list.stream().anyMatch(s - > s.equals("not")));
//요소들 모두 "z"가 아니므로 false
System.out.println(list.stream().allMatch(s - > s.equals("z")));
//요소중 "not"이 하나도 없으므로 true
System.out.println(list.stream().noneMatch(s - > s.equals("not")));
}
Reduce
모든 요소들의 데이터를 변환하지 않고 연산을 수행해 결과를 낼 수 있다.
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
public static void main(String[] args) {
List < Integer > list2 = new ArrayList < > ();
list2.add(1);
list2.add(2);
list2.add(3);
list2.add(4);
Integer test = list2.stream().reduce(4, (integer, integer2) - > integer + integer2);
System.out.println(test);
System.out.println(list2.stream().reduce((integer, integer2) - > integer + integer2).get());
}
reduce를 사용하면 인자가 integer, integer2인 걸 볼 수 있는데 이것은 요소를 거치면서 integer가 전에 수행된 연산의 값을 가지게 된다. 위 코드로 봤을 때는 처음 연산이 integer + integer2라면 그 다음의 인자는 reduce(지금까지의 총 합, 다음 인자)이 될 것이다.
reduce의 첫번째 인자에 초기값을 선언하면 초기값부터 연산이 시작된다.
collect(Collectors.toList())
요소들을 List로 반환해준다.
public static void main(String[] args) {
List < String > list3 = new ArrayList < > ();
list3.add("aa");
list3.add("bbb");
list3.add("cc");
list3.add("dd");
//요소 길이가 2인 요소들의 List 반환
List < String > collect = list3.stream().filter(s - > s.length() == 2).collect(Collectors.toList());
collect.forEach(System.out::println);
}
collect(Collectors.joining())
스트림 작업 결과를 하나의 String으로 연결해 반환. 인자에는 세개가 있다.
인자
- delimiter: 각 요소 중간에 들어가는 구분자
- prefix: 이어붙인 결과 맨 앞에 붙는 문자
- suffix: 이어붙인 결과 맨 끝에 붙는 문자
public static Collector < CharSequence, ? , String > joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
return new CollectorImpl < > (
() - > new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}
public static void main(String[] args) {
List < String > list3 = new ArrayList < > ();
list3.add("aa");
list3.add("bbb");
list3.add("cc");
list3.add("dd");
System.out.println(list3.stream().collect(Collectors.joining("-", "start-", "-end")));
}
결과
start-aa-bbb-cc-dd-end
collect(Collectors.partitioningBy())
요소들을 Predicate로 true, false 그룹으로 나누어 Map<Boolean, List<T>>형태로 반
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
public static void main(String[] args) {
List < String > list3 = new ArrayList < > ();
list3.add("aa");
list3.add("bbb");
list3.add("cc");
list3.add("dd");
Map < Boolean, List < String >> map = list3.stream().collect(Collectors.partitioningBy(s - > s.length() == 3));
//조건이 맞는 경우 key가 true인 map에 쌓인다
map.get(true).forEach(System.out::println);
map.get(false).forEach(System.out::println);
}
2. Optional
Optional은 객체를 포장해 주는 래퍼 클래스이다. 가장 큰 특징은 NullPointerException 예외처리를 간단하게 처할 수 있다.
Optional 객체 생성
Optional 객체 생성방법은 Optional.of, Optional.ofNullable(), stream()의 종료 연산으로 생성할 수 있다.
Optional.of()를 사용할 때는 null이 들어갈 수 없다.
어쩌다 null이 들어갈 수도 있다면 Optional.ofNullable()을 사용한다.
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("c");
list.add("b");
//stream()의 종료 연산
Optional < String > op1 = list.stream().findFirst();
//of()
Optional < String > op2 = Optional.of("a");
//Optional<String> op2 = Optional.of(null); //에러
//ofNullable()
Optional < String > op3 = Optional.ofNullable(null);
}
Get
get()은 Optional안의 값을 반환한다. 하지만 사용을 권장하지 않는다. 왜냐하면 Optional이 null일 경우 런타임 에러가 나기 때문이다. 아래에서 볼 다른 method들은 예외처리도 함께 가능하기에 get()은 되도록 사용하지 않는다.
public static void main(String[] args) {
List < String > list = new ArrayList < > ();
list.add("c");
list.add("b");
Optional < String > op1 = list.stream().findFirst();
System.out.println(op1.get());
Optional < String > op3 = Optional.ofNullable(null);
//op3는 null이므로 NoSuchElementException 발생
System.out.println(op3.get());
}
결과
c
NoSuchElementException
ifPresent(Consumer)
ifPresent()는 만약 Optional객체가 null아니면 Consumer가 실행되고 null이면 실행되지 않는다.
아래 코드의 경우 op1은 test가 출력되고 op2는 아무것도 실행되지 않는다.
public static void main(String[] args) {
Optional < String > op1 = Optional.ofNullable("test");
op1.ifPresent(s - > System.out.println(s));
Optional < String > op2 = Optional.ofNullable(null);
op2.ifPresent(s - > System.out.println("s"));
}
결과
test
orElse(T)
orElse()는 Optional객체가 null이 아니면 값을 가져오고 null이면 Optional의 인자 타입이랑 같은 타입을 반환하게 할 수 있다.
public static void main(String[] args) {
Optional < String > op1 = Optional.ofNullable("test");
//Null이 아니므로 test출력
System.out.println(op1.orElse("orElse"));
Optional < String > op2 = Optional.ofNullable(null);
//Null이므로 else출력
System.out.println(op2.orElse("orElse"));
}
결과
test
orElse
orElseGet(Supplier)
orElseGet는 orElse와 비슷하지만 인자로 Supplier를 받아 람다식으로 타입 인자를 반환할 수 있다.
public static void main(String[] args) {
Optional < String > op1 = Optional.ofNullable(null);
//Null이므로 else get을 출력
System.out.println(op1.orElseGet(() - > {
return "elseGet";
}));
}
결과
elseGet
orElseThrow()
optional이 null이면 NoSuchElementException을 던진다.
public static void main(String[] args) {
Optional < String > op1 = Optional.ofNullable(null);
op1.orElseThrow();
}
결과
NoSuchElementException
Filter
optional에도 filter()가 존재하는데 filter의 predicate가 true이면 그대로 타입 객체를 포함한 optional이 반환되고 false이면 null optional이 반환된다.
public static void main(String[] args) {
Optional < String > op2 = Optional.ofNullable("a");
//s.equals("a")는 true이므로 "a"를 포함한 optional객체 반환
System.out.println(op2.filter(s - > s.equals("a")).get());
Optional < String > op1 = Optional.ofNullable("a");
//false이므로 null Optional객체 반환
System.out.println(op1.filter(s - > s.equals("b")).get());
}
결과
a
NoSuchElementException
'자바' 카테고리의 다른 글
[Java] 함수형 인터페이스, 람다 표현식 개념/예제 (3) | 2020.11.27 |
---|---|
[JAVA] final, static 키워드 정리 (0) | 2020.07.19 |
[JAVA] Garbage Collection 정리(과정/방식) (0) | 2020.06.11 |
[JAVA] JVM 개념 및 구조 (0) | 2020.06.09 |
[Java]String과 StringBuilder, StringBuffer 차이/예제 (0) | 2020.05.05 |