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 ...
随机推荐
- web设计_7_页面缺失图片或CSS的情况下仍然易读
1. 在任何可能使用背景图片的地方应设置同样的颜色的背景色. 防止图片不能加载的情况下,页面内容同样保持较好可读性. 例如文字为白色,背景图为深色,如果不设置背景色,当背景图未成功加载, 而浏览器多数 ...
- TestNG独立运行的几种方法.md
目录 通过main函数调用testng.xml文件,然后打成可执行jar包执行 1 假设我们已经写好了testng.xml,现在我们需要写一个主类和main函数用来调用testng.xml 2 把项目 ...
- pheatmap绘制“热图”,你需要的都在这
热图可以聚合大量的数据,并可以用一种渐进色来优雅地表现,可以很直观地展现数据的疏密程度或频率高低. 本文利用R语言 pheatmap 包从头开始绘制各种漂亮的热图.参数像积木,拼凑出你最喜欢的热图即可 ...
- python:函数可以返回值--编写脚本计算24 + 34 / 100 - 1023
1.脚本 def add(a,b): return (a+b)def div(a,b,c): return (a/b-c)x = div(34,100,1023)y = add(24,x) ...
- python3 读取文件-2
1.脚本 from sys import argv script,filename = argv#以读的模式打开txt文件txt = open(filename,'r+')print ("t ...
- 2019.7 佳木斯培训A层
day1题目及题解 day2题目及题解 day3题目及题解 day4题目及题解 day5题目及题解
- git之coding.net的使用
先在Coding上创建个项目 现在是这样,我本地有个项目Project(/Users/huang/Desktop/Project),我想把它上传到刚创建的项目里,以后就用git代码托管.可我之 ...
- WPF:事件委托对于不同界面间通信的应用
界面1内设定点击事件,生成Path用事件传出public partial class TemplateWindow : Window { internal delegate v ...
- java并发编程(十九)----(JUC集合)总体框架介绍
本节我们将继续学习JUC包中的集合类,我们知道jdk中本身自带了一套非线程安全的集合类,我们先温习一下java集合包里面的集合类,然后系统的看一下JUC包里面的集合类到底有什么不同. java集合类 ...
- 前端模板引擎doT.js的用法
简介 一款简单好用的前端模板引擎 用法 <script type="text/javascript" src="js/doT.min.js">< ...