[Java] 자바 #46, 람다식의 함수형 인터페이스 개념 및 예제

표준 API함수적 인터페이스


 - JDK 1.8 부터 제공

 - java.util.function 패키지

 - 함수적 인터페이스 집합 패키지 

 - 오로지 람다식만을 지원하기 위해 만들어진 인터페이스 모음

 - 람다식 타겟 타입 = 표준 API 함수적 인터페이스 + 사용자 정의 인터페이스


표준 API함수적 인터페이스 종류

 - 추상메소드 딱 1개

 

1. Consumer : 매개변수O, 반환값X -> 추상메소드 제공.

2. Supplier : 매개변수X, 반환값O

3. Function : 매개변수O, 반환값O, 주로 매개변수를 반환값 타입으로 변환 후 반환

4. Operator : 매개변수O, 반환값O, 주로 매개변수를 연산 후 결과값 반환 역할

5. Predicate : 매개변수O, 반환값O, 주로 매개변수를 조사한 후 논리값 반환 역할



 함수적 인터페이스 예제1, Consumer


1. Consumer 매개변수O, 반환값X

 - accept() 메소드를 제공하는 함수형 인터페이스

 - 매개변수를 받아서 소비하는 일을 구현하는 역할.

 - 다양한 오버로딩 지원.


다음과 같은 인터페이스가 있을 떄!


1
2
3
4
@FunctionalInterface
interface MyConsumer {
    void accept(String txt);
}
cs

- @FunctionalInterface 는 해당 인터페이스를 함수적 인터페이스로 사용하겠다는 어노테이션

- 만약 추상메소드를 하나 더 선언하게되면 문법오류

- Invalid '@FunctionalInterface' annotation; MyConsumer is not a functional interface


기존 람다식 이용

ex)

MyConsumer c1 = (txt) -> {

System.out.println("결과 : " + txt);

};

c1.accept("홍길동");



Consumer 이용

ex)

Consumer<String> c2 = (txt) -> System.out.println("결과 : " + txt);

c2.accept("홍길동");



- Consumer 인터페이스 -> 추상메소드, 인자값 1개 -> 소비 메소드 구현부. ? 전달된 매개변수를 어떤식으로 사용하는지는 제한 없음.(구현하는 개발자 마음)

- 매개변수 1개짜리 Consumer

Consumer<Integer> c3 = count -> {

for (int i = 0; i < count; i++) {

System.out.println(i);

}

};

c3.accept(10); 


매개변수 2개짜리 Consumer

ex)

BiConsumer<String, Integer> bc = (name, age) -> {

System.out.println("이름 : " + name);

System.out.println("나이 : " + age);

System.out.println("결과 : " + ((age > 19) ? "어른" : "아이"));

};

bc.accept("홍길동", 25);


Consumer 심화 예제


1
2
3
interface MyNumber {
    int getNum();
}
cs

다음과 같은 MyNumber 인터페이스가 있을 때!


Consumer<MyNumber> c4 = num -> System.out.println(num.getNum());


c4.accept(new MyNumber() {  A

@Override

public int getNum() {

return 100;

}

});


c4.accept(() -> {  B

return 200;

});


예제 해설 >> 

Consumer는 MyNumber 객체를 매개변수로 갖는 accept(MyNumber num) 형태로 선언하고 그 accept 메소드 내부구현은 에로우 다음에나오는 System.out.println(num.getNum()); 부분입니다.

 

즉, accept() 안에다가 MyNumber 를 구현한 객체든 익명객체든 넣어줘야 한다. 

A부분은 기존 익명객체를 만드는 방법으로 getNum 메소드를 구현한 익명객체를 넣어주었고, B부분은 람다식을 이용해 익명객체를 만들어 대입하였다. 그 객체들의 getNum 메소드는 각각 100과 200을 리턴하게 되므로 순차적으로 100을 출력하고 200을 출력하는 결과를 보이게 된다. 



함수적 인터페이스 예제2, Supplier


2. Supplier 매개변수X , 반환값O

 - getXXX() 매ㅔ소드를 제공하는 함수형 인터페이스

 - 데이터를 공급하는 역할.


기본 예제

ex)

