函数式接口与Lambda表达式的详解

392次阅读
没有评论

Java 自 jdk8 之后,引入了一种新的思想:函数式编程思想。什么是函数式编程思想?说白了就是注重结果,但是忽略过程。所以这里就有两个比较重要的知识点,一个是 Lambda 表达式,另一个是函数式接口。

在这里,我会先讲下函数式接口,然后再说下这个 Lambda 表达式,这个很重要。因为Lambda 表达式的使用前提是,这个使用对象它必须有接口且必须是函数式接口,我还记得上一次在学校上课的时候,我们老师在这一点上就讲的非常含糊,所以我会在本文详解讲解下。

1, 什么是函数式接口?

函数式接口,其实就是一个接口,它有且仅有一个抽象方法,这样我们就可以称它为函数式接口。函数式接口虽然只能有一个抽象方法,但它也可以包含多个默认方法或静态方法。

// 只有一个抽象方法,那么它就是一个函数式接口
@FunctionalInterface
public interface Runnable {
    // 抽象方法
    public abstract void run();
 /*/}

在 java 中,还提供了注解 @Functionallnterface 用于检测一个接口它是否为函数式接口。当然,我们在定义函数式接口的时候,这个注解不是必需的。

2,内置的函数式接口

Java8 默认给我们提供了大量的函数式接口,但我们通常重点学习的是以下这四大函数式接口:

  • Supplier 接口 – 供应型
  • Consumer 接口 – 消费型
  • Predicate 接口 – 比较型
  • Function 接口 – 转化型

a. Supplier 接口

我们讲通俗点,Supplier 接口,它就相当于一个供应商,它不接受任何参数,只给你提供结果,提供你想要的数据。

@FunctionalInterface
public interface Supplier<T> {
    // 返回数据
    T get();
 /**/}

使用例子:

public class SupplierDemo {/**/
    public static void main(String[] args) {/**/
        getMax(new Supplier<Integer>() {/**/
            @Override
            public Integer get() {/**/
                Integer[] integers = new Integer[]{1,8,6,2,3};
                return integers[integers.length-1];
            }
        });
    }
    public static void getMax(Supplier<Integer> supplier) {/**/
        Integer max = supplier.get();
        System.out.println("Max 值是" + max);
    }
} // 运行结果:3

b. Consumer 接口

既然有供应,那对应的是不是也得有消费?Consumer 接口就相当于一个消费者,去 ” 消费我们的数据 ”,即对数据进行一些操作或使用它。而具体要如何消费,就写在我们 accept 方法里。

@FunctionalInterface
public interface Consumer<T> { //

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) { //
        Objects.requireNonNull(after);
        return (T t) -> {accept(t); after.accept(t); };
    }
}

使用例子:

public class ConsumerDemo {//
    public static void main(String[] args) {//
        echoString(new Consumer<String>() {//
            @Override // 消费一个字符串数据
            public void accept(String s) {//
                StringBuilder stringBuilder = new StringBuilder(s);
                System.out.println(stringBuilder.reverse()); // 字符串倒序
            }
        });
    }
    public static void echoString(Consumer<String> consumer) {//
        consumer.accept("LUJIA IS ME");
    }
} // 运行结果: EM SI AIJUL 

c. Predicate 接口

Predicate 接口,它就相当于是一个比较器,对给定数据进行比较或判断,返回一个布尔值。

并且 Predicate 接口默认还提供了很多的默认方法,我认为非常实用,我们主要重点学习下面这个几个:

default Predicate<T> and(Predicate<? super T> other); // 组合条件判断,等价于 "和"
default Predicate<T> negate(); // 返回非逻辑布尔值 等价于 "!" 或 "非"
default Predicate<T> or(Predicate<? super T> other);// 组合条件判断,等价于 "或"
static <T> Predicate<T> isEqual(Object targetRef);// 判断是否为空

使用例子:

假如我们有一张集合表,这张集合表里面储存了学生们的名字和它们对应的成绩,然后我们要从这张表里面筛选出 成绩合格(大于 60 分) 且名字是两个字,名字开头不是 S 的学生。

public class PredicateDemo { //
    public static void main(String[] args) { 
        // 学生表信息
        HashMap<String, Integer> students = new HashMap<>();
        students.put("John", 73);
        students.put("Jia", 57);
        students.put("Li", 67);
        students.put("Juggler", 94);
        students.put("Wd", 77);
        students.put("Sb", 61);

        HashMap<String, Integer> s = filterStudents(students,
                new Predicate<String>() {
                    // 判断名字长度是不是为 2
                    public boolean test(String s) { //
                        if (s.length() == 2)
                            return true;
                        return false;
                    }
                },
                new Predicate<String>() {
                    // 判断名字开头是否为大小写 S
                    public boolean test(String s) { //
                        if (!s.startsWith("s") & !s.startsWith("S"))
                            return true;
                        return false;
                    }
                }
                ,
                new Predicate<Integer>() {
                    // 判断成绩是否大于等于 60
                    public boolean test(Integer s) { //
                        if (s>= 60)
                            return true;
                        return false;
                    }
                });

        System.out.println(s);
    }

