文章目录
Lambda表达式和函数式编程
一、Lambda表达式
1.1、函数式编程思想概述
函数式编程思想
函数式编程(英语:functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。
其中,λ演算(lambda calculus)为该语言最重要的基础。而且,λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。
比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程
,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
在函数式编程中,函数是第一类对象,意思是说一个函数,既可以作为其它函数的参数(输入值), 也可以从函数中返回(输入值),被修改或者被分配给一个变量。
我们关注的是得到的结果而不是过程。
函数式编程的理论基础是Lambda演算,其本身是一种数学的抽象但不是编程语言。另一个组合逻辑是比它更加古老和基础的数学根基。两者都是为了更好的表达数学基础才被开发的。
1.2、演示冗余的Runnable代码
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是使用匿名内部类实现的线程任务");
}
}).start();
使用Lambda表达式来优化:
new Thread(()-> System.out.println("我是使用lambda表达式来创建的线程,参数必须是一个函数式接口。即只含有一个抽象方法。")).start();
1.3、Lambda标准格式
首先创建一个函数式接口,cook
package Lambda表达式;
/**
* 定义一个实验接口,函数式接口,利用一个注解声明。里面可以有默认方法和静态方法。
* 但是只能有一个抽象方法。
*
*/
@FunctionalInterface
public interface Lambda02_Cook {
void makeRice(String mess);//做饭
default void say(){
System.out.println("我会做饭!");
}
}
测试:
package Lambda表达式;
import java.util.Arrays;
/**
* 匿名内部类的弊端、好处:
* 1、使用一个子类,省略了一个类的定义。
* 2、写法复杂。
*
* Lambda表达式的好处:
* 1、简化书写。
* 2、更加关注于结果。
*
* Lambda的格式,以及满足Lambda表达式简化的条件:
*
* 标准格式:
* 1、一些参数
* 2、一个括号
* 3、语句块
*
* 如()->{}
*
*
*/
public class Lambda02_BasicFormat {
public static void main(String[] args) {
new Lambda02_BasicFormat().canCook("我做的大米饭喔", new Lambda02_Cook() {
@Override
public void makeRice(String mess) {
System.out.println(mess);
}
});
new Lambda02_BasicFormat().canCook("我会做大米饭2",(String mess)-> System.out.println(mess));
//IDEA还提示了简化:就是方法引用
new Lambda02_BasicFormat().canCook("我会做大米饭3", System.out::println);
}
public void canCook(String mess,Lambda02_Cook cook){//有一个方法,其参数是包含一个函数式接口
cook.makeRice(mess);
}
}
1.4、Lambda表达式有无参数及其格式省略
返回值的函数式接口一样可以用Lambda表达式。
关于格式的省略问题:
Lambda的表达式 :可推到、可省略。
即凡是通过上下文来推导得出的都可以省略。一般Lambda的表达式省略的可以有:数据类型,返回值,{},;
1、如果代码只有一行,则可以省略大括号{},省略分号、省略返回值,要省略则这三个要一起省略
2、如果参数列表有多个,则可以省略参数类型。
3、如果参数只有一个,则可以将类型省略,()省略。
4、如果没有参数,则()不可省略。空着。
首先创建一个测试的函数式接口
package Lambda表达式;
/**
* 测试格式
*
*/
@FunctionalInterface
public interface Lambda03_Test{
int getE(int a);
}
测试:
package Lambda表达式;
public class Lambda03_ReturnValue {
public static void main(String[] args) {
new Lambda03_ReturnValue().getSum(3,4,new Lambda03_Calculator(){
@Override
public int sum(int a, int b) {
return a+b;
}
});
new Lambda03_ReturnValue().getSum(3,4,(a,b)->a+b);
//IDEA会提示简化,即方法引用。
new Lambda03_ReturnValue().getSum(3,4, Integer::sum);
//有函数值和参数类型只有一个时:
new Lambda03_ReturnValue().getType(4,(int a)->a+4);
new Lambda03_ReturnValue().getType(5,a->a+5);//类型省略,()省略,只有一语句,则return,;{}都省略。
new Lambda03_ReturnValue().getType(5, new Lambda03_Test() {//原本的写法
@Override
public int getE(int a) {
return a+7;
}
});
}
public void getSum(int a,int b,Lambda03_Calculator calculator){
int result = calculator.sum(a,b);
System.out.println(result);
}
public void getType(int a,Lambda03_Test test){
System.out.println(a+test.getE(a));
}
}
1.5、Lambda表达式是匿名内部类的‘语法糖’?
语法糖
什么是语法糖?
-
就是原理上是一样的,但是简化了书写;
-
集合元素遍历里面的,如For-each,原理都是利用了底层的迭代器。但是简化了书写而已。
但是Lambda表达式从应用上看,是匿名内部类 的‘语法糖’。但是实际上原理是不一样的。
-
Java Lambda表达式的一个重要用法是简化某些匿名内部类(Anonymous Classes)的写法。
-
实际上Lambda表达式并不仅仅是匿名内部类的语法糖,JVM内部是通过invokedynamic指令来实现Lambda表达式的。
1.6、Lambda表达式的注意事项
- 1、必须在方法中使用
- 2、参数是一个函数式接口,才能用Lambda表达式替代。
- 3、不能在lambda内部修改定义在域外的变量,可以读取
int a = 0;
new Thread(() -> a++).start();
//Variable used in lambda expression should be final or effectively final
- 4、当代码体不修改Lambda表达式提供的参数时候,代码体可以替换为方法引用,如果修改参数的时候只能使用Lambda表达式
new Lambda02_BasicFormat().canCook("我会做大米饭2",(String mess)-> System.out.println(mess));//mess没有修改
//IDEA还提示了简化:改为了方法引用
new Lambda02_BasicFormat().canCook("我会做大米饭3", System.out::println);
二、函数式编程及常用接口
2.1、性能浪费的例子:
package Lambda表达式;
/**
* 性能浪费的例子:
* 日志打印
*/
public class FunctionInterface02 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "c";
new FunctionInterface02().log(1,s1+s2+s3);//无论级别level等不等于1,字符串拼接都会进行
}
public void log(int level,String mess){
if(level==1)
System.out.println("mess = " + mess);
}
}
这一行代码:假设级别level=2,压根就不满足条件,但是却将字符串进行拼接了。性能耗费了。
new FunctionInterface02().log(1,s1+s2+s3);
//无论级别level等不等于1,字符串拼接都会进行
这样的情况如果使用Lambda表达式:
1、定义一个接口:
package Lambda表达式;
@FunctionalInterface
public interface FunctionInterface02_test {
String concat();
}
2、使用该接口优化:
package Lambda表达式;
/**
* 性能浪费的例子:
* 日志打印
*/
public class FunctionInterface02 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "c";
/*new FunctionInterface02().log(1,s1+s2+s3);//无论级别level等不等于1,字符串拼接都会进行*/
new FunctionInterface02().log(1,()->s1+s2+s3);
}
public void log(int level,FunctionInterface02_test test){
if(level==1){
System.out.println(test.concat());
}
}
}
从调用过程来看,只有level满足条件,才会调用concat方法的调用。也就是现在才拼接。否则不会拼接。
2.2、常用的函数式接口
2.2.1、Supplier接口—— 生产数
生产型接口,通过泛型参数可以知道,你给它什么类型的数据,它就返回什么类型的数据。
下面演示利用其来返回数据。
package Lambda表达式;
import java.util.function.Supplier;
public class FunctionInterface_Supplier {
public static String getMess(Supplier<String> supplier){
return supplier.get();
}
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
/* String mess = FunctionInterface_Supplier.getMess(new Supplier<String>() {
@Override
public String get() {
return s1+s2;//函数式接口,如果函数内不修改外部的数据,则可以。否则外部数据需要声明为efficiency final
}
});*/
String mess = FunctionInterface_Supplier.getMess(() -> s1 + s2);//无论有没有返回值都省略,泛型参数都省略了。。。
System.out.println("mess = " + mess);
//利用lambda表达式
}
}
2.2.2、Consumer接口——消费数
Interface Consumer<T>
参数类型
T - 操作的输入类型
All Known Subinterfaces:
Stream.Builder<T>
Functional Interface:
这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。
@FunctionalInterface
public interface Consumer<T>
表示接受单个输入参数并且不返回任何结果的操作。 与大多数其他功能接口不同, Consumer预计会通过副作用运行。
这是一个functional interface,其功能方法是accept(Object) 。
从以下版本开始:
1.8
Consumer里面有两个定义的方法
1、抽象方法accept(T t);
消费一个数据,至于怎么消费,我们自己需要定义。如打印出来,计算最大值并打印。。。
public class FunctionInterface03_Consumer {
public static void displayMess(String mess, Consumer<String> consumer){
consumer.accept(mess);
}
public static void main(String[] args) {
displayMess("我是cumsumer,使用Lambda表达式",( mess)->System.out.println(mess));
//参数只有一个,语句只有一个,可以省略类型定义,返回值、{},;
//当语句没有对参数进行修改,仅仅进行访问,可以改为方法引用。
displayMess("我是cumsumer,使用方法引用", System.out::println);
}//参数只有一个,语句只有一个,可以省略类型定义,返回值、{},;
}
2、默认方法andThen();
andThen
源码
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);//先判断是否为空,是则抛出空指针异常。
return (T t) -> { accept(t); after.accept(t); };//lambda表达式,调用两个consumer对象的accept方法,消费同一个数据
}
返回一个组合Consumer ,它按顺序执行此操作,然后执行after操作。 如果执行任一操作抛出异常,它将被中继到组合操作的调用者。 如果执行此操作会引发异常,则不会执行after操作。
- 参数
after - 此操作后要执行的操作 - 结果
一个由 Consumer组成的 Consumer ,按顺序执行此操作,然后执行 after操作 - 异常
NullPointerException - if after is null
比如现在我们要消费两次的该数据
Consumer<String>con1;
Consumer<String>con2;
String s = "猪猪";
con1.accept(s);
con2.accept(s);
使用andThen方法简写,也就是用andThen链接两个Consumer接口,进行消费同一个数据。
con1.andThen(con2).accept();
测试:
public class FunctionInterface03_Consumer {
public static void displayMess(String mess, Consumer<String> consumer){
consumer.accept(mess);
}
public static void displayMessTwice(String mess, Consumer<String>consumer){
consumer.andThen(s -> System.out.println(mess + " 我是二号 ")).accept(mess);
}
public static void main(String[] args) {
displayMessTwice("我是comsumer",(mess)->System.out.println(mess+" 我是一号 "));
}
}
2.2.3、Predicate接口——断言型函数式接口、逻辑运算
/**
* Interface Predicate<T>
* 参数类型
* T - 谓词的输入类型
* Functional Interface:
* 这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。
* --------------------------------------------------------------------------------
*@FunctionalInterface
* public interface Predicate<T>
表示一个参数的谓词(布尔值函数)。
* 这是一个functional interface,其功能方法是test(Object) 。
* 从以下版本开始:
* 1.8
*/
里面的方法:
- 一个抽象方法
boolean test(T t);//根据给定的参数计算此谓词。
- 两个静态方法
static <T> Predicate<T> not(Predicate<? super T> target)
// 返回谓词,该谓词是提供的谓词的否定。
static <T> Predicate<T> isEqual(Object targetRef)
//返回一个谓词,根据 Objects.equals(Object, Object)测试两个参数是否相等。
- 三个默认方法。
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}//返回一个组合谓词,表示此谓词和另一个谓词的短路逻辑AND。
default Predicate<T> negate() {
return (t) -> !test(t);
}
//返回表示此谓词的逻辑否定的谓词。 结果 :表示此谓词的逻辑否定的谓词
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
//返回一个组合谓词,表示此谓词与另一个谓词的短路逻辑OR。
测试:
1、test方法
public class FunctionInterface04_Predicate {
public static void isBiggerThanTen(int t, Predicate<Integer> predicate){
if(predicate.test(t)){//大于10则输出
System.out.println("输入的数字大于10,可以输出:"+t);
}else System.out.println("t = " + t + "小于10");
}
public static void isThreeBitNumber(int t,Predicate<Integer> predicate1,Predicate<Integer> predicate2){//判断是否为三位数
if(predicate1.and(predicate2).test(t)){
//等效于:predicate1.test()&&predicate2.test();
System.out.println("输入的数字是三位数"+t);
}else System.out.println("输入的数字不是三位数"+t);
}
public static void main(String[] args) {
isBiggerThanTen(23,(t)->t>10);
isThreeBitNumber(120,(t)->t>99,(t)->t<1000);
}
}
public static void testAllMethod(int t, Predicate<Integer> predicate1, Predicate<Integer> predicate2){
if(!predicate1.negate().test(0)){//测试是否为负数
System.out.println("输入的数是负数: "+t);
}else System.out.println("输入的数不是负数: "+t);
}
public static void main(String[] args) {
testAllMethod(100,(t)->t>0,(t)->t>10);
}
其他方法:
if(predicate1.and(predicate2).test(t)){//——test方法需要对应
//等效于:predicate1.test()&&predicate2.test();
System.out.println("输入的数字是三位数"+t);
}else System.out.println("输入的数字不是三位数"+t);
if(predicate1.or(predicate2).test(t)){//输入大于10或者小于5的数————test方法需要对应
//等价于predicate1.test(t)||predicate2.test(t)
System.out.println("输入的数大于10或者小于5 "+t);
}else System.out.println("输入的数小于10,大于5 "+t);
if(Predicate.isEqual(100).test(t)){//——test方法需要对应
//等价于t==100
System.out.println("输入的数等于100: "+t);
}else System.out.println("输入的数不等于100: "+t);
2.2.4、Function接口——功能型函数式接口
Interface Function<T,R>
参数类型
T - 函数输入的类型
R - 函数结果的类型
All Known Subinterfaces:
UnaryOperator<T>
Functional Interface:
这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。
--------------------------------------------------------------------------------
@FunctionalInterface
public interface Function<T,R>表示接受一个参数并生成结果的函数。
这是一个functional interface,其功能方法是apply(Object) 。
从以下版本开始:
1.8
方法
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
// 返回首先将此函数应用于其输入的 after函数,然后将 after函数应用于结果。
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
//将此函数应用于给定的参数。
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
//返回一个组合函数,该函数首先将 before函数应用于其输入,然后将此函数应用于结果。
static <T> Function<T, T> identity() {
return t -> t;
}
//返回一个始终返回其输入参数的函数。
方法测试:
package Lambda表达式;
import java.util.function.Function;
public class FunctionInterface05_Function {
public static int getInt(String s, Function<String,Integer>function){//将字符串变为整数
return function.apply(s);
}
public static Object getInput(String s){
return Function.identity().apply(s);
}
public static String getUppercase(String s,Function<String,String>function1,Function<String,String>function2){
return function1.compose(function2).apply(s);//function2 先对输入的数据进行apply,得到的结果再作为function1的输入
}
public static String getString(String s,Function<String,Integer>function1,Function<Integer,String>function2){
return function1.andThen(function2).apply(s);//function1先对输入apply,function2再将结果apply
//等价于
/*
int i = function1.apply(s);
return function2.apply(i);
*/
}
public static void main(String[] args) {
System.out.println(getInt("1000",(t)->Integer.parseInt(t)));
System.out.println("args = " +getInput("我能返回输入的参数") );
System.out.println(getUppercase(" abcS dddDD",(s)->s=s.trim()+"function1",(s)->s=s.toUpperCase()+"function2"));
//将字符串转化为整数,将该整数加10,然后返回其字符串
System.out.println(getString("100",(s)->Integer.parseInt(s)+10,(i)->Integer.toString(i)));
}
}