Lambda表达式

Lambda表达式

在说Lambda表达式之前我们了解一下函数式编程思想,在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。

相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。 下面以匿名内部类创建线程的代码案例详细说明这个问题。

 

复制代码

public class ThreadDemo {
    public static void main(String[] args) {
        //实现Runnable方式创建简单线程--传统匿名内部类形式
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("开启了一个线程----匿名内部类");
            }
        }).start();

        //实现Runnable方式创建简单线程--Lambda表达式形式
        new Thread(()-> System.out.println("开启了一个线程---Lambda表达式")).start();
    }
}
运行结果:

开启了一个线程----匿名内部类
开启了一个线程---Lambda表达式

 

复制代码

对以上代码的分析:

对于 Runnable 的匿名内部类用法,可以分析出几点内容:

Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心;
为了指定 run 的方法体,不得不需要 Runnable 接口的实现类;
为了省去定义一个 RunnableImpl 实现类的麻烦,不得不使用匿名内部类;
必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
而实际上,似乎只有方法体才是关键所在。

传统的写法比Lambda表达式写法显而易见代码繁琐了许多,而且2者实现目的是相同的。

我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将 run 方法体内的代码传递给 Thread 类知晓。

传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。

那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。 

这时就要用到函数式编程思想了,只关注“做什么”,而不是以什么方式做!!

了解过函数式编程思想后,我们要尝试着转变思想,从面向对象的"怎么做"转换为函数式编程思想的“做什么”,只有思想有了转变,才能更好的了解和学习Lambda表达式。

什么是Lambda表达式?

Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。

作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升 。(2014年3月Oracle所发布的Java 8(JDK 1.8)中,加入了Lambda表达式 

Lambda表达式语法:( ) ->  { }

Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为

两个部分:

左侧 (): 指定了 Lambda 表达式需要的所有参数

右侧  {}: 指定了 Lambda 体,即 Lambda 表达式要执行的功能。 

Lambda表达式标准格式:(参数类型 参数名称) ‐> { 代码语句 } 

格式进一步说明:

小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
-> 是新引入的语法格式,代表指向动作。
大括号内的语法与传统方法体要求基本一致

Lambda表达式如何使用呢?
 

Lambda表达式的使用是有前提的,必须要满足2个条件:1.函数式接口      2.可推导可省略。

函数式接口是指一个接口中只有一个必须被实现的方法。这样的接口都满足一个注解@FunctionalInterface

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

可推导可省略是指上下文推断,也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例 。

下面我们自定义一个函数式接口,使用Lambda表达式完成功能。

复制代码

public class Demo {
    public static void main(String[] args) {
        invokeCook(()->{
            System.out.println("做了一盘红烧鱼....");
        });

    }
    //需要有个以函数式接口为参数的方法
    public static void invokeCook(Cook cook) {
        cook.makeFood();
    }
}
//自定义函数式接口
@FunctionalInterface
interface Cook{
    void makeFood();
}

复制代码

以上案例是函数式接口以及Lambda表达式最简单的定义和用法。

针对Lambda表达式还可以做出进一步的省略写法:

1.小括号内参数的类型可以省略;
2. 如果小括号内有且仅有一个参,则小括号可以省略;
3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。 

所以上面的代码可以简写为:

invokeCook(()-> System.out.println("做了一盘红烧鱼...."));

 

Lambda表达式有多种语法,下面我们了解一下。(直接写省略形式)

1.无参,无返回值,Lambda体只需一条语句

Runnable r  = ()->System.out.println("hell lambda");

2.Lambda表达式需要一个参数,无返回值

Consumer c = (str)-> System.out.println(args);

当Lambda表达式只有一个参数时,参数的小括号可以省略

 Consumer c = str-> System.out.println(args);

3.Lambda表达式需要2个参数,并且有返回值

BinaryOperator<Long> bo = (num1,num2)->{ return num1+num2;};

当Lambda体中只有一条语句时,return 和 大括号、分号可以同时省略。

BinaryOperator<Long> bo = (num1,num2)-> num1+num2;

有没有发现我们没写参数类型,Lambda表达式依然可以正确编译和运行,这是因为Lambda表达式拥有的类型推断功能。

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。 Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,

在后台推断出了参数的类型。 Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断” 。

 

Lambda表达式还具有延迟执行的作用:改善了性能浪费的问题,代码说明。

复制代码

public class Demo {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "Lambda";
        String str3 = "表达式";
        log(1,str1+str2+str3);
    }
    public static void log(int level,String str) {
        if (level == 1) {
            System.out.println(str);
        }
    }
}

复制代码

在上面代码中,存在的性能浪费问题是如果 输入的level!=1,而str1+str2+str3作为log方法的第二个参数还是参与了拼接运算,但是我们的实际想法应该是不满足level=1的条件就不希望str1+str2+str3进行拼接运算,下面通过Lambda表达式来实现这个功能。

复制代码

public class Demo {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "Lambda";
        String str3 = "表达式";
        log(1,()->str1+str2+str3);
    }
    public static void log(int level,Message message) {
        if (level == 1) {
            System.out.println(message.message());
        }
    }
}
@FunctionalInterface
interface Message {
    String message();
}

复制代码

以上代码功能相同,Lambda表达式却实现了延迟,解决了性能浪费,下面我们来验证一下:

复制代码

public class Demo {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "Lambda";
        String str3 = "表达式";
        log(2,()->{
            System.out.println("lambda 执行了");
            return str1+str2+str3;
        });
    }
    public static void log(int level,Message message) {
        if (level == 1) {
            System.out.println(message.message());
        }
    }
}
@FunctionalInterface
interface Message {
    String message();
}

复制代码

此时在输入level=2的条件时,如果Lambda不延迟加载的话会执行输出语句输出lambda 执行了,而实际是控制台什么也没输出,由此验证了Lambda表达式的延迟执行。

在Lambda表达式的应用过程中还有一种比较常用的方式:方法引用。方法引用比较难以理解,而且种类也较多,需要多费脑筋去理解。

Lambda表达式应用之 :方法引用

方法引用也是有前提的,分别为:

1.前后的参数名一致,

2.Lambda表达式的方法体跟对应的方法的功能代码要一模一样


 

方法引用种类可以简单的分为4+2种,4种跟对象和类有关,2种跟构造方法有关。下面一一说明。

跟对象和类有关的方法引用:

1.对象引用成员方法

  格式:对象名 :: 成员方法名      (双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用)

  原理:将对象的成员方法的参数和方法体,自动生成一个Lambda表达式。

复制代码

 1 public class Demo {
 2     public static void main(String[] args) {
 3         Assistant assistant = new Assistant();
 4         work(assistant::dealFile);//对象引用成员方法(注意是成员的方法名,没有小括号)
 5     }
 6     //以函数式接口为参数的方法
 7     public static void work(WokerHelper wokerHelper) {
 8         wokerHelper.help("机密文件");
 9     }
10 }
11 //助理类,有个成员方法
12 class Assistant{
13     public void dealFile(String file) {
14         System.out.println("帮忙处理文件:"+file);
15     }
16 }
17 //函数式接口,有个需要实现的抽象方法
18 @FunctionalInterface
19 interface WokerHelper {
20     void help(String file);
21 }

复制代码

 

2.类调用静态方法

  格式:类名 :: 静态方法名

  原理:将类的静态方法的参数和方法体,自动生成一个Lambda表达式。

复制代码

public class Demo {
    public static void main(String[] args) {
        methodCheck((str)->StringUtils.isBlank(str),"   ");//非省略模式
        methodCheck(StringUtils::isBlank,"  ");//省略模式  类名调用静态方法
    }
    //
    public static void methodCheck(StringChecker stringChecker,String str) {
        System.out.println(stringChecker.checkString(str));
    }
}
//定义一个类包含静态方法isBlank方法
class StringUtils{
    public static boolean isBlank(String str) {
        return str==null || "".equals(str.trim());//空格也算空
    }
}
//函数式接口,有个需要实现的抽象方法
@FunctionalInterface
interface StringChecker {
    boolean checkString(String str);
}

复制代码

 

3.this引用本类方法

  格式:this :: 本类方法名

  原理:将本类方法的参数和方法体,自动生成一个Lambda表达式。

复制代码

public class Demo {
    public static void main(String[] args) {
        new Husband().beHappy();
    }
}
class Husband{
    public void buyHouse() {
        System.out.println("买套房子");
    }

    public void marry(Richable richable) {
        richable.buy();
    }
    public void beHappy() {
        marry(this::buyHouse);//调用本类中方法
    }
}
//函数式接口,有个需要实现的抽象方法
@FunctionalInterface
interface Richable {
    void buy();
}

复制代码

 

4.super引用父类方法

  格式:super :: 父类方法名

  原理:将父类方法的参数和方法体,自动生成一个Lambda表达式。

复制代码

public class Demo {
    public static void main(String[] args) {
       new Man().sayHello();
    }
}
//子类
class Man extends Human{
    public void method(Greetable greetable) {
        greetable.greet();
    }
    @Override
    public void sayHello() {
        method(super::sayHello);
    }
}
//父类
class Human{
  public void sayHello() {
      System.out.println("Hello");
  }
}
//函数式接口,有个需要实现的抽象方法
@FunctionalInterface
interface Greetable {
    void greet();
}

复制代码

 

跟构造方法有关的方法引用:

5.类的构造器引用

  格式:  类名  :: new

  原理:将类的构造方法的参数和方法体自动生成Lambda表达式。

复制代码

public class Demo {
    public static void main(String[] args) {
        printName("张三",(name)->new Person(name));
        printName("张三",Person::new);//省略形式,类名::new引用
    }

    public static void printName(String name, BuildPerson build) {
        System.out.println(build.personBuild(name).getName());
    }
}

//函数式接口,有个需要实现的抽象方法
@FunctionalInterface
interface BuildPerson {
    Person personBuild(String name);
}
//实体类
class Person{
    String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

复制代码

 

6.数组的构造器引用

  格式: 数组类型[] :: new

  原理:将数组的构造方法的参数和方法体自动生成Lambda表达式。

复制代码

public class Demo {
    public static void main(String[] args) {
        int[] array1 = method(10, (length) -> new int[length]);
        int[] array2 = method(10, int[]::new);//数组构造器引用
    }

    public static int[] method(int length, ArrayBuilder builder) {
       return builder.buildArray(length);
    }
}

//函数式接口,有个需要实现的抽象方法
@FunctionalInterface
interface ArrayBuilder {
    int[] buildArray(int length);
}

复制代码

 

到此,Lambda表达式的基本知识就算学完了。

有人可能会提出疑问,Lambda表达式使用前要定义一个函数式接口,并在接口中有抽象方法,还要创建一个以函数式接口为参数的方法,之后调用该方法才能使用Lambda表达式,感觉并没有省很多代码!!哈哈,之所以有这样的想法,那是因为是我们自定义的函数式接口,而JDK1.8及更高的版本都给我们定义函数式接口供我们直接使用,就没有这么繁琐了。接下来我们学习一下JDK为我们提供的常用函数式接口。

常用的函数式接口

1.Supplier<T> 供给型接口

  

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。 

 如果要定义一个无参的有Object返回值的抽象方法的接口时,可以直接使用Supplier<T>,不用自己定义接口了。

复制代码

public class Demo {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "lambda";
        String s = method(() -> str1 + str2);
        System.out.println("s = " + s);
    }

    public static String method(Supplier<String> supplier) {
       return supplier.get();
    }
}

复制代码

2.Consumer<T> 消费型接口

复制代码

@FunctionalInterface
public interface Consumer<T> {


    void accept(T t);

  
  //合并2个消费者生成一个新的消费者,先执行第一个消费者的accept方法,再执行第二个消费者的accept方法  
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

复制代码

Consumer<T> 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定 。
如果要定义一个有参的无返回值的抽象方法的接口时,可以直接使用Consumer<T>,不用自己定义接口了。

复制代码

public class Demo {
    public static void main(String[] args) {
       consumerString(string -> System.out.println(string));
       consumerString(System.out::println);//方法引用形式
    }

    public static void consumerString(Consumer<String> consumer) {
       consumer.accept("fall in love!");
    }
}

复制代码

3.Predicate<T> 断定型接口

复制代码

@FunctionalInterface
public interface Predicate<T> {

   //用来判断传入的T类型的参数是否满足筛选条件,满足>true
    boolean test(T t);

    //合并2个predicate成为一个新的predicate---->并且&&
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

   //对调用的predicate原来的结果进行取反---->取反 !
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    //合并2个predicate成为一个新的predicate---->或||
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
  
}

复制代码

 

  Predicate<T>接口主要是对某种类型的数据进行判断,返回一个boolean型结果。可以理解成用来对数据进行筛选。

  当需要定义一个有参并且返回值是boolean型的方法时,可以直接使用Predicate接口中的抽象方法

  

复制代码

 1 //1.必须为女生;
 2 //2. 姓名为4个字。
 3 public class Demo {
 4     public static void main(String[] args) {
 5         String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
 6         List<String> list = filter(array,
 7                 str-> "女".equals(str.split(",")[1]),
 8                 str->str.split(",")[0].length()==3);
 9         System.out.println(list);
10     }
11     private static List<String> filter(String[] array, Predicate<String> one, Predicate<String> two) {
12         List<String> list = new ArrayList<>();
13         for (String info : array) {
14             if (one.and(two).test(info)) {
15                 list.add(info);
16             }
17         }
18         return list;
19     }
20 }

复制代码

 

4.Function<T,R> 函数型接口  

复制代码

@FunctionalInterface
public interface Function<T, R> {

   //表示数据转换的实现。T--->R
    R apply(T t);

   //合并2个function,生成一个新的function,调用apply方法的时候,先执行before,再执行this
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
  //合并2个function,生成一个新的function,调用apply方法的时候,先执行this,再执行after
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
}

复制代码

 

  Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有进有出,所以称为“函数Function”。

   该接口可以理解成一个数据工厂,用来进行数据转换,将一种数据类型的数据转换成另一种数据.   泛型参数T:要被转换的数据类型(原料),泛型参数R:想要装换成的数据类型(产品)。

复制代码

public class Demo {
    public static void main(String[] args) {
        String str = "赵丽颖,20";
        int age = getAgeNum(str,
                string ->string.split(",")[1],
                Integer::parseInt,//str->Integer.parseInt(str);
                n->n+=100);
        System.out.println(age);
    }
    //实现三个数据转换 String->String, String->Integer,Integer->Integer
    private static int getAgeNum(String str, Function<String, String> one,
                                Function<String, Integer> two,
                                Function<Integer, Integer> three) {
        return one.andThen(two).andThen(three).apply(str);
    }
}

复制代码

至此,常用的四个函数式接口已学习完毕。

总结一下函数式表达式的延迟方法与终结方法:

延迟方法:默认方法都是延迟的。

终结方法:抽象方法都是终结的。

接口名称方法名称抽象方法/默认方法延迟/终结
Supplierget抽象终结
Consumeraccept抽象  终结
 andThen默认延迟
Predicatetest抽象终结
 and默认延迟
 or默认延迟
 negate默认延迟
Functionapply抽象终结
 andThen  默认  延迟

 

函数式接口在Stream流中的应用较为广泛,其中Stream流中的过滤Filter方法使用到了Predicate的判定,map方法使用到了Function的转换,将一个类型的流转换为另一个类型的流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值