Java8新特性_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表达式。
 public class Demo {
     public static void main(String[] args) {
         Assistant assistant = new Assistant();
         work(assistant::dealFile);//对象引用成员方法(注意是成员的方法名,没有小括号)
     }
     //以函数式接口为参数的方法
     public static void work(WokerHelper wokerHelper) {
         wokerHelper.help("机密文件");
     }
 }
 //助理类,有个成员方法
 class Assistant{
     public void dealFile(String file) {
         System.out.println("帮忙处理文件:"+file);
     }
 }
 //函数式接口,有个需要实现的抽象方法
 @FunctionalInterface
 interface WokerHelper {
     void help(String file);
 }
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.必须为女生;
//2. 姓名为4个字。
public class Demo {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
List<String> list = filter(array,
str-> "女".equals(str.split(",")[1]),
str->str.split(",")[0].length()==3);
System.out.println(list);
}
private static List<String> filter(String[] array, Predicate<String> one, Predicate<String> two) {
List<String> list = new ArrayList<>();
for (String info : array) {
if (one.and(two).test(info)) {
list.add(info);
}
}
return list;
}
}
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的转换,将一个类型的流转换为另一个类型的流。
Java8新特性_lambda表达式和函数式接口最详细的介绍的更多相关文章
- Java8新特性—四大内置函数式接口
		
Java8新特性--四大内置函数式接口 预备知识 背景 Lambda 的设计者们为了让现有的功能与 Lambda 表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念. 什么是函数式接口? 函数 ...
 - Java8 新特性_Lambda 表达式
		
1. Java8新特性_简介 Lambda 表达式 函数式接口 方法引用与构造器引用 Stream API 接口中的默认方法与静态方法 新时间日期 API 减少空指针异常的容器 Optional 2. ...
 - Java8新特性-Lambda表达式是什么?
		
目录 前言 匿名内部类 函数式接口 和 Lambda表达式语法 实现函数式接口并使用Lambda表达式: 所以Lambda表达式是什么? 实战应用 总结 前言 Java8新特性-Lambda表达式,好 ...
 - 乐字节-Java8新特性-Lambda表达式
		
上一篇文章我们了解了Java8新特性-接口默认方法,接下来我们聊一聊Java8新特性之Lambda表达式. Lambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作 ...
 - java8新特性—四大内置核心接口
		
java8新特性-四大内置核心接口 四大内置核心接口 //消费型接口 Consumer<T>:: vode accept(T t); //供给型接口 Supplier<T>:: ...
 - java8新特性——Lambda表达式
		
上文中简单介绍了一下java8得一些新特性,与优点,也是为本次学习java8新特性制定一个学习的方向,后面几篇会根据上文中得新特性一一展开学习.本文就从java8新特性中比较重要的Lambda表达式开 ...
 - Java8 新特性 Lamdba表达式
		
Lamdba 表达式为什么出现 Java8是自java延生以来最大的改变,他允许java中的方法和函数成为一等公民(可以在方法间传递),所以就应运而出现了Lamdba表达式,他可以将表达式传递给另 ...
 - JAVA8新特性——Lamda表达式
		
JAVA9都要出来了,JAVA8新特性都没搞清楚,是不是有点掉队哦~ Lamda表达式,读作λ表达式,它实质属于函数式编程的概念,要理解函数式编程的产生目的,就要先理解匿名内部类. 先来看看传统的匿名 ...
 - java8新特性1--Lambda表达式
		
一.Lambda表达式是什么? Lambda表达式有两个特点 一是匿名函数,二是可传递. 匿名函数的应用场景是 通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用Lambda表达式.lam ...
 
随机推荐
- 手工sql注入(重点)
			
sql 子查询:() select goods_name from goods where goods_id=(select max(goods_id) from goods): 联合查询:selec ...
 - 【iOS】打印方法名
			
为了便于追踪程序运行过程,可以在日志打印方法名,示例代码如下: NSLog(@"%@", NSStringFromSelector(_cmd)); 结果如图所示: 此外,在多个中, ...
 - OI/ACM最全卡常大招
			
NO.10: 循环展开: 在缓存和寄存器允许的情况下一条语句内大量的展开运算会刺激 CPU 并发(蛤?这是个什么原理,算了,反正写了没坏处就这么写吧) NO.9: 特殊运算优化:(或许这真的没用) 取 ...
 - python协程详解
			
目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...
 - X-Admin&ABP框架开发-系统日志
			
网站正常运行中有时出现异常在所难免,查看系统运行日志分析问题并能够根据错误信息快速解决问题尤为重要,ABP对于系统运行日志这块已经做了很好的处理,默认采用的Log4Net已经足够满足开发过程中的需要了 ...
 - Asp.Net Core 发布到 Docker(Linux Centos 虚拟机,使用Dockerfile)
			
实践一下 Asp.Net Core (基于.net core 2.2)部署到Docker 一.准备工作: 1. 使用Virtualbox创建一个Centos系统的虚拟机,并安装docker和vim 2 ...
 - basic
			
vmware三种网络类型 bridged(桥接)通过物理主机网卡架设桥,从而连入实际网络,最接近正式网络环境 NAT(地址转换)虚拟机通过宿主机转发地址上网,宿主机ip更改虚拟机不需要改.过程:虚拟机 ...
 - 【原创】原来你竟然是这样的Chrome?!Firefox笑而不语
			
书接上文 上一篇文章<[原创]用事实说话,Firefox 的性能是 Chrome 的 2 倍,Edge 的 4 倍,IE11 的 6 倍!>,我们对比了不同浏览器下FineUIPro一个页 ...
 - Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理
			
本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看 ...
 - android ——调用摄像头拍照和相册
			
先在布局文件中加入两个按钮和一个图片控件 <?xml version="1.0" encoding="utf-8"?> <LinearLayo ...