目录
1.函数式编程思想
1.1.概念
面向对象思想需要关注用什么对象完成什么事情,而函数式编程思想就类似于数学中的函数。
它主要关注的是对数据进行了什么操作。
1.2.优点
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于“并发编程”
2.Lambda表达式
2.1.概述
Lambda是JDK8中一个语法。他可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现。让我们不用关注是什么对象。而是更关注我们对数据进行了什么操作。
2.2.Lambda的由来
public class Test01 { public static void main(String[] args) { //开启一个线程 该构造函数需要传递一个Runnable类型的接口参数 Thread thread = new Thread(new My()); thread.start(); //开启线程 执行run方法 //匿名内部类 Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("通过匿名内部类实现线程任务====="); } }); thread1.start();//开启线程 执行匿名内部类的run方法 } } class My implements Runnable { @Override public void run() { System.out.println("使用实现类完成线程任务====="); } }
分析:
- Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
- 为了指定run方法体,不得不需要Runnable的实现类
- 为了省去定义一个Runnable 的实现类,不得不使用匿名内部类
- 必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错,
- 而实际上,我们只在乎方法体中的代码.
- 我们可以使用Lambda表达式来完成上面的功能。
使用Lambda表达式来完成上面的功能:
public class Test01 { public static void main(String[] args) { //Lambda表达式 /*Runnable runnable = () -> { System.out.println("Lambda表达式完成线程任务"); }; Thread thread2 = new Thread(runnable); thread2.start(); //开启线程*/ //Lambda表达式2 Thread thread2 = new Thread(() -> { System.out.println("Lambda表达式完成线程任务"); }); thread2.start(); //开启线程 } } class My implements Runnable { @Override public void run() { System.out.println("使用实现类完成线程任务====="); } }
2.3.Lambda表达式的语法
Lambda省去了面向对象的条条框框,Lambda的标准格式由3个部分组成:
格式:( 参数列表 ) -> { 方法体 }
- ():参数列表
- ->:连接符 连接的是参数以及方法体。
- {}: 方法体。
2.4.Lambda练习1
练习无参无返回值的Lambda表达式:
public class Test01 { public static void main(String[] args) { //主函数调用fun方法 //第一种:匿名内部类方式 User user1 = new User() { @Override public void show() { System.out.println("匿名内部类的show方法实现"); } }; fun(user1); //第二种:Lambda表达式方式 (接口必须为函数式接口) fun(() -> { System.out.println("Lambda表达式的show方法实现"); }); } //定义一个静态方法 public static void fun(User user){ user.show(); } } //函数式接口(里面只有一个抽象方法) @FunctionalInterface interface User{ public void show(); }
2.5.Lambda练习2
练习有参有返回值的Lambda表达式:
public class Test03 { public static void main(String[] args) { List<Person> personList = new ArrayList<>(); personList.add(new Person("张三",25)); personList.add(new Person("李四",18)); personList.add(new Person("王五",30)); personList.add(new Person("马六",20)); //对集合中的元素按照年龄从小到大排序 集合工具类:Collections System.out.println("没排序:"+personList); //方法一:使用匿名内部类 Comparator<Person> comparator = new Comparator<Person>() { @Override//如果是0表示相同 大于0表示o1大于o2 public int compare(Person o1, Person o2) {//自带的参数 return o1.getAge()-o2.getAge(); } }; Collections.sort(personList,comparator); System.out.println("从小到大排序后:"+personList); //方法二:使用Lambda表达式 (就是对函数式接口中抽象方法的简写) Comparator<Person> comparator2 =(o1,o2) -> { return o1.getAge()- o2.getAge(); }; Collections.sort(personList,comparator2); System.out.println(personList); } } @Data @NoArgsConstructor @AllArgsConstructor class Person{ private String name; private Integer age; }
2.6.Lambda表达式的省略写法
在lambda表达式的标准写法基础上,可以使用省略写法的规则为:
- 小括号内的参数类型可以省略[ ]
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号 和 return 关键字及语句分号
案列1:
public class Test04 { public static void main(String[] args) { //未省略 /* U01 u = (a) ->{ System.out.println("heelo=="+a); };*/ //省略后 U01 u = a -> System.out.println("hello=="+a); fun(u); //U02 u2 = a -> {return a * 2;}; //未省略 U02 u2 = a -> a * 2; //省略后 Lambda默认把最后一条语句作为返回结果 fun02(u2); } public static void fun(U01 u01) { u01.show(10); } public static void fun02(U02 u02) { int print = u02.print(20); System.out.println(print); } } interface U01 { public void show(int a); } interface U02 { public int print(int b); }
案列2:
public class Test004 { public static void main(String[] args) { //Lambda表达式 (toUpperCase) fun((str) -> str.toUpperCase()); } public static void fun(ABC abc) { String s = abc.toUpper("hello==");//使用Lambda表达式把字符串转为大写 System.out.println(s); } interface ABC { public String toUpper(String str); } }
2.7 Lambda表达式使用的前提
Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意
- 方法的参数或局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法(@FunctionalInterface)
3. 内置函数式接口
要想使用lambda表达式它的前提就是必须是函数式接口
3.1.内置函数式接口的由来
案列:
public class Test05 { public static void main(String[] args) { /*Operation o = (int[] arr) -> { int sum = 0; for (int s:arr){ sum += s ; } return sum; }; fun(o);*/ //简写 fun(arr -> { int sum = 0; for (int s:arr){ sum += s ; } return sum; }); } public static void fun(Operation operation){ int[] arr = {1,2,3,4,5,6,7,8,9}; int s = operation.getSum(arr); System.out.println("数组和为:"+s); } } @FunctionalInterface interface Operation{ public int getSum(int[] arr); }
分析:
我们知道使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名。只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda表达式更加的方便,在JDK中提供了大量常用的函数式接口. 大多数无需自己再定义函数式接口,而可以直接使用jdk内置的函数式接口。分成以下四类:
函数式接口 参数类型 返回类型 说明 Consumer<T> 消费型接口 T void void accept(T t);对类型为T的对象应用操作 Supplier<T> 供给型接口 无 T T get();返回类型为T的对象 Function<T,R>函数型接口 T R R apply(T t);对类型为T的对象应用操作,并返回类型
为R类型的对象
Predicate<T>断言型接口 T boolean boolean test(T t);确定类型为T的对象是否满足条件,
并返回boolean类型
3.2. 消费型函数式接口Consumer
适合有参数,但是没有返回值的
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
案列:
public class Test06 { public static void main(String[] args) { Consumer<Double> c = t ->{ System.out.println("今天吃饭花费:"+t+"元"); }; fun(c,200.0); } public static void fun(Consumer<Double> consumer , Double money){ consumer.accept(money); } }
3.3. 供给型函数式接口---Supplier
无参,需要返回值的接口类
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回
案列:
public class Test07 { public static void main(String[] args) { Supplier<Integer> s = () -> new Random().nextInt(10); //Random:随机获取一为数 fun(s); } public static void fun(Supplier<Integer> supplier) { Integer a = supplier.get(); System.out.println("结果:" + a); } }
3.4. 函数型函数式接口---Function<T,R>
T: 参数的泛型
R:返回值的泛型
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回
修改(3.1.内置函数式接口的由来)中的案列:
public class Test05 { public static void main(String[] args) { /*Function<int[] , Integer> o = (int[] arr) -> { int sum = 0; for (int s:arr){ sum += s ; } return sum; }; fun(o);*/ //简写 fun(arr -> { int sum = 0; for (int s:arr){ sum += s ; } return sum; }); } public static void fun(Function<int[] , Integer> fun){ int[] arr = {1,2,3,4,5,6,7,8,9}; int s = fun.apply(arr); System.out.println("数组和为:"+s); } }
修改分析:
不需要自己再定义函数式接口,直接用内置的 函数式接口就行了。因为方法有参有返回值
3.5. 断言型函数式接口--Predicate
T: 参数
boolean:返回值类型
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果
public class Test09 { public static void main(String[] args) { Predicate<String> p = t -> t.length()>3; fun(p,"cj"); //字符串长度小于3为false } public static void fun(Predicate<String> predicate , String name){ boolean test = predicate.test(name); System.out.println("是否成年:"+test); } }
4. 方法引用
我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码
4.1.推荐用法
- 我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。
- 我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
- 当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
4.2. 基本格式
类名或者对象名 :: 方法名
4.3.方法引用的由来
public class Test10 { public static void main(String[] args) { Consumer<int[]> c = t -> { int sum = 0; for (int a : t) { sum += a; } System.out.println("数组的和是:" + sum); }; fun(c); } public static void fun(Consumer<int[]> consumer) { int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9}; consumer.accept(arr); } //求和方法 public static void sum(int[] arr) { int sum = 0; for (int a : arr) { sum += a; } System.out.println("数组的和是:" + sum); } }
分析:
如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接“引 用”过去就好了----方法引用 :: 可以办到
public class Test10 { public static void main(String[] args) { //Consumer<int[]> c=(t)->Test10.sum(t); Consumer<int[]> c = Test10::sum; //方法引用 //静态方法引用 fun(c); } public static void fun(Consumer<int[]> consumer) { int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9}; consumer.accept(arr); } //求和方法 public static void sum(int[] arr) { int sum = 0; for (int a : arr) { sum += a; } System.out.println("数组的和是:" + sum); } }
4.4.方法引用的类型
类型 | 语法 | 对应的Lambda表达式 |
静态方法引用 | 类名::staticMethod | (args)->类名.staticMethod(args) |
实例方法引用 | inst::instMethod | (args)->inst.instMethod(args) |
对象方法引用 | 类名::instMethod | (inst,args)->inst.instMethod(args) |
构建方法引用 | 类名::new | (args)->new 类名(args) |
4.5.方法引用举例
4.5.1.静态方法引用
使用前提:如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法
public class Test11 { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); //从小到大排序 list.add(2); list.add(8); list.add(4); list.add(1); //Comparator<Integer>comparator=(o1,o2)->o1-o2; //第一种排序方法 //Comparator<Integer>comparator=(o1,o2)->Integer.compare(o1,o2);//第二种排序方法//Integer.compare排序 Comparator<Integer> comparator = Integer::compareTo;//第三种排序方法 静态方法引用 Collections.sort(list, comparator); System.out.println(list); } } @Data @NoArgsConstructor @AllArgsConstructor class Person { private String name; private Integer age; }
4.5.2.实例方法引用
使用前提: 如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
实例方法引用特点:( ) -> 对象.普通方法( );
案例:
public class Test01 { public static void main(String[] args) { User u = new User("张三",20); //在这个lambda表达式中只有一条语句,而且该语句又是调用了某个方法 //Supplier<String> s = ()->u.getName(); //实例方法引用 Supplier<String> s = u::getName; //实例方法引用特点:( ) -> 对象.普通方法( ); fun(s); } public static void fun(Supplier<String> supplier){ String s = supplier.get(); System.out.println("结果为:"+s); } } @Data @AllArgsConstructor class User{ private String name; private Integer age; }
4.5.3.对象方法引用
使用前提: 如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法
案例1:
public class Test02 { public static void main(String[] args) { //对象方法引用: 类名::实例方法 (参数1,参数2)->参数1.实例方法(参数2) //Lambda表达式 函数式接口Function<T,R> /* Function<String,Integer> function = (str)->{ return str.length(); };*/ //使用对象方法引用 Function<String,Integer> function = String::length; Integer integer = function.apply("hello~~"); //获取字符串长度 System.out.println(integer); } }
案例2:
比较两个字符串内容是否一致
public class Test02 { public static void main(String[] args) { //对象方法引用: 类名::实例方法 (参数1,参数2)->参数1.实例方法(参数2) //案例2:比较两个字符串内容是否一致 T , U , R //R apply(T t, U u); /*BiFunction<String,String,Boolean> bi =(t,u)->{ return t.equals(u); };*/ BiFunction<String,String,Boolean> bi = String::equals; Boolean apply = bi.apply("hello", "hello"); System.out.println(apply); } }
4.5.4.构造方法引用
使用前提:如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
public class Test03 { public static void main(String[] args) { //构造方法引用 : 类名::new (参数)->new 类名(参数) // 供给型函数式接口---Supplier /*Supplier<People>supplier=()->{ return new People(); };*/ Supplier<People> supplier = People::new; //调无参构造 People s = supplier.get(); System.out.println(s); //函数型函数式接口---Function<T,R> //Function<String,People> function=(n)->new People(n); Function<String, People> function = People::new; // 调有参构造 People s2 = function.apply("张三"); System.out.println(s2); } } @Data @NoArgsConstructor @AllArgsConstructor class People { private String name; }
5.方法引用总结
类型 | 语法 | 对应的Lambda表达式 |
静态方法引用 | 类名::静态方法 | lambda表达式时:(参数)->类名.静态方法(参数) |
实例方法引用 | 对象::实例方法 | lambda表达式时:(参数)->对象.实例方法(参数) |
对象引用方法 | 类名::实例方法 | lambda表达式时:(参数1,参数2...)->参数1.实例方法(参数2...) |
构造方法引用 | 类名::new | lambda表达式时:(参数)->new 类名(参数) |