메소드 참조(Method references)

메소드를 참조해서 

매개변수의 정보 및 리턴타입을 알아내어 람다식에서 불필요한 매개변수를 제거하는 것이 목적이다.


종종 람다식은 기존 메소드를 단순하게 호출만 하는 경우가 있다.

 (left, right) -> Math.max(left,right);           
    --->  
 Math :: max  -----메소드 참조


메소드 참조도 람다식과 마찬가지로 인터페이스의 익명 구현 객체로 생성됨.

타겟 타입에서 추상메소드의 매개변수 및 리턴 타입에 따라 메소드 참조도 달라진다.

예)  IntBinayOperator 인터페이스는 두개의 int  매개값을 받아 int 값을 리턴 하므로
 동일한 매개값과 리턴타입을 갖는 Math 클래스의 max() 메소드를 참조할 수 있다.

IntBinaryOperator operator = Math :: max;  -----메소드 참조



정적 메소드와 인스턴스 메소드 참조

정적 메소드 참조

클래스 :: 메소드

인스턴스 메소드 참조

참조변수 :: 메소드


Exam

public class Calculator {
public static int staticMethod(int x, int y) {
return x + y;
}

public int instanceMethod(int x, int y) {
return x + y;
}

}
import java.util.function.IntBinaryOperator;

public class MethodReferencesExample {
public static void main(String[] args) {
IntBinaryOperator operator;

//정적 메소드 참도
operator = (x, y) -> Calculator.staticMethod(x, y);
System.out.println("결과1 : " + operator.applyAsInt(1, 2));
//결과1 : 3

operator = Calculator::staticMethod;
System.out.println("결과2 : " + operator.applyAsInt(3, 4));
//결과2 : 7

//인스턴스 메소드 참조
Calculator obj = new Calculator();
operator = (x,y) -> obj.instanceMethod(x, y);
System.out.println("결과3 : " + operator.applyAsInt(5,6));
//결과3 : 11

operator = obj::instanceMethod;
System.out.println("결과3 : " + operator.applyAsInt(7,8));
//결과4 : 15
}
}



매개변수의 메소드 참조

(a,b) -> {a.instanceMethod(b);}   --->   클래스 :: instanceMethod



ToIntBiFunction<String, String> function;


function = (a,b) -> a.compareToIgnoreCase(b);

print(function.applyAsInt( "Java8", "JAVA8"));


---->


function = String :; compareToIgnoreCase;

print(function.applyAsInt( "Java8", "JAVA8"));


exam

import java.util.function.ToIntBiFunction;

public class ArgumentMethodReferencesExample {
public static void main(String[] args) {
ToIntBiFunction<String, String> function;

function = (a, b) -> { return a.compareToIgnoreCase(b); };
print(function.applyAsInt("java8", "JAVA8"));
//동일한 문자열입니다.

function = String::compareToIgnoreCase;
print(function.applyAsInt("java8", "JAVA8"));
//동일한 문자열입니다.
}

private static void print(int order) {
if (order < 0) {System.out.println("사전 순으로 먼저 옵니다."); }
else if (order == 0) { System.out.println("동일한 문자열입니다."); }
else {System.out.println("사전순으로 나중에 옵니다."); }
}
}



생정자 참조

(a, b)  -> {return new 클래스(a,b);}   --- > 클래스 :: new

Exam

public class Member {
private String name;
private String id;

public Member() {
System.out.println("Member() 실행");
}

public Member(String id) {
System.out.println("Member(String id) 실행");
this.id = id;
}

public Member(String name, String id) {
System.out.println("Member(String name, String id) 실행");
this.name = name;
this.id = id;
}
}
import java.util.function.BiFunction;
import java.util.function.Function;

public class ConstructorReferencesExample {
public static void main(String[] args) {
Function<String, Member> function1 = Member::new;
Member member1 = function1.apply("angel");
// Member(String id) 실행

BiFunction<String, String, Member> function2 = Member::new;
Member member2 = function2.apply("김나박", "angel");
//Member(String name, String id) 실행
}
}


한 개의 추상메소드를 가지는 인터페이스들은 모두 람다식 사용 가능

예: Runnable 인터페이스

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

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




