[모던 자바 인 액션] 01. 자바 8, 9, 10, 11 무슨 일이 일어나고 있는가?

seony·2023년 2월 24일
0

모던 자바 인 액션

목록 보기
1/6
post-thumbnail

1.1 역사의 흐름은 무엇인가?

자바 8

  • 간결한 코드
  • 멀티코어 프로세서의 쉬운 활용

위 두가지 요구사항을 기반으로 한다.

자바8 추가된 기능
1. 스트림 API
2. 메서드에 코드를 전달하는 기법
3. 인터페이스의 디폴트 메서드

스트림 API 덕분에
➡️ 메서드에 코드를 전달하는 간결 기법(메서드 참조와 람다)과 인터페이스의 디폴트 메서드가 존재

  • 메서드에 코드를 전달하는 기법을 이용하면 간결한 방식으로 동작 파라미터화를 구현할 수 있다.
  • 자바 8 기법은 함수형 프로그래밍에서 위력을 발휘

1.2 왜 아직도 자바는 변화하는가?

  1. 병렬성을 활용하는 코드
  2. 간결한 코드를 구현

할 수 있도록 3 가지 프로그래밍 개념을 다룬다.

① 스트림처리

스트림이란?

  • 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임
  • 이론적으로 프로그램은 입력 스트림에서 데이터를 한 개씩 읽어 들이며 출력 스트림으로 데이터를 한 개씩 기록한다

자바 8에서 java.util.stream 패키지에 스트림 API 추가

  • Stream<T>: T 형식으로 구성된 일련의 항목을 의미

스트림 API의 핵심

자바 8에서는 우리가 하려는 작업을 (DB 쿼리처럼) 고수준으로 추상화해서 일련의 스트림으로 만들어 처리할 수 있음

② 동작 파라미터화로 메서드에 코드 전달하기

  • 코드 일부를 API로 전달하는 기능
  • 예) sort 명령에 파라미터를 추가하고 싶은 사용자도 있음
    • 2013UK0001, 2014US0002 를 여러 방식에 따라 정렬하고 싶을 수 있음
    • 방법 1. Comparator 객체를 만들어서 전달
    • 방법 2. 메서드를 다른 메서드의 인수로 넘겨줌 (자바 8)
      ➡️ 동작 파라미터화

③ 병렬성과 공유 가변 데이터

  • 스트림 메서드로 전달하는 코드는 다른 코드와 동시에 실행하더라도 안전하게 실행될 수 있어야 한다.
  • 공유된 가변 데이터에 접근하지 않아야 한다.
  • 병렬 처리를 위해 synchronized를 이용해서 공유된 가변 데이터 보호 규칙을 만들 수도 있다.
    • But, 훨씬 더 비싼 대가를 치러야 할 수도 있음

1.3 자바 함수

  • 프로그래밍 언어에서 함수 ⩬ 정적 메서드
  • 자바 8에서는 함수를 새로운 값의 형식으로 추가했다.
    • int, double
    • 객체 (instance): "abc", new Integer(111), new HashMap<Integer, String> (100)

일급 값(시민)

  • 값을 바꿀 수 있음
  • int, double, 객체 ...

이급 시민

  • 전달할 수 없는 구조체
  • 메서드, 클래스 등이 이에 해당

➡️ 런 타임에 메서드를 전달할 수 있다면, 즉 메서드를 일급 시민으로 만들면 프로그래밍에 유용하게 활용될 수 있다.

메서드와 람다를 일급 시민으로

1. 메서드 참조

<자바 8 이전>

	File[] hiddenFiled = new File(".").listFiles(new Filter() {
    	public boolean accept(File file) {
        	return file.isHidden();
        }
    });

File 클래스에는 이미 isHidden이라는 메서드가 있는데 굳이 FileFilterisHidden을 복잡하게 감싼 다음에 FileFilter를 인스턴스화해야 할까?

<자바 8 이후>

	File[] hiddenFiles = new File.(".").listFiles(File::isHidden);

