Programming/JAVA

Java - 람다 (기본, 함수형 인터페이스 API)

잇(IT) 2023. 8. 31. 12:31

- 람다식을 알아보기 전 람다의 기본이 되는 인터페이스에 대해 다시 한 번 알아보겠다.

 

- 인터페이스 구현 방법

 

- MyInterface.java

public interface MyInterface {
    public void print();
}

- 일반적인 인터페이스 선언 방식이다.


1. implements 키워드로 클래스 선언

 

- MyClass.java

class MyClass1 implements MyInterface {
    @Override
    public void print() {
        System.out.println("MyClass2");
    }
}
class MyClass2 implements MyInterface {

    @Override
    public void print() {
        System.out.println("MyClass2");
    }
}
public class Class {
    public static void main(String[] args) {

        MyClass1 myClass1 = new MyClass1();
        MyClass2 myClass2 = new MyClass2();
        myClass1.print();
        myClass2.print();
    }
}

- implements를 통해 인터페이스를 상속 받고 클래스 구현체를 작성한 것을 볼 수 있다.

- 클래스의 메서드를 사용하기 위해 객체를 생성하고 인터페이스의 구현체인 print() 메서드를 각각 호출 한 것을 볼 수 있다.


2. 익명 클래스 사용

.....

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

        MyClass1 myClass1 = new MyClass1();
        MyClass2 myClass2 = new MyClass2();
        myClass1.print();
        myClass2.print();

        MyInterface mi = new MyInterface() {
            @Override
            public void print() {
                System.out.println("익명 클래스로 구현");
            }
        };
    }
}

- 익명 클래스는 일반적으로 클래스 선언과 동시에 객체를 생성한다.

- 결국 인터페이스도 구현체가 있다면 객체를 생성할 수 있기 때문에 참조형이 인터페이스인 변수에 인터페이스 객체를 생성함과 동시에 구현체를 작성하는 방법이다.


3. 선언, 생성, 호출을 한번에 처리

.....

(new MyInterface(){
            @Override
            public void print() {
                System.out.println("선언, 생성, 호출을 한번에 처리");
            }
        }).print();

- 매개변수

 

- 인터페이스 타입으로 매개변수가 선언된 경우를 살펴보겠다.

public class Class {


//----------------------------
    public static void test(MyInterface mi) {
        mi.print();
    }
//----------------------------

    public static void main(String[] args) {

        MyClass1 myClass1 = new MyClass1();
        MyClass2 myClass2 = new MyClass2();
        myClass1.print();
        myClass2.print();

        MyInterface mi = new MyInterface() {
            @Override
            public void print() {
                System.out.println("익명 클래스로 구현");
            }
        };

        (new MyInterface(){
            @Override
            public void print() {
                System.out.println("선언, 생성, 호출을 한번에 처리");
            }
        }).print();

//-------------------------------
        test(myClass1);
        test(mi);
//-------------------------------
    }
}

- 위의 코드는 test()의 매개변수로 인터페이스를 받는 것을 볼 수 있다.

- 아래 test() 메서드의 경우 파라미터로 인터페이스 구현체인 myClass1과 익명 클래스 방식을 이용한 인터페이스 객체 mi를 파라미터로 넘긴 것을 확인 할 수 있다.


- 리턴 타입
public class Class {

    public static MyInterface test2() {
        MyInterface mi = new MyInterface() {
            @Override
            public void print() {
                System.out.println("test2() 메서드에서 반환된 MyInterface");
            }
        };
        return mi;
    }
    
    MyInterface mi2 = test2();
        mi2.print();
          }
}

- 리턴 타입을 인터페이스로 받는 경우, 위 코드처럼 test2() 메서드 안에 익명 클래스로 인터페이스를 구현한 객체가 있기 때문에 반환이 가능하다.


- 람다식 사용하기
- 람다식 기본
public static MyInterface test3() {
        return new MyInterface() {
            @Override
            public void print() {
                System.out.println("Hello");
            }
        };
    }
    
    MyInterface m = test3();
        m.print();

