Java8新特性_lambda表达式和函数式接口最详细的介绍

Lambda表达式

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

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

 

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表达式数组

 

对以上代码的分析:app

对于 Runnable 的匿名内部类用法,能够分析出几点内容:ide

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

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

咱们真的但愿建立一个匿名内部类对象吗?不。咱们只是为了作这件事情而不得不建立一个对象。咱们真正但愿作的事情是:将 run 方法体内的代码传递给 Thread 类知晓。性能

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

那,有没有更加简单的办法?若是咱们将关注点从怎么作回归到作什么的本质上,就会发现只要可以更好地达到目的,过程与形式其实并不重要。 ui

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

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

什么是Lambda表达式?

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

做为一种更紧凑的代码风格,使Java的语言表达能力获得了提高 。20143Oracle所发布的Java 8JDK 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);
    }
}

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

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

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

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

接口名称 方法名称 抽象方法/默认方法 延迟/终结
Supplier get 抽象 终结
Consumer accept 抽象   终结
  andThen 默认 延迟
Predicate test 抽象 终结
  and 默认 延迟
  or 默认 延迟
  negate 默认 延迟
Function apply 抽象 终结
  andThen   默认   延迟

 

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

相关文章
相关标签/搜索