자바 8의 메서드 참조 ::를 이용해서 listFiles에 직접 전달할 수 있다.

  • 자바 8에서는 더 이상 메서드가 이급값이 아닌 일급값
  • 기존에 객체 참조(new로 객체 참조를 생성함)를 이용해서 객체를 주고 받았던 것처럼 File::isHiden을 이용해서 메서드 참조를 만들어 전달할 수 있다.

2. 람다

  • 자바 8에서는 메서드를 일급값으로 취급할 뿐만 아니라 람다(or 익명 함수)를 포함하여 함수도 값으로 취급할 수 있다.
    • (int x) -> x + 1
      • x라는 인수로 호출하면 x + 1을 반환
  • 이용할 수 있는 편리한 클래스나 메서드가 없을 때 새로운 람다 문법을 이용하면 더 간결하게 코드를 구현할 수 있다.

코드 넘겨주기 : 예제

	public static boolean isHeavyApple(Apple apple) {
    	return apple.getWeight() > 150;
    }
    
    public static boolean isGreenApple(Apple apple) {
    	return GREEN.equals(apple.getColor());
    }
    
    // java.util.function 에서 import 함
    public interface Predicate<T> {
    	boolean test(T t);
    }
    
    static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) {
    	List<Apple> result = new ArrayList<>();
        for (Apple apple: inventory) {
        	if (p.test(apple)) {
            	result.add(apple);
            }
        }
        return result;
   }

다음처럼 메서드 호출할 수 있다.

	filterApple(inventory, Apple::isGreenApple);
    filterApple(inventory, Apple::isHeavyApple);

프레디케이트(predicate)란 무엇인가?
인수를 받아 true나 false 값을 반환하는 함수를 프레디케이트라고 한다.
자바 8에서도 Function<Apple, Boolean> 같이 코드를 구현할 수 있지만 Predicate<Apple>을 사용하는 것이 더 표준적인 방식이다.

메서드 전달에서 람다로

  • isHeavyApple, isGreenApple처럼 한두 번만 사용할 메서드를 매번 정의하는 것은 귀찮다.
  • 자바 8에서는 익명함수 또는 람다라는 새로운 개념을 이용해서 코드를 구현할 수 있다.

filterApples(inventory, (Apple a) -> GREEN.equals(a.getColor()));
filterApples(inventory, (Apple a) -> a.getWeight() > 150);
filterApples(inventory, (Apple a) -> a.getWeight() < 80 || RED.equals(a.getColor()) );

  • But, 람다가 몇 줄 이상으로 길어진다면(즉, 조금 복잡한 동작을 수행하는 상황) 메서드를 정의하고 메서드 참조를 활용 하는 것이 바람직하다.

⭐️ 1.4 스트림

  • 거의 모든 자바 애플리케이션은 컬렉션을 만들고 활용한다.

<컬렉션 사용>

	Map<Currency, List<Tranaction>> transactionByCurrencies = new HashMap<>();

	for (Transaction transaction : transactions) {
    	if (transaction.getPrice() > 1000) { // 고가의 트랜잭션 필터링
        	Currency currency = transaction.getCurrency(); // 통화 추출
            List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
            // 현재 통화의 그룹화된 맵에 항목이 없으면 새로 만듦
            if (transactionsForCurrency == null) {
            	transactionsForCurrency = new ArraysList<>();
                transactionsByCurrencies.put(currency, transactionsForCurrency);
            }
            transactionsForCurrency.add(transaction); // 현재 탐색된 트랜잭션을 같은 통화의 트랜잭션 리스트에 추가
        }
   }

<스트림 사용>

import static java.util.stream.Collectors.groupingBy;
Map<Currency, List<Transaction>> transactionsByCurrencies 
	= transactions.stream()
			      .filter((Transaction t) -> t.getPrice() > 1000) // 고가의 트랜잭션 필터링
                  .collect(groupingBy(Transaction::getCurrency)); // 통화로 그룹화함
  • 외부 반복(for-each 루프)
    • 각 요소를 반복하면서 작업을 수행
  • 내부 반복
    • 스트림 API에서는 라이브러리 내부에서 모든 데이터가 처리된다.