- 기존의 인터페이스는 위와 같이 익명 클래스 사용방식을 이용하여 인터페이스를 구현했다.

 

MyInterface m2 = () -> System.out.println("Hello");
        m2.print();

- 람다식을 이용하면 위의 복잡한 코드가 아래와 같이 깔끔하게 작성 가능한 것을 볼 수 있다. 

- new MyInterface() -> () / test(), print() 메서드 사라짐 / 구현체만 남음

 

- 기본 문법
() -> 명령문;

() -> {
	명령문1;
    명령문2;
    명령문3;
};

 

- 람다식 매개변수
(변수명) -> 명령문;

(변수명1, 변수명2, ... , 변수명n) -> 명령문;

// () 안에 타입을 지정하고 싶다면 다음처럼 할 수도 있다.
(타입 변수명) -> 명령문;
class Verify1 implements Verify {

    @Override
    public boolean check(int n) {
        return (n % 2) == 0;
    }
}

public class Class {

	public static void main(String[] args) {
    
    Verify v1 = new Verify1();
        System.out.println(v1.check(10));

        Verify v2 = (n) -> (n * 2) == 20;
        System.out.println(v2.check(10));
    }
}

 

- 람다식 블록
// 람다식 본문이 한 줄일 때
() -> 명령문;

// 람다식 본문이 여러 줄일 때
() -> {
	명령문1;
    명령문2;
    return 값;
};
interface NumberFunc {
    int func(int n);
}

public class Test05 {

    public static void main(String[] args) {
        NumberFunc sum = (n) -> {
            int result = 0;
            for (int i = 0; i <= n; i++) {
                result += i;
            }
            return result;
        };

        System.out.println("1부터 10까지의 합 : " + sum.func(10));
        System.out.println("1부터 10까지의 합 : " + sum.func(100));
    }
}

- 메서드 참조

- 메서드 참조 방식은 람다식의 구현체가 길어 질 경우 코드의 가독성이 떨어질 수 있기 때문에, 인터페이스 메서드를 특정 클래스의 메서드를 통해 구현 시키는 방식이다.

- 단, 메서드 참조 방식은 반환 타입, 매개변수가 동일해야만 사용할 수 있다.

 

- 인터페이스 객체 생성
클래스명::메서드명			 - static 메서드로 선언했을 때
참조변수명::메서드명			- 인스턴스 메서드로 선언했을 때

 

- static 메서드 참조

interface StringFunc {
    String modify(String s);
}

public class Test10 {

    static String func(String s) {
        String result = "";
        char c;
        for (int i = 0; i < s.length(); i++) {
            c = s.charAt(i);
            if(c==',')
                result += " ";
            else result += c;
        }
        return result;
    }

    public static void main(String[] args) {

        StringFunc sf = Test10::func;

        String str = "Korea,Australia,China,Germany,Spain,Turkey";
        String result = sf.modify(str);
        System.out.println(result);
    }
}

- Test10::func는 Test10 클래스의 func() 메서드를 참조하겠다는 의미이다.

- 참조 메서드의 경우 주의 할 점은 람다식을 구현하는 메서드의 리턴 타입과 매개변수는 함수형 인터페이스의 추상 메서드와 동일해야 한다는 점이다.

 

- 인스턴스 메서드 참조

String func1(String s) {
        String result = "";
        char c;
        for (int i = 0; i < s.length(); i++) {
            c = s.charAt(i);
            if(c==',')
                result += " ";
            else result += c;
        }
        return result;
    }
    
public static void main(String[] args) {
    
    Test10 obj = new Test10();
        StringFunc sf1 = obj::func1;
        
        String str1 = "Korea,Australia,,Germany,Spain,";
        String result1 = sf1.modify(str1);
        System.out.println(result1);
    }
}

- 인스턴스 메서드의 경우 클래스의 객체를 생성해 준 다음 메서드 참조하는 방식으로 사용해야 한다.


- 함수형 인터페이스 API

- 기본적인 함수형 인터페이스는 java.util.function 패키지에서 제공한다.

