[Java] 자바버전에 대하여 (1) - Java 8

건미누·2023년 12월 10일
0

자바

목록 보기
2/2
post-thumbnail

SSAFY의 후반기 교육을 통해 총 3번의 프로젝트를 진행하며 자바 11, 17을 사용했다.
8부터는 Stream과 람다가, 11은 .isBlank(), .strip()과 같은 내용과 GC defalut가 변경되었다는 것은 알고 있었지만 프로젝트 간 그 차이를 느껴보지 못했다.
(8은 프로젝트에서 사용해보지 않았고, 17은 단순히 제가 변경점을 안써서 그런걸 수도...)

그래서 차이를 명확하게 이해하고 자바에 대해 더 깊이 알고자 구글링과 서적 "모던 자비 인 액션"을 읽고 내용을 정리한다.

Java 8

자바 8의 주요 변경사항은 다음과 같았다.
<핵심>
1. Stream(스트림)
2. Lambda expression (람다 표현식)
3. Functional interface(함수형 인터페이스)

<유용>
4. Default method(디폴트 메서드)
5. Optional(옵셔널)
6. 새롭게 추가된 날짜 API
7. CompletableFuture(컴플리터블 퓨처)
8. JVM의 변화

(9에는 새로운 Module 시스템, Flow API를 통한 리액티브 프로그래밍 지원 등이 추가되었다고 합니다.)


Stream(스트림 API)

프로젝트를 수행하며 Stream API를 굉장히 많이 썼다. for문을 돌리는 <외부반복>보다 Stream을 활용한 <내부반복>이 병렬로 처리해서 더 좋다고 들었기 때문이다.
그러면 구체적으로 Stream을 쓰면 어떤 점이 더 좋은 걸까?

1.가독성

Map<Currency, List<Transaction>> transactionByCurrency = new HashMap<>();
for (Transaction transaction : transactions) {
	if (transaction.gerPrice() > 1000 ) {
    	Currency currency = transaction.getCurrency();
        List<Transaction> transactionsForCurrency =
        	transacionsByCurrency.get(currency);
        if (transactionsForCurrency == null) {
        	transactionsForCurrency = new ArrayList<>();
            transactionsByCurrency.put(currency,
            						transactionForCurrency);
        }
        transactionsForCurrency.add(transaction);
    }
}

transaction을 foreach문으로 불러와서 transaction의 Price가 1000 초과일 경우 Currency를 가져오고 TransactionByCurrency에 해당 Currency가 존재하면 List에 추가시키고, 없다면 새로 ArrayList를 생성하여 currency를 만들어 transactionByCurrency에 추가 후, transactionsForCurrency에도 추가한다.

솔직히 읽는데 딱히 어려운 코드는 아니다. 하지만 Stream을 사용하면 훨씬 간결하고 직관적으로 정리할 수 있다.

import static java.util.stream.Collectors.groupingBy;
List<Transaction> transactions = new ArrayList<>();
	Map<Currency, List<Transaction>> transactionByCurrency =
    	transactions.stream()
        	.filter(transaction -> transaction.price > 1000)
            .collect(groupingBy(Transactions::getCurrency));

중간연산 filter를 활용해 transaction.price가 1000 이상인 내용만 Currency를 기준으로 그룹화하였다. 2개의 if문과 중간마다 생성해주던 List가 사라짐으로써 간결해지고 filter, collect 메서드를 통해 가독성이 매우 좋아졌다.

2. 멀티 코어 CPU

기존 자바는 CPU가 2개 이상일지라도 단 하나만 사용하며 나머지 CPU를 낭비시켰다고 한다. 스레드 API를 통해 멀티스레딩을 구현해서 병렬성을 이용할 수는 있으나 공유자원에서 적절하게 제어가 되지 않으면 원치 않는 방식으로 이루어질 수 있다.

Java는 이를 Stream API를 통해 해결하였다. 예시로 2개의 CPU가 있다면 Stream을 통해 어떤식으로 Filter결과를 가져오는지 확인해보자.

과정은 다음과 같다.
(1).포크 - 작업을 분리
(2).필터 - 각 CPU가 각 작업을 처리
(3).병합 - 결과를 반환

그림을 통해 알 수 있듯 분리한 작업을 병렬적으로 처리하기 때문에 처리속도가 더 빠를 수 있다.

❗프로젝트에서 나는 Stream을 사용하면 무조건적으로 병렬처리가 되는 줄로 알았는데 그렇지 않았다. Stream은 순차처리방식, parallelStream을 사용해야 병렬처리가 되는 것이었다.

병렬처리 코드

Map<Currency, List<Transactions>> transactionByCurrency =
                transactions.parallelStream()
                        .filter(t -> t.price > 1000)
                        .collect(groupingBy(Transactions::getCurrency));

그렇다면 무조건적으로 빠른가?

처음에는 stream처리를 모두 parallelStream으로 바꿔야하나? 라고 생각했으나 병렬으로 처리가 된다면 당연히 빠르기야 하겠지만 분리하고 병합하는 과정과 컨텍스트 스위칭을 하며 오버헤드가 발생할 수 있으니까 무조건 빠를것 같지는 않았다.

Stream과 parallelStream의 차이

직접 테스트해야하나 했는데 감사하게도 이미 테스트를 해주신 분이 계셨다.
결과를 보니 groupingBy의 경우 성능이 개선되었지만 홀짝의 경우에는 오히려 성능이 떨어지는 것을 알 수 있었다. 어떨 때 쓰면 좋은지는 위의 글을 읽어보면 된다.


lamda (람다 표현식)

자바 8에서 들어온 람다 표현식은 코드를 더 간결하게 만들어 준다.
코드는 간결하고 명확할 수록 좋다. 기존의 자바 코드들은 클래스를 구현해서 인스턴스화하는 과정이 조금 거추장스러웠지만 람다는 이러한 거추장스러움을 해결해준다.

List<Apple> result = filterApples(inventory, (Apple apple) -> 
					RED.equals(apple.getColor()));

➡️ 단 2줄의 코드만으로 가지고 있는 사과 중에서 빨간 사과만 리스트에 담는다.

람다는 다음과 같은 특징을 가진다.

  • 익명 : 메서드의 이름이 없으므로 익명이라고 표현한다.
  • 함수 : 메서드처럼 특정 클래스에 종속되지 않기에 함수라고 한다.
  • 전달 : 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다.
  • 간결성 : 자질 구레한 코드를 구현하지 않아도 된다.

즉, 람다는 이전의 자바는 할 수 없었던 것이 아니라 이전에 했던 것을 더욱 간결하게 만드는 문법이다. 람다는 총 3부분으로 이루어진다.

#1                   #2 #3
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

#1. 람다 파라미터 : compareTo 메서드의 파라미터
#2. 화살표 : 파라미터 리스트와 바디를 구분
#3. 람다 바디 : 람다의 반환값에 해당하는 표현식

책에서는 유효 람다 표현식으로 총 5가지의 예제가 소개되었다.

#1. String형식의 파라미터 하나를 가지고 int를 반환. return이 함축
(String s) -> s.length()

#2. Apple형식의 파라미터 하나를 가지고, boolean을 반환
(Apple a) -> a.getWeight() > 150

#3. int형식의 파라미터 두 개를 가지고 리턴이 없는 void형태, 여러행의 문장 포함 가능
(int x, int y) -> {
					System.out.println("Result");
                    System.out.println(x + y);
                    }
                   
#4. 파라미터 없음. 입력 값 반환
() -> 42

#5. 파라미터 2, int(두 객체의 비교 결과)를 반환
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
profile
꺾여도 해야지

0개의 댓글