Chapter 13 내부클래스, 람다식, 스트림

Ruinak·2021년 6월 14일
1

Java

목록 보기
13/15
post-thumbnail

13-1 내부 클래식

내부 클래스 정의와 유형

  • 내부 클래스(inner class)는 말 그대로 '클래스 내부에 선언한 클래스'임

  • 내부에 클래스를 선언하는 이유는 대개 이 클래스와 외부 클래스가 밀접한 관련이 있기 때문임

  • 다른 클래스와 협력할 일이 없는 경우에 내부 클래스로 선언해서 사용함

    내부 클래스를 간단하게 표현

  • 내부 클래스는 선언하는 위치나 예약어에 따라 크게 네 가지 유형으로 나눌 수 있음
    1) 인스턴스 내부 클래스
    2) 정적(static) 내부 클래스
    3) 지역(local) 내부 클래스

  • 위 세 유형은 클래스 내부에 선언하는 변수의 유형(인스턴스 변수, 정적 변수, 지역 변수)과 유사함
    4) 익명(anonymous) 내부 클래스 : 클래스 이름 없이 선언하고 바로 생성하여 사용할 수 있음

    변수 유형과 내부 클래스 유형 비교

  • 내부 클래스를 보면 가장 바깥에 선언한 ABC 클래스를 외부 클래스, ABC 내부에 선언한 클래스는 내부 클래스 또는 중첩된 클래스라고도 함

  • 내부 클래스는 멤버 변수처럼 클래스 내부에 정의하는 인스턴스 내부 클래스, static 키워드를 사용하는 정적 내부 클래스, 그리고 메서드 내부에 정의하는 지역 내부 클래스로 나눌 수 있음

  • 내부 클래스는 유형에 따라 만드는 방법이 다를뿐더러 클래스 내부에 선언할 수 있는 변수 유형과 사용할 수 있는 외부 클래스 변수 유형도 다름

인스턴스 내부 클래스

  • 인스턴스 내부 클래스(instance inner class)는 인스턴스 변수를 선언할 때와 같은 위치에 선언하며, 외부 클래스 내부에서만 생성하여 사용하는 객체를 선언할 때 씀
    - 예를 들어 어떤 클래스 내에 여러 변수가 있고 이들 변수 중 일부를 모아 클래스로 표현 가능함
    - 이 클래스를 다른 외부 클래스에서 사용할 일이 없는 경우 내부 인스턴스 클래스로 정의함
  • 인스턴스 내부 클래스는 외부 클래스 생성 후 생성됨
  • 외부 클래스를 먼저 생성하지 않고 인스턴스 내부 클래스를 사용할 수는 없음

예제 13-1 인스턴스 내부 클래스 예제

  • OutClass 외부 클래스를 만들고 내부 클래스로 InClass를 선언함
  • 외부 클래스를 먼저 생성해야 내부 클래스를 사용할 수 있음

인스턴스 내부 클래스에서 사용하는 변수와 메서드

  • 외부 클래스 안에 private 예약어로 변수 num과 sNum을 선언함

  • 두 변수는 private로 선언했지만 외부 클래스 안에 있기 때문에 내부 클래스에서도 사용할 수 있음

  • 인스턴스 내부 클래스는 외부 클래스를 생성한 이후에 사용해야 하기 때문에 정적 변수 부분에서는 오류가 남

  • 클래스의 생성과 상관없이 사용할 수 있는 정적 변수는 인스턴스 내부 클래스에서 선언할 수 없음

    정리

인스턴스 내부 클래스는 외부 클래스가 먼저 생성되어야 사용할 수 있음

인스턴스 내부 클래스의 메서드는 외부 클래스의 메서드가 호출될 때 사용할 수 있음

다른 클래스에서 인스턴스 내부 클래스 생성하기

  • 내부 클래스를 생성하는 이유는 그 클래스를 감싸고 있는 외부 클래스에서만 사용하기 위해서 임
  • 내부 클래스를 그 밖의 다른 클래스에서 생성해서 사용하는 것은 맞지 않음
  • 외부 클래스 외의 다른 클래스에서 private가 아닌 내부 클래스를 생성하는 것이 문법적으로 가능하기는 함
  • 일반적인 인스턴스 내부 클래스 사용 방법은 InnerTest.java 예제의 36행과 같음
  • OutClass 클래스를 생성하고 인스턴스 변수를 이용하여 outClass.usingClass( ); 문장으로 내부 클래스 기능을 호출하는 것임
  • 이때 내부 클래스를 private로 선언하지 않았다면 외부 클래스가 아닌 다른 클래스에서도 다음처럼 내부 클래스를 생성할 수 있음
    - 먼저 OutClass를 만들고, 생성할 참조변수를 사용하여 내부 클래스를 생성함
  • 내부 클래스를 private로 선언했다면 다른 클래스에서 InClass를 사용할 수 없음
    - 어떤 클래스의 내부에서만 사용할 목적이라면 내부 클래스를 private로 선언함

정적 내부 클래스

  • 인스턴스 내부 클래스는 외부 클래스가 먼저 생성되어야 생성할 수 있기 때문에 정적 변수나 정적 메서드는 사용할 수 없다고 했음
  • 내부 클래스가 외부 클래스 생성과 무관하게 사용할 수 있어야 하고 정적 변수도 사용할 수 있어야 한다면 정적 내부 클래스(static inner class)를 사용하면 됨
  • 정적 내부 클래스는 인스턴스 내부 클래스처럼 외부 클래스의 멤버 변수와 같은 위치에 정의하여 static 예약어를 함께 사용함

예제 13-2 정적 내부 클래스 예제

  • 정적 메서드에서는 인스턴스 변수를 사용할 수 없음

  • 정적 내부 클래스에서도 외부 클래스의 인스턴스 변수는 사용할 수 없음

    변수 유형에 따른 사용 가능 여부 정리

  • 예제와 표에서 알 수 있듯이 정적 내부 클레스에서 사용하는 메서드가 정적 메서드인 경우에는 외부 클래스와 정적 내부 클래스에 선언된 변수 중 정적 변수만 사용할 수 있음

다른 클래스에서 정적 내부 클래스 사용하기

  • 정적 내부 클래스는 외부 클래스를 생성하지 않고도 내부 클래스 자료형으로 바로 선언하여 생성할 수 있음
  • 정적 내부 클래스에 선언한 메서드(정적 메서드 포함)나 변수는 private가 아닌 경우에 다른 클래스에서도 바로 사용할 수 있음
  • 내부 클래스를 만들고 외부 클래스와 무관하게 다른 클래스에서도 사용하려면 정적 내부 클래스로 생성하면 됨
  • 정적 내부 클래스를 private로 선언했다면 이것 역시 다른 클래스에서 사용할 수 없음

지역 내부 클래스

  • 지역 내부 클래스는 지역 변수처럼 메서드 내부에 클래스를 정의하여 사용하는 것을 말함
  • 지역 내부 클래스는 메서드 안에서만 사용할 수 있음

예제 13-3 지역 내부 클래스 예제

  • getRunnable( ) 메서드의 반환형은 Runnable임
  • getRunnable( ) 메서드에서는 Runnable 자료형의 객체를 생성하여 반환해야 하므로, 메서드 내부에 클래스를 하나 정의함
    - MyRunnable로 Runnable 인터페이스를 구현한 클래스임
  • 메서드 안에 정의한 MyRunnable 클래스가 바로 지역 내부 클래스임
  • 메서드에서 Runnable 자료형을 반환해야 하므로 return new MyRunnable( ); 문장으로 MyRunnable 클래스를 생성한 후 반환함

지역 내부 클래스에서 지역 변수의 유효성

  • 지역변수는 메서드가 호출될 때 스택 메모리에 생성되고 메서드의 수행이 끝나면 사라짐
  • 지역 내부 클래스에 포함된 getRunnable( ) 메서드의 매개변수 i와 메서드 내부에 선언한 변수 num은 지역 변수임
  • run( ) 메서드는 getRunnable( ) 메서드의 지역 변수 i와 num을 사용하는데, 지역 내부 클래스를 가지고 있는 getRunnable( ) 메서드 호출이 끝난 후에도 run( ) 메서드가 정상적으로 호출됨
    - getRunnable( ) 메서드 호출이 끝나고 스택 메모리에서 지워진 변수를 이후에 또 참조할 수 있다는 뜻임
    - 지역 내부 클래스에서 사용하는 지역 변수는 상수로 처리됨

