본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석11-2 java.util.Function 패키지의 함수형 인터페이스와 메서드 참조

by 이쟝 2022. 1. 16.

java.util.function 패키지

java.util.function패키지에 일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해놓았다. 

 

- java.util.function 패키지의 주요 함수형 인터페이스

 

 

-> Runnable은 입출력 없고 <-> Function 입출력 있음(일반적인 함수 반드시 두 개의 타입 지정)

-> Consumer은 입력만 있고 출력이 없다. <-> Supplier는 입력이 없고 출력만 있다. 

-> Predicate는 Function의 변형으로 반환값이 boolean이라는 것만 제외하면 Function과 동일하다.(조건식을 함수로 표현하는데 사용함): 반환 값이 ture or false

-> 매개변수의 타입으로 보통 'T'를 사용하기 때문에 알파벳에서 'T'이 다음 문자인 'U', 'V', 'W'를 매개변수의 타입으로 사용하는 것일뿐 별다른 의미는 없다! 

 

문제 풀어보기

 

더보기

1) Supplier<Integer> 1 ~ 100 난수 생성, 입력 xx 출력만

2) Consumer<Integer> 입력만 있고 출력 xx

3) Predicate<Integer> 입출력 모두 있고, 조건식을 표현 (입력값이 i, i는 2의 배수인가?)

-> 원래는 Function<T,R>처럼 Predicate<Integer, Boolean>이라고 써야 하지만 반환타입이 항상 Boolean이어서 Boolean 사용 xx

4) Function<Integer> 입출력 모두 있음(입력값이 i, i/10*10을 구하기)

 

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

 

 

-> 이름 앞에 접두사 ‘BI’(Binary)가 붙는다.

-> BiConsumer<T,U> 2개의 입력, 출력 xx

-> BiPredicate<T,U> 2개의 입력, 출력은 Boolean(true or false)

-> BiFunction<T,U,R> 2개의 입력, 출력은 R

 

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

 

 

-> UnaryOperator 단항연산자, BinaryOperator 이항연산자 / 둘 다 입출력 일치

 


 

- 컬렉션 프레임웍과 함수형 인터페이스

- 함수형 인터페이스를 사용하는 컬렉션 프레임웍의 메서드(와일드 카드 생략)

 

 

-> Map인터페이스에 있는 ‘compute’로 시작하는 메서드들은 맵의 value를 변환하는 일을 하고 merge( )Map을 병합하는 일을 한다.

-> 기존의 컬렉션 프레임웍을 쓰면 Iterator를 사용해서 요소를 가져왔었어야 했는데 람다식으로 변형하면서 코드를 훨씬 간결하게 해주었다.

 

list.forEach(i->System.out.print(i+" "));   // list의 모든 요소 출력
list.removeif(x -> x%2==0 || x%3==0);       // 2 또는 3의 배수 제거
list.replaceAll(i -> i*10);                 // 모든 요소에 10을 곱하기

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

 

예제1) 

 

import java.util.*;
public class LambdaEx4 {

	public static void main(String[] args) {
		ArrayList<Integer> list = new ArrayList();  // Integer형인 ArrayList 객체 생성
		for(int i=0;i<10;i++) {
			list.add(i);                            // Arraylist에 0부터 9까지 차례대로 삽입
		}
		
		// list의 모든 요소를 출력
		list.forEach(i->System.out.print(i+" "));
		System.out.println();
		
		// list에서 2 또는 3의 배수를 제거한다.
		list.removeIf(i -> i%2==0 || i%3==0);
		System.out.println(list);
		
		list.replaceAll(i->i*10);;  // list 각 요소에 10을 곱한다.
		System.out.println(list);
	
		Map<String, String> map = new TreeMap<>();
		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.println("{"+k+","+v+"},"));
	}
}

 

더보기

<출력값>

0 1 2 3 4 5 6 7 8 9

[1, 5, 7]

[10, 50, 70]

{1,1},

{2,2},

{3,3},

{4,4},

 


 

예제2)

 

import java.util.*;
import java.util.function.*;
public class Ex14_2 {

	public static void main(String[] args) {
		Supplier<Integer> s = ()-> (int)(Math.random()*100)+1;  // 1~100난수 Supplier는 입력xx 출력만
		Consumer<Integer> c = i -> System.out.print(i + " "); // i의 출력 Consumer는 입력만 출력 xx
		Predicate<Integer> p = i -> i%2==0;  // 짝수인지 검사 Predicate는 입출력 oo 출력값이 boolean
		Function<Integer,Integer> f = i -> i/10*10; // i의 일의 자리를 없앤다. Function은 입출력 oo 
		
		List<Integer> list = new ArrayList<>();  
		makeRandomList(s, list);  // list를 랜덤값(s)으로 채운다. (makeRandomList 메서드 호출)
		System.out.println(list);
		
		printEvenNum(p, c, list);  // 짝수를 출력
		List<Integer> newList = doSomething(f, list);
		System.out.println(newList);

	}

	static <T> void makeRandomList(Supplier<T> s, List<T> list) {
		for(int i = 0; i < 10; i++) {
			list.add(s.get());  // Supplier로부터 1~100의 난수를 받아서 list에 추가
		}
	}

	static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
		System.out.print("[");
		for(T i : list) {
			if(p.test(i))     // Preadicate의 메서드인 test를 통해 짝수 검사
			  c.accept(i);  // System.out.print(i + ", "): 화면에 i 출력
		}
		System.out.println("]");
	}

	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));  // 일의 자리를 없애서 새로운 list에 저장
		}
		return newList;
	}
}

 

더보기

<출력값>

[24, 54, 67, 25, 21, 87, 85, 71, 89, 76]

[24 54 76]

[20, 50, 60, 20, 20, 80, 80, 70, 80, 70]

 

->

Predicte 메서드 test(): true if the input argument matches the predicate, otherwise false (파라미터로 T)

Consumer 메서드 accecpt(): Performs this operation on the given argument. (파라미터로 T)

 


Function의 합성과 Predicate의 결합

- and( ): &&, or( ): ||, negate( ): !로 두 Predicate를 하나로 결합(default 메서드)

-> Predicate는 함수형 인터페이스(인터페이스는 defult 메서드, static 메서드, 추상메서드를 가진다.)

 

Predicate<Integer> p1 = i -> i<100;    // i는 100보다 작아야 함
Predicate<Integer> q = i -> i<200;    // i는 200보다 작아야 함
Predicate<Integer> r = i -> i%3==0;   // i는 3의 배수
Predicate<Integer> notP1 = p.negate(); // i는 p의 반대 -> i >= 100

// i는 100보다 크거나 같고 i는 200보다 작거나 3의 배수(100<=i && i<200 || i%3==0)
Predicate<Integer> all = notP1.and(q.or(r));

//Predicate<Integer> all = notP1.and(i->i<200).or(i->i%3==0); // 위의 코드와 동일
System.out.println(all.test(169));  // true(100보다 크고, 200보다 작기때문)

 

- 등가비교를 위한 Predicate의 작성에는 isEqual()를 사용(static 메서드)

(1) Predicate에는 입력 값이 하나이기 때문에 먼저 isEqual를 이용해서 조건식을 하나 만들고

(2) 두 번째 비교할 것은 test 메서드를 이용해서 비교

 

String str1 = "ABC";
String str2 = "ABC";
		
Predicate<String> p3 = Predicate.isEqual(str1);
boolean result = p3.test(str2);  // Predicate.isEqual(str1)이 생략
System.out.println(result);  // true
		
//위의 문장을 한 문장으로 줄여쓸 수 있음
boolean result2 = Predicate.isEqual(str1).test(str2);
System.out.println(result2);  // true

 

예제)

 

import java.util.function.*;
public class Ex14_3 {

	public static void main(String[] args) {
		Function<String, Integer> f = (s) -> Integer.parseInt(s,16); //(1)
		Function<Integer, String> g = (i) -> Integer.toBinaryString(i); //(2)
		
		Function<String, String>  h = f.andThen(g); //(3) 
		Function<Integer, Integer> h2 = f.compose(g); //(4)
		
		System.out.println(h.apply("FF")); // (5)"FF" -> 255 -> "11111111"
		System.out.println(h2.apply(2));   // 2 -> "10" -> 16
		
		Function<String, String> f2 = x -> x; // 항등 함수(identity function)
		System.out.println(f2.apply("AAA"));  // AAA가 그대로 출력됨

	}
}

 

더보기

<출력값>
11111111
16
AAA

-> 
(1)
Function f에서 String은 입력, Integer은 출력, 문자열 s가 들어오면 s를 16진수로 해석해서 숫자로 바꿔라!
(2)
Function g에서 Integer은 입력, String은 출력, 숫자 i가 들어오면 2진수로 변환해서 문자열로 바꿔라!

(3) andThen( )으로 기존의 두 함수를 하나로 연결해서 새로운 함수 h를 생성
f의 출력부분과 g의 출력부분이 같아야 함수를 하나로 연결할 수 있다.
andThen: Returns a composed function that first applies this function to its input, and then applies the after function to the result.
f.andThen(g) : Function f의 입력 String + Function g의 출력 = Function h의 입력 -> String, 출력 -> String

(4) f.compose(g)는 g.andThen(f)와 같음
Function g의 입력 Integer + Function f의 출력 = Function h2의 입력 -> Integer 출력 -> Integer

(5) Function f에서 하는 작업 “FF” -> 255 / Function g에서 하는 작업 255 -> “11111111”


메서드 참조(method reference)

- 하나의 메서드만 호출하는 람다식은 메서드 참조로 간단히 할 수 있다.

- 클래스 이름 :: 메서드 이름

 

 

-> 특정 객체 인스턴스메서드 참조는 사용하지 않음

 

- static메서드 참조와 인스턴스메서드 참조

 

Integer method (String s) { // 그저 Integer.parseInt(String s) 호출 문자열 s -> 숫자형 s로 변환

       return Integer.parseInt(s);

}

 

-> 입력값이 String, 출력값이 Integer라는 것을 알고 있고, parseInt의 매개변수로는 String이 온다는 것을 알고 있기 때문에 굳이 적을 필요 없음

 

<람다식>

Function<String, Integer> f = (String s) -> Integer.parseInt(s); 

-> 입력 String, 출력 Integer, 매개변수 String 

 

<메서드 참조>

Function<String, Integer> f = Integer::parseInt; 

-> 더 짧게 메서드 참조로 나타낼 수 있음

-> 메서드 참조를 람다식으로 바꿀 수 있을 줄 알아야 함!(1) 입력과 출력을 먼저 생각해보고 (2) 메서드 생각해보기

 

//Function<String,Integer> f = (String s) -> Integer.parseInt(s);
//Function<String,Integer> f = 클래스이름::메서드이름;

Function<String,Integer> f = Integer::parseInt; // 클래스:Integer, 메서드:parseInt
System.out.println(f.apply("100")+200);         // 300

 


생성자의 메서드 참조

 

Supplier<MyClass> s = () -> new MyClass(); // 매개변수 없는 경우의 생성자
Supplier<MyClass> s = MyClass::new;        // 메서드 참조
		
Function<Integer, MyClass> s = (i) -> new Myclass(i);  // 매개변수 하나 있는 경우
Function<Integer, MyClass> s = MyClass::new;           // 메서드 참조
		
-배열과 메서드 참조
Function<Integer, int[]> f = x -> new int[x];  // 람다식
Function<Integer, int[]> f2 = int[]::new;       // 메서드 참조
-> 배열 타입[ ]:: new

 

예제1) 매개변수가 없는 생성자 메서드 호출

 

import java.util.function.Supplier;

public class Ex14_0 {
	public static void main(String[] args) {
		// Supplier는 입력 x, 출력 O
//		Supplier<MyClass> s = () -> new MyClass();
		Supplier<MyClass> s = MyClass::new; // 메서드 참조

        MyClass mc = s.get();
		System.out.println(mc);
		System.out.println(s.get()); // 위의 두줄을 한 줄로 Myclass의 객체를 반환
		}	
}
class MyClass { // Myclass라는 클래스 생성
	MyClass() {}
}

 

예제2) 예제1에서 매개변수 있는 생성자 메서드 참조

 

import java.util.function.Function;

public class Ex14_0 {
	public static void main(String[] args) {
		//Supplier는 입력 x, 출력 o -> Function은 입력 o, 출력 o
//		Function<Integer, MyClass> f = (i) -> new MyClass(i);
		Function<Integer, MyClass> f = MyClass::new;   // 메서드 참조
		
		MyClass mc = f.apply(100);  // Function는 apply, Supplier는 get
		System.out.println(mc.iv);  // 100 
		System.out.println(f.apply(100).iv);  // 위의 두줄을 한 줄로
		
	}
}
class MyClass {
	int iv;
	MyClass(int iv) {
		this.iv = iv;
	}
}

 

예제3) 배열 생성하고 메서드 참조

 

// 배열을 생성할 때는 꼭 Function으로 해야함(배열의 길이를 줘야하기 때문에) 
//		Function<Integer, int[]> f2 = (i) -> new int[i];  // 람다식
		Function<Integer, int[]> f2 = int[]::new; // 메서드 참조
        
		int[] arr = f2.apply(100);
		System.out.println("arr.length = " + arr.length);