본문 바로가기

Java/기본

[Java] 람다식(Lambda) 익히기

수정이력

2022.04.16. 글 표현 다듬기. 불필요한 내용 삭제


람다식이란

람다식은 익명함수(anonymous function)로 구동된다. 자바 8버전부터 적용 가능하다. 람다식은 마치 함수처럼 작성하지만, 실행시 익명구현 객체를 생성하는 방식으로 구동된다. 람다식은 병렬처리, 이벤트 처리 등 함수적 프로그래밍에서 유용하게 쓰인다.

 

람다식을 쓰는 이유

  • 람다식을 쓰지 않을 때보다 코드가 간결해진다 (기호 및 약속된 표현을 쓰기 때문에)
  • 필터링 및 매핑시 집계결과를 쉽게 가져올 수 있다 (내장된 메서드를 사용할 수 있음)

 

 

함수적 인터페이스

함수적 인터페이스(functional interface)는 추상메서드가 1개만 정의된 인터페이스이다. 람다식은 함수적 인터페이스에 사용할 수 있다. 람다식을 실행하면 인터페이스를 구현하는 객체가 생성된다. 람다식은 이름을 따로 지정하지 않으므로 익명구현객체가 생성된다. 

 

인터페이스에는 추상메서드가 있고, 이를 구현해야 메서드로써 사용할 수 있다. 메서드가 1개라면 당연히 해당 메서드가 구현하려는 메서드일 것이다. 하지만 메서드가 여러개라면 람다식으로 표현했을때 어떤 메서드를 실행해야 하는지 컴파일러가 알 수 없을 것이다.

 

함수적 인터페이스는 @FunctionalInterface 애노테이션을 붙일 수 있다. @FunctionalInterface 애노테이션은 추상메서드가 1개만 선언되도록 표시하는 것이다. 이 어노테이션이 있으면 인터페이스에 메서드가 2개 이상 선언되면 컴파일 오류가 발생한다. 애노테이션은 선택사항이다.

@FunctionalInterface

 

요약

  • 람다식을 실행하면 익명구현객체가 생성된다
  • 람다식은 모든 인터페이스에서 사용한다(x)
  • 람다식은 함수적 인터페이스에서 사용한다(o)

 

람다식의 기본형태

A a = (매개값) -> { 구현코드 };

 

*example

Runnable 인터페이스를 구현해보자. 참고로 Runnable 인터페이스는 run() 메서드 1개만 갖고 있으므로 람다식 적용이 가능하다.

 

 

람다식을 적용하지 않는다면 아래와 같이 코딩한다.

Runnable 인터페이스를 구현하는 코드 (람다식 적용x)

Runnable runnable = new Runnable(){
	public void run(){
    	System.out.println("Lambda");
    }
}

 

람다식을 적용하면 아래와 같이 코딩한다.

Runnable 인터페이스를 구현하는 람다식코드

Runnable runnable = () -> {
	System.out.println("Lambda");
};

 

 

람다식 작성

람다식은 매개변수와 리턴값의 유무에 따라 아래와 같이 구분된다. 코드 작성방법을 알아보자.

  • 매개변수가 없고, 리턴값이 없는 람다식
  • 매개변수가 있고, 리턴값이 없는 람다식
  • 매개변수가 없고, 리턴값이 있는 람다식
  • 매개변수가 있고, 리턴값이 있는 람다식

 

1) 매개변수가 없고, 리턴값이 없는 람다식

람다식 테스트를 위해서는 함수적 인터페이스가 필요하다. JavaCoding이라는 인터페이스에  nowCoding() 메서드를 선언했다.

@FunctionalInterface
public interface JavaCoding {
    void nowCoding();
}

 

public class Execute {
    public static void main(String[] args) {
        //객체 선언
        JavaCoding jc;

        //{} 실행코드 뒤에 세미콜론(;)을 붙여야한다
        jc = () -> {
            System.out.println("Rollin' Rollin' Rollin' Rollin'");
        };
        jc.nowCoding();

        // {} 실행코드가 1줄인경우 {} 생략가능
        jc = () -> System.out.println("Rollin' Rollin' Rollin' Rollin'");
        jc.nowCoding();
    }
}

 

실행 결과

Rollin' Rollin' Rollin' Rollin'
Rollin' Rollin' Rollin' Rollin'

 


2) 매개변수가 있고, 리턴값이 없는 람다식

@FunctionalInterface
public interface JavaCoding {
    void nowCoding(String str);
}

 

public class Execute {
    public static void main(String[] args) {
        //객체 선언
        JavaCoding jc;
        String str;

        jc = (a) -> {
            System.out.println(a+ " Rolling in the deep");
        };
        str = "하루가 멀다하고";
        jc.nowCoding(str);

        //람다식 바디{}를 생략하고 한 줄에 작성하기
        jc = (a) -> System.out.println(a+ " Babe just only you");
        str= "기다리고 있잖아";
        jc.nowCoding(str);

        //매개변수가 1개인 경우 () 생략할 수 있음
        jc = a -> System.out.println(a+ " 기다리고 있어요");
        jc.nowCoding("온종일 난 그대 생각에");
    }
}

 

실행 결과

하루가 멀다하고 Rolling in the deep
기다리고 있잖아 Babe just only you
온종일 난 그대 생각에 기다리고 있어요

3) 매개변수가 없고, 리턴값이 있는 람다식

@FunctionalInterface
public interface JavaCoding {
    String nowCoding();
}

 

public class Execute {
    public static void main(String[] args) {
        //객체 선언
        JavaCoding jc;

        String str1 = "그 날을 잊지 못해 baby";
        String str2 = "날 보며 환히 웃던 너의 미소에";
        String str3 = "홀린 듯 I'm fall in love";

        jc = () -> {
            return str1;
        };
        System.out.println(jc.nowCoding());

        jc = () -> { return str2; };
        System.out.println(jc.nowCoding());

        //실행코드가 return 만 있는 경우 {}와 return문 생략가능
        jc = () -> str3;
        System.out.println(jc.nowCoding());
    }
}

 

실행 결과

그 날을 잊지 못해 baby
날 보며 환히 웃던 너의 미소에
홀린 듯 I'm fall in love

4) 매개변수가 있고, 리턴값이 있는 람다식

@FunctionalInterface
public interface JavaCoding {
    String nowCoding(String s);
}

 

public class Execute {
    public static void main(String[] args) {
        //객체 선언
        JavaCoding jc;

        String str1 = " 너의 생각뿐이야";
        String str2 = " 미치겠어";
        String str3 = " 보고 싶어";

        jc = (s) -> {
            return s+ str1;
        };
        System.out.println(jc.nowCoding("온통"));

        jc = (s) -> { return s+ str2; };
        System.out.println(jc.nowCoding("나도"));

        jc = s -> s+ str3;
        System.out.println(jc.nowCoding("너무"));
    }
}

 

실행 결과

온통 너의 생각뿐이야
나도 미치겠어
너무 보고 싶어

 


표준 API의 함수적 인터페이스

java.util.function 표준 API 패키지의 함수적 인터페이스를 사용해보자.

종류 매개값 리턴값 메서드형태
활용
Consumer O X accept  
Supplier X O get...  
Function O O apply... 매개값을 매핑(=타입변환)해서 리턴하기
Operator O O apply... 매개값을 연산해서 결과 리턴하기
Predicate O O test 매개값이 조건에 맞는지 확인해서 boolean 리턴

 

1) Consumer 함수적 인터페이스

  • 역할 : 소비자. 매개값이 있고, 리턴값 없다
  • 메서드 : accept()
인터페이스 형태 내용
XXXConsumer<T> XXX 형태의 인자값을 받는다
BiConsumer<T, U> T, U 형태의 인자값 2개를 받는다
ObjXXXConsumer<T> T, XXX 형태의 인자값 2개를 받는다

 

 

아래 코드를 보자

public class ConsumerTest {

    Consumer<String> c1 = a -> System.out.println("입력값 : "+ a);
    
    BiConsumer<String, Integer> c2 =  (a, b) -> System.out.println("입력값1 : "+ a+ " 입력값2 : "+ b);
    
    IntConsumer c3 = a -> System.out.println("입력값 : "+ a);
    
    DoubleConsumer c4 = a -> System.out.println("입력값 : "+ a);
    
    LongConsumer c5 = a -> System.out.println("입력값 : "+ a);
    
    ObjIntConsumer<Student> c6 = (a, b) -> System.out.println("이름 : "+ a.name+ " 숫자 : "+ b);
    
    ObjDoubleConsumer<Student> c7 = (a, b) -> System.out.println("이름 : "+ a.name+ " 숫자 : "+ b);
    
    ObjLongConsumer<Student> c8 = (a, b) -> System.out.println("이름 : "+ a.name+ " 숫자 : "+ b);


    class Student{
        private String name;
        Student(String name){this.name = name;};
    }

    public static void main(String[] args) {
        ConsumerTest test = new ConsumerTest();

        test.c1.accept("김유신");
        test.c2.accept("홍길동", 123);
        test.c3.accept(456);
        test.c4.accept(123.45);
        test.c5.accept(1232345);

        Student student = test.new Student("이순신");
        test.c6.accept(student, 12);
        test.c7.accept(student, 23.24);
        test.c8.accept(student, 98765);
    }
}

 

실행 결과

입력값 : 김유신
입력값1 : 홍길동 입력값2 : 123
입력값 : 456
입력값 : 123.45
입력값 : 1232345
이름 : 이순신 숫자 : 12
이름 : 이순신 숫자 : 23.24
이름 : 이순신 숫자 : 98765

 


2) Supplier 함수적 인터페이스

  • 역할 : 생산자. 매개값이 없고, 리턴값이 있다
  • 메서드 : getXXX()
인터페이스 형태 내용
Supplier<T> T형 반환
XXXSupplier XXX형 반환

 

 

아래 코드를 보자

public class SupplierTest {
    public static void main(String[] args) {
        String stringValue = "helloWorld";
        boolean booleanValue = true;
        double doubleValue = 13245.123;
        int intValue = 123;
        long longValue = 1234567;

        Supplier<String> supplier = () -> stringValue;
        BooleanSupplier booleanSup =  () -> booleanValue;
        DoubleSupplier doubleSup = () -> doubleValue;
        LongSupplier longSup = () -> longValue;
        IntSupplier intSup = () -> intValue;

        SupplierTest test = new SupplierTest();

        String s = supplier.get();
        System.out.println("String 값 : "+ s);

        boolean b = booleanSup.getAsBoolean();
        System.out.println("boolean 값 : "+ b);

        double d = doubleSup.getAsDouble();
        System.out.println("double 값 : "+ d);

        long l = longSup.getAsLong();
        System.out.println("long 값 : "+ l);

        int i = intSup.getAsInt();
        System.out.println("int 값 : "+ i);
    }
}

 

실행 결과

String 값 : helloWorld
boolean 값 : true
double 값 : 13245.123
long 값 : 1234567
int 값 : 123

3) Function 함수적 인터페이스

  • 역할 : 매핑(타입변환)하기
  • 메서드 : applyXXX()
  • 예를들어 List 항목중에서 int 값을 추출하거나 다른 타입으로 변환하는 등의 작업에 사용한다
  • 앞서 실행한 함수적 인터페이스의 결과값이 다음 함수적 인터페이스의 인자값으로 할당되어 최종값을 도출한다.
인터페이스 형태 내용
Function<T, U> T 받아서 U 리턴
BiFunction<T, U, R> T, U 형태를 받아서 R 리턴
XXXFunction<T> XXX 받아서 T 리턴
XXXtoYYYFunction XXX 받아서 YYY 리턴
toXXXFunction<T> T 받아서 XXX 리턴
toXXXBiFunction<T, U> T, U 받아서 XXX 리턴

 

아래 코드를 보자

public class Test01 {
    class Student{
        private int stuNum;
        private String stuName;
        private int math;
        private int english;

        Student(int stuNum, String stuName, int math, int english){
            this.stuNum = stuNum;
            this.stuName = stuName;
            this.math = math;
            this.english = english;
        }
    }

    public static void main(String[] args) {
        Test01 test = new Test01();
        Student st1 = test.new Student(123, "홍길동", 11, 36);

        //매핑 : Student객체 - Student의 Integer 값
        Function<Student, Integer> function = a -> a.stuNum;
        int result01 = function.apply(st1);
        System.out.println("홍길동 번호 : "+ result01);


        //매핑 : 두 Integer 값 - Double 값
        BiFunction<Integer, Integer, Double> biFunction = (a, b) -> (double) (a+b)/2;
        double result02 = biFunction.apply(3, 5);
        System.out.println("두 숫자 평균 : "+ result02);


        //매핑 : Double 값 - Integer 값
        DoubleFunction<Integer> doubleFunction = a -> {
            Double d = Math.floor(a);
            return d.intValue();
        };
        int result03 = doubleFunction.apply(246.71);
        System.out.println("소수점 버리기 : "+ result03);


        //매핑 : Integer, Integer - Double
        ToDoubleBiFunction<Integer, Integer> toDoubleBiFunction;
        toDoubleBiFunction = (math, english) -> (double)(math+english)/2;
        double result04 = toDoubleBiFunction.applyAsDouble(st1.math, st1.english);
        System.out.println("홍길동의 수학 영어 평균 : "+ result04);
    }
}

 

실행 결과

홍길동 번호 : 123
두 숫자 평균 : 4.0
소수점 버리기 : 246
홍길동의 수학 영어 평균 : 23.5

 

이번에는 난이도를 조금 높여보자

 

List 에 Student 객체를 요소로 담는다. ToIntFunction 을 활용해 특정과목의 점수를 가져오도록 매핑 설정을 한다. 이후 메서드를 이용해 원하는 과목의 점수를 가져오도록 메서드도 구현했다.

 

public class Test02 {
    class Student{
        private int stuNum;
        private String stuName;
        private int math;
        private int english;

        Student(int stuNum, String stuName, int math, int english){
            this.stuNum = stuNum;
            this.stuName = stuName;
            this.math = math;
            this.english = english;
        }
    }

    public static void main(String[] args) {
        Test02 test = new Test02();

        List<Student> list = Arrays.asList(
            test.new Student(11, "조성모", 27, 22),
            test.new Student(13, "성시경", 37, 26),
            test.new Student(14, "이승철", 36, 81),
            test.new Student(15, "이수영", 37, 29)
        );


        //매핑 : Student - 수학점수
        ToIntFunction<Student> toIntFunction_math = student -> student.math;
        //매핑 : Student - 영어점수
        ToIntFunction<Student> toIntFunction_english = student -> student.english;


        System.out.println("--수학점수 가져오기");
        for(Student stu : list){
            int math = test.getScore(toIntFunction_math, stu);
            System.out.print(math+ " ");
        }

        System.out.println("\n--영어점수 가져오기");
        for(Student stu : list){
            int english = test.getScore(toIntFunction_english, stu);
            System.out.print(english+ " ");
        }

    }
    //메서드 : function에 해당하는 과목 점수를 반환하기
    public int getScore(ToIntFunction toIntFunction, Student student){
        int score = toIntFunction.applyAsInt(student);
        return score;
    }
}

 

실행 결과

--수학점수 가져오기
27 37 36 37 
--영어점수 가져오기
22 26 81 29 

평균 점수도 구해보자

각 학생의 수학, 영어 평균점수와 모든학생 평균점수를 구해보자

public class Test03 {
    class Student{
        private int stuNum;
        private String stuName;
        private int math;
        private int english;

        Student(int stuNum, String stuName, int math, int english){
            this.stuNum = stuNum;
            this.stuName = stuName;
            this.math = math;
            this.english = english;
        }

        public int getMath(){
            return math;
        }
        public int getEnglish(){
            return english;
        }
    }

    public static void main(String[] args) {
        Test03 test = new Test03();

        List<Student> list = Arrays.asList(
                test.new Student(11, "조성모", 27, 22),
                test.new Student(13, "성시경", 37, 26),
                test.new Student(14, "이승철", 36, 81),
                test.new Student(15, "이수영", 37, 29)
        );

        //매핑 : 수학, 영어 --> 평균점수
        ToDoubleFunction<Student> toDoubleFunction =
                student -> (double)(student.getMath() + student.getEnglish())/2;
        //메서드에 값 넣기
        test.testT(toDoubleFunction, list);
    }

    void testT(ToDoubleFunction function, List<Student> list){
        double sum = 0;
        for(Student stu : list){

            double score = function.applyAsDouble(stu);
            System.out.println(stu.stuName+ "의 평균점수 : "+ score);
            sum += score;
        }
        double stuAverage = sum/list.size();
        System.out.println("점수 총 평균 : "+ stuAverage);
    }
}

 

실행 결과

조성모의 평균점수 : 24.5
성시경의 평균점수 : 31.5
이승철의 평균점수 : 58.5
이수영의 평균점수 : 33.0
점수 총 평균 : 36.875

 


4) Operator 함수적 인터페이스

  • 역할 : 매개값 계산해서 리턴하기
  • 메서드 : applyXXX()
  • 매개값으로 연산을 수행뒤 동일한 타입으로 리턴하는 역할
인터페이스 형태 내용
UnaryOprater<T> T타입 연산하고 리턴
BinaryOperator<T> T타입 연산하고 리턴
XXXUnaryOperator XXX 타입 1개 연산
XXXBinaryOperator XXX 타입 2개 연산

* unary 단항(연산이 1개), binary 이항(연산이 2개)

 

 

아래 코드를 보자

Operator 인터페이스를 활용해 최댓값, 제곱값, 온도단위 바꾸기 등을 연산한다.

public class Test02 {
    int[] numbers = {3, 1, 7, 6, 5};
    double[] celciousArr = {25, 37, 100, 0};

    //함수적 인터페이스를 받아 최댓값을 구하는 메서드
    int getMax(IntBinaryOperator operator){
        int result = numbers[0];
        //int[] 반복 돌면서 지정한 operator 연산수행 -> 연산결과 리턴
        for(int number : numbers){
            result = operator.applyAsInt(result, number);
        }
        return result;
    }

    //함수적 인터페이스를 받아 제곱값을 구하는 메서드
    int[] getSquare(IntUnaryOperator operator){
        int[] intArr = new int[numbers.length];

        for(int i = 0; i< numbers.length; i++){
            intArr[i] = operator.applyAsInt(numbers[i]);
        }
        return intArr;
    }

    //함수적 인터페이스를 받아 섭씨를 화씨로 바꾸는 메서드
    void getSumOfMultiple(UnaryOperator<Double> operator){
        int sum = 0;
        for(double celcious : celciousArr){
            double fahrenheit = operator.apply(celcious);
            System.out.print(fahrenheit+ " ");
        }
    }

    public static void main(String[] args) {
        Test02 test = new Test02();

        //연산식 설정 - 최댓값
        int max = test.getMax(
            (a, b) -> {
                int number = a;
                if (a < b) number = b;
                return number;
            }
        );
        System.out.println("최댓값 : "+ max);

        //연산식 설정 - 제곱값
        System.out.print("제곱값 : ");
        int[] intArr = test.getSquare( a -> a*a );
        for(int d : intArr){
            System.out.print(d + " ");
        }

        //연산식 설정 - 섭씨, 화씨 바꾸기
        System.out.print("\n섭씨 화씨 바꾸기 : ");
        test.getSumOfMultiple(a -> a*9/5 + 32);
    }
}

 

실행 결과

최댓값 : 7
제곱값 : 9 1 49 36 25 
섭씨 화씨 바꾸기 : 77.0 98.6 212.0 32.0 

 


5) Predicate 함수적 인터페이스

  • 역할 : 매개값 확인해서 boolean(true/false) 값 리턴
  • 메서드 : test()
인터페이스 형태 내용
Predicate<T> T 를 받아 boolean 리턴
BiPredicate<T, U> T, U를 받아 boolean 리턴
XXXPredicate XXX를 받아 boolean 리턴

 

아래 코드를 보자

특정 조건에 부합하는 점수만 가져와서 평균을 구하는 코드이다.

public class Test01 {
    private List<Student> list;

    private enum Gender { MALE, FEMALE }

    class Student{
        private String name;
        private Gender gender;
        private int score;

        Student(String name, Gender gender, int score){
            this.name = name;
            this.gender = gender;
            this.score = score;
        }
    }

    public static void main(String[] args) {
        Test01 test = new Test01();

        test.list = Arrays.asList(
            test.new Student("박효신", Gender.MALE, 15),
            test.new Student("김윤아", Gender.FEMALE, 73),
            test.new Student("민경훈", Gender.MALE, 92),
            test.new Student("장혜진", Gender.FEMALE, 47)
        );

        //남성 평균점수 구하기
        Predicate<Student> predicate_male = t -> t.gender.equals(Gender.MALE);
        double avgOfMale = test.getAverage(predicate_male);
        System.out.println("남성점수평균 : "+ avgOfMale);

        //60점 이상일 경우 평균 구하기
        Predicate<Student> predicate_sixty = t -> t.score >= 60;
        double avgOver60 = test.getAverage(predicate_sixty);
        System.out.println("60점이상평균 : "+ avgOver60);
    }

    private double getAverage(Predicate<Student> predicate){
        int count = 0;
        int sum = 0;
        for (Student stu : list){
            if(predicate.test(stu)){
                count++;
                sum += stu.score;
            }
        }
        return (double) sum/count;
    }
}

 

실행 결과

남성점수평균 : 53.5
60점이상평균 : 82.5