JAVA 람다식

금송·2024년 9월 27일
1

이론

목록 보기
20/26
post-thumbnail

람다식

2010년대 병렬 처리, 이벤트 지향 프로그래밍에 적합한 함수적 프로그래밍이 다시 부각되면서 자바도 새로운 개편을 하게 된다.

그 중에 하나의 개념이 람다.

람다식은 함수(메서드)를 간단한 식(expression)으로 표현하는 방법으로 익명함수를 생성하기 위한 식 이라고도 할 수 있다.

자바에서는 하나의 객체로 취급이 된다.

자바에서 수용한 이유 - 자바8부터 람다식 지원

  • 코드가 매우 간결해진다.
  • 컬렉션 요소를 필터링 하거나 매핑해서 원하는 결과를 쉽게 집계할 수 있다.

객체지향언어 - 람다식 (=함수를 간단하게 표현한 형태)

Runnable 인터페이스의 익명 구현 객체를 생성하는 전형적인 코드와 람다식 코드

// 전형적인 코드
Runnable runnable = new Runnable() { // 익명 구현 객체
	public void run() {...}
}
//람다식 표현  (매개변수) -> {실행코드}
Runnable runnable = () -> {...}; // () 이후 람다식

기본 문법

(타입 매개변수, ...) -> { 실행문; ...; }

(타입 매개변수, …)는 오른쪽 중괄호 {} 블록을 실행하기 위해 필요한 값을 제공하는 역할을 함.

매개변수 이름은 개발자가 자유롭게 설정.

→ 해당 기호는 매개변수를 이용하여 중괄호 {}를 실행한다는 의미.

// int 매개변수 a의 값을 콘솔에 출력하기 위해 지정한 람다식 그리고 축약 과정
(int a) -> { System.out.println(a);}
// 1. 매개변수 타입은 런타입 시 대입되는 값에 따라 자동으로 인식될 수 있기 때문에 람다식에서는 매개 변수의 타입을 일반적으로 언급하지 않음.
(a) -> {System.out.println(a);}    
// 2. 하나의 매개변수만 있다면 괄호 생략 가능, 하나의 실행문이 있다면 중괄호도 생략 가능.
a -> System.out.println(a)

// 매개변수가 없다면 람다식에서 매개변수 자리가 없어지므로 빈괄호 반드시 사용
() -> {실행문 ...}

// 중괄호 안의 실행문을 실행하고 결과값을 리턴해야한다면
(x, y) -> {return x + y;};  // 해당 식을 더 축약한다면 (x , y) -> x + y;

람다식 변경 연습

int max(int a, int b) {
    return a > b ? a : b;
}
// (a, b) -> a > b ? a : b;

int print(String name, int i) {
    System.out.println(name+"="+i);
}
//(name, i) -> System.out.println(name + "=" + i);

int square(int x) {
    return x * x;
}
// x -> x * x;

함수형 인터페이스

함수형 인터페이스는 단 하나의 추상 메서드만 선언된 인터페이스를 이야기한다.

아래 gif를 보면 method()라는 기존 메서드가 있는데 otherMethod를 선언하니 발생하는 오류.

@FunctionalInterface
public interface MyFuntionalInterface {
    public void method();
    public void method2();    // 컴파일 오류 발생
}

매개 변수와 리턴값이 없는 람다식

@FunctionalInterface
public interface MyFuntionalInterface {
    public void method();
}

해당 인터페이스를 타겟 타입으로 갖는 람다식의 형태

MyFunctionalInterface finter = () -> {...}

// 람다식이 대입된 인터페이스의 참조변수를 호출하는 방법
finter.method();

매개 변수가 있는 람다식

//매개변수가 있고 리턴값이 없는 추상 메소드를 가진 함수형 인터페이스
@FunctionalInterface
public interface MyFuntionalInterface2 {
	void method(int x);
}
MyFunctionalInterface finter = (x) -> { ... };

// 해당 메서드 호출 실행문
finter.method(5);

리턴값이 있는 람다식

// 리턴값이 있는 람다식
@FunctionalInterface
public interface MyFuntionalInterface3 {
    int method(int x, int y);
}
MyFunctionalInterface fi = (x, y) -> {...; retrun 값; }

//만약 중괄호에 return문만 있고 그 뒤에 연산식이나 메서드 호출이 오는 경우라면 다음과 같이 작성 가능
MyFunctionalInterface fi = (x, y) -> {return x + y;} == MyFunctionalInterface fi = (x, y) -> x + y;

