(参数类型 参数名称)->{
方法体;
}
格式说明
无参Lambda与匿名内部类
package com.lld;
import com.lld.Inte.Smoke;
import com.lld.Inte.Study;
/**
* @ClassName Lambdademo1
* @Description TODO
* @Author LLD
* @Date 2020/3/6 17:51
* @Version 1.0
*/
public class Lambdademo1 {
/*
Lambda的标准格式:
(参数列表) -> {
}
(参数列表):参数列表
{}:方法体
->:没有实际含义
*/
public static void main(String[] args) {
goSmokeing(() -> {
System.out.println("Lambda在抽烟");
});
goSmokeing(new Smoke() {
@Override
public void smokeing() {
System.out.println("匿名内部类在抽烟");
}
});
}
}
//练习无参数的Lambda
public static void goSmokeing(Smoke smoke){
smoke.smokeing();
}
}
package com.lld.Inte;
/**
* @ClassName Smoke
* @Description TODO
* @Author LLD
* @Date 2020/3/6 18:00
* @Version 1.0
*/
public interface Smoke {
public abstract void smokeing();
}
有参Lambda与匿名内部类
package com.lld;
import com.lld.Inte.Smoke;
import com.lld.Inte.Study;
/**
* @ClassName Lambdademo1
* @Description TODO
* @Author LLD
* @Date 2020/3/6 17:51
* @Version 1.0
*/
public class Lambdademo1 {
/*
Lambda的标准格式:
(参数列表) -> {
}
(参数列表):参数列表
{}:方法体
->:没有实际含义
*/
public static void main(String[] args) {
goStudying(new Study() {
@Override
public int Studying(String str) {
System.out.println(str);
return 666;
}
},"曲秃在学习");
goStudying((String str) -> {
System.out.println(str);
return 666;
},"张渣在学习");
}
//练习有参数的Lambda
public static void goStudying(Study study,String str){
int i = study.Studying(str);
System.out.println("返回值:" + i);
}
}
package com.lld.Inte;
/**
* @ClassName Study
* @Description TODO
* @Author LLD
* @Date 2020/3/6 18:39
* @Version 1.0
*/
public interface Study {
public int Studying(String str);
}
匿名内部类在编译时候会生成一个class文件
Lambda在程序运行后会生成一个类
Lambda标准格式上的基础上,使用省略写法的规则为:
省略前
goStudying((String str) -> {
System.out.println(str);
},"张渣在学习");
省略后
goStudying(str -> System.out.println(str),"张渣在学习");
Lambda语法虽然简洁,但使用Lambda必须注意以下几点
示范代码
package com.lld;
/**
* @ClassName demo02
* @Description TODO
* @Author LLD
* @Date 2020/3/6 22:11
* @Version 1.0
*/
public class demo02 {
/*
1. 方法的参数或者局部变量类型必须为接口才能使用Lambda
2. 接口中只有一个抽象方法
*/
public static void main(String[] args) {
//方法参数为接口时
isFly(str -> {
System.out.println(str);
return true;
});
//局部变量类型为接口时
Flyable flyable = str -> true;
}
public static void isFly(Flyable flyable){
boolean b = flyable.isFly("是的");
System.out.println(b);
}
}
//只有一个抽象方法的接口称为函数式接口,我们就能使用Lambda表达式
@FunctionalInterface//该注解用于检测该接口内是否只有一个方法,当该接口内有多个方法时,就会报错
interface Flyable{
public boolean isFly(String str);
}
所需的类型不一样
匿名内部类需要的类型可以是类,抽象类,接口
Lambda表达式需要的必须是接口
抽象方法的数量不一样
匿名内部类所需接口中抽象方法数量随意
Lambda表达式所需接口只能有一个抽象方法
实现原理不同
匿名内部类会在编译后形成class
Lambda表达式在程序运行时动态形成class
jdk8以前的接口
interface 接口名{
静态常量;
抽象方法
}
增强后的
interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法
}
格式
interface 接口名{
修饰符 default 返回值类型 方法名(){
方法体;
}
}
package com.lld;
/**
* @ClassName demo03
* @Description TODO
* @Author LLD
* @Date 2020/3/7 10:03
* @Version 1.0
*/
public class demo03 {
public static void main(String[] args) {
BB bb = new BB();
bb.test1();
CC cc = new CC();
cc.test1();
}
}
interface AA{
//默认方法是有方法体的
public default void test1(){
System.out.println("这是AA接口的默认方法");
}
}
//默认方法使用方法一:实现类可以直接使用
class BB implements AA{
}
//默认方法使用方法二:实现类可以根据自己需要进行复写
class CC implements AA{
@Override
public void test1() {
System.out.println("这是CC实现类复写的默认方法");
}
}
格式
interface 接口名{
修饰符 static 返回值类型 方法名(){
方法体;
}
}
示例代码
package com.lld;
/**
* @ClassName demo04
* @Description TODO
* @Author LLD
* @Date 2020/3/7 10:13
* @Version 1.0
*/
public class demo04 {
/*
1. 接口的静态方法不会被实现类继承
2. 接口的静态方法不会被实现类复写
3. 接口的的静态方法使用:接口名.静态方法名进行调用
*/
public static void main(String[] args) {
AAA.test1();
}
}
interface AAA{
//接口的静态方法
public static void test1(){
System.out.println("这是接口AAA的静态方法");
}
}
小结
Lambda表达式的前提是函数式接口,而Lambda不需要关心接口名,抽象方法名,只关心参数列表及返回值类型
都在java.util.function;包下
Supplier接口
供给型接口,用于产生一个数据
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
package com.lld;
import java.util.Arrays;
import java.util.function.Supplier;
/**
* @ClassName demo05
* @Description TODO
* @Author LLD
* @Date 2020/3/7 15:08
* @Version 1.0
*/
public class demo05 {
/*
使用Supplier接口的get方法,没有入参,有返回值
*/
public static void main(String[] args) {
text(() ->{
//定义一个数组
int array[] = {1,99,88,126,66};
//对数组进行排序,默认升序排序
Arrays.sort(array);
//获取最大值
int i = array[array.length - 1];
return i;
});
}
public static void text(Supplier<Integer> supplier){
Integer integer = supplier.get();
System.out.println("integer:" + integer);
}
}
Consumer接口
消费型接口,用于消费一个数据,其参数类型由泛型决定
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
package com.lld;
import java.util.function.Consumer;
/**
* @ClassName demo06
* @Description TODO
* @Author LLD
* @Date 2020/3/7 15:39
* @Version 1.0
*/
public class demo06 {
/*
使用Consumer将一个字符串转成小写,再转成大写
*/
public static void main(String[] args) {
//转成小写再打印,s是入参 转成大写再打印,s是入参
printHello(s -> System.out.println(s.toLowerCase()),s -> System.out.println(s.toUpperCase()));
}
public static void printHello(Consumer<String> c1,Consumer<String> c2){
String str = "Hello World";
//表示执行完c1的accept再执行c2的accept
c1.andThen(c2).accept(str);
//c1.accept(str);
// c2.accept(str);
}
}
Function接口
java.util.function.Function
接口,根据一个类型的数据得到另一个类型的数据,前者为前置条件,后者为后置条件。有参数有返回值
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
package com.lld;
import java.util.function.Function;
/**
* @ClassName demo07
* @Description TODO
* @Author LLD
* @Date 2020/3/7 16:00
* @Version 1.0
*/
public class demo07 {
public static void main(String[] args) {
number(s -> Integer.parseInt(s), integer -> integer * 5);
}
//将字符串转成整型,再将整型数据乘以五
public static void number(Function<String,Integer> f1,Function<Integer,Integer> f2){
String str = "10";
/*Integer num1 = f1.apply(str);
System.out.println(num1);
Integer num2 = f2.apply(num1);
System.out.println(num2);*/
//将str转成Integer,再将Integer乘以5
System.out.println(f1.andThen(f2).apply(str));
}
}
Predicate接口
判断某个值,得到一个Boolean类型的数据,可以用java.util.function.Predicate接口
@FunctionalInterface
public interface Predicate {
boolean test(T t);
}
package com.lld;
import java.util.function.Predicate;
/**
* @ClassName demo08
* @Description TODO
* @Author LLD
* @Date 2020/3/7 16:40
* @Version 1.0
*/
public class demo08 {
public static void main(String[] args) {
//如果名字长度大于三,返回true,否则返回false
isLongName(s -> s.length()>3,"曲秃");
}
public static void isLongName(Predicate<String> predicate,String string){
boolean b = predicate.test(string);
System.out.println("名字很长吗? "+b);
}
}
package com.lld;
import java.util.function.Predicate;
/**
* @ClassName demo08
* @Description TODO
* @Author LLD
* @Date 2020/3/7 16:40
* @Version 1.0
*/
public class demo08 {
public static void main(String[] args) {
// * 使用Lambda表达式判断一个字符串中既包含W也包含H
// * 使用Lambda表达式判断一个字符串中既包含W或者包含H
// * 使用Lambda表达式判断一个字符串中既不包含W
test(s -> s.contains("W"),s -> s.contains("H"));
}
public static void test(Predicate<String> p1,Predicate<String> p2){
// * 使用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");
if (b2) {
System.out.println("不包含W");
}
}
}
使用方法引用前
package com.lld;
import java.util.function.Consumer;
/**
* @ClassName demo09
* @Description TODO
* @Author LLD
* @Date 2020/3/7 17:10
* @Version 1.0
*/
public class demo09 {
//将数组所有元素相加输出
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) {
//不使用方法引用
//int[] arr是入参
//getSum(arr);是调用了上面的静态方法
pringMax((int[] arr) -> {
getSum(arr);
});
}
//使用Consumer获得数组的总和
public static void pringMax(Consumer<int []> consumer){
int [] arr = {1,2,3,4,5,6,7,8,9,10};
consumer.accept(arr);
}
}
使用方法引用后
package com.lld;
import java.util.function.Consumer;
/**
* @ClassName demo09
* @Description TODO
* @Author LLD
* @Date 2020/3/7 17:10
* @Version 1.0
*/
public class demo09 {
//将数组所有元素相加输出
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) {
/*//不使用方法引用
//int[] arr是入参
//getSum(arr);是调用了上面的静态方法
pringMax((int[] arr) -> {
getSum(arr);
});*/
// 使用方法引用
pringMax(demo09::getSum);
}
//使用Consumer获得数组的总和
public static void pringMax(Consumer<int []> consumer){
int [] arr = {1,2,3,4,5,6,7,8,9,10};
consumer.accept(arr);
}
}
符号表示:::(一对冒号)
符号说明:双冒号为方法引用运算符,而他所在的表达式称为方法引用
应用场景:如果Lambda所要实现的方案,已经有其他方法存在相同方案,那么则可以使用方法引用
//对象::方法名
@Test
public void test01(){
//创建一个对象
Date date = new Date();
//不使用方法引用
//Supplier supplier = () -> date.getTime();
//使用方法引用
Supplier<Long> supplier1 = date::getTime;
System.out.println(supplier1.get());
/*
使用方法引用的两个注意事项
1. 被引用的方法,参数要和接口中的抽象方法参数一致
2. 当接口抽象方法有返回值时,被引用的方法必须有返回值
*/
}
注意事项
//类名::静态方法
@Test
public void test02(){
//不使用方法引用
//Supplier su = () -> System.currentTimeMillis();
//使用方法引用
Supplier<Long> su = System::currentTimeMillis;
Long time = su.get();
System.out.println("time = " + time);
}
//获取系统时间:new Date().getTime()底层也是调用的System.currentTimeMillis();方法
@Test
public void test03(){
//获得系统的时间,单位为毫秒,转换为妙
long totalMilliSeconds = System.currentTimeMillis();
long totalSeconds = totalMilliSeconds / 1000;
//求出现在的秒
long currentSecond = totalSeconds % 60;
//求出现在的分
long totalMinutes = totalSeconds / 60;
long currentMinute = totalMinutes % 60;
//求出现在的小时
long totalHour = totalMinutes / 60;
long currentHour = totalHour % 24;
//显示时间
System.out.println("总毫秒为: " + totalMilliSeconds);
System.out.println(currentHour + ":" + currentMinute + ":" + currentSecond + " GMT");
}
在Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者
//类名::实例方法
@Test
public void test04(){
//根据字符串获取长度
/*
//不使用方法引用
Function fn = (String s) -> {
return s.length();
};*/
//使用方法引用
Function<String,Integer> fn = String::length;
Integer integer = fn.apply("Hello World");
System.out.println("integer = " + integer);
//截取字符串
//使用方法引用:BiFunction泛型有三个参数,参数一:方法调用者,参数二:方法参数,参数三:返回值类型
BiFunction<String,Integer,String> bfn = (s, integer1) -> s.substring(integer1);
//上面方法其实就是
BiFunction<String,Integer,String> bfn1 = (String s,Integer i) ->{
return s.substring(i);
};
String s = bfn.apply("Hello world", 3);
System.out.println("s = " + s);
}
由于构造器名称与类名完全一致。所以构造器引用使用类名称::new的格式表示。
package com.lld;
/**
* @ClassName Person
* @Description TODO
* @Author LLD
* @Date 2020/3/7 19:27
* @Version 1.0
*/
public class Person {
private String name ;
private Integer age;
public Person() {
System.out.println("调用了无参数的构造方法");
}
public Person(String name, Integer age) {
System.out.println("调用了有参数的构造方法");
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//类名::new
@Test
public void test05() {
//不使用方法引用
/*Supplier s = () -> {
return new Person();
};*/
//使用方法引用
Supplier<Person> s = Person::new;
Person person = s.get();
System.out.println("person = " + person);
//不使用方法引用
/*BiFunction bfn = (s1, integer) -> {
return new Person(s1, integer);
};*/
//使用方法引用
BiFunction<String,Integer,Person> bfn = Person::new;
Person p1 = bfn.apply("曲秃", 22);
System.out.println("p1 = " + p1);
}
//数组::new
@Test
public void test06() {
//不使用方法引用
/*Function function = (Integer integer) -> {
return new int[integer];
};*/
//使用方法引用
Function<Integer,int[]> function = int[]::new;
int[] ints = function.apply(10);
System.out.println("Arrays.toString(ints) = " + Arrays.toString(ints));
}
方法引用是对Lambda表达式符合特定情况的一种缩写,它使我们的Lambda表达式更加精简,也可以理解为Lambda表达式的缩写形式,不过注意的是方法引用只能“引用”已经存在的方法!
package com.lld;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @ClassName demo01
* @Description TODO
* @Author LLD
* @Date 2020/3/7 20:09
* @Version 1.0
*/
public class demo01 {
// 1.拿到集合中所有姓张的
// 2.拿到名字为三个字的
// 3.进行输出
@Test
public void test01(){
//准备工作:将数据添加到list集合中
List<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
//不使用Stream流操作
// 1.拿到集合中所有姓张的
List<String> zhangList = new ArrayList<>();
for (String s : list) {
//判断第一个字符是不是张
if (s.startsWith("张")){
zhangList.add(s);
}
}
// 2.拿到名字为三个字的
List<String> twoList = new ArrayList<String>();
for (String s : zhangList) {
if (s.length() == 3){
twoList.add(s);
}
}
// 3.进行输出
System.out.println(twoList.toString());
}
}
注意:Stream流和IO流没有任何关系
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理,Stream可以看做流水线上的一个工序,在流水线上通过多个工序让一个原材料加工成一个商品
package com.lld;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @ClassName demo01
* @Description TODO
* @Author LLD
* @Date 2020/3/7 20:09
* @Version 1.0
*/
public class demo01 {
// 1.拿到集合中所有姓张的
// 2.拿到名字为三个字的
// 3.进行输出
@Test
public void test01(){
//准备工作:将数据添加到list集合中
List<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
//不使用Stream流操作
// 1.拿到集合中所有姓张的
List<String> zhangList = new ArrayList<>();
for (String s : list) {
//判断第一个字符是不是张
if (s.startsWith("张")){
zhangList.add(s);
}
}
// 2.拿到名字为三个字的
List<String> twoList = new ArrayList<String>();
for (String s : zhangList) {
if (s.length() == 3){
twoList.add(s);
}
}
// 3.进行输出
System.out.println(twoList.toString());
//分割线
System.out.println("------------------------------------------");
//使用Stream流操作
// 1.拿到集合中所有姓张的
// 2.拿到名字为三个字的
// 3.进行输出
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
}
}
@Test
//方法一:通过Collection的默认静态方法:default Stream stream()
public void test01(){
//方法一:通过Collection的默认静态方法:default Stream stream()
//list集合
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//set集合
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
//Map集合:map集合和collection没有关系,那么他怎么获取Stream流呢
Map<String,String> map = new HashMap<>();
//他的keyset方法时返回一个set集合,里面有所有的key
Set<String> keySet = map.keySet();
Stream<String> stream3 = keySet.stream();
//他的entrySet方法时返回一个set集合,里面有所有的key和value的关系
Set<Map.Entry<String, String>> entrySet = map.entrySet();
Stream<Map.Entry<String, String>> stream4 = entrySet.stream();
//他的values方法时返回一个sCollection集合,里面有所有的value
Collection<String> values = map.values();
Stream<String> stream5 = values.stream();
}
@Test
//通过Stream的静态方法of获取Stream流:public static Stream of(T... values)参数是可变长度的
public void test02() {
Stream<String> stream1 = Stream.of("张渣", "段黑", "曲秃", "莽夫", "奥特曼");
//String数组
String[] str = {"张渣", "段黑", "曲秃", "莽夫", "奥特曼"};
Stream<String> stream2 = Stream.of(str);
//基本数据类型的数组是不行的,因为他会将数组当做一个整体处理
int[] ints = {11,22,33,44,55,66};
Stream<int[]> Sstream3 = Stream.of(ints);//返回的泛型类型是int数组,而不是int
}
方法名 | 方法作用 | 返回值类型 | 方法种类 |
---|---|---|---|
count | 统计个数 | long | 终结 |
forEach | 逐一处理 | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取用前几个 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
其余更多方法:参考api文档
遍历
forEach用来输出
void forEach(Consumer super T> action);//接收一个Consumer接口
//使用forEach方法
@Test
public void test03() {
List<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
//使用Lambda表达式
list.stream().forEach(s -> {
System.out.println(s);
});
System.out.println("-----------------------------------------------");
//使用Lambda表达式简写
list.stream().forEach(s -> System.out.println(s));
System.out.println("-----------------------------------------------");
//使用方法引用
list.stream().forEach(System.out::println);
}
计算个数
long count();
//Stream流中Count计数
@Test
public void test04() {
List<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
long count = list.stream().count();
System.out.println(count);
}
过滤
Stream filter(Predicate super T> predicate);
//Stream流中Filter方法
@Test
public void test05() {
List<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
//过滤出姓名长度为二的并输出
list.stream().filter(s -> s.length() == 2).forEach(System.out::println);
}
选择前几个
Stream limit(long maxSize);
//Stream流的limit方法
@Test
public void test06() {
List<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
//取数组前三个进行输出
list.stream().limit(3).forEach(System.out::println);
}
跳过前几个
Stream skip(long n);
//Stream流的skip方法
@Test
public void testSkip() {
List<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
//跳过集合前三个进行输出
list.stream().skip(3).forEach(System.out::println);
}
将流中一种类型的数据转换成另一种类型的数据
//Stream流的map方法:将一种类型的流转换成另一种类型的流
@Test
public void testMap() {
Stream<String> stream = Stream.of("11", "22", "33", "44");
//使用Lambda表达式
/*stream.map((String s)->{
return Integer.parseInt(s);
}).forEach(System.out::println);*/
//使用Lambda表达式简写
/*stream.map(s -> Integer.parseInt(s)).forEach(System.out::println);*/
//使用方法引用
stream.map(Integer::parseInt).forEach(System.out::println);
}
如果要将数据进行排序,可以使用sorted方法
Stream<T> sorted();进行自然排序Stream<T>
Stream<T> sorted(Comparator<? super T> comparator);使用传入的比较器进行排序
//Stream流中的sorted方法:进行排序
@Test
public void testSorted() {
// Stream sorted();进行自然排序
// Stream sorted(Comparator super T> comparator);使用传入的比较器进行排序
Stream<Integer> stream = Stream.of(11, 22, 33, 66, 55, 982);
// 使用sorted的无参方法进行自然排序
//stream.sorted().forEach(System.out::println);
// 使用sorted的有参方法传入比较器进行降序排序
/* stream.sorted((Integer o1, Integer o2) -> {
return o2 - o1;
}).forEach(System.out::println);*/
// 使用sorted的有参方法传入比较器进行降序排序:使用简写
stream.sorted((o1, o2) -> o2-o1).forEach(System.out::println);
}
去重
//Stream流中distinct方法:String和Integer等类型去重
@Test
public void testDistinct() {
//字符串去重
Stream<String> stream = Stream.of("aa", "bb", "cc", "aa", "cc");
stream.distinct().forEach(System.out::println);
//Integer去重
Stream<Integer> stream1 = Stream.of(11, 22, 33, 44, 55, 66, 66, 55, 44);
stream1.distinct().forEach(System.out::println);
}
//Stream流中distinct方法:自定义类型去重(自定义类中一定要复写)
@Test
public void testDistinct1() {
Stream<Person> personStream = Stream.of(
new Person("段黑", 22),
new Person("张渣", 22),
new Person("曲秃", 22),
new Person("曲秃", 22),
new Person("段黑", 22),
new Person("张渣", 22)
);
personStream.distinct().forEach(System.out::println);
}
@Test
public void testMatch() {
Stream<Integer> stream = Stream.of(1, 2, 5, 9, 3);
//boolean b = stream.allMatch(integer -> integer > 0);//allMatch:当stream流中所有元素都满足大于0这个条件时,返回true,否则返回false
//boolean b = stream.anyMatch(integer -> integer > 7);//anyMatch:当stream流中任意元素满足大于7这个条件时,返回true,否则返回false
boolean b = stream.noneMatch(integer -> integer > 10);//noneMatch:当stream流中所有元素都不满足大于10这个条件时,返回true,否则返回false
System.out.println(b);
}
找出Stream流中的第一个元素
Optional<T> findAny();
Optional<T> findFirst();
@Test
public void testFind() {
Stream<Integer> integerStream = Stream.of(11, 22, 33, 44, 55);
//查找Stream中的第一个元素
//Optional integer = integerStream.findAny();
//查找Stream中的第一个元素,与findAny效果一致
Optional<Integer> first = integerStream.findFirst();
System.out.println(first.get());
}
@Test
public void testMax_Min() {
//获取最大值
Optional<Integer> max = Stream.of(11, 22, 1, 5, 9).max((o1, o2) -> o1 - o2);
System.out.println("最大值:" + max.get());
//获取最小值
Optional<Integer> min = Stream.of(11, 22, 1, 5, 9).min((o1, o2) -> o1 - o2);
System.out.println("最小值:" + min.get());
}
将所有的数据归纳总结,得到一个
@Test
public void testReduce() {
Stream<Integer> stream = Stream.of(4, 5, 9, 3);
// T reduce(T identity, BinaryOperator accumulator);
// 获得总和
// reduce是怎么执行的呢?
// 第一次,将默认值(参数一)赋给x,将Stream流第一个元素赋给y
// 第二次,将上一次返回的值赋给x,将Stream流中的第二个元素赋给y
// 第三次,将上一次返回的值赋给x,将Stream流中的第三个元素赋给y
// 第四次,将上一次返回的值赋给x,将Stream流中的第四个元素赋给y
//Integer reduce = stream.reduce(0, (x, y) -> x + y);
//获得最大值
Integer reduce = stream.reduce(0, (x, y) -> x > y ? x : y);
System.out.println("reduce = " + reduce);
}
//map方法和reduce方法一同使用
@Test
public void testMap_Reduce() {
// 1.求出年龄的和
// 使用map方法处理,其返回的流是年龄
// 使用reduce方法,设置默认值为0,使用integer的sum方法求和
Integer reduce = Stream.of(
new Person("段黑", 22),
new Person("张渣", 50),
new Person("曲秃", 28)).map(person -> person.getAge()).reduce(0, Integer::sum);
System.out.println("年龄总数 = " + reduce);
// 2.求出年龄最大的
// 使用map方法处理,其返回的流是年龄
// 使用reduce方法,设置默认值为0,使用integer的max方法获取最大值
Integer reduce1 = Stream.of(
new Person("段黑", 22),
new Person("张渣", 50),
new Person("曲秃", 28)).map(person -> person.getAge()).reduce(0, Integer::max);
System.out.println("年龄最大的 = " + reduce1);
// 3.计算a出现的次数
// 使用map方法进行判断,如果是a,返回1,不是,返回0
// 处理后的数据 1 0 1 0 1
// 使用reduce方法,设置默认值为零,使用Integer的sum方法求出a的个数
Integer reduce2 = Stream.of("a", "b", "a", "c", "a").map(s -> {
if (s.equals("a")) {
return 1;
} else {
return 0;
}
}).reduce(0, Integer::sum);
System.out.println("a出现的次数 = " + reduce2);
}
如果需要将Stream类型的数据转换为int
@Test
public void testmapToInt() {
// Integer占用内存比int多,在Stream六中操作会进行自动装箱和拆箱
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
// 把大于三的打印出类
// stream.filter(integer -> integer > 3).forEach(System.out::println);
// IntStream:内部操作的是int类型的数据,可以节省内存空间
/*IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(value -> {
return value.intValue();
});
intStream.forEach(System.out::println);*/
// 简写
IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(Integer::intValue);
intStream.filter(value -> value > 3).forEach(System.out::println);
}
将两个Stream流合并成一个流
@Test
public void testConcat() {
// 定义两个流
Stream<Person> 曲秃 = Stream.of(new Person("曲秃", 22));
Stream<Person> 张渣 = Stream.of(new Person("张渣", 21));
// 将两个流合并成为一个流
Stream<Person> newStream = Stream.concat(曲秃, 张渣);
newStream.forEach(System.out::println);
}
package com.lld;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
* @ClassName demo03
* @Description TODO
* @Author LLD
* @Date 2020/3/8 20:26
* @Version 1.0
*/
public class demo03 {
public static void main(String[] args) {
// 第一个队伍
List<String> one = new ArrayList<>();
Collections.addAll(one,"迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
// 第二个队伍
List<String> two = new ArrayList<>();
Collections.addAll(two,"古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
// 1.第一个队伍只要名字为三个字的成员名称
// one.stream().filter(s -> s.length() == 3).forEach(System.out::println);
// 2.第一个队伍筛选之后只要前三人
Stream<String> stream1 = one.stream().filter(s -> s.length() == 3).limit(3);
System.out.println("------------分割线---------------");
// 3.第二个队伍只要姓张的成员名称
//two.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
// 4.第二个队伍筛选之后不要前两人
Stream<String> stream2 = two.stream().filter(s -> s.startsWith("张")).skip(2);
// 5.将两个队伍合并成为一个队伍
Stream<String> newStream = Stream.concat(stream1,stream2);
// 6.根据姓名创建Person对象
Stream<Person> stream = newStream.map(Person::new);
// 7.打印整个队伍的Person对象的信息
stream.forEach(System.out::println);
}
}
@Test
public void testToCollection(){
// 一个Stream流
Stream<String> stream = Stream.of("ss", "xx", "cc");
//转成List
// List collect = stream.collect(Collectors.toList());
// System.out.println("collect = " + collect);
// 转成set
// Set collect = stream.collect(Collectors.toSet());
// System.out.println("collect = " + collect);
// 转成ArrayList
// ArrayList collect = stream.collect(Collectors.toCollection(ArrayList::new));
// System.out.println("collect = " + collect);
// 转成HashSet
// HashSet collect = stream.collect(Collectors.toCollection(HashSet::new));
// System.out.println("collect = " + collect);
}
@Test
public void testToArray() {
// 一个Stream流
Stream<String> stream = Stream.of("ss", "xx", "cc");
// toArray无参构造方法,转成object
// Object[] objects = stream.toArray();
// for (Object object : objects) {
// System.out.println("object = " + object);
// }
// toArray有参构造方法,参数:数组构造器
String[] strings = stream.toArray(String[]::new);
for (String string : strings) {
System.out.println("string = " + string);
}
}
// 其他流中数据聚合的方式:相当于数据库中的聚合函数
@Test
public void testStreamToOther() {
Stream<Student> studentStream = Stream.of(
new Student("张渣", 20, 90),
new Student("曲秃", 22, 92),
new Student("段黑", 21, 80),
new Student("奥特曼", 28, 85),
new Student("高莽夫", 24, 96)
);
// 1.获取成绩最大值
/*Optional student = studentStream.collect(Collectors.maxBy((o1, o2) -> o1.getGrade() - o2.getGrade()));
if (student.get() != null){
System.out.println(student.get());
}*/
// 2.获取成绩最小值
/*Optional student = studentStream.collect(Collectors.minBy((o1, o2) -> o1.getGrade() - o2.getGrade()));
if (student.get() != null){
System.out.println(student.get());
}*/
// 3.求总和
/*Integer sum = studentStream.collect(Collectors.summingInt(value -> value.getGrade()));
System.out.println("sum = " + sum);*/
// 4.平均值
/*Double avg = studentStream.collect(Collectors.averagingInt(value -> value.getGrade()));
System.out.println("avg = " + avg);*/
// 5.统计数量
Long count = studentStream.collect(Collectors.counting());
System.out.println("count = " + count);
}
@Test
public void testGroupBy() {
Stream<Student> studentStream = Stream.of(
new Student("张渣", 20, 30),
new Student("曲秃", 20, 40),
new Student("段黑", 22, 50),
new Student("奥特曼", 22, 85),
new Student("高莽夫", 22, 96)
);
// 1.根据年龄进行分组
// Map> map = studentStream.collect(Collectors.groupingBy(Student::getAge));
// map.forEach((integer, students) -> System.out.println(integer + "::" + students));
// 2.根据是否及格进行分组
Map<String, List<Student>> map = studentStream.collect(Collectors.groupingBy(o -> o.getGrade() > 60 ? "及格" : "不及格"));
map.forEach((s, students) -> System.out.println(s + "::" + students));
}
结果
不及格::[Student{name='张渣', age=20, grade=30}, Student{name='曲秃', age=20, grade=40}, Student{name='段黑', age=22, grade=50}]
及格::[Student{name='奥特曼', age=22, grade=85}, Student{name='高莽夫', age=22, grade=96}]
@Test
public void testCustomGroupBy() {
Stream<Student> studentStream = Stream.of(
new Student("张渣", 20, 30),
new Student("曲秃", 20, 80),
new Student("段黑", 22, 50),
new Student("奥特曼", 22, 30),
new Student("高莽夫", 22, 96)
);
// 先按照年龄分组,在按照成绩分组
Map<Integer, Map<String, List<Student>>> map = studentStream.collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy(o -> o.getGrade() > 60 ? "及格" : "不及格")));
// 遍历map集合
map.forEach((integer, stringListMap) -> {
System.out.println(integer);
// 遍历map中的map
stringListMap.forEach((s, students) -> System.out.println("\t" + s + " == " + students));
});
结果
20
不及格 == [Student{name='张渣', age=20, grade=30}]
及格 == [Student{name='曲秃', age=20, grade=80}]
22
不及格 == [Student{name='段黑', age=22, grade=50}, Student{name='奥特曼', age=22, grade=30}]
及格 == [Student{name='高莽夫', age=22, grade=96}]
Collectors.partitioningBy 会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表。
@Test
public void testPartition() {
Stream<Student> studentStream = Stream.of(
new Student("张渣", 20, 30),
new Student("曲秃", 20, 80),
new Student("段黑", 22, 50),
new Student("奥特曼", 22, 30),
new Student("高莽夫", 22, 96)
);
Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(o -> o.getGrade() > 60));
map.forEach((aBoolean, students) -> System.out.println(aBoolean + " :: " +students));
}
结果
false :: [Student{name='张渣', age=20, grade=30}, Student{name='段黑', age=22, grade=50}, Student{name='奥特曼', age=22, grade=30}]
true :: [Student{name='曲秃', age=20, grade=80}, Student{name='高莽夫', age=22, grade=96}]
Collectors.joining 会根据指定的连接符,将所有元素连接成一个字符串。
@Test
public void testJoining() {
Stream<Student> studentStream = Stream.of(
new Student("张渣", 20, 30),
new Student("曲秃", 20, 80),
new Student("段黑", 22, 50),
new Student("奥特曼", 22, 30),
new Student("高莽夫", 22, 96)
);
// 根据一个拼接:张渣__曲秃__段黑__奥特曼__高莽夫
// String s = studentStream.map(Student::getName).collect(Collectors.joining("__"));
// 根据三个参数拼接:^___^张渣__曲秃__段黑__奥特曼__高莽夫V___V
String s = studentStream.map(Student::getName).collect(Collectors.joining("__", "^___^", "V___V"));
System.out.println("s = " + s);
}
收集Stream流中的结果
到集合中: Collectors.toList()/Collectors.toSet()/Collectors.toCollection()
到数组中: toArray()/toArray(int[]::new)
聚合计算:
Collectors.maxBy/Collectors.minBy/Collectors.counting/Collectors.summingInt/Collectors.averagingInt
分组: Collectors.groupingBy
分区: Collectors.partitionBy
拼接: Collectors.joinging
@Test
public void test0Serial() {
long count = Stream.of(4, 5, 3, 9, 1, 2, 6)
.filter(s -> {
System.out.println(Thread.currentThread() + ", s = " + s);
return true;
})
.count();
System.out.println("count = " + count);
}
@Test
public void testgetParallelStream() {
ArrayList<Integer> list = new ArrayList<>();
// 直接获取并行的流
// Stream stream = list.parallelStream();
// 将串行流转成并行流
Stream<Integer> stream = list.stream().parallel();
}
操作代码
@Test
public void test0Parallel() {
long count = Stream.of(4, 5, 3, 9, 1, 2, 6)
.parallel() // 将流转成并发流,Stream处理的时候将才去
.filter(s -> {
System.out.println(Thread.currentThread() + ", s = " + s);
return true;
})
.count();
System.out.println("count = " + count);
}
private static Long times = 500000000L;
private Long start;
@Before
public void init(){
start = System.currentTimeMillis();
}
@After
public void destory(){
System.out.println(System.currentTimeMillis() - start);
}
//时间:2688毫秒
@Test
public void testFor() {
Long sum = 0L;
for (Long i = 0L; i < times; i++) {
sum += sum;
}
}
//时间:831毫秒
@Test
public void serialStream() {
LongStream.rangeClosed(0,times).reduce(0,Long::sum);
}
//时间:110毫秒
@Test
public void parallelStream() {
LongStream.rangeClosed(0,times).parallel().reduce(0,Long::sum);
}
我们可以看到parallelStream的效率是最高的。Stream并行处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作。
//parallelStream流的线程安全问题
@Test
public void parallelStreamNotice() {
List<Integer> list = new ArrayList<>();
// 测试线程是否安全:792个
/*IntStream.rangeClosed(0,1000).parallel().forEach(value -> list.add(value));
System.out.println(list.size());*/
// 解决方法一:使用同步代码块
/*Object obj = new Object();
IntStream.rangeClosed(0,999).parallel().forEach(value -> {
synchronized (obj){
list.add(value);
}
});
System.out.println(list.size());*/
// 解决方法二:使用线程安全的集合
// 获得一个线程安全的list集合
/*List list1 = Collections.synchronizedList(list);
IntStream.rangeClosed(0,999).parallel().forEach(value -> list1.add(value));
System.out.println(list1.size());*/
// 解决方法三:调用Stream的 toArray() / collect()
/*List collect = IntStream.rangeClosed(0, 999).parallel().boxed().collect(Collectors.toList());
System.out.println("collect = " + collect.size());*/
Integer[] array = IntStream.rangeClosed(0, 999).boxed().toArray(Integer[]::new);
System.out.println("array = " + array.length);
}
parallelStream使用的是Fork/Join框架。Fork/Join框架自JDK 7引入。Fork/Join框架可以将一个大任务拆分为很多小任务来异步执行。 Fork/Join框架主要包含三个模块:
ForkJoinPool 主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法,ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。
Fork/Join最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的cpu,那么如何利用好这个空闲的cpu就成了提高性能的关键,而这里我们要提到的工作窃取(work-stealing)算法就是整个Fork/Join框架的核心理念Fork/Join工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。
那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。上文中已经提到了在Java 8引入了自动并行化的概念。它能够让一部分Java代码自动地以并行的方式执行,也就是我们使用了ForkJoinPool的ParallelStream。
对于ForkJoinPool通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置系统属性:java.util.concurrent.ForkJoinPool.common.parallelism=N (N为线程数量),来调整ForkJoinPool的线程数量,可以尝试调整成不同的参数来观察每次的输出结果。
需求:使用Fork/Join计算1-10000的和,当一个任务的计算数量大于3000时拆分任务,数量小于3000时计算。
package com.lld;
/**
* @ClassName demo05ForkJoin
* @Description TODO
* @Author LLD
* @Date 2020/3/9 14:31
* @Version 1.0
*/
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class demo05ForkJoin {
public static void main(String[] args) {
long start = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
SumRecursiveTask task = new SumRecursiveTask(1, 10000L);
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;
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;
}
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();
}
}
}
// 以前对null的处理
@Test
public void test1(){
String str = "曲秃";
if (str == null){
System.out.println("str为null");
}else {
System.out.println(str);
}
}
Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。
Optional类的创建方式:
Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
Optional 类的常用方法:
isPresent() : 判断是否包含值,包含值返回true,不包含值返回false
get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException
orElse(T t) : 如果调用对象包含值,返回该值,否则返回参数t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
// 现在对null的处理
@Test
public void test02() {
// of:不可以传入null
// ofNullable:可以传入值,也可以传入null
// empty:默认null,不可传值
// Optional userNameO = Optional.of("凤姐");
// Optional userNameO = Optional.of(null);
// Optional userNameO = Optional.ofNullable(null);
Optional<String> userNameO = Optional.empty();
// isPresent() : 判断是否包含值,包含值返回true,不包含值返回false。
if (userNameO.isPresent()) {
// get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException。
String userName = userNameO.get();
System.out.println("用户名为:" + userName);
} else {
System.out.println("用户名不存在");
}
}
@Test
public void test3() {
User hello_world = new User(null);
String s = getUpperCase(hello_world);
System.out.println(s);
Optional<User> hello_world1 = Optional.of(hello_world);
System.out.println(getUpperCase1(hello_world1));
}
// 现在的方法
public String getUpperCase1(Optional<User> uO){
String s1 = uO.map(user -> user.getUsername()).map(s -> s.toUpperCase()).orElse("null");
return s1;
}
// 以前的方法
public String getUpperCase(User user){
if (user != null){
if (user.getUsername() != null){
return user.getUsername().toUpperCase();
}else {
return null;
}
}else {
return null;
}
}
@Test
public void test4() {
Optional<String> userNameO = Optional.of("凤姐");
// Optional userNameO = Optional.empty();
// 存在做的什么
// userNameO.ifPresent(s -> System.out.println("用户名为" + s));
// 存在做的什么,不存在做点什么
userNameO.ifPresentOrElse(s -> System.out.println("用户名为" + s)
, () -> System.out.println("用户名不存在"));
}
@Test
public void test5() {
// Optional userNameO = Optional.of("凤姐");
Optional<String> userNameO = Optional.empty();
// 如果调用对象包含值,返回该值,否则返回参数t
System.out.println("用户名为" + userNameO.orElse("null"));
// 如果调用对象包含值,返回该值,否则返回参数Supplier得到的值
String s1 = userNameO.orElseGet(() -> {return "未知用户名";});
System.out.println("s1 = " + s1);
}
Optional是一个可以为null的容器对象。orElse,ifPresent,ifPresentOrElse,map等方法避免对null的判断,写出更加优雅的代码
@Test
public void test01(){
/*
问题一:设计不规范
1. java.util.Date:包含日期和时间
2. java.sql.Date:包含日期
3. java.text.SimpleDateFormat:用于格式化和解析
*/
Date date = new Date(1999,1,12);
System.out.println(date);
// 问题二:线程不安全
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 50; i++) {
new Thread(() -> {
try {
Date parse = dateFormat.parse("1999-09-28");
System.out.println("parse = " + parse);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包
中,下面是一些关键类。
LocalDate :表示日期,包含年月日,格式为 2019-10-16
LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
DateTimeFormatter :日期时间格式化类。
Instant:时间戳,表示一个特定的时间瞬间。
Duration:用于计算2个时间(LocalTime,时分秒)的距离
Period:用于计算2个日期(LocalDate,年月日)的距离
ZonedDateTime :包含时区的时间
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。
此外Java 8还提供了4套其他历法,分别是:
ThaiBuddhistDate :泰国佛教历
MinguoDate :中华民国历
JapaneseDate :日本历
HijrahDate :伊斯兰历
LocalDate 、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用 ISO -8601 日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
@Test
public void testLocalDate() {
// 获取当前时间
LocalDate now = LocalDate.now();
System.out.println("now = " + now);
// 获取指定时间
LocalDate date = LocalDate.of(1999, 9, 28);
System.out.println("date = " + date);
// 获得年
System.out.println(now.getYear());
// 获得月
System.out.println(now.getMonthValue());
// 获得日
System.out.println(now.getDayOfMonth());
}
@Test
public void testDateTime() {
// 获得当前的时:分:秒:纳秒
LocalTime now = LocalTime.now();
System.out.println("now = " + now);
// 指定时:分:秒
LocalTime of = LocalTime.of(9, 19, 19);
System.out.println("of = " + of);
// 获得时
System.out.println(now.getHour());
// 获得分
System.out.println(now.getMinute());
// 获得秒
System.out.println(now.getSecond());
}
@Test
public void testLocalDateTime() {
// 获取年-月-日 时:分:秒:纳秒
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
// 指定年-月-日 时:分:秒
LocalDateTime time = LocalDateTime.of(1999, 9, 28, 19, 19, 19);
System.out.println("time = " + time);
// 获取年
System.out.println(now.getYear());
// 获取月
System.out.println(now.getMonthValue());
// 获取日
System.out.println(now.getDayOfMonth());
// 获取时
System.out.println(now.getHour());
// 获取分
System.out.println(now.getMinute());
// 获取秒
System.out.println(now.getSecond());
}
对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。
// 对日期时间修改
@Test
public void test() {
// 获取年-月-日 时:分:秒:纳秒
LocalDateTime now = LocalDateTime.now();
// 修改时间:with···
LocalDateTime setYear = now.withYear(2078);
System.out.println("修改年份: " + setYear);
System.out.println("now == setYear: " + (now == setYear));
System.out.println("修改月份: " + now.withMonth(6));
System.out.println("修改小时: " + now.withHour(9));
System.out.println("修改分钟: " + now.withMinute(11));
// 再当前对象的基础上加上或减去指定的时间 plus···增加/minus···减少
LocalDateTime localDateTime = now.plusDays(5);
System.out.println("5天后: " + localDateTime);
System.out.println("now == localDateTime: " + (now == localDateTime));
System.out.println("10年后: " + now.plusYears(10));
System.out.println("20月后: " + now.plusMonths(20));
System.out.println("20年前: " + now.minusYears(20));
System.out.println("5月前: " + now.minusMonths(5));
System.out.println("100天前: " + now.minusDays(100));
}
@Test
public void testEquals() {
// 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2018, 8, 8);
// 判断两个日期谁在谁之前
System.out.println(now.isBefore(date)); // false
// 判断两个日期谁在谁之后
System.out.println(now.isAfter(date)); // true
// 判断两个日期是否相等
System.out.println(now.equals(date)); //
}
通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。
// 日期格式化
@Test
public void test02() {
// 获取当前日期
LocalDateTime now = LocalDateTime.now();
// JDK自带的日期格式化
DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
String format = now.format(dtf);
System.out.println("format = " + format);
// 自己定义格式化
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyy年MM月dd日 HH时mm分ss秒");
String s = now.format(pattern);
System.out.println("s = " + s);
// 解析及测试多线程是否安全
for (int i = 0; i < 50; i++) {
new Thread(() -> {
LocalDateTime time = LocalDateTime.parse("1999年03月09日 20时54分03秒", pattern);
System.out.println("time = " + time);
}).start();
}
}
Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
// 时间戳
@Test
public void test03() {
// Instant:内部保存了秒和纳秒,不是给用户用的,是用于给程序做一些统计的
Instant now = Instant.now();
System.out.println("now = " + now);
// 减少时间操作
Instant time = now.minusSeconds(20);
System.out.println("instant = " + time);
// 增加时间操作
Instant seconds = now.plusSeconds(20);
System.out.println("time = " + seconds);
// 得到秒
long epochSecond = now.getEpochSecond();
System.out.println("epochSecond = " + epochSecond);
// 得到纳秒
int nano = now.getNano();
System.out.println("nano = " + nano);
}
Duration/Period类: 计算日期时间差。
// Duration/Period 类: 计算时间/日期差
@Test
public void test04() {
// 获取当前时间
LocalTime nowTime = LocalTime.now();
// 指定一个时间
LocalTime time = LocalTime.of(10, 8, 27);
// 使用Duration比较:后面时间减去前面时间
Duration duration = Duration.between(time, nowTime);
System.out.println("计算相差的天数 " + duration.toDays());
System.out.println("计算相差的小时数 " + duration.toHours());
System.out.println("计算相差的分钟数 " + duration.toMinutes());
// 获取当前日期
LocalDate nowDate = LocalDate.now();
// 指定一个日期
LocalDate date = LocalDate.of(1999, 9, 28);
// 使用Period比较:后面的日期减去前面的日期
Period period = Period.between(date, nowDate);
System.out.println("计算相差年 " + period.getYears());
System.out.println("计算相差月 " + period.getMonths());
System.out.println("计算相差日 " + period.getDays());
}
有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
TemporalAdjuster : 时间校正器。
TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
// TemporalAdjuster时间调整器
// TemporalAdjusters提供了大量TemporalAdjuster的实现类
@Test
public void test05() {
LocalDate now = LocalDate.now();
// 自定义一个时间调整器:将时间调到下个月第一天
TemporalAdjuster firstDateOfNextMonth = temporal -> {
// 将传进来的参数强转成时间(因为传进来的就是时间)
LocalDate date = (LocalDate) temporal;
return date.plusMonths(1).withDayOfMonth(1);
};
// 时间调用with方法,将时间调整器传入
LocalDate newDate = now.with(firstDateOfNextMonth);
System.out.println("newDate = " + newDate);
// TemporalAdjusters提供了大量TemporalAdjuster的实现类
LocalDate localDate = now.with(TemporalAdjusters.firstDayOfMonth());
System.out.println("localDate = " + localDate);
}
Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别
为:ZonedDate、ZonedTime、ZonedDateTime。
其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。
ZoneId:该类中包含了所有的时区信息。
// 设置日期时间的时区
@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:21:44.248794200-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]
}
JDK 8新的日期和时间 API的优势:
自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解。
重复注解的使用步骤:
package com.lld;
/**
* @ClassName demo02
* @Description TODO
* @Author LLD
* @Date 2020/3/9 21:59
* @Version 1.0
*/
import org.junit.Test;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
// 3.配置重复注解
@MyTest("ta")
@MyTest("tb")
@MyTest("tc")
public class demo02 {
@Test
@MyTest("ma")
@MyTest("mb")
public void test() {
}
public static void main(String[] args) throws NoSuchMethodException {
// 4.解析重复注解
// 获取类上的重复注解
// getAnnotationsByType是新增的API用户获取重复的注解
MyTest[] annotationsByType = demo02.class.getAnnotationsByType(MyTest.class);
for (MyTest myTest : annotationsByType) {
System.out.println(myTest);
}
System.out.println("----------");
// 获取方法上的重复注解
MyTest[] tests = demo02.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 :表示注解可以再任何用到类型的地方使用。
TYPE_PARAMETER的使用
package com.lld;
/**
* @ClassName demo03
* @Description TODO
* @Author LLD
* @Date 2020/3/9 22:00
* @Version 1.0
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
public class demo03 <@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 {
}
通过@Repeatable元注解可以定义可重复注解, TYPE_PARAMETER 可以让注解放在泛型上, TYPE_USE 可以让注解放在类型的前面