初识Lambda函数式编程
2021/7/18 11:06:21
本文主要是介绍初识Lambda函数式编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1、Lambda 入门
1.1、从一个需求例子开始
需求:对一个整型数组进行排序,使用冒泡排序,如何进行优化,如何灵活实现排序规则。
1.2、方案一:在 sort 方法中增加一个参数控制排序规则
/** * * @param datas 数组 * @param sortType true 表示升序,false 表示降序 */ public static void sort(int[] datas,boolean sortType){ for (int i = 0; i < datas.length; i++) { for (int j = i+1; j < datas.length; j++) { if(sortType){ //两两元素比较,前一个数大于后一个数则交换位置,最终实现升序 if(datas[i]>datas[j]){ //交换位置 int temp = datas[i]; datas[i] = datas[j]; datas[j] = temp; } }else{ //两两元素比较,前一个数小于后一个数则交换位置,最终实现降序 if(datas[i]<datas[j]){ //交换位置 int temp = datas[i]; datas[i] = datas[j]; datas[j] = temp; } } } } }
问题:代码冗余严重!!
1.3、方案二:将元素比较的代码抽取到一个接口中
public interface SortCompare { boolean compare(int a,int b); }
public class SortCompareImpl implements SortCompare{ @Override public boolean compare(int a, int b) { //前一个是否大于后一个数 return a>b; } }
就可以使用一个方法实现不同的排序规则,方法的代码不再繁琐
//增加一个参数,就是排序比较接口的对象 public static void sort(int[] datas,SortCompare sortCompare){ for (int i = 0; i < datas.length; i++) { for (int j = i+1; j < datas.length; j++) { if(sortCompare.compare(datas[i],datas[j])){ //交换位置 int temp = datas[i]; datas[i] = datas[j]; datas[j] = temp; } } } }
问题:为了封装一行比较代码就需要定义多个接口实现类,这个过程繁琐的。
1.4、方案 3:匿名内部类实现比较接口
//匿名内部类,实现升序 sort(datas, new SortCompare() { @Override public boolean compare(int a, int b) { return a>b; } });
优点:不用再定义独立的类,代码逻辑也很简洁。
问题:封装一行比较的代码,但是需要定义一些无关的代码。
1.5、Lambda表达式实现
sort(datas,(int a,int b)->a<b);//降序
Lambda 是一种匿名内部类的简化形式。
Lamdba 表达式可以使编程过程更高效。
语法: (参数,参数) –> {方法体代码}
1、 参数列表
2、 箭头
3、 方法体代码
2、Lambda语法规则
2.1、Lambda 表达式语法格式
Lambda 是一种匿名内部类的简化形式。
注意:不是所有的匿名内部类可以使用 Lambda 表达式来实现的。
Lambda 表达式只能代替有一个抽 象方法的接口所对应的匿名内部类。
Lamdba 表达式可以使编程过程更高效。
语法: (参数,参数) –> {方法体代码}
1、 参数列表
2、 箭头
3、 方法体代码
2.2、Lambda表达式测试
1、 无参、无返值 2、 有参、有返回值
定义 Fun1、Fun2 两个接口。
2.3、函数式接口
什么是函数接口?
只有一个抽象方法的接口叫函数式接口。
Lambda 表达式只能赋值给函数式接口的引用变量。
2.4、 简化形式
1、 参数类型可以省略。 注意:如果有多个参数,参数类型要么都省略,要么都不省略
2、 如果函数体只有一条语句可以省略大括号和分号及 return
3、 如果参数只有一个,可以省略参数列表的小括号
2.5、函数式编程思想
函数式编程和面向对象编程对比:
1) 核心不同
面向对象编程,它的核心是对象。
函数式编程,它的核心是函数。方法就是函数。
2) 目标不同
面向对象,是需要分析对象的属性,对象的行为。
函数式编程,首先确定要做什么事,也就确定了函数的功能。
3、java.util.function 函数式接口
3.1、认识函数式接口
函数式接口,是只有一个抽象方法的接口叫函数式接口。
比较常用的几个接口
接口 | 抽象方法 | 说明 |
Function<T, R> | R apply(T t); | 接收一个参数,有返回值,接收参数类型T,返回参数类型R |
Consumer<T> | void accept(T t); | 接收一个参数,无返回值 |
Supplier<T> | T get(); | 没有参数,有返回值 |
Predicate<T> | boolean test(T t); | 接收一个参数,返回布尔值 |
@FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
@FunctionalInterface 它是一个注解,标记在这个接口上就表示该接口是一个函数式接口。
在一个函数式接口中不标记@FunctionalInterface,也是可以的!
3.2、Function 函数式接
@FunctionalInterface public interface Function<T, R> { 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)); } default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static <T> Function<T, T> identity() { return t -> t; } }
andThen 方法: 原型:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }
参数:也是一个 Function 函数式接口类型
结果:也是一个 Function 函数式接口类型
内容:return (T t) -> after.apply(apply(t));
//测试andThen方法 public static void testAndThen(){ //前边的一个操作 Function<String,String> before = s1 -> "www."+s1; //后边的一个操作 Function<String,String> after = s1 -> s1+".com"; //调用before的andThen方法,实现前后执行两个操作 String pbteach = before.andThen(after).apply("pbteach"); System.out.println(pbteach); }
3.3、BiFunction 函数式接口
@FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u); default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t, U u) -> after.apply(apply(t, u)); } }
apply 方法有两个参数,一个返回值
public class TestBiFunction { public static void main(String[] args) { //求两个数的和 BiFunction<Integer, Integer, Integer> fun1 = (x, y) -> x + y; //求两个数的乘积 BiFunction<Integer, Integer, Integer> fun2 = (x, y) -> x * y; //调用方法 System.out.println(operate(fun1, 1, 2)); System.out.println(operate(fun2, 1, 2)); //简化 System.out.println(operate((x, y) -> x * y, 1, 2)); } //实现两个数运算 public static int operate(BiFunction<Integer, Integer, Integer> fun, int n1, int n2) { return fun.apply(n1, n2); } }
3.4、Consumer 接口
@FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
Consumer 是一个消费接口 ,接收一个数据进行消费。
public class TestConsumer { //一个整型数组 static int[] datas = {1,2,3,4,5,6,7}; //静态变量 static int sum = 0; public static void main(String[] args) { //定义一个Consumer接口对象 Consumer<Integer> fun1 = n -> System.out.println(n); //判断奇偶 Consumer<Integer> fun2 = n ->{ if(n % 2 == 0){ System.out.println(n + "是偶数"); }else{ System.out.println(n + "是奇数"); } }; //求累加和 Consumer<Integer> fun3 = n -> sum+=n; //调用consumer consumer(fun1,datas); System.out.println("===========判断奇偶========="); consumer(fun2,datas); System.out.println("===========求累加和========="); consumer(fun3,datas); System.out.println("sum="+sum); } //消费方法 public static void consumer(Consumer<Integer> fun,int[] datas){ for (int i = 0; i < datas.length; i++) { //调用accept方法进行消费,一次消费一个数据 fun.accept(datas[i]); } } }
3.5、Predicate 函数式接口
@FunctionalInterface public interface Predicate<T> { boolean test(T t); // 接收一个参数,得到一个布尔类型的结果,用于判断,返回判断结果。 default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } 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); } static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }
public class TestPredicate { //一个整型数组 static int[] datas = {1,2,3,4,5,6,7}; public static void main(String[] args) { //定义Lambda表达式,实现奇偶判断 Predicate<Integer> filter1 = n ->{ if(n % 2 ==0){ return true; }else{ return false; } }; System.out.println("========奇偶判断======="); int[] datas1 = doFilter(filter1, datas); for (int i = 0; i < datas1.length; i++) { System.out.println(datas1[i]); } System.out.println("========大于5的数======="); //定义一个过虑器 Predicate<Integer> filter2 = n -> n>5; int[] datas2 = doFilter(filter2, datas1); for (int i = 0; i < datas2.length; i++) { System.out.println(datas2[i]); } } /** * 对数据进行过虑 * @param filter 过虑器 * @param datas 待过虑的数组 * @return 保留数据的数组 */ public static int[] doFilter(Predicate<Integer> filter,int[] datas){ //新建一个数组,用于存储保留数据 int[] result = new int[datas.length]; //保留数组的下标 int n=0; for (int i = 0; i < datas.length; i++) { if(filter.test(datas[i])){ //保留数据 result[n++] = datas[i]; } } //最后只返回一个正好包含保留数据容量的数组 //参数1:原始数组,参数2:需要保留数组长度 int[] newResult = Arrays.copyOf(result, n); return newResult; } }
3.6、Supplier 函数接口
@FunctionalInterface public interface Supplier<T> { T get(); }
Supplier 接口表示供应,和 Consumer 相反,Supplier 是一个生产接口,T get()没有参数,只有结果。
public class TestSupplier { static long sequence = 0; //生成不同类型的编号,比如:订单号、课程编号等,采用方法是:随机,顺序编号,时间编号 //定义随机数的生成器,Math.abs获取绝对值 static Supplier<Long> randomNum = () -> Math.abs(new Random().nextLong()); //顺序编号 static Supplier<Long> sequenceNum = () -> ++sequence; //时间编号,获取当前时间纳秒值 static Supplier<Long> timeNum = () -> System.nanoTime(); public static void main(String[] args) { System.out.println("=========生产随机数========"); for (int i = 0; i < 10; i++) { System.out.println(randomNum.get()); } System.out.println("=========生产顺序编号========"); for (int i = 0; i < 10; i++) { System.out.println(sequenceNum.get()); } System.out.println("=========生产时间编号========"); for (int i = 0; i < 10; i++) { System.out.println(timeNum.get()); } } }
4、方法引用
4.1、方法引用入门
Lambda 表达式要对应一个函数式接口,具体依据函数式接口中的抽象方法来定义
Lambda。使用 Lambda 表达式进行函数式编程,使用 Lambda 表达式所定义是一个函数,在 Java 中方法就是函数。
什么是方法引用?
Java 中的方法可以当作一个引用作为函数来使用,它叫方法引用。方法引用是一种将方法应用于函 数式编程的方法,方法引用可以代替 Lambda。
简写为: 将 Java 中方法作一个引用,代替 Lambda 表达式。
1、 定义一个函数式接口
public interface Fun2 { //有参数有返回值 int handler(int a,int b); }
2、 使用 Lambda 表达式测试
//使用 Lambda 表达式测试 doFun2((x,y)->{ int z = x * y; return z; });
依据函数式接口中的抽象方法来定义 Lambda 表达式.
3、使用方法引用代替 Lambda 表达式
1) 定义一个方法
如何定义一个可以代替上边这个 Lambda 表达式的方法呢? 依据函数式接口中的抽象方法来定义 一个可以代替 Lambda 表达式的方法。
public class Utils { //实现两个数的乘积 public static int product(int a,int b){ return a * b; } }
2) 可以使用方法引用来代替 Lambda
//使用方法引用 doFun2(Utils::product);
Utils::product 表示引用了 Utils 类中的 product 方法,::符号为引用运算符,它所在的表达式被称为 方法引用。
小结: 一个函数式接口,可以使用 Lambda 表达式来实现,也可以使用方法引用实现。 最关键的是知道如何定义一个 Lambda 和方法引用。
一个方法引用所引用的方法必须和函数式接口中的抽象方法的参数、返回值类型一致
4.2、静态方法引用
类型 | 语法 | 对应的lambda表达式 |
静态方法引用 | 类名::staticMethod | (args) -> 类名.staticMethod(args) |
特定对象实例方法引用 | instance::instanceMethod | (args) -> instance.instanceMethod(args) |
任意对象实例方法引用 | 类名::instanceMethod | (instance,args) -> instance.instanceMethod(args) |
构造方法引用 | 类名::new | (args) -> new 类名(args) |
1、 确定函数式接口
@FunctionalInterface public interface BiFunction<T, U, R> { /** * Applies this function to the given arguments. * * @param t the first function argument * @param u the second function argument * @return the function result */ R apply(T t, U u); }
2、 使用方法引用
public class TestStaticFun { public static void main(String[] args) { //使用Lambda表达式实现 System.out.println(operate((x,y)->x+y,1,2)); //使用方法引用,求两个数的和 System.out.println(operate(Integer::sum,1,2)); //测试当函数式接口的抽象方法的返回值为void时,仍然可以使用一个返回值不为void的方法引用 Consumer<String> fun = TestStaticFun::toUpperCase; fun.accept("www.pbteach.com"); } //实现两个数的操作 public static int operate(BiFunction<Integer,Integer,Integer> fun,int n1,int n2){ return fun.apply(n1,n2); } /** * 将字符串转大写 * @param s 输入一个字符串 * @return 转大写后的字符串 */ public static String toUpperCase(String s){ System.out.println("s="+s); //将字符串转在大写 return s.toUpperCase(); } }
4.3、抽象方法为 void 的情况
@FunctionalInterface public interface Consumer<T> { void accept(T t); }
根据前边的知识可知,要想一个方法引用和上边的 Consumer 对应上,这个方法的参数类型和返回 值类型要和函数式接口的抽象方法一致。void accept(T t);
所引用的方法不管是否有返回值都可以和函数式接口对应上。
使用上边的函数式接口来测试,用一个不为 void 返回值的方法引用,和 Consumer 接口对应上。 还是建议:定义一个方法引用,依据函数式接口中的抽象方法来定义,让引用方法的参数和返回值 类型与函数式接口中的抽象方法的参数和返回值一致!!!
4.4、特定实例方法引用
指: 引用某个对象的方法,叫特定实例方法的引用。
//特定实例方法引用,引用某个对象的实例方法 public static void test1(){ //创建一个对象 PbStudent s1 = new PbStudent(); s1.setNickname("攀博课堂"); //引用 s1 对象的 getNickname 方法,方法引用赋值给 Supplier 函数接口 ,它的 T get();抽象方 法与 getNickname 一致 Supplier<String> fun1 = s1::getNickname; //获取学生昵称,fun1.get()相当于调用了 s1 对象的 getNickname System.out.println(fun1.get()); //引用 s1 对象的 setNickname Consumer<String> fun2 = s1::setNickname; System.out.println("设置学生昵称"); fun2.accept("攀博课堂 www.pbteach.com"); System.out.println("学生新昵称:"+fun1.get()); }
4.5、任意对象实例方法引用
它和特定对象实例方法引用区别:
特定对象实例方法:
引用某个类型的某个对象的实例方法。
例如:s1::getNickname,表示引用 s1 对象的 getNickname 方法。
语法:实例名::实例方法名
任意对象实例方法:
引用某个类型的所有对象的实例方法。
例如:PbStudent::getNickname,表示引用 PbStudent 这个类型的所有对象的 getNickname 方法。
语法:类名::实例方法名
任意对象实例方法引用规则:
1、固定有第一个参数,是实例的类型。
2、 从第二个开始才是实例方法的参数。
//任意实例方法引用 public static void test2(){ //引用 PbStudent 类型的任意对象的 setNickname 方法 BiConsumer<PbStudent, String> setNickname = PbStudent::setNickname; //引用 PbStudent 类型的任意对象的 getNickname 方法 Function<PbStudent,String> getNickname = PbStudent::getNickname; //通过 setNickname 方法引用设置昵称 PbStudent s1 = new PbStudent(); setNickname.accept(s1,"攀博课堂"); //通过 getNickname 方法引用获取昵称 String nickName = getNickname.apply(s1); System.out.println(nickName); // System.out.println(s1.getNickname()); }
一个案例: 如何使用任意对象实例方法引用,引用下边的 String 类下的 charAt 方法。
实现:获取字符串某个索引位置上的字符。
public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }
分析:charAt 方法有一个参数,一个返回值。 如果要使用任意对象实例方法引用,找一个函数式接口,它的抽象方法,参数有两个,必须有返回值。
//测试任意对象实例方法的引用,测试 String 类型下的 charAt 方法 public static void test3(){ //引用 String 类型下的 charAt 方法 BiFunction<String,Integer,Character> fun = String::charAt; System.out.println(fun.apply("www.pbteach.com",4)); System.out.println(fun.apply("www.pbteach.com",5)); }
4.6、构造方法引用
1) 无参构造方法 语法:类名::new。
引用的无参构造方法,对应哪个函数式接口,这个函数式接口的抽象方法没有参数,必须有一个返 回值。
使用 Supplier 函数式接口。
//无参构造方法引用 public static void test1(){ //引用 PbStudent 类型的无参构造方法 Supplier<PbStudent> fun = PbStudent::new; //创建学生对象 PbStudent pbStudent = fun.get(); System.out.println(pbStudent); }
2)有参构造方法
语法:类名::new 引用有参构造方法,根据函数式接口确定要引用哪个有参构造方法。
//有参构造方法引用 public static void test2(){ //引用 PbStudeng 类型下的 pbstudent(String nickname)构造方法。 Function<String,PbStudent> fun = PbStudent::new; //通过方法引用调用有参构造方法 PbStudent pbStudent = fun.apply("www.pbteach.com"); System.out.println("昵称:"+pbStudent.getNickname()); //引用 PbStudeng 类型下的 PbStudent(String id,String nickName) BiFunction<String,String,PbStudent> fun2 = PbStudent::new; PbStudent pbStudent1 = fun2.apply("100", "攀博课堂"); System.out.println(pbStudent1.getId()+" "+pbStudent1.getNickname()); }
这篇关于初识Lambda函数式编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-23线下车企门店如何实现线上线下融合?
- 2024-12-23鸿蒙Next ArkTS编程规范总结
- 2024-12-23物流团队冬至高效运转,哪款办公软件可助力风险评估?
- 2024-12-23优化库存,提升效率:医药企业如何借助看板软件实现仓库智能化
- 2024-12-23项目管理零负担!轻量化看板工具如何助力团队协作
- 2024-12-23电商活动复盘,为何是团队成长的核心环节?
- 2024-12-23鸿蒙Next ArkTS高性能编程实战
- 2024-12-23数据驱动:电商复盘从基础到进阶!
- 2024-12-23从数据到客户:跨境电商如何通过销售跟踪工具提升营销精准度?
- 2024-12-23汽车4S店运营效率提升的核心工具