[Java 일반] 07-06 함수형 프로그래밍

07-06 함수형 프로그래밍

자바는 Java 8 버전부터 함수형 프로그래밍을 지원하기 위해 람다(lambda)와 스트림(stream)이 도입되었다. 람다와 스트림을 사용하면 함수형 프로그래밍 스타일로 자바 코드를 작성할 수 있다. 물론 람다와 스트림을 사용하여 작성한 코드를 일반 스타일의 자바 코드로 바꾸어 작성하는 것이 불가능하지는 않다. 달리 말하면 람다와 스트림 없이도 자바 코드를 작성하는 데 어려움이 없다는 뜻이다. 그런데도 람다와 스트림을 사용하는 이유는 작성하는 코드의 양이 줄어들고 읽기 쉬운 코드를 만들 수 있기 때문이다.

사실 람다와 스트림은 시간을 들여 깊게 공부해야 한다. 초보자를 위한 이 책에서는 이 두 개에 대해서 자세히 다루지는 않는다. 하지만 람다와 스트림이 무엇이고 어떻게 사용하는지 간단히 알아보고자 한다.

람다

람다(lambda)는 익명 함수(anonymous function)를 의미한다. 일반적인 코드와 람다를 적용한 코드를 비교하며 람다에 대해서 자세히 알아보자.

일반적인 코드

다음과 같은 인터페이스를 보자. 두 개의 정수를 입력으로 받아 정수의 결괏값을 리턴하는 sum 함수를 정의한 인터페이스이다.

interface Calculator {
    int sum(int a, int b);
}

앞서 정의한 Calculator 인터페이스를 사용하려면 다음처럼 Calculator 인터페이스를 구현해야 한다.

class MyCalculator implements Calculator {
    public int sum(int a, int b) {
        return a+b;
    }
}

Calculator 인터페이스를 구현한 MyCalculator 클래스를 생성하였다.

Calculator 인터페이스와 MyCalculator 클래스를 사용하여 자바 프로그램을 완성해 보자.

interface Calculator {
    int sum(int a, int b);
}

class MyCalculator implements Calculator {
    public int sum(int a, int b) {
        return a+b;
    }
}

public class Sample {
    public static void main(String[] args) {
        MyCalculator mc = new MyCalculator();
        int result = mc.sum(3, 4);
        System.out.println(result);  // 7 출력
    }
}
7

람다를 적용한 코드

이제 앞에서 작성한 일반적인 자바 코드를 다음처럼 람다를 적용한 코드로 바꾸어 보자.

interface Calculator {
    int sum(int a, int b);
}

public class Sample {
    public static void main(String[] args) {
        Calculator mc = (int a, int b) -> a +b;  // 람다를 적용한 코드
        int result = mc.sum(3, 4);
        System.out.println(result);
    }
}
7

코드는 다르지만 결과는 같다.

이 코드에서 사용한 람다 함수는 다음과 같다.

(int a, int b) -> a +b

괄호 사이의 int a, int b는 Calculator 인터페이스의 sum 함수의 입력 항목에 해당하고 -> 뒤의 a+b가 리턴값에 해당한다. 이렇게 람다 함수를 사용하면 MyCalculator와 같은 실제 클래스 없이도 Calculator 객체를 생성할 수 있고, 일반적인 코드보다 훨씬 간단해진다.

인터페이스 사용 시 주의 사항

여기서 주의해야 할 점은 Calculator 인터페이스의 메서드가 1개 이상이면 람다 함수를 사용할 수 없다는 점이다.

interface Calculator {
    int sum(int a, int b);
    int mul(int a, int b);  // mul 메서드를 추가하면 컴파일 에러가 발생한다.
}

그래서 람다 함수로 사용할 인터페이스는 다음처럼 @FunctionalInterface 어노테이션을 사용하는 것이 좋다. @FunctionalInterface 어노테이션을 사용하면 다음처럼 2개 이상의 메서드를 가진 인터페이스를 작성하는 것이 불가능해진다.

