Lambda表达式相当于是对接口抽象方法的重写
package com.bz.jdk8.demo01_Lambda;
/**
* 体验Lambda表达式
*/
public class Demo01LambdaIntro {
public static void main(String[] args) {
// 使用匿名内部类存在的问题
// public Thread(Runnable target)
// 匿名内部类做了哪些事情
// 1.定义了一个没有名字的类
// 2.这个类实现了Runnable接口
// 3.创建了这个类的对象
// 使用匿名内部类语法是很冗余的
// 其实我们最关注的是run方法和里面要执行的代码.
// Lambda表达式体现的是函数式编程思想,只需要将要执行的代码放到函数中(函数就是类中的方法)
// Lambda就是一个匿名函数, 我们只需要将要执行的代码放到Lambda表达式中即可
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("执行一个线程");
}
}).start();
// Lambda表达式的好处: 可以简化匿名内部类,让代码更加精简
//使用Lambda表达式
new Thread(()->{
System.out.println("使用了Lambda表达式");
}).start();
}
}
()->{}
package com.bz.jdk8.demo01_Lambda;
/**
* Lambda表达式的标准格式
* ()->{}
*
* // 小结:Lambda表达式相当于是对接口抽象方法的重写
*/
public class Demo02LambdaUse {
public static void main(String[] args) {
goSwimming(new Swimmable() {
@Override
public void swimming() {
System.out.println("练习无参数无返回值的匿名内部类");
}
});
System.out.println("===============");
goSwimming(()-> System.out.println("练习无参数无返回值的Lambda"));
}
// 练习无参数无返回值的Lambda
public static void goSwimming(Swimmable s) {
s.swimming();
}
}
在Lambda标准格式的基础上,使用省略写法的规则为:
Lambda表达式的前提条件:
package com.bz.jdk8.demo01_Lambda;
/**
* Lambda表达式的前提条件:
* 1. 方法的参数或变量的类型是接口
* 2. 这个接口中只能有一个抽象方法
*
* @FunctionalInterface // 检测这个接口是不是只有一个抽象方法
*/
public class Demo04LambdaCondition {
public static void main(String[] args) {
test(()-> System.out.println("吃饭了吗?"));
System.out.println("===========");
Flyable f = ()->{
System.out.println("飞呀飞。。。");
};
}
public static void test(Flyable flyable){
System.out.println("使用了lambda表达式");
}
}
// 只有一个抽象方法的接口称为函数式接口,我们就能使用Lambda
@FunctionalInterface // 检测这个接口是不是只有一个抽象方法
interface Flyable {
// 接口中有且仅有一个抽象方法
public abstract void eat();
// public abstract void eat2();
}
如果给接口新增抽象方法,所有实现类都必须重写这个抽象方法。
不利于接口的扩展。
package com.bz.jdk8.demo02interfaceupgrade;
/**
* 接口引入默认方法的背景
* 在JDK 8以前接口中只能有抽象方法。存在以下问题:
* 如果给接口新增抽象方法,所有实现类都必须重写这个抽象方法。
* 不利于接口的扩展。
*/
public class Demo01InterfaceDefaultIntro{
}
interface A {
public abstract void test01();
// 接口新增抽象方法,所有实现类都需要去重写这个方法,非常不利于接口的扩展
// public abstract void test02();
}
class B implements A {
@Override
public void test01() {
System.out.println("B test01");
}
}
class C implements A {
@Override
public void test01() {
System.out.println("C test01");
}
}
package com.bz.jdk8.demo02interfaceupgrade;
/**
* 接口默认方法的使用
* 方式一:实现类直接调用接口默认方法
* 方式二:实现类重写接口默认方法
*/
public class Demo02UseDefaultFunction {
public static void main(String[] args) {
//方式一:实现类直接调用接口默认方法
AA aa = new BB();
aa.test1();
System.out.println("===========");
//方式二:实现类重写接口默认方法
AA ac = new CC();
ac.test1();
}
}
interface AA{
default void test1(){
System.out.println("jdk8新增的默认的方法");
}
}
class BB implements AA{
//不重写方法
}
class CC implements AA{
@Override
public void test1() {
System.out.println("重写了方法");
}
}
package com.bz.jdk8.demo02interfaceupgrade;
/**
* 接口的静态方法
* 接口静态方法的使用
* 直接使用接口名调用即可:接口名.静态方法名();
*
* 接口默认方法和静态方法的区别
* 1. 默认方法通过实例调用,静态方法通过接口名调用。
* 2. 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
* 3. 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用。
*
* 如何选择呢?如果这个方法需要被实现类继承或重写,使用默认方法; 如果接口中的方法不需要被继承就使用静态方法
*/
public class Demo03UseStaticFunction {
public static void main(String[] args) {
DD dd = new EE();
//
//通过接口名调用
DD.test();
}
}
interface DD{
public static void test(){
System.out.println("jdk8新增的静态方法");
}
}
//无法重写静态方法
class EE implements DD{
// @Override
// test
}
接口默认方法和静态方法的区别
如何选择呢?如果这个方法需要被实现类继承或重写,使用默认方法; 如果接口中的方法不需要被继承就使用静态方法。
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。
java.util.function.Supplier 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
package com.bz.jdk8.demo03functionalinterface;
import java.util.Arrays;
import java.util.function.Supplier;
/**
* java.util.function.Supplier 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
* 供给型接口,通过Supplier接口中的get方法可以得到一个值,无参有返回的接口。
*/
public class Demo02Supplier {
// 使用Lambda表达式返回数组元素最大值
public static void main(String[] args) {
printMax(()->{
int[] arr = new int[]{23,25,11,56,45};
Arrays.sort(arr);//升序
return arr[arr.length - 1];
});
}
public static void printMax(Supplier<Integer> supplier){
System.out.println("supplier函数式接口");
Integer max = supplier.get();
System.out.println("max: "+max);
}
}
java.util.function.Consumer 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
package com.bz.jdk8.demo03functionalinterface;
import java.util.function.Consumer;
/**
* java.util.function.Consumer 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
*/
public class Demo03Consumer {
// 使用Lambda表达式将一个字符串转成大写的字符串
public static void main(String[] args) {
System.out.println("开始了");
printHello((s)->{
System.out.println(s.toUpperCase());
});
}
public static void printHello(Consumer<String> consumer){
System.out.println("consumer");
consumer.accept("holle world");
}
}
默认方法:andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。
package com.bz.jdk8.demo03functionalinterface;
import java.util.function.Consumer;
public class Demo04ConsumerAndThen {
// 使用Lambda表达式先将一个字符串转成小写的字符串,再转成大写
public static void main(String[] args) {
System.out.println("开始啦");
printHello((String str) -> {
System.out.println(str.toLowerCase());
}, (String str) -> {
System.out.println(str.toUpperCase());
});
}
public static void printHello(Consumer<String> c1, Consumer<String> c2) {
System.out.println("aa");
String str = "Hello World";
// c1.accept(str);
// c2.accept(str);
c1.andThen(c2).accept(str);
}
}
java.util.function.Function
Function转换型接口,对apply方法传入的T类型数据进行处理,返回R类型的结果,有参有返回的接口。使用的场景例如:将 String 类型转换为 Integer
package com.bz.jdk8.demo03functionalinterface;
import java.util.function.Function;
/**
* Function接口
* java.util.function.Function 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,
* 后者称为后置条件。有参数有返回值。
*
* Function转换型接口,对apply方法传入的T类型数据进行处理,返回R类型的结果,有参有返回的接口。使用的场景
* 例如:将 String 类型转换为 Integer 类型。
*/
public class Demo05Function {
// 使用Lambda表达式将字符串转成数字
public static void main(String[] args) {
System.out.println("开始了...");
getNumber(str->{
return Integer.parseInt(str);
});
}
public static void getNumber(Function<String,Integer> function){
System.out.println("输入字符串得到int数据");
Integer apply = function.apply("98");
System.out.println("num:"+apply);
}
}
默认方法:andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。
package com.bz.jdk8.demo03functionalinterface;
import java.util.function.Function;
/**
* 默认方法:andThen
* Function 接口中有一个默认的 andThen 方法,用来进行组合操作。
*/
public class Demo06FunctionAndThen {
//第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一起。
public static void main(String[] args) {
getNumber(str->{
return Integer.parseInt(str);
},i->{
return i * 6;
});
}
public static void getNumber(Function<String,Integer> f1,Function<Integer,Integer> f2){
System.out.println("先转为int类型,如何再乘以6");
/* Integer apply = f1.apply("5");
Integer count = f2.apply(apply);*/
Integer count = f1.andThen(f2).apply("5");
System.out.println("结果:"+count);
}
}
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate 接口。
package com.bz.jdk8.demo03functionalinterface;
import java.util.function.Predicate;
public class Demo07Predicate {
// 使用Lambda判断一个人名如果超过3个字就认为是很长的名字
public static void main(String[] args) {
System.out.println("开始啦");
isLongName((String name) -> {
return name.length() > 3;
});
}
public static void isLongName(Predicate<String> predicate) {
System.out.println("aa");
boolean isLong = predicate.test("迪丽热巴");
System.out.println("是否是长名字: " + isLong);
}
}
默认方法:and,使用“与”逻辑实现“并且”的效果。
默认方法:or,实现逻辑关系中的“或”。
默认方法:negate,“非”(取反)。
package com.bz.jdk8.demo03functionalinterface;
import java.util.function.Predicate;
public class Demo08Predicate_And_Or_Negate {
// 使用Lambda表达式判断一个字符串中即包含W,也包含H
// 使用Lambda表达式判断一个字符串中包含W或者包含H
// 使用Lambda表达式判断一个字符串中不包含W
public static void main(String[] args) {
test((String str) -> {
// 判断是否包含W
return str.contains("W");
}, (String str) -> {
// 判断是否包含H
return str.contains("H");
});
}
public static void test(Predicate<String> p1, Predicate<String> p2) {
// String str = "Hello orld";
// boolean b1 = p1.test(str);
// boolean b2 = p2.test(str);
// if (b1 && b2) {
// System.out.println("即包含W,也包含H");
// }
// 使用Lambda表达式判断一个字符串中即包含W,也包含H
String str = "Hello World";
boolean b = p1.and(p2).test(str);
if (b) {
System.out.println("即包含W,也包含H");
}
// 使用Lambda表达式判断一个字符串中包含W或者包含H
boolean b1 = p1.or(p2).test(str);
if (b1) {
System.out.println("包含W或者包含H");
}
// 使用Lambda表达式判断一个字符串中不包含W
boolean b2 = p1.negate().test("Hello W");
// negate相当于取反 !boolean
if (b2) {
System.out.println("不包含W");
}
}
}
方法引用的格式
符号表示 ::
符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用,
package com.bz.jdk8.demo04methodref;
import java.sql.SQLOutput;
import java.util.function.Consumer;
/**
* 方法引用的格式
* 符号表示 : ::
* * 符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
* 应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用
*/
public class Demo01MethodRefIntro {
public static void printMax(Consumer<int[]> consumer) {
int[] arr = {11, 22, 33, 44, 55};
consumer.accept(arr);
}
//求一个数组的和
public static void getSum(int[] arr){
int sum = 0;
for (int i : arr){
sum += i;
}
System.out.println("和:"+sum);
}
// 使用方法引用
// 让这个指定的方法去重写接口的抽象方法,到时候调用接口的抽象方法就是调用传递过去的这个方法
public static void main(String[] args) {
// 使用Lambda表达式求一个数组的和
/*printMax((int[] arr) -> {
getMax(arr);
});*/
printMax(Demo01MethodRefIntro::getSum);
}
}
方法引用的注意事项:
被引用的方法,参数要和接口中抽象方法的参数一样。
当接口抽象方法有返回值时,被引用的方法也必须有返回值。
方法引用是对Lambda表达式符合特定情况下的一种缩写,它使得我们的Lambda表达式更加的精简,也可以理解为 Lambda表达式的缩写形式 , 不过要注意的是方法引用只能"引用"已经存在的方法!
/**
*对象名::引用成员方法
*
*注意:方法引用有两个注意事项
* 1.被引用的方法,参数要和接口中抽象方法的参数一样
* 2.当接口抽象方法有返回值时,被引用的方法也必须有返回值
*/
@Test
public void test1(){
Date now = new Date();
Supplier<Long> supplier = ()->{
return now.getTime();
};
Supplier<Long> sp = now::getTime;
Long aLong = sp.get();
System.out.println("时间:"+aLong);
}
/**
*类名::静态方法
*/
@Test
public void test2(){
Supplier<Long> supplier = ()->{
return System.currentTimeMillis();
};
Supplier<Long> sup = System::currentTimeMillis;
Long aLong = supplier.get();
Long aLong1 = sup.get();
System.out.println(aLong + "---"+aLong1);
}
/**
*类名::实例方法
*/
@Test
public void test3(){
/*Function f1 = (String str) -> {
return str.length();
};*/
// 类名::实例方法(注意:类名::类名::实例方法实际上会将第一个参数作为方法的调用者)
Function<String, Integer> f1 = String::length;
int length = f1.apply("hello");
System.out.println("length = " + length);
// BiFunction f2 = String::substring;
// 相当于这样的Lambda
BiFunction<String, Integer, String> f2 = (String str, Integer index) -> {
return str.substring(index);
};
String str2 = f2.apply("helloworld", 3);
System.out.println("str2 = " + str2); // loworld
}
// 类名::new引用类的构造器
@Test
public void test04() {
/*Supplier su1 = () -> {
return new Person();
};*/
Supplier<Person> su1 = Person::new;
Person person = su1.get();
System.out.println("person = " + person);
/*BiFunction bif = (String name, Integer age) -> {
return new Person(name, age);
};*/
BiFunction<String, Integer, Person> bif = Person::new;
Person p2 = bif.apply("凤姐", 18);
System.out.println("p2 = " + p2);
}
// 类型[]::new
@Test
public void test05() {
/*Function f1 = (Integer length) -> {
return new int[length];
};*/
Function<Integer, int[]> f1 = int[]::new;
int[] arr1 = f1.apply(10);
System.out.println(Arrays.toString(arr1));
}
注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!
Stream是流式思想,相当于工厂的流水线,对集合中的数据进行加工处理
package com.bz.jdk8.demo05stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/*
目标:体验集合操作数据的弊端
小结:
1.集合操作数据的弊端?
每个需求都要循环一次,还要搞一个新集合来装数据, 麻烦
*/
public class Demo01Intro {
public static void main(String[] args) {
// 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
// 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
List<String> arr = new ArrayList<>();
Collections.addAll(arr,"张无忌","周芷若","张强","张三丰");
List<String> list = new ArrayList<>();
// 1.拿到所有姓张的
for (String str : arr){
if (str.contains("张")){
list.add(str);
}
}
//拿到名字长度为3个字的
List<String> three = new ArrayList<>();
for (String str : arr){
if (str.length() == 3){
three.add(str);
}
}
System.out.println(Arrays.toString(list.toArray()));
System.out.println(Arrays.toString(three.toArray()));
System.out.println("=================");
//使用流
arr.stream().filter(str->{
return str.startsWith("张");
}).filter(str->{
return str.length() == 3;
}).forEach(s -> System.out.println(s));
}
}
package com.bz.jdk8.demo05stream;
import java.util.*;
import java.util.stream.Stream;
/**
* java.util.stream.Stream 是JDK 8新加入的流接口。
* 获取一个流非常简单,有以下几种常用的方式:
* 所有的 Collection 集合都可以通过 stream 默认方法获取流;
* Stream 接口的静态方法 of 可以获取数组对应的流。
*/
public class Demo02GetStream {
public static void main(String[] args) {
// 方式1 : 根据Collection获取流
// Collection接口中有一个默认的方法: default Stream stream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String, String> map = new HashMap<>();
Stream<String> stream3 = map.keySet().stream();
Stream<String> stream4 = map.values().stream();
Stream<Map.Entry<String, String>> stream5 = map.entrySet().stream();
}
}
// 方式2 : Stream中的静态方法of获取流
// static Stream of(T... values)
Stream<String> stream6 = Stream.of("aa", "bb", "cc");
String[] strs = {"aa", "bb", "cc"};
Stream<String> stream7 = Stream.of(strs);
// 基本数据类型的数组行不行?不行的,会将整个数组看做一个元素进行操作.
int[] arr = {11, 22, 33};
Stream<int[]> stream8 = Stream.of(arr);
package com.bz.jdk8.demo05stream;
import java.util.stream.Stream;
/**
* 我们学习了Stream的常用方法,我们知道Stream这些常用方法可以分成两类,
* 终结方法,
* 函数拼接方法
*
* Stream的3个注意事项:
* 1. Stream只能操作一次
* 2. Stream方法返回的是新的流
* 3. Stream不调用终结方法,中间的操作不会执行
*/
public class Demo03StreamNotice {
public static void main(String[] args) {
Stream<String> stream = Stream.of("aa", "bb", "cc");
//stream只能操作一次
// long count = stream.count();
// long count1 = stream.count();// stream has already been operated upon or closed
// Stream limit = stream.limit(1);
//
// System.out.println("流是否相等:"+(stream == limit));//流是否相等:false
//3. Stream不调用终结方法,中间的操作不会执行
stream.filter(s->{
System.out.println(s);
return true;
}).count();
}
}
/**
* forEach 用来遍历流中的数据
* 该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。
*/
@Test
public void testForEach() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
//lambda表达式遍历
// one.stream().forEach(str-> System.out.println(str));
//使用方法引用
one.stream().forEach(System.out::println);
}
/**
* Stream流提供 count 方法来统计其中的元素个数
* 该方法返回一个long值代表元素个数。
*/
@Test
public void testCount() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
long count = one.stream().count();
System.out.println("总数:"+count);
}
/**
* filter用于过滤数据,返回符合过滤条件的数据
* 可以通过 filter 方法将一个流转换成另一个子集流。
*
* 该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
*/
@Test
public void testFilter() {
List<String> arr = new ArrayList<>();
Collections.addAll(arr, "宋远桥", "苏星河", "老子", "庄子", "孙子");
arr.stream().filter(str->str.length() == 3).forEach(System.out::println);//宋远桥,苏星河
}
/**
*limit 方法可以对流进行截取,只取用前n个。
* 参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。
*/
@Test
public void testLimit() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
one.stream().limit(3).forEach(System.out::println);
}
/**
* 如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
*
* 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
*/
@Test
public void testSkip() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
one.stream().skip(2).forEach(System.out::println);
}
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
/**
* 如果需要将流中的元素映射到另一个流中,可以使用 map 方法。
* 该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
*/
@Test
public void testMap() {
Stream<String> original = Stream.of("11", "22", "33");
// original.map(str->Integer.parseInt(str)).forEach(System.out::println);
// 将Stream流中的字符串转成Integer
//方法引用简化
original.map(Integer::parseInt).forEach(System.out::println);
}
/**
*如果需要将数据排序,可以使用 sorted 方法。
*sorted 方法根据元素的自然顺序排序,也可以指定比较器排序
*/
@Test
public void testSorted() {
// sorted(): 根据元素的自然顺序排序
// sorted(Comparator super T> comparator): 根据比较器指定的规则排序
Stream<Integer> stream = Stream.of(33, 22, 11, 55);
// stream.sorted().forEach(System.out::println);
//自定义排序规则
stream.sorted((o1, o2) -> o2-o1).forEach(System.out::println);
}
/**
*如果需要去除重复数据,可以使用 distinct 方法。
*
* 自定义类型是根据对象的hashCode和equals来去除重复元素的。
*/
@Test
public void testDistinct() {
Stream<Integer> stream = Stream.of(22, 33, 22, 11, 33);
stream.distinct().forEach(System.out::println);
Stream<String> streamStr = Stream.of("aa","bb","cc","bb");
streamStr.distinct().forEach(System.out::println);
System.out.println("===============");
//自定义类型是根据对象的hashCode和equals来去除重复元素的。
Stream<Person> streamPer = Stream.of(
new Person("貂蝉", 18),
new Person("杨玉环", 20),
new Person("杨玉环", 20),
new Person("西施", 16),
new Person("西施", 16),
new Person("王昭君", 25)
);
streamPer.distinct().forEach(System.out::println);
}
/**
*如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法。
* allMatch: 元素是否全部满足条件
* anyMatch: 元素是否任意有一个满足条件
* noneMatch: 元素是否全部不满足条件
*/
@Test
public void testMatch() {
Stream<Integer> stream = Stream.of(5, 3, 6, 1);
//allMatch: 匹配所有元素,所有元素都需要满足条件
// boolean all = stream.allMatch(i -> i > 0);
// System.out.println("是否都大于0:"+all);
//anyMatch: 匹配某个元素,只要有其中一个元素满足条件即可
// boolean any = stream.anyMatch(i -> i > 5);
// System.out.println("是否存在大于5的元素:"+any);
//noneMatch: 匹配所有元素,所有元素都不满足条件
boolean none = stream.noneMatch(i -> i < 0);
System.out.println("是否都不满足:"+none);
}
/**
*如果需要找到某些数据,可以使用 find 相关方法。
*/
@Test
public void testFind() {
Stream<Integer> stream = Stream.of(33, 11, 22, 5);
//获取第一个元素
// Optional first = stream.findFirst();
Optional<Integer> any = stream.findAny();
System.out.println(any.get());
}
/**
* 如果需要获取最大和最小值,可以使用 max 和 min 方法。
*/
@Test
public void testMax_Min() {
Integer max = Stream.of(7, 5, 3, 8, 2, 6).max((o1, o2) -> o1 - o2).get();
System.out.println("最大值:"+max);//最大值:8
Integer min = Stream.of(7, 5, 3, 8, 2, 6).min((o1, o2) -> o1 - o2).get();
System.out.println("最小值:"+min);//最小值:2
}
/**
* 如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法。
*/
@Test
public void testReduce() {
// T reduce(T identity, BinaryOperator accumulator);
// T identity: 默认值
// BinaryOperator accumulator: 对数据进行处理的方式
// reduce如何执行?
// 第一次, 将默认值赋值给x, 取出集合第一元素赋值给y
// 第二次, 将上一次返回的结果赋值x, 取出集合第二元素赋值给y
// 第三次, 将上一次返回的结果赋值x, 取出集合第三元素赋值给y
// 第四次, 将上一次返回的结果赋值x, 取出集合第四元素赋值给y
Integer reduce = Stream.of(4, 5, 3, 9).reduce(0, (x, y) -> {
System.out.println("x:" + x + ", y: " + y);
return x + y;
});
System.out.println("reduce:"+reduce);
System.out.println("==============");
//获取最大值
Integer max = Stream.of(6,3,7,9).reduce(0,(x,y)->{
return x > y ? x : y;
});
System.out.println("最大值:"+max);
}
Stream流的map和reduce组合使用
//Stream流的map和reduce组合使用
@Test
public void testMapReduce() {
// 求出所有年龄的总和
// 1.得到所有的年龄
// 2.让年龄相加
Integer totalAge = Stream.of(
new Person("刘德华", 58),
new Person("张学友", 56),
new Person("郭富城", 54),
new Person("黎明", 52))
.map(Person::getAge).reduce(0, Integer::sum);
System.out.println("totalAge = " + totalAge);
// 找出最大年龄
// 1.得到所有的年龄
// 2.获取最大的年龄
Integer maxAge = Stream.of(
new Person("刘德华", 58),
new Person("张学友", 56),
new Person("郭富城", 54),
new Person("黎明", 52))
.map(Person::getAge)
.reduce(0, Math::max);
System.out.println("maxAge = " + maxAge);
// 统计 a 出现的次数
// 1 0 0 1 0 1
Integer count = Stream.of("a", "c", "b", "a", "b", "a")
.map(s -> {
if (s == "a") {
return 1;
} else {
return 0;
}
})
.reduce(0, Integer::sum);
System.out.println("count = " + count);
}
/**
* 如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。
*/
@Test
public void testNumericStream() {
// Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
// 把大于3的打印出来
stream.filter(i -> i > 3).forEach(System.out::println);
System.out.println("=========先将流中的Integer数据转成int,后续都是操作int类型=======");
IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(Integer::intValue);
intStream.filter(i -> i > 3).forEach(System.out::println);
}
/**
* 如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
*
* 备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的
*/
@Test
public void testContact() {
Stream<String> streamA = Stream.of("张三");
Stream<String> streamB = Stream.of("李四");
// 合并成一个流
Stream<String> newStream = Stream.concat(streamA, streamB);
// 注意:合并流之后,不能操作之前的流啦.
// streamA.forEach(System.out::println);
newStream.forEach(System.out::println);
}
package com.bz.jdk8.demo05stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
* Stream综合案例
* 现在有两个 ArrayList 集合存储队伍当若干集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依
* 次进行以下操作步骤:
* 1. 第一个队伍只要名字为3个字的成员
* 2. 第一个队伍筛选之后只要前3个人
* 3. 第二个队伍只要姓张的成员姓名;
* 4. 第二个队伍筛选之后不要前2个人;
* 5. 将两个队伍合并为一个队伍;
* 6. 根据姓名创建 Person 对象;
* 7. 打印整个队伍的Person对象信息。
*/
public class Demo05 {
public static void main(String[] args) {
// 第一个队伍
List<String> one = new ArrayList<>();
Collections.addAll(one,"迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
// 第二个队伍
List<String> two = new ArrayList<>();
Collections.addAll(two,"古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
//1. 第一个队伍只要名字为3个字的成员姓名;
//2. 第一个队伍筛选之后只要前3个人;
Stream<String> streamA = one.stream().filter(str -> str.length() == 3).limit(3);
//3. 第二个队伍只要姓张的成员姓名;
//4. 第二个队伍筛选之后不要前2个人;
Stream<String> streamB = two.stream().filter(str -> str.startsWith("张")).skip(2);
//5. 将两个队伍合并为一个队伍;
Stream<String> stream = Stream.concat(streamA,streamB);
//6. 根据姓名创建 Person 对象;
//7. 打印整个队伍的Person对象信息。
stream.map(Person2::new).forEach(System.out::println);
}
}
/**
* 将流中数据收集到集合中
* Stream流提供 collect 方法,其参数需要一个 java.util.stream.Collector 接口对象来指定收集到哪
* 种集合中。java.util.stream.Collectors 类提供一些方法,可以作为 Collector`接口的实例:
*/
@Test
public void testStreamToCollection() {
Stream<String> stream = Stream.of("aa", "bb", "cc", "bb");
//list集合
// List list = stream.collect(Collectors.toList());
// System.out.println("list= "+list);
//set集合
// Set set = stream.collect(Collectors.toSet());
// System.out.println("set="+set);
//收集到指定的集合中ArrayList
// ArrayList arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
// System.out.println("收集到指定的集合中ArrayList="+arrayList);
//HashSet
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
System.out.println("hashSet="+hashSet);
}
/**
* Stream流中的结果到数组中
* Stream提供 toArray 方法来将结果放到一个数组中,返回值类型是Object[]的:
*/
@Test
public void testStreamToArray() {
Stream<String> stream = Stream.of("aa", "bb", "cc");
// 转成Object数组不方便
// Object[] objects = stream.toArray();
// for (Object o : objects) {
// System.out.println("o = " + o);
// }
// String[]
String[] strings = stream.toArray(String[]::new);
for (String string : strings) {
System.out.println("string = " + string + ", 长度: " + string.length());
}
}
// 其他收集流中数据的方式(相当于数据库中的聚合函数)
@Test
public void testStreamToOther() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
// Optional max = studentStream.collect(Collectors.maxBy((s1, s2) -> s1.getSocre() - s2.getSocre()));
// System.out.println("最大值: " + max.get());
// Optional min = studentStream.collect(Collectors.minBy((o1, o2) -> o1.getSocre() - o2.getSocre()));
// System.out.println("最小值:"+min);
// Integer count = studentStream.collect(Collectors.summingInt(Student::getAge));
// System.out.println("年龄总和:"+count);
// 平均值
// Double avg = studentStream.collect(Collectors.averagingInt(s -> s.getSocre()));
// Double avg = studentStream.collect(Collectors.averagingInt(Student::getSocre));
// System.out.println("平均值: " + avg);
// 统计数量
Long count = studentStream.collect(Collectors.counting());
System.out.println("统计数量: " + count);
}
/**
* 对流中数据进行分组
* 当我们使用Stream流处理数据后,可以根据某个属性将数据分组:
*/
@Test
public void testGroup() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33));
//根据年龄分组
// Map> ageGroup = studentStream.collect(Collectors.groupingBy(Student::getAge));
//根据分数分组
Map<String, List<Student>> socreGroup = studentStream.collect(Collectors.groupingBy(s -> {
if (s.getSocre() > 60) {
return "及格";
} else {
return "不及格";
}
}));
socreGroup.forEach((k,v)-> System.out.println(k + "::" +v));
}
// 多级分组
@Test
public void testCustomGroup() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33));
// 先根据年龄分组,每组中在根据成绩分组
Map<Integer, Map<String, List<Student>>> map = studentStream.collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy((s) -> {
if (s.getSocre() > 60) {
return "及格";
} else {
return "不及格";
}
})));
// 遍历
map.forEach((k, v) -> {
System.out.println(k);
// v还是一个map,再次遍历
v.forEach((k2, v2) -> {
System.out.println("\t" + k2 + " == " + v2);
});
});
}
// 分区
@Test
public void testPartition() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33));
Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(s -> {
return s.getSocre() > 60;
}));
map.forEach((k,v)-> System.out.println(k + "=="+v));
}
// 拼接
@Test
public void testJoining() {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
//姓名使用_拼接
// String name = studentStream.map(Student::getName).collect(Collectors.joining("_"));
// System.out.println(name); //赵丽颖_杨颖_迪丽热巴_柳岩
// 根据三个字符串拼接
String name = studentStream.map(Student::getName).collect(Collectors.joining("_", "^_^", "V_V"));
System.out.println(name);//^_^赵丽颖_杨颖_迪丽热巴_柳岩V_V
}
@Test
public void test0Serial() {
Stream.of(4, 5, 3, 9, 1, 2, 6)
.filter(s -> {
System.out.println(Thread.currentThread() + "::" + s);
return s > 3;
}).count();
}
@Test
public void testgetParallelStream() {
// 掌握获取并行Stream流的两种方式
// 方式一:直接获取并行的Stream流
List<String> list = new ArrayList<>();
Stream<String> stream = list.parallelStream();
// 方式二:将串行流转成并行流
Stream<String> parallel = list.stream().parallel();
}
private static final int times = 500000000;
long start;
@Before
public void init() {
start = System.currentTimeMillis();
}
@After
public void destory() {
long end = System.currentTimeMillis();
System.out.println("消耗时间:" + (end - start));
}
// 并行的Stream : 消耗时间:137 ---155
@Test
public void testParallelStream() {
LongStream.rangeClosed(0, times).parallel().reduce(0, Long::sum);
}
// 串行的Stream : 消耗时间:343 ---210
@Test
public void testStream() {
// 得到5亿个数字,并求和
LongStream.rangeClosed(0, times).reduce(0, Long::sum);
}
// 使用for循环 : 消耗时间:235 ---164
@Test
public void testFor() {
int sum = 0;
for (int i = 0; i < times; i++) {
sum += i;
}
}
// parallelStream线程安全问题
@Test
public void parallelStreamNotice() {
ArrayList<Integer> list = new ArrayList<>();
/*IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> {
list.add(i);
});
System.out.println("list = " + list.size());*/
// 解决parallelStream线程安全问题方案一: 使用同步代码块
/*Object obj = new Object();
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> {
synchronized (obj) {
list.add(i);
}
});*/
// 解决parallelStream线程安全问题方案二: 使用线程安全的集合
// Vector v = new Vector();
/*List synchronizedList = Collections.synchronizedList(list);
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> {
synchronizedList.add(i);
});
System.out.println("list = " + synchronizedList.size());*/
// 解决parallelStream线程安全问题方案三: 调用Stream流的collect/toArray
List<Integer> collect = IntStream.rangeClosed(1, 1000)
.parallel()
.boxed()
.collect(Collectors.toList());
System.out.println("collect.size = " + collect.size());
}
parallelStream使用的是Fork/Join框架。Fork/Join框架自JDK 7引入。Fork/Join框架可以将一个大任务拆分为很多小任务来异步执行。
Fork/Join框架主要包含三个模块:
Fork/Join原理-分治法
ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法,ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。
Fork/Join原理-工作窃取算法
Fork/Join最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的cpu,那么如何利用好这个空闲的cpu就成了提高性能的关键,而这里我们要提到的工作窃取(work-stealing)算法就是整个Fork/Join框架的核心理念Fork/Join工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。
package com.bz.jdk8.demo05stream;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* Fork/Join框架介绍
* parallelStream使用的是Fork/Join框架。
*
* Fork/Join框架可以将一个大任务拆分为很多小任务来异步执行。
* Fork/Join框架主要包含三个模块:
* 1. 线程池:ForkJoinPool
* 2. 任务对象:ForkJoinTask
* 3. 执行任务的线程:ForkJoinWorkerThread
*/
public class Demo08ForkJoin {
public static void main(String[] args) {
long start = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
SumRecursiveTask task = new SumRecursiveTask(1, 99999999999L);
Long result = pool.invoke(task);
System.out.println("result = " + result);
long end = System.currentTimeMillis();
System.out.println("消耗时间: " + (end - start));
}
}
// 1.创建一个求和的任务
// RecursiveTask: 一个任务
class SumRecursiveTask extends RecursiveTask<Long> {
// 是否要拆分的临界值
private static final long THRESHOLD = 3000L;
// 起始值
private final long start;
// 结束值
private final long end;
public 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;
}
return sum;
} else {
// 拆分
long middle = (start + end) / 2;
SumRecursiveTask left = new SumRecursiveTask(start, middle);
left.fork();
SumRecursiveTask right = new SumRecursiveTask(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
总结:
Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。
package com.bz.jdk8.demo06optional;
import org.junit.Test;
import java.util.Optional;
/**
* Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。
* 它的作用主要就是为了解决避免Null检查,防止NullPointerException。
*/
public class Demo01 {
public static void main(String[] args) {
}
@Test
public void test05() {
User u = new User("Hello", 18);
// getUpperUserName1(u);
Optional<User> op = Optional.of(u);
System.out.println(getUpperUserName2(op));
}
// 定义一个方法将User中的用户名转成大写并返回
// 使用Optional方式
public String getUpperUserName2(Optional<User> op) {
/*String upperName = op.map(u -> u.getUserName())
.map(s -> s.toUpperCase())
.orElse("null");*/
String upperName = op.map(User::getUserName)
.map(String::toUpperCase)
.orElse("null");
return upperName;
}
// 定义一个方法将User中的用户名转成大写并返回
// 使用传统方式
public String getUpperUserName1(User u) {
if (u != null) {
String userName = u.getUserName();
if (userName != null) {
return userName.toUpperCase();
} else {
return null;
}
} else {
return null;
}
}
// Optional类的基本使用
@Test
public void test02() {
// 1.创建Optional对象
// of:只能传入一个具体值,不能传入null
// ofNullable: 既可以传入具体值,也可以传入null
// empty: 存入的是null
Optional<String> op1 = Optional.of("凤姐");
// 2.isPresent: 判断Optional中是否有具体值, 有值返回true,没有值返回false
// boolean present = op1.isPresent();
// System.out.println("present = " + present);
// 3.get: 获取Optional中的值,如果有值就返回值具体值,没有值就报错
// System.out.println(op3.get());
if (op1.isPresent()) {
System.out.println(op1.get());
} else {
System.out.println("没有值");
}
}
// 以前对null的处理方式
@Test
public void test01() {
String userName = "凤姐";
if (userName != null) {
System.out.println("姓名为: " + userName);
} else {
System.out.println("姓名不存在");
}
}
}
旧版日期时间 API 存在的问题
package com.bz.jdk8.demo07newdatetimeapi;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 旧版日期时间 API 存在的问题
* 1. 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包
* 含日期。此外用于格式化和解析的类在java.text包中定义。
* 2. 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
* 3. 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和
* java.util.TimeZone类,但他们同样存在上述所有的问题。
*/
public class Demo01 {
public static void main(String[] args) {
//设计不合理
Date date = new Date(2022,9,23);
System.out.println(date);//Mon Oct 23 00:00:00 GMT+08:00 3922
// 2.时间格式化和解析是线程不安全的
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0;i < 50;i++){
new Thread(()->{
Date now = null;
try {
now = sdf.parse("2022-12-10");
} catch (ParseException e) {
throw new RuntimeException(e);
}
System.out.println(now);
}).start();
}
}
}
@Test
public void testLocalDate() {
//日期格式,年月日
LocalDate localDate = LocalDate.of(2022, 12, 20);
System.out.println(localDate);//2022-12-20
//获取当前的年,月,日
LocalDate now = LocalDate.now();
System.out.println(now.getYear());
System.out.println(now.getMonthValue());
System.out.println(now.getDayOfMonth());
}
/**
* LocalTime: 表示时间,有时分秒
*/
@Test
public void testLocalTime() {
LocalTime localTime = LocalTime.of(15, 43, 56);
System.out.println(localTime);//15:43:56
LocalTime now = LocalTime.now();
System.out.println("当前时间:"+now);//当前时间:15:46:47.207
System.out.println("时:"+now.getHour()+"分:"+now.getMinute()+"秒:"+now.getSecond()+"纳秒:"+now.getNano());//时:15分:46秒:47纳秒:207000000
}
/**
* 日期时间
*/
@Test
public void testLocalDateTime() {
LocalDateTime localDateTime = LocalDateTime.of(2022, 12, 20, 15, 48, 30);
System.out.println(localDateTime);//2022-12-20T15:48:30
LocalDateTime now = LocalDateTime.now();
System.out.println("当前日期:"+now);//2022-12-20T15:50:30.465
System.out.println("当前年:"+now.getYear());//当前年:2022
System.out.println("当前时:"+now.getHour());//当前时:15
}
/**
* 修改时间
*/
@Test
public void testLocalDateTime2() {
LocalDateTime now = LocalDateTime.now();
//修改时间
LocalDateTime withYear = now.withYear(9102);
System.out.println("修改后的时间:"+withYear);//修改后的时间:9102-12-20T15:54:55.656
System.out.println(now == withYear);//false
//增加或减去指定的时间,plus:增加 minus:减少
System.out.println("增加了2年后:"+now.plusYears(2));//增加了2年后:2024-12-20T15:54:55.656
System.out.println("减少了5个月:"+now.minusMonths(5));//减少了5个月:2022-07-20T15:54:55.656
}
/**
* 比较时间
*/
@Test
public void testEquals() {
LocalDateTime dateTime = LocalDateTime.of(2022, 12, 20, 16, 00, 00);
LocalDateTime now = LocalDateTime.now();
System.out.println("当前时间是否在指定时间之前:"+now.isBefore(dateTime));//当前时间是否在指定时间之前:true
System.out.println("当前时间是否在指定时间之后:"+now.isAfter(dateTime));//当前时间是否在指定时间之后:false
System.out.println("当前时间是否等于指定时间:"+now.isEqual(dateTime));//当前时间是否等于指定时间:false
}
// 日期格式化
@Test
public void test04() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分SS秒");
String format = now.format(dtf);
System.out.println("格式化后的日期:"+format);//格式化后的日期:2022年12月20日 16时04分12秒
//解析
for (int i = 0; i < 50; i++) {
new Thread(()->{
LocalDateTime parse = LocalDateTime.parse("2022年09月20 15时16分16秒", dtf);
System.out.println("parse:"+parse);
}).start();
}
}
这里解析错误:
Exception in thread “Thread-1” Exception in thread “Thread-13” Exception in thread “Thread-8” Exception in thread “Thread-6” Exception in thread “Thread-2” Exception in thread “Thread-15” Exception in thread “Thread-18” Exception in thread “Thread-11” Exception in thread “Thread-12” java.time.format.DateTimeParseException: Text ‘2022年09月20 15时16分16秒’ could not be parsed at index 10
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)
// 时间戳
@Test
public void test07() {
// Instant内部保存了秒和纳秒,一般不是给用户使用的,而是方便我们程序做一些统计的.
Instant now = Instant.now();
System.out.println("Instant:"+now);//Instant:2022-12-20T08:13:24.426Z
Instant plus = now.plusSeconds(20);
System.out.println("plus:"+plus);//plus:2022-12-20T08:13:44.426Z
Instant minusSeconds = now.minusSeconds(20);
System.out.println("minus:"+minusSeconds);//minus:2022-12-20T08:13:04.426Z
//得到纳秒
long epochSecond = now.getEpochSecond();
System.out.println("epoch:"+epochSecond);//epoch:1671524004
}
// Duration/Period类: 计算日期时间差
@Test
public void test08() {
LocalTime now = LocalTime.now();
LocalTime localTime = LocalTime.of(16, 17, 20);
Duration duration = Duration.between(now, localTime);
System.out.println("相差的天数:"+duration.toDays());//相差的天数:0
System.out.println("相差的小时数:"+duration.toHours());//相差的小时数:0
System.out.println("相差的分钟数:"+duration.toMinutes());//相差的分钟数:-3
System.out.println("相差的秒数:"+duration.toMillis());//相差的秒数:-218907
System.out.println("================");
// Period计算日期的距离
LocalDate nowDate = LocalDate.now();
LocalDate localDate = LocalDate.of(1998, 11, 14);
// 让后面的时间减去前面的时间
Period period = Period.between(localDate,nowDate);
System.out.println("相差的年:" + period.getYears());//相差的年:24
System.out.println("相差的月:" + period.getMonths());//相差的月:1
System.out.println("相差的天:" + period.getDays());//相差的天:6
}
// TemporalAdjuster类:自定义调整时间
@Test
public void test09() {
LocalDateTime now = LocalDateTime.now();
// 将日期调整到“下一个月的第一天”操作。
TemporalAdjuster firstDayOfNextMonth = temporal -> {
// temporal要调整的时间
LocalDateTime dateTime = (LocalDateTime)temporal;
return dateTime.plusMonths(1).withDayOfMonth(1); // 下一个月的第一天
};
// JDK中自带了很多时间调整器
// LocalDateTime newDateTime = now.with(firstDayOfNextMonth);
LocalDateTime newDateTime = now.with(TemporalAdjusters.firstDayOfNextYear());
System.out.println("newDateTime = " + newDateTime);//newDateTime = 2023-01-01T18:33:24.152
}
// 设置日期时间的时区
@Test
public void test10() {
// 1.获取所有的时区ID
// ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 不带时间,获取计算机的当前时间
LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时
System.out.println("now = " + now);
// 2.操作带时区的类
// now(Clock.systemUTC()): 创建世界标准时间
ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
System.out.println("bz = " + bz);
// now(): 使用计算机的默认的时区,创建日期时间
ZonedDateTime now1 = ZonedDateTime.now();
System.out.println("now1 = " + now1); // 2019-10-19T16:19:44.007153500+08:00[Asia/Shanghai]
// 使用指定的时区创建日期时间
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver"));
System.out.println("now2 = " + now2); // 2019-10-19T01:53:41.225898600-07:00[America/Vancouver]
// 修改时区
// withZoneSameInstant: 即更改时区,也更改时间
ZonedDateTime withZoneSameInstant = now2.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
System.out.println("withZoneSameInstant = " + withZoneSameInstant); // 2019-10-19T16:53:41.225898600+08:00[Asia/Shanghai]
// withZoneSameLocal: 只更改时区,不更改时间
ZonedDateTime withZoneSameLocal = now2.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));
System.out.println("withZoneSameLocal = " + withZoneSameLocal); // 2019-10-19T01:54:52.058871300+08:00[Asia/Shanghai]
}
自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解。
package com.bz.jdk8.demo08annotation;
import org.junit.Test;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 配置重复注解
* 自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限
* 制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注
* 解。在JDK 8中使用@Repeatable注解定义重复注解。
*/
@MyTest("ta")
@MyTest("tb")
@MyTest("tc")
public class Demo01 {
@Test
@MyTest("ma")
@MyTest("mb")
public void test() {
}
public static void main(String[] args) throws NoSuchMethodException {
// 4.解析重复注解
// 获取类上的重复注解
// getAnnotationsByType是新增的API用户获取重复的注解
MyTest[] annotationsByType = Demo01.class.getAnnotationsByType(MyTest.class);
for (MyTest myTest : annotationsByType) {
System.out.println(myTest);
}
System.out.println("----------");
// 获取方法上的重复注解
MyTest[] tests = Demo01.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (MyTest test : tests) {
System.out.println(test);
}
}
}
// 1.定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyTests { // 这是重复注解的容器
MyTest[] value();
}
// 2.定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
String value();
}
JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。
TYPE_USE :表示注解可以再任何用到类型的地方使用。
package com.bz.jdk8.demo08annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
public class Demo02 <@TypeParam T> {
private @NotNull int a = 10;
public void test(@NotNull String str, @NotNull int a) {
@NotNull double d = 10.1;
}
public <@TypeParam E extends Integer> void test01() {
}
}
@Target(ElementType.TYPE_USE)
@interface NotNull {
}
@Target(ElementType.TYPE_PARAMETER)
@interface TypeParam {
}
介绍!!
要散布阳光到别人心里,先得自己心里有阳光。 --罗曼罗兰