해당 내용은 Java8 In Action 책을 요약 및 정리한 내용입니다.
좀 더 자세히 알고 싶으신 분들은 책을 사서 읽어보심을 추천드립니다.!
3.5 형식 검사,형식추론,제약
Entity
AppleEntity.java
package Part1.Chapter3.Chapter3_3_5.entity;
public class AppleEntity {
private String color;
private Integer weight;
public AppleEntity() {}
public AppleEntity(String color, Integer weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
}
Main
package Part1.Chapter3.Chapter3_3_5;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.ToIntBiFunction;
import Part1.Chapter3.Chapter3_3_5.entity.AppleEntity;
/*
* 3.5 형식 검사, 형식 추론, 제약
*
* 람다 표현식 자체에는 람다가 어떤 함수형 인터페이스를 구현하는지의 정보가 포함되어 있지 않다.
* 따라서 람다 표현식을 더 제대로 이해하려면 람다의 실제 형식을 파악해야 한다.
*/
public class Main_3_5 {
public static List<AppleEntity> filter(List<AppleEntity> inventory, Predicate<AppleEntity> p) {
List<AppleEntity> result = new ArrayList<>();
for(AppleEntity apple : inventory) {
if(p.test(apple)) {
result.add(apple);
}
}
return result;
}
public static void main(String[] args) throws Exception {
List<AppleEntity> inventory = Arrays.asList(new AppleEntity("red", 100)
, new AppleEntity("green", 200));
/*
* 3.5.1 형식 검사
*
* 람다가 사용되는 콘텍스트(context)를 이용해서 람다의 형식(type)을 추론할 수 있다.
* 어떤 콘텍스트(예를 들면 람다가 전달될 메서드 파라미터나 람다가 할당되는 변수 등)
* 에서 기대되는 람다 표현식의 형식을 "대상 형식(target type)"이라고 부른다.
*
* 위 람다 표현식을 사용할 때 다음과 같은 순서로 형식 확인 과정이 진행된다.
* 1. filter 메서드의 선언을 확인.
* 2. filter 메서드는 두 번째 파라미터로 Predicate<AppleEntity> 형식(대상 형식)을 기대한다.
* 3. Predicate<AppleEntity>은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스이다.
* 4. test 메서드는 AppleEntity을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
* 5. filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다.
*
* 위 예제에서 람다 표현식은 AppleEntity을 인수로 받아 boolean을 반환하므로 유효한 코드이다.
* 람다 표현식이 예외를 던질 수 있다면 추상 메서드도 같은 예외를 던질 수 있도록 throws로 선언해야 한다.
*
* 람다 표현식의 형식 검사 과정의 재구성
* filter(inventory, (AppleEntity a) -> a.getWeight() > 150)
* 1. 람다가 사용된 콘텍스트는 무엇인가? 우선 filter의 정의를 확인한다.
* filter(inventory, Predicate<AppleEntity> p)
* 2. 대상 형식은 Predicate<AppleEntity>이다.
* 대상 형식
* 3. Predicate<AppleEntity>인터페이스의 추상 메서드는 무엇인가?
* boolean test(AppleEntity appleEntity)
* 4. AppleEntity을 인수로 받아 boolean을 반환하는 test 메서드다.
* AppleEntity -> boolean
* 5. 함수 디스크립터는 AppleEntity -> boolean이므로 람다의 시그너처와 일치한다.
* 람다도 AppleEntity을 인수로 받아 boolean을 반환하므로 코드 형식 검사가 완료된다.
*/
filter(inventory
, (AppleEntity a) -> a.getWeight() > 150)
.stream()
.forEach((AppleEntity a) -> System.out.println("3.5.1 형식 검사 : "
+ a.getColor() +" apple - " + a.getWeight() + " weight." ));
/*
* 3.5.2 같은 람다, 다른 함수형 인터페이스
*
* 대상 형식이라는 특징 때문에 같은 람다 표현식이더라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다.
* 예를 들어 Callable과 PrivilegedAction 인터페이스는 () -> T와 같은 메서드 시그너처를 가지고 있다.
* 따라서 아래 할당문은 모두 유효한 코드이다.
*/
Callable<Integer> c = () -> 42;
System.out.println("Callable : " + c.call());
PrivilegedAction<Integer> p = () -> 52;
System.out.println("PrivilegedAction : " + p.run());
/* 아래 코드와 같이 하나의 람다 표현식을 다양한 함수형 인터페이스에 사용할 수 있다.*/
Comparator<AppleEntity> c1 = (AppleEntity a1, AppleEntity a2) -> a1.getWeight().compareTo(a2.getWeight());
System.out.println("Comparator : " + c1.compare(new AppleEntity("green", 200), new AppleEntity("red", 100)));
ToIntBiFunction<AppleEntity, AppleEntity> c2 = (AppleEntity a1, AppleEntity a2) -> a1.getWeight().compareTo(a2.getWeight());
System.out.println("ToIntBiFunction : " + c2.applyAsInt(new AppleEntity("green", 100), new AppleEntity("red", 200)));
BiFunction<AppleEntity, AppleEntity, Integer> c3 = (AppleEntity a1, AppleEntity a2) -> a1.getWeight().compareTo(a2.getWeight());
System.out.println("BiFunction : " + c3.apply(new AppleEntity("green", 100), new AppleEntity("red", 200)));;
/*
* 다이아몬드 연산자
* 주어진 클래스 인스턴스 표현식을 두 개 이상의 다양한 콘텍스트에 사용할 수 있다.
* 이때 인스턴스 표현식의 형식 인수는 콘텍스트에 의해 추론된다.
* List<String> listOfStrings = new ArrayList<>();
* List<Integer> listOfIntegers = new ArrayList<>();
*
* 특별한 void 호환 규칙
* 람다의 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환된다.(물론 파라미터 리스트도 호환되어야 함.)
* 예를 들어 다음 두 행의 예제에서 List의 add 메서드는 Consumer 콘텍스트(T -> void)가 기대하는 void 대신 boolean을 반환하지만
* 유효한 코드이다.
* // Predicate는 불린 반환값을 갖는다.
* Predicate<String> p = s -> list.add(s);
* // Consumer는 void 반환값을 갖는다.
* Consumer<String> b = s -> list.add(s);
*/
/*
* 3.5.3 형식 추론
*
* 자바 컴파일러는 람다 표현식이 사용된 콘텍스트(대상 형식)을 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.
* 즉, 대상 형식을 이용해서 함수 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그너처도 추론할 수 있다.
* 결과적으로 컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서는 이를 생략할 수 있다.
*/
filter(inventory, (a) -> "green".equals(a.getColor()) )
.stream()
.forEach((a) -> System.out.println("3.5.2 형식 추론 : "
+ a.getColor() +" apple - " + a.getWeight() + " weight." ));
/*
* 여러 파라미터를 포함하는 람다 표현식에서는 코드 가독성 향상이 두드러진다.
* 다음은 Comparator 객체를 만드는 코드다.
*/
// 형식 추론을 하지 않음.
Comparator<AppleEntity> notTypeResaoning = (AppleEntity a1, AppleEntity a2) -> a1.getWeight().compareTo(a2.getWeight());
System.out.println("notTypeResaoning : " + notTypeResaoning.compare(new AppleEntity("red", 300), new AppleEntity("blue", 400)));
// 형식 추론 함.
Comparator<AppleEntity> typeResaoning = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
System.out.println("typeResaoning : " + typeResaoning.compare(new AppleEntity("red", 500), new AppleEntity("blue", 400)));
/*
* 3.5.4 지역 변수 사용
*
* 람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수(free variable - 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를
* 활용할 수 있다. 이와 같은 동작을 람다 캡쳐링(capturing lambda)이라고 한다.
* 아래 예는 portNumber 변수를 캡처하는 람다이다.
*/
int portNumber = 80;
Runnable r = () -> System.out.println("capturing lambda : " + portNumber);
r.run();
/*
* 하지만 자유 변수에도 약간의 제약이 있다. 인스턴스 변수와 정적 변수를 캡처(자신의 바디에서 참조할 수 있도록)할 수 있다.
* 하지만 그러려면 지역 변수는 명시적으로 final로 선언되어 있어야 하거나 실질적으로 final로 선언된 변수와 똑같이 사용되어야 한다.
* 즉, 람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡처할 수 있다.(참고 : 인스턴스 변수 캡처는 final 지역 변수 this를 캡처하는 것과 마찬가지이다.)
*/
int errorPortNumber = 81;
Runnable r1 = () -> System.out.println("errorPortNumber lambda : " + errorPortNumber);
/* 아래 변수의 재할당 하는 부분을 주석 해제 하면 위 람다 표현식에서 에러가 발생한다. */
//errorPortNumber = 82;
r1.run();
/*
* 지역 변수의 제약
* 지역 변수의 이런 제약은 인스턴스 변수와 지역 변수는 태생이 다른데 부터 발생한다.
* 인스턴스 변수는 힙에 저장되는 반면 지역 변수는 스택에 위치한다. 람다에서 지역 변수에 바로 접근할 수 있다는
* 가정 하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었음에도 람다를 실행하는
* 스레드에서는 해당 변수에 접근하려 할 수 있다.
* 따라서 원래 변수에 접근을 허용하는 것이 아니라 자유 변수의 복사본을 제공한다.
* 따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴 것이다.
*
* 또한 지역 변수의 제약 때문에 외부 변수를 변화시키는 일반적인 명령형 프로그래밍 패턴(병렬화를 방해는 요소)에 제동을 걸 수 있다.
*/
/*
* 클로저
* 원칙적으로 클로저란 함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스를 가리킨다.
* 예를 들어 클로저를 다른 함수의 인수로 전달할 수 있고 클로저는 클로저 외부에 정의된 변수의 값에 접근하고 값을 바꿀 수 있다.
* 자바 8의 람다와 익명 클래스는 클로저와 비슷한 동작을 수행한다.
* 람다와 익명 클래스 모두 메서드의 인수로 전달될 수 있으며 자신의 외부 영역의 변수에 접근할 수 있다.
* 다만 람다와 익명 클래스는 람다가 정의된 메서드의 지역 변수 값은 바꿀 수 없다.
* 람다가 정의된 메서드의 지역 변수값은 final 변수여야 한다. 덕분에 람다는 변수가 아닌 값에 국한되어 어떤 동작을 수행한다는 사실이
* 명확해진다.
* 이전에도 설명한 것 처럼 지역 변수값은 스택에 존재하므로 자신을 정의한 스레드와 생존을 같이 해야 하며 따라서 지역 변수는 final이어야 한다.
* 가변 지역 변수를 새로운 스레드에서 캡처할 수 있다면 안전하지 않는 동작을 수행할 가능성이 생긴다.(인스턴스 변수는 스레드가 공유하는 힙에
* 존재하므로 특별한 제약이 없다.)
*/
}
}
3.6 메서드 레퍼런스
Entity
Fruit.java
package Part1.Chapter3.Chapter3_3_6.entity;
public class Fruit {
protected String color;
protected Integer weight;
public Fruit() {}
public Fruit(Integer weight) {
this.weight = weight;
}
public Fruit(String color, Integer weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
}
AppleEntity.java
package Part1.Chapter3.Chapter3_3_6.entity;
public class AppleEntity extends Fruit {
public AppleEntity() {}
public AppleEntity(Integer weight) {
this.weight = weight;
}
public AppleEntity(String color, Integer weight) {
this.color = color;
this.weight = weight;
}
}
OrangeEntity.java
package Part1.Chapter3.Chapter3_3_6.entity;
public class OrangeEntity extends Fruit {
public OrangeEntity() {}
public OrangeEntity(Integer weight) {
this.weight = weight;
}
public OrangeEntity(String color, Integer weight) {
this.color = color;
this.weight = weight;
}
}
Main
package Part1.Chapter3.Chapter3_3_6;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import Part1.Chapter3.Chapter3_3_6.entity.AppleEntity;
import Part1.Chapter3.Chapter3_3_6.entity.Fruit;
import Part1.Chapter3.Chapter3_3_6.entity.OrangeEntity;
/*
* 3.6 메서드 레퍼런스
*
* 메서드 레퍼런스를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있다.
* 때로는 람다 표현식보다 메서드 레퍼런스를 사용하는 것이 더 가독성이 좋으며 자연스러울수 있다.
*
* 3.6.1 요약
* 메서드 레퍼런스는 특정 메서드만을 호출하는 람다의 축약형이라고 생각할 수 있다.
* 예를 들어 람다가 '이 메서드를 직접 호출해'라고 명령한다면 메서드를 어떻게 호출해야 하는지 설명을
* 참조하기보다는 메서드명을 직접 참조하는 것이 편리하다.
* 실제로 메서드 레퍼런스를 이용하면 기존에 구현된 메서드를 람다 표현식을 만들수 있다.
* 이때 명시적으로 메서드명을 참조함으로써 가독성을 높일수 있다.
* 메서드 레퍼런스는 메서드명 앞에 구분자(::)를 붙이는 방식으로 메서드 레퍼런스를 활용할 수 있다.
*
* 람다와 메서드 레퍼런스 단축 표현 예제
* 람다 : (AppleEntity a) -> a.getWeight()
* 메서드 레퍼런스 단축 표현 : AppleEntity::getWeight
*
* 람다 : () -> Thread.currentThread().dumpStack()
* 메서드 레퍼런스 단축 표현 : Thread.currentThread()::dumpStack
*
* 람다 : (str, i) -> str.substring(i)
* 메서드 레퍼런스 단축 표현 : String::substring
*
* 람다 : (String s) -> System.out.println(s)
* 메서드 레퍼런스 단축 표현 : System.out::println
*
* 메서드 레퍼런스를 새로운 기능이 아니라 하나의 메서드를 참조하는 람다를 편리하게 표현할 수 있는 문법으로 간주할 수 있다.
*/
public class Main_3_6 {
public static Fruit giveMeFruit(Map<String, Function<Integer, Fruit>> map, String fruit, Integer weight) {
return map.get(fruit.toLowerCase()).apply(weight);
}
public static List<AppleEntity> map(List<Integer> list , Function<Integer, AppleEntity> f) {
List<AppleEntity> result = new ArrayList<>();
for(Integer e : list) {
result.add(f.apply(e));
}
return result;
}
public static void main(String[] args) {
List<AppleEntity> inventory = Arrays.asList(new AppleEntity("green", 100)
, new AppleEntity("red", 200)
, new AppleEntity("blue", 300));
/*
* 람다 표현식으로 정렬.
*/
inventory.sort((a1, a2) -> a2.getWeight().compareTo(a1.getWeight()));
inventory.stream().forEach((appleEntity) -> System.out.println("[Lambda - Descending] color : "
+ appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
/*
* java.util.Comparator.comparing와 메서드 레퍼런스를 이용한 정렬.
*/
inventory.sort(Comparator.comparing(AppleEntity::getWeight));
inventory.stream().forEach((appleEntity) -> System.out.println("[Comparator.comparing - Ascending] color : "
+ appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
/*
* 정적 메서드 레퍼런스
*/
Function<String, Integer> f = (str) -> Integer.parseInt(str);
System.out.println("정적 메서드 람다 표현식 : " + f.apply("1234"));
f = Integer::parseInt;
System.out.println("정적 메서드 레퍼런스 표현식: " + f.apply("7894"));
/*
* 다양한 형식의 인스턴스 메서드 레퍼런스
*/
f = (str) -> str.length();
System.out.println("인스턴스 메서드 람다 표현식 : " + f.apply("kim sung"));
f = String::length;
System.out.println("인스턴스 메서드 레퍼런스 표현식 : " + f.apply("kim sung wook"));
/*
* 기존 객체의 인스턴스 메서드 레퍼런스
*/
List<AppleEntity> appleInventory = new ArrayList<AppleEntity>() {
private static final long serialVersionUID = 1L;
{
add(new AppleEntity("blue1", 100));
add(new AppleEntity("blue2", 200));
add(new AppleEntity("blue3", 300));
}
};
Function<AppleEntity, Boolean> appleEntityFunction = appleInventory::add;
appleEntityFunction.apply(new AppleEntity("white2", 500));
appleInventory.stream().forEach((appleEntity) -> System.out.println("기존 객체의 인스턴스 메서드 레퍼런스 : color : "
+ appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
/*
* 3.6.2 생성자 레퍼런스
*
* ClassName::new처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 레퍼런스를 만들수 있다.
* 이것은 정적 메서드의 레퍼런스를 만드는 방법과 비슷하다.
*/
Supplier<AppleEntity> c1 = AppleEntity::new;
AppleEntity a1 = c1.get();
System.out.println("3.6.2 생성자 레퍼런스 Supplier(메서드 레퍼런스) : " + a1);
/*
* 위 예제는 아래 코드와 같다.
*/
c1 = () -> new AppleEntity();
a1 = c1.get();
System.out.println("3.6.2 생성자 레퍼런스 Supplier(람다 표현식) : " + a1);
/*
* AppleEntity(Integer weight)라는 시그너처를 갖는 생성자는 Function 인터페이스의 시그너처와 같다.
* 따라서 다음과 같이 구현이 가능하다.
*/
Function<Integer, AppleEntity> c2 = AppleEntity::new;
AppleEntity a2 = c2.apply(110);
System.out.println("3.6.2 생성자 레퍼런스 Function(메서드 레퍼런스) : " + a2);
c2 = (Integer weight) -> new AppleEntity(weight);
a2 = c2.apply(220);
System.out.println("3.6.2 생성자 레퍼런스 Function(람다 표현식) : " + a2);
/*
* 다음 코드에서 Integer를 포함하는 리스트의 각 요소를 map 메서드를 이용해서 AppleEntity 생성자로 전달한다.
* 결과적으로 다양한 무게를 포함하는 사과 리스트가 만들어진다.
*/
List<Integer> weights = Arrays.asList(7, 3, 4, 10);
List<AppleEntity> apples = map(weights, AppleEntity::new);
apples.stream().forEach((AppleEntity a) -> System.out.println("3.6.2 생성자 레퍼런스 map 메서드 : weight = "
+ a.getWeight() ));
/*
* AppleEntity(String, Integer)처럼 두 인수를 갖는 생성자는 BiFunction인터페이스와 같은 시그너처를 가지므로
* 다음처럼 할 수 있다.
*/
BiFunction<String, Integer, AppleEntity> c3 = AppleEntity::new;
AppleEntity a3 = c3.apply("red", 100);
System.out.println("3.6.2 생성자 레퍼런스 BiFunction(메서드 레퍼런스) : " + a3);
/*
* 인스턴스화하지 않고도 생성자에 접근할 수 있는 기능을 다양한 상황에 응용할 수 있다.
* 예를 들어 Map으로 생성자와 문자열값을 관련시킬 수 있다.
* 그리고 String과 Integer가 주어졌을 때 다양한 무게를 갖는 여러 종류의 과일을 만드는 메서드를
* 만들수 있다.
*/
Map<String, Function<Integer, Fruit>> map = new HashMap<String, Function<Integer,Fruit>>(){
private static final long serialVersionUID = 1L;
{
put("apple", AppleEntity::new);
put("orange", OrangeEntity::new);
}
};
System.out.println("giveMeFruit 메서드(AppleEntity 객체) : " + giveMeFruit(map, "apple", 100).getWeight());
System.out.println("giveMeFruit 메서드(OrangeEntity 객체) : " + giveMeFruit(map, "orange", 200).getWeight());
}
}
3.7 람다,메서드 레퍼런스 활용하기
Entity
AppleEntity.java
package Part1.Chapter3.Chapter3_3_7.entity;
public class AppleEntity {
private String color;
private Integer weight;
public AppleEntity() {}
public AppleEntity(String color, Integer weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
}
Implements
AppleEntityComparator.java
package Part1.Chapter3.Chapter3_3_7.impl;
import java.util.Comparator;
import Part1.Chapter3.Chapter3_3_7.entity.AppleEntity;
public class AppleEntityComparator implements Comparator<AppleEntity>{
@Override
public int compare(AppleEntity o1, AppleEntity o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
}
Main
package Part1.Chapter3.Chapter3_3_7;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import Part1.Chapter3.Chapter3_3_7.entity.AppleEntity;
import Part1.Chapter3.Chapter3_3_7.impl.AppleEntityComparator;
/*
* 3.7 람다, 메서드, 레퍼런스 활용하기!
*/
public class Main_3_7 {
public static void main(String[] args) {
List<AppleEntity> inventory = Arrays.asList(new AppleEntity("red", 300)
, new AppleEntity("green", 120)
, new AppleEntity("blue", 100));
/*
* 3.7.1 1단계 : 코드 전달
* 자바 8의 List API에서 제공하는 sort메서드에 정렬 전략을 전달.
*/
inventory.sort(new AppleEntityComparator());
inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.1 코드 전달 : 색깔 = "
+ a.getColor() + ", 무게 = " + a.getWeight() ));
/*
* 3.7.2 2단계 : 익명 클래스 사용
*/
inventory.sort(new Comparator<AppleEntity>() {
@Override
public int compare(AppleEntity o1, AppleEntity o2) {
return o2.getWeight().compareTo(o1.getWeight());
}
});
inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.2 익명 클래스 사용 : 색깔 = "
+ a.getColor() + ", 무게 = " + a.getWeight() ));
/*
* 3.7.3 3단계 : 람다 표현식 사용
*
* 람다 표현식으로 경량화된 문법을 이용해서 코드를 전달.
* 함수형 인터페이스를 기대하는 곳 어디에서나 람다 표현식을 이용할 수 있다.
* 함수형 인터페이스란 오직 하나의 추상 메서드를 정의하는 인터페이스이다.
* Comparator의 함수 디스크립터는 (T, T) -> int다.
* 우리 같은 경우에는(AppleEntity, AppleEntity) -> int로 표현할 수 있다.
*/
/*
* 람다 표현식이 사용된 콘텍스트를 활용해서 람다의 파라미터 형식을 추론할 수 있다.
*/
inventory.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight() ));
inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.3 람다 표현식 사용 : 색깔 = "
+ a.getColor() + ", 무게 = " + a.getWeight() ));
inventory.sort(Comparator.comparing((AppleEntity a) -> a.getWeight() ));
inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.3 람다 표현식 사용(Comparator.comparing) : 색깔 = "
+ a.getColor() + ", 무게 = " + a.getWeight() ));
/*
* 3.7.4 4단계 : 메서드 레퍼런스 사용
*/
inventory.sort(Comparator.comparing(AppleEntity::getWeight));
inventory.stream().forEach((AppleEntity a) -> System.out.println("3.7.4 메서드 레퍼런스 사용 : 색깔 = "
+ a.getColor() + ", 무게 = " + a.getWeight() ));
}
}
3.8 람다 표현식을 조합할 수 있는 유용한 메서드
Entity
AppleEntity.java
package Part1.Chapter3.Chapter3_3_8.entity;
public class AppleEntity {
private String color;
private Integer weight;
public AppleEntity() {}
public AppleEntity(String color, Integer weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
}
Process
Letter.java
package Part1.Chapter3.Chapter3_3_8.letter;
public class Letter {
public static String addHeader(String text) {
return "From Raoul, Mario and Alan : " + text;
}
public static String addFooter(String text) {
return text + " Kind regards";
}
public static String checkSpelling(String text) {
return text.replaceAll("labda", "lambda");
}
}
Main
package Part1.Chapter3.Chapter3_3_8;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import Part1.Chapter3.Chapter3_3_8.entity.AppleEntity;
import Part1.Chapter3.Chapter3_3_8.letter.Letter;
/*
* 3.8 람다 표현식을 조합할 수 있는 유용한 메서드
*
* 자바 8 API의 몇몇 함수형 인터페이스는 다양한 유틸리티 메서드를 포함한다.
* 간단한 여러 개의 람다 표현식을 조합해서 복잡한 람다 표현식을 만들 수 있다.
* 예를 들어 두 프레디케이트를 조합해서 두 프레디케이트의 or 연산을 수행하는 커다란 프레디케이트를 만들수 있다.
* 또한 한 함수의 결과가 다른 함수의 입력이 되도록 두 함수를 조합할 수도 있다.
* 이와 같이 가능한 이유는 함수형 인터페이스의 디폴트 메서드(default method) 때문이다.
* (디폴트 메서드는 추상 메서드가 아니므로 함수형 인터페이스 정의에 어긋나지 않는다.)
*/
public class Main_3_8 {
public static void main(String[] args) {
List<AppleEntity> inventory = Arrays.asList(new AppleEntity("green", 140)
, new AppleEntity("red", 200)
, new AppleEntity("green", 200)
, new AppleEntity("blue", 300)
, new AppleEntity("red", 100));
/*
* 3.8.1 Comparator 조합
*
* 정적 메서드 Comparator.comparing을 이용해서 비교에 사용할 키를 추출하는 Function 기반의 Comparator를
* 반환할 수 있다.
* Comparator<AppleEntity> c = Comparator.comparing(AppleEntity::getWeight);
*/
/*
* 역정렬
* 사과의 무게를 내림차순으로 정렬하고 싶다면? 다른 Comparator 인스턴스를 만들 필요가 없다.
* 인터페이스 자체에 주어진 비교자의 순서를 뒤바꾸는 reverse라는 디폴트 메서드를 제공하기 떄문이다.
* 아래 코드와 같이 처음 비교자 구현을 그대로 재사용해서 사과의 무게를 기준으로 역정렬할 수 있다.
*/
inventory.sort(Comparator.comparing(AppleEntity::getWeight).reversed());
inventory.stream().forEach((appleEntity) -> System.out.println("3.8.1 Comparator 조합(reversed 메서드 사용) color : "
+ appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
/*
* Comparator 연결
* 무게가 같다면 색깔로 사과를 정렬할 수 있다.
* thenComparing 메서드로 두 번재 비교자를 만들수 있다.
* thenComparing은 함수를 인수로 받아 첫 번째 비교자를 이용해서 두 객체가 같다고 판단되면
* 두 번째 비교자에 객체를 전달한다.
*/
inventory.sort(Comparator.comparing(AppleEntity::getWeight).reversed()
.thenComparing(Comparator.comparing(AppleEntity::getColor)) );
inventory.stream().forEach((appleEntity) -> System.out.println("3.8.1 Comparator 조합(thenComparing 메서드 사용) color : "
+ appleEntity.getColor() + ", weight : " + appleEntity.getWeight() ));
/*
* 3.8.2 Predicate 조합
*
* Predicate 인터페이스는 복잡한 프레디케이트를 만들수 있도록 negate, and, or 세 가지 메서드를 제공한다.
*/
inventory.stream().forEach((AppleEntity a) -> {
Predicate<AppleEntity> redApple = (AppleEntity a1) -> "red".equals(a1.getColor());
Predicate<AppleEntity> gram150Up = (AppleEntity a1) -> a1.getWeight() > 150;
/*
* 예를 들어 "빨간색이 아닌 사과"처럼 특정 프레디케이트를 반전시킬 때 negate메서드를 사용할 수 있다.
*/
Predicate<AppleEntity> notRedApple = redApple.negate();
if(notRedApple.test(a)) {
System.out.println("3.8.2 Predicate 조합(빨간색이 아닌 사과 - negate) color :" + a.getColor() + ", weight : " + a.getWeight());
}
/*
* and 메서드를 이용해서 "빨간색이 아니면서 무거운 사과"를 선택하도록 두 람다를 조합할 수 있다.
*/
Predicate<AppleEntity> notRedAndHeavyApple = notRedApple.and(gram150Up);
if(notRedAndHeavyApple.test(a)) {
System.out.println("3.8.2 Predicate 조합(빨간색이 아니면서 무거운 사과 - and) color :" + a.getColor() + ", weight : " + a.getWeight());
}
/*
* or을 이용해서 "빨간색이면서 무거운(150그램이상) 사과 또는 그냥 녹색 사과"인 경우의 조합
* 왼쪽에서 오른쪽으로 연결된다.
*/
Predicate<AppleEntity> redAndHeavyAppleOrGreen = redApple
.and(gram150Up)
.or((AppleEntity a1) -> "green".equals(a1.getColor()));
if(redAndHeavyAppleOrGreen.test(a)) {
System.out.println("3.8.2 Predicate 조합(빨간색이면서 무거운 사과 또는 녹색 사과 - or) color :" + a.getColor() + ", weight : " + a.getWeight());
}
} );
/*
* 3.8.3 Function 조합
*
* Function 인터페이스는 Function 인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메서드를 제공한다.
* addThen 메서드는 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환한다.
* 예를 들어 숫자를 증가(x -> x + 1)시키는 f라는 함수가 있고, 숫자에 2를 곱하는 g라는 함수가 있다고 가정하면
* f와 g를 조립해서 숫자를 증가시킨 뒤 결과에 2를 곱하는 h라는 함수를 만들 수 있다.
*/
Function<Integer, Integer> f = (x) -> x + 1;
Function<Integer, Integer> g = (x) -> x * 2;
// 수학적으로 write g(f(x)) 또는 (g o f)(x)라고 표현.
System.out.println("3.8.3 Function 조합(andThen 메서드): " + f.andThen(g).apply(1));
/*
* compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공한다.
* 즉, f.andThen(g)에서 andThen 대신에 compose를 사용하면 g(f(x))가 아니라 f(g(x))라는 수식이 된다.
*/
// 수학적으로 write f(g(x)) 또는 (f o g)(x)라고 표현.
System.out.println("3.8.3 Function 조합(andThen 메서드): " + f.compose(g).apply(1));
/*
* 아래 코드는 문자열로 구성된 편지 내용을 변환하는 다양한 유틸 메서드이다.
* 여러 유틸 메서드를 조합해서 다양한 변환 파이프라인을 만들 수 있다.
*/
Function<String, String> addHeader = Letter::addHeader;
System.out.println("3.8.3 Function 조합 : " + addHeader
.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter)
.apply("kim sungw wook - labda") );
/*
* 철자 검사는 빼고 헤더와 푸터만 추가하는 파이프라인도 만들 수 있다.
*/
System.out.println("3.8.3 Function 조합 : " + addHeader
.andThen(Letter::addFooter)
.apply("kim sungw wook - labda") );
}
}
람다 표현식
- 동작 파라미터화를 이용해서 변화하는 요구사항에 효과적으로 대응하는 코드를 구현할 수 있음을 확인 했다.
- 또한 정의한 코드 블록을 다른 메서드로 전달할 수 있고,
- 정의한 코드 블록을 특정 이벤트(예를 들면 마우스 클릭)가 발생할 때 실행 되도록 설정 하거나
- 알고리즘의 일부(150그램 이상의 사과와 같은 프레디게이트)로 실행되도록 설정할 수 있다. 따라서 동작 파라미터화를 이용하면 더 유연하고 재사용할 수 있는 코드를 만들 수 있다.
- 익명 클래스로 다양한 동작을 구현할 수 있지만 만족할 만큼 코드가 깔끔하지 않다. 깔끔하지 않는 코드는 동작 파라미터를 실전에 적용하는 것을 막는 요소다.
- 람다 표현식은 익명 클래스처럼 이름이 없는 함수면서 메서드를 인수로 전달할 수 있으므로 일단은 람다 표현식이 익명 클래스와 비슷한 것이라고 생각하자.
요약
람다 표현식은 익명 함수의 일종이다. 이름은 없지만, 파라미터 리스트, 바디, 반환 형식을 가지며 예외를 던질 수 있다.
람다 표현식은 간결한 코드를 구현할 수 있다.
함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스다.
함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용할 수 있다.
람다 표현식을 이용해서 함수형 인터페이스의 추상 메서드를 즉석으로 제공할 수 있으며 "람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다."
java.util.function 패키지는
- Predicate
- Function<T, R>
- Supplier
- Consumer
- BinaryOperator
등을 포함해서 자주 사용하는 다양한 함수형 인터페이스를 제공한다.
자바 8은 Predicate와 Function<T, R>같은 제네릭 함수형 인터페이스와 관련한 박싱 동작을피할 수 있도록 IntPredicate, IntToLongFunction 등과 같은 기본형 특화 인터페이스도 제공한다.
실행 어라운드 패턴(예를 들면 자원 할당, 자원 정리 등 코드 중간에 실행해야 하는 메서드에 꼭 필요한 코드)을 람다와 활용하면 유연성과 재사용성을 추가로 얻을 수 있다.
람다 표현식의 기대 형식을 "대상 형식"이라고 한다.
메서드 레퍼런스를 이용하면 기존의 메서드 구현을 재사용하고 직접 전달할 수 있다.
Comparator, Predicate, Function 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공한다.