Java8新特性
全网详解(波哥)Java8新特性(Lambda、Stream、LocalDate等)新特性
一、Lambda表达式
1.需求分析
创建一个新的线程,指定线程要执行的任务
public static void main(String[]args){
// 开启一个新的线程
// new Runnable() 匿名内部类
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("新线程中执行的代码:"+Thread.currentThread().getName());
}
}).start();
System.out.println("主线程中的代码:"+Thread.currentThread().getName());
}
2.代码分析:
1.Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
2.为了指定run方法体,不得不需要Runnable的实现类
3.为了省去定义一个Runnable的实现类,不得不使用匿名内部类
4.必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错
5.实际上,我们只在乎方法体上的代码
3.Lambda表达式初体验
Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码
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());
System.out.println("------------------------");
new Thread(() -> System.out.println("新线程Lamdba表达式"+ Thread.currentThread().getName());}).start();
}
Lambda表达式的优点:简化了匿名内部类的使用,语法更加简单。
匿名内部类语法冗余,体验了Lambda表达式后,发现Lambda表达式是简化匿名内部类的一种方式。
4.Lambda的语法规则
Lambda省去了面向对象的条条框框,标准格式由3个部分组成:
(参数类型 参数名称) -> {
代码体;
}
格式说明:
- (参数类型 参数名称):参数列表
- {代码体;}:方法体
- ->:箭头,分割参数列表和方法体
4.1 Lambda练习1
练习无参无返回值的Lambda
定义一个接口
package com.king.service;
public interface UserService {
void show() ;
}
创建主方法使用
package com.king;
import com.king.service.UserService;
public class Thread_Demo {
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方法执行了。。。。。");
});
}
public static void goShow(UserService userService){
userService.show();
}
}
输出:
show方法执行了。。。。。。
------------------
Lambda方法执行了。。。。。
4.2 Lambda练习2
完成一个有参且有返回值的Lambda表达式案例
创建一个Person对象
package com.king.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private String heigt;
}
创建主方法启动
package com.king;
import com.king.pojo.Person;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class List_Demo {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("巢瘠薄",22,"175"));
list.add(new Person("曹瘠薄",25,"185"));
list.add(new Person("冯小狗",32,"187"));
list.add(new Person("方鸿俊",21,"176"));
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
list.forEach(System.out::println);
System.out.println("------------------");
}
@Test
public void test_Lambda(){
List<Person> list = new ArrayList<>();
list.add(new Person("巢瘠薄",22,"175"));
list.add(new Person("曹瘠薄",25,"185"));
list.add(new Person("冯小狗",32,"187"));
list.add(new Person("方鸿俊",21,"176"));
list.sort((Person p1, Person p2) -> {
return p1.getAge() - p2.getAge();
});
list.forEach(System.out::println);
}
}
输出;
Person(name=方鸿俊, age=21, heigt=176)
Person(name=巢瘠薄, age=22, heigt=175)
Person(name=曹瘠薄, age=25, heigt=185)
Person(name=冯小狗, age=32, heigt=187)
5.@FunctionalInterface注解
/**
* @FunctionalInterface
* 这是一个标志注解,被该注解修饰的接口只能声明一个抽象方法
*/
@FunctionalInterface
public interface UserService {
void show();
}
6.Lambda表达式的原理
匿名内部类的本质是在编译时生成一个Class文件---Xxxx$1().class
Lambda表达式在程序运行的时候会形成一个类。
1.在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码
2.还会形成一个匿名内部类,实现接口,重写实现方法
3.在借口中重写方法会调用新生成的方法
7.Lambda表达式的省略写法 简写
在Lambda表达式的标准写法基础上,可以使用省略写法的规则为:
1.小括号内的参数类型可以省略
2.如果小括号内有且仅有一个参数,则小括号可以省略
3.如果大括号内有且仅有一个语句,可以同时省略大括号,return关键字及语句分号
package com.king;
import com.king.service.OrderSercice;
import com.king.service.StudentService;
public class Simplify_Demo {
public static void main(String[] args) {
goStudent((String name,Integer age)->{
System.out.println(name+age);
return name+age+"666..";
});
System.out.println("--------------");
//省略写法
String s = goStudent((name, age) -> name + age + "6666.。。");
System.out.println(s);
System.out.println("------------------------");
Integer i = goOrder((String name) -> {
System.out.println("---->" + name);
return 6666;
});
System.out.println(i);
System.out.println("------------------------");
goOrder(name -> {
System.out.println(name);
return 666;
});
}
public static String goStudent(StudentService studentService){
return studentService.show("张三",1231);
}
public static Integer goOrder(OrderSercice orderSercice){
return orderSercice.show("小明");
}
}
8.Lambda表达式的使用前提
Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意
1.方法的参数或者局部变量类型必须为接口才能使用
2.接口中有且仅有一个抽象方法 @FunctionalInterface
9.Lambda和匿名内部类的对比
1.所需类型不一样
a.匿名内部类的类型可以是类、抽象类、接口
b.Lambda表达式需要的类型必须是接口
2.实现方法数量不一样
a.匿名内部类所需的接口中抽象方法的数量是随意的
b.Lambda表达式所需的接口中只能有一个抽象方法
3.实现原理不一样
a.匿名内部类是在变以后形成一个class文件
b.Lamb表达式是在程序运行的时候动态生成class
二、接口中新增的方法
1.JDK8中接口的新增
在jdk中正对接口有做增强,在jdk8之前
interface 接口名{
静态常量;
抽象方法;
}
在jdk8之后对接口做出了增强,接口中可以有默认方法和静态方法
interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法;
}
2.默认方法
2.1 为什么要增强默认方法
在jdk8以前接口中只能有抽象方法和静态常量,会存在一下的问题
如果接口中新增抽象方法,那么实现类都必须要抽象这个抽象方法,非常不利于接口的扩展
package com.king.inter;
public interface Demo01Interface {
public static void main(String[] args) {
A b = new B();
A c = new C();
}
}
interface A{
void test1();
// 接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
//void test2();
}
class B implements A{
@Override
public void test1() {
}
}
class C implements A{
@Override
public void test1() {
}
}
2.2 接口默认方法的格式
接口中默认方法的语法格式是:
interface 接口名{
修饰符 default 返回值类型 方法名{
方法体;
}
}
2.3 接口中默认方法的使用
package com.king.inter;
public interface Demo01Interface {
public static void main(String[] args) {
A b = new B();
b.test3();
A c = new C();
c.test3();
}
}
interface A{
void test1();
// 接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
//void test2();
public default String test3(){
System.out.println("接口中的默认方法执行了。。。。");
return "hello";
}
}
class B implements A{
@Override
public void test1() {
}
@Override
public String test3() {
System.out.println("B 实现类中重写了A的默认方法");
// return A.super.test3(); //接口中的默认方法执行了。。。。
return "ok...";
}
}
class C implements A{
@Override
public void test1() {
}
}
接口中默认方法有两种的使用方式
- 实现类的默认方法用接口的默认方法
- 实现类重写接口的默认方法
3.静态方法
JDk8中为接口新增了静态方法,作用也是为了接口的扩展
3.1语法规则
interface 接口名{
修饰符 static 返回值类型 方法名{
方法体;
}
}
package com.king.inter;
public interface Demo01Interface {
public static void main(String[] args) {
A b = new B();
b.test3();
A.test4();
System.out.println("------------------");
A c = new C();
c.test3();
}
}
interface A{
void test1();
// 接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
//void test2();
public default String test3(){
System.out.println("接口中的默认方法执行了。。。。");
return "hello";
}
public static String test4(){
System.out.println("接口中的静态方法");
return "hello";
}
}
class B implements A{
@Override
public void test1() {
}
@Override
public String test3() {
System.out.println("B 实现类中重写了A的默认方法");
// return A.super.test3(); //接口中的默认方法执行了。。。。
return "ok...";
}
}
class C implements A{
@Override
public void test1() {
}
}
3.2 静态方法的使用
接口中的静态方法在实现类中是不能被重写的,调用的话只能通过接口类型来实现:接口名.静态方法名();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ha6QaZDB-1661781169548)(img/image-20220804152419586.png)]
4.两者的区别
- 默认方法通过实例调用,静态方法通过接口名调用
- 默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口方法
- 静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用
三、函数式接口
+++
1.函数式接口的由来
+++
我们知道使用lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型,因此,为了让我们使用Lambda表达式更加的方便,在JDK中提供了大量常用的函数式接口
package com.king.fun;
public class Demo01Fun {
public static void main(String[] args) {
fun1((arr) -> {
int sum = 0;
for (int i : arr) {
sum += arr[i];
}
return sum;
});
}
public static void fun1(Operator operator) {
int[] arr = {1, 2, 3, 4};
int sum = operator.getSum(arr);
System.out.println("sum=" + sum);
}
}
/*
* 函数式接口
* */
interface Operator {
int getSum(int[] arr);
}
2.函数式接口介绍
+++
在jdk中帮我们提供的函数式接口,主要是在java.util.function包中
2.1 Supplier
无参有返回值的接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
使用:
package com.king.fun;
import java.util.Arrays;
import java.util.function.Supplier;
/*
* Suplier 函数式接口的使用
* */
public class SupplierTest {
public static void main(String[] args) {
fun1(()->{
int arr[]={22,33,44,55,12,312};
//计算出数组的最大值
Arrays.sort(arr);
return arr[arr.length-1];
});
}
public static void fun1(Supplier<Integer> supplier) {
//get()是一个无参有返回值的抽象方法
Integer max = supplier.get();
System.out.println("max=" + max);
}
}
2.2 Consumer
有参无返回值的接口,前面介绍的supplier接口是用来身缠生产数据的,而Consumer是用来消费数据的,使用的时候需要指定一个泛型来定义参数类型
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
使用:将输入的数据统一的转换为大写输出
package com.king.fun;
import java.util.Locale;
import java.util.function.Consumer;
public class ConsumerTest {
public static void main(String[] args) {
test(msg->{
System.out.println(msg+"->装换为大写"+msg.toUpperCase(Locale.ROOT));
});
}
public static void test(Consumer<String> consumer){
//accept()是有参数无返回类型额接口
consumer.accept("hello,world");
}
}
默认方法:andThen
如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现效果,消费一个数据的时候,首先做一个操作,然后在做一个操作,实现组合,而这个方法的Consumer接口中的default方法 andThen()方法
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
具体操作:
package com.king.fun;
import java.util.Locale;
import java.util.function.Consumer;
public class ConsumerAndThenTest {
public static void main(String[] args) {
test2(msg1->{
System.out.println(msg1+"->装换为小写"+msg1.toLowerCase(Locale.ROOT));
},msg2->{
System.out.println(msg2+"->装换为大写"+msg2.toUpperCase(Locale.ROOT));
});
}
public static void test2(Consumer<String> consumer1, Consumer<String> consumer2){
String str="Hello,Wolrd";
//consumer1.accept(str);//转小写
//consumer2.accept(str);//转大写
//consumer1.andThen(consumer2).accept(str);
consumer2.andThen(consumer1).accept(str);
}
}
2.3 Function
有参有返回值的接口, Function接口是根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件,有参数有返回值。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
使用:传递进去一个字符串,返回一个数字
package com.king.fun;
import java.util.function.Function;
public class FunctionTest {
public static void main(String[] args) {
test(msg->{
return Integer.parseInt(msg);
});
}
public static void test(Function<String, Integer> function) {
Integer apply = function.apply("666");
System.out.println("apply=" + apply);
}
}
默认方法:andThen,也会用来做组合操作
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
具体操作:
package com.king.fun;
import java.util.function.Function;
public class FunctionAndThenTest {
public static void main(String[] args) {
test(msg1 -> {
return Integer.parseInt(msg1);
}, msg2 -> {
return msg2 * 10;
});
}
public static void test(Function<String, Integer> function1, Function<Integer, Integer> function2) {
//Integer apply = function1.apply("666");
//Integer integer = function2.apply(apply);
Integer apply1 = function1.andThen(function2).apply("666");
//Integer apply = function2.apply(function1.apply("666"));
//System.out.println("integer = " + integer);
System.out.println("---------------");
System.out.println("apply= "+apply1);
}
}
默认的compose方法的作用顺序和andThen方法刚好相反
而静态方法的identity则是,输入什么参数就返回值什么参数
2.4 Predicate
有参且返回值为boolean类型的接口
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
使用;
package com.king.fun;
import java.util.function.Predicate;
public class PredicateTest {
public static void main(String[] args) {
test(msg -> {
return msg.length() > 3;
}, "hello");
}
private static void test(Predicate<String> predicate, String msg) {
boolean b = predicate.test(msg);
System.out.println("b:" + b);
}
}
在Predicate中的默认方法提供了逻辑关系操作 and,or,negate,isEqual方法
package com.king.fun;
import java.util.function.Predicate;
public class PredicateDefaultTest {
public static void main(String[] args) {
test(msg1 -> {
return msg1.contains("H");
}, msg2 -> {
return msg2.contains("W");
});
}
private static void test(Predicate<String> p1, Predicate<String> p2) {
//b1 包含H b2包含W
//b2 包含H 同时 p2 包含W
boolean b1 = p1.and(p2).test("Hello");
// p1包含H 或者 p2 包含W
boolean b2 = p1.or(p2).test("Hello");
//p1 不包含H
boolean b3 = p1.negate().test("Hello");
System.out.println(b1);//false
System.out.println(b2);//true
System.out.println(b3);//false
}
}
四、方法引用
1.为什么要用方法引用
1.1 lambda表达式冗余
在使用lambda表达式的时候,也会出现代码冗余的情况,比如:用lambda表达式求一个数组的和
package com.king.funref;
import java.util.function.Consumer;
public class FunctionRefTest01 {
public static void main(String[] args) {
printMax(a -> {
int sum = 0;
for (int i : a) {
sum += i;
}
System.out.println("数组求和:" + sum);
});
}
private static void printMax(Consumer<int[]> consumer) {
int[] a = {10, 20, 30, 40, 50, 60};
consumer.accept(a);
}
}
1.2 解决方案
因为在Lambda表达式中要执行的代码和我们另一个方法中的代码是一样的,这时就没有必要重写一份逻辑了,这时我们就可以“引用重复代码“
:: 方法引用,jdk8中新的语法
package com.king.funref;
import java.util.function.Consumer;
import java.util.function.Function;
public class FunctionRefTest01 {
public static void main(String[] args) {
// :: 方法引用,jdk8中新的语法
printMax(FunctionRefTest01::getSum);
}
/*
* 求数组中所有元素的和
* */
public static void getSum(int[] a){
int sum = 0;
for (int i : a) {
sum += i;
}
System.out.println("数组求和:" + sum);
}
private static void printMax(Consumer<int[]> consumer) {
int[] a = {10, 20, 30, 40, 50, 60};
consumer.accept(a);
}
}
2.方法引用的格式
符号表示:::
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用
应用场景:如果lambda表达式所要实现的方案,已经有其它方法存在相同的方案,那么则可以使用方法引用
常见的应用方式:
方法引用在jdk8中的使用时相当灵活的,有以下几种形式
- InstanceName::methodName 对象::方法名
- ClassName::staticMethodName 类名::静态方法
- ClassName::methodName 类名::普通方法
- ClassName::new 类名::new 调用的构造器
- TypeName[]::new String[]::new 调用数组构造器
2.1 对象名::方法名
这是最常见的一种用法。如果一个类中已经存在了一个成员方法,即可通过类名引用成员方法
package com.king.funref;
import java.util.Date;
import java.util.function.Supplier;
public class FunctionRefTest02 {
public static void main(String[] args) {
Date date = new Date();
Supplier<Long> supplier = () -> {
return date.getTime();
};
System.out.println(supplier.get());
System.out.println("-------------------");
//然后我们通过 方法引用 的方式类处理
Supplier<Long> time = date::getTime;
System.out.println(time.get());
}
}
方法引用的注意事项:
- 被引用的方法,参数要和接口中的抽象方法的参数一致
- 当接口抽象方法有返回值时,被引用的方法也必须有返回值
2.2 类名::静态方法名
也是比较常用的方式
package com.king.funref;
import java.util.function.Supplier;
public class FunctionRefTest03 {
public static void main(String[] args) {
Supplier<Long> supplier = () -> {
return System.currentTimeMillis();
};
System.out.println(supplier.get());
//通过 方法引用 来实现
Supplier<Long> longSupplier= System::currentTimeMillis;
System.out.println(longSupplier.get());
}
}
2.3 类名::引用实例方法
Java面向对象的时候,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者
package com.king.funref;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
public class FunctionRefTest04 {
public static void main(String[] args) {
Function<String,Integer> function=(msg)->{
return msg.length();
};
System.out.println(function.apply("hello"));
//通过 方法引用 来实现
Function<String,Integer> function1=String::length;
System.out.println(function1.apply("hello"));
System.out.println("---------------------------------");
BiFunction<String,Integer,String> biFunction=String::substring;
String msg = biFunction.apply("helloworld", 3);
System.out.println(msg);
}
}
2.4 类名::构造器
由于构造器的名称和类名完全一致,所以构造器引用使用::new的格式使用,
package com.king.funref;
import com.king.pojo.Person;
import com.king.pojo.Student;
import java.util.function.BiFunction;
import java.util.function.Supplier;
public class FunctionRefTest05 {
public static void main(String[] args) {
Supplier<Person> supplier = () -> {
return new Person();
};
System.out.println(supplier.get());
System.out.println("------------");
//通过 方法引用 来实现
Supplier<Person> personSupplier = Person::new;
System.out.println(personSupplier.get());
System.out.println("------------");
BiFunction<String, Integer, Student> biFunction = Student::new;
System.out.println(biFunction.apply("张丹",12));
}
}
2.5 数组::构造器
数组是怎么构造出来的呢?
package com.king.funref;
import com.king.pojo.Person;
import com.king.pojo.Student;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
public class FunctionRefTest06 {
public static void main(String[] args) {
Function<Integer, String[]> function = (len) -> {
return new String[len];
};
String[] apply = function.apply(12);
System.out.println("数组的长度是:" + apply.length);
System.out.println("--------------");
Function<Integer, String[]> function1 = String[]::new;
String[] s = function1.apply(5);
System.out.println("数组的长度是" + s.length);
}
}
小结:方法引用是对Lambda表达式符合特定情况的一种缩写方式,它使得我们的Lambda表达式更加的精简,也可以理解为Lambda表达式的缩写形式,不过要注意的是方法引用只能引用已经存在的方法。
五、Stream API
1.集合处理数据的弊端
当我们在需要对集合中的元素进行操作的时候,除了必需的添加,删除,获取外,最典型的操作是集合的遍历
package com.king.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class StreamTest01 {
public static void main(String[] args) {
//定义一个集合
List<String> list= Arrays.asList("张三","张三丰","周新民");
//1.获取所有姓张的信息
List<String> list1=new ArrayList<>();
for (String s:list){
if (s.startsWith("张")){
list1.add(s);
}
}
//2.获取名称长度为3的用户
List<String> list2=new ArrayList<>();
for (String s : list1) {
if (s.length() == 3) {
list2.add(s);
}
}
//3.输出符合要求的用户信息
for (String s : list2) {
System.out.println(s);
}
}
}
上面的代码针对与我们不同的需求总是一次次的循环循环循环,这时我们希望有更加高效的处理方式,这是我们就可以通过jdk8中提供的Stream API来解决这个问题了。
package com.king.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class StreamTest02 {
public static void main(String[] args) {
//定义一个集合
List<String> list = Arrays.asList("张三", "张三丰", "周新民");
//1.获取所有姓张的信息
//2.获取名称长度为3的用户
//3.输出符合要求的用户信息
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(s -> {
System.out.println(s);
});
System.out.println("---------------");
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
上面的Stream代码的含义:获取流,过滤张,过滤长度,逐一打印。代码相比于上面的案例更加的简洁直观
2.Stream流式思想概述
注意:Stream 和 IO(InputStream/OutStream) 没有任何关系,请暂时忘记对传统IO流的固有影响!Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理,Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品
Stream API能让我们快速完成许多复杂的操作,如筛选,切片,映射,查找,去重,统计,匹配和归约。
3.Stream流的获取方式
3.1 根据Collection获取
首先,java.util.Collection接口中加入了default方法stream。也就是说Collection接口下的所有食安县实现都可以通过Stream方法来获取Stream流
package com.king.stream;
import java.util.*;
public class StreamTest03 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.stream();
Set<String> set=new HashSet<>();
set.stream();
Vector vector=new Vector();
vector.stream();
}
}
但是map接口没有实现Collection接口,那这时怎么办呢?这是我们可以根据Map获取对应的key value集合。
public class StreamTest04 {
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
Stream<String> stream = map.keySet().stream();//key
Stream<Object> stream1 = map.values().stream();//value
Stream<Map.Entry<String, Object>> entryStream = map.entrySet().stream();
}
}
3.2 通过Stream的of方法
在实际开发中,我们不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所有Stream接口中提供了静态方法of
public class StreamTest05 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("a1", "a2", "a3", "a4");
String[] arr1={"b1", "b2", "b3", "b4"};
Stream<String> arr11 = Stream.of(arr1);
Integer[] arr2={1,2,3,4,5};
Stream<Integer> arr21 = Stream.of(arr2);
arr21.forEach(System.out::println);
System.out.println("------------------------");
//注意:基本数据类型是不行的
int[] arr3={1,2,3,4};
Stream.of(arr3).forEach(System.out::println);
}
}
4.Stream常用方法介绍
Stream常用方法
Stream流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分为两种:
方法名 | 方法作用 | 返回值类型 | 方法种类 |
---|---|---|---|
count | 统计个数 | long | 终结 |
forEach | 逐一处理 | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取用前几个 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
终结方法:返回值类型不再是Stream类型的方法,不在支持链式调用。
非终结方法:返回值类型仍然是Stream类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法)
Stream注意事项(重要):
- Stream只能操作一次
- Stream方法返回的是新的流
- Stream不能调用终结方法,中间的操作不会执行
public class StreamTest06{
public static void main(String[] args) {
Stream<String> stream = Stream.of("a1", "a2", "a3", "a4");
stream.filter(s -> {
System.out.println("----------");
return s.contains("a");
}).forEach(System.out::println);
System.out.println("------------");
}
}
4.1 forEach
forEach用来遍历流中的数据
void forEachOrdered(Consumer<? super T> action);
该方法接受一个Consumer接口,会将每一个流元素交给函数处理
public class StreamTest07ForEach {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3", "a4").forEach(System.out::println);
}
}
4.2 count
Stream流中的count方法用来统计其中的元素个数的
long count();
该方法返回一个long值,代表元素个数。
public class StreamTest07Count {
public static void main(String[] args) {
long count = Stream.of("a1", "a2", "a3").count();
System.out.println(count);
}
}
4,3 filter
filter方法的作用是用来过滤数据的,返回符合条件的数据
可以通过filter方法讲一个流转换成另一个子集流
Stream<T> filter(Predicate<? super T> predicate);
该接口接受一个Predicate函数式接口参数作为筛选条件
public class StreamTest07Filter {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3","bb","cc","dd").filter(s -> s.contains("a"))
.forEach(System.out::println);
}
}
输出:
a1
a2
a3
4.4 limit
limit方法可以对流进行截取处理,支取前n个数据
Stream<T> limit(long maxSize);
参数是一个long类型的数值,如果集合长度大于参数就进行截取,否则不操作:
public class StreamTest08Limit {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3", "bb", "cc", "aa", "dd").limit(5)
.forEach(System.out::println);
}
}
输出:
a1
a2
a3
bb
cc
4.5 skip
如果希望跳过前面几个元素,可以使用skip方法获取一个截取之后的新流:
Stream<T> skip(long n);
操作:
public class StreamTest09Skip {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3","bb","cc","aa","dd").skip(3)
.forEach(System.out::println);
}
}
输出:
bb
cc
aa
dd
4.6 map
如果我们需要将流中的元素映射到另一个流中可以使用map方法;
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个Function函数式的接口参数,可以将当前的流中的T类型数据转换为另一种R类型的数据
public static void main(String[] args) {
Stream.of("1", "2", "3","4","5","6","7")
//.map(s -> Integer.parseInt(s))
.map(Integer::parseInt)
.forEach(System.out::println);
}
4.7 sorted
如果需要将数据排序,可以使用sorted方法:
Stream<T> sorted();
在使用的时候可以根据自然规则排序,也可以通过比较器来指定对应的排序规则
public class StreamTest11Sorted {
public static void main(String[] args) {
Stream.of("1", "9", "3","4","22","6","7")
.map(Integer::parseInt)
//.sorted() //根据数据的自然顺序排序
.sorted((o1,o2)->o2-o1) //根据比较指定排序规则
//.sorted(Integer::compareTo)
.forEach(System.out::println) ;
}
}
4.8 distinct
如果要去掉重复的数据,可以使用distinct方法:
Stream<T> distinct();
package com.king.stream;
import com.king.pojo.Person;
import java.util.stream.Stream;
public class StreamTest12Distinct {
public static void main(String[] args) {
Stream.of("1", "9", "3","4","3","6","7")
.distinct() //去掉重复数据
.forEach(System.out::println);
System.out.println("---------------------------------");
Stream.of(
new Person("张三",18,"198"),
new Person("李四",22,"199"),
new Person("张三",18,"167")
).distinct()
.forEach(System.out::println);
}
}
Stream流中的 方法对于基本数据类型是可以直接去重的,但是对于自定义类型,我们是需要重写 hashCode和equals方法来移除重复元素
使用;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private String heigt;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) && Objects.equals(age, person.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
4.9 match
如果需要判断数据是否匹配指定的条件,可以使用match相关的方法
boolean anyMatch(Predicate<? super T> predicate);// 元素是否有任意一个满足条件
boolean allMatch(Predicate<? super T> predicate);// 元素是否都满足条件
boolean noneMatch(Predicate<? super T> predicate);// 元素是否都不满足条件
使用:
public class StreamTest13Match {
public static void main(String[] args) {
boolean b = Stream.of("1", "9", "3", "4", "3", "6", "7")
.map(Integer::parseInt)
//.allMatch(s -> s > 0)
//.anyMatch(s -> s > 4)
.noneMatch(s-> s > 4);
System.out.println(b);
}
}
4.10 find
如果我们需要找到某些元素,可以使用find方法来实现
Optional<T> findFirst();
Optional<T> findAny();
使用:
public class StreamTest14Find {
public static void main(String[] args) {
Optional<String> first = Stream.of("1", "9", "3", "4", "3", "6", "7").findFirst();
System.out.println(first.get());
Optional<String> any = Stream.of("1", "9", "3", "4", "3", "6", "7").findAny();
System.out.println(any.get());
}
}
4.11 max和min
如果我们想要获取最大值和最小值没那么可以使用max和min方法
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
使用:
public class StreamTest15MaxMin {
public static void main(String[] args) {
Optional<Integer> first = Stream.of("1", "9", "3", "4", "3", "6", "7")
.map(Integer::parseInt)
.max((o1,o2)->o1-o2);
System.out.println(first.get());
Optional<Integer> any = Stream.of("1", "9", "3", "4", "3", "6", "7")
.map(Integer::parseInt)
.min(((o1, o2) -> o1-o2));
System.out.println(any.get());
}
}
4.12 reduce方法
如果需要将所有数据归纳得到一个数据,使用reduce方法
T reduce(T identity, BinaryOperator<T> accumulator);
使用:
public class StreamTest16Reduce {
public static void main(String[] args) {
Integer total = Stream.of(4, 5, 3, 9)
// identity是默认值
// 第一次的时候会将默认值赋给x
// 之后每次会将上一次的操作结果赋值给x y就是每次从数据中获取的元素
.reduce(1, (x, y) -> {
System.out.println("x=" + x + ",y=" + y);
return x * y;
});
System.out.println(total);
// 获取最大值
Integer max = Stream.of(4, 5, 3, 9)
.reduce(0, (x, y) -> {
return x > y ? x : y;
});
System.out.println(max);
// 获取最小值
Optional<Integer> reduce2 = Stream.of(4, 5, 3, 9)
.reduce((x, y) -> {
return x < y ? x : y;
});
System.out.println(reduce2.get());
}
}
4.13 map和reduce的组合
在实际开发中,我们经常会将map和reduce组合使用
public class StreamTest17MapReduce {
public static void main(String[] args) {
//求年龄总和
Integer sumAge = Stream.of(
new Person("张三", 18, 198),
new Person("李四", 22, 199),
new Person("张三", 18, 167),
new Person("赵六", 19, 176),
new Person("张三", 33, 180)
).map(Person::getAge)
.reduce(0, Integer::sum);
System.out.println(sumAge);
// 求出年龄最大值
Integer maxAge = Stream.of(
new Person("张三", 18, 198),
new Person("李四", 22, 199),
new Person("张三", 18, 167),
new Person("赵六", 19, 176),
new Person("张三", 33, 180)
).map(Person::getAge)
.reduce(0, Math::max);
System.out.println(maxAge);
//统计 字符 a 出现的次数
Integer count = Stream.of("a", "b", "c", "d", "a", "c", "a")
.map(s -> "a".equals(s) ? 1 : 0)
.reduce(0, Integer::sum);
System.out.println(count);
}
}
4.14 mapToInt
如果需要将Stream中的Integer类型转换成int类型,可以使用mapToInt方法来实现
public class StreamTest18MapToInt {
public static void main(String[] args) {
// Integer占用的内存比int多很多,在stream流操作中会自动拆装箱操作
Integer arr[] = {1,2,3,4,5};
Stream.of(arr)
.filter(integer -> integer > 0)
.forEach(System.out::println);
// 为了提高程序代码的效率, 我们可以现将流中Integer数据转换为int数据,然后操作
Integer arr2[] = {1,2,3,4,5,7,9};
Stream.of(arr2)
.mapToInt(Integer::intValue)
.filter(i -> i > 4)
.forEach(System.out::println);
}
}
4.15 concat
如果有两个流希望合并成为一个流,那么可以使用Stream接口的静态方法concat
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
使用:
public class StreamTest19tConcat {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("a", "b", "c");
Stream<String> stream2 = Stream.of("x", "y", "z");
// 通过concat方法将两个流合并成一个新的流
Stream.concat(stream1,stream2)
.forEach(System.out::println);
}
}
4.16 综合案例
定义两个集合,然后在集合中存储多个用户名称,然后完成如下的操作:
- 第一个队伍只保留姓名长度为3的成员
- 第一个队伍筛选后只要前三个
- 第二个队伍只要姓张的成员
- 第二个队伍筛选之后不要前两个人
- 将两个队伍合并成一个队伍
- 根据姓名创建Person对象
- 打印整个队伍的Person信息
public class StreamTest20Demo {
/*
1. 第一个队伍只保留姓名长度为3的成员
2. 第一个队伍筛选后只要前三个
3. 第二个队伍只要姓张的成员
4. 第二个队伍筛选之后不要前两个人
5. 将两个队伍合并成一个队伍
6. 根据姓名创建Person对象
7. 打印整个队伍的Person信息
* */
public static void main(String[] args) {
List<String> list1 = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
List<String> list2 = Arrays.asList("古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
// 1. 第一个队伍只保留姓名长度为3的成员
// 2. 第一个队伍筛选后只要前三个
list1.stream().filter(s -> s.length() == 3).limit(3)
.forEach(System.out::println);
System.out.println("--------------");
// 3. 第二个队伍只要姓张的成员
// 4. 第二个队伍筛选之后不要前两个人
list2.stream().filter(s -> s.contains("张")).skip(2)
.forEach(System.out::println);
System.out.println("--------------");
// 5. 将两个队伍合并成一个队伍
Stream.concat(list1.stream(), list2.stream()).forEach(System.out::println);
// 6. 根据姓名创建Person对象
// 7. 打印整个队伍的Person信息
System.out.println("-----------------");
Stream.concat(list1.stream(),list2.stream()).map(Person::new).forEach(System.out::println);
}
}
5.Stream结果收集
5.1 结果收集到集合中
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
public class test01 {
/*
* Stream结果收集
* 收集到集合中
* */
@Test
public void test01(){
//Stream<String> stream = Stream.of("aa", "bb", "cc");
List<String> list = Stream.of("aa", "bb", "cc","aa").collect(Collectors.toList());
System.out.println(list);
System.out.println("-----------------");
//收集到set集合中
Set<String> set=Stream.of("aa", "bb", "cc","aa").collect(Collectors.toSet());
System.out.println(set);
System.out.println("------------------");
//如果需要获取的类型为具体的实现,比如:ArrayList。HashList
//ArrayList<String> arrayList=Stream.of("aa", "bb", "cc","aa").collect(Collectors.toCollection(()->new ArrayList<>()));
ArrayList<String> arrayList=Stream.of("aa", "bb", "cc","aa").collect(Collectors.toCollection(ArrayList::new));
System.out.println(arrayList);
System.out.println("------------------");
HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toCollection(HashSet::new));
System.out.println(hashSet);
System.out.println("------------------");
}
}
输出:
[aa, bb, cc, aa]
-----------------
[aa, bb, cc]
------------------
[aa, bb, cc, aa]
------------------
[aa, bb, cc]
------------------
5.2 结果收集到数组中
Stream中提供了toArray方法来讲结果放到一个数组中,返回值类型是Object[].如果我们要指定返回的类型,那么可以使用另一个重载的toArray(intFunction)方法
@Test
public void test02() {
Object[] array = Stream.of("aa", "bb", "cc", "aa").toArray();//返回的数组中的元素是Object类型
System.out.println(Arrays.toString(array));
//如果我们需要指定返回的数组中的元素类型
String[] toArray = Stream.of("aa", "bb", "cc", "aa").toArray(String[]::new);
System.out.println(Arrays.toString(toArray));
}
5.3 对流中的数据做聚合计算
当我们使用Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作,比如获取最大值,最小值,求和,平均值,统计数量。
@Test
public void test03() {
//获取年龄的最大值
Optional<Person> maxAge = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 18, "167"),
new Person("赵六", 19, "176"),
new Person("张三", 33, "180")
).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));
System.out.println("最大年龄的人" + maxAge);
//获取年龄的最小值
Optional<Person> minAge = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 18, "167"),
new Person("赵六", 19, "176"),
new Person("张三", 33, "180")
).collect(Collectors.minBy((p1, p2) -> p1.getAge() - p2.getAge()));
System.out.println("最小年龄的人" + minAge);
//求所有人年龄之和
Integer sumAge = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 18, "167"),
new Person("赵六", 19, "176"),
new Person("张三", 33, "180")
).collect(Collectors.summingInt(s -> s.getAge()));
System.out.println("年龄之和" + sumAge);
//年龄的平均值
Double avgAge = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 18, "167"),
new Person("赵六", 19, "176"),
new Person("张三", 33, "180")
).collect(Collectors.averagingInt(s -> s.getAge()));
System.out.println("年龄的平均值" + avgAge);
//统计数量
Long count = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 18, "167"),
new Person("赵六", 19, "176"),
new Person("张三", 33, "180")
).filter(person -> person.getAge() > 18).collect(Collectors.counting());
System.out.println("满足条件的记录数" + count);
}
5.4 对流中数据做分组操作
当我们使用Stream流处理数据后,可以根据某个属性将数据分组,
/*
* 分组计算
* */
@Test
public void test04() {
//根据账号对数据进行分组
Map<String, List<Person>> map = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 18, "167"),
new Person("赵六", 19, "176"),
new Person("张三", 33, "180")
).collect(Collectors.groupingBy(Person::getName));
map.forEach((k, v) -> System.out.println("k=" + k + "v=" + v));
System.out.println("----------------");
//根据年龄对数据进行分组,如果大于等于18成年否则未成年
Map<String, List<Person>> listMap = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 13, "167"),
new Person("赵六", 12, "176"),
new Person("张三", 33, "180")
).collect(Collectors.groupingBy(person -> person.getAge() >= 18 ? "成年" : "未成年"));
listMap.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
}
输出结果:
k=李四v=[Person(name=李四, age=22, heigt=199)]
k=张三v=[Person(name=张三, age=18, heigt=198), Person(name=张三, age=18, heigt=167), Person(name=张三, age=33, heigt=180)]
k=赵六v=[Person(name=赵六, age=19, heigt=176)]
----------------
k=未成年 v=[Person(name=张三, age=13, heigt=167), Person(name=赵六, age=12, heigt=176)]
k=成年 v=[Person(name=张三, age=18, heigt=198), Person(name=李四, age=22, heigt=199), Person(name=张三, age=33, heigt=180)]
多级分组:根据name分组,然后根据age(成年和未成年)对数据进行分组
/*
* 分组计算--多级分组
* */
@Test
public void test05() {
//根据name分组,然后根据age(成年和未成年)对数据进行分组
Map<String, Map<String, List<Person>>> listMap = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 13, "167"),
new Person("赵六", 12, "176"),
new Person("张三", 33, "180")
).collect(Collectors.groupingBy(
Person::getName
, Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年")));
listMap.forEach((k, v) -> {
System.out.println(k);
v.forEach((k1, v1) -> System.out.println("\t" + "k1=" + k1 + "\t" + "v1=" + v1));
}
);
}
输出结果:
李四
k1=成年 v1=[Person(name=李四, age=22, heigt=199)]
张三
k1=未成年 v1=[Person(name=张三, age=13, heigt=167)]
k1=成年 v1=[Person(name=张三, age=18, heigt=198), Person(name=张三, age=33, heigt=180)]
赵六
k1=未成年 v1=[Person(name=赵六, age=12, heigt=176)]
5.5 对流中的数据做分区操作
Collectors.partitioningBy会根据值是否为true,把集合中的数据分割为里那个列表,一个为true列表,一个为false列表
/*
* 分区操作
* */
@Test
public void test06(){
Map<Boolean, List<Person>> map = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 13, "167"),
new Person("赵六", 12, "176"),
new Person("张三", 33, "180")
).collect(Collectors.partitioningBy(p -> p.getAge() > 18));
map.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
}
输出结果:
k=false v=[Person(name=张三, age=18, heigt=198), Person(name=张三, age=13, heigt=167), Person(name=赵六, age=12, heigt=176)]
k=true v=[Person(name=李四, age=22, heigt=199), Person(name=张三, age=33, heigt=180)]
5.6对流中的数据做拼接
Collectors.joining会根据指定的连接符,将所有的元素连接成一个字符串
/*
* 对流中的数据进行数据拼接操作
* */
@Test
public void test07(){
String s = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 13, "167"),
new Person("赵六", 12, "176"),
new Person("张三", 33, "180")
).map(person -> person.getName()).collect(Collectors.joining("_"));
System.out.println(s);
System.out.println("----------------");
String s1 = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 13, "167"),
new Person("赵六", 12, "176"),
new Person("张三", 33, "180")
).map(person -> person.getName()).collect(Collectors.joining("_"));
System.out.println(s1);
System.out.println("-----------------");
String s2 = Stream.of(
new Person("张三", 18, "198"),
new Person("李四", 22, "199"),
new Person("张三", 13, "167"),
new Person("赵六", 12, "176"),
new Person("张三", 33, "180")
).map(person -> person.getName()).collect(Collectors.joining("_","###","$$$"));
System.out.println(s2);
}
输出结果:
张三_李四_张三_赵六_张三
----------------
张三_李四_张三_赵六_张三
-----------------
###张三_李四_张三_赵六_张三$$$## 并行的Stream流
6. 并行的Stream流
6.1 串行的Stream流
我们前面使用的Stream流都是串行的,也就是在一个线程上面执行
/*
* 串行流
* */
public static void main(String[] args) {
Stream.of(4, 5, 67, 234, 1).filter(s -> {
System.out.println(Thread.currentThread() + " " + s);
return s > 3;
}).count();
}
输出结果:
Thread[main,5,main] 4
Thread[main,5,main] 5
Thread[main,5,main] 67
Thread[main,5,main] 234
Thread[main,5,main] 1
6.2 并行流
parallelStream其实就是一个并行执行的流,他通过默认的ForkjoinPool,可以提高多线程任务的速度
6.2.1 获取并行流
我们可以通过两种方式来获取并行流。
- 通过List接口中的parallerStream方法来获取
- 通过已有的串行流转换为并行流(parallel)
实现:
@Test
public void test02(){
List<Integer> list=new ArrayList<>();
//方式一:直接通过list接口,获取并行流
Stream<Integer> parallelStream = list.parallelStream();
//方式二:将已有的串行流转换为并行流
Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();
}
6.2.2 并行流操作
@Test
public void test03() {
Stream.of(1, 2, 43, 54, 23, 5634, 12)
.parallel()// 将流转换为并发流,Stream处理的时候就会通过多线程处理
.filter(s -> {
System.out.println(Thread.currentThread() + " s=" + s);
return s > 2;
}).count();
}
6.3 并行流和串行流对比
我们通过for循环,串行Stream流,并行Stream流来对5亿个数字求和,来看消耗时间
package com.king.res;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.stream.LongStream;
public class test03 {
private static long times = 500000000;
private long start;
/*
* 如果在Before方法中分配外部资源,则需要在测试运行后释放它们。使用@After注释public void方法会导致该方法在Test方法之后运行*/
@Before
public void before() {
start = System.currentTimeMillis();
}
@After
public void after() {
long end = System.currentTimeMillis();
System.out.println("消耗时间=" + (end - start));
}
/*
* 普通的for循环
* */
@Test
public void test01() {
System.out.println("普通的for循环:");
long res = 0;
for (long i = 0; i < times; i++) {
res += i;
}
}
/*
* 串行流处理
* */
@Test
public void test02() {
System.out.println("串行流:serialStream");
LongStream.rangeClosed(0,times)
.reduce(0,Long::sum);
}
/*
* 并行流处理,消耗时间156
* */
@Test
public void test03() {
System.out.println("并行流:parallelStream");
LongStream.rangeClosed(0,times)
.parallel()
.reduce(0,Long::sum);
}
}
输出结果:
普通的for循环:
消耗时间=188
串行流:serialStream
消耗时间=313
并行流:parallelStream
消耗时间=156
通过案例我们可以看到paralleStreaml的效率数最高的。
Stream并行处理的过程会分而治之,也就是讲一个打的任务切分成了多个小任务,这表示每个任务都是一个小进程操作。
6.4 线程安全问题
在多线程的处理下,肯定会出现数据安全问题。如下:
@Test
public void test01(){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
System.out.println(list.size());
List<Integer> newList = new ArrayList<>();
//使用并行流来向集合中添加数据
list.parallelStream()
.forEach(newList::add);
System.out.println(newList.size());
}
针对这个问题我们的解决方案有哪些呢?
- 加同步锁
- 使用线程安全的容器
- 将线程不安全发容器转换为你线程安全的容器
- 通过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<Integer> vector = new Vector<>();
Object obj = new Object();
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(
i -> {
vector.add(i);
});
System.out.println(vector.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(synchronizedList::add);
System.out.println(listNew.size());
}
/*
* 我们还可以通过Stream中的toArray方法或者 collector 方法来操作
* 就是满足线程安全的要求
* */
@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());
}
7. Fork/Join框架
parallelStream 使用的是Fork/Join框架。Fork/Join框架自JDK7引入。Fork/Join框架可以将一个大任务拆分为很多小
任务来异步执行。Fork/Join框架主要包含三个模块:
1.线程池:ForkJoinPool
2.任务对象:ForkJoinTask
3.执行任务的线程:ForkJoinWorkerThread
7.1 Fork/Join原理-分治法
ForkJoinPool主要用来使用分治法Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法,
ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。
7.2 Fork/Join原理-工作窃取算法
Fork/Join最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的cpu,那么如何利用好这个空闲的cpu就成了提高性能的关键,而这里我们要提到的工作窃取(work-stealing)算法就是整个Fork/Join框架的核心理念
Fork/Join工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。
那么为什么需要使用工作窃取算法呢?即假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一对应。比如A线程负责处理A队列里的任务,但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端部队列的尾部拿任务执行。
工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
上文中已经提到了在java8引入了自动并行化的概念。它能够让一部分java代码自动地以并行的方式执行,也就是我们使用了ForkloinPool的ParallelStream.对于ForkJoinPool通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置系统属性:java.utl.concurrent.ForkJoinPool.common.parallelism=N(N为线程数量),来调整ForkJoinPool的线程数量,可以尝试调整成不同的参数来观察每次的输出结果。
7.3 Fork/Join案例
需求:使用Fork/Join计算1-10000的和,当一个任务的计算数量大于3000的时候拆分任务,数量小3000的时候就计算
实现:
public class test05 {
/*需求:使用Fork/Join计算1-10000的和,
当一个任务的计算数量大于3000的时候拆分任务,
数量小3000的时候就计算
* */
public static void main(String[] args) {
long start = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
SumRecursivetask task = new SumRecursivetask(1, 10000);
Long result = pool.invoke(task);
System.out.println("result:" + result);
long end = System.currentTimeMillis();
System.out.println("总的耗时:" + (end - start));
}
}
class SumRecursivetask extends RecursiveTask<Long> {
//定义一个拆分的临界池
private static final long THRESHOLD = 3000l;
private final long start;
private final long end;
SumRecursivetask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if (length <= THRESHOLD) {
//任务不需要拆分,可以计算
long sum = 0;
for (long i = start; i < end; i++) {
sum += i;
}
System.out.println("计算:" + start + "--->" + end + ",结果为:" + sum);
return sum;
} else {
// 数量大于预定的数量,那说明还要继续拆分
long middle = (start + end) / 2;
System.out.println("拆分:左边 " + start + "-->" + middle + ",右边" + (middle + 1) + "-->" + end);
SumRecursivetask left = new SumRecursivetask(start, middle);
left.fork();
SumRecursivetask right = new SumRecursivetask(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
输出结果:
拆分:左边 1-->5000,右边5001-->10000
拆分:左边 1-->2500,右边2501-->5000
计算:1--->2500,结果为:3123750
计算:2501--->5000,结果为:9371250
拆分:左边 5001-->7500,右边7501-->10000
计算:5001--->7500,结果为:15618750
计算:7501--->10000,结果为:21866250
result49980000
总的耗时:5
六、Optional类
这个optional类主要是解决空值针的问题
1. 以前对null的处理
@Test
public void tes01() {
//String username="张三“
String username = null;
if (username != null) {
System.out.println("字符串长度:" + username.length());
} else {
System.out.println("字符串为空");
}
}
2. Optional类
Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象,它的主要作用就是为了避免null检查,防止NullpointException,
3. Optional的基本使用
Optional对象的创建方式:
/*
* Optional对象的创建方式
* */
@Test
public void test02(){
//第一种方式:通过of方法---of方法不支持null
Optional<String> op1 = Optional.of("zhangsan");
//Optional<Object> op2 = Optional.of(null);
//第二种方式:通过ofNullable方法 支持null;
Optional<String> op3 = Optional.ofNullable("lisi");
Optional<Object> op4 = Optional.ofNullable(null);
//第三种方式:通过empty方法直接创建一个空的Optional对象
Optional<Object> op5 = Optional.empty();
}
4. Optionam常用的方法
/*
* Optional的常用方法介绍
* get():如果optional有值则返回,否则抛出NoSuchElementException异常
* get()通常和isPresent一块使用
* isPresent():判断是否包含值,包含值返回true,不包含返回false
* orElse(t):如果调用对象包含值,就返回值,否则返回t
* orElseGet:如果调用对象包含值,就返回值,否则返回 Lambda表达式的返回值
* */
@Test
public void test03() {
Optional<String> op1 = Optional.of("zhangsan");
Optional<String> op2 = Optional.empty();
//获取Optional中的值
if (op1.isPresent()) {
String s1 = op1.get();
System.out.println("用户名称:" + s1);
}
if (op2.isPresent()) {
System.out.println(op2.get());
} else {
System.out.println("op2是一个空的Optional对象");
}
String s3 = op1.orElse("李四");
System.out.println(s3);
String s4 = op2.orElse("王五");
System.out.println(s4);
String s5 = op2.orElseGet(() -> {
return "hello";
});
System.out.println(s5);
}
@Test
public void test04() {
Optional<String> op1 = Optional.of("zhangsan");
Optional<String> op2 = Optional.empty();
//如果存在值,就做什么
op1.ifPresent(s -> System.out.println("有值:" + s));
}
/*
* 自定义一个方法,将Person对象中的name转换为大写,并返回
* 通过Optional方式实现
* */
@Test
public void test05() {
Person person = new Person("zhangsan", 12, "123");
Optional<Person> op = Optional.of(person);
String name = getNameForOptional(op);
System.out.println(name);
}
public String getNameForOptional(Optional<Person> op){
if (op.isPresent()){
String msg = op.map(Person::getName)
.map(String::toUpperCase)
.orElse("null");
return msg;
}
return null;
}
七、新时间日期API
1. 旧版日期时间的问题
在旧版本的中的jdk对于日期和时间这块的实际是非常差的
/*
* 旧版日期时间设计的问题
* */
@Test
public void test01() throws ParseException {
// 1.设计不合理
Date date = new Date(2022,11,20);
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("2022-11-20"));
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
- 设计不合理,在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间的,而java.sql.Date仅仅包含日期,此外用于格式化和解析的类在java.text包下。
- 非线程安全,java.util.Date是非线程安全的,所有的日期类都是可变的,这是java日期类最大的问题之一
- 时区处理麻烦,日期并不提供国际化,没有时区支持
2. 新日期时间API介绍
JDK8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于java.time包
中,下面是一些关键类。
- LocalDate:表示日期,包含年月日,格式为2019-10-16
- LocalTime: 表示时间,包含时分秒,格式为16:38:54.158549300
- LocalDateTime:表示日期时间,包含年月日,时分秒,格式为2018-09-06T15:33:56.750
- DateTlmeFormatter:日期时间格式化类。
- Instant:时间戳,表示一个特定的时间瞬间。
- Duration:用于计算2个时间(LocalTime,时分秒)的距离
- Period:用于计算2个日期LocalDate,年月日)的距离
- ZonedDateTime:包含时区的时间
Java中使用的历法是IS0 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366
天。此外java8还提供了4套其他历法,分别是:
- ThaiBuddhistDate:泰国佛教历
- MinguoDate:中华民国历
- JapaneseDate:日本历
- HijrahDate:伊斯兰历
2.1 日期时间的常见操作
LocalDate,LocalTime以及LocalDateTime的操作
/*
* JDk8 日期时间的操作
* */
@Test
public void test01() {
// 1.创建指定的日期
LocalDate date = LocalDate.of(2022, 11, 21);
System.out.println("date=" + date);
// 2.得到当前的日期
LocalDate now = LocalDate.now();
System.out.println("now=" + now);
// 3.根据LocalDate对象获取对应的日期信息
System.out.println("年:" + now.getYear());
System.out.println("月:" + now.getMonth());
System.out.println("日:" + now.getDayOfMonth());
System.out.println("星期:" + now.getDayOfWeek());
}
/*时间操作
**/
@Test
public void test02() {
//1.得到指定的时间
LocalTime time = LocalTime.of(5, 26, 33, 23145);
System.out.println(time);
//2.获取当前的额时间
LocalTime now = LocalTime.now();
System.out.println(now);
//3.获取时间信息
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
System.out.println(now.getNano());
}
/*
* 日期时间类型 LocalDateTime
* */
@Test
public void test03() {
//获取指定的日期时间
LocalDateTime dateTime = LocalDateTime.of(2022, 03, 01, 12, 12, 12, 25);
System.out.println(dateTime);
//获取当前的日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
//获取日期时间信息
System.out.println(now.getYear());
System.out.println(now.getMonth().getValue());
System.out.println(now.getDayOfMonth());
System.out.println(now.getDayOfWeek().getValue());
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
System.out.println(now.getNano());
}
2.2 日期时间的修改和比较
/*
* 日期时间的修改
* */
@Test
public void test01() {
LocalDateTime now = LocalDateTime.now();
System.out.println("now=" + now);
// 修改日期时间 对日期时间的修改,对已存在的LocalDate对象,创建了它的模板
// 并不会修改原来的信息
LocalDateTime localDateTime = now.withYear(1998);
System.out.println("now:" + now);
System.out.println("修改后:" + localDateTime);
System.out.println("月份:" + now.withMonth(10));
System.out.println("天:" + now.withDayOfMonth(6));
System.out.println("小时:" + now.withHour(2));
System.out.println("分钟:" + now.withMinute(14));
// 在当前日期时间的额基础上,加上或者减去指定的时间
System.out.println("两天后:" + now.plusDays(2));
System.out.println("十天后:" + now.plusDays(10));
System.out.println("6个月后:" + now.plusMonths(6));
System.out.println("10年前:" + now.minusYears(10));
System.out.println("半年前:" + now.minusMonths(6));
System.out.println("一周前:" + now.minusDays(7));
}
/**
* 日期时间的比较
*/
@Test
public void test02() {
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2020, 1, 3);
//在jdk8中要实现日期的比较 isAfter isBefore isEqual通过这几个方法来直接比较
System.out.println(now.isAfter(date));
System.out.println(now.isBefore(date));
System.out.println(now.isEqual(date));
}
注意:在进行日期时间修改的时候,原来的LocalDate对象是不会被修改的,每次操作都是返回了一个新的LocalDate对象,所以在多线程场景下数据是安全的。
2.3 格式化和解析操作
在jdk8中我们可以通过java.time.format.DateTimeFoematter类可以进行日期的解析和格式化操作
/*
* 日期格式化
* */
@Test
public void test01() {
LocalDateTime now = LocalDateTime.now();
// 指定格式 使用系统默认的格式
DateTimeFormatter isoLocalDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
// 将日期时间转换为字符串
String format = now.format(isoLocalDateTime);
System.out.println("format = " + format);
// 通过 ofPattent 方法来指定特定的格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format1 = now.format(dateTimeFormatter);
// 2022-08-08 16:31:57
System.out.println(format1);
// 将字符串解析为日期时间类型
LocalDateTime parse = LocalDateTime.parse("2001-05-03 22:45:45", dateTimeFormatter);
// parse = 2001-05-03T22:45:45
System.out.println("parse = " + parse);
}
2.4 Instant类
在jdk8中我们新增了一个instant类(时间戳/时间线),内部保存了1970年11月1日 00:00:00 以来的秒和纳秒
/*
* Instant 时间戳
* 可以用来统计时间的消耗
* */
@Test
public void test01() throws InterruptedException {
Instant now = Instant.now();
System.out.println("now = " + now);
// 获取从1970年一月一日 00:00:00 到现在的 纳秒
System.out.println("now.getNano() = " + now.getNano());
Thread.sleep(5);
Instant now1 = Instant.now();
System.out.println("耗时:" + (now1.getNano()-now.etNano()));
}
2.5 计算日期时间差
JDK8中提供了两个工具类Duration/Period:计算日期时间差
- Duration:用来计算两个时间差(LocalTime)
- Period:用来计算两个日期差(LocalDate)
/*
* 计算日期时间差
* */
@Test
public void test01(){
// 计算时间差
LocalTime now = LocalTime.now();
LocalTime time = LocalTime.of(22, 48, 56);
System.out.println("now = " + now);
// 通过Duration计算时间差
Duration duration = Duration.between(now, time);
System.out.println("duration.toDays() = " + duration.toDays());
System.out.println("duration.toHours() = " + duration.toHours());
System.out.println("duration.toMinutes() = " + duration.toMinutes());
System.out.println("duration.toMillis() = " + duration.toMillis());
System.out.println("------------------------");
//计算日期差
LocalDate nowDate = LocalDate.now();
LocalDate date = LocalDate.of(1997, 12, 5);
Period period = Period.between(date, nowDate);
System.out.println("period.getYears() = " + period.getYears());
System.out.println("period.getMonths() = " + period.getMonths());
System.out.println("period.getDays() = " + period.getDays());
}
2.6 时间校正器
有时候我们需要如下调整:将日期调整到下个星期的第一天等操作。这时我们通过时间校正器效果可能会更好
- TemporalAdjust:时间校正器
- TemporalAdjusts:通过该类静态方法提供了大量的常用TemporalAdjust的实现
/*
* 时间校正器
* */
@Test
public void test02() {
LocalDateTime now = LocalDateTime.now();
// 将当前的日期调整到下个月一号
TemporalAdjuster adjuster = (temporal) -> {
LocalDateTime dateTime = (LocalDateTime) temporal;
LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
System.out.println("nextMonth = " + nextMonth);
return nextMonth;
};
// 我们还可以通过 TemparalAdjust 来实现
//LocalDateTime nextMonth = now.with(adjuster);
LocalDateTime nextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println("nextMonth = " + nextMonth);
}
2.7 日期时间的时区
lava8中加入了对时区的支持,LocalDate、.LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为
ZonedDate,ZonedTime,ZonedDateTime。
其中每个时区都对应着ID,ID的格式为"“区域/城市”。例如:Asia/Shanghai等。
/*
* 时区操作
* */
@Test
public void test01() {
// 1.获取所有的时区id
//ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 2.获取当前时间 中国使用的 东八区的时区,比标椎时间慢8个小时
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);//2022-08-08T22:34:27.153
//获取标椎时间
ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());
System.out.println("zonedDateTime = " + zonedDateTime);//2022-08-08T14:34:27.155Z
// 使用计算机默认的时区,创建日期时间
ZonedDateTime now1 = ZonedDateTime.now();
System.out.println("now1 = " + now1);//2022-08-08T22:34:27.155+08:00[Asia/Shanghai]
// 使用指定的时区创建日期时间
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Marigot"));
System.out.println("now2 = " + now2);
}
JDK新的日期和时间API的优势:
- 新版日期时间APi中,日期和时间对象是不变的,操作日期不会影响原来的值,而是生成一个新的实例
- 提供了不同的两种方式,有效的区分了人和机器的操作
- TemporalAdjust可以更精确的操作日期,还可以自定义日期调整期
- 线程安全
八、其他新特性
+++
1. 重复注解
自从Java5中引入注解以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK8中使用@Repeatable注解定义重复注解。
1.1 定义一个重复注解的容器
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
1.2 定义一个可以重复的注释
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
1.3 配置多个重复的注释
@MyAnnotation("test1")
@MyAnnotation("test2")
@MyAnnotation("test3")
public class AnnoTest01 {
@MyAnnotation("fun1")
@MyAnnotation("fun2")
public void test01(){
}
1.4 解析得到重复的注解
/*
* 解析重复注解
* */
public static void main(String[] args) throws NoSuchMethodException {
// 获取类中标注的重复注解
MyAnnotation[] annotationsByType = AnnoTest01.class.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation myAnnotation : annotationsByType) {
System.out.println(myAnnotation.value());
}
//获取方法上标的重复注解
MyAnnotation[] test01s = AnnoTest01.class.getMethod("test01")
.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation test01 : test01s) {
System.out.println(test01.value());
}
}
2. 类型注解
JDK8为@Target元注解新增了两种类型:TYPE_PARAMETER,TYPE_USE。
- TYPE PARAMETER:表示该注解能写在类型参数的声明语句中。类型参数声明如:、
- TYPE_USE:表示注解可以再任何用到类型的地方使用。
TYPE PARAMETER:
@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParam {
}
使用:
public class TypeDemo01<@TypeParam T> {
public <@TypeParam k extends Object> k test01(){
return null;
}
}
TYPE_USE
@Target(ElementType.TYPE_USE)
public @interface NotNull {
}
使用:
public class TypeUseDemo01 {
public Integer age=10;
public Integer sum(@NotNull Integer a,@NotNull Integer b){
return a+b;
}
}
java
/*
时区操作
*/
@Test
public void test01() {
// 1.获取所有的时区id
//ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 2.获取当前时间 中国使用的 东八区的时区,比标椎时间慢8个小时
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);//2022-08-08T22:34:27.153
//获取标椎时间
ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());
System.out.println("zonedDateTime = " + zonedDateTime);//2022-08-08T14:34:27.155Z// 使用计算机默认的时区,创建日期时间
ZonedDateTime now1 = ZonedDateTime.now();
System.out.println("now1 = " + now1);//2022-08-08T22:34:27.155+08:00[Asia/Shanghai]// 使用指定的时区创建日期时间
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of(“America/Marigot”));
System.out.println("now2 = " + now2);
}
JDK新的日期和时间API的优势:
1. 新版日期时间APi中,日期和时间对象是不变的,操作日期不会影响原来的值,而是生成一个新的实例
2. 提供了不同的两种方式,有效的区分了人和机器的操作
3. TemporalAdjust可以更精确的操作日期,还可以自定义日期调整期
4. 线程安全
## 八、其他新特性
+++
### 1. 重复注解
自从Java5中引入注解以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK8中使用@Repeatable注解定义重复注解。
1.1 定义一个重复注解的容器
```java
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
1.2 定义一个可以重复的注释
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
1.3 配置多个重复的注释
@MyAnnotation("test1")
@MyAnnotation("test2")
@MyAnnotation("test3")
public class AnnoTest01 {
@MyAnnotation("fun1")
@MyAnnotation("fun2")
public void test01(){
}
1.4 解析得到重复的注解
/*
* 解析重复注解
* */
public static void main(String[] args) throws NoSuchMethodException {
// 获取类中标注的重复注解
MyAnnotation[] annotationsByType = AnnoTest01.class.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation myAnnotation : annotationsByType) {
System.out.println(myAnnotation.value());
}
//获取方法上标的重复注解
MyAnnotation[] test01s = AnnoTest01.class.getMethod("test01")
.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation test01 : test01s) {
System.out.println(test01.value());
}
}
2. 类型注解
JDK8为@Target元注解新增了两种类型:TYPE_PARAMETER,TYPE_USE。
- TYPE PARAMETER:表示该注解能写在类型参数的声明语句中。类型参数声明如:、
- TYPE_USE:表示注解可以再任何用到类型的地方使用。
TYPE PARAMETER:
@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParam {
}
使用:
public class TypeDemo01<@TypeParam T> {
public <@TypeParam k extends Object> k test01(){
return null;
}
}
TYPE_USE
@Target(ElementType.TYPE_USE)
public @interface NotNull {
}
使用:
public class TypeUseDemo01 {
public Integer age=10;
public Integer sum(@NotNull Integer a,@NotNull Integer b){
return a+b;
}
}