스트림 (1)

 

 

❑ 람다식(Lambda)

 

 

➤ 람다식이란?

 

자바는 객체 지향 프로그래밍 언어이다.

객체 지향은 객체를 만들어야지만 함수(메서드)를 사용할 수 있지만

함수형 프로그래밍은 함수의 구현과 호출만으로 프로그램을 만들 수 있다.

자바가 처음 나온 90년대에는 객체 지향 언어가 각광받았지만 지금은 함수형 프로그래밍이 다시 각광받고 있다.

이런 패러다임에 맞춰서 자바 8버전부터 함수형 프로그래밍을 할 수 있도록 '람다식(Lambda Expressions)'을 지원한다.

 

자바에서 제공하는 함수형 프로그래밍 방식을 '람다식'이라고 한다.

람다식을 사용하면 간결하게 함수를 만들고 사용할 수 있다.

람다식은 먼저 인터페이스를 작성하고 사용하려는 인터페이스를 익명으로 구현하는 방법이다.

 

 

➤ 람다식의 작성

 

(매개변수) -> {실행코드}

 

 

람다식 문법 ⬇️

public class LambdaTest {
    public static void main (String[] args) {

        x -> {System.out.println(x);}   // 매개변수가 한 개인 경우 괄호 생략 가능 ⭕️
        x, y -> {System.out.println(x * y);}  // 매개변수가 두 개 이상인 경우 괄호 생략 ❌
        (x, y) -> System.out.println(x * y);    // 실행문이 한 개인 경우 괄호 생략 가능 ⭕️
        (x, y) -> {return x + y;}   // 하지만 실행문이 한 개여도 return문인 경우 괄호 생략 ❌
        (x, y) -> x + y;        // 실행문이 한 개이고 return문인 경우 return과 중괄호 모두 생략 ⭕️

    }
}

 

<작성규칙>

매개변수가 하나일 경우는 괄호를 생략 가능하지만 2개 이상인 경우엔 생략할 수 없다.

실행문이 하나인 경우에도 중괄호를 생략할 수 있지만 하나인 실행문이 return문인 경우에는 생략할 수 없다.

실행문에 return문만 있는 경우는 return과 중괄호 모두 생략하는 것이 올바르다.

 

 

 

➤ 함수형 인터페이스

 

자바에서 메소드를 사용하려면 클래스를 만들고 객체롤 메소드를 호출해야 한다.

인터페이스는 클래스마다 공통적으로 사용하는 기능을 메소드마다 다르게 정의할 수 있게 해준다.

하지만 함수를 만들어서 사용할 때는 함수형 인터페이스를 만들면 같은 식에 다른 값을 넣어서 재사용할 수 있어 편리하다.

 

🌱 함수형 인터페이스를 작성하기

package lambdaPractice;

@FunctionalInterface
public interface BigNum {
    int getMax(int num1, int num2); // 추상 메서드 선언
//    int add(int num1, int num2);  // 함수형 인터페이스는 한 개의 추상 메서드만 선언할 수 있음!⚠️
}

 

위에는 두개의 숫자를 받아서 더 큰 숫자를 반환하는 함수를 포함한 함수형 인터페이스이다.

위에를 보면 작성하는 규칙이 있다.

1. '@FunctionalInterface' 애너테이션을 사용해서 컴파일러에 함수형 인터페이스임을 알릴 수 있다.

2. 한 개의 추상 메소드만 하나만 정의할 수 있다.

 

🌱 함수형 인터페이스 구현하기!

package lambdaPractice;

public class BigNumTest {
    public static void main(String[] args) {
        BigNum big = (x, y) -> (x >= y) ? x : y;
        System.out.println(big.getMax(10, 12));
        System.out.println(big.getMax(5,1));       
    }
}

 

작성한 함수형 인터페이스형인 BigNum형 big 함수를 선언해주었다.

매개변수로 x와 y를 받아서 'x >= y' 가 true이면 x, false이면 y를 반환하도록 만들었다.

 

 

 

➤ 왜 함수형 프로그래밍을 이용할까?

 

자바에서 람다식을 이용한 함수형 프로그램 코드가 객체를 만들어서 사용하는 객체지향형 코드보다 훨씬 간결하고 직관적이다.

함수식을 통해서 용도를 한눈에 이해할 수 있게 된다.

 

그리고 재사용하기도 용이하고 확장성 있는 프로그래밍이 가능하다.

함수형 인터페이스를 한번 구현하면 구현한 객체명으로 같은 식을 계속 사용할 수 있다.

만약 중간에 식을 조금 수정해야 한다면 다시 만들 필요없이 람다식을 이용해서 만들어 놓은 객체에 함수식을 고쳐주면 된다.

 

함수가 입력받은 자료 이외에 외부 자료에 영향을 미치지 않기 때문에 여러 자료를 동시에 처리하는 병렬 처리에 적합하다.

 

 

 

➤ 메서드 레퍼런스

 