@FunctionalInterface
interface Calculator {
    int sum(int a, int b);
    int mul(int a, int b);  // @FunctionalInterface 는 두 번째 메서드를 허용하지 않는다.
}

@FunctionalInterface는 인터페이스가 함수형 인터페이스임을 표시하며, 단 하나의 메서드만 가질 수 있게 한다. 주로 람다 표현식과 함께 사용한다.

람다 축약하기

앞서 작성한 람다를 적용한 자바 코드는 다음처럼 조금 더 축약이 가능하다. 인터페이스에 이미 입출력에 대한 타입이 정의되어 있으므로 입력값의 자료형인 int를 생략할 수 있다.

interface Calculator {
    int sum(int a, int b);
}

public class Sample {
    public static void main(String[] args) {
        Calculator mc = (a, b) -> a +b;
        int result = mc.sum(3, 4);
        System.out.println(result);
    }
}

 

점프 투 자바Integer.sum으로 더 축약하기

 

두 수를 더하여 그 결과를 리턴하는 함수 (a, b) -> a+b는 Integer.sum(int a, int b)와 동일하기 때문에 다음과 같이 더 축약이 가능해진다.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

Integer::sum

어떤 클래스의 메서드를 사용할 때는 이와 같이 :: 기호를 사용하여 클래스와 메서드를 구분하여 표기한다.

Integer::sum을 적용한 코드는 다음과 같다.

@FunctionalInterface
interface Calculator {
    int sum(int a, int b);
}

public class Sample {
    public static void main(String[] args) {
        Calculator mc = Integer::sum;
        int result = mc.sum(3, 4);
        System.out.println(result);
    }
}

 

 

스트림

스트림(stream)은 글자 그대로 해석하면 ‘흐름’이라는 뜻이다. 데이터가 물결처럼 흘러가면서 필터링 과정을 통해 여러 번 변경되어 반환되기 때문에 이러한 이름을 갖게 되었다. 다음 문제를 통해 스트림을 이해해 보자.

다음과 같은 정수 배열이 있다.

int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};

이 배열에서 짝수만 뽑아 중복을 제거한 후에 역순으로 정렬하는 프로그램을 만들어 보자. 이 때 프로그램의 수행 결과는 다음과 같아야 한다.

int[] result = {8, 6, 4, 2};

이 문제를 해결하기 위해서는 다음처럼 코드를 작성해야 한다.

import java.util.*;

public class Sample {
    public static void main(String[] args) {
        int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};

        // 짝수만 포함하는 ArrayList 생성
        ArrayList<Integer> dataList = new ArrayList<>();
        for(int i=0; i<data.length; i++) {
            if(data[i] % 2 == 0) {
                dataList.add(data[i]);
            }
        }

        // Set을 사용하여 중복을 제거
        HashSet<Integer> dataSet = new HashSet<>(dataList);

        // Set을 다시 List로 변경
        ArrayList<Integer> distinctList = new ArrayList<>(dataSet);

        // 역순으로 정렬
        distinctList.sort(Comparator.reverseOrder());

        // Integer 리스트를 정수 배열로 변환
        int[] result = new int[distinctList.size()];
        for(int i=0; i< distinctList.size(); i++) {
            result[i] = distinctList.get(i);
        }
    }
}

정수 배열에서 짝수만을 찾아 ArrayList에 넣고 Set을 사용하여 중복을 제거한 후에 다시 Set을 리스트로 변환했다. 그리고 리스트의 sort를 사용하여 역순으로 정렬하고 정렬된 값으로 다시 정수 배열을 생성했다. 복잡하지만 보통 이와 비슷한 과정을 통해 문제를 해결해야 한다.

그런데 이 문제는 스트림을 사용하면 다음과 같이 간단하게 해결할 수 있다.

import java.util.Arrays;
import java.util.Comparator;

public class Sample {
    public static void main(String[] args) {
        int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};
        int[] result = Arrays.stream(data)  // IntStream을 생성한다.
                .boxed()  // IntStream을 Stream<Integer>로 변경한다.
                .filter((a) -> a % 2 == 0)  //  짝수만 뽑아낸다.
                .distinct()  // 중복을 제거한다.
                .sorted(Comparator.reverseOrder())  // 역순으로 정렬한다.
                .mapToInt(Integer::intValue)  // Stream<Integer>를 IntStream으로 변경한다.
                .toArray()  // int[] 배열로 반환한다.
                ;
    }
}

이 코드는 다음과 같은 순서로 동작한다.

  1. Arrays.stream(data)로 정수 배열을 IntStream으로 생성한다.
  2. .boxed()로 IntStream을 Integer의 Stream으로 변경한다. 이렇게 하는 이유는 뒤에서 사용할 Comparator.reverseOrder 메서드는 원시 타입인 int 대신 Integer를 사용해야 하기 때문이다.
  3. .filter((a) -> a % 2 == 0)로 짝수만 필터링한다. 이때 사용한 (a) -> a % 2 == 0 구문은 앞에서 공부한 람다 함수이다. 입력 a가 짝수인지를 조사하는 람다 함수로 짝수에 해당되는 데이터만 필터링한다.
  4. .distinct()로 스트림에서 중복을 제거한다.
  5. .sorted(Comparator.reverseOrder())로 역순으로 정렬한다.
  6. .mapToInt(Integer::intValue)로 Integer의 Stream을 IntStream으로 변경한다. 왜냐하면 최종적으로 int[] 타입의 배열을 리턴해야 하기 때문이다.
  7. .toArray()를 호출하여 IntStream의 배열인 int[] 배열을 리턴한다.

스트림 방식은 일반적인 코드보다 확실히 간결하고 가독성이 좋다는 것을 확인할 수 있을 것이다.

사실 Arrays.stream처럼 스트림을 생성하고 boxed, filter, distinct, sorted, mapToInt처럼 스트림을 가공하고, toArray처럼 스트림을 원하는 형태로 반환하는 방법에는 이것 말고도 여러 가지가 있다. 하지만 이 책에서는 스트림을 이해하기 위한 목적으로 이와 같이 몇 가지만 알고 넘어가자.

[출처] https://wikidocs.net/157858

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
18 [java 인공지능] Spring AI 로 ChatGPT API 만들기 졸리운_곰 2024.12.30 214
17 [java 인공지능] 오라클, 자바 머신러닝 라이브러리 ‘트리뷰오’ 오픈소스로 공개 졸리운_곰 2023.08.27 296
16 [java 인공지능] 자바를 위한 머신 러닝 라이브러리 졸리운_곰 2023.08.27 360
15 [Java 인공지능] 오라클, 자바 머신러닝 라이브러리 ‘트리뷰오’ 오픈소스로 공개 file 졸리운_곰 2023.08.13 358
14 [java 인공지능] [java] 라이프 게임 (life game) file 졸리운_곰 2021.10.19 337
13 How to use Weka in your Java code 졸리운_곰 2020.02.01 307
12 weka and java eclipse example : A Simple Machine Learning Example in Java file 졸리운_곰 2020.01.31 362
11 머신러닝? weka file 졸리운_곰 2020.01.31 357
10 [Weka] Weka를 이용한 Iris 데이터 머신러닝 file 졸리운_곰 2020.01.30 363
9 [강좌] WEKA 사용법 (간단한 분류, 의사결정트리 분석 설명) file 졸리운_곰 2020.01.30 343
8 [JESS] Jess , 이클립스 연동 file 졸리운_곰 2019.12.22 215
7 Jess 간단한 문법 요약 졸리운_곰 2019.12.22 277
6 Jess 6.1 다운로드 friedman-hill_src_1_jess_se file 졸리운_곰 2019.12.22 184
5 java artificial intelligence Rule Engine Jess Working Memory 졸리운_곰 2019.12.22 300
4 Defining Functions in Jess 졸리운_곰 2019.12.22 389
3 Jess Language Basics 졸리운_곰 2019.12.22 272
2 Embedding Jess in a Java Application 졸리운_곰 2019.12.22 298
1 다섯개의 탑 자바로 머신러닝 라이브러리 Top 5 machine learning libraries for Java file 졸리운_곰 2017.08.22 388
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED