JUC和JAVA8新特性学习笔记

0、普通线程知识回顾

  1. 线程创建方式对比
    在这里插入图片描述
    在这里插入图片描述
  2. 静态代理模式
    在这里插入图片描述
  3. 线程的几种状态
    在这里插入图片描述

1、基础知识

  1. java默认有两个线程,一个是main,一个是GC
  2. 创建线程的方法:继承Thread类 、实现Runnable接口、Callable接口
//Thread
public class MyThread extends Thread {
 
    private String name;
 
    public MyThread(String name){
        this.name = name;
    }
 
    /**
     * 这就是线程执行的逻辑体
     */
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(name+"下载了"+i+"%");
        }
    }
}
//测试代码
public class ThreadTest {
 
    public static void main(String[] args) {
        //创建一个线程的对象
        MyThread mt = new MyThread("肖申克的救赎");
        //启动一个线程
        mt.start();
 //创建一个线程的对象
        MyThread mt1 = new MyThread("当幸福来敲门");
        //启动一个线程
        mt1.start();
 
 
        //System.out.println("方法结束");
 
    }
}
//Runnable
public class DownLoad implements Runnable {
 
    private String name;
 
    public DownLoad(String name) {
        this.name = name;
    }
 
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(name+"下载了"+i+"%");
        }
    }
}
//测试代码
public class ThreadTest {
 
    public static void main(String[] args) {
        //创建线程对象
        Thread t = new Thread(new DownLoad("肖申克的救赎"));
        Thread t1 = new Thread(new DownLoad("当幸福来敲门"));
        t.start();
        t1.start();
    }
}
//Callable

/**
 1. 读文件大小
 */
public class ReadFileCallable implements Callable<Long> {

	private String fileName;

	public ReadFileCallable(String fileName) {
		this.fileName = fileName;
	}

	/**
	 * 计算文件大小并返回
	 */
	@Override
	public Long call() throws Exception {
		File f = new File(fileName);
		if (f.exists() && f.isFile()) {
			return f.length();
		} else {
			return null;
		}
	}

	/**
	 * 测试
	 */
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		FutureTask<Long> task = new FutureTask<Long>(new ReadFileCallable("D:\\temp\\501.txt"));
		Thread thread = new Thread(task);
		thread.start();
		System.out.println(task.get());// 等待结果返回
	}
}

问题:实现callable接口启动线程的时候为什么用FutureTask?
关系图:
在这里插入图片描述

答:我们都知道实现Runnable接口可以启动线程,而Runnable有几个实现类,其中一个就是FutureTask,
在这里插入图片描述
关于Callable的细节:
在这里插入图片描述

  1. java自己是无法开启线程的,它底层调用了start0();是一个native修饰的本地方法,也就是说调用了底层的c++,也就是说java无法直接操作硬件

2、关于synchronized锁的八个问题(八锁现象)

案例1:
在这里插入图片描述
原因:不是因为先调用的发短信再调用打电话导致一定先打印发短信,而是因为synchronized锁的存在,锁的对象是方法的调用者,调用者都是new出来的phone,即两个方法用的是同一个锁
案例2:
在这里插入图片描述
原因:进入hello方法不需要锁,所以当发短信延迟4秒等待的时候,hello直接就进去了
案例3:
在这里插入图片描述
原因:new了两个phone对象,所占用锁不一样,发短信延迟4秒的时候,也可以进入打电话方法
案例4:
在这里插入图片描述
原因:被static修饰的方法在类加载时候就已经存在了,可以理解为是一个模板,实际上就是Phone3这个类被加了锁
案例5:
在这里插入图片描述
原因:和上面一样,被static修饰的方法在类加载时候就已经存在了,可以理解为是一个模板,实际上就是Phone3这个类被加了锁,所以不管new几个对象出来,都是加载的同一个类模板
案例6:
在这里插入图片描述

原因:被static修饰的发短信方法在类加载时候就已经存在了,可以理解为是一个模板,实际上就是Phone4这个类被加了锁,而打电话这个方法没有被static修饰,所以它锁的还是调用对象,当new出一个对象的时候,类的锁和对象的锁互不影响,所以先执行打电话
案例7:
在这里插入图片描述
原因:被static修饰的发短信方法在类加载时候就已经存在了,可以理解为是一个模板,实际上就是Phone4这个类被加了锁,而打电话这个方法没有被static修饰,所以它锁的还是调用对象,当new出两个对象的时候,类的锁和对象的锁互不影响,所以先执行打电话
案例总结: new this 具体的一个手机
static Class 唯一的模板

3、并发集合类

案例:
在这里插入图片描述
分析:执行结果会有异常,因为ArrayList是线程不安全的,解决方案有以下几个

  1. ArrayList改成用synchronized修饰的VectorList<String> list=new Vector<>();但是Vector是在ArrayList之前JDK1.0都已经有了,1.2又补充了ArrayList,这说明Vector并不是最佳解决方案
  2. 不想写了,参考我的另一篇博客 第一阶段的第2条集合类线程不安全问题

4、线程锁

不想写了,参考我的另一篇博客 第一阶段的第3条线程锁

5、阻塞队列

在这里插入图片描述

具体参考参考我的另一篇博客 第一阶段的第4条阻塞队列(BlockingQueue,先进先出)

5、线程池

具体参考参考我的另一篇博客 第一阶段的第7条线程池)

6、函数式接口

1、概念介绍

JDK 8以前的接口:

interface 接口名 {
	 静态常量; 
	 抽象方法; 
 }

JDK 8对接口的增强,接口还可以有默认方法和静态方法

interface 接口名 { 
	静态常量; 
	抽象方法; 
	默认方法;
	静态方法; 
}

接口默认方法和静态方法的区别

  1. 默认方法通过实例调用,静态方法通过接口名调用。
  2. 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
  3. 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用。

总结:如果这个方法需要被实现类继承或重写,使用默认方法,如果接口中的方法不需要被继承就使用静态方法

内置的函数式接口具体参考:JUC学习笔记,6、四大函数式接口

2、四大内置函数式接口

  1. Function函数型接口
    在这里插入图片描述

  2. Predicate断定型接口
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    非运算:执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前调。用 negate 方法,正如 and 和 or 方法一样
    在这里插入图片描述

  3. Consumer消费型接口
    在这里插入图片描述
    多个consumer的情况
    在这里插入图片描述

  4. Supplier供给型接口
    在这里插入图片描述

  5. 自定义函数式接口案例

package com.skweb.qt.filter;

/**
 * @Author Eric
 * @params 不能为空的参数
 * */
@FunctionalInterface
public interface MyPredicate {

    boolean predicateParam(Object... o);
}

package com.skweb.qt.filter;

/*
* @Author Eric
* */
public class PredicateParams implements MyPredicate {
    @Override
    public boolean predicateParam(Object... os) {
        for (Object o:os){
            if(null==o){
                return true;
            }
        }
        return false;
    }
}

7、方法引用简化

  • 符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
  • 应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用。
  • 方法引用在JDK 8中使用方式相当灵活,有以下几种形式:
  1. instanceName::methodName 对象::方法名
  2. ClassName::staticMethodName 类名::静态方法
  3. ClassName::methodName 类名::普通方法
  4. ClassName::new 类名::new 调用的构造器
  5. TypeName[]::new String[]::new 调用数组的构造器
  • 方法引用的注意事项:
  1. 被引用的方法,参数要和接口中抽象方法的参数一样
  2. 当接口抽象方法有返回值时,被引用的方法也必须有返回值

8、Stream流式

1、串行Stream流

(1)获取穿行Stream流的两种方法

方式1 : 根据Collection获取流;首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)常用方法

在这里插入图片描述
补充常用方法具体使用:

//公用部分代码
List<String> one = new ArrayList<>(); 
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
  1. Stream流的forEach方法,forEach 用来遍历流中的数据,one.stream().forEach(System.out::println);
  2. Stream流的count方法,Stream流提供 count 方法来统计其中的元素个数,System.out.println(one.stream().count());
  3. Stream流的filter方法,filter用于过滤数据,返回符合过滤条件的数据,one.stream().filter(s -> s.length() == 2).forEach(System.out::println);
  4. Stream流的limit方法,limit 方法可以对流进行截取,只取用前n个。one.stream().limit(3).forEach(System.out::println);
  5. Stream流的skip方法,如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:one.stream().skip(2).forEach(System.out::println);
  6. Stream流的map方法,如果需要将流中的元素映射到另一个流中,可以使用 map 方法(将字符串类型转换成为了int类型,并自动装箱为 Integer 类对象):