정리

지역 내부 클래스에서 사용하는 메서드의 지역 변수는 모두 상수로 바뀜

익명 내부 클래스

  • 지금까지 만든 클래스는 모두 이름이 있었음
  • 익명 클래스 : 클래스 이름을 사용하지 않는 클래스
  • 지역 내부 클래스 MyRunnable을 선언했지만, 이 클래스 이름을 사용하는 곳은 맨 마지막에 클래스를 생성하여 반환할 때뿐임

예제 13-4 익명 내부 클래스 예제

  • 익명 내부 클래스는 단 하나의 인터페이스 또는 단 하나의 추상 클래스를 바로 생성할 수 있음
  • Runnable 인터페이스를 생성할 수 있으려면 인터페이스 몸체가 필요함( 앞에서 인터페이스는 인스턴스로 생성할 수 없다고 했음 )
  • 9 ~ 14행에 Runnable 인터페이스에서 반드시 구현해야 하는 run( ) 메서드가 포함되어 있음
  • 익명 내부 클래스가 끝났다는 것은 마지막에 세미콜론( ; )을 사용해서 알려줌
  • 익명 내부 클래스는 18 ~ 23행처럼 인터페이스나 추상 클래스 자료형으로 변수를 선언한 후 익명 내부 클래스를 생성해 대입할 수도 있음
  • 추상 메서드나 인터페이스를 구현한 후 세미콜론으로 클래스 끝을 나타냄
  • 28 ~ 31행은 익명 클래스를 사용하는 코드이며, 방법은 지역 내부 클래스와 동일함
  • Runnable 인터페이스 자료형으로 변수를 선언하고, 인터페이스의 익명 내부 클래스가 구현된 메서드를 호출하면 인스턴스를 반환하고, runnable.run( ) 또는 out.runner.run( )으로 인터페이스를 호출할 수 있음

정리

  • 익명 내부 클래스는 변수에 직접 대입하는 경우도 있고 메서드 내부에서 인터페이스나 추상 클래스를 구현하는 경우도 있음
  • 이때 사용하는 지역 변수는 상수화되므로 메서드 호출이 끝난 후에도 사용할 수 있음

13-2 람다식

함수형 프로그래밍과 람다식

  • 자바는 객체를 기반으로 프로그램을 구현함
  • 만약 어떤 기능이 필요하다면 클래스를 먼저 만들고, 클래스 안에 기능을 구현한 메서드를 만든 후 그 메서드를 호출해야 함
    - 클래스가 없다면 메서드를 사용할 수 없음
  • 함수형 프로그래밍(Fucntional Programming : FP) : 함수의 구현과 호출만으로 프로그램을 만들 수 있는 프로그래밍 방식
  • 자바에서 제공하는 함수형 프로그래밍 방식을 '람다식(Lambda expression)'이라고 함

람다식 구현하기

  • 람다식을 구현하는 방법은 지금까지 배운 프로그래밍 방식과 조금 다름

  • 람다식은 간단히 설명하면 함수 이름이 없는 익명 함수를 만드는 것임

    람다식 문법

  • 메서드에서 사용하는 매개변수가 있고, 이 메서드가 매개변수를 사용하여 실행할 구현 내용, 즉 메서드의 구현부를 { } 내부에 작성함

  • 메서드 이름 add와 반환형 int를 없애고 -> 기호를 사용하여 구현함

  • 람다식의 의미를 살펴보면 두 입력 매개변수 (x, y)를 사용하여 {return x + y;} 문장을 실행해 반환하라는 의미임

람다식 문법 살펴보기

매개변수 자료형과 괄호 생략하기

  • 람다식 문법에서는 매개변수 자료형을 생략할 수 있음
  • 매개변수가 하나인 경우에는 괄호를 생략할 수 있음
  • 매개변수가 두개인 경우에는 괄호를 생략할 수 없음

