GYUD-TECH

[스터디: 모던 자바] 자바의 진화 본문

스터디

[스터디: 모던 자바] 자바의 진화

GYUD 2024. 3. 25. 17:59

 

오브젝트 스터디를 끝내고 다음 서적으로는 모던 자바 인 액션을 선택했다.

우테코 프리코스를 하면서 stream을 공부하고 사용하였는데 stream의 원리에 대해 깊게 공부하고, 모듈이나 함수형 프로그래밍에 대한 내용을 학습하기 위해서 이 책을 선택하게 되었다.

 

1장에서는 앞으로 자세히 소개할 자바의 진화과정을 간략하게 요약해준다.

내용을 깊이 다루지는 않고, 자바 8, 9, 10에 어떤 기능들이 도입되었는지 정도로 편하게 읽으면 좋을 것 같다.

 


자바 8

자바 8의 대표적인 기능은 Stream이다.

자바 8 부터 Stream API를 지원함으로써 코드를 간결하게 작성할 수 있으며, 멀티 코어 프로세서를 쉽게 활용할 수 있다.

 

조금 어려운 말로 동적 파라미터화를 통해 메서드에 코드를 전달할 수 있게 되었고, 병렬성과 공유 가변 데이터를 쉽게 처리할 수 있게 되었다.

 

 

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

프로그래밍의 핵심은 값을 바꾸는 것이고 이는 메서드를 통해서 실현된다.

 

int 나 객체 인스턴스 값들은 다른 메서드에 파라미터로 전달되어 값이 변경 가능하지만, 메서드 자체를 전달하여 메서드를 유연하게 바꾸는 것은 어렵다.

int 나 객체 인스턴스 같이 값을 쉽게 변경할 수 있는 것을 일급 값이라고 부르고, 메서드나 클래스 같이 메서드의 파라미터로 전달할 수 없는 값을 이급 시민이라고 한다.

 

이를 해결하기 위해서 자바 8 은 메서드를 값으로 취급할 수 있는 기능을 추가했고, 이는 Stream의 토대를 제공해주었다.

List<Apple> 이 주어질 때 이 중 초록색 사과만 선별해주거나, 사과의 무게가 150보다 큰 사과만 선별해주는 코드를 작성해보자

public static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    
    for (Apple apple: inventory) {
        if (GREEN.equals(apple.getColor())) {
            result.add(apple);
        }
        return result;
    }
}


public static List<Apple> filterHeavyApple(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    
    for (Apple apple: inventory) {
        if (apple.getWight() > 150) {
            result.add(apple);
        }
        return result;
    }
}

이렇게 filterGreenApples 와 filterHeavyApple 메서드를 각각 작성하여 상황에 따라 호출해줘야 한다.

 

하지만 위 두 코드는 if 조건절을 제외하고는 모두 동일한 코드이기 때문에 이를 공통적으로 묶고, if 조건절만을 파라미터로 전달받아서 중복코드를 제거하면 좋겠다는 생각이 들었다.

Apple 객체가 파라미터로 들어올 때 식에 따라서 boolean 값을 리턴하는 함수를 파라미터로 전달받아야만 한다.

 

자바 8에서는 Predicate를 사용하여 이를 구현할 수 있다.

 

 

Predicate

Predicate은 단일 인수를 사용하여 일반적으로 조건을 평가하거나, 데이터를 필터링 하는데 사용되는 boolean 값을 반환하는 함수이다.

 

우리는 지금 Apple 이라는 단일 인수를 사용하여 데이터 필터링을 위한 boolean 값을 반환하면 되기 때문에 Predicate의 사용 목적과 정확하게 일치한다.

