더 자바, Java 8 수강 정리
2022, Nov 30
java8
강의주소 : https://inf.run/qD8a
수강 후기
- 몰랐던 내용도 배울 수 있었습니다
- java 문서를 제대로 봐야겠다고 다짐!
함수형 인터페이스
추상형 메서드가 하나만 있으면 그게 함수형 인터페이스다
- stiatic, default 메서드가 있어도 추상 메서드가 하나이기만 하면 함수형 인터페이스다.
- 함수형 인터페이스를 정의하려면
@FunctionalInterface
를 선언한다. - 멱등성
- 입력받은 값이 동일하다면 결과값도 동일해야한다.
- 그렇지 못한 경우
- 외부에 있는 변수값을 변경하려 하는 경우
- 인터페이스임에도 static메서드를 정의할 수 있다 (public은 생략가능)
- default메서드도 정의할 수 있다.
- 인터페이스 상세 구현의 변화
- 기존에는 익명 내부 클래스를 선언해서 구현했어야 했다.
자바8부터는 메서드가 하나인 경우에는 람다표현식을 써서 줄여서 사용 가능해졌다 (= 인라인으로 표현가능)
RunSomething rs = () -> System.out.println("Hello");
- api
function
Function<Integer, Integer> plus10 = (i) -> i + 10;
- compose, andThen
- 두개 이상 조합
- 고차함수 higher-order function으로 함수가 함수를 매개변수로 받을 수 있고 리턴할 수 있다.
Function<Integer, Integer> plus10 = (i) -> i + 10; Function<Integer, Integer> multiply2 = (i) -> i * 2; plus10.compose(multiply2); // or plus10.andThen(multiply2).apply(2);
- compose, andThen
- BIFunction
- Cosumer
- Supplier
- 인자 없이 받아올 값을 정의한다.
- Predicate
- 인자 받아서 boolean리턴
- UnaryOperator
- 인자와 리턴 타입이 일치할 때
UnaryOperator<Integer> plus10 = (i) -> i + 10;
- BinaryOperator
- BiFunction의 특수한 형태로 세개의 타입이 모두 일치할 때, 하나만 입력하면 됨
- BiConsumer
람다 표현식
- (인자 리스트) -> {바디}
- 변수 캡쳐
- 로컬 변수 캡처
- final이거나 effective final 인 경우에만 참조할 수 있다.
- effective final : final을 선언하지 않았지만, 사실상 final인 경우
- 그렇지 않을 경우 concurrency 문제가 생길 수 있어서 컴파일가 방지한다.
- final이거나 effective final 인 경우에만 참조할 수 있다.
private void run() { final int baseNumber = 10; // 이 변수가 사실상 final인 경우, final은 생략가능 IntConsumer printInt = (i) -> System.out.println(i + baseNumber); printInt.accept(10); }
- 익명 클래스 구현체와 달리 ‘쉐도윙’하지 않는다.
- 익명 클래스는 새로 스콥을 만들지만, 람다는 람다를 감싸고 있는 스콥과 같다
private void run() { int baseNumber = 10; /*IntConsumer printInt = (i) -> System.out.println(i + baseNumber); printInt.accept(10);*/ // 로컬 클래스 class LocalClass { void printBaseNumber() { int baseNumber = 11; // 재정의를 하는 것을 위에 10인 변수를 가리는 것을 쉐도윙이라고 한다. 이것은 익명클래스도 마찬가지 System.out.println(baseNumber); // 11 } } // 익명 클래스 Consumer<Integer> integerConsumer = new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(baseNumber); // 이건 10이지만 } }; // 익명 클래스 Consumer<Integer> integerConsumer2 = new Consumer<Integer>() { @Override public void accept(Integer baseNumber) { // 이렇게 인자를 통해 전달되면, 10인 baseNumber를 가리는 쉐도잉이 된다, System.out.println(baseNumber); // 이건 10이 아닌 파라미터값이 된다, } }; // 람다 IntConsumer printInt = (i) -> {// 하지만 람다는 i대신에 baseNumber로 할 수 없다. System.out.println(i + baseNumber); // effective final이어야 한다. }; printInt.accept(10); // 만약 아래처럼 바꾼다면, effective final이 아니기 때문에 람다식에서 참조 에러가 난다. //baseNumber++; }
- 로컬 변수 캡처
메소드 레퍼런스
람다를 인라인으로 작성했던 부분을 메소드 참조로 변경
// 1 UnaryOperator<String> hi = (s) -> "hi" + s; // 이렇게 가능하지만 // 2 Greeting greeting = new Greeting(); UnaryOperator<String> hello = greeting::hello; // 클래스의 메소드 참조로도 구현가능
생성자도 참조할 수 있다.
public class Greeting { private String name; public Greeting() { } public Greeting(String name) { this.name = name; } public String getName() { return name; } public String hello(String name) { return "hello " + name; } public static String hi(String name) { return "hi " + name; } }
// 이건 Greeting 인스턴스를 만드는 것이 아니다. Supplier<Greeting> newGreeting = Greeting::new; // 이건 인자가 없는 빈 생성자(Greeting()) 를 호출한 것 // 이때 인스턴스가 생성된다. newGreeting.get();
Function<String, Greeting> greetingFunction = Greeting::new; // 이건 String을 인자로 받는 public Greeting(String name) 생성자를 호출하는 것 Greeting greeting = greetingFunction.apply("abc");
임의 객체의 인스턴스 메소드 참조
String[] nammes = {"aaa", "bbb", "ccc"}; Arrays.sort(nammes, String::compareToIgnoreCase); // 임의의 "aaa", "bbb", "ccc" 인스턴스들을 거쳐가며 System.out.println(Arrays.toString(nammes));
인터페이스의 변화
인터페이스의 default 메소드와 static 메소드
- default 메소드
- 이미 구현된 인터페이스에 공통 메서드를 또 추가할 경우 에러가 발생하는데 그때 default메서드를 사용하면 가능하다
- Collection.java의 removeIf의 경우도 default메소드로 제공하고 있다.
예
// interface class public interface Foo1 { void printName(); default void printNameUpperCase() { System.out.println(getName().toUpperCase()); } String getName(); } // 구현 class public class DefaultFoo implements Foo1 { String name; public DefaultFoo(String name) { this.name = name; } @Override public void printName() { System.out.println(this.name); } @Override public String getName() { return this.name; } } // 호출 main메소드 public static void main(String[] args) { Foo1 foo1 = new DefaultFoo("aaa"); foo1.printName(); foo1.printNameUpperCase(); }
- 하지만 default메소드를 구현한 부분이 항상 제대로 동작할 것이라는 보장이 안된다.
- 구현하는 쪽에서 npe같은 오류가 발생하지 않도록 재정의를 한다.
Object가 제공하는 메서드는 default로 재정의할 수 없다.
default String toString() { --> 안됨 }
인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있다
public interface Bar extends Foo1 { void printNameUpperCase(); }
- static메소드
- 해당 타입관련해서 유틸리티 메소드를 제공할때 인스턴스에 static메소드를 정의할 수 있다.
자바 8 API의 기본 메소드와 스태틱 메소드
- Iterable
- foreach
- spliterator
- parallel하게 처리를 할 때 , 순서가 중요하지 않다면
List<String> name = new ArrayList<>(); name.add("aaa"); name.add("bbb"); name.add("ccc"); name.add("ddd"); Spliterator<String> spliterator = name.spliterator(); Spliterator<String> spliterator1 = spliterator.trySplit(); while (spliterator.tryAdvance(System.out::println)); while (spliterator1.tryAdvance(System.out::println));
- Collection
- stream() / parallelStream()
- 내부에 Spliterator를 써서 하고 있다.
- removeIf(Predicate)
- if조건에 해당하는 것을 배열에서 제거
- spliterator()
- stream() / parallelStream()
- Comparator
- functionalInterface다
- sort할때 쓰임
- api
- reversed()
- thenComparing()
- static reverseOrder() / naturalOrder()
- static nullsFirst() / nullsLast()
- static comparing()
- default 메소드가 있기 때문에 같은 맥락에서 WebMvcConfigurer interface 와 이를 구현한 WebMvcConfigurerAdapter abstract클래스는 deprecated되었다.
Stream
- 특징
- Funtional in nature, 스트림이 처리하는 데이터 소스를 변경하지 않는다.
- stream은 중개 operator와 종료 operator로 나눠진다.
- 손쉽게 병렬처리할 수 있다.
- API
- 중개 operator
- 계속 이어나가는 것으로 stream으로 리턴된다.
- filter, map, limit, skip, sorted…
- 터미널 operator가 올때까지 리턴하지 않는다.
- 터미널 operator
- 종료시키는 것
- 중개 operator
Optional
- 주의
- 리턴값으로만 쓰기를 권장한다. (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.)
- 매개변수타입으로 쓰면 null체크를 한번 더 해야하니까 번거롭게 될 뿐이다.
프리미티브 타입용 Optional을 따로 있다. OptionalInt, OptionalLong,…
Optional.of(10); // 이건 박싱, 언박싱이 일어나서 성능에 안좋다 OptionalInt.of(10); // 이렇게 사용하도록 하자
- Optional을 반환해야하는데 null로 반환하지 말자. 대신 Optional.empty로 반환하자
- Collection, Map, Stream Array, Optional은 Opiontal로 감싸지 말 것.
- 두번 감싸게 되는 것
- 리턴값으로만 쓰기를 권장한다. (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.)
- api
- Optional.isEmpty() → java11부터
- Optional의 Optional은 flatMap을 써서 추출한다.
Date와 Time
- Instant는 기계용, LocalDateTime은 휴먼용
- 기간 비교
Period - 휴먼용
Period.between(); today.until();
Duration - 기계용
Duration.between()
CompletableFuture
- Thread
- 생성방식
- 1안) Thread를 상속받아서 thread.run()
- 2안) Thread안에 Runnable익명 클래스로 구현
- 3안) 자바8 이후는 2안에서 람다로 변경해서 구현
- 생성방식
- ExecutorService
- shutdown은 graceful shutdown
- shutdownNow()는 바로 terminal
- Fork/Join
- ExecutorService의 구현체
- Callable과 Future
- Runnable과 유사하지만 리턴받을 수 있다는 차이가 있다
- Future.get() 블로킹
- completableFuture
- 비동기 인터페이스
콜백도 다른 쓰레드로 줘서 실행시키도록 할 수 있다.
ExecutoreService executorService = Executors.newFixedThreadPool(4); CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> { return "hello"; }, executorService).thenRunAsync(() -> { System.out.println(Thread.currentThread().getName()); }, executorService); future.get();
Annotation
- 어노테이션의 변화
- 타입선언부에서도 사용할 수 있고,
- @Target에 Type_Parameter, Type_use추가됨(9에선 module추가됨)
- 중복해서 사용할 수 있다
- @Repeatable
- 타입선언부에서도 사용할 수 있고,
배열 Parallel 정렬
- Arrays.parallelSort()
- 정렬할 때 Fork/Join 프레임워크를 사용해서 배열을 병렬로 정렬하는 기능을 제공한다.
- 알고리즘의 효율은 같지만, 여러 쓰레드에 분산해서 하기 때문에 시간이 빠르다
Metaspace
- Jvm의 여러 메모리 영역 중에 PermGen 메모리 영역이 없어지고 Metaspace영역이 생겼다.
- PermGen
- permanent generation, 클래스 메타 데이터를 담는 곳
- Heap영역에 속함
- Metaspace
- 클래스 메타데이터를 담는 곳
- Heap영역이 아니라, Native메모리 영역이다
- 기본값으로 제한된 크기를 가지고 있지 않다.(필요한 만큼 계속 늘어난다.)
- -XX:MaxMetaspaceSize=N
- metaspace도 늘어나고 gc가 계속 일어난다는것은 어딘가 누수가 있는 것으로 모니터링해서 찾아내야함.