티스토리 뷰

728x90
반응형

지난번 글에서 스트림은 크게 세 가지 과정으로 나뉜다고 했다.

 

  1. 스트림 생성하기
  2. 중간 연산 - 데이터의 가공
  3. 최종 연산 - 결과 만들기

또한 스트림을 사용할 때 주의해야 할 점도 있었는데,

 

  • 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지 않는다(Read-only)
  • 스트림은 일회용으로, 사용시마다 새로 생성해야 한다.

가 그것이다. 이 글에선 위의 세 가지 과정 중심으로 스트림에 대해 알아본다.

 


스트림 생성

 

스트림은 주로 컬렉션과 배열을 이용해 얻는다.

 

그중에서도 컬렉션에는 아예 stream()이 정의되어 있기 때문에, 간단하게 스트림을 얻을 수 있다.

// List로부터 스트림을 생성
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> listStream = list.stream();
listStream.forEach(System.out::prinln); //스트림의 모든 요소를 출력.

리스트의 이름을 이용해 list.stream()으로 스트림을 생성한 것을 볼 수 있다.

 

배열을 이용해 스트림을 생성하는 방법은 크게 두 가지가 있는데,

 

Stream의 of() 메서드와 Arrays의 stream()가 그것이다.

// 배열로부터 스트림을 생성
Stream<String> stream = Stream.of("a", "b", "c"); //가변인자
Stream<String> stream = Stream.of(new String[] {"a", "b", "c"});
Stream<String> stream = Arrays.stream(new String[] {"a", "b", "c"});
Stream<String> stream = Arrays.stream(new String[] {"a", "b", "c"}, 0, 3); //end 범위 미포함

Stream.of()와 Arrays.stream()은 비슷한 역할을 하는 것 같지만 반환 타입이 다르다.

 

정수 타입의 요소들로부터도 스트림을 생성하는 방법도 있는데,

 

이를 포함해 스트림을 생성하는 메서드를 정리하면 다음과 같다.

 

 


중간 연산

 

중간 연산은 스트림의 생성부터 시작해서 최종 연산 직전까지의 연산을 말한다.

 

중간 연산은 최종 연산과 달리 여러 번 수행할 수 있다는 특징이 있다.

 

 

필터링(filter(), distinct() 등)

 

필터링 연산은 특정 조건에 따라 데이터를 걸러내는 중간 연산이다.

import java.util.Arrays;
import java.util.List;

public class FilteringExample {
    public static void main(String[] args) throws Exception {
        List<String> names = Arrays.asList("김코딩", "이자바", "김인기", "김코딩");

        names.stream()
                .distinct() //중복제거
                .forEach(n -> System.out.println(n));
        System.out.println();

        names.stream()
                .filter(n -> n.startsWith("김")) //필터링
                .forEach(n -> System.out.println(n));
        System.out.println();

        names.stream()
                .distinct() //중복제거
                .filter(n -> n.startsWith("김")) //필터링
                .forEach(n -> System.out.println(n));
    }
}

//출력 결과
김코딩
이자바
김인기

김코딩
김인기
김코딩

김코딩
김인기

위 예제는 names라는 리스트를 받아 데이터를 가공하는 스트림을 보여주고 있는데,

 

특정 조건에 참인 값을 걸러내는 filter()와 중복을 제거하는 distinct()를 사용하는 것을 알 수 있다.

 

이외에도 필터링엔 결괏값 개수를 제한하는 limit(), 처음부터 특정 개수를 건너뛰는 skip()등이 있다.

 

 

매핑(Mapping)

 

매핑은 기존 스트림의 요소들을 하나씩 특정 값으로 변환해 새로운 스트림을 만들어 준다.

 

이때 값을 변환하기 위한 인자로 람다식을 입력받는다.

List<String> names = Arrays.asList("kimcoding", "javalee", "ingikim", "kimcoding");
        names.stream()
        	.map(s -> s.toUpperCase()) // 요소들을 대문자로 대치
       	        .forEach(n->System.out.println(n));
/*
KIMCODING
JAVALEE
INGIKIM
KIMCODING

map()과 람다식 문법을 이용해 주어진 리스트의 요소들을 대문자로 바꾼 새로운 스트림을 생성하는 것을 볼 수 있다.

 

이외에도 flatMap()이라는 메서드도 있는데, 중복된 스트림을 1차원으로 평면화시킬 때 사용된다.

 

 

정렬(sorted())

 

sorted() 메서드는 스트림 내의 요소들을 정렬하기 위해 사용된다.

List<String> list = Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");

        list.stream()
            .sorted() // 오름차순 정렬
            .forEach(n -> System.out.println(n));
        System.out.println();

/*
Go, Groovy, Java, Python, Scala, Swift
*/
        list.stream()
            .sorted(Comparator.reverseOrder()) // 내림차순 정렬
            .forEach(n -> System.out.println(n));

/*
Swift, Scala, Python, Java, Groovy, Go
*/

데이터를 다루는 데 있어서 별도의 기준이 있다면 Comparator를 sorted()의 인수로 넘겨줄 수도 있다.

import java.lang.*;
import java.util.*;
import java.util.stream.Stream;

class Employee implements Comparable<Employee>{
    int id;
    String name, department;

    public Employee(int id, String name, String department) {
        this.id = id;
        this.name = name;
        this.department = department;
    }

    String getName() {
        return name;
    }
    int getId() {
        return id;
    }
    String getDepartment() {
        return department;
    }
    public String toString() {
        return String.format("[%d, %s, %s]", id, name, department);
    }
    public int compareTo(Employee e) {
        return e.id - this.id;
    }

}

public class ComparatorExample {
    public static void main(String[] args) {
        Stream<Employee> workersStream =
                Stream.of(new Employee(11, "Kim Coding", "Software Engineering"),
                        new Employee(5, "Hello World", "Growth Marketing"),
                        new Employee(7, "Park Hacker", "Software Engineering"));
        workersStream.sorted(Comparator.comparing(Employee::getId)).forEach(System.out::println);

    }
}

// 출력 결과
[5, Hello World, Growth Marketing]
[7, Park Hacker, Sofrware Engineering]
[11, Kim Coding, Software Engineering]

 

연산 결과 중간 확인(peek())

 

peek() 메서드는 연산 중간에 결과를 확인하여 디버깅하는 데 사용된다.

intStream
	.filter(a -> a % 2 == 0)
	.peek(n-> System.out.println(n))
	.sum();

 


최종 연산

 

앞서 적은 바와 같이 최종 연산은 말 그대로 최종 연산이기 때문에 한 번만 호출이 가능하다.

 

최종 연산 메서드가 호출되고 나면 스트림은 닫히기 때문에, 다른 작업을 하려면 스트림을 새로 생성해 주어야 한다.

 

 

연산 결과 최종 확인(forEach())

 

중간 연산 마지막에서 보았던 peek()와 역할이 비슷하지만 forEach()의 경우는 최종 연산자이다.

 

요소를 하나씩 돌면서 출력하는 역할이다.

intStream
    .filter(a -> a % 2 == 0)
    .forEach(n -> System.out.println(n));

 

매칭(match())

 

매칭은 스트림의 요소가 특정 조건을 만족하는지를 boolean으로 반환하는 메서드이다.

 

크게 다음 세 가지 종류가 있다.

 

  • allMatch() - 모든 요소들이 조건을 만족하는지 검사
  • anyMatch() - 하나라도 만족하는 요소가 있는지 검사
  • noneMatch() - 모든 요소들이 조건을 만족하지 않는지 검사
import java.util.Arrays;

public class MatchesExample {
    public static void main(String[] args) throws Exception {
        int[] intArr = {2,4,6};
        boolean result = Arrays.stream(intArr).allMatch(a -> a % 2 == 0);
        System.out.println("모두 2의 배수인가? " + result);

        result = Arrays.stream(intArr).anyMatch(a -> a % 3 == 0);
        System.out.println("하나라도 3의 배수가 있는가? " + result);

        result = Arrays.stream(intArr).noneMatch(a -> a % 3 == 0);
        System.out.println("3의 배수가 없는가? " + result);
    }
}

// 출력 결과
모두 2의 배수인가? true
하나라도 3의 배수가 있는가? true
3의 배수가 없는가? false

 

기본 집계(count(), sum(), average(), max(), min(), findFirst())

 

기본 집계는 최종 연산을 통해 하나의 값을 산출하는 용도로 쓰인다.

import java.util.Arrays;

public class AggregateExample {
    public static void main(String[] args) throws Exception {
        int[] intArr = {1,2,3,4,5};

        long count = Arrays.stream(intArr).count();
        System.out.println("intArr의 전체 요소 개수 " + count);

        long sum = Arrays.stream(intArr).sum();
        System.out.println("intArr의 전체 요소 합 " + sum);

        double avg = Arrays.stream(intArr).average().getAsDouble();
        System.out.println("전체 요소의 평균값 " + avg);

        int max = Arrays.stream(intArr).max().getAsInt();
        System.out.println("최대값 " + max);

        int min = Arrays.stream(intArr).min().getAsInt();
        System.out.println("최소값 " + min);

        int first = Arrays.stream(intArr).findFirst().getAsInt();
        System.out.println("배열의 첫번째 요소 " + first);

    }
}

//출력 결과
intArr의 전체 요소 개수 5
intArr의 전체 요소 합 15
전체 요소의 평균값 3.0
최대값 5
최소값 1
배열의 첫번째 요소 1

메서드마다 getAsInt()나 getAsDouble()등이 쓰인 것도 확인할 수 있다.

 

 

reduce()

 

reduce()는 바로 위의 기본 집계와 비슷하지만 별개의 로직이 있는 경우에 쓰인다.

 

정확하게 말하자면 집계 메서드는 내부적으로 reduce()를 사용하고 있다.

import java.util.Arrays;

public class ReduceExample {
    public static void main(String[] args) throws Exception {
        int[] intArr = {1,2,3,4,5};

        long sum = Arrays.stream(intArr).sum();
        System.out.println("intArr의 전체 요소 합 " + sum);

        int reduce1 = Arrays.stream(intArr)
                .map(el -> el*2)
                .reduce((a,b) -> a * b) // 초기값 생략
                .getAsInt();
        System.out.println("초기값 없는 reduce " + reduce1);

        int reduce2= Arrays.stream(intArr)
                .map(el -> el*2)
                .reduce(2, (a,b) -> a * b); // 초기값 1
        System.out.println("초기값 존재하는 reduce " + reduce2)
    }
}

// 출력 결과
intArr의 전체 요소 합 15
초기값 없는 reduce 3840
초기값 존재하는 reduce 7680

스트림의 모든 요소를 하나씩 곱한 결과가 출력됨을 확인할 수 있다.

 

 

요소 수집(collect())

 

collect()는 스트림의 요소를 컬렉션(Collection, List, Set, Map)으로 받고 싶을 때 사용한다.

//Student.java
public class Student {
    public enum Gender {Male, Female};
    private String name;
    private int score;
    private Gender gender;

    public Student(String name, int score, Gender gender) {
        this.name = name;
        this.score = score;
        this.gender = gender;
    }

    public Gender getGender(){
        return gender;
    }

    public String getName(){
        return name;
    }

    public int getScore(){
        return score;
    }
}
//CollectExample.java
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class CollectExample {
    public static void main(String[] args) throws Exception {
        List<Student> totalList = Arrays.asList(
                new Student("김코딩", 10, Student.Gender.Male),
                new Student("김인기", 8, Student.Gender.Male),
                new Student("이자바", 9, Student.Gender.Female),
                new Student("최민선", 10, Student.Gender.Female)
        );

        List<Student> maleList = totalList.stream()
                .filter(s -> s.getGender() == Student.Gender.Male)
                .collect(Collectors.toList());

        maleList.stream().forEach(n->System.out.println(n.getName()));

        Set<Student> femaleSet = totalList.stream()
                .filter(s -> s.getGender() == Student.Gender.Female)
                .collect(Collectors.toCollection(HashSet :: new));

        femaleSet.stream().forEach(n -> System.out.println(n.getName()));
    }
}

// 출력 결과
김코딩
김인기

최민선
이자바

최종 결과를 컬렉션이 아닌 배열로 받고 싶은 경우엔 Stream.toArray(Type []::new)를 사용하면 된다.

 


Optional<T>

 

Optional은 객체를 포장해주는 래퍼(Wrapper) 클래스이다.

 

가장 큰 특징은 null 값으로 인해 발생하는 NullPointerException(NPE) 예외처리를 간단하게 할 수 있다는 것이다.

 

Optional 객체를 생성하려면 of() 또는 ofNullable()을 사용하는데,

 

참조 변수의 값이 null 일 가능성이 있다면 ofNullable()을 사용한다.

	Optional<String> opt1 = Optional.ofNullable(null);
	Optional<String> opt2 = Optional.of("123");
	System.out.println(opt1.isPresent()); //Optional 객체의 값이 null인지 여부를 리턴
	System.out.println(opt2.isPresent());

Optional 타입의 참조 변수를 기본값으로 초기화하려면 empty()를,

 

Optional 객체에 저장된 값을 읽어오려면 get()을 사용한다.

        Optional<String> optString = Optional.of("codestates");
        System.out.println(optString); // Optional[codestates] 출력
        System.out.println(optString.get()); // codestates 출력
        System.out.println(optString.empty()); // Optional.empty 출력

        String nullName = null;
        String name = Optional.ofNullable(nullName).orElse("kimcoding");
        System.out.println(name); // kimcoding 출력

참조 변수의 값이 null일 가능성이 있다면 orElse() 메서드를 이용해 기본 값을 지정해 준다.

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        List<String> languages = Arrays.asList(
                "Ruby", "Python", "Java", "Go", "Kotlin");
        Optional<List<String>> listOptional = Optional.of(languages);

        int size = listOptional
                .map(List::size)
                .orElse(0);
        System.out.println(size);
    }
}

마지막으로 Optional 객체는 스트림과 유사하게 메서드를 연결해서 사용할 수 있다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함