중괄호 생략하기

  • 중괄호 안의 구현 부분이 한 문장인 경우 중괄호를 생략할 수 있음
  • 중괄호 안의 구현 부분이 한 문장이더라도 return문은 중괄호를 생략할 수 없음

return 생략하기

  • 중괄호 안의 구현 부분이 return문 하나라면 중괄호와 return을 모두 생략하고 식만 씀

람다식 사용하기

  • 람다식을 구현하기 위해서는 먼저 인터페이스를 만들고, 인터페이스에 람다식으로 구현할 메서드를 선언함(함수형 인터페이스)

예제 13-5 함수형 인터페이스 선언하기

  • getMax( ) 추상 메서드는 입력받은 두 수중 더 큰 수를 반환하는 기능을 구현할 것임
    - 이를 람다식으로 구현하면 아래의 왼쪽 코드이고, 더 간단하게 쓰면 오른쪽과 같음

예제 13-6 람다식 구현과 호출

  • 구현함 람다식은 MyNumber 인터페이스의 getMax( ) 메서드임
  • MyNumber 인터페이스형 변수(max)를 선언하고 변수에 람다식을 대입함

함수형 인터페이스

  • 람다식은 메서드 이름이 없고 메서드를 실행하는데 필요한 매개변수와 매개변수를 활용한 실행코드를 구현하는 것임
  • 함수형 언어에서는 함수만 따로 호출할 수 있지만, 자바에서는 참조 변수 없이 메서드를 호출할 수 없으므로, 람다식을 구현하기 위해 함수형 인터페이스를 만들고, 인터페이스에 람다식으로 구현할 메서드를 선언하는 것임
  • 람다식은 하나의 메서드를 구현하여 인터페이스형 변수에 대입하므로 인터페이스가 두 개 이상의 메서드를 가져서는 안됨
  • 람다식은 이름이 없는 익명 함수를 구현하기 때문에 인터페이스에 메서드가 여러 개 있다면 어떤 메서드를 구현한 것인지 모호해짐
  • 람다식은 오직 하나의 메서드만 선언한 인터페이스를 구현할 수 있음

@FunctionalInterface 애노테이션

  • 프로그래밍을 하다 보면 람다식으로 구현한 인터페이스에 실수로 다른 메서드를 추가할 수도 있음
  • 실수를 막기 위해 @FunctionalInterface 애노테이션을 사용함
  • @FunctionalInterface를 사용하면 함수형 인터페이스라는 의미이고, 메서드를 하나 이상(두개) 선언하면 오류가 남
  • 이 애노테이션은 반드시 써야하는 것은 아니며, 함수형 인터페이스라는 것을 명시적으로 표현할 수 있으므로 나중에 발생할 오류를 방지할 수 있음

객체 지향 프로그래밍 방식과 람다식 비교

  • 람다식을 사용하면 기존 방식보다 간결한 코드를 구현할 수 있음
  • 메서드의 구현부를 클래스에 만들고, 이를 다시 인스턴스로 생성하고 호출하는 코드가 줄어들기 때문임

예제 13-7 인터페이스 구현하기

  • String Concat 인터페이스는 문자열 두개를 매개변수로 입력 받아 두 문자열을 연결하여 출력하는 makeString( ) 메서드를 가짐
  • makeString( ) 메서드는 두 문자열을 쉼표( , )로 연결하여 출력하도록 구현할 것임
  • 예로 s1 = Hello이고 se = World라면 Hello, World를 출력함

클래스에서 인터페이스 구현하기

예제 13-8 추상 메서드 구현하기

  • StringConcat 인터페이스는 추상 메서드 makeString( )을 가지고 있으므로 StringConCatImpl 클래스에서 재정의 함

예제 13-9 메서드 테스트하기

  • makeString( ) 메서드를 수행하려면 STring Concat 인터페이스를 구현한 StringConCatImpl 클래스를 인스턴스로 생성해야 함

람다식으로 인터페이스 구현하기

예제 13-10 람다식으로 인터페이스 구현하기

  • 두 매개변수 s, v를 사용해 연결된 문자열이 출력되도록 함

  • 위 구현부분을 StringConcat 인터페이스 자료형인 concat2 변수에 대입하고, 이 변수를 사용하여 makeString( ) 메서드를 호출함

  • 두 구현 방법을 비교해 보면, 람다식으로 구현하는 경우에 코드가 더 간결해지는 것을 알 수 있음

  • 람다식으로 구현하려면 메서드를 하나만 포함하는 함수형 인터페이스만 가능하다는 점 강조함

익명 객체를 생성하는 람다식

  • 자바는 객체 지향 언어인데, 람다식은 객체 없이 인터페이스의 구현만으로 메서드를 호출할 수 있음
  • 익명 내부 클래스는 클래스 이름 없이 인터페이스 자료형 변수에 바로 메서드 구현부를 생성하여 대입할 수 있음
  • 람다식으로 메서드를 구현해서 호출하면 컴퓨터 내부에서는 아래처럼 익명 클래스가 생성되고 이를 통해 익명 객체가 생성되는 것임

람다식에서 사용하는 지역 변수

  • i는 main( ) 함수의 지역 변수임
  • 람다식 내부에서 변수 i값을 변경하면 오류가 발생하지만, 변수 값을 변경하지 않고 출력만하면 오류가 발생하지 않음
  • 지역 변수는 메서드 호출이 끝나면 메모리에서 사라지기 때문에 익명 내부 클래스에서 사용하는 경우에는 지역 변수가 상수로 변함
  • 람다식 역시 익명 내부 클래스가 생성되므로 외부 메서드의 지역 변수를 사용하면 변수는 final 변수, 즉 상수가 됨
  • 따라서 이 변수를 변경하면 상수를 변경하는 것이기에 오류가 발생하는 것임

함수를 변수처럼 사용하는 람다식

  • 람다식을 이용하면 구현된 함수를 변수처럼 사용할 수 있음
  • 프로그램에서 변수를 사용하는 경우
  • 람다식으로 구현된 메서드도 변수에 대입해서 사용할 수 있고, 매개변수로 전달하고 반환할 수 있음

인터페이스형 변수에 람다식 대입하기

  • 함수형 인터페이스 PrintString이 있고, 여기에 메서드를 하나 선언함
  • 위 메서드를 구현한 람다식
  • 이를 실행하기 위해 인터페이스형 변수를 선언하고 여기에 람다식 구현부를 대입함
  • 람다식이 대입된 변수 lambdaStr를 사용하여 람다식 구현부를 호출할 수 있음

매개변수로 전달하는 람다식

  • 람다식을 변수에 대입하면 이를 매개변수로 전달할 수 있음

  • 전달되는 매개변수의 자료형은 인터페이스형임

    예제 13-11 매개변수로 전달하는 람다식

  • TestLambda 클래스에 정적 메서드 showMyString( )을 메서드를 호출할 떄 구현된 람다식을 대입한 labmdaStr 변수를 매개변수로 전달함을 하나 추가함

  • showMyString( )을 메서드를 호출할 때 구현된 람다식을 대입한 labmdaStr 변수를 매개변수로 전달함

  • 매개변수의 자료형은 인터페이스형인 PrintString이고 변수는 p임

  • p.showString("hello lamda_2");라고 호출하면 람다식의 구현부인 출력문이 호출됨

반환 값으로 쓰이는 람다식

  • 아래와 같이 메서드의 반환형을 람다식의 인터페이스형으로 선언하면 구현한 람다식을 반환할 수 있음

  • 위 람다식은 매개변수로 전달된 문자열에 "world"를 더하여 반환하도록 구현함

  • 반환형은 인터페이스형인 PrintString임

    예제 13-12 반환 값으로 쓰이는 람다식

  • 람다식은 함수의 구현부를 변수에 대입하고, 매개변수로 전달하고, 함수의 반환 값으로 사용할 수 있음
    - 마치 변수처럼 사용할 수 있는 것
    - 함수형 프로그래밍의 특징 중 하나

13-3 스트림

