Java8內置的函數式編程接口應用場景和方式


首先,我們先定義一個函數式編程接口

@FunctionalInterface
public interface BooleanFunctionalInterface<T> {
    boolean test(T t);
}

很簡單,該接口的唯一一個抽象方法(並且非Object類的方法)返回值為boolean

下面,定義一個方法,接受一個List,利用實現了該接口的test方法的對象,篩選出需要的元素:

import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class Filter<T> {
    public static <T> List<T> filter(List<T> list, BooleanFunctionalInterface b){
        if (CollectionUtils.isEmpty(list)){
            return new ArrayList<>(0);
        }

        List<T> result = new ArrayList<>(list.size());
        for(int i=0; i<list.size(); i++){
            T t = list.get(i);
            if (b.test(t)) {
                result.add(t);
            }
        }

        return result;
    }
}

 測試類,篩選出年齡大於25的People對象:

public class FunctionalInterfaceTest {
    private List<People> peopleList = new ArrayList<>();
    @Before
    public void init(){
        peopleList.add(new People("LuoTianyan",23));
        peopleList.add(new People("ff",26));
        peopleList.add(new People("Tony",33));
    }
    /**
     * 自定義函數式接口
     */
    @Test
    public void testUserDefined(){
        List<People> filter = Filter.filter(peopleList, p -> ((People) p).getAge() > 25);
        filter.forEach(System.out::println);
        /*People(name=ff, age=26)
        People(name=Tony, age=33)*/
    }
}
import lombok.*;

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class People {
    private String name;
    private int age;
}

 

JDK中已有的函數式接口

上面的自定義的函數式接口,返回boolean,其實在Java8中已經有該類型的接口,那就是Predicate。

Predicate<T> 接口

該接口定義了一個支持泛型的boolean test( T)的抽象方法,其函數描述符為 (T)-> boolean,現在我們就可以直接使用Predicate<T>接口來替代上面自定義的接口了。

在上面的Filter類中追加一個方法,修改這里形參為Predicate。

public static <T> List<T> predicate(List<T> list, Predicate predicate){
        if (CollectionUtils.isEmpty(list)){
            return new ArrayList<>(0);
        }

        List<T> result = new ArrayList<>(list.size());
        for(int i=0; i<list.size(); i++){
            T t = list.get(i);
            if (predicate.test(t)) {
                result.add(t);
            }
        }

        return result;
    }

獲取Age>25的People對象,測試如下:

/**
     * Java8內置的函數式編程接口Predicate,返回boolean類型
     */
    @Test
    public void testPredicate(){
        List<People> predicate = Filter.predicate(peopleList, p -> ((People) p).getAge() > 25);
        predicate.forEach(System.out::println);
    }

 

Consumer<T>接口

該接口定義了一個void accept(T)的抽象方法,其函數描述符為 (T) -> void,如果你需要一個操作某一對象,但無需返回的的函數式接口,那么就可以使用Consumer<T>接口。

追加方法,形參為Consumer:

public static <T> void consumer(List<T> list, Consumer consumer){
        if (CollectionUtils.isEmpty(list)){
            return ;
        }

        List<T> result = new ArrayList<>(list.size());
        for(int i=0; i<list.size(); i++){
            T t = list.get(i);
            consumer.accept(t);
        }

    }

下面實現修改所有Age為18,並且輸出,測試如下: 

/**
     * Java8內置的函數式編程接口Consumer,直接消費無返回值
     */
    @Test
    public void testConsumer(){
        // setAge操作不需要返回值
        Consumer<People> setAgeConsumer = p -> ((People) p).setAge(18);
        Filter.consumer(peopleList, setAgeConsumer);

        // 輸出操作不需要返回值
        Consumer<People> sout = p -> System.out.println((People)p);
        Filter.consumer(peopleList, sout);
        /*People(name=LuoTianyan, age=18)
        People(name=ff, age=18)
        People(name=Tony, age=18)*/
    }

 

Supplier<T>接口

 

既然有消費者接口(Consumer<T>),那就要有生產者接口(Supplier<T>),該接口定義了一個 T get() 的抽象方法,其函數描述符為 () -> T,如果不接受入參,直接為我們生產一個指定的結果,那么就可以用Supplier<T>。

追加方法,形參Supplier

public static <T> List<T> listFactory(int count, Supplier<T> supplier){

        List<T> result = new ArrayList<>(count);
        for(int i=0; i<count; i++){
            T t = supplier.get();
            result.add(t);
        }

        return result;
    }

下面生成count個對象,設置對象默認屬性值:

/**
     * Java8內置的函數式編程接口supplier,無形參,返回對象
     */
    @Test
    public void testSupplier(){
        // 生成對象
        Supplier<People> peopleSupplier = () -> new People("init",18);
        List<People> people = Filter.listFactory(5, peopleSupplier);

        // 輸出操作不需要返回值
        Consumer<People> sout = p -> System.out.println((People)p);
        Filter.consumer(people, sout);
        /*People(name=init, age=18)
        People(name=init, age=18)
        People(name=init, age=18)
        People(name=init, age=18)
        People(name=init, age=18)*/
    }

 

