Lambda
函数式编程思想概述
在数学中,函数就是有输入流,输出流的一套计算方案,也就是“拿数据做操作”,面向对象思想强调必须通过对象的形式来做事情,而函数式思想则尽量忽略面向对象的复杂语句,强调做什么,额不是以书面形式去做,而Lambda表达式正是函数式思想的体现
Lambda表达式的标准格式
- 匿名内部类中重写run()方法的代码分析
- 方法形式参数为空,说明调用方法时不需要传递参数
- 方法返回值类型为void,说明方法执行没有结果返回
- 方法体中的内容,是具体要做的事情
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello,world");
}
}).start();
- Lambda表达式的代码分析
- ():里面没有内容,可以看成是方法形式参数为空
- ->:用箭头指向后面要做的事情
- {}:包含一段代码,称之为代码块,可以看做是方法体中的内容
new Thread(() -> {
System.out.println("hello,world");
}).start();
-
组成Lambda表达式的三要素:形式参数,箭头,代码块
-
Lambda表达式的格式
- 格式:(形式参数) ->{代码块}
- 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ->:由英文中划线和大于符号组成,固定写法,代表指向动作
- 代码块:是具体要做的事情,也就是写的方法体内容
-
Lambda表达式的使用前提
- 有一个接口
- 接口中有且仅有一个抽象方法
-
Lambda表达式的省略模式
- 参数类型可以省略,但是有多个参数的情况下,不能只省略一个
usePrint((s) ->{ System.out.println(s); });
- 如果参数有且仅有一个,那么小括号可以省略
usePrint(s ->{ System.out.println(s); });
- 如果代码块的语句只有一条,则可以省略大括号和分号,甚至是return
usePrint(s -> System.out.println(s));
-
Lambda表达式的注意事项
- 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
- 必须有上下文环境,才能推导出Lambda对应的接口
- 根据局部变量的赋值的值Lambda对应的接口:Runnable r = () -> System.out.println(“Lambda表达式”);
- 根据调用方法的参数的值Lambda对应的接口:new Thread(() -> System.out.println(“Lambda表达式”)).start();
-
Lambda表达式和匿名内部类的区别
- 所需类型不同
- 匿名内部类:可以使接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
- 使用权限不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
- 实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class。对应的字节码会在运行时动态生成
- 所需类型不同
接口组成更新
-
接口的组成
- 常量:public static final
- 抽象方法:public abstract
- 默认方法(Java 8)
- 静态方法(Java 8)
- 私有方法(Java 9)
-
接口中的默认方法
- 格式:public default 返回值类型 方法名(参数列表){ }
-
接口中默认方法的注意事项
- 默认方法不是抽象方法,所以不强制被重写,但是也可以被重写,重写时候去掉default关键字
- public可以省略,default不能省略
-
接口中的静态方法
- 格式:public static 返回值类型 方法名(参数列表){ }
-
接口中静态方法的注意事项
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略
-
接口中的私有方法
当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有隐藏起来
-
私有方法的定义格式
- 格式1:private 返回值类型 方法名(参数列表){ }
- 格式2:private static 返回值类型 方法名(参数列表){ }
-
接口中私有方法的注意事项
- 默认方法可以调用私有的静态方法和非静态方法
- 静态方法只能调用私有的静态方法
- 该方法是在Java 9 中新增的功能
方法引用
-
方法引用符 :: 该方法为引用运算符,而它所在的表达式被称为方法引用
-
Lambda表达式:usePrintable(System.out.println(s));
- 分析:拿到参数s之后通过Lambda表达式,传递给System.out.println方法去处理
-
方法引用:usePrintable(System.out::println);
- 分析:直接使用System.out中的println方法来取代Lambda,代码更加简洁
-
常见的引用方式
- 引用类方法
- 引用对象的实例方法
- 引用类的实例方法
- 应用构造器
-
引用类方法
- 引用类方法,其实就是引用类的静态方法
- 格式:类名::静态方法
- 范例:Integer::parseInt
- Lambda表达式被类方法替代时,它的形式参数全部传递给静态方法作为参数
-
引用对象的实例方法
- 引用对象的实例方法,其实就是引用类中的成员方法
- 格式:对象::成员方法
- 范例:“HelloWorld”::toUpperCase
- Lambda表达式被对象的实例方法替代时,它的形式参数全部传递给该方法作为参数
-
引用类的实例方法
- 引用类的实例方法,其实就是引用类中的成员方法
- 格式:类名::成员方法
- 范例:String::substring
- Lambda表达式被类的实例方法替代时,第一个参数作为调用者,后面的参数全部传递给该方法作为参数
-
引用构造器
- 引用构造器,其实就是引用构造方法
- 格式:类名::new
- 范例:Student::new
- Lambda表达式被构造器替代时,它的形式参数全部传递给构造器作为参数
函数式接口
-
函数式接口:有且仅一个抽象方法的接口
-
Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以适用于Lambda使用的接口,只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导
-
将@FunctionalInterface放在接口定义的上方,如果接口是函数式接口,编译通过,如果不是,编译失败
-
我们自己定义函数式接口的时候,@FunctionalInterface是可选的,就算不写这个注释,只要保证满足函数式接口定义的条件,也照样是函数式接口,建议加上该注释
-
-
函数式接口作为方法的参数
- 如果方法的参数是一个函数式接口,我们可以使用Lambda表达式作为参数传递
- startThread(()-> System.out.println(Thread.currentThread().getName() + “线程启动了”));
-
函数式接口作为方法的返回值
-
如果方法的返回值是一个函数接口,我们可以使用Lambda表达式作为结果返回
-
private static Comparator getComparator(){
return (s1,s2) -> s1.length() - s2.length();
}
-
常用的函数式接口
- Supplier:包含一个无参的方法
- T get():获得结果
- 该方法不需要参数,他会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
- Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用
- Consumer:包含两个方法
- void accept(T t):对给定的参数执行此操作
- default Consumer andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行then操作
- Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定
- Predicate:常用的四个方法
- boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
- default Predicate negate():返回一个逻辑的否定,对应逻辑非
- default Predicate and(Predicate other):返回一个组合判断,对应短路与
- default Predicate or(Predicate other):返回一个组合判断,对应短路或
- Predicate接口通常用于判断参数是否满足指定的条件
- Function<T,R>:常用的两个方法
- R apply(T t):将此函数应用于给定的参数
- default Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
- Function<T,R>接口通常用于对参数进行处理,转换(逻辑处理由Lambda表达式实现),然后返回一个新的值
Stream流
-
使用Stream流的方式完成过滤操作
- 例如:list.stream().filter(s -> s.startsWith(“张”)).filter(s -> s.length() == 3).forEach(System.out::println);
- 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:生成流、过滤姓张、过滤长度为3、逐一打印
- Stream流把真正的函数式编程风格引入到Java中
-
Stream流的使用
- 生成流
- 通过数据源(集合,数组等)生成流
- list.stream()
- 中间操作
- 一个流后面可以跟随零个或多个中间操作,其主要目的是打开流,作出某种程度的数据过滤/映射,然后返回一个新的流,交给下一个操作使用
- filter()
- 终结操作
- 一个流只能有一个终结操作,当这个操作执行后,流就被使用光了,无法在被操作。所以这必定是最后一个操作
- forEach()
- 生成流
-
Stream流的常见生成方式
- Collection体系的集合可以使用默认方法stream()生成流
- default Stream stream()
- Map体系的集合间接的生成流
- 数组可以通过Stream接口的静态方法of(T…values)生成流
- Collection体系的集合可以使用默认方法stream()生成流
public static void main(String[] args) {
//Collection体系的集合可以使用默认方法stream()生成流
List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();
//Map体系的集合间接的生成流
Map<String,Integer> map = new HashMap<String, Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
//数组可以通过Stream接口的静态方法of(T...values)生成流
String[] strArray = {"hello","world","java"};
Stream<String> strArrayStream = Stream.of(strArray);
Stream<String> strArrayStream2 = Stream.of("hello","world","java");
Stream<Integer> intStream = Stream.of(10,20,30);
}
- Stream流的常见中间操作方法
- Stream filter(Predicate predicate):用于对流中的数据进行过滤
- Stream limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
- Stream skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
- static Stream concat(Stream a,Stream b):合并a和b两个流为一个流
- Stream distinct():返回由该流的不同元素(根据Object.equals(Object))组成的流
- Stream sorted():返回由此流的元素组成的流,根据自然顺序排序
- Stream sorted(Comparator comparator):返回由此流的元素组成的流,根据提供的Comparator排序
- Stream map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流
- IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("星际争霸");
list.add("星球大战");
list.add("炉石");
list.add("魔兽");
list.add("lol");
list.add("风暴要火");
list.add("荒野大嫖客");
//需求1:把长度为 4的数据输出到控制台
list.stream().filter(s -> s.length() == 4).forEach(System.out::println);
System.out.println("=================");
//需求2:把前三个数据输出到控制台
list.stream().limit(3).forEach(System.out::println);
System.out.println("=================");
//需求3:跳过三个元素,把剩下的在控制台输出
list.stream().skip(3).forEach(System.out::println);
System.out.println("=================");
//需求4:把需求 1和需求 2得到的的数据合并
Stream<String> s1 = list.stream().filter(s -> s.length() == 4);
Stream<String> s2 = list.stream().limit(3);
Stream.concat(s1, s2).forEach(System.out::println);
System.out.println("=================");
//需求4:把需求 1和需求 2得到的的数据合并,要求结果不能重复
Stream<String> s3 = list.stream().filter(s -> s.length() == 4);
Stream<String> s4 = list.stream().limit(3);
Stream.concat(s3, s4).distinct().forEach(System.out::println);
System.out.println("=================");
//需求5:按照字符串长度进行排序
list.stream().sorted((ss1, ss2) -> ss1.length() - ss2.length()).forEach(System.out::println);
System.out.println("=================");
ArrayList<String> list2 = new ArrayList<String>();
list2.add("10");
list2.add("20");
list2.add("30");
list2.add("40");
list2.add("50");
//需求6:将集合中的字符串转换为整数之后在控制台输出
list2.stream().map(Integer::parseInt).forEach(System.out::println);
System.out.println("=================");
//需求7:将集合中的字符串转换为整数之后求和并在控制台输出结果
int sum = list2.stream().mapToInt(Integer::parseInt).sum();
System.out.println(sum);
}
- Stream流的常见终结操作方法
- void forEach(Consumer action):对此流的每个元素执行操作
- long count():返回此流中的元素数
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("张三四");
list.add("张九六");
list.add("王五五");
list.add("赵七八");
list.add("孙二三");
list.add("张四七");
list.add("刘八二");
//需求1:把集合中的每个元素在控制台输出
list.stream().forEach(System.out::println);
//需求2:统计集合中有几个姓张的元素,并把统计结果输出在控制台
long count = list.stream().filter(s -> s.startsWith("张")).count();
System.out.println(count);
}
- Stream流的收集操作
- R collect(Collector collector)
- 但是这个收集方法的参数是一个Collector接口
- 工具类Collectors提供了具体的收集方式
- public static Collector toList():把元素收集到List集合中
- public static Collector toSet():把元素收集到Set集合中
- public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中
public static void main(String[] args) {
//创建list集合对象
List<String> list = new ArrayList<String>();
list.add("周星驰");
list.add("成龙");
list.add("彭于晏");
list.add("吴彦祖");
//需求1:得到名字为3个字的流
Stream<String> listStream = list.stream().filter(s -> s.length() == 3);
//需求2:使用Stream流操作完毕的数据收集到list集合中并遍历
List<String> name = listStream.collect(Collectors.toList());
for (String s : name) {
System.out.println(s);
}
//创建set集合对象
Set<Integer> set = new HashSet<Integer>();
set.add(20);
set.add(25);
set.add(30);
set.add(35);
set.add(40);
//需求3:得到年龄大于25的流
Stream<Integer> setStream = set.stream().filter(s -> s > 25);
//需求4:使用Stream流操作完毕的数据收集到Set集合中并遍历
Set<Integer> age = setStream.collect(Collectors.toSet());
for (int i : age) {
System.out.println(i);
}
System.out.println("=============");
//定义一个字符串数组,每一个字符串数据由姓名和年龄组成
String[] str = {"周星驰,20", "成龙,25", "彭于晏,30", "吴彦祖,40"};
//需求5:得到字符串中年龄数据大于25的流
Stream<String> stringStream = Stream.of(str).filter(s -> Integer.parseInt(s.split(",")[1]) > 25);
//需求6:使用Stream流操作完毕的数据收集到Map集合中并遍历
Map<String, Integer> map = stringStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));
Set<String> keySet = map.keySet();
for (String s : keySet) {
Integer value = map.get(s);
System.out.println(s + "," + value);
}
}