멀티스레딩은 어렵다

  • 멀티스레딩 모델은 순차적인 모델보다 다루기가 어렵다.

  • 자바 8은 스트림 API(java.util.stream)로 아래 2가지를 해결했다.

    1. 컬렉션을 처리하면서 발생하는 모호함과 반복적인 코드 문제
    2. 멀티코어 활용 어려움
  • 컬렉션

    • 어떻게 데이터를 저장하고 접근할지에 중점
  • 스트림

    • 데이터에 어떤 계산을 할 것인지 묘사하는 것에 중점
    • 스트림 내의 요소를 쉽게 병렬로 처리할 수 있는 환경 제공

컬렉션을 필터링할 수 있는 가장 빠른 방법
1. 컬렉션을 스트림으로 바꾸고
2. 병렬로 처리한 다음
3. 리스트로 다시 복원

스트림과 람다 표현식을 이용하면 병렬성을 공짜로 얻을 수 있으며 리스트에서 무거운 사과를 순차적으로 또는 병렬로 필터링할 수 있다.

<순차 처리 방식의 코드>

import static java.util.stream.Collectors.toList;
List<Apple> heavyApples = inventory.stream().filter((Apple a) -> a.getWeight() > 150)
											.collect(toList());

<병렬 처리 방식의 코드>

import static java.util.stream.Collectors.toList;
List<Apple> heavyApples = inventory.parallel.Stream().filter((Apple a) -> a.getWeight() > 150)
													 .collect(toList());

1.5 디폴트 메서드와 자바 모듈

  • 자바 8에서는 인터페이스를 쉽게 바꿀 수 있도록 디폴트 메서드를 지원한다.

    • But, 프로그래머가 직접 디폴트 메서드를 구현하는 상황은 흔치 않다.
    • 특정 프로그램을 구현하는데 도움을 주는 기능이 아니라 미래에 프로그램이 쉽게 변화할 수 있는 환경을 제공하는 기능
  • 인터페이스에 새로운 메서드를 추가하면 → 인터페이스를 구현하는 모든 클래스는 새로 추가된 메서드를 구현해야 한다....

어떻게 기존의 구현을 고치지 않고도 이미 공개된 인터페이스를 변경할 수 있을가?

  • 구현 클래스에서 구현하지 않아도 되는 메서드를 인터페이스에 구할 수 있는 기능 제공
    • default 라는 새로운 키워드를 지원

자바 8에서는 List에 직접 sort 메서드를 호출할 수 있다.

	default void sort(Comparator<? super E> c) {
    	Collections.sort(this, c);
    }

하나의 클래스에서 여러 인터페이스를 구현할 수 있음
➡️ 여러 인터페이스에 다중 디폴트 메서드가 존재할 수 있다는 것은 결국 다중 상속이 혀용되는 의미일까??
➡️ 어느 정도는 그렇다라고 말할 수 있다.


1.6 함수형 프로그래밍에서 가져온 다른 유용한 아이디어

자바에 포함된 함수형 프로그래밍의 핵심적인 두 아이디어
1. 메서드와 람다를 일급값으로 사용하는 것
2. 가변 공유 상태가 없는 병렬 실행을 이용해서 효율적이고 안전하게 함수나 메서드를 호출할 수 있다는 것

  • 자바 8에서는 NullPointer 예외를 피할 수 있도록 도와주는 Optional<T> 클래스를 제공한다.
  • Optional<T>는 값을 갖거나 갖지 않을 수 있는 컨테이너 객체

1.7 마치며

  • 언어 생태계의 모든 언어는 변화해서 살아남거나 그대로 머물면서 사라지게 된다.
  • 함수는 일급값이다. 메서드를 어떻게 함수형값으로 넘겨주는지, 익명 함수(람다)를 어떻게 구현하는지 기억하자
  • 자바 8의 스트림 개념 중 일부는 컬렉션에서 가져온 것이다.
    • 스트림과 컬렉션을 적절하게 활용하면 스트림의 인수를 병렬로 처리할 수 있으며 더 가독성이 좋은 코드를 구현할 수 있다.

0개의 댓글