虽然java8已经快过期了,但是作为技术总结,还是简单总结一下。
目录
1所谓的java8新特性有哪些
-
Lambda表达式
-
接口的增强
-
函数式接口
-
方法引用
-
Stream API
-
Optional
-
新时间日期API
-
其他新特性
2.java8新特性详解
2.1 lambda表达式
1.概述
Java中的Lambda表达式是JDK8中的一种新特性,它允许我们将一段代码(这段代码可以理解为一个接口的实现)当成参数传递给某个方法,然后进行业务处理,这种方式更像是一种函数式编程风格,可以让代码结构更简洁,开发效率更高。
比较让人容易迷乱的是lambda的几个简化写法。后面会一一细说。
lambda的学习点也就两个,一个是把一点代码作为参数传递,一个是它的简化写法。
2.示例代码
public static void main(String[] args) {
// 开启一个新的线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程中执行的代码 : "+Thread.currentThread().getName());
}
}).start();
System.out.println("主线程中的代码:" + Thread.currentThread().getName());
}
从这段代码看到,将Runnable接口实现作为一个参数传递,创建了Thread类后,调用了start()。这也是lamnda的核心思想所在。当然在使用中,我们更多的使用在这个基础上的简化写法。
分析上面代码,在创建Thread对象时,其实在乎的作为入参的这段代码的结果,这个结果才是Thread对象创建需要的。换句话说就是这段代码的params和body体。而lambda简化的精髓就在于,只需要关注作为参数的这个代码块的params和body体,其他的编译器会通过底层的类型推断编译时自动补充。(因此能用lanbda的也是一些常用的固定写法:编译器能识别到的写法。事实上必须入参代码块是个函数式接口。
new Thread(() ->System.out.println("新线程中执行的代码 : "+Thread.currentThread().getName())).start();
上面是简化后的写法。看起来是不是很清爽,因为在作为入参的代码块的组成元素:
访问修饰符
返回值类型
方法名
参数列表
body体
中为只关注了参数列表(没有就用()表示)和body体。
3. Lambda的语法规则
Lambda的标准格式由3个部分组成:
(参数类型 参数名称) -> { 代码体; }
格式说明:
-
(参数类型 参数名称):参数列表
-
{代码体;} :方法体
-
-> : 箭头,分割参数列表和方法体
常见结构如下:
() -> statement |
Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果
Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:
Arrays.asList( "a", "b", "d" )
.sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
4.函数式接口
@FunctionInterface注解
/**
* @FunctionalInterface
* 这是一个标志注解,被该注解修饰的接口只能声明一个抽象方法
*/
@FunctionalInterface
public interface UserService {
void show();
}
5. 练习
public interface UserService {
void show();
}
public class Demo03Lambda {
public static void main(String[] args) {
goShow(new UserService() {
@Override
public void show() {
System.out.println("show 方法执行了...");
}
});
System.out.println("----------");
goShow(() -> { System.out.println("Lambda show 方法执行了..."); });
}
public static void goShow(UserService userService){
userService.show();
}
}
输出
show 方法执行了...
----------
Lambda show 方法执行了...
6.Lambda表达式的使用前提
Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意
-
方法的参数或局部变量类型必须为接口(new 接口)才能使用Lambda
-
接口中有且仅有一个抽象方法(@FunctionalInterface)
7.有价值的参考文章
3.接口的增强
3.1. JDK8中接口的新增
在JDK8中针对接口有做增强,在JDK8之前
interface 接口名{ 静态常量; 抽象方法; }
JDK8之后对接口做了增加,接口中可以有默认方法和静态方法
interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法; }
3.2 默认方法
1为什么要增加默认方法
在JDK8以前接口中只能有抽象方法和静态常量,会存在以下的问题:
如果接口中新增抽象方法,那么实现类都必须要抽象这个抽象方法,非常不利于接口的扩展的
package com.bobo.jdk.inter;
public class Demo01Interface {
public static void main(String[] args) {
A a = new B();
A c = new C();
}
}
interface A{
void test1();
// 接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
void test2();
}
class B implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
}
class C implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
}
2接口默认方法的格式
接口中默认方法的语法格式是
interface 接口名{
修饰符 default 返回值类型 方法名{
方法体;
}
}
package com.bobo.jdk.inter;
public class Demo01Interface {
public static void main(String[] args) {
A a = new B();
a.test3();
A c = new C();
c.test3();
}
}
interface A{
void test1();
// 接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
void test2();
/**
* 接口中定义的默认方法
* @return
*/
public default String test3(){
System.out.println("接口中的默认方法执行了...");
return "hello";
}
}
class B implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
@Override
public String test3() {
System.out.println("B 实现类中重写了默认方法...");
return "ok ...";
}
}
class C implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
}
3 接口中默认方法的使用
接口中的默认方法有两种使用方式
-
实现类直接调用接口的默认方法
-
实现类重写接口的默认方法
换句话说就是,需要用的实现类可以重写或者直接调用,不需要用的实现类,可以不用管接口中的默认方法的存在
3.3. 静态方法
JDK8中为接口新增了静态方法,作用也是为了接口的扩展
1 语法规则
interface 接口名{
修饰符 static 返回值类型 方法名{
方法体;
}
}
package com.bobo.jdk.inter;
public class Demo01Interface {
public static void main(String[] args) {
A a = new B();
a.test3();
A c = new C();
c.test3();
A.test4();
}
}
interface A{
void test1();
// 接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
void test2();
/**
* 接口中定义的默认方法
* @return
*/
public default String test3(){
System.out.println("接口中的默认方法执行了...");
return "hello";
}
/**
* 接口中的静态方法
* @return
*/
public static String test4(){
System.out.println("接口中的静态方法....");
return "Hello";
}
}
class B implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
@Override
public String test3() {
System.out.println("B 实现类中重写了默认方法...");
return "ok ...";
}
}
class C implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
}
2 静态方法的使用
接口中的静态方法在实现类中是不能被重写的,调用的话只能通过接口类型来实现: 接口名.静态方法名();
3 两者的区别介绍
-
默认方法通过实例调用,静态方法通过接口名调用
-
默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法
-
静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用,专属于接口
4.函数式接口
4.1定义
当且仅当,一个接口被@FunctionInterface注解,且接口中有且只有一个抽象方法的接口
注意:
1.一旦被@FunctionInteface注解后,接口中有多个抽象方法就会报错
2.其他方法:
* 函数时接口就中的静态方法,默认方法
4.2.可看成函数式接口的接口
接口没有被@FunctionInterface注解,但是接口中只有一个抽象方法,这种接口也可以堪称函数式接口
4.3函数式接口存在的意义
配和 lambda表达式 操作
4.4 常见的几种函数是接口的分类
1 消费性接口: 有入参没有返回值,如 Consumer<T>
2 函数式接口: 有入参,入参在接口方法中计算后返回接口,如 Function<T,R>
3 判定式样接口: 有入参,返回值是boolean 类型 如 Predicate
4 供给式接口: 无参数,有返回值 ,如 supply
package com.tmooc.interfacedefaultandstaticmethod;
import java.sql.SQLOutput;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class FunctionIntefaceDemo {
public static void main(String[] args) {
//创建函数式接口实例
Fun fun = new Fun() {
@Override
public void domethod01() {
System.out.println("函数式接口被成功调用");
}
};
//调用函数式接口中唯一的抽象方法
fun.domethod01();
System.out.println("=======================================");
//================================常见的集中函数式接口类型举例================================
//消费型接口
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("消费型接口成功被调用");
System.out.println("=======================================");
//函数式接口举例
Function<String, Integer> function = new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
};
Integer result = function.apply("100");
System.out.println(result);
consumer.accept("函数型接口成功被调用");
System.out.println("=======================================");
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return false;
}
};
boolean res = predicate.test("测试");
System.out.println(res);
System.out.println("判定型接口被成功调用");
System.out.println("=======================================");
Supplier<Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
return 0;
}
};
Integer ress = supplier.get();
System.out.println(ress);
System.out.println("供给型接口被成功调用");
}
}
@FunctionalInterface
interface Fun {
//函数式接口中的唯一抽象方法
void domethod01();
//其他的都是一个或多个静态方法或则默认方法
static int domethod2() {
return 1;
}
default void domethod03() {
}
}
5.方法引用
5.1为什么要使用方法引用
方法引用是对Lambda表达式符合特定情况下的一种缩写方式,它使得我们的Lambda表达式更加的精简,也可以理解为lambda表达式的缩写形式。
要注意的是方法引用只能引用已经存在的方法。 是当lamnda表达式中的接口的参数列表和已处在的方法的参数列表和返回值一样时,通过引用已存在的方法,从而简化lambda表达式的书写。
5.2方法引用的5中语法格式
1.instanceName::methodName 实例名::方法名
2.className::methodName 类名::方法名
3.className:: staticMethodName 类名::静态方法名
4.className::new 类名::构造器
5.Type[] ::new Strnig[]::new 数组::构造器
5.3方法引用的举例
package com.example.demo;
@FunctionalInterface
public interface NumService {
static final Long defaultNum=1L;
default Long getDefaultNum(){
return defaultNum;
}
static Long defaultNumAddOne(){
return defaultNum+1;
}
abstract int show(int showNum);
}
### 5.3.1 实例名::方法名
这是最常见的一种用法。如果一个类中的已经存在了一个成员方法,则可以通过对象名引用成员方法
```java
public static void main(String[] args) {
Date now = new Date();
Supplier<Long> supplier = ()->{return now.getTime();};
System.out.println(supplier.get());
// 然后我们通过 方法引用 的方式来处理
Supplier<Long> supplier1 = now::getTime;
System.out.println(supplier1.get());
}
```
方法引用的注意事项:
1. 被引用的方法,参数要和接口中的抽象方法的参数一样
2. 当接口抽象方法有返回值时,被引用的方法也必须有返回值
也就是被引用的方法的参数列表和返回值类型和lambda表达式中的接口参数列表和返回值类型一样
### 5.3.2 类名::静态方法名
也是比较常用的方式:
```java
public class FunctionRefTest04 {
public static void main(String[] args) {
Supplier<Long> supplier1 = ()->{
return System.currentTimeMillis();
};
System.out.println(supplier1.get());
// 通过 方法引用 来实现
Supplier<Long> supplier2 = System::currentTimeMillis;
System.out.println(supplier2.get());
}
}
```
### 5.3.3 类名::引用实例方法
Java面向对象中,类名只能调用静态方法,类名引用实例方法是用前提的,实际上是拿第一个参数作为方法的调用者
```java
package com.bobo.jdk.funref;
import java.util.Date;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
public class FunctionRefTest05 {
public static void main(String[] args) {
Function<String,Integer> function = (s)->{
return s.length();
};
System.out.println(function.apply("hello"));
// 通过方法引用来实现
Function<String,Integer> function1 = String::length;
System.out.println(function1.apply("hahahaha"));
BiFunction<String,Integer,String> function2 = String::substring;
String msg = function2.apply("HelloWorld", 3);
System.out.println(msg);
}
}
```
### 5.3.4 类名::构造器
由于构造器的名称和类名完全一致,所以构造器引用使用`::new`的格式使用,
```java
public class FunctionRefTest06 {
public static void main(String[] args) {
Supplier<Person> sup = ()->{return new Person();};
System.out.println(sup.get());
// 然后通过 方法引用来实现
Supplier<Person> sup1 = Person::new;
System.out.println(sup1.get());
BiFunction<String,Integer,Person> function = Person::new;
System.out.println(function.apply("张三",22));
}
}
```
### 5.3.5 数组::构造器
数组是怎么构造出来的呢?
```java
public static void main(String[] args) {
Function<Integer,String[]> fun1 = (len)->{
return new String[len];
};
String[] a1 = fun1.apply(3);
System.out.println("数组的长度是:" + a1.length);
// 方法引用 的方式来调用数组的构造器
Function<Integer,String[]> fun2 = String[]::new;
String[] a2 = fun2.apply(5);
System.out.println("数组的长度是:" + a2.length);
}
```
小结:方法引用是对Lambda表达式符合特定情况下的一种缩写方式,它使得我们的Lambda表达式更加的精简,也可以理解为lambda表达式的缩写形式,不过要注意的是方法引用只能引用已经存在的方法。
6.Optional
6.1Optional作用和特点
Optional类是 Java 8 引入的一个很有趣的特性。它主要解决的问题是臭名昭著的空指针异常(NullPointerException)
它是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,其中就有对空指针的处理,这样我们就不用显式进行空值检测。
Optional 是一个对象容器,具有以下两个特点:
1提示用户要注意该对象有可能为null
2简化if else代码
举一个简单的例子,在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException:
用户 -> 家庭住址 -> 城市 ->邮编 String postCode = user.getAddress().getCity().getPostCode();
在这个示例中,为了避免异常,就得在访问每一个值之前对其进行明确地检查:
if (user != null) {
Address address = user.getAddress();
if (address != null) {
City city= address.getCity();
if (city != null) {
String postCode = city.getPostCode();
if (postCode != null) {
//对postCode进行操作
test(postCode);
}
}
}
}
这很容易就变得冗长,难以维护。
为了简化这个过程,我们就可以用 Optional 类。
6.2 Optional的使用
1.创建 2.获取 3.判断 4.空指针处理
1.创建
Optional类的实例创建有三种方式:
Optional.empty() :创建一个空的 Optional 实例。
Optional.of(T t) :创建一个 Optional 实例,当 t为null时抛出异常(NullPointerException)。所以就不要Optional.of(null)了
Optional.ofNullable(T t) :创建一个 Optional 实例,但当 t为null时不会抛出异常,而是返回一个空的实例。
2. 获取
- get():获取optional实例中的对象,当optional 容器为空时报错。所以获取前一定要判断不为空。
3.判断
-
isPresent():判断optional是否为空,如果空则返回false,否则返回true
-
ifPresent(Consumer c):如果optional不为空,则程序往下走,将optional中的对象传给Comsumer函数,反之,fPresent中的代码不运行
package com.example.demo; import java.util.Optional; public class LambdaDemo { public static void main(String[] args) { User user1 = new User("张三"); User user2=null ; Optional<User> optionalUser1 = Optional.of(user1); Optional<User> userOptional2 = Optional.ofNullable(user2); System.out.println("optionalUser1.isPresent() = " + optionalUser1.isPresent()); optionalUser1.ifPresent(user -> System.out.println("user.getName() = " + user.getName())); System.out.println("---------------- "); userOptional2.ifPresent(user -> System.out.println("user.getName() = " + user.getName())); } } class User{ private String name="默认名称"; public String getName() { return name; } public User(String name) { this.name = name; } }
运行结果:
4.空指针处理
- 这个才是Optional存在的意义,核心所在
- orElse() 和 orElseGet()
- orElseThrow(Supplier exception):如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
7.stream
7.1 概述
Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象! Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工 处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
7.2 流处理的三阶段
创建,将集合装换为流(List 或者 Array)
处理,对流中数据进行各种处理
收集,将处理后的流转换为所需类型结构的数据
并行流的线程安全问题
1.创建
List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
2.处理
2.1流处理-- 遍历并筛选出需要的数据
stream.filter 、stream.match
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
Stream<Integer> large3 = list.stream().filter(e -> e > 3);
boolean b = list.stream().anyMatch(e -> e > 3);
boolean b1 = list.stream().allMatch(e -> e > 2);
boolean b2 = list.stream().noneMatch(e -> e > 100);
2.2 流处理-- 获取最大、最小的元素
stream.max、stream.min
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
Optional<Person> max = personList.stream().max(Comparator.comparingInt(Person::getSalary));
System.out.println("员工工资最大值:" + max.get().getSalary());
}
}
2.3 流处理-- 流中元素的转换
map:将流中每一个元素通过某种相同的算法,转换为另一个形态,但结构上没变,一个元素对应还是一个元素
flatMap:将流中每一个元素通过某种相同的算法,转换为另一个形态,但一个元素对应已经不是一个元素,而是另一种数据结构
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
// 不改变原来员工集合的方式
List<Person> personListNew = personList.stream().map(person -> {
Person personNew = new Person(person.getName(), 0, 0, null, null);
personNew.setSalary(person.getSalary() + 10000);
return personNew;
}).collect(Collectors.toList());
Stream.of("1", "2", "3", "4", "5", "6", "7").map(Integer::parseInt);
Stream.of("1", "2", "3", "4", "5", "6", "7").map(e -> Integer.parseInt(e));
public static void main(String[] args) {
Stream<String> stream = Stream.of("h-e-l-l-o", "w-o-r-l-d");
List<String> collect = stream.flatMap(e -> {
return Stream.of(e.split("-"));
}).collect(Collectors.toList());
System.out.println("collect = " + collect);
}
mapToInt 、mapToDouble、mapToLong 、flatToInt、flatToLong、flatToDouble 。。。。。。
这些用法上和对应的map 或flatMap 一样,不同的只是数据最后作了转换。
2.4 流处理-- reduce 规约:将流中数据缩减为一个数据
能实现对集合求和、求乘积和求最值操作。
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
// 求和方式1
Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
// 求和方式2
Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
// 求和方式3
Integer sum3 = list.stream().reduce(0, Integer::sum);
// 求乘积
Optional<Integer> product = list.stream().reduce((x, y) -> x * y);
// 求最大值方式1
Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
// 求最大值写法2
Integer max2 = list.stream().reduce(1, Integer::max);
System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);
System.out.println("list求积:" + product.get());
System.out.println("list求和:" + max.get() + "," + max2);
}
}
map和reduce组合
在实际开发中我们经常会将map和reduce一块来使用
public static void main(String[] args) {
// 1.求出所有年龄的总和
Integer sumAge = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).map(Person::getAge) // 实现数据类型的转换
.reduce(0, Integer::sum);
System.out.println(sumAge);
// 2.求出所有年龄中的最大值
Integer maxAge = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).map(Person::getAge) // 实现数据类型的转换,符合reduce对数据的要求
.reduce(0, Math::max); // reduce实现数据的处理
System.out.println(maxAge);
// 3.统计 字符 a 出现的次数
Integer count = Stream.of("a", "b", "c", "d", "a", "c", "a")
.map(ch -> "a".equals(ch) ? 1 : 0)
.reduce(0, Integer::sum);
System.out.println(count);
}
2.5 流处理-- 计数
stream.count()
2.6 流处理-- 排序
package org.guanmi.common.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Auther: cuitao
* @Date: 2023/6/15 - 06 - 15 - 9:25
* @Description: org.guanmi.common.utils
*/
public class StreamDemo {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
personList.add(new Person("Lily", 8800, 26, "male", "New York"));
personList.add(new Person("Alisa", 9000, 26, "female", "New York"));
List<Person> list =
personList.stream().sorted(Comparator.comparing(Person::getSalary)).collect(Collectors.toList());
System.out.println("list = " + list);
List<Person> collect =
personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()).collect(Collectors.toList());
System.out.println("collect = " + collect);
// 先按工资再按年龄自然排序(从小到大)
List<Person> newList3 =
personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
.sorted(Comparator.comparing(Person::getAge)).collect(Collectors.toList());
}
}
@Data
@AllArgsConstructor
class Person {
private String name;
private Integer salary;
private Integer age;
private String sex;
private String address;
}
2.7 流处理-- 合并两个流
stream.concat()
public static void main(String[] args) {
Stream<Integer> integerStream1 = Stream.of(1, 3, 4, 5, 6);
Stream<Integer> integerStream2 = Stream.of(1, 2, 3, 5, 6);
Stream<Integer> concatStream = Stream.concat(integerStream1, integerStream2);
System.out.println("concatStream.collect(Collectors.toList()) = " + concatStream.collect(Collectors.toList()));
}
2.8 流处理-- 数据去重
stream.distinct
public static void main(String[] args) {
Stream<Integer> integerStream1 = Stream.of(1, 3, 4, 5, 6);
Stream<Integer> integerStream2 = Stream.of(1, 2, 3, 5, 6);
Stream<Integer> concatStream = Stream.concat(integerStream1, integerStream2);
List<Integer> collect = concatStream.distinct().collect(Collectors.toList());
System.out.println("collect = " + collect);
}
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
personList.add(new Person("Lily", 8800, 26, "male", "New York"));
personList.add(new Person("Alisa", 9000, 26, "female", "New York"));
personList.add(new Person("Alisa", 9000, 26, "female", "New York"));
personList.add(new Person("Alisa", 9000, 26, "female", "New York"));
List<String> collect = personList.stream().distinct().map(Person::getName).collect(Collectors.toList());
System.out.println("collect = " + collect);
List<String> nameList = collect.stream().distinct().collect(Collectors.toList());
System.out.println("nameList = " + nameList);
}
java8 List根据某个字段去重_丿乐灬学的博客-CSDN博客
1、使用toCollection和TreeSet去重
TreeSet内部使用的是TreeMap,使用指定Comparator比较元素,如果元素相同,则新元素代替旧元素。List<TalentPlanStudentEntity> studentList = relatePlanStudentList.stream()
.collect(Collectors.collectingAndThen(Collectors.toCollection(
() -> new TreeSet<>(Comparator.comparing(TalentPlanStudentEntity::getUserId))), ArrayList::new));
1
2
3
4
5
2、使用Collectors.toMap去重
Collectors.toMap需要使用三个参数的版本,前两个参数一个是keyMapper函数一个是valueMapper函数的,第三个参数BinaryOperator函数接口。BinaryOperator函数接收两个参数,一个oldValue,一个newValue。用于当key重复时的数据处理List<TalentPlanStudentEntity> studentList = new ArrayList<>(relatePlanStudentList.stream()
.collect(Collectors.toMap(TalentPlanStudentEntity::getUserId, Function.identity(), (oldValue, newValue) -> oldValue))
.values());1
2
3
4
例子
List<User> userList = new ArrayList<>();
userList.add(new User("1","李大锤","23","南京"));
userList.add(new User("2","张无忌","18","西安"));
userList.add(new User("3","刘德华","26","苏州"));
userList.add(new User("4","郭靖","33","上海"));
userList.add(new User("1","李大锤","23","南京")); //id相同,其他数据也相同
userList.add(new User("3","带头大哥","36","杭州")); //id相同,其他数据不同
System.out.println(userList);
//根据userid去重
userList = userList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User :: getUserid))), ArrayList::new));
3.收集
搞一个流的收集,我理解是,1毕竟是流,我们是直观看不到,需要转换为程序员可识别的数据格式,常见的就是各种集合,基本数据类型。2.流的创建来源于集合,数组,经过流处理的过程后,最终也要转换为集合或数组,从哪里来到哪里去。
首先要着重强调一下,以为我之前一直在这里了迷糊着,收集的表示是stream.collect(),收集成什么类型的数据结构是有collect()中的固定搭配Collectors类来决定的。下面将常用的收集类型举例说明。
3.1收集为set集合
3.2收集为List集合
3.3收集为Map集合
要注意的两点,1map的key一定要具有唯一性,2.map的值的类型,根据自己需求写
personList.add(new Person(1L,"Sherry", 9000, 24, "female", "New York"));
personList.add(new Person(2L,"Tom", 8900, 22, "male", "Washington"));
personList.add(new Person(3L,"Jack", 9000, 25, "male", "Washington"));
personList.add(new Person(4L,"Lily", 8800, 26, "male", "New York"));
personList.add(new Person(5L,"Alisa", 9000, 26, "female", "New York"));
personList.add(new Person(6L,"Alisa", 9000, 26, "female", "New York"));
personList.add(new Person(7L,"Alisa", 9000, 26, "female", "New York"));
Set<Person> personSet = personList.stream().collect(Collectors.toSet());
List<Person> per = personList.stream().collect(Collectors.toList());
Map<Long, Person> stringPersonMap = personList.stream().collect(Collectors.toMap(Person::getId, item -> item));
Map<Long, String> stringPersonNameMap = personList.stream().collect(Collectors.toMap(Person::getId, item -> item.getName()));
3.4求和
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
// 求总数
Long count = personList.stream().collect(Collectors.counting());
3.5求平均值
// 求平均工资
Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
3.6收集成分组的形式
personList.add(new Person(1L, "Sherry", 9000, 24, "female", "New York"));
personList.add(new Person(2L, "Tom", 8900, 22, "male", "Washington"));
personList.add(new Person(3L, "Jack", 9000, 25, "male", "Washington"));
personList.add(new Person(4L, "Lily", 8800, 26, "male", "New York"));
personList.add(new Person(5L, "Alisa", 9000, 26, "female", "New York"));
personList.add(new Person(6L, "Alisa", 9000, 26, "female", "New York"));
personList.add(new Person(7L, "Alisa", 9000, 26, "female", "New York"));
Map<String, List<Person>> nameGroup1 = personList.stream().collect(Collectors.groupingBy(p -> p.getSex()));
Map<String, List<Person>> nameGroup2 = personList.stream().collect(Collectors.groupingBy(Person::getName));
Map<Boolean, List<Person>> salaryGroup = personList.stream().collect(Collectors.groupingBy(p -> p.getSalary() > 8000));
//先按性别分组,再按地址分组
Map<String, Map<String, List<Person>>> collect1 = personList.stream().collect(Collectors.groupingBy(p -> p.getSex(), Collectors.groupingBy(p2 -> p2.getAddress())));
Map<String, Map<String, List<Person>>> collect2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getAddress)));
System.out.println("collect2 = " + collect2);
collect2 =
{female={New York=[com.example.demo.Person@7c3df479, com.example.demo.Person@7106e68e, com.example.demo.Person@7eda2dbb, com.example.demo.Person@6576fe71]},
male={New York=[com.example.demo.Person@76fb509a], Washington=[com.example.demo.Person@300ffa5d, com.example.demo.Person@1f17ae12]}}
3.7收集为以某个字符隔开的字符串
List<Person> personList = new ArrayList<Person>();
personList.add(new Person(1L, "Sherry", 9000, 24, "female", "New York"));
personList.add(new Person(2L, "Tom", 8900, 22, "male", "Washington"));
personList.add(new Person(3L, "Jack", 9000, 25, "male", "Washington"));
personList.add(new Person(4L, "Lily", 8800, 26, "male", "New York"));
personList.add(new Person(5L, "Alisa", 9000, 26, "female", "New York"));
personList.add(new Person(6L, "Alisa", 9000, 26, "female", "New York"));
personList.add(new Person(7L, "Alisa", 9000, 26, "female", "New York"));
String nameJoining = personList.stream().map(Person::getName).collect(Collectors.joining("---"));
System.out.println("nameJoining = " + nameJoining);
7.3并行流的线程安全问题
针对并行流的线程安全问题,我们的解决方案有哪些呢?
-
加同步锁
-
使用线程安全的容器
-
通过Stream中的toArray/collect操作
/** * 加同步锁 */ @Test public void test02(){ List<Integer> listNew = new ArrayList<>(); Object obj = new Object(); IntStream.rangeClosed(1,1000) .parallel() .forEach(i->{ synchronized (obj){ listNew.add(i); } }); System.out.println(listNew.size()); } /** * 使用线程安全的容器 */ @Test public void test03(){ Vector v = new Vector(); Object obj = new Object(); IntStream.rangeClosed(1,1000) .parallel() .forEach(i->{ synchronized (obj){ v.add(i); } }); System.out.println(v.size()); } /** * 将线程不安全的容器转换为线程安全的容器 */ @Test public void test04(){ List<Integer> listNew = new ArrayList<>(); // 将线程不安全的容器包装为线程安全的容器 List<Integer> synchronizedList = Collections.synchronizedList(listNew); Object obj = new Object(); IntStream.rangeClosed(1,1000) .parallel() .forEach(i->{ synchronizedList.add(i); }); System.out.println(synchronizedList.size()); } /** * 我们还可以通过Stream中的 toArray方法或者 collect方法来操作 * 就是满足线程安全的要求 */ @Test public void test05(){ List<Integer> listNew = new ArrayList<>(); Object obj = new Object(); List<Integer> list = IntStream.rangeClosed(1, 1000) .parallel() .boxed() .collect(Collectors.toList()); System.out.println(list.size()); }
8.新时间日期API
8.1.旧版日期时间的问题
在旧版本中JDK对于日期和时间这块的时间是非常差的。
/** * 旧版日期时间设计的问题 */ @Test public void test01() throws Exception{ // 1.设计不合理 Date date = new Date(2021,05,05); System.out.println(date); // 2.时间格式化和解析操作是线程不安全的 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); for (int i = 0; i < 50; i++) { new Thread(()->{ // System.out.println(sdf.format(date)); try { System.out.println(sdf.parse("2021-05-06")); } catch (ParseException e) { e.printStackTrace(); } }).start(); } }
8.2新日期时间API介绍
JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包 中,下面是一些关键类。
LocalDate :表示日期,包含年月日,格式为 2019-10-16
LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
DateTimeFormatter :日期时间格式化类。
Instant:时间戳,表示一个特定的时间瞬间。
Duration:用于计算2个时间(LocalTime,时分秒)的距离
Period:用于计算2个日期(LocalDate,年月日)的距离
ZonedDateTime :包含时区的时间
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366 天。此外Java 8还提供了4套其他历法,分别是:
ThaiBuddhistDate:泰国佛教历
MinguoDate:中华民国历
JapaneseDate:日本历
HijrahDate:伊斯兰历
8.2.1 日期操作LocalDate
// 1.创建指定的日期
LocalDate date1 = LocalDate.of(2021, 05, 06);
System.out.println("date1 = "+date1); //date1 = 2021-05-06
// 2.得到当前的日期
LocalDate now = LocalDate.now();
System.out.println("now = "+now); //now = 2023-06-18
// 3.根据LocalDate对象获取对应的日期信息
System.out.println("年:" + now.getYear());//年:2023
System.out.println("月:" + now.getMonth().getValue()); //月:6
System.out.println("日:" + now.getDayOfMonth());//日:18
System.out.println("星期:" + now.getDayOfWeek().getValue());//星期:7
8.2.2 时间操作LocalTime
// 1.得到指定的时间
LocalTime localTime = LocalTime.of(12, 10, 03, 10000);
System.out.println("精确到纳秒 = " + localTime); //localTime = 12:10:03.000010
LocalTime time = LocalTime.of(12, 12, 12);
System.out.println("精确到秒= " + time);
// 2.获取当前的时间
LocalTime now = LocalTime.now();
System.out.println("now = " + now);//now = 07:27:00.135303700
// 3.获取时间信息
System.out.println("now.getHour() = " + now.getHour());//now.getHour() = 7
System.out.println("now.getMinute() = " + now.getMinute());//now.getMinute() = 28
System.out.println("now.getSecond() = " + now.getSecond());//now.getSecond() = 15
System.out.println("now.getNano() = " + now.getNano());//now.getNano() = 161836100
8.2.3 日期时间类型 LocalDateTime
public static void main(String[] args) {
// 获取指定的日期时间
LocalDateTime localDateTime = LocalDateTime.of(2023, 06, 18, 07, 31, 00, 100);
System.out.println("localDateTime = " + localDateTime); // localDateTime = 2023-06-18T07:31:00.000000100
// 获取当前的日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);// now = 2023-06-18T07:33:11.081956500
// 获取日期时间信息
System.out.println("localDateTime.getYear() = " + localDateTime.getYear());// localDateTime.getYear() = 2023
System.out.println("localDateTime.getDayOfYear() = " + localDateTime.getDayOfYear());// localDateTime.getDayOfYear() = 169
System.out.println("localDateTime.getMonth() = " + localDateTime.getMonth());// localDateTime.getMonth() = JUNE
System.out.println("localDateTime.getDayOfMonth() = " + localDateTime.getDayOfMonth());// localDateTime.getDayOfMonth() = 18
System.out.println("localDateTime.getDayOfWeek() = " + localDateTime.getDayOfWeek());// localDateTime.getDayOfWeek() = SUNDAY
System.out.println("localDateTime.getHour() = " + localDateTime.getHour());// localDateTime.getHour() = 7
System.out.println("localDateTime.getMinute() = " + localDateTime.getMinute());// localDateTime.getMinute() = 31
System.out.println("localDateTime.getSecond() = " + localDateTime.getSecond());// localDateTime.getSecond() = 0
System.out.println("localDateTime.getNano() = " + localDateTime.getNano());// localDateTime.getNano() = 100
}
8.3 日期时间的修改和比较
// 获取指定的日期时间
LocalDateTime localDateTime = LocalDateTime.of(2023, 06, 18, 07, 31, 00, 100);
System.out.println("localDateTime = " + localDateTime); // localDateTime = 2023-06-18T07:31:00.000000100
// 修改日期时间,只是复制了一份,并不影响原有对象数据
LocalDateTime updateLocalDateTime = localDateTime.withYear(1998);
System.out.println(localDateTime.hashCode() == localDateTime.hashCode());// true
System.out.println(localDateTime.hashCode() == updateLocalDateTime.hashCode());// false ,课件不是一个对象
System.out.println("updateLocalDateTime = " + updateLocalDateTime);// updateLocalDateTime = 1998-06-18T07:31:00.000000100
System.out.println("localDateTime.withMinute(8) = " + localDateTime.withMinute(8));
System.out.println(localDateTime.withDayOfMonth(6));// 2023-06-06T07:31:00.000000100
System.out.println("localDateTime.withMinute(10) = " + localDateTime.withMinute(10));// 2023-06-18T07:10:00.000000100
// 在当前的日期时间基础上加上或者减去指定的时间
LocalDateTime localDateTime1 = localDateTime.plusYears(2);
System.out.println(+localDateTime.hashCode() == localDateTime1.hashCode());// false,可见是以之前的为模板创建了对象
System.out.println("两年后 = " + localDateTime1);// 两年后 = 2025-06-18T07:31:00.000000100
System.out.println("五天后 = " + localDateTime.plusDays(5));// 五天后 = 2023-06-23T07:31:00.000000100
System.out.println("十年前= " + localDateTime.minusYears(10));//十年前= 2013-06-18T07:31:00.000000100
System.out.println("5分钟前 = " + localDateTime.minusMinutes(5));//5分钟前 = 2023-06-18T07:26:00.000000100
System.out.println("两周前 = " + localDateTime.minusWeeks(2));//两周前 = 2023-06-04T07:31:00.000000100
// 日期时间比较
LocalDateTime localDateTimeBefore = LocalDateTime.of(2023, 06, 18, 07, 31, 00, 100);
LocalDateTime localDateTimeAfter = LocalDateTime.of(2033, 06, 18, 07, 31, 00, 100);
System.out.println(localDateTimeBefore.isEqual(localDateTimeAfter));//false
System.out.println(localDateTimeBefore.isBefore(localDateTimeAfter));//true
System.out.println(localDateTimeBefore.isAfter(localDateTimeAfter));//false
注意:在进行日期时间修改的时候,原来的LocalDate对象是不会被修改,每次操作都是返回了一个新的LocalDate对象,所以在多线程场景下是数据安全的。
8.4格式化和解析操作
package com.example.demo;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @Auther: cuitao
* @Date: 2023/6/15 - 06 - 15 - 9:25
* @Description: org.guanmi.common.utils
*/
public class LambdaDemo {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
// 指定格式 使用系统默认的格式 2021-05-27T16:16:38.139
DateTimeFormatter isoLocalDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
// 将日期时间转换为字符串
String format = now.format(isoLocalDateTime);
System.out.println("format = " + format);
// 通过 ofPattern 方法来指定特定的格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format1 = now.format(dateTimeFormatter);
// 2021-05-27 16:16:38
System.out.println("format1 = " + format1);
// 将字符串解析为一个 日期时间类型
LocalDateTime parse = LocalDateTime.parse("1997-05-06 22:45:16", dateTimeFormatter);
// parse = 1997-05-06T22:45:16
System.out.println("parse = " + parse);
}
}
8.5 Instant类
在JDK8中给我们新增一个Instant类(时间戳/时间线),内部保存了从1970年1月1日 00:00:00以来的秒和纳秒
/**
* Instant 时间戳
* 可以用来统计时间消耗
*/
@Test
public void test01() throws Exception{
Instant now = Instant.now();
System.out.println("now = " + now);
// 获取从1970年一月一日 00:00:00 到现在的 纳秒
System.out.println(now.getNano());
Thread.sleep(5);
Instant now1 = Instant.now();
System.out.println("耗时:" + (now1.getNano() - now.getNano()));
}
8.6 计算日期时间差
JDK8中提供了两个工具类Duration/Period:计算日期时间差
-
Duration:用来计算两个时间差(LocalTime)
-
Period:用来计算两个日期差(LocalDate)
// 计算时间差
LocalTime localTime1 = LocalTime.of(11, 12, 30);
LocalTime localTime2 = LocalTime.of(10, 10, 30);
System.out.println("localTime1 = " + localTime1);//localTime1 = 11:12:30
Duration duration = Duration.between(localTime1, localTime2);
System.out.println("duration.toDays() = " + duration.toDays());//duration.toDays() = 0
System.out.println("duration.toHours() = " + duration.toHours());//duration.toHours() = -1
System.out.println("duration.toMinutes() = " + duration.toMinutes());//duration.toMinutes() = -62
System.out.println("duration.toSeconds() = " + duration.toSeconds());//duration.toSeconds() = -3720
//计算日期差
LocalDate localDate1 = LocalDate.of(2020, 11, 1);
LocalDate localDate2= LocalDate.of(2022, 12, 1);
Period period = Period.between(localDate1, localDate2);
System.out.println("period.getYears() = " + period.getYears());//period.getYears() = 2
System.out.println("period.getMonths() = " + period.getMonths());//period.getMonths() = 1
System.out.println("period.getDays() = " + period.getDays());//period.getDays() = 0
LocalDateTime localDateTime1 = LocalDateTime.of(2022, 10, 10, 10, 10, 10);
LocalDateTime localDateTime2 = LocalDateTime.of(2023, 10, 10, 10, 10, 10);
Duration between1 = Duration.between(localDateTime1, localDateTime2);
System.out.println("between1.toDays() = " + between1.toDays());//365
8.7日期时间的时区
Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。 其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。 ZoneId:该类中包含了所有的时区信息
// 1.获取所有的时区id
// ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 获取当前时间 中国使用的 东八区的时区,比标准时间早8个小时
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // 2021-05-27T17:17:06.951
// 获取标准时间
ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
System.out.println("bz = " + bz); // 2021-05-27T09:17:06.952Z
// 使用计算机默认的时区,创建日期时间
ZonedDateTime now1 = ZonedDateTime.now();
System.out.println("now1 = " + now1); // 2021-05-27T17:17:06.952+08:00[Asia/Shanghai]
// 使用指定的时区创建日期时间
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Marigot"));
System.out.println("now2 = " + now2);//now2 = 2023-06-17T23:46:01.867460100-04:00[America/Marigot]