其實在java8就已經(jīng)有java的函數(shù)式編程寫法娩井,只是難度較大,大家都習慣了對象式用法饶号,但在其它語言中都有函數(shù)式的用法,如js,scala姻成,函數(shù)式其實是抽象到極致的思想。
什么是函數(shù)式編程
函數(shù)式編程并不是Java新提出的概念愿棋,其與指令編程相比科展,強調函數(shù)的計算比指令的計算更重要;與過程化編程相比糠雨,其中函數(shù)的計算可以隨時調用才睹。
當然,大家應該都知道面向對象的特性(抽象甘邀、封裝琅攘、繼承、多態(tài))松邪。其實在Java8出現(xiàn)之前坞琴,我們關注的往往是某一類對象應該具有什么樣的屬性,當然這也是面向對象的核心--對數(shù)據(jù)進行抽象逗抑。但是java8出現(xiàn)以后剧辐,這一點開始出現(xiàn)變化寒亥,似乎在某種場景下,更加關注某一類共有的行為(這似乎與之前的接口有些類似)荧关,這也就是java8提出函數(shù)式編程的目的护盈。如圖1-1所示,展示了面向對象編程到面向行為編程的變化羞酗。
函數(shù)接口
接口 | 參數(shù) | 返回類型 | 描述 |
---|---|---|---|
Predicate<T> | T | boolean | 用來比較操作 |
Consumer<T> | T | void | 沒有返回值的函數(shù) |
Function<T, R> | T | R | 有返回值的函數(shù) |
Supplier<T> | None | T | 工廠方法-返回一個對象 |
UnaryOperator<T> | T | T | 入?yún)⒑统鰠⒍际窍嗤瑢ο蟮暮瘮?shù) |
BinaryOperator<T> | (T,T) | T | 求兩個對象的操作結果 |
java8 函數(shù)式編程的入口,每個函數(shù)接口都帶有 @FunctionalInterface 注釋,有且僅有一個未實現(xiàn)的方法紊服,表示接收 Lambda 表達式檀轨,它們存在的意義在于將代碼塊作為數(shù)據(jù)打包起來。
這幾個函數(shù)接口欺嗤,完全可以把它們看成普通的接口参萄,不過他們有且僅有一個抽象方法(因為要接收 Lambda 表達式)。
@FunctionalInterface 該注釋會強制 javac 檢查一個接口是否符合函數(shù)接口的標準煎饼。 如果該注釋添加給一個枚舉類型讹挎、 類或另一個注釋, 或者接口包含不止一個抽象方法吆玖, javac 就會報錯筒溃。
Lambda 表達式
Lambda 表達式,有時候也稱為匿名函數(shù)或箭頭函數(shù)沾乘,幾乎在當前的各種主流的編程語言中都有它的身影怜奖。Java8 中引入 Lambda 表達式,使原本需要用匿名類實現(xiàn)接口來傳遞行為翅阵,現(xiàn)在通過 Lambda 可以更直觀的表達歪玲。
- Lambda 表達式,也可稱為閉包掷匠。閉包就是一個定義在函數(shù)內部的函數(shù)滥崩,閉包使得變量即使脫離了該函數(shù)的作用域范圍也依然能被訪問到。
- Lambda 表達式的本質只是一個”語法糖”讹语,由編譯器推斷并幫你轉換包裝為常規(guī)的代碼,因此你可以使用更少的代碼來實現(xiàn)同樣的功能钙皮。
- Lambda 表達式是一個匿名函數(shù),即沒有函數(shù)名的函數(shù)募强。有些函數(shù)如果只是臨時一用株灸,而且它的業(yè)務邏輯也很簡單時,就沒必要非給它取個名字不可擎值。
- Lambda 允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中).
Lambda 表達式語法如下:形參列表=>函數(shù)體(函數(shù)體多于一條語句的可用大括號括起)慌烧。在Java里就是() -> {}:
(parameters) -> expression
(parameters) ->{ statements; }
Lambda表達式的重要特征:
- Lambda 表達式主要用來定義行內執(zhí)行的方法類型接口,例如鸠儿,一個簡單方法接口屹蚊。
- Lambda表達式是通過函數(shù)式接口(必須有且僅有一個抽象方法聲明)識別的
- 可選類型聲明:不需要聲明參數(shù)類型厕氨,編譯器可以統(tǒng)一識別參數(shù)值。
- 可選的參數(shù)圓括號:一個參數(shù)無需定義圓括號汹粤,但多個參數(shù)需要定義圓括號命斧。
- 可選的大括號:如果主體包含了一個語句,就不需要使用大括號嘱兼。
- 可選的返回關鍵字:如果主體只有一個表達式返回值国葬,則編譯器會自動返回值,大括號需要指定表達式返回一個值芹壕。
Lambda表達式中的變量作用域:
- 訪問權限與匿名對象的方式非常類似汇四。只能夠訪問局部對應的外部區(qū)域的局部final變量,以及成員變量和靜態(tài)變量踢涌。
- 在Lambda表達式中能訪問域外的局部非final變量通孽、但不能修改Lambda域外的局部非final變量。因為在Lambda表達式中睁壁,Lambda域外的局部非final變量會在編譯的時候背苦,會被隱式地當做final變量來處理。
- Lambda表達式內部無法訪問接口默認(default)方法.
例子:使用Java 8之前的方法來實現(xiàn)對一個string列表進行排序:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
Java 8 Lambda 表達式:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
// 只有一條邏輯語句潘明,可以省略大括號
Collections.sort(names, (String a, String b) -> b.compareTo(a));
// 可以省略入?yún)㈩愋?Collections.sort(names, (a, b) -> b.compareTo(a));
FunctionalInterface
Java8的新引入行剂,包含函數(shù)式的設計,接口都有@FunctionalInterface的注解钉疫。
如聲明一個接口
@FunctionalInterface
public interface fun{}
這會編譯錯硼讽,編譯器會告訴你*no target method*。而如果加一個方法:
@FunctionalInterface
public interface fun{
void run();
}
這就OK了牲阁,一個函數(shù)式接口聲明好了固阁。再加一個呢?
@FunctionalInterface
public interface fun{
void run();
void test();
}
不ok城菊,明確說了只有一個抽象方法嘛备燃。但是如果換一種函數(shù)簽名:
@FunctionalInterface
public interface fun{
Object equals(Object o);
void test();
}
這就OK了。一個抽象方法凌唬,一個Object的public方法并齐,相安無事。Object還有其他方法客税,clone方法試試會怎么樣况褪?
@FunctionalInterface
public interface fun{
Object clone();
void test();
}
這又不行了,因為前面明確說了更耻,要是Object的public方法测垛,而clone是protected的。
小結:函數(shù)式接口秧均,有且僅有一個抽象方法食侮,Object的public方法除外号涯。
因為Java本身支持多接口實現(xiàn),你定義一個Class可以implements多個interface锯七。所以這個限制也沒什么影響链快,如果想約定一個函數(shù)式接口來統(tǒng)一,也可以做一些默認的實現(xiàn)來達到一個接口多個抽象方法的目的眉尸,比如下面這種做法:
一個普通接口NonFunc:
public interface NonFunc {
void foo();
void voo();
}
函數(shù)式接口Func:
public interface Func extends NonFunc {
default void foo();
default void voo();
void run();
}
實現(xiàn)的測試類:
Public class FunTest implements Func {
public static void main(String... args) {
Func func = new FunTest();
func.run();
func.foo();
func.voo();
}
@Override
public void run() {
System.out.println("run");
}
@Override
public void foo() {
System.out.println("foo");
}
@Override
public void voo() {
System.out.println("voo");
}
}
函數(shù)式接口的一大特性就是可以被lambda表達式和函數(shù)引用表達式代替域蜗。
public class FunTest {
public static void main(String... args) {
FunTest t = new FunTest();
//lambda
t.test(10, ()->System.out.println("xxxxx"))
//method reference
t.test(100, t::customedFunc);
}
public void customedFunc(){
System.out.println("a customed method reference");
}
public void test(int x, Func func) {
System.out.println(x);
func.run();
}
}
上面例子列舉了一個lambda模式和一個方法引用模式,這樣就可以利用函數(shù)式編程強大的能力噪猾,將方法作為參數(shù)了地消。
下面再加一例
public class Main {
public static void main(String[] args) {
Action action = System.out :: println;
action.execute("Hello World!");
test(System.out :: println, "Hello World!");
}
static void test(Action action, String str) {
action.execute(str);
}
}
@FunctionalInterface
interface Action<T> {
public void execute(T t);
}
Function
關于Function接口,其接口聲明是一個函數(shù)式接口畏妖,其抽象表達函數(shù)為
@FunctionalInterface
public interface Function<T,R>{
R apply(T t);
...
}
函數(shù)意為將參數(shù)T傳遞給一個函數(shù),返回R疼阔。即R=Function(T)
其默認實現(xiàn)了3個default方法戒劫,分別是compose、andThen和identity婆廊,對應的函數(shù)表達為:compose對應V=Function(ParamFunction(T))迅细,體現(xiàn)嵌套關系;andThen對應V=ParamFunction(Function(T))淘邻,轉換了嵌套的順序茵典;還有identity對應了一個傳遞自身的函數(shù)調用對應Function(T)=T。從這里看出來宾舅,compose和andThen對于兩個函數(shù)f和g來說统阿,f.compose(g)等價于g.andThen(f)〕镂遥看個例子:
public static void main(String... args){
Function<Integer, Integer> incr1 = x->x*2;
Function<Integer, Integer> multiply = x->x*2;
int x=2;
System.out.println("f(x)=x+1,when x="+x+", f(x)="+incr1.apply(x));
System.out.println("f(x)=x+1,g(x)=2x, when x="+x+",f(g(x))="+incr1.compose(multiply).apply(x));
System.out.println("f(x)=x+1,g(x)=2x, when x="+x+",g(f(x))="+incr1.andThen(multiply).apply(x));
System.out.println("compose vs andThen: f(g(x))="+incr1.compose(multiply).apply(x)+","+multiply.andThen(incr1).apply(x));
}
高階函數(shù)
只是普通的lambda表達式扶平,其能力有限。我們會希望引入更強大的函數(shù)能力——高階函數(shù)蔬蕊,可以定義任意同類計算的函數(shù)结澄。
比如這個函數(shù)定義,參數(shù)是z岸夯,返回值是一個Function麻献,這個Function本身又接受另一個參數(shù)y,返回z+y猜扮。于是我們可以根據(jù)這個函數(shù)勉吻,定義任意加法函數(shù):
//high order function
Function<Integer, Function<Integer, Integer>> makeAdder = z->y->z+y;
x=2;
//define add1
Function<Integer, Integer> add1 = makeAdder.apply(1);
System.out.println("f(x)=x+1,when x="+x+",f(x)="+add1.apply(x));
//define add5
Function<Integer, Integer> add5 = makeAdder.apply(5);
System.out.println("f(x)=x+5, when x="+x+", f(x)="+add5.apply(x));
由于高階函數(shù)接受一個函數(shù)作為參數(shù),結果返回另一個函數(shù)破镰,所以是典型的函數(shù)到函數(shù)的映射餐曼。
BiFunction提供了二元函數(shù)的一個接口聲明压储,舉例來說:
//binary fun
BiFunction<Integer, Integer, Integer> multiply = (a,b)->a*b;
System.out.println("f(z)=x*y, when x=3,y=5 when f(z)="+multiply,apply(3,5));
其輸出結果將是:f(z)=x*y, when x=3,y=5, then f(z)=15。
二元函數(shù)沒有compose能力源譬,只是默認實現(xiàn)了andThen集惋。
有了一元和二元函數(shù),那么可以通過組合擴展出更多的函數(shù)可能踩娘。
Function接口相關的接口包括:
- BiFunction :R apply(T t, U u);接受兩個參數(shù)刮刑,返回一個值,代表一個二元函數(shù)养渴;
- DoubleFunction :R apply(double value);只處理double類型的一元函數(shù)雷绢;
- IntFunction :R apply(int value);只處理int參數(shù)的一元函數(shù);
- LongFunction :R apply(long value);只處理long參數(shù)的一元函數(shù)理卑;
- ToDoubleFunction:double applyAsDouble(T value);返回double的一元函數(shù)翘紊;
- ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函數(shù);
- ToIntFunction:int applyAsInt(T value);返回int的一元函數(shù)藐唠;
- ToIntBiFunction:int applyAsInt(T t, U u);返回int的二元函數(shù)帆疟;
- ToLongFunction:long applyAsLong(T value);返回long的一元函數(shù);
- ToLongBiFunction:long applyAsLong(T t, U u);返回long的二元函數(shù)宇立;
- DoubleToIntFunction:int applyAsInt(double value);接受double返回int的一元函數(shù)踪宠;
- DoubleToLongFunction:long applyAsLong(double value);接受double返回long的一元函數(shù);
- IntToDoubleFunction:double applyAsDouble(int value);接受int返回double的一元函數(shù)妈嘹;
- IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函數(shù)柳琢;
- LongToDoubleFunction:double applyAsDouble(long value);接受long返回double的一元函數(shù);
- LongToIntFunction:int applyAsInt(long value);接受long返回int的一元函數(shù)润脸;
Operator
Operator其實就是Function柬脸,函數(shù)有時候也叫作算子。算子在Java8中接口描述更像是函數(shù)的補充毙驯,和上面的很多類型映射型函數(shù)類似肖粮。
算子Operator包括:UnaryOperator和BinaryOperator。分別對應單元算子和二元算子尔苦。
算子的接口聲明如下:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity(){
return t->t;
}
}
二元算子的聲明:
算子就是一個針對同類型輸入輸出的一個映射涩馆。在此接口下,只需聲明一個泛型參數(shù)T即可允坚。對應上面的例子:
public class TestOperator {
public static void main(String... args) {
UnaryOperator<Integer> add = x->x+1;
System.out.println(add.apply(1))
BinaryOperator<Integer> addxy = (x,y)->x+y;
System.out.println(addxy.apply(3,5));
BinaryOperator<Integer> min = BinaryOperator.minBy((o1,o2)->o1-o2);
System.out.println(min.apply(100,200));
BinaryOperator<Integer> max = BinaryOperator.maxBy((o1,o2)->o1-o2);
System.out.println(max.apply(100,200));
}
}
例子里補充一點的是魂那,BinaryOperator提供了兩個默認的static快捷實現(xiàn),幫助實現(xiàn)二元函數(shù)min(x,y)和max(x,y)稠项,使用時注意的是排序器可別傳反了:)
其他的Operator接口:(不解釋了)
- LongUnaryOperator:long applyAsLong(long operand);
- IntUnaryOperator:int applyAsInt(int operand);
- DoubleUnaryOperator:double applyAsDouble(double operand);
- DoubleBinaryOperator:double applyAsDouble(double left, double right);
- IntBinaryOperator:int applyAsInt(int left, int right);
- LongBinaryOperator:long applyAsLong(long left, long right);
Predicate
predicate是一個謂詞函數(shù)涯雅,主要作為一個謂詞演算推導真假值存在,其意義在于幫助開發(fā)一些返回bool值的Function展运。本質上也是一個單元函數(shù)接口活逆,其抽象方法test接受一個泛型參數(shù)T精刷,返回一個boolean值。等價于一個Function的boolean型返回值的子集蔗候。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
...
}
其默認方法也封裝了and怒允、or和negate邏輯。寫個小例子看看:
public class TestJ8Predicate {
public static void main(String... args){
TestJ8Predicate t = new TestJ8Predicate();
t.pringBigValue(10, val->val>5);
t.printBigValueAnd(10, val->val>5);
t.printBigValueAnd(6, val->val>5);
}
public void pringBigValue(int value, Predicate<Integer> predicate) {
if (predicate.test(value)) {
System.out.println(value);
}
}
public void printBigValueAnd(int value, Predicate<Integer> predicate) {
if (predicate.and(v->v<8).test(value)) {
System.out.println("value < 8:" + value);
}else{
System.out.println("value should < 8 at least.");
}
}
}
Predicate在Stream中有應用锈遥,Stream的filter方法就是接受Predicate作為入?yún)⒌娜沂隆_@個具體在后面使用Stream的時候再分析深入。
其他Predicate接口:
- BiPredicate:boolean test(T t, U u);接受兩個參數(shù)的二元謂詞
- DoublePredicate:boolean test(double value);入?yún)閐ouble的謂詞函數(shù)
- IntPredicate:boolean test(int value);入?yún)閕nt的謂詞函數(shù)
- LongPredicate:boolean test(long value);入?yún)閘ong的謂詞函數(shù)
Consumer
看名字就可以想到所灸,這像謂詞函數(shù)接口一樣丽惶,也是一個Function接口的特殊表達——接受一個泛型參數(shù),不需要返回值的函數(shù)接口爬立。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
...
}
這個接口聲明太重要了钾唬,對于一些純粹consume型的函數(shù),沒有Consumer的定義真無法被Function家族的函數(shù)接口表達侠驯。因為Function一定需要一個泛型參數(shù)作為返回值類型(當然不排除你使用Function來定義知纷,但是一直返回一個無用的值)。比如下面的例子陵霉,如果沒有Consumer,類似的行為使用Function表達就一定需要一個返回值伍绳。
public static void main(String... args) {
Consumer<Integer> consumer = System.out::println;
consumer.accept(100);
//use function, you always need one return value.
Function<Integer,Integer> function = x->{
System.out.println(x);
return x;
}
function.apply(100);
}
其他Consumer接口:
- BiConsumer:void accept(T t, U u);接受兩個參數(shù)
- DoubleConsumer:void accept(double value);接受一個double參數(shù)
- IntConsumer:void accept(int value);接受一個int參數(shù)
- LongConsumer:void accept(long value);接受一個long參數(shù)
- ObjDoubleConsumer:void accept(T t, double value);接受一個泛型參數(shù)一個double參數(shù)
- ObjIntConsumer:void accept(T t, int value);接受一個泛型參數(shù)一個int參數(shù)
- ObjLongConsumer:void accept(T t, long value);接受一個泛型參數(shù)一個long參數(shù)
Supplier
其聲明如下:
@FunctionalInterface
public interface Supplier<T> {
T get();
...
}
其簡潔的聲明踊挠,會讓人以為不是函數(shù)。這個抽象方法的聲明冲杀,同Consumer相反效床,是一個只聲明了返回值,不需要參數(shù)的函數(shù)(這還叫函數(shù)权谁?)剩檀。也就是說Supplier其實表達的不是從一個參數(shù)空間到結果空間的映射能力,而是表達一種生成能力旺芽,因為我們常見的場景中不止是要consume(Consumer)或者是簡單的map(Function)沪猴,還包括了new這個動作。而Supplier就表達了這種能力采章。
比如你要是返回一個常量运嗜,那可以使用類似的做法:
這保證supplier對象輸出的一直是1。
如果是要利用構造函數(shù)的能力呢悯舟?就可以這樣:
Supplier<TestJ8Supplier> anotherSupplier;
for(int i=0; i<10; i++){
anotherSupplier = TestJ8Supplier::new;
System.out.println(anotherSupplier.get())
}
這樣的輸出可以看到担租,全部的對象都是new出來的。
這樣的場景在Stream計算中會經(jīng)常用到抵怎,具體在分析Java 8中Stream的時候再深入奋救。
其他Supplier接口:
BooleanSupplier:boolean getAsBoolean();返回boolean
DoubleSupplier:double getAsDouble();返回double
IntSupplier:int getAsInt();返回int
LongSupplier:long getAsLong();返回long
參考:
阮一峰 - 函數(shù)式編程入門教程:http://www.ruanyifeng.com/blog/201
https://blog.csdn.net/lz710117239/article/details/76192629