
요즘 공부하면서 새롭게 도입된 기능과 문법에 대해서 제가 제대로 알지 못한다는 생각이 들어 전체적인 큰 그림을 그리고자 포스팅을 하게 되었습니다.
최근에 var과 Stream, record에 대한 것들을 접하면서
Java 버전 별로 어떤 문법들이 도입되었는지
어떻게 개발자를 편하게 했는지에 대해서 정리해야 겠다는 생각이 들었습니다.
그래서 오늘은 Java Version별 특징을 정리해보려고 합니다.
//7버전 이전
List<String> list = new ArrayList<String>();
// 7버전 이후
List<String> list = new ArrayList<>();
switch(fruit){
case "사과" :
...
break;
case "수박" :
...
brak;
default :
...
break;
}
7버전 이전에는 무조건 finally 블록을 통해서 리소스 해제가 가능했지만
이후에는 try-catch를 사용해도 자동으로 리소스가 해체되도록 변경되었습니다.
예외 상황을 여러개 줄 수 있습니다.
즉 하나의 catch 안에 논리연산자 or에 해당하는 '|'를 사용하여 여러개의 예외를 연결할 수 있습니다.
tyr{
} catch(예외1 | 예외2 | 예외3 e) {
}
🧨 몇 가지 주의사항이 있습니다.
이전에는 분할 정복 방식으로 작업하기 위해서 관련 코드를 작성해야 했지만 7버전 이후에는 ForkJoinPool 클래스가 제공되었습니다.
단일 추상 메서드를 포함하는 인터페이스를 구현할 수 있는 람다 표현식이 도입되었습니다. 이후 함수형 프로그래밍, 스트림 API 그리고 컬렉션 프레임워크의 개선에 영향을 주었습니다.
public interface Operate {
// 추상 메서드가 하나이다
int operate(int a, int b);
// default 메서드는 추상 메서드에 포함되지 않는다
default void print() {
System.out.println("출력");
}
}
Operate operate = (a, b) -> {
print();
return a + b;
};
특정 메서드만을 호출하는 람다의 축약형입니다.
새로운 기능이 아니라 하나의 메서드를 참조하는 람다를 편리하게 표현할 수 있는 문법입니다.
| 람다 | 메서드 레퍼런스 |
|---|---|
| (Soccer s) -> s.getGoal() | Soccer::getGoal |
| ()-> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
| (str,i) -> str.substring(i) | String::substring |
| (String s) -> System.out.println(s) | System.out::println |
interface에도 구현체 작성이 가능하게 되었습니다.
위 예시에서도 확인 가능합니다.
Java에서는 null을 다루는게 까다롭습니다. NPE에 대한 예외처리를 신경써야 했습니다. 그래서 8버전 이후부터 null 체크를 깔끔하게 해줄 수 있는 Optional 클래스가 도입되었습니다.
//Optional 구조체 생성
Optional<String> optionalStr = Optional.empty();
Optional<String> optionalStr = Optional.of("str");
Optional<String> optionalStr = Optional.ofNullable("str");
// 이전
String value = null;
String result = "";
try{
result = value.toUpperCase();
} catch (NullPointException e) {
throw new Exception();
}
// 이후
String value = null;
Optional<String> result = Optional.ofNullable(value);
List를 반복문 대신 깔끔하고 편한게 해주는 Stream API를 사용할 수 있게 되었습니다.
public List<LocationResponse> autoCompleteLocation(String fullAddress, Pageable pageable){
return locationRepository.findAllLocation(fullAddress,pageable).stream()
.map(LocationResponse::buildLocationResponse)
.collect(Collectors.toList());
이 부분이 흥미로웠습니다.

Method 영역에 클래스에 대한 메타 정보( 클래스의 이름, 생성정보, 필드정보, 메서드 정보 등)와 정적 멤버 변수 그리고 interned String이 저장됩니다.
이 Method 영역은 Permenent Generation이 관리하는 한 영역이며 Permenent Generation 영역의 크기가 제한적이라는 것에 문제가 발생했습니다.
java.lang.OutOfMemoryError: PermGen space
그래서 자바 8 버전 이후부터 PermGen(Permenent Generation) Area가 제거되었습니다.
이 영역이 제거되고 MetaSpace 영역이 등장하게 되었고 이는 heap이 아닌 Native Memory 영역으로 옮겨가 이제는 JRE가 아닌 OS가 관리하는 메모리 대상이 되었습니다.
하지만 기존에 PermGen이 했던 모든 역할이 MataSpace로 옮겨간 것은 아니고
클래스 메타 정보만 MetaSpace에서 관리하고
정적 멤버 변수와 interned String은 heap에서 관리되도록 쪼개졌습니다.
사실 이건 무슨 말인지 모르겠습니다.
공부하다가 추후에 알게 된다면 추가하겠습니다.
HttpURLConnection을 대체하기 위해서 java.net.http 패키지가 추가되었습니다.
java.net.http는 HTTP에 대한 상위 클라이언트 인터페이스와 WebSocket에 대한 하위 클라이언트 인터페이스를 제공합니다.
정의된 주요 유형은 아래와 같습니다.
List<String> list = List.of("one", "two", "three");
Set<String> set = Set.of("one", "two", "three");
Map<String, String> map = Map.of("foo", "one", "bar", "two");
takeWhile, dropWhile, iterate 메서드 형태가 추가되었습니다.
Stream<String> stream = Stream.iterate("", s -> s + "s")
.takeWhile(s -> s.length() < 10);
Optional을 포함하는 스트림을 쉽게 처리할 수 있도록 Optional에 stream()이 추가되었습니다.
Optional<Menu> selectedMenu = input.enterMenu();
selectedMenu.ifPresentOrElse(
menu -> run(menu),
() -> output.viewMenuInputError());
interface NewInterface {
private static String staticPrivate() {
return "static private";
}
}
아래 상황에서만 사용될 수 있습니다.
즉 추론이 가능한 상황에서 사용이 가능합니다.
var customerId = UUID.randomUUID();
var numList = List.of(1,2,3,400);
for(var number : numList) {
System.out.println(number);
}
이 3가지 키워드는 제가 아직 이해하지 못해서 추후에 추가하도록 하겠습니다.
| 매소드 | 기능 |
|---|---|
| strip() | 문자열 앞, 뒤 공백 제거 |
| stripLeading() | 문자열 앞의 공백 제거 |
| stripTrailing() | 문자열 뒤의 공백 제거 |
| isBlank() | 문자열이 비어있거나 공백만 포함되어 있을 경우 true를 반환 |
| lines() | 문자열을 라인 단위로 쪼개는 스트림을 반환 |
| repeat(n) | 지정된 수 만큼 문자열을 반복하여 붙여 반환 |
String str = "A \n B \n C \n D";
Stream<String> lines = str.lines();
lines.forEach(System.out::println);

list.stream()
.map((var s) -> s.toLowerCase())
.collect(Collectors.toList());
근데 제가 느꼈을 때는 굳이? 라는 생각이 들었습니다.
없는 편이 더 간단하고 어차피 추론이 가능한 상태라고 생각했기 때문입니다.
JEP 323의 목표를 보면
명시적으로 선언되던 구문을 var로 선언할 수 있게 되었는데
원래 람다식에서 암시적으로 선언되었던 형태에 var를 적용함으로써 표현식을 통일할 수 있도록 하였다고 합니다.
람다표현식에 var를 선언하는 경우 람다표현식에도 어노테이션을 사용하는 경우 조금 더 코드가 간단해집니다.
list.stream()
.map((@NotNull var s) -> s.toLowerCase())
.collect(Collectors.toList());
https://headf1rst.github.io/TIL/jvm-static
https://meetup.nhncloud.com/posts/171