Supplier<Integer> s1 = () -> {

return 100;

};

System.out.println(s1.get());


다음과 같은 클래스가 있을때는!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class User {
    private String name;
    private int age;
 
    public User() {
 
    }
 
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    @Override
    public String toString() {
        return String.format("%s(%s)"this.name, this.age);
    }
 
}
cs


Supplier<User> s3 = () -> new User("아무개", 30);

User u = s3.get();

System.out.println(u); 

>> 이런식으로 객체를 생성하는 생성자를 담을 수도 있다.


Supplier<List<User>> s4 = () -> {

  List<User> list = new ArrayList<User>();

list.add(new User("아무개", 20));

list.add(new User("우성환", 25));

list.add(new User("김김김", 23));

return list;

};

for (User user : s4.get()) {

System.out.println(user);

}


혹은 이런식으로 컬렉션을 반환타입으로 삼고 작업을 한후 해당 컬렉션을 반환하는 형태로도 쓸 수 있다.


>> 즉, 어떤 타입을 반환타입으로 선언하여 return 하는 형태이므로 반환하는 형태를 정해놓고 짜임새 있게 사용하는 방법이 Supplier를 사용하는데 가장 중요한 의미라고 할 수 있다.



함수적 인터페이스 예제3, Function


함수형 인터페이스

 - 람다식을 저장하는 용도(내부적으로는 람다식으로 만들어진 메소드를 소유한 익명객체를 생성 + 저장)


 1. 값 -> Consumer -> (소비)

 -void accept(값)

 

 2. () -> Supplier -> 값 반환.

 -값 get()


 3. 값 -> Function -> 값 반환

 - 값 apply(값)


Function

 - 매개변수와 반환값이 있는 메소드를 제공한다.

 - applyXXX()메소드를 제공하는 함수형 인터페이스.

 - 매개값을 반환값으로 매핑(변환)하는 역할.


- Function<매개변수타입, 반환값 타입>


Function<String, Integer> f1 = (txt) -> { txt 매개변수는 String이고 나는 Integer를 반환할거야

return txt.length();

};

System.out.println(f1.apply("동해물과 백두산이 마르고 닳도록")); //17


Function<Integer, String> f2 = num -> { 

return num > 0 ? "양수" : "음수";

};

System.out.println(f2.apply(7)); "양수"

System.out.println(f2.apply(-1)); "음수"



다음과 같이 Mouse 클래스가 있을 때! (Comparable 구현)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Mouse implements Comparable {
    private String name;
    private int price;
 
    public Mouse() {
 
    }
 
    public Mouse(String name, int price) {
        this.name = name;
        this.price = price;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getPrice() {
        return price;
    }
 
    public void setPrice(int price) {
        this.price = price;
    }
 
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return String.format("[이름 : %s]\n[가격 : %d]\n"this.name, this.price);
    }
 
    @Override
    public int compareTo(Object m) {
 
        if (m instanceof Mouse) {
            Mouse m2 = (Mouse) m;
            if (this.name.equals(m2.getName()) && this.price == m2.price) {
                return 1;
            }
        } else {
            return -1;
        }
        return 0;
    }
}
cs


Function<Mouse, Integer> f3 = mouse -> (int) (mouse.getPrice() * 1.1); 

>> 매개변수 mouse는 Mouse 클래스형이고, 객체로 생성한 생성자의 매개값을 받아서 Price를 초기화한 값으로 1.1을 곱해서 반환하겠다.

System.out.println(f3.apply(new Mouse("TTT-5455", 1250)));

>> 1375



함수적 인터페이스 예제4, Operator


Operator

 - 매개변수와 반환값이 있는 메소드를 제공한다.

 - applyXXX()

 - Function의 하위 호환 인터페이스(서브셋)

 - 주로 매개값을 연산(***)을 통해 결과값을 만들어 내고 그 값을 반환.

 - Function과 큰 차이는 Operator는 매개값과 반환값의 타입이 동일하다.(연산자들의 특징)

ex 1)

BinaryOperator<Integer> b1 = (n1,n2) -> n1+n2; 

System.out.println(b1.apply(3, 5));


ex 2)

BinaryOperator<String> b2 = (firstName, lastName) -> lastName+"가 "+firstName;

System.out.println(b2.apply("길동", "홍"));



함수적 인터페이스 예제5, Predicate


Predicate

 - 매개변수와 반환값이 있는 메소드를 제공한다.

 - testXXX()

 - 매개값을 사용해 조사(조건) 후 논리값을 반환한다.

 - Function 서브 셋.


Predicate<Integer> p1 = n -> n>0;

System.out.println(p1.test(0)?"양수":"양수가 아니다.");

Predicate<String> p2 = s -> s.length()>10;

System.out.println(p2.test("홍길동입니다.")?"긴 문장":"짧은 문장");

Predicate<Student> p3 = s -> (s.getEng()+s.getKor()+s.getMath())>=240;

System.out.println(p3.test(new Student("홍길동",100,80,90))?"합격":"불합격");


- 매개변수가 2개

BiPredicate<String, String> p4 = (s1,s2) -> s1.length()>s2.length();

System.out.println(p4.test("홍길동", "홍민"));



함수적 인터페이스 심화, 인터페이스 합치기


인터페이스를 합치는 작업.

 - 여러 함수형 인터페이스를 하나의 인터페이스로 합치는 작업


1. andThen() 

 - A.andThen(B) -> A와 B 인터페이스의 조합

 - A실행 -> B실행.


2. compose()

 - A.compose(B)

 - B실행 -> A실행


간단예제

Consumer<Mouse> c1 = m -> System.out.println(m.getName());

Consumer<Mouse> c2 = m -> System.out.println(m.getPrice());


Mouse m1 = new Mouse("TT-543 광마우스", 50000);

요구사항 -> m1의 이름과 가격을 출력하라

c1.accept(m1);

c2.accept(m1);


// c3호출 -> c1호출 + c2호출;

Consumer<Mouse> c3 = c1.andThen(c2); c1 실행하고 c2실행하겠다.

c3.accept(m1);


Consumer<Mouse> c4 = c2.andThen(c1);

c4.accept(m1);



Function 예제

- Student 클래스가 다음과 같이 있을 때!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Student {
    private String name;
    private int kor;
    private int eng;
    private int math;
 
    public Student() {
 
    }
 
    public Student(String name, int kor, int eng, int math) {
        this.name = name;
        this.kor = kor;
        this.eng = eng;
        this.math = math;
 
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getKor() {
        return kor;
    }
 
    public void setKor(int kor) {
        this.kor = kor;
    }
 
    public int getEng() {
        return eng;
    }
 
    public void setEng(int eng) {
        this.eng = eng;
    }
 
    public int getMath() {
        return math;
    }
 
    public void setMath(int math) {
        this.math = math;
    }
 
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        int score = this.getEng() + this.getKor() + this.getMath();
        return String.format("이름 : %s,총점 : %d\n"this.name, score);
    }
 
}
cs


Student student = new Student("아무개",100,95,90);


Function<Student, Integer> f1 = s -> s.getKor() + s.getEng() + s.getMath(); // 총점반환.

Function<Integer, String> f2 = n -> n >= 240 ? "합격" : "불합격"; // 점수로 -> 합격여부 반환


int total = f1.apply(student); // 총점반환.

String result = f2.apply(total); // 합격여부 반환

System.out.println(result);


Function<Student, String> f3 = f1.andThen(f2); 

System.out.println(f3.apply(student));  이 두개는 결과가


Function<Student, String> f4 = f2.compose(f1);

System.out.println(f4.apply(student));  같다.



Predicate에만 있는 기능

 - 자바의 논리연산자와 같은 역할.

 1. and()

 2. or()

 3. negate()

 4. isEquals()


Predicate<Integer> p1 = n -> n % 2 == 0;

Predicate<Integer> p2 = n -> n % 5 == 0;


int num = 4;

if (p1.test(num)) {

System.out.println("2의 배수입니다.");

} else if (p2.test(num)) {

System.out.println("5의 배수입니다.");

}

Predicate<Integer> p3 = p1.and(p2);

if (p3.test(num)) {

System.out.println("2와 5의 공배수입니다.");

}



댓글

Designed by JB FACTORY