继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

01-函数式接口和 lambda 表达式

黑桃SEVEN_PIG
关注TA
已关注
手记 46
粉丝 10
获赞 8

本文写的是java8新特性中的函数式接口、lambda表达式,介绍一下它们的 是什么怎么用
若想了解java8其它新特性,请到 00-java8常用新特性文章索引 阅读。

1 函数式接口

1.1 是什么

只包含一个抽象方法的接口,不是规定接口中只能有一个方法,因为接口中还可以包含 default方法和 static 方法。

  • 在接口上声明@FunctionalInterface注解,可以检查该接口是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口
  • 使用 lambda 表达式可以创建一个函数式接口的实例(下面介绍)。

1.2 Java内置的四大核心函数式接口

JDK中提供了一些默认的函数式接口,能够满足大部分业务场景,当然,也可以通过自定义的函数式接口处理业务。

1.2.1 Consumer<T>

消费型接口:对给定的 T 类型的参数,进行操作,
核心方法:void accept(T t);

源码

@FunctionalInterface
public interface Consumer<T> {

    /**
     * 对给定的 T 类型的参数,进行操作
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * 这个是接口中定义的 default 方法,也是 java8 的新特性
     * 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

1.2.2 Supplier<T>

供给型接口: 获取一个 T 类型的对象
核心方法:T get();

源码

@FunctionalInterface
public interface Supplier<T> {

    /**
     * 返回一个 T 类型的对象
     *
     * @return a result
     */
    T get();
}

1.2.3 Function<T, R>

函数型接口: 对 T 类型的参数进行操作,返回 R 类型的结果
核心方法:R apply(T t);

源码

@FunctionalInterface
public interface Function<T, R> {

    /**
     * 对 T 类型的参数进行操作,返回 R 类型的结果
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

    /**
     * default 方法,也是 java8 的新特性
     * 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * default 方法,也是 java8 的新特性
     * 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
     * static 方法,也是 java8 的新特性
     * 后面章节会介绍接口中的 static 方法,这里可以把看作是普通的类方法
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

1.2.4 Predicate<T>

断定型接口: 判断 T 类型的参数,是否满足某约束,返回 boolean 值
核心方法:boolean test(T t);

源码

@FunctionalInterface
public interface Predicate<T> {

    /**
     * 判断 T 类型的参数,是否满足某约束,返回 boolean 值
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

    /**
     * default 方法,也是 java8 的新特性
     * 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     * default 方法,也是 java8 的新特性
     * 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     * default 方法,也是 java8 的新特性
     * 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     * static 方法,也是 java8 的新特性
     * 后面章节会介绍接口中的 static 方法,这里可以把看作是普通的类方法
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

1.3 四大核心函数式接口简单使用

四大核心函数式接口简单使用


/**
 * Description: 函数式接口简单使用
 *
 * @author Xander
 * datetime: 2020/8/31 9:25
 */
public class FuncIntfTest {

    /**
     * 要生成的字符串长度
     */
    public static final int LENGTH = 2;

    /**
     * 十六进制字符数组
     */
    public static char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};


    public static void main(String[] args) {
        int setLength = 5;// set 的 length
        Set<String> set = new LinkedHashSet<>();

        Supplier<String> supplier = getRandomHexStrSupplier();// 获取一个 Supplier:随机十六进制字符串生成器
        for (int i = 0; i < setLength; i++) {
            set.add(supplier.get());//生成随机 16 进制字符串
        }
        Consumer<Set> setConsumer = getSetConsumer();//获取一个Consumer: 遍历打印 Set
        System.out.println("----------打印所有的16进制字符串---------");
        setConsumer.accept(set);//遍历打印 set 中的每个元素

        //将字符串转为 long 值
        Function<String, Long> toDecimalFunction = new Function<String, Long>() {
            @Override
            public Long apply(String s) {
                Long value = Long.valueOf(s, 16);//将16进制字符串转为10进制的long值
                return value;
            }
        };

        Predicate<String> lt150Predicate = new Predicate<String>() {
            /**
             * 判断一个十六进制字符串是否小于150
             *
             * @param s
             * @return
             */
            @Override
            public boolean test(String s) {
                Long value = Long.valueOf(s, 16);
                return value.longValue() < 150;
            }
        };

        Set<Long> longSet = new LinkedHashSet<>();
        for (String str : set) {
            if (lt150Predicate.test(str)) {//判断一个十六进制字符串是否小于150
                Long value = toDecimalFunction.apply(str);//将16进制字符串转为10进制的long值
                longSet.add(value);
            }
        }
        System.out.println("----------打印小于150的值---------");
        setConsumer.accept(longSet);//遍历打印 longSet 中的每个元素
    }

    /**
     * 获取一个Supplier:随机十六进制字符串生成器
     *
     * @return
     */
    private static Supplier<String> getRandomHexStrSupplier() {
        Supplier<String> supplier = new Supplier<String>() {

            /**
             * 生成随机 16 进制字符串
             *
             * @return
             */
            @Override
            public String get() {
                Random random = new Random();
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < LENGTH; i++) {
                    sb.append(hexChars[random.nextInt(hexChars.length)]);
                }
                return sb.toString();
            }
        };
        return supplier;
    }

    /**
     * 获取一个Consumer: 遍历打印 Set
     *
     * @return
     */
    private static Consumer<Set> getSetConsumer() {
        Consumer<Set> consumer = new Consumer<Set>() {

            /**
             * 遍历打印 set 中的每个元素
             *
             * @param set
             */
            @Override
            public void accept(Set set) {
                if (set == null || set.isEmpty())
                    return;
                for (Object s : set) {
                    System.out.println(s);
                }
            }
        };
        return consumer;
    }
}

运行结果:

----------打印所有的16进制字符串---------
48
B1
0B
46
5F
----------打印小于150的值---------
72
11
70
95

1.4 Java其他内置的函数式接口

函数式接口 参数类型 返回类型 用途
BiFunction<T, U, R> T, U R 对类型为 T, U 参数应用操作,
返回 R 类型的结果。
核心方法为 R apply(T t, U u);
UnaryOperator
(Function子接口)
T T 对类型为T的对象进行一元运算,
并返回T类型的结果。
核心方法为T apply(T t);
BinaryOperator
(BiFunction 子接口)
T, T T 对类型为T的对象进行二元运算,
并返回T类型的结果。
核心方法为T apply(T t1, T t2);
BiConsumer<T, U> T, U void 对类型为T, U 参数应用操作。
核心方法为void accept(T t, U u)
ToIntFunction
ToLongFunction
ToDoubleFunction
T int/long/double T 类型的参数,
返回int\long\double 类型的结果,
核心方法为int\long\double applyAsInt(T value);
IntFunction
LongFunction
DoubleFunction
int/long/double R 参数分别为int、long、double 类型的函数,
返回 R 类型的结果,
核心方法: R apply(int\long\double value);

2、 Lambda 表达式

作用:可以通过 Lambda 表达式来创建函数式接口的对象,这样可以取代大部分的匿名内部类,可优化代码结构,写出更加优雅的代码。

2.1 Lambda 是什么

Java8中引入了一个新的操作符 “->” 该操作符称为箭头操作符或 Lambda 操作符
箭头操作符将 Lambda 表达式拆分成两部分:
左侧:参数列表
右侧:业务逻辑代码, 即 Lambda 体

2.2 Lambda 简单使用

可以看出 lambda 表达式的代码量更少,更加简洁。

    /**
     * 分别通过匿名内部类和 lambda 表达式方式新建 Runnable 对象,创建线程,打印随机整数
     */
    @Test
    public void test00() {
        Random random = new Random();
        // 匿名内部类创建一个 Runable 对象
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类:" + random.nextInt(10));
            }
        });
        thread.start();
        // lambda 表达式创建一个 Runable(函数式接口) 对象
        Runnable rLambda = () -> System.out.println("lambda表达式1:" + random.nextInt(10));
        Thread tLambda1 = new Thread(rLambda);
        tLambda1.start();

        //lambda 可以代替函数式接口的匿名内部类作为参数传递
        Thread tLambda2 = new Thread(() -> System.out.println("lambda表达式2:" + random.nextInt(10)));
        tLambda2.start();
    }

运行结果:

匿名内部类:8
lambda表达式12
lambda表达式25

2.3 类型推断

Lambda表达式中的类型推断,实际上是Java 7就引入的目标类型推断的扩展,Java7中的菱形操作符,它可使javac推断出泛型参数的类型。

        List<String> list1 = new ArrayList<String>();
        List<String> list2 = new ArrayList<>();

Lambda 表达式中的参数类型依赖于上下文环境,是由编译器推断出来的。lambda表达式中无需指定类型,亦可编译通过。

// 类型推断,lambda可以推断出参数 str 的类型为String
Consumer<String> c = str -> System.out.println(str);

2.3 Lambda 语法格式

Lambda 语法格式分别从 参数lambda体 进行介绍

2.3.1 参数:无参

参数:无参,参数部分为 ()

    /**
     * 参数:无参
     */
    @Test
    public void test01() {
        // 参数:无参,参数部分为 ()
        Supplier<Integer> supplier = () -> {
            int num = new Random().nextInt(10);
            System.out.println(num);
            return num;
        };
        System.out.println(supplier.get());
    }

运行结果:

4

2.3.2 参数:只有一个参数

参数:只有一个参数,可以省略括号 ()

    /**
     * 参数:只有一个参数
     */
    @Test
    public void test02() {
        // 参数:只有一个参数,可以省略括号 ()
        Consumer<String> c1 = str -> {
            System.out.println(str);
        };
        c1.accept("hello lambda");
    }

运行结果:

hello lambda

2.3.3 参数:多个参数

参数:多个参数,所有的参数写在小括号 () 中

    /**
     * 参数:多个参数
     */
    @Test
    public void test03() {
        //参数:多个参数,所有的参数写在小括号 () 中
        BinaryOperator<Integer> binaryOperator = (n1, n2) -> {
            return n1 + n2;
        };
        System.out.println(binaryOperator.apply(2, 3));
    }

运行结果:

5

2.3.4 lambda体:一行语句,无返回值

lambda体:一行语句,无返回值,可以省略大括号


    /**
     * lambda体:一行语句
     */
    @Test
    public void test04() {
        // lambda体:一行语句,返回值,可以省略大括号
        Consumer<String> c1 = str -> System.out.println(str);
        c1.accept("lambda体:一行语句,返回值,可以省略大括号");
    }

运行结果:

lambda体:一行语句,返回值,可以省略大括号

2.3.5 lambda体:一行语句,有返回值

lambda体:一行语句,有返回值,可以省略 return 关键字 和 大括号

    /**
     * lambda体:一行语句,有返回值
     */
    @Test
    public void test05() {
        // 二元运算:加法
        BinaryOperator<Integer> biOpr1 = (n1, n2) -> {
            return n1 + n2;
        };
        // 二元运算:乘法
        // lambda体:一行语句,有返回值,可以省略 return 关键字 和 大括号
        BinaryOperator<Integer> biOpr2 = (n1, n2) -> n1 * n2;

        System.out.println(biOpr1.apply(5, 10));
        System.out.println(biOpr2.apply(5, 10));
    }

运行结果:

15
50

2.3.6 lambda体:多行语句

lambda体:多行语句,所有代码语句要写在大括号 {} 中

    /**
     * lambda体:多行语句
     */
    @Test
    public void test06() {

        // 二元运算:加法
        // lambda体:多行语句,所有代码语句要写在大括号 {} 中
        BinaryOperator<Integer> biOpr1 = (n1, n2) -> {
            System.out.println("参数一:" + n1);
            System.out.println("参数二:" + n2);
            return n1 + n2;
        };
        System.out.println("加法运算结果:" + biOpr1.apply(5, 10));
    }

运行结果:

参数一:5
参数二:10
加法运算结果:15

好了,函数式接口lambda 表达式的简单介绍就到这,多加理解,学以致用,才是王道。


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP