文章目录
1.Lambda表达式
(1)在学习Lambda之前,我们先来看一段代码
// 匿名内部类
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
};
(2)当我们看完上述代码后,我们会感到这段代码很复杂,但其实只是为了重写Runnable接口的run方法,输出“hello world”,
其实使用Lambda可以简化代码
1.1 使用Lambda去实现上述代码
// Lambda表达式
Runnable r1 = () -> {
System.out.println("hello Lambda");
};
在学习lambda之前,我们先来了解两个概念:
(1)面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情
(2)函数式编程思想:只要能获取到结果,谁去做的都不重要,重视的是结果,不重视过程。Lambda表达式就是函数式编程
思想的很好体现。Lambda 表达式需要“函数式接口”的支持,接口中只有一个抽象方法的接口,称为函数式接口。可以使用
注解@FunctionalInterface在接口上,去检查是否为函数式接口
1.2.Java8内置的四大核心函数式接口
(1)四大函数式接口
//1.消费型接口(没有返回值,有去无回)
Consumer<T> {
void accept(T t);
}
//2.供给型接口(一调用该方法就返回一些内容)
Supplier<T>{
T get();
}
//3.函数型接口(传进去T返回R)
Function<T,R>{
R apply(T t);
}
//4.断言型接口(用于做一些判断操作,返回boolean值)
Predicate<T>{
boolean test(T t);
}
(2)实例
public class TestLambda3 {
// 1.Consumer<T>:消费型接口(没有返回值,有去无回)
@Test
public void test1() {
happy(1000, (m) -> System.out.println("消费了" + m + "元"));
}
public void happy(double money, Consumer<Double> con) {
con.accept(money);
}
// 2.供给型接口(一调用该方法就返回一些内容)
@Test
public void test2() {
List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
for (Integer num : numList) {
System.out.println(num);
}
}
//需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
//3.函数型接口(传进去T返回R)
@Test
public void test3() {
String newStr = strHandler("我爱学习java", (str) -> str.substring(1, 4));
System.out.println(newStr);
}
// 需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun) {
return fun.apply(str);
}
//4.断言型接口(用于做一些判断操作,返回boolean值)
@Test
public void test4() {
List<String> list = Arrays.asList("hello", "java", "ok", "1", "python");
List<String> strings = filterStr(list, (s) -> s.length() > 3);
for (String str : strings) {
System.out.println(str);
}
}
// 需求: 将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre) {
List<String> strList = new ArrayList<>();
for (String str : list) {
if (pre.test(str)) {
strList.add(str);
}
}
return strList;
} }
1.3 Lambda的书写规范
1.3.1 Lambda表达式的标准格式
(1)由三部分组成:
a.一些参数():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号隔开
b.一个箭头 ->:传递的意思,把参数传递给方法体{ }
c.一段重写方法的代码 { }:重写接口的抽象方法的方法体(有返回值的话加上 return)
(2)格式:
(一些参数) -> { 一段重写方法的代码 };
1.3.2 Lambda基础语法示例
(1)语法格式一:无参数,无返回值
() -> System.out.println("hello Lambda");
(2)语法格式二:有一个参数,无返回值
(x) -> System.out.println(x);
(3)语法格式三:只有一个参数,小括号可以省略不写
x -> System.out.println(x);
(4)有两个以上的参数,有返回值,并且Lambda体中有多条语句
Comparator<Integer> com = (x,y) -> {
System.out.println("函数式接口");
return Integer.compare(x,y);
};
(5)若Lambda体中只有一条语句,return和大括号都可以省略不写
Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);
(6)Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器可以通过上下文退出推出
1.3.3 Lambda的书写细节
(1).(参数列表):括号中参数列表的数据类型,可以省略不写
(2).(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略
(3).{一些代码}:如果{ }中的代码只有一行,无论是否有返回值,都可以省略({ },return,分号)
注意:第三条要省略时:{ },return,分号必须一起省略
1.4 方法引用与构造器引用
1.4.1 方法引用
若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”(可以理解为方法引用是Lambda表达式的另外一种表现形式)
(1)方法引用主要有以下三种语法格式
①对象::实例方法名
②类::静态方法名
③类::实例方法名
(2)实例
// 1.对象::实例方法名
Consumer<String> con = (x) -> System.out.println(x);
/* PrintStream ps = System.out;
Consumer<String> con1 = ps::println;*/
Consumer<String> con1 = System.out::println;
// 2.类::静态方法名
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
Comparator<Integer> com1 = Integer::compare;
// 3.类::实例方法名
BiPredicate<String,String> bp = (x,y) -> x.equals(y);
BiPredicate<String,String> bp2 = String::equals;
(3)注意
①Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致
②若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用 类::实例方法名
1.4.2 构造器引用
(1)格式
类::new
(2)实例
// 构造器引用
Supplier<Employee> sup = () -> new Employee();
Supplier<Employee> sup2 = Employee::new;
Function<Integer,Employee> fun = (x) -> new Employee(x);
Function<Integer,Employee> fun2 = Employee::new;
(3)注意
需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
2.Optional概述
(1)从 Java 8 引入的一个很有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException)
每个 Java 程序员都非常了解的异常。本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。
Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现。
(2)原来我们使用null表示一个值不存在,现在Optional可以更好的表达这个概念,并且可以避免空指针异常
2.1常用方法
(1)Optional.of(T t):创建一个Optional实例
(2)Optional.empty():创建一个空的Optional实例
(3)Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
(4)isPresent():判断是否包含空值
(5)orElse(T t):如果调用对象包含值,返回该值,否则返回t
(6)orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值
(7)map(Function f):如果有值对其处理,返回处理后的Optional,否则返回Optional.empty()
(8)flatMap(Function mapper):与map相似,要求返回值必须是Optional
// (1)Optional.of(T t):创建一个Optional实例
Optional<Employee> op = Optional.of(new Employee());//如果传入null,方便排查空指针异常
Employee emp = op.get();
System.out.println(emp);
// (2)Optional.empty():创建一个空的Optional实例
Optional<Object> empty = Optional.empty();
// (3)Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
Optional<Employee> employee = Optional.ofNullable(new Employee());
3.Stream流
(1)学习Stream流之前,我们先看一下下面这个问题
任务要求:有如下一个ArrayList集合,只要名字为3个字的成员姓名,筛选之后且只要前三个人
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("孙悟空");
one.add("猪八戒");
one.add("古力娜扎");
one.add("洪七公");
one.add("唐三藏");
(2)使用传统的实现方法如下:
// 将集合进行筛选,只要名字为3个字的成员姓名,并且只要前三个人
List<String> one1 = new ArrayList<>();
for (String name : one) {
if (name.length() == 3){
one1.add(name);
}
}
List<String> one2 = new ArrayList<>();
for (int i = 0; i < 3; i++) {
one2.add(one1.get(i));
}
System.out.println(one2);
(3)使用Stream实现如下:
Stream<String> oneStream = one.stream().filter(name -> name.length() == 3).limit(3);
oneStream.forEach((p) ->{
System.out.println(p);
});
System.out.println(oneStream);
3.1 什么是Stream流?
(1)Stream流是JDK1.8之后出现的,Stream流其实是一个集合元素的函数模型,他并不是集合,也不是数据结构,其本身并不会存储任何元素(或地址值)
(2)Stream流属于管道流,只能被使用一次,当一个Stream流使用过后,就不能再调用方法了
// 创建一个stream流
Stream<String> stream = Stream.of("马云", "王健林", "马化腾");
// 对Stream流中的元素进行过滤,只要姓马的人
Stream<String> stream1 = stream.filter((String name) -> {
return name.startsWith("马");
});
// 遍历Stream流
stream1.forEach(name -> System.out.println(name));
//Stream是管道流,下面这个会报IllegalStateException: stream has already been operated upon or closed异常
stream.forEach(name -> System.out.println(name));
3.2 Stream流的获取
(1)单列集合(Collection集合):可以通过stream或parallelStream方法获取流;
default Stream stream()
default Stream parallelStream()
ArrayList<Object> list = new ArrayList<>();
Stream<Object> stream = list.parallelStream();
(2) 数组:通过Arrays.stream(数组)或者使用Stream.of来创建
static Stream of(T values)
注:参数是一个可变参数,那么我们就可以传递一个数组
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
(3)双列集合:转换成单列集合后再创建
Map<String,Integer> map = new HashMap<>();
map.put("张三",12);
map.put("李四",16);
map.put("王五",18);
Stream<Map.Entry<String,Integer> = map.entrySet().stream();
3.3 Stream流的常用方法
3.3.1 中间操作
(1)Stream<T> filter(Predicate<? super T> predicate)
filter方法的参数Predicate是一个函数式接口,可以传递Lambda表达式,对数据进行过滤,符合过滤条件的才能继续留在流中
// 创建一个stream流
Stream<String> stream = Stream.of("马云", "王健林", "马化腾");
// 对Stream流中的元素进行过滤,只要姓马的人
Stream<String> stream1 = stream.filter((String name) -> {
return name.startsWith("马");
});
// 遍历Stream流
stream1.forEach(name -> System.out.println(name));
(2)<R> Stream<R> map(Function<? super T, ? extends R> mapper);
可以对流中的元素进行计算或转换;map方法可以将流中的元素映射到另一个流中,该接口需要一个Function函数式(函数型接口)接口参数,可以将流中的T类型转换为另一种R类型(R可以和T是同一种类型)的流。
// 获取一个String类型的Stream流
Stream<String> stream = Stream.of("1", "2", "4", "6");
// 使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数
Stream<Integer> stream1 = stream.map((String s) -> {
return Integer.parseInt(s);
});
// 遍历Stream流
stream1.forEach(i -> System.out.println(i));
补充:
①map:接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
②flatMap:接收一个函数作为参数,将流中的每个值都转换成另一个流,然后把所有流连接成一个流
(3)distinct:可以去除流中的重复元素
注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法。
(4)sorted:可以对流中的元素进行排序。
(3)Stream<T> limit(long maxSize);
用于截取流中的元素,参数是一个long类型,如果集合当前长度大于参数进行截取,否则不进行操作
limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法
Stream<String> stream = Stream.of("胡歌", "彭于晏", "马天宇","刘德华");
Stream<String> stream1 = stream.limit(3);
stream1.forEach(name -> System.out.println(name));
(4)Stream<T> skip(long n);
用于跳过前几个元素,可以使用skip方法获取一个截取之后的新流;
如果流的长度大于n,则跳过前n个元素;否则将会得到一个长度为0的空流
Stream<Integer> stream = Stream.of(1, 2, 4, 9);
Stream<Integer> stream1 = stream.skip(2);
stream1.forEach(name -> System.out.println(name));
3.3.2 终结操作
(1)void forEach(Consumer<? super T> action)
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理
Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据
简言之:foreach方法就是用来遍历流中的数据,并且是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
// 获取一个Stream流
Stream<String> stream = Stream.of("亚瑟", "李白", "鲁班");
// 使用Stream流中的方法forEach对Stream流中的数据进行遍历
stream.forEach((String name) ->{
System.out.println(name);}
6.public static Stream concat(Stream<? extends T> a, Stream<? extends T> b)
用于把流组合到一起
Stream<String> stream1 = Stream.of("周星驰", "李小龙", "成龙");
Stream<String> stream2 = Stream.of("胡歌", "彭于晏");
Stream<String> stream = Stream.concat(stream1, stream2);
stream.forEach(name -> System.out.println(name));
7.排序
(1)sorted()——自然排序(Comparable,按照字典排)
(2)sorted(Comparator com)——定制排序(Comparator)
8.终止操作
(1)allMatch——检查是否匹配所有元素
(2)anyMatch——检查是否至少匹配一个元素
(3)noneMatch——检查是否没有匹配所有元素
(4)findFirst——返回第一个元素
(5)findAny——返回当前流中的任意元素
(6)count——返回流中元素的总个数
(7)max——返回流中最大值
(8)min——返回流中最小值
9.规约
T reduce(T identity, BinaryOperator<T> accumulator)
:可以将流中元素反复结合起来,得到一个值,返回T
Optional<T> reduce(BinaryOperator<T> accumulator)
:可以将流中元素反复结合起来,得到一个值,返回Optional<T>
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);//得到总和
// 先把0作为x,y作为1,x+y=1;再把结果1作为x
System.out.println(sum);
10.收集
collect(Collcetor c) 方法,收集流中的数据到【集合】或者【数组】中去。
Collector接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors实用类提供了
很多静态方法,可以方便的创建常见收集器实例
//1.收集数据到list集合中
stream.collect(Collectors.toList())
//2.收集数据到set集合中
stream.collect(Collectors.toSet())