Java 8 (2/6篇) - Lambda表达式 & 函数式接口(FunctionalInterface Lib)

【函数式编程】

数学:有输入和输出的一套计算方案(拿数据做操作)

面向对象:必须通过对象的形式做事情

函数编程:强调做什么,而不是以什么对象的形式去做(怎么做)

特点:

①可作为参数传递和赋值:函数与其它数据类型一样,可以赋值给变量,也就可以作为参数传入到另一个函数,还能作为别的函数的返回值。

②只用表达式不用语句:表达式(Expression)是一个单纯的运算过程并总是有返回值,语句(statement)是执行某种操作没有返回值。

③没有副作用:副作用指修改系统状态,意味着函数要保持独立,所有功能就是返回一个新值,没有其它行为尤其是不得修改外部变量的值。

④引用透明:即如果提供相同的输入,那么函数总是返回同样的结果。

优缺点:

代码简洁,易于并发编程(因为不修改变量所以不用担心线程的问题)

提高学习成本,维护性差。

 【函数式接口 Functional Interface】

定义: 

Java8 定义的新接口类型,只包含一个抽象方法的接口,也叫SAM(Single Abstract Method)类型接口。

说明

① @FunctionalInterface 注解用于编译期间检查接口是否符合函数式接口的语法,防止后人修改使其变成非函数接口,当然也可不写。

②接口里可以有 static 方法和 default 方法,毕竟它们都已经是实现过的。

③接口里可以定义 Object 中的 public 方法,虽然它是抽象方法,实现类继承自Object 自然也就实现了。

@FunctionalInterface
interface A{
    //包含单个抽象方法
    void method();
    //可以有默认方法
    default void show(){...}
    //可以有静态方法
    static void run(){...}
    //可以定义Object中的方法
    public boolean equals(Object object);
}

 实例创建:①Lambda表达式、②方法引用、③构造方法。

TODO...

【Lambda表达式】

Lambda表达式能赋值给一个变量,也就能当作参数传给函数。这个Lambda形式的变量/参数的类型是它所实现的那个接口,所包含的方法体便是这个接口抽象方法的实现。以后看到调用方法的参数是一个SAM类型接口的时候就可以考虑使用Lambda表达式替换匿名内部类来写。

作用:

①减少代码量,突出代码意图

②对集合数据 Collection 操作更简便

③使变量记住一段逻辑:

        任务逻辑传递(传递一段运算逻辑给执行者)

        回调逻辑传递(简化接口回调的时候 new匿名类后实现抽象方法的模版代码)

将一个方法写成Lambda表达式,只需要关注参数列表和方法体。

在这里插入图片描述

语法组成:

(参数类型 参数名) -> {

        方法体;

        return 返回值;

}

①形式参数:最前面的部分是一对括号,里面是参数,无参数就是一对空括号

②箭头:中间的是 -> ,用来分割参数和body部分,读作“ goes to”

③方法体:body部分可以是一个表达式或者一个代码块。

简写:

①可选类型声明:不用声明参数类型,编译器可以自动识别。

②可选参数括号:一个参数无需定义括号,多个参数需要定义。

③可选大括号:如果body部分只包含一个语句或表达式,就不需要使用大括号括起来。

④可选 return 关键字:如果body部分是一个表达式,表达式的值会被作为返回值返回;如果是代码块,需要用 return 指定返回值。

例如 Runnable 接口只有一个 run() 方法,像这样的接口都可以通过匿名函数实现。


public interface Runnable {
    public abstract void run();
}

//【传统写法】匿名内部类,可读性差、不能引用外部非 final 变量等
//1.创建了一个没有名字的类
//2.实现了Runnable接口的抽象方法
//3.new了这个类的实例
//而我们关注的只是run()方法中要执行的代码
new Thread(new Runnable(){
    public void run(){
        System.out.println("hello"); 
    }
}).start();

//【Java8写法】匿名函数,精简易读
//只需要将执行的代码放到函数(方法)中,Lambda就是一个匿名函数(方法)
new Thread( ()-> { System.out.println("hello"); }).start();

Java8 之前创建接口实现类总会有很多冗余的模版代码,接口中定义的抽象方法越多,每次实现的模版代码就越多,而很多时候这个接口实现类只需要用到一次。 

变量作用域 :

①局部变量:引用的局部变量不可被修改值,即必须是final所修饰的,或者不被后面代码更改的(隐形final属性)。

②成员变量:可以修改类成员变量(非 final 修饰的)和静态变量。

③在Lambda表达式中,不允许声明一个与局部变量同名的参数或者局部变量。

④在Lambda表达式中,使用this调用的是该Lambda表达式所属方法的所属实例的this。

⑤在Lambda表达式中,无法访问到自身接口的默认方法(不存在实例)。

public class Test {
    int aa = 3;
    static int bb = 3;

    public String show() {
        return "GOOD!";
    }

    public void method(int num) {
        int cc = 3; //可以不被final修饰但是值不能被后面代码更改
        Supplier<Integer> supplier1 = () -> 2 + aa; //引用成员变量
        Supplier<Integer> supplier2 = () -> 2 + bb; //引用静态成员变量
        Supplier<Integer> supplier3 = () -> 2 + cc; //引用局部变量
        Supplier<Integer> supplier4 = () -> 2 + num;   //引用形参
        Supplier<String> supplier5 = this::show;    //调用的是表达式所属方法所属实例的this
    }
}

Lambda与匿名内部类的区别:

①所需类型不同:

        匿名内部类:接口、抽象类、具体类

        Lambda:接口

②使用限制不同:

        匿名内部类:接口中可以有多个抽象方法

        Lambda:接口中只能有一个抽象方法

③实现原理不同:

        匿名内部类:编译后会产生一个单独的.class字节码文件

        Lambda:编译后没有单独的.class字节码文件,对应的字节码在运行的时候动态生成。①在类中新增一个方法,方法体就是Lambda中的代码。②还会新增一个匿名内部类,实现接口重写方法。③在重写方法中调用新生成的方法。

【内置函数接口】

为了减少自定义函数式接口的编写,JDK已经帮我们抽出几种常用的函数式接口,存放于 java.util.function包中。(例如写接口回调的时候,自定义的与自带的功能一样当然是用自带的)

函数式接口名称        方法说明
Supplier <T>供给型接口

T get ( )

无参数传入,返回一个结果

Consumer <T>消费型接口

void accept ( T t )

接收一个参数,无需返回结果

default Consumer<T> andThen(Consumer<? super T> after)

执行自身 this.apply()之后再执行参数 after.apply()

Function <T,R>函数型接口

R apply ( T t )

接收 T 类型参数,返回 R 类型结果

default <V> Function<T,V> andThen (Function<? super R,? extends V> after)

执行自身 this.apply() 之后再执行参数 after.apply()

default <V> Function<V,R> compose (Function<? super V,? extends T> before)

执行自身 this.apply() 之前先执行参数 before.apply()

Predicate <T>断言型接口

boolean test ( T t )

接收一个参数,返回 boolean 类型结果

default Predicate<T> and(Predicate<? super T> other)

底层原理是:自身 this.test() && 参数 other.test()

default Predicate<T> or(Predicate<? super T> other) 

底层原理是:自身 this.test() || 参数 other.test()

default Predicate<T> negate()

底层原理:! test()

public class TestJava {

    public static void main(String[] args) {

        //获取数字3(无参数传入,返回一个结果)
//        Supplier<Integer> supplier = () -> 3;
//        int number3 = getNumber3(supplier);
        int number3 = supplierGet(() -> 3);  //3

        //打印输入的数字(接收一个参数,无需返回结果)
        consumerAccept(123, bb -> System.out.println(bb));    //123
        //对传入的数字进行两次操作
        consumerAndThen(3, it -> System.out.println(it += 1), it -> System.out.println(it += 1)); //打印两次

        //输入字符串返回字数(接收 T 类型参数,返回 R 类型结果)
        int strLength = functionApply("今天天气不错!", str -> str.length());    //7
        //输入一个数字返回它是几位数
        int num1 = functionAndThen(555, it -> it.toString(), it -> it.length());    //3
        int num2 = functionCompose(555, it -> it.length(), it -> it.toString());    //3

        //输入的数字是否等于100(接收一个参数,返回 boolean 类型结果)
        boolean isNumber5 = predicateTest(45, cc -> cc == 100);    //false
        //对传入的字符串,用两个单独的判断进行操作
        boolean b1 = predicateAnd("Hello World!", str -> str.contains("!"), str -> str.length() < 5);   //true
        boolean b2 = predicateOr("Hello World!", str -> str.contains("Nihao"), str -> str.contains("Shijie"));   //false
        //输入的数字是否小于50,对判断结果取反
        boolean b3 = predicateNegate(55, num -> num < 50);  //false

    }

    //【Supplier】
    public static int supplierGet(Supplier<Integer> supplier) {
        return supplier.get();
    }

    //【Consumer】
    public static void consumerAccept(int num, Consumer<Integer> consumer) {
        consumer.accept(num);
    }

    public static void consumerAndThen(int num, Consumer<Integer> consumer1, Consumer<Integer> consumer2) {
        //常规写法
//        consumer1.accept(num);
//        consumer2.accept(num);
        //andThen写法
        consumer1.andThen(consumer2).accept(num);
    }

    //【Function】
    public static int functionApply(String str, Function<String, Integer> it) {
        return it.apply(str);
    }

    //function1 的输出类型要与 function2 的输入类型一致,方法返回值类型要与参数 function2 的输出类型一致
    public static Integer functionAndThen(int num, Function<Integer, String> function1, Function<String, Integer> function2) {
        //常规写法
//        String str = function1.apply(num);
//        Integer num2 = function2.apply(str);
        //精简写法
//        function2.apply(function1.apply(num));
        //andThen写法
        return function1.andThen(function2).apply(num);
    }

    //function1 的输入类型要与 function2 的输出类型一致,方法返回值类型要与 function1 的输出类型一致
    public static Integer functionCompose(int num, Function<String, Integer> function1, Function<Integer, String> function2) {
        return function1.compose(function2).apply(num);
    }

    //【Predicate】
    public static boolean predicateTest(int num, Predicate<Integer> it) {
        return it.test(num);
    }

    public static boolean predicateAnd(String str, Predicate<String> p1, Predicate<String> p2) {
        //常规写法
//        p1.test(str) && p2.test(str);
        return p1.and(p2).test(str);
    }

    public static boolean predicateOr(String str, Predicate<String> p1, Predicate<String> p2) {
        //常规写法
//        p1.test(str) || p2.test(str);
        return p1.or(p2).test(str);
    }

    public static boolean predicateNegate(int num, Predicate<Integer> it) {
        //常规写法
//        !it.test(num);
        return it.test(num);
    }

类似的多参数接口:

函数式接口抽象方法
BiConsumer <T,U>

void accept ( T t, U u )

接收 T 和 U 两个类型参数,无需返回结果

BiFunction <T,U,R>

R apply ( T t, U u )

接收 T 和 U 两个类型参数,返回 R 类型结果

BiPredicate <T,U>

boolean test ( T t, U u )

接收 T 和 R 两个类型参数,返回判断结果

UnaryOperator <T>一元操作
BinaryOperator <T>二元操作
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值