Lambda表达式和函数式编程

Lambda表达式和函数式编程

一、Lambda表达式

1.1、函数式编程思想概述

函数式编程思想
函数式编程(英语:functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。
其中,λ演算(lambda calculus)为该语言最重要的基础。而且,λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。

比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

在函数式编程中,函数是第一类对象,意思是说一个函数,既可以作为其它函数的参数(输入值), 也可以从函数中返回(输入值),被修改或者被分配给一个变量。

我们关注的是得到的结果而不是过程。

函数式编程的理论基础是Lambda演算,其本身是一种数学的抽象但不是编程语言。另一个组合逻辑是比它更加古老和基础的数学根基。两者都是为了更好的表达数学基础才被开发的。

1.2、演示冗余的Runnable代码

 new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是使用匿名内部类实现的线程任务");
            }
        }).start();

使用Lambda表达式来优化:

 new Thread(()-> System.out.println("我是使用lambda表达式来创建的线程,参数必须是一个函数式接口。即只含有一个抽象方法。")).start();

1.3、Lambda标准格式

首先创建一个函数式接口,cook

package Lambda表达式;

/**
 * 定义一个实验接口,函数式接口,利用一个注解声明。里面可以有默认方法和静态方法。
 * 但是只能有一个抽象方法。
 *
 */
@FunctionalInterface
public interface Lambda02_Cook {
    void makeRice(String mess);//做饭
    default void say(){
        System.out.println("我会做饭!");
    }
}

测试:

package Lambda表达式;

import java.util.Arrays;

/**
 * 匿名内部类的弊端、好处:
 * 1、使用一个子类,省略了一个类的定义。
 * 2、写法复杂。
 *
 * Lambda表达式的好处:
 * 1、简化书写。
 * 2、更加关注于结果。
 *
 * Lambda的格式,以及满足Lambda表达式简化的条件:
 *
 * 标准格式:
 * 1、一些参数
 * 2、一个括号
 * 3、语句块
 *
 * 如()->{}
 *
 *
 */

public class Lambda02_BasicFormat {
    public static void main(String[] args) {
        new Lambda02_BasicFormat().canCook("我做的大米饭喔", new Lambda02_Cook() {
            @Override
            public void makeRice(String mess) {
                System.out.println(mess);
            }
        });

        new Lambda02_BasicFormat().canCook("我会做大米饭2",(String mess)-> System.out.println(mess));
        //IDEA还提示了简化:就是方法引用
        new Lambda02_BasicFormat().canCook("我会做大米饭3", System.out::println);

    }
    public void canCook(String mess,Lambda02_Cook cook){//有一个方法,其参数是包含一个函数式接口
        cook.makeRice(mess);
    }
}

1.4、Lambda表达式有无参数及其格式省略

返回值的函数式接口一样可以用Lambda表达式。
关于格式的省略问题:
Lambda的表达式 :可推到、可省略。
即凡是通过上下文来推导得出的都可以省略。一般Lambda的表达式省略的可以有:数据类型,返回值,{},;
1、如果代码只有一行,则可以省略大括号{},省略分号、省略返回值,要省略则这三个要一起省略
2、如果参数列表有多个,则可以省略参数类型。
3、如果参数只有一个,则可以将类型省略,()省略。
4、如果没有参数,则()不可省略。空着。

首先创建一个测试的函数式接口

package Lambda表达式;

/**
 * 测试格式
 *
 */
@FunctionalInterface
public interface Lambda03_Test{
    int getE(int a);
}

测试:

package Lambda表达式;


public class Lambda03_ReturnValue {
    public static void main(String[] args) {
        new Lambda03_ReturnValue().getSum(3,4,new Lambda03_Calculator(){

            @Override
            public int sum(int a, int b) {
                return a+b;
            }
        });

        new Lambda03_ReturnValue().getSum(3,4,(a,b)->a+b);
        //IDEA会提示简化,即方法引用。
        new Lambda03_ReturnValue().getSum(3,4, Integer::sum);

        //有函数值和参数类型只有一个时:
        new Lambda03_ReturnValue().getType(4,(int a)->a+4);
        new Lambda03_ReturnValue().getType(5,a->a+5);//类型省略,()省略,只有一语句,则return,;{}都省略。
        new Lambda03_ReturnValue().getType(5, new Lambda03_Test() {//原本的写法
            @Override
            public int getE(int a) {
                return a+7;
            }
        });

    }
    public void getSum(int a,int b,Lambda03_Calculator calculator){
        int result =  calculator.sum(a,b);
        System.out.println(result);
    }
    public void getType(int a,Lambda03_Test test){
       System.out.println(a+test.getE(a));
    }
}

1.5、Lambda表达式是匿名内部类的‘语法糖’?

语法糖
什么是语法糖?

  • 就是原理上是一样的,但是简化了书写;

  • 集合元素遍历里面的,如For-each,原理都是利用了底层的迭代器。但是简化了书写而已。
    但是Lambda表达式从应用上看,是匿名内部类 的‘语法糖’。但是实际上原理是不一样的。

  • Java Lambda表达式的一个重要用法是简化某些匿名内部类(Anonymous Classes)的写法。

  • 实际上Lambda表达式并不仅仅是匿名内部类的语法糖,JVM内部是通过invokedynamic指令来实现Lambda表达式的。

1.6、Lambda表达式的注意事项

  • 1、必须在方法中使用
  • 2、参数是一个函数式接口,才能用Lambda表达式替代。
  • 3、不能在lambda内部修改定义在域外的变量,可以读取
int a = 0;
new Thread(() -> a++).start();
 //Variable used in lambda expression should be final or effectively final

  • 4、当代码体不修改Lambda表达式提供的参数时候,代码体可以替换为方法引用,如果修改参数的时候只能使用Lambda表达式
 new Lambda02_BasicFormat().canCook("我会做大米饭2",(String mess)-> System.out.println(mess));//mess没有修改
//IDEA还提示了简化:改为了方法引用
new Lambda02_BasicFormat().canCook("我会做大米饭3", System.out::println);

二、函数式编程及常用接口

2.1、性能浪费的例子:

package Lambda表达式;

/**
 * 性能浪费的例子:
 * 日志打印
 */

public class FunctionInterface02 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        new FunctionInterface02().log(1,s1+s2+s3);//无论级别level等不等于1,字符串拼接都会进行
        
    }
    public void log(int level,String mess){
        if(level==1)
            System.out.println("mess = " + mess);
    }
}

这一行代码:假设级别level=2,压根就不满足条件,但是却将字符串进行拼接了。性能耗费了。

   new FunctionInterface02().log(1,s1+s2+s3);
   //无论级别level等不等于1,字符串拼接都会进行

这样的情况如果使用Lambda表达式:
1、定义一个接口:

package Lambda表达式;

@FunctionalInterface
public interface FunctionInterface02_test {
    String concat();
}

2、使用该接口优化:

package Lambda表达式;

/**
 * 性能浪费的例子:
 * 日志打印
 */

public class FunctionInterface02 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        /*new FunctionInterface02().log(1,s1+s2+s3);//无论级别level等不等于1,字符串拼接都会进行*/
        new FunctionInterface02().log(1,()->s1+s2+s3);
    }
  public void log(int level,FunctionInterface02_test test){
      if(level==1){
          System.out.println(test.concat());
      }
  }
}

从调用过程来看,只有level满足条件,才会调用concat方法的调用。也就是现在才拼接。否则不会拼接。

2.2、常用的函数式接口

2.2.1、Supplier接口—— 生产数

生产型接口,通过泛型参数可以知道,你给它什么类型的数据,它就返回什么类型的数据。
在这里插入图片描述

下面演示利用其来返回数据。

package Lambda表达式;

import java.util.function.Supplier;

public class FunctionInterface_Supplier {
    public static String  getMess(Supplier<String> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "world";
       /* String mess = FunctionInterface_Supplier.getMess(new Supplier<String>() {
            @Override
            public String get() {
                return s1+s2;//函数式接口,如果函数内不修改外部的数据,则可以。否则外部数据需要声明为efficiency final
            }
        });*/
        String mess = FunctionInterface_Supplier.getMess(() -> s1 + s2);//无论有没有返回值都省略,泛型参数都省略了。。。
        System.out.println("mess = " + mess);
        //利用lambda表达式

    }
}

