[자바 8] (1) 함수형 프로그래밍 입문

사고방식의 변화

시카고가 도시 모음에 존재하는지 여부를 찾는 예를 살펴보겠습니다.

익숙한 방법

boolean found = false ;
for ( String city : cities ) {
    if ( city . equals ( "Chicago" )) {
        found = true ;
        break ;
    }
}
System . out . println ( "Found chicago?:" + found );
复制代码

위의 코드는 이 문제에 직면했을 때 대부분의 개발자가 가장 먼저 하는 반응입니다. 명령형 코드를 사용하여 필요한 논리를 완성하지만 코드의 양이 많기 때문에 더 복잡해 보입니다.

\

경험이 많은 개발자는 기존 API를 사용하여 코드 스타일을 명령형에서 선언형(선언적 스타일)으로 변경하기 때문에 코드를 더 간결하고 읽기 쉽게 만듭니다.

System . out . println ( "Found chicago?:" + cities . contains ( "Chicago" ));
复制代码

간단한 코드 줄은 프로그램의 의도를 표시할 수 있습니다.

또 다른 예

20위안 이상의 상품에 10% 할인을 적용하여 최종적으로 이 상품의 할인 가격을 구했다고 가정합니다. 다음 솔루션이 즉시 떠오릅니다.

final List < BigDecimal > 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 ); 
复制代码

이런 종류의 코드를 정기적으로 작성하다 보면 지루함이나 불안감이 생길지 모르겠습니다. 이 코드는 너무 일상적이어서 약간 지루하고 작동하는 동안 항상 그렇게 우아하지 않은 것처럼 느껴집니다.

보다 우아한 방법은 선언적 코드를 사용하는 것입니다.

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 );
复制代码

임시변수를 선언하지 않고, if판단을 하지 않고 로직을 한번에 완료합니다. 또한 더 읽기 쉽습니다. 먼저 조건에 따라 가격 세트를 필터링한 다음(필터), 필터링된 세트에 대해 할인 처리를 수행하고(지도), 마지막으로 할인된 가격을 추가합니다(감소).

Java 8의 새로운 기능, Lambda 표현식 및 stream(), reduce() 등과 같은 관련 메서드를 활용하여 코드를 기능적 스타일로 변환합니다. 람다 표현식 및 관련 내용은 나중에 자세히 소개하겠습니다.

함수형 코드 사용의 이점

  • 축소 변수 선언(불변 변수)
  • 병렬 처리(Parallelism)를 더 잘 사용할 수 있습니다.
  • 코드가 더 간결하고 읽기 쉽습니다.

물론 Java 8에 함수형 프로그래밍 스타일을 도입한 것은 확립된 객체 지향 프로그래밍 스타일을 전복시키려는 의도가 아니었습니다. 오히려 조화롭게 공존하고 서로의 장점을 배우도록 하세요. 예를 들어 개체 지향을 사용하여 엔터티를 모델링하고 엔터티 간의 관계를 표현하고 기능 코딩을 사용하여 엔터티, 비즈니스 프로세스 및 데이터 처리의 다양한 상태 변경을 구현합니다.

함수형 프로그래밍의 핵심

  • 声明式的代码风格(Declarative Style) : 这需要提高代码的抽象层次,比如在前面的例子中,将从集合中搜索一个元素的操作封装到contains方法中。

  • 更多的不变性(Promote Immutability) : 能不声明变量就不要声明,需要变量时尽量使用final来修饰。因为变量越多,就意味着程序越难以并行。实现了不变性的方法意味着它不再有副作用,不会因为调用而改变程序的状态。

  • 使用表达式来代替语句(Prefer Expression to Statement) : 使用语句也就意味着不变性的破坏和程序状态的改变,比如赋值语句的使用。

  • 使用高阶函数(High-Order Function) : 在Java 8以前,重用是建立在对象和类型系统之上。而Java 8中则将重用的概念更进一步,使用函数也能够实现代码的重用。所谓高阶函数,不要被其名字唬住了,实际上很简单:

    • 将函数作为参数传入到另外一个函数中
    • 函数的返回值可以是函数类型
    • 在函数中创建另一个函数

在前文中,已经见过函数作为参数传入到另一个函数的例子了:

prices . stream ()
    . filter ( price -> price . compareTo ( BigDecimal . valueOf ( 20 )) > 0 )
    . map ( price -> price . multiply ( BigDecimal . valueOf ( 0.9 )))
    . reduce ( BigDecimal . ZERO , BigDecimal: : add );
复制代码

price -> price.multiply(BigDecimal.valueOf(0.9))实际上就是一个函数。只不过它的写法使用了Lambda表达式,当代码被执行时,该表达式会被转换为一个函数。

函数式接口(Functional Interface)

为了在Java中引入函数式编程,Java 8中引入了函数式接口这一概念。

函数式接口就是仅声明了一个方法的接口,比如我们熟悉的Runnable,Callable,Comparable等都可以作为函数式接口。当然,在Java 8中,新添加了一类函数式接口,如Function,Predicate,Consumer,Supplier等。

在函数式接口中,可以声明0个或者多个default方法,这些方法在接口内就已经被实现了。因此,接口的default方法也是Java 8中引入的一个新概念。

函数式接口使用@FunctionalInterface注解进行标注。虽然这个注解的使用不是强制性的,但是使用它的好处是让此接口的目的更加明确,同时编译器也会对代码进行检查,来确保被该注解标注的接口的使用没有语法错误。

如果一个方法接受一个函数式接口作为参数,那么我们可以传入以下类型作为参数:

  • 匿名内部类(Anonymous Inner Class)
  • Lambda表达式
  • 方法或者构造器的引用(Method or Constructor Reference)

第一种方式是Java的以前版本中经常使用的方式,在Java 8中不再被推荐。 第二种方式中,Lambda表达式会被编译器转换成相应函数式接口的一个实例。 第三种方式会在后文中详细介绍。

Ich denke du magst

Origin juejin.im/post/7143049506160902151
Empfohlen
Rangfolge