본문 바로가기

Spring

4. 람다함수의 개념 이해하기 + 스트림

fastcampus 사이트의 'Java 웹 개발 마스터' ,유튜브 '어라운드 허브 스튜디오 - 람다함수'를 보고 공부 목적으로 정리 하였습니다.

 


Lambda

 

▶ 람다함수란

프로그래밍 언어에서 사용되는 개념으로 익명함수(Anonymous Function)를 지칭하는 용어로 함수를 변수처럼 사용합니다. 따라서 클래스를 생성하지 않고 함수의 호출만으로 기능을 수행할 수 있습니다. 람다함수를 사용하는 것은 함수형 프로그래밍이라고 이야기합니다. 함수형 인터페이스를 선언하고 메소드를 구체화하여 사용합니다. 

- JAVA 8버전 이상부터 지원

 

 

▶ 함수형 프로그래밍이란

람다함수는 추가 클래스를 생성하지 않고 함수의 호출만으로 기능을 수행하는 방식입니다.

  • 순수함수를 구현하고 호출  부작용(sside-effect)가 없는 함수, 함수의 실행이 외부에 영향을 끼치지 않는다.
  • 매개변수만을 사용하도록 만든 함수로 외부자료에 부수적인 영향이 발생하지 않도록 한다.
  • 입력받은 자료를 바탕으로 수행되고 외부에 영향을 미치지 않으므로 병렬처리가 가능
  • 안정적인 확장성있는 프로그래밍 방식

 

▶ 장점

  • 코드의 라인 수가 줄어듦 메소드로 표현된 코드에 비해 확연히 라인 수가 줄어든다.
  • 병렬 프로그래밍이 가능
  • 람다식으로 바로 실행문을 전달할 수 있음(메소드 호출 필요 X)
  • 코드가 간결해진다.
  • 필요한 정보만을 사용함으로써 성능이 향상된다.(지연 연산 지원)

▶ 단점

  • 람다식은 재사용이 불가능하다.(람다함수는 일회용 함수의 정의가 목적)
  • 불필요하게 너무 많이 사용할 경우 가독성이 떨어진다.(같은 기능의 함수를 여러번 정의하는 경우)
  • 디버깅시에 추적이 어렵다.

 

▶ 람다식의 문법

구조:     (매개변수) -> {함수구현부} / ()->{함수구현}    

 

1. 매개 변수가 하나인 경우 괄호 생략이 가능하다. (두개인 경우는 괄호 생략 불가)

str -> { System.out.println(str); }         //str을 받으면 str을 출력하라

2. 중괄호 안의 구현부가 한 문장인 경우 중괄호 생략가능

str-> System.out.println(str);

3. 중괄호 안의 구현부가 한 문장이라도 return문은 중괄호를 생략할 수 없다.

str -> return str.length( );    X
str -> {return str.length( );}  O

4. 중괄호 안의 구현부가 반환문 하나라면 return과 중괄호를 모두 생략할 수 있다.

(x, y) -> x + y          //두 값을 더하여 반환
str -> str.length()      //문자열 길이를 반환

 

▶ @FunctionalInterface 함수형 인터페이스

위의 어노테이션이 들어간 경우 '인터페이스 내부에 구현해야할 추상메소드는 하나만 쓸 수 있다' 라는 것을 명시합니다.

 

@FunctionalInterface
interface Print {
    void print(int a, int b);
}

class Test {
    public void testMethod(Print pr) {
        pr.print(1, 2);
        System.out.println("콘솔출력 실행문");
    }
}

public class LambdaExample1 {
    public static void main(String[] args) {
        Test noLambda = new Test();
        
        // 람다함수 사용X
        noLambda.testMethod(new Print() {
            @Override
            public void print(int a, int b) {
                System.out.println("a와 b의 합은 " + (a + b));
                System.out.println("a와 b의 차는 " + (a - b));
            }
        });
        //람다함수 사용O
        Test lambdaTest = new Test();
        lambdaTest.testMethod((a, b) -> {
            System.out.println("a와 b의 합은 " + (a + b));
            System.out.println("a와 b의 차는 " + (a - b));
        });
    }
}
<<Console>>
a와 b의 합은 3
a와 b의 차는 -1
콘솔 출력 실행문
a와 b의 합은 3
a와 b의 차는 -1
콘솔 출력 실행문
@FunctionalInterface
interface BigNumber {
    int getBigNumber(int num1, int num2);
}

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

        BigNumber bigNumber = (x, y) -> {
            if(x > y) {
                return x;
            } else {
                return y;
            }
        };

        int result = bigNumber.getBigNumber(2156, 12382);
        System.out.println(result);
    }
}
<<Console>>
12382

 

import java.util.function.BinaryOperator;
import java.util.function.IntFunction;

public class JavaFunction {

    public static void main(String[] args) {

        //int 값을 매개변수로 받아 함수를 생성
        IntFunction intSum = (a) -> a + 10;

        System.out.println(intSum.apply(5));       

        //동일한 타입의 두 값을 받아 연산을 처리
        BinaryOperator binarySum = (a, b)  -> a + " " + b;
   
        System.out.println(binarySum.apply(1, 2));
        System.out.println(binarySum.apply("어라운드 허브", "스튜디오")); 

    }
}
<<Console>>
15
1 2
어라운드 허브 스튜디오

▶ 스트림(Stream)

  • 자료의 대상과 관계없이 동일한 연산을 수행할 수 있는 기능(자료의 추상화)
  • 배열, 컬렉션에 동일한 연산이 수행되어 일관성 있는 처리가능
  • 한번 생성하고 사용한 스트림은 재사용할 수 없음
  • 스트림 연산은 기존 자료를 변경하지 않음
  • 중간연산과 최종연산으로 구분
  • - 중간연산 : 특정 조건에 맞는 연산을 가져온다.
  • - 최종연산 : 중간연산을 거친 후 최종 연산의 결과를 출력
  • 최종연산이 수행되어야 모든 연산이 적용되는 지연연산

 

▶ 스트림 연산 - 중간연산

  • filter() : 조건에 맞는 요소를 추출
  • map() : 요소를 반환

ex)

1. 문자열의 길이가 5 이상인 요소만 출력하기

sList.stream().filter(s->s.length() >= 5).forEach(s->System.out.println(s));

    스트림 생성            중간연산                       최종연산

 

2. 고객 클래스에서 고객이름만 가져오기

customerList.stream().map(c->c.getName()).forEach(s->System.out.println(s));

 

▶ 스트림 연산 - 최종연산

  • 스트림의 자료를 소모하면서 연산을 수행
  • 최종연산 후에 스트림은 더 이상 다른 연산을 적용할 수 없음
  • forEach() : 요소를 하나씩 꺼내옴
  • count() : 요소의 개수
  • sum() : 요소의 합
  • 이외에도 여러가지 연산이 있다.

▶ reduce() 연산

  • 정의된 연산이 아닌 프로그래머가 직접 지정하는 연산을 적용
  • 최종연산으로 스트림의 요소를 소모하며 연산수행
  • 배열의 모든 요소의 합을 구하는 reduce()연산

 

<예제1 >

int[] arr = {1,2,3,4,5};

int sum = Arrays.stream(arr).sum();
int count = (int)Arrays.stream(arr).count();

System.out.println(sum);      //15
System.out.println(count);    //5
System.out.println(Arrays.stream(arr).reduce(0, (a, b) -> a + b));   //15

 

<예제2 >

List<String> sList = newArrayList<String>();
sList.add("Tomas");
sList.add("Edward");
sList.add("Jack");

Stream<String> stream = sList.stream();
stream.forEach(s->System.out.print( s + " "));                                   // Tomas Edward Jack
System.out.println();

sList.stream().sorted().forEach(s->System.out.print( s + " "));                // Edward Jack Tomas
System.out.println();
sList.stream().map(s->s.length()).forEach(n->System.out.print( n + " ")); // 5 6 4

 

<예제3 >

import java.util.Arrays;
import java.util.function.BinaryOperator;

class CompareString implements BinaryOperator<String> {
    @Override
    public String apply(String s1, String s2) {
        if(s1.getBytes().length >= s2.getBytes().length)
            return s1;
        else
            return s2;
    }
}

public class ReduceTest {
    public static void main(String [] args) {
        String[] greetings = {"안녕하세요~~~", "hello", "Good morning", "반갑습니다"};
    
        System.out.println(Arrays.stream(greetings).reduce("",(s1, s2)->
            {  if(s1.getBytes().length >= s2.getBytes.length)
                return s1;
            else return s2;
            }));          // 안녕하세요~~~
        System.out.println(Arrays.stream(greetings).reduce(new CompareString()).get());  // 안녕하세요~~~

'Spring' 카테고리의 다른 글

6. 스프링 Servlet  (0) 2022.07.02
5. 스프링 AOP  (0) 2022.06.28
3. 템플릿 메소드 패턴 VS 전략패턴  (0) 2022.01.12
2. 토비 Chapter 4.템플릿 내용 정리  (0) 2022.01.09
1. Spring annotation  (0) 2021.12.30