Java 8 - More Functional Interface
이전 포스트에서 functional Interfcae에 대하여 알아보았었습니다.
이전 포스트 보러 가기 - https://beomseok95.tistory.com/277
이번 글에서는 더 자세히 Functional Interface에 대하여 파보도록 하겠습니다.(이전포스트과 겹치는 내용이 있을 수 있습니다.)
Runnable
기존에 존재하던 인터페이스로 Java 8에서 추가된 인터페이스는 아니지만 , 함수형 인터페이스라 추가하였습니다.
java.lang패키지에 존재하며 원형은 아래와 같습니다.
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
thread 사용할 때 많이 보았을 runnable 인터페이스는
리턴타입은 void이고 파라미터도 존재하지 않습니다.
public class RunnableTest {
@Test
public void test(){
Runnable runnable = ()->{
System.out.println("hello runnable");
};
runnable.run();
}
}
스레드와 같이 사용할 때는 주로 스레드 클래스를 만들고 run() 메서드를 구현하는 방법으로 사용하지만
Runnable interface는 추상 메서드가 1개뿐인 인터페이 스므로
람다를 이용하여 바로 인스턴스를 생성할 수도 있습니다.( 사용 예는 위와 같습니다.)
Callable <V>
Callable도 앞선 Runnable과 마찬가지로
기존에 존재하던 인터페이스로 Java 8에서 추가된 인터페이스는 아니지만 , 함수형 인터페이스라 추가하였습니다.
Runnable은 실행결과를 전달받기 위해서 공용 메모리나 파이프 같은 것을 사용하여 결괏값을 받아야만 했습니다.
Callable은 이런 Runnable은 단점을 해결해주기 위해 사용됩니다.
아래는 Callable의 원형입니다.
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Call함수를 수행하고 미리 제네릭으로 선언해둔 타입으로 결과를 리턴합니다.
public class CallableTest {
@Test
public void test() {
Callable<String> callable = ()-> "returns";
try {
callable.call();
} catch (Exception e) {
e.printStackTrace();
}
}
}
함수의 원형에서 볼 수 있듯이 실행 중 에러가 발생하는 경우도 감안해 두었기 때문에
위의 예와 같이 에러 처리가 필요합니다(여기서는 try/catch 구문으로 감싸주었습니다)
CallInterface와 비슷한 Future라는 인터페이스도 있는데 ( 함수형 인터페이스는 아닙니다)
주로 API통신 등과 같은 곳에서 사용합니다. 알아두는 게 좋을 것 같습니다.
Future Interface
- V get()
Callable 등 작업의 실행이 완료될 때까지 블록킹 되며, 완료되면 그 결괏값을 리턴합니다. - V get(long timeout, TimeUnit unit)
지정한 시간 동안 작업의 실행 결과를 기다립니다. 지정한 시간 내에 수행이 완료되면 그 결괏값을 리턴합니다. 대기 시간이 초과되면 TimeoutException을 발생시킨다. - boolean cancel(boolean mayInterruptIfRunning)
작업을 취소합니다. - boolean isCancelled()
작업이 정상적으로 완료되기 이전에 취소되었을 경우 true를 리턴합니다. - boolean isDone()
작업이 완료되었다면 true를 리턴합니다.
Supplier <T>
드디어 Java 8에서 추가된 인터페이스입니다.
매개변수를 받지 않고 특정 타입의 결과를 리턴합니다.
Supplier Inteface원형
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Supplier를 활용하면 값을 그냥 전하는 것이 아니라, 중간에 로직을 추가해서 전달할 수 있습니다.
public class SupplierTest {
@Test
public void test(){
Supplier<Integer> intSupplier = () -> {
int num = (int) (Math.random() * 6) + 1;
return num;
};
int num = intSupplier.get();
System.out.println("눈의 수 : " + num);
}
}
get
제네릭으로 전달받은 타입으로 값을 전달해줍니다.
Consumer <T>
리턴을 하지 않고(void), 인자를 받는 메서드를 갖고 있습니다.
인자를 받아 소모한다는 뜻으로 인터페이스 명칭을 이해하면 될 것 같습니다.
Consumner Interface원형
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
사용 예시
public class ConsumerTest {
@Test
public void test() {
andThen();
accept();
}
private void andThen() {
final Consumer<String> consumer1 = (i) -> System.out.println("consumer1 "+i);
final Consumer<String> consumer2 =(i) -> System.out.println("consumer2 "+i);
consumer1.andThen(consumer2).accept("is Consume!!");
}
private void accept() {
final Consumer<String> greetings = value -> System.out.println("Hello " + value);
greetings.accept("World"); // Hello World
}
}
andThen
Consumer 인터페이스는 처리 결과를 리턴하지 않기 때문에 andThen() 디폴트 메서드는 함수적 인터페이스의 호출 순서만 정합니다.
BiConsumer <T,U>
리턴을 하지 않고(void), 서로 다른 타입의 두 개의 인자를 받는 메서드를 갖고 있습니다.
Consumer와 동일하게 데이터를 소모한다고 보면 됩니다.
Consumner Interface원형
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}
위의 Consumer인터페이스와 동일합니다.
다른 점은 서로 다른 타입 T, 와 U를 파라미터로 받고 소모하는 것입니다.
사용 예시
BiConsumer<Integer, String> consumer = (i, s) -> {
System.out.println("<T> is " + i + ", <U> is " + s);
};
consumer.accept(1,"s");
//result
<T> is 1, <U> is s
andThen
BiConsumer 인터페이스도 마찬가지로 처리 결과를 리턴하지 않기 때문에
andThen() 디폴트 메서드는 함수적 인터페이스의 호출 순서만 정합니다.
Function <T, R>
인터페이스 명칭에서부터 알 수 있듯이 전형적인 함수를 지원합니다.
하나의 인자 <T>와 리턴 타입 <R>을 가지며 그걸 제네릭으로 지정해 줄 수 있습니다.
Function Interface원형
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
사용 예시
public class FunctionTest {
@Test
public void test() {
compose();
andThen();
identity();
}
private void compose() {
Function<Integer, String> intToString = Objects::toString;
Function<String, String> quote = s -> "'" + s + "'";
Function<Integer, String> quoteIntToString = quote.compose(intToString);
System.out.println(quoteIntToString.apply(5)); // '5'
}
private void andThen() {
Function<String, String> upperCase = v -> v.toUpperCase();
String result = upperCase.andThen(s -> s + "abc").apply("a");
System.out.println(result); // Aabc
}
private void identity() {
final Function<Integer, String> intToStr = value -> value.toString();
System.out.println(intToStr.apply(10)); // 10
final Function<Integer, Integer> identity = Function.identity();
System.out.println(identity.apply(100)); // 100
}
}
compose
before 메서드 실행 후 결과를 받아서 현재 메서드 실행합니다.
제네릭 타입은 처음 input과 마지막 output의 타입입니다.
즉 , 메서드를 순서대로 실행시키기 위한 함수입니다.
andThen
compose와 반대로
현재 메서드를 실행 후 매게 변수로 받은 람다를 실행합니다.
identity
자신의 값을 그대로 리턴하는 스테틱 메서드입니다.
compose , andThen 차이점
andThen()과 compose()의 차이점은 어떤 함수적 인터페이스부터 먼저 처리하느냐에 따라 다릅니다.
인터페이스A.compose(인터페이스B);
B실행 -> A실행
인터페이스A.andThen(인터페이스B);
A실행 -> B실행
Predicate <T>
하나의 인자를 가지고 , 리턴 타입은 boolean으로 고정입니다.
Function <T, Boolean> 형태와 같다고 할 수 있습니다.
Predicate Interface의 원형
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
public class PredicateTest {
@Test
public void test() {
tests();
and_or_negate();
}
private void and_or_negate() {
Predicate<Integer> predicateA = a -> a % 2 == 0;
Predicate<Integer> predicateB = b -> b % 3 == 0;
boolean result;
result = predicateA.and(predicateB).test(9);
System.out.println("9는 2와 3의 배수입니까? " + result);
result = predicateA.or(predicateB).test(9);
System.out.println("9는 2또는 3의 배수입니까? " + result);
result = predicateA.negate().test(9);
System.out.println("9는 홀수입니까? " + result);
}
private void tests() {
Predicate<Integer> isZero = (i) -> i == 0;
System.out.println(isZero.test(0));
;
}
}
and
인자로 받는 다른 Predicate의 람다 수행 내용과 , 자기 자신의 람다 수행 내용을 &&연산합니다.
or
인자로 받는 다른 Predicate의 람다 수행 내용과 , 자기 자신의 람다 수행 내용을 ||연산합니다.
negate
람다 수행 내용을! 연산합니다.
BiPredicate <T, U>
서로 다른 타입의 2개의 인자를 받아 boolean으로 반환합니다.
BiPredicate Interface의 원형
@FunctionalInterface
public interface BiPredicate<T, U> {
boolean test(T t, U u);
default BiPredicate<T, U> and(BiPredicate<? super T, ? super U> other) {
Objects.requireNonNull(other);
return (T t, U u) -> test(t, u) && other.test(t, u);
}
default BiPredicate<T, U> negate() {
return (T t, U u) -> !test(t, u);
}
default BiPredicate<T, U> or(BiPredicate<? super T, ? super U> other) {
Objects.requireNonNull(other);
return (T t, U u) -> test(t, u) || other.test(t, u);
}
}
원형을 보면 서로 다른 타입 T, U를 받고
test 함수를 통해 boolean을 반환합니다.
BiPredicate<Integer, String> biPredicate1 = (i, s) -> i.toString().equals(s);
BiPredicate<Integer, String> biPredicate2 = (i, s) -> i.compareTo(Integer.valueOf(s)) > 0;
Assert.assertFalse(biPredicate1.and(biPredicate2).test(1,"2"));
biPredicate1의 수행 결과와 biPredicate2의 수행 결과를 &&연산합니다.
and
인자로 받는 다른 Predicate의 람다 수행 내용과 , 자기 자신의 람다 수행 내용을 &&연산합니다.
or
인자로 받는 다른 Predicate의 람다 수행 내용과 , 자기 자신의 람다 수행 내용을 ||연산합니다.
negate
람다 수행 내용을! 연산합니다.
UnaryOperator <T>
하나의 인자와 인자를 가지고, 리턴 타입은 인자의 타입과 같습니다.
UnaryOperator의 원형
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
원형을 보면 알 수 있듯이 Function Interface를 상속하고 있습니다.
Function Interface의 메서드
- T apply(T t)
- default <V> Function <T, V> andThen(Function <? super R,? extends V> after)
- default <V> Function <V, R> compose(Function <? super V,? extends T> before)
도 가지고 있습니다.
public class UnaryOperatorTest {
@Test
public void test() {
identitry();
andThen();
}
private void identity() {
String result = (String) UnaryOperator.identity().apply("abcd");
System.out.println(result);
}
private void andThen() {
UnaryOperator<Integer> divTwo = (i) -> i / 2;
UnaryOperator<Integer> mulThree = (i) -> i * 3;
int result = divTwo.andThen(mulThree).apply(4);
Assert.assertThat(6, CoreMatchers.is(result));
}
}
예를 보면 Interget Type을 받아서 Integer Type을 리턴하는 것을 알 수 있고
부모 클래스 Function의 메서드도 사용할 수 있습니다.
그리고 Static 함수인 identity()를 호출하여 파라미터 그대로 반환할 수도 있습니다.
BiFunction <T, U, R>
BiFunction은 서로 다른 타입의 2개의 인자를 받아 또 다른 타입으로 리턴합니다.
BiFunction Interface의 원형
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
함수의 원형을 보면 T 타입 , 그리고 U 타입을 파라미터로 받고, R 타입으로 리턴합니다.
사용 예시
public class BiFunctionTest {
@Test
public void test() {
BiFunction<Integer, String, Integer> biFunction = (i, s) -> i + Integer.valueOf(s);
int result = biFunction.apply(10, "10");
System.out.println(result);
}
}
Integer , String 타입을 받아 , 람다 내부 연산을 수행하고
Integer타입으로 겨로 가를 리턴합니다.
Comparator <T>
자바 8에서 추가된 인터페이스는 아닙니다.
객체 간 우선순위를 비교할 때 사용하는 인터페이스이고 주고 1회성 구현을 많이 합니다.
람다의 등장으로 1회성 인스턴스 생성이 매우 간편해지면서 Comparable인터페이스 보다 많이 쓰이고 있습니다.
Comparator Interface의 원형
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
default <U> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
return thenComparing(comparing(keyExtractor, keyComparator));
}
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}
public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
}
}
Comperator Interface의 원형을 보게 되면 아주 깁니다.
compare(op1, op2) 함수는 람다식으로 비교하고 그 결과를 리턴합니다.
결과는 음수 , 0 , 양수입니다.
public class ComparatorTest {
class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
@Test
public void test() {
compare();
sortPerson();
}
private void compare() {
Comparator<Integer> comparator = (op1, op2) -> op1 - op2;
System.out.println(comparator.compare(1, 0));
}
private void sortPerson() {
List<Person> peoples = new ArrayList<Person>() {{
add(new Person(10, "a"));
add(new Person(11, "b"));
add(new Person(12, "c"));
add(new Person(12, "d"));
}};
Function<Person, Integer> sortFirst = person -> person.age;
Function<Person, String> sortSecond = person -> person.name;
peoples.stream()
.sorted(Comparator.comparing(sortFirst).thenComparing(sortSecond))
.forEach(p -> System.out.println("name is " + p.name + ",age is " + p.age));
}
}
위 예에서 compare() 함수 안의 Comparator comparator = (op1, op2) -> op1 - op2; 를 보게 되면
람다식 내부의 op1 - op2의 결과가
op1 <op2 인 경우는 음수를 리턴하고, op1=op2일 때는 0을 리턴하고, op1> op2일 때 양수를 리턴합니다.
두 번째 예 sortPerson은 Person객체의 age를 기준으로 정렬하고, 그다음 thenComparing을 통해 이름순으로 정렬합니다.
(1차 정렬을 나이로 한 후에 2차 정렬은 이름으로으로 진행된 결과를 갖습니다.)
Comparator의 static 메서드는 comparing은 Function을 파라미터로 받게 됩니다.
BinaryOperator <T>
동일한 타입의 인자 2개와 인자와 같은 타입의 리턴 타입을 가집니다.
BinaryOperator Interface의 원형
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
BinaryOperator의 원형을 보면 BiFunction을 상속받고 있습니다.
그리고 파라미터로 받은 T 타입을
BiFunction <T, T, T>로 전달하고 있습니다. (인자 두 개와 결과 모두 같은 타입인걸 알 수 있습니다.)
그리고 static 메서드 minBy와 maxBy가 존재합니다.
위 두 메서드는 Comparator <T>를 인자로 받습니다.
Comparator <T>는 위에서 설명했듯이 o1과 o2를 비교하여
o1==o2 이면 0을 , o1> o2이면 양수를 o1 <o2이면 음수를 리턴하는 compare() 메서드가 선언되어있습니다.
그리고 BinaryFunction은 두 값을 비교하여 min 또는 max값을 리턴합니다.
public class BinaryOperatorTest {
class Fruit {
public String name;
public int price;
public Fruit(String name, int price) {
this.name = name;
this.price = price;
}
}
@Test
public void test() {
min_max();
apply();
}
private void min_max() {
BinaryOperator<Fruit> binaryOperator;
Fruit fruit;
binaryOperator = BinaryOperator.minBy((f1, f2) -> Integer.compare(f1.price, f2.price));
fruit = binaryOperator.apply(new Fruit("apple", 5000), new Fruit("banana", 4000));
System.out.println(fruit.name);
binaryOperator = BinaryOperator.maxBy((f1, f2) -> Integer.compare(f1.price, f2.price));
fruit = binaryOperator.apply(new Fruit("apple", 5000), new Fruit("banana", 4000));
System.out.println(fruit.name);
}
private void apply() {
BinaryOperator<Integer> plus = (i, j) -> i + j;
int result = plus.apply(2, 5);
System.out.println(result);
}
}
첫 번째 예시 min_max()을 보면
만약 o1과 o2가 int 타입이라면 다음과 같이 Integer.compare(int, int) 메서드를 이용할 수 있습니다.
Integer.compare()는 첫 번째 매개 값이 두 번째 매개 값보다 작으면 음수, 같으면 0, 크면 양수를 리턴합니다.
정리
Runnable |
void run() 메서드가 선언되어있는 인터페이스. 리턴 타입 void이고 파라미터가 없음. 람다 블록 내의 내용만 수행하고 리턴하지 않습니다. |
Callable<V> |
V call() 메서드가 선언되어있는 인터페이스. 람다 내의 내용을 수행하고 제네릭으로 전달받은 V타입으로 리턴합니다. |
Supplier<T> |
T get() 메서드가 선언되어있는 인터페이스입니다. 매개변수를 받지 않고 특정 타입의 결과를 리턴합니다
|
Cunsumer<T> |
void accept(T) 메서드가 선언되어 있는 인터페이스. 입력된 T type 데이터에 대해 어떤 작업을 수행하고자 할 때 사용합니다. 리턴 타입이 void이므로 처리 결과를 리턴해야 하는 경우에는 Function 인터페이스를 사용해야 합니다. |
BiConsumer<T,U> |
void accept(T, U) 메서드가 선언되어있는 인터페이스. 리턴을 하지 않고(void), 서로 다른 두 개의 타입 인자를 받는 메서드를 갖고 있습니다. Consumer와 동일하게 데이터를 소모한다고 보면 됩니다. |
Function<T, R> |
R apply(T) 메서드가 선언되어 있는 인터페이스. 입력된 T type 데이터에 대해 일련의 작업을 수행하고 R type 데이터를 리턴할 때 사용합니다. 입력된 데이터를 변환할 때 사용할 수 있습니다. |
Predicate<T> | boolean test(T) 메서드가 선언되어 있는 인터페이스. 입력된 T type 데이터가 특정 조건에 부합되는지 확인하여 boolean 결과를 리턴합니다. |
BiPredicate<T,U> |
boolean test(T, U) 메서드가 선언되어 있는 인터페이스. 입력된 서로 다른 타입 T 타입, U타입 데이터가 특정 조건에 부합되는지 확인하여 boolean 결과를 리턴합니다. |
UnaryOperator <T> |
Fucntion인터페이스를 상속하는 인터페이스로, UnaryOperator <T>로 전달받은 T 타입을 Function <T, T>로 전달합니다. (interface UnaryOperator extends Function<T, T>) 하나의 인자를 가지고, 리턴 타입은 인자의 타입과 같습니다.
|
BiFunction <T ,U ,R> |
R apply (T, U) 메서드가 선언되어있는 인터페이스. 서로 다른 타입의 2개의 인자를 받아 또 다른 타입 R로 리턴합니다. |
Comparator<T> |
int compare(T op1, T op2) 메서드가 선언되어 있는 인터페이스로 op1과 op2를 람다 식으로 비교하고 그 결과를 int로 리턴합니다.
ex) op1 <op2 인 경우는 음수를 리턴하고, op1=op2일 때는 0을 리턴하고, op1> op2 일 때 양수 리턴 |
BinaryOperator<T> |
BiFunction 인터페이스를 상속하는 인터페이스로 BinaryOperator <T>로 전달받은 T 타입을 Bifunction <T, T , T>로 전달합니다.
타입이 같은 두 개의 인자 T, 와 리턴 값 T를 갖습니다. |
'JAVA' 카테고리의 다른 글
Concrete class == 구상클래스, 구현클래스, 구체클래스?? (0) | 2019.10.18 |
---|---|
Java - Comparable, Comparator (1) | 2019.10.15 |
Java 8 - Function Interface (3) | 2019.10.13 |
Java 8 - Interface바뀐점을 알아보기 (0) | 2019.10.11 |
equals,hashCode 알아보기 (2) | 2019.10.01 |
댓글