본문 바로가기

프로그래밍(TA, AA)/JVM 언어

[자바] 람다 표준 API의 함수 인터페이스

출처: http://palpit.tistory.com/673

 

자바에서 제공되는 표준 API에서 한 개의 추상 메소드를 가지는 인터페이스들은 모두 람다식을 이용해서 익명 구현 객체로 표현이 가능합니다. 예를 들어 스레드의 작업을 정의하는 Runnable 인터페이스는 매개변수와 리턴값이 없는 run() 메소드만 존재하기 때문에 다음과 같이 람다식을 이용해서 Runnable 인스턴스를 생성시킬 수 있습니다.

package lambda;

public class RunnableExam {

	public static void main(String[] args) {
		Runnable runnable = () -> {
			for (int i = 0; i < 10; i++) {
				System.out.println(i);
			}
		};
		
		Thread thread = new Thread(runnable);
		thread.start();
	}
}

 

Thread 생성자를 호출할 때 다음과 같이 람다식을 매개값에 대입해도 됩니다.

Thread thread = new Thread( () -> {
		for (int i = 0; i < 10; i++) {
			System.out.println(i);
		}
});

 

자바8부터는 빈번하게 사용되는 함수적 인터페이스(Functional Interface)는 java.util.function 표준 API 패키지로 제공합니다. 이 패키지에서 제공하는 함수적 인터페이스의 목적은 메소드 또는 생성자의 매개 타입으로 사용되어 람다식을 대입할 수 있도록 하기 위해서 입니다. java.util.function 패키지의 함수적 인터페이스는 크게 Consumer, Supplier, Function, Operator, Predicate로 구분됩니다.

 

 

 

Consumer 함수형 인터페이스


Consumer 함수적 인터페이스의 특징은 리턴값이 없는 accept() 메소드를 가지고 있습니다. accept() 메소드는 단지 매개값을 소비하는 역할만 합니다. 여기서 소비한다는 말은 사용만 할 뿐 리턴값이 없다는 뜻입니다.

 

매개 변수의 타입과 수에 따라서 아래와 같은 Consumer들이 있습니다.

 

 

 인터페이스 명

 추상 메소드

 설명

 Consumer<T>

 void accept(T t)

 객체 T를 받아 소비

 BiConsumer<T, U>

 void accept(T t, U u)

 객체 T와 U를 받아 소비

 DoubleConsumer

 void accept(double value)

 double 값을 받아 소비

 IntConsumer

 void accept(int value)

 int 값을 받아 소비

 LongConsumer

 void accept(long value)

 long 값을 받아 소비

 ObjDoubleConsumer<T>

 void accept(T t, double value)

 객체 T와 double 값을 받아 소비

 ObjIntConsumer<T>

 void accept(T t, int value)

 객체 T와 int 값을 받아 소비

 ObjLongConsumer<T>

 void accept(T t, long value)

 객체 T와 long 값을 받아 소비

 

 

메소드는 매개값으로 T 객체 하나를 가지므로 람다식도 한 개의 매개 변수를 사용합니다. 타입 파라미터 T에 String이 대입되었기 때문에 람다식의 t 매개변수 타입은 String이 됩니다.

Consumer<String> consumer = t -> { t를 소비하는 실행문; };
package lambda;

import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.ObjIntConsumer;

public class ConsumerExam {

	public static void main(String[] args) {
		Consumer<String> consumer = t -> System.out.println(t + "8");
		consumer.accept("자바");
 
		BiConsumer<String, String> biConsumer = (t, u) -> System.out.println(t + u);
		biConsumer.accept("자바", "8");

		DoubleConsumer doubleConsumer = d -> System.out.println("자바" + d);
		doubleConsumer.accept(8.0);
 
		ObjIntConsumer<String> objIntConsumer = (t, i) -> System.out.println(t + i);
		objIntConsumer.accept("자바", 8);
	}

}

 

 

Supplier 함수형 인터페이스


Supplier 함수적 인터페이스의 특징은 매개 변수가 없고 리턴값이 있는 getXXX() 메소드를 가지고 있습니다. 이 메소드들은 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 합니다.

 

리턴 타입에 따라서 아래와 같은 Supplier 함수적 인터페이스들이 있습니다. 

 

 

 인터페이스 명

 추상 메소드

 설명

 Supplier<T>

 T get()  T 객체를 리턴

 BooleanSupplier

 Boolean getAsBoolean()  Boolean 값을 리턴
 DoubleSupplier  double getAsDouble()  double 값을 리턴

 IntSupplier

 int getAsInt()  int 값을 리턴
 LongSupplier  long getAsLong()  long 값을 리턴

 

 

Supplier<T> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있습니다. getAsInt() 메소드가 매개값을 가지지 않으므로 람다식도 ()을 사용합니다. 람다식의 중괄호 {}는 반드시 int 값을 리턴하도록 해야 합니다.

IntSupplier supplier = () -> { ...; return int_value; }

 

다음 예제는 주사위의 숫자를 랜덤하게 공급하는 IntSupplier 인터페이스를 타겟 타입으로 하는 람다식입니다.

package lambda;

import java.util.function.IntSupplier;

public class SupplierExam {

	public static void main(String[] args) {
		IntSupplier intSupplier = () -> {
			int num = (int) (Math.random() * 6) + 1;
			return num;
		};
		
		int num = intSupplier.getAsInt();
		System.out.println("눈의 수 : " + num);
	}
}

 

 

Function 함수형 인터페이스


Function 함수형 인터페이스의 특징은 매개값과 리턴값이 있는 applyXXX() 메소드를 가지고 있습니다. 이 메소드들은 매개값을 리턴값으로 매핑하는 역할을 합니다. 매개 변수 타입과 리턴 타입에 따라서 아래와 같은 Function 함수형 인터페이스로 표현됩니다.

 

 인터페이스 명

 추상 메소드

 설명

 Function<T, R>

 R apply(T t)

 객체 T를 객체 R로 매핑

 BiFunction<T, U, R>

 R apply(T t, U u)

 객체 T와 U를 객체 R로 매핑

 DoubleFunction<R>

 R apply(double value)

 double을 객체 R로 매핑

 IntFunction<R>

 R apply(int value)

 int을 객체 R로 매핑

 IntToDoubleFunction

 double applyAsDouble(int value)

 int를 double로 매핑

 IntToLongFunction

 long applyAsLong(int value)

 int를 long으로 매핑

 LongToDoubleFunction

 double applyAsDouble(long value)

 long을 double로 매핑

 LongToIntFunction

 int applyAsInt(long value)

 long을 int로 매핑

 ToDoubleBiFunction<T, U>

 double applyAsDouble(T t, U u)

 객체 T와 U를 double로 매핑

 ToDoubleFunction<T>

 double applyAsDouble(T value)

 객체 T를 double로 매핑

 ToIntBiFunction<T, U>

 int applyAsInt(T t, U u)

 객체 T와 U를 int로 매핑

 ToIntFunction<T>

 int applyAsInt(T t)

 객체 T를 int로 매핑

 ToLongBiFunction<T, U>

 long applyAsLong(T t, U u)

 객체 T와 U를 long으로 매핑

 ToLongFunction<T>

 long applyAsLong(T t)

 객체 T를 long으로 매핑

 

 

Function<T, R> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있습니다. apply() 메소드는 매개값으로 T 객체 하나를 가지므로 람다식도 한 개의 매개 변수를 사용합니다. 그리고 appy() 메소드의 리턴 타입이 R이므로 람다식 중괄호 {}의 리턴값은 R 객체가 됩니다. T가 Student 타입이고 R이 String 타입이므로 t 매개 변수 타입은 Student가 되고, 람다식의 중괄호 {}는 String을 리턴해야합니다. t.getName()은 Student 객체의 getName() 메소드를 호출해서 학생 이름을 얻습니다. return문만 있을 경우 중괄호 {}와 return 문은 생략할 수 있습니다. 다음 코드는 Student 객체를 학생 이름(String)으로 매핑하는 것입니다.

Function<Student, String> function = t -> { return t.getName(); };

or

Function<Student, String> function = t -> t.getName();

 

다음 예제는 List에 저장된 학생 객체를 하나씩 꺼내서 이름과 점수를 출력합니다.

package lambda;
 
public class Student {
    private String name;
    private int englishScore;
    private int mathScore;
 
    public Student(String name, int englishScore, int mathScore) {
        super();
        this.name = name;
        this.englishScore = englishScore;
        this.mathScore = mathScore;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getEnglishScore() {
        return englishScore;
    }
 
    public void setEnglishScore(int englishScore) {
        this.englishScore = englishScore;
    }
 
    public int getMathScore() {
        return mathScore;
    }
 
    public void setMathScore(int mathScore) {
        this.mathScore = mathScore;
    }
 
}
package lambda;

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.ToIntFunction;

public class FunctionExam1 {

	private static List<Student> list = Arrays.asList(
		new Student("Jackie", 90, 65),
		new Student("Jolie", 100, 100)
	);

	public static void printString(Function<Student, String> function) {
		
		for (Student std : list) {
			System.out.print(function.apply(std) + " ");
		}
		System.out.println();
	}
	
	public static void printInt(ToIntFunction<Student> function) {
		
		for (Student std : list) {
			System.out.print(function.applyAsInt(std) + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		System.out.println("학생 이름: ");
		printString( t -> t.getName() );

		System.out.println("영어 접수: ");
		printInt( t -> t.getEnglishScore() );

		System.out.println("수학 점수: ");
		printInt( t -> t.getMathScore() );
	}
}

 

다음 예제는 List에 저장된 학생 객체를 하나씩 꺼내어 영어 점수와 수학 점수의 평균값을 산출합니다.

package lambda;

import java.util.Arrays;
import java.util.List;
import java.util.function.ToIntFunction;

public class FunctionExam2 {
	
	private static List<Student> list = Arrays.asList(
		new Student("Jolie", 100, 89),
		new Student("Martin", 77, 94),
		new Student("Pierre", 49, 100),
		new Student("Paul", 80, 78)
	);

	public static double avg(ToIntFunction<Student> function) {
		int sum = 0;

		for (Student std : list) {
			sum += function.applyAsInt(std);
		}

		double avg = (double) sum / list.size();

		return avg;
	}
	
	public static void main(String[] args) {
		double englishAvg = avg( s -> s.getEnglishScore() );
		System.out.println("영어 평균 점수: " + englishAvg);

		double mathAvg = avg( s -> s.getMathScore() );
		System.out.println("영어 평균 점수: " + mathAvg);
	}
}

 

 

Operator 함수형 인터페이스


Operator 함수형 인터페이스는 Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있습니다. 하지만 이 메소드들은 매개값을 리턴값으로 매핑(타입 변환)하는 역할보다는 매개값을 이용해서 연산을 수행한 후 동일한 타입으로 리턴값을 제공하는 역할을 합니다. 매개 변수의 타입과 수에 따라서 아래와 같은 Operator 함수형 인터페이스들이 있습니다.

 

 인터페이스 명

 추상 메소드

 설명

 BinaryOperator<T>

 BiFunction<T,U,R>의 하위 인터페이스

 T와 U를 연산 후 R 리턴

 UnaryOperator<T>

 Function<T, R>의 하위 인터페이스

 T를 연산한 후 R 리턴

 DoubleBinaryOperator

 double applyAsDouble(double, double)

 두 개의 double을 연산

 DoubleUnaryOperator

 double applyAsDouble(double)

 한 개의 double을 연산

 IntBinaryOperator

 int applyAsInt(int, int)

 두 개의 int를 연산

 IntUnaryOperator

 int applyAsInt(int)

 한 개의 int를 연산

 LongBinaryOperator

 long applyAsLong(long, long)

 두 개의 long을 연산

 LongUnarayOperator

 long applyAsLong(long)

 한 개의 long을 연산

 

다음 예제는 int[] 배열에서 최대값과 최소값을 얻습니다. maxOrMin() 메소드는 IntBinaryOperator 매개변수를 가지고 있습니다. 따라서 maxOrMin() 메소드를 호출할 때 람다식을 이용할 수 있습니다.

package lambda;

import java.util.function.IntBinaryOperator;

public class OperatorExam {

	private static int[] scores = ;
	
	public static int maxOrMin(IntBinaryOperator operator) {
		int result = scores[0];
		
		for(int score: scores) {
			result = operator.applyAsInt(result, score);
		}
		
		return result;
	}

	public static void main(String[] args) {
		int max = maxOrMin(
			(a, b) -> {
				if (a >= b)
					return a;
				else
					return b;
			}
		);
		System.out.println("최대 값 : " + max);

		int min = maxOrMin(
			(a, b) -> {
				if (a <= b)
					return a;
				else
					return b;
			}
		);
		System.out.println("최소 값 : " + min);
	}
}

 

 

Predicate 함수형 인터페이스


Predicate 함수형 인터페이스는 매개 변수와 boolean 리턴값이 있는 testXXX() 메소드를 가지고 있습니다. 이 메소드들은 매개값을 조사해서 true 또는 false를 리턴하는 역할을 합니다. 매개 변수 타입과 수에 따라서 아래와 같은 Predicate 함수적 인터페이스들이 있습니다.

 

 인터페이스 명

 추상 메소드

 설명

 Predicate<T>

 Boolean test(T t)

 객체 T를 조사

 BiPredicate<T, U>

 Boolean test(T t, U u)

 객체 T와 U를 비교 조사

 DoublePredicate

 Boolean test(double value)

 double 값을 조사

 IntPredicate

 Boolean test(int value)

 int 값을 조사

 LongPredicate

 Boolean test(long value)

 long 값을 조사

 

다음 예제는 List에 저장된 남자 또는 여자 학생들의 평균 점수를 출력하는 예제입니다.

package lambda;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class PredicateExam {
	
	private static List<Student> list = Arrays.asList(
		new Student("Martin", 80, 90, "Male"),    
		new Student("Jolie", 74, 88, "Female"),    
		new Student("Sophie", 66, 100, "Female"),    
		new Student("Pierre", 100, 78, "Male"),    
		new Student("anne", 80, 90, "Female"),    
		new Student("Paul", 42, 91, "Male"),    
		new Student("cristianne", 99, 100, "Female"),    
		new Student("Mcg", 100, 90, "Male")
	);

	public static double[] avg(Predicate<Student> predicate) {
		int count = 0, engSum = 0, mathSum = 0;

		for (Student std : list) {
			if (predicate.test(std)) {
				count++;
				engSum += std.getEnglishScore();
				mathSum += std.getMathScore();
			}
		}

		double avg[] = {((double) engSum / count), ((double) mathSum / count)};

		return avg;
	}

	public static void main(String[] args) {
		double maleAvg[] = avg(t -> t.getSex().equals("Male"));
		System.out.println("남자 평균 점수(영어, 수학)");

		for (double avg : maleAvg) {
			System.out.print(avg + " ");
		}
		System.out.println();

		double femaleAvg[] = avg( t -> t.getSex().equals("Female"));
		System.out.println("여자 평균 점수(영어, 수학)");
 
		for (double avg : femaleAvg) {
			System.out.print(avg + " ");
		}
		System.out.println();
	}
}