2023. 11. 29. 20:03ㆍ백엔드/JAVA
Stream의 구조
- Stream 생성
- 중간연산
- 최종연산
Stream 중간연산
생성된 스트림을 원하는 형태에 알맞게 가공하는 연산
중간 연산은 반환값으로 다른 스트림을 반환하기 때문에 이어서 호출하는 메서드 체이닝이 가능하며, 모든 중간 연산을 합친 다음에 합친 연산을 마지막으로 한 번에 처리
2. 중간연산
연산 | 연산 인수 |
filter | Predicate<T> |
map | Function<T, R> |
flatMap | |
distinct | |
sorted | Comparator |
peek | Consumer<T> |
limit | |
skip | |
boxed |
아래코드는 중간연산에 어떤 연산이 있는지 확인 전 미리보기로 'filter', 'map', 'limit'이라는 중간연산을 사용한 예시
//예시코드
List<String> streamList = List.of("l1", "l22", "l333", "l4444");
List<String> streamList2 = streamList.stream()
.filter(l -> {
System.out.println(l + " ::filter");
return l.length() >= 3;
})
.map(l -> {
System.out.println(l + " ::map");
return l.toUpperCase();
})
.limit(2)
.collect(Collectors.toList());
streamList2.forEach(l->{System.out.println("stream최종결과::"+l);});
//출력
l1 ::filter
l22 ::filter
l22 ::map
l333 ::filter
l333 ::map
stream최종결과::L22
stream최종결과::L333
filter
filter는 스트림 내의 요소들을 특정 조건에 따라 필터링 가능함.
메서드의 인자인 Predicate<T> 인터페이스는 test라는 추상 메서드를 정의 하는데, 이 메서드는 T타입의 객체를 파라미터로 받아서 boolean 타입을 리턴.
//예시코드
List<String> filter = List.of("l1", "l22");
filter.stream().filter(s -> s.length() == 2).forEach(System.out::println);
//출력
l1
//예시코드
List<String> filter = List.of("l1", "l22");
filter.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() == 3;
}
}).forEach(System.out::println);
//출력
l22
map
map은 스트림 내 요소 하나하나에 접근해서 함수를 실행한 뒤 최종연산에서 지정한 형식으로 반환.
메서드의 인자인 Function<T, R> 인터페이스는 apply라는 추상 메서드를 정의 하는데, 이 메서드는 T타입의 객체를 파라미터로 받아서 R타입의 객체를 리턴.
//예시코드
List<String> mapStream = List.of("AA", "bbS", "CC");
mapStream.stream().map(m->m.toLowerCase()).forEach(System.out::println);
//출력
aa
bbs
cc
//예시코드
mapStream.stream().map(new Function<String, String>(){
@Override
public String apply(String s) {
return s.toLowerCase();
}
}).forEach(System.out::println);
//출력
aa
bbs
cc
기본 타입 스트림 변환
박싱(boxing) 비용을 피할 수 있도록 기본 타입의 스트림으로 변환가능
//예시코드
List<String> intList = List.of("10", "20");
IntStream intStream = intList.stream().mapToInt(value -> Integer.parseInt(value));
intStream.forEach(System.out::println);
//출력
10
20
//예시
List<String> intList = List.of("10", "20");
intList.stream().mapToInt(new ToIntFunction<String>() {
@Override
public int applyAsInt(String value) {
return Integer.parseInt(value);
}
}).forEach(System.out::println);
//출력
10
20
flatMap
//예시코드
List<String> strList1 = List.of("안녕", "스트림");
List<String> strList2 = List.of("안녕2", "스트림2");
List<List<String>> strList3 = List.of(strList1, strList2);
List<String> resultList = strList3.stream().flatMap(l -> l.stream()).collect(Collectors.toList());
resultList.stream().forEach(System.out::println);
//출력
안녕
스트림
안녕2
스트림2
//예시코드
String[][] strArr = new String[][]{{"안녕", "스트림"},{"안녕2", "스트림2"}};
List<String> resultStream = Arrays.stream(strArr).flatMap(a -> Arrays.stream(a)).collect(Collectors.toList());
resultStream.stream().forEach(System.out::println);
//출력
안녕
스트림
안녕2
스트림2
distinct
distinct는 중복되는 요소들을 모두 제거해주고 새로운 스트림을 반환합니다. 단, 기본형 타입의 값은 값으로 비교하지만 객체의 경우 Object.equals메서드로 비교를 하기에 커스텀 클래스의 경우 hashCode()와 equals() 메서드를 오버라이드 하여야 중복 제거가 가능.
//예시코드(hashCode(), equals() 메서드 오버라이드 하지 않았을 경우)
public class Main {
public static void main(String[] args) {
IntStream stream = Arrays.stream(
new int[]{1, 2, 2, 3, 3});
stream.distinct().forEach(System.out::println);
Foo foo1 = new Foo("123");
Foo foo2 = new Foo("123");
List<Foo> list = List.of(foo1, foo2, foo1);
list.stream().distinct()
.forEach(System.out::println);
}
}
class Foo {
private String bar;
public Foo(String bar) {
this.bar = bar;
}
@Override
public String toString() {
String str = "bar::";
return str+bar;
}
}
//출력
1
2
3
bar::123
bar::123
//예시코드(hashCode(), equals() 메서드 오버라이드 적용)
public class Main {
public static void main(String[] args) {
IntStream stream = Arrays.stream(
new int[]{1, 2, 2, 3, 3});
stream.distinct().forEach(System.out::println);
Foo foo1 = new Foo("123");
Foo foo2 = new Foo("123");
List<Foo> list = List.of(foo1, foo2, foo1);
list.stream().distinct()
.forEach(System.out::println);
}
}
class Foo {
private String bar;
public Foo(String bar) {
this.bar = bar;
}
@Override
public boolean equals(Object o) {
if (o instanceof Foo) {
return bar.equals(((Foo) o).bar);
}
return false;
}
@Override
public int hashCode() {
return bar.hashCode();
}
@Override
public String toString() {
String str = "bar::";
return str+bar;
}
}
//출력
1
2
3
bar::123
sorted
sorted는 스트림 내 요소들을 정렬 해줌. 단, IntStream, DoubleStream, LongStream 등의 기본형 스트림의 경우에는 sorted에 인자를 넘길 수 없기 때문에 boxed를 이용해 객체 스트림으로 변환 후 사용 필요.
//예시
System.out.println("순차정렬~~");
List.of(30, 50, 20).stream().sorted().forEach(System.out::println);
System.out.println("역순정렬~~");
List.of(20, 50, 30).stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
//출력
순차정렬~~
20
30
50
역순정렬~~
50
30
20
//예시
System.out.println("기본형스트림순차정렬~~");
IntStream.of(2, 1, 3).sorted().forEach(System.out::println);
System.out.println("기본형스트림역순정렬~~");
IntStream.of(2, 1, 3).boxed().sorted(Comparator.reverseOrder()).forEach(System.out::println);
//출력
기본형스트림순차정렬~~
1
2
3
기본형스트림역순정렬~~
3
2
1
peek
peek은 스트림의 중간처리시 디버깅을 할 수 있는 기능을 제공. 단, 최종연산이 존재하는 경우에만 동작하며 peek으로 연산을 끝내버릴 경우 동작하지 않음.
여기서잠깐!! peek과 forEach의 차이는?
forEach는 return 값이 void라서 최종처리연산으로 그 자체만으로 수행이 가능하지만, peek은 return 타입이 Stream이기 때문에 스트림 중간연산에서만 사용가능(중간연산에서 호출이 가능한거지, 그 자체만으로 끝낼 경우는 수행되지 않으니 반드시 최종처리연산과 함께 사용해야함.
//예시
System.out.println("~~~peek으로 최종연산을 끝내면 동작하지 않는 예시");
List.of("출력안됨1","출력안됨2","출력안됨3").stream()
.peek(System.out::println);
System.out.println("~~~peek을 최종연산과 함께 사용시 동작하는 예시");
List.of("출력됨1", "출력됨2", "출력됨3").stream()
.peek(System.out::println)
.collect(Collectors.toList());
//출력
~~~peek으로 최종연산을 끝내면 동작하지 않는 예시
~~~peek을 최종연산과 함께 사용시 동작하는 예시
출력됨1
출력됨2
출력됨3
limit
스트림 요소 내 개수를 제한하여 스트림 반환
//예시
List.of("출력됨1", "출력됨2", "출력됨3").stream()
.limit(2)
.peek(System.out::println)
.collect(Collectors.toList());
//출력
출력됨1
출력됨2
skip
skip은 스트림 요소중 첫 번째 요소부터 skip의 인자로 전달 된 개수만큼 의 요소를 제외한 요소로 구성된 새로운 스트림을 반환
//예시
List.of("출력됨1", "출력됨2", "출력됨3").stream()
.skip(2)
.peek(System.out::println)
.collect(Collectors.toList());
//출력
출력됨3
boxed
IntStream, LongStream, DoubleStream 과 같은 기본 스트림을 객체 스트림으로 반환
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
Stream<Integer> objectStream = intStream.boxed();
참고 : https://madplay.github.io/post/java-streams-intermediate-operations
'백엔드 > JAVA' 카테고리의 다른 글
[JAVA] 자바 날짜 타입 포맷 (0) | 2024.06.27 |
---|---|
[JAVA] 람다(Lamda) (0) | 2023.12.13 |
[JAVA] Stream_ map() 과 flatMap() 차이 (0) | 2023.11.29 |
[JAVA] Stream_ (1) Stream 생성 (2) | 2023.11.28 |
[JAVA] 맥북 자바 완적 삭제 (0) | 2023.03.27 |