스트림이란?

  • 자료가 모여 있는 배열이나 컬렉션 또는 특정 범위 안에 있는 일련의 숫자를 처리하는 기능이 미리 구현되어 있따면 프로그램의 코드가 훨씬 간결해지고 일관성있게 다룰 수 있을 것임
    - 예로 배열 요소를 특정 기준에 따라 정렬(sorting)하거나, 요소 중 특정 값은 제외하고 출력(filter)하는 기능 등
  • 여러 자료흐이 처리에 대한 기능을 구현해 놓은 클래스가 스트림(stream)임
  • 스트림을 활용하면 배열, 컬렉션 등의 자료를 일관성 있께 처리할 수 있음
  • 자료에 따라 기능을 각각 새로 구현하는 것이 아니라 처리해야 하는 자료가 무엇인지와 상관없이 같은 방식으로 메서드를 호출할 수 있기 때문임
    - 다른 말로는 자료를 추상화 했다고 표현함
  • 아래 코드는 정수 5개를 요소로 가진 배열이고 이를 모두 출력하는 출력문임
  • 이 배열에 대한 스트림을 생성하여 출력하면 아래와 같음
  • 스트림을 생성하고 미리 구현되어 있는 forEach( ) 메서드(최종 연산)를 사용하여 배열의 요소를 하나씩 꺼내어 출력할 수 있음

스트림 연산

  • 스트림 연산의 종류에는 크게 중간 연산과 최종 연산 두 가지가 있음
  • 중간 연산은 자료를 거르거나 변경하여 또 다른 자료를 내부적으로 생성함
  • 최종 연산은 생성된 내부 자료를 소모해 가면서 연산을 수행함
    - 따라서 최종연산은 마지막에 한 번만 호출되고 최종 연산이 호출되어야 중간 연산의 결과가 만들어짐

중간 연산 - filter( ), map( )

filter( )

  • filter( )는 조건을 넣고 그 조건에 맞는 참인 경우만 추출하는 경우에 사용함
  • 아래는 문자열 배열이 있을 때 문자열의 길이가 5 이상인 경우만 출력하는 코드

map ( )

  • map( )은 클래스가 가진 자료 중 이름만 출력하는 경우에 사용함

  • 예로 고객 클래스가 있다면 고객 이름만 가져와서 출력할 수 있음

  • mpa( )은 요소들을 순회하여 다른 형식으로 변환하기도 함

  • filter( )와 map( ) 둘 다 함수를 수행하면서 해당 조건이나 함수에 맞는 결과를 추출해 내는 중간 역할을 함

  • 최종 연산으로 중간 연산 결과를 출력함

최종 연산 - forEach( ), count( ), sum( ), reduce( )

  • 최종 연산은 스트림의 자료를 소모하면서 연산을 수행하기 때문에 최종 연산이 수행되고 나면 해당 스트림은 더 이상 사용할 수 없음
  • 최종 연산은 결과를 만드는데 주로 사용함
  • forEach( )는 앞에서도 보았듯이 요소를 하나씩 꺼내는 기능을 함
  • 통계용으로 사용되는 sum( ), count( )는 배열 요소의 합계를 구한다든가 개수를 출력하는 등의 연산을 수행함

스트림 생성하고 사용하기

정수배열에 스트림 생성하고 사용하기

예제 13-13 정수 배열에서 스트림 활용하기

  • 출력 결과를 보면 배열의 합과 개수가 계산되는 것을 알 수 있음
  • count( ) 메서드의 반환 값이 long형이므로 int형으로 형 변환을 함
  • count( ), sum( ) 이외에 max( ), min( ), average( ) 등 통계 연산을 위한 메서드도 제공함
  • 처음 보는 형태가 등장함. 그냥 이런게 있구나 하고 넘어가야겠음