    public static HashMap<String,Integer> filterStudents( //
            HashMap<String, Integer> students,
            Predicate<String> p1,
            Predicate<String> p2,
            Predicate<Integer> p3
    ) { //
        HashMap<String, Integer> result = new HashMap<>();
        Iterator<Map.Entry<String, Integer>> iterator = students.entrySet().iterator();

        while (iterator.hasNext()) { //
            Map.Entry<String, Integer> next = iterator.next();
            Boolean nameIf = p1.and(p2).test(next.getKey()); // 判断名字是不是两位且开头不是 S
            Boolean socre = p3.test(next.getValue()); // 判断成绩是否大于等于 60
            if (nameIf && socre) { //
                result.put(next.getKey(), next.getValue());
            }
        }
        return result;
    }
}

最终的运行结果就是:

{Li=67, Wd=77}

d. Function 接口

我们通俗点讲,Function 接口就相当于是一个 ” 转换器 ”,主要负责将给定数据的数据类型转化为另一种数据类型。

@FunctionalInterface
public interface Function<T, R> { //
    R apply(T t);
}

Function 接口接受两个泛型,一个是原始数据的数据类型,另一个是要转化的数据类型。

使用例子:

public class FunctionDemo { //
    public static void main(String[] args) { //
        Integer result = getInt(new Function<String, Integer>() { //
            @Override
            public Integer apply(String s) { //
                return Integer.parseInt(s);
            }
        });
        System.out.println(result + 1);
    }

    // 将字符串转化为整数型
    public static Integer getInt(Function<String, Integer> F) { //
        return F.apply("123");
    }
}// 运行结果:124

3, Lambda 表达式

讲完了函数式接口,我们可以来讲讲 Lambda 表达式了。前面我提到,函数式编程思想的核心就是注重结果,忽略过程。而 Lambda 表达式就是能够让我们对一些过程进行简化,忽略,使我们的代码更加简洁和更高的可读性。

a. Lambda 表达式的使用前提

1, 使用对象必须是一个函数式接口

2,若函数式接口方法作为形参,则可以考虑使用 Lambda 表达式

b. Lambda 表达式使用规则

1,表达式形式: (参数列表) -> {重写的逻辑}

类和函数声明的过程可以被忽略,包括参数的数据类型也是可以忽略的。

使用例子:

先给大家看一下,我们之前写的函数式接口的代码:

public class SupplierDemo {/**/
    public static void main(String[] args) {/**/
        getMax(new Supplier<Integer>() {/**/
            @Override
            public Integer get() {/**/
                Integer[] integers = new Integer[]{1,8,6,2,3};
                return integers[integers.length-1];
            }
        });
    }
    public static void getMax(Supplier<Integer> supplier) {/**/
        Integer max = supplier.get();
        System.out.println("Max 值是" + max);
    }
} 

可以看到,Supplier 接口方法是作为形参,我们传入的时候,我们声明一个匿名类然后去实现 Supplier 接口中的 run。在使用 Lambda 表达式之前,我们要先分析一下,哪些是过程,哪些是结果;然后忽略过程,保留结果。

很明显,我们声明匿名类和声明 get 方法是不是都属于过程?而 get 方法中代码是不是就是我们想要得到的结果?然后前面我们也说到了,Lambda 表达式的形式为: (参数列表) -> {重写的逻辑}

因此,使用 Lambda 表达式后,代码就是如下:

public class SupplierDemo { /**/
    public static void main(String[] args) { /**/
        getMax(() -> { /**/
                Integer[] integers = new Integer[]{1,8,6,2,3};
                return integers[integers.length-1];
            });
    } 
    public static void getMax(Supplier<Integer> supplier) { /**/
        Integer max = supplier.get();
        System.out.println("Max 值是" + max);
    }
}

代码是不是一下子就简洁了许多?这就是 Lambda 表达式的强大之处。

此外,如果重写的方法,代码只有一行,我们也可以忽略掉花括号。

假如有如下代码:

public class SupplierDemo {/**/
    public static void main(String[] args) {/**/
        getMax(new Supplier<Integer[]>() {/**/
            @Override
            public Integer[] get() {/**/
                return new Integer[]{1,8,6,2,3};
            }
        });
    }

    public static void getMax(Supplier<Integer[]> supplier) {/**/
        Integer[] arrays = supplier.get();
        System.out.println("数组:" + Arrays.toString(arrays));
    }
}

使用 Lambda 表达式后,则是

public class SupplierDemo {/**/
    public static void main(String[] args) {/**/
        getMax(() -> new Integer[]{1,8,6,2,3});
    }

    public static void getMax(Supplier<Integer[]> supplier) {/**/
        Integer[] arrays = supplier.get();
        System.out.println("数组:" + Arrays.toString(arrays));
    }
}

后面的博文中,我们还会讲到 Stream 流和方法引用,它们与 Lambda 配合使用,将会非常强大。

函数式接口与 Lambda 表达式的详解
正文完
 0
lujia
版权声明:本站原创文章,由 lujia 于2024-12-13发表,共计5657字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)