@Test public void testMap() { Stream<String> original = Stream.of("11", "22", "33"); 
Stream<Integer> result =
 original.map(Integer::parseInt); result.forEach(s -> System.out.println(s + 10)); }
  1. Stream流的sorted方法,如果需要将数据排序,可以使用 sorted 方法
@Test public void testSorted() { 
// sorted(): 根据元素的自然顺序排序
// sorted(Comparator<? super T> comparator): 根据比较器指定的规则排序
Stream.of(33, 22, 11, 55) .sorted() .sorted((o1, o2) -> o2 - o1) .forEach(System.out::println); }
  1. Stream流的distinct方法,如果需要去除重复数据,可以使用 distinct 方法。
@Test 
public void testDistinct() { 
Stream.of(22, 33, 22, 11, 33) .distinct().forEach(System.out::println); 
}
  1. Stream流的match方法,如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法
@Test public void testMatch() { 
boolean b = Stream.of(5, 3, 6, 1) 
// .allMatch(e -> e > 0);// allMatch: 元素是否全部满足条件
 // .anyMatch(e -> e > 5); // anyMatch: 元素是否任意有一个满足条件
  .noneMatch(e -> e < 0); // noneMatch: 元素是否全部不满足条件
   System.out.println("b = " + b); }
  1. Stream流的find方法,如果需要找到某些数据,可以使用 find 相关方法
@Test public void testFind() { 
Optional<Integer> first = Stream.of(5, 3, 6, 1).findFirst(); 
System.out.println("first = " + first.get()); 
Optional<Integer> any = Stream.of(5, 3, 6, 1).findAny(); 
System.out.println("any = " + any.get()); }
  1. Stream流的max和min方法,如果需要获取最大和最小值,可以使用 max 和 min 方法
@Test public void testMax_Min() { 
Optional<Integer> max = Stream.of(5, 3, 6, 1).max((o1, o2) -> o1 - o2);
 System.out.println("first = " + max.get()); 
Optional<Integer> min = Stream.of(5, 3, 6, 1).min((o1, o2) -> o1 - o2); 
System.out.println("any = " + min.get()); }
  1. Stream流的reduce方法,如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法
    在这里插入图片描述
  2. Stream流的mapToInt,如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。
    在这里插入图片描述
    14.Stream流的concat方法,如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
@Test 
public void testContact() {
 Stream<String> streamA = Stream.of("张三"); 
 Stream<String> streamB = Stream.of("李四");
 Stream<String> result = Stream.concat(streamA, streamB); 
 result.forEach(System.out::println); }

Stream注意事项(重要)

  1. Stream只能操作一次
  2. Stream方法返回的是新的流
  3. Stream不调用终结方法,中间的操作不会执行

(3)计算案例

案例一:
在这里插入图片描述

import java.util.Arrays;
import java.util.List;

public class StreamDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		User u1 = new User(1, "a", 1);
		User u2 = new User(2, "b", 2);
		User u3 = new User(3, "c", 3);
		User u4 = new User(4, "d", 4);
		User u5 = new User(5, "e", 5);
		List<User> asList = Arrays.asList(u1, u2, u3, u4, u5);
		asList.stream().filter((n) -> {
			return n.getId() % 2 == 0;
		}).filter((n) -> {
			return n.getAge() > 3;
		}).map((n) -> {
			return n.getName().toUpperCase();
		}).sorted((n1, n2) -> {
			return n1.compareTo(n2);
		}).limit(1).forEach(System.out::printf);
	}
	
List<String> collect = asList.stream().filter((n) -> {
			return n.getId() % 2 == 0;
		}).filter((n) -> {
			return n.getAge() > 3;
		}).filter((n) -> {
			return n != null;
		}).map((n) -> {
			return n.getName().toUpperCase();
		}).sorted((n1, n2) -> {
			return n1.compareTo(n2);
		}).collect(Collectors.toList());
}

// 拼接 
@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 collect = studentStream .map(Student::getName) .collect(Collectors.joining(">_<", "^_^", "^v^")); 
System.out.println(collect); }

//累加案例:假设你有一个名为Person的类,其中有一个整型的age属性:
public class Person {  
    private String name;  
    private int age;  
  
    // getters and setters...  
}
//你可以通过以下方式使用Stream API来统计一个List<Person>中所有age属性的总和:
import java.util.Arrays;  
import java.util.List;  
  
public class Main {  
    public static void main(String[] args) {  
        List<Person> people = Arrays.asList(  
                new Person("Alice", 25),  
                new Person("Bob", 30),  
                new Person("Charlie", 35)  
        );  
  
        int sum = people.stream()  
                .mapToInt(Person::getAge)  
                .sum();  
  
        System.out.println("Total age: " + sum);  
    }  
}
//在这个例子中,stream()方法将列表转换为Stream,mapToInt(Person::getAge)将每个Person对象转换为其年龄,
//然后sum()方法将所有年龄相加得到总和。

总结:收集Stream流中的结果有以下几种方式

  1. 到集合中: Collectors.toList()/Collectors.toSet()/Collectors.toCollection()
  2. 到数组中: toArray()/toArray(int[]::new)
  3. 聚合计算:
    Collectors.maxBy //最大值
    Collectors.minBy //最小值
    Collectors.counting //数量
    Collectors.summingInt //求和
    Collectors.averagingInt //平均值
  4. 分组: Collectors.groupingBy
  5. 分区: Collectors.partitionBy
  6. 拼接: Collectors.joinging
  7. 去重:.stream().distinct()

补充一个项目中实际用到的案例代码,仅供参考(包含了通过stream流进行分组、自定义排序、收集流结果等操作):

import com.waterlog.system.common.ResponseEnum;
import com.waterlog.system.common.ResponseResult;
import com.waterlog.system.service.WaterLogService;
import com.waterlog.system.vo.WaterLogVO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.waterlog.system.utils.ReadTxtUtil.resolveCharset;

@Service
public class WaterLogServiceImpl implements WaterLogService {

    @Value("${waterLog.appPath}")
    public String WATERLOG_APP_PATH;

    @Value("${waterLog.resultPath}")
    public String WATERLOG_RESULT_PATH;

    @Override
    public ResponseResult getWaterLogModelDatas() throws Exception {
        File[] resultList = new File(WATERLOG_RESULT_PATH).listFiles();
        if (resultList == null || resultList.length == 0) {
            return new ResponseResult(ResponseEnum.FILENOTFOUND.getCode(), ResponseEnum.FILENOTFOUND.getMessage());
        }
        ArrayList<WaterLogVO> WaterLogVOList = new ArrayList<WaterLogVO>();
        for (File file : resultList) {
            if (file.getName().equals("日志.txt")) {
                continue;
            }
            String filePath = file.getPath();
            InputStream inputStream = new FileInputStream(filePath);
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, resolveCharset(filePath));
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String text;
            WaterLogVO waterLogVO = new WaterLogVO();
            Integer q = 0;
            Integer h = 0;
            Integer v = 0;
            Integer temp = 2;
            ArrayList<WaterLogVO.WaterLogData> waterLogDataList = new ArrayList<>();
            while ((text = bufferedReader.readLine()) != null) {
                if (text.contains("结果")) {
                    waterLogVO.setName(text);
                } else {
                    String[] titleArr = text.split("\\s+");
                    Stream<String> titleArrStream = Stream.of(titleArr);
                    boolean isTitle = titleArrStream.anyMatch(item -> item.contains("时间"));
                    titleArrStream = Stream.of(titleArr);
                    if (isTitle) {
                        q = titleArrStream.anyMatch(item -> item.contains("q")) ? ++temp : q;
                        titleArrStream = Stream.of(titleArr);
                        h = titleArrStream.anyMatch(item -> item.contains("h")) ? ++temp : h;
                        titleArrStream = Stream.of(titleArr);
                        v = titleArrStream.anyMatch(item -> item.contains("v")) ? ++temp : v;
                    } else {
                        WaterLogVO.WaterLogData waterLogData = new WaterLogVO.WaterLogData();
                        waterLogData.setTime(titleArr[0] + " " + titleArr[1]);
                        waterLogData.setId(titleArr[2]);
                        if (q > 0) {
                            waterLogData.setQ(new BigDecimal(titleArr[q]));
                        }
                        if (h > 0) {
                            waterLogData.setH(new BigDecimal(titleArr[h]));
                        }
                        if (v > 0) {
                            waterLogData.setV(new BigDecimal(titleArr[v]));
                        }
                        waterLogDataList.add(waterLogData);
                    }
                }
            }
            Map<String, List<WaterLogVO.WaterLogData>> map =
                    waterLogDataList.stream().collect(Collectors.groupingBy(WaterLogVO.WaterLogData::getTime));
            List<List<WaterLogVO.WaterLogData>> collect = map.entrySet().stream().sorted((o1, o2) -> {
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 将日期时间格式化为字符串
                LocalDateTime time1 = LocalDateTime.parse(o1.getKey(), formatter);
                LocalDateTime time2 = LocalDateTime.parse(o2.getKey(), formatter);
                return time1.compareTo(time2);
            }).map(o -> o.getValue()).collect(Collectors.toList());
            waterLogVO.setData(collect);
            WaterLogVOList.add(waterLogVO);
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
        }
        return new ResponseResult(ResponseEnum.SUCCESS.getCode(), WaterLogVOList, ResponseEnum.SUCCESS.getMessage());
    }
