Java8常用的內置函數式接口(一)
簡介
- JDK 1.8 API中包含了很多內置的函數式接口。有些是在以前版本的Java中大家耳熟能詳的,例如
Comparator
接口,或者Runnable
接口。對這些現成的接口進行實現,可以通過@FunctionalInterface
標注來啟用Lambda
功能支持。 - 此外,Java 8 API 還提供了很多新的函數式接口,來降低程序員的工作負擔。
- 比如我們今天要了解到的四大常用的內置函數式接口:下表
序號 | 接口名 | 接口類型 |
---|---|---|
1 | Predicate
|
斷言型接口 |
2 | Consumer
|
消費型接口 |
3 | Supplier
|
供給型接口 |
4 | Function<T, R> | 函數式接口 |
- JDK 1.8之前已有的函數式接口
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
Predicate
斷言型接口
Predicate是一個斷言式的函數式接口,返回的是一個boolean值,用於進行判斷行為與參數是否相符。Java8Stream中的filter使用的就是此函數式接口。
Predicate是一個布爾類型的函數,該函數只有一個輸入參數。Predicate接口包含了多種默認方法,用於處理復雜的邏輯動詞(and, or,negate)
Predicate常用方法
1. test()方法
test源碼:
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);// 用來判斷傳過來的參數是否符合規則
test方法的作用是:
1 . 評估參數里面的表達式(說白了就是驗證傳進來的參數符不符合規則)
2 . 它的返回值是一個boolean類型(這點需要注意一下)。
測試示例代碼:
//傳入一個字符串,test方法判斷該字符串長度是否大於2
@Test
public void test1(){
Predicate<String> predicate = (s) -> s.length() > 2;
boolean foo = predicate.test("foo");// true
}
2.and()方法(默認方法)
and源碼:
/**
* Returns a composed predicate that represents a short-circuiting logical
* AND of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code false}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ANDed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* AND of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
//通過源碼我們可以看到,return最總返回的是並且的邏輯,必須滿足兩個條件,等同於我們的邏輯與&&,存在短路特性
}
- 注意:and 方法返回一個Predicate<? super T>
測試示例代碼:
//通過調用下面的方法,我們將and和test方法連續使用
boolean b = testAndMethod("zhangssss",
stringOne -> stringOne.equals("zhangsan"), stringTwo -> stringTwo.length() > 5);
System.out.println("測試and方法打印結果:"+b);
/**
*
* @param stringOne 待判斷的字符串
* @param predicateOne 斷定表達式1
* @param predicateTwo 斷定表達式2
* @return 是否滿足兩個條件
*/
public boolean testAndMethod(String stringOne, Predicate<String> predicateOne,Predicate<String> predicateTwo) {
return predicateOne.and(predicateTwo).test(stringOne);//and 方法返回一個Predicate<? super T>
}
打印結果:
測試and方法打印結果:false
因為使用and方法同時需要滿足兩個條件
3.negate()方法(默認方法)
negate源碼:
/**
* Returns a predicate that represents the logical negation of this
* predicate.
* @return a predicate that represents the logical negation of this
* predicate
*/
default Predicate<T> negate() {
return (t) -> !test(t);
//我們可以看到return返回的是test()方法的相反結果,等同於我們的邏輯非
}
返回值一樣需要注意, 是Predicate
測試示例代碼:
//測試negate()和test()
boolean f = testNageteMethod("zhangsan", stringOne -> stringOne.equals("zhangsan"));
System.out.println("測試negate方法打印結果:"+f);
public boolean testNageteMethod(String stringValue, Predicate<String> predicate) {
return predicate.negate().test(stringValue);
}
打印結果:
測試negate方法打印結果:false
4.or()方法(默認方法)
or源碼:
/**
* Returns a composed predicate that represents a short-circuiting logical
* OR of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code true}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ORed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* OR of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
//通過return我們可以看出返回的是多者選其一即可, 等同於我們的邏輯或
}
返回值一樣需要注意,Predicate
測試示例代碼:
//測試or方法
boolean a = testOrMethod("zhangsan"
, stringOne -> stringOne.equals("zhangsan111")
, stringTwo -> stringTwo.length() > 50
, stringThree -> stringThree.length() % 2 == 0);
System.out.println("測試or方法打印結果:"+a);
public boolean testOrMethod(String stringOne, Predicate<String> predicateOne, Predicate<String> predicateTwo, Predicate<String> predicateThree) {
return predicateOne.or(predicateTwo).or(predicateThree).test(stringOne);
}
5. isEqual()方法(靜態方法)
判斷兩個對象是否相等—> 使用的是Objects里面的equals()方法進行比較
isEqual源碼:
/**
* Returns a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}.
*
* @param <T> the type of arguments to the predicate
* @param targetRef the object reference with which to compare for equality,
* which may be {@code null}
* @return a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
解釋如下: 本類由一些操作對象的靜態工具方法構成,這些工具方法包括了非空檢查、方法的非空參數檢查、比較對象的hashCode、為對象返回一個字符串表示、比較兩個對象,說的很明顯了,比較的兩個對象的HashCode值
通俗一點解釋: 先判斷對象是否為NULL
—> 這個由Objects
里面的isNull
進行判斷,如果,不為Null
的話,那么接下來用java.lang.object
里面的equals()
方法進行比較.
測試示例代碼:
//測試isEqual()方法
System.out.println(testMethodIsEquals("zhangsan","zhangsan"));
System.out.println("~~~ ~~~ ~~~ ~~~");
System.out.println(testMethodIsEquals("zhangsan","lisi"));
System.out.println("~~~ ~~~ ~~~ ~~~");
System.out.println(testMethodIsEquals(null,"zhangsan")); /* 我們來Debug一下這個程序*/
public boolean testMethodIsEquals(String strValue, String strValue2) {
return Predicate.isEqual(strValue).test(strValue2);
}
Consumer
消費型接口
Consumer的作用顧名思義,是給定義一個參數,對其進行(消費)處理,處理的方式可以是任意操作.
接口源碼:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
Consumer常用方法
1.accept()方法
accept源碼:
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
accept(T t),接受一個參數,沒有返回值
測試示例代碼:
List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// 通過lambda表達式構造出Consumer對象
Consumer listener = i -> System.out.println(i);
list.forEach(listener);
上面是通過lambda
表達式構造出Consumer
對象,將list
中每一個元素,傳遞給consumer
,執行打印的操作,我再把這個例子做一個變化
// 通過lambda表達式構造出Consumer對象
list.forEach(i -> System.out.println(i * 2));
這里打印的元素是乘2后的結果,這就說明了通過lambda表達式,我們傳遞的是行為
,accept(T t)方法只負責接收一個參數
,至於要做什么,是我們再調用的時候,把行為傳遞過去。
另外還可以使用方法引用的方式來調用Consumer
的accept
方法。
// 通過方法引用的方式構造出Consumer對象
list.forEach(System.out::println);
這里也可以實現遍歷每一個元素並打印出來,這是通過方法引用的方式來構造出的Consumer
對象。"::"
這里兩個連續的冒號,是jdk8支持的語法,可以自動定位到具體的函數式接口,這里就可以自動定位到Consumer
。
2. andThen()方法
andThen源碼:
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
該方法默認實現,它接收一個Consumer
對象,同時會返回一個Consumer
對象,返回的Consumer
對象還可以繼續調用andThen
方法,這樣該方法就實現了將執行操作給串行化
。舉個例子:
print(list, item -> System.out.print(" consumer1-->:" + item * 2), item -> System.out.println(" consumer2-->:" + item * 3));
/*
andThen方法, 將參數傳遞給調用者執行accept方法,然后再傳給第二個consumer執行accept方法。
*/
public void print(List<Integer> list, IntConsumer con1, IntConsumer con2) {
list.forEach(item -> con1.andThen(con2).accept(item));
}
該示例構造了兩個Consumer
對象,通過consumer
的andThen
方法,將兩個操作給串行
起來,對於list中每個元素,都會先執行con1的appect方法,再執行con2的accept方法。
打印結果:
consumer1-->:2 consumer2-->:3
consumer1-->:4 consumer2-->:6
consumer1-->:6 consumer2-->:9
consumer1-->:8 consumer2-->:12
consumer1-->:10 consumer2-->:15
consumer1-->:12 consumer2-->:18
consumer1-->:14 consumer2-->:21
consumer1-->:16 consumer2-->:24
consumer1-->:18 consumer2-->:27
與Consumer相關的接口
BiConsumer<T, U>
- 處理一個兩個參數
DoubleConsumer
- 處理一個double類型的參數
IntConsumer
- 處理一個int類型的參數
LongConsumer
- 處理一個long類型的參數
ObjIntConsumer
- 處理兩個參數,且第二個參數必須為int類型
ObjLongConsumer
- 處理兩個參數,且第二個參數必須為long類型
Supplier
供給型接口
Supplier
接口是對象實例的提供者,定義了一個名叫get
的抽象方法,它沒有任何入參,並返回一個泛型T對象,具體源碼如下:
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
常用方法
1. get()方法
下面我們使用聲明一個Supplier
的實例:
/**
*supplier供給型函數接口測試
*/
@Test
public void testSupplier() {
Supplier<Person> supplier = Person::new;
Person person = supplier.get();
System.out.println("通過Supplier接口創建Person空對象:"+person);
System.out.println("---------------------------");
Supplier<Person> supplier1 =() -> new Person("Evonne", "Shari", "JAVA", "female", 40, 1800);
Person person1 = supplier1.get();
System.out.println("通過Supplier接口創建Person非空對象:"+person1);
}
打印結果:
通過Supplier接口創建Person空對象:Person(firstName=null, lastName=null, job=null, gender=null, salary=0, age=0)
---------------------------
通過Supplier接口創建Person非空對象:Person(firstName=Evonne, lastName=Shari, job=JAVA, gender=female, salary=40, age=1800)
特別需要注意的是,本例中每一次調用get方法都會創建新的對象。
Function<T,R>函數式接口
Function接口可以創建更加復雜的Function接口實例。有兩個參數:T入參,R返參
常用方法
1. apply()方法
Function 就是一個函數,其作用類似於數學中函數的定義,所以Function中沒有具體的操作,具體的操作需要我們去為它指定,因此apply具體返回的結果取決於傳入的lambda表達式。
apply源碼:
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
測試示例代碼:
public void test(){
Function<Integer,Integer> test=i->i+1;
test.apply(5);
}
/**打印結果 print:6*/
解析:上面的示例用lambda表達式定義了一個行為使得i
自增1,我們使用參數5(相當於傳入參數5)執行apply
,最后打印結果6
。這跟我們以前看待Java的眼光已經不同了,在函數式編程之前我們定義一組操作首先想到的是定義一個方法,然后指定傳入參數,返回我們需要的結果。函數式編程的思想是先不去考慮具體的行為,而是先去考慮參數,具體的方法我們可以后續再設置。
再舉個例子:
public void test(){
Function<Integer,Integer> test1=i->i+1;
Function<Integer,Integer> test2=i->i*i;
System.out.println(calculate(test1,5));
System.out.println(calculate(test2,5));
}
public static Integer calculate(Function<Integer,Integer> test,Integer number){
return test.apply(number);
}
/** 打印結果print:6*/
/**打印結果 print:25*/
通過傳入不同的Function
,實現了在同一個方法中實現不同的操作
。在實際開發中這樣可以大大減少很多重復的代碼,比如我在實際項目中有個新增用戶的功能,但是用戶分為VIP和普通用戶,且有兩種不同的新增邏輯。那么此時我們就可以先寫兩種不同的邏輯。除此之外,這樣還讓邏輯與數據分離
開來,我們可以實現邏輯的復用。
當然實際開發中的邏輯可能很復雜,比如兩個方法F1,F2都需要兩個個邏輯AB,但是F1需要A->B,F2方法需要B->A。這樣的我們用剛才的方法也可以實現,源碼如下:
public void test(){
Function<Integer,Integer> A=i->i+1;
Function<Integer,Integer> B=i->i*i;
System.out.println("F1:"+B.apply(A.apply(5)));
System.out.println("F2:"+A.apply(B.apply(5)));
}
/** F1:36 */
/** F2:26 */
2 . compose()和andThen()方法
接着上面的例子,假如我們F1,F2需要四個邏輯ABCD,那我們還這樣寫就會變得很麻煩了。
compose
和andThen
可以解決我們的問題。先看compose
的源碼:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));//before,先執行傳入的V,在執行原有的
}
compose
接收一個Function
參數,返回時先用傳入的邏輯執行apply
,然后使用當前Function的apply。
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));//after,先執行原有的,再執行傳入的參數
}
andThen跟compose正相反,先執行當前的邏輯,再執行傳入的邏輯
換個說法:
compose等價於B.apply(A.apply(5)),而andThen等價於A.apply(B.apply(5))。
public void test(){
Function<Integer,Integer> A=i->i+1;
Function<Integer,Integer> B=i->i*i;
System.out.println("F1:"+B.apply(A.apply(5)));
System.out.println("F1:"+B.compose(A).apply(5));
System.out.println("F2:"+A.apply(B.apply(5)));
System.out.println("F2:"+B.andThen(A).apply(5));
}
/** F1:36 */
/** F1:36 */
/** F2:26 */
/** F2:26 */
我們可以看到上述兩個方法的返回值都是一個Function,這樣我們還可以使用建造者模式
(待研究)的操作來使用。
Integer apply1 = B1.compose(A1).compose(A1).andThen(A1).apply(5);//待研究
結束語
至此,對於函數式接口有了一個初步認識,革命向未成功,同志仍需努力。
推薦參考blog:
Function接口的使用
死磕Lambda表達式