java8新特性,老早之前学的,当时写的笔记,上传做个备份,之后自己博客上线了会移走,不是很难的东西,看看就懂了,比c++11的好理解多了。
lambda,optional和streamApi合在一起了,optional没写多少,主要理解了lambda和strreamApi就行。
Lambda
Lambda是一个匿名函数,我们可以把lambda表达式理解为一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更加简介、灵活的代码。作为一种更紧凑的代码风格,使Java的语言能力得到了提升。
- Lambda示范:
@Test
public void test02(){
Comparator<Integer> comparable = new Comparator<Integer>() {
@Override
public int compare(Integer o1,Integer o2) {
return Integer.compare(o1,o2);
}
};
int compare = comparable.compare(1, 2);
System.out.println(compare);
//lambda表达式
Comparator<Integer> comparator = (o1,o2) -> Integer.compare(o1,o2);
int compare1 = comparator.compare(21, 20);
System.out.println(compare1);
//方法引用
Comparator<Integer> comparator1 = Integer::compare;
int compare2 = comparator.compare(21, 20);
System.out.println(compare2);
}
Lambda格式:
->
为lambda操作符或箭头操作符。
箭头左边为形参列表(其实就是接口中的抽象方法的参数列表)
箭头右边是lambda体(其实就是重写的抽象方法的方法体)
lambda表达式的本质作为函数式接口的实例。
lambda使用分为6种情况:
- 无参,无返回值
@Test
public void test01(){
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello world");
}
};
r.run();
Runnable r2 = () -> System.out.println("Hello!");
r2.run();
}
- 有一个参数,但无返回值
@Test
public void test02(){
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("Hello World!");
Consumer<String> con1 = (String s) -> {
System.out.println(s);
};
con1.accept("Hello World!");
}
- 数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
@Test
public void Test03(){
//类型推断
Consumer<String> con = (s) -> {
System.out.println(s);
};
con.accept("Hello World!");
}
- lambda若只需要一个参数,则左边的小括号也可以省略
@Test
public void Test04(){
Consumer<String> con = s -> {
System.out.println(s);
};
con.accept("Hello World!");
}
- lambda需要两个或两个以上参数的时候,多条执行语句,并且可以有返回值
@Test
public void Test05(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
int compare = com.compare(12, 21);
System.out.println(compare);
Comparator<Integer> com1 = (o1,o2)->{
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
int compare1 = com1.compare(6, 5);
System.out.println(compare1);
}
- 当lambda体只有一条语句时候,return与大括号若有,都可以省略
@Test
public void Test06(){
Comparator<Integer> com1 = (o1,o2)->{
return o1.compareTo(o2);
};
System.out.println(com1.compare(12,21));
Comparator<Integer> com2 = (o1,o2)->o1.compareTo(o2);
System.out.println(com2.compare(6,5));
}
- 总结:
- 左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数小括号也可以省略。
- 右边: lambda体应该使用一对大括号包裹,但如果只有一条执行语句,那么可以省略大括号和return关键字。
函数式接口(Functional)
如果一个接口中,只声明了一个抽象方法,那么这个接口就称为函数式接口
可以通过lambda表达式来创建接口的对象。(若lambda表达式抛出一个受检异常,即运行时异常,那么改异常需要在目标接口的抽象方法上进行声明。)
我们可以在一个接口上使用@FunctionalInterface
注解,这样做可以检查它是否是一个函数时接口。同时javadoc也会包含这样一条声明,说明这个接口是一个函数式接口。
java.util.function包中定义了Java8的丰富函数式接口
四大核心接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T> 消费型接口 | T | void | 对类型T的对象应用操作,包含方法:void accept(T t) |
Supplier<T> 供给型接口 | 无 | T | 返回类型为T的对象,包含方法:T get() |
Function<T,R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法R apply(T t) |
Predicate<T> 断定型函数 | T | boolean | 确定类型为T的对象是否满足某约定,并返回boolean值。包含方法boolean test(T t) |
@Test
public void Test08(){
happyTime(500,money-> System.out.println("花费: "+money));
//happyTime(500,(Double money)->{ System.out.println("花费: "+money);});
List<String> list = Arrays.asList("北京","天津","东京","西京","普京");
List<String> list1 = filterString(list, s -> s.contains("京"));//取出所有包含“京”的数据
//List<String> list2 = filterString(list,(String s)->{return s.contains("京");});
System.out.println(list1);
}
public void happyTime(double money,Consumer<Double> consumer){
consumer.accept(money);
}
public List<String> filterString(List<String> list, Predicate<String> predicate){
List<String> arrayList = new ArrayList<>();
for (String s:list){
if (predicate.test(s)){
arrayList.add(s);
}
}
return arrayList;
}
其他接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T,U,R> | T,U | R | 对类型为T,U参数应用操作,返回R类型的结果。包含方法:R apply(T t,U u); |
UnaryOperator<T> (Function子接口) | T | T | 对类型为T的对象进行一元操作,并返回T类型的结果。包含方法T apply(T t) |
BinaryOperator<T> (BiFunction子接口) | T,T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。包含的对象为T apply(T t1,T t2); |
BiConsumer<T,U> | T,U | void | 对类型为T,U参数应用操作。包含方法为:void accept(T t,U u) |
BiPredicate<T,U> | T,U | boolean | 包含方法为:boolean test(T t,U u) |
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> | T | int long double | 分别计算int、long、double值的函数 |
IntFunction<R> LongFunction<R> DoubleFunction<R> | int long double | R | 参数分别为int、long、double类型的函数 |
方法引用
当要传递给lambda体的操作已经有实现方法了,就可以使用方法引用。
方法引用可以看作是lambda表达式深层次的表达。换句话说,方法引用就是lambda表达式,也就是函数式接口的一个实例,通过方法的名称来指引一个方法,可以认为是lambda表达式的一个语法糖。
要求:实现接口的抽象方法的参数列表和返回值类型,必须与引用方法的参数列表和返回值类型保持一致。
格式:使用::
将(类)或对象与方法名分割开。
常有如下三种情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
public class Test02 {
public static void main(String[] args) {
Consumer<String> consumer = System.out::println;
consumer.accept("Hello");
Function<Double,Long> function = new Function<Double, Long>() {
@Override
public Long apply(Double aDouble) {
return Math.round(aDouble);
}
};
Function<Double,Long> function1 = aDouble -> Math.round(aDouble);
Function<Double,Long> function2 = Math::round;
Long apply = function2.apply(1.2);
System.out.println(apply);
Comparator<String> comparator = (s1,s2)->s1.compareTo(s2);
Comparator<String> comparator1 = String::compareTo;
int compare = comparator1.compare("abc", "abd");
System.out.println(compare);
}
}
Optional类
到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因,为解决空指针异常,最后经过修改引入Optional类,现在Optional类以及称为Java8类库的一部分。
Optional<T>
类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原本用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
Optional类的javadoc描述下:这是一个可以为null的容器对象。如果值存在则isPresent()方法返回true,调用get()方法就会返回该对象。
StreamAPI
Java8中最重要的改变一个是lambda表达式,另一个就是StreamAPI。
Stream API(java.util.stream)把真正的函数式编程风格引入Java中。这是目前为止对Java库最好的补充,因为Stream API可以极大提升Java程序员的生产力,让程序员写出更高效、干净、简洁的代码。
Stream是Java8中处理集合的关键抽象概念。他可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进程操作,就类似于使用SQL查询数据库。也可以使用Stream API来执行并行操作。简而言之,Stream API提供了一种搞笑且易于使用的处理数据的方式。
为何要使用Stream API
实际开发中,项目中很多数据都来自Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Redis等,而这些NoSQL的数据就需要Java层面进行处理。
Stream和Collection集合的区别:Collection是一种静态的内存数据结构,而Stream是有关计算的。前者是主要面向内存,存储在内存中,而后者主要是面向CPU,通过CPU实现计算。
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,Stream讲的是计算”。
注意:
- Stream不会自己存储元素。
- Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream操作是有延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream操作三步骤
-
创建Stream
一个数据源(如:集合、数组),获取一个流。 -
中间操作
一个中间操作链,对数据源的数据进行处理。 -
终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被调用。
![](https://z3.ax1x.com/2021/08/28/hlTN0P.png)
Stream构造方法
- 自定义Person类
@Data
@AllArgsConstructor
@NoArgsConstructor//Lombok插件,maven导入即可
@ToString
public class Person {
private long id;
private String name;
private int age;
}
- Stream四种构造方法
public class Test01 {
public List<Person> getPersonList(){
List<Person> list = new ArrayList<>();
list.add(new Person(1,"user1",12));
list.add(new Person(2,"user2",13));
list.add(new Person(3,"user3",14));
list.add(new Person(4,"user4",15));
list.add(new Person(5,"user5",12));
list.add(new Person(6,"user6",12));
list.add(new Person(6,"user6",12));
return list;
}
@Test
public void test01() {
//创建Stream方式一:通过集合
List<Person> personList = getPersonList();
Stream<Person> stream = personList.stream();//default Stream<E> stream()返回一个顺序流
Stream<Person> personStream = personList.parallelStream();//default Stream<E> parallelStream()返回一个并行流
//创建Stream方式二:通过数组
int[] arr = new int[]{1,2,3,4};
IntStream stream1 = Arrays.stream(arr);//调用Arrays类的static <T> Stream<T> stream(T[] array)返回一个流
Person[] people = new Person[]{new Person(1,"hello1",1),new Person(2,"hello2",3)};
Stream<Person> personStream1 = Arrays.stream(people);
//创建Stream方法三:通过Stream的of()
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Person> personStream2 = Stream.of(new Person(1, "hello1", 1), new Person(2, "hello2", 3));
//创建Stream方法四:创建无限流(使用较少)
//1)迭代 public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f)
//seed-起始 UnaryOperator-函数式接口,你要执行的操作 .limit()终止 .forEach()遍历,对每个元素执行
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);//遍历前十个偶数
//2)生成 public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
}
Stream中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上出发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
1. 筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接受lambda,从流中排除某些元素。 |
distinct() | 筛选,通过流所生成元素的hashCode()和equals()去除重复元素。 |
limit(long maxSize) | 截断流,使其元素不会超过给定数量。 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit()互补。 |
@Test
public void test02(){
List<Person> personList = getPersonList();
Stream<Person> personStream = personList.stream();
//查找年龄大于12并输出
personStream.filter(e->e.getAge()>12).forEach(System.out::println);
System.out.println("--------------------------");
//此时不能使用personStream.其他有Stream方法() 原因是此时personStream流已经被销毁,需要重新创建
//年龄大于12的前两个数据并输出
personList.stream().filter(e->e.getAge()>12).limit(2).forEach(System.out::println);
System.out.println("--------------------------");
//跳过前三个元素并打印
personList.stream().skip(3).forEach(System.out::println);
System.out.println("--------------------------");
//筛选,通过流生成的hashCode()和equals()去除重复数据
personList.stream().distinct().forEach(System.out::println);
}
2. 映射
方法 | 描述 |
---|---|
map(Function f) | 接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射为一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接受一个函数作为参数,该函数会应用到每个元素上,产生一个新的DoubleStream流。 |
mapToInt(ToIntFunction f) | 接受一个函数作为参数,该函数会应用到每个元素上,产生一个新的IntStream流。 |
mapToLong(ToLongFunction f) | 接受一个函数作为参数,该函数会应用到每个元素上,产生一个新的LongStream流。 |
flatMap(Function f) | 接受一个流作为参数,然后将流中的每个值都换成另一个流,最后把所有的流连接成一个流。 |
@Test
public void test03(){
List<String> stringList = Arrays.asList("aa","bb","cc","dd");
stringList.stream().map(String::toUpperCase).forEach(System.out::println);
System.out.println("=================");
List<Person> personList = getPersonList();
personList.stream().filter(e->e.getName().length()>4).map(Person::getId).forEach(System.out::println);
}
3. 排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator com) | 产生一个流,其中按比较容器顺序排序 |
@Test
public void test04(){
List<Integer> integers = Arrays.asList(12, 43, 25, 78, 10, 28);
integers.stream().sorted().forEach(System.out::println);
System.out.println("-----------------");
getPersonList().stream().sorted((e1,e2)->Integer.compare(e1.getAge(),e2.getAge())).forEach(System.out::println);
}
Stream终止操作
1. 查找与匹配
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMathch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用Collectinon接口需要用户去做迭代,称为外部迭代。相反Stream API使用内部迭代-即帮你把迭代做了) |
@Test
public void test05(){
//是否所有人都大于12岁
boolean b = getPersonList().stream().allMatch(person -> person.getAge() > 12);
System.out.println(b);
//是否有至少一个人大于12岁
boolean b1 = getPersonList().stream().anyMatch(person -> person.getAge() > 12);
System.out.println(b1);
//检查是否没有人12岁以下
boolean b2 = getPersonList().stream().noneMatch(person -> person.getAge() < 12);
System.out.println(b2);
//返回匹配到的第一个人
Optional<Person> person1 = getPersonList().stream().filter(person -> person.getAge()>13).findFirst();
System.out.println(person1);
//返回匹配到的任意元素
Optional<Person> person2 = getPersonList().stream().filter(person -> person.getAge() > 13).findAny();
System.out.println(person2);
//返回记录数量
long count = getPersonList().stream().count();
System.out.println(count);
//返回最大值
Optional<Integer> max = getPersonList().stream().map(person -> person.getAge()).max(Integer::compare);
System.out.println(max);
//返回最大值
Optional<Person> min = getPersonList().stream().min((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
System.out.println(min);
}
2. 归约
方法 | 描述 |
---|---|
reduce(T iden,BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值,返回T |
reduct(BinartOperator b) | 可以将流中的元素反复结合起来,得到一个值。返回Optional<T> |
@Test
public void test06(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer reduce = list.stream().reduce(0, Integer::sum);
System.out.println(reduce);
Integer reduce1 = getPersonList().stream().map(person -> person.getAge()).reduce(1, (e1, e2) -> (e1 * e2));
System.out.println(reduce1);
}
3. 收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换成其他形式。接受一个Collector接口的实现,用于给Stream中元素做汇总的方法。 |
Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到list、set、map)。
另外,Collectors实用类提供了很多静态方法,可以方便的创建收集器实例,具体方法如下:
方法 | 返回类型 | 作用 | |
---|---|---|---|
toList | List<T> | 把流中元素收集到List中 | list.stream().collect(Collectors.toList()) |
toSet | Set<T> | 把流中元素收集到Set中 | list.stream().collect(Collectors.toSet()) |
toCollection | Collection<T> | 把流中元素收集到创建的集合中 | list.stream().collect(Collectors.toCollection(ArrayList::new)) |
counting | Long | 计算流中元素个数 | list.stream().collect(Collectors.counting) |
summingInt | Integer | 计算流中元素的整数属性和 | list.stream().collect(Collectors.summingInt(Person::getAge)) |
averageingInt | Double | 计算流中元素Integer属性的平均值 | list.stream().collect(Collectors.averageingInt(Person::getAge)) |
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。如:平均值 | list.stream().collect(Collectors.summarizingInt(Person::getAge)) |
joining | String | 连接流中每个字符串 | list.stream().map(Person::getName).collect(Collectors.joining()) |
maxBy | Optional<T> | 根据比较器选择最大值 | list.stream().collect(Collectors.maxBy(comparingInt(Person::getAgge))) |
minBy | Optional<T> | 根据比较器选择最小值 | list.stream().collect(Collectors.minBy(comparingInt(Person::getAgge))) |
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值。 | list.stream().collect(Collectors.reducing(0,Person::getAge,Integer::sum)) |
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换函数 | list.stream().collect(Collectors.collectingAndThen(Collectors.toList(),List::size)) |
groupingBy | Map<K,List<T>> | 根据某属性值对流分组,属性为K,结果为V | list.stream().collect(Collectors.groupingBy(Person::getAge)) |
paritioningBy | Map<Boolean,List<T>> | 根据True或False分区 | list.stream().collect(Collectors.paritioningBy(Person::getManage)) |