去重案例:
1、如果是基本类型,直接stream().distinct(),如果是对象,首先要重写equals和hashCode
2、使用
ctkjDevicesDatas.stream().distinct().forEach(n -> {
            n.setUpdateTime(new Date());
            ctkjDevicesDatasMapper.insert(n);
        });
}

2、并行Stream流

(1)获取并行流的两种方式

  1. 直接获取并行的流
  2. 将串行流转成并行流
@Test 
public void testgetParallelStream() { 
ArrayList<Integer> list = new ArrayList<>(); 
// 直接获取并行的流 //
Stream<Integer> stream = list.parallelStream(); 
// 将串行流转成并行流 	
Stream<Integer> stream = list.stream().parallel(); }

在这里插入图片描述

(2)并行stream流带来的线程不安全问题

问题描述:Stream并行处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个线程操作
解决方法: 加锁、使用线程安全的集合或者调用Stream的 toArray() / collect() 操作就是满足线程安全的了
方案一,同步代码块
在这里插入图片描述
方案二,使用安全集合类
在这里插入图片描述
方案三,使用stream内置方法转为安全的流
在这里插入图片描述

(3)parallelStream背后的技术

在这里插入图片描述

8、ForkJoin

必须在大数据量时使用
在这里插入图片描述
在这里插入图片描述
特点:工作窃取
在这里插入图片描述
说明:如上图,双端队列里有多个子任务,当B线程比A线程先执行完后,B会窃取A的任务帮忙执行,从而提高效率

ForkJoin案例:求和计算的任务(当求和数量过于庞大的时候,需要把任务拆分执行,最后合并)
业务代码:
在这里插入图片描述

测试代码:
在这里插入图片描述

9、Optional类

1、基本使用

在这里插入图片描述
在这里插入图片描述

2、高级使用

在这里插入图片描述
在这里插入图片描述
小结:Optional是一个可以为null的容器对象。orElse,ifPresent,ifPresentOrElse,map等方法避免对null的判断,写出更加优雅的代码。

9、时间API

1、基本使用

import java.time.LocalDate;
LocalDate的使用1
LocalDate的使用2
尽量减少旧版(utils包、sql包)时间api的使用!!!能用java8尽量使用这个,旧版日期时间 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类,但他们同样存在上述所有的问题。
    在这里插入图片描述

