티스토리 뷰
728x90
서문
자바의 정석 기초편 챕터 14편을 기재합니다.
목적은 공부한 내용을 기록하는 것에 있기 때문에 완전한 문장이 아닐 수도 있습니다.
또한 모든 내용을 적은 것은 아닙니다.
- 참고 자료
자바의 정석 (ch.14)
람다식(Lambda Expression)
참고
- 함수형 언어 (Functional Programming) jdk 1.8>
- JAVA = oop + fp;
- 빅데이터가 부각되면서 함수형 언어가 주목받기 시작
- Haskell
- scala
- ...
- python, js 등등 oop도 가지고 있고 fp기능도 제공
- 함수(메서드)를 간단한 식으로 표현한 방법
- 익명 함수(이름이 없는)함수, anonymous function
- 함수와 메서드의 차이
- 근본적으론 동일, 함수는 일반적 용어, 메서드는 객체지향적 용어
- 함수는 클래스에 독립적, 메서드는 클래스에 종속적
람다식 작성
- 메서드의 이름과 반환타입을 제거하고 "->"를 블록{} 앞에 추가
int max(int a, int b){
return a > b ? a : b;
}
(int a, int b) -> {
return a > b ? a : b;
}
- 반환값이 있는 경우, 식이나 값만 적고 return문은 생략 가능(끝에 ; x)
(int a, int b)->{
return a > b ? a : b;
}
(int a, int b) -> a > b ? a : b
- 매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략가능)
(int a, int b) -> a > b ? a : b
(a, b) -> a > b ? a : b
람다식 작성하기 - 주의사항
- 매개변수가 하나인 경우 괄호() 생략 가능(타입이 없을 때만)
(a) -> a * a
a -> a * a // OK
(int a) -> a * a
int a -> a * a // error
- 블록 안의 문장이 하나뿐인 경우 괄호 {} 생략 가능(끝에 ; x)
- 단, 하나뿐인 문장이 return문이면 괄호{} 생략 불가
(int i) -> {
System.out.println(i);
}
(int i) -> System.out.println(i) // OK
(int a, int b) -> { return a > b ? a : b;} // OK
(int a, int b) -> return a > b ? a : b // error
- 람다식 예시
1.
int max(int a, int b){
return a > b ? a : b;
}
(a, b)-> a > b ? a : b
----------------------------------------
2.
int printVar(String name, int i){
System.out.println(name + "=" + i);
}
(name, i) -> System.out.println(name + "=" + i)
----------------------------------------
3.
int square(int x){
return x * x;
}
(x) -> x * x
----------------------------------------
4.
int roll() {
return (int)(Math.random() * 6);
}
() -> (int)(Math.random() * 6)
람다식은 익명함수? 익명 객체!
- 람다식은 익명 함수가 아니라 익명 객체
- 람다식(객체)을 다루기 위해서 참조변수가 필요, 참조변수의 타입은?
(a, b) -> a > b ? a : b
new Object(){
int max(int a, int b){
return a > b ? a : b;
}
}
Object obj = new Object(){
int max(int a, int b){
return a > b ? a : b;
}
}
obj.max() // error, 리모콘이 Object인데 Object에는 max기능이 없음
----------------------------------------------------
class Ex{
public static void main(String[] args){
// Object obj = (a, b) -> a > b ? a : b // 람다식은 익명객체, error
Object obj = new Object(){
int max(int a, int b){
return a > b ? a : b;
}
};
int value = obj.max(3,5); // error, 참조변수 obj는 Object이기 때문에 max 기능이 없음, 이 때 함수형 인터페이스를 사용
}
}
함수형 인터페이스
- 단 하나의 추상 메서드만 선언된 인터페이스
// @FunctionalInterface // 이를 사용하면 잘 사용했는지 컴파일러가 체크
interface MyFunction{
public abstract int max(int a, int b);
}
MyFunction m = new MyFunction(){
public int max(int a, int b){
return a > b ? a : b;
}
};
int value = m.max(3, 5);
System.out.println(value);
> 5
- 함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있음
- 단, 함수형 인터페이스의 메서드와 람다식의 매개변수 수와 반환타입이 일치해야 함
1. 함수형 인터페이스 사용 예시
MyFunction m = (a, b) -> a > b ? a : b;
int value = m.max(3, 5);
----------------------------------------
2. 함수형 인터페이스 예제
interface MyFunction{
public abstract int max(int a, int b);
// int max(int a, int b); // 인터페이스의 모든 메서드는 기본적으로 public abstract이기 때문에 생략 가능
}
public class MyClass {
public static void main(String args[]) {
MyFunction m = new MyFunction(){
public int max(int a, int b){
return a > b ? a : b;
}
};
// MyFunction m2 = (a, b) -> a > b ? a : b; // 람다식을 다룰 수 있는 참조변수 타입은 함수형 인터페이스의 타입으로 함
int value = m.max(3,5);
System.out.println(value);
}
}
> 5
함수형 인터페이스 - example
- 익명 객체를 람다식으로 대체
List<String> lst = Arrays.asList("abc","aaa","bbb","ddd","aaa");
Collections.sort(lst, new Comparator<String>(){
public int compare(String s1, String s2){
return s2.compareTo(s1);
}
});
Collection.sort(lst, (s1, s2) -> s2.compareTo(s1));
함수형 인터페이스 타입의 매개변수, 반환타입
- 함수형 인터페이스 타입의 매개변수
void aMethod(MyFunction f){
f.myMethod(); // MyFunction에 정의된 메서드
}
MyFunction f = () -> System.out.println("myMethod()");
aMethod(f);
aMethod(() -> System.out.println("myMethod()"));
- 함수형 인터페이스 타입의 반환타입
MyFunction myMethod(){ MyFunction f = () -> {}; return f; }
MyFunction myMethod(){
return () -> {};
}
<br>
- 예제
```java
@FunctionalInterface
interface MyFunction{
void run();
}
class Ex{
static void execute(MyFunction f){
f.run();
}
static MyFunction getMyFunction(){
return () -> System.out.println("f3.run()");
}
public static void main(String[] args){
MyFunction f1 = () -> System.out.println("f1.run()");
MyFunction f2 = new MyFunction(){
public void run(){
System.out.println("f2.run()");
}
};
MyFunction f3 = getMyFunction();
f1.run();
f2.run();
f3.run();
execute(f1);
execute( () -> System.out.println("run()"));
}
}
> f1.run()
> f2.run()
> f3.run()
> f1.run()
> run()
java.util.function 패키지(1/3)
- 자주 사용되는 다양한 함수형 인터페이스를 제공
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
java.lang.Runnable | void run() | 매개변수도 없고, 반환타입도 없음 |
Supplier |
T get() | 매개변수는 없고, 반환값만 있음(공급만 해줌) |
Consumer |
void accept(T t) | Supplier와 반대로 매개변수만 있고 반환값이 없음(소비만 함) |
Function |
R apply(T t) | 일반적인 함수, 하나의 매개변수를 받아서 결과를 반환(입력과 출력이 있는 것) |
Predicate<T, Boolean(생략가능)> | boolean test(T t) | 조건식을 표현하는데 사용, 매개변수는 하나, 반환 타입은 boolean(조건식, 반환을 true, false로), Predicate는 항상 boolean을 반환하기 때문에 Predicate<T, Boolean>이지만 Predicate |
Pridicate<String> isEmptyStr = s -> s.length == 0;
String s = "";
if (isEmptyStr.test(s)){
System.out.println("This is an empty string.");
}
----------------------------------------------
2. Quiz
? f = () -> (int)(Math.random()*100)+1 // 입력은 없고 반환만 있다, 공급자 Supplier
? f = i -> System.out.println(i + " "); // 입력이 있고 반환이 없다, 소비자 Consumer
? f = i -> i%2==0; // 입력이 있고 boolean으로 반환한다, 조건식 Predicate
? f = i -> i/10*10; // 입력도 있고 반환값도 있는데 boolean이 아니다, Function
java.util.function 패키지(2/3)
- 매개변수가 2개인 함수형 인터페이스
- BiSupplier가 없는 이유는 함수는 반환값으로 하나만 내보낼 수 있기 때문에
- 매개변수 3개 이상은 직접 만들어서 사용
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
BiConsumer<T,U> | void accept(T t, U u) | 두개의 매개변수가 있지만, 반환값이 없음 |
BiPredicate<T,U> | boolean test(T t, U u) | 조건식을 표현하는데 사용, 매개변수는 둘, 반환은 boolean(true, false) |
BiFunction<T,U> | R apply(T t, U u) | 두 개의 매개변수를 받아서 결과값을 반환 |
@FunctionalInterface
interface TriFunction<T,U,V,R>{
R apply(T t, U u, V v);
}
java.util.function 패키지(3/3)
- 매개변수의 타입과 반환타입이 일치하는 함수형 인터페이스
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
UnaryOperator |
T apply(T t) | Function의 자손, Function과 달리 매개변수와 반환 타입이 같음(단항연산자) |
BinaryOperator |
T apply(T t, T t) | BiFunction의 자손, BiFunction과 달리 매개변수와 반환 타입이 같음(이항연산자) |
1. UnaryOperator의 구조와 조상인 Function의 구조
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T>{
static <T> UnaryOperator<T> identity(){
return t -> t;
}
}
@FunctionalInterface
public interface Function<T, R>{
R apply<T t>;
...
}
-----------------------------------------------------
2. 예제
class Ex{
public static void main(String[] args){
Supplier<Integer> s = () -> (int)(Math.random()* 100) + 1;
Consumer<Integer> c = i -> System.out.print(i + ", ");
Predicate<Integer> p = i -> i%2==0;
Function<Integer, Integer> f = i ->i/10*10;
List<Integer> lst = new ArrayList<>();
makeRandomList(s, lst); // lst에 랜덤값을 추가
System.out.println("randomList : " + lst);
printEvenNum(p, c, lst);
List<Integer> newList = doSomething(f,lst);
System.out.println("new List : " + newList);
}
static <T> List<T> doSomething(Function<T, T> f, List<T> list){
List<T> newList = new ArrayList<T>(list.size());
for(T i: list){
newList.add(f.apply(i));
}
return newList;
}
static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list){
System.out.print("EvenNum : [");
for (T i: list){
if(p.test(i)){
c.accept(i);
}
}
System.out.println("]");
}
static <T> void makeRandomList(Supplier<T> s, List<T> list){
for(int i = 0; i <10; i++){
list.add(s.get()); // Supplier로부터 1~100의 난수를 받아 list에 추가
}
}
}
> randomList : [28, 50, 95, 47, 36, 90, 17, 60, 23, 49]
> EvenNum : [28, 50, 36, 90, 60, ]
> new List : [20, 50, 90, 40, 30, 90, 10, 60, 20, 40]
Predicate의 결합
- and()(&&), or()(||), negate()(!)로 두 Predicate를 하나로 결합 가능(default 메서드)
- 인터페이스에서 가질 수 있는 메서드는 추상메서드, default, static
1. Predicate의 결합에 대한 예시
Predicate<Integer> p1 = i -> i < 100;
Predicate<Integer> p2 = i -> i < 200;
Predicate<Integer> p3 = i -> i i%2 == 0;
Predicate<Integer> notP = p1.negate(); // i >= 100;
Predicate<Integer> all = notP.and(p2).or(p3); // 100 <= i && i < 200 || i%2==0;
Predicate<Integer> all2 = notP.and(p2.or(p3)); // 100 <= i && (i<200 || i%2==0);
System.out.println(all.test(2)); // true
System.out.println(all2.test(2)); // false
- 등가비교를 위한 Predicate의 작성에는 isEqual()를 사용(static메서드)
Predicate<String> p = Predicate.isEqual(str1);
Boolean result = p.test(str2);
boolean result = Predicate.isEqual(str1).test(str2);
------------------------------------------------------
2. 예제
class Ex{
public static void main(String[] args){
Function<String, Integer> f = (s) -> Integer.parseInt(s, 16);
Function<Integer, String> g = (i) -> Integer.toBinaryString(i);
Function<String, String> h = f.andThen(g); // f 다음 g 실행
Function<Integer Integer> h2 = f.compose(g);
System.out.println(h.apply("FF")); // "FF" -> 255 -> 11111111
System.out.println(h2.apply(2)); // 2 -> "10" -> 16
Function<String, String> f2 = x -> x; // 항등함수, 자기 자신을 출력
System.out.println(f2.apply("AAA")); // AAA
Predicate<Integer> p1 = i -> i < 100;
Predicate<Integer> p2 = i -> i < 200;
Predicate<Integer> p3 = i -> i%2 == 0;
Predicate<Integer> notP = p1.negate(); // i >= 100;
Predicate<Integer> all = notP.and(p2.or(p3));
System.out.println(all.test(500)); // true
String str1 = "abc";
String str2 = "abc";
// str1, str2를 비교
Predicate<String> ps = Predicate.isEqual(str1);
boolean result = ps.test(str2); // str1.equals(str2)랑 같음
System.out.println(result); // true , "abc" eqauls "abc" , new String으로 비교해도 같음, equals로 비교하기 때문
}
}
> 11111111
> 16
> AAA
> true
> true
컬렉션 프레임워크와 함수형 인터페이스
- 함수형 인터페이스를 사용하는 컬렉션 프레임워크의 메서드(와일드 카드 생략)
인터페이스 | 메서드 | 설명 |
---|---|---|
Collection | boolean removeIf(Predicate |
조건에 맞는 요소 삭제 |
List | void replaceAll(UnaryOperator |
모든 요소를 반환하여 대체 |
Iterable | void forEach(Consumer |
모든 요소에 작업 action을 수행 |
Map | V compute(K key, BiFunction<K,V,V> f) V computeIfAbsent(K key, Function<K,V> f) V computeIfPresent(K key, BiFunction<K,V,V> f) V merge(K key, V vale, BiFunction<V,V,V> f) void forEach(BiConsumer<K,V> action) void replaceAll(BiFunction<K,V,V> f) |
지정된 키의 값에 작업 f를 수행 키가 없으면, 작업 f 수행 후 추가 지정된 키가 있을 때, 작업 f 수행 모든 요소에 병합작업 f 수행 모든 요소에 작업 action을 수행 모든 요소에 치환작업 f를 수행 |
1. 컬렉션 함수형 인터페이스 사용 예시
list.forEach(i -> System.out.println(i+",")); // list의 모든 요소를 출력
list.removeIf(x -> x % 2 == 0 || x % 3 == 0 ); // 2의 배수거나 3의 배수 요소면 삭제
list.replaceAll(i -> i*10); // 모든 요소에 10을 곱함
map.forEach((k,v) -> System.out.print("{" + k + ", "+ v+ "}, ")); // 맵에 있는 k,v를 받아서 모든 요소를 출력
-----------------------------------------------------------------------------------------------------------
2. 예제
import java.util.*;
import java.lang.*;
class Ex{
public static void main(String[] args){
ArrayList<Integer> lst = new ArrayList<Integer>();
for (int i = 0; i < 6; i++){
lst.add((int)(Math.random() * 45) + 1);
}
lst.forEach(i -> System.out.print(i + ", "));
System.out.println();
lst.removeIf(x -> x % 2 == 0 || x % 3 == 0);
System.out.println(lst);
lst.replaceAll(i -> i * 10);
System.out.println(lst);
Map<String, String> m = new HashMap<>();
m.put("1", "1");
m.put("3", "3");
m.put("2", "2");
m.put("4", "4");
m.forEach((k,v) -> System.out.print("{" + k + ", " + v + "}, "));
System.out.println();
// Iterator it = m.entrySet.iterator();
// while(it.hasNext()){
// System.out.print(it.next());
// }
}
}
> 16, 6, 37, 29, 25, 35,
> [37, 29, 25, 35]
> [370, 290, 250, 350]
> {1, 1}, {2, 2}, {3, 3}, {4, 4},
$E = mc^2$ -> $ERROR = (MORE CODE)^2$
메서드 참조(method reference)
- 하나의 메서드만 호출하는 람다식은 메서드 참조로 간단히 할 수 있음
- 클래스 이름 :: 메서드 이름
종류 | 람다 | 메서드참조 |
---|---|---|
static 메서드 참조 | (x) -> ClassName.method(x) | ClassName::method |
인스턴스메서드 참조 | (obj.x) -> obj.method(x) | ClassName::method |
특정객체인스턴스메서드 참조 | (x) -> obj.method(x) | obj::method (사용하지 않음) |
- static 메서드 참조
Integer method(String s ){ // 그저 Integer.parseInt(String s)만 호출
return Integer.parseInt(s);
}
= Integer.parseInt(s)
= Function<String, Integer> f = (String s) -> Integer.parseInt(s);
= Function<String, Integer> f = Integer::parseInt; // 메서드 참조
-----------------------------------------------------------
class Ex{
public static void main(String[] args){
Function<String, Integer> f = (String s) -> Integer.parseInt(s);
System.out.println(f.apply("100") + 200);
Function<String, Integer> f2 = Integer::parseInt;
System.out.println(f2.apply("100") + 200);
}
}
> 300
> 300
생성자의 메서드 참조
- 생성자와 메서드 참조
Supplier<MyClass> s = () -> new MyClass();
Supplier<MyClass> s = MyClass::new;
Function<Integer, MyClass> f = (i) -> new MyClass(i);
Function<Integer, MyClass> f = MyClass::new;
- 배열과 메서드 참조
Function<Integer, int[]> f = x -> new int[x]; Function<Integer, int[]> f = int[]::new; // 배열 타입 :: new
1. 예제
class Ex{
public static void main(String[] args){
Supplier<MyClass> s1 = () -> new MyClass();
Supplier<MyClass> s2 = MyClass::new;
System.out.println(s1.get());
System.out.println(s2.get());
Function<Integer, MyClass2> f1 = (i) -> new MyClass2(i);
Function<Integer, MyClass2> f2 = MyClass2::new;
System.out.println(f1.apply(1));
System.out.println(f2.apply(2));
MyClass2 mc2 = f2.apply(10);
System.out.println(mc2.iv);
Function<Integer, int[]> f3 = (i) -> new int[i];
Function<Integer, int[]> f4 = int[]::new;
System.out.println(f3.apply(10).length);
System.out.println(f4.apply(100).length);
}
}
class MyClass{
}
class MyClass2{
int iv;
MyClass2(int iv){
this.iv = iv;
}
}
> MyClass@7b1d7fff
> MyClass@2e5c649
> MyClass2@3caeaf62
> MyClass2@e6ea0c6
> 10
> 10
> 100
스트림(Stream)
- 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
- Stream이라는 표준화된 방법으로 다룸
- 1. stream 생성
- 2. 중간연산
- 3. 최종연산
List<Integer> lst = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = lst.stream(); // 컬렉션, Stream<T> Collection.stream()
Stream<String> strStream = Stream.of(new String[]{"a","b","c"}); // 배열
Stream<Integer> evenStream = Stream.iterate(0, n->n+2); // 0,2,4,6 ...
Stream<Double> randomStream = Stream.generate(Math::random); // 람다식, 난수 생성
IntStream intStream = new Random().ints(5) // 난수 스트림(크기가 5)
- 스트림이 제공하는 기능 - 중간 연산과 최종연산
- 중간연산 : 연산결과가 스트림인 연산, 반복적으로 적용가능
- 최종연산 : 연산결과가 스트림이 아닌 연산, 단 한번만 적용가능(스트림의 요소를 소모)
stream.distinct().limit(5).sorted().forEach(System.out::println);
// distinct : 중복 제거( 중간 연산 )
// limit : 5개 요소만 자르기( 중간 연산 )
// sorted : 정렬( 중간 연산 )
// forEach : 요소를 돌면서 메서드 적용 ( 최종 연산 )
Stream의 특징 (1/3)
- Stream은 데이터소스로부터 데이터를 읽기만할 뿐 변경하지 않음
List<Integer> lst = Arrays.asList(3,1,5,2,4);
List<Integer> sortedList = lst.stream().sorted().collect(Collectors.toList()); // 정렬 후 새로운 list 저장
System.out.println(lst);
System.out.println(sortedList);
> [3,1,5,2,4]
> [1,2,3,4,5]
- Stream은 Iterator처럼 일회용(필요하면 다시 생성해야 함)
- 최종연산 후에는 스트림이 끝나기 때문에 다시 생성해주어야 함
strStream.forEach(System.out::println); // 스트림을 최종연산함.
int numOfStr = strStream.count(); // error, 스트림은 이미 끝남
- 최종연산까지 중간연산이 수행되지 않음 - 지연된 연산
IntStream intStream = new Random()ints(1,46); // 스트림 생성(1~45범위의 무한 스트림)
intStream.distinct().limit(6).sorted() // 중간 연산(표현만 하고 실행하지 않고 생성만)
.forEach(i -> System.out.print(i + ", ")); // 최종연산
Stream의 특징(2/3)
- Stream은 작업을 내부 반복으로 처리
for(String str : strList){
System.out.println(str);
}
stream.forEach(System.out::println);
void forEach(Consumer<? super T> action){
Objects.requireNonNull(action); // 매개변수 널 체크
for(T t : src){ // 내부반복(for문을 메서드 안에 넣음)
action.accept(t);
}
}
Stream의 특징(3/3)
- Stream의 작업은 병렬로 처리
Stream<String> strStream = Stream.of("dd","aaa","CC","cc","b");
int sum = strStream.parallel() // 병렬 스트림으로 전환(속성만 변경)
.mapToInt(s -> s.length()).sum(); // 모든 문자열의 길이의 합
- 기본형 스트림 - IntStream, LongStream, DoubleStream
- 오토박싱&언박싱의 비효율을 제거함(Stream
대신 IntStream을 사용) - 숫자와 관련된 유용한 메서드를 Stream
보다 더 많이 제공 - 언제나 사용할 수 있는 것이 아님, 데이터소스가 기본형일 때 가능
- 오토박싱&언박싱의 비효율을 제거함(Stream
스트림 만들기 - 컬렉션
- Collection 인터페이스의 stream()으로 컬렉션을 스트림으로 변환
1. Collection 인터페이스에 있는 stream()메서드의 구성
Stream<T> stream(); // Collection 인터페이스 메서드
-----------------------------------------------
2. 스트림 컬렉션 사용 예시
List<Integer> lst = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = lst.stream();
intStream.forEach(System.out::println); // 모든 요소를 출력, 최종연산
intStream.forEach(System.out::println); // error, 한번 최종연산을 하면 끝남 새로 생성해야함
-----------------------------------------------
3. 예제
import java.util.*;
import java.lang.*;
class Ex{
public static void main(String[] args){
List<Integer> lst = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = lst.stream();
intStream.forEach(System.out::print);
intStream.forEach(System.out::print); // error
}
}
스트림 만들기 - 배열
- 객체 배열로부터 스트림 생성
Stream<T> Stream.of(T .... values) // 가변 인자
Stream<T> Stream.of(T[])
Stream<T> Arrays.stream(T[])
Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive)
Stream<String> strStream = Stream.of("a","b","c");
Stream<String> strStream = Stream.of(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"}, 0, 3);
- 기본형 배열로부터 스트림 생성
IntStream IntStream.of(int.... values) // Stream이 아니라 IntStream
IntStream IntStream.of(int[])
IntStream Arrays.stream(int[])
IntStream Arrays.stream(int[] array, int startInclusive, int endExclusive)
IntStream IntStream.of(1,2,3,4,5);
IntStream IntStream.of(new int[]{1,2,3,4,5});
IntStream Arrays.stream(new int[]{1,2,3,4,5});
IntStream Arrays.stream(new int[]{1,2,3,4,5}, 0, 5);
-------------------------------------------------------------------------
2. IntStream 예제
import java.util.stream.*;
class Ex{
public static void main(String[] args){
IntStream intStream1 = IntStream.of(1,2,3,4,5);
IntStream intStream2 = IntStream.of(new int[]{1,2,3,4,5});
IntStream intStream3 = Arrays.stream(new int[]{1,2,3,4,5});
IntStream intStream4 = Arrays.stream(new int[]{1,2,3,4,5}, 0, 5);
intStream1.forEach(System.out::print);
System.out.println();
intStream2.forEach(System.out::print);
System.out.println();
intStream3.forEach(System.out::print);
System.out.println();
intStream4.forEach(System.out::print);
System.out.println();
IntStream intStream5 = Arrays.stream(new int[]{1,2,3,4,5});
IntStream intStream6 = Arrays.stream(new int[]{1,2,3,4,5}, 0, 5);
int sum = intStream5.sum();
double aver = intStream6.average().getAsDouble(); // OptionalDouble을 Double로 바꿔줌
System.out.println("Sum : " + sum);
System.out.println("average : " + aver);
}
}
> 12345
> 12345
> 12345
> 12345
> Sum : 15
> average : 3.0
스트림 만들기 - 임의의 수
- 난수를 요소로 갖는 스트림 생성
IntStream intStream = new Random().ints(); //무한 스트림
intStream.limit(5).forEach(System.out::println);
IntStream intStream = new Random().ints(5); // 크기가 5인 난수 스트림 반환
- 지정된 범위의 난수를 요소로 갖는 스트림을 생성하는 메서드(Random클래스)
1. 난수 스트림 메서드
IntStream ints(int begin, int end) // 무한 스트림
LongStream longs(long begin, long end)
DoubleStream doubles(double begin, double end)
IntStream ints(long streamSize, int begin, int end) // 유한 스트림
LongStream longs(long streamSize, long begin, long end)
DoubleStream doubles(long streamSize, double begin, double end)
-----------------------------------------------------------------
2. 예제
class Ex{
public static void main(String[] args){
IntStream intStream1 = new Random().ints(); // 무한 스트림
IntStream intStream2 = new Random().ints(5); // 5개
IntStream intStream3 = new Random().ints(1,10); // 무한 스트림 1~ 10의 범위 무한
IntStream intStream4 = new Random().ints(10,1,10); // 무한 스트림 1~10범위 10개 난수
intStream2.forEach(System.out::println) // min_value < ints < max_value 범위를 가지는 5개 난수
intStream3.forEach(System.out::println) // 1 < ints < 10 범위를 가지는 무한 난수
intStream4.forEach(System.out::println) // 1 < ints < 10 범위를 가지는 10개 난수
}
}
- 특정 범위의 정수를 요소로 갖는 스트림 생성(IntStream, LongStream)
IntStream IntStream.range(int begin, int end)
IntStream IntStream.rangeClosed(int begin, int end)
IntStream intStream = IntStream.range(1, 5) // 1,2,3,4 - 끝을 포함하지 않음
IntStream intStream = IntStream.rangeClosed(1, 5) // 1,2,3,4,5 - 끝을 포함
스트림 만들기 - 람다식 iterate(), generate()
- 람다식을 소스로 하는 스트림 생성
- 무한 스트림
1. 람다식 iterate, generate 구성과 사용 예시
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f) // seed : 초기값, 이전 요소에 종속적
static <T> Stream<T> generate(Supplier<T> s) // 이전 요소에 독립적
Stream<Integer> evenStream = Stream.iterate(0, n->n+2) // 0, 2, 4, 6, ... 초기값이 0 -> 2 -> 4로 바뀌는 것
Stream<Double> randomstate = Stream.generate(Math::random); // seed가 없음
-----------------------------------------------------------
2. 예제
class Ex{
public static void main(String[] args){
Stream<Integer> intStream = Stream.iterate(1, n -> n + 2);
intStream.limit(10).forEach(System.out::println); // 1, 3, 5, 7, ... 19
Stream<Integer> intStream2 = Stream.generate(() -> 1);
intStream.limit(5).forEach(System.out::println); // 1, 1, 1, 1, 1
}
}
스트림 만들기 - 파일과 빈 스트림
- 파일을 소스로 하는 스트림 생성
Stream<Path> Files.list(Path dir) // Path는 파일 또는 디렉토리
Stream<String> Files.lines(Path path) // 파일 내용을 라인 단위로 읽어서 STRING으로 만들어서 Stream<String>으로 반환. log파일 읽을 때
Stream<String> Files.lines(Path path, Charset cs)
Stream<String> lines() // BufferedReader클래스의 메서드,
- 비어있는 스트림 생성
Stream emptyStream = Stream.empty(); // empty()는 빈 스트림을 생성해서 반환
long count = emptyStream.count(); // 0
스트림 연산
- 스트림이 제공하는 연산 - 중간 연산과 최종 연산
- 중간연산
중간연산 | 설명 |
---|---|
Stream |
중복을 제거 |
Stream |
조건에 안 맞는 요소 제외 |
Stream |
스트림을 일부를 잘라냄 |
Stream |
스트림을 일부 건너 뜀 |
Stream |
스트림의 요소에 작업수행 |
Stream Stream |
스트림의 요소를 정렬 |
Stream DoubleStream mapToDouble(ToDoubleFunction IntStream mapToInt(ToIntFunction LongStream mapToLong(ToLongFunction Stream DoubleStream flatMapToDouble(Function<T, DoubleStream> m) IntStream flatMapToInt(Function<T, IntStream> m) LongStream flatMapToLong(Function<T, LongStream> m) |
스트림의 요소를 반환 |
- 최종연산
최종연산 | 설명 |
---|---|
void forEach(Consumer<? super T> action) void forEachOrdered(Consumer<? super T> action) |
각 요소의 지정된 작업 수행 |
long count() | 스트림의 요소 수 반환 |
Optional Optional |
스트림의 최대 / 최소값 반환 |
Optional Optional |
스트림의 요소 하나를 반환 |
boolean allMatch(Predicate boolean anyMatch(Predicate boolean noneMatch(Predicate |
주어진 조건을 모든 요소가 만족 시키는지, 만족시키지 않는지 확인 |
Object[] toArray() A[] toArray(IntFunction<A[]> generator) |
스트림의 모든 요소를 배열로 반환 |
Optional T reduce(T identity, BinaryOperator U reduce(U identity, BinaryOperator<U,T,U> accumulator) |
스트림의 요소를 하나씩 줄여가면서(리듀싱) 계산 |
R collect(Collection<T,A,R> c) R collect(Supplier |
스트림의 요소를 수집, 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용 |
스트림의 중간연산(1/7)
- 스트림 자르기 - skip(), limit()
Stream<T> skip(long n) // 앞에서부터 n개 건너 뜀
Stream<T> limit(long maxSize) // maxSize 이후의 요소를 잘라냄
IntStream intStream = IntStream.rangeClosed(1, 10);
intStream.skip(3).limit(5).forEach(System.out::print) // 123 45678 910
스트림의 중간연산(2/7)
- 스트림의 요소 걸러내기 - filter(), distinct()
Stream<T> filter(Predicate<? super T> p) // 조건에 맞지 않는 요소 제거
Stream<T> distinct() // 중복제거
----------------------------------------------------------
2. 예제
class Ex{
public static void main(String[] args){
IntStream intStream = IntStream.of(1,2,3,4,2,3,4,5,6);
intStream.distinct().forEach(System.out::print); // 중복제거
IntStream intStream = IntStream.rangeClosed(1, 10);
intStream.filter(i->i%2==0).forEach(System.out::print); // 2의 배수만 filter
System.out.println();
IntStream intStream3 = IntStream.rangeClosed(1, 10);
intStream3.filter(i->i%2==0).filter(i->i%3==0).forEach(System.out::print);
}
}
> 123456
> 246810
> 6
스트림의 중간연산(3/7)
스트림 정렬 - sorted()
정렬에 필요한 것
- 정렬의 대상
- 정렬의 기준
Stream<T> sorted() // 스트림 요소의 기본 정렬(Comparable)로 정렬
Stream<T> sorted(Comparator<? super T> c) // 지정된 Comparator로 정렬
---------------------------------------------------------------
class Ex{
public static void main(String[] args){
List<String> strList = Arrays.asList("aaa","b","cc","dd","CC");
// 기본정렬
strList.stream().sorted().forEach(System.out::print);
System.out.println();
strList.stream().sorted(Comparator.naturalOrder()).forEach(System.out::print);
System.out.println();
strList.stream().sorted((s1,s2)->s1.compareTo(s2)).forEach(System.out::print);
System.out.println();
strList.stream().sorted(String::compareTo).forEach(System.out::print);
System.out.println();
// 반대정렬
strList.stream().sorted(Comparator.reverseOrder()).forEach(System.out::print);
System.out.println();
strList.stream().sorted(Comparator.<String>naturalOrder().reversed()).forEach(System.out::print);
System.out.println();
// 대소문자 무시와 반대정렬
strList.stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(System.out::print);
System.out.println();
strList.stream().sorted(String.CASE_INSENSITIVE_ORDER.reversed()).forEach(System.out::print);
System.out.println();
// 길이 순 정렬
strList.stream().sorted(Comparator.comparing(String::length)).forEach(System.out::print);
System.out.println();
strList.stream().sorted(Comparator.comparingInt(String::length)).forEach(System.out::print);
System.out.println();
strList.stream().sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::print);
System.out.println();
}
}
> CCaaabccdd // 기본정렬
> CCaaabccdd // 기본정렬
> CCaaabccdd // 기본정렬
> CCaaabccdd // 기본정렬
> ddccbaaaCC // 역순
> ddccbaaaCC // 역순
> aaabccCCdd // 대소문자 구분없음
> ddccCCbaaa // 대소문자 구분 없이 역순
> bccddCCaaa // 길이 순 정렬, 길이가 짧은 순
> bccddCCaaa // 길이 순 정렬, no오토박싱
> aaaccddCCb // 길이가 긴 순
스트림의 중간연산(4/7)
- Comparator의 comparing()으로 정렬기준을 제공
- comparing의 반환타입이 Comparator임
- 즉 sorted는 Comparator가 매개로 필요하기 때문에 sorted안에 있는 것
1. comparing의 구조와 사용 예시
comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)
studentStream.sorted(Comparator.comparing(Student::getBan)) // 반별로
.thenComparing(Student::getTotalScore) // 총점별로
.thenComparing(Student::getName) // 이름별로
.forEach(System.out::println);
---------------------------------------------------------------
2.예제
class Student implements Comparable<Student>{
String name;
int ban;
int totalScore;
Student(String name, int ban, int totalScore){
this.name = name;
this.ban = ban;
this.totalScore = totalScore;
}
public String toString(){
return String.format("[%s, %d, %d]", name, ban, totalScore);
}
String getName(){ return name; }
int getBan(){ return ban; }
int getTotalScore(){ return totalScore; }
// 총점 내림차순을 기본 정렬로
public int compareTo(Student s){
return s.totalScore - this.totalScore;
}
}
class Ex{
public static void main(String[] args){
Stream<Student> studentStream = Stream.of(
new Student("김자바", 3, 300),
new Student("이자바", 1, 200),
new Student("박자바", 2, 100),
new Student("정자바", 2, 200),
new Student("위자바", 3, 160),
new Student("유자바", 1, 100),
new Student("진자바", 1, 300),
);
studentStream.sorted(Comparator.comparing(Student::getBan) // 반 정렬
.thenComparing(Comparator.naturalOrder()))
.forEach(System.out::println);
}
}
> [진자바, 1, 300]
> [이자바, 1, 200]
> [유자바, 1, 100]
> [정자바, 2, 200]
> [박자바, 2, 100]
> [김자바, 3, 300]
> [위자바, 3, 160]
스트림의 중간연산(5/7)
- 스트림의 요소 변환하기 - map(), flatMap()
Stream<R> map(Function<? super T, ? extends R> mapper) // Stream<T> -> Stream<R>
Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1"), new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt"));
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println); // 스트림의 모든 파일의 이름을 출력
fileStream.map(File::getName()) // Stream<File> -> Stream<String>
.filter(s -> s.indexOf('.') != -1) // 확장자가 없는 것은 제외
.map(s->s.substring(s.indexOf('.') + 1)) // Stream<String> -> Stream<String>
.map(String::toUpperCase) // Stream<String> -> Stream<String>, 대문자로
.distinct() // 중복제거
.forEach(System.out::print);
-----------------------------------------------------------------------------------
class Ex{
public static void main(String[] args){
File[] fileArr = {new File("Ex1.java"), new File("Ex1"), new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt")};
Stream<File> fileStream = Stream.of(fileArr);
// map으로 Stream<File>을 Stream<String>으로
Stream<String>filenameStream = fileStream.map((f) -> f.getName());
filenameStream.forEach(System.out::println);
fileStream = Stream.of(fileArr);
fileStream.map(File::getName)
.filter(s -> s.indexOf('.') != -1) // 문자가 해당문자열에 없으면 -1을 반환하기 때문에 -1을 제외한 값이 나오면 문자열에 .이 포함되어 있다는 뜻, 즉 .가 포함된 문자열만 반환(확장자 없는 것은 제외)
.map(s -> s.substring(s.indexOf('.')+1)) // .를 찾아서 그 오른쪽부터 끝까지
.map(String::toUpperCase) // 대문자로 반환
.distinct() // 중복 제거
.forEach(System.out::print);
System.out.println()
}
}
> Ex1.java
> Ex1
> Ex1.bak
> Ex2.java
> Ex1.txt
> JAVABAKTXT
스트림의 중간연산(6/7)
- 스트림의 요소를 소비하지 않고 보기 - peek()
- 디버깅 용도로 사용
Stream<T> peek(Consumer<? super T> action) // 중간 연산 ( 스트림을 소비 x)
void forEach(Consumer<? super T> action) // 최종 연산 ( 스트림을 소비 o )
fileStream.map(File::getName)
.filter(s -> s.indexOf('.')!=-1)
.peek(s -> System.out.printf("filename = %s%n", s)) // 파일명만 출력
.map(s -> s.substring(s.indexOf('.') + 1)) // 확장자만 추출
.peek(s -> System.out.printf("extension name = %s%n",s))
.forEach(System.out::println); // 최종 연산 스트림 소비
> filename = Ex1.java // file name
> extension name = java // extension name
> java // 최종 연산 결과
> filename = Ex1.bak
> extension name = bak
> bak
> filename = Ex2.java
> extension name = java
> java
> filename = Ex1.txt
> extension name = txt
> txt
스트림의 중간연산(7/7)
- 스트림의 스트림을 스트림으로 변환 - flatMap()
1. flatMap의 사용 예시
Stream<String[]> strArrStrm = Stream.of(new String[]{"abc", "def", "ghi"}, new String[]{"ABC", "DEF", "GHI"});
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);
Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream); // Arrays.stream(T[])
-------------------------------------------------------------------------
2. 예제
class Ex{
public static void main(String[] args){
String[] lineArr = {
"Believe or not It is true",
"Do or do not There is no try"
};
Stream<String> lineStream = Arrays.stream(lineArr);
lineStream.flatMap(line -> Stream.of(line.split(" +"))) // 정규표현식, 하나 이상의 공백이 있을 때 나눔
.map(String::toLowerCase)
.forEach(System.out::println);
}
}
> believe
> or
> not
> it
> is
> true
> do
> or
> do
> not
> there
> is
> no
> try
Optional
- T 타입 객체의 래퍼클래스
- null을 직접 다루지 않고 래퍼클래스로 다루면 NullPointerException에 의한 에러를 막을 수 있음
- null체크하는 코드를 더 해주어야 하는 수고를 덜어줌
public final class Optional<T>{
private final T value; // T타입의 참조변수 , 모든 종류의 객체 저장 가능, null까지
...
}
- Optional
객체를 생성하는 방법
String str = "abc";
Optional<String> optVal = Optional.of(str);
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(null); // NullPointerException 발생
Optional<String> optVal = Optional.ofNullable(null) // ok, null을 저장할 때
- null 대신 빈 Optional
객체를 사용하자
Optional<String> optVal = null; // null로 초기화 하지 말자
Optional<String> optVal = Optional.<String>empty(); // 빈 객체로 초기화
Optional객체의 값 가져오기
- Optional객체의 값 가져오기 - get(), orElse(), orElseGet(), orElseThrow()
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get(); // optVal에 저장된 값을 반환, null이면 예외 발생
String str2 = optVal.orElse(""); // optVal에 저장된 값이 null일 때는 "" 반환
String str3 = optVal.orElseGet(String::new); // null 일 때, 빈 객체를 반환, 람다식 사용가능 () -> new String();
String str4 = optVal.orElseThrow(NullPointerException::new); // 널이면 예외 발생
-isPresent() - Optional객체의 값이 null이면 false, 아니면 true를 반환
if(Optional.ofNullable(str).isPresent()){ // if(str!=null)
System.out.println(str);
}
Optionam.ofNullable(str).isPresent(System.out::println);
1. 예제
class Ex{
public static void main(String[] args){
// int[] arr = null ;
int[] arr = new int[0];
System.out.println("arr.length : " + arr.length);
// Optional<String> opt = null;
Optional<String> opt = Optional.empty();
System.out.println("opt : " + opt);
System.out.println("opt : " + opt.get()); // error발생
String str = "";
str = opt.orElse("EMPTY"); // null이면 ""반환
System.out.println("str : " + str);
}
}
OptionalInt, OptionalLong, OptionalDouble
- 기본형 값을 감싸는 래퍼클래스
- 래퍼는 기본형보다 성능이 조금 떨어질 수 밖에 없는데 이를 보완하고자 나온 것
public final class OptionalInt{
...
private final boolean isPresent; // 값이 저장되어 있으면 true
private final int value; // int타입의 변수
}
- OptionalInt의 값 가져오기 - int getAsInt()
Optional 클래스 | 반환 메서드 |
---|---|
Optional |
T get() |
OptionalInt | int getAsInt() |
OptionalLong | long getAsLong() |
OptionalDouble | double getAsDouble() |
- 빈 Optional객체와의 비교
OptionalInt opt = OptionalInt.of(0); // OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empty(); // OptionalInt에 0을 저장
이를 구분하기 위해서 isPresent()를 사용
System.out.println(opt.isPresent()); // true
System.out.println(opt2.isPresent()); // false
System.out.println(opt.equals(opt2)); // false
1. 예제
class Ex{
public static void main(String[] args){
Optional<String> optStr = Optional.of("abcde");
Optional<Integer> optInt = optStr.map(s -> s.length()); // 5
System.out.println("optStr : " + optStr.get());
System.out.println("optInt : " + optInt.get());
int result1 = Optional.of("123").filter(x -> x.length() > 0).map(Integer::parseInt).get();
int result2 = Optional.of("").filter(x -> x.length() > 0).map(Integer::parseInt).orElse(-1);
System.out.println("result1 : " + result1);
System.out.println("result2 : " + result2);
Optional.of("456").map(Integer::parseInt).ifPresent(x -> System.out.printf("result3 = %d%n",x));
OptionalInt optInt1 = OptionalInt.of(0); // 0 저장, isPresent() true
OptionalInt optInt2 = OptionalInt.empty(); // 0 저장, isPresent() false
System.out.println(optInt1.isPresent()); // true
System.out.println(optInt2.isPresent()); // false
System.out.println(optInt1.getAsInt()); // 0
System.out.println(optInt2.getAsInt()); // NoSuchElementException
System.out.println("optInt1.equals(optInt2) : " + optInt1.equals(optInt2));
}
}
스트림의 최종연산
- 스트림의 요소를 소모하여 연산하는 것, 그러므로 한 번만 연산가능
- 반환값이 스트림이 아닌 타입
- 스트림은 기본적으로 직렬스트림
forEach()
- 스트림의 모든 요소에 지정된 작업을 수행 - forEach(), forEachOrdered()
void forEach(Consumer<? super T> action) // 병렬스트림인 경우 순서가 보장되지 않음
void forEachOrdered(Consumer<? super T> action) // 병렬스트림인 경우에도 순서가 보장
IntStream.range(1,10).sequential().forEach(System.out::print); // 123456789
IntStream.range(1,10).sequential().forEachOrdered(System.out::print); // 123456789
IntStream.range(1,10).parallel().forEach(System.out::print); // 657493128
IntStream.range(1,10).parallel().forEachOrdered(System.out::print); // 123456789
조건검사
- 조건 검사 - allMatch(), anyMatch(), nonMatch()
boolean allMatch(Predicate<? super T> p) // 모든 요소가 조건을 만족시키면 true
boolean anyMatch(Predicate<? super T> p) // 한 요소라도 조건을 만족시키면 true
boolean noneMatch(Predicate<? super T> p) // 모든 요소가 조건을 만족시키지 않으면 true
boolean hasFailedStudent = stuStream.anyMatch(s -> s.getTotalScore()<=100); // 낙제자가 있나?
- 조건에 일치하는 요소 찾기 - findFirst(), findAny()
- filter랑 같이 사용할 경우가 많음
Optional<T> findFirst() // 첫 번째 요소를 반환, 순차 스트림에 사용
Optional<T> findAny() // 아무거나 하나를 반환, 병렬스트림에 사용
Optional<Student> result = stuStream.filter(s -> s.getTotalScore() <= 100).findFirst();
Optional<Student> result = parallelStream.filter(s -> s.getTotalScore() <= 100).findAny();
reduce()
- 스트림의 요소를 하나씩 줄여가며 누적연산 수행
- 어떤 식으로 누적연산을 할지(accumulator)를 정해주어야 함
- identity : 초기값
- accumulator : 이전 연산결과와 스트림의 요소에 수행할 연산
- combiner : 병렬처리된 결과를 합치는데 사용할 연산(병렬 스트림에 사용)
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)
// int reduce(int identity, IntBinaryOperator op)
int count = intStream.reduce(0, (a,b) -> a + 1); //
int sum = intStream.reduce(0, (a,b) -> a + b);
int max = intStream.reduce(Integer.MIN_VALUE, (a,b) -> a > b ? a : b);
int min = intStream.reduce(Integer.MAX_VALUE, (a,b) -> a < b ? a : b);
// int a = identity;
// for(int b : stream)
// a += b
class Ex{
public static void main(String[] args){
String[] strArr = {
"Inheritance", "Java", "Lambda", "stream", "OptionalDouble", "IntStream", "count", "sum"
};
Stream.of(strArr).forEach(System.out::println);
boolean noEmptyStr = Stream.of(strArr).noneMatch(s -> s.length() == 0);
Optional<String> sWord = Stream.of(strArr).filter(s -> s.charAt(0)=='s').findFirst();
System.out.println("noEmptyStr : " + noEmptyStr);
System.out.println("sWord : " + sWord);
System.out.println("sWord.get() : " + sWord.get());
// Stream<Integer> intStream = Stream.of(strArr.map(String::length)); // Stream<Integer>
//Stream<String[]>을 IntStream으로 반환
IntStream intStream1 = Stream.of(strArr).mapToInt(String::length); // str의 길이로 반환
IntStream intStream2 = Stream.of(strArr).mapToInt(String::length); // str의 길이로 반환
IntStream intStream3 = Stream.of(strArr).mapToInt(String::length); // str의 길이로 반환
IntStream intStream4 = Stream.of(strArr).mapToInt(String::length); // str의 길이로 반환
int count = intStream1.reduce(0, (a , b)-> a + 1);
int sum = intStream2.reduce(0, (a, b) -> a + b);
OptionalInt min = intStream3.reduce(Integer::min);
OptionalInt max = intStream4.reduce(Integer::max);
System.out.println("max : " + max );
System.out.println("min : " + min );
System.out.println("max.getAsInt() : " + max.getAsInt() );
System.out.println("min.getAsInt() : " + min.getAsInt() );
}
}
> Inheritance
> Java
> Lambda
> stream
> OptionalDouble
> IntStream
> count
> sum
> noEmptyStr : true
> sWord : Optional[stream]
> sWord.get() : stream
> max : OptionalInt[14]
> min : OptionalInt[3]
> max.getAsInt() : 14
> min.getAsInt() : 3
collect()와 Collectors
- collect는 Collector를 매개변수로 하는 스트림의 최종연산
- Collector는 수집(collect)에 필요한 메서드를 정의해 놓은 인터페이스
- Collectors 클래스는 다양한 기능의 컬렉터(Collector를 구현한 클래스)를 제공
Object collect(Collector collector) // Collector를 구현한 클래스의 객체를 매개변수로
Object collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) // 잘 안씀
public interface Collector<T, A, R> { // T요소를 A에 누적한 후 결과를 R로 변환하여 반환
**Supplier<A> supplier(); // StringBuilder::new 누적할 곳
**BiConsumer<A, T> accumulator(); // (sb, s) -> sb.append(s) 누적방법
BinaryOperator<A> combiner(); // (sb1, sb2) -> sb1.append(sb2) 결합방법(병렬)
Function<A, R> finisher(); // sb -> sb.toString() 최종변환
Set<Characteristics> characteristics(); // 컬렉터의 특징이 담긴 Set을 반환
}
- 변환 : mapping(), toList(), toSet(), toMap(), toCollection(), ...
- 통계 : counting(), summingInt(), averagingInt(), maxBy(), minBy(), summarizingInt(), ...
- 문자열 결합 : joining()
- 리듀싱 : reducing()
- 그룹화와 분할 : groupingBy(), partitioningBy(), collectingAndThen()
스트림을 컬렉션, 배열로 변환
- 스트림을 컬렉션으로 변환 : toList(), toSet(), toMap(), toCollection()
List<String> names = stuStream.map(Student::getName) // Stream<Student> -> Stream<String>
.collect(Collectors.toList()); // Stream<String> -> List<String>
ArrayList<String> lst = names.stream().collect(Collectors.toCollection(ArrayList::new)); // Stream<String> -> ArrayList<String>
Map<String, Person> map = personStream
.collect(Collectors.toMap(p -> p.getRegId(), p->p)); //Stream<Person> -> Map<String, Person>
// key = p.getRegId, value = p객체
- 스트림을 배열로 변환 : toArray()
Student[] stuNames = studentStream.toArray(Student[]::new); // ok
Student[] stuNames = studentStream.toArray(); // error
Object[] stuNames = studentStream.toArray(); // ok
- 스트림의 통계 : counting(), summingInt(), maxBy(), minBy(), ...
long count = stuStream.count(); // 전체 카운팅
long count = stuStream.collect(counting()); // 그룹별 카운팅 가능
long totalScore = stuStream.mapToInt(Student::getTotalScore).sum(); // IntStream의 sum
long totalScore = stuStream.collect(summingInt(Student::getTotalScore)); // 그룹별 합계 가능
OptionalInt topScore = stuStream.mapToInt(Student::getTotalScore).max();
Optional<Student> topStudent = stuStream.max(Comparotor.comparingInt(Student::getTotalScore));
Optional<Student> topStudent = stuStream.collect(maxBy(Comparator.comparingInt(Student::getTotalScore))); // 비교기준이 총점
- 스트림을 리듀싱 - reducing()
Collector reducing(BinaryOperator<T> op)
Collector reducing(T identity, BinaryOperator<T> op)
Collector reducing(U identity, Function<T, U> mapper, BinaryOperator<U> op) // map + reduce
IntStream intStream = new Random().ints(1, 46).distinct().limit(6);
OptinalInt max = intStream.reduce(Integer::max);
Optional<Integer> max = intStream.boxed().collect(reducing(Integer::max)); // 그룹별 리듀싱
long sum = intStream.reduce(0, (a,b) -> a + b);
long sum = intStream.boxed().collect(reducing(0, (a,b) -> a + b));
int grandTotal = stuStream.map(Student::getTotalScore).reduce(0, Integer::sum);
int grandTotal = stuStream.collect(reducing(0, Student::getTotalScore, Integer::sum));
- 문자열 스트림의 요소를 모두 연결 : joining()
String studentNames = stuStream.map(Student::getName).collect(Collectors.joining());
String studentNames = stuStream.map(Student::getName).collect(Collectors.joining(",")); // 구분자
String studentNames = stuStream.map(Student::getName).collect(Collectors.joining(",", "[","]"));
String studentInfos = stuStream.collect(joining(",")); // Student의 toString()
스트림의 그룹화와 분할
- partitioningBy()는 스트림을 2분할로 나눔
- groupingBy()는 스트림을 n분할로 나눔
Collector partitioningBy(Predicate p)
Collector partitioningBy(Predicate p, Collector downstream)
Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
partitioningBy()
Map<Boolean, List<Student>> stuBySex = stuStream.collect(partitioningBy(Student::isMale)); //학생들을 성별로 구분
List<Student> maleStudent = stuBySex.get(true); // Map에서 남학생의 목록을 반환
List<Student> femaleStudent = stuBySex.get(false); // Map에서 여학생의 목록을 반환
-------------------------------------------------------------------------------------------------
Map<Boolean, Long> stuNumBySex = stuStream.collect(partitioningBy(Student::isMale, counting())); // 분할 + 통계
System.out.println("남학생 수 : " + stuNumBySex.get(true)); // 남학생 수
System.out.println("여학생 수 : " + stuNumBySex.get(false)); // 여학생 수
-------------------------------------------------------------------------------------------------
Map<Boolean, Optional<Student>> topScoreBySex = stuStream.collect(partitioningBy(Student::isMale, maxBy(comparingInt(Student::getScore))));
System.out.println("남학생 1등 : " + topScoreBySex.get(true)); // 남학생 1등
System.out.println("여학생 1등 : " + topScoreBySex.get(false)); // 여학생 1등
-------------------------------------------------------------------------------------------------
Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex = stuStream
.collect(partitioningBy(Student::isMale, partitioningBy(s -> s.getScore() < 150))); // 다중 분할, 성별별 합격여부
List<Student> failedMaleStu = failedStuBySex.get(true).get(true);
List<Student> failedFemaleStu = failedStuBySex.get(false).get(true);
groupingBy()
- 스트림의 요소를 그룹화( n 분할 )
Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
Map<Integer, List<Student>> stuByBan = stuStream.collect(groupingby(Student::getBan, toList())) // 반별로 그룹화,toList 생략가능
Map<Integer, List<Student>> stuByHakAndBan = stuStream.collect(groupingby(Student::getHak, groupingby(Student::getBan))); // 다중 그룹화, 학년별, 반별
Map<Integer,Map<Integer, Set<Student.Level>>> stuByHakAndBan = stuStream
.collect(
groupingBy(Student::getHak, groupingBy(Student::getBan,
mapping(s -> { List<Student>, Set<Student.LEVEL>
if(s.getScore>=200) return Student.LEVEL.HIGH;
else if(s.getScore>=100) return Student.LEVEL.MID;
else return Student.LEVEL.LOW;
}, toSet()) // mapping
)) // groupingBy
); // collect
스트림의 변환(1/2)
from | to | 변환 메서드 | 설명 |
---|---|---|---|
Stream |
IntStream LongStream DoubleStream |
mapToInt(ToIntFunction mapToLong(ToLongFunction mapToDouble(ToDoubleFunction |
스트림 -> 기본형 스트림 |
IntStream LongStream DoubleStream |
Stream Stream Stream Stream |
boxed() mapToObj(DoubleFunction mapper) |
기본형 스트림 -> 스트림 |
IntStream LongStream DoubleStream |
LongStream DoubleStream |
asLongStream() asDoubleStream() |
기본형 스트림 -> 기본형 스트림 |
Stream IntStream |
Stream IntStream |
skip(long n) limit(long maxSize) |
스트림 -> 부분 스트림 |
Stream IntStream, IntStream LongStream, LongStream DoubleStream, DoubleStream |
Stream IntStream LongStream |
concat(Stream concat(IntStream a, IntStream b) concat(LongStream a, LongStream b) concat(DoubleStream a, DoubleStream b) |
두 개의 스트림 -> 스트림 |
Stream<Stream Stream Stream Stream |
Stream IntStream LongStream DoubleStream |
flatMap(Function mapper) flatMapToInt(Function mapper) flatMapToLong(Function mapper) flatMapToDouble(Function mapper) |
스트림의 스트림 -> 스트림 |
스트림의 변환(2/2)
from | to | 변환 메서드 | 설명 |
---|---|---|---|
Stream IntStream LongStream DoubleStream |
Stream IntStream LongStream DoubleStream |
parallel() // 스트림 -> 병렬 스트림 sequential() // 병렬 스트림 -> 직렬 스트림 |
스트림 -> 병렬 스트림 |
Stream IntStream LongStream DoubleStream |
Collection List Set |
collect(Collectors.toCollection(Supplier factory)) collect(Collectors.toList()) collect(Collectors.toSet()) |
스트림 -> 컬렉션 |
Collection |
Stream |
stream() | 컬렉션 -> 스트림 |
Stream IntStream LongStream DoubleStream |
Map<K,V> | collect(Collectors.toMap(Function key, Function value)) collect(Collectors.toMap(Function k, Fucntion v, BinaryOperation)) collect(Collectors.toMap(Function k, function v, BinaryOperator merge, Supplier mapSupplier)) |
스트림 -> Map |
Stream IntStream LongStream DoubleStream |
Object[], T[] int[] long[] double[] |
toArray() toArray(IntFuntion<A[]> generator) toArray() |
스트림 -> 배열 |
- 자바의 정석 챕터 14에서는 람다식, 함수형 인터페이스, java.util.function패키지, Predicate, 메서드 참조, 생성자 메서드 참조, 스트림의 정의와 종류 및 사용법, Optional을 배웠습니다.
댓글