새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
JDK 18~21 feature는 알고 쓰자
자바 12 버전부터 17버전까지 자바 feature를 정리했습니다.
자바 18 ~ 21 feature 정리Permalink
자바 21Permalink
record patternPermalink
record class(DTO)를 instanceof pattern matching과 함께 사용할 때 내부 필드에 바로 접근할 수 있는 기능입니다.
-
record class
는 자바 16에 추가된 데이터 전송 전용 객체입니다.public record Member( //component private final 기본 장착 String name, int age ){ static field; static method; instance method; //auto override toString,equals,hashCode; //get }
-
instanceof pattern matching
: 형변환후 새 변수에 할당하는 과정을 생략할 수 있습니다.public void checkPerson(Person person){ if(!(person instanceof Member member)){ return; } // use member }
record pattern을 사용하여 Member 객체에 바로 접근할 수 있습니다
public class Main {
record Member(String name, int age) {
public Member {
if (age < 0) {
throw new IllegalArgumentException("나이는 0보다 커야 합니다.");
}
}
}
public static void main(String[] args) {
Member member = new Member("홍길동", 20);
printMember(member);
}
public static void printMember(Object member){
if (member instanceof Member(String name, int age)){
System.out.println("name = " + name);
System.out.println("age = " + age);
}
}
}
매개변수의 자료형을 간단하게 var
로 표현할 수 있습니다.
public static void printMember(Object member){
if (member instanceof Member(var name, var age)){
System.out.println("name = " + name);
System.out.println("age = " + age);
}
}
중첩 record patternPermalink
내부 클래스에서도 적용이 가능합니다.
public class Main {
record Address(String city, String street, int zipCode) {}
record Member(String name, int age, Address address) {}
record Family(Member father, Member mother) {}
public static void main(String[] args) {
// address create
Address address1 = new Address("서울", "강남", 12345);
Address address2 = new Address("잠실", "잠실51번길", 12345);
Family family = new Family(new Member("아빠", 50,address1), new Member("엄마", 45,address2));
printMember(family);
}
public static void printMember(Object member) {
if (member instanceof Member(String name, int age, Address address)) {
System.out.println("name = " + name);
System.out.println("age = " + age);
System.out.println("address = " + address);
}
if (member instanceof Family(
Member(String fatherName, int fatherAge, Address(String city1, String street2, int zipCode2)),
Member(String motherName, int motherAge, Address(String city2, String street3, int zipCode3))
)) {
System.out.println("fatherName = " + fatherName);
System.out.println("fatherAge = " + fatherAge);
System.out.println("city1 = " + city1);
System.out.println("street2 = " + street2);
System.out.println("zipCode3 = " + zipCode2);
: : :
}
}
}
위 코드를 보면 중첩안에 다시 중첩 클래스도 사용가능합니다.
switch pattern matchingPermalink
자바 14에 추가된 switch Expression
은 switch
문이 statement
가 아니라 expression
으로 변경되어 아래와 같이 작성이 가능했습니다.
private int calculateStudyCafeFee(int hours){
return switch(hours){
case 1,2 -> 2_000;
case 3,4 -> 4_000;
case 5,6 -> 5_000;
default -> 7_000;
};
}
switch문을 하나의 값으로 사용할 수 있게 되었고 ->
화살표를 통해서 간단하게 반환할 값을 선택할 수 있었습니다.
switch(selector
): selector는 char
,byte
,short
,int
,String
,Enum
만 가능했습니다.
객체는 사용할 수 없고, selector는 분기를 칠 수 있는 값도 상수에 대해서만 가능했습니다
reference type 허용Permalink
자바 21부터 selector 안에 어떤 reference type도 들어갈 수 있습니다.
추가로 case 뒤에 패턴 매칭이 올 수 있습니다.
즉, case Member member 와 같은 구문도 사용이 가능하게 변화했습니다.
이렇게 변경된 이후로 if-else-if
를 사용했던 instanceOf
분기 처리를 switch expression
으로 변경할 수 있습니다.
public static void printMemberSwitch(Object obj) {
switch(obj){
case Member member -> {
System.out.println("member = " + member);
}
case Family family -> {
System.out.println("family = " + family);
}
default -> {
System.out.println("default");
}
}
}
switch에 객체를 받아서 변수에 바로 값을 할당하여 사용할 수 있습니다.
sealed class 추가 기능Permalink
만약 들어오는 객체가 sealed class
일 경우 default
라벨도 제거할 수 있습니다( 컴파일 시점에 클래스 상황을 알 수 있습니다.)
public sealed interface Computer permits DeskTop, Laptop {
void boot();
void shutdown();
}
//switch pattern matching example by Computer
public static void boot(Computer computer) {
switch (computer) {
case DeskTop deskTop -> System.out.println("데스크탑 부팅 : " );
case Laptop laptop -> System.out.println("랩탑 부팅 : ");
}
}
순서 주의Permalink
만약 스위치 문장에서 전혀 도달 할 수 없는 경우, 즉 아래 switch로 접근할 수 없는 경우 컴파일 에러가 발생합니다.
public static void boot(Computer computer) {
switch (computer) {
[에러] case Computer computerTmp -> System.out.println("컴퓨터 부팅");
case Laptop laptop when laptop.isOn() -> System.out.println("랩탑 부팅 성공");
case DeskTop deskTop -> System.out.println("데스크탑 부팅 : " );
case Laptop laptop -> System.out.println("랩탑 부팅 : ");
case null -> ...
}
}
Laptop
과 DeskTop
의 상위 클래스는 Computer
입니다. case 문 첫 줄에 있는 경우 나머지 case
문은 전혀 도달 할 수 없습니다.
이런 경우 예외가 발생하게 됩니다.
대신 when
을 추가하는 경우가능합니다. 왜냐하면 &&
으로 조건이 추가 되기 때문입니다.
public static void boot(Computer computer) {
switch (computer) {
case Computer computerTmp when computerTmp.isIntel() -> System.out.println("컴퓨터 부팅");
case Laptop laptop when laptop.isOn() -> System.out.println("랩탑 부팅 성공");
case DeskTop deskTop -> System.out.println("데스크탑 부팅 : " );
case Laptop laptop -> System.out.println("랩탑 부팅 : ");
case null -> ...
}
}
switch의 NPE 조건도 변경Permalink
이전 버전에서는 switch 선택자에 null이 들어오는 경우 바로 NPE가 발생했습니다.
21 버전부터는 선택자 is null And Case null not existing 일 경우에만 NPE가 발생하게 됩니다.
//switch pattern matching example by Computer
public static void boot(Computer computer) {
switch (computer) {
case DeskTop deskTop -> deskTop.boot();
case Laptop laptop -> laptop.boot();
case null, default -> System.out.println("컴퓨터가 없습니다.");
}
}
맨 아랫줄에 case null, defualt ->
가 없을 경우에만 NPE 예외가 발생합니다.
그런데 이런 생각이 들었습니다. default가 나머지를 다 처리하니 null
이 들어와도 실행되지 않을까?
예외가 발생합니다.
public static void main(String[] args) {
boot(null);
}
//switch pattern matching example by Computer
public static void boot(Computer computer) {
switch (computer) {
case DeskTop deskTop -> deskTop.boot();
case Laptop laptop -> laptop.boot();
default -> System.out.println("컴퓨터가 없습니다.");
}
}
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:233)
sealed class 와 같이 활용Permalink
한 API에서 서로 다른 스펙을 반환하는 경우에 sealed class
와 switch pattern matching
을 사용하면 조금 더 깔끔한 코딩이 가능하게 됩니다. 예를 들어 API 결과 response
로 노트북이나 데스크탑 정보를 반환해야할 때 두 스펙이 다를 경우 사용할 수 있는 방법입니다.
public record DeskTop(
String name,
boolean isIntelCPU,
int price
) implements Computer {}
public record Laptop(
String name,
double screenSize,
int price
) implements Computer {}
ComputerAsService
이후 수정
Math API clamp 함수Permalink
테스트하려는 value의 값과 min 과 max 사이에 있는지 확인합니다.
- min <= value <= max : value 반환
- value < min: min 반환
- max < value : max 반환
public static void main(String[] args) {
//test clamp
// case 1 : value < min // 1
System.out.println(Math.clamp(0, 1, 5));
// case 2 : value > max // 5
System.out.println(Math.clamp(10, 1, 5));
// case 3 : value == min // 1
System.out.println(Math.clamp(1, 1, 5));
// case 4 : value == max // 5
System.out.println(Math.clamp(5, 1, 5));
// case 5 : min < value < max // 3
System.out.println(Math.clamp(3, 1, 5));
}
String 관련 함수Permalink
String indexOf 함수Permalink
특정 범위 안에 있는문자 또는 문자열의 위치를 찾을 수 있습니다.
public static void main(String[] args) {
// test String.indexOf(ch,idx,end)
String str = "안녕하세요. 자바 21 입니다.";
// find 1st '자' from 0 to 7 (exclusive)
System.out.println(str.indexOf('자', 0, 7)); // -1
System.out.println(str.indexOf('자', 0, 10)); // 7
// find 2nd '자'
System.out.println(str.indexOf('자', 8, 10)); // -1
}
주의사항은 마지막 인덱스는 exclusive
로 포함되지 않습니다.
String splitWithDelimiters 함수Permalink
delimiters까지 포함하여 배열로 반환합니다.
String str2 = "안녕하세요.|자바|21|입니다.";
String[] split = str2.splitWithDelimiters("\\|", -1);
System.out.println(Arrays.toString(split));
// [안녕하세요., |, 자바, |, 21, |, 입니다.]
StringBuffer/Builder repeat 함수Permalink
public static void main(String[] args) {
//test StringBuilder repeat
StringBuilder sb = new StringBuilder();
sb.repeat("abc ", 3);
System.out.println(sb);
}
// abc abc abc
Character 클래스 - emoji 관련 함수들 추가Permalink
public static void main(String[] args) {
int codePoint = Character.codePointAt("😀", 0); // 예시 이모지 코드 포인트 ()
// isEmoji: 주어진 코드 포인트가 이모지인지 확인
System.out.println(Character.isEmoji(codePoint));
// isEmojiPresentation: 주어진 코드 포인트가 이모지 프레젠테이션을 가지고 있는지 확인
System.out.println(Character.isEmojiPresentation(codePoint));
// isEmojiModifier: 주어진 코드 포인트가 이모지 수정자인지 확인
System.out.println(Character.isEmojiModifier(codePoint));
// isEmojiModifierBase: 주어진 코드 포인트가 이모지 수정자 베이스인지 확인
System.out.println(Character.isEmojiModifierBase(codePoint));
// isEmojiComponent: 주어진 코드 포인트가 이모지 구성 요소인지 확인
System.out.println(Character.isEmojiComponent(codePoint));
// isEmojiZeroWidthJoiner: 주어진 코드 포인트가 확장된 그림문자인지 확인
System.out.println(Character.isExtendedPictographic(codePoint));
// codePoint가 이모지인지 확인하고, 맞다면 int로 반환하는 함수
int emojiInt = getEmojiCodePoint(codePoint);
if (emojiInt != -1) {
System.out.println("이모지 코드 포인트: " + emojiInt);
} else {
System.out.println("이모지가 아닙니다.");
}
}
public static int getEmojiCodePoint(int codePoint) {
if (Character.isEmoji(codePoint) ||
Character.isEmojiPresentation(codePoint) ||
Character.isEmojiModifier(codePoint) ||
Character.isEmojiModifierBase(codePoint) ||
Character.isEmojiComponent(codePoint) ||
Character.isExtendedPictographic(codePoint)) {
return codePoint;
}
return -1; // 이모지가 아닌 경우 -1 반환
}
true
true
false
false
false
true
이모지 코드 포인트: 128512
Sequenced Collection API 추가Permalink
자바 21에 추가된 새로운 인터페이스로 순서가 있는 컬렉션의 요소에 대해 통일된 방법으로 접근하고 조작할 수 있도록 설계되었습니다.
이 개념은 List와 Deque 처럼 요소의 순서가 중요한 컬렉션에서 일관된 방법으로 요소를 접근,추가,제거할 수 있습니다.
반대로 뒤집는 reversed()
도 사용할 수 있습니다.
기존 비일관적인 APIPermalink
작업 | List 예시 코드 | Deque 예시 코드 | Sequenced Collection 예시 코드 |
---|---|---|---|
첫 번째 위치에 요소 추가 | list.add(0, "첫 번째"); |
deque.addFirst("첫 번째"); |
sequencedCollection.addFirst("첫 번째"); |
마지막 위치에 요소 추가 | list.add("마지막"); |
deque.addLast("마지막"); |
sequencedCollection.addLast("마지막"); |
첫 번째 요소 가져오기 | list.get(0); |
deque.getFirst(); |
sequencedCollection.getFirst(); |
마지막 요소 가져오기 | list.get(list.size() - 1); |
deque.getLast(); |
sequencedCollection.getLast(); |
첫 번째 요소 제거 | list.remove(0); |
deque.removeFirst(); |
sequencedCollection.removeFirst(); |
마지막 요소 제거 | list.remove(list.size() - 1); |
deque.removeLast(); |
sequencedCollection.removeLast(); |
reversed()Permalink
자바 view Collection
을 만드는 List<String> result = Collections.unmodifiableList(list);
와 유사합니다.
차이점은 reversed만 원본의 데이터가 변경되면, reversed
결과도 변경되어 보이고, reversed
의 데이터가 바뀌면, 원본도 바뀝니다.
원본 데이터 변경시 영향을 받는다.Permalink
public static void main(String[] args) {
List<Integer> origins = new ArrayList<>(List.of(1, 2, 3, 4, 5));
List<Integer> reversed = origins.reversed();
List<Integer> unmodifiableList = Collections.unmodifiableList(origins);
// size 비교 3가지 모두 동일
System.out.println(origins.size()); // 5
System.out.println(reversed.size()); // 5
System.out.println(unmodifiableList.size()); // 5
// 원본 데이터 추가
origins.addLast(6);
// size 비교 3가지 모두 동일
System.out.println(origins.size()); // 6
System.out.println(reversed.size()); // 6
System.out.println(unmodifiableList.size()); // 6
}
reversed 수정시 원본 데이터도 영향을 받는다.Permalink
public static void main(String[] args) {
List<Integer> origins = new ArrayList<>(List.of(1, 2, 3, 4, 5));
List<Integer> reversed = origins.reversed();
List<Integer> unmodifiableList = Collections.unmodifiableList(origins);
// size 비교 3가지 모두 동일
System.out.println(origins.size()); // 5
System.out.println(reversed.size()); // 5
System.out.println(unmodifiableList.size()); // 5
// reversed 데이터 추가
reversed.addLast(6);
// size 비교 3가지 모두 동일
System.out.println(origins.size()); // 6
System.out.println(reversed.size()); // 6
System.out.println(unmodifiableList.size()); // 6
}
Set은 다르게 동작한다.Permalink
- LinkedHashSet: 집합의 동일한 원소는 하나만 존재하므로 위치가 재조정됩니다.
- SortedSet: 맨처음과 맨 뒤에 데이터를 저장하면 예외(
UnsupportedOperationException
)이 발생합니다.
요소를 추가할때 자동으로 정렬된 순서로 배치되기 때문에addFirst
,addLast
는 예외가 발생합니다.
Map은 다르게 동작한다.Permalink
정렬된 상태로 관리하는 SortedMap
,LinkedHashMap
에서 추가된 메서드가 있습니다.
메서드 | 설명 | 반환 타입 | 불변(Immutable) 여부 |
---|---|---|---|
firstEntry() |
첫 번째 키-값 쌍을 반환합니다. | Map.Entry<K, V> |
불변 |
lastEntry() |
마지막 키-값 쌍을 반환합니다. | Map.Entry<K, V> |
불변 |
pollFirstEntry() |
첫 번째 키-값 쌍을 제거하고 반환합니다. | Map.Entry<K, V> |
불변 |
pollLastEntry() |
마지막 키-값 쌍을 제거하고 반환합니다. | Map.Entry<K, V> |
불변 |
putFirst(K key, V value) |
맵의 첫 번째 위치에 키-값 쌍을 추가합니다. | V | 아니오 |
putLast(K key, V value) |
맵의 마지막 위치에 키-값 쌍을 추가합니다. | V | 아니오 |
reversed() |
역순으로 정렬된 SequencedMap<K, V> 를 반환합니다. |
SequencedMap<K, V> |
아니요 |
sequencedEntrySet() |
순서를 유지하면서 키-값 쌍을 반환하는 Set<Map.Entry<K, V>> 를 반환합니다. |
Set<Map.Entry<K, V>> |
불변 |
sequencedKeySet() |
순서를 유지하면서 키들을 반환하는 NavigableSet<K> 을 반환합니다. |
NavigableSet<K> |
불변 |
sequencedValues() |
순서를 유지하면서 값들을 반환하는 Collection<V> 을 반환합니다. |
Collection<V> |
불변 |
코드로 확인해보겠습니다.
- 불변 반환 확인
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put("key1", "value1"); linkedHashMap.put("key2", "value2"); linkedHashMap.put("key3", "value3"); Map.Entry<String, String> firstEntry = linkedHashMap.pollFirstEntry(); firstEntry.setValue("new value"); // 예외발생
-
reversed()
수정확인@Test @DisplayName("reversed 데이터 추가 및 원본 데이터 수정가능하다.") void add() { LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put("key1", "value1"); linkedHashMap.put("key2", "value2"); linkedHashMap.put("key3", "value3"); SequencedMap<String, String> reversed = linkedHashMap.reversed(); reversed.put("key4", "value4"); Assertions.assertSame(4, linkedHashMap.size()); }
Iterable로 조회한 경우 수정이 가능하다.Permalink
firstEntry()
와 같은 메서드가 아닌 이터레이터로 데이터를 가져오는 경우 원본 데이터를 수정할 수 있습니다.
@Test
@DisplayName("iterator로 조회한 Entry의 value 수정시 원본 데이터 수정가능하다.")
void modify() {
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("key1", "value1");
linkedHashMap.put("key2", "value2");
linkedHashMap.put("key3", "value3");
Map.Entry<String, String> firstEntry = linkedHashMap.entrySet().iterator().next();
firstEntry.setValue("new value");
Assertions.assertSame("new value", linkedHashMap.get("key1"));
}
댓글남기기