Java常用API类

68 阅读38分钟

Java常用API类

1、Object类

1.1、描述

Object类是一个特殊的类,是所有类的超类。

1.2、常用方法

方法描述
public final native Class<?> getClass();返回对象的运行时类
public boolean equals(Object obj)判断对象是否相等
public native int hashCode();返回对象的哈希值
protected native Object clone()返回对象的浅拷贝
public String toString()返回对象的字符串表示
protected void finalize()当对象即将消亡时运行
public final void notify()唤醒正在等待对象监视器的单个线程。
public final void notifyAll()唤醒正在等待对象监视器的所有线程。
public final void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
public final void wait(long timeout)导致当前线程等待,直到另一个线程调用 notify()方法或该对象的 notifyAll()方法,或者指定的时间已过。
public final void wait(long timeout,int nanos)导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或其他一些线程中断当前线程,或一定量的实时时间。

1.3、==和equals的区别

  • ==:

    • 基本类型:比较的是值
    • 引用类型:比较的是对象的内存地址
  • equals

    • 不能判断基本数据类型

    • 引用类型,类是否重写equals

      • 重写:一般我们重写equals()来比较两个对象中的属性是否相等;若他们属性相等,则返回true
      • 未重写:等价于==

1.4、重写equals为什么必须重写hashCode方法

  • hashCode() 有什么⽤?

    hashCode() 的作⽤是获取哈希码( int 整数),也称为散列码。该⽅法通常⽤来将对象的内存地址转换为整数之后返回。

  • 为什么要有 hashCode

    以“ HashSet 如何检查重复”为例⼦,当你把对象加⼊ HashSet 时, HashSet 会先计算对象的 hashCode 值来判断对象加⼊的位置,同时也会与其他已经加⼊的对象的 hashCode 值作比较,如果没有相符的 hashCodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象, 这时会调⽤ equals() ⽅法来检查 hashCode 相等的对象是否真的相同。如果两者相同, HashSet 就不会让其加⼊操作成功。如果不同的话,就会重新散列到其他位置。这样我们 就⼤⼤减少了 equals 的次数,相应就⼤⼤提⾼了执⾏速度。

  • 为什么提供了hashCode还需要equals?

    两个对象的 hashCode 值相等并不代表两个对象就相等。因为hashCode() 所使⽤的哈希算法也许刚好会让多个对象传回相同的哈希值(哈希冲撞)

    两个对象hashCode 值相等并且 equals() ⽅法也返回 true ,我们才认为这两个对象相等

  • 重写 equals() 时没有重写 hashCode() ⽅法的话,会有什么问题?

    HashSet为例:

    两个对象equals判断是相等的,但是加入前会先判断hashCode值,因为hashCode值不相等,可能会在集合中添加两个相等的元素

1.5、使用Object的clone()必须实现Cloneable接口

在不实现Cloneable接口的实例上调用对象的克隆方法导致抛出异常CloneNotSupportedException

1.6、深拷贝、浅拷贝、引用拷贝

  • 引用拷贝:直接将对象的引用赋值给另一个变量即可。

  • 浅拷贝:只复制对象本身,而不复制对象包含的子对象。新旧对象之间共享子对象的引用,即新对象和原始对象中的子对象指向同一个内存地址。使用clone()方法

  • 深拷贝:不仅复制对象本身,还要复制对象包含的所有子对象。可以使用序列化和反序列化实现或者重写clone()方法来实现

    • 序列化反序列化

      public class Person implements Serializable {
          private static final long serialVersionUID = -7466779555039906117L;
          private String name;
          private Integer age;
          private Person friend;
      ​
          public String getName() {
              return name;
          }
      ​
          public void setName(String name) {
              this.name = name;
          }
      ​
          public Integer getAge() {
              return age;
          }
      ​
          public void setAge(Integer age) {
              this.age = age;
          }
      ​
          public Person getFriend() {
              return friend;
          }
      ​
          public void setFriend(Person friend) {
              this.friend = friend;
          }
      ​
          public Person deepClone() {
              ByteArrayOutputStream bos = null;
              ObjectOutputStream oos = null;
              ByteArrayInputStream bis = null;
              ObjectInputStream ois = null;
      ​
              try {
                  //序列化
                  bos = new ByteArrayOutputStream();
                  oos = new ObjectOutputStream(bos);
                  oos.writeObject(this);
      ​
                  //反序列化
                  bis = new ByteArrayInputStream(bos.toByteArray());
                  ois = new ObjectInputStream(bis);
                  return (Person) ois.readObject();
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  try {
                      ois.close();
                      bis.close();
                      oos.close();
                      bos.close();
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
              return null;
          }
      }
      
    • 重写clone()

      public class Address implements Cloneable{
          private String name;
          // 省略构造函数、Getter&Setter⽅法
          @Override
          public Address clone() {
              try {
                  return (Address) super.clone();
              } catch (CloneNotSupportedException e) {
                  throw new AssertionError();
              }
          }
      }
      ​
      public class Person implements Cloneable {
          private Address address;
          // 省略构造函数、Getter&Setter⽅法
          @Override
          public Person clone() {
              try {
                  Person person = (Person) super.clone();
                  //无这一行就是浅拷贝,有这一行则为深拷贝
                  person.setAddress(person.getAddress().clone())
                  return person;
               } catch (CloneNotSupportedException e) {
                  throw new AssertionError();
               }
           }
      }   
      

2、System类

2.1、描述

在 System 类提供的设施中,有标准输入、标准输出和错误输出流;对外部定义的属性和环境变量的访问;加载文件和库的方法;还有快速复制数组的一部分的实用方法。

2.2、常用方法

方法描述
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);进行数组拷贝
public static native long currentTimeMillis();获取当前时间(毫秒值)
public static void exit(int status)退出当前程序
public static void gc()运行垃圾回收
public static java.util.Map<String,String> getenv()获取系统环境变量
public static String getenv(String name)获取指定环境变量
public static Properties getProperties()获取当前系统属性
public static String getProperty(String key)获取指定系统属性
public static String getProperty(String key, String def)获取指定系统属性(可设默认值)
public static String clearProperty(String key)清除指定系统属性
public static String lineSeparator()返回系统的分隔符

3、Math类

3.1、描述

Math 类包含执行基本数字运算的方法,如基本指数,对数,平方根和三角函数。

3.2、常用方法

方法描述
public static int max(int a, int b)返回两个数中的最大值
public static int min(int a, int b)返回两个数中的最小值
public static int abs(int a)返回指定数字的绝对值
public static double floor(double a)返回小于或等于参数的最大整数
public static double ceil(double a)返回大于或等于参数的最小整数
public static long round(double a)返回四舍五入后整数值
public static double pow(double a, double b)返回a的b次幂
public static double log(double a)返回log以e为底的值
public static double sqrt(double a)返回a的正平方根
public static double random()返回一个[0.0 , 1.0)之间的随机数 返回指定范围:(int) (Math.random() * (max - min + 1) + min)
public static double sin(double a)返回正弦值
public static double cos(double a)返回余弦值
public static double tan(double a)返回正切值
public static double asin(double a)返回反正弦值
public static double acos(double a)返回反余弦值
public static double atan(double a)返回反正切值

4、String类(字符串)

4.1、定义

由多个字符连在一起组成的,通常使用双引号括起来。Java提供了一个预定义类来对字符串进行操作,名为String

4.2、不可变字符串

String是被final修饰的一个类,String类没有提供修改字符串中某个字符的方法。

不可变字符串有一个优点:编译器可以让字符串共享。可以想象将各种字符串放在公共的存储池里面。字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串和复制的字符串共享相同的字符。

4.3、空串与null

空串""是长度为0的字符串。String还可以存放一个特殊的值,名为null,表示目前没有任何对象与该变量关联。

4.4、StringBuffer和StringBuilder

我们在构建字符串时,如果采用字符串拼接时,效率会比较低,每次拼接时都会构建一个新的String对象,即耗时,又浪费空间。使用StringBuffer和StringBuilder就可以避免这个问题。

StringBuilder sb = new StringBuilder();
sb.append("hello ");
sb.append("world");
System.out.println(sb.toString());//输出hello world
StringBuffer sb = new StringBuffer();
sb.append("hello ");
sb.append("world");
System.out.println(sb.toString());//输出hello world

注意事项

StringBufferStringBuilder
内容可以变内容可以变
线程安全线程不安全
多线程操作字符串单线程操作字符串

4.5、StringAPI

常用方法(更多请参考API文档):

方法描述
public char charAt(int index)返回 char指定索引处的值。
public String concat(String str)将指定的字符串连接到该字符串的末尾
public char[] toCharArray()将此字符串转换为新的字符数组
public String trim()返回一个删除前后空格的字符串
public String toString()返回当前字符串
public String toLowerCase()返回一个转换为小写的字符串
public String toUpperCase()返回一个转换为大写的字符串
public String substring(int beginIndex)返回一个以指定索引处的字符开头到该字符串的末尾的子字符串
public String substring(int beginIndex, int endIndex)返回一个以指定索引处的字符开头到指定索引处的字符结尾的子字符串
public String[] split(String regex)根据正则表达式拆分字符串并返回拆分后的字符串数组
public boolean startsWith(String prefix)判断此字符串是否以指定的前缀开头
public boolean endsWith(String suffix)判断此字符串是否以指定的后缀结尾
public boolean matches(String regex)根据正则表达式判断当前字符串是否符合要求
public boolean isEmpty()判断当前字符串是否为空字符串
public boolean contains(CharSequence s)判断当前字符串是否包含s字符
public boolean equals(Object anObject)判断当前字符串是否和anObject相等

5、Number类

5.1、描述

抽象类 NumberByteShortIntegerLongFloatDoubleBigIntegerBigDecimal等类的父类。

5.2、常用方法

方法描述
public byte byteValue()以 byte 形式返回指定的数值
public short shortValue()以 short 形式返回指定的数值
public abstract int intValue();以 int 形式返回指定的数值
public abstract long longValue();以 long形式返回指定的数值
public abstract float floatValue();以 float 形式返回指定的数值
public abstract double doubleValue();以 double 形式返回指定的数值

6、包装类

每一种基本数据类型都对应者一种包装类型

基本类型包装类型
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean
  • 自动装箱:把基本数据类型转换为对应的包装类型

    例如:Integer total = 99;

    对应:Integer total = Integer.valueOf(99);

  • 自动拆箱:把包装类型转换为对应的基本数据类型

    例如:int totalprim = total;

    对应:int totalprim = total.intValue();

  • Integer类

方法描述
public static String toBinaryString(int i)将整数转为二进制字符串
public static String toOctalString(int i)将整数转为八进制字符串
public static String toHexString(int i)将整数转为十六进制字符串
public static int parseInt(String s)将十进制数字字符串解析为整数
public static int parseInt(String s, int radix)将指定进制的字符串解析为整数
public static Integer valueOf(int i)将十进制数字转换为Integer对象
public static Integer valueOf(String s)将十进制数字字符串转换为Integer对象
public static Integer valueOf(String s, int radix)将指定进制的字符串解析为Integer对象
public static int max(int a, int b)返回两个数中的最大值
public static int min(int a, int b)返回两个数中的最小值
public static long sum(long a, long b)返回两个数的和
  • Long类
方法描述
public static String toBinaryString(long i)将整数转为二进制字符串
public static String toOctalString(long i)将整数转为八进制字符串
public static String toHexString(long i)将整数转为十六进制字符串
public static long parseLong(String s)将十进制数字字符串解析为整数
public static long parseLong(String s, int radix)将指定进制的字符串解析为整数
public static Long valueOf(long l)将十进制数字转换为Long对象
public static Long valueOf(String s)将十进制数字字符串转换为Long对象
public static Long valueOf(String s, int radix)将指定进制的字符串解析为Long对象
public static long max(long a, long b)返回两个数中的最大值
public static long min(long a, long b)返回两个数中的最小值
public static long sum(long a, long b)返回两个数的和

......具体可查看其他类的API

7、大数字类

7.1、BigInteger类

7.1.1、使用
BigInteger bi1 = new BigInteger("100");
BigInteger bi2 = new BigInteger("50");
​
// public BigInteger add(BigInteger val):加
System.out.println("add:" + bi1.add(bi2));
​
// public BigInteger subtract(BigInteger val):减
System.out.println("subtract:" + bi1.subtract(bi2));
​
// public BigInteger multiply(BigInteger val):乘
System.out.println("multiply:" + bi1.multiply(bi2));
​
// public BigInteger divide(BigInteger val):除
System.out.println("divide:" + bi1.divide(bi2));
7.1.2、底层存储方式

对于计算机而言,其实是没有数据类型的概念的,都是0101010101,数据类型是编程语言自己规定的,所以在实际存储的时候,先把具体的数字变成二进制,每32个bit为一组,存储在数组中。

数组中最多能存储元素个数:21亿多

数组中每一位能表示的数字:42亿多

理论上,BigInteger能表示的最大数字为:42亿的21亿次方。

但是还没到这个数字,电脑的内存就会撑爆,所以一般认为BigInteger是无限的。

存储方式如图所示:

bigInteger的底层原理

7.2、BigDecimal类:

7.2.1、使用
BigDecimal bd1 = new BigDecimal("0.09");
BigDecimal bd2 = new BigDecimal("0.01");
// public BigDecimal add(BigDecimal val):加
System.out.println("add:" + bd1.add(bd2));
​
BigDecimal bd3 = new BigDecimal("1.0");
BigDecimal bd4 = new BigDecimal("0.32");
// public BigDecimal subtract(BigDecimal val):减
System.out.println("subtract:" + bd3.subtract(bd4));
​
BigDecimal bd5 = new BigDecimal("1.015");
BigDecimal bd6 = new BigDecimal("100");
// public BigDecimal multiply(BigDecimal val):乘
System.out.println("multiply:" + bd5.multiply(bd6));
​
BigDecimal bd7 = new BigDecimal("1.301");
BigDecimal bd8 = new BigDecimal("100");
// public BigDecimal divide(BigDecimal val):除
System.out.println("divide:" + bd7.divide(bd8));
7.2.2、底层存储方式

把数据看成字符串,遍历得到里面的每一个字符,把这些字符在ASCII码表上的值,都存储到数组中。

bigdecimal存储原理

8、Throwablel类(异常体系)

8.1、概述

异常就是程序出现了不正常的情况

8.2、异常分类

image-20230718160654670

  • Error : Error 属于程序⽆法处理的错误。例如 Java 虚拟机运⾏错误( Virtual MachineError )、虚拟机内存不够错误 ( OutOfMemoryError )、类定义错误( NoClassDefFoundError )等 。这些异常发⽣时,Java 虚拟机(JVM)⼀般会选择线程终⽌
  • Exception:程序本身可以处理的异常,可以通过 catch 来进⾏捕获。 Exception ⼜可以分为RuntimeException (运行时异常,可以不处理) 和 非RuntimeException (受检查异常,必须处理)

8.3、常见异常

  • RuntimeException 及其⼦类都统称为运行时异常(可以不处理)

    • NullPointerException (空指针错误)
    • IllegalArgumentException (参数错误⽐如⽅法⼊参类型错误)
    • NumberFormatException (字符串转换为数字格式错误
    • IllegalArgumentException 的⼦类)
    • ArrayIndexOutOfBoundsException (数组越界错误)
    • ClassCastException (类型转换错误)
    • ArithmeticException (算术错误)
    • SecurityException (安全错误⽐如权限不够)
    • UnsupportedOperationException (不⽀持的操作错误⽐如重复创建同⼀⽤户)
  • 非RuntimeException都属于受检查异常(必须处理)

    • IOException(输入输出异常)
    • ClassNotFoundException(未找到相应类异常)
    • FileNotFoundException(文件未找到异常)

8.4、处理异常

解决方案一:用try-catch-finally的方式处理异常
try {
    可能出现异常的代码;
} catch(异常类名 变量名) {
    异常的处理代码;
} finally {
    释放资源的代码;
}
​
解决方案二:用try-with-resources方式处理异常
try (定义流对象;定义流对象....) {
    可能出现异常的代码;
} catch(异常类名 变量名) {
    异常的处理代码;
}
​
解决方案三:用throws方式抛出异常
public void 方法名() throws 异常类名 {
    
}
​
解决方案四:用throw方式抛出异常
public void 方法名() {
    //...
    //抛出异常
    throw new 异常类名(异常信息);
    //...
}

8.5、throws和throw的区别

  • throws:

    • 用在方法声明后面,跟的是异常类名
    • 表示抛出异常,由该方法的调用者来处理
    • 表示出现异常的一种可能性,不一定会发生异常
  • throw:

    • 用在方法体内,跟的是异常对象名
    • 表示抛出异常,由方法体内的语句处理
    • 执行throw一定抛出了某种异常

9、Random类

9.1、概述

主要用来生成随机数

9.2、常用方法

使用方法前需要创建对象

方法描述
protected int next(int bits)生成一个随机数
public boolean nextBoolean()随机生成boolean类型随机数
public int nextInt()生辰一个int类型随机数
public int nextInt(int bound)返回一个伪随机数,范围在 0 (包括)和指定值 n (不包括)之间的int 值。
public long nextLong()生成一个long类型随机数
public float nextFloat()生成一个float类型随机数
public float nextDouble()生成一个double类型随机数
public void nextBytes(byte[] bytes)生成随机字节并将其放入用户提供的字节数组中

10、旧日期时间相关类

10.1、Date类(线程不安全)

描述:该类是一个日期类

构造方法:

方法描述
public Date()构造一个 Date对象,它代表当前的毫秒值
public Date(long date)使用给定的毫秒时间值构造一个Date对象

10.2、SimpleDateFormat类(线程不安全)

Date类显示的格式没有考虑国际化问题,如该格式不符合中国人查看时间的格式习惯,因此需要对其进行格式化操作。java.text.SimpleDateFormat类可以实现格式化操作,它是java.text.DateFormat的子类。

  • 将日期格式化为字符串

    Date d = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String s = sdf.format(d);
    
  • 将字符串格式化为日期

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date d = sdf.parse("2023-07-15 01:02:03");
    
需要获取的时间单位格式
YYYY or yyyy
MM(大写)
dd(小写)
HH or hh
mm(小写)
ss(小写)
毫秒SS(大写)
从当年的1月1日起,到获取时间是该年份的第几天DD(大写)

10.3、Calendar类(线程不安全)

Date类用于返回日期对象,不适合获取日历字段。Calendar用于设置和获取日历字段的方法。

Calendar c = Calendar.getInstance();//实例化对象
//获取日历时间属性
int year = c.get(Calendar.YEAR);//获取年份
int month = c.get(Calendar.MONTH) + 1;//获取月份
int dayOfMonth = c.get(Calendar.DAY_OF_MONTH);//获取今天是当月第几天
int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);//获取今天是本周第几天,从周日为第一天开始算
int hour = c.get(Calendar.HOUR);//获取十二时制小时
int hourOfDay = c.get(Calendar.HOUR_OF_DAY);//获取二十四时制小时
int minute = c.get(Calendar.MINUTE);//获取分
int second = c.get(Calendar.SECOND);//获取秒
int millisecond = c.get(Calendar.MILLISECOND);//获取毫秒
System.out.println(year+"年"+month+"月"+dayOfMonth+"日"+hourOfDay+"点"+minute+"分"+second+"秒"+millisecond+"毫秒");//2023年7月18日17点11分46秒72毫秒//设置日历时间属性
c.set(Calendar.YEAR, 2000); //将年设置为2000年
c.set(Calendar.MONTH,0);   //将月设置为1月    传入0为1月
c.set(Calendar.DAY_OF_MONTH,16); //将日设置为16日。
c.set(Calendar.HOUR_OF_DAY,18); //将小时设置为18点。
c.set(Calendar.MINUTE,1); //将分钟设置为1分。
c.set(Calendar.SECOND,2); //将秒设置为2秒。
c.set(Calendar.MILLISECOND,3); //将毫秒设置为3毫秒。//转化为Date,并完成格式化
Date time = c.getTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
String format = sdf.format(time);
System.out.println(format);//2000-01-16 18:01:02.03

10.4、TimeZone类

TimeZone 表示时区偏移量,也可以计算夏令时。在操作 Date, Calendar等表示日期/时间的对象时,经常会用到TimeZone;因为不同的时区,时间不同。

TimeZone tzDefault = TimeZone.getDefault();//当前系统默认时区
String[] availableIDs = TimeZone.getAvailableIDs();//获取Java支持的所有时区id
String[] availableIDs1 = TimeZone.getAvailableIDs(28800000);//根据偏移量获取时区id
TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai");//获取指定失去id的TimeZone
String displayName = timeZone.getDisplayName();//获取展示时区名称
System.out.println(displayName);//中国标准时间
String id = timeZone.getID();//获取时区id
System.out.println(id);//Asia/Shanghai

11、Arrays类

描述:该类包含用于操作数组的各种方法

方法描述
public static <T> List<T> asList(T... a)描述:返回由指定数组支持的固定大小的列表 举例:List stooges = Arrays.asList(“Larry”, “Moe”, “Curly”);
public static String toString(int[] a)描述:返回指定数组的内容的字符串表示形式 举例: int[] a = { 10, 20, 30, 4, 5, 60, 70, 80 }; String as = Arrays.toString(a);
public static void sort(int[] a)描述:按照数字升序顺序排列指定的数组 举例: int[] a = { 10, 20, 30, 4, 5, 60, 70, 80 }; Arrays.sort(a);
public static void sort(int[] a, int fromIndex, int toIndex)描述:按照数字升序顺序对数组的指定下标范围进行排序 举例:int[] a = { 10, 20, 30, 4, 5, 60, 70, 80 }; Arrays.sort(a, 2, 7);
public static int binarySearch(int[] a, int key)描述:使用二分查找算法在指定的int数组中搜索指定的值 ,返回下标 举例:int[] a = { 10, 20, 30, 4, 5, 60, 70, 80 }; Arrays.sort(a); int index = Arrays.binarySearch(a, 5);
public static int binarySearch(int[] a, int fromIndex, int toIndex, int key)描述:使用二分查找算法在指定的int数组中的指定下标范围搜索指定的值 举例: int[] a = { 10, 20, 30, 4, 5, 60, 70, 80 }; Arrays.sort(a); int index = Arrays.binarySearch(a, 2, 7, 60);

12、Objects类

描述:该类包含用于操作对象的各种方法。

方法描述
public static boolean isNull(Object obj)判断对象是否为空
public static boolean nonNull(Object obj)判断对象是否不空
public static <T> T requireNonNull(T obj)检查对象是否不空 如果为空,抛出NullPointerException()
public static <T> T requireNonNull(T obj, String message)检查对象是否不空 如果为空:抛出NullPointerException(message)
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier)检查对象是否不空 如果为空:抛出NullPointerException(messageSupplier.get())
public static boolean equals(Object a, Object b)比较两个对象是否相等
public static boolean deepEquals(Object a, Object b)比较两个对象是否相等
public static int hashCode(Object o)获取对象的哈希码
public static int hash(Object... values)获取对象的哈希值
public static <T> int compare(T a, T b, Comparator<? super T> c)比较两个对象大小
public static String toString(Object o)输出对象的字符串形式
public static String toString(Object o, String nullDefault)输出对象的字符串形式,可以设置为空时默认值

13、新日期时间相关类(1.8新特性,线程安全)

13.1、概述

Java 的 java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,而且都不是线程安全的,Java 8的日期和时间类包含 LocalDate、LocalTime、LocalDateTime、Instant、Duration 以及 Period,这些类都包含在 java.time 包中,它们是线程安全的。

13.2、LocalDate(年、月、日、星期几)

public class LocalDateDemo {
    public static void main(String[] args) {
        //1.获取时间年、月、日、星期
        LocalDate nowTime = LocalDate.now();
        System.out.println(nowTime);//获取日期--2023-07-18
        System.out.println(nowTime.getYear());//获取当前日期的年--2023
        System.out.println(nowTime.getMonth());//获取当前日期的月份枚举--JULY
        System.out.println(nowTime.getMonthValue());//获取当前日期的月份整数--7
        System.out.println(nowTime.getDayOfWeek());//获取该对象表示的日期是本周星期几枚举--TUESDAY
        System.out.println(nowTime.getDayOfMonth());//获取该对象表示的日期是本月第几天--18
        System.out.println(nowTime.getDayOfYear());//表示该对象表示的日期是本年第几天--199
​
        System.out.println(nowTime.get(ChronoField.YEAR));//获取当前日期的年--2023
        System.out.println(nowTime.get(ChronoField.MONTH_OF_YEAR));//获取当前日期的月份整数--7
        System.out.println(nowTime.get(ChronoField.DAY_OF_MONTH));//获取该对象表示的日期是本月第几天--18
        System.out.println(nowTime.get(ChronoField.DAY_OF_WEEK));//获取该对象表示的日期是本周第几天--2
        System.out.println(nowTime.get(ChronoField.DAY_OF_YEAR));//表示该对象表示的日期是本年第几天--199
​
        //2.修改时间年、月、日、星期
        System.out.println(nowTime.withYear(2000));//修改当前对象的年份--2000-07-18
        System.out.println(nowTime.withMonth(01));//修改当前对象的月份--2023-01-18
        System.out.println(nowTime.withDayOfMonth(16));//修改当前对象在本月的日期--2023-07-16
        System.out.println(nowTime.withDayOfYear(30));//修改当前对象在本年的日期--2023-01-30
​
        System.out.println(nowTime.minusYears(1));//一年前--2022-07-18
        System.out.println(nowTime.minusMonths(1));//一月前--2023-06-18
        System.out.println(nowTime.minusWeeks(1));//一周前--2023-07-11
        System.out.println(nowTime.minusDays(1));//一天前--2023-07-17
​
        System.out.println(nowTime.plusYears(1));//一年后--2024-07-18
        System.out.println(nowTime.plusMonths(1));//一月后--2023-08-18
        System.out.println(nowTime.plusWeeks(1));//一周后--2023-07-25
        System.out.println(nowTime.plusDays(1));//一天后--2023-07-19
​
        //3.比较时间大小
        LocalDate other = LocalDate.now().minusDays(1);//一天前
        System.out.println(nowTime.isBefore(other));//比较当前对象日期是否在other对象日期之前--false
        System.out.println(nowTime.isAfter(other));//比较当前对象日期是否在other对象日期之后--true
        System.out.println(nowTime.isEqual(other));//比较当前对象日期是否与other对象日期相等--false
        System.out.println(nowTime.compareTo(other));//比较当前对象日期与other对象日期在时间上的大小,为正,则nowTime晚--1
​
​
        //4.创建时间对象的方法
        System.out.println(LocalDate.of(2000, 1, 16));//直接传入对应的年月日--2000-01-16
        System.out.println(LocalDate.of(2000, Month.JANUARY, 16));//相对上面只是把月换成了枚举--2000-01-16
        System.out.println(LocalDate.ofYearDay(2000, 16));//第一个参数为年,第二个参数为当年的第多少天--2000-01-16
        LocalDate birDay = LocalDate.of(2000, 1, 16);
        System.out.println(LocalDate.ofEpochDay(birDay.toEpochDay()));//参数为距离1970-01-01的天数--2000-01-16
​
        System.out.println(LocalDate.parse("2000-01-16"));//2000-01-16
        System.out.println(LocalDate.parse("20000116", DateTimeFormatter.ofPattern("yyyyMMdd")));//2000-01-16
​
    }
}

13.3、LocalTime(时、分、秒、纳秒)

public class LocalTimeDemo {
    public static void main(String[] args) {
        //1.获取时间时、分、秒、纳秒
        LocalTime nowTime = LocalTime.now();
        System.out.println(nowTime);//获取时间--20:00:15.900
        System.out.println(nowTime.getHour());//获取--20
        System.out.println(nowTime.getMinute());//获取分--0
        System.out.println(nowTime.getSecond());//获取秒--15
        System.out.println(nowTime.getNano());//获取纳秒--900000000
​
        System.out.println(nowTime.get(ChronoField.HOUR_OF_DAY));//获取时--20
        System.out.println(nowTime.get(ChronoField.MINUTE_OF_HOUR));//获取分--0
        System.out.println(nowTime.get(ChronoField.SECOND_OF_MINUTE));//获取秒--15
        System.out.println(nowTime.get(ChronoField.NANO_OF_SECOND));//获取纳秒--900000000
​
        //2.修改时间时、分、秒、纳秒
        System.out.println(nowTime.withHour(18));//修改时--18:00:15.900
        System.out.println(nowTime.withMinute(17));//修改分--20:17:15.900
        System.out.println(nowTime.withSecond(16));//修改秒--20:00:16.900
        System.out.println(nowTime.withNano(15));//修改纳秒--20:00:15.000000015
​
​
        System.out.println(nowTime.minusHours(1));//一小时前--19:00:15.900
        System.out.println(nowTime.minusMinutes(1));//一分钟前--19:59:15.900
        System.out.println(nowTime.minusSeconds(1));//一秒钟前--20:00:14.900
        System.out.println(nowTime.minusNanos(1));//一纳秒前--20:00:15.899999999
        
        System.out.println(nowTime.plusHours(1));//一小时后--21:00:15.900
        System.out.println(nowTime.plusMinutes(1));//一分钟后--20:01:15.900
        System.out.println(nowTime.plusSeconds(1));//一秒钟后--20:00:16.900
        System.out.println(nowTime.plusNanos(1));//一纳秒后--20:00:15.900000001
​
        //3.比较时间大小
        LocalTime other = LocalTime.now().minusHours(1);//一小时前
        System.out.println(nowTime.isBefore(other));//比较当前时间是否在other对象时间之前--false
        System.out.println(nowTime.isAfter(other));//比较当前对象时间是否在other对象时间之后--true
        System.out.println(nowTime.compareTo(other));//比较当前对象时间与other对象时间在时间上的大小,为正,则nowTime晚--1
​
        //4.创建时间对象的方法
        System.out.println(LocalTime.of(8, 20));//时分--08:20
        System.out.println(LocalTime.of(8, 20, 30));//时分秒--08:20:30
        System.out.println(LocalTime.of(8, 20, 30, 150));//时分秒纳秒--08:20:30.000000150
        LocalTime mTime = LocalTime.of(8, 20, 30, 150);
        System.out.println(LocalTime.ofSecondOfDay(mTime.toSecondOfDay()));//参数为距离当天零时的秒数--08:20:30
        System.out.println(LocalTime.ofNanoOfDay(mTime.toNanoOfDay()));//参数为距离当天零时的纳秒数--08:20:30.000000150
​
        System.out.println(LocalTime.parse("08:20:30"));//08:20:30
        System.out.println(LocalTime.parse("082030", DateTimeFormatter.ofPattern("HHmmss")));//08:20:30
​
    }
}

13.4、LocalDateTime(年、月、日、时、分、秒、纳秒、星期)

public class LocalDateTimeDemo {
    public static void main(String[] args) {
        //1.获取时间年、月、日、时、分、秒、纳秒
        LocalDateTime nowDateTime = LocalDateTime.now();
        System.out.println(nowDateTime);
        System.out.println(nowDateTime.getYear());//获取年
        System.out.println(nowDateTime.getMonthValue());//获取月
        System.out.println(nowDateTime.getDayOfMonth());//获取日
        System.out.println(nowDateTime.getHour());//获取时
        System.out.println(nowDateTime.getMinute());//获取分
        System.out.println(nowDateTime.getSecond());//获取秒
        System.out.println(nowDateTime.getNano());//获取纳秒
​
        System.out.println(nowDateTime.get(ChronoField.YEAR));//获取年
        System.out.println(nowDateTime.get(ChronoField.MONTH_OF_YEAR));//获取月
        System.out.println(nowDateTime.get(ChronoField.DAY_OF_MONTH));//获取日
        System.out.println(nowDateTime.get(ChronoField.HOUR_OF_DAY));//获取时
        System.out.println(nowDateTime.get(ChronoField.MINUTE_OF_HOUR));//获取分
        System.out.println(nowDateTime.get(ChronoField.SECOND_OF_MINUTE));//获取秒
        System.out.println(nowDateTime.get(ChronoField.NANO_OF_SECOND));//获取纳秒
​
        //2.修改时间年、月、日、时、分、秒、纳秒
        System.out.println(nowDateTime.withYear(1997));//修改年
        System.out.println(nowDateTime.withMonth(12));//修改月
        System.out.println(nowDateTime.withDayOfMonth(5));//修改日
        System.out.println(nowDateTime.withHour(19));//修改时
        System.out.println(nowDateTime.withMinute(18));//修改分
        System.out.println(nowDateTime.withSecond(17));//修改秒
        System.out.println(nowDateTime.withNano(16));//修改纳秒
​
        System.out.println(nowDateTime.minusYears(1));//一天前
        System.out.println(nowDateTime.minusMonths(1));//一月前
        System.out.println(nowDateTime.minusDays(1));//一天前
        System.out.println(nowDateTime.minusHours(1));//一小时前
        System.out.println(nowDateTime.minusMinutes(1));//一分钟前
        System.out.println(nowDateTime.minusSeconds(1));//一秒钟前
        System.out.println(nowDateTime.minusNanos(1));//一纳秒前
​
        System.out.println(nowDateTime.plusYears(1));//一天后
        System.out.println(nowDateTime.plusMonths(1));//一月后
        System.out.println(nowDateTime.plusDays(1));//一天后
        System.out.println(nowDateTime.plusHours(1));//一小时后
        System.out.println(nowDateTime.plusMinutes(1));//一分钟后
        System.out.println(nowDateTime.plusSeconds(1));//一秒钟后
        System.out.println(nowDateTime.plusNanos(1));//一纳秒后
​
        //3.比较时间大小
        LocalDateTime other = LocalDateTime.now().minusHours(1);//一小时前
        System.out.println(nowDateTime.isBefore(other));//比较当前时间是否在other对象时间之前
        System.out.println(nowDateTime.isAfter(other));//比较当前对象时间是否在other对象时间之后
        System.out.println(nowDateTime.isEqual(other));//比较当前对象时间是否与other对象时间相等
        System.out.println(nowDateTime.compareTo(other));//比较当前对象时间与other对象时间在时间上的大小,为正,则nowDateTime晚
​
        //4.创建时间对象的方法
        LocalDate birDay = LocalDate.of(1991, 11, 11);
        LocalTime mTime = LocalTime.of(8, 20, 30, 150);
        System.out.println(LocalDateTime.of(birDay, mTime));//参数为LocalDate和LocalTime
        System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20));
        System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20));
        System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20, 30));
        System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20, 30));
        System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20, 30, 150));
        System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20, 30, 150));
​
        System.out.println(LocalDateTime.parse("1991-11-11T08:20:30"));
        System.out.println(LocalDateTime.parse("1991-11-11 08:20:30", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
​
    }
}

13.5、Instant(时间戳)

public class InstantDemo {
    public static void main(String[] args) {
        //创建时间戳
        Instant instant = Instant.now();
        System.out.println("当前时间戳是:" + instant);
        Date date = Date.from(instant);
        System.out.println("当前时间戳是:" + date);
        instant = date.toInstant();
        System.out.println("当前时间戳是:" + instant);
​
        // 获取秒数
        long currentSecond = instant.getEpochSecond();
        System.out.println(currentSecond);
​
        // 获取毫秒数
        long currentMilli = instant.toEpochMilli();
        System.out.println(currentMilli);
​
        // 获取纳秒数
        int currentNano = instant.getNano();
        System.out.println(currentNano);
    }
}

13.6、Period(计算日期时间差年、月、日、总月)

public class PeriodDemo {
    public static void main(String[] args) {
        //1.创建两个时间点
        LocalDate today = LocalDate.now();
        System.out.println(today);//2023-07-18
        LocalDate birthDate = LocalDate.of(2000, 1, 16);
        System.out.println(birthDate);//2000-01-16
        //2.计算日期时间差,得到时间差对象
        Period period = Period.between(birthDate, today);//第二个参数减第一个参数
​
        int years = period.getYears();
        int months = period.getMonths();
        int days = period.getDays();
        long totalMonths = period.toTotalMonths();
​
        System.out.println(years);// 年--23
        System.out.println(months);// 月--6
        System.out.println(days);// 天--2
        System.out.println(totalMonths);// 总月282
    }
}

13.7、Duration(计算日期时间差日、时、分、秒、毫秒、纳秒)

public class DurationDemo {
    public static void main(String[] args) {
        //1.创建两个时间点
        LocalDateTime today = LocalDateTime.now();
        System.out.println(today);//2023-07-18T20:32:39.079
        LocalDateTime birthDate = LocalDateTime.of(2000, 01, 16, 20, 10, 5);
        System.out.println(birthDate);//2000-01-16T20:10:05
​
        //计算时间差,得到时间差对象
        Duration duration = Duration.between(birthDate, today);//第二个参数减第一个参数
        System.out.println(duration.toDays());//两个时间差的天数--8584
        System.out.println(duration.toHours());//两个时间差的小时数--206016
        System.out.println(duration.toMinutes());//两个时间差的分钟数--12360982
        System.out.println(duration.toMillis());//两个时间差的毫秒数--741658954079
        System.out.println(duration.toNanos());//两个时间差的纳秒数--741658954079000000
    }
}

13.8、ChronoUnit(用单个时间单位去衡量时间差)

public class ChronoUnitDemo {
    public static void main(String[] args) {
        LocalDateTime today = LocalDateTime.now();
        System.out.println(today);//2023-07-18T20:39:14.654
        LocalDateTime birthDate = LocalDateTime.of(2000, 1, 16, 20, 10, 5);
        System.out.println(birthDate);//2000-01-16T20:10:05
​
        System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));//相差的年数:23
        System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));//相差的月数:282
        System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));//相差的周数:1226
        System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));//相差的天数:8584
        System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));//相差的时数:206016
        System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));//相差的分数:12360989
        System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));//相差的秒数:741659349
        System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));//相差的毫秒数:741659349654
        System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));//相差的微秒数:741659349654000
        System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));//相差的纳秒数:741659349654000000
​
        System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));//相差的半天数:17168
        System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));//相差的十年数:2
        System.out.println("相差的百年数:" + ChronoUnit.CENTURIES.between(birthDate, today));//相差的百年数:0
        System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));//相差的千年数:0
        System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));//相差的纪元数:0
​
    }
}

13.9、DateTimeFormatter(时间解析和格式化)

解析:

public class DateTimeFormatterDemo {
    public static void main(String[] args) {
        // 内置格式
        LocalDate localDate1 = LocalDate.parse("2023-07-18", DateTimeFormatter.ISO_LOCAL_DATE);
        System.out.println(localDate1);
​
        // 自定义格式
        LocalDate localDate2 = LocalDate.parse("2023-07-18", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        System.out.println(localDate2);
    }
}

格式化:

public class DateTimeFormatterDemo {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.of(2023, 07, 18);
​
        // 内置格式
        String s1 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
        System.out.println(s1);
​
        // 自定义格式
        String s2 = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        System.out.println(s2);
    }
}

13.10、ZonedDateTime(带时区的时间)

public class ZonedDateTimeDemo {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
        System.out.println(zdt);
    }
}

14、原子工具类

14.1、概述

在多线程编程中,线程之间可能会出现数据竞争(Data Race)的问题,即多个线程同时访问和操作同一个变量,导致数据出现异常。Java中提供了原子类,可以通过一些特殊的实现来保证对于共享变量的读写操作具有原子性。

14.2、实现原理

原子类的实现基于一些硬件支持的操作,例如CAS(Compare-And-Swap)操作。CAS操作是CPU提供的一种原子性操作指令,通过比较内存地址上的值是否和期望值相等,如果相等则把新的值赋给内存地址上的值,否则不做任何操作。原子类的实现就是基于这些硬件支持的原子性操作指令,使得Java程序能够安全地进行并发访问。

14.3、方法使用

14.3.1、AtomicBoolean
public class AtomicBooleanDemo {
    public static void main(String[] args) {
        AtomicBoolean atomicBoolean = new AtomicBoolean();//参数可选填,不填默认为false
​
        // 获取当前 atomicBoolean 的值
        System.out.println(atomicBoolean.get());//false
​
        // 设置当前 atomicBoolean 的值
        atomicBoolean.set(true);
        System.out.println(atomicBoolean.get());//true
​
        // 获取并设置 getAndSet 的值
        System.out.println("获取并设置结果:" + atomicBoolean.getAndSet(false));//获取并设置结果:true
        System.out.println(atomicBoolean.get());//false
​
        // 比较并设置 atomicBoolean 的值,如果期望值不等于传入的第一个参数,则比较失败,返回false
        System.out.println("比较并设置结果:" + atomicBoolean.compareAndSet(false, true));//比较并设置结果:true
        System.out.println(atomicBoolean.get());//true
​
    }
}
14.3.2、AtomicInteger、AtomicLong
public class AtomicIntegerDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
​
        // 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
        System.out.println(atomicInteger.getAndIncrement());
​
        // 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
        System.out.println(atomicInteger.incrementAndGet());
​
        // 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
        System.out.println(atomicInteger.decrementAndGet());
​
        // 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
        System.out.println(atomicInteger.getAndDecrement());
​
        // 获取并加值(i = 0, 结果 i = 5, 返回 0)
        System.out.println(atomicInteger.getAndAdd(5));
​
        // 加值并获取(i = 5, 结果 i = 0, 返回 0)
        System.out.println(atomicInteger.addAndGet(-5));
​
        // 获取并更新(i = 0, pi 的当前值, 结果 i = -2, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        System.out.println(atomicInteger.getAndUpdate(p -> p - 2));
​
        // 更新并获取(i = -2, pi 的当前值, 结果 i = 0, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        System.out.println(atomicInteger.updateAndGet(p -> p + 2));
​
        // 获取并计算(i = 0, pi 的当前值, x 为参数1, 结果 i = 10, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        // getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
        // getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
        System.out.println(atomicInteger.getAndAccumulate(10, (p, x) -> p + x));
​
        // 计算并获取(i = 10, pi 的当前值, x 为参数1, 结果 i = 0, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        // updateAndGet 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
        // accumulateAndGet 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
        System.out.println(atomicInteger.accumulateAndGet(-10, (p, x) -> p + x));
​
    }
}
14.3.3、原子引用

有些时候,我们不一定会使用基础类型作为共享变量,也可能会使用对象类型作为共享变量,如何确保在多线程下的线程安全呢?

AtomicReference类:

class BigDecimalAccount {
    private AtomicReference<BigDecimal> balance;
​
    // 初始余额
    public BigDecimalAccount(BigDecimal balance) {
        this.balance = new AtomicReference<>(balance);
    }
​
    // 获取余额
    public BigDecimal getBalance() {
        return balance.get();
    }
​
    // 增加余额
    public void increase(BigDecimal money) {
        while (true) {
            BigDecimal prev = balance.get();
            BigDecimal next = prev.add(money);
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
    }
​
    // 减少余额
    public void decrease(BigDecimal money) {
        while (true) {
            BigDecimal prev = balance.get();
            BigDecimal next = prev.subtract(money);
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
    }
}
​
public class Test {
    public static void main(String[] args) throws InterruptedException {
        BigDecimalAccount account = new BigDecimalAccount(new BigDecimal(1000.00));
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(() -> { account.decrease(new BigDecimal(100.00)); }));
        }
        for (int i = 0; i < 10; i++) {
            threads.get(i).start();
        }
        for (int i = 0; i < 10; i++) {
            threads.get(i).join();
        }
        System.out.println("money : " + account.getBalance());//money : 0
    }
}

compareAndSet方法,首先,先比较传递过来的参数是否是期望的值,如果是,才会修改,如果不是,则修改失败。

那有没有有一种可能,在 A 线程第二次修改的时候,虽然,他的期望值是 10,但是这个 10,是被 B 线程修改的,他以为别人没有动过,然后执行更改操作,其实中间已经被更改过了,这就是ABA问题。

AtomicInteger atomicInteger = new AtomicInteger(10);
​
// A 线程修改预期值10为 100
atomicInteger.compareAndSet(10,100);
​
// B 线程修改预期值100 为 10
atomicInteger.compareAndSet(100,10);
​
// A 线程修改预期值10 为 0
atomicInteger.compareAndSet(10,0);

为了解决这个问题,我们只需要在每一次的修改操作上加一个版本号,这样即使中间被修改过,也能知道,JDK就提供了一种带版本号的原子引用对象。

AtomicStampedReference类:

AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(10, 0);
​
// A 线程修改预期值10为 100
Integer prev = asr.getReference();//10
int stamp = asr.getStamp();//0
asr.compareAndSet(prev, 100, stamp, stamp + 1);
System.out.println(asr.getStamp());//1
​
// B 线程修改预期值100 为 10
prev = asr.getReference();//100
stamp = asr.getStamp();//1
asr.compareAndSet(prev, 10, stamp, stamp + 1);
System.out.println(asr.getStamp());//2
​
// A 线程修改预期值10 为 0
prev = asr.getReference();//10
stamp = asr.getStamp();//2
asr.compareAndSet(prev, 0, stamp, stamp + 1);
System.out.println(asr.getStamp());//3

AtomicStampedReference可以给原子引用加上版本号,追踪原子引用的变化过程: A -> B -> A,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了 AtomicMarkableReference。

AtomicMarkableReference类:

class GarbageBag {
    String desc;
​
    public GarbageBag(String desc) {
        this.desc = desc;
    }
}
​
public class Test {
    public static void main(String[] args) {
        GarbageBag garbageBag = new GarbageBag("垃圾已满");
        AtomicMarkableReference<GarbageBag> amr = new AtomicMarkableReference<>(garbageBag, true);
​
        // 如果垃圾已满,请及时清理
        GarbageBag prev = amr.getReference();
        System.out.println(amr.compareAndSet(prev, new GarbageBag("垃圾已空"), true, false));//true
        System.out.println(amr.isMarked());//false
    }
}
​
14.3.4、原子数组

AtomicIntegerArray类AtomicLongArray类AtomicReferenceArray类

以 AtomicIntegerArray 为例:

AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
​
for (int i = 0; i < atomicIntegerArray.length(); i++) {
    while (true) {
        int prev = atomicIntegerArray.get(i);
        int next = prev + i;
        if (atomicIntegerArray.compareAndSet(i, prev, next)) {
            break;
        }
    }
}
​
System.out.println(atomicIntegerArray);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
14.3.5、字段更新器

AtomicIntegerFieldUpdater类AtomicLongFieldUpdater类AtomicReferenceFieldUpdater类

以 AtomicIntegerFieldUpdater 为例:

class Person {
    volatile String name;
    volatile int age;
​
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}
​
public class Test {
    public static void main(String[] args) {
        Person person = new Person("张三", 20);
        System.out.println(person);//Person{name='张三', age=20}
        AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
​
        updater.compareAndSet(person, 20, 18);
        System.out.println(person);//Person{name='张三', age=18}
    }
}
14.3.6、原子累加器

LongAdder类:

LongAdder longAdder = new LongAdder();
​
longAdder.add(100L);
longAdder.add(200L);
longAdder.add(300L);
System.out.println(longAdder.sum());//600
​
longAdder.increment();
System.out.println(longAdder.sum());//601
​
longAdder.decrement();
System.out.println(longAdder.sum());//600

DoubleAdder类:

DoubleAdder doubleAdder = new DoubleAdder();
​
doubleAdder.add(100.00D);
doubleAdder.add(200.00D);
doubleAdder.add(300.00D);
System.out.println(doubleAdder.sum());//600

15、Optional类

15.1、概述

Optional 类是一个容器类,代表一个值存在或不存在, 原来用 null 表示一个值不存在,现在 Optional类可以更好的表达这个概念并且可以避免空指针异常.

15.2、容器创建

Optional<String> name = Optional.of("wxx");//创建一个非空的 Optional 对象,参数不能为null,为null抛出空指针异常
System.out.println(name);//Optional[wxx]
Optional<Object> empty = Optional.empty();//创建一个空的 Optional 对象
System.out.println(empty);//Optional.empty
Optional<Object> optional = Optional.ofNullable("wxx");//即可空又可非空
System.out.println(optional);//Optional[wxx]

15.3、判断值是否存在

Optional<String> name = Optional.of("wxx");
System.out.println(name.isPresent());//true

15.4、非空表达式

基础版:

Optional<String> optOrNull = Optional.ofNullable(null);
if (optOrNull.isPresent()) {
    System.out.println(optOrNull.get().length());
}

简化版(ifPresent()):

Optional<String> opt = Optional.of("wxx");
opt.ifPresent(v-> System.out.println(v.length()));//3

15.5、获取值

  • get(): 使用前必须判断容器不为空,否贼抛出NoSuchElementException异常

    String s1 = Optional.of("zhangsan").get();
    System.out.println(s1);
    
  • orElse(): 如果 Optional 中有值则将其返回,否则返回 orElse 方法传入的参数。

    String s2 = Optional.ofNullable("zhangsan").orElse("lisi");
    System.out.println(s2);
    
  • orElseGet: 如果 Optional 中有值则将其返回,否则返回 orElseGet 方法传入的接口值。

    String s3 = Optional.ofNullable("zhangsan").orElseGet(() -> "lisi");
    System.out.println(s3);
    
  • orElseThrow : 如果 Optional 中有值则将其返回,否则抛出 orElseThrow 方法传入的异常信息。

    String s4 = Optional.ofNullable("zhangsan").orElseThrow(() -> new RuntimeException("空值"));
    System.out.println(s4);
    

15.6、过滤(filter)

Optional<String> name = Optional.of("zhangsan");
Optional<String> optional = name.filter(s -> s.startsWith("zhang"));
optional.ifPresent(System.out::println);

15.7、映射(map)

Optional<String> name = Optional.of("zhangsan");
Optional<String> optional = name.map(s -> s.toUpperCase());
optional.ifPresent(System.out::println);

15.8、展开(flatMap)

Optional<String> name = Optional.of("zhangsan");
Optional<String> optional = name.flatMap(s -> Optional.of(s.toUpperCase()));
optional.ifPresent(System.out::println);

16、Comparable与Comparator (排序)

16.1、概述

ComparableComparator都是两个接口,接口都可以用来实现集合中元素的比较、排序,Comparator位于包java.util下,而Comparable位于包java.lang下,Comparable接口将比较代码嵌入自身类中Comparator既可以嵌入到自身类中,也可以在一个独立的类中实现比较。

Integer、String等这些基本类型的JAVA封装类都已经实现了Comparable接口,这些类对象本身就支持自比较,直接调用Collections.sort()就可以对集合中元素的排序,无需自己去实现Comparable接口。

而有些自定义类的List序列,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较,也就是指定使用Comparator(临时规则排序,也称作专门规则排序),如果不指定Comparator,那么就用自然规则排序,这里的自然顺序就是实现Comparable接口设定的排序方式。

16.2、Comparable

Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。如果开发者add进入一个Collection的对象想要Collections的sort方法帮你自动进行排序的话,那么这个对象必须实现Comparable接口。compareTo方法的返回值是int,有三种情况:

  • 比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数
  • 比较者等于被比较者,那么返回0
  • 比较者小于被比较者,那么返回负整数

16.3、Comparator

Comparator可以认为是是一个外比较器,个人认为有两种情况可以使用实现Comparator接口的方式:

  • 一个对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较
  • 一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式

Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:

  • o1大于o2,返回正整数
  • o1等于o2,返回0
  • o1小于o3,返回负整数

16.4、区别.

参数ComparableComparator
排序逻辑排序逻辑必须写在待排序对象的类中,故称之为自然排序排序逻辑既可以写在待排序对象的类中,也可以在另一个类使用过程中实现
实现实现Comparable接口实现Comparator接口
排序方法int compareTo(Object o1)int compare(Object o1,Object o2)
触发排序集合排序: Collections.sort(List) 数组排序: Arrays.sort(Object[])集合排序: Collections.sort(List) Collections.sort(List,Comparator) 数组排序: Arrays.sort(Object[]) Arrays.sort(T[] a, Comparator)
接口所在包java.lang.Comparablejava.util.Comparator
排序规则数量类只能有一种默认排序规则可定义多个不同的排序规则

16.5、实现方法

16.5.1、实现Comparable接口
public class User implements Comparable<User> {
    private String name;
​
    private int age;
​
    private boolean sex;
​
    public User() {
    }
​
    public User(String name, int age, boolean sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public boolean isSex() {
        return sex;
    }
​
    public void setSex(boolean sex) {
        this.sex = sex;
    }
​
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
​
    @Override
    public int compareTo(User o) {
        return this.age - o.age;
    }
}
public class ComparableDemo {
    public static void main(String[] args) {
        List<User> list = new ArrayList<User>() {{
            add(new User("ake", 25, true));
            add(new User("reo", 24, false));
            add(new User("fg", 24, false));
        }};
        Collections.sort(list);
        System.out.println("类自身实现Comparable:" + list);
    }
}
打印:
类自身实现Comparable:[User{name='reo', age=24, sex=false}, User{name='fg', age=24, sex=false}, User{name='ake', age=25, sex=true}]
16.5.2、实现Comparator接口
public class User implements Comparator<User> {
    private String name;
​
    private int age;
​
    private boolean sex;
​
    public User() {
    }
​
    public User(String name, int age, boolean sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public boolean isSex() {
        return sex;
    }
​
    public void setSex(boolean sex) {
        this.sex = sex;
    }
​
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
​
​
    @Override
    public int compare(User o1, User o2) {
        return o1.age-o2.age;
    }
}
public class ComparatorDemo {
    public static void main(String[] args) {
        List<User> list = new ArrayList<User>(){{
            add(new User("zhangsan", 25, true));
            add(new User("li", 24, false));
            add(new User("wangwu", 24, false));
        }};
        Collections.sort(list);
        //类实现了的Comparator能满足需求
        System.out.println("类自身实现Comparator:"+list);
        //现在我想按名字升序,显然类中实现的不能满足要求,于是可以在类外自己实现想要的比较器
        Collections.sort(list, new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        System.out.println("匿名内部类实现Comparator:"+list);
​
        //由于Comparator接口是一个函数式接口,因此根据jdk1.8新特性,我们可以采用Lambda表达式简化代码
        Collections.sort(list,(u1,u2)->{return u1.getName().compareTo(u2.getName());});
        System.out.println("Lambda表达式方式Comparator:"+list);
​
    }
}
打印:
类自身实现Comparator:[User{name='li', age=24, sex=false}, User{name='wangwu', age=24, sex=false}, User{name='zhangsan', age=25, sex=true}]
匿名内部类实现Comparator:[User{name='li', age=24, sex=false}, User{name='wangwu', age=24, sex=false}, User{name='zhangsan', age=25, sex=true}]
Lambda表达式方式Comparator:[User{name='li', age=24, sex=false}, User{name='wangwu', age=24, sex=false}, User{name='zhangsan', age=25, sex=true}]

17、Runtime

17.1 概述

Runtime表示Java中运行时对象,可以获取到程序运行时设计到的一些信息

17.2 常见方法

常见方法介绍

我们要学习的Object类中的常见方法如下所示:

public static Runtime getRuntime()      //当前系统的运行环境对象
public void exit(int status)            //停止虚拟机
public int availableProcessors()        //获得CPU的线程数
public long maxMemory()                 //JVM能从系统中获取总内存大小(单位byte)
public long totalMemory()               //JVM已经从系统中获取总内存大小(单位byte)
public long freeMemory()                //JVM剩余内存大小(单位byte)
public Process exec(String command)     //运行cmd命令

代码示例:

public class RunTimeDemo1 {
    public static void main(String[] args) throws IOException {
        /*
            public static Runtime getRuntime() 当前系统的运行环境对象
            public void exit(int status) 停止虚拟机
            public int availableProcessors() 获得CPU的线程数
            public long maxMemory() JVM能从系统中获取总内存大小(单位byte)
            public long totalMemory() JVM已经从系统中获取总内存大小(单位byte)
            public long freeMemory() JVM剩余内存大小(单位byte)
            public Process exec(string command) 运行cmd命令
        */
​
        //1.获取Runtime的对象
        //Runtime r1 =Runtime.getRuntime();
​
        //2.exit 停止虚拟机
        //Runtime.getRuntime().exit(0);
        //System.out.println("看看我执行了吗?");
​
​
        //3.获得CPU的线程数
        System.out.println(Runtime.getRuntime().availableProcessors());//8
        //4.总内存大小,单位byte字节
        System.out.println(Runtime.getRuntime().maxMemory() / 1024 / 1024);//4064
        //5.已经获取的总内存大小,单位byte字节
        System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);//254
        //6.剩余内存大小
        System.out.println(Runtime.getRuntime().freeMemory() / 1024 / 1024);//251
​
        //7.运行cmd命令
        //shutdown :关机
        //加上参数才能执行
        //-s :默认在1分钟之后关机
        //-s -t 指定时间 : 指定关机时间
        //-a :取消关机操作
        //-r: 关机并重启
        Runtime.getRuntime().exec("shutdown -s -t 3600");
​
​
    }
}