오늘은 Stream 을 사용하는 방법에 대해 쭉 정리를 해보고자 한다.
Stream 생성하기
stream 은 두가지 방법으로 생성이 가능하다.
Collection 으로 생성하기
// Collection(List) 로부터 스트림 생성
List<String> collection = Arrays.asList("a", "b", "c", "e", "f");
Stream<String> collectionStream = collection.stream();
Array로 생성하기
// 배열로부터 스트림을 생성
Stream<String> arrayStream1 = Stream.of("a", "b", "c"); //가변인자
Stream<String> arrayStream2 = Stream.of(new String[]{"a", "b", "c"});
Stream<String> arrayStream3 = Arrays.stream(new String[]{"a", "b", "c"});
Stream<String> arrayStream4 = Arrays.stream(new String[]{"a", "b", "c"}, 0, 2);
스트림을 리스트로
역으로 리스트를 스트림으로 바꾸는것도 가능하다.
//스트림을 리스트로
List<String> changeList = arrayStream2.collect(Collectors.toList());
원시 스트림 생성
Stream 은 int, long, double 같은 원시 자료형을 위한 스트림이 존재한다. int나 long 같은 경우는 range 함수로 for문을 대체하여 사용이 가능하다.
// 원시 스트림 생성
LongStream longStream = LongStream.range(3l, 10l);
Stream 중간연산 (객체요소 가공하기)
생성된 Stream 은 반환되기전 중간의 연산과정을 거칠수 있다. 각 연산 방법을 정리해보겠다.
Filter
filter는 말그대로 조건에 맞춰 필터링하여 컬렉션을 만들어내는 방식이다.
// 필터 사용
List<String> list = new ArrayList<>();
list.add("aa"); list.add("ab"); list.add("bb");
Stream<String> streamFilter = list.stream().filter(s -> s.contains("a"));
Map
Map 은 기존에 Stream 에 있던 데이터를 변경하여 새로운 Stream을 만들어 내는 연산 방식이다.
// 맵 사용
List<Integer> numList = new ArrayList<>();
numList.add(1); numList.add(2); numList.add(3);
Stream<Integer> streamMap = numList.stream().map(n -> n*3);
map 에서는 메서드 참조도 자주 이용하여 사용하므로 방법을 익혀두면 좋을듯하다.
// 맵에서 메서드 참조 사용
Stream<File> fileStream = Stream.of(new File("file1.txt"), new File("file2.txt"), new File("file3.txt"));
Stream<String> streamMapMethodRef = fileStream.map(File::getName);
Sort
stream 에 있는 요소는 sorted 함수를 통해 정렬이 가능하다.
// 정렬 기능
// 오름차순 정렬
List<Integer> notSortedList = Arrays.asList(3,5,2,4,1,7);
Stream<Integer> streamAsc = notSortedList.stream().sorted();
// 내림차순 정렬
Stream<Integer> streamDesc = notSortedList.stream().sorted(Comparator.reverseOrder());
Distinct
stream 에 있는 요소 중 중복된 요소를 distinct 를 이용하여 제거할 수 있다.
distinct 를 제대로 쓰기 위해서는 사용하고자하는 객체의 equals 와 hashCode를 오버라이드 해야 제대로 사용이 가능하다. 예시로 한 클래스에 대하여 오버라이딩을 해보았다.
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
그리고 이 클래스를 통해 객체를 만들고 distinct를 써보면 아래와 같다.
// 중복제거
Person p1 = new Person("kang");
Person p2 = new Person("kim");
Person p3 = new Person("kang");
List<Person> people = new ArrayList<>();
people.add(p1); people.add(p2); people.add(p3);
Stream<Person> peopleStream = people.stream().distinct();
peek
peek 은 stream에 특정 영향을 미치지않고 단순하게 확인을 위한 함수라고 생각하면된다.
// 데이터 확인하기 (peek)
IntStream.of(1,2,3,4,5).peek(System.out::println);
Stream 결과 연산
최대,최소,평균 연산
최대, 최소 , 평균 연산의 경우 Stream의 데이턱다 Null 인경우에 대한 이슈(Optional)가 있어 별도로 처리해줄 필요가 있다. (없을경우에 대한 값을 세팅해줘야함)
// Max. Min, Avg
OptionalInt max = IntStream.of().max();
int min =IntStream.of(1,2,3,4,5).min().orElse(0);
double average = IntStream.of(1,2,3,4,5).average().orElse(0);
합, 카운트
총합이나 카운팅을 하는 경우에는 비어있으면 0이 반환되어 Null 이슈가 없다.
// Count , Sum
long count = IntStream.of(1,2,3,4,5).count();
int sum = IntStream.of(1,2,3,4,5).sum();
데이터 수집 연산 (Collect) 처리
toList
스트림을 리스트로 변환하여 반환한다.
//toList
List<String> nameList = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
joining
결과를 이어붙일 때사용한다. 파라미터가 순차적으로 세개가 올수 있는데 요소를 구분시켜주는 구분자(delimiter), 요소 맨앞에 올 문자 (prefix), 요소 맨뒤에 올 문자(suffix) 가 파라미터로 올 수 있다.
//joining
String joining = people.stream()
.map(Person::getName)
.collect(Collectors.joining(" "));
averaging, summing
collect 에서도 합이나 평균을 구할 수 있다.
// averaging, summing
List<Fruit> fruitList = Arrays.asList(
new Fruit("apple", 9),
new Fruit("banana", 13),
new Fruit("mango", 8),
new Fruit("peach", 9),
new Fruit("melon",16));
Double collectAverage = fruitList.stream().collect(Collectors.averagingInt(Fruit::getAmount));
Integer collectSum = fruitList.stream().collect(Collectors.summingInt(Fruit::getAmount));
summarizing
요약기능을 통해 최대값, 최소값, 평균값, 갯수, 합을 구할 수 있다.
//summarize
IntSummaryStatistics collectStatics = fruitList.stream().collect(Collectors.summarizingInt(Fruit::getAmount));
int fruitMax = collectStatics.getMax();
int fruitMin = collectStatics.getMin();
double fruitAverage = collectStatics.getAverage();
long fruitCount = collectStatics.getCount();
long fruitSum = collectStatics.getSum();
partitioningBy
partitioningBy 를 이용하여 특정 조건에 대한 구분을 지을수 있다. 예를 들어 과일의 갯수가 10보다 작다라는 조건을 주면 10보다 작으면 true 크거나 같으면 false 가 주어질 수 있다.
// partitioningBy
Map<Boolean, List<Fruit>> collectPartitioningBy = fruitList.stream().collect(Collectors.partitioningBy(c -> c.getAmount() < 10));
groupingBy
groupingBy를 이용하여 특정 그룹으로 데이터를 묶을 수 있다. 예를 들어 과일의 갯수를 기준으로 그룹을 묶을수 있다.
// groupingBy
Map<Integer, List<Fruit>> collectGroupingBy = fruitList.stream().collect(Collectors.groupingBy(Fruit::getAmount));
특정 조건 검사
match
match 를 이용하여 특정 조건을 검사할 수 있다. anyMatch는 1개 요소라도 만족하는지, allMatch 는 모든 요소가 만족하는지, nonMatch는 모든 요소가 조건을 만족하지 않는지를 검사한다.
// match
boolean anyMatch = fruitList.stream().map(Fruit::getName).anyMatch(name -> name.contains("a"));
boolean allMatch = fruitList.stream().map(Fruit::getName).allMatch(name -> name.length() > 1);
boolean nonMatch = fruitList.stream().map(Fruit::getName).noneMatch(name -> name.equals("fruit"));
반복문
forEach
각 데이터를 순차적으로 처리하기 위해 forEach를 활용할 수 있다.
//forEach
nameList.stream().forEach(System.out::println);
끝.
'Programming > JAVA' 카테고리의 다른 글
[Effective Java] 인스턴스 생성을 막기 위해 private 생성자를 이용하자 (0) | 2023.01.28 |
---|---|
[Effective Java] 생성자 대신 Static Factory Method를 사용하자 (0) | 2023.01.27 |
[java] 함수형 인터페이스 (0) | 2021.11.04 |
[Java] Generic 이해 하기 - Generic이란 (0) | 2021.10.25 |
[Java] Java Exception 처리하기 (0) | 2021.09.15 |
댓글