目录
1.Lambda表达式
1.需求分析
创建一个新的线程,指定线程要执行的任务
package demo01;
/**
* @ProjectName: jdk1.8_1
* @Package: demo01
* @ClassName: Test01
* @Author: 王振华
* @Description:
* @Date: 2022/7/19 19:03
* @Version: 1.0
*/
public class Test01 {
public static void main(String[] args) {
My my = new My();
Thread t1 = new Thread(my);
t1.start();
//匿名内部类
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("这是使用匿名内部类方法时的任务对象");
}
};
Thread t2 = new Thread(task);
t2.start();
}
}
class My implements Runnable{
@Override
public void run() {
System.out.println("自定义任务接口类");
}
}
代码分析:
* 1.Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
* 2.为了指定run方法体,不得不需要Runnable实现类
* 3.为了省去定义一个Runnable的实现类,不得不使用匿名内部类
* 4.必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错
* 5.而实际上,我们只在乎方法体中的代码
2.Lambda表达式初体验
Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码
//lambda表达式
Thread t3 = new Thread(()->{
System.out.println("这是使用lambda表达式完成的");
});
t3.start();
前提:必须是函数式接口。
Lambda表达式的优点:简化了匿名内部类的使用,语法更加简单。
匿名内部类语法冗余,体验了Lambda表达式后,发现Lambda表达式是简化匿名内部类的一种方式。
3.Lambda的语法规则
概念:
Lambda表达式:特殊的匿名内部类,语法更加简洁
Lambda表达式允许把函数作为一个方法的参数(函数作为方法参数传递),将代码像数据一样传递
语法:
<函数式接口> <变量名> = (参数1,参数2...) -> {
//方法体
};
格式说明:
- (参数类型 参数名称):参数列表
- (代码体):方法体
- ->:箭头,分割参数列表和方法体
注意:函数式接口:接口中只有一个抽象方法.
3.1.练习无参无返回值的Lambda表达式
package demo02;
/**
* @ProjectName: jdk1.8_1
* @Package: demo02
* @ClassName: Test02
* @Author: 王振华
* @Description:
* @Date: 2022/7/19 19:13
* @Version: 1.0
*/
//无参无返回值的Lambda表达式
public class Test02 {
public static void main(String[] args) {
//匿名内部类
/* Swimmable swimmable = new Swimmable() {
@Override
public void swimming(String name) {
System.out.println("这是使用匿名内部类的方式");
}
};
fun(swimmable);*/
//lambda表达式
fun(name -> {
System.out.println("这是使用lambda表达式"+name);
});
}
public static void fun(Swimmable s){
s.swimming("郭富城");
}
}
//函数式接口
interface Swimmable{
public void swimming(String name);
}
3.2.练习有参数且有返回值的Lambda表达式
下面举例演示 java.util.Comparator 接口的使用场景代码,其中的抽象方法定义为:
-
public abstract int compare(T o1, T o2);
当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
package demo02;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @ProjectName: jdk1.8_1
* @Package: demo02
* @ClassName: Test03
* @Author: 王振华
* @Description:
* @Date: 2022/7/19 19:26
* @Version: 1.0
*/
//有参有返回值的Lambda表达式
public class Test03 {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("张三",20,158));
list.add(new Person("李四",12,178));
list.add(new Person("王五",30,167));
list.add(new Person("赵六",15,184));
//对集合中的元素进行排序 按照年龄从大到小
//Collections: 集合的工具类 //传统做法:Comparator: 排序规则接口
Collections.sort(list, new Comparator<Person>() {
//int: 0表示新加的元素和集合中原来的比对的相同
// //1: o2比o1大
// //-1: o2比o1小
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
for(Person person : list){
System.out.println(person);
}
System.out.println("=================================================");
//使用Lambda表达式 按年龄从小到大
Collections.sort(list,(o1,o2)->{return o2.getAge()- o1.getAge();});
for(Person person : list){
System.out.println(person);
}
}
}
class Person{
private String name;
private int age;
private int height;
public Person() {
}
public Person(String name, int age, int height) {
this.name = name;
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
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 int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
3.3.详细介绍Lambda表达式
2.函数式接口
内置函数式接口的由来
package demo03;
/**
* @ProjectName: jdk1.8_1
* @Package: demo03
* @ClassName: Test03
* @Author: 王振华
* @Description:
* @Date: 2022/7/19 19:43
* @Version: 1.0
*/
public class Test03 {
public static void main(String[] args) {
int[] arr = {2,2,3,4,56,8};
fun((arr1)->{
int sum = 0;
for(int n : arr1){
sum += n;
}
System.out.println("数组的和:"+sum);
},arr);
}
public static void fun(Operater operater,int[] arr){
operater.getSum(arr);
}
}
@FunctionalInterface
interface Operater{
//求数组的和
public abstract void getSum(int[] arr);
}
分析
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。
常见得函数式接口
java.util.function保存
2.1.Consumer<T>消费型接口
T:表示参数的泛型
有参数,无返回值。
package demo04;
import java.util.function.Consumer;
/**
* @ProjectName: jdk1.8_1
* @Package: demo04
* @ClassName: Test01
* @Author: 王振华
* @Description: 消费型接口
* @Date: 2022/7/19 19:50
* @Version: 1.0
*/
public class Test01 {
public static void main(String[] args) {
fun((t)->{
System.out.println("花费了"+t+"元");
},1000);
}
//调用某个方法时,该方法需要的参数为接口类型,这时就应该能想到Lambda
public static void fun(Consumer<Double> consumer,double money){
consumer.accept(money);
}
}
2.2.Supperlier<T>供给型接口
T:表示返回结果的泛型
无参数,有返回值
package demo04;
import java.util.Random;
import java.util.function.Supplier;
/**
* @ProjectName: jdk1.8_1
* @Package: demo04
* @ClassName: Test02
* @Author: 王振华
* @Description: 供给型接口
* @Date: 2022/7/19 19:56
* @Version: 1.0
*/
public class Test02 {
public static void main(String[] args) {
//0-9的随机整数
fun(()->{return new Random().nextInt(10);
});
}
public static void fun(Supplier<Integer> supplier){
Integer s = supplier.get();
System.out.println(s);
}
}
2.3.Function<T,R>函数型接口
T: 参数类型的泛型
R: 函数返回结果的泛型
有参数,有返回值。
例子: 传入一个字符串把小写转换为大写。
package demo04;
import java.util.Locale;
import java.util.function.Function;
/**
* @ProjectName: jdk1.8_1
* @Package: demo04
* @ClassName: Test03
* @Author: 王振华
* @Description: 函数型接口
* @Date: 2022/7/19 20:01
* @Version: 1.0
*/
public class Test03 {
public static void main(String[] args) {
fun((t)->{
//转成大写
return t.toUpperCase();
},"hello world");
}
public static void fun(Function<String,String> function,String msg){
String apply = function.apply(msg);
System.out.println(apply);
}
}
2.4.Predicate<T>断言型接口
T: 参数的泛型
boolean test(T t);
当传入一个参数时,需要对该参数进行判断时,则需要这种函数。
package demo04;
import java.util.function.Predicate;
/**
* @ProjectName: jdk1.8_1
* @Package: demo04
* @ClassName: Test04
* @Author: 王振华
* @Description: 断言型接口
* @Date: 2022/7/19 20:04
* @Version: 1.0
*/
public class Test04 {
public static void main(String[] args) {
//判断字符串的长度是否大于3,大于为true,否则相反
fun((t)->{return t.length()>3;},"会当凌绝顶");
}
public static void fun(Predicate<String> predicate,String msg){
boolean test = predicate.test(msg);
System.out.println(test);
}
}
3.方法引用
我们用Lambda表达式来实现匿名方法。但有些情况下,我们用Lambda表达式仅仅是调用一些已经存在的方法,除了调用方法外,没有其他任何多余的动作,在这种情况下,我们倾向于通过方法名来调用它,而Lambda表达式可以帮助我们实现这一要求,它使得Lambda在调用那些已经拥有方法名的方法的代码更简洁、更容易理解。方法引用可以理解为Lambda表达式的另外一种表现形式。
3.1.Lambda表达式的冗余
package demo05;
import java.util.function.Consumer;
/**
* @ProjectName: jdk1.8_1
* @Package: demo05
* @ClassName: Test01
* @Author: 王振华
* @Description: Lombda的冗余
* @Date: 2022/7/19 20:14
* @Version: 1.0
*/
public class Test01 {
public static void main(String[] args) {
fun((t)->{
String s1 = t.toUpperCase();
System.out.println(s1);
},"hello world");
}
public static void fun(Consumer<String> consumer,String msg){
consumer.accept(msg);
}
public static void toUpperCase(String s){
String s1 = s.toUpperCase();
System.out.println(s1);
}
}
分析:
我们发现toUpperCase方法和Consumer消费型接口中的代码体此时一致,我们重复写会使代码冗余。
如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接“引 用”过去就好了:---方法引用。
package demo05;
import java.util.function.Consumer;
/**
* @ProjectName: jdk1.8_1
* @Package: demo05
* @ClassName: Test01
* @Author: 王振华
* @Description:
* @Date: 2022/7/19 20:14
* @Version: 1.0
*/
public class Test01 {
public static void main(String[] args) {
fun((t)->Test01.toUpperCase(t),"hello world");
fun(Test01::toUpperCase,"hello world"); //这两种 方法是一样的
}
public static void fun(Consumer<String> consumer,String msg){
consumer.accept(msg);
}
public static void toUpperCase(String s){
String s1 = s.toUpperCase();
System.out.println(s1);
}
}
请注意其中的双冒号 :: 写法,这被称为“方法引用”,是一种新的语法。
3.2.什么是方法引用
3.3.方法引用的分类
类型 | 语法 | 对应的Lambda表达式 |
---|---|---|
静态方法引用 | 类名::staticMethod | (args) -> 类名.staticMethod(args) |
实例方法引用 | inst::instMethod | (args) -> inst.instMethod(args) |
对象方法引用 | 类名::instMethod | (inst,args) -> inst.instMethod(args) |
构建方法引用 | 类名::new | (args) -> new 类名(args) |
3.3.1.静态方法引用
有一个Person类,如下所示:
@Data
public class Person {
private String name;
private Integer age;
//比较两个Person对象的年龄是否一致。
public static int compareByAge(Person a, Person b) {
return a.age.compareTo(b.age);
}
}
现假设,一个部门有10人,把他们存放在一个数组中,并按年龄排序,通常我们可以自己写一个比较器,代码如下:
Person[] rosterAsArray = new Person[10];
// 添加数组元素省略
class PersonAgeComparator implements Comparator<Person> {
public int compare(Person a, Person b) {
return a.getAge().compareTo(b.getAge());
}
}
Arrays.sort(rosterAsArray, new PersonAgeComparator());
Arrays.sort的声明为:public static <T> void sort(T[] a, Comparator<? super T> c),比较器参数Comparator为一个函数式接口,利用上一节Lambda表达式所学知识,可以改写为以下代码:
Person[] rosterAsArray = new Person[10];
// 添加数组元素省略
Arrays.sort(rosterAsArray, (a,b) -> a.getAge().compareTo(b.getAge()));
然而,你会发现,Person类中已经有了一个静态方法的比较器:compareByAge,因此,我们改用Person类已经提供的比较器:
Person[] rosterAsArray = new Person[10];
// 添加数组元素省略
Arrays.sort(rosterAsArray, (a,b) -> Person.compareByAge(a,b));
以上代码,因为Lambda表达式调用了一个已经存在的静态方法,根据我们第2节表格中的语法,上面的代码可以最终改写成静态方法引用:
Person[] rosterAsArray = new Person[30];
// 添加数组元素省略
Arrays.sort(rosterAsArray, Person::compareByAge);
下面这个例子更简单:
package demo06;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* @ProjectName: jdk1.8_1
* @Package: demo06
* @ClassName: Test06
* @Author: 王振华
* @Description: 静态方法引用
* @Date: 2022/7/19 21:23
* @Version: 1.0
*/
public class Test06 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(2);
list.add(6);
list.add(4);
//Comparator<Integer> comparator = ((o1, o2) -> o1-o2);
//Comparator<Integer> comparator = ((o1, o2) -> Integer.compare(o1,o2));
//调用了Integer类的静态方法compare方法
Comparator<Integer> comparator = Integer::compare;
list.sort(comparator);
System.out.println(list);
}
}
对一个Integer列表进行排序,因为Integer中已经存在静态的比较方法compare(),因此可以直接用静态方法引用的方式来调用
3.3.2.实例方法引用
实例方法引用,顾名思义就是调用已经存在的实例的方法,与静态方法引用不同的是类要先实例化,静态方法引用类无需实例化,直接用类名去调用。
package demo07;
import java.time.Period;
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Supplier;
/**
* @ProjectName: jdk1.8_1
* @Package: demo07
* @ClassName: Test07
* @Author: 王振华
* @Description: 实例方法引用
* @Date: 2022/7/19 21:31
* @Version: 1.0
*/
public class Test07 {
public static void main(String[] args) {
People[] peoples = {new People("张三",15),new People("李四",12),new People("王五",58),new People("赵六",20)};
//该数组进行排序---年龄排序int compare(T o1, T o2);
//Comparator<People> comparator = (o1, o2) -> o1.getAge()-o2.getAge();
//如果在lambda表达式中有且仅有一条语句,而且这条语句是对方法的调用。这时可以考虑使用方法引用
//Comparator<People> comparator = (o1,o2)-> People.compare(o1,o2);
Comparator<People> comparator = People::compare;
Arrays.sort(peoples,comparator);
System.out.println(Arrays.asList(peoples));
People p=new People("李四",25);
//在这个lambda表达式中只有一条语句,而且这条语句又是调用了某个方法
//Supplier<String> s=()-> p.getName();
Supplier<String> s=p::getName;
fun(s);
}
public static void fun(Supplier<String> supplier){
String s = supplier.get();
System.out.println("结果为:"+s);
}
}
class People{
private String name;
private int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static int compare(People p1, People p2){
return p1.getAge()-p2.getAge();
}
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;
}
}
3.3.3.对象方法引用
若Lambda参数列表中的第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数时,可以使用对象方法引用。
String的equals()方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public static void main(String[] args) {
BiPredicate<String,String> bp = (x, y) -> x.equals(y);
BiPredicate<String,String> bp1 = String::equals;
boolean test = bp1.test("xy", "xx");
System.out.println(test);
}
代表了一个两个参数的boolean
值方法
BiPredicate的test()方法接受两个参数,x和y,具体实现为x.equals(y),满足Lambda参数列表中的第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数,因此可以使用对象方法引用。
package demo01;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* @ProjectName: jdk1.8_2
* @Package: demo01
* @ClassName: Test01
* @Author: 王振华
* @Description: 对象方法引用
* @Date: 2022/7/20 19:15
* @Version: 1.0
*/
public class Test01 {
public static void main(String[] args) {
//对象方法引用: 类名::实例方法. (参数1,参数2)->参数1.实例方法(参数2)
//Function<String,Integer> function = (str) -> {return str.length();};
Function<String,Integer> function = String::length;
Integer apply = function.apply("hello");
System.out.println(apply);
//比较两个字符串的内容是否一致
BiFunction<String,String,Boolean> biFunction = (o1,o2)->{ return o1.equals(o2);};
Boolean apply1 = biFunction.apply("hello", "world");
System.out.println(apply1);
}
}
3.3.4.构造方法引用
注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。
如:要获取一个空的User列表:
Supplier<List<User>> userSupplier = () -> new ArrayList<>();
List<User> user = userSupplier.get();
Supplier<List<User>> userSupplier2 = ArrayList<User>::new; // 构造方法引用写法
List<User> user2 = userSupplier.get();
package demo01;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @ProjectName: jdk1.8_2
* @Package: demo01
* @ClassName: Test02
* @Author: 王振华
* @Description: 构造方法引用
* @Date: 2022/7/20 19:23
* @Version: 1.0
*/
public class Test02 {
public static void main(String[] args) {
//构造方法引用: 类名::new (参数)->new 类名(参数)
//Supplier<String> supplier = ()->{return new String();};
//无参
Supplier<People> supplier = People::new;
People s = supplier.get();
System.out.println(s);
//有参
//Function<String,People> function = (str)->{return new People(str);};
Function<String,People> function = People::new;
People people = function.apply("李四");
System.out.println(people);
}
}
class People{
private String name;
public People() {
}
public People(String name) {
this.name = name;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
'}';
}
}
4. Stream流
Java8的两个重大改变,一个是Lambda表达式,另一个就是本节要讲的Stream API表达式。Stream 是Java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的查找、过滤、筛选等操作.
4.1 为什么使用Stream流
当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。我们来体验 集合操作数据的弊端,需求如下:
一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰,何线程
需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
代码如下:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class My {
public static void main(String[] args) {
// 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
// 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
// 1.拿到所有姓张的
ArrayList<String> zhangList = new ArrayList<>(); // {"张无忌", "张强", "张三丰"}
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
// 2.拿到名字长度为3个字的
ArrayList<String> threeList = new ArrayList<>(); // {"张无忌", "张三丰"}
for (String name : zhangList) {
if (name.length() == 3) {
threeList.add(name);
}
}
// 3.打印这些数据
for (String name : threeList) {
System.out.println(name);
}
}
}
分析:
这段代码中含有三个循环,每一个作用不同:
首先筛选所有姓张的人;
然后筛选名字有三个字的人;
最后进行对结果进行打印输出。
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环 是做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使 用另一个循环从头开始。
那Stream能给我们带来怎样更加优雅的写法呢?
Stream的更优写法
package demo08;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;
/**
* @program: qy151-jdk8
* @description:
* @author: 闫克起2
* @create: 2022-07-20 14:57
**/
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰","何线程");
list.stream()
.filter(item->item.startsWith("张"))
.filter(item->item.length()==3)
.forEach(item-> System.out.println(item));
}
}
对集合的操作语法简洁:性能比传统快。
4.2 Stream流的原理
注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工 处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
Stream不存在数据,只对数据进行加工处理。
特点:
Stream有如下三个操作步骤:
一、创建Stream
从一个数据源,如集合、数组中获取流。
二、中间操作
一个操作的中间链,对数据源的数据进行操作。
三、终止操作
一个终止操作,执行中间操作链,并产生结果。
4.3 如何获取Stream流对象。
stream():串行流
parallelStream():并行流 多线程
package demo02;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @ProjectName: jdk1.8_2
* @Package: demo02
* @ClassName: Test01
* @Author: 王振华
* @Description: 如何获取Stream流对象
* @Date: 2022/7/20 19:55
* @Version: 1.0
*/
public class Test01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("孙七");
//集合获取Stream对象
Stream<String> stream = list.stream();
//通过Arrays数组工具类获取Stream对象
int[] arr = {1, 2, 5, 4, 6, 3};
IntStream stream1 = Arrays.stream(arr);
//使用Stream类of方法
Stream<String> stream2 = Stream.of("hello", "world", "spring", "java", "js");
//使用Stream类iterate方法获取流对象
Stream<Integer> stream3 = Stream.iterate(0, (x) -> {
return x + 5;
});
//使用Stream类generate方法
Stream.generate(()->{return new Random().nextInt(50); });
//通过IntStream接口的rangeClosed方法
IntStream intStream = IntStream.rangeClosed(0, 50);
intStream.forEach(System.out::println);
//上面都是获取的串行流。还可以获取并行流。如果流中的数据量足够大,并行流可以加快处速度。
Stream<String> stringStream = list.parallelStream();
}
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Demo1 {
public static void main(String[] args) {
//1.通过Collection得对象中stream方法或parallelStream
// List<String> list=new ArrayList<>();
// list.add("apple");
// list.add("huawei");
// list.add("xiaomi");
// list.add("vivo");
// Stream<String> stream = list.stream();//串行流
//遍历集合(T t)-{}; 方法引用
// stream.forEach(e->{
// System.out.println(e);
// });
//方法引用
// stream.forEach(System.out::println);
// Stream<String> stringStream = list.parallelStream(); //并行流
//2.通过Arrays转化为流
// int[] arr={2,34,5,6,7,2};
// IntStream stream = Arrays.stream(arr);
// stream.forEach(System.out::println);
//3.通过Stream中of,iterator,generator();
// Stream<String> list = Stream.of("java01", "java02", "java03");
//static <T> UnaryOperator<T> identity() {
// return t -> t;
// }
// UnaryOperator<Integer> unaryOperator=new UnaryOperator<Integer>() {
// @Override
// public Integer apply(Integer x) {
// return x+5;
// }
// };
// UnaryOperator<Integer> unaryOperator=x->x+5;
// Stream<Integer> list = Stream.iterate(0,unaryOperator);//0,5,10,15,20,25,30
// list.limit(10).forEach(System.out::println);
//Supplier<T> s
// Supplier<Integer> s=new Supplier() {
// @Override
// public Integer get() {
// return new Random().nextInt(50);
// }
// };
// Supplier<Integer> s=()->new Random().nextInt(50);
// Stream<Integer> stream = Stream.generate(s);
// Stream<Integer> stream = Stream.generate(()->new Random().nextInt(50));
// stream.limit(10).forEach(System.out::println);
//IntStream LongStream DoubleStream
IntStream stream = IntStream.rangeClosed(0, 90);
stream.forEach(System.out::println);
}
}
串行流和并行流的比较
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class Demo3 {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
for (int i = 0; i <5000000 ; i++) {
list.add(UUID.randomUUID().toString());
}
long l1 = System.currentTimeMillis();
list.stream().sorted().count();
// list.parallelStream().sorted().count();
long l2 = System.currentTimeMillis();
System.out.println("耗时:"+(l2-l1));
}
}
4.4 Stream初体验
举个简单的例子:
假设有一个Person类和一个Person列表,现在有两个需求:1)找到年龄大于18岁的人并输出;2)找出所有中国人的数量。
@Data
class Person {
private String name;
private Integer age;
private String country;
private char sex;
public Person(String name, Integer age, String country, char sex) {
this.name = name;
this.age = age;
this.country = country;
this.sex = sex;
}
}
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("小梅",20,"中国",'F'));
personList.add(new Person("何雪",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
在JDK8以前,我们可以通过遍历列表来完成。但是在有了Stream API后,可以这样来实现:
public static void main(String[] args) {
// 1)找到年龄大于18岁的人并输出;
personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
System.out.println("-------------------------------------------");
// 2)找出所有中国人的数量
long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
System.out.println("中国人有:" + chinaPersonNum + "个");
}
在这个例子中,personList.stream()是创建流,filter()属于中间操作,forEach、count()是终止操作。
4.5 Stream流中常见的api
中间操作api: 一个操作的中间链,对数据源的数据进行操作。而这种操作的返回类型还是一个Stream对象。
终止操作api: 一个终止操作,执行中间操作链,并产生结果,返回类型不在是Stream流对象。
4.6.中间操作
filter:接收Lambda,从流中排除某些操作;
limit:截断流,使其元素不超过给定对象
skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素。
4.6.1.filter过滤
package demo02;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @ProjectName: jdk1.8_2
* @Package: demo02
* @ClassName: TestStream
* @Author: 王振华
* @Description:
* @Date: 2022/7/20 19:53
* @Version: 1.0
*/
public class TestStream {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("小梅",20,"中国",'F'));
personList.add(new Person("何雪",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
//filter过滤
// 1)找到年龄大于18岁的人并输出;
//filter()过滤器需要一个断言接口函数,断言接口返回true,获取该元素
//无论执行多少个中间操作,如果没有执行终止操作,那么中间操作都不会被执行
personList.stream().filter((t)->{return t.getAge()>18;}).forEach(System.out::println);
//2)找出所有中国人的数量。--->filter过滤掉其他国家的人 count()终止操作
long count = personList.stream().filter((t) -> {
return t.getCountry().equals("中国");
}).count();
System.out.println(count);
}
}
class Person {
private String name;
private Integer age;
private String country;
private char sex;
public Person(String name, Integer age, String country, char sex) {
this.name = name;
this.age = age;
this.country = country;
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", country='" + country + '\'' +
", sex=" + sex +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
4.6.2.limit限制
//limit:限制
personList.stream().limit(5).forEach(System.out::println);
4.6.3.skip跳过
//skip:跳过
personList.stream().skip(3).forEach(System.out::println);
4.6.4.distinct去重
//distinct:去重 //treeset: 先按照hashcode比较 再按照equal
personList.stream().distinct().forEach(System.out::println);
4.6.5.sorted排序
sorted()--自然排序(Comparable)
sorted(Comparator com)--定制排序(Comparator)
//sorted:排序
personList.stream().sorted((o1,o2)->o1.getAge()-o2.getAge()).forEach(System.out::println);
自然排序比较好理解,这里只讲一下定制排序,对前面的personList按年龄从小到大排序,年龄相同,则再按姓名排序
final Stream<Person> sorted = personList.stream().sorted((p1, p2) -> {
if (p1.getAge().equals(p2.getAge())) {
return p1.getName().compareTo(p2.getName());
} else {
return p1.getAge().compareTo(p2.getAge());
}
});
sorted.forEach(System.out::println);
运行结果:
Person(name=欧阳雪, age=18, country=中国, sex=F)
Person(name=向天笑, age=20, country=中国, sex=M)
Person(name=小梅, age=20, country=中国, sex=F)
Person(name=何雪, age=21, country=中国, sex=F)
Person(name=Harley, age=22, country=英国, sex=F)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=Tom, age=24, country=美国, sex=M)
4.6.6.map映射
map--接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap--接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
//map:映射 ---->结构发生改变 原来流中每个元素转换为另一种格式。
personList.stream().map((t)->t.getName()).forEach(System.out::println);
//集合中每个元素只要名.map--->原来流中每个元素转换为另一种格式。
// personList.stream()
// .map(item->{
// Map<String,Object> m=new HashMap<>();
// m.put("name",item.getName());
// m.put("age",item.getAge());
// return m;
// })
// .forEach(System.out::println);
map举例
例1:比如,我们用一个PersonCountry类来接收所有的国家信息:
@Data
class PersonCountry {
private String country;
}
personList.stream().map((p) -> {
PersonCountry personName = new PersonCountry();
personName.setCountry(p.getCountry());
return personName;
}).distinct().forEach(System.out::println);
输出结果为:
PersonName(country=中国)
PersonName(country=美国)
PersonName(country=英国)
例2:假如有一个字符列表,需要提出每一个字符
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","ddd");
代码如下:
根据字符串获取字符方法:
public static Stream<Character> getCharacterByString(String str) {
List<Character> characterList = new ArrayList<>();
for (Character character : str.toCharArray()) {
characterList.add(character);
}
return characterList.stream();
}
运行结果:
java.util.stream.ReferencePipeline$Head@3f91beef
java.util.stream.ReferencePipeline$Head@1a6c5a9e
java.util.stream.ReferencePipeline$Head@37bba400
java.util.stream.ReferencePipeline$Head@179d3b25
java.util.stream.ReferencePipeline$Head@254989ff
从输出结果及返回结果类型(Stream<Stream<Character>>)可以看出这是一个流中流,要想打印出我们想要的结果,需要对流中的每个流进行打印:
streamStream.forEach(sm -> sm.forEach(System.out::print));
运行结果为:
aaabbbcccdddddd
但我们希望的是返回的是一个流,而不是一个包含了多个流的流,而flatMap可以帮助我们做到这一点。
flatMap举例
改写上面的方法,将map改成flatMap:
1 final Stream<Character> characterStream = list.stream().flatMap(TestStreamAPI::getCharacterByString);
2 characterStream.forEach(System.out::print);
运行结果为:
aaabbbcccdddddd
map图解:
map在接收到流后,直接将Stream放入到一个Stream中,最终整体返回一个包含了多个Stream的Stream。
flatMap图解:
flatMap在接收到Stream后,会将接收到的Stream中的每个元素取出来放入一个Stream中,最后将一个包含多个元素的Stream返回。
4.6.7.parallel并行操作
//parallel:并行操作
personList.stream().parallel().forEach(System.out::println);
4.7.终止操作
allMatch--检查是否匹配所有元素
anyMatch--检查是否至少匹配一个元素
noneMatch--检查是否没有匹配所有元素
findFirst--返回第一个元素
findAny--返回当前流中的任意元素
count--返回流中元素的总个数
max--返回流中最大值
min--返回流中最小值
这些方面在Stream类中都有说明,这里不一一举例,只对allMatch、max各举一例进行说明。
4.7.1.allMatch检查
判断personList中的人是否都是成年人:
final boolean adult = personList.stream().allMatch(p -> p.getAge() >= 18);
System.out.println("是否都是成年人:" + adult);
final boolean chinaese = personList.stream().allMatch(p -> p.getCountry().equals("中国"));
System.out.println("是否都是中国人:" + chinaese);
运行结果:
是否都是成年人:true
是否都是中国人:false
4.7.2.findFirst findAny返回元素
//Optional<Person> optional = personList.stream().filter(t -> t.getAge() > 20).findFirst();
Optional<Person> optional = personList.stream().filter(t -> t.getAge() > 20).findAny();
System.out.println(optional);
4.7.3.max min最大最小值
final Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
System.out.println("年龄最大的人信息:" + maxAge.get());
final Optional<Person> minAge = personList.stream().min((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
System.out.println("年龄最小的人信息:" + minAge.get());
//max:最大值 查找最大年龄的人. max终止操作
Optional<Person> max = personList.stream().max(((o1, o2) -> {
return o1.getAge() - o2.getAge();
}));
System.out.println(max.get());
运行结果:
年龄最大的人信息:Person(name=Tom, age=24, country=美国, sex=M)
年龄最小的人信息:Person(name=欧阳雪, age=18, country=中国, sex=F)
4.7.4.count返回元素个数
long count = personList.stream().filter((t) -> {
return t.getCountry().equals("中国");
}).count();
System.out.println(count);
4.7.5.reduce归约
Stream API的归约操作可以将流中元素反复结合起来,得到一个值
System.out.println("---------reduce--------");
//求集合中所有人的年龄和。参数和返回类型必须一致
//[18,24,22,20,22,20,21]--->(18,24)->18+24;--->(42,22)->42+22--->(64,20)->64+20
Optional<Integer> reduce = personList.stream()
.map(item -> item.getAge())
.reduce((a, b) -> a + b);
System.out.println(reduce.get());
Integer reduce = personList.stream()
.map(item -> item.getAge())
.reduce(10, (a, b) -> a + b);
System.out.println(reduce);
求一个1到100的和
List<Integer> integerList = new ArrayList<>(100);
for(int i = 1;i <= 100;i++) {
integerList.add(i);
}
final Integer reduce = integerList.stream().reduce(0, (x, y) -> x + y);
System.out.println("结果为:" + reduce);
这个例子用到了reduce第二个方法:T reduce(T identity, BinaryOperator<T> accumulator)
把这个动作拆解一下,其运算步骤模拟如下:
0 (1,2) -> 1 + 2 + 0
3 (3,4) -> 3 + 4 + 3
10 (5,6) -> 5 + 6 + 10
.
.
.
其运算步骤是,每次将列表的两个元素相加,并将结果与前一次的两个元素的相加结果进行累加,因此,在开始时,将identity设为0,因为第1个元素和第2个元素在相加的时候,前面还没有元素操作过。
4.7.6.collect搜集
collect:将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法
<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);
collect不光可以将流转换成其他集合等形式,还可以进行归约等操作,具体实现也很简单,主要是与Collectors类搭配使用。
将国家收集起来转换成List
final List<String> collect = personList.stream().map(p -> p.getCountry()).distinct().collect(Collectors.toList());
System.out.println(collect);
输出结果:
[中国, 美国, 英国]
//搜集方法 collect 它属于终止方法 //年龄大于20且性别为M
List<Person> collect = personList.stream()
.filter(t -> t.getAge() > 20)
.filter(t -> t.getSex() == 'M')
.collect(Collectors.toList());
System.out.println(collect);
计算出平均年龄
final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge()));
System.out.println("平均年龄为:" + collect1);
输出结果:
平均年龄为:21.125
找出最小年龄、最大年龄
final Optional<Integer> maxAge2 = personList.stream().map(Person::getAge).collect(Collectors.maxBy(Integer::compareTo));
System.out.println(maxAge2.get());
还有其他很操作,可以参考java.util.stream.Collectors。
4.8.完整测试代码
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestStreamAPI {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("小梅",20,"中国",'F'));
personList.add(new Person("何雪",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
// 1)找到年龄大于18岁的人并输出;
personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
System.out.println("-------------------------------------------");
// 2)找出所有中国人的数量
long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
System.out.println("中国人有:" + chinaPersonNum);
// limit
personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println);
System.out.println();
// skip
personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println);
// distinct
personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);
// map
personList.stream().map((p) -> {
PersonCountry personName = new PersonCountry();
personName.setCountry(p.getCountry());
return personName;
}).distinct().forEach(System.out::println);
// map2
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","ddd");
final Stream<Stream<Character>> streamStream
= list.stream().map(TestStreamAPI::getCharacterByString);
// streamStream.forEach(System.out::println);
streamStream.forEach(sm -> sm.forEach(System.out::print));
// flatMap
final Stream<Character> characterStream = list.stream().flatMap(TestStreamAPI::getCharacterByString);
characterStream.forEach(System.out::print);
// sort
final Stream<Person> sorted = personList.stream().sorted((p1, p2) -> {
if (p1.getAge().equals(p2.getAge())) {
return p1.getName().compareTo(p2.getName());
} else {
return p1.getAge().compareTo(p2.getAge());
}
});
sorted.forEach(System.out::println);
// allMatch
final Stream<Person> stream = personList.stream();
final boolean adult = stream.allMatch(p -> p.getAge() >= 18);
System.out.println("是否都是成年人:" + adult);
final boolean chinaese = personList.stream().allMatch(p -> p.getCountry().equals("中国"));
System.out.println("是否都是中国人:" + chinaese);
// max min
final Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
System.out.println("年龄最大的人信息:" + maxAge.get());
final Optional<Person> minAge = personList.stream().min((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
System.out.println("年龄最小的人信息:" + minAge.get());
// reduce
List<Integer> integerList = new ArrayList<>(100);
for(int i = 1;i <= 100;i++) {
integerList.add(i);
}
final Integer reduce = integerList.stream().reduce(0, (x, y) -> x + y);
System.out.println("结果为:" + reduce);
final Optional<Integer> totalAge = personList.stream().map(Person::getAge).reduce(Integer::sum);
System.out.println("年龄总和:" + totalAge);
// collect
final List<String> collect = personList.stream().map(p -> p.getCountry()).distinct().collect(Collectors.toList());
System.out.println(collect);
final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge()));
System.out.println("平均年龄为:" + collect1);
final Optional<Integer> maxAge2 = personList.stream().map(Person::getAge).collect(Collectors.maxBy(Integer::compareTo));
System.out.println(maxAge2.get());
try(final Stream<Integer> integerStream = personList.stream().map(Person::getAge)) {
final Optional<Integer> minAge2 = integerStream.collect(Collectors.minBy(Integer::compareTo));
System.out.println(minAge2.get());
}
}
public static Stream<Character> getCharacterByString(String str) {
List<Character> characterList = new ArrayList<>();
for (Character character : str.toCharArray()) {
characterList.add(character);
}
return characterList.stream();
}
}
@Data
class PersonCountry {
private String country;
}
@Data
class Person {
private String name;
private Integer age;
private String country;
private char sex;
public Person(String name, Integer age, String country, char sex) {
this.name = name;
this.age = age;
this.country = country;
this.sex = sex;
}
}
5. 新增了日期时间类
旧的日期时间的缺点:
设计比较乱: Date日期在java.util和java.sql也有,而且它的时间格式转换类在java.text包。
线程不安全。
jdk1.8之后的新增日期时间相关类
public static void main(String[] args) {
LocalDate now = LocalDate.now();
System.out.println(now);
LocalTime now1 = LocalTime.now();
System.out.println(now1);
LocalDateTime now2 = LocalDateTime.now();
System.out.println(now2);
//使用DateTimeFormatter 将日期转化成字符串类型
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String ss = now.format(dateTimeFormatter);
System.out.println(ss);
//使用DateTimeFormatter 将字符串转化成Date类型
String str = "2020/10/16";
//创建一个格式化日期类,用来解析字符串日期
DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date = LocalDate.parse(str, dateTimeFormatter1);
System.out.println(date);
DateTimeFormatter格式化日期类
方法:
ofPattern("日期格式"); 创建特定格式的格式化日期对象
Duraction类 表示:时间的区间,用来度量秒和纳秒之间的时间值
Period类 表示:一段时间的区间,用来度量年月日和几天之间的时间值
Duration.between();
Period.between();
计算时间差值
计算时间的差值:Duration.between()
public static void main(String[] args) throws ParseException {
LocalDateTime ldt=LocalDateTime.now();
LocalDateTime paldt=LocalDateTime.parse("1999-10-16T17:10:16.355");
Duration dt=Duration.between(paldt,ldt);
System.out.println(dt.toDays());//间隔的天数
System.out.println(dt.toHours());//小时
System.out.println(dt.toMinutes());//分钟
System.out.println(dt.getSeconds());//毫秒
}
public static void main(String[] args) {
String s1 = "2020-11-20";
String s2 = "2022-10-11";
LocalDate ld1 = LocalDate.parse(s1);
LocalDate ld2 = LocalDate.parse(s2);
//Duration d1 = Duration.between(ld1,ld2);
//Period p = Period.between(ld1,ld2);
// ChronoUnit类可用于在单个时间单位内测量一段时间,例如天数或秒。
long days = ChronoUnit.DAYS.between(ld1,ld2);
System.out.println(days);
}
package demo03;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
/**
* @ProjectName: jdk1.8_2
* @Package: demo03
* @ClassName: TestLocalDate
* @Author: 王振华
* @Description:
* @Date: 2022/7/20 20:50
* @Version: 1.0
*/
public class TestLocalDate {
public static void main(String[] args) {
LocalDate localDate = LocalDate.now(); //获取当前时间
LocalDate localDate1 = LocalDate.of(2000, 10, 10);//获取指定时间
String s = "2000-10-20";
String s1 = "2000-11-21";
LocalDate parse = LocalDate.parse(s);
LocalDate parse1 = LocalDate.parse(s1);
//Period类 表示:一段时间的区间,用来度量年月日和几天之间的时间值
Period p = Period.between(parse,parse1);
System.out.println(p.getYears());
LocalDateTime now = LocalDateTime.now();//获取当前日期时间
LocalDateTime localDateTime = LocalDateTime.of(2022, 07, 20, 21, 20, 20);//获取指定日期时间
//Duraction类 表示:时间的区间,用来度量秒和纳秒之间的时间值
Duration between = Duration.between(now, localDateTime);
System.out.println(between.toMinutes());
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
//把字符串转为日期格式
LocalDate parse2 = LocalDate.parse("2000-10-10", dateTimeFormatter);
System.out.println(parse2);
//把日期转为字符串
String format = parse2.format(dateTimeFormatter);
System.out.println(format);
}
}