Java8的新特性
在Java8中,Lambda表达式就是一个函数式接口的实例,所以以前使用匿名实现类表示的现在都可以用Lambda表达式来写
1、Lambda表达式
举例:(o1,o2) -> Integer.compare(o1, o2)
格式:
- 操作符的左边是接口中抽象方法的形参列表
- 右边是 Lambda体,是重写的抽象方法的方法体
总结:
- Lambda表达式的本质就是作为接口的实例(该接口只有一个需要被重写的抽象方法),此接口被称为函数式接口
- 操作符的左边:
- 形参列表的数据类型可省略
- 如果形参列表只有一个参数,其一对 () 也可以省略
- 操作符的右边:
- Lambda 体由一对 {} 包裹
- 若Lambda 体只有一条执行语句,则可以省略一对大括号 {} 和 return 关键字
体会Lambda表达式
@Test
public void test1(){
//体会Lambda表达式
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("小草莓");
}
};
runnable.run();
System.out.println("*********************");
Runnable runnable1 = () -> System.out.println("大西瓜");
runnable1.run();
}
执行结果如下
体会Lambda表达式
@Test
public void test2(){
Comparator comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1 , o2);
}
};
System.out.println(comparator.compare(1, 21));
System.out.println("*********************");
//Lambda表达式的写法
Comparator<Integer> comparator1 = (o1,o2) -> Integer.compare(o1, o2);
System.out.println(comparator1.compare(21, 1));
//方法引用的写法
Comparator<Integer> comparator2 = Integer::compare;
System.out.println(comparator2.compare(2, 22));
}
执行结果如下
1.1、无参,无返回值
语法格式一:
无参,无返回值
//格式一:无参,无返回值
@Test
public void teat1(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("小菜鸟");
System.out.println("小草莓");
}
};
runnable.run();
System.out.println("**************");
Runnable runnable1 = () -> {
System.out.println("小菜鸟");
System.out.println("小草莓");
};
runnable1.run();
}
执行结果如下
1.2、一个参数,无返回值
语法格式二:
有一个参数,无返回值
@Test
public void test2(){
//Consumer类是工具类,其中有抽象方法void accept(T t)
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
System.out.println("小草莓");
}
};
consumer.accept("大西瓜");
System.out.println("**************");
Consumer<String> consumer1 = (String c) -> {
System.out.println(c);
System.out.println("小草莓");
};
consumer1.accept("大灰狼");
}
执行结果如下
1.3、类型推断
语法格式二:
数据类型可以省略,因为可由编译器推断得出,称为 “ 类型推断 ”
这种 类型推断 其实之前就有碰到过,比如创建ArrayList
集合的时候,推断等式右边泛型的数据类型。比如创建数组时候,可以省略等式右边的数据类型
ArrayList<String> arrayList = new ArrayList<>();
int[] a = {1,2,3}; //int[] a = new int[]{3,2,1};
@Test
public void tedt3(){
// Consumer<String> consumer1 = System.out::println; 可以写成方法引用形式
Consumer<String> consumer1 = (String c) -> {
System.out.println(c);
};
consumer1.accept("大灰狼");
System.out.println("**************");
Consumer<String> consumer2 = (c) -> { //省略了数据类型
System.out.println(c);
};
consumer2.accept("大灰狼");
}
1.4、一个参数可省略括号
Consumer<String> consumer2 = c -> {
System.out.println(c);
};
consumer2.accept("大灰狼");
1.5、多个参数,有返回值
@Test
public void teat4(){
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
System.out.println(comparator.compare(12, 99));
System.out.println("**************");
Comparator<Integer> comparator1 = (o1, o2) -> {
return Integer.compare(o1, o2);
};
System.out.println(comparator1.compare(12, 99));
}
1.6、一条执行 语句,省略return与大括号
@Test
public void test4(){
Comparator<Integer> comparator1 = (o1, o2) -> {
return o1.compareTo(o2);
};
System.out.println(comparator1.compare(12, 99));
System.out.println("**************");
Comparator<Integer> comparator2 = (o1, o2) -> o1.compareTo(o2);
System.out.println(comparator2.compare(12, 99));
}
@Test
public void test5(){
Consumer<String> consumer = s -> {
System.out.println(s);
};
consumer.accept("小草莓");
System.out.println("**************");
Consumer<String> consumer1 = s ->System.out.println(s);
consumer1.accept("小草莓");
}
2、函数式(Functional)接口
如果一个接口中,只声明了一个抽象方法,则此接口就被称为函数式接口。比如Runnable
接口,只有一个抽象方法run()
。函数式接口是 JDK 1.8 之后出现的
自定义函数式接口,可以用到注解@FunctionalInterface
,该注解只是做到检验的作用,比如@Override
,检验作用,如果格式正确前提下,不加该注解也同样是重写方法,但是这里的注解可以给你检验提示
@FunctionalInterface
public Interface MyInterface{
void method();
}
2.1、函数式接口介绍
Java 内置四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
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 test(T t) |
Consumer<T>
:消费型接口,void accept(T t)
@Test
public void test(){
method1(11, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println(aDouble);
}
});
System.out.println("*************");
method1(55, s -> System.out.println(s));
}
public void method1(double money, Consumer<Double> consumer){
consumer.accept(money);
}
执行结果如下
Supplier<T>
:供给型接口,T get()
Function<T,R>
:函数型接口,R apply(T t)
Predicate<T>
:断定型接口boolean test(T t)
@Test
public void test2(){
List<String> strings = Arrays.asList("北京", "天津", "普京", "今天", "东京", "西经");
filterString(strings, new Predicate<String>() {
@Override
public boolean test(String string) {
return string.contains("京");
}
});
System.out.println("*************");
filterString(Arrays.asList("西瓜", "西天", "冬天", "今天", "冬瓜", "西经"),s -> s.contains("西"));
}
//将list1中满足条件的元素添加进list2
public void filterString(List<String> list1, Predicate<String> predicate){
ArrayList<String> list2 = new ArrayList<>();
for (String s : list1) {
if(predicate.test(s))
list2.add(s);
}
System.out.println(list2);
}
执行结果如下
2.2、其他接口
3、方法引用method reference
举例:Integer :: compare;
方法引用的前提,是引用的方法和被引用的方法,需要达到功能一致,就像使用比较方法的时候,也总是在方法体中直接调用 String 或 Integer 中已经被重写的比较方法
方法引用,本质上就是 Lambda 表达式,而 Lambda 表达式是函数式接口的实例,所以方法引用也是函数式接口的实例
使用情景:
当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用
要求:
要求接口中的抽象方法的参数列表和返回值类型,必须与被引用的方法的参数列表和返回值类型保持一致!
以上要求是针对于前两种情况,对于 类::实例方法
,参数列表和返回值类型不一定一样
3.1、对象 :: 实例方法
Consumer
中的 void accept(T t)
PrintStream
中的 void println(T t)
@Test
public void test3(){
//Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
//};
//情景一:对象 :: 实例方法
//Consumer 中的 void accept(T t)
//PrintStream 中的 void println(T t)
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("小草莓");
System.out.println("*************");
//System.out返回一个打印流PrintStream的对象,通过这个对象调用println实例方法
PrintStream printStream = System.out;
Consumer<String> consumer1 = printStream::println;
//Consumer<String> consumer1 = System.out::println;
consumer1.accept("大西瓜");
}
执行结果如下
Supplier
中的 T get()
Object
中的 T getClass()
@Test
public void test3(){
//情景一:对象 :: 实例方法
//Supplier 中的 T get()
//Object 中的 T getClass()
Supplier supplier = () -> "小草莓".getClass();
System.out.println(supplier.get());
System.out.println("*************");
Supplier supplier1 = "小草莓" :: getClass;
System.out.println(supplier1.get());
}
执行结果如下
3.2、类 :: 静态方法
Comparator
中的 int compare(T t1, T t2)
Integer
中的 int compare(T t1, T t2)
@Test
public void test4(){
//情况二:类 :: 静态方法
//Comparator 中的 int compare(T t1, T t2)
//Integer 中的 int compare(T t1, T t2)
Comparator<Integer> comparator = (o1, o2) -> Integer.compare(o1, o2);
System.out.println(comparator.compare(11,21));
System.out.println("*************");
Comparator<Integer> comparator1 = Integer::compare;
System.out.println(comparator1.compare(99,11));
}
执行结果如下
Function
中的 R apply(T t)
Math
中的 Long round(Double d) 四舍五入的方法
@Test
public void test4(){
//情况二:类 :: 静态方法
//Function 中的 R apply(T t)
//Math 中的 Long round(Double d)
// Function function = new Function() {
// @Override
// public Long apply(Double o) {
// return Math.round(o);
// }
// };
Function<Double,Long> function = o -> Math.round(o);
System.out.println(function.apply(11.8));
System.out.println("*************");
Function<Double,Long> function1 = Math::round;
System.out.println(function1.apply(99.1));
}
执行结果如下
3.3、类 :: 实例方法
Comparator
中的 int compare(T t1, T t2)
String
中的 int t1.compareTo(t2)
注意,以下中第一个参数是作为方法的调用者
@Test
public void test4(){
//情景三:类 :: 实例方法
//Comparator 中的 int compare(T t1, T t2)
//String 中的 int t1.compareTo(t2)
Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);
System.out.println(comparator.compare("AA","BB"));
System.out.println("*************");
Comparator<String> comparator1 = String::compareTo;
System.out.println(comparator1.compare("AA","BB"));
}
执行结果如下
BiPredicate
中的 boolean test(T t, U u)
String
中的 boolean t1.equals(t2)
@Test
public void test5(){
//BiPredicate 中的 boolean test(T t, U u)
//String 中的 boolean t1.equals(t2)
// BiPredicate<String, String> biPredicate = new BiPredicate() {
// @Override
// public boolean test(String s1, String s2) {
// return s1.equals(s2);
// }
// };
BiPredicate<String, String> biPredicate = (s1,s2) -> s1.equals(s2);
System.out.println(biPredicate.test("sa", "sa"));
System.out.println("*************");
BiPredicate<String, String> biPredicate1 = String::equals;
System.out.println(biPredicate1.test("aa", "ba"));
}
执行结果如下
Function
中的 R apply(T t)
Object
中的 T getClass()
@Test
public void test6(){
//Function 中的 R apply(T t)
//Object 中的 T getClass()
// Function function = new Function(){
// @Override
// public Object apply(Object o) {
// return o.getClass();
// }
// };
Function function = o -> o.getClass();
System.out.println(function.apply("aa"));
System.out.println("*************");
Function function1 = Object::getClass;
System.out.println(function1.apply("aa"));
}
执行结果如下
4、构造器引用
构造器引用,和方法引用类似
函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法的返回值类型即为构造器所属类的类型
构造器会自动根据抽象方法的形参列表来匹配对应的参数构造器
提供一个类
public class Person{
String name;
int age;
public Person(){
System.out.println("空参构造器");
}
public Person(String name){
this.name = name;
System.out.println("一个参构造器");
}
public Person(String name,int age){
this.name = name;
this.age = age;
System.out.println("两个参构造器");
}
}
构造器引用举例:
Supplier
中的 T get()
public class Test{
@Test
public void test1(){
Supplier<Person> sup = new Supplier<Person>(){
@Override
public Person get(){
return new Person();
}
}
System.out.println(sup.get());
System.out.println("**********************");
Supplier<Person> sup1 = () -> new Person();
System.out.println(sup1.get());
System.out.println("**********************");
Supplier<Person> sup2 = Person :: new;
System.out.println(sup2.get());
}
}
Function<T,R>
中的 R apply(T t)
public class Test{
@Test
public void test1(){
Function<String,Person> fuc = new Function<String,Person>(){
@Override
public Person apply(String s){
return new Person(s);
}
}
System.out.println(fuc.apply("Tom"));
System.out.println("**********************");
Function<String,Person> fuc1 = s -> new Person(s);
System.out.println(fuc1.apply("Tom"));
System.out.println("**********************");
Function<String,Person> fuc2 = Person :: new;
System.out.println(fuc2.apply("Tom"));
}
}
BiFunction<T,U,R>
中的 R apply(T t, U u)
public class Test{
@Test
public void test1(){
BiFunction<String,Integer,Person> bif = new Function<String,Integer,Person>(){
@Override
public Person apply(String s, int i){
return new Person(s,i);
}
}
System.out.println(bif.apply("Tom",1));
System.out.println("**********************");
BiFunction<String,Integer,Person> bif1 = s -> new Person(s,i);
System.out.println(bif1.apply("Tom",1));
System.out.println("**********************");
BiFunction<String,Integer,Person> bif2 = Person :: new;
System.out.println(bif2.apply("Tom",1));
}
}
4.1、数组引用
有了构造器引用的例子,就可以写出数组的引用,因为创建数组也是在创建对象
可以把数组看做是一个特殊的类,则写法与构造器引用一致
Function<T,R>
中的 R apply(T t)
public class Test{
@Test
public void test1(){
Function<Integer,String[]> fuc = new Function<Integer,String[]>(){
@Override
public String[]apply(int length){
return new String[length];
}
}
String[] arr = fuc.apply(5);
System.out.println(Arrays.toString(arr));
System.out.println("**********************");
Function<Integer,String[]> fuc1 = length -> new String[length];
String[] arr1 = fuc1.apply(3);
System.out.println(Arrays.toString(arr1));
System.out.println("**********************");
Function<Integer,String[]> fuc2 = String[] :: new;
String[] arr2 = fuc2.apply(1);
System.out.println(Arrays.toString(arr2));
}
}
5、Stream API
5.1、Stream API说明
Java 8 的两大改变: Lambda表达式、Stream API
使用Stream API
对集合数据进行操作,就类似于使用 SQL 执行的数据库查询
为什么要使用
Stream API
?在实际开发中,数据源大多数来自 Mysql、Oracle等,但有的数据源来自 MongDB、Radis等,这些NoSQL 的数据就需要Java层面去处理
Stream
和 Collection 集合的区别?
Collection 是一种静态的内存数据结构,而 Stream 是有关计算的
Collection 主要是面向内存的,存储在内存中;而 Stream 主要是面向 CPU 的,通过 CPU 实现计算
Stream
到底是什么?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
“ 集合讲的是数据,Stream 讲的是计算 ”
注意:
Stream
自己不会存储元素Stream
不会改变源对象,相反,他们会返回一个持有结果的新Stream
(相当于是有可变性)Stream
操作是延迟执行的,这意味着他们会等到需要结果的时候才执行(即有结束语句才执行,没有终止语句就不执行)- 一个中间操作链,对数据源的数据进行处理
- 一旦执行终止操作,就执行中间操作链,并产生结果。注意之后不会再被使用
Stream
操作的三个步骤:
- 创建 Stream
一个数据源(集合、数组等),获取一个流 - 中间操作(操作链)
一个中间操作链,对数据源的数据进行处理 - 终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。注意之后不会再被使用(即若想再次执行,就要从头创建)
5.2、Stream的实例化
5.2.1、通过集合
创建 Stream
方式一:通过集合
注意:
是通过集合来调用集合 Collection
中定义的实例方法,需要一个集合对象来调用方法
default Stream<T> stream()
:返回一个顺序流
default Stream<T> parallelStream()
:返回一个并行流
void forEach(Consumer<? super T> action)
:Stream 接口中的一个抽象方法
<? super T>
指 T 及其父类
@Test
public void test1() {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1,"草莓",12));
employees.add(new Employee(2,"西瓜",88));
employees.add(new Employee(3,"哈密瓜",20));
employees.add(new Employee(4,"樱桃",45));
//default Stream<T> stream():返回一个顺序流
Stream<Employee> stream = employees.stream();
//default Stream<T> parallelStream():返回一个并行流
Stream<Employee> employeeStream = employees.parallelStream();
}
5.2.2、通过数组
创建 Stream
方式二:通过数组
Java8 中的 Arrays
的静态方法stream()
可以获取数组流
static <T>Stream<T> stream(T[] array)
:返回一个流
<T>Stream
是指返回的数据类型的类名,类名是随着传入参数的类型变化而变化,是重载形式:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
@Test
public void test2(){
//基本数据类型的数组
int[] ints = {1, 2, 3, 4, 5};
//static <T>Stream<T> stream(T[] array):返回一个流
IntStream stream = Arrays.stream(ints);
//自定义类类型的数组
Employee e1 = new Employee(1, "草莓", 12);
Employee e2 = new Employee(1, "草莓", 12);
Employee e3 = new Employee(1, "草莓", 12);
Employee[] employees = new Employee[]{e1,e2,e3};
Stream<Employee> stream1 = Arrays.stream(employees);
}
5.2.3、通过Stream的of()
创建 Stream
方式三:通过Stream的of()
调用Stream
类的静态方法of()
,通过显示值创建一个流,可以接收任意数量的参数
Stream<T> of(T... values) {return Arrays.stream(values);}
@Test
public void test3(){
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
}
5.2.4、通过Stream的iterate()、generate()
创建 Stream
方式四:创建无限流
调用Stream
类的静态方法iterate()
、generate()
1、 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
迭代
返回通过将函数 f 迭代应用到初始元素seed产生的无限顺序有序Stream
Stream的第一个元素(位置0 )将是提供的seed 。 对于n > 0 ,位置n处的元素将是将函数f应用于位置n - 1处的元素的结果。
是一个无限循环的方式
为了更好的体验,来写一个完整的 Stream 执行操作
Stream<T> limit(long maxSize);
Stream接口中的一个抽象方法,返回由该流的元素组成的流,其长度被截断为不超过maxSize
这是一个短路状态中间操作
@Test
public void test4(){
//输出1-10
Stream<Integer> iterate = Stream.iterate(1, new UnaryOperator<Integer>() {
@Override
public Integer apply(Integer integer) {
return integer + 1;
}
});
iterate.limit(10).forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
}
//以上是使用匿名内部类
//以下使用方法引用
@Test
public void test4(){
//输出1-10
Stream.iterate(1, integer -> integer + 1).limit(10).forEach(System.out :: println);
}
执行结果如下
2、 public static<T> Stream<T> generate(Supplier<T> s)
生成
返回一个无限连续的无序流,其中每个元素都由提供的Supplier生成。 这适用于生成恒定流、随机元素流等
为了更好的体验,来写一个完整的 Stream 执行操作
@Test
public void test4(){
//生成10个随机数
Stream<Double> generate = Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
});
generate.limit(10).forEach(new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println(aDouble);
}
});
}
//以上是使用匿名内部类
//以下使用方法引用
@Test
public void test4(){
//生成10个随机数
Stream.generate(Math :: random).limit(10).forEach(System.out :: println);
}
执行结果如下
5.3、Stream的中间操作
多个中间操作可以连接起来,形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!
而是在终止操作时一次性全部处理,称为 “ 惰性求值 ”
5.3.1、筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的hashCode()和equals()去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过指定数量 |
skip(long l) | 跳过元素,返回一个去掉了前n个元素的流,若流中个数不足,则返回一个空流,与limit(n)互补 |
filter(Predicate p)
:接收Lambda,从流中排除某些元素
Predicate<T>
:断定型接口boolean test(T t)
@Test
public void test5(){
//default Stream<T> stream():返回一个顺序流
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1,"草莓",12));
employees.add(new Employee(2,"西瓜",88));
employees.add(new Employee(3,"哈密瓜",20));
employees.add(new Employee(4,"樱桃",45));
Stream<Employee> stream = employees.stream();
//filter(Predicate p):接收Lambda,从流中排除某些元素
//练习:筛选年龄50以下的员工信息
// stream.filter(new Predicate<Employee>() {
// @Override
// public boolean test(Employee employee) {
// return employee.getAge()<50;
// }
// }).forEach(System.out :: println);
stream.filter(employee -> employee.getAge()<50).forEach(System.out :: println);
}
执行结果如下
distinct()
:筛选,通过流所生成元素的hashCode()和equals()去除重复元素
@Test
public void test6() {
//default Stream<T> stream():返回一个顺序流
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "草莓", 12));
employees.add(new Employee(2, "西瓜", 88));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(3, "哈密瓜", 20));
Stream<Employee> stream = employees.stream();
//distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素
// Stream<Employee> distinct = stream.distinct();
// distinct.forEach(new Consumer<Employee>() {
// @Override
// public void accept(Employee employee) {
// System.out.println(employee);
// }
// });
stream.distinct().forEach(System.out :: println);
}
执行结果如下
limit(long maxSize)
:截断流,使其元素不超过指定数量
@Test
public void test7() {
//default Stream<T> stream():返回一个顺序流
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "草莓", 12));
employees.add(new Employee(2, "西瓜", 88));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(4, "樱桃", 45));
Stream<Employee> stream = employees.stream();
//limit(long maxSize):截断流,使其元素不超过指定数量
stream.limit(2).forEach(System.out :: println);
}
执行结果如下
skip(long l)
:跳过元素,返回一个去掉了前n个元素的流,若流中个数不足,则返回一个空流,与limit(n)互补
@Test
public void test8() {
//default Stream<T> stream():返回一个顺序流
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "草莓", 12));
employees.add(new Employee(2, "西瓜", 88));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(4, "樱桃", 45));
Stream<Employee> stream = employees.stream();
//skip(long l):跳过元素,返回一个去掉了前n个元素的流,若流中个数不足,则返回一个空流,与limit(n)互补
stream.skip(2).forEach(System.out :: println);
}
执行结果如下
5.3.2、映射
map(Function f)
:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
Function<T,R>
:函数型接口,R apply(T t)
@Test
public void test9() {
//练习:将数组中的小写字母转换为大写字母
List<String> strings = Arrays.asList("aa", "bb", "cc", "dd");
Stream<String> stream = strings.stream();
//map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
// stream.map(new Function<String, Object>() {
// @Override
// public Object apply(String s) {
// return s.toUpperCase();
// }
// }).forEach(System.out :: println);
stream.map(s -> s.toUpperCase()).forEach(System.out :: println);
//练习:获取员工名字长度大于等于3的员工的 “ 姓名 ”
//default Stream<T> stream():返回一个顺序流
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "草莓", 12));
employees.add(new Employee(2, "西瓜", 88));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(4, "樱桃", 45));
Stream<Employee> stream1 = employees.stream();
//先筛选,再输出姓名
stream1.filter(employee -> employee.getName().length() >= 3).forEach(employee -> System.out.println(employee.getName()));
//先换成姓名,再筛选(注意,Stream只可以被用一次)
//这里要重新创建,否则编译不报错,运行报错:java.lang.IllegalStateException: stream has already been operated upon or closed
Stream<Employee> stream2 = employees.stream();
stream2.map(Employee::getName).filter(name -> name.length() >= 3).forEach(System.out :: println);
}
执行结果如下
flatMap(Function f)
:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
注意:
在List当中,有 add() 方法和 addAll() 方法,
public void test1(){
List list1 = new ArrayList();
List list2 = new ArrayList();
List a = new ArrayList(1, 2, 3);
List b = new ArrayList(4, 5, 6);
//add() 方法,输出的是集合作为元素的嵌套集合
list1.add(a);
list1.add(b);
System.out.println(list1); // [ [1,2,3] , [4,5,6] ]
//addAll() 方法,输出的是集合中元素展开,融合成一个新的集合,非嵌套
list2.addAll(a);
list2.addAll(b);
System.out.println(list2); // [1, 2, 3, 4, 5, 6]
}
flatMap(Function f)
:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
map(Function f)
与 flatMap(Function f)
的区别,就像上述的 List 中的 add() 方法和 addAll() 方法,下面代码体会此种说法
public StreamAPITest1{
//定义一个方法,该方法可以将传入的 String 拆开,以每个字符作为元素,形成一个新的流
public static Stream<character> fromStringToStream(String s){
List list = new ArrayList();
for(character c : s){
list.add(c);
}
return list.stream();
}
public void test2(){
//练习:将集合中的字符串,字符拆开转换成Stream流
//第一种情况,使用map(Function f):此时就相当于是 add() 方法,当传入的元素的流的形式时候,输出的结果也是嵌套的流的形式
//上面的例子中 Stream<String> s = stream.map(s -> s.toUpperCase());
List list = new ArrayList("aa", "bb", "cc", "dd");
// Stream<Stream<character>> stream = list.stream().map(new Function<String, Stream<character>>() {
// @Override
// public Object apply(String s) {
// return StreamAPITest1.fromStringToStream(s);
// }
// });
Stream<Stream<character>> stream = list.stream().map(StreamAPITest1 :: fromStringToStream);
//所以现在进行输出的时候,也需要嵌套来输出
stream.forEach(() -> System.out.println(forEach(System.out :: println)));
//第一种情况,使用flatMap(Function f):此时就相当于是 addAll() 方法,当传入的元素的流的形式时候,将流中的每个值都换成另一个流,然后把所有流连接成一个流
Stream<character> stream = list.stream().flatMap(StreamAPITest1 :: fromStringToStream);
stream.forEach(System.out :: println);
}
}
5.3.3、排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按照自然顺序排序 |
sorted(Comparator com) | 产生一个新流,其中按照比较器顺序排序 |
sorted()
:产生一个新流,其中按照自然顺序排序
@Test
public void test10() {
//sorted():产生一个新流,其中按照自然顺序排序
List<String> strings = Arrays.asList("ff", "aa", "ww", "cc");
Stream<String> stream = strings.stream();
stream.sorted().forEach(System.out :: println);
}
执行结果如下
sorted(Comparator com)
:产生一个新流,其中按照比较器顺序排序
@Test
public void test10() {
//sorted(Comparator com):产生一个新流,其中按照比较器顺序排序
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "草莓", 12));
employees.add(new Employee(2, "西瓜", 88));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(4, "樱桃", 45));
Stream<Employee> stream = employees.stream();
// sorted((o1, o2) -> o1.getName().compareTo(o2.getName()))
stream.sorted(Comparator.comparing(Employee::getName)).forEach(System.out :: println);
}
执行结果如下
5.4、Stream的终止操作
5.4.1、匹配与查找
终端操作会从留的流水线生成结果,其结果可以是任何不是流的值,例如: List、Integer,甚至是void
流进行了终止操作后,不能再次使用
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否不匹配所有元素 |
findFirst() | 返回第一个元素(并行流和顺序流结果有可能不同) |
findAny() | 返回当前流中的任意元素 |
Predicate<T>
:断定型接口boolean test(T t)
@Test
public void test1() {
//default Stream<T> stream():返回一个顺序流
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "草莓", 12));
employees.add(new Employee(2, "西瓜", 88));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(4, "樱桃", 45));
//allMatch(Predicate p):检查是否匹配所有元素
//练习:是否所有员工年龄都大于30岁
// Stream<Employee> stream = employees.stream();
// boolean b = stream.allMatch(new Predicate<Employee>() {
// @Override
// public boolean test(Employee employee) {
// return employee.getAge() > 30;
// }
// });
Stream<Employee> stream1 = employees.stream();
boolean b1 = stream1.allMatch(employee ->employee.getAge() > 30);
System.out.println(b1); //false
//anyMatch(Predicate p):检查是否至少匹配一个元素
//练习:是否存在员工年龄大于30岁
Stream<Employee> stream2 = employees.stream();
boolean b2 = stream2.anyMatch(employee ->employee.getAge() > 30);
System.out.println(b2); //true
//noneMatch(Predicate p):检查是否不匹配所有元素
//练习:是否所有员工姓名都不小以 “ 阿 ” 字开头
Stream<Employee> stream3 = employees.stream();
boolean b3 = stream3.noneMatch(employee ->employee.getName().startsWith("阿"));
System.out.println(b3); //true
//findFirst():返回第一个元素(并行流和顺序流结果有可能不同)
//顺序流
Stream<Employee> stream4 = employees.stream();
Optional<Employee> first = stream4.findFirst();
System.out.println(first); //Optional[Employee{id=1, name='草莓', age=12}]
//并行流
Stream<Employee> stream = employees.parallelStream();
Optional<Employee> first1 = stream.findFirst();
System.out.println(first1); //Optional[Employee{id=1, name='草莓', age=12}]
//findAny():返回当前流中的任意元素
Stream<Employee> stream5 = employees.stream();
Optional<Employee> any = stream5.findAny();
System.out.println(any); //Optional[Employee{id=1, name='草莓', age=12}]
}
执行结果如下
方法 | 描述 |
---|---|
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代 |
使用Collection接口需要用户去做迭代,称为外部迭代,相反,StreamAPI使用内部迭代,它帮你把迭代做了
@Test
public void test2(){
//default Stream<T> stream():返回一个顺序流
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "草莓", 12));
employees.add(new Employee(2, "西瓜", 88));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(4, "樱桃", 45));
//count():返回流中元素总数
//练习:员工年龄大于30岁的员工个数
Stream<Employee> stream1 = employees.stream();
System.out.println(stream1.filter(employee -> employee.getAge() > 30).count()); //2
//max(Comparator c):返回流中最大值
//练习:返回最大的年龄
Stream<Employee> stream2 = employees.stream();
//直接拿着Employee对象进行排序
// System.out.println(stream2.max(Comparator.comparingInt(Employee::getAge))); //Optional[Employee{id=2, name='西瓜', age=88}]
//先筛选出年龄,再进行排序。此时输出的是Integer对象,就不是Employee对象
System.out.println(stream2.map(Employee::getAge).max(Integer::compareTo)); //Optional[88]
//min(Comparator c):返回流中最小值
//练习:返回最小的年龄
Stream<Employee> stream3 = employees.stream();
System.out.println(stream3.min((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()))); //Optional[Employee{id=1, name='草莓', age=12}]
//forEach(Consumer c):内部迭代
Stream<Employee> stream4 = employees.stream();
//流内的迭代
stream4.forEach(System.out :: println);
//集合中的一个方法
employees.forEach(System.out :: println);
}
执行结果如下
5.4.2、归约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值,返回T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值,返回Optional<T> |
map 和 reduce 通常会结合起来,称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名
对一串数字求和可以表示为:Integer sum = integers.reduce(0, (a, b) -> a+b);
或者:Integer sum = integers.reduce(0, Integer::sum);
@Test
public void test3(){
//reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值,返回T
//BinaryOperator<T,T,T>:包含方法 T apply(T t1, T t2)
//练习1:求和 1-10
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer reduce = integers.stream().reduce(10, (i1, i2)-> i1+i2);
System.out.println(reduce); //65
//reduce(BinaryOperator b):可以将流中元素反复结合起来,得到一个值,返回Optional<T>
//练习2:计算所有员工 年龄总和
//default Stream<T> stream():返回一个顺序流
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "草莓", 12));
employees.add(new Employee(2, "西瓜", 88));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(4, "樱桃", 45));
Stream<Employee> stream = employees.stream();
Optional<Integer> reduce1 = stream.map(Employee::getAge).reduce(Integer::sum);
System.out.println(reduce1); //Optional[165]
}
执行结果如下
5.4.3、收集
收集就是将数据装进一个容器里
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式,接受一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector接口中方法的实现决定了如何对流执行收寄的操作(如收集到List、Set、Map)
另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表
@Test
public void test4(){
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "草莓", 12));
employees.add(new Employee(2, "西瓜", 88));
employees.add(new Employee(3, "哈密瓜", 20));
employees.add(new Employee(4, "樱桃", 45));
Stream<Employee> stream = employees.stream();
//collect(Collector c):将流转换为其他形式,接受一个Collector接口的实现,用于给Stream中元素做汇总的方法
//练习1:查找年龄大于30的关公,结果返回一个 List或者Set
List<Employee> collect = stream.filter(employee -> employee.getAge() > 30).collect(Collectors.toList());
//这里使用的是 Iterable 中的forEach()方法,Collection<E> extends Iterable<E>
collect.forEach(System.out :: println); //[Employee{id=2, name='西瓜', age=88}, Employee{id=4, name='樱桃', age=45}]
}
执行结果如下
6、Optional 类
6.1、Optional 类介绍
Optional<T>
类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在,或者仅仅保存 null ,表示这个值不存在,原来用 null 表示一个只不存在,现在 Optional 可以更好的表达这个概念,并且可以避免空指针异常
这是一个可以为 null 的容器对象,如果值存在,则 isPresent()
方法会返回 true,调用get()
方法会返回该对象
- 创建 Optional 类对象的方法
Optional.of(T t)
:创建一个 Optional 实例,t 必须为非空Optional.empty()
:创建一个空的 Optional 实例Optional.ofNullable(T t)
:t 可以为 null
- 判断 Optional 容器中是否包含对象
boolean isPresent()
:判断是否包含对象void ifPresent(Consumer<? super T> con)
:如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它
- 获取 Optional 容器中的对象
T get()
: 如果调用对象包含值,返回该值,否则抛异常(一般和of(T t)
配合使用)T orElse(T other)
:如果有值则将其返回,否则返回指定的 other 对象T orElseGet(Supllier<? extends T> other)
:如果有值则将其返回,否则返回由 Supllier 接口实现提供的对象T orElseThrow(Supllier<? extends X> exceptionSupllier)
:如果有值则将其返回,否则抛出由 Supllier 接口实现提供的异常
Optional.of(T t)
:创建一个 Optional 实例,t 必须为非空
一般和 T get()
配合使用
Optional.empty()
:创建一个空的 Optional 实例
Optional.ofNullable(T t)
:t 可以为 null
public class Test{
@Test
public void test1(){
//Optional.of(T t):创建一个 Optional 实例,t 必须为非空
Girl girl1 = new Girl();
//这种情况会报空指针异常,t 必须为非空
//java.lang.NullPointerException
//girl1 = null;
Optional<Girl> optionalgirl1 = Optional.of(girl1);
System.out.println(optionalgirl1); //Optional[Girl{name='null'}]
}
@Test
public void test2(){
//Optional.ofNullable(T t):t 可以为 null
//当存放的不是null的时候,输出结果与Optional.of(T t)一致
Girl girl1 = new Girl();
Optional<Girl> optionalgirl1 = Optional.ofNullable(girl1);
System.out.println(optionalgirl1); //Optional[Girl{name='null'}]
//当存放的为 null 的时候,输出结果 Optional.empty
Girl girl2 = new Girl();
girl2 = null;
Optional<Girl> optionalgirl2 = Optional.ofNullable(girl2);
System.out.println(optionalgirl2); //Optional.empty
}
}
6.2、Optional 使用举例
以下代码体会 Optional 是可以作为 null 的容器对象
先创建Girl类和Boy类
//Girl类
public class Girl {
private String name;
public Girl() {}
public Girl(String name) { this.name = name; }
@Override
public String toString() {
return "Girl{" +
"name='" + name + '\'' +
'}';
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
//Boy类
public class Boy {
private Girl girl;
public Boy(Girl girl) { this.girl = girl; }
public Boy() { }
@Override
public String toString() {
return "Boy{" +
"girl=" + girl +
'}';
}
public Girl getGirl() { return girl; }
public void setGirl(Girl girl) { this.girl = girl; }
}
在不使用 Optional 类的情况下,
创建测试testBoy类
public class Test{
//创建一个方法,返回Boy中的Girl名字
/*
如果直接这样写,调用时候会报空指针异常
public String getGirlName(Boy boy){
return boy.getGirl().getName();
}
*/
//优化之后的getGirlName()方法
public String getGirlName(Boy boy){
if(boy != null)
Girl girl = boy.getGirl();
if(girl != null)
return girl.getName();
return null
}
@Test
public void testBoy(){
Boy boy = new Boy();
String girlname = getGirlName(boy);
System.out.println(girlname);
}
}
使用 Optional 类的情况下
T orElse(T other)
:如果有值则将其返回,否则返回指定的 other 对象
public class Test{
//创建一个方法,返回Boy中的Girl名字
public String getGirlName(Boy boy){
//第一层:除去 Boy 为空的情况
//ofNullable(T t):t 可以为 null
Optional<Boy> optionalboy = Optional.ofNullable(boy);
//orElse(T other):如果有当前的Optional内部封装的t是非空的,则返回内部的t
//如果内部的t是空的,则返回orElse(T other)方法中的other
Boy boy1 = optionalboy.orElse(new Boy(new Girl("小草莓")));
//此时 Boy 必定不为空,则可以获取里面的Girl对象
Girl girl = boy1.getGirl();
//第二层:除去 Girl 为空的情况
//ofNullable(T t):t 可以为 null
Optional<Boy> optionalboy = Optional.ofNullable(girl);
Girl girl1 = optionalboy.orElse(new Girl("小西瓜"));
//此时 Girl 必定不为空,则可以获取里面的姓名name属性
String name = girl1.getName();
return name;
}
@Test
public void test1(){
Boy boy = new Boy();
String girlname = getGirlName(boy);
System.out.println(girlname); //小西瓜
Boy boy1 = null;
String girlname1 = getGirlName(boy1);
System.out.println(girlname1); //小草莓
Boy boy2 = new Boy(new Girl("小樱桃"));
String girlname2 = getGirlName(boy2);
System.out.println(girlname2); //小樱桃
}
}
7、小结
Functional
Consumer<T>
:消费型接口,void accept(T t)Supplier<T>
:供给型接口,T get()Function<T,R>
:函数型接口,R apply(T t)Predicate<T>
:断定型接口boolean test(T t)
method reference
- 对象 :: 实例方法
- 类 :: 静态方法
- 类 :: 实例方法
Stream
- Stream 关注的是对数据的运算,与CPU打交道
- Stream
- 自己不会存储元素
- 不会改变源对象。相反,会返回一个持有结果的新Stream
- 操作是延迟执行的。意味着这会等到需要结果的时候才会执行
- Stream的执行流程
- Stream的实例化
- 一系列中间操作(过滤、映射、排序)
- 终止操作
- 说明
- 一个中间操作链,对源数据的数据进行处理
- 一旦执行终止操作,就执行中间操作链,并产生结果,之后,不会再被使用
- Stream的实例化
-
通过集合——
Collection
中定义的实例方法
default Stream<T> stream()
:返回一个顺序流
default Stream<T> parallelStream()
:返回一个并行流 -
通过数组——
Arrays
的静态方法stream()
static <T>Stream<T> stream(T[] array)
:返回一个流 -
调用
Stream
类的静态方法of()
-
调用
Stream
类的静态方法iterate()
、generate()
-