함수형 인터페이스와 비슷한 기능을 제공하는 것이 메서드 레퍼런스이다.

메서드 레퍼런스는 작성해놓은 클래스의 객체를 생성하지 않고도 클래스 내의 메서드를 이용할 수 있게 해준다.

매개변수를 작성할 필요없이 메서드로 인수를 전달한다.

 

IntBinaryOperator operator1 = (a, b) -> Math.max(a, b);
IntBinaryOperator operator2 = Math :: max;
System.out.println(operator1.applyAsInt(1, 5) + ", " + operator2.applyAsInt(1, 5));

메서드 레퍼런스는 :: 을 이용해서 클래스(또는 인터페이스)의 메서드에 접근할 수 있다.

클래스명::메소드명

IntBinaryOperator은 두 개의 int 매개값을 받아 int값을 리턴하는 함수형 인터페이스이다.

추상형 메서드 이름은 applyAsInt 이다.

 

정적 메서드 레퍼런스

클래스 :: 메서드

 

인스턴스 메서드 레퍼런스

참조 변수 :: 메서드

 

참고 사이트

https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

 

Method References (The Java™ Tutorials > Learning the Java Language > Classes and Objects)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

 

 

➤ 매개변수의 메서드 레퍼런스

 

람다식에서 제공되는 메개변수의 메서드를 호출하여 매개변수를 매개값으로 사용하는 경우도 있다.

(a,b) -> {a.instanceMethod(b);};

클래스 :: instanceMethod

정적 메서드 레퍼런스와 작성방법은 동일하지만 a의 인스턴스 메서드가 참조된다는 점에서 다른 코드이다.

 

 

➤ 생성자 참조

 

생성자를 참조하는 람다식 표현도 작성 가능하다.

(a, b) -> {return new Object(a, b);};

 

위의 코드를 생성자 참조 표현으로 간단하게 표현할 수 있다.

클래스 :: new

 

 

 


 

 

❑ 스트림(Stream)

 

 

➤ 스트림이란?

 

스트림은 배열, 컬렉션의 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자 이다.

정렬이나 특정 값을 제외하고 출력하는 등 여러 자료 처리에 대한 기능을 구현해 놓은 클래스이다.

스트림을 사용하면 List, Set, Map, 배열 등 다양한 데이터 소스를 스트림형으로 변환하고 다양한 메서드를 제공한다.

 

처리해야하는 자료에 상관없이 같은 방식으로 메서드를 호출할 수 있기 때문에 자료를 추상화 했다고 표현한다.

 

 

➤ 스트림 생성하기

 

배열을 스트림으로 생성하기 : Arrays.stream(arr)

Collection 인터페이스에는 Stream<E> stream() 메서드가 정의되어 있기 때문에

Collection을 구현한 클래스에서는 모두 클래스명.stream()으로 스트림 클래스를 만들 수 있다.

int[] arr = new int[];
Arrays.stream(arr);		// 배열을 stream형으로 만들기
List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream();	// ArrayList를 stream형으로 만들기

 

 

 

➤ 스트림의 자주 사용하는 메서드

 

forEach() : 내부적으로 반복문이 수행됨

stream.forEach(s -> System.out.println(s));

 

filter() : 조건을 넣고 조건이 참인 경우만 추출

stream.filter(s -> s.length() >= 5);

 

map() : 요소들을 순회하여 다른 형식으로 변환

stream.map(c -> c * 2);

 

forEach() : 요소를 하나씩 꺼내는 기능, 요소를 하나씩 소모한다.

stream.forEach(System.out.println(s));

 

count() : 요소의 개수를 셈

sum() : 요소의 합을 구함

average() : 요소의 평균값

max(), main() : 요소의 최대값, 최소값을 구함

 

sorted() : 요소들을 정렬함

stream.sorted() 	// 오름차순으로 정렬
stream.sorted(Comparator.reverseOrder());	// 내림차순으로 정렬

 

match() : 요소들이 특정한 조건을 충족하는지 검사, boolean형으로 반환

stream.allMatch(a -> a%2 == 0);	// 모든 요소들이 조건을 만족하는지 조사
stream.anyMatch(a -> a%2 == 0); // 최소 한 개 이상의 요소가 조건을 만족하는지 조사
stream.noneMatch(a -> a%2 == 0); // 모든 요소들이 조건을 만족하지 않는지 조사

 

 

 

➤ 스트림의 특징

 

  • 자료의 대상과 관계없이 동일한 연산을 수행한다.
  • 한 번 생성하고 사용한(소모한) 스트림은 재사용 할 수 없다.
  • 스트림의 연산은 기존 자료를 변경하지 않는다.
  • 스트림의 연산은 중간 연산과 최종 연산이 있다.

 

 

-Optional<T> 에 대해서 더 공부 필요

 

 

읽어주셔서 감사합니다😍

오개념에 대한 지적은 환영입니다.

1