자바 8부터  표준 API로 제공되는 함수적 인터페이스

java.util.function 패키지에 포함되어 있다.

매개타입으로 사용되어 람다식을 매개값으로 대입할 수 있도록 해준다.

method( Consummer con ){ ... };
method(    (x) -> { ...}   );

종류

Consumer 함수적 인터페이스류 :      데이터를 소비

매개값만 있고 리턴값이 없는 추상 메소드를 가지고 있다.

Supplier 함수적 인터페이스류 :         데이터를 공급

매개값은 없고 리턴값만 있는  추상 메소드를 가지고 있다.

Function 함수적 인터페이스류 :       A를 B로 변환

매개값과  리턴값이 모두 있는 추상 메소드를 가지고 있다.
주로 매개값을 리턴값으로 매핑(타입변환)할 경우에 사용

Operator 함수적 인터페이스류 :     데이터를 연산

매개값과 리턴값이 모두 있는 추상 메소드를 가지고 있다.
주로 매개값을 연산하고 그 결과를 리턴할 경우에 사용

Predicate 함수적 인터페이스류 :        매개 값을 조사해서 true & false 리턴

매개값을 조사해서 true 또는 false를 리턴할 때 사용




Consumer 함수적 인터페이스

Consumer 함수적 인터페이스의 특징        :        리턴값이 없는 accept() 메소드를 가지고 있다.

accept()메소드는 단지 매개값을 소비하는 역할만 한다..     소비한다는 말은 사용만 할 뿐  리턴값이 없다는 뜻.


Consumer인터페이스와 추상메소드 참고 -->    https://docs.oracle.com/javase/8/docs/api/java/util/function/package-frame.html                       


Exam

import java.util.function.*;

public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> consumer =
s -> System.out.println("s + \"8\" = " + s + "8");
consumer.accept("JAVA");

BiConsumer<String, String> biConsumer =
(s, s2) -> System.out.println("s + s2 = " + s + s2);
biConsumer.accept("Java","8");

DoubleConsumer doubleConsumer =
value -> System.out.println("Java" + value);
doubleConsumer.accept(7.0);

ObjIntConsumer<String> objIntConsumer =
(s, value) -> System.out.println("s + value = " + s + value);
objIntConsumer.accept("JAVA", 8);
}
}


Supplier 함수적 인터페이스

Supplier 함수적 인터페이스 특징    :         매개변수가 없고, 리턴값이 있는 getXXX() 메소드를 가지고 있다.

         이들 메소드는 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 한다.


Supplier 인터페이스와 추상메소드  참고 -->    https://docs.oracle.com/javase/8/docs/api/java/util/function/package-frame.html              

Exam

import java.util.function.IntSupplier;

public class SupplierExample {
public static void main(String[] args) {

IntSupplier intSupplier = () -> {
int num = (int) (Math.random() * 6) + 1;
return num;
};
System.out.println("주사위 1~6중의 수: = " + intSupplier.getAsInt());
System.out.println("주사위 1~6중의 수: = " + intSupplier.getAsInt());
System.out.println("주사위 1~6중의 수: = " + intSupplier.getAsInt());
//외에 DoubleSupplier, BooleanSupplier, LongSupplier 등등이 있다.
}
}


Function 함수적 인터페이스

Function 함수적 인터페이스 특징   :         매개변수와 리턴값이 있는 applyXXX(), XXXToXXFuncion() , ToXXFuntion() 메소드를 가지고 있다.
이들 메소드는 매개값을 리턴값으로 매핑(타입변환)하는 역할을 한다. 

   A      --->  function   ---> B
매개값 --- >  function  ---> 리턴값

Exam

public class Student {
private String name;
private int englishScore;
private int mathScore;

public Student(String name, int englishScore, int mathScore) {
this.name = name;
this.englishScore = englishScore;
this.mathScore = mathScore;
}

public String getName() {
return name;
}

public int getEnglishScore() {
return englishScore;
}

public int getMathScore() {
return mathScore;
}
}

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

public class FunctionExample1 {
//두개의 Student를 가진 리스트 생성
private static List<Student> list = Arrays.asList(
new Student("홍길동", 90, 96),
new Student("김나박", 95, 93)
);

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

public static void printInt(ToIntFunction<Student> function) {
for (Student student : list) {
System.out.print(function.applyAsInt(student) + " ");
}
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());
}
}

Exam2

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

public class FunctionExample2 {
//두개의 Student를 가진 리스트 생성
private static List<Student> list = Arrays.asList(
new Student("홍길동", 90, 96),
new Student("김나박", 95, 93)
);
//평균을 구하는..
public static double avg(ToIntFunction<Student> function) {
int sum = 0;
for (Student student : list) {
sum += function.applyAsInt(student);
}
double avg = (double)sum / list.size();
return avg;
}

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

double mathAvg = avg(t -> t.getMathScore());
System.out.println("수학 평균 점수 = " + mathAvg);
}
}



Operator 함수적 인터페이스

Operator 함수적 인터페이스 특징      :             Function 과 동일하게 매개변수와 리턴값이 있는     applyXXX() 메소드를 가지고 있다.
하지만 이들 메소드는 매개값을 리턴값으로 매핑(타입변환)하는  역할보다는 
매개 값을  이용해서 연산을 수행한 후 동일한 타입으로 리턴 값을 제공하는 역할을 한다.


Operator 인터페이스와 추상메소드  참고 -->    https://docs.oracle.com/javase/8/docs/api/java/util/function/package-frame.html              



Exam

import java.util.function.IntBinaryOperator;

public class OperatorExample {
private static int[] scores = {92, 95, 87};

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 인터페이스와 추상메소드  참고 -->    https://docs.oracle.com/javase/8/docs/api/java/util/function/package-frame.html              



Exam


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

public class PredicateExample {
private static List<Student> list = Arrays.asList(
new Student("홍길동", "남자", 90),
new Student("김나박", "여자", 90),
new Student("김범수", "남자", 95),
new Student("박정현", "여자", 92)
);

public static double avg(Predicate<Student> predicate) {
int count = 0;
int sum = 0;
for (Student student : list) {
if (predicate.test(student)) {
count++;
sum += student.getScore();
}
}
return (double)sum / count;
}

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

//여자 평균 점수
double femaleAvg = avg(t -> t.getSex().equals("여자"));
System.out.println("여자 평균 점수 : " + femaleAvg);
}
}





andThen()과 compose() 디폴트 메소드

함수적 인터페이스가 가지고 있는 디폴트 메소드이다.

두 개의 함수적 인터페이스를  순차적으로 연결해서 실행한다.

첫번째 리턴값을 두번째 매개값으로 제공해서 최종 결과값 리턴

andThen()과 compose()의 차이점은 어떤 함수적 인터페이스부터 처리하느냐이다.


andThen() 디폴트 메소드    

인터페이스AB  = 인터페이스A.andThen(인터페이스B)
최종결과 = 인터페이스 AB.method();

인터페이스AB method()  ---> 인터페이스A(람다식)  ---> 인터페이스B(람다식) ---> 최종결과

compose() 디폴트 메소드

인터페이스AB  = 인터페이스A.compose(인터페이스B)
최종결과 = 인터페이스 AB.method();


최종결과   <--- 인터페이스A(람다식)  <--- 인터페이스B(람다식) <--- 인터페이스AB method()





Consumer의 순차적 연결

     Consumer 종류의 함수적인터페이스는 처리 결과를 리턴하지 않기 때문에 andThen()과 compose()디폴트 메소드는 함수적 인터페이스의 호출 순서만 정한다.


exam

public class Member {
private String name;
private String id;
private Address address;

public Member(String name, String id, Address address) {
this.name = name;
this.id = id;
this.address = address;
}

public String getName() {
return name;
}

public String getId() {
return id;
}

public Address getAddress() {
return address;
}
}
public class Address {
private String country;
private String city;

public Address(String country, String city) {
this.country = country;
this.city = city;
}

public String getCountry() {
return country;
}

public String getCity() {
return city;
}
}
import java.util.function.Consumer;

public class ConsumerAndThenExample {
public static void main(String[] args) {
Consumer<Member> consumerA = m -> {
System.out.println("consumerA: " + m.getName());
};

Consumer<Member> consumerB = m -> {
System.out.println("consumerB: " + m.getId());
};

Consumer<Member> consumerAB = consumerA.andThen(consumerB);
consumerAB.accept(new Member("홍길동", "IdHong", null));
// 실행 결과
// consumerA: 홍길동
// consumerB: IdHong

Consumer<Member> consumerAC = consumerB.andThen(consumerA);
consumerAC.accept(new Member("홍길동", "IdHong", null));
// 실행 결과
// consumerB: IdHong
// consumerA: 홍길동
}
}



Function의 순차적 연결

Function과 Operator종류의 함수적 인터페이스는 
먼저 실행한 함수적 인터페이스의 결과 를 다음 함수적 인터페이스의 매개값 으로 넘겨주고 , 최종 처리결과를 리턴한다.


결과적으로 Address로 String을 얻는 결과
Function<Member,Address> + Function<Address, String>    = Function<Member, String>


Exam

import java.util.function.Function;

public class FunctionAndThenComposeExample {
public static void main(String[] args) {
Function<Member, Address> functionA;
Function<Address, String> functionB;
Function<Member, String> functionAB;

functionA = m -> m.getAddress();
functionB = a -> a.getCity();

functionAB = functionA.andThen(functionB);

String city =
functionAB.apply(new Member("홍길동", "IdHong", new Address("한국", "서울")));
System.out.println("거주도시 = " + city);
// 실행 결과
// 거주도시 = 서울

functionAB = functionB.compose(functionA);
city = functionAB.apply(new Member("홍길동", "IdHong", new Address("한국", "서울")));
System.out.println("거주도시 = " + city);
// 실행 결과
// 거주도시 = 서울
}
}



and() , or(), negate() 디폴드 메소드와 isEqual() 정적 메소드

and(), or(), negate() 디폴트메소드

Predicate 함수적 인터페이스의 디폴트 메소드

and():  &&와 대응 - 두 Predicate가 모두 true 를 리턴하면 최종적으로 true를 리턴
PredicateAB = predicateA.and(predicateB);

or():     ||와 대응 - 두 Predicate 중 하나만 true를 리턴하면 최종적으로 true를 리턴
       PredicateAB = predicateA.or(predicateB);

negate(): !과 대응  - Predicate의 결과가 true면 false,   false면 true를 리턴
      PredicateAB = predicateA.negate();

Exam

import java.util.function.IntPredicate;

public class PredicateAndOrNegateExample {
public static void main(String[] args) {
//2의 배수를 검사
IntPredicate predicateA = a -> { return (a % 2 == 0); };

//3의 배수를 검사
IntPredicate predicateB = a ->{ return (a % 3 == 0);};

IntPredicate predicateAB;
boolean result;

//and()
predicateAB = predicateA.and(predicateB);
result = predicateAB.test(9);
System.out.println("9는 2와 3의 배수입니까? " + result);

//or()
predicateAB = predicateA.or(predicateB);
result = predicateAB.test(9);
System.out.println("9는 2와 3의 배수입니까? " + result);

//negate
predicateAB = predicateA.negate();
result = predicateAB.test(9);
System.out.println("9는 false 입니까? " + result);
}
}


isEqual() 정적 메소드

Predicate<T>의 정적 메소드



Exam

import java.util.function.Predicate;

public class PredicateIsEqualExample {
public static void main(String[] args) {
Predicate<String > predicate;
predicate = Predicate.isEqual(null);
System.out.println("null, null: " + predicate.test(null));
//null과 null은 true

predicate = Predicate.isEqual("Java8");
System.out.println("null, Java8: " + predicate.test(null));
//false

predicate = Predicate.isEqual("Java8");
System.out.println("null, Java8: " + predicate.test(null));
//false

predicate = Predicate.isEqual("null");
System.out.println("null, Java8: " + predicate.test("Java8"));
//false

predicate = Predicate.isEqual("Java8");
System.out.println("Java8, Java8: " + predicate.test("Java8"));
//true
}
}




minBy(), maxBy 정적 메소드

BinaryOperator<T> 함수적 인터페이스의 정적 메소드

Comparator를 이용해서  최대 T와 최소 T를 얻는 BinaryOperator<T>를 리턴한다.

리턴타입 

정적 메소드 

BinaryOperator<T> 

minBy(Compareator<? super T> comparator) 

BinaryOperator<T>  

maxBy(Compareator<? super T> comparator) 


Comparator<T> 는 다음과 같이 선언된 함수적 인터페이스이다. 


o1과 o2를 비교해서 o1이 작으면 음수를,

 o1 과 o2를 비교해서 동일하면  0,    

 o1이 크면 양수를 리턴해야하는 compara() 메소드가 선언되어 있다.



@functionalInterface

public interface Comparator<T> {

public int compare(T o1, T o2);

}


Comparator<T>를 타겟 타입으로하는 람다식은 다음과 같이    작성할 수 있다.


(o1, o2) -> { ...; return int 값 ;}


만약 o1 와 o2가 int 타입이라면 다음과 같이 Integer.compare(int, int) 메소드를 이용 할 수 있다.


(o1, o2) -> Integer.compare(o1, o2);




Exam

public class Fruit {
public String name;
public int price;

public Fruit(String name, int price) {
this.name = name;
this.price = price;
}
}
import java.util.function.BinaryOperator;

public class OperatorMinByMaxByExample {
public static void main(String[] args) {

BinaryOperator<Fruit> binaryOperator;

Fruit fruit;
binaryOperator = BinaryOperator.minBy((f1, f2) -> Integer.compare(f1.price, f2.price));
fruit = binaryOperator.apply(new Fruit("딸기", 6000), new Fruit("수박", 10000));
System.out.println("fruit.name = " + fruit.name);
// fruit.name = 딸기

binaryOperator = BinaryOperator.maxBy((f1, f2) -> Integer.compare(f1.price, f2.price));
fruit = binaryOperator.apply(new Fruit("딸기", 6000), new Fruit("수박", 10000));
System.out.println("fruit.name = " + fruit.name);
// fruit.name = 수박
}
}


클래스의 멤버 사용

람다식 실행 블록에는 클래스의 멤버인 필드와 메소드를 제약없이 사용할 수 있다.

람다식 실행 블록내에서 this는 람다식을 실행한 객체의 참조이다.


Exam    ( UsingThis 의 Filed2 와 Inner의 Filed2이름이 같을때 this 

public class UsingThis {
public int outterField1 = 10;
public int Filed2 = 1000;

class Inner {
int innerField1 = 20;
int Field2 = 2000;

void method() {
MyFunctionalInterface fi = () -> {
System.out.println("outterField = " + outterField1); //결과값 10
System.out.println("innerField = " + innerField1); //결과값 20

System.out.println("Field2 = " + Field2); // 결과값 2000
System.out.println("Field2 = " + Field2); // 결과값 2000

System.out.println("UsingThis.this.Filed2 = " + UsingThis.this.Filed2); //결과값 1000
System.out.println("this.Field2 = " + this.Field2); // 또는 Field2 호출 결과값 2000
};
fi.method();
}
}
}



로컬 변수의 사용

람다식은 함수적 인터페이스의  익명 구현 객체를 생성한다.

람다식에서 사용하는 외부 로컬 변수는 final 특성을 갖는다.

Exam


public class UsingLocalVariable {
void method(int arg) {
int localVar = 40;

// arg = 31; 수정하려고하면 에러 발생 아래 람다식에서 사용했기때문에 final
// localVar = 20; 수정하려고하면 에러 발생 아래 람다식에서 사용했기때문에 final

MyFunctionalInterface fi = () -> {
System.out.println("arg = " + arg); //arg 는 final특성을 가짐
System.out.println("localVar = " + localVar); //localVar 는 final특성을 가짐
};
fi.method();
}
}



'JAVA > JAVA' 카테고리의 다른 글

자바 컬렉션) Set 컬렉션  (0) 2018.06.14
람다식) 표준 API의 함수적 인터페이스  (0) 2018.06.10
람다식 타겟 타입과 함수적 인터페이스  (0) 2018.06.09
람다식 기본 문법  (0) 2018.06.09
람다식이란?  (0) 2018.06.09

+ Recent posts