LocalDate 、LocalTime 、LocalDateTime 案例介绍

 // LocalDate:获取日期时间的信息。格式为 2019-10-16
    @Test
    public void test01() {
        //创建指定日期
        LocalDate fj = LocalDate.of(1985, 9, 23);
        System.out.println("fj = " + fj); // 1985-09-23
        //得到当前日期
        LocalDate nowDate = LocalDate.now();
        System.out.println("nowDate = " + nowDate); // 2019-10-16 
        //获取日期信息 
        System.out.println("年: " + nowDate.getYear());
        System.out.println("月: " + nowDate.getMonthValue());
        System.out.println("日: " + nowDate.getDayOfMonth());
        System.out.println("星期: " + nowDate.getDayOfWeek());
    }// LocalTime类: 获取时间信息。格式为 16:38:54.158549300 

    @Test
    public void test02() {
        // 得到指定的时间
        LocalTime time = LocalTime.of(12, 15, 28, 129_900_000);
        System.out.println("time = " + time); // 得到当前时间 
         LocalTime nowTime = LocalTime.now(); 
         System.out.println("nowTime = " + nowTime); 
         //获取时间信息 
         System.out.println("小时: " + nowTime.getHour());
         System.out.println("分钟: " + nowTime.getMinute()); 
         System.out.println("秒: " + nowTime.getSecond()); 
         System.out.println("纳秒: " + nowTime.getNano()); }//获取纳秒
         //LocalDateTime类: 获取日期时间信息。格式为 2018-09-06T15:33:56.750 
        @Test 
        public void test03() {
             LocalDateTime fj = LocalDateTime.of(1985, 9, 23, 9, 10, 20);
        } System.out.println("fj = " + fj); // 1985-09-23T09:10:20 
        // 得到当前日期时间 
        LocalDateTime now = LocalDateTime.now(); 
        System.out.println("now = " + now); // 2019-10-16T16:42:24.497896800 
        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());
        System.out.println(now.getNano());
    }

对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。
withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对
象,他们不会影响原来的对象。

// LocalDateTime类: 对日期时间的修改 
@Test 
public void test05() { 
	LocalDateTime now = LocalDateTime.now(); 
	System.out.println("now = " + now); // 修改日期时间 
	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)); // 再当前对象的基础上加上或减去指定的时间 
	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 test06() { 
// 在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 
}

2、时间格式化和解析

通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。

// 日期格式化 
@Test 
public void test04() { 
	// 得到当前日期时间 
	LocalDateTime now = LocalDateTime.now(); 
	DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 将日期时间格式化为字符串 
	String format = now.format(formatter); 
	System.out.println("format = " + format); // 将字符串解析为日期时间
	LocalDateTime parse = LocalDateTime.parse("1985-09-23 10:12:22", formatter); 
	System.out.println("parse = " + parse); 
}

JDK 8的 Instant 类,Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。

// 时间戳 
@Test 
public void test07() { 
	Instant now = Instant.now(); 
	System.out.println("当前时间戳 = " + now); // 获取从1970年1月1日 00:00:00的秒 
	System.out.println(now.getNano()); 
	System.out.println(now.getEpochSecond()); 
	System.out.println(now.toEpochMilli()); 
	System.out.println(System.currentTimeMillis()); 
	Instant instant = Instant.ofEpochSecond(5); 
	System.out.println(instant); 
}

3、JDK 8的计算日期时间差类

Duration/Period类: 计算日期时间差。

  1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
  2. Period:用于计算2个日期(LocalDate,年月日)的距离
// Duration/Period类: 计算日期时间差 
@Test 
public void test08() { // Duration计算时间的距离 
	LocalTime now = LocalTime.now(); 
	LocalTime time = LocalTime.of(14, 15, 20); 
	Duration duration = Duration.between(time, now); 
	System.out.println("相差的天数:" + duration.toDays()); 
	System.out.println("相差的小时数:" + duration.toHours()); 
	System.out.println("相差的分钟数:" + duration.toMinutes()); 
	System.out.println("相差的秒数:" + duration.toSeconds()); // Period计算日期的距离 
	LocalDate nowDate = LocalDate.now(); 
	LocalDate date = LocalDate.of(1998, 8, 8); // 让后面的时间减去前面的时间 
	Period period = Period.between(date, nowDate); 
	System.out.println("相差的年:" + period.getYears());
	System.out.println("相差的月:" + period.getMonths()); 
	System.out.println("相差的天:" + period.getDays()); 
 }

