Java 8
함수형 인터페이스
함수형 인터페이스 구현
@Functionalnterface
public interface RunSomething {
void doIt(); // 추상 메소드가 하나만 있다면 함수형 인터페이스이다. 앞에 abstract 생략
// void doItAgain(); // 추상 메소드가 하나를 초과하면 함수형 인터페이스가 아니다.
}
public class Foo {
public static void main(String[] args) {
// Java 8 이전 방식
// 익명 클래스
RunSomething runSomething = new RunSomething() {
@Override
public void doIt() {
System.out.println("dohyun");
}
};
// Java 8 람다 표현식
RunSomething runSomething = () -> System.out.println("dohyun");
}
}자바에서 제공하는 함수형 인터페이스
Function<T, R>
// Function<> 구현방법 1.
public class Plus10 implements Function<Integer, Integer> {
@Override
public Integer apply(Integer Integer) {
return integer + 10;
}
}
// Function<> 구현방법 2.
public class Foo {
public static void main(String[] args) {
// apply
Function<Integer, Integer> plus10 = (i) -> i + 10;
plus10.appy(1);
// compose
Function<Integer, Integer> multi2 = (i) -> i * 2;
plus10.compose(multi2).apply(2); // 먼저 2를 곱하고 10을 더한다.
//andThen
plus10.andThen(multi2).apply(2); // 먼저 10을 더하고 2를 곱한다.
}
}BiFunction<T, U, R>
- 두 개의 값(T, U)를 받아서 R 타입을 리턴하는 함수 인터페이스 : R apply(T t, U u)
Consumer
- T 타입을 받아서 아무값도 리턴하지 않는 함수 인터페이스 : void Accept(T t)
- 함수 조합용 메소드 : andThen
Supplier
public class Foo {
// get
public static void main(String[] args) {
Supplier<Integer> get10 = () -> 10;
get10.get();
}
}Predicate
public class Foo {
// test
public static void main(String[] args) {
Predicate<String> str = (s) -> s.startsWith("dohyun");
str.test("dohyun world") // return true
}
}람다 표현식
public class Foo {
public static void main(String[] args) {
Foo foo = new Foo();
foo.run();
}
private void run() {
int baseNumber = 10;
//로컬 클래스
class LocalClass {
void printBaseNumber() {
int baseNumber = 11; // 쉐도윙 !
System.out.println(baseNumber); // 11
}
}
//익명 클래스
Consumer<integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(baseNumber);
}
};
//람다
IntConsumer printInt = (i) -> { // 만약 i -> baseNumber 라면 같은 스콥이기 때문에 컴파일 에러 !
System.out.println( i + baseNumber);
};
/**
셋의 공통점 : 로컬 변수를 참조할 수 있다.
로컬, 익명 클래스 vs 람다 : 로컬, 익명 클래스는 쉐도윙하고 ( run() 안에 새로운 스콥 생성 ), 람다는 쉐도잉하지 않는다 ( run() 과 같은 스콥 ).
**/
}
}메소드 레퍼런스
- 스태틱 메소드 참조 : 타입::스태틱 메소드
- 특정 객체의 인스턴스 : 메소드 참조 객체 레퍼런스::인스턴스 메소드
- 임의 객체의 인스턴스 : 메소드 참조 타입::인스턴스 메소드
- 생성자 참조 타입 : ::new
Java 8 API의 기본 메소드와 스태틱 메소드
public class App {
public static void main(String[] args) {
List<String> name = new ArrayList<>();
name.add("dohyun");
name.add("github");
//forEach
name.forEach( s -> {
System.out.println(s);
});
//stream
name.stream()
.map(String::toUpperCase)
.filter( s -> s.startsWith("K"))
.count();
//removeIf
name.removeIf( s -> s.startsWith("K"));
}
}Iterable의 기본 메소드
- forEach()
- spliterator()
Collection의 기본 메소드
- stream() / parallelStream()
- removeIf(Predicate)
- spliterator()
Comparator의 기본 메소드 및 스태틱 메소드
- reversed()
- thenComparing()
- static reverseOrder() / naturalOrder()
- static nullsFirst() / nullsLast()
- static comparing()
Stream
- 간단하게 말해, 연속된 데이터들을 처리하는 오퍼레이션들의 집합이다.
- 비유하자면 컨베이어 벨트에 데이터를 하나씩 흘려보내면서 특정 오페레이션을 수행하는 것이다.
- 스트림 자체가 데이터를 담고 있는 저장소 ( 컬렉션 ) 이 아니다.
- 손쉽게 병렬 처리가 가능하다.
public class App {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
name.add("dohyun");
name.add("github");
Stream<String> stringStream = names.stream()
.map(s -> s.toUpperCase());
names.forEach(System.out::printLn); // 데이터가 그대로 소문자로 남아있다.
}
}스트림 파이프라인
- 0 또는 다수의 중개 오퍼레이션 (intermediate operation)과 한개의 종료 오퍼레이션 (terminal operation)으로 구성한다.
- 스트림의 데이터 소스는 오직 터미널 오퍼네이션을 실행할 때에만 처리한다.
중개 오퍼레이션
- Stream을 리턴한다.
- Stateless / Stateful 오퍼레이션으로 더 상세하게 구분할 수도 있다. (대부분은 Stateless지만 distinct나 sorted 처럼 이전 이전 소스 데이터를 참조해야 하는 오퍼레이션은 Stateful 오퍼레이션이다.)
- filter, map, limit, skip, sorted, ...
종료 오퍼레이션
- Stream을 리턴하지 않는다.
- collect, allMatch, count, forEach, min, max, ...
Stream API 예제
걸러내기 - Filter(Predicate)
List<OnlineClass> springClasses = new ArrayList<>();
List<OnlineClass> javaClasses = new ArrayList<>();
// spring 으로 시작하는 수업
springClasses .stream()
.filter(oc -> oc.getTitle().startsWith("spring")) // OnlineClass 타입 그대로
.forEach(oc -> System.out.println(oc.getId()));
// close 되지 않은 수업
springClasses.stream()
.filter(oc -> !oc.isClosed())
.forEach(oc -> System.out.println(oc.getId()));
// 수업 이름만 모아서 스트림 만들기
springClasses.stream()
.map(ec -> oc.getTitle()) // String 타입으로 변경
.forEach(oc -> System.out.println(oc.getId()));
List<List<OnlineClass>> dohyunEvents = new ArrayList<>();
dohyunEvents.add(springClasses);
dohyunEvents.add(javaClasses);
// 두 수업 목록에 들어있는 모든 수업 아이디 출력
dohyunEvents.stream()
.flatMap(list -> list.stream())
.forEach(oc -> System.out.println(oc.getId()));
// 10부터 1씩 증가하는 무제한 스트림 중에서 앞에 10개 빼고 최대 10개 까지만
Stream.iterate(10, i -> i + 1)
.skip(10)
.limit(10)
.forEach(System.out::println);
// 자바 수업 중에 Test 가 들어있는 수업이 있는 지 확인
javaClasses.stream()
.anyMatch(oc -> oc.getTitle().contains("Test"));
// 스프링 수업 중에 제목이 spring 이 들어간 것만 모아서 List 로 만들기
springClasses.stream()
.filter(oc -> oc.getTitle().contains("spring"))
,map(oc -> oc.getTitle())
.collect(Collectors.toList());Optional
메소드에서 작업 중 특별한 상황에서 값을 제대로 리턴할 수 없는 경우 선택할 수 있는 방법
- 예외를 던진다. (비싸다, 스택트레이스를 찍어두니까.)
- null을 리턴한다. (비용 문제가 없지만 그 코드를 사용하는 클리어인트 코드가 주의해야 한다.)
- (자바 8부터) Optional을 리턴한다. (클라이언트에 코드에게 명시적으로 빈 값일 수도 있다는 걸 알려주고, 빈 값인 경우에 대한 처리를 강제한다.)
Optional
- 오직 값 한 개가 들어있을 수도 없을 수도 있는 컨네이너.
public Optional<Progress> getProgress() {
return Optional.ofNullable(progress);
}주의할 것
- 리턴값으로만 쓰기를 권장한다. (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.)
- Optional을 리턴하는 메소드에서 null을 리턴하지 말자.
- 프리미티브 타입용 Optional을 따로 있다. OptionalInt, OptionalLong,...
- Collection, Map, Stream Array, Optional은 Opiontal로 감싸지 말 것.
Optional API 예제
public class App {
public static void main(String[] args) {
List<OnlineClass> springClasses = new ArrayList<>();
Optional<OnlineClass> spring = springClasses.stream()
.filter(oc -> oc.getTitle().startsWith("spring"))
.findFirst();
spring.isPresent(); // 있는 지 없는 지
spring.get(); // Optional 안의 값을 꺼낸다. 만약 Optional 안에 값이 없다면? NosuchElementException !
spring.ifPresent(oc -> {
System.out.println(oc.getTitle());
});
OnlineClass onlineClass = spring.orElse(createNewClasses()); // spring 이 있던 없던 createNewClasses() 를 실행한다.
OnlineClass onlineClass = spring.orElseGet(() -> createNewClasses()); // spring 이 있는 경우 createNewClasses() 를 실행하지 않는다.
OnlineClass onlineClass = spring.orElseThrow(() -> {
return new IllegalStateException();
});
Optional<OnlineClass> onlineClass = spring.filter(oc -> oc.getId() > 10); // 빈 Optional
Optional<Integer> integer = spring.map(oc -> oc.getId());
}
private static OnlineClass createNewClasses() {
return new OnlineClass(10, "New class", false);
}
}