java8新特性
Lambda表达式
-
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。
-
Lambda表达式是一种语法规则,接口中的抽象方法的形参列表(左)-> 重写的抽象方法的方法体(右)=Lambda表达式
-
类型 自定义参数=(目标/方法体类型)->(函数体)。
-
下面举一个很简单的例子。
@Test public void test1(){ Runnable runnable = new Runnable(){ @Override public void run() { System.out.println("这是一个线程!"); } }; runnable.run(); }
-
以上是Runnable接口,可见,run()便是抽象方法,那么可以根据上面的公式,写出Lambda表达式。
Runnable runnable1=()-> System.out.println("Lambda表达式线程!");
- 可见我们现在是可以省略new,Runnable(),和花括号的。
-
()便是接口中抽象方法的形参列表,->为指向符,最后便是方法体。
-
-
以上是一个无参的例子,来一个有参的例子。
Comparator<Integer> objectComparator = new Comparator<Integer>(){ @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }; System.out.println(objectComparator.compare(1, 2)); }
-
可见,这里的形参列表为o1,o2,方法体为return o1.compareTo(o2),那么可以根据公式等到Lambda表达式。
Comparator<Integer> r=(o1,o2)->o1.compareTo(o2); System.out.println(r.compare(1,2));
- 注意:当我们只有一个形参的时候是可以省略参数左右的括号的。
-
-
然后,我们引入一个思考,既然Lambda表达式引入是为了方便我们写代码,那是否更加简洁呢?这里自然是可以的。
-
Comparator<Integer> a=Integer::compare; System.out.println(a.compare(1, 2));
- 左边返回一个类型,右边是方法体,进行什么类型的比较。
-
-
但我们仔细观察以上的代码就会发现,我没有提到当方法体里有多条语句的时候。
-
我们可以将以上方法更改,让其更加合理介绍Lanbda表达式。
Comparator<Integer> objectComparator = new Comparator<Integer>(){ @Override public int compare(Integer o1, Integer o2) { System.out.println("多一条语句!"); return o1.compareTo(o2); } };
-
我们可以将这些代码用Lambda表达式
Comparator<Integer> a=Integer::compare; System.out.println(a.compare(1, 2)); //-1
会发现它只返回了-1,也就是说,当我们里面有其他语句的时候,若是不显式表达我们另外的代码,就会默认是系统的。
所以,面对这种情况,我们Lambda只能写成下列各式。
Comparator<Integer> r1=(o1,o2)->{ System.out.println("多一条语句!"); return o1.compareTo(o2); }; System.out.println(r1.compare(1,2));
我们会发现,不仅我们的return不可以省略,还必须带上我们的{}。
-
-
归结以上练习,我们可以明白一个道理,在Lambda表达式中,似乎在告诉我们一个道理:能省则省。
函数式(Functional))接口
-
只包含一个抽象方法的接口,称为函数式接口,例如上面的Runnable接口。
-
我们可以使用使用 @FunctionalInterface 注解,自定义函数式接口,但是不写注解也可以,写了注解方便我们检查是否是函数式接口。
-
在Java8里提供了丰富的函数式接口
-
Consumer消费型接口 ,对类型为T的对象应用操作,包含方法:void accept(T t)。
-
Supplier供给型接口 ,返回类型为T的对象,包含方法:T get()。
-
Function<T, R>函数型接口,对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)。
-
Predicate断定型接口,确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法:boolean test(T t)。
-
下面提供额外的,需要自取。
https://i.loli.net/2021/06/20/JLhBtUWRlHsmk5N.png
-
-
Lambda表达式正是实现了这些函数式接口。所以Lambda表达式也是对象。
-
Consumer消费型接口
@Test public void test1(){ // 调用下面方法,我们肯定要在Consumer里重写accept方法 TestConsumer(1, new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(integer); } }); } public void TestConsumer(int a, Consumer<Integer> consume){ consume.accept(a); }
@Test public void test1(){ /*下面是Lambda表达式写法*/ TestConsumer(1,integer -> System.out.println(integer)); } public void TestConsumer(int a, Consumer<Integer> consume){ consume.accept(a); }
- 可以看见,实现同样的功能,用Lambda表达式精简很多,但增强了代码的阅读难度。
-
Supplier供给型接口
/*Supplier<T>供给型接口*/ @Test public void test2(){ String s="a"; getSupplier(s, new Supplier<String>() { @Override public String get() { return s; } }); } public void getSupplier(String a, Supplier<String> s){ System.out.println(s.get());//a }
/*Supplier<T>供给型接口*/ @Test public void test2(){ String s="a"; getSupplier(s,()->s); } public void getSupplier(String a, Supplier<String> s){ System.out.println(s.get());//a }
-
Function<T, R>函数型接口
/*Function<T, R>函数型接口*/ @Test public void test3(){ getFunction(new Function<Integer, String>() { @Override public String apply(Integer integer) { System.out.println("这是一个字符串!"); return integer+""; } }); } public void getFunction(Function<Integer,String> s){ // 对Integer操纵 System.out.println(s.apply(123)); // 这是一个字符串! // 123 // 这是一个字符串! // 123 }
/*Function<T, R>函数型接口*/ @Test public void test3(){ getFunction(integer -> { System.out.println("这是一个字符串!"); return integer+""; } ); } public void getFunction(Function<Integer,String> s){ // 对Integer操纵 System.out.println(s.apply(123)); // 这是一个字符串! // 123 // 这是一个字符串! // 123 }
-
Predicate断定型接口
/*Predicate<T>断定型接口*/ @Test public void test4(){ getPredicated(new Predicate<Integer>() { @Override public boolean test(Integer integer) { return integer==123; } }); getPredicated(integer -> integer==123); } public void getPredicated(Predicate<Integer> i){ if (i.test(123)){ System.out.println("这是123"); }else System.out.println("这不是123"); // 这是123 // 这是123 }
/*Predicate<T>断定型接口*/ @Test public void test4(){ getPredicated(integer -> integer==123); } public void getPredicated(Predicate<Integer> i){ if (i.test(123)){ System.out.println("这是123"); }else System.out.println("这不是123"); // 这是123 // 这是123 }
方法引用
-
在这之前,我们提到过有个Lambda表达式可以简化成
Comparator<Integer> a=Integer::compare; System.out.println(a.compare(1, 2)); //-1
此为方法引用。
-
要从上面的Lambda表达式去理解方法引用比较困难,我们可以总结出这个公式。
类型 变量 = 类 (对象):: 方法名
Comparator a=Integer::compare,我们可以理解一下这条语句,得到类型Comparator,变量为a,Integer为我们要比较的类型,方法名为compare。
a.compare(1, 2),这条语句就是我们要得到变量a,用a调用compare方法,来比较(1,2);
通俗理解为,方法引用只是一种引用,省去形参,但我们必须传入实参。
这个类型必须是方法引用以上说明的接口实现类型
-
“=”后面主要是:
对象::实例方法名(这里具体到了哪个对象的方法,自然可以,不能使用功能型接口)
类::静态方法名(这里也自然可以,因为静态方法跟着类一样加载,也不能使用功能型接口)
类::实例方法名(本质上类::实例方法是不行的,但是我们可以这样理解,这里表示一个大类下的大类方法,但是具体应用还是得应用到对象,使用功能型接口)
注意:没有对象::静态方法名
package com.hyb.LambdaTest;
import org.junit.Test;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @program: FunctionalMethodTest
* @description:
* @author: Huang Yubin
* @create: 2021-06-18 17:50
**/
public class FunctionalMethodTest {
@Test
public void test1(){
// 通过对象去调用
Person hyb = new Person("hyb",20);
Supplier<String> s=hyb::getName;
/*已经具体到哪个对象的方法了*/
System.out.println(s.get());
}
@Test
public void test2(){
// 通过类去调用静态
Supplier<Integer> i=Person::getStaticAge;
/*这里为什么可以用供给型接口?
* 因为静态方法是随着类的加载而加载的,在调用的时候已经具现出来了。*/
System.out.println(i.get());
}
@Test
public void test3(){
//通过类去调用
Person hyb = new Person("hyb", 20);
Function<Person,Integer> i=Person::getAge;
/*这里很容易被误导,用供给型接口去声明i
* 其实这是不行的,供给型是返回一个具体的值,而这个功能性接口,强调getAge这个功能,不代表哪个对象*/
System.out.println(i.apply(hyb));
}
}
- 方法引用比较抽象,要注意要获取的变量类型使用哪个接口实现。
构造器引用
- 与方法引用类似,对此还有数组引用,只要将它们看成两种特殊的类就可以了。
package com.hyb.LambdaTest;
import org.junit.Test;
import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @program: FunctionalConstructionTest
* @description:构造器引用
* @author: Huang Yubin
* @create: 2021-06-19 07:40
**/
public class FunctionalConstructionTest {
// 这里我们还是以Person类为例,并加入了一个构造器以说明情况
// public Person(double salary) {
// this.salary = salary;
// }
@Test
public void test1(){
// Supplier<T>单个参数构造器
Supplier<Person> salary=()->new Person(2000);
System.out.println(salary.get());//Person{name='null', age=0, salary=2000.0}
/*若是重写Supplier里的方法,只有一条语句,new Person,返回给salary*/
// 下面我们用构造器调用
Supplier<Person> salary1=Person::new;
System.out.println(salary1.get());
/*但你会发现这好像不可以初始化对象的构造器!*/
}
/*上面既然提到了Supplier调用构造器无法初始化的问题,我们用Function试一试*/
/*单个参数构造器*/
@Test
public void test2(){
Function<Double,Person> r=salary->new Person(salary);//对Double的salary操作,返回一个Person类型的对象
System.out.println(r.apply(2000.0));
// Person{name='null', age=0, salary=2000.0}
// 构造器引用
Function<Double,Person> r1=Person::new;
System.out.println(r1.apply(2000.0));
// Person{name='null', age=0, salary=2000.0}
/*结构供给型接口构造器引用无法初始化问题*/
}
/*当两个参数初始化时,Java里提供了一个接口BiFunction<T, U, R>,*/
@Test
public void test3(){
BiFunction<String ,Integer,Person> r=Person::new;
System.out.println(r.apply("hyb", 20));
// Person{name='hyb', age=20, salary=0.0}
/*若是想要设置多个参数*/
Person hyb = r.apply("hyb", 20);
hyb.setSalary(2000);
System.out.println(hyb);
// Person{name='hyb', age=20, salary=2000.0}
}
/*数组引用*/
@Test
public void test4(){
Supplier<String[]> r=()->new String[5];
System.out.println(Arrays.toString(r.get()));
// Supplier<String[]> New= String[]::new;//这样不行,因为供给型是返回一个具体数组
System.out.println("----------------");
Function<Integer,String[]> r1=length->new String[length];
System.out.println(Arrays.toString(r1.apply(6)));
Function<Integer,String[]> New=String[]::new;
String[] apply = New.apply(5);
System.out.println(Arrays.toString(apply));
// [null, null, null, null, null]
}
}
Stream API
-
Stream讲的是计算和CPU打交道,而集合讲究数据的存储,与内存打交道。
-
关于Stream的三个特点:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
-
Stream有三步操作流程
- 创建一个数据源,获取一个流。
- 中间操作,如映射等。
- 终止操作,一旦终止不再执行,每次执行完都会默认关闭。
创建
- 有四种方式,在这之前,我们先建立一个类
package com.hyb.StreamAPI;
/**
* @program: Student
* @description:
* @author: Huang Yubin
* @create: 2021-06-19 09:12
**/
public class Student {
private String name;
private int age;
private String id;
private String sex;
public Student() {
}
public Student(String name, int age, String id, String sex) {
this.name = name;
this.age = age;
this.id = id;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", id='" + id + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
package com.hyb.StreamAPI;
import org.junit.Test;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @program: SteamAPITest
* @description:
* @author: Huang Yubin
* @create: 2021-06-19 09:04
**/
public class SteamAPITest {
public static List<Student> getList(){
ArrayList<Student> s = new ArrayList<>();
s.add(new Student("hyb",20,"代号01","男"));
s.add(new Student("hyb1",21,"代号02","男"));
s.add(new Student("hyb2",22,"代号03","男"));
s.add(new Student("hyb3",23,"代号04","男"));
return s;
}
/*方式一,通过集合*/
@Test
public void test1(){
// default Stream<E> stream() : 返回一个顺序流
Stream<Student> studentStream1 = getList().stream();
// default Stream<E> parallelStream() : 返回一个并行流
Stream<Student> studentStream2 = getList().parallelStream();
}
/*方式二,通过数组*/
@Test
public void test2(){
// Arrays中的static <T> Stream<T> stream(T[] array): 返回一个流
int[] ints = {1, 2, 3, 4, 5, 6};
IntStream stream1 = Arrays.stream(ints);
}
/*方式三:通过Stream的of()*/
public void test3(){
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
}
/*方式四:(了解)创建无限流*/
@Test
public void test4(){
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
// final UnaryOperator<T> f返回T
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.iterate(0,t->t+1).limit(10).forEach(System.out::println);//遍历0,1,2,3,4,5,6,7,8,9
Stream.generate(Math::random).limit(10).forEach(System.out::println);//前十个随机数
}
}
中间操作
- 筛选与切片
- 映射
- 排序
- 匹配与查找
筛选与切片
- filter(Predicate p) 接收 Lambda , 从流中排除某些元素
- distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
- limit(long maxSize) 截断流,使其元素不超过给定数量
- **skip(long n)**跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
package com.hyb.StreamAPI;
import com.hyb.LambdaTest.Person;
import org.junit.Test;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.List;
/**
* @program: StreamMiddleOperation
* @description:
* @author: Huang Yubin
* @create: 2021-06-19 09:39
**/
public class StreamMiddleOperation {
public static List<Student> getList(){
ArrayList<Student> s = new ArrayList<>();
s.add(new Student("hyb",20,"代号01","男"));
s.add(new Student("hyb1",21,"代号02","男"));
s.add(new Student("hyb2",22,"代号03","男"));
s.add(new Student("hyb3",23,"代号04","男"));
return s;
}
//- **filter(Predicate p)** 接收 Lambda , 从流中排除某些元素,()里返回一个布尔值,表示是否存在某个值,便可以过滤掉某个值。
//- **distinct()** 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
//- **limit(long maxSize)** 截断流,使其元素不超过给定数量
//- **skip(long n)**跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
@Test
public void test1(){
List<Student> list = getList();
list.stream().filter(r->r.getName().equals("hyb")).forEach(System.out::println);
//返回一群对象的getName方法,判断是否存在hyb,并forEach输出
System.out.println("----------------------------------------");
list.add(new Student("hyb",20,"代号01","男"));
list.stream().distinct().forEach(System.out::println);
// Student{name='hyb', age=20, id='代号01', sex='男'}
// Student{name='hyb1', age=21, id='代号02', sex='男'}
// Student{name='hyb2', age=22, id='代号03', sex='男'}
// Student{name='hyb3', age=23, id='代号04', sex='男'}
System.out.println("----------------------------123");
list.forEach(System.out::println);
System.out.println("——————————————————————————————————————————————————");
list.stream().limit(1).forEach(System.out::println);
// Student{name='hyb', age=20, id='代号01', sex='男'}
System.out.println("____________________________________________");
list.stream().skip(2).forEach(System.out::println);
System.out.println("=====================");
list.forEach(System.out::println);
// Student{name='hyb', age=20, id='代号01', sex='男'}
// Student{name='hyb1', age=21, id='代号02', sex='男'}
// Student{name='hyb2', age=22, id='代号03', sex='男'}
// Student{name='hyb3', age=23, id='代号04', sex='男'}
// Student{name='hyb', age=20, id='代号01', sex='男'}
}
}
映射
public static List<Student> getNewList(){
ArrayList<Student> s = new ArrayList<>();
s.add(new Student("hyb",20,"代号01","男"));
s.add(new Student("hyb1",21,"代号02","男"));
s.add(new Student("hyb2",22,"代号03","男"));
s.add(new Student("hyb3",23,"代号04","男"));
return s;
}
-
**map(Function f)**接收一个函数作为参数,该函数会被应用到你要映射的元素上,并将其映射成一个新的元素。
- 映射容易搞混,里面一定要是功能函数,一般用Lambda表达式更简洁,而括号里的功能函数就决定了可以映射出的东西。
- 集合.流(流化).映射(该流要映射的东西(功能)).遍历(次数由该集合决定)
@Test public void test2(){ // 找出学生姓名长度大于3的 // 过滤(映射出所有名字())长度大于3 List<Student> newList = getNewList(); newList.stream().map(Student::getName).filter((name)->name.length()>3).forEach(System.out::println); // 集合.流.映射(所有的名字).过滤.(所有长度大于3的名字).遍历(打印所有值) // 具体细节说明,比如里面的Lambda表达式请看上一章,有详细说明 }
-
**mapToDouble(ToDoubleFunction f)**接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
-
**mapToInt(ToIntFunction f)**接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
-
**mapToLong(ToLongFunction f)**接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
-
**flatMap(Function f)**接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
-
引出问题:若有二维映射出现,如何解决?解决无非的主要问题就是,遍历问题。
@Test public void test() { // 我们先自定义了一个集合,里面放置String类型的元素 List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee"); /*那么问题来了,若是我们要将一个一个字符取出来,怎么做?*/ /*答案自然不言而喻,再次打散,但你要看该元素还能不能打散。 * 我们定义一个filterCharacter方法,去提供将《一个一个字符串》打散, * 然后转换成流,再去映射。*/ // 得到这个流后,我们采取映射,记住这里的类型是《流中流(整个strList集合是一个,里面每一个字符串转换成的流也是一个)》 Stream<Stream<Character>> stream2 = strList.stream() .map(StreamMiddleOperation::filterCharacter); // 既然是流中流,肯定要进行两遍forEach stream2.forEach((sm) -> { sm.forEach(System.out::println); }); System.out.println("---------------------------------------------"); /*那么有没有更加适合的办法去遍历这种流中流呢?提供了flatMap,其他步骤都一样,只是forEach少了一遍。*/ Stream<Character> stream3 = strList.stream() .flatMap(StreamMiddleOperation::filterCharacter); stream3.forEach(System.out::println); } public static Stream<Character> filterCharacter(String str) { // 声名一个集合去接收 List<Character> list = new ArrayList<>(); // toCharArray是str转换成字符串数组 for (Character ch : str.toCharArray()) { list.add(ch); } count++; System.out.println(count); // 返回这个集合打散成的流 return list.stream(); }
-
探讨:
List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee"); .... Stream<Character> stream3 = strList.stream() .flatMap(StreamMiddleOperation::filterCharacter); stream3.forEach(System.out::println); .... public static Stream<Character> filterCharacter(String str) { // 声名一个集合去接收 List<Character> list = new ArrayList<>(); // toCharArray是str转换成字符串数组 for (Character ch : str.toCharArray()) { list.add(ch); } System.out.println(count); // 返回这个集合打散成的流 return list.stream(); }
映射的时候是将整个strList集合里的所有字符串一起映射,还是一个一个字符串映射输出呢?
只要我们通过以下代码,便可以知道答案。
private static int count = 0;//整个类下定义 ...... Stream<Character> stream3 = strList.stream() .flatMap(StreamMiddleOperation::filterCharacter); stream3.forEach(System.out::println); if (count != 0 && count != 1) { System.out.println("该流每循环一次就被打散一次!"); } public static Stream<Character> filterCharacter(String str) { // 声名一个集合去接收 List<Character> list = new ArrayList<>(); // toCharArray是str转换成字符串数组 for (Character ch : str.toCharArray()) { list.add(ch); } count++; System.out.println(count); // 返回这个集合打散成的流 return list.stream(); }
观察count的值,我们便会知道这个二级映射是每进行完一次字符串映射,才进行下一个字符串映射的。
-
-
解决问题之后,我们再来探讨,若是二维集合里装的都是对象,该如何?下例子可看。
-
大概意思是这样:若一个集合A里装了a个对象,集合B装了b个对象。在Java中,提供了addAll()方法,可以将两个结集合结合,总数为两集合元素的个数总和,但Java中还有一个方法add(),若A.add(B)的集合总数是a+1的,也就是将集合B当成了一个元素添加到了集合A中,那么此刻又该如何映射?
小编思路知识有限,且这只是学习笔记,下面只给出探讨过程,答案是错误的。
public static List<Object> getNewList() { List<Object> s = new ArrayList<>(); s.add(new Student("hyb", 20, "代号01", "男")); s.add(new Student("hyb1", 21, "代号02", "男")); /*s.add(new Student("hyb2",22,"代号03","男")); s.add(new Student("hyb3",23,"代号04","男"));*/ return s; } @Test public void test2() { /*经过上面的例题,我们发现,map异常的强大,但你可能会想, * 如何集合是一个二维集合呢?我们该如何去多级映射呢? * 答案不言而喻,只要我们一层一层去映射,便可以得到结果。 * 但要是遍历结果的话,map只能做到一层一层遍历。 * * 那有没有一种方法可以做到直接遍历呢? * * 在Java中,提供了一个比map更强大的方法,flatMap,可以做到直接遍历*/ /*下面用一个例子来练习这个方法,为了方便演示,我们不约束集合的类型*/ List<Object> newList = getNewList(); List<Object> newList1 = getNewList(); newList1.add(new Student("hyb3", 23, "代号04", "男")); newList.add( newList1); System.out.println(newList); System.out.println(); /*经过上面的变换,我们知道,集合newList里是镶嵌有集合newList1的, * 如此,我们不难发现,我们只是单纯地将集合newList一维映射出来 * 会出现镶嵌集合的情况,如下: * Student{name='hyb', age=20, id='代号01', sex='男'} Student{name='hyb1', age=21, id='代号02', sex='男'} [Student{name='hyb', age=20, id='代号01', sex='男'}, Student{name='hyb1', age=21, id='代号02', sex='男'}, Student{name='hyb3', age=23, id='代号04', sex='男'}] * * 而我们的主要目的是要将镶嵌的集合也解脱,一行一行的输出,如下: Student{name='hyb', age=20, id='代号01', sex='男'} Student{name='hyb1', age=21, id='代号02', sex='男'} Student{name='hyb', age=20, id='代号01', sex='男'} Student{name='hyb1', age=21, id='代号02', sex='男'} Student{name='hyb3', age=23, id='代号04', sex='男'} * */ /*经过以上的问题的发现,我们可以尝试下列解决方案*/ // newList.stream().flatMap(s->newList1.stream()).forEach(System.out::println); /*上述可以自己跑跑,会出现三次重复输出,且输出的是newList1集合的元素, * 这很容易解释,三次输出是因为newList集合长度就是3,而输出newList1是因为你要映射的就是这个集合 * 这是初学者很容易走的坑,或者在flatMap的()里填入要遍历的集合本身。*/ /*那么二维映射该怎么解决呢? * 现在我们面临的主要问题是: * 1.我们将一个集合打散成流,是将集合中的元素全部打散,而这里的元素有可能本来就是集合, * 我们要将他们全部打散,还要将元素打散。 * */ Stream<Stream<Object>> stream = newList.stream().map(StreamMiddleOperation::getList); stream.forEach(s->{ s.forEach(System.out::println); }); } public static Stream<Object> getList(Object obj) { ArrayList<Object> objects = new ArrayList<>(); objects.add(obj); return Arrays.stream(objects.toArray()); }
-
排序
-
sorted() 产生一个新流,其中按自然顺序排序
-
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
package com.hyb.StreamAPI;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* @program: SortTest
* @description:
* @author: Huang Yubin
* @create: 2021-06-20 07:59
**/
public class SortTest {
public static List<Student> getNewList() {
List<Student> s = new ArrayList<>();
s.add(new Student("hyb", 20, "代号01", "男"));
s.add(new Student("hyb1", 21, "代号02", "男"));
s.add(new Student("hyb2",22,"代号03","男"));
s.add(new Student("hyb3",23,"代号04","男"));
return s;
}
// - **sorted()** 产生一个新流,其中按自然顺序排序
@Test
public void SortedTest(){
List<Student> list = getNewList();
list.stream().map(Student::getAge).sorted().forEach(System.out::println);
// 20
// 21
// 22
// 23
list.stream().map(Student::getName).sorted().forEach(System.out::println);
// hyb
// hyb1
// hyb2
// hyb3
/*注意:若涉及到对象排序,所在类应该重写应有的排序方法。*/
}
// - **sorted(Comparator com)** 产生一个新流,其中按比较器顺序排序
@Test
public void sortedComparatorTest(){
List<Student> list = getNewList();
list.stream().map(Student::getName).sorted((a,b)->a.compareTo(b)).forEach(System.out::println);//String::compareTO
}
}
终止操作
匹配与查找
-
allMatch(Predicate p) 检查是否匹配所有元素
-
anyMatch(Predicate p) 检查是否至少匹配一个元素
-
noneMatch(Predicate p) 检查是否没有匹配所有元素
-
findFirst() 返回第一个元素
-
findAny() 返回当前流中的任意元素
package com.hyb.StreamAPI;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* @program: FindTest
* @description:
* @author: Huang Yubin
* @create: 2021-06-20 08:15
**/
public class FindTest {
public static List<Student> getNewList() {
List<Student> s = new ArrayList<>();
s.add(new Student("hyb", 20, "代号01", "男"));
s.add(new Student("hyb1", 21, "代号02", "男"));
s.add(new Student("hyb2",22,"代号03","男"));
s.add(new Student("hyb3",23,"代号04","男"));
return s;
}
//- **allMatch(Predicate p)** 检查是否匹配所有元素
@Test
public void AllMatchTest(){
List<Student> list = getNewList();
boolean ishyb = list.stream().map(Student::getName).allMatch( s-> "hyb".equals("hyb"));
System.out.println(ishyb);//true
boolean ishyb1 = list.stream().map(Student::getName).allMatch( s->s.equals("hyb"));//true
boolean ishyb2 = list.stream().allMatch(r->r.getAge()>20);//true
}
//
//- **anyMatch**(**Predicate p**) 检查是否至少匹配一个元素
@Test
public void AnyMatchTest(){
List<Student> list = getNewList();
boolean ishyb = list.stream().map(Student::getName).anyMatch(s -> s.equals("hyb"));
System.out.println(ishyb);
boolean ishyb1 = list.stream().map(Student::getAge).anyMatch(s->s>20);
System.out.println(ishyb1);//true
boolean b = list.stream().anyMatch(s -> s.getAge() < 10);
System.out.println(b);//false
}
//
//- **noneMatch(Predicate p)** 检查是否没有匹配所有元素
@Test
public void noneMatchTest(){
List<Student> list = getNewList();
boolean b = list.stream().noneMatch(s -> s.getAge() > 100);
System.out.println(b);//true
}
//
//- **findFirst()** 返回第一个元素
@Test
public void findFist(){
List<Student> newList = getNewList();
Optional<Student> first = newList.stream().findFirst();//类型待会会讲到
System.out.println(first);
// Optional[Student{name='hyb', age=20, id='代号01', sex='男'}]
}
//
//- **findAny()** 返回当前流中的任意元素
@Test
public void findAnyTest(){
List<Student> list = getNewList();
Optional<Student> any = list.stream().findAny();
System.out.println(any);
// Optional[Student{name='hyb', age=20, id='代号01', sex='男'}]
}
}
-
count() 返回流中元素总数
-
max(Comparator c) 返回流中最大值
-
min(Comparator c) 返回流中最小值
-
**forEach(Consumer c)**内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
// count() 返回流中元素总数
@Test
public void countTest(){
List<Student> li = getNewList();
System.out.println(li.stream().count());//4
}
// max(Comparator c) 返回流中最大值
@Test
public void maxTest(){
List<Student> list = getNewList();
Optional<Integer> max = list.stream().map(Student::getAge).max(Integer::compare);//23
System.out.println(max);
}
// min(Comparator c) 返回流中最小值
// forEach(Consumer c)
// 内部迭代(使用 Collection 接口需要用户去做迭代,
// 称为外部迭代。相反,Stream API 使用内部迭
// 代——它帮你把迭代做了)
归约
-
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
-
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional
// - **reduce(T iden, BinaryOperator b)** 可以将流中元素反复结合起来,得到一个值。返回 T
@Test
public void ReduceTest(){
List<Student> list = getNewList();
System.out.println(list.stream().map(Student::getAge).reduce(1, Integer::sum));//86
System.out.println();
list.stream().filter(s->s.getAge()<22).forEach(System.out::println);
System.out.println(list.stream().map(Student::getAge).filter(s -> s < 22).reduce(0, Integer::sum));
}
//
// - **reduce(BinaryOperator b)** 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
@Test
public void ReduceTest1(){
List<Student> list = getNewList();
Optional<Integer> reduce = list.stream().map(Student::getAge).reduce((a, b) -> a + b);
System.out.println(reduce);//86
}
收集
- **collect(Collector c)**将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
@Test
public void collectTest(){
List<Student> list = getNewList();
List<Integer> collectList = list.stream().map(Student::getAge).collect(Collectors.toList());
System.out.println(collectList);
// [20, 21, 22, 23]
}
Optional类
-
到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。
以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,
Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代
码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
-
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表
这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不
存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
-
Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在
则isPresent()方法会返回true,调用get()方法会返回该对象。
-
Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
-
创建Optional类对象的方法:
- Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
- Optional.empty() : 创建一个空的 Optional 实例
- Optional.ofNullable(T t):t可以为null
-
判断Optional容器中是否包含对象:
- boolean isPresent() : 判断是否包含对象
- void ifPresent(Consumer<? super T> consumer) **:**如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
-
获取Optional容器的对象:
-
T get(): 如果调用对象包含值,返回该值,否则抛异常
-
**T orElse(T other):**如果有值则将其返回,否则返回指定的other对象。
-
T orElseGet(Supplier<? extends T> other) **:**如果有值则将其返回,否则返回由
Supplier接口实现提供的对象。
-
T orElseThrow(Supplier<? extends X> exceptionSupplier) **:**如果有值则将其返
回,否则抛出由Supplier接口实现提供的异常。
-
@Test
public void test1() {
Boy b = new Boy("张三");
Optional<Girl> opt = Optional.ofNullable(b.getGrilFriend());
// 如果女朋友存在就打印女朋友的信息
opt.ifPresent(System.out::println);
}
@Test
public void test2() {
Boy b = new Boy("张三");
Optional<Girl> opt = Optional.ofNullable(b.getGrilFriend());
// 如果有女朋友就返回他的女朋友,否则只能欣赏“嫦娥”了
Girl girl = opt.orElse(new Girl("嫦娥"));
System.out.println("他的女朋友是:" + girl.getName());
}
@Test
public void test3(){
Optional<Employee> opt = Optional.of(new Employee("张三", 8888));
//判断opt中员工对象是否满足条件,如果满足就保留,否则返回空
Optional<Employee> emp = opt.filter(e -> e.getSalary()>10000);
System.out.println(emp);
}
@Test
public void test4(){
Optional<Employee> opt = Optional.of(new Employee("张三", 8888));
//如果opt中员工对象不为空,就涨薪10%
Optional<Employee> emp = opt.map(e ->
{e.setSalary(e.getSalary()%1.1);return e;});
System.out.println(emp);
}
java8之上
- Java版本迭代过快,企业一般用Java8特性
- 下列之给出一些版本的网址
官方提供的新特性列表:https://docs.oracle.com/javase/9/whatsnew/toc.htm#JSNEW-GUID-C23AFD78-C777-460B-8ACE-58BE5EA681F6
或参考 Open JDKhttp://openjdk.java.net/projects/jdk9/
在线Oracle JDK 9 Documentationhttps://docs.oracle.com/javase/9/