lambda与java8函数式编程

lambda表达式

Lambda表达式的基本语法::(parameters) -> expression 或 (parameters) ->{ statements; }

  • () -> 代表了 lambda的一个表达式
  • 单行代码无需写return (无论函数式接口有没有返回值),花括号
  • 多行代码必须写花括号,有返回值的一定要写返回值
  • 单行代码且有参数的情况下可以不写 () 如 s->System.out.println(s)
  • (T t)中的参数类型可写可不写

例子

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)  

主要使用场景

1. 简化匿名类的编码
2. 减少不必要的方法创建
3. 事件处理
4. stream中使用

函数式编程

函数式接口

1 .概念

函数式接口在java中是指:有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口。而java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

备注:“语法糖"是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。

2. 格式

接口中只能存在一个抽象方法

    @FunctionalInterface
    interface Callback {
        void callback();
    }

3.@FunctionalInterface注解

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

  • 被@FunctionalInterface注释的接口,满足函数式接口的约束。
  • 没有被@FunctionalInterface注释的接口,但是满足函数式接口的约束。

函数式的约束:  

  • 接口有且只能有个一个抽象方法,只有方法定义,没有方法体  。
  • 在接口中覆写Object类中的public方法,不算是函数式接口的方法。
  • 在接口中的default方法,不算是函数式接口的方法。
  • 在接口中的static方法,不算是函数式接口的方法。

常用函数式接口

Consumer<T>:消费型接口

代表了接受一个输入参数并且无返回的操作

        Consumer<String> testConsumer = param -> System.out.println(param);
        testConsumer.accept("testConsumer");

Consumer默认提供了andThen

作用:用于连接两个Consumer接口,一个是调用andThen方法的Consumer接口this,一个是andThen方法的参数after。

con1.andThen(con2).accept(s);等价于con1.accept(s);con2.accept(s);

Supplier<T>:供给型接口

无参数,返回一个结果。

        Supplier<String> testSupplier = () -> String.valueOf(Math.random());
        System.out.println(testSupplier.get());

Function<T, R>:函数型接口

接受一个输入参数,返回一个结果

        Function<Integer, Integer> testFunction = s -> s * 2;
        System.out.println(testFunction.apply(6));

默认提供的一些方法

Predicate<T>:断言型接口

 接受一个输入参数,返回一个布尔值结果。

        Predicate<String> testPredicate = s -> s.equals("test");
        System.out.println(testPredicate.test("test"));

默认提供的一些方法

注意:

  • and方法与逻辑运算符&&功能相同
  • or方法与逻辑运算符||功能相同
  • negate方法与逻辑运算符!功能相同

每种接口都有一些默认的方法,可以根据业务需求组合出更多的效果

扩展函数接口

参数个数上扩展:

比如接收双参数的,有 Bi 前缀, 比如 BiConsumer<T,U>, BiFunction<T,U,R> ;

特殊常用的变形:

比如 BinaryOperator , 是同类型的双参数 BiFunction<T,T,T> ,二元操作符 ; UnaryOperator 是 Function<T,T> 一元操作符。

类型上扩展:

比如接收原子类型参数的,比如 [Int|Double|Long] [Function|Consumer|Supplier|Predicate]

为什么要有基本类型扩展

只有对象类型才能作为泛型参数,对于基本类型就涉及到装箱拆箱的操作,虽然是自动的

但是这不可避免给内存带来了额外的开销,装箱和拆箱都会带来开销

所以为了减小这些性能开销   对基本类型进行类型扩展

Stream 类的某些方法对基本类型和装箱类型做了区分

Java 8中,仅对 整型、长整型和双浮点型做了特殊处理  因为它们在数值计算中用得最多

对基本类型做特殊处理的方法在命名上有明确的规范

  • 如果参数是基本类型,则不加前缀只需类型名即可
  • 如果方法返回类型为基本类型,则在基本类型前再加上一个 To

总结一句话:加了类型前缀[Int|Double|Long] 表示参数是基本类型, 如果在此基础上又加上了To  表示返回类型是基本类型

如有可能,应尽可能多地使用对基本类型做过特殊处理的方法,进而改善性能

其它

方法引用

方法引用可以在某些条件成立的情况下,更加简化lambda表达式的声明。方法引用语法格式有以下三种:

  • objectName::instanceMethod
  • ClassName::staticMethod
  • ClassName::instanceMethod

前两种方式类似,等同于把 lambda 表达式的参数直接当成 instanceMethod/staticMethod 的参数来调用。比如 System.out::println 等同于 x->System.out.println(x);Math::max 等同于 (x, y)->Math.max(x,y) 。

最后一种方式,等同于把lambda表达式的第一个参数当成 instanceMethod 的目标对象,其他剩余参数当成该方法的参数。比如 String::toLowerCase 等同于 x -> x.toLowerCase()。

//Funciton
Lambda表达式: (Apple a) -> a.getWeight()
等价的方法引用: Apple::getWeight
//Conusmer
Lambda表达式: () -> Thread.currentThread().dumpStack()
等价的方法引用: Thread.currentThread()::dumpStack
//BiFunction
Lambda表达式: (str, i) -> str.substring(i)
等价的方法引用: String::substring
//Function
Lambda表达式: (String s) -> System.out.println(s)
等价的方法引用: System.out::printl

只有当lambda表达式的体只调用一个方法而不做其他操作时,才可以把lambda表达式重写为方法引用。如以下表达式:

s -> s.length == 0

这里有一个方法调用,但是还有一个比较,因而这里不能使用方法引用。

构造器引用

构造器引用语法如下:ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)。3

//无参构造函数
Supplier<Apple> c1 = () -> new Apple();
Supplier<Apple> c1 = Apple::new;
//一元构造函数
Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Function<Integer, Apple> c2 = Apple::new;
//二元构造函数
BiFunction<Integer, String, Apple> c3 =(weight, color) -> new Apple(weight, color);
BiFunction<Integer, String, Apple> c3 = Apple::new

Lambda表达式和匿名内部类的区别

一、所需类型不同:

  • 匿名内部类可以是:接口、抽象类、或者具体类
  • Lambda 表达式只能是:接口

二、使用限制不同:

  • 匿名内部类:接口中可以多个或者一个方法
  • Lambda 表达式要求:接口中的只能有一个方法

三、实现原理不同:
查看项目文件夹中的时候可以看到

  • 匿名内部类:编译之后会产生单独的 .class 文件
  • Lambda 表达式:编译之后不会有单独的 .class 文件出现,对应的字节码会在运行的时候动态生成。

转载

https://blog.csdn.net/wanghao112956/article/details/91865095

https://www.cnblogs.com/dgwblog/p/11739500.html

https://cloud.tencent.com/developer/article/1333532

https://www.cnblogs.com/hellovan/p/13528201.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章