인터페이스 메서드 설명
Fuction<T,R> R apply(T) T 타입 인자 처리 후 R 타입 값 반환
Predicate<T> boolean test(T) T 타입 인자 처리 후 boolean 값 반환
Consumer<T> void accept(T) T 타입 인자 처리(반환값 없음)
Suppiler<T> T get() 인자 없이 처리 후 T 타입 값 반환

 

- Function

- 인터페이스 : Function<T, R>

- 메서드 : R apply(T)

 

- apply() 메서드는 매개변수 T와 리턴 타입 R로 선언되어 있다.

 

package ch14;

import java.util.function.Function;

public class Test14 {
    public static void main(String[] args) {
        Function<String, Integer> func = (s) -> {
            int cnt = 0;
            String[] word = s.split(" ");
            cnt = word.length;
            return cnt;
        };

        int wordCnt = func.apply("고개를 들어 별들을 보라 당신 발만 내다 보지말고");
        System.out.println("단어 수 : " + wordCnt);
    }
}

- Ramda.java

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

public class Ramda {

    static void execute(MyFunction f) { // 매개변수의 타입이 MyFunction인 메서드
        f.run();
    }

    static MyFunction getMyFunction() { // 반환 타입이 MyFunction인 메서드
//        MyFunction f = () -> System.out.println("f3.run()");
//        return f;
        return () -> System.out.println("f3.run()");
    }

    public static void main(String[] args) {
        // 람다식으로 MyFunction의 run()을 구현
        MyFunction f1 = () -> System.out.println("f1.run()");

        MyFunction f2 = new MyFunction() { // 익명클래스로 run()을 구현
            @Override
            public void run() { // public을 반드시 붙여야 함
                System.out.println("f2.run()");
            }
        };

        MyFunction f3 = getMyFunction();

        f1.run();
        f2.run();
        f3.run();

        execute(f1);
        execute(() -> System.out.println("run()"));
    }
}

 

- ExFunction.java

import java.util.function.Function;

public class ExFunction {
    public static void main(String[] args) {
        
        Function<Integer, Integer> f1 = new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return integer / 10 * 10;
            }
        };

        Function<Integer, Integer> f2 = i -> i / 10 * 10;
        
        int inputValue = 25;
        
        int result1 = f1.apply(inputValue);
        int result2 = f2.apply(inputValue);

        System.out.println("f1("+inputValue +") = "+result1);
        System.out.println("f2("+inputValue +") = "+result2);
    }
}

- Predicate

- 인터페이스 : Predicate<T>

- 메서드 : boolean test(T)

 

- test() 메서드는 매개변수 T와 리턴 타입은 boolean으로 선언되어 있다.

public class Test15 {

    public static void main(String[] args) {
        Predicate<Integer> func = (n) -> n % 2 == 0;
        if(func.test(123))
            System.out.println("짝수입니다");
        else
            System.out.println("홀수입니다.");
    }
}

- Consumer

- 인터페이스 : Consumer<T>

- 메서드 : void accept(T)

 

- accept() 메서드는 매개변수 T와 리턴 타입은 void로 선언되어 있다.

public class Test16 {

    public static void main(String[] args) {
        Consumer<Date> date = (d) -> {
            String s = new SimpleDateFormat("YY-MM-dd").format(d);
            System.out.println(s);
        };
        date.accept(new Date());
    }
}

- Supplier

- 인터페이스 : Supplier<T>

- 메서드 : T get()

 

- get() 메서드는 전달받은 인자가 없고 리턴 타입은 T로 선언되었다.

public class Test17 {

    public static void main(String[] args) {
        Supplier<String> day = () -> new SimpleDateFormat("E요일").format(new Date());
        String result = day.get();
        System.out.println(result);
    }
}
728x90

'Programming > JAVA' 카테고리의 다른 글

JAVA - Stream API (1) (스트림 생성 방법)  (0) 2023.06.08
JAVA - 열거형  (0) 2023.06.07
JAVA - Optional  (0) 2023.06.05
JAVA - Reflection API  (0) 2023.06.01
JAVA - 람다 (기본)  (0) 2023.05.18