본문 바로가기
Programming/JAVA

[java] Java 에서 Stream 사용하기

by 코딩의성지 2021. 11. 10.

오늘은 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);

 

끝.

반응형

댓글