10、重复注解与类型注解

1、重复注解

自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解。
使用步骤:

  1. 定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME) 
@interface MyTests { 
MyTest[] value(); 
}
  1. 定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME) 
@Repeatable(MyTests.class) 
@interface MyTest { 
String value(); 
}
  1. 配置多个重复的注解
@MyTest("tbc") 
@MyTest("tba") 
@MyTest("tba") 
public class Demo01 { 
	@MyTest("mbc") 
	@MyTest("mba") 
	public void test() throws NoSuchMethodException { }
 }
  1. 解析得到指定注解
// 3.配置多个重复
@MyTest("tbc") 
@MyTest("tba") 
@MyTest("tba")
public class Demo01 {
	 @Test 
	 @MyTest("mbc") 
	 @MyTest("mba") 
	 public void test() throws NoSuchMethodException { 
		 // 4.解析得到类上的指定注解 
		 MyTest[] tests = Demo01.class.getAnnotationsByType(MyTest.class); 
		 for (MyTest test : tests) { 
			 System.out.println(test.value()); }// 得到方法上的指定注解 
			 Annotation[] tests1 =  Demo01.class.getMethod("test").getAnnotationsByType(MyTest.class); 
			 for (Annotation annotation : tests1) { 
			 	System.out.println("annotation = " + annotation); 
			 } 
	 }
  }

2、类型注解

JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER、TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。 类型参数声明如: <T>
TYPE_USE :表示注解可以再任何用到类型的地方使用。

案例一:TYPE_PARAMETER的使用

@Target(ElementType.TYPE_PARAMETER) 
@interface TyptParam {
}
public class Demo02<@TyptParam T> { 
	public static void main( String[] args) { 
	}
	public <@TyptParam E> void test( String a) {
	}
}

案例二:TYPE_USE的使用

@Target(ElementType.TYPE_USE) 
@interface NotNull {
}
public class Demo02<@TyptParam T extends String> {
	private @NotNull int a = 10;
	public static void main(@NotNull String[] args) {
	@NotNull int x = 1;
	@NotNull String s = new
	@NotNull String();
	}
	public <@TyptParam E> void test( String a) {
	}
}

小结:通过@Repeatable元注解可以定义可重复注解, TYPE_PARAMETER 可以让注解放在泛型上, TYPE_USE 可以让注解放
在类型的前面

11、异步回调

在这里插入图片描述
案例一:无返回值的异步方法,runAsync()和主线程异步执行,互不影响
在这里插入图片描述
案例二:有返回值的异步方法supplyAsync(),分为正常返回和异常返回,注意下面方法执行最后有个.get()方法,这个就是Callable接口的get()方法,为了得到线程的返回值
在这里插入图片描述
关于案例二的补充说明,whenCompleteAsync()方法能感知到执行结果和异常,但无法处理。所以异常执行时处理异常的方法,使用下图的exceptionally(),代码如下:
在这里插入图片描述

在这里插入图片描述
同样的,在案例二种,whenCompleteAsync()方法能感知到结果,但处理return结果得使用handle()方法,如下图:
在这里插入图片描述
补充一:异步回调还有一些串行方法,对上一次的返回结果再次进行处理
在这里插入图片描述
各方法的区别
在这里插入图片描述
补充二:任务组合
组合一,两个任务都完成后,触发第三个任务
在这里插入图片描述
在这里插入图片描述

组合二,两个任务完成一个,就触发第三个任务
在这里插入图片描述
在这里插入图片描述
多任务组合:
在这里插入图片描述

12、JMM

在这里插入图片描述
在这里插入图片描述

其他相关知识点不想写了,参考我的另一篇博客 1、 volatile关键字的理解
补充单例模式案例,单例模式就一定安全吗? 不一定,因为java里的反射可以破坏单例模式,可使用枚举实现单例模式,来解决反射带来的不安全问题
饿汉式
在这里插入图片描述
懒汉式是线程不安全的,需要用到synchronized和volatile实现DCL(双重锁定检查(DCL,Double Check Lock))
在这里插入图片描述
使用枚举来解决反射带来的单例模式的破坏
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值