함수형 인터페이스의 장점

함수형 인터페이스는 재사용 가능한 코드를 만들 수 있는 장점이 있다.

반복적인 조건이나 동작을 매번 새롭게 클래스 혹은 메소드를 만들어서 작성하는 대신, 필요한 부분만 매개변수화 하여 사용할 수 있다.

또한 간결하고 직관적인 코드를 작성할 수 있다.

// 람다식을 사용하지 않음
Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from a thread!");
    }
};
new Thread(task).start();

// 람다식을 이용한 방식
Runnable task = () -> System.out.println("Hello from a thread!");
new Thread(task).start();

java.util.function 패키지

JDK에서 제공해주는 함수형 인터페이스

  • 리턴타입
  • 매개변수

Runnable

  • 매개변수와 리턴 값 모두 없는 경우
package java.lang;

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

아래 예시처럼 매개변수와 리턴값(타입)이 없는 람다식을 참조 가능하다.

Runnable r = () -> System.out.println("출력문 테스트");
r.run();    // "출력문 테스트" 

Supplier

  • 매개변수는 없고, 리턴값(타입)이 있다.
package java.util.function;

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

아래 예시처럼 매개변수가 없고, 리턴타입이 있는 람다식을 참조 가능하다.

Supplier<String> s = **() -> "리턴되는 값";**
String result = s.get();
System.out.println(result);   // "리턴되는 값"

Consumer

  • Supplier와 반대로, 매개변수는 있지만 리턴타입이 없다
  • 매개변수는 있지만 리턴타입이 없는 람다식을 참조 가능하다.
package java.util.function;

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
Consumer<String> c = (a) -> System.out.println(a);
c.accept("consumer");

Function<T, R>

  • 하나의 매개변수를 받아서 하나의 결과를 리턴한다.
  • 매개변수를 받아서 하나의 결과를 리턴하는 람다식을 참조 가능하다.
package java.util.function;

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
Function<Integer, String> f = a -> String.valueOf(a);
Function<String, Integer> f2 = b -> {
    return Integer.valueOf(b) + 100;
};

Predicate

  • 조건식을 표현하는데 사용된다.
  • 매개변수는 하나, 리턴타입은 boolean을 갖는 함수형 인터페이스이다.
package java.util.function;

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

하나의 매개변수, 리턴타입이 boolean인 람다식을 참조한다.

