자바에서 Stream은 매우 중요한 개념이다. 자바 8에서 나온거 같은데... 요즘 이걸 모르면 자바를 한다고 할 수 없지. 그만큼 사용하면 매우 편리한 녀석이다. 다만... 알아야(외워야) 할 부분이 늘어난 점이...
자바 8에서 추가한 스트림은 람다를 활용할 수 있는 기술 중 하나입니다. 스트림은 '데이터의 흐름’입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.
또 하나의 장점은 간단하게 병렬처리(multi-threading)가 가능하다는 점입니다. 하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행하는 것을 병렬 처리라고 하는데, 즉 쓰레드를 이용해 많은 요소들을 빠르게 처리할 수 있습니다.
스트림에 대한 내용은 크게 세 가지로 나눌 수 있습니다.
배열은 다음과 같이 Arrays.stream 메서드를 사용해서 생성할 수 있다.
String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
컬렉션 타입(Collection, List, Set)의 경우 인터페이스에 추가된 디폴트 메소드 stream 을 이용해서 스트림을 만들 수 있습니다.
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream(); // 병렬 처리 스트림
제네릭을 사용하지 않고 직접적으로 해당 타입의 스트림을 다룰 수도 있습니다. 참고로, range와 rangeClosed는 잘 알아두면 좋습니다. ㅎㅎ
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
LongStream longStream = LongStream.rangeClosed(1, 5); // [1, 2, 3, 4, 5]
제네릭을 사용하지 않기 때문에 불필요한 오토박싱(auto-boxing)이 일어나지 않습니다. 필요한 경우 boxed 메소드를 이용해서 박싱(boxing)할 수 있습니다.
Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();
Stream을 만들었다면 그 다음부터는 참 많은 것을 할 수 있습니다. 대표적인게 바로 필터링.
Stream<String> stream = names.stream()
.filter(name -> name.contains("a")); // [Elena, Java]
map은 요소들을 특정조건에 해당하는 값으로 변환해 줍니다.
Stream<String> stream = names.stream()
.map(String::toUpperCase); // [ERIC, ELENA, JAVA]
// .map(s -> s.toUpperCase); 이렇게도 사용가능
혹은 클래스에서 특정 요소만 뽑아서 리스트를 만들 수도 있죠.
List<String> humanNames = humans.stream()
.map(h -> h.getName())
.collect(Collectors.toList());
자바에서 정렬하는 방법은 여러 개가 있는데, Stream에도 있습니다. 먼저, 인자 없이 그냥 호출할 경우 오름차순 정렬을 해줍니다.
IntStream.of(14, 11, 20, 39, 23)
.sorted()
.boxed()
.collect(Collectors.toList());
// [11, 14, 20, 23, 39]
역순으로 정렬하고 싶으면 다음과 같이 해줍니다.
List<String> lang =
Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");
lang.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// [Swift, Scala, Python, Java, Groovy, Go]
혹은 String compareTo를 직접 사용해서 정렬해줄수도 있습니다.
lang.stream()
.sorted((o1, o2) -> o2.compareTo(o1))
.collect(Collectors.toList());
마지막으로 문자열 길이를 기준으로 정렬하는 예시입니다.
lang.stream()
.sorted((s1, s2) -> s2.length() - s1.length())
.collect(Collectors.toList());
// [Groovy, Python, Scala, Swift, Java, Go]
스트림을 IntStream으로 변환해주는 메서드다. 주로 아래와 같이 사용한다. 보면 알겠지만 Integer를 int로 변환해준다.
List<Integer> integerList = Arrays.asList(20,30,50,88,100);
int[] mapToInt = integerList.stream().mapToInt(x->x).toArray(); // Integer -> int(primitive type)
// mapToInt(x->x)는 mapToInt(Integer::intValue)로도 가능한듯
mapToInt를 해야 sum, max 같은걸 할 수 있다. 이건 IntStream에 내장되어있어서 그렇다.
int[] arr = {1,2,3,10};
int sum = IntStream.of(arr).sum();
System.out.println(sum);
JavaScript에서도 forEach가 있듯이 자바에도 forEach가 있다.
IntStream.range(1,11).forEach(i-> {
System.out.println(i);
});
IntStream.range(1,11).forEach(System.out::println);
mapToInt와는 반대로 IntStream 같이 원시 타입에 대한 스트림 지원을 클래스 타입(예: IntStream -> Stream<Integer>
)으로 전환해준다.
// int[] -> IntStream -> Stream<Integer> -> Integer[] 하는 방법
int[] num = {3, 4, 5};
//1. int[] -> IntStream
IntStream stream = Arrays.stream(num);
//2. IntStream -> Stream<Integer>
Stream<Integer> boxed = stream.boxed();
//3. Stream<Integer> -> Integer[]
Integer[] result = boxed.toArray(Integer[]::new);
System.out.println(Arrays.toString(result));
// one line
Integer[] oneLineResult = Arrays.stream(num)
.boxed()
.toArray(Integer[]::new);
Collectors.toList()를 이용해서 리스트로 결과를 가져옵니다. 이를 사용하려면 java.util.stream.Collectors
를 필수로 해줘야 된다.
List<String> collectorCollection =
productList.stream()
.map(Product::getName)
.collect(Collectors.toList());
// [potatoes, orange, lemon, bread, sugar]
만약 Set으로 변환하고 싶다면 Collectors.toSet()을 해주면 된다. Array는 그냥 toArray 해주면 된다.
만약 Stream 요소를 1개의 String 객체로 변환해주고 싶다면 Collectors.joining()을 사용할 수 있다.
List<String> colors = Arrays.asList("RED", "BLUE", "BLACK", "GREEN");
// 요소를 문자열로 변환하고 쉼표로 구분하여 연결합니다.
String joined = colors.stream().collect(Collectors.joining(", "));
중개 오퍼레이션을 통해 가공된 값들의 개수를 출력한다.
public static void main(String[] args) {
List <String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
//요소중 "b"인 것의 개수
System.out.println(list.stream().filter(s -> s.equals("b")).count()); // 1
}
findFirst()는 filter 조건에 일치하는 element 1개를 Optional로 리턴합니다. 조건에 일치하는 요소가 없다면 empty가 리턴됩니다.
List<String> elements = Arrays.asList("a", "a1", "b", "b1", "c", "c1");
Optional<String> firstElement = elements.stream()
.filter(s -> s.startsWith("b")).findFirst();
System.out.println("findFirst: " + firstElement.get()); // findFirst: b
참고 : https://cornswrold.tistory.com/300
자바스크립트로 생각해보면 some과 every 정도 된다고 보면 될 듯.
anyMatch() 메서드 == some
allMatch() 메서드 == every
noneMatch() 메서드 == !every
이를 응용하면 아래와 같은 코드 작성도 가능하다. - 프로그래머스 "없는 숫자 더하기" 문제 참고.
import java.util.Arrays;
import java.util.stream.IntStream;
class Solution {
public int solution(int[] numbers) {
return IntStream.range(0, 10).filter(i ->
Arrays.stream(numbers).noneMatch(num -> i == num)).sum();
}
}
Optional은 어떤 객체를 wrapping하는 객체입니다. 즉, 어떤 객체를 포함할 수 있고 또는 null 객체를 포함할 수 있습니다. 자바의 null 처리를 유연하게 하고자 도입된 것 같습니다. 아래에서 보듯이 String 객체를 생성하고 Optional 객체는 단순히 내부에 String 객체를 감싸는 wrapper 객체라는 것을 알 수 있다.
String string = "a string in optional";
Optional<String> opString = Optional.of(string);
System.out.println(opString.get());
get()은 Optional안의 값을 반환한다. 하지만 사용을 권장하지 않는다. 왜냐하면 Optional이 null일 경우 런타임 에러가 나기 때문이다. 하지만 위 경우 Optional.of(null)은 허용되지 않기 때문에 상관은 없지만...
만약 null을 wrapping하는 Optional 객체를 만드려면 어떻게 해야 할까요? Optional.ofNullable()은 객체 생성 시 null을 허용합니다. 이를 이용하면 null을 포함하는 Optional 객체를 만들 수 있습니다.
String nullString = null;
Optional<String> nullOpString = Optional.ofNullable(nullString);
// Optional<String> emptyOptional = Optional.empty();
try {
System.out.println(nullOpString.get());
} catch (NoSuchElementException e) {
System.out.println("No such element");
}
다만 null인데 get을 했다가는 NoSuchElementException 예외가 발생됩니다.
예외가 발생하지 않으려면 Null인지 미리 체크하고 사용해야 한다. isPresent()는 내부 객체가 null인지 알려줌.
Optional<String> opString = Optional.of("a string in optional");
Optional<String> nullOpString = Optional.ofNullable(nullString);
if (opString.isPresent()) {
System.out.println("opString: " + opString.get());
}
if (nullOpString.isPresent()) {
// null인경우 실행되지 않음.
System.out.println("nullOpString: " + nullOpString.get());
}
혹은 아래처럼 사용도 가능하다.
opString.ifPresent(s - > System.out.println(s)); // 출력함
nullOpString.ifPresent(s - > System.out.println(s)); // 출력하지 않음 *null이면 실행되지 않는다.
만약 null일 때 다른 값을 갖도록 설정할 수 없을까요? orElse를 사용하면 null인 경우 다른 값을 출력하도록 할 수 있다.
Optional<String> opString = Optional.of("a string in optional");
Optional<String> nullOpString = Optional.ofNullable(nullString);
String str = opString.orElse("new string from orElse");
System.out.println(str); // "a string in optional"
String str2 = nullOpString.orElse("new string from orElse");
System.out.println(str2); // "new string from orElse"