簡析JAVA8函數式接口


一,定義

“有且只有一個抽象方法的接口”----函數式接口的定義。
@FunctionalInterface
public interface Ifun{
    void test();
}

如上就是一個簡單的函數式接口的定義。@FunctionalInterface就表示這是一個函數式接口,你不定義也可以,其實這就是個編譯選項,用來強制驗證你到底是不是函數式接口。像這樣的接口,我們就可以使用Lambda表達式來進行函數式編程,調用方式為()-{},如下:

public class Test {
    public static void funTest(Ifun ifun) {
        ifun.test();
    }

    public static void main(String[] args) {
        Test.funTest(() -> {
            System.out.println("hello world!");
        });
    }
}

out:

"hello world!"

代碼還是比較簡單的,現在開發用到最多的場景還是流這一塊的操作,替換了以前的比較丑陋的一些遍歷操作,你可以說他只是一種語法糖的包裝,但是不可否認確實帶來代碼上的簡潔和書寫上的流暢。

具體我自己的體會就是:

函數式接口只提供了數據和行為,但是行為的實現放在每個具體的業務場景中。

這個其實有點像匿名內部類的變種的感覺,它只是表現形式上和匿名內部類相似,但是和它又有些不一樣,它提供了更加豐富的操作過程。

下面通過JAVA四大內置函數式接口,結合例子來具體了解下這句話。

二,四大內置函數式接口

1. Consumer接口

上面是機翻,雖然有些句子表述不太准確,但是大概的意思我們都能理解,消費接口就是一個接受參數然后進行消費處理的操作,甚至於把異常處理的過程都寫了很清楚。下面看個例子:

public class Test {
    public static void consumerIt(Consumer<String> cook, Consumer<String> eat) {
        cook.andThen(eat).accept("煮飯,吃飯");
    }

    public static void main(String[] args) {
        Test.consumerIt((i) -> {
            System.out.println(i.split(",")[0]);
        }, (i) -> {
            System.out.println(i.split(",")[1]);
        });
    }
}

out:
煮飯
吃飯

你得先煮飯,才能吃飯,在函數定義中我們就已經把這個順序給固定了,同時給了入參,所以我們后續只要把對應的每個步驟的具體實現過程進行處理就行了。這邊要注意的一點是,這里雖然有先后順序,但是沒有因果關系,不知道這么表述有沒有問題,意思就是andThen里after的入參跟之前的入參是一樣的,不是說你before里執行處理過的數據再給after,after里的也是原始入參,這一點其實在源碼中已經寫的很清楚,而且Consumer接口本來也只執行處理,並沒有返回,所以這一點一定要弄清楚,不小心有時會弄混。

  • 源碼應用

    /**
     * 檢查spring容器里是否有對應的bean,有則進行消費
     *
     * @param clazz    class
     * @param consumer 消費
     * @param <T>      泛型
     */
    private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {
        if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
            consumer.accept(this.applicationContext.getBean(clazz));
        }
    }
        // TODO 注入填充器
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        // TODO 注入主鍵生成器
        this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(i));
        // TODO 注入sql注入器
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        // TODO 注入ID生成器
        this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);

Mybatis-plus這一段查找bean進行消費的處理應用的就是很簡單但是經典。

2.Supplier接口

Supplier接口很簡單,就一個無參的get方法,用來獲取一個泛型對象數據,說白了就是提供一個數據生成。下面看個例子:

public class Test {
    public static String supplierGet(Supplier<String> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        System.out.println(Test.supplierGet(() -> "hello world!"));
    }
}

out:
hello world!

  • 源碼應用

public class ClientHttpRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {

	private static final Map<String, String> REQUEST_FACTORY_CANDIDATES;

	static {
		Map<String, String> candidates = new LinkedHashMap<>();
		candidates.put("org.apache.http.client.HttpClient",
				"org.springframework.http.client.HttpComponentsClientHttpRequestFactory");
		candidates.put("okhttp3.OkHttpClient", "org.springframework.http.client.OkHttp3ClientHttpRequestFactory");
		REQUEST_FACTORY_CANDIDATES = Collections.unmodifiableMap(candidates);
	}

	@Override
	public ClientHttpRequestFactory get() {
		for (Map.Entry<String, String> candidate : REQUEST_FACTORY_CANDIDATES.entrySet()) {
			ClassLoader classLoader = getClass().getClassLoader();
			if (ClassUtils.isPresent(candidate.getKey(), classLoader)) {
				Class<?> factoryClass = ClassUtils.resolveClassName(candidate.getValue(), classLoader);
				return (ClientHttpRequestFactory) BeanUtils.instantiateClass(factoryClass);
			}
		}
		return new SimpleClientHttpRequestFactory();
	}

}

springboot中ClientHttpRequestFactory的生成就是實現的Supplier接口,它基於類路徑上的可用實現來檢測首選候選對象。

3.Function接口


Function接口表示用來接受一個參數並產生結果的函數,同時跟Consumer接口很相似,他也有一個后置函數andThen,同時還有一個前置函數compose

Function接口乍一看跟Consumer接口很像,但是區別是他是有返回參數的,而Consumer接口沒有,他是有因果關系。具體我們看下例子:

public class Test {
    public static String funGet(Function<String, String> before, Function<String, String> function, Function<String, String> after) {
        return function.compose(before).andThen(after).apply("煮飯,吃飯,洗碗");
    }

    public static void main(String[] args) {
        System.out.println(Test.funGet((t) -> {
            System.out.println(t.split(",")[0]);
            t = t.substring(t.indexOf(",") + 1);
            return t;
        }, (t) -> {
            System.out.println(t.split(",")[0]);
            t = t.substring(t.indexOf(",") + 1);
            return t;
        }, (t) -> t));
    }
}

out:
煮飯
吃飯
洗碗

上面就看出來了,他是before->fun->after這么一個流程,並且每一步的輸入都是上一步的輸出,這樣就很清楚了。

  • 源碼應用

  @Test
  void testWithCallable() {
    test(mapper -> mapper.getUserWithCallableAndUnset(new RowBounds(5, 3)), 0);
    test(mapper -> mapper.getUserWithCallableAndDefault(new RowBounds(4, 3)), 1);
    test(mapper -> mapper.getUserWithCallableAndForwardOnly(new RowBounds(3, 3)), 2);
    test(mapper -> mapper.getUserWithCallableAndScrollInsensitive(new RowBounds(2, 2)), 2);
    test(mapper -> mapper.getUserWithCallableAndScrollSensitive(new RowBounds(1, 1)), 1);
  }

  private void test(Function<Mapper, List<User>> usersSupplier, int expectedSize) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      Mapper mapper = sqlSession.getMapper(Mapper.class);
      List<User> users = usersSupplier.apply(mapper);
      Assertions.assertEquals(expectedSize, users.size());
    }
  }

mybaits里這一段單元測試執行mapper來獲得userList就用的很經典,雖然沒有用到before和after,但是確實常用的就是apply更多一點,再有就是其實我們現實開發中用到最多的funciton接口應該是map。

類似於valueList.stream().map(ValueText::getValue).collect(Collectors.toSet())這種我們常用的map操作,其實操作的就是一個funciton。

4.Predicate接口


我們先來看下Predicate的字面意思:數理邏輯中表示一個個體的性質和兩個或兩個以上個體間關系的詞。看起來似明非明的,其實很簡單就是個true or false。

其實就是用來做表達式驗證的,輸入一個參數,返回表達式是否成立的結果true or false。 具體我們看下例子:

public class Test {
    public static boolean preGet(Predicate<String> predicate) {
        return predicate.test("我是帥哥");
    }

    public static void main(String[] args) {
        System.out.println(Test.preGet((t) -> t.contains("帥哥")
        ));
    }
}

out:

true

Predicate接口還是比較容易理解的,其它幾個方法:andnegateorisEqual光從字面上都能理解,無非就是常用的判斷用語,所以就不一一展示了。

  • 源碼應用

abstract class IsTestableMethod implements Predicate<Method> {

	private final Class<? extends Annotation> annotationType;
	private final boolean mustReturnVoid;

	IsTestableMethod(Class<? extends Annotation> annotationType, boolean mustReturnVoid) {
		this.annotationType = annotationType;
		this.mustReturnVoid = mustReturnVoid;
	}

	@Override
	public boolean test(Method candidate) {
		// Please do not collapse the following into a single statement.
		if (isStatic(candidate)) {
			return false;
		}
		if (isPrivate(candidate)) {
			return false;
		}
		if (isAbstract(candidate)) {
			return false;
		}
		if (returnsVoid(candidate) != this.mustReturnVoid) {
			return false;
		}

		return isAnnotated(candidate, this.annotationType);
	}

}

junit中的IsTestableMethod就是比較典型的用法,實現test方法去判斷測試方法的類型。其實我們常用的stream操作filter(m -> m.getValue() > 1)中的filter方法用來過濾就是用的Predicate。

三,其它

java.util.function包下其實不僅僅是這四大接口,其它也有其它好幾個它們的變種,無非是把泛型改成具體類型等一系列操作,有興趣的話,具體可以到對應的路徑下去查看。


免責聲明!

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



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