[Java 8] (1) Introduction to functional programming

Change in way of thinking

Take the example of finding whether Chicago exists in a collection of cities:

accustomed way

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

The above code is the first reaction of most developers when faced with this problem. It uses Imperative Style code to complete the required logic, but it looks more complicated because of the large amount of code.

\

More experienced developers will use the existing API to make the code more concise and more readable, because it changes the code style from imperative to declarative (Declarative Style).

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

A simple line of code can display the intent of the program.

another example

Suppose a 10% discount is applied to commodities over 20 yuan, and finally the discounted prices of these commodities are obtained. The following solutions immediately come to mind:

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

When you regularly write this type of code, I don't know if there will be a feeling of boredom or anxiety. Because this code is so commonplace that it's a bit tedious, and while it works, it always feels like it's not that elegant.

A more elegant way is to use declarative code:

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

No temporary variables are declared, no if judgments are made, and the logic is completed in one go. It is also more readable: first filter the price set according to the conditions (filter), then perform discount processing on the filtered set (map), and finally add the discounted prices (reduce).

It takes advantage of the new features of Java 8, Lambda expressions and related methods such as stream(), reduce(), etc. to transform the code into a functional style. Lambda expressions and their related content will be introduced in detail later.

The benefits of using functional code

  • Reduced variable declarations (Immutable Variable)
  • Can make better use of parallelism (Parallelism)
  • The code is more concise and readable

Of course, the introduction of the functional programming style in Java 8 was not intended to subvert the entrenched object-oriented programming style. Rather, let them coexist harmoniously and learn from each other's strengths. For example, use object-oriented to model entities and express the relationship between entities; and use functional coding to implement various state changes in entities, business processes and data processing.

The core of functional programming

  • 声明式的代码风格(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表达式会被编译器转换成相应函数式接口的一个实例。 第三种方式会在后文中详细介绍。

Guess you like

Origin juejin.im/post/7143049506160902151