[자바의정석]Chapter 14. 람다와 스트림

seungwon·2023년 4월 29일
0

자바의 정석

목록 보기
14/14

1. 람다식

람다식의 도입 -> 자바는 객체지향언어+함수형 언어

1.1 람다식이란?

메서드를 하나의 식(expression)으로 표현한 것

메서드의 이름x, 반환값x == 익명 함수(anonymous function)

  • 메서드를 생성하기 위해 클래스를 새로 만들거나 객체 생성 필요x
  • 메서드의 매개변수로 전달/결과로 반환 가능 (==변수)

1.2 람다식 작성하기

반환타입 메서드이름(매개변수 선언){
	문장들
}

⬇️

(매개변수 선언) -> {
	문장들
}

ex)

int max(int a, int b){
	return a > b ? a : b;
}

⬇️

(a, b) ->  { a > b ? a : b }
  • return 문을 식으로 대신, 끝에 세미콜론(;) 생략
  • 매개변수의 타입은 추론이 가능한 경우(대부분) 생략 가능
    • 매개변수가 여러개인 경우 일부의 타입만 생략은 불가능
      ex) (int a, b) -> a > b ? a : b (x)
  • 매개변수가 하나일 때 타입이 없는 경우 괄호 생략 가능
    (a) -> a * a -> a -> a * a
  • 괄호 {} 안의 문장이 하나인 경우 괄호 생략 가능(return 문인 경우 불가능)
    (a, b) -> { a > b ? a : b } -> (a, b) -> a > b ? a : b

ex)
매개변수가 없는 경우

int roll(){
	return (int)(Math.random()*6); 
}

-> () -> (int)(Math.random()*6)

1.3 함수형 인터페이스(Functional Interface)

👽 자바에서 모든 메서드는 클래스 내에 포함되어야 한다
=> 람다식은 익명 클래스의 객체와 동등하다

(int a, int b) -> a > b ? a : b
↔️

new Object() { 
    int max(int a, int b){
        return a > b ? a : b;
    }
}

👽 객체를 가리키는 참조변수가 있어야 호출이 가능하다
참조형이므로 그 타입은 클래스/인터페이스
타입 f = (int a, int b) -> a > b ? a : b
\\
MyFunction 인터페이스 내에 max()라는 메서드가 정의되어 있다면

interface MyFunction {
	public abstract int max(int a, int b);
}

이 인터페이스를 구현한 익명 클래스의 객체 생성은 아래와 같이 할 수 있다.
타입 f = (int a, int b) -> a > b ? a : b 에서 타입이 MyFunction, 람다 함수 대신 익명 클래스


MyFunction f = new MyFunction(){ 
                    public int max(int a, int b){
                        return a > b ? a : b;
                    }
                };
// 익명 객체의 메서드 호출 
int big = f.max(5, 3);

↔️

MyFunction f = (int a, int b) -> a > b ? a : b; // 익명 객체를 람다식으로 대체 
int big = f.max(5, 3); // 익명 객체의 메서드를호출

👽 함수형 인터페이스(functional interface)

람다식을 다루기 위한 인터페이스(ex. MyFunction)

  • 단점 : 오직 하나의 '추상 메서드'만 정의되어 있어야 함
    • static 메서드 / default 메서드의 개수에는 제약 x
  • @FunctionalInterface
    함수형 인터페이스를 올바르게 정의하였는지 확인해 줌

👽 메서드의 매개변수의 타입이 함수형 인터페이스인 경우

= 메서드를 호출할 때 람다식을 참조하는 참조변수 / 람다식 자체를 매개변수로 지정
ex)

@Functionallnterface
interface FunctionalInterface{ // 함수형 인터페이스 정의
    void method();
}
void call(method arg){
    arg.method();
}	    
...
FunctionalInterface f = () -> System.out.println("method()");
call(f); // call(() -> System.out.println("method()")); 람다식을 직접 매개변수로 지정도 가능

👽 람다식의 타입과 형변환

람다식의 타입 \neq 함수형 인터페이스의 타입
단지 함수형 인터페이스로 람다식을 참조할 수 있는 것

-> 대입연산자의 양변의 타입을 일치시키기 위해서는 형변환이 필요

& 람다식은 오직 함수형 인터페이스로만 형변환이 가능

👽 람다식에서 외부 변수 참조

익명클래스와 동일

  • 람다식 내에서 참조하는 지역변수는 상수로 간주(final이 붙지 않아도)
  • 외부 지역변수와 같은 이름의 람다식 매개변수는 허용되지 않음

1.4 java.util.function패키지

자주 쓰이는 형식의 메서드를 함수형 인터페이스로 정의해 둔 것

ex)

Predicate<String> isEmptyStr = s -> s.length () == 0;
String s = "";

if(isEmptyStr.test (s)) // if (s.length () == 0)
	System.out.println ("This is an empty String.");
  • 매개변수가 두 개인 함수형 인터페이스 : Bi~

  • 컬렉션 프레임원과 함수형 인터페이스

  • 기본형을 사용하는 함수형 인터페이스 : 보다 효율적
    ex) IntSupplier, IntConsumer, ..

1.5 Function의 합성과 Predicate의 결합

👽 Function의 합성

ex)

Function<String, Integer> f = (s) -> Integer.parselnt(s, 16);
Function<Integer, String> g = (i) -> Integer.toBinaryString(i); 
Function<String,String> h = f.andThen(g);

👽 Predicate의 결합

== 여러 조건식을 논리 연산자로 연결

논리연산자Predicate
&&(and)and()
||(or)or()
!(not)negate()

1.6 메서드 참조

종류람다메서드 참조
static메서드 참조(x) -> ClassName.method(x)ClassName::method
인스턴스메서드 참조(obj.x) -> obj.method(x)ClassName::method
특정 객체 인스턴스메서드 참조(x) -> obj.method(x)obj::method

ex)

Function<String, Integer〉 f = (String s) -> Integer.parselnt(s); 

⬇️

Function<String, Integer〉 f = (String s) -> Integer::parselnt(s); 

생성자의 메서드 참조

Supplier<MyClass> s = () -> new MyClass(); // 람다식 
Supplier<MyClass> s = MyClass::new; // 메서드참조

매개변수가 있는 생성자는 매개변수의 개수에 따라 알맞은 함수형 인터페이스 사용

Functior<Integer, MyClass> f = (i) ->newMyClass(i); //람다식 
Functior<Integer, MyClass> f2 = MyClass::new; // 메서드 참조

BiFunctior<Integer, String, MyClass〉 bf = (i, s) -> new MyClass(i, s); 
BiFunctior<Integer, String, MyClass> bf2 = MyClass::new; // 메서드 참조

2. 스트림(stream)

2.1 스트림이란?

데이터 소스를 추상화하고 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓은 것

데이터 소스 추상화-> 데이터 소스가 무엇이던 간어 같은 방식으로 다를 수 있게함

  • 데이터 소스를 변경하지 않고 읽기만 하는 것
    (필요하다면 결과를 컬렉션/배열에 담아서 반환할 수 있음)
    List<String> sortedList = strStream2.sorted().collect(Collectors.toList()); //정렬된 결과를 List에 담아서 반환
  • 일회용(==Iterator) -> 필요시 다시 생성
  • 내부 반복으로 처리 (=반복문을 메서드의 내부에 숨길 수 있다)
    for(String str : strList)
        System.out.println(str);
    ⬇️
    stream.forEach(System.out::println)

👽 스트림의 연산

연산(operation) : 스트림에 정의된 메서드 중에서 데이터 소스를 다루는 작업을 수행하는 것

여러개의 중간 연산을 연속해서 연결할 수 있음

중간 연산 : 연산 결과가 스트림인 연산

최종 연산 : 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능

ex)

  • 연산 순서 : 최종 연산 -> 중간 연산
    최종 연산이 수행될 때까지는 중간연산은 어떤 작업이 수행되어야 하는지를 지정해주는 역할 뿐임.
  • 기본형을 다루는 Stream : IntStream, LongStream, DoubleStream
  • 병렬 처리 : 스트림에 parallel() 호출(이 메서드 취소시에는 sequential())
    내부적으로 fork&join 프레임웍으로 병렬 수행

2.2 스트림 만들기

Collection

Stream<T> Collection.stream()

ex)

List<Integer> list = Arrays.asList(1,2,3,4,5); // 가변인자
Stream<Integer〉 intStream = list.stream(); // list를 소스로 하는 컬렉션 생성

배열(Arrays)

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)

기본형 배열을 소스로 하는 경우 : IntStream.of, LongStream...

  • 연속된 정수를 스트림으로 생성﹒반환
    • IntStream IntStream.range(int begin, int end)
    • IntStream IntStream.rangeClosed(int begin, int end)
  • 난수(랜덤값)

    • 무한 스트림(infinite stream) : 반환하는 스트림의 크기가 정해져 있지x
      -> limit()로 크기 제한

      IntStream intStream = newRandom().ints();//무한 스트
      intStream.limit(5).forEach(System.out::println); // 5개의 요소만 출력
    • 유한 스트림

      IntStream ints(long streamSize) 
      LongStream longs(long streamSize)
      DoubleStream doubles(long streamSize) 
      
      IntStream intStream=newRandom().ints(5);//크기가 5인 난수스트림을 반환
    • 지정된 범위의 난수를 발생

      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)
  • 람다식 : iterate(), generate()
    람다식을 매개변수로 받아서 이 람다식에 의해 계산되는 값들을 요소로 하는 무한 스트림 생성

    • iterate() : static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
    • generate() : static <T> Stream<T> generate(Supplier<T> s)
  • 파일

    • Stream<Path> Files.list(Path dir) : 디렉토리(dir)에 있는 파일의 목록을 소스로 하는 스트림 반환
  • 빈 스트림 : null을 반환하는 것 보다 나음
    Stream.empty();

  • 두 스트림 연결 : Stream의 static 메서드 concat()

    String[] strl = {"123", "456", "789"}; 
    String[] str2 = {"ABC", "abc", "DEF"};
    
    Stream<String> strsl = Stream.of(strl);
    Stream<String> strs2 = Stream.of(str2);
    Stream<String> strs3 = Stream.concat (strsl, strs2);

2.3 스트림의 중간연산

  • 자르기 : skip(), limit()
  • 요소 걸러내기 : filter(), distinct()
  • 정렬 : sorted()
  • 원하는 필드 추출 / 특정 형태로 변환 : map()
    • map() 의 연산 결과를 기본형 스트림으로 변환 : mapTpInt(), mapToLong(), mapToDouble()
  • 조회 : peek()

ex)

fileStream.map (File: : getName) // Stream<File> ->Stream<String)
	.filter(s -> s. indexof ('.')! =-1) //확장자가 없는 것은 제외
	.peek(s -> system.out.printf("flename = %s%n", s)) // 파일명 출력
	.map(s -> s.substring (s.indexof('.')+1)) // 확장자만 추출
	.peek (s->system.out.printf ("extension=%s%n", s)) // 확장자를 출력한다.
	.forEach (System.out::println);

\\
\\

👽 flatMap() : Stream<T[ ]>를 Stream<T>로 변환

ex)

Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);
⬇️
Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream);

2.4 - (1) Optional<T>

Optional<T> : 지네릭 클래스, T 타입의 객체를 감싸는 래퍼 클래스

  • 모든 타입의 참조변수를 담을 수 있음
  • 최종 연산의 결과 타입이 Optional 인 경우(Optional 객체에 담아서 반환)
    : 반환된 결과가 null인지 매번 if 문으로 체크하는 대신 Optional에 정의된 메서드를 통해 간단히 처리 가능(isNull(), nonNull(), requireNonNull())

👽 Optional 객체 생성 : of(), ofNullable()

  • of() vs ofNullable()
    • ofNullable() : 참조변수의 값이 null일 가능성이 있는 경우
    • of() : null이면 NullPointerException 발생
  • 기본값으로 초기화 : empty() (null도 가능하지만 비추천)

👽 Optional 객체의 값 가져오기 : get()

  • 값이 null인 경우 : NoSuchElementException 발생
    -> 대체값 지정 :orElse() / 대체할 값을 반환하는 람다식 지정 : orElseGet() / 예외 발생 : orElseThrow()
  • filter(), map(), flatMap() 사용 가능(==Stream)

  • isPresent() : Optional객체의 값이 null이면 false, 아니면 true를 반환

  • ifPresent(Consumer<T) block) : 값이 있으면 주어진 람다식을 실행, 없으면 아무 일도 하지 않음

    • findAny(), findFirst()와 같이 optional<T>를 반환하는 최종 연산과 잘 어울림

👽 기본형 Optional

: OptionalInt, OptionalLong, OptionalDouble

  • 메서드 명 주의

2.5 - 2.6 스트림의 최종연산

최종 연산후에는 스트 림이 닫히게 되고 더 이상 사용할 수 없음

  • forEach : void forEach(Consumer<? super T> action)
  • 조건 검사 : allMatch(),anyMatch(),noneMatch(),findFirst(),findAny()
  • 통계 : count()sum()average()max(), min()
  • 리듀싱 : reduce()
    • 스트림의 요소를 줄여나가면서 연산을 수행 = 처음 두 요소를 가지고 연산한 결과를 가지고 그 다음 요소와 연산 -> 스트림의 요소를 하나씩 소모

👽 collect() : 스트림의 요소를 수집하는 최종 연산

수집 방법 : collector (매개변수로 collector 필요)

  • Collector 인터페이스, 컬렉터는 이 인터페이스를 구현해야 한다.
  • Collectors 클래스, static메서드로 미리 작성된 컬렉터를 제공

2.7 Collector 구현하기
2.8 스트림의 변환

0개의 댓글