새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
자바 21, 스트림, GC학습
오늘의 학습 목표
목표!!
-
자바 9~11까지 전략적으로 자바 feature에 대해 학습하기
- 자바8의 스트림 학습하기 키워드는
Lazy!
- 대표적인 GC에 대해 학습하기
- git flow, githup flow,gitlab flow
- redis 복습
주말 시작
- 스터디 카페 공부 시작 09:00
자바 9 추가 Feature
확장된 try-with-resources
자바 7 버전에 추가된 try-with-resources
을 통해서 자동으로 리소스를 끊어주는 코드입니다.
기존에는 try-catch-finllay
를 통해서 반드시 리소스 연결을 종료를 작성해야합니다. 자원 닫는걸 깜박하거나 try가 중첩되는 경우 닫는 작업이 번거로웠습니다.
// try-catch-finally 가 복잡해서 close하기 어려운 예시
public void tryCatchFinally() {
java.io.InputStream inputStream = null;
try {
inputStream = new java.io.FileInputStream("test.txt");
System.out.println("tryCatchFinally");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// 위의 예시를 try-with-resources로 변경
public void tryWithResourcesInputStream() {
try (java.io.InputStream inputStream = new java.io.FileInputStream("test.txt")) {
System.out.println("tryWithResources");
} catch (Exception e) {
e.printStackTrace();
}
}
아쉬운점
try()
외부에 있는 자원은 닫을 수 없었습니다.
자바 9부터는 외부에 리소스가 final이거나 사실상 final 인 경우 닫아 줄 수 있습니다.
사실상 final이라는 말은 final 키워드를 붙이지 않아도 해당 값을 변경한 적이 없으면 그 변수는 사실상 final 이라고 합니다.
public void tryWithResourcesInputStream3() {
InputStream inputStream = new FileInputStream("test.txt");
InputStream inputStream2 = new FileInputStream("test2.txt");
try (inputStream; inputStream2) {
System.out.println("tryWithResources");
} catch (Exception e) {
e.printStackTrace();
}
}
물론 AutoCloseable을 구현해야합니다.
@SafeVarargs 애노테이션 + private 메소드
SafeVarargs Arguments
와 private
메소드와 함께 붙일 수 있다는 점입니다.
이 이야기는 가변인자와 제네릭을 같이 사용할 경우 문제가 발생할 수 있습니다.
// 제네릭과 가변인자를 혼합하여 사용할 때 발생할 수 있는 문제
public static <T> List<T> flat(List<T>... lists) {
Object[] objs = lists;
objs[0] = Arrays.asList("A","B","C");
ArrayList<T> ts = new ArrayList<>();
for (List<T> list : lists) {
ts.addAll(list);
}
return ts;
}
가변인자는 메서드를 호출할 때 매개변수의 개수를 자유롭게 넣을 수 있고, 해당 값들이 배열로 내부에서 사용할 수 있습니다
배열은 다운 캐스팅이 암묵적으로 가능하기에 Object[] objs = lists
라는 코드가 가능하게 됩니다.
그런데 문제는 타입 안정성으로 같은 타입이 들어와도 내부에서는 배열로 관리되어 자유롭게 데이터가 저장됩니다.
따라서 리스트 배열을 사용하게 되면 런타임 에러를 발생할 수 있게 하므로 컴파일러는 경고를 하게 됩니다.
@SafeVarargs
어노테이션을 사용하게 된다면 해당 함수는 가변인자와 제네릭으로 문제가 발생하지 않게 작성되었다는 의미입니다.
기존 자바 8 버전에서는 외부에 노출된 메서드만 사용이 가능했습니다. 이 메서드를 사용하는 클라이언트가 믿고 사용해도 된다는 의미인거죠 하지만 내부에서 사용되는 메서드도 타입 안정성이 되어있다는 표시가 필요하게 되어 추가되었습니다.
익명 inner class + diamond syntax
diamond syntax는 컴파일러가 타입 인자를 확인할 수 있으면 타입을 생략할 수 있도록 하는 기능입니다.
//타입 생략
List<String> strings = new ArrayList<>(); // <String> 생략됨.
내부 클래스란 외부 클래스를 참조 할 수 있는 클래스를 말합니다. static
이 붙은 클래스는 정적 멤버 클래스 혹은 nested 클래스라고 합니다. 외부 클래스를 참조하다 보니 메모리 관리(GC)와 직렬화 표준도 없기 때문에 권장되지 않는 방식입니다.
내부 클래스를 익명 클래스로 만들 때 diamond syntax
사용이 가능하게 되었습니다.
public static class StaticInnerClass<T> {
private final T t;
public StaticInnerClass(T t) {
this.t = t;
}
}
public class InnerClass<T> {
private final T t;
public InnerClass(T t) {
this.t = t;
}
}
public static void main(String[] args) {
StaticInnerClass<Integer> innerClass = new StaticInnerClass<>(5);
MainV1 mainV1 = new MainV1();
InnerClass<Integer> innerClass1 = mainV1.new InnerClass<>(5);
}
인터페이스 + private method
자바에서 인터페이스 규칙 개선은 자바 언어의 큰 변화입니다. 인터페이스는 구현부가 없는 추상 메소드만 있는 코드를 말하게 됩니다. 인터페이스 메서드는 구현부를 넣지 못한다는 제약은 프레임 워크의 유지 보수성을 낮추고 있었습니다.
인터페이스가 발전하려면 새로운 기능을 넣어야 했습니다. 새로운 추상 메서드를 추가하면 기존에 구현한 클래스들도 모두 구현해야했고 만들어 놓은 클래스들의 추상화 호환성이 깨지게 됩니다.
호환성 문제를 우회하는 방법을 생각하게 되었고 interface List
대신에 Lists
라는 클래스를 만들어 기능을 추가하는 방식입니다.
public class Lists {
private Lists() { }
public static <T> boolean contains(List<T> list,T element){
// 로직
}
}
하위 호환성을 유지하면서 interface List
의 코드를 수정하지 않아도 됩니다.
하지만 Lists
의 클래스를 모른다거나 시간이 오래 지나면 List
와 Lists
의 차이를 알지 못한다거나, 본인이 직접 메서드를 추가할 수 있습니다. 이 우회 방법으로 탄생한 것이 Collection
과 Collections
입니다.
인터페이스에 완전한 메서드를 추가하고 사용하고 싶으면 사용하고 해당 클래스에 맞게 재정의를 통해서 변경할 수 있는 디폴트 메서드가 생기게 됩니다.
핵심은 사용하고 싶으면 사용하고, 재정의 하고 싶으면 재정의를 하도록 만드는 것이 디폴드 메서드 역할입니다.
다만 인터페이스에 디폴트 메서드를 구현하다보니 겹치는 코드가 발생하게 되었고 private
키워드를 통해 인터페이스 내부에서 사용할 수 있는 방식이 추가되었습니다.
언더스코어 네이밍 불가
int _ = 3
이렇게 불가능하게 변경되었습니다.
자바 9 feature 정리
- module 추가로 강력한 캡슐화와 접근 제어가 가능하도록 추가되었습니다.
- 서비스 로드 방식을 module-info를 활용하여 구현할 수 있게 되었습니다.
- try-with-resources가 외부 final이나 사실상 final 의 리소스도 가능하게 됩니다.
@SafeVarargs
으로 가변인자와 제네릭 경고를 제거했는데private
도 가능하게 됩니다.inner class diamond
내부 클래스에<>
를 생략할 수 있습니다.- 디폴트 메서드에
private
접근제어자가 추가됩니다.
자바 9 표준 라이브러리 변경내용
Collection 기능 추가
Collection
객체를 생성하는 간결한 방법이 추가되었습니다.
// 자바 8
List<Integer> oldList = Arrays.asList(1, 2);
List<Integer> newList = List.of(1, 2, 3);
정적 팩토리 메서드를 통해 간결하게 List
를 반환하도록 변경되었습니다.
set/map
도 마찬가지로
Set<Integer> oldSet = new HashSet<>(Arrays.asList(1, 2));
Set<Integer> newSet = Set.of(1, 2);
HashMap<String, String> oldMap = new HashMap<>();
oldMap.put("호랑이", "고양이과");
oldMap.put("삼색냥이", "고양이과");
Map<String, String> newMap = Map.of("호랑이", "고양이과", "삼색냥이", "고양이과");
Map<String, String> newMapEntry = Map.ofEntries(Map.entry("호랑이", "고양이과"),
Map.entry("삼색냥이", "고양이과"));
of로 만들어진 컬렉션의 특징
자바 8 이후 LocalDateTime
부터 정적 팩토리 메서드로 만든 컬렉션은 불변으로 원소를 추가,삭제,업데이트시 에러가 발생합니다.
Collections.unmodifableXX()와 구현체가 다릅니다. 구현체가 다르기 때문에 몇몇 경우는 기존 방식보다 효율적인 메모리 구조를 가지고 있습니다. 여기서 효율적인 메모리 구조란 기존 해시 구조를 사용하는 경우 기본 용량이 16입니다(HashSet). 하지만 불변으로 사용하는 경우 내부에서 크기에 맞는 Array
구조에 저장합니다.
특히 데이터가 2개인 경우에는 배열도 아닌 필드에 값을 2개 저장합니다.
static <E> Set<E> of(E e1, E e2) {
return new ImmutableCollections.Set12<>(e1, e2);
}
@Stable
private final E e0;
@Stable
private final Object e1;
관리하는 배열의 크기가 작을 경우 더 효율적으로 관리할 수 있게 됩니다.
Optional - 3가지 추가
-
ifPresentOrElse
public static void optional(Optional<String> str) { str.ifPresentOrElse( s -> System.out.println("값이 있는 경우 출력" + s), () -> { System.out.println("값이 없는 경우"); } ); }
기존에 있던
ifPresent(action)
기능을 좀 더 확대하여 값 유무에 대한 로직을 추가할 수 있습니다. -
or
어떤 옵셔널의 값이 없는 경우 다른 옵셔널을 반환할 수 있도록 합니다. 체이닝을 통해 값이 없을 경우 다른Optional
로 변환합니다.public Optional<String> or(Optional<String> str) { return str.or(() -> Optional.of("none")); }
-
stream
public Optional<String> stream(Optional<String> str) { Stream<String> stream = str.stream(); }
옵셔널도
stream()
을 추가하여 스트림 객체를 반환합니다. 값이 비어있는 경우 비어있는 스트림이 만들어지게 됩니다. 값이 있는 경우 1개의 값이 있는 스트림이 됩니다.
Stream 4가지 기능 추가
-
takeWhile
: 순회중 하나라도 실패하면 뒤의 데이터는 버립니다.List<Integer> integers = List.of(1, 5, 15, 3, 4, 20); // 15 이후는 체크를 하지 않는다. long count = integers.stream().takeWhile(it -> it < 10).count(); System.out.println("count = " + count); // 2 long count1 = integers.stream().filter(it -> it < 10).count(); System.out.println("count1 = " + count1); // 4
-
dropWhile
: takeWhile과 반대로 조건이true
인 경우 데이터를 버리다가, false를 만나면 모두 남깁니다.List<Integer> integers = List.of(1, 5, 15, 3, 4, 20); Integer[] takeWhile = integers.stream().takeWhile(it -> it < 10).toArray(Integer[]::new); // print System.out.println("takeWhile = " + Arrays.toString(takeWhile)); // [1, 5] Integer[] dropCount = integers.stream().dropWhile(it -> it < 10).toArray(Integer[]::new); // print System.out.println("dropCount = " + Arrays.toString(dropCount)); // [15, 3, 4, 20]
-
ofNulllable
: null이면 비어있는 Stream, 값이 있으면 원소가 하나인 Stream
stream
이 null 인 경우 비어있는 스트림을 사용하게 되며 아무 동작도 하지 않기에 문제가 발생하지 않습니다.Stream<String> ofNull = Stream.ofNullable("is null");
인자로 null이 들어올 경우 안전하게 비어있는 Stream을 사용할 수 있게 됩니다.
-
Stream.iterate()
: 기존 방식에for (; i < 10 ; i++ )
와 같이 전통적인 for문 구조로 조건을 추가할 수 있습니다.// Stream.iterate Stream.iterate(1, i -> i < 10, i -> i + 1).forEach(System.out::println); // Stream.iterate java 8 ver Stream.iterate(0,i -> i+1).limit(10).forEach(System.out::println);
CompletableFuture API 기능 추가
자바 9에서 CompletableFutre를 복사하거나 Default Executor를 가져오는 기능이 추가되었습니다.
CompletableFuture 란?
CompletableFuture는 비동기 작업의 결과물을 표현하는 객체며 Excutor는 비동기 작업이 실행되는 Thread Pool이라고 생각하면 됩니다.
원래 있었던 기능들에 복사하거나 , 현재 사용되고 있는 스레드 풀을 가져오는 기능이 추가되었으며 외에도 Timeout
그리고 지연실행
기능도 추가되었습니다.
// timeout 시간동안 마무리가 안될경우 Timeout Ex가 발생합니다.
// 정해진 시간내에 마무리 되지 않으면 예외가 발생합니다.
CompletableFuture<Void> future = CompletableFuture.runAsync(run)
.orTimeout(1, TimeUnit.SECONDS);
// 정해진 시간내에 마무리 되지 않으면 정해진 값을 반환합니다.
// 동시에 여러 API를 사용하여 전송하는 경우 Timeout 기능을 사용하여 안전하게 비동기 작업이 가능합니다.
CompletableFuture<Void> future = CompletableFuture.runAsync(run)
.completeOnTimeout(null,1, TimeUnit.SECONDS);
타임 아웃을 설정하는 이유가 뭘까?
비동기를 사용하는 이유는 대기하지 않고 필요한 시점에 결과를 확인하고 싶을 때 사용합니다. 그런데 필요한 정보가 조회 되지 않는다면 클라이언트에게 메세지를 전달하거나 재시도를 할 수 있습니다.
다만 외부 API가 수정이나 변경과 같은 경우에는 별도의 로그를 남기는 작업이 필요하다고 생각합니다.
CompletableFuture를 학습하면서 배운건 new Thread
를 통해서 작성된 작업은 예외가 발생시 해당 쓰레드에서 종료가 되고 메인 쓰레드까지 전파가 되지 않습니다. CompletableFuture을 사용하는 경우 예외가 메인 쓰레드까지 상호작용이 되며 별도의 예외 처리 방법을 제공합니다.
지연 기능
DelayedExecutor
를 사용하면 비동기 작업을 지연 처리가 가능합니다.
Executor executor = CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS);
Runnable runnable = () -> {
System.out.println(System.currentTimeMillis() + " - 작업 완료");
};
System.out.println(System.currentTimeMillis() + " - 작업 실행");
CompletableFuture<Void> future = CompletableFuture.runAsync(runnable, executor);
//block
future.get();
Process API 추가
ProcessHandle
: native 프로세스를 제어할 수 있는 인터페이스
예를 들어) 프로세스를 이용해 정보를 읽고 프로세스를 제거하는 등의 조작을 할 수 있습니다.
//use ProcessHandle
ProcessHandle currentProcess = ProcessHandle.current();
System.out.println("PID: " + currentProcess.pid()); // 현재 프로세스의 pid를 가져온다.
System.out.println("PID: " + currentProcess.info()); // 현재 프로세스의 정보를 가져온다.
기존 Process API
는 프로세스 PID를 제공하지 않아서 특정 프로세스를 식별하여 제어하기 어려웠던 걸 해결하기 위해 추가된 기능입니다.
Stack-Walking API 추가
StackWalker API: 스택을 훑는 기능으로 현재 실행 중인 함수, 이전 함수, 최초 함수까지 확인할 수 있습니다.
StackWalker.Option으로 StackWalker.Option.SHOW_HIDDEN_FRAMES
등 옵션을 추가하여 내부 함수 호출 기록까지 확인이 가능합니다.
자바 9에서 지금 시점의 StackFrame을 제어하는 기능이 추가되었다는 걸 확인하면됩니다.
// make call a, b, c
private static void a() {
b();
}
private static void b() {
c();
}
public static void c() {
StackWalker walker = StackWalker.getInstance();
List<String> walk = walker.walk(s -> s.map(StackWalker.StackFrame::getMethodName)
.toList());
for (String methodName : walk) {
System.out.println("Method Name: " + methodName);
}
}
public static void main(String[] args) {
// call a
a();
}
Method Name: c
Method Name: b
Method Name: a
Method Name: main
내부 문자열 처리 방식 개선
자바 8 버전에서 모든 문자에 대해 2byte(char)을 사용했지만, 알파벳으로만 저장되는 문자열은 1byte로 최대 50%까지 줄어들게 됩니다.
이러한 방식을 compact String
이라고 합니다.
public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}
JDK 버전 스키바 방식 변경
기존에는 1.7.0_60
또는 JDK 7u80
처럼 직관적이지 않았습니다.
자바 9 부터는 메이저.마이너.보안패치 로 메이저는 자바 버전을 나타내며 특이점은 마이너 패치가 변경되어도 보안패치 버전은 내려가지 않습니다 예를 들어, 9.0.1
에서 마이너 버전이 패치되어도 9.1.1
이 됩니다.
GC!! 변경사항
자바 9 이전에는 Pareallel GC
가 기본 값이였고 각 세대를 물리적으로 구분하여 저장하는 방식이였습니다.
자바 9 부터는 G1GC로 변경되며 논리적으로 바둑판처럼 메모리를 관리한다고 생각하면됩니다.
G1GC의 장점은 영역을 통째로 관리하는 것이 아니라 영역을 세분화하여 메모리 회수할 때 발생하는 STW의 중단 시간이 평균적으로 줄어들게 됩니다.
- 튜닝에 사용되던 CMS GC는 deprecated 되었습니다.
- 추후 자바 버전에서 아예 삭제되게 됩니다.
- 자바 9에서 OOM 옵션 추가되었습니다.
- ExitOnOutOfMemeryError
메모리 부족 오류가 발생하면 JVM을 즉시 종료하여 추가적인 문제를 방지합니다. - CrashOnOutOfMemoryError
메모리 부족 오류가 발생하면 JVM을 강제로 크래시시키고 크래시 덤프 파일을 생성하여 문제를 진단할 수 있게 합니다.
- ExitOnOutOfMemeryError
OOM 옵션이 추가된 이유는 기존 자바 8에서 메모리 부족 오류 발생 시 더 나은 대응 방안을 제공하기 위해서 입니다.
리액티브 프로그래밍 지원을 위한 API 추가
리액티브 프로그래밍이란 데이터를 넣는 주체를 Publisher
그리고 데이터를 필요로 하는 주체를 Subscriber
이라고 합니다.
이때 데이터를 요청하기 위한 행위를 Subscription
이라고 합니다.
Publisher
는 여러 명이 구독할 수 있도록 subscribe()
을 통해 구독자를 받습니다.
리액티프 프로그래밍이란?
응답이 빠르고, 탄력성과 회복성이 좋으며, 메세지 기반으로 통신하는 비동기 non-blocking 방식의 프로그래밍
리액티브 라이브러리 뼈대가 Pub-Sub 구조로 동작한다는 정도를 알아두자
Flow API 추가의 의미
다양한 리액티브 라이브러리간의 호환성이 좋아지고, 자바 내부에서도 리액티브 프로그래밍이 가능해졌다.
예를 들면, dataSource와 유사하다. 표준 인터페이스 Flow를 구현한 라이브러리끼리는 호환성이 좋아진다는 의미입니다.
자바 11 버전에서 비동기 HTTP 클라이언트 호출을 할때 Flow api를 사용하므로 외부 라이브러리가 아닌 표준 라이브러리로 구현을 했습니다.
RxJava Reactor Flow API 차이
RxJava | reactor | Flow API | |
---|---|---|---|
JDK 버전 | 8 미만에서 사용 가능 | 8 이상에서만 사용 가능 | 9 이상에서만 사용 가능 |
활용 | 클라이언트 | spring webflux | 호환성 표준 라이브러리 |
리액티브 스트림즈 | 약간 다르다 | 완전히 동일 | 완전히 동일 |
퍼블리셔
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Flow;
import java.util.concurrent.Future;
public class CoffeePublisher implements Flow.Publisher<String> {
@Override
public void subscribe(Flow.Subscriber<? super String> subscriber) {
subscriber.onSubscribe(new CoffeeSubscription(subscriber));
}
private static class CoffeeSubscription implements Flow.Subscription {
private final Flow.Subscriber<? super String> subscriber;
private Future<?> future;
private CoffeeSubscription(Flow.Subscriber<? super String> subscriber) {
this.subscriber = subscriber;
}
@Override
public void request(long n) {
/**
* 클라이언트 요청을 처리하는 로직이 작성됩니다.
* 대신 구독자의 메서드를 실행하므로 비동기로 작성되어야합니다.
* onNext()가 동기처리될경우 구독자는 Block 상태가 됩니다.
*/
// onNext
if (n < 0) {
subscriber.onError(new IllegalArgumentException());
} else {
//이게 실행되는 위치는 subscriber Main 위치로 비동기로 하지 않으면 subscriber가 Block 처리된다.
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
subscriber.onNext("아메리카노");
} catch (InterruptedException e) {
// throw new RuntimeException(e);
}
});
}
}
@Override
public void cancel() {
if (future != null) {
future.cancel(false); // 인터럽트 설정
}
}
}
}
구독자
import java.util.concurrent.Flow;
public class CoffeeSubscriber implements Flow.Subscriber<String> {
private Flow.Subscription subscription;
private int coffeeCount = 0;
// 구독을 요청하면 subscription을 받고
// subscription을 통해서 퍼블리셔에게 요청합니다.
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(String item) {
System.out.println("수신 = " + item);
coffeeCount += 1;
if (coffeeCount < 2) {
subscription.request(1L);
}
}
// subscriber의 에러 처리 로직
@Override
public void onError(Throwable throwable) {
System.out.println("CoffeeSubscriber.onError : 에러 발생!");
}
// 퍼블리셔에서 종료를 실행할 경우
@Override
public void onComplete() {
System.out.println("더 이상 받을 커피가 없다.");
subscription.cancel();
}
}
메인
public static void main(String[] args) throws InterruptedException {
CoffeeSubscriber coffeeSubscriber = new CoffeeSubscriber();
CoffeePublisher coffeePublisher = new CoffeePublisher();
coffeePublisher.subscribe(coffeeSubscriber);
}
이렇게 자바에서 표준 인터페이스를 제공하여 PUB/SUB 방식으로 사용할 수 있습니다.
자바 10 변경내역
언어적 변경 내역
지역 변수의 타입을 추론하는 새로운 예약어 var
public static void main(String[] args) {
// 지역 변수
var num = 10;
num = "String"; // error
}
처음 초기화의 타입외에는 다른 타입으로 변경될 수 없습니다.
var
는 제네릭 타입을 사용할 때 간결하게 코드를 작성하도록 도와줍니다.
var는 “불변”을 위한 예약어가 없습니다.
현재 var는 지역 변수에만 적용됩니다. 지역 변수는 불변 예약어의 필요성이 덜하고 var
는 키워드가 아니기 때문에 변수명으로 사용 가능합니다. int var = 10;
기존 호환성을 위해서 유지하게 됩니다.
주의사항
- 타입을 추론하는 예약어입니다, 값이 없거나 null이면 안됩니다.
var num;
var b = (String s) -> System.out.println("s = " + s);
var c = {"가","나","다"};
위 3가지 모두 안됩니다.
- 다이아몬드 연산자와 함께 사용하는 경우,
<Object>
로 간주합니다.var nums = new ArrayList<>(); nums.add("5"); // ok nums.add(5); // ok
- 익명 클래스와 함께 쓸 수 있지만, 별도의 타입으로 간주합니다.
주요 API 변경
컬렉션 추가 API
-
copyOf()
메소드 : List/Set/Map의 원본 컬렉션을 깊게 복사한다.public static void main(String[] args) { List<Integer> oldNums = new ArrayList<>(); oldNums.add(1); oldNums.add(2); List<Integer> copyNums = List.copyOf(oldNums); List<Integer> viewNums = Collections.unmodifiableList(oldNums); oldNums.add(3); System.out.println("copyNums = " + copyNums.size()); // 2 System.out.println("viewNums = " + viewNums.size()); // 3 }
Collections.unmodifiableList(xxx)는 원본 객체의 뷰 역할을 합니다.
public static void main(String[] args) { ArrayList<List<Integer>> agg = new ArrayList<>(); ArrayList<Integer> list1 = new ArrayList<>(List.of(1,2)); ArrayList<Integer> list2 = new ArrayList<>(List.of(3,4,5)); agg.add(list1); agg.add(list2); List<List<Integer>> copyOf = List.copyOf(agg); agg.get(0).add(3); List<Integer> list1Copy = copyOf.get(0); System.out.println("list1Copy.size() = " + list1Copy.size()); // 3 }
단, 리스트 내부에 객체가 있는 건 복사가 되지 않습니다.
-
Collectors.toUnmodifiableXXX()
: 불변 객체로 반환하는 스트림이 추가되었습니다.
Optional 추가 API
orElseThrow()
: 매개변수없이 사용해도NoSuchElementException
이 자동으로 발생합니다.
시간 기반의 배포 버전 관리
기존 자바 9 버전은 메이저.마이너.보안패치 구조에서 x.y.z.u 구조로 변경되었습니다.
x,y,z,u에서 항상 y는 0으로 고정된다. 메이저 버전은 6개월 마다 출시됩니다. x.y.z.u
z는 첫 출시 한 달 이후 1이 올라가고 그 후 3개울 마다 1이 추가로 올라갑니다.
- 10.0.0.0 - 2018년 3월 출시
- 10.0.1.0 - 2018년 4월 출시(1개월)
- 10.0.2.0 - 2018년 7월 출시(3개월)
u는 긴급한 패치 이후 1이 올라갑니다.
메이저.0.마이너.긴급패치 구조로 생각해도 됩니다.
3년에 한 번은 LTS 버전이 출시되도록 바뀌었습니다.
G1GC 성능 개선
G1GC의 병렬 처리가 개선되었습니다
자바 11 주요 변경 내용
언어적 변경내용
- 람다식 매개변수에 var를 사용할 수 있습니다.
Consumer<String> consumerA = (String x) -> System.out.println("x = " + x);
Consumer<String> consumerVar = (var x) -> System.out.println("x = " + x);
람다식 매개변수는 타입을 명시하지 않아도 되지만 어노테이션을 추가하려면 자료형을 명시해야하므로 타입 대신에 var를 사용한다고 생각하면 됩니다.
Consumer<String> consumerX = x -> System.out.println("x = " + x);
Consumer<String> consumerAX = (@Nullable var x) -> System.out.println("x = " + x);
주요 API 변경내용
String 타입
-
String.strip()
: 문자열 앞 뒤의 공백과 탭을 제거합니다.// 앞뒤 모두 제거 System.out.println("abc.strip() = " + abc.strip()); //a b c // 앞만 제거 System.out.println("abc.stripLeading() = " + abc.stripLeading()); //a b c' ' // 뒤만 제거 System.out.println("abc.stripTrailing() = " + abc.stripTrailing()); //' 'a b c
-
isBlack()
: 특정 문자열이 white space만으로 구성되면 true를 반환합니다.tab
도 포함됩니다.String onlyBlank = " "; System.out.println("onlyBlank.isBlank() = " + onlyBlank.isBlank()); // true
-
lines()
: 개행 문자를 기준으로 문자열을 쪼개String<String>
을 반환합니다
\n\n
은 공백으로 스트림에 포함됩니다.
String str1 = "A\nB\nC\n\nD";
long count = str1.lines().count();
System.out.println("count = " + count); // 5
repeat()
: 반복 횟수를 파라미터로 받아, 주어진 문자열을 반복해 이어붙이 문자열을 반환한다.
Collection API
-
toArray()
: 컬렉션을 배열로 쉽게 만들 수 있는 메소드
자바 11 이전에서 컬렉션을 배열로 만드는 방법은 직접 만들어옮겨야합니다.// 기존 버전 String[] string2 = list.toArray(new String[0]); // 자바 11 String[] string2 = list.toArray(String[]::new);
-
Predicate
:간단 기능 추가String str1 = "A\nB\nC\n\nD"; long count = str1 .lines() .filter(Predicate.not(String::isBlank)) .count(); System.out.println("count = " + count); // 4
문자열 중 공백이 아닌 애들(not is blank) 만 적용 될수 없습니다.
조절할 수 없는 매소드 조건을 반대로 조정해야할 때 유용합니다.
Files 클래스 기능 추가
파일 전체를 읽어 String으로 만드는 기능, 반대로 String을 파일로 변환하는 기능이 추가되었습니다.
-
파일을 String 타입
지나치게 큰 파일을 읽을 경우 메모리가 부족할 수 있습니다.Path path = Paths.get(Paths.get(".").toAbsolutePath() + "/con-api/text.txt"); String str = Files.readString(path); System.out.println("str = " + str);
-
String 타입을 파일로 생성
public static void main(String[] args) throws IOException { Path path1 = Paths.get("."); Path path = Paths.get(path1.toAbsolutePath() + "/con-api/text.txt"); String str = Files.readString(path); Files.writeString(Paths.get(path1.toAbsolutePath()+"/con-api/text2.txt"),str); }
HTTP 클라이언트 추가
자바를 이용해 HTTP 요청을 주고 받아야 할 때 사용할 수 있는 API이 추가되었습니다.
기존에는 HttpUrlConnection
은 25년 전에 개발되었습니다.
- HTTP/2.0, HTTP/1.1 지원이 불가능
-
비동기 처리 불가능
- 사용하거나 유지 보수하는 것이 매우 어렵다.
apache HttpClient ,OkHttp ,Jetty HttpClient 등을 가져와 사용했습니다.
RestTemplate
도Apache Http Client
를 사용해서 HTTP 요청을 했습니다.
자바 9에 인큐베이팅 모듈로 등장하고 자바 11에서 표준 모듈로 인정받았습니다.
HttpClient
- HTTP/2.0, HTTP/1.1 지원
- WebSocket 지원
- CompletableFuture 를 이용한 비동기 매커니즘 지원
- 람다와 같은 새로운 언어 시스템에 우호적
해당 모듈이 java.base 모듈이 아니므로 별도로 의존성을 추가해야합니다.
unnamed module은 별도의 의존성을 하지 않아도 됩니다.
예시코드
-
GET
요청 동기HttpClient httpClient = HttpClient.newHttpClient(); var request = HttpRequest.newBuilder(URI.create("https://postman-echo.com/get")) .GET() .build(); // 기본은 GET 입니다. // send는 동기적으로 처리하는 방식입니다. var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.body()); //자바 21은 autoCloseable구현되어있음 httpClient.close();
응답 객체를 어떻게 핸들링 할 것 인지
HttpResonse.BodyHandler
를 통해 설정할 수 있습니다. -
GET
요청 비동기HttpClient httpClient = HttpClient.newHttpClient(); // 쓰레드풀을 지정할 수도 있습니다. var request = HttpRequest.newBuilder(URI.create("https://postman-echo.com/get")) .GET() .build(); // send는 비동기적으로 처리하는 방식입니다. // 다른 쓰레드에서 응답을 받을 수 있습니다. httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenAccept((response) -> { System.out.println(response.statusCode()); System.out.println(response.body()); }); httpClient.close();
비동기 방식은
main
쓰레드가 아닌 별도의 쓰레드에서 동작합니다. -
post
동기HttpClient postClient = HttpClient.newHttpClient(); HttpRequest post = HttpRequest.newBuilder(URI.create("https://postman-echo.com/post")) .POST(HttpRequest.BodyPublishers.ofString("{\"num\":1}")) .build(); HttpResponse<String> postResponse = postClient.send(post, HttpResponse.BodyHandlers.ofString()); System.out.println(postResponse.statusCode()); System.out.println(postResponse.body()); postClient.close();
POST는 body가 필요하므로 각자 환경에 맞게
json
으로 전송합니다.
Http Client가 나올 필요가 있을까?
HttpClient
를 갈아 끼우는 래퍼 클래스를 사용하다보니 표준 라이브러리에 있는HttpClient
를 사용하는 일이 많이 없습니다. 새로운 모듈이 추가되었다는 것은
외부 의존성을 추가하기 어려운 상황에서, 쓸만한 HTTP Client 표준 라이브러리가 생겼다.
상황에 따라서
Apache HTTP
대신HttpClient
를 사용할 수 있도록 스프링 6.1에서는JDKClientHttpRequestFactory
클래스가 추가되어 내부에 쓰도록 할 수 있습니다.
댓글남기기