코린이의 소소한 공부노트

function 패키지와 메서드 본문

Java

function 패키지와 메서드

무지맘 2023. 1. 12. 01:11

[java.util.function 패키지]

- 자주 사용되는 다양한 함수형 인터페이스를 제공하는 패키지

1. 기본 함수형 인터페이스

- 함수는 반환값이 0개 또는 1개이다.

// 1) java.lang.Runnable 인터페이스
void run() // 매개변수X, 반환값X
 
// 2) Supplier<T> 인터페이스
T get() // 매개변수X, 반환값 1개
 
// 3) Consumer<T> 인터페이스
void accept(T t) // 매개변수 1개, 반환값X -> Supplier와 반대
 
// 4) Function<T,R> 인터페이스
R apply(T t) // 매개변수 1개, 반환값 1개 -> 일반적인 함수
 
// 5) Predicate<T> 인터페이스
boolean test(T t) // 매개변수 1개, 반환값 논리형 -> 조건식을 표현하는 데 사용
 
// Predicate 사용 예시
Predicate<String> isEmptyStr = s -> s.length()==0; // 매개변수 s, 반환값 논리형인 람다식
// 지네릭 타입이 String이므로 s의 타입도 String이라는 것을 알 수 있다.
// 람다식을 쓸 때 test()를 호출하면 된다.
String s = "";
if(isEmptyStr.test(s)) // if(s.length()==0)와 같음
    System.out.println("This is an empty String.");
 
// 패키지 관련 간단 퀴즈
[ ➀ ] f = ()-> (int)(Math.random()*100)+1;
[ ➁ ] f = i -> System.out.print(i+", ");
[ ➂ ] f = i -> i%2==0;
[ ➃ ] f = i -> i/10*10;
 
// 정답
➀ Supplier<Integer> // 매개변수X, 반환값 int 1개
➁ Consumer<Integer> // 매개변수 int 1개, 반환값X
➂ Predicate<Integer> // 매개변수 1개, 반환값 논리형
➃ Function<Integer,Integer> // 매개변수 int 1개, 반환값 int 1개

 2. 매개변수가 2개인 함수형 인터페이스

- 매개변수가 없고 반환값만 있는 Supplier의 경우, 반환값이 2개일 경우 함수라고 할 수 없기 때문에 BiSupplier는 없다.

// 1) BiConsumer<T,U> 인터페이스
void accept(T t, U u) // 매개변수 2개, 반환값X
 
// 2) BiPredicate<T,U> 인터페이스
boolean test(T t, U u) // 매개변수 2개, 반환값은 논리형 -> 조건식을 표현하는 데 사용
 
// 3) BiFunction<T,U,R> 인터페이스
R apply(T t, U u) // 매개변수 2개, 반환값 1개
 
// 매개변수를 3개를 받아야 한다면 아래처럼 직접 만들면 된다.
@FunctionalInterface
interface TriFunction<T,U,V,R> {
    R apply(T t, U u, V v);
}

3. 매개변수의 타입과 반환타입이 일치하는 함수형 인터페이스

// 1) UnaryOperator<T> 인터페이스 – 단항 연산자
T apply(T t) // Function의 자손. Function과 달리 매개변수와 결과의 타입이 같다.
 
// 2) BinaryOperator<T> 인터페이스 – 이항 연산자
T apply(T t, T t) // BiFunction의 자손. BiFunction과 달리 매개변수와 결과의 타입이 같다.
 
@FunctionalInterface
public interface Function<T,R> { // 보통의 Function은 매개변수와 반환타입을 함께 표시
    R apply(T t);
}
 
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T,T> { // 단항 연산자는 1개만 표시한다.
    static <T> UnaryOperator<T> identity() { // 항등함수
        return t -> t;
    }
}

4. 예제 코드

import java.util.*;

// main()
Supplier<Integer>  s = ()-> (int)(Math.random()*100)+1; // [1, 100] 사이의 난수를 출력
Consumer<Integer>  c = i -> System.out.print(i+", "); // 입력 받은 숫자를 출력
Predicate<Integer> p = i -> i%2==0; // 입력 받은 숫자가 짝수인지 확인
Function<Integer, Integer> f = i -> i/10*10; // 입력 받은 숫자의 일의 자리를 0으로 만듦

List<Integer> list = new ArrayList<>();	
makeRandomList(s, list);
System.out.println(list); // [95, 75, 21, 87, 29, 94, 19, 6, 68, 72]
printEvenNum(p, c, list); // [94, 6, 68, 72, ] // 짝수만 출력됨
List<Integer> newList = doSomething(f, list);
System.out.println(newList); // [90, 70, 20, 80, 20, 90, 10, 0, 60, 70]

// main()에서 사용한 메서드

// list에 s를 이용해 만든 난수를 저장
static <T> void makeRandomList(Supplier<T> s, List<T> list) {
    for(int i=0;i<10;i++) {
        list.add(s.get());
    }
}

// list에 있는 i에 대하여 p에 대입한 결과(i는 짝수?)가 참이면 c를 이용하여 i를 출력
static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
    System.out.print("[");
    for(T i : list) {
        if(p.test(i))
            c.accept(i);
    }	
    System.out.println("]");
}

// list와 같은 크기의 새 list를 만들어서 각 요소에 f를 수행한 후 list에 저장 후 반환
static <T> List<T> doSomething(Function<T,T> f, List<T> list) {
    List<T> newList = new ArrayList<T>(list.size());
    for(T i : list){
        newList.add(f.apply(i));
    }	
    return newList;
}

 

[Predicate의 결합]

1. and(), or(), negate()로 두 Predicate를 하나로 결합할 수 있다.

- and()는 &&, or()는 ||, negate()는 !의 역할을 하며, 모두 Predicate 인터페이스의 default 메서드이다.

- 참고: 인터페이스는 default / static / 추상 메서드를 가질 수 있다.

Predicate<Integer> p = i -> i < 100; // i < 100이면 true
Predicate<Integer> q = i -> i < 200; // i < 200이면 true
Predicate<Integer> r = i -> i%2 == 0; // i가 짝수이면 true

Predicate<Integer> notP = p.negate(); // i >= 100이면 true
Predicate<Integer> all = notP.and(q).or(r); // 100 <= i && i < 200 || i%2==0
Predicate<Integer> all2 = notP.and(q.or(r)); // 100 <= i && (i < 200 || i%2==0)
// all과 all2의 순서 유의

System.out.println(all.test(2));
// 100 <= i && i < 200 || i%2==0
// ( false && true ) || true
// false || true
// true

System.out.println(all2.test(2));
// 100 <= i && (i < 200 || i%2==0)
// false && ( true || true )
// false && true
// false

 2. 등가비교를 위한 Predicate의 작성에는 isEqual()을 사용한다.

- isEqual()은 Predicate 인터페이스의 static메서드이다.

Predicate<String> p = Predicate.isEqual(str1); // static메서드는 인터페이스이름 필요
boolean result = p.test(str2); // 매개변수로 넘긴 str2가 미리 정해둔 str1과 같은지 비교한 결과를 반환

// 위의 두 줄을 합치면 다음과 같이 쓸 수 있다.
boolean result = Predicate.isEqual(str1).test(str2);

// 위 문장은 다음과 같다.
boolean result = str1.equals(str2);

3. 예제 코드

import java.util.function.*;

Function<String, Integer> f  = (s) -> Integer.parseInt(s, 16); // 16진수 문자열 S를 10진수로 변환
Function<Integer, String> g  = (i) -> Integer.toBinaryString(i); // 입력을 2진수 문자열로 변환

// f와 g를 이용한 합성함수
Function<String, String> h  = f.andThen(g); // f 수행 후 g 수행
Function<Integer, Integer> h2 = f.compose(g); // g 수행 후 f 수행

System.out.println(h.apply("FF")); // "FF" -> f -> 255 -> g -> "11111111"
System.out.println(h2.apply(2));   // 2 -> g -> "10" -> f -> 16

Function<String, String> f2 = x -> x; // 항등 함수(identity function)
System.out.println(f2.apply("AAA"));  // "AAA"

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일때 true

Predicate<Integer> all = notP.and(q.or(r));
System.out.println(all.test(150));
// i >= 100 && (i < 200 || i%2 == 0)
// true && ( true || true )
// true && true
// true

String str1 = "abc";
String str2 = "abc";
		
// str1과 str2가 같은지 비교한 결과를 반환
Predicate<String> p2 = Predicate.isEqual(str1); 
boolean result = p2.test(str2);   
System.out.println(result); // true
// str1과 str2를 new String("abc")로 선언할 경우에는 주소 비교를 하게 되는데
// equals()를 사용하고 있으므로 결과는 마찬가지로 true가 나온다.

 

[함수형 인터페이스를 사용하는 컬렉션 프레임워크의 메서드]

- 간단히 설명하기 위해 와일드카드는 생략한 내용이다.

1. Collection 인터페이스

boolean removeIf(Predicate<E> filter)
// 조건에 맞는 요소를 삭제

2. List 인터페이스

void replaceAll(UnaryOperator<E> operator)
// 모든 요소를 변환하여 대체

3. Iterable 인터페이스

void forEach(Consumer<T> action)
// 모든 요소에 작업 action을 수행

4. 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를 수행

5. 예제 코드

ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++)
    list.add(i);

// list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// list의 모든 요소를 출력
list.forEach(i->System.out.print(i+",")); // 0,1,2,3,4,5,6,7,8,9,
System.out.println();

// list에서 2 또는 3의 배수를 제거한다.
list.removeIf(x-> x%2==0 || x%3==0);
System.out.println(list); // [1, 5, 7]

// list의 각 요소에 10을 곱한다.
list.replaceAll(i->i*10);
System.out.println(list); // [10, 50, 70]

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

// map의 모든 요소를 {k,v}의 형식으로 출력한다.
map.forEach((k,v)-> System.out.print("{"+k+","+v+"},")); // {1,1},{2,2},{3,3},{4,4},
System.out.println();

 

'Java' 카테고리의 다른 글

스트림의 정의와 특징  (0) 2023.01.30
람다식과 메서드 참조  (0) 2023.01.13
함수형 인터페이스의 정의와 활용  (0) 2022.12.29
람다식의 정의와 작성 방법  (0) 2022.12.28
쓰레드의 동기화와 실행제어  (0) 2022.12.09