Collection에서 스트림 생성하고 사용하기

  • Collection 인터페이스의 스트림 메서드

  • Collection 인터페이스를 구현한 클래스 중 가장 많이 사용하는 ArrayList에 스트림을 생성하고 활용해 보겠음

  • 아래와 같이 문자열을 요소로 가지는 ArrayList가 있음

  • Collection에서 stream( ) 메서드를 사용하면 위 클래스는 제네릭형을 사용해 아래와 같이 자료형을 명시할 수 있음

  • 이렇게 생성된 스트림은 내부적으로 ArrayList의 모든 요소를 가지고 있음

  • 모든 요소를 하나씩 가져와서 처리할 때 스트림의 forEach 메서드를 활용함

  • forEach( ) 메서드는 내부적으로 반복문이 수행됨

  • forEach( ) 괄호 안에 구현되는 람다식의 의미는 forEach( ) 메서드가 수행되면 요소가 하나씩 차례로 변수 s에 대입되고 이를 매개변수로 받아 출력문이 호출됨

  • ArrayList에 저장된 이름을 정렬하여 결과를 호출해보자

  • 앞에서 stream 변수에 스트림을 생성했지만 forEach( ) 메서드가 수행되면서 자료가 소모됨
    - 따라서 스트림을 새로 생성해야 함

  • 중간 연산으로 정렬을 위한 sorted( ) 메서드를 호출하고, 최종 연산으로 출력을 위해 forEach( ) 메서드를 사용함

  • sorted( ) 메서드를 사용하려면 정렬 방식에 대한 정의가 필요함
    - 따라서 사용하는 자료 클래스가 Comparable 인터페이스를 구현해야 함
    - 만약 구현되어 있지 않다면 sorted( ) 메서드의 매개 변수로 Comparator 인터페이를 구현한 클래스를 지정할 수 있음

  • ArrayList 이외에 다른 Collection의 자료도 같은 방식으로 정렬하고 출력할 수 있음

예제 13-14 ArrayList에서 스트림 활용하기

스트림의 특징

자료의 대상과 관계없이 동일한 연산을 수행한다

  • 배열이나 컬렉션에 저장된 자료를 가지고 수행할 수 있는 연산은 여러가지가 있음
  • 배열에 저장된 요소 값을 출력한다던지, 조건에 따라 자료를 추출하거나, 자료가 숫자일 때 합계·평균 등을 구할 수도 있음
  • 스트림은 컬렉션의 여러 자료 구조에 대해 이러한 작업을 일관성 있게 처리할 수 있는 메서드를 제공함

한 번 생성하고 사용한 스트림은 재사용할 수 없다

  • 어떤 자료에 대한 스트림을 생성하고 이 스트림에 메서드를 호출하여 연산을 수행했다면 해당 스트림을 다시 다른 연산에 사용할 수 없음
  • 예로 스트림을 생성하여 배열에 있는 요소를 출력하기 위해 각 요소들을 하나씩 순회하면서 출력에 사용하는데, 이때 요소들이 '소모된다'고 이야기함
  • 소모된 요소는 재사용할 수 없으며, 만약 다른 기능을 호출하려면 스트림을 새로 생성해야함

스트림의 연산은 기존 자료를 변경하지 않는다

  • 스트림을 생성하여 정렬한다거나 합을 구하는 등의 여러 연산을 수행한다고 해서 기존 배열이나 컬렉션이 변경되지는 않음
  • 스트림 연산을 위해 사용하는 메모리 공간이 별도로 존재하므로, 스트림의 여러 메서드를 호출하더라도 기존 자료에는 영향을 미치지 않음

스트림의 연산은 중간 연산과 최종 연산이 있다

  • 스트림에서 사용하는 메서드는 크게 중간 연산과 최종 연산 두 가지로 나뉨
  • 스트림에 중간 연산은 여러 개가 적용될 수도 있고, 최종 연산은 맨 마지막에 한 번 적용 됨
  • 만약 중간 연산이 여러 개 호출되었떠라도 최종 연산이 호출되어야 스트림의 중간 연산이 모두 적용됨
  • 예로 자료를 정렬하거나 검색하는 중간 연산이 호출되어도 최종 연산이 호출되지 않으면 정렬이나 검색한 결과를 가져올 수 없음
    - 이를 '지연 연산(lazy evaluation)'이라고 함

프로그래머가 지정하는 reduce( ) 연산

  • 이제까지 우리가 사용한 연산은 기능이 미리 정해져 있었음
  • reduce( ) 연산은 내부적으로 스트림의 요소를 하나씩 소모하면서 프로그래머가 직접 지정한 기능을 수행함

JDK에서 제공하는 reduce( ) 메서드의 정의

  • 첫 번째 매개변수 T identify는 초깃값을 의미함

  • 두 번째 매개변수 BinaryOperator&tT> accumulator는 수행해야 할 기능임

  • BinaryOperator 인터페이스는 두 매개변수로 람다식을 구현하며 이 람다식이 각 요소가 수행해야 할 기능이 됨
    - 이때 BinaryOperator 인터페이스를 구현한 람다식을 직접 써도 되고, 람다식이 길면 인터페이스를 구현한 클래스를 생성하여 대입해도 됨

    • 또한 BinaryOperator는 함수형 인터페이스로 apply( ) 메서드를 반드시 구현해야 함
  • apply( ) 메서드는 두 개의 매개변수와 한 개의 반환 값을 가지는데, 세 개 모두 같은 자료형임

  • reduce( ) 메서드가 호출될 때 BinaryOperator의 apply( ) 메서드가 호출됨

  • reduce( ) 메서드를 사용해 모든 요소의 합을 구할 때, 두 번째 매개변수에 람다식을 직접 쓰는 경우는 아래와 같음

  • 초깃값은 0이고 스트림 요소가 매개변수로 전달되면서 합을 구함

  • 내부적으로는 반복문이 호출되면서 람다식에 해당하는 부분이 리스트 요소만큼 호출되는 것임
    - 따라서 reduce( ) 메서드에 어떤 람다식이 전달되느냐에 따라 다양한 연산을 수행할 수 있음

  • reduce( ) 는 처음부터 마지막까지 모든 요소를 소모하면서 람다식을 반복해서 수행하므로 최종 연산임

예제 13-15 reduce( ) 사용하기

  • 17 ~ 20행에서는 reduce( ) 메서드 내에 직접 람다식을 구현함
  • 람다식을 살펴보면 문자열을 비교하여 바이트 수가 더 긴 문자열을 반환함
  • 내부적으로 이 람다식 부분이 요소 개수만큼 반복해서 호출되고 긴 문자열을 반환함
  • 구현하는 람다식이 너무 긴 경우에는 6 ~ 12행과 같이 직접 BinaryOperator 인터페이스를 구현한 reduce( ) 메서드에 해당 클래스로 생성한 인스턴스를 매개변수로 전달하면 여기에 구현된 apply( ) 메서드가 자동으로 호출됨
  • 람다식으로 구현된 부분도 익명 클래스의 인스턴스가 생성되는 것이므로 내부적으로는 동일한 구조라 할 수 있음

스트림을 활용하여 여행객의 여행 비용 계산하기

예제 13-16 스트림 활용하기(1)

  • 고객 클래스는 이름, 나이, 비용을 멤버 변수로 가지며, 멤버 변수에 대한 get( ) 메서드만 제공함

예제 13-16 스트림 활용하기(2)

  • 고객 명단을 출력하는 코드를 살펴보면 19행에서는 map( ) 메서드를 사용하여 고객의 이름을 가져오고 forEach( ) 메서드로 이름을 출력하고 있음

  • 21행에서는 각 고객이 지불한 비용을 가져와서 mapToInt( ) 메서드로 그 값을 정수로 변환한 후 sum( )으로 합을 구함

  • 최종 연산 sum( )의 반환 값이 int형이므로 int형 변수 total 변수에 결과를 대입함

  • 25행의 20세 이상 고객을 가져와서 이름을 정렬하는 부분은 3개의 중간 연산을 사용함
    - filter( )를 사용하여 20세 이상만 추출한 후 map( )으로 이들의 이름을 가져오고, sorted( )를 사용하여 이름을 정렬함
    - 윗 부분까지가 중간 연산이고 최종 연산 forEach( )를 활용하여 출력함

  • 데이터베이스의 쿼리문이 스트림과 비슷한 느낌을 줌

  • 스트림은 많은 데이터 속에서 우리가 원하는 데이터를 추출하고 적용하고 계산하고 출력하는 등의 기능을 제공함

profile
Nil Desperandum <절대 절망하지 마라>

0개의 댓글