目录

函数式接口

函数式接口简介

什么是 @FunctionalInterface

内置的函数式接口

Stream和Lambda常用的函数式接口

函数式接口的使用

Lambda表达式

Lambda来源

Lambda原理

Lambda语法

Lambda语法案例

Lambda简化Runnable例子

自定义接口实现lambda

方法引用

处理Unchecked Exception

处理checked Exception


函数式接口

函数式接口简介

函数式接口:接口中只能有一个抽象方法,其他的可以有default、static、Object里继承的方法等。

​ 作用:在Java中主要用在Lambda表达式和方法引用(想使用Lambda表达式, 接口必须为函数式接口)。只有确保接口中有且只有一个抽象方法,Java中的lambda才能顺利推导。

什么是 @FunctionalInterface

​ JDK8专门提供了@FunctionalInterface注解,用来进行编译检查。

就是说一个接口有这个注解了,一定是函数式接口,必须满足函数式接口的要求,不然就编译出错!

一旦使用该注解来定义接口,编译器就会强制检查该接口,是否确实有且只有一个抽象方法,否则就会编译报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义规范,这仍然是一个函数式接口,使用起来都一样。

要求:

只含有且只有单个抽象方法的接口

必须是接口不能是抽象类

接口中可以有default (默认) 方法,私有方法和其他的静态方法

主要用途 用作 Lambda 表达式的类型

@FunctionalInterface
public interface FuncInterface {//只有一个抽象方法public void  method1();//default方法不计default  void method2(){}//static方法不计static void method3(){}//从Object继承的方法不计public boolean equals(Object obj);
}

如图,不满足条件就会报错

内置的函数式接口

JDK 也提供了大量的内置函数式接口,使得 Lambda 表达式的运用更加方便、高效。这些内置的函数式接口已经可以解决我们开发过程中绝大部分的问题,只有一小部分比较特殊得情况需要我们自己去定义函数式接口。在这里特别介绍四个函数式接口。

Consumer:消费型接口(void accept(T t))。有参数,无返回值 (上文forEach的参数类型就是Consumer)

Supplier:供给型接口(T get())。只有返回值,没有入参

Function<T, R>:函数型接口(R apply(T t))。一个输入参数,一个输出参数,两种类型不可不同、可以一致

Predicate:断言型接口(boolean test(T t))。输入一个参数,输出一个boolean类型得返回值

函数式接口 方法名 输入参数 输出参数 参数/吃草 返回/挤奶
消费型接口Consumer void accept(T t) T void 只吃草不挤奶
供给型接口Supplier T get() void T 只挤奶不吃草
函数型接口Function R apply(T t) T R 又吃草又挤奶
断言型接口Predicate boolean test(T t) T boolean Boolean类型

Stream和Lambda常用的函数式接口

函数式接口 参数类型 返回类型 描述
Supplier<T> T 提供一个T类型的值
Consumer<T> T void 处理一个T类型的值
BiConsumer<T,U> T, U void 处理T类型和U类型的值
Predicate<T> T boolean 一个计算Boolean值的函数
ToIntFunction<T> T int 计算int值的函数
ToLongFunction<T> T long 计算long值的函数
ToDoubleFunction<T> T double 计算double的函数
IntFunction<R> int R 参数为int类型的函数(特别注意)
LongFunction<R> long R 参数为long类型的函数
DoubleFunction<R> double R 参数类型为double的函数
Function<T,R> T R 一个参数类型为T的函数
BiFunction<T,U,R> T,U R 一个参数为T和U的函数
UnaryOperator<T> T T 对T进行一元操作
BinaryOperator<T> T,T T 对T进行二元操作

lambda常用的函数式接口

函数式接口 参数类型 返回类型 抽象方法名 描述 其他方法
Runnable void run 执行一个没有参数和返回值的操作
Supplier<T> T get 提供一个T类型的值
Consumer<T> T void accept 处理一个T类型的值 chain
BiConsumer<T,U> T,U void accept 处理T类型和U类型的值 chain
Function<T,R> T R apply 一个参数类型为T的函数 compose,andThen,identity
BiFunction<T,U,R> T,U R apply 一个参数类型为T和U的函数值 andThen
UnaryOperator<T> T T apply 对类型T进行的一元操作 compose,andThen,identity
BinaryOperator<T> T,T T apply 对类型T进行的二元操作 andThen
Predicate<T> T boolean test 一个计算boolean值的函数 And,or,negate,isEqual
BiPredicate<T,U> T,U boolean test 一个含有两个参数,计算boolean的函数 and,or,negate

函数式接口的使用

例如容器类的api里面,sort,replaceAll,foreach方法,会接受一个接口类型的对象,这里的接口一般就是一个函数式接口。

使用这些api时,一般会传入一个匿名内部类或者一个lambda表达式进去,作为这个接口类型的实例。

    default void sort(Comparator<? super E> c) {Object[] a = this.toArray();Arrays.sort(a, (Comparator) c);ListIterator<E> i = this.listIterator();for (Object e : a) {i.next();i.set((E) e);}}default void replaceAll(UnaryOperator<E> operator) {Objects.requireNonNull(operator);final ListIterator<E> li = this.listIterator();while (li.hasNext()) {li.set(operator.apply(li.next()));}}default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}}

消费型接口

public class TestFunctional1 {public static void main(String[] args) {List<Integer > list = new ArrayList<>();Collections.addAll(list,34,56,89,65,87);//使用匿名内部类实现Consumer consumer = new Consumer<Integer>() {@Overridepublic void accept(Integer elem) {System.out.println(elem);}};list.forEach(consumer);//使用Lambda表达式//list.forEach((elem)->{System.out.println(elem);});list.forEach((elem)->System.out.println(elem));}
}

断言型接口

public class TestFunctional2 {public static void main(String[] args) {List<Integer > list = new ArrayList<>();Collections.addAll(list,34,56,89,65,87);//使用匿名内部类实现System.out.println(list);Predicate predicate = new Predicate<Integer>(){@Overridepublic boolean test(Integer i) {if(i<60){return true;}return false;}};list.removeIf(predicate);System.out.println(list);//使用Lambda表达式实现list.removeIf((i)->{if(i > 80) {return true;}return false;});//list.removeIf(i->i>80); System.out.println(list);}
}

Lambda表达式

Lambda来源

2014年3月Oracle所发布的Java 8(JDK 1.8)中,加入了Lambda表达式的重量级新特性,为我们打开了新世界的大门。

Lambda表达式基于函数式编程思想,在数学中函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事”。和我们之前学的面向对象的编程思想“通过对象来做事情”有很大不同。函数式编程思想尽可能简化和忽略了面向对象的复杂语法----强调做什么,而不是像面向对象那样强调以什么形式做什么。

Java Lambda 表达式,也可以称为闭包,是Java 8引入的重要新特性, Lambda允许把函数作为一个方法的参数,使用它可以使代码变得简洁紧凑。

Lambda原理

“语法糖”是指使用更加方便,但原理不变的代码语法。例如在遍历集合是使用的for-each语法,其实它的底层的原理实现仍然是迭代器,这便是语法糖,从应用层面来说,Java中的Lambda可以被当作匿名内部类的“语法糖”,但两者在原理上是不一样的。 Lambda大致原理可能是,在编译的过程中动态生成一个内部类和一个静态私用方法,然后在使用lambda的地方调用这个生成的静态方法,这个静态方法的内容就和函数式接口中函数要实现的功能一致。

Lambda语法

( ) -> { }

一个放参数的圆括号 ():里面放参数,也可以没有,多个就以 ' , ' 分割

一个箭头 -> :用来传递参数到方法体中

一些要执行的代码 { } :重写的接口中的抽象方法的方法体

(parameters)->expression

//或

(parameter)->{statements;}

下面是lambda表达式的一些重要特征:

可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要。

可选的大括号:如果主题包含了一个语句,就不需要使用大括号。

可选的返回关键字:如果主体只有一个表达式返回值,则编译器会自动返回值,大括号需要指定明表达式返回了一个值。

Lambda语法案例

//01 不需要参数,返回值是5。
()->5//02 接收一个参数(数字类型),返回其两倍的值。
(x)->2*x  或  x->2*x//03 接收两个参数(数字),并返回他们的差值。
(int x , int y)-> x - y//接收一个String对象,并在控制台打印,不返回任何值,有点像void
(String s)->System.out.print(s)

Lambda简化Runnable例子

原本的实现方式:可以使用匿名内部类的方式创建一个线程,这已经是面向对象的编程方法能做到的最简化的书写了

 new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"线程启动成功");}}).start();

Thread类需要Runnable接口作为参数,其中的抽象run方法才是用来指定线程任务的核心。

为了指定run方法体,不得不创建Runnable接口的实现类。

为了省去定义实现类的麻烦,不得不使用匿名内部类。

为了覆盖重写原先在Runnable中的抽象方法run(),不得不再写一遍方法名称,方法参数,方法返回值,而且不能写错。

但经过分析好像只有run()中的方法体才是关键。

Lambda表达式的实现:

new Thread(()-> { System.out.println(Thread.currentThread().getName()+"线程启动成功"); }).start();

我们删去了前面提到的匿名内部类里面冗余的所有代码,只保留核心的任务——传递一个语句到Thread中执行。这样的写法在JDK1.8及以后都能编译通过,功能和前面写的匿名内部类的实现是一样的。

自定义接口实现lambda

先定义一个只有一个抽象方法的接口:

再在测试类中按格式定义一个静态方法并在主函数中书写Lambda表达式

public interface Cook {void makeFood(); //抽象方法无参无返回值
}public class cookDemo {public static void main(String[] args) {invokeCook(()->System.out.println("做食物ing")); //调用定义的静态方法}public static void invokeCook(Cook cook){cook.makeFood();}//按照这样的格式书写静态方法
}

方法引用

有时候,Lambda体可能仅调用一个已存在方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰。方法引用是一个更加紧凑,易读的 Lambda 表达式,注意方法引用是一个 Lambda 表达式,方法引用操作符是双冒号 “::”。

​ 方法引用有下面几种方式:

1 对象引用::实例方法名

2 类名::静态方法名

3 类名::实例方法名

4 类名::new (也称为构造方法引用)

5 类型[]::new (也称为数组引用)

public class LambdaTestC {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list, 1,2,4,5,12,56,121);System.out.println("===========内部匿名类===============");Consumer<Integer> co = new Consumer<Integer>() {@Overridepublic void accept(Integer integer) {System.out.println(integer);}};list.forEach(co);//简写System.out.println("=============lambda================");Consumer<Integer> co1 = v-> System.out.println(v);list.forEach(co1);System.out.println("=============lambda2================");//省略赋值操作最终简写list.forEach(v-> System.out.println(v));//方法引用list.forEach(System.out::println);}
}
public class FunctionalTestA {public static void main(String[] args) {Comparator<Integer> co = new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return Integer.compare(o1, o2);
//                return o1-o2;}};int i = co.compare(1, 20);System.out.println(i);System.out.println("===========lambda=============");Comparator<Integer> co2 = (x,y)->Integer.compare(x,y);System.out.println(co2.compare(1, 20));System.out.println("=============方法引用=================");Comparator<Integer> co3 = Integer::compare;System.out.println(co3.compare(1, 20));
;}
}

处理Unchecked Exception

Unchecked exception也叫做RuntimeException,出现RuntimeException通常是因为我们的代码有问题。RuntimeException是不需要被捕获的。也就是说如果有RuntimeException,没有捕获也可以通过编译。

我们看一个例子:

List<Integer> integers = Arrays.asList(1,2,3,4,5);

integers.forEach(i -> System.out.println(1 / i));

这个例子是可以编译成功的,但是上面有一个问题,如果list中有一个0的话,就会抛出ArithmeticException。

虽然这个是一个Unchecked Exception,但是我们还是想处理一下:

  integers.forEach(i -> {try {System.out.println(1 / i);} catch (ArithmeticException e) {System.err.println("Arithmetic Exception occured : " + e.getMessage());}});

上面的例子我们使用了try,catch来处理异常,简单但是破坏了lambda表达式的最佳实践。代码变得臃肿。

我们将try,catch移到一个wrapper方法中:

 static Consumer<Integer> lambdaWrapper(Consumer<Integer> consumer) {return i -> {try {consumer.accept(i);} catch (ArithmeticException e) {System.err.println("Arithmetic Exception occured : " + e.getMessage());}};}

则原来的调用变成这样:

integers.forEach(lambdaWrapper(i -> System.out.println(1 / i)));

但是上面的wrapper固定了捕获ArithmeticException,我们再将其改编成一个更通用的类:

 static <T, E extends Exception> Consumer<T>consumerWrapperWithExceptionClass(Consumer<T> consumer, Class<E> clazz) {return i -> {try {consumer.accept(i);} catch (Exception ex) {try {E exCast = clazz.cast(ex);System.err.println("Exception occured : " + exCast.getMessage());} catch (ClassCastException ccEx) {throw ex;}}};}

上面的类传入一个class,并将其cast到异常,如果能cast,则处理,否则抛出异常。

这样处理之后,我们这样调用:

integers.forEach(consumerWrapperWithExceptionClass(i -> System.out.println(1 / i),ArithmeticException.class));

处理checked Exception

checked Exception是必须要处理的异常,我们还是看个例子:

static void throwIOException(Integer integer) throws IOException {

}

List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);

integers.forEach(i -> throwIOException(i));

上面我们定义了一个方法抛出IOException,这是一个checked Exception,需要被处理,所以在下面的forEach中,程序会编译失败,因为没有处理相应的异常。

最简单的办法就是try,catch住,如下所示:

  integers.forEach(i -> {try {throwIOException(i);} catch (IOException e) {throw new RuntimeException(e);}});

当然,这样的做法的坏处我们在上面已经讲过了,同样的,我们可以定义一个新的wrapper方法:

 static <T> Consumer<T> consumerWrapper(ThrowingConsumer<T, Exception> throwingConsumer) {return i -> {try {throwingConsumer.accept(i);} catch (Exception ex) {throw new RuntimeException(ex);}};}

我们这样调用:

integers.forEach(consumerWrapper(i -> throwIOException(i)));

我们也可以封装一下异常:

static <T, E extends Exception> Consumer<T> consumerWrapperWithExceptionClass(ThrowingConsumer<T, E> throwingConsumer, Class<E> exceptionClass) {return i -> {try {throwingConsumer.accept(i);} catch (Exception ex) {try {E exCast = exceptionClass.cast(ex);System.err.println("Exception occured : " + exCast.getMessage());} catch (ClassCastException ccEx) {throw new RuntimeException(ex);}}};}

然后这样调用:

integers.forEach(consumerWrapperWithExceptionClass(i -> throwIOException(i), IOException.class));

java 函数式接口与Lambda表达式相关推荐

  1. java 函数式接口与lambda表达式的关系

    函数式接口与lambda表达式的关系 在java中,lambda表达式与函数式接口是不可分割的,都是结合起来使用的. 对于函数式接口,我们可以理解为只有一个抽象方法的接口,除此之外它和别的接口相比并没 ...

  2. JAVA学习笔记 15 - 函数式接口、Lambda表达式和方法引用

    本文是Java基础课程的第十五课.主要介绍在JDK8中,Java引入的部分新特性,包括函数式接口.Lambda表达式和方法引用.这些新特性使得Java能够在按照面向对象思想进行开发的基础上,融合函数式 ...

  3. Java新特性:Java8函数式接口与Lambda表达式(一)

    摘要 何为函数式接口? 什么是lambda表达式,lambda表达式的本质: 函数式接口与lambda表达式的联系:lambda是实现函数式接口的一个快捷方式,可以作为函数式接口的一个实例: 常用Ja ...

  4. Java 函数式编程和 lambda 表达式

    为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于 ...

  5. Java函数式编程和Lambda表达式

    文章目录 什么是函数式编程 Lambda表达式 @FunctionalInterface函数式接口 Lambda表达式的格式 方法引用 什么是函数式编程 相信大家都使用过面向对象的编程语言,面向对象编 ...

  6. java8 Lambda表达式的应用(函数式接口、lambda表达式,方法引用及Stream API)

    之前写了一篇博客简单介绍了一下java 8发布新增的一些特性功能,java 8在2014年发布,距今也不少年了,但是lambda表达式使用并不熟练,现在一边学习,一边记录一下. 目录 一.Lambda ...

  7. Java8函数式接口与Lambda表达式

    摘要 何为函数式接口? 什么是lambda表达式,lambda表达式的本质: 函数式接口与lambda表达式的联系:lambda是实现函数式接口的一个快捷方式,可以作为函数式接口的一个实例: 常用Ja ...

  8. Lambda01 编程范式、lambda表达式与匿名内部类、函数式接口、lambda表达式的写法...

    1 编程范式 主要的编程范式有三种:命令式编程,声明式编程和函数式编程. 1.1 命令式编程 关注计算机执行的步骤,就是告诉计算机先做什么后做什么 1.2 声明式编程 表达程序的执行逻辑,就是告诉计算 ...

  9. 【Java】函数式接口与Lambda表达式

    函数式接口--@FunctionalInterface Code @FunctionalInterface interface IService {void say(String message); ...

最新文章

  1. 大学生学python到底有没有有-为什么我会想建议每个大学生都学一点编程?
  2. 静态程序分析chapter1 - 概述和两个重要步骤
  3. shell把字符串中的字母去掉,只保留数字
  4. [Serializable]序列化一句话理解
  5. 庞佐错觉_水晶球错觉
  6. [react] React中怎么操作虚拟DOM的Class属性
  7. 什么样的产品可以成功?
  8. 无向图的邻接表表示法 及 深搜遍历DFS
  9. winform窗体对象 单例模式与泛型结合
  10. android person类_es5 类与es6中class的区别小结_javascript技巧
  11. IOS开发学习笔记027-UITableView 使用模型对象
  12. ARINC818(FC-AV)协议,ADVB
  13. git鉴权失败问题 以及每次clone 都要输入用户名密码问题
  14. Pandas CSV 文件
  15. vm服务器虚拟机如何导出报表,教程:浏览 VM 中的 Power BI 报表服务器 - Power BI | Microsoft Docs...
  16. 慕课编译原理(第十章.构造优先关系表)
  17. 什么人适合学习大数据开发?学大数据难吗?
  18. android textview 文字居中无效,android – 不能垂直居中textview的文本
  19. OSPF——DR和BDR讲解
  20. IBM ACE User Defined Node

热门文章

  1. linux模拟树莓派,使用QEMU模拟树莓派Raspberry Pi
  2. 录屏怎么录声音?注意一点轻松录制外部音源
  3. 小程序uni-app介绍
  4. 华为新版Datacom认证介绍
  5. Apache Camel入门教程
  6. starUML for MAC 破解方法
  7. 找回密码功能实现步骤
  8. SLAM的数学基础(3):几种常见的概率分布的实现及验证
  9. JAVA:List复制
  10. 想到我爱你的绝对不正常