Java8之函數式接口及常用函數式接口講解

函數式接口

1.概念

函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。

函數式接口可以被隱式轉換為 lambda 表達式。

Lambda 表達式和方法引用(實際上也可認為是Lambda表達式)上。

2.@FunctionalInterface

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

我們常用的Runnable接口就是個典型的函數式接口,我們可以看到它有且僅有一個抽象方法run。並且可以看到一個註解@FunctionalInterface,這個註解的作用是強制你的接口隻有一個抽象方法。

如果有多個話直接會報錯,如圖:

idea錯誤提示:

編譯時錯誤提示:

這裡當你寫瞭第二個方法時,編譯就無法通過,idea甚至在編碼階段就行瞭提示。

3.函數式接口使用方式

我們直接上代碼,首先定義一個函數式接口

@FunctionalInterface
public interface SingleAbstraMethodInterface {
    public abstract void singleMethod();
}

我們定一個test類,封裝一個方法,將SingleAbstraMethodInterface當做參數傳入方法,並打印一句話

public class Test {
    public void testMethod(SingleAbstraMethodInterface single){
        System.out.println("即將執行函數式接口外部定義方法");
        single.singleMethod();
    }
    public static void main(String[] args) {
        Test test = new Test();
        test.testMethod(new SingleAbstraMethodInterface() {
            @Override
            public void singleMethod() {
                System.out.println("執行函數式接口定義方法");
            }
        });
    }
}

執行結果:

即將執行函數式接口外部定義方法
執行函數式接口定義方法

是不是和我們預期結果一樣。這個過程是不是有的同學已經發現,怎麼這麼像jdk裡面的一些接口的使用,比如常用的Runnable接口。

我們來看看代碼。

public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("run方法執行瞭");
            }
        }).start();
}

再看下Runnable接口源碼,是不是一樣

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

這時,有的同學會說,我用Runnable接口可不是這麼用的,我的寫法比你優雅,我是這麼寫的。

沒錯函數式接口,函數式編程,我們可以使用lambda表達式的寫法,可以讓代碼更優雅,具體可以參照我的前一篇帖子。Java8之Lambda表達式

public static void main(String[] args) {
        new Thread(()-> {
           System.out.println("run方法執行瞭");
        }).start();
}

常用函數式接口

1.JDK提供的函數式接口舉栗

java.lang.Runnable,
java.awt.event.ActionListener,
java.util.Comparator,
java.util.concurrent.Callable

java.util.function包下的接口,如Consumer、Predicate、Supplier等

其實,jdk中給我們提供瞭很多的函數式接口,我們平時都會用到,隻不過大傢沒有註意到而已,這裡我結合實際代碼講解幾個常用的函數式接口。想想大傢平時常常用到stream流的各種方法來處理list。我看去stream類中看一下它提供的方法。可以看到有幾個出鏡率較高的函數式接口

  • Supplier
  • Comsumer
  • Predicate
  • Function

2.Supplier

@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     * @return a result
     */
    T get();
}

Supplier接口的get方法沒有入參,返回一個泛型T對象。

我們來看幾個使用 的實例。

先寫一個實驗對象girl,我們定義一個方法,創建對象,我們使用lambda的方式來調用並創建對象。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Girl {

    private String name;

    private Double size;

    private Double price;

    public static Girl create(final Supplier<Girl> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
    	//創建對象
        Girl girl1 = Girl.create(Girl::new);
        Girl girl2 = Girl.create(()-> new Girl("lily",33d,1700d));
        Girl girl = null;
        //orElseGet
        Girl girl3 = Optional.ofNullable(girl).orElseGet(Girl::new);
    }
    
}

需要註意的是,我沒每調用一次get方法,都會重新創建一個對象

orElseGet方法入參同樣也是Supplier,這裡對girl實例進行判斷,通過Supplier實現瞭懶加載,也就是說隻有當判斷girl為null時,才會通過orElseGet方法創建新的對象並且返回。不得不說這個設計是非常的巧妙。

3.Consumer

源碼如下:

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * 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); };
    }
}

可以看到accept方法接受一個對象,沒有返回值。

那麼我們來實戰,首先使用Lambda表達式聲明一個Supplier的實例,它是用來創建Girl實例;再使用Lambda表達式聲明一個Consumer的實例,它是用於打印出Girl實例的toString信息;最後Consumer消費瞭Supplier生產的Girl。

我們常用的forEach方法入參也是Consumer。

使用代碼如下:

	Supplier<Girl> supplier = ()-> new Girl("lucy",33d,1700d);
	Consumer<Girl> consumer = (Girl g)->{
	   System.out.println(g.toString());
	};
	consumer.accept(supplier.get());
	ArrayList<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
    list.forEach(System.out::println);

4.Predicate

@FunctionalInterface
public interface Predicate<T> {

    /**
     * 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方法接受一個對象,返回boolean類型,這個函數顯然是用來判斷真假。

那麼我們用這個接口來構造一個判斷女孩子條件的示例,還有我們常用的stream流中,filter的入參也是Predicate,代碼如下:

		Supplier<Girl> supplier = ()-> new Girl("lucy",33d,1700d);
        Predicate<Girl> girl36d = (Girl g)-> Objects.equals(g.getSize(),36d);
        Predicate<Girl> girl33d = (Girl g)-> Objects.equals(g.getSize(),33d);
        boolean test33 = girl33d.test(supplier.get());
        boolean test36 = girl36d.test(supplier.get());
        System.out.println(supplier.get().getName() +"是否為[36d] :"+test36);
        System.out.println(supplier.get().getName() +"是否為[33d] :"+test33);

結果為:

lucy是否為[36d] :false
lucy是否為[33d] :true

		ArrayList<Girl> list = Lists.newArrayList();
        list.add(new Girl("露西", 33d, 2000d));
        list.add(new Girl("格蕾絲", 36d, 3000d));
        list.add(new Girl("安娜", 28d, 1500d));
        list.add(new Girl("克瑞斯", 31d, 1800d));
        Predicate<Girl> greaterThan30d = (Girl g)-> g.getSize()>=30d;
        list.stream().filter(greaterThan30d).forEach(System.out::println);

結果如下:

Girl(name=露西, size=33.0, price=2000.0)
Girl(name=格蕾絲, size=36.0, price=3000.0)
Girl(name=克瑞斯, size=31.0, price=1800.0)

5.Function

@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);
}

這個看到apply接口接收一個泛型為T的入參,返回一個泛型為R的返回值,所以它的用途和Supplier還是略有區別。還是一樣我們舉個栗子用代碼來說明:

Supplier<Girl> supplier = ()-> new Girl("lucy",33d,1700d);
 Function<Girl,String> girlMark = (Girl g)-> g.getName()+"的尺寸是"+g.getSize()+",每次能賺"+g.getPrice()+"。";
 System.out.println(girlMark.apply(supplier.get()));

我們可以看到funtion接口接收Girl對象實例,對girl的成員變量進行拼接,返回girl的描述信息。其基本用法就是如此。

lucy的尺寸是33.0,每次能賺1700.0。

6.常用函數式接口相關擴展接口

這裡我列舉一些Supplier相關接口

a.Supplier相關拓展接口

接口名稱 方法名稱 方法簽名
Supplier get () -> T
BooleanSupplier getAsBoolean () -> boolean
DoubleSupplier getAsDouble () -> double
IntSupplier getAsInt () -> int
LongSupplier getAsLong () -> long

b.Comsumer相關拓展接口

接口名稱 方法名稱 方法簽名
Consumer accept (T) -> void
DoubleConsumer accept (double) -> void
IntConsumer accept (int) -> void
LongConsumer accept (long) -> void
ObjDoubleConsumer accept (T, double) -> vo
ObjIntConsumer accept (T, int) -> void
ObjLongConsumer accept (T, long) -> void

c.Predicate相關拓展接口

接口名稱 方法名稱 方法簽名
Predicate test (T) -> boolean
BiPredicate test (T, U) -> boolean
DoublePredicate test (double) -> bool
IntPredicate test (int) -> boolean
LongPredicate test (long) -> boolean

d.Function相關的接口

接口名稱 方法名稱 方法簽名
Function apply (T) -> R
BiFunction apply (T, U) -> R
DoubleFunction apply (double) -> R
DoubleToIntFunction applyAsInt (double) -> int
DoubleToLongFunction applyAsLong (double) -> long
IntFunction apply (int) -> R
IntToDoubleFunction applyAsDouble (int) -> double
IntToLongFunction applyAsLong (int) -> long
LongFunction apply (long) -> R
LongToDoubleFunction applyAsDouble (long) -> double
LongToIntFunction applyAsInt (long) -> int
ToDoubleFunction applyAsDouble (T) -> double
ToDoubleBiFunction applyAsDouble (T, U) -> double
ToIntFunction applyAsInt (T) -> int
ToIntBiFunction applyAsInt (T, U) -> int
ToLongFunction applyAsLong (T) -> long
ToLongBiFunction applyAsLong (T, U) -> long

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: