관리 메뉴

나구리의 개발공부기록

자바의 정석 기초편 ch14 - 7 ~ 14[java.util.function패키지, Predicate의 결합, CF와 함수형 인터페이스, 메서드 참조, 생성자의 메서드 참조] 본문

유튜브 공부/JAVA의 정석 기초편(유튜브)

자바의 정석 기초편 ch14 - 7 ~ 14[java.util.function패키지, Predicate의 결합, CF와 함수형 인터페이스, 메서드 참조, 생성자의 메서드 참조]

소소한나구리 2023. 12. 20. 12:55

1) java.util.function패키지

  • 자주 사용되는 다양한 함수형 인터페이스를 제공함
  • 각 함수형 인터페이스별로 가지고있는 추상메서드가 다르며 반환값, 매개변수가 있는것도있고 없는것도 있음
함수형 인터페이스 메서드
설 명
java.lang.Runnable   void run()   매개변수, 반환값 없음
Supplier<T>   T get() -> T 매개변수 없음, 반환값 있음
Consumer<T> T -> void accept(T t)   매개변수 있음, 반환값 없음(supplier와 반대)
Function<T, R> T -> R apply(T t) -> R 하나의 매개변수를 받아 결과를 반환(일반적인 함수)
Predicate<T> T -> boolean test(T t) -> boolean 조건식을 표현, 매개변수는 하나, 반환 타입은 boolean

 

(1) Predicate 사용 예시

Predicate<String> isEmptyStr = s -> s.length()==0;
String s = "";

if(isEmptyStr.test(s)) // if(s.length()==0)
    System.out.println("This is an empty String.");

 

(2) 문제

 

  1. Supplier <Integer> : 반환값이 있음
  2. Consumer <Integer> : 매개변수가 있음
  3. Predicate <Integer> : 매개변수가 있고 반환값은 boolean
  4. Function <Integer, Integer> : 매개변수,반환값이 있음

(3) 매개변수가 2개인 함수형 인터페이스

  • Bi로 시작하는 함수형 인터페이스, (Bi 두개)
함수형 인터페이스 메서드
설 명
Biconsumer<T,U> T,U -> void accept(T t, U u)   두개의 매개변수만 있고 반환값 없음
BiPredicate<T,U> T,U -> boolean test(T t, U u) -> boolean 조건식을 표현, 매개변수 둘, 반환값 boolean
BiFunction<T,U,R> T,U -> R apply(T t, U u) -> R 두 개의 매개변수를 받아 하나의 결과를 반환

 

(4) 매개변수가 3개 이상인 함수가 필요한 경우 직접 만들면 됨

  • 제네릭스에 타입변수의 개수를 입력하여 추상메서드를 정의하면 됨

 

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

  • 입력값의 개수만 차이가 있고 두 함수형 인터페이스는 입력과 반환타입이 동일함
함수형 인터페이스 메서드
설 명
UnaryOperator<T> T -> T apply(T t) -> T Function의 자손, Function과 달리 매개변수와 결과의 타입 동일
BinaryOperator<T> T, T -> T apply(T t, T t) -> T BiFuction의 자손, BiFuction과 달리 매개변수와 결과 타입이 동일

 

사용 예시

 

(5) 예제

  • 각 함수형 인터페이스의 참조변수로 람다식의 결과를 저장함
  • 메서드의 매개변수로 각 참조변수를 입력하여 메서드 각 참조형 인터페이스의 메서드를 사용하여 로직을 구성함
import java.util.function.*;
import java.util.*;

public class Ex14_2 {
	public static void main(String[] arags) {
		// 1 ~ 100사이의 난수를 반환
		Supplier<Integer> s = () -> (int)(Math.random()*100)+1;
		
		// 화면에 i + ", "를 출력
		Consumer<Integer> c = i -> System.out.print(i+ ", ");
		
		// 짝수인지 검사 후 True / false로 반환
		Predicate<Integer> p = i -> i%2 == 0;
		
		// 1의 자리를 없앰, 1의자리만 있는 수의 경우 0으로 반환
		Function<Integer, Integer> f = i -> i/10*10;
		
		List<Integer> list = new ArrayList<>(); // ArrayList생성
		
		// makeRandomList메서드 호출
		makeRandomList(s, list);
		System.out.println(list);
		
		// printEvenNum메서드 호출
		printEvenNum(p, c, list);
		
		// doSomething메서드를 호출 하여 newList에 저장
		List<Integer> newList = doSomething(f, list);
		System.out.println(newList);
		
	}
	
	// list와 똑같은 크기의 ArrayList를 생성 후 list의 값을 일의자리수를 0으로 변경
	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)); // 일의자리수를 0으로 변경
		}
		return newList;
	}
	
	//  매개변수로 받은 list의 값중 짝수만 ", "와 함께 출력 
	static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
		System.out.print("[");
		for(T i : list) { // 매개변수로 받은 list의 요소를 하나씩 꺼냄
			if(p.test(i)) // 짝수인지 검사 ->  true이면 c.accept(i); 실행
				c.accept(i); // 화면에 i+", "를 출력 
		}
		System.out.println("]");
	}
	
	// list를 랜덤값으로 10개를 채움
	static <T> void makeRandomList(Supplier<T> s, List<T> list) {
		for(int i=0; i<10; i++ ) {
			list.add(s.get());
		}
	}
	
}

 


2) Predicate의 결합

  • Predicate 함수형 인터페이스에는 다양한 디폴트 메서드가 정의되어있음
  • 디폴트 메서드와 static 메서드로 정의되어있기 때문에 함수형 인터페이스에는 하나의 추상메서드만 있다는 정의에 영향을 주지 않음
  • Java8 이후로 인터페이스에 디폴트메서드와 static메서드를 추가할 수 있게 되었음

(1) and() = &&, or() = ||, negate() = !(논리부정)

  • 디폴드 메서드로 작성되어있음
  • 두개의 Predicate를 하나로 결합하여 최종 연산결과를 반환
  • and()는 두개의 연산결과가 모두 true여야만 true
  • or()는 둘 중 하나만 true이면 true
  • negate()는 람다식의 결과를 반대로 반환
public class PredicateExample {
    public static void main(String[] args) {
        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));     // 100 <= i && (i < 200 || i%2==0)
        Predicate<Integer> all2 = notP.or(q.or(r));     // 100 <= i || (i < 200 || i%2==0)    

        System.out.println(all.test(2));    // false
        System.out.println(all2.test(2));   // true
    }
}

 

(2) isEqual()

  • static 메서드로 작성되어있음
  • 등가비교를 위한 Predicate의 작성에는 isEqual()를 사용 
import java.util.function.Predicate;

public class PredicateEqualExample {
    public static void main(String[] args) {
    
        String str1 = "Hello";
        String str2 = "World";

        // 첫 번째 방법
        Predicate<String> p = Predicate.isEqual(str1);  // isEqual()은 static메서드
        Boolean result = p.test(str2);  // str1과 str2가 같은지 비교한 결과를 반환

        // 두 번째 방법 (위의 2줄을 1줄로 축약)
        boolean result2 = Predicate.isEqual(str1).test(str2);

        System.out.println("Result 1: " + result);
        System.out.println("Result 2: " + result2);
    }
}

 

(3) 예제

import java.util.function.*;

class Ex14_3 {
	public static void main(String[] args) {
    
		// 16진수로 변경
		Function<String, Integer> f = (s) -> Integer.parseInt(s, 16);
		// 2진수로 변경
		Function<Integer, String> g = (i) -> Integer.toBinaryString(i);
		

// 자바의 정석 3판 : andThen(), compose() - 자바의정석 기초편 ppt에도 설명이 있음
// Function을 하나로 합치는 메서드(처음 함수의 반환타입과 다음 함수의 입력 타입이 같아야 함)
		
		// f.andThen(g); f -> 시작함수, g -> 다음함수
		// f(String 시작 Integer 반환) -> g(Integer 시작 String 반환)
		Function<String, String> h = f.andThen(g);
		
		// f.compose(g); g -> 시작함수, f -> 다음함수 (andThen의 반대)
		// g(Integer 시작 String 반환) -> f(String 시작 Integer 반환) 
		Function<Integer, Integer> h2 = f.compose(g);
		
		System.out.println(h.apply("FF")); // "FF" -> 16진수(255) -> 2진수("11111111")로 변환
		System.out.println(h2.apply(2)); // 숫자2 -> 2진수("10") -> 16진수(16)로 변환

		
		// 항등함수(identity function) -> 입력한 값이 그대로 출력
		Function<String, String> f2 = x -> x;
		System.out.println(f2.apply("AAA")); // "AAA"가 그대로 출력

		
		// Predicate의 결합
		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;
		
		// all = i >= 100 && (i < 200 || i % 2 == 0);
		Predicate<Integer> all = notp.and(q.or(r));
		System.out.println(all.test(150)); // true
		
		// 등가비교를 위한 Predicate
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		// isEqual()메서드를 사용하여 str1과 str2가 같은지 비교후 결과를 반환
//		Predicate<String> p2 = Predicate.isEqual(str1);
//		boolean result = p2.test(str2);
		
		//위 두줄을 한번에 작성
		boolean result = Predicate.isEqual(str1).test(str2);
		
		System.out.println(result);
/*
출력값
11111111
16
AAA
true
true
*/

 

3) 컬렉션 프레임워크와 함수형 인터페이스

  • 함수형 인터페이스를 사용하는 컬렉션 프레임워크의 메서드들(와일드 카드는 생략하여 표시)
인터페이스 메서드 설명
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를 수행
사용 예시

(1) 예제

import java.util.*;

public class Ex14_4 {
    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.print(i + ","));
        System.out.println();

        // list의 요소 중 2 or 3의 배수를 제거
        list.removeIf(x -> x % 2 == 0 || x % 3 == 0);
        System.out.println("list = " + list);

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

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

        map.forEach((k, v) -> System.out.print("{" + k + " " + v + "},"));
    }
}

/*
출력값
0,1,2,3,4,5,6,7,8,9,
list = [1, 5, 7]
list = [10, 50, 70]
{1 1},{2 2},{3 3},{4 4},
*/

3) 메서드 참조

  • 클래스이름::메서드이름
  • 하나의 메서드만 호출하는 람다식은 메서드 참조로 간단히 표현할 수 있음
종류 람다 메서드참조
static 메서드 참조 (x) -> ClassName.method(x) ClassName::method
인스턴스메서드 참조 (obj, x) -> obj.method(x) ClassName::method
특정 객체 인스턴스메서드 참조(거의안씀) (x) -> obj.method(x) obj::method

(1) static메서드 참조 예시

  • 기능이 하나인 메서드를 람다식, 메서드 참조로 변환
// 원본 메서드
Integer method(String s) {  
    return Integer.parseInt(s);
}

// 람다 표현식으로 변환
Function<String, Integer> f = (String s) -> Integer.parseInt(s);

// 메서드 참조로 최종 변환
Function<String, Integer> f = Integer::parseInt;  // 메서드 참조

 

(2) 생성자와 메서드 참조 

  • 생성자, 즉 객체를 생성할 때에도 메서드 참조를 사용할 수 있음
  • 생성자에 매개변수가 없을 때 = 참조변수의 타입은 Supplier
  • 매개변수가 하나 = Function
  • 배열 = Function <입력타입, 배열타입>
  • 매개변수가 둘 = BiFunction
생성자의 인수 객체 생성 예시 참조변수 타입 메서드참조 변환
인수가 없음 new MyClass() Supplier<T> MyClass::new
인수가 하나일 때 new MyClass(i) Function<T> MyClass::new
인수가 두개일 때 new MyClass(i, y) BiFunction<T, T> MyClass::new
배열 생성 시 new int[x] Function<입력타입, 배열타입> int[]::new
// 기본 생성자를 사용하는 Supplier
Supplier<MyClass> s = MyClass::new;  // () -> new MyClass()

// 정수 매개변수를 받는 생성자를 사용하는 Function
Function<Integer, MyClass> f2 = MyClass::new;  // (i) -> new MyClass(i)

// 정수를 받아 해당 크기의 int 배열을 생성하는 Function
Function<Integer, int[]> f2 = int[]::new;  // x -> new int[x]

 

(3) 예제 

  • 람다식을 메서드참조로 변환
public class Ex14_MethodRef {
    public static void main(String[] args) {
        // 람다식
        Function<String, Integer> f = (String s) -> Integer.parseInt(s);
        System.out.println(f.apply("100")+200);
        // 메서드참조
        Function<String, Integer> f1 = Integer::parseInt;
        System.out.println(f1.apply("100")+200);

        // 기본 생성자 람다식
        Supplier<MyClass> s = () -> new MyClass();
        System.out.println(s.get());
        // 메서드 참조
        Supplier<MyClass> s1 = MyClass::new;
        System.out.println(s1.get());

        // 매개변수가 있는 람다식
        Function<Integer, MyClass> f3 = (i) -> new MyClass(i);
        System.out.println(f3.apply(100).iv);
        // 메서드 참조
        Function<Integer, MyClass> f4 = MyClass::new;
        System.out.println(f4.apply(100).iv);

        // 배열 람다식
        Function<Integer, int[]> fArr = (i) -> new int[i];
        System.out.println(fArr.apply(100).length); // 길이가 100인 배열의 길이를 출력
        // 위 코드를 풀어서 작성
        int[] arr = fArr.apply(100);
        System.out.println(arr.length);
        // 메서드 참조
        Function<Integer, int[]> fArr2 = int[]::new;
        System.out.println(fArr2.apply(200).length); // 길이가 200인 배열의 길이를 출력
    }
}

class MyClass {
    int iv;

    MyClass() {
    }

    MyClass(int iv) {
        this.iv = iv;
    }
}

 

** 출처 : 남궁성의 정석코딩_자바의정석_기초편 유튜브