Consumer介紹與實例分析
函數式接口:@FunctionalInterface
Consumer(消費者)
函數式接口:@FunctionalInterface
自從jdk8提供了函數式接口這一新的特性,極大地精簡了java開發的方式。而在jdk8之前的版本,函數僅僅只能傳遞參數,而不能將一個函數或者說是行為傳遞過去,這意味着我們在調用某個函數時,該函數所表示的執行功能已經很明確了,對於lambda表達式來說,函數的調用,是將函數的行為傳遞過去,真正執行的是調用時傳遞的行為。@FunctionalInterface注解是標識一個接口是函數式接口。那么什么樣的接口是函數式接口呢?
下面是@FunctionalInterface的注釋說明:
Conceptually, a functional interface has exactly one abstract method. Since {@linkplain java.lang.reflect.Method#isDefault() default methods} have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of {@code java.lang.Object}, that also does <em>not</em> count toward the interface's abstract method count since any implementation of the interface will have an implementation from {@code java.lang.Object} or elsewhere. Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.
該注釋說了,一個函數式接口應該只有一個抽象方法,對於default methods,是有一個實現,所以它們不是抽象的,這里就說明了jdk8的接口支持方法的實現。如果一個接口聲明了一個抽象方法,該方法是被Object類給重寫的,那么它不會為該接口的抽象方法增加,因為在Object或者別處會有一個具體的實現。函數式接口的實例可以通過lambda表達式,方法引用,構造方法引用的方式創建出來。這里我們就理解了函數式接口和lambda表達式之間的關系了。下面我主要講解一個函數式接口Consumer的用法。
Consumer(消費者)
對於Consumer這個接口,我們來看一下它提供的抽象方法是什么?
/** * 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對象 list.forEach(i -> System.out.println(i));
這里是通過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);
比如將給定的一批用戶里面的名稱為"lisi"的用戶都給打包起來
/** * 此處使用lombok插件(值得了解) */ @Data @Accessors(chain = true) @AllArgsConstructor public class Person { private Integer age; private String name; } List<Person> lisiList = new ArrayList<>(); Consumer<Person> consumer = x -> { if (x.getName().equals("lisi")){ lisiList.add(x); } }; Stream.of( new Person(21,"zhangsan"), new Person(22,"lisi"), new Person(23,"wangwu"), new Person(24,"wangwu"), new Person(23,"lisi"), new Person(26,"lisi"), new Person(26,"zhangsan") ).forEach(consumer); System.out.println(JSON.toJSONString(lisiList));
結果為:
[{"age":22,"name":"lisi"},{"age":23,"name":"lisi"},{"age":26,"name":"lisi"}]
這里也可以實現遍歷每一個元素並打印出來,這是通過方法引用的方式來構造出的Consumer對象。"::"這里兩個連續的冒號,是jdk8支持的語法,可以自動定位到具體的函數式接口,這里就可以自動定位到Consumer。
Consumer中還提供了一個默認方法,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方法,這樣該方法就實現了將執行操作給串行化。舉個例子:
public static void main(String[] args) { ConsumerTest02 test = new ConsumerTest02(); List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9); test.print(list, item -> System.out.print(" consumer1:" + item * 2), item -> System.out.print(" 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
1
比如將給定的一批用戶里面的名稱為"lisi"且年齡大於22歲的用戶都給打包起來
List<Person> lisiList = new ArrayList<>(); Consumer<Person> consumer = x -> { if (x.getName().equals("lisi")){ lisiList.add(x); } }; consumer = consumer.andThen( x -> lisiList.removeIf(y -> y.getAge() < 23) ); Stream.of( new Person(21,"zhangsan"), new Person(22,"lisi"), new Person(23,"wangwu"), new Person(24,"wangwu"), new Person(23,"lisi"), new Person(26,"lisi"), new Person(26,"zhangsan") ).forEach(consumer); System.out.println(JSON.toJSONString(lisiList)); }
結果為:
[{"age":23,"name":"lisi"},{"age":26,"name":"lisi"}]
與Consumer相關的接口
- BiConsumer<T, U>
處理一個兩個參數
- DoubleConsumer
處理一個double類型的參數
- IntConsumer
處理一個int類型的參數
- LongConsumer
處理一個long類型的參數
- ObjIntConsumer
處理兩個參數,且第二個參數必須為int類型
- ObjLongConsumer
處理兩個參數,且第二個參數必須為long類型
原文鏈接: