왜 주제를 자바8로 정하였는가?
자바 역사를 통틀어 가장 큰 변화가 자바8에서 일어났다.
자바 9에서도 중요한 변화가 있었지만, 8만큼 획기적이거나 생산성이 변한 것은 아니다. 자바 10에서는 형 추론과 관련해 약간의 변화만 일어났다.
자바8의 획기적인 변화란?
자바 8은 프로그램을 더 효과적이고 간결하게 구현할 수 있는 새로운 개념과 기능을 제공한다.
- 자바 8의 기본적인 변화 관점 1. 간결한 코드 2. 멀티코어 프로세서 사용
- 멀티코어 CPU 대중화로, 기존 하나의 코어만 사용했던 JAVA에서 나머지 코어 활용을 위해 많은 방법이 나왔다.1버전 쓰레드와 락, 5버전 쓰레드 풀, 병렬실행 컬렉션, 7버전 병렬에 도움이 되는 포크/조인 프레임워크
- 8버전 스트림 API (병렬 실행을 위한 가장 쉽고 단순한 방식)
멀티코어 병렬 연산을 지원하는 스트림 api덕분에
코드를 전달하는 기법(메서드참조, 람다), 인터페이스의 디폴트 메소드가 존재할 수 있다.
ex ) 간결한 코드 변화 예시
//Collections.sort를 할 때,
Collections.sort(inventory, new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
});
방법 1.메서드참조
inventory.sort(comparing(Apple::getWeight));
방법2.람다식(익명메소드 전달)
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compreTo(a2.getWeight));
ex ) 스트림을 통해 병렬처리하는 예시
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
순차처리
// 질의 1: 2011년부터 발생한 모든 거래를 찾아 값으로 정렬(작은 값에서 큰 값).
List<Transaction> tr2011 = transactions.**stream()**
.filter(transaction -> transaction.getYear() == 2011)
.sorted(comparing(Transaction::getValue))
.collect(toList());
System.out.println(tr2011);
병렬처리
List<Transaction> tr2011 = transactions.**parallelStream()**
.filter(transaction -> transaction.getYear() == 2011)
.sorted(comparing(Transaction::getValue))
.collect(toList());
System.out.println(tr2011);
자바 8 설계의 밑바탕을 이루는 세 가지 프로그래밍 개념
1. 스트림처리
스트림이란 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임이다. 이론적으로 프로그램은 입력 스트림에서 데이터를 한 개씩 읽어들이며 마찬가지로 출력 스트림으로 데이터를 한 개씩 기록한다. 즉, 어떤 프로그램의 출력 스트림은 다른 프로그램의 입력 스트림이 될 수 있다. (파이프라인)
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3
--파일의 단어를 소문자로 바꾼 다음에 사전순으로 단어를 정렬했을 때,
-- 가장 마지막에 위치한 세 단어를 출력하는 프로그램
유닉스에서는 여러 명령(cat, tr, sort, tail) 을 병렬로 실행한다. cat이나 tr이 완료되기 전에 sort가 행을 처리하기 시작할 수 있다.
- 기존 자바는 한번에 한 항목을 처리했지만, 이제 파이프라인을 구성했던 것 처럼 스트림API가 제공하는 많은 메서드로 병렬처리를 한다.
- 스트림 파이프라인을 이용하여 여러 CPU 코어에 쉽게 할당할 수 있따는 부가적인 이득도 있다.(스레드라는 복잡한 작업 사용x, 공자로 병렬성을 얻을 수 있다)
- 자바 8에서는 java.util.stream 패키지에 스트림 API가 추가되었다. Stream는 T 형식으로 구성된 일련의 항목을 의미한다.
2. 동작 파라미터화로 메서드에 코드 전달하기(메서드 참조, 람다)
동작파라미터화 =
메서드가 다양한 동작을 받아서 내부적으로 다양한 동작을 수행할 수 있게 해주는 것.
- 기존 자바 8에서는 정해진 동작(method)에 인자(숫자, 문자 등) 만 전달할 수 있었다. (객체를 생성하여 전달할 수 있지만 너무 복잡하고 기존 동작을 단순하게 재활용)
- 자바 8에서는 메서드(우리 코드) 를 다른 메서드의 인수로 넘겨주는 기능을 제공한다.
왜 동작 파라미터화가 중요한가?
스트림 API는 연산의 동작을 파라미터화할 수 있는 코드를 전달한다는 사상에 기초하기 때문이다. (위의 Transaction 예시처럼)
사과농장의 주인에게 사과 분류에 대한 여러 요청이 들어온 경우
**//기존의 방법**
**1.녹색 사과 필터링**
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;
}
단단히 응집되어 빨강을 비교하려면 같은 메서드를 하나 추가해야한다.
**2.색을 파라미터화**
public static List<Apple> filterGreenApples(List<Apple> inventory. **Color color**) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (**color.equals(apple.getColor())**) {
result.add(apple);
}
}
return result;
}
**3.가능한 모든 속성으로 필터링**
public static List<Apple> filterApplesByWeight(List<Apple> inventory, **int weight**,
**Color color)** {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (**apple.getWeight() > weight ||
color**.**equals(apple.getColor())** {
result.add(apple);
}
}
return result;
}
**//자바8에서의 방법**
**4.추상적 조건으로 필터링**
public static List<Apple> filterApplesByWeight(List<Apple> inventory, **ApplePredicate p**) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (**p.test**) {
result.add(apple);
}
}
return result;
}
//동작 파라미터화시
interface ApplePredicate {
boolean test(Apple a);
}
static class AppleWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
static class AppleColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getColor() == Color.GREEN;
}
}
//사용법
List<Apple> greenApples = filter(inventory, new AppleColorPredicate());
List<Apple> heavyApples = filter(inventory, new AppleWeightPredicate());
**//간소화 단계
5.익명클래스 사용**
List<Apple> redApples2 = filter(inventory, **new ApplePredicate() {
@Override
public boolean test(Apple a) {
return a.getColor() == Color.RED;
}
});**
System.out.println(redApples2);
}
**6.람다 표현식 사용**
List<Apple> redApples2 = filter(inventory, **(Apple apple) -> RED.equals(apple.getColor()));**
**7.리스트 형식으로 추상화**
//자바에서 제공
public interface Predicate<T> {
boolean test(T t);
}
//형식 파라미터 T
public static <T> List<T> filter(List<T> list, Predicate<T> p){
List<T> result = new ArrayList<>();
for(T e : list){
if(p.test(e)){
result.add(e);
}
}
return result;
}
//이제 오렌지, 바나나, 정수, 문자열 등의 리스트에 필터 메서드를 사용할 수 있다.
List<Apple> redApples = filter(inventory, (Apple apple) -> RED.equals(apple.getColor());
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
한결 간결하고 유연한 코드가 되었다.
동작 파라미터화 패턴은 동작을 한 조각의 코드로 캡슐화한 다음에 메서드로 전달해서
메서드의 동작을 파라미터화한다. (ex 사과의 다양한 프레디케이트).
** 자바API의 많은 메서드를 다양한 동작으로 파라미터화할 수 있다. 그리고 많은 익명 클래스와
자주사용하곤 한다. (ex Runnable, GUI 이벤트 처리하기)**
ex) GUI 이벤트 처리하기
Button button = new Button("Send");
button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event){
label.setText("Sent!!");
}
});
즉, EventHandler는 setOnAction 메서드의 동작을 파라미터화한다. 람다식으로 표현하면
button.setOnAction((ActionEvent event) -> label.setTExt("Sent!!")); 이런식
3.병렬성과 공유 가변 데이터
자바 8은 '병렬성을 공짜로 얻을 수 있다'라는 말에서 시작된다. 병렬성을 얻은 대신, 스트림 메서드로 전달하는 코드의 동작 방식을 조금 바꿔야 한다. 스트림 메서드로 전달하는 코드는 다른 코드와 동시에 실행하더라도 안전하게 실행될 수 있어야 하기 때문이다.
안전하게 실행할 수 있는 코드는 '공유된 가변 데이터'에 접근하지 않아야 한다. 이를 위해 기존 자바는 synchronized를 이용해 사용했지만, 높은 난이도, 시스템 성능에 악영향 등의 단점이 있었다. 스트림 API는 수학적인 함수처럼 함수가 정해진 기능만 수행하여 다른 부작용은 일으키지 않는다.
공유되지 않는 가변데이터, 메서드와 함수 코드를 다른 메서드로 전달하는 두 가지 기능은 함수형 프로그래밍으로의 변화에서 핵심적인 사항이다.
함수
프로그래밍 언어에서 함수(function)은 보통 메서드와 같은 의미로 사용된다.
자바에서는 이에 더해 수학적인 함수처럼 사용되며 부작용을 일으키지 않는 함수를 의미한다.
자바 8에서는 함수를 새로운 값의 형식으로 추가했다. 멀티코어 병렬 프로그래밍을 활용할 수 있는 Stream과 연계될 수 있도록 만들었다.
함수를 값처럼 사용하다
자바에서 조작할 수 있는 값은 첫 번째로 42(int), 3.14(dobule) 같은 기본값이 있다. 두 번째로 객체 클래스의 인스턴스(엄밀히 말하자면 객체의 참조 또는 라이브러리 함수를 이용해서 객체의 값을 얻는 방식) ex) "abc"(String 형식), new HashMap<Integer, String>이 있다.
프로그래밍의 핵심은 값을 바꾸는 것이다. 값이 변하는 것들은 '일급값' 이라고 한다.
변하지 않는 구조체 (메서드, 클래스) 등은 '이급 시민' 이라고 부른다. 이런 이급 시민인 메서드를 런타임시에 전달할 수 있다면 프로그래밍에 유용하게 사용할 수 있다.
'개발언어 > JAVA' 카테고리의 다른 글
[디자인 패턴] 프록시 패턴(Proxy Pattern), 데코레이터 패턴(Decorator Pattern) (0) | 2022.11.05 |
---|---|
[디자인 패턴] 템플릿 메서드 패턴, 전략 패턴 (0) | 2022.11.03 |
동시성 문제 해결(ThreadLocal) (0) | 2022.08.31 |
[모던자바인액션] 2. 스트림 정리 (0) | 2022.04.05 |
[모던자바인액션] 1. 람다 정리 (0) | 2022.04.05 |