자바

Predicate, 컬렉션 프레임웍에서 함수형 인터페이스

망재이 2023. 3. 11. 22:26
  • Predicate와 논리 연산자들을 연결해서 하나의 식을 구성할 수 있는데 && -> and(), || -> or(), ! -> negate()로 연결할 수 있음
Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i%2 == 0;
Predicate<Integer> notP = p.negate();  // i>=100;

Predicate<Integer> all = notP.and(q.or(r));
// i>=100 && i<200 || i%2==0 ;
System.out.println(all.test(150));
// 150>=100 && 150<200 || 150%2==0 ;
  • isEqual()도 역시 사용할 수 있는데, 이는 str1.equals(str2)과 같음
Predicate <String> p = Predicate.isEqual(str1);
boolean result = p.test(str2);	

// 같은 뜻!
boolean result = Predicate.isEqual(str1).test(str2)

 

[ 활용 ]

package java14;

import java.util.function.Function;

public class Predicate {

    public static void main(String[] args) {
        // andThen 메소드 -> 두 개의 함수를 더해주는 메소드
        // A함수의 출력 메소드와 B함수의 입력 메소드가 같아야 함
        // compose 메소드 -> andThen과 같은 기능이지만 위치만 다른 것!(둘의 위치만 다른 것이므로 그냥 andThen만 기억해도 무방)
        Function<String, Integer> f = (s) -> Integer.parseInt(s, 16); //문자열을 16진수로 변환
        Function<Integer, String> g = (i) -> Integer.toBinaryString(i); //정수를 2진수 문자열로 변환

        Function<String, String> h = f.andThen(g);
        Function<Integer, Integer> h2 = f.compose(g);

        System.out.println(h.apply("FF")); //"FF" -> 255 -> "11111111" f를 거쳐서 g로 반환
        System.out.println(h2.apply(2)); //2 -> "10" -> 16

        Function<String, String> f2 = x -> x; //항등함수(identify function) 매개변수 반환값 같음
        System.out.println(f2.apply("ABC")); //"ABC"출력

        java.util.function.Predicate<Integer> p = i -> i < 100;
        java.util.function.Predicate<Integer> q = i -> i < 140;
        java.util.function.Predicate<Integer> r = i -> i%2 == 1;
        java.util.function.Predicate<Integer> notP = p.negate();

        java.util.function.Predicate<Integer> all = notP.or(q.and(r));
        //i>=100 || (i<140 && i%2==1)
        System.out.println(all.test(90));
        //90>=100 || (90<140 && 90%2==1)
        //F || (T && F) -> F

        String str = new String("abc");
        String str2 = "abc";
        // -> true 왜냐하면 Predicate.isEqual메소드는 equals와 같기 때문에 주소값을 비교하는게 아니라 값을 비교

        boolean result = java.util.function.Predicate.isEqual(str).test(str2);
        System.out.println(result);

    }
}

 

  • 함수형 인터페이스가 빛을 발하는 순간은 컬렉션 프레임워크와 함께 사용할 때 같다. 그전까진 람다식을 어디서 어떻게 왜 써야하는건지 의문이였는데 이 파트 공부를 하고나서 그런 생각이 완벽하게 깨졌다. 람다식은 꼭 필요한 녀석이다,,,!
인터페이스 메서드 설명
Collection boolean removeIf(Predicate<E> filter) 조건에 맞는 요소를 삭제
List void replaceAll(UnaryOperator<E> operator) 모든 요소를 변환하여 대체
Iterable void forEach(Consumer<T> action) 모든 요소에 작업 action을 수행
Map V compute(K key, BiFunction<K, V, V> f) 지정된 키의 값에 작업 f를 수행
V computeIfAbsent(K key, Function<K, V> f) 키가 없으면, 작업 f 수행 후 추가
V computeIfPresent(K key, Bifunction<K, V, V> f) 지정된 키가 있을 때, 작업 f 수행
V merge(K key, V value, BiFunction<V, V, V> f) 모든 요소에 병합작업 f를 수행
void forEach(BiConsumer<K, V> action) 모든 요소데 작업 action을 수행
void replaceAll(BiFunction<K, V, V> f) 모든 요소에 지환작업 f를 수행

 

[ 활용 ]

package java14;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class CollectionRamda {

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        for(int i=0;i<10;i++) {
            list.add(i);
        }

        //list의 모든 요소 출력
        list.forEach(i -> System.out.println(i+" "));

        //iterator를 이용해서 list출력 -> 람다식을 이용해서 한 줄로 줄일 수 있음!
        Iterator it=list.iterator();
        while(it.hasNext()) {
            System.out.println(it.next());
        }

        //2 또는 3의 배수를 삭제
        list.removeIf(x -> x%2==0 || x%3==0);
        System.out.println(list);

        //list의 모든 요소에 10 곱함
        list.replaceAll(i -> i*10);
        System.out.println(list);

        Map<String, String> map = new HashMap<>();
        map.put("1","a");
        map.put("2","b");
        map.put("3","c");
        map.put("4","d");

        map.forEach((k,v) -> System.out.println("["+k+", "+v+"]"));

        //iterator이용 -> 람다식을 이용해서 한 줄로 표현
        Iterator iterator = map.entrySet().iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        
    }
}

 

  • 좀 더 구체적으로 Map의 메소드들을 살펴보자
  • computeIfAbsent : 키가 없으면 작업 수행 후 추가
map.put("사과",1);
map.put("딸기",1);

map.computeIfAbsent("사과",(key) -> key.length());
map.computeIfAbsent("포도",(key) -> key.length());

System.out.println(map);
//{포도=2, 사과=1, 딸기=1}

 

  • computeIfPresent : 키가 존재하면 작업 수행, 존재하지 않으면 수행하지 않음
map.computeIfPresent("사과", (key, value) -> value + 1);
map.computeIfPresent("멜론", (key, value) -> value + 1);
System.out.println(map);
//{포도=2, 사과=2, 딸기=1}

 

  • compute : 키가 존재하든, 존재하지 않든 작업 수행. 
map.compute("사과", (key, value) -> value+1);
map.compute("멜론", (key, value) -> value+1);
System.out.println(map);
//nullpointexception 에러 -> 멜론의 value값이 null인 상태로 값이 들어왔는데 +1 연산을 수행해서 예외발생

 

  • merge : 위에서 구현한 내용들을 한 줄로 구현할 수 있게 해줌. 
  • 첫 번째 인자는 key를 받는다.
  • 두 번째 인자는 key가 존재하지 않을 경우 삽입될 value를 받는다.
  • 세 번째 인자는 key가 존재할 경우 remapping할 BiFunction을 받는다

첫 번째 인자인 key가 맵에서 존재하지 않으면 두 번째 인자와 함께 map에 삽입되고
key가 맵에서 존재하면 세 번째 인자인 BiFunction의 연산 결과를 value에 remapping한다.

Key가 맵에서 존재할 때 BiFunction의 연산 결과가 null이면 map에서 삭제된다.

map.merge("사과",3,(value, putValue) -> value + 1);
map.merge("멜론",1,(value, putValue) -> value + 1);
map.merge("딸기",3,(value, putValue) -> value + 2);
System.out.println(map);
//{포도=2, 멜론=1, 사과=3, 딸기=3}
728x90