Java - Java 8 ~ Java 17


이 포스트에서는 Java 8~17 의 주요 변경점에 대해 알아본다.


Java 8

인터페이스에 디폴트 메서드와 정적 메서드 포함 가능

interface TestInterface {
    default String test1() {
        return "test";
    }
    static String test2() {
        return "test2";
    }
}

디폴트 메서드는 구현 클래스에서 재정의 가능하지만, 정적 메서드는 구현 클래스에서 재정의 불가

Java8 - 디폴트 메서드


함수형 인터페이스

@FunctionalInterface
public interface BufferedReaderProcess {
  String process(BufferedReader b) throws IOException;
}

하나의 추상 메서드를 정의하는 인터페이스
추상 메서드 외 디폴트 메서드가 정적 메서드는 제한없이 사용 가능
주요 함수형 인터페이스는 Predicate, Consumer, Supplier, Function, UnaryOperator 등이 있음

Java8 - 람다 표현식 (1): 함수형 인터페이스, 형식 검사


람다 표현식

정렬 - 일반

// 무게가 큰 순으로 정렬
Comparator<Apple> byWeight = new Comparator<Apple>() {
  @Override
  public int compare(Apple o1, Apple o2) {
    return o2.getWeight().compareTo(o1.getWeight());
  }
};

정렬 - 람다식

Comparator<Apple> byWeight = (Apple o1, Apple o2) -> o2.getWeight().compareTo(o1.getWeight());
// 외부 반복
for (String value: myCollection) { 
    System.out.println(value); 
}

// 내부 반복
myCollection.forEach(value -> System.out.println(value));

익명 클래스처럼 이름이 없는 함수이면서 메서드를 인수로 전달하거나 메서드의 결과로 반환될 수 있음
즉, 함수를 변수로 다를 수 있음
메서드를 식으로 나타낸 것이지만 엄밀히 말하면 이 메서드를 가진 객체를 생성해내는 것이므로 주로 함수형 인터페이스의 익명 객체를 대체하기 위해 사용함
간결하고 직관적인 코드 생성 가능

Java8 - 람다 표현식 (1): 함수형 인터페이스, 형식 검사
Java8 - 람다 표현식 (2): 메서드 레퍼런스, 람다 표현식과 메서드의 조합


메서드 레퍼런스

람다식을 좀 더 간결하게 사용하는 것

메서드 참조에는 4가지 방법이 있음

정적 메서드 레퍼런스

ClassName::staticMethod 으로 사용

람다식 사용 시

boolean isUser = list.stream().anyMatch(user -> User.isUser(user));

메서드 레퍼런스 사용 시

boolean isUser = list.stream.anyMatch(User::isUser);

인스턴스 메서드 레퍼런스

ClassName::instanceMethod 으로 사용
String, BigDecimal 등의 인스턴스 메서드에 대해 사용

BiPredicate<List<String>, String> stringList = (list, ele) -> list.contains(ele);
BiPredicate<List<String>, String> stringList = List::contains;

기존 객체의 인스턴스 메서드 레퍼런스

ClassName::instanceMethod 으로 사용

User user = new User();
boolean isFirstName = list.stream().anyMatch(user::isFirstName());

생성자 레퍼런스

ClassName::new

생성자 인수가 0개인 경우

람다식

Supplier<Apple> c2 = () -> new Apple(); // 디폴트 생성자 Apple() 의 람다 표현식
        Apple a2 = c2.get();  // Supplier 의 get() 메서드롤 호출하여 새로운 Apple 객체 생성

생성자 레퍼런스

Supplier<Apple> c1 = Apple::new;  // 디폴트 생성자 Apple() 의 생성자 레퍼런스
        Apple a1 = c1.get();  // Supplier 의 get() 메서드롤 호출하여 새로운 Apple 객체 생성

생성자 인수가 1개인 경우

람다식

Function<Integer, Apple> c4 = (weight) -> new Apple(weight);  // Apple(Integer weight) 의 람다 표현식
        Apple a4 = c4.apply(10);  // Function 의 apply() 메서드롤 호출하여 새로운 Apple 객체 생성

생성자 레퍼런스

Function<Integer, Apple> c3 = Apple::new; // Apple(Integer weight) 의 생성자 레퍼런스
Apple a3 = c3.apply(10);  // Function 의 apply() 메서드롤 호출하여 새로운 Apple 객체 생성

생성자 인수가 2개인 경우

람다식

BiFunction<Integer, String, Apple> c6 = (weight, color) -> new Apple(weight, color);  // Apple(Integer weight, String color) 의 람다 표현식
Apple a6 = c6.apply(10, "red"); // BiFunction 의 apply() 메서드롤 호출하여 새로운 Apple 객체 생성

생성자 레퍼런스

BiFunction<Integer, String, Apple> c5 = Apple::new; // Apple(Integer weight, String color) 의 생성자 레퍼런스
Apple a5 = c5.apply(10, "red"); // BiFunction 의 apply() 메서드롤 호출하여 새로운 Apple 객체 생성

Java8 - 람다 표현식 (2): 메서드 레퍼런스, 람다 표현식과 메서드의 조합


스트림 API

컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 내부 반복자

  • 조립 가능, 병렬화 있음
  • 자바 컬렉션은 외부 반복, 스트림은 내부 반복 사용
  • 중간 연산은 파이프라인으로 구성되어 최종 연산에서 한번에 처리됨, 이를 지연 계산된다고 함
  • 중간 연산은 Stream 을 반환하고, 최종 연산은 스트림이 아닌 결과를 반환
  • 중간 연산의 예시로는 map(), filter(), flatMap 이 있고, 최종 연산의 예시로는 count(), foreach(), collect() 가 있음

컬렉션은 데이터를 어떻게 저장/관리/접근하는지가 목표이지만,
스트림은 데이터를 직접 접근/조작하는 기능 제공은 하지 않고, 어떻게 계산할지를 목표로 함

Java8 - Stream
Java8 - Stream 활용 (1): 필터링, 슬라이싱, 매핑, 검색, 매칭
Java8 - Stream 활용 (2): 리듀싱, 숫자형 스트림, 스트림 생성
Java8 - Stream 으로 데이터 수집 (1): Collectors 클래스, Reducing 과 Summary, Grouping
Java8 - Stream 으로 데이터 수집 (2): Partitioning, Collector 인터페이스, Custom Collector
Java8 - Stream 으로 병렬 데이터 처리 (1): 병렬 스트림, 포크/조인 프레임워크
Java8 - Stream 으로 병렬 데이터 처리 (2): Spliterator 인터페이스


Date and Time API

기존의 Date, Calendar, DateFormat, TimeZone 을 LocalDate, LocalTime, LocalDateTime, DateTimeFormat, ZoneId 등이 대체
기존보다 훨씬 쉽게 날짜 로직 작성 가능

기존

// 오늘 날짜 구하기
Calendar cal = Calendar.getInstance();
String format = "yyyy-MM-dd";
SimpleDateFormat sdf = new SimpleDateFormat(format);
String date = sdf.format(cal.getTime());

Java 8

// 오늘 날짜 rngkrl
LocalDate today = LocalDate.now();

Java8 - 날짜와 시간


Optional

Optional 을 통해 null 에 대한 참조를 안전하게 할 수 있음

기존

public String logic() {
    User user = getUser();
    if (user != null) {
        Address address = user.getAddress();
        if (address != null) {
            String street = address.getStreet();
            if (street != null) {
                return street;
            }
        }
    }
    return "not specified";
}

Optional

public String logic() {
    Optional<User> user = Optional.ofNullable(getUser());
    String result = user
      .map(User::getAddress)
      .map(Address::getStreet)
      .orElse("not specified");
    return result;
}

Java8 - Optional 클래스


배열 정렬 시 병렬 처리

기존엔 Arrays.sort() 로 배열을 정렬했는데 이 메서드는 정렬이 순차적으로 실행됨
Java 8 부터는 Arrays.parallelSort() 를 통해 병렬적으로 배열 정렬 가능


Java 9

모듈화 (jigsaw)

기존의 jar 패키징으로 모듈화를 진행하지만 jar 에는 문제점이 있음
Jar Hell 이 있는데 복잡한 ClassLoader 로 정의되었을 때 JVM 컴파일은 성공하지만 런타임 때 ClassNotFoundExeption 발생
또한 jar 는 무거움

jigsaw 라는 새로운 모듈화를 통해 가볍고 복잡하지 않은 java 모듈 시스템 구축 가능

  • jar 기반 모노리틱 방식을 개선하여 모듈 지정 및 모듈별 버전 관리 기능 가능
  • 필요한 모듈만 구동하여 크기와 성능 최적화 가능

사용법

// Java 코드 최상위에 module-info.java 파일을 만들고, 아래와 같이 사용함
module java.sql {
        requires public java.logging;
        requires public java.xml;
        exports java.sql;
        exports javax.sql;
        exports javax.transaction.xa;
}

자세한 사용법은 jigsaw start 에서 확인


Stream 에 새로운 메서드 추가

takeWhile(), dropWhile(), iterate(), `ofNullable() 등의 메서드 추가

iterate()

IntStream
	.iterate(1, i -> i < 20, i -> i * 2)
	.forEach(System.out::println);

takeWhile()

//for ordered Stream
Stream.of(1, 2, 3, 4, 5, 6).takeWhile(i -> i <= 3).forEach(System.out::println);
// The result is:
// 1
// 2
// 3
        
// for unordered Stream
Stream.of(1, 6, 5, 2, 3, 4).takeWhile(i -> i <= 3).forEach(System.out::println);
// The result is:
// 1

dropWhile()

/for ordered Stream
Stream.of(1, 2, 3, 4, 5, 6).dropWhile(i -> i <= 3).forEach(System.out::println);
// It drops (1,2,3), the result is:
// 4
// 5
// 6
        
//for unordered Stream
Stream.of(1, 6, 5, 2, 3, 4).dropWhile(i -> i <= 3).forEach(System.out::println);
// It drops (1), the result is:
// 6
// 5
// 2
// 3
// 4

ofNullable()

// numbers [1,2,3,null]
// mapNumber [1 - one, 2 - two, 3 - three, null - null]
List<String> newstringNumbers = numbers.stream()
        .flatMap(s -> Stream.ofNullable(mapNumber.get(s)))
        .collect(Collectors.toList());
// The result is:
// [one, two, three]

Optional 개선

Optional::stream추가

Optional::stream 으로 Optional 객체 지연작업 제공하며, 0 또는 하나 이상의 요소 스트림 반환
또한 빈 요소를 자동으로 확인하고 제거

// streamOptional(): [(Optional.empty(), Optional.of("one"), Optional.of("two"), Optional.of("three")]
List<String> newStrings = streamOptional()
				.flatMap(Optional::stream)
				.collect(Collectors.toList());

// Result: newStrings[one, two, three]

ifPresentOrElse() 메서드 추가

Optional<Integer> result3 = getOptionalEmpty();
result3.ifPresentOrElse(
      x -> System.out.println("Result = " + x),
      () -> System.out.println("return " + result2.orElse(-1) + ": Result not found."));

// return -1: Result not found.

or() 메서드 추가

값이 존재하는지 검사 후 값이 있으면 해당 Optional 반환, 값이 없으면 Suppier Function 에서 생성한 다른 Optional 반환

Optional<Integer> result = getOptionalEmpty() // Empty Optional object
        .or(() -> getAnotherOptionalEmpty()) // Empty Optional object
        .or(() -> getOptionalNormal())  // this return an Optional with real value 42
        .or(() -> getAnotherOptionalNormal());  // this return an Optional with real value 99

// Result: Optional[42]

인터페이스에 Private 메서드 추가

인터페이스에서 private 메서드를 사용한다는 의미는 해당 메서드는 인터페이스 내부에서만 사용하고, 인터페이스를 구현하는 클래스는 해당 메서드를 따로 구현할 수 없다는 의미임

public interface CustomInterface {
     
    public abstract void method1();
     
    public default void method2() {
        method4();  //private method inside default method
        method5();  //static method inside other non-static method
        System.out.println("default method");
    }
     
    public static void method3() {
        method5(); //static method inside other static method
        System.out.println("static method");
    }
     
    private void method4(){
        System.out.println("private method");
    } 
     
    private static void method5(){
        System.out.println("private static method");
    } 
}

불변 컬렉션 생성 메서드 제공

불변 List, Set, Map, Map.Entry 생성 가능

List immutableList = List.of();
List immutableList = List.of(one, two, thress);
Map immutableMap = Map.of(1, "one", 2, "two");

try-with-resources 개선

void tryWithResourcesByJava7() throws IOException {
     BufferedReader reader1 = new BufferedReader(new FileReader("test.txt"));
     try (BufferedReader reader2 = reader1) {
          // do something
     }
}
// final or effectively final이 적용되어 reader 참조를 사용할 수 있음
void tryWithResourcesByJava9() throws IOException {
     BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
     try (reader) {
          // do something
     }
}

Reactive Stream: Flow API 추가

상호 통신 가능한 publish-subscribe 프레임워크를 지원하는 java.util.concurrent.Flow 에서 Reactive Stream 추가


java.util.concurrent.Flow
java.util.concurrent.Flow.Publisher
java.util.concurrent.Flow.Subscriber
java.util.concurrent.Flow.Processor

@FunctionalInterface
public static interface Publisher<T> {
 public void subscribe(Subscriber<?superT> subscriber);
}
public static interface Subscriber<T> {
 public void onSubscribe(Subscription subscription);
 public void onNext(Titem);
 public void onError(Throwable throwable);
 public void onComplete();
}
public static interface Subscription {
 public void request(long n);
 public void cancel();
}
public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> { }

CompletableFuture API 개선

// 50초후에 새로운 Executor 생성
Executor executor = CompletableFuture.delayedExecutor(50L, TimeUnit.SECONDS);

JShell

기본 제공툴로 제공
java 코드를 미리 검증해보는 프로토타이핑 도구이므로 간단한 실습이나 다른 라이브러리 테스트 시 유용함
cmd 에서 실행


Java 10

var 예약어

var 예약어 사용 시 중복을 줄임으로써 코드를 간결하게 만들 수 있음
기존의 엄격한 타입 선언 방식에서 탈피하여 컴파일러에게 타입을 추론하게 함
var 는 지역 변수 타입 추론을 허용하며, 메서드 내부의 변수에만 적용 가능

var 는 아래 상황에서만 사용 가능

  • 초기화된 로컬 변수 선언 시
  • 반복문에서 지역 변수 선언 시
var list = new ArrayList<String>();  // infers ArrayList<String>
var stream = list.stream();          // infers Stream<String>
var numbers = List.of(1, 2, 3, 4, 5);

for (var number : numbers) {
    System.out.println(number);
}

Java 11

String 클래스에 새로운 메서드 추가

아래 5개 메서드 추가

  • strip()
    • 앞뒤 공백 제거
  • stripLeading()
    • 앞 공백 제거
  • stripTrailing()
    • 뒤 공백 제거
  • isBlank()
    • 문자열이 비어있거나 공백이 포함되어 있을 경우 true 반환
    • String.trim().isEmpty() 와 동일한 결과
  • repeat(n)
    • n 개만큼 문자열을 반복하여 붙여서 반복
  • line()
    • 문자열을 줄 단위로 쪼개서 스트림 반환

java.nio.file.Files 클래스에 새로운 메서드 추가

아래 3개 메서드 추가

  • Path writeString(Path, String, Charset, OpenOption)
    • 파일에 문자열을 작성하고 Path 로 반환
    • OpenOption 에 따라 작동 방식을 달리하며, Charset 을 지정하지 않으면 UTF-8 사용
  • String readString(Path, Charset)
    • 파일 전체를 읽어서 String 으로 반환
    • 파일을 모두 읽거나 예외 발생 시 알아서 close 함
    • Charset 을 지정하지 않으면 UTF-8 사용
  • boolean isSameFile(Path, Path)
    • 두 Path 가 같은 파일을 가리키면 true 반환

컬렉션 인터페이스에 새로운 메서드 추가

컬렉션의 toArray() 메서드를 오버 로딩하는 메서드 추가 (원하는 타입의 배열을 선택하여 반환 가능)

List list = Arrays.asList("java", "test");
String[] strings = list.toArray(String[]::new);
assertTath(list).containsExactly("java", "test");

Predicate 인터페이스에 새로운 메서드 추가

not() 메서드 추가

List<String> list = Arrays.asList("java", " ", "\n \n", "test");
List withoutBlanks = list.stream()
        .filter(Predicate.not(String::isBlank))
        .collect(Collectors.toList());
assertTath(withoutBlanks).containsExactly("java", "test");

람다에서 로컬 변수 var 사용 가능

람다는 타입을 스킵할 수 있는데 로컬 변수를 사용하는 이유는 @Nullable 등의 어노테이션을 사용하기 위해 타입을 명시해야 할 때
var 를 사용하려면 괄호를 써야하고, 모든 파라메터에 사용해야 하며, 다른 타입과 혼용하거나 일부 스킵은 불가능함

List<String> list = Arrays.asList("java", "test");
    String result = list.stream()
    .map((@Nonnull var x) -> x.toUpperCase())
    .collect(Collectors.joining(", "));
(var n1, var n2) -> n1 + n2;  // 가능

(var n1, n2) -> n1 + n2;  // 불가능, n2 에도 var 필요
(var n1, String n2) -> n1 + n2; // 불가능, String 과 혼용 불가 
var n1 -> n1; // 불가능, 괄호 필요

자바 파일 실행

javac 를 통해 컴파일하지 않고도 바로 java 파일 실행 가능

기존

$ javac HelloWorld.java
$ java Helloworld
Hello Java 8!

Java 11

$ java Helloworld
Hello Java 8!

Java 12

Switch 확장

기존

String time;
switch (weekday) {
	case MONDAY:
	case FRIDAY:
		time = "10:00-18:00";
		break;
	case TUESDAY:
	case THURSDAY:
		time = "10:00-14:00";
		break;
	default:
		time = "휴일";
}

Java 12

String time = switch (weekday) {
	case MONDAY, FRIDAY -> "10:00-18:00";
	case TUESDAY, THURSDAY -> "10:00-14:00";
	default -> "휴일";
};

Java 13

Switch 표현식 (preview)

preview
향후 변경될 수 있음

Switch 표현식이 값을 반환할 수 있으며, fall-through/break 문제없이 람다 스타일 구문 사용 가능

기존

switch(status) {
  case SUBSCRIBER:
    *// code block*break;
  case FREE_TRIAL:
    *// code block*break;
  default:
    *// code block*
}

Java 13

boolean result = switch (status) {
    case SUBSCRIBER -> true;
    case FREE_TRIAL -> false;
    default -> throw new IllegalArgumentException(*"something is murky!"*);
};

Java 14

Switch 표현식 표준화

Java 13 에서 preview 였던 Switch 표현식이 표준화됨

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    default      -> {
      String s = day.toString();
      int result = s.length();
      yield result;
    }
};

만일 메서드도 실행하고 값도 반환하고 싶으면 yield 키워드 사용

int returnFrom = switch (type) {
    case TYPE_1 -> 3;
    default -> {
        System.out.println("return default value");
        yield 2; // `yield` 키워드를 사용한다.
    }
}
int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    default      -> {
      String s = day.toString();
      int result = s.length();
      yield result;
    }
};

record 선언 기능 추가 (preview)

기존

final class Point {
    public final int x;
    public final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

Java 14

record Point(int x, int y) {
    // 상속은 불가하다. (마치 final 클래스처럼)

    // 초기화 필드는 `private final`이다. 즉, 수정 불가
    x = 5; // 에러

    // static 필드와 메서드를 가질 수 있다.
    static int LENGTH = 25;

    public static int getDefaultLength() {
        return LENGTH;
    }
}

사용할 때는 아래와 같이 사용

Point point = new Point(2, 3);

// getter가 자동 생성
point.x();

Pattern matching for instanceof() (preview)

instanceof() 연산자가 적용되는 if 블록내에서 지역 변수 사용 가능

기존

// 캐스팅이 들어가게 된다.
if (obj instanceof String) {
    String text = (String) obj;
}

Java 14

// 형 변환 과정이 없고, 그 변수를 담을 수 있다.
if (obj instanceof String s) {
    System.out.println(s);
    if (s.length() > 2) {
        // ...
    }
}

// 조건을 중첩해서 넣을 수도 있다.
if (obj instanceof String s && s.length() > 2) {
    // okay!
}

지역 변수 개념으로 적용되기 때문에 else 문에서는 instanceof() 에서 적용한 변수가 접근되지 않음

if (obj instanceof String s) {

} else {
    // s가 접근되지 않는다.
}

Text Blocks (second preview)

긴 문자열을 +\n 을 사용하는 것이 아니라 Text block 개념을 사용하면 편함

private void runJEP368() {
    String html = """
            {
                "list": [
                    {
                        "title": "hello, taeng",
                        "author": "taeng"
                    },
                    {
                        "title": "hello, news",
                        "author": "taeng"
                    }
                ]
            }
            """.indent(2);
    // indent 는 자바 12에서 추가된 것인데, 문자열 각 맨 앞 행을 n만큼 띄운다.
    System.out.println(html);
}

동적으로 문자열을 다루고 싶을 땐 아래처럼 placeholder 와 같이 사용

// placeholder in textBlocks
String textBlock = """
            {
                "title": "%s"
            }
        """.indent(1);
System.out.println(String.format(textBlock, "Hello Madplay"));
# 앞에   띄워짐
 {
   "title": "Hello Madplay"
 }

formatted() 를 사용하여 아래와 같이 사용 가능

String textBlock = """
        {
            "title": "%s",
            "author": "%s",
            "id": %d
        }
        """.formatted("hi", "taeng", 2);

System.out.println(textBlock);
{
  "title": "hello World!",
  "author": "taeng",
  "id": 2
}

Java 15

Text Blocks 도입

Java 14 에서 preview 였던 기능이 프로덕션 준비 완료


Sealed Classes (preview)

상속 가능한 클래스를 지정할 수 있는 Sealedclass 제공
상속 가능한 대상은 상위 클래스 혹은 인터페이스 패키지 내에 속해있어야 함

public abstract sealed class Shape
    permits Circle, Rectangle, Square {...}

즉, 클래스가 public 인 동안 하위 클래스로 허용되는 유일한 클래스는 Circle, Rectangle, Square 임


Java 17

Sealed Classes (finalized)

Java 15 에서 preview 였던 기능이 완료됨


Deprecating the security manager

자바 1.0 이후로 security manager 가 존재해 왔었지만 현재는 더 이상 사용되지 않으며 향후 버전에서는 제거될 예정


참고 사이트 & 함께 보면 좋은 사이트






© 2020.08. by assu10

Powered by assu10