什么是Functional interfaces
Functional interfaces 也被稱作Single Abstract Method interfaces (SAM Interfaces). 顧名思義孤钦,它們有且只有一個(gè)抽象方法. Java 8引入了一個(gè)注釋西轩,即@FunctionalInterface汹想,當(dāng)你使用@FunctionalInterface注釋的接口違反了Functional Interface的規(guī)定時(shí)绍弟,編譯器將會(huì)報(bào)錯(cuò)录粱。
在Java 8中摔桦,F(xiàn)unctional interfaces也可以使用lambda表達(dá)式靠益,方法引用和構(gòu)造函數(shù)引用來(lái)表示疮鲫。
一個(gè)典型的Functional Interface示例如下:
@FunctionalInterface
public interface MyFirstFunctionalInterface {
public void firstWork();
}
我們?cè)囍偌尤胍粋€(gè)抽象方法:
@FunctionalInterface
public interface MyFirstFunctionalInterface
{
public void firstWork();
public void doSomeMoreWork(); //error
}
編譯器報(bào)錯(cuò):
D:\workspace\ideaws\test\java8\src\main\java\com\ancs\java8\MyFirstFunctionalInterface.java
Error:Error:line (3)java: 意外的 @FunctionalInterface 注釋
com.ancs.java8.MyFirstFunctionalInterface 不是函數(shù)接口
在 接口 com.ancs.java8.MyFirstFunctionalInterface 中找到多個(gè)非覆蓋抽象方法
Note: 即使省略@FunctionalInterface注釋,F(xiàn)unctional Interface也是有效的昏苏。 注釋的作用僅僅是告訴編譯器尊沸,檢查接口是否只有一個(gè)抽象方法。
此外贤惯,由于java8 引入了默認(rèn)方法洼专,默認(rèn)方法不屬于抽象方法,所以你可以根據(jù)需要在Functional Interface隨意增加默認(rèn)默認(rèn)方法孵构。如下所示:
@FunctionalInterface
public interface MyFirstFunctionalInterface
{
public void firstWork();
default void doSomeMoreWork1(){
//Method body
}
default void doSomeMoreWork2(){
//Method body
}
}
另外需要注意的是屁商,如果接口聲明的抽象方法來(lái)自java.lang.Object,那么它也不會(huì)計(jì)入抽象方法的計(jì)數(shù)颈墅。因?yàn)槿魏谓涌诘膶?shí)現(xiàn)都默認(rèn)繼承自java.lang.Object蜡镶。例如,下面的Functional Interface也是有效的:
@FunctionalInterface
public interface MyFirstFunctionalInterface
{
public void firstWork();
@Override
public String toString(); //Overridden from Object class
@Override
public boolean equals(Object obj); //Overridden from Object class
}
java8 中常用函數(shù)式接口
Java API中已經(jīng)有了幾個(gè)函數(shù)式接口恤筛,比如Comparable官还、Runnable和 Callable。 而且再java.util.function包中引入了幾個(gè)新的函數(shù)式接口毒坛。
Note : (T,U) -> R的表達(dá)方式展示了應(yīng)當(dāng)如何思考 一個(gè)函數(shù)描述符望伦。表的左側(cè)代表了參數(shù)類型林说。這里它代表一個(gè)函數(shù),具有兩個(gè)參數(shù)屯伞,分別為泛型 T和U腿箩,返回類型為R。
Java API中提供的常用的函數(shù)式接口及其函數(shù)描述符列表如下:
函數(shù)式接口 | 函數(shù)描述符 | 原始類型特化 |
---|---|---|
Predicate<T> | T->boolean | IntPredicate LongPredicate DoublePredicate |
Consumer<T> | T->void | IntConsumer LongConsumer DoubleConsumer |
Function<T,R> | T->R | IntFunction<R> IntToDoubleFunction IntToLongFunction LongFunction<R> LongToDoubleFunction LongToIntFunction DoubleFunction<R> ToIntFunction<T> ToDoubleFunction<T> ToLongFunction<T> |
Supplier<T> | ()->T | BooleanSupplier IntSupplier LongSupplier DoubleSupplier |
UnaryOperator<T> | T->T | IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
BinaryOperator<T> | (T,T)->T | IntBinaryOperator LongBinaryOperator DoubleBinaryOperator |
BiPredicate<L,R> | (L,R)->boolean | |
BiConsumer<T,U> | (T,U)->void | ObjIntConsumer<T> ObjLongConsumer<T> ObjDoubleConsumer<T> |
BiFunction<T,U,R> | (T,U)->R | ToIntBiFunction<T,U> ToLongBiFunction<T,U> ToDoubleBiFunction<T,U> |
我們接下來(lái)會(huì)介紹Predicate劣摇、Consumer和Function.
Predicate
在數(shù)學(xué)中, predicate 通常被理解為布爾值函數(shù)' 'P: X? {true, false}', 稱之為 predicate on X. 它可以被認(rèn)為是一個(gè)返回值為true或false的運(yùn)算符或函數(shù).
在Java 8中, Predicate 是一個(gè) functional interface 珠移,因此可以用作賦值給 lambda expression 或者方法引用. 那么你認(rèn)為我們會(huì)在日常編程當(dāng)中使用這些返回true/false得函數(shù)么?我可以肯定得告訴你饵撑,你可以使用predicates在你需要判斷任何組/集合中的對(duì)象是 true/false 的地方使用.
例如剑梳,您可以在這些實(shí)時(shí)用例中使用Predicate:
- 查找特定日期之后出生的所有孩子
- 查找特定日期訂購(gòu)的披薩訂單
- 超過(guò)特定年齡的員工等等
所以Predicate似乎是一個(gè)很有意思的類,我們接下來(lái)深入了解一下滑潘。
正如我們所說(shuō)的那樣,Predicate
是一個(gè)functional interface. 這意味著我們可以在需要使用predicate的地方使用lambda 表達(dá)式. 例如 Stream 接口中的filter()
方法.
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param predicate a non-interfering stateless predicate to apply to each element to determine if it
* should be included in the new returned stream.
* @return the new stream
*/
Stream<T> filter(Predicate<? super T> predicate);
我們可以把stream看作一個(gè)可以用來(lái)創(chuàng)建支持串行和并行聚合操作的元素序列的機(jī)制垢乙。就是說(shuō),我們可以隨時(shí)對(duì)流中的元素做一些操作.
所以我們可以使用stream和predicate做如下操作:
- 先對(duì)集合中的元素做過(guò)濾操作
- 然后對(duì)過(guò)濾之后的元素做其他操作
在集合中使用Predicate
為了演示语卤,我們有一個(gè)Employee類追逮,如下所示:
package predicateExample;
public class Employee {
public Employee(Integer id, Integer age, String gender, String fName, String lName){
this.id = id;
this.age = age;
this.gender = gender;
this.firstName = fName;
this.lastName = lName;
}
private Integer id;
private Integer age;
private String gender;
private String firstName;
private String lastName;
//Please generate Getter and Setters
//To change body of generated methods, choose Tools | Templates.
@Override
public String toString() {
return this.id.toString()+" - "+this.age.toString();
}
}
1. 查找所有年齡超過(guò)21歲的男性員工
public static Predicate<Employee> isAdultMale()
{
return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
}
2. 查找所有年齡超過(guò)18歲的女性員工
public static Predicate<Employee> isAdultFemale()
{
return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
}
3. 查找超過(guò)給定年齡的員工
public static Predicate<Employee> isAgeMoreThan(Integer age)
{
return p -> p.getAge() > age;
}
你可以根據(jù)需求創(chuàng)建更多的Predicate,EmployeePredicates.java包含了上面的三個(gè)方法粹舵,如下所示:
package predicateExample;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class EmployeePredicates
{
public static Predicate<Employee> isAdultMale() {
return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
}
public static Predicate<Employee> isAdultFemale() {
return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
}
public static Predicate<Employee> isAgeMoreThan(Integer age) {
return p -> p.getAge() > age;
}
public static List<Employee> filterEmployees (List<Employee> employees,
Predicate<Employee> predicate)
{
return employees.stream()
.filter( predicate )
.collect(Collectors.<Employee>toList());
}
}
我寫了一個(gè) filterEmployees()方法來(lái)說(shuō)明 predicate filter的使用方式. 它使得代碼看起來(lái)更簡(jiǎn)潔钮孵,重復(fù)性更低. 你可以可以創(chuàng)建多個(gè) predicate 鏈, 類似于 builder設(shè)計(jì)模式.
在 filterEmployees()方法中有兩個(gè)參數(shù),List<Employee>
和Predicate<Employee>
,返回滿足Predicate條件的一個(gè)新的Employee集合.
下面是測(cè)試類TestEmployeePredicates.java,如下所示:
package predicateExample;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static predicateExample.EmployeePredicates.*;
public class TestEmployeePredicates
{
public static void main(String[] args)
{
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List<Employee> employees = new ArrayList<Employee>();
employees.addAll(Arrays.asList(new Employee[]{e1,e2,e3,e4,e5,e6,e7,e8,e9,e10}));
System.out.println( filterEmployees(employees, isAdultMale()) );
System.out.println( filterEmployees(employees, isAdultFemale()) );
System.out.println( filterEmployees(employees, isAgeMoreThan(35)) );
//Employees other than above collection of "isAgeMoreThan(35)"
//can be get using negate()
System.out.println(filterEmployees(employees, isAgeMoreThan(35).negate()));
}
}
Output:
[1 - 23, 3 - 43, 4 - 26, 8 - 79, 10 - 45]
[5 - 19, 7 - 68]
[3 - 43, 7 - 68, 8 - 79, 10 - 45]
[1 - 23, 2 - 13, 4 - 26, 5 - 19, 6 - 15, 9 - 15]
Predicates是java 8中一個(gè)非常好的新工具類眼滤,每當(dāng)有使用場(chǎng)景時(shí)巴席,我都會(huì)用到它。
Consumer
java.util.function.Consumer<T>定義了一個(gè)名叫accept的抽象方法诅需,它接受泛型T的對(duì)象參數(shù)漾唉,沒(méi)有返回值(void)。你如果需要訪問(wèn)類型T的對(duì)象堰塌,并對(duì)其執(zhí)行某些操作赵刑,就可以使用 這個(gè)接口。比如场刑,你可以用它來(lái)創(chuàng)建一個(gè)forEach方法般此,接受一個(gè)Integers的列表,并對(duì)其中 每個(gè)元素執(zhí)行操作牵现。你可以使用這個(gè)forEach方法铐懊,并配合Lambda來(lái)打印列表中的所有元素。示例代碼TestConsumer 如下所示:
package consumerExample;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class TestConsumer {
public static void main(String[] args) {
List<String> names = Arrays.asList("Larry", "Steve", "James");
//use lambda expression
System.out.println("Consumer test with lambda expression");
Consumer<String> printConsumer = s -> System.out.println(s);
names.forEach(printConsumer);
// use method reference
System.out.println("Consumer test with method reference");
names.forEach(System.out::println);
}
}
輸出如下:
Consumer test with lambda expression
Larry
Steve
James
Consumer test with method reference
Larry
Steve
James
Consumer較Predicate更容易理解瞎疼,它也是java 8 中新增的工具類科乎。
Function
java.util.function.Function<T, R>接口定義了一個(gè)叫作apply的方法,它接受一個(gè) 泛型T的對(duì)象丑慎,并返回一個(gè)泛型R的對(duì)象。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
如果你需要定義一個(gè)Lambda,將輸入對(duì)象的信息映射 到輸出竿裂,就可以使用這個(gè)接口玉吁。在下面的代碼中,我們向你展示如何利用它來(lái)創(chuàng)建一個(gè)map方法腻异,以將一個(gè)String列表映射到包含每個(gè)String長(zhǎng)度的Integer列表进副。 TestFunction如下所示:
package functionExample;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class TestFunction {
public static void main(String[] args) {
List<Integer> result = map(Arrays.asList("lambdas","in","action"),
(String s) -> s.length()
);
System.out.println(result);
}
public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
List<R> result = new ArrayList<>();
for(T s: list){
result.add(f.apply(s));
}
return result;
}
}
輸出如下:
[7, 2, 6]
由于本章節(jié)主要介紹Function Interfaces,所以例如Function的其他三個(gè)default 方法:addThen(),compose(),identity()就不在此詳細(xì)論述了悔常。大家可以參考TestFunctionAndThen,TestFunctionCompose和TestFunctionIdentity.
原始類型特化
Java類型要么是引用類型(比如Byte影斑、Integer、Object机打、List)矫户,要么是原 始類型(比如int、double残邀、byte皆辽、char)。但是泛型(比如Consumer<T>中的T)只能綁定到 引用類型芥挣。這是由泛型內(nèi)部的實(shí)現(xiàn)方式造成的驱闷。因此,在Java里有一個(gè)將原始類型轉(zhuǎn)換為對(duì)應(yīng) 的引用類型的機(jī)制空免。這個(gè)機(jī)制叫作裝箱(boxing)空另。相反的操作,也就是將引用類型轉(zhuǎn)換為對(duì)應(yīng)的原始類型蹋砚,叫作拆箱(unboxing)扼菠。Java還有一個(gè)自動(dòng)裝箱機(jī)制來(lái)幫助程序員執(zhí)行這一任務(wù):裝 箱和拆箱操作是自動(dòng)完成的。比如都弹,這就是為什么下面的代碼是有效的(一個(gè)int被裝箱成為 Integer):
List<Integer> list = new ArrayList<>();
for (int i = 300; i < 400; i++)
{
list.add(i);
}
但這在性能方面是要付出代價(jià)的娇豫。裝箱后的值本質(zhì)上就是把原始類型包裹起來(lái),并保存在堆 里畅厢。因此冯痢,裝箱后的值需要更多的內(nèi)存,并需要額外的內(nèi)存搜索來(lái)獲取被包裹的原始值框杜。
Java 8為我們前面所說(shuō)的函數(shù)式接口帶來(lái)了一個(gè)專門的版本浦楣,以便在輸入和輸出都是原始類 型時(shí)避免自動(dòng)裝箱的操作。比如咪辱,在下面的代碼中振劳,使用IntPredicate就避免了對(duì)值1000進(jìn)行 裝箱操作,但要是用Predicate<Integer>就會(huì)把參數(shù)1000裝箱到一個(gè)Integer對(duì)象中:
// true(無(wú)裝箱)
IntPredicate evenNumbers = (int i) -> i % 2 == 0;
evenNumbers.test(1000);
//false(裝箱)
Predicate<Integer> oddNumbers = (Integer i) -> i % 2 == 1;
oddNumbers.test(1000);
一般來(lái)說(shuō)油狂,針對(duì)專門的輸入?yún)?shù)類型的函數(shù)式接口的名稱都要加上對(duì)應(yīng)的原始類型前历恐,比 如DoublePredicate寸癌、IntConsumer、LongBinaryOperator弱贼、IntFunction等蒸苇。Function 接口還有針對(duì)輸出參數(shù)類型的變種:ToIntFunction<T>、IntToDoubleFunction等吮旅。