Function<T,R>接口

該接口定義了一個 R apply(T)類型的抽象函數,它接受一個泛型變量T,並返回一個泛型變量R,如果你需要將一個對象T映射成R,那么就可以使用Function<T,R>接口。

下面,我們將對象轉化為String類型的例子

public static <T> List<String> function(List<T> list, Function<T,String> function){
        if (CollectionUtils.isEmpty(list)){
            return new ArrayList<>(0);
        }

        List<String> result = new ArrayList<>(list.size());
        for(int i=0; i<list.size(); i++){
            T t = list.get(i);
            String apply = function.apply(t);
            result.add(apply);
        }

        return result;
    }

將People對象,轉換為的字符串輸出:
/**
     * Java8內置的函數式編程接口Function,接受形參T,轉換為對象R
     */
    @Test
    public void testFunction(){
        // 將People對象,轉換為如下形式的字符串
        Function<People, String> function = (People p) -> "name:" + p.getName() + "  , age:" + p.getAge();
        List<String> strings = Filter.function(peopleList, function);

        // 輸出操作不需要返回值
        Consumer<String> sout = p -> System.out.println(p);
        Filter.consumer(strings, sout);
        /*name:LuoTianyan  , age:23
        name:ff  , age:26
        name:Tony  , age:33*/
    }

 

上面代碼的優化

由於上面的people是list集合,可以直接利用stream的形式;

比如People對象轉換成字符串

@Test
    public void testListStream(){
        List<String> collect = peopleList.stream()
                .map(p -> p.getName() + p.getAge())
                .collect(Collectors.toList());

        collect.forEach(System.out::println);
        /*LuoTianyan23
         ff26
        Tony33*/
    }

 

 

Java 8 中函數式接口列表

現在我們給出一份較為全的函數式接口與描述符對應的接口聲明列表:

 

 

    函數式接口     函數描述符
Predicate<T>   (T)  -> boolean
Consumer<T>   (T)  -> void
Function< T, R >   (T)  -> R
Supplier<T>   ( )  -> T
UnaryOperator<T>    (T)  ->  T
BinaryOperator<T>   (T, T) -> T
BiPredicate<L, R>   (L, R)  -> boolean
BiConsumer<T, U>   (T, U)  -> void
BiFunction<T, U, R>   (T, U)  -> R

 

 需要的函數式接口沒有被覆蓋,可以根據JDK中的聲明來編寫適合自己使用的函數式接口聲明。

 

BiFunction例子

 

 

關於裝箱與拆箱

泛型的使用使得函數式接口有了更高的靈活性,我覺得這里應該先說一下參數化,參數化是相對於硬編碼來說的 ,如我們常用的函數聲明具有參數列表lambda表達式采用了代碼參數化技術泛型則使用了類型參數化技術,參數化是代碼走向通用的方法 ,同時也是編程抽象的一種體現。

 

為了程序員的方便,JDK中提供了現成的支持泛型的函數式接口,但是由於泛型的支持,使得接口也會存在一些性能浪費的問題。我們知道Java泛型只能支持引用類型,也就是對象,不支持原始類型(int、double、char等),在 Java SE5之前Java程序員在泛型中使用原始類型時,只能通過其對應的引用類型(Interger、Double、Charactor)來替換,並且需要使用函數式式的方式進行原始類型到引用類型的轉換,如 Integer i = new Integer(10),從 Java SE5開始,Java支持自動裝箱拆箱技術,通過賦值操作,便可以將原始類型包裝成引用類型如Integer i = 10,相對的自動拆箱便是將引用類型轉為原始類型。

 

但是這樣的特性也會帶來犧牲性能的代價,裝箱的本質是將原始類型包裹起來生成一個對象出來,並將原始類型的值保存到該對象中,相對於原始類型包裝的過程和內存的占用都會相應的提高,並且在很多情況下我們使用原始類型就足夠了。為此,Java 8 提供了一批避免原始類型裝箱的函數式接口。例如IntPredicate、IntConsumer、DoublePredicate、IntFunction等,使用原始類型作為接口命名的前綴便是對應的避免裝箱的函數式接口聲明。

查看具體聲明,讀者應該可以發現,這些接口的函數描述符完全沒變,只是泛型使用了具體的原始類型來替代,如下:

 

 

來源:https://mp.weixin.qq.com/s?__biz=MzIzMzgxOTQ5NA==&mid=2247483845&idx=1&sn=08990fd78e4f62ddf38238660cc4dd64&chksm=e8fe9dccdf8914da580e13a9fc5fee64c135f55d11f3c2de5731f052fa9f1a39060ed6375110&scene=21#wechat_redirect

Java8 BiFunction


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM