자바8에서 추가한 스트림은 람다를 활용할 수 있는 기술 중 하나입니다. 자바 8 이전에는 배열 또는 컬렉션 인스턴스를 다루는 방법은 for 또는 foreach 문을 돌면서 하나씩 꺼내서 다루는 방법이였습니다.
간단한 경우라면 상관없지만 로직이 복잡해질수록 코드의 양이 많아져 로직이 섞이게 되고 메소드를 나눌 경우 루프를 여러번 도는 경우가 발생합니다.
스트림은 데이터의 흐름입니다. 배열 또는 컬렉션 인스턴스에 함수 여러개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉 배열과 컬렉션을 함수형으로 처리할 수 있습니다.
스트림의 다른 장점으로 간단하게 병렬처리가 가능하다는 점 입니다. 하나의 작업을 둘 이상의 작업으로 잘게 나눠 동시에 진행하는 것을 병렬 처리라고 합니다.
즉 쓰레드를 이용해 많은 요소들을 빠르게 처리할 수 있습니다.
스트림에 대한 내용은 크게 세가지로 생성 / 가공 / 결과 순으로 있습니다.
전체 > 맵핑 > 필터링 1 > 필터링 2 > 결과만들기 > 결과
생성하기 실습해보기
public class StreamPractice {
public static void main(String[] args) throws IOException {
// 생성하기 1 - 배열로 생성
String[] arr = new String[]{"A", "B", "C"};
Stream<String> stream = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);
// 생성하기 2 - 컬렉션 스트림
// 생성하기 3 - 비어있는 스트림
// 빈 스트림은 요소가 없을 때 null 대신 사용할 수 있다.
List<String> list = Arrays.asList("a", "b", "C");
Stream<String> stream1 = list.stream();
Stream<String> stringStream = list.parallelStream(); // 병렬처리 스트림
// 생성하기 4 - Stream.generate()
// generate 메소드를 이용하면 Supplier<T>에 해당하는 람다로 값을 넣을 수 있다.
// Suplier<T>는 인자는 없고 리턴값만 있는 함수형 인터페이스다.
Stream<String> generate = Stream.generate(() -> "get").limit(5); // ["get","get",...] 5개
// 생성하기 5 - Stream.builder()
// 빌더를 사용하면 스트림에 직접적으로 원하는 값을 넣을 수 있습니다. 마지막에 build메서드를 통해 스트림을 리턴합니다.
Stream<String> streamBuild = Stream.<String>builder()
.add("Eric")
.add("Elena")
.add("Java")
.build();
// 생성하기 6 - Stream.iterate()
// 메소드를 이용하면 초기값과 해당 값을 이루는 람다를 이용해서 스트림에 들어갈 요소를 만듭니다.
// 다음 예제에는 30이 초기값이고 2씩 증가하는 값들이 들어가게 된다.
Stream<Integer> iteratedStream = Stream.iterate(30, n -> n + 2).limit(5);
// 기본 타입형 스트림
// 제네릭을 사용하면 리스트나 배열을 이용해서 기본 타입(int, long, double) 스트림을 생성할 수 있습니다.
IntStream intStream = IntStream.range(1, 5);// [1,2,3,4]
IntStream intStream1 = IntStream.rangeClosed(1, 5); // [1,2,3,4,5]
// 제네릭을 사용하지 않기 때문에 불필요한 오토박싱이 일어나지 않습니다. 필요한 경우 boxed 메소드를 이용해서 박싱할 수 있습니다.
Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();
// 자바 8의 랜덤 클래스는 난수를 가지고 세가지 타입의 스트림(Int, Long, Double)을 만들 수 있습니다. 쉽게 난수 스트림을 생성해서 여러가지 후속 작업을 취할 수 있어 유용합니다.
DoubleStream doubles = new Random().doubles(3); // 난수 3개 발생
// 문자열 스트링
// 스트링을 사용해서 스트림을 생성할 수 잇습니다. 다음은 스트링의 각 문자를 IntStream으로 변환하는 예제입니다. char는 문자이지만 본질적으로는 숫자이기 때문에 가능합니다.
IntStream charsStream = "Stream".chars(); // [83,116,114,101,97,109]
// 정규표현식을 이용해서 문자열을 자르고 각 요소들을 스트림으로 만드는 예제
Stream<String> stringStream1 = Pattern.compile(", ").splitAsStream("Eric, Elena, Java");
// 파일 스트림
// 자바 NIO의 파일클래스의 lines 메소드는 해당 파일의 각 라인을 스트링 타입의 스트림으로 만들어줍니다.
Files.lines(Paths.get("file.txt"), Charset.forName("UTF-8"));
// 병렬 스트림
// 스트림 생성 시 사용하는 스트림 대신 parallelStream 메소드를 사용해서 병렬 스트림을 쉽게 생성할 수 있습니다.
// 내부적으로는 쓰레드를 처리하기 위해 자바 7부터 도입된 Fork/Join Framework를 사용합니다.
List<Integer> integers = Arrays.asList(1, 2, 3);
Stream<Integer> parallelStream = integers.parallelStream();
// 병렬 여부 확인
boolean isParallel = parallelStream.isParallel();
// 따라서 다음 코드는 각 작업을 쓰레드를 이용해 병렬 처리됩니다.
parallelStream
.map(integer -> integer * 10)
.anyMatch(amount -> amount > 20);
// 배열을 이용해서 병렬스트림을 생성하는 경우
Arrays.stream(new int[]{1, 2, 3}).parallel();
// 컬렉션과 배열이 아닌 경우는 다음과 같이 parallel 메소드를 이용해서 처리합니다.
IntStream parallel = IntStream.range(1, 150).parallel();
parallel.isParallel();
// 다시 시퀀셜 모드로 돌리고 싶다면 sequential 메소드를 사용합니다
IntStream sequential = parallel.sequential();
boolean parallel1 = sequential.isParallel();
// 스트림 가공하기
// 스트림 concat 메소드를 이용해 두 개의 스트림을 연결해서 새로운 스트림을 만들어 낼 수 있습니다.
Stream<String> stream_1 = Stream.of("Java", "Scala", "Groovy");
Stream<String> stream_2 = Stream.of("python", "C++", "C#");
Stream<String> concat = Stream.concat(stream_1, stream_2);
}
}
스트림 가공하기
전체요소 중 다음과 같은 API를 이용해서 내가 원하는 것만 뽑아낼 수 있습니다. 이러한 가공 단계를 중간작업이라고 하는데 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어 붙일 수 있습니다.
아래의 예제는 List names = Arrays.asList(“Eric”,“Elena”,“Java”) 리스트를 대상으로합니다.