[Java] 버전 별 차이점 (java 8, 11, 17)
회사에서는 레거시 프로젝트로 거의 자바 8 버전을 사용하고 있고.
이번에 차세대를 진행하면서 자바 17이냐 자바 21이냐... 하는 얘기가 나왔다.
개인적으로 17이든 21이든 큰 차이점을 크게 느끼지 못 해
버전 별 차이점을 알아보고 앞으로 자바 버전 선택에 참고해보고자 한다.
가장 먼저 많은 버전들 중에서 자바 8, 11, 17을 선호하는 이유는 무엇인가 하면.
이 버전들이 LTS(Long Term Support) 버전이기 때문이다.
LTS는 말 그대로 오랫동안 보안 패치와 성능 개선이 지원된다.
다른 버전이 6개월인 것에 비해 LTS는 8년 정도로 긴 시간을 지워하기 때문에 많은 회사가 사용한다.
해외 설문조사에서 현재 높은 사용률을 보이는 건 자바 11이라고 한다. (우리나라는 자바 8이지 않을까?)
어쨌거나 최신 LTS 버전이 발표되면 변경점을 확인해보는 것이 좋다.
자바 버전에 관한 버전 이력은 아래 링크에서 확인 가능하다.
https://www.oracle.com/java/technologies/downloads/?er=221886
Download the Latest Java LTS Free
Subscribe to Java SE and get the most comprehensive Java support available, with 24/7 global access to the experts.
www.oracle.com
Java 8
1. 람다 표현식
함수형 프로그래밍 지원. 코드의 간결성이 높아짐.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 람다 이전 comparator
names.sort(new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
//람다 이후
names.sort((s1, s2) -> s1.compareTo(s2));
2. 스트림 API
컬렉션을 선언적으로 처리 가능하게 해주는 기능이다.
스트림 API를 사용하면 데이터 집합을 필터링, 변환, 정렬, 집계하는 작업을 더 쉽게 할 수 있다.
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
// 'A'로 시작하는 이름 필터링
List<String> filteredNames
= names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
// 알파벳 순으로 정렬
List<String> sortedNames
= names.stream()
.sorted()
.collect(Collectors.toList());
3. java.time
새로운 날짜와 시간 API인 java.time 패키지는 이전의 java.util.Date나 java.util.Calendar 클래스보다 더 나은 날짜와 시간 처리 방법을 제공한다.
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now();
등. 로컬 타임을 지원하며, 불변 객체를 사용하여 스레드 안정성을 보장한다.
4. 디폴트 메서드
디폴트 메서드를 통해 인터페이스 내에서도 메서드를 구현할 수 있다.
기존 추상 메서드처럼 오버라이드 할 수 있으며, 디폴트 메서드는 기존 인터페이스를 변경하지 않고 새로운 메서드를 추가할 수 있다는 게 장점이다.
기존 인터페이스 메서드 | 디폴트 메서드 |
무조건 추상적. 실제 구현은 상속받은 클래스에서. | 인터페이스 내에서 구현 가능. |
반드시 모든 메소드 구현. | 선택적으로 오버라이드 가능. 오버라이드 하지 않으면 디폴트 메서드 내에 구현된 코드로 동작. |
인터페이스 다중 구현 시에 충돌 위험 없음. | 다중 구현 시 인터페이스를 명시해줘야 함. |
새로운 메서드 추가시 호환성 문제 | 새롭게 추가하더라도 호환성 문제 없음 |
위와 같은 특징을 통해 디폴트 메서드는 재사용성, 확장성이 좋다.
public interface InterfaceA {
default void defaultMethod() {
System.out.println("Default method in InterfaceA.");
}
}
public interface InterfaceB {
default void defaultMethod() {
System.out.println("Default method in InterfaceB.");
}
}
public class MyClass implements InterfaceA, InterfaceB {
// 디폴트 메서드 충돌 해결
@Override
public void defaultMethod() {
System.out.println("Default method in MyClass.");
InterfaceA.super.defaultMethod(); // InterfaceA의 디폴트 메서드 호출
InterfaceB.super.defaultMethod(); // InterfaceB의 디폴트 메서드 호출
}
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.defaultMethod();
// 출력:
// Default method in MyClass.
// Default method in InterfaceA.
// Default method in InterfaceB.
}
}
5. 메서드 레퍼런스
메서드 레퍼런스란 말그대로 메서드 참조. 보통 람다식에서 많이 사용되며.
람다식을 더 짧게 만들어 가독성을 높이기 위한 것이라 이해하면 된다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
//람다식
names.forEach(name -> System.out.println(name));
//메서드 레퍼런스
names.forEach(System.out::println);
'메서드 레퍼런스'라는 명칭처럼 메소드가 마치 매개변수처럼 들어가 있는 형태가 인상적이다.
- 정적 메서드 레퍼런스 : 클래스 이름과 함께 ' :: ' 연산자를 사용하여 메서드를 참조. 함수형 인터페이스의 메서드와 매개변수가 일치해야 한다.
- 인스턴스 메서드 레퍼런스 : 인스턴스의 메서드를 참조. 매개변수는 참조하는 메서드와 일치해야 하며, 참조할 객체를 생성한 후에 사용할 수 있다.
줄글로만 보면 이해가 안 되니 예시를 보면.
import java.util.function.BiFunction;
public class StaticMethodReferenceExample {
public static void main(String[] args) {
// 정적 메서드 정의
BiFunction<Integer, Integer, Integer> add = StaticMethodReferenceExample::add;
// 메서드 호출
int result = add.apply(5, 3);
System.out.println(result); // 출력: 8
}
public static int add(int a, int b) {
return a + b;
}
}
정적 메서드 레퍼런스의 경우 StaticMethodReferenceExample이란 클래스 안에 static으로 선언된 add 메서드를 참조한다.
import java.util.function.BiFunction;
public class InstanceMethodReferenceExample {
public static void main(String[] args) {
// 객체 생성
String prefix = "Hello, ";
// 인스턴스 메서드 레퍼런스
BiFunction<String, String, String> concatenate = prefix::concat;
// 메서드 호출
String result = concatenate.apply("Alice");
System.out.println(result); // 출력: Hello, Alice
}
}
인스턴스 메서드 레퍼런스는 String 클래스 안에 정의되어 있는 concat을 참조한다.
때문에 반드시 String 객체가 먼저 생성되어야지 사용할 수 있다.
실제 result를 출력해 보면 결과가 prefix.concat("Alice")와 동일하다.
더 자세한 설명은 다른 링크를 참고.
https://codechacha.com/ko/java8-method-reference/
Java - 메소드 레퍼런스(Method Reference) 이해하기
메소드 레퍼런스(Method Reference)는 Lambda 표현식을 더 간단하게 표현하는 방법입니다. 메소드 레퍼런스는 사용하는 패턴에 따라 다음과 같이 분류할 수 있습니다. Static 메소드 레퍼런스, Instance 메
codechacha.com
6. Optinal 클래스
값이 존재할 수도 있고 존재하지 않을 수도 있다. 말장난 같지만 개발할 때 가장 많이 보는 예외가 NullPointerException이다. 이걸 피하려면 null을 검사해야 하는데 Optional 클래스로 'null'을 반환하거나 참조하는 등 null로 인한 문제를 줄일 수 있다.
주요 메서드
- isPresent()로 값의 존재 여부를 확인한다.
- get()은 값을 반환하고 없을 경우 'NoSuchElementException'을 던진다.
- orElse(T other)는 값이 있다면 그걸 반환하고 없다면 기본값을 반환한다.
- orElseGet(Supplier <? extends T> other)는 orElse와 비슷하지만 함수를 넘긴다.
Optinal 클래스는 null일 경우 값을 처리하는 과정이 추가되기 때문에 상황에 따라서는 오버헤드가 발생할 수도 있다.
따라서 null로 발생할 오류가 큰 경우에 사용하는 게 좋다.
더 자세한 내용은 아래로.
https://mangkyu.tistory.com/70
[Java] Optional이란? Optional 개념 및 사용법 - (1/2)
이번에는 Java8부터 지원하는 Optional 클래스에 대해 알아보도록 하겠습니다. 1. Optional이란? Optional 개념 및 사용법 [ NPE(NullPointerException) 이란? ] 개발을 할 때 가장 많이 발생하는 예외 중 하나가 바
mangkyu.tistory.com
Java 11
1. Oracle JDK와 OpenJDK 통합
2. String과 File 기능 향상
- String - .isBlank(), .lines(), .strip() 등 메서드 추가
- File - .writeString(), .readString() 등 메서드 추가
3. HTTP 클라이언트 API
HTTP/1.1과 HTTP/2 프로토콜을 지원하는 새로운 API가 추가되었다.
이전에는 'HttpURLConnection' 클래스를 통해 http를 요청했으나 자바 11에서는 보다 클라이언트 API가 향상되었다.
4. Epsilon 가비지 컬렉터
메모리 할당만 하고 가비지 컬렉션은 수행하지 않는 가비지 컬렉터 도입.
주로 테스트와 벤치마킹에 사용된다. G1 GC가 기본 GC로 설정되었다.
*G1 GC(Garbage-First Garbage Collector)
G1 가비지 컬렉터는 자바 7에서 도입되어 11에서도 기본 가비지 컬렉터로 사용된다.
주요 특징은 높은 힙 공간 회수와 짧은 중단 시간이며 성능 최적화를 목표로 한다.
작동 방식은 간단하게, 힙을 여러 개의 지역(region)으로 나누고 이 지역들 내에서 독립적으로 가비지 컬렉션을 수행한다. 즉 전체 힙을 동시에 수집하지 않기에 더 효율적이다.
5. 람다에서 var 매개변수
람다식에서 매개변수로 var 키워드 사용 가능해졌다.
var 키워드는 Java 10에서 추가되었으면 11부터는 람다식에서도 사용 가능하다.
Java 17
1. Record 클래스
자바 14에서 처음 도입되었으며 15에서 표준 기능으로 확정됐다.
기본적으로 record는 불변이기 때문에 DTO나 VO를 작성할 때 유용하다.
주요 특징
- 불변성: 생성 시 정의된 필드 값을 변경할 수 없으며, 모든 필드는 'final'로 취급된다.
- 내부에 toString(), equlas(), hashCode() 등이 포함되어 있다.
- 다른 클래스를 상속 받을 수 없다.
public record Person(String name, int age) {
// 생성자는 자동으로 생성됩니다.
// toString(), equals(), hashCode() 메서드도 자동으로 생성됩니다.
// 추가적인 메서드나 필드를 정의할 수 있습니다.
public String greeting() {
return "Hello, my name is " + name + " and I am " + age + " years old.";
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
System.out.println(person.name()); // Alice
System.out.println(person.age()); // 30
System.out.println(person.greeting()); // Hello, my name is Alice and I am 30 years old.
System.out.println(person); // Person[name=Alice, age=30]
}
}
위가 레코드를 사용했을 때.
아래가 레코드를 사용하지 않았을 때 코드다.
public class Person {
private final String name;
private final int age;
// 생성자
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter 메서드
public String getName() {
return name;
}
public int getAge() {
return age;
}
// toString() 메서드
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
// equals() 메서드
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person pe
눈으로만 봐도 차이를 한 눈에 알 수 있듯.
레코드를 통해 반복적인 코드를 획기적으로 줄이고 피로도도 낮출 수 있다.
2. Sealed 클래스
봉인 클래스, 제한 클래스라고 번역되는 Sealed 클래스는 자바 15부터 도입된 클래스이다.
하위 클래스에서 상위 클래스를 무분별하게 사용하지 못하도록 '상위 클래스에서 지정한 하위 클래스만' 상위 클래스를 상속받을 수 있다.
// 봉인 클래스 정의
public sealed class Shape permits Circle, Rectangle, Triangle {
// 봉인 클래스는 직접 인스턴스를 생성할 수 없습니다.
}
위처럼 접근 제한자 뒤에 sealed 라는 키워드를 붙여서 사용한다. 그리고 permits 뒤에 상속을 허락할 하위 클래스를 작성하면 된다.
그러나 하위 클래스에는 하위 클래스가 또 존재할 수 있기 때문에 이 경우에 무조건 상속을 허락해버리면 Sealed 클래스를 사용하는 의미가 없다.
// 봉인 클래스를 상속하는 하위 클래스
public non-sealed class Triangle extends Shape {
private final double base;
private final double height;
...
}
// Triangle을 상속하는 추가 하위 클래스
public final class EquilateralTriangle extends Triangle {
public EquilateralTriangle(double side) {
super(side, side); // 모든 변의 길이가 같은 정삼각형
}
...
}
그래서 'non-sealed'와 'final'이란 키워드로 하위 클래스를 허용하는지 아닌지를 명시한다.
'non-sealed'일 때는 봉인 클래스(sealed) -> 하위 클래스(non-sealed) -> 하위 클래스 ... 처럼 계속해서 상속 받을 수 있다.
'final' 키워드가 붙으면 해당 클래스를 끝으로 더 이상 상속받을 수 없음을 뜻한다.
3. 스위치 표현
자바 14 이후로 스위치 표현식이 표준화 되었다.
기존 switch 문에 비교하면 훨씬 가독성이 높고, 값을 반환할 수 있는 등 활용할 수 있는 범위가 넓어졌다.
// 스위치 표현식 예제
public class SwitchExpressionExample {
public static void main(String[] args) {
int dayOfWeek = 3; // 예: 월요일(1), 화요일(2), 수요일(3), ...
String dayName = switch (dayOfWeek) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
case 4 -> "Thursday";
case 5 -> "Friday";
case 6 -> "Saturday";
case 7 -> "Sunday";
default -> throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeek);
};
System.out.println(dayName); // Wednesday
}
}
표준화된 switch 표현식은 위와 같다.
람다식에서 사용되는 '->' 표현으로 코드 흐름이 명확해진 게 특징이다.
추가적으로 'yield'라는 키워드가 생기면서 case 블록 내에서도 값을 반환할 수 있다.
// `yield`를 사용하는 예제
public class SwitchYieldExample {
public static void main(String[] args) {
int number = 2;
String result = switch (number) {
case 1 -> "One";
case 2 -> {
String value = "Two";
yield value; // 복잡한 로직을 사용한 후 결과를 반환
}
case 3 -> "Three";
default -> throw new IllegalArgumentException("Unexpected value: " + number);
};
System.out.println(result); // Two
}
}
4. Stream.toList() 사용 가능
5. 텍스트 블록
String을 여러 줄 사용시 """으로 묶어서 블록으로 표현할 수 있게 됐다.
본문 내 예시는 챗GPT를 활용하여 작성하였습니다.