考え方の変化
都市のコレクションにシカゴが存在するかどうかを調べる例を見てみましょう。
慣れた方法
boolean found = false ;
for ( String city : cities ) {
if ( city . equals ( "Chicago" )) {
found = true ;
break ;
}
}
System . out . println ( "Found chicago?:" + found );
复制代码
上記のコードは、この問題に直面したときのほとんどの開発者の最初の反応です。Imperative Style コードを使用して必要なロジックを完成させますが、コード量が多いため、より複雑に見えます。
\
より経験豊富な開発者は、既存の 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 判定も行わず、ロジックは 1 回で完了します。また、条件に応じて価格セットをフィルタリングし (filter)、フィルタリングされたセットに対して値引き処理を実行し (map)、最後に値引き後の価格を加算 (reduce) することで、より読みやすくなります。
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表达式会被编译器转换成相应函数式接口的一个实例。 第三种方式会在后文中详细介绍。