一,定义

“有且只有一个抽象方法的接口”----函数式接口的定义。
@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四大内置函数式接口,结合例子来具体了解下这句话。

二,四大内置函数式接口

简析JAVA8函数式接口-LMLPHP

1. Consumer接口

简析JAVA8函数式接口-LMLPHP

上面是机翻,虽然有些句子表述不太准确,但是大概的意思我们都能理解,消费接口就是一个接受参数然后进行消费处理的操作,甚至于把异常处理的过程都写了很清楚。下面看个例子:

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接口

简析JAVA8函数式接口-LMLPHP

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接口

简析JAVA8函数式接口-LMLPHP
简析JAVA8函数式接口-LMLPHP

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接口

简析JAVA8函数式接口-LMLPHP
简析JAVA8函数式接口-LMLPHP

我们先来看下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包下其实不仅仅是这四大接口,其它也有其它好几个它们的变种,无非是把泛型改成具体类型等一系列操作,有兴趣的话,具体可以到对应的路径下去查看。

简析JAVA8函数式接口-LMLPHP

04-27 21:37