在说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的语言表达能力获得了提高 。(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); } }
至此,经常使用的四个函数式接口已学习完毕。
总结一下函数式表达式的延迟方法与终结方法:
延迟方法:默认方法都是延迟的。
终结方法:抽象方法都是终结的。
接口名称 | 方法名称 | 抽象方法/默认方法 | 延迟/终结 |
Supplier | get | 抽象 | 终结 |
Consumer | accept | 抽象 | 终结 |
andThen | 默认 | 延迟 | |
Predicate | test | 抽象 | 终结 |
and | 默认 | 延迟 | |
or | 默认 | 延迟 | |
negate | 默认 | 延迟 | |
Function | apply | 抽象 | 终结 |
andThen | 默认 | 延迟 |
函数式接口在Stream流中的应用较为普遍,其中Stream流中的过滤Filter方法使用到了Predicate的断定,map方法使用到了Function的转换,将一个类型的流转换为另外一个类型的流。