编译软件:IntelliJ IDEA 2019.2.4 x64
运行环境:win10 家庭中文版
jdk版本:1.8.0_361
一. 函数式编程思想
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。编程中的函数,也有类以的概念,你调用我的时候,给我实参为形参赋值,然后通过运行方法体,给你返回一个结果。
对于调用者来做,关注汶个方法具备什么样的功能。相对而言,面向对象过分强调"必须涌过对象的形式来做事情",而函数式思想则尽量忽略面向对象的复杂语法一一强调做什么,而不是以什么形式做。
面向对象的思想:
做一件事情,找一个能解决这个事情的对象调用对象的方法,完成事情
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
二. 函数式接口
函数式接口是指一种特殊的接口,SAM接口 (Single Abstract Method) 只有唯一抽象方法的接口。
说明: 抽象只有唯一的一个,但是对于非抽象方法不限制。
函数式接口的概念时JDK1.8引入的,然后它除了要求接口是SAM特征之外,还强烈建议接口的声明上方加一个注解:
@FunctionalInterface 函数式接口的注解
回忆之前学过的接口中,哪些接口符合SAM接口特征的?
(1)java.util.Comparator int compare(T t1 ,T t2)
(2)java.lang.Comparable int compareTo(T t)
(3)java.lang.Runnable void run()
(4)java.util.function.Predicate boolean test()
(5)java.lang.Iterable<>接口 Iterator iterator()
(6)java.io.FileFilter boolean accept(File pathname)
java.io.Serializable接口,不是,没有抽象方法
java.Lang.CLoneabLe接口,不是,没有抽象方法
java.util.Iterator接口,不是,有两个抽象方法boolean hasNext()
E next)
java.util.Collection、List、Set、Oueue、Deque、Map<K,V>,不是,有很多抽象方法
上面这些SAM接口中,哪些有@FunctionalInterface注解:
(1)java.util.Comparator int compare(T t1 ,T t2)
(2)java.lang.Runnable void run() boolean test() (这个其实也是新的)
(3)java.util.function .Predicate
(4) java.io.FileFilter boolean accept(File pathname)
Java8在java.util.function包中新增了很多很多的函数式接口。
三. 新版函数式接口的四大经典代表
(1) 消费型接口代表
Consumer
抽象方法: void accept(T t)
特点: 有一个参数,返回值是void,表示无返回值。
调用这个抽象方法时,相当于你要给它一个实参,但是得不到返回值,相当于 有去无回,纯消费行为。
(2) 供给型接口代表/泰献型接口代表
Supplier
抽象方法 T get()
特点:没有参数,有返回值
调用这个抽象方法时,相当于你不用给它传参数,却可以得到一个返回值,相当于 空手套白狼。
(3)判断型接口/断定型接口代表
Predicate
抽 象方法 boolean test(T t)
特点: 有一个参数,返回值类型是固定的boolean
调用这个抽象方法时,相当于你给它一个参数之后,它会告诉你这个参数是否满足xx条件,满足就返回true,否则就返回false
(4)功能型接口代表
Function<T,R>
抽象方法 RLapply(T t)
特点是:有一个参数,有一个返回值
调用这个抽象方法时,相当于你要给它一个实参,同时也 可以得到一个返回值,相当于礼尚往来
四. Lambda 表达式
4.1 Lambda表达式的作用
给函数式接口的变量或形参赋值用的。传递一段代码这段代码本质上就是西数式接口的抽象方法的方法体。
4.2 语法格式
(形参列表) ->{Lambda体)
(形参列表) : 是函数式接口的抽象方法的形参列表
->: 称为Lambda操作符,中间不能有空格
{Lambda体: 就是函数式接口的抽象方法的方法体
代码演示如下
//为何不用JUite的test方法去测试,该方法有一个缺点,在其内部使用多线程,有可能test里的线程还没启动就被test干掉了 //相比而言,main方法会等待里面的线程全部执行完,相比较而言,main方法比较保险 public static void main(String[] args) { //使用lambda表达式对Runnable接口的变量赋值。要实现打印一句话: hello lambda //Runnable接口的抽象方法: void run() Runnable r= () -> { System.out.println("hello lambda"); //里面的;是输出语句的 }; //外面的;是 赋值语句 + lambda表达式的 new Thread(r).start(); //完全等价于下面的代码 System.out.println("-----------------"); new Thread(new Runnable() { @Override public void run() { System.out.println("hello lambda"); } }).start(); }
4.3 Lambda表达式在某些情况下,可以简化
(1)当{Lambda体}里面只有一个语句时,那么可以省略 {},同时省略里面的;
代码演示如下:
Runnable r= () -> { System.out.println("hello lambda"); //里面的;是输出语句的 }; //外面的;是 赋值语句 + lambda表达式的 new Thread(r).start(); @Test public void test01(){ //精简如下 Runnable r= () -> System.out.println("hello lambda"); new Thread(r).start(); }
(2)当Lambda体)里面只有一个语句时,那么可以省略,同时省略里面的;如果此时这个语句是一个return语句,那么要连同return省掉
代码演示如下:
//案例:在一个全是字符串的集合中,删除 包含“o”字母的单词 ArrayList<String> list = new ArrayList<>(); list.add("hello"); list.add("world"); list.add("atguigu"); list.add("java"); list.add("bigdata"); //删除 包含“o”字母的单词 //匿名类的形式 list.removeIf(new Predicate<String>() { @Override public boolean test(String s) { return s.contains("o"); } }); System.out.println(list); //从匿名类转换为lambda表达式 list.removeIf( (String s) -> { return s.contains("o"); } ); //上面的lambda式子精简如下 list.removeIf( (String s) -> s.contains("o") ); System.out.println(list);
(3)当(形参列表)的形参类型是已知的,或者可以自动推断的,那么形参列表的类型可以省略
代码演示如下:
//接上面的案例 //精简如下 list.removeIf( (s) -> s.contains("o") );
💡如果此时省略了形参的数据类型之后,只剩下一个形参,它是这样的(形参名),那么此时()也可以省略
代码演示如下:
//再精简lambda表达式 list.removeIf( s -> s.contains("o") );
🔔说明:
如果形参列表是(),()不能省略
如果形参不止一个,()也不能省略
如果Lambda体中不止一个语句等也不能省略。
代码演示如下:
@Test public void test03(){ String[] arr = {"hello","Bob","Rose","java","chai"}; //使用匿名类 Arrays.sort(arr, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareToIgnoreCase(o2); } } ); //使用lambda表达式 Arrays.sort(arr, (o1,o2) -> o1.compareToIgnoreCase(o2) ); }
五. 消费型接口与lambda 表达式
消费型接口Consumer抽象方法:
void accept(T t)
有参无返回值
例子:
JDK1.8 java.Lang.terable接口增加了一个默认方法,
public default void forEach(Consumer<? super T> action)
Iterable接口增加了方法,意味着所有的CoLLection系列的集合都有这个方法。因为ColLection接口继承了Iterable接口
代码演示如下:
@Test public void test04(){ ArrayList<String> list = new ArrayList<>();list.add("hello"); list.add("world"); list.add("atguigu"); list.add("java"); list .add("bigdata"); //forEach方法的功能是,遍历集合,并对集合的每一个元素做xx事情,具体做什么事情 // 由Consumer接口的accept抽象方法决定 //使用匿名内部类的形式 /*list.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }); */ //使用lambda表达式 list.forEach((s) -> System.out.println(s) ); }
六. 供给型接口与lambda表达式
供给型接口Supplier 抽象方法:
T get() 无参有返回值
例如: 使用Lambda表达式给一个Supplier类型的变量赋值一个“轨道”
代码演示如下:
@Test public void test05(){ Supplier<String> s= ()-> {return "轨道";}; //镜检如下 Supplier<String> str= ()-> "轨道"; methiod(s); //注意以下,在方法()中并非为lambda式的精简缩写,而是str的替换 methiod(()-> "轨道"); } //泛型方法 public <T> void methiod(Supplier<T> s){ System.out.println(s.get()); }
七. 判断型接口与lambda表达式
案例:在一个全是字符串的集合中,删除 包含“o”字母的单词
代码演示如下:
ArrayList<String> list = new ArrayList<>(); list.add("hello"); list.add("world"); list.add("atguigu"); list.add("java"); list.add("bigdata"); //删除 包含“o”字母的单词 //匿名类内部类的形式 list.removeIf(new Predicate<String>() { @Override public boolean test(String s) { return s.contains("o"); } }); //精简如下 list.removeIf( s -> s.contains("o") ); System.out.println(list);
八. 功能型接口与lambda表达式
案例一:使用Lambda表达式,给一个Function接口的变量赋值完成给某个字符串的首字母转为大写。
思路:
把字符串的首字母变为大写怎么实现?
(1) 把字符串的首字母拿出来,转为大写 (2) 把转换后的首字母 拼接上原来字符串除了首字母的部分,例如"heLlo"
把h拿出来,转为H,然后用H 与eLLo"拼接
Function<T,R> 抽象方法 R apply(T t) 有参有返回值
代码演示如下:
@Test public void test06(){ //某个字符串的首字母转为大写 //Function<T,R> T是指定形参类型,R是指定返回值类型 Function<String,String> f= s -> Character.toUpperCase(s.charAt(0))+s.substring(1); System.out.println(f.apply("hello")); }
JDK1.8中Map接口新增了一个方法:
default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
BiFunction<T,U,R>函数式接口,它的抽象方法 R apply(T t,u u)有2个参数,一个返回值。
案例二:将所有的value转换为大写
代码演示如下:
@Test public void test07(){ //将所有的value转换为大写 HashMap<Integer,String> map=new HashMap<>(); map.put(1,"jack"); map.put(2,"hello"); map.replaceAll((key,value) -> value.toUpperCase()); System.out.println(map); }
九. lambda 表达式综合案例
9.1 案例一
创建一个ArrayList,并添加26个小写字母到List中,并使用forEach遍历输出
方法思路:
第一步:
搞清楚forEach 方法的方法签名
方法 = 方法头 + 方法体,方法头又被称为方法签名。
方法头:[修饰符] 返回值类型
方法名([形参列表] )[throws 异常列表] 方法体:[语句代码}
void forEach(Consumerk? super E> action)搞清楚Consumer接口的抽象方法,它是一个函数式接口,就可以使用Lambda表达式
第二步:
抽象方法: void accept(T t) 【有参无返回值】
对于函数式接口的抽象方法来说,用Lambda表达式给他赋值时,不关心方法名,关心形参列表和返回值类型。
代码演示如下:
@Test public void test08(){ ArrayList<Character> list=new ArrayList<>(); for (char i = 'a'; i <='z' ; i++) { list.add(i); } System.out.println(list); System.out.println("------------------"); list.forEach(c -> System.out.print(c+"\t")); }
9.2 案例二
创建一个HashMap,并添加如下编程语言排名和语言名称到map中,并使用forEach遍历输出
排名 | 语言 |
1 | Java |
2 | c |
3 | python |
4 | c++ |
5 | c# |
代码演示如下:
@Test public void test09(){ HashMap<Integer,String> map=new HashMap<>(); map.put(1,"java"); map.put(2,"c"); map.put(3,"python"); map.put(4,"c++"); map.put(5,"c#"); map.forEach((key,value) -> System.out.println(key+":"+value)); }