09 -Java8 中的函数式方法接口实例

1,034 阅读9分钟

Java8 中的函数式方法接口实例

通过最近写java8 新特性教程,让我对java8中的一些特性和方法又有的新的理解,之前不清楚的地方豁然开朗,同时有些读者私信给我,说对于java8中的有些方法晦涩难懂,可能简单的方法会使用了,但是在实际的工作中会遇到各种的数据处理,那个时候就不知道该用那个接口来处理,对于这种问题,这对于初学者来说都是一个普遍的问题,作者在16 刚开始接触 Java8 的时候同样是出现这种问题,要想解决这个问题无非就是记住我下面的两条建议

  • 理解函数式接口的用法和概念
  • 对于Java 本身的几种基本的函数式接口理解彻底,并做到举一反三
  • 多写代码,多练习

谨记以上三个建议,慢慢的你们就能在工作中得心应手的使用Java8的新特性了,下面我就给大家总结一些基本的函数式接口,以及这些函数式接口使用实例以及技巧,我写的这些实例,可以覆盖工作中90%的使用场景.

1. Java内置的基本六个函数接口
接口名称内置方法实例
UnaryOperator<T>T apply(T t)String::toLowerCase,Math::tan
BinaryOperator<T>T apply(T t1, T t2)BigInteger::add,Math::pow
Function<T, R>R apply(T t)Arrays::asList,Integer::toBinaryString
Predicate<T, U>boolean test(T t, U u)String::isEmpty,Character::isDigit
Supplier<T>T get()LocalDate::now,Instant::now
Consumer<T>void accept(T t)System.out::println,Error::printStackTrace

以上就是Java 内置的六种基本的函数式接口,下面我会针对不同的方法,提供不同的实例

1.1 Function 方法实例
@FunctionalInterface
public interface Function<T, R> {

      R apply(T t);

}

他接受一个参数(参数类型是T),并且返回一个类型为 R 的对象,输入的参数类型和输出的参数类型可以时不一样的,也可以时一样的

1.2 Function<T,R>

编写一个传入一个String 类型的数据,并返回一个Integer 类型的数据

  public static void main(String[] args) {

  	Function<String, Integer> function = (String x) -> x.length();
  	
  	// 以上lambda 表达式可以写成 方法引用的方式
  	//Function<String, Integer> function = String::length;
  	
  	Integer integer = function.apply("abc");

  }
1.3 Function<T,R> 函数链

Function<T,R> 类型的函数 可以当做链使用,Function 内部有三种方法,

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    /**
    * 从方法源码中可以推算出,先执行before 方法的 apply 方法,然后执行 接口的apply 方法
     */
    
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    /**
    * 从方法源码中可以推算出,先执行接口中Apply 方法,然后执行after方法的apply方法
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
    * 从方法中可以看出,返回值 和 传入的参数是同一个
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }

实例

Function<String, Integer> function1 = String::length;
//		Integer integer = function.apply("abc");


		Function<Integer, Integer> function2 = x -> x * 2;


		Function<String, String> function3 = x -> x + x;


		Integer abc = function1.andThen(function2).apply("abc");
		System.out.println(abc);
		
		Integer abc1 = function1.compose(function3).apply("abc");

		System.out.println(abc1);

有些同学可能会问,为什么 例子中 compose 方法中放到是 function3 而不是 function2?

对于提出这个问题的同学,我可能要批评你了, 首先你没有理解这两个方法的意思,然后你自己没有动手敲一敲这些代码,通过idea 编译器,你放入function2的话编译器会报错的,,至于为什么报错,这个问题,就交给同学们自己解答了,,如果自己找到问题,那么恭喜你,,你已经完全掌握这几个方法了.

1.3 通过List 转Map

先上代码

public static void main(String[] args) {

      	/*
      	*  从这个集合 转换成一个,元素为key, 元素长度为Value 的map
      	* */
		List<String> list = Arrays.asList("node", "c++", "java", "javascript");
      Map<String, Integer> collect = list.stream().collect(Collectors.toMap(x ->x,String::length);

		//Map<String, Integer> collect = list.stream().collect(Collectors.toMap(x -> x,                    //String::length, (k1, k2) -> k2));

List<String> list2 = Arrays.asList("node", "c++", "java", "javascript","java");
                                                           
                                                           
	}

以上代码就是将一个List 转成Map 的写法, 但是当我修改了List 这个集合后,这样写法就不对了.我们在list2 这个集合中增加了一个相同的元素java,试试运行我们刚才的代码

Exception in thread "main" java.lang.IllegalStateException: Duplicate key 4

运行报错,错误信息显示第四个索引位置的Key 重复, 第四个索引不就是我们刚 加入的 元素java 吗? 我们知道 map 这个集合的 key 是唯一的,当后面的key 和前面的key 重复的时候,后面的数据会覆盖前面的数据,,同样java8 中也提供了 覆盖key的写法

Map<String, Integer> collect = list.stream().collect(Collectors.toMap(x -> x,String::length,(newValue,oldValue)->oldValue));

这句代码的意思是,当发生主键重复的时候,oldVlaue 不被替换 ,,当我们将oldVlaue 换成newValue 的时候意思是 当发生key 重复的时候,新的值 会替换老的值.

2. BiFunction 方法实例
2.1 通过源码我们知道接口方法,默认接收两个参数,返回一个参数,
@FunctionalInterface
public interface BiFunction<T, U, R> {

    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    R apply(T t, U u);

   
    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

先上代码

 public static void main(String[] args) {

        // takes two Integers and return an Integer
        BiFunction<Integer, Integer, Integer> func = (x1, x2) -> x1 + x2;

        Integer result = func.apply(2, 3);

        System.out.println(result); // 5

        // take two Integers and return an Double
        BiFunction<Integer, Integer, Double> func2 = (x1, x2) -> Math.pow(x1, x2);

        Double result2 = func2.apply(2, 4);

        System.out.println(result2);    // 16.0

        // take two Integers and return a List<Integer>
        BiFunction<Integer, Integer, List<Integer>> func3 = (x1, x2) -> Arrays.asList(x1 + x2);

        List<Integer> result3 = func3.apply(2, 3);

        System.out.println(result3);

    }

2.2 BiFunction < t,u,r > + Function < t,r >

BiFunction 和 Function 组成链进行使用

 public static void main(String[] args) {

        // 传入一个
        BiFunction<Integer, Integer, Double> func1 = (a1, a2) -> Math.pow(a1, a2);

        // takes Double, returns String
        Function<Double, String> func2 = (input) -> "Result : " + String.valueOf(input);

        String result = func1.andThen(func2).apply(2, 4);

        System.out.println(result);

    }

同样我们可以自己封装成一个转换方法,更加方便我们使用

public static void main(String[] args) {

        String result = powToString(2, 4,
                (a1, a2) -> Math.pow(a1, a2),
                (r) -> "Result : " + String.valueOf(r));

        System.out.println(result); // Result : 16.0

    }

   // 这个方法 的意思是 func 方法先接收a1,a2两个参数,调用apply 方法,然后将返回的Double 数值,在传入func2 方法中

    public static <R> R powToString(Integer a1, Integer a2,
                                    BiFunction<Integer, Integer, Double> func,
                                    Function<Double, R> func2) {

        return func.andThen(func2).apply(a1, a2);

    }

我们可以将上述的放进继续修改成更加通用的一个方法

public static <A1, A2, R1, R2> R2 convert(A1 a1, A2 a2,
                                          BiFunction<A1, A2, R1> func,
                                          Function<R1, R2> func2) {

    return func.andThen(func2).apply(a1, a2);

}

这个就是通用的方法, 只要符合参数规范都可以使用这个 方法

3. BinaryOperator 方法实例

BinaryOperator 同样是一个函数式接口,它继承扩展了BiFunction,BinaryOperator 接受两个相同的参数,并返回相同类型参数的结果. 接受参数和返回参数都必须是相同的,这个也是 二进制操作与其他方法最大的区别.

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {

    // 扩展的方法
    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

  // 扩展的方法
    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

3.1

可以替换BiFunction使用

public static void main(String[] args) {

        // BiFunction
        BiFunction<Integer, Integer, Integer> func = (x1, x2) -> x1 + x2;

        Integer result = func.apply(2, 3);

        System.out.println(result); // 5

        // BinaryOperator
        BinaryOperator<Integer> func2 = (x1, x2) -> x1 + x2;

        Integer result2 = func.apply(2, 3);

        System.out.println(result2); // 5

    }

3.2 IntBinaryOperator , longBinaryOperator, doubleBinaryOperator

如果我们操作的数据都是 int 类型 ,long 类型,double 类型的话,可以 用专门的函数式接口来处理

 public static void main(String[] args) {

        int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        int result = math((numbers), 0, (a, b) -> a + b);

        System.out.println(result); // 55

        int result2 = math((numbers), 0, Integer::sum);

        System.out.println(result2); // 55

        IntStream
    }

    public static int math(int[] list, int init, IntBinaryOperator accumulator) {
        int result = init;
        for (int t : list) {
            result = accumulator.applyAsInt(result, t);
        }
        return result;
    }
4. UnaryOperator 方式实例

在java8 中 UnaryOperator是一个函数式接口,它扩展了Function 接口, 这个接口接收一个参数,并返回一个相同类型的参数,Function中的使用方式,UnaryOperator都可以使用.

直接看代码

public static void main(String[] args) {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> result = math(list, x -> x * 2);

        System.out.println(result); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

    }

    public static <T> List<T> math(List<T> list, UnaryOperator<T> uo) {
        List<T> result = new ArrayList<>();
        for (T t : list) {
            result.add(uo.apply(t));
        }
        return result;
    }
5. Predicate (谓词)

​ predicate 函数式接口, 他接受一个参数并返回一个boolean 值,长用于我们代码中的判断,先看下源码

@FunctionalInterface
public interface Predicate<T> {

   
    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

   
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

   
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

从接口中方法名字,我们基本上都知道这些方法的意思,所以这里我就不做解释了,,不理解的可以敲一下我写的代码,验证

5.1 filter(Predicate predicate)

​ 这个是stream 中的方法,这个方法接收一个predicate方法 作为参数

public static void main(String[] args) {

       List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

       List<Integer> collect = list.stream().filter(x -> x > 5).collect(Collectors.toList());

       System.out.println(collect); // [6, 7, 8, 9, 10]

   }

以上代码是一个直接写法,我们可以吧predicate 方法单独定义出来

public static void main(String[] args) {

        Predicate<Integer> noGreaterThan5 =  x -> x > 5;

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> collect = list.stream()
                .filter(noGreaterThan5)
                .collect(Collectors.toList());

        System.out.println(collect); // [6, 7, 8, 9, 10]

    }
5.2 多个过滤器使用
  • predicate.and()
// java8之前的写法
public static void main(String[] args) {
			
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // multiple filters
        List<Integer> collect = list.stream()
                .filter(x -> x > 5 && x < 8).collect(Collectors.toList());

        System.out.println(collect);

    }

// java8之后的写法
 public static void main(String[] args) {

        Predicate<Integer> noGreaterThan5 = x -> x > 5;
        Predicate<Integer> noLessThan8 = x -> x < 8;

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> collect = list.stream()
                .filter(noGreaterThan5.and(noLessThan8))
                .collect(Collectors.toList());

        System.out.println(collect);

    }

同理 || 符合可以用or() 替换, != 可以用 negate () 替换

我们还可以将多个 predicate链接起来使用

public static void main(String[] args) {

        Predicate<String> startWithA = x -> x.startsWith("a");

        // start with "a" or "m"
        boolean result = startWithA.or(x -> x.startsWith("m")).test("mkyong");
        System.out.println(result);     // true

        // !(start with "a" and length is 3)
        boolean result2 = startWithA.and(x -> x.length() == 3).negate().test("abc");
        System.out.println(result2);    // false

    }
6 . BiPredicate

Bipredicate是一个函数式接口,基本上用法和 Predicate 是一致的,区别在于Bipredicate 接受两个参数,

  • 实例1
public static void main(String[] args) {

        BiPredicate<String, Integer> filter = (x, y) -> {
            return x.length() == y;
        };

        boolean result = filter.test("mkyong", 6);
        System.out.println(result);  // true

        boolean result2 = filter.test("java", 10);
        System.out.println(result2); // false
    }
  • 实例2 predicate 作为一个参数传入

    public class JavaBiPredicate2 {
    
        public static void main(String[] args) {
    
            List<Domain> domains = Arrays.asList(new Domain("google.com", 1),
                    new Domain("i-am-spammer.com", 10),
                    new Domain("baidu.com", 0),
                    new Domain("microsoft.com", 2));
    
            BiPredicate<String, Integer> bi = (domain, score) -> {
                return (domain.equalsIgnoreCase("google.com") || score == 0);
            };
    
            // if google or score == 0
            List<Domain> result = filterBadDomain(domains, bi);
            System.out.println(result); //
            //  if score == 0
            List<Domain> result2 = filterBadDomain(domains, (domain, score) -> score == 0);
            System.out.println(result2); // mkyong.com, microsoft.com
    
            // if start with i or score > 5
            List<Domain> result3 = filterBadDomain(domains, (domain, score) -> domain.startsWith("i") && score > 5);
            System.out.println(result3); // 
    
            // chaining with or
            List<Domain> result4 = filterBadDomain(domains, bi.or(
                    (domain, x) -> domain.equalsIgnoreCase("microsoft.com"))
            );
            System.out.println(result4); // 
    
    
        }
    
        public static <T extends Domain> List<T> filterBadDomain(
                List<T> list, BiPredicate<String, Integer> biPredicate) {
    
            return list.stream()
                    .filter(x -> biPredicate.test(x.getName(), x.getScore()))
                    .collect(Collectors.toList());
    
        }
    }
    
    class Domain {
    
        String name;
        Integer score;
    
        public Domain(String name, Integer score) {
            this.name = name;
            this.score = score;
        }
        // getters , setters , toString
    }
    
7 . Consumer (消费者)

Consumer 是一个函数式接口,他接受一个参数,但是不返回任何值,我们可以看做是终端操作

public static void main(String[] args) {

        Consumer<String> print = x -> System.out.println(x);
        print.accept("java");   // java

    }

以上是一个最简单的 消费者接口

7.1 接受一个 consumer 接口作为参数
public static void main(String[] args) {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        // implementation of the Consumer's accept methods.
        Consumer<Integer> consumer = (Integer x) -> System.out.println(x);
        forEach(list, consumer);

        // or call this directly
        forEach(list, (Integer x) -> System.out.println(x));

    }

    static <T> void forEach(List<T> list, Consumer<T> consumer) {
        for (T t : list) {
            consumer.accept(t);
        }
    }
7.2 BiConsumer

BiConsumerConsumer 接口函数的扩展,与其他Bi 开头的其他函数式接口一样,BiConsumer 函数式接口接受两个参数, 而且不返回结果

public static void main(String[] args) {

      BiConsumer<Integer, Integer> addTwo = (x, y) -> System.out.println(x + y);
      addTwo.accept(1, 2);    // 3

    }
8.supplier (提供者)

java8 中,Supplier 是一个函数式接口,它不接受参数并返回结果

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
8.1 通过Supplier 返回当前日期时间
public class Java8Supplier1 {

    private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {

        Supplier<LocalDateTime> s = () -> LocalDateTime.now();
        LocalDateTime time = s.get();

        System.out.println(time);

        Supplier<String> s1 = () -> dtf.format(LocalDateTime.now());
        String time2 = s1.get();

        System.out.println(time2);

    }

}
8.2 返回一个 Supplier
ublic class Java8Supplier2<T> {

    public static void main(String[] args) {

        Java8Supplier2<String> obj = new Java8Supplier2();

        List<String> list = obj.supplier().get();

    }

    public Supplier<List<T>> supplier() {

        // lambda
        // return () -> new ArrayList<>();

        // constructor reference
        return ArrayList::new;

    }
8.3 返回一个Developer 工厂
public class Java8Supplier3 {

    public static void main(String[] args) {

        Developer obj = factory(Developer::new);
        System.out.println(obj);

        Developer obj2 = factory(() -> new Developer("mkyong"));
        System.out.println(obj2);

    }

    public static Developer factory(Supplier<? extends Developer> s) {

        Developer developer = s.get();
        if (developer.getName() == null || "".equals(developer.getName())) {
            developer.setName("default");
        }
        developer.setSalary(BigDecimal.ONE);
        developer.setStart(LocalDate.of(2017, 8, 8));

        return developer;

    }

}


public class Developer {

    String name;
    BigDecimal salary;
    LocalDate start;

    // for factory(Developer::new);
    public Developer() {
    }

    // for factory(() -> new Developer("mkyong"));
    public Developer(String name) {
        this.name = name;
    }

    // get, set, constructor, toString
    //...

}