Java 8의 특징
Java 8
- 2014년 3월에 출시
- 2021년 가장 많이 사용하고 있는 버전
- 2021 jetbrain report에 따르면 가장 많이 사용하고 있는 버전은 Java 8로, 사용 비율은 약 72%임
- 2022 뉴텔릭에서 조사한 통계에 따르면 Java 11이 48.44%, Java 8이 46.45%로 여전히 사용 비율 높은 편
- 새로운 기능과 향상된 기능 및 버그 픽스가 포함되어 Java 프로그램 개발 및 실행의 효율성을 높여주는 Java 릴리스
- 프로그래밍 언어 Java의 중요한 변화 중 하나로 기록됨
- 여러 가지 새로운 기능과 개선 사항을 도입하여 Java 개발자들에게 큰 영향을 미침
- 특히 함수형 프로그래밍의 도입과 더불어, 개발자들이 보다 효율적이고 간결한 코드를 작성할 수 있게 함
- 이에 따라 Java의 사용성과 생산성을 크게 향상 시킴
주요 변경사항
1. 람다 표현식 (Lambda expression)
- 메소드로 전달할 수 있는 익명 함수(Anonymous function)를 단순한 문법으로 표기한 것
- 함수를 하나의 표현식으로 나타냄
- 메소드에 이름이 없어서 익명
- 특정 클래스에 종속되지 않으므로 함수
- 메소드 인수로 전달하거나 변수로 저장 가능
- 코드를 간결하게 작성할 수 있음
- 언어의 표현이 더 풍부해졌기 때문
- 개발자가 더 쉽게 객체 지향 프로그래밍, 함수형 프로그래밍을 할 수 있게 됨
- 가독성 증가
- 단위 테스트에 용이
- 재활용이 불가능함
- 익명 함수를 여러 곳에서 반복적으로 사용할 경우, 재활용을 위해 익명 함수를 사용하지 않는 방법을 고려해야 함
람다 표현식 구성
- 람다 파라미터 리스트
- 화살표 : 파라미터 리스트와 바디를 구분
- 람다 바디 : 람다의 반환값에 해당하는 표현식
- 어떤 값이 들어가면(람다 파라미터) 결과물이 나옴(람다 바디)
- 반환타입 메소드이름(매개변수) -> { 메소드내용 }
- 반환타입과 메소드명을 제거하고 매개변수 선언부와 메소드내용 사이에 '->'를 추가
형식 검사, 형식 추론, 제약
- 람다 표현식 자체에는 람다가 어떤 함수형 인터페이스를 구현하는지의 정보가 포함되어 있지 않음
- 람다 표현식을 더 제대로 이해하려면 람다의 실제 형식을 파악해야 함
- 람다 표현식의 형식 검사 과정의 재구성
형식 검사
List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);
- 람다가 사용된 콘텍스트는 무엇인가? 우선 filter의 정의를 확인
- 대상 형식은 Predicate
- Predicate인터페이스의 추상 메서드는 무엇?
- Apple을 인수로 받아 boolean을 반환하는 test 메서드
- 함수 디스크립터는 Apple → boolean이므로 람다의 시그니처와 일치. 람다도 Apple을 인수로 받아 boolean을 반환하므로 코드 형식 검사가 성공적으로 완료
형식 추론
- 자바 컴파일러는 람다 표현식이 사용된 콘텍스트를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론
- 결과적으로 컴파일러는 람다 표현식의 파라미터 형식에 접긍할 수 있으므로 람다 문법에서 이를 생략할 수 있음
람다 표현식 예
- 비교 :
// (1) 일반 메소드 int max(int a, int b){ return a < b ? a : b; } // (2) 람다 표현식 (a, b) -> a < b ? a : b
- 하나의 파라미터를 갖고 리턴 타입이 없는 람다 표현식
(String str) -> System.out.println("parameter is " + str);
// 타입을 생략한 코드
(str) -> System.out.println("parameter is " + str);
// 람다 파라미터 괄호를 생략한 코드
str -> System.out.println("parameter is " + str);
- 하나의 파라미터를 갖고 리턴 타입이 있는 람다 표현식
// 람다 바디를 하나의 라인으로 표현 (i) -> i + 10; // 람다 바디를 여러 라인으로 표현 (i) -> { int result = i + 10; return result; };
- 파라미터가 없고 리턴 타입이 있는 람다 표현식
()-> Integer.MAX_VALUE;
2. Method Reference (메소드 참조)
- 메소드 참조
- 특정 메소드만 호출하는 람다의 축약 표현
- 새로운 기능이 아니라 하나의 메소드를 참조하는 람다를 편리하게 표현할 수 있는 문법으로 간주할 수 있음
- 람다 표현식을 줄일 수 있어 상황에 따라 가독성이 더 좋아보임
- 다음과 같이 사용
- 클래스 이름::메소드 이름
- 생성자::new
Method Reference 예
- 간단 예시
람다 | Method Reference |
---|---|
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
Method Reference 형태
정적 메소드 참조
- 예를 들어 Integer의 parseInt 정적 메서드를 다음과 같이 메서드 참조 형태로 바꿀 수 있음
Function<String, Integer> function = s -> Integer.parseInt(s);
Function<String, Integer> function = Integer::parseInt;
다양한 형식의 인스턴스 메소드 참조
- 예를 들어 String의 length 메서드를 호출하는 람다 표현식을 다음과 같은 형태로 바꿀 수 있음
Function<String, Integer> function = a -> a.length();
Function<String, Integer> function = String::length;
기존 객체의 인스턴스 메소드 참조
- 예를 들어 Member Class의 메서드를 호출하는 람다 표현식을 다음과 같은 형태로 바꿀 수 있음
class Member {
private String name;
public String getName() {
return name;
}
}
Function<Member, String> function = member -> member.getName();
Function<Member, String> function = Member::getName;
생성자 참조
- 클래스의 생성자도 메서드와 같이 참조를 만들 수 있음
- 생성자 참조는 정적 메서드의 참조를 만드는 방법과 비슷함
- Member Class를 생성하는 람다 표현식을 다음과 같이 작성할 수 있음
Supplier<Member> supplier = () -> new Member();
Supplier<Member> supplier = Member::new;
- 만약 생성자에 파라미터가 있다면 다음과 같이 바꿔 작성할 수 있음
Function<String, Member> function = name -> new Member(name);
Function<String, Member> function = Member::new;
- 항상 주의할 것은, 람다 표현식과 함수형 인터페이스와 시그니처가 같아야 한다는 것
- 만약 Member 생성자가 Member(Long, String, String)와 같다면 이 시그니처를 갖는 함수형 인터페이스를 다음과 같이 선언해주어야 함
@FunctionalInterface
public interface MemberFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
MemberFunction<Long, String, String, Member> memberFunction = Member::new;
3. 스트림 API(Stream API)
- 데이터 컬렉션을 함수형 스타일로 처리할 수 있게 해주는 기능을 제공
- 데이터를 더욱 효율적으로 처리 가능
- 대량의 데이터 처리에 있어서의 성능 개선
- 별다른 노력없이 병렬 처리를 쉽게 구현할 수 있음
- 멀티 코어 프로세서의 이점을 최대한 활용 가능해짐
- 파이프라인 방식으로 처리할 수 있음
- 데이터의 필터링, 변환, 정렬 등의 작업을 간결하고 명확하게 표현할 수 있게 해줌
- 데이터를 추상화하여 데이터 처리 로직에만 집중할 수 있게 해줌
- 코드의 가독성 높임
- 데이터베이스 쿼리를 작성하듯이 직관적인 코드를 제공
- 유지보수성을 향상시킴
스트림의 특징
- 예시 코드
// 스트림 사용X
books.sort(Comparator.comparing(Book::getName));
List<String> booksWrittenByNietzsche = new ArrayList<>();
for (Book book : books) {
if (book.getAuthor().equals("Friedrich Nietzsche")) {
booksWrittenByNietzsche.add(book.getIsbn());
}
}
// 스트림 사용O
List<String> booksWrittenByNietzsche =
books.stream()
.filter(book -> book.getAuthor().equals("Friedrich Nietzsche"))
.sorted(Comparator.comparing(Book::getName))
.map(Book::getIsbn)
.collect(Collectors.toList());
1. 파이프라이닝 지원
- 메서드 체이닝으로 연결
- 위 예제에서 filter(), sorted(), map(), collect() 등이 계속 이어짐
- 스트림 객체끼리 연속으로 처리하면서 하나의 파이프라인이 되어 최종적인 결괏값을 반환
- 함수형 인터페이스를 설명하면서 언급했던 그림이 하나의 파이프라인이라고 생각하면 됨
2. 내부 반복을 지원
- 코드 외부에서 for문을 사용하지 않고 filter()처럼 내부에서 알아서 처리
- 그래서 코드를 분석하는데 방해되는 요인을 줄이고 비즈니스 로직 구현에만 충실한 코드를 짤 수 있음
3. 딱 한 번만 탐색
- 스트림의 기능을 거치게 되면 이전 상태로 돌아갈 수 없음
- 무슨 뜻이냐면 연산 이전의 값을 저장하지 않고 연산된 값만 새롭게 반환한다는 뜻
- 예제에서 filter() 다음에 sorted()를 호출한다. sorted()에는 이미 저자 이름이 니체인 책 정보만 파라미터로 들어가게 됨
- 그래서 재사용이 필요하다면 변수에 저장해 두고 사용해야 함
4. 게으르게 동작
- 필요한 시점까지 실행을 미루다 특정 시점이 되면 동작
5. 중간 연산과 종료 연산으로 구분
- 스트림은 크게 이 둘로 구분
- 이들을 구분하는 특징으로 반환 타입을 보면 됨
- Stream 형태의 스트림이 반환된다면 중간 연산이고, 그렇지 않으면 종료 연산
4. Parallel Stream
- Stream을 병렬로 처리가능하도록 하는 기능
- 여러 스레드에서 처리할 수 있도록 분할한 Stream
5. java.time 패키지
- 기존에 자바에서 제공하던 Date와 Calendar가 가지고 있는 단점을 보완하기 위해 추가됨
- 기존 Calendar 단점 :
- Calendar클래스는 월(month)를 표현할 때 0부터 시작
- 즉 1월은 0, 2월은 1... 그래서 +1을 해줘야 원하는 월을 얻을 수 있음
- Calendar클래스는 불변객체가 아님
- 이 말은 수정이 가능하여 멀티스레드 환경에 노출되었을 때 안전하지 않다는 것
- Calendar클래스는 윤초와 같은 특별한 상황을 고려하지 않음
- 이를 보완하여 나온 것이 바로 'java.time'
- 날짜와 시간을 같이 보여주던 Calendar와 다르게 java.time에서는 별도로 클래스를 분리
- Calendar클래스는 월(month)를 표현할 때 0부터 시작
6. Default Method
- 인터페이스의 구현체를 인터페이스 자체에서 기본으로 제공 가능
- default 키워드를 사용하여 간단하게 구현할 수 있음
- 구현 클래스에서 인터페이스를 구현하지 않아도 됨
- 인터페이스는 기능에 대한 선언만 가능하기 때문에 실제 코드를 구현한 로직은 포함될 수 없지만 Java 8 에서는 메소드 선언 시에 default를 명시하게 되면 인터페이스 내부에서도 로직이 포함된 메소드를 선언할 수 있음
- 재정의 가능
- 참조 변수로 호출 가능
- 하위 호환성을 유지하고 인터페이스의 보완을 진행할 수 있음
public interface test {
int no();
default boolean test() { // Default Method
return no() == 0;
}
}
7. 나즈혼(Nashorn)
- 지금까지 자바스크립트의 기본 엔진으로는 모질라의 리노(Rhino)가 사용되어 왔음
- 하지만 자바의 최신 개선 사항 등을 제대로 활용하지 못하는 등 노후화된 모습을 보여주게 됨
- 따라서 이번 Java SE 8 버전부터는 자바스크립트의 새로운 엔진으로 오라클의 나즈혼(Nashorn)을 도입하게 됨
- 새로운 경량의 고성능 JavaScript 엔진이 구현되어 JDK에 통합되었으며 기존 API를 통해 Java 응용 프로그램에서 이 엔진을 사용할 수 있음
- 나즈혼(Nashorn)은 기존에 사용되어 온 리노에 비해 성능과 메모리 관리 면에서 크게 개선된 스크립트 엔진이라고 이해하면 됨
참고 자료
- https://velog.io/@chancehee/Java-Java-8-11-%EB%B2%84%EC%A0%84-%ED%8A%B9%EC%A7%95
- https://seoarc.tistory.com/87
- https://www.java.com/ko/download/help/java8.html
- https://assu10.github.io/dev/2023/05/21/java8-intro/#22-%EB%9E%8C%EB%8B%A4-%EC%9D%B5%EB%AA%85-%ED%95%A8%EC%88%98
- https://yeoonjae.tistory.com/entry/Java-JAVAJDK-8-%ED%8A%B9%EC%A7%95
- https://developer-talk.tistory.com/804
- https://incheol-jung.gitbook.io/docs/q-and-a/java/jdk-8
- https://bbubbush.tistory.com/23
- https://velog.io/@skyepodium/%EC%9E%90%EB%B0%94-Java-8-%EB%B2%84%EC%A0%84-%ED%8A%B9%EC%A7%95
- https://f-lab.kr/insight/java-8-features-and-impact
'공부 > 자바' 카테고리의 다른 글
[자바/Java] Gradle (2) | 2024.08.27 |
---|---|
[자바/Java] Call by Value vs Call by Reference (0) | 2024.04.05 |