public static List<Apple> filtersApples(List<Apple> inventory, Predicate<Apple> p) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple: inventory) {
        if (p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}

public interface Predicate<T> {
    boolean test(T t);
}

public static boolean isGreenApple(Apple apple) {
    return GREEN.equals(apple.getColor());
}

public static boolean isHeavyApple(Apple apple) {
    return apple.getWeight() > 150;
}

이렇게 Predicate<Apple>을 filterApples 메서드의 파리미터로 받음으로써 파라미터로 Apple을 가지고, boolean 값을 리턴하는 메서드를 파라미터로 전달할 수 있다.

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

 

Predicate를 활용하여 메서드를 파라미터로 전달함으로써 코드의 중복을 없에고 더 간단한 코드를 작성할 수 있게 되었다.

 

 

람다

만약 isGreenApple과 isHeavyApple은 filterApple 메서드에서만 사용한다면 람다식을 활용하여 코드를 더 간단하게 표현할 수 있다.

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

전달하는 메서드의 길이가 짧고, 반복 사용할 필요가 없다면 람다식을 활용하여 코드를 더 간단하고 직관적으로 표현할 수 있다.

 

뿐만 아니라 람다식을 사용하면 테스트에 용이하다.

test 하고 싶은 식을 람다식으로 바로 주입하여 사용할 수 있기 때문에 다양한 테스트를 쉽게 짤 수 있다는 장점도 가지고 있다.

 

 

Stream 활용하기

사실, Stream API는 filter 라는 메서드를 제공하여 사용자가 filterApples와 같은 메서드를 구현할 필요 없이, 필터링 조건만 입력하면 필터링된 결과를 stream으로 리턴해주는 filter() 메서드를 제공한다.

inventory.stream()
        .filter(Apple::isGreenApple)
        .toList();

처음에 20줄이 넘었던 코드를 단 3줄만으로 표현하였다.

이게 바로 Stream의 첫번째 장점인 동적 파라미터화를 통한 간결한 코드 표현이다.

 

 

간단한 병렬처리 환경 제공

요즘 대부분의 컴퓨터는 멀티코어 CPU를 가지고 있다.

하지만 기본적인 자바 프로그램은 하나의 코어만을 사용하여 프로그램을 실행한다.

 

자바 8 이전에는 멀티코어를 활용하기 위해서는 스레드를 활용해야만 했다.

하지만, 스레드를 통한 병렬처리는 관리가 어렵고, 공유 가변 데이터 접근할 때 에러가 발생하기 쉽다.

 

공유 가변데이터 문제를 해결하기 위해서 synchronized 를 사용해야만 했지만, Stream을 활용하면 복잡한 과정 없이 간단하게 병렬 처리를 할 수 있다.

 

이전의 코드에서 멀티코어로 처리를 원한다면 parallelStream()을 활용하면 병렬성을 쉽게 처리할 수 있다.

inventory.parallelStream()
        .filter(Apple::isGreenApple)
        .toList();

 

Stream Pipeline을 이용해서 입력 부분을 여러 CPU 코어에 쉽게 할당할 수 있기 때문에 스레드라는 복잡한 작업을 사용하지 않고도 병렬성을 얻을 수 있었다.

 

 

디폴트 메서드

자바 8에서는 디폴트 메서드를 도입하여 이미 작성되어 있는 복잡한 인터페이스를 쉽게 변경할 수 있는 기능을 제공한다.

디폴트 메서드는 앞선 오브젝트 스터디의 믹스인 부분에서 자세히 다루었기 때문에 참고하면 좋을 것 같다.

https://gyuwon-tech.tistory.com/48

 

[스터디: 오브젝트] 타입계층의 구현 방법

앞서 서브클래싱과 서브타이핑에 대해 공부하면서 타입에 대해서 공부했다. 앞에서는 타입계층을 구현하는 방식으로 상속을 소개하고, 상속의 목적은 코드의 중복제거가 아닌 타입계층의 구현

gyuwon-tech.tistory.com

 


자바 9

자바 9의 대표적인 변경사항은 모듈의 도입이다.

모듈을 이용하여 대규모 컴포넌트 기반 프로그램에서 시스템의 구조를 블럭 조립하듯이 만들 수 있게 되었다.

 

자바 9 를 공부할 때 자세하게 배우겠지만, 모듈에 대한 기본적인 개념은 아래 글의 모듈 부분을 참고하면 좋을 것 같다.

https://gyuwon-tech.tistory.com/36

 

[스터디: 오브젝트] 유연한 설계

앞선 챕터에서 유연한 설계를 위한 의존성 설계 방법을 공부했다. 이번 챕터에서는 앞선 챕터의 내용을 명시적인 원칙으로 소개하여 앞장의 내용을 정리해 주었다. 이번 챕터를 읽을 떄 앞 장을

gyuwon-tech.tistory.com

 


 

이번 장에서는 앞으로 책에서 어떤 내용을 중점적으로 설명할지에 대해 간단하게 살펴보았다.

 

자바 8의 스트림을 통해서 프로그램을 더 효과적이고 간결하게 구현할 수 있을 뿐만 아니라, 스트림의 인수를 병렬로 처리할 수 있다.

또한 디폴트 메서드를 통해 기존 인터페이스를 구현하는 클래스를 바꾸지 않고도 인터페이스를 변경할 수 있게 되었다.

 

자바 9의 모듈을 이용하여 대규모 컴포넌트 기반 프로그램에서 시스템의 구조를 만들 수 있게 되어 문서화와 모듈 확인 작업이 용이해졌다.

 

이 외에도 함수형 프로그래밍에서 null 처리 방법이나 패턴 매칭 활용 등의 기능이 추가되며 자바는 계속해서 진화했고, 계속해서 개발자들의 선택을 받아 많은 곳에서 사용되고 있다.