티스토리 뷰

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 filter) 조건에 맞는 요소 삭제
List void replaceAll(UnaryOperator operator) 모든 요소를 반환하여 대체
Iterable void forEach(Consumer action) 모든 요소에 작업 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보다 더 많이 제공
    • 언제나 사용할 수 있는 것이 아님, 데이터소스가 기본형일 때 가능


스트림 만들기 - 컬렉션

  • 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 distict() 중복을 제거
Stream filter(Predicate p) 조건에 안 맞는 요소 제외
Stream limit(long maxSize) 스트림을 일부를 잘라냄
Stream skip(long n) 스트림을 일부 건너 뜀
Stream peek(Consumer action) 스트림의 요소에 작업수행
Stream sorted()
Stream sorted(Comparator c)
스트림의 요소를 정렬
Stream map(Function<T,R> mapper
DoubleStream mapToDouble(ToDoubleFunction mapper
IntStream mapToInt(ToIntFunction mapper
LongStream mapToLong(ToLongFunction mapper

Stream flatMap(Function<T, Stream> mapper)
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 max(Comparator<? super T> c)
Optional min(Comparator<? super T> c)
스트림의 최대 / 최소값 반환
Optional findAny() // 아무거나 하나(병렬), filter와 같이 사용
Optional findFirst() // 첫 요소(직렬)
스트림의 요소 하나를 반환
boolean allMatch(Predicate p) // 모두 만족하는지
boolean anyMatch(Predicate p) // 하나라도 만족하는지
boolean noneMatch(Predicate p) // 모두 만족하지 않는지
주어진 조건을 모든 요소가 만족 시키는지, 만족시키지 않는지 확인
Object[] toArray()
A[] toArray(IntFunction<A[]> generator)
스트림의 모든 요소를 배열로 반환
Optional reduce(BinaryOperator accumulator)
T reduce(T identity, BinaryOperator accumulator)
U reduce(U identity, BinaryOperator<U,T,U> accumulator)
스트림의 요소를 하나씩 줄여가면서(리듀싱) 계산
R collect(Collection<T,A,R> c)
R collect(Supplier s, BiConsumer<R, T> accumulator, BiConsumer<R,R> combiner)
스트림의 요소를 수집, 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용


스트림의 중간연산(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 mapper)
mapToLong(ToLongFunction mapper)
mapToDouble(ToDoubleFunction mapper)
스트림 -> 기본형 스트림
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, Stream
IntStream, IntStream
LongStream, LongStream
DoubleStream, DoubleStream
Stream
IntStream
LongStreamDoubleStream
concat(Stream a, Stream b)
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, List, Set 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을 배웠습니다.

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