2.2.2、Consumer接口——消费数
Interface Consumer<T>

 参数类型
 T - 操作的输入类型
 All Known Subinterfaces:
 Stream.Builder<T>

 Functional Interface:
 这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。
@FunctionalInterface
 public interface Consumer<T>
 表示接受单个输入参数并且不返回任何结果的操作。 与大多数其他功能接口不同, Consumer预计会通过副作用运行。
 这是一个functional interface,其功能方法是accept(Object) 。

 从以下版本开始:
 1.8

Consumer里面有两个定义的方法
1、抽象方法accept(T t);
消费一个数据,至于怎么消费,我们自己需要定义。如打印出来,计算最大值并打印。。。

public class FunctionInterface03_Consumer {
    public static void displayMess(String mess, Consumer<String> consumer){
        consumer.accept(mess);
    }

    public static void main(String[] args) {
        displayMess("我是cumsumer,使用Lambda表达式",( mess)->System.out.println(mess));
    //参数只有一个,语句只有一个,可以省略类型定义,返回值、{},;

    //当语句没有对参数进行修改,仅仅进行访问,可以改为方法引用。

        displayMess("我是cumsumer,使用方法引用", System.out::println);
    }//参数只有一个,语句只有一个,可以省略类型定义,返回值、{},;
}

2、默认方法andThen();

andThen
源码

 default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);//先判断是否为空,是则抛出空指针异常。
        return (T t) -> { accept(t); after.accept(t); };//lambda表达式,调用两个consumer对象的accept方法,消费同一个数据
    }

返回一个组合Consumer ,它按顺序执行此操作,然后执行after操作。 如果执行任一操作抛出异常,它将被中继到组合操作的调用者。 如果执行此操作会引发异常,则不会执行after操作。

  • 参数
    after - 此操作后要执行的操作
  • 结果
    一个由 Consumer组成的 Consumer ,按顺序执行此操作,然后执行 after操作
  • 异常
    NullPointerException - if after is null

比如现在我们要消费两次的该数据

Consumer<String>con1;
Consumer<String>con2;
String s = "猪猪";
con1.accept(s);
con2.accept(s);

使用andThen方法简写,也就是用andThen链接两个Consumer接口,进行消费同一个数据。

con1.andThen(con2).accept();

测试:

public class FunctionInterface03_Consumer {
    public static void displayMess(String mess, Consumer<String> consumer){
        consumer.accept(mess);
    }

    public static void displayMessTwice(String mess, Consumer<String>consumer){
        consumer.andThen(s -> System.out.println(mess + " 我是二号 ")).accept(mess);
    }

    public static void main(String[] args) {
        displayMessTwice("我是comsumer",(mess)->System.out.println(mess+" 我是一号 "));

    }
}

在这里插入图片描述

2.2.3、Predicate接口——断言型函数式接口、逻辑运算
/**
 * Interface Predicate<T>
 * 参数类型
 * T - 谓词的输入类型
 * Functional Interface:
 * 这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。
 * --------------------------------------------------------------------------------
 *@FunctionalInterface
 * public interface Predicate<T>
 表示一个参数的谓词(布尔值函数)。
 * 这是一个functional interface,其功能方法是test(Object) 。
 * 从以下版本开始:
 * 1.8

 */

里面的方法:

  • 一个抽象方法
boolean test​(T t);//根据给定的参数计算此谓词。 
  • 两个静态方法
 static <T> Predicate<T> not​(Predicate<? super T> target) 
// 返回谓词,该谓词是提供的谓词的否定。  
static <T> Predicate<T> isEqual​(Object targetRef) 
//返回一个谓词,根据 Objects.equals(Object, Object)测试两个参数是否相等。
  • 三个默认方法。
default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }//返回一个组合谓词,表示此谓词和另一个谓词的短路逻辑AND。
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);
    }
//返回一个组合谓词,表示此谓词与另一个谓词的短路逻辑OR。

测试:
1、test方法

public class FunctionInterface04_Predicate {
    public static void isBiggerThanTen(int t, Predicate<Integer> predicate){
        if(predicate.test(t)){//大于10则输出
            System.out.println("输入的数字大于10,可以输出:"+t);
        }else System.out.println("t = " + t + "小于10");
    }
    public static void isThreeBitNumber(int t,Predicate<Integer> predicate1,Predicate<Integer> predicate2){//判断是否为三位数
        if(predicate1.and(predicate2).test(t)){
        //等效于:predicate1.test()&&predicate2.test();
            System.out.println("输入的数字是三位数"+t);
        }else  System.out.println("输入的数字不是三位数"+t);

    }

    public static void main(String[] args) {
        isBiggerThanTen(23,(t)->t>10);
        isThreeBitNumber(120,(t)->t>99,(t)->t<1000);
    }
}
   public static void testAllMethod(int t, Predicate<Integer> predicate1, Predicate<Integer> predicate2){
        if(!predicate1.negate().test(0)){//测试是否为负数
            System.out.println("输入的数是负数:  "+t);
        }else  System.out.println("输入的数不是负数:  "+t);
    }

    public static void main(String[] args) {     
        testAllMethod(100,(t)->t>0,(t)->t>10);
    }

在这里插入图片描述
其他方法:

  		if(predicate1.and(predicate2).test(t)){//——test方法需要对应
            //等效于:predicate1.test()&&predicate2.test();
            System.out.println("输入的数字是三位数"+t);
        }else  System.out.println("输入的数字不是三位数"+t);

        if(predicate1.or(predicate2).test(t)){//输入大于10或者小于5的数————test方法需要对应
            //等价于predicate1.test(t)||predicate2.test(t)
            System.out.println("输入的数大于10或者小于5  "+t);
        }else  System.out.println("输入的数小于10,大于5  "+t);

        if(Predicate.isEqual(100).test(t)){//——test方法需要对应
            //等价于t==100
            System.out.println("输入的数等于100:  "+t);
        }else System.out.println("输入的数不等于100:  "+t);
2.2.4、Function接口——功能型函数式接口
Interface Function<T,​R>

参数类型 
T - 函数输入的类型 
R - 函数结果的类型 
All Known Subinterfaces: 
UnaryOperator<T> 
Functional Interface: 
这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。 

--------------------------------------------------------------------------------

@FunctionalInterface
public interface Function<T,​R>表示接受一个参数并生成结果的函数。 
这是一个functional interface,其功能方法是apply(Object) 。 

从以下版本开始: 
1.8 

方法

  default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
// 返回首先将此函数应用于其输入的 after函数,然后将 after函数应用于结果。  

 /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
//将此函数应用于给定的参数。  

 default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
//返回一个组合函数,该函数首先将 before函数应用于其输入,然后将此函数应用于结果。  

  static <T> Function<T, T> identity() {
        return t -> t;
    }
//返回一个始终返回其输入参数的函数。  

方法测试:

package Lambda表达式;

import java.util.function.Function;

public class FunctionInterface05_Function {
    public static int getInt(String s, Function<String,Integer>function){//将字符串变为整数
        return function.apply(s);
    }
    public static Object getInput(String s){
        return Function.identity().apply(s);
    }
    public static String getUppercase(String s,Function<String,String>function1,Function<String,String>function2){
        return function1.compose(function2).apply(s);//function2 先对输入的数据进行apply,得到的结果再作为function1的输入

    }
    public static String getString(String s,Function<String,Integer>function1,Function<Integer,String>function2){
        return function1.andThen(function2).apply(s);//function1先对输入apply,function2再将结果apply
        //等价于
        /*
        int i = function1.apply(s);
        return function2.apply(i);
         */
    }

    public static void main(String[] args) {
        System.out.println(getInt("1000",(t)->Integer.parseInt(t)));
        System.out.println("args = " +getInput("我能返回输入的参数") );
        System.out.println(getUppercase("    abcS   dddDD",(s)->s=s.trim()+"function1",(s)->s=s.toUpperCase()+"function2"));

        //将字符串转化为整数,将该整数加10,然后返回其字符串
        System.out.println(getString("100",(s)->Integer.parseInt(s)+10,(i)->Integer.toString(i)));
    }
}



在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨夜※繁华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值