Predicate<String> isEmptyStr = s -> s.length()==0;
String str = "";
if (isEmptyStr.test(str)) { // if(s.length()==0)
		System.out.println("This is an empty String.")
}
// 스트림에서 filter메소드 내부에는 Predicate 타입이 들어감
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.stream()
		.filter(x -> x%2==0)
		.collect(Collectors.toList()); // [2,4] 
  • 실습 코드
    package chap12.custom;
    
    @FunctionalInterface
    public interface MyFuntionalInterface {
        void run();
    }
    package chap12.custom;
    
    @FunctionalInterface
    public interface MyFuntionalInterface2 {
        void method(int x);
    }
    package chap12.custom;
    
    // 매개변수와 리턴타입이 있는 추상 메서드
    
    @FunctionalInterface
    public interface MyFuntionalInterface3 {
        int method(int a);
    }
    package chap12.custom;
    
    // 매개변수와 리턴타입이 있는 추상 메서드
    
    @FunctionalInterface
    public interface MyFuntionalInterface4 {
        int method(int a, int b);
    }
    package chap12.custom;
    
    // 매개변수와 리턴타입이 있는 추상 메서드
    
    public class MyFuntionalInterface4Impl implements MyFuntionalInterface4 {
        @Override
        public int method(int a, int b){
            return a + b;
        };
    }
    package chap12;
    
    import chap12.custom.MyFuntionalInterface;
    import chap12.custom.MyFuntionalInterface2;
    import chap12.custom.MyFuntionalInterface3;
    import chap12.custom.MyFuntionalInterface4;
    
    public class LamdaExample {
        public static void main(String[] args) {
    //        Runnable runnable = new Runnable() {
    //            @Override
    //            public void run() {
    //                System.out.println("익명 구현 객체의 메서드")
    //            }
    //        };      // 익명 구현 객체 (구현체의 이름이 익명)
            // 람다식 : 메소드를 간결하게 표현한 식
            // 자바에서는 람다식을 객체로 취급할 수 있음 (Java 8 부터)
            Runnable runnable = () -> System.out.println("익명 구현 객체의 메서드");      // 람다식
    
            MyFuntionalInterface inter = new MyFuntionalInterface() {
                @Override
                public void run() {
                    System.out.println("Hello Lambda");
                }
            }; // 익명 구현 객체
            inter.run();
    
            MyFuntionalInterface inter2 = () -> System.out.println("Hello Lambda");
            inter2.run();
            // 매개변수가 있는 람다식
            MyFuntionalInterface2 inter3 = (x) -> System.out.println(x);
            inter3.method(12);
    
            // 매개변수와 리턴타입이 있는 람다식 (매개변수 1개)
            MyFuntionalInterface3 inter4 = (i) -> i*2;
            int result = inter4.method(12);
            System.out.println("result: " + result);
    
            // 매개변수와 리턴타입이 있는 람다식 (매개변수 2개)
            MyFuntionalInterface4 inter5 = (i, j) -> {
                System.out.println("매개변수가 2개인 method");
                return i * j;
            };
            int result02 = inter5.method(12, 5);
            System.out.println("result02: " + result02);        // 60
    
            MyFuntionalInterface4 inter6 = (i, j) -> i + j; // 익명 구현 객체
            int result03 = inter6.method(12, 5);
    
        }
    }
    package chap12;
    
    import java.util.function.*;
    
    public class LamdaExample2 {
        public static void main(String[] args) {
            Supplier<String> supplier = () -> "str";
            String getStr = supplier.get();
            System.out.println(getStr);
    
            IntSupplier supplier2 = () -> 310;
            int getInt = supplier2.getAsInt();
            System.out.println(getInt);
    
            //Consumer
            Consumer<String> consumer = a -> System.out.println(a);
            consumer.accept("nwjns");
    
            //Function
            Function<Integer, String> function = a -> String.valueOf(a) + ":문자열"; // == x + "문자열"
            System.out.println(function.apply(3458));
    
            Function<String, Double> function2 = x -> Double.valueOf(x) * 1.2;
            Double resultFunction = function2.apply("56.7");
            System.out.println("String to Double: " + resultFunction);
    
            // Predicate 구현 객체를 람다식으로 작성
            Predicate<String> isEmpty = x -> x.length() == 0;      // == Predicate<String> isEmpty = String::isEmpty;
            System.out.println("빈값 체크 : " + isEmpty.test(""));
            System.out.println("빈값 체크 : " + isEmpty.test("123433"));
            Predicate<Integer> predicate = x -> x % 2 == 0;
            System.out.println("짝수 체크 : " + predicate.test(0));
            System.out.println("짝수 체크 : " + predicate.test(19));
    
        }
    }
    

메서드 참조

Method Reference, 말 그대로 메서드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내, 람다식에서 불필요한 매개 변수를 제거하는 것이 목적.

종류람다메소드 참조
정적(static) 메소드 참조(x) → ClassName.method(x)ClassName::method
인스턴스 메소드 참조(obj, x) → obj.method(x)ClassName::method
// 두 개의 값을 받아 큰 수를 리턴하는 Math 클래스의 max() 정적 메소드를 호출하는 람다식
(left, right) -> Math.max(left, right) // == Math::max

메소드 참조도 람다식과 마찬가지로 인터페이스의 익명 구현 객체로 생성되므로 타겟 타입인 인터페이스의 추상 메소드가 어떤 매개 변수를 가지고, 리턴 타입이 무엇인가에 따라 달라진다.

// IntBinaryOperator 인터페이스는 두 개의 int 매개값을 받아 int 값을 리턴하므로 Math::max 메소드 참조를 대입할 수 있다.
@FunctionalInterface
public interface IntBinaryOperator {
    int applyAsInt(int left, int right);
}

// Math.max 메서드
public static int max(int a, int b){
	return Math.max(a, b);
}

// 람다식을 '메서드 참조'로 변경 과정
IntBinaryOperator operator = (a, b) -> Math.max(a, b); // 1단계
IntBinaryOperator operator = Math::max; // 2단계

메서드 참조는 정적 메서드 또는 인스턴스 메서드를 참조할 수 있고, 생성자 참조도 가능.

profile
goldsong

0개의 댓글

관련 채용 정보