JavaRush /Java Blog /Random-KO /책의 번역. Java의 함수형 프로그래밍. 제1장
timurnav
레벨 21

책의 번역. Java의 함수형 프로그래밍. 제1장

Random-KO 그룹에 게시되었습니다
오류를 찾아내고 번역 품질을 개선할 수 있도록 기꺼이 도와드리겠습니다. 나는 영어 실력을 향상시키기 위해 번역을 하는데, 번역 오류를 읽고 찾아보면 나보다 훨씬 더 나아질 것입니다. 책의 저자는 이 책이 Java 작업 경험이 많다고 가정하고 있는데, 솔직히 나 자신은 특별히 경험이 없지만 책의 내용을 이해했습니다. 이 책은 손가락으로 설명하기 어려운 몇 가지 이론을 다루고 있는데, 위키에 괜찮은 기사가 있으면 링크를 제공하겠지만, 더 나은 이해를 위해서는 직접 구글링해 보시기를 권합니다. 모두에게 행운을 빕니다. :) 내 번역을 수정하고 싶은 분들은 물론, 러시아어로 읽기가 너무 불편하다고 생각하시는 분들을 위해 여기에서 원본 책을 다운로드하실 수 있습니다 . 목차 1장 안녕하세요, 람다 표현식 - 현재 읽고 있는 내용 2장 컬렉션 사용 - 개발 중 3장 문자열, 비교기 및 필터 - 개발 중 4장 람다 표현식을 사용한 개발 - 개발 중 5장 리소스 작업 - 개발 중 6장 게으름 피우기 - 중 개발 7장 리소스 최적화 - 개발 중 8장 람다 표현식을 사용한 레이아웃 - 개발 중 9장 모두 통합 - 개발 중

1장 안녕하세요, 람다 표현식입니다!

우리의 Java 코드는 놀라운 변화를 이룰 준비가 되어 있습니다. 우리가 수행하는 일상 업무는 더욱 단순해지고, 쉬워지고, 표현력이 더욱 풍부해졌습니다. Java를 프로그래밍하는 새로운 방식은 수십 년 동안 다른 언어에서도 사용되었습니다. Java에 대한 이러한 변경을 통해 우리는 오류가 적은 간결하고 우아하며 표현력이 풍부한 코드를 작성할 수 있습니다. 이를 사용하여 더 적은 코드 줄로 쉽게 표준을 적용하고 공통 디자인 패턴을 구현할 수 있습니다. 이 책에서는 우리가 매일 하는 문제의 간단한 예를 사용하여 프로그래밍의 함수형 스타일을 탐구합니다. 이 우아한 스타일과 새로운 소프트웨어 개발 방식에 대해 알아보기 전에 이것이 왜 더 나은지 살펴보겠습니다.
생각을 바꾸세요
명령형 스타일은 Java가 언어가 시작된 이래로 우리에게 제공한 것입니다. 이 스타일은 우리가 언어에서 수행하려는 작업의 모든 단계를 Java에 설명하고 해당 단계가 충실하게 수행되는지 확인하는 것을 제안합니다. 이것은 훌륭하게 작동했지만 여전히 낮은 수준입니다. 코드가 너무 장황해져서 우리는 종종 좀 더 지능적인 언어를 원했습니다. 그런 다음 우리가 원하는 것이 무엇 인지 선언적으로 말할 수 있으며 이를 수행하는 방법을 탐구하지 않아도 됩니다 . 개발자 덕분에 이제 Java가 이를 수행하는 데 도움을 줄 수 있습니다. 이러한 접근 방식 간의 이점과 차이점을 이해하기 위해 몇 가지 예를 살펴보겠습니다.
일반적인 방법
두 가지 패러다임이 어떻게 작동하는지 살펴보기 위해 친숙한 기본 사항부터 시작해 보겠습니다. 이는 명령형 방법을 사용하여 도시 컬렉션에서 Chicago를 검색합니다. 이 책의 목록에는 코드 조각만 표시됩니다. boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); 코드의 명령형 버전은 시끄럽고(이 단어가 그것과 무슨 관련이 있습니까?) 낮은 수준이며 여러 변경 가능한 부분이 있습니다. 먼저 find 라는 불쾌한 부울 플래그를 만든 다음 컬렉션의 각 요소를 반복합니다. 찾고 있는 도시를 찾으면 플래그를 true 로 설정 하고 루프를 중단합니다. 마지막으로 검색 결과를 콘솔에 인쇄합니다.
더 좋은 방법이 있어요
주의 깊은 Java 프로그래머로서 이 코드를 잠깐 살펴보면 다음과 같이 더 표현력이 풍부하고 읽기 쉬운 코드로 바뀔 수 있습니다. 다음은 System.out.println("Found chicago?:" + cities.contains("Chicago")); 선언적 스타일의 예입니다. contain() 메소드는 필요한 것을 직접 얻을 수 있도록 도와줍니다.
실제 변화
이러한 변경으로 인해 코드가 상당히 개선될 것입니다.
  • 변경 가능한 변수로 인해 번거롭지 않음
  • 루프 반복은 후드 아래에 숨겨져 있습니다.
  • 코드 복잡도 감소
  • 코드 명확성 향상, 주의 집중
  • 더 적은 임피던스; 코드는 비즈니스 의도를 밀접하게 추적합니다.
  • 오류 가능성이 적음
  • 더 쉽게 이해하고 지원할 수 있습니다.
단순한 사례를 넘어서
이는 컬렉션에 요소가 있는지 확인하는 선언적 함수의 간단한 예이며 Java에서 오랫동안 사용되었습니다. 이제 파일 구문 분석, 데이터베이스 작업, 웹 서비스 요청, 멀티스레딩 생성 등과 같은 고급 작업을 위해 명령형 코드를 작성할 필요가 없다고 상상해 보십시오. 이제 Java를 사용하면 단순한 작업뿐만 아니라 전체 애플리케이션 전반에 걸쳐 실수하기 어렵게 만드는 간결하고 우아한 코드를 작성할 수 있습니다.
옛날 방식
또 다른 예를 살펴보겠습니다. 우리는 가격이 포함된 컬렉션을 만들고 있으며 모든 할인된 가격의 합계를 계산하기 위해 여러 가지 방법을 시도할 것입니다. 값이 $20를 초과하는 모든 가격을 10% 할인하여 합산하라는 요청을 받았다고 가정해 보겠습니다. 먼저 일반적인 Java 방식으로 이를 수행해 보겠습니다. 이 코드는 우리에게 매우 익숙할 것입니다. 먼저 결과 값을 저장할 변경 가능한 변수 totalOfDiscountedPrices를 만듭니다 . 그런 다음 가격 수집을 반복하여 $20보다 높은 가격을 선택하고 할인된 가격을 얻은 다음 해당 값을 totalOfDiscountedPrices 에 추가합니다 . 마지막에는 할인을 고려한 모든 가격의 합계가 표시됩니다. 아래는 콘솔에 출력되는 내용입니다. final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
할인된 총 가격: 67.5
작동하지만 코드가 지저분해 보입니다. 하지만 그것은 우리 잘못이 아닙니다. 우리는 가능한 것을 사용했습니다. 코드는 매우 낮은 수준입니다. 기본 요소(google it, 흥미로운 내용)에 대한 집착으로 고통받고 있으며 단일 책임 원칙 에 어긋납니다 . 집에서 일하는 우리는 프로그래머가 되고 싶어하는 아이들의 눈에서 그러한 코드를 멀리해야 합니다. 아이들의 연약한 마음을 놀라게 할 수도 있고, "이것이 살아남기 위해 해야 하는 일인가요?"라는 질문에 대비해야 합니다.
더 좋은 방법이 있어요, 또 다른 방법이 있어요
이제 우리는 훨씬 더 잘할 수 있습니다. 우리 코드는 사양 요구 사항과 유사할 수 있습니다. 이를 통해 비즈니스 요구 사항과 이를 구현하는 코드 간의 격차를 줄이고 요구 사항이 잘못 해석될 가능성을 더욱 줄일 수 있습니다. 변수를 생성한 다음 반복적으로 변경하는 대신 다음 목록과 같이 더 높은 수준의 추상화에서 작업해 보겠습니다. final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); 소리내어 읽어 보겠습니다. 가격 필터는 20보다 크고, "가격" 키를 사용하여 매핑("키" "값" 쌍 생성), 할인이 포함된 가격을 추가합니다.
- 번역자 코멘트는 코드를 읽으면서 머릿속에 떠오르는 단어를 의미합니다. .filter(price -> Price.compareTo(BigDecimal.valueOf(20)) > 0)
코드는 우리가 읽은 것과 동일한 논리적 순서로 함께 실행됩니다. 코드는 단축되었지만 Java 8의 새로운 기능을 많이 사용했습니다. 먼저 가격표에서 stream() 메서드를 호출 했습니다 . 이는 나중에 논의할 풍부한 편의 기능 세트를 갖춘 사용자 정의 반복기에 대한 문을 열어줍니다. 가격 목록 의 모든 값을 직접 반복하는 대신 filter()map() 과 같은 몇 가지 특수 메서드를 사용합니다 . Java 및 JDK에서 사용한 메소드와 달리 이러한 메소드는 익명 함수(람다 표현식)를 괄호 안의 매개변수로 사용합니다. 나중에 더 자세히 연구하겠습니다. Reduce() 메소드를 호출하여 map() 메소드 에서 얻은 값(할인된 가격)의 합을 계산합니다 . 루프는 Contains() 메서드를 사용할 때와 같은 방식으로 숨겨집니다 . 그러나 filter()map() 메서드는 훨씬 더 복잡합니다. 가격 목록 의 각 가격에 대해 전달된 람다 함수를 호출하고 이를 새 컬렉션에 저장합니다. 최종 결과를 생성하기 위해 이 컬렉션에 대해 Reduce() 메서드가 호출됩니다. 아래는 콘솔에 출력되는 내용입니다.
할인된 총 가격: 67.5
변경 사항
다음은 일반적인 방법과 관련된 변경 사항입니다.
  • 코드가 보기에도 좋고 복잡하지도 않습니다.
  • 낮은 수준의 작업 없음
  • 로직을 개선하거나 변경하기가 더 쉽습니다.
  • 반복은 메서드 라이브러리에 의해 제어됩니다.
  • 효율적인 지연 루프 평가
  • 필요에 따라 병렬화가 더 쉬워짐
Java가 이러한 개선 사항을 어떻게 제공하는지 나중에 논의하겠습니다.
람다가 구조해줬어요 :)
Lambda는 명령형 프로그래밍의 번거로움에서 우리를 해방시켜주는 기능적 열쇠입니다. Java의 최신 기능을 사용하여 프로그래밍 방식을 변경함으로써 우아하고 간결할 뿐만 아니라 오류가 덜 발생하고 더 효율적이며 더 쉽게 최적화하고 개선하며 멀티스레드를 만드는 코드를 작성할 수 있습니다.
함수형 프로그래밍으로 큰 성공을 거두세요
함수형 프로그래밍 스타일은 신호 대 잡음비가 더 높습니다 . 작성하는 코드 줄은 적지만 각 줄이나 표현식이 더 많은 기능을 수행합니다. 명령형에 비해 코드의 기능적 버전에서는 거의 얻지 못했습니다.
  • 우리는 오류의 원인이 되고 동시에 다른 스레드의 코드를 처리하기 어렵게 만드는 원치 않는 변수 변경이나 재할당을 피했습니다. 명령형 버전에서는 루프 전체에서 totalOfDiscountedPrices 변수에 대해 서로 다른 값을 설정합니다 . 기능 버전에서는 코드의 변수에 명시적인 변경이 없습니다. 변경 사항이 적을수록 코드의 버그가 줄어듭니다.
  • 코드의 기능적 버전은 병렬화가 더 쉽습니다. map() 메소드의 계산이 길더라도 걱정 없이 병렬로 실행할 수 있습니다. 다른 스레드에서 명령형 코드에 액세스하는 경우 totalOfDiscountedPrices 변수를 동시에 변경하는 것에 대해 걱정해야 합니다 . 기능적 버전에서는 모든 변경이 이루어진 후에만 변수에 액세스하므로 코드의 스레드 안전성에 대해 걱정할 필요가 없습니다.
  • 코드가 더 표현력이 좋습니다. 더미 값으로 변수 생성 및 초기화, 가격 목록 반복, 변수에 할인 가격 추가 등 여러 단계로 코드를 실행하는 대신 목록의 map() 메서드에 다른 목록을 반환하도록 요청하기만 하면 됩니다. 할인된 가격 더해 보세요 .
  • 기능적 스타일은 더 간결합니다. 명령형 버전보다 필요한 코드 줄이 더 적습니다. 코드가 간결할수록 작성 및 읽기가 적고 유지 관리가 더 쉬워집니다.
  • 코드의 기능적 버전은 구문을 알면 직관적이고 이해하기 쉽습니다. map() 메소드는 아래 이미지에서 볼 수 있듯이 전달된 함수(할인된 가격을 계산하는)를 컬렉션의 각 요소에 적용하고 결과가 포함된 컬렉션을 생성합니다.

그림 그림 1 - map 메소드는 컬렉션의 각 요소에 전달된 함수를 적용합니다.
람다 표현식의 지원을 통해 Java 프로그래밍의 기능적 스타일의 강력한 기능을 완벽하게 활용할 수 있습니다. 이 스타일을 익히면 변경 사항과 오류를 줄이면서 더욱 표현력이 뛰어나고 간결한 코드를 만들 수 있습니다. 이전에 Java의 주요 장점 중 하나는 객체 지향 패러다임을 지원하는 것이었습니다. 그리고 기능적 스타일은 OOP와 모순되지 않습니다. 명령형 프로그래밍에서 선언형 프로그래밍으로 전환하는 데 있어 진정한 탁월함을 보여줍니다. Java 8을 사용하면 함수형 프로그래밍과 객체 지향 스타일을 매우 효과적으로 결합할 수 있습니다. 객체, 객체의 범위, 상태 및 관계에 OO 스타일을 계속 적용할 수 있습니다. 또한 변화의 행동과 상태, 비즈니스 프로세스, 데이터 처리를 일련의 기능 세트로 모델링할 수 있습니다.
함수형 스타일로 코딩하는 이유는 무엇입니까?
우리는 함수형 프로그래밍 스타일의 전반적인 이점을 살펴보았습니다. 하지만 이 새로운 스타일을 배울 가치가 있을까요? 이것이 언어의 사소한 변화일까요, 아니면 우리의 삶을 바꿀까요? 우리는 시간과 에너지를 낭비하기 전에 이러한 질문에 대한 답을 얻어야 합니다. Java 코드를 작성하는 것은 그리 어렵지 않으며 언어 구문은 간단합니다. 우리는 익숙한 라이브러리와 API에 익숙합니다. 코드를 작성하고 유지 관리하기 위해 실제로 노력해야 하는 것은 개발에 Java를 사용하는 일반적인 엔터프라이즈 애플리케이션입니다. 우리는 동료 프로그래머가 정확한 시간에 데이터베이스에 대한 연결을 닫고, 연결을 유지하거나 필요 이상으로 오랫동안 트랜잭션을 수행하지 않고, 올바른 수준에서 예외를 완전히 포착하고, 잠금을 적절하게 적용하고 해제하는지 확인해야 합니다. ... 이 시트는 아주 오랫동안 계속될 수 있습니다. 위의 각 주장은 단독으로는 중요하지 않지만, 본질적인 구현 복잡성과 결합하면 압도적이고 시간이 많이 걸리며 구현하기 어려워집니다. 이러한 복잡성을 잘 관리할 수 있는 작은 코드 조각으로 캡슐화할 수 있다면 어떨까요? 그러면 우리는 표준을 구현하는 데 지속적으로 에너지를 소비하지 않을 것입니다. 이는 심각한 이점을 제공하므로 기능적인 스타일이 어떻게 도움이 될 수 있는지 살펴보겠습니다.
조가 묻는다
짧은* 코드는 단순히 코드 문자 수가 적다는 것을 의미합니까?
* 우리는 람다 표현식을 사용하여 코드의 기능적 스타일을 특징짓는 concise라는 단어에 대해 이야기하고 있습니다.
이러한 맥락에서 코드는 간결하고 군더더기 없이 간결해야 하며 의도를 보다 효과적으로 전달하기 위해 직접적인 영향을 줄 수 있도록 줄여야 합니다. 이는 광범위한 이점입니다. 코드를 작성하는 것은 재료를 모으는 것과 같습니다. 간결하게 만드는 것은 소스를 추가하는 것과 같습니다. 때로는 그러한 코드를 작성하는 데 더 많은 노력이 필요합니다. 읽을 코드가 적지만 코드가 더 투명해집니다. 코드를 단축할 때 코드를 명확하게 유지하는 것이 중요합니다. 간결한 코드는 디자인 트릭과 유사합니다. 이 코드에서는 탬버린과 함께 춤을 추는 것이 덜 필요합니다. 이는 우리의 아이디어를 신속하게 구현하고 효과가 있으면 계속 진행하고 기대에 부응하지 않으면 포기할 수 있음을 의미합니다.
스테로이드에 대한 반복
반복자를 사용하여 객체 목록을 처리하고 세트 및 맵으로 작업합니다. Java에서 사용하는 반복자는 우리에게 익숙하지만 원시적이지만 단순하지는 않습니다. 여러 줄의 코드를 차지할 뿐만 아니라 작성하기도 매우 어렵습니다. 컬렉션의 모든 요소를 ​​어떻게 반복합니까? for 루프를 사용할 수 있습니다. 컬렉션에서 일부 요소를 어떻게 선택합니까? 동일한 for 루프를 사용하지만 컬렉션의 항목과 비교해야 하는 몇 가지 추가 변경 가능한 변수를 사용합니다. 그러면 특정 값을 선택한 후 최소값, 최대값 또는 일부 평균값과 같은 단일 값에 대한 작업을 어떻게 수행합니까? 다시 순환하고, 다시 새로운 변수가 됩니다. 이것은 숲 때문에 나무를 볼 수 없다는 속담을 연상시킵니다(원문은 반복과 관련된 단어의 유희를 사용하며 "모든 것이 진행되지만 모든 것이 성공하는 것은 아닙니다"를 의미합니다 - 번역가의 메모). jdk는 이제 다양한 명령문에 대한 내부 반복자를 제공합니다. 하나는 루프를 단순화하고, 하나는 필요한 결과 종속성을 바인딩하고, 하나는 출력 값을 필터링하고, 하나는 값을 반환하고, 최소, 최대, 평균 등을 얻기 위한 여러 편의 함수입니다. 또한 이러한 작업의 기능은 매우 쉽게 결합될 수 있으므로 다양한 작업 집합을 결합하여 더 적은 코드로 더 쉽게 비즈니스 논리를 구현할 수 있습니다. 작업이 완료되면 문제에 필요한 순서대로 논리적 솔루션을 생성하므로 코드를 이해하기가 더 쉬울 것입니다. 우리는 이 책의 2장과 뒷부분에서 그러한 코드의 몇 가지 예를 살펴볼 것입니다.
알고리즘 적용
알고리즘은 엔터프라이즈 애플리케이션을 구동합니다. 예를 들어, 권한 확인이 필요한 작업을 제공해야 합니다. 우리는 거래가 신속하게 완료되고 점검이 올바르게 완료되었는지 확인해야 합니다. 이러한 작업은 아래 목록과 같이 매우 일반적인 방법으로 축소되는 경우가 많습니다. Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); 이 접근 방식에는 두 가지 문제가 있습니다. 첫째, 이로 인해 개발 노력이 두 배로 늘어나게 되고, 결과적으로 애플리케이션 유지 비용이 증가하게 됩니다. 둘째, 이 애플리케이션의 코드에서 발생할 수 있는 예외를 놓치기 매우 쉬우며, 이로 인해 트랜잭션 실행 및 검사 통과가 위태로워집니다. 적절한 try-finally 블록을 사용할 수 있지만 누군가 이 코드를 터치할 때마다 코드의 논리가 손상되지 않았는지 다시 확인해야 합니다. 그렇지 않으면 공장을 버리고 전체 코드를 뒤집어버릴 수도 있습니다. 트랜잭션을 수신하는 대신 아래 코드와 같이 잘 관리되는 함수에 처리 코드를 보낼 수 있습니다. runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); 이러한 작은 변화로 인해 엄청난 비용 절감 효과를 얻을 수 있습니다. 상태 확인 및 애플리케이션 확인을 위한 알고리즘에는 새로운 수준의 추상화가 제공되며 runWithinTransaction() 메서드를 사용하여 캡슐화됩니다 . 이 방법에서는 트랜잭션 컨텍스트에서 실행되어야 하는 코드 조각을 배치합니다. 더 이상 해야 할 일을 잊어버리거나 올바른 위치에서 예외를 포착했는지 걱정할 필요가 없습니다. 알고리즘 함수가 이를 처리합니다. 이 문제는 5장에서 더 자세히 논의될 것이다.
알고리즘 확장
알고리즘은 점점 더 자주 사용되고 있지만, 기업용 애플리케이션 개발에서 이를 충분히 활용하려면 이를 확장하는 방법이 필요합니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION