Java常用类全面解析(含部分源码)

常用类

文章目录

字符串相关的类

String 类

说明

String:字符串,使用一对 “” 包起来表示

  1. String 声明为 final 的,不可以被继承

  2. String 实现了 Serializable 接口:表示字符串是支持序列化的
    实现了 Comparable 接口:表示 String 可以比较大小

  3. String 内部定义了 final char[] value 用于存储字符串数据

  4. String:代表不可变的字符序列(简称:不可变性)
    体现:

    • 当对字符串重新赋值时,需要重新指定内存区域,不能使用原有的 value 上进行赋值
    • 当对现有的字符串进行连接操作时,也需要重新指定内存区域,不能再原有的 value 上进行赋值
    • 当调用 String 的 replace() 方法修改指定的字符或字符串时,也需要重新指定内存区域,不能再原有的 value 上进行赋值
  5. 通过字面量的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中

    JVM 中字符串常量池的位置说明

    JDK1.6:字符串常量池存储在方法区(永久区)

    JDK1.7:字符串常量池存储在堆空间中

    JDK1.8:字符串常量池存储在方法区(元空间)

  6. 字符串常量池中是不会存储相同内容(使用 String 类的 equals() 方法进行比较,返回 true 的就叫做相同内容)的字符串的

String 部分源码

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
}

案例

package com.laoyang.test.day01;

import org.junit.Test;

/**
 * @ClassName StringTest
 * @Description: String 的使用
 * @Author Laoyang
 * @Date 2021/9/2 16:09
 */
public class StringTest {
    @Test
    public void testOne() {
        String sA = "abc";   // 字面量的定义方式
        String sB = "abc";

        // 比较 sA 和 sB 的地址值
        System.out.println(sA == sB);

        sA = "Hello";
        System.out.println(sA == sB);

        System.out.println(sA); // Hello
        System.out.println(sB); // abc

        System.out.println("------------------------------");

        String sC = "abc";
        sC += "def";
        System.out.println(sC);  // abcdef

        System.out.println("------------------------------");

        String sD = "abc";
        String replace = sD.replace('a', 'Q');
        System.out.println(sD);         // abc
        System.out.println(replace);    // Qbc
    }
}

String 的实例方式

方式一:通过字面量定义
方式二:通过 new + 构造器

package com.laoyang.test.day01;

import org.junit.Test;

/**
 * @ClassName StringTest
 * @Description: String 的使用
 * @Author Laoyang
 * @Date 2021/9/2 16:09
 */
public class StringTest {
    /**
     String 的实例化方式
     方式一、通过字面量定义
     方式二、通过 new + 构造器
     */
    @Test
    public void testTwo() {
        /*
         通过字面量定义
         此时的 sA 和 sB 的数据 javaEE 声明在方法区中的字符串常量池中
         */
        String sA = "javaEE";
        String sB = "javaEE";

        /*
        通过 new + 构造器
        此时的 sC 和 sD 保存的地址值是数据在堆空间中开辟空间以后对应的地址值
         */
        String sC = new String("javaEE");
        String sD = new String("javaEE");

        System.out.println(sA == sB);   // true
        System.out.println(sA == sC);   // false
        System.out.println(sA == sD);   // false
        System.out.println(sC == sD);   // false

        System.out.println("------------------------------");

        Person personA = new Person("Tom", 12);
        Person personB = new Person("Tom", 12);
        System.out.println(personA.name.equals(personB.name));  // true
        System.out.println(personA.name == personB.name);       // true

        personA.name = "Jerry";
        personB.name = "Tom";
        System.out.println(personA.name);
    }

    /**
     结论:
     1. 常量与常量的拼接结果在常量池中,且常量池中不会存在相同内容的常量
     2. 只要其中一个是变量,结果就在堆空间中!
     3. 如果拼接的结果调用 intern() 方法,那么返回值就在常量值中
     */
    @Test
    public void testThree() {
        String strA = "javaEE";
        String strB = "hello";

        String strC = "javaEEhello";
        String strD = "javaEE" + "hello";
        String strE = strA + "hello";
        String strF = "javaEE" + strB;
        String strG = strA + strB;

        System.out.println(strC == strD);   //true
        System.out.println(strC == strE);   //false
        System.out.println(strC == strF);   //false
        System.out.println(strE == strF);   //false
        System.out.println(strC == strG);   //false
        System.out.println(strE == strG);   //false

        /*
        返回值得到的 strH 使用的常量值中已经存在的 “javaEEhello”
        intern():返回字符串对象的规范表示。
         */
        String strH = strE.intern();
        String strI = strF.intern();
        System.out.println(strC == strH);  // true
        System.out.println(strC == strI);  // true
    }

    /**
     * 了解
     */
    @Test
    public void test() {
        String strA = "JavaEEhello";
        String strB = "JavaEE";
        String strC = strB + "hello";
        System.out.println(strA == strC);  // false

        final String strD = "JavaEE";    // 常量,拼接完字符串之后还是存在于常量池中
        String strE = strD + "hello";
        System.out.println(strA == strE);  // true
    }
}
package com.laoyang.test.day01;

/**
 * @ClassName Person
 * @Description:辅助测试的类
 * @Author Laoyang
 * @Date 2021/9/2 17:13
 */
public class Person {
    String name;
    int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

面试题:

问:String name = new String(“abc”); 方式创建对象,在内存中创建了几个对象?

答:两个,一个是堆空间中 new 结构;另一个是 char[] 对应的常量池中的数据:“abc”

String 中的常用方法

方法作用
int length()返回字符串的长度: return value.length
char charAt(int index)返回某索引处的字符return value[index]
boolean isEmpty()判断是否是空字符串:return value.length == 0
String toLowerCase()使用默认语言环境,将 String 中的所有字符转换为小写
tring toUpperCase()使用默认语言环境,将 String 中的所有字符转换为大写
String trim()返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj)比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString)与equals方法类似,忽略大小写
String concat(String str)将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString)比较两个字符串的大小
String substring(int beginIndex)返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex)返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
boolean endsWith(String suffix)测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix)测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset)测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s)当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str)返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex)返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str)返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex)返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
String replace(char oldChar, char newChar)返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement)使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement)使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement)使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
boolean matches(String regex)告知此字符串是否匹配给定的正则表达式。
String[] split(String regex)根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit)根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

注意:indexOf 和 lastIndexOf 方法如果未找到都是返回 -1

案例一
package com.laoyang.test.day01;

import org.junit.Test;

/**
 * @ClassName StringCommonMethod
 * @Description: String 中的常用方法
 * @Author Laoyang
 * @Date 2021/9/8 10:17
 */
public class StringCommonMethod {
    /**
     * int length():返回字符串的长度: return value.length
     * char charAt(int index): 返回某索引处的字符return value[index]
     * boolean isEmpty():判断是否是空字符串:return value.length == 0
     * String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
     * String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
     * String trim():返回字符串的副本,忽略前导空白和尾部空白
     * boolean equals(Object obj):比较字符串的内容是否相同
     * boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
     * String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
     * int compareTo(String anotherString):比较两个字符串的大小
     * String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
     * String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
     */
    @Test
    public void testOne() {
        String strA = "HelloWorld";

        // 指定字符串的长度
        System.out.println(strA.length());  // 10

        // 返回对应位置的字符
        System.out.println(strA.charAt(1)); // e

        // 判断是否是空字符串
        System.out.println(strA.isEmpty()); // false
        // 如果是赋值的时候为 “”,那么判断的时候就会是 true
        //strA = "";
        //System.out.println(strA.isEmpty()); // true

        // 将字符修改为小写
        String strB = strA.toLowerCase();
        System.out.println(strA);   // HelloWorld
        System.out.println(strB);   // helloworld

        // 将字符修改为大写
        System.out.println(strA.toUpperCase());  // HELLOWORLD

        // 消除前后空格
        String strC = "  Hello World  ";
        System.out.println(strC);   // " Hello World  "
        System.out.println(strC.trim()); // Hello World

        // 比较两个字符串是否相等
        System.out.println(strA.equals(strC));  // false

        // 忽略大小写进行比较
        String strD = "abc";
        String strE = "ABC";
        System.out.println(strD.equalsIgnoreCase(strE));    // true

        // 将指定的字符串添加到当前字符串的末尾
        String strF = strD.concat("DEF");
        System.out.println(strF);   // abcDEF

        /*
         比较两个字符串的大小,结果为负数,表示当前字符小,如果为正数,则当前对象大
         根据每一个字符进行比较,调用的字符串减去比较的字符串
         a = 97; b = 98; c = 99
         A = 65; B = 66; C = 67
         97 - 65; 98 - 66; 99 - 67(减完以后的总数就是结果)
         字符详细对应的值请自行查看 ASCII表
         */
        System.out.println(strD.compareTo(strE));   // 32

        // 截取指定位置到最后位置的字符
        System.out.println(strA.substring(5));  // World

        // 截取指定开始位置和结束位置的字符
        System.out.println(strA.substring(0, 5));  // Hello
    }
}
案例二
package com.laoyang.test.day01;

import org.junit.Test;

/**
 * @ClassName StringCommonMethod
 * @Description: String 中的常用方法
 * @Author Laoyang
 * @Date 2021/9/8 10:17
 */
public class StringCommonMethod {
    /**
     * boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
     * boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
     * boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
     * boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
     * int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
     * int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
     * int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
     * int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
     * 注意:indexOf和lastIndexOf方法如果未找到都是返回-1
     */
    @Test
    public void testTwo() {
        String strA = "HelloWorld";

        // 当前字符串是否以指定的后缀结束
        System.out.println(strA.endsWith("d")); // true
        System.out.println(strA.endsWith("f")); // false

        // 当前字符串是否以指定的前缀开始
        System.out.println(strA.startsWith("h"));   // false
        System.out.println(strA.startsWith("H"));   // true

        // 当前字符串从指定索引开始的子字符串是否以指定前缀开始
        System.out.println(strA.startsWith("ll", 2));   // true
        System.out.println(strA.startsWith("ll", 4));   // false

        // 判断是否包含指定的字符
        System.out.println(strA.contains("Hello"));    // true

        // 在指定的字符串中找对应的字符串,找到后返回对应的索引值,未找到则返回 -1(从左往右查找)
        System.out.println(strA.indexOf("W"));      // 5
        System.out.println(strA.indexOf("aaa"));    // -1

        // 从指定位置开始找对应的字符串,找到则返回对应的索引值,未找到则返回 -1(从左往右查找)
        System.out.println(strA.indexOf("W", 6));   // -1
        System.out.println(strA.indexOf("W", 2));   // 5

        // 返回指定子字符串在此字符串中最右边出现处的索引,未找到返回 -1(从右往左查找)
        String strB = "abcaabde";
        System.out.println(strB.lastIndexOf("ab"));  // 4
        System.out.println(strB.lastIndexOf("db"));  // -1

        // 从指定位置开始找对应的字符串,找到则返回对应的索引值,未找到则返回 -1(从右往左查找)
        System.out.println(strB.lastIndexOf("ab", 3));  // 0
        System.out.println(strB.lastIndexOf("abccc", 3));  // -1

        /*
        什么情况下,indexOf(String str) 和 lastIndexOf(String str) 的返回值相同?
        情况一、存在唯一的一个 str(比如在 acbd 中找 c)
        情况二、不存在 str(比如在 abcd 中找 e)
         */
    }
}
案例三
package com.laoyang.test.day01;

import org.junit.Test;

/**
 * @ClassName StringCommonMethod
 * @Description: String 中的常用方法
 * @Author Laoyang
 * @Date 2021/9/8 10:17
 */
public class StringCommonMethod {
    /**
     * 替换:
     * String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
     * String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
     * String replaceAll(String regex, String replacement) : 使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
     * String replaceFirst(String regex, String replacement) : 使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
     *
     * 匹配:
     * boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
     *
     * 切片:
     * String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
     * String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
     */
    @Test
    public void testThree() {
        String strA = "HelloWorld";

        // 替换指定字符
        System.out.println(strA.replace('l', 'A'));     // HeAAoWorAd

        // 替换指定字符串
        System.out.println(strA.replace("Hello", "你好"));     // 你好World

        /*
         根据对应的正则表达式替换对应的字符
         把字符串中的数字替换成 “,” (逗号),如果结果中开头和结尾有逗号,那么就删除开头和结尾的逗号在进行打印
         */
        String str = "12hello34world5java7891mysql456";
        String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
        System.out.println(string);   // hello,world,java,mysql

        System.out.println("----------------------------------------");

        /*
        根据对应的正则表达式替换对应的字符
        把字符串中的数字替换成 “,” (逗号)
         */
        String stringA = str.replaceFirst("\\d+", ",");
        System.out.println(stringA);   // ,hello34world5java7891mysql456

        System.out.println("----------------------------------------");

        /*
         当前字符串是否匹配给定的正则表达式
         */
        String strB = "12345";
        //判断str字符串中是否全部由数字组成,即有1-n个数字组成
        boolean matches = strB.matches("\\d+");
        System.out.println(matches);    // true

        String tel = "86-16612345678";
        //判断这是否是一个国内的电话号码
        boolean result = tel.matches("86-\\d{11}");
        System.out.println(result);     // true

        System.out.println("----------------------------------------");

        /*
        根据给定正则表达式的匹配拆分指定的字符串
         */
        String strC = "hello|world|java";
        // 根据 “|” 进行拆分
        String[] strs = strC.split("\\|");
        for (int i = 0; i < strs.length; i++) {
            /*
            hello
            world
            java
             */
            System.out.println(strs[i]);
        }

        System.out.println("----------------------------------------");

        /*
        根据匹配给定的正则表达式来拆分当前字符串,最多不超过 limit 个,如果超过了,剩下的全部都放到最后一个元素中。
         */
        String strD = "hello.world.java.A.C.D.F.G";
        String[] strs2 = strD.split("\\.", 4);
        for (int i = 0; i < strs2.length; i++) {
            /*
            hello
            world
            java
            A.C.D.F.G
             */
            System.out.println(strs2[i]);
        }
    }
}

String 类与其它结构之间的转换

小复习-与基本数据类型和包装类的转换
package com.laoyang.test.day02;

import org.junit.Test;

public class StringConversion {
    /**
     * String 与 基本数据类型、包装类的转换
     * String  ->  基本数据类型、包装类(调用包装类中的静态方法:parseXxx(str) )
     * 基本数据类型、包装类  ->  String(调用 String 重载的 valueOf(xxx) 方法)
     */
    @Test
    public void testOne() {
        String age = "18";
        //int ages = (int) age;   // 错误转换
        int ageI = Integer.parseInt(age);
        System.out.println(ageI);   //18

        double balance = 2200.00;
        String s = String.valueOf(balance);
        System.out.println(s);  //2200.0
        String str = balance + "";
        System.out.println(str);    //2200.0
    }
}
String 与 字符数组的转换
  • 字符数组 -> 字符串

    String 类的构造器:String(char[]) 和 String(char[],int offset,int length) 分别用字符数组中的全部字符和部分字符创建字符串对象。

  • 字符串 -> 字符数组

    public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。

    public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。

案例
package com.laoyang.test.day02;

import org.junit.Test;
import java.util.Arrays;

/**
 * @ClassName StringConversion
 * @Description: String 类与其它结构之间的转换
 * @Author Laoyang
 * @Date 2021/9/9 10:35
 */
public class StringConversion {
    /**
     * String 与 char[] (字符)之间的转换
     * String  ->  char[](调用 String 中的 toCharArray() 方法)
     * char[]  ->  String(调用 String 的构造器)
     */
    @Test
    public void testTwo() {
        String str = "abc123";
        // String 转 char[]
        char[] chars = str.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            System.out.println(chars[i]);   // a b c 1 2 3
        }

        // char[] 转 String
        char[] arr = new char[]{'q', 'w', 'e', 'r'};
        String string = new String(arr);
        System.out.println(string);     // qwer
    }

    /**
     * 小练习:将对应的字符进行翻转;比如:abc123  翻转为 a21cb3
     */
    @Test
    public void testExercise() {
        String strA = "abc123";
        char[] charsA = strA.toCharArray();
        for (int i = 0; i < charsA.length / 2; i++) {
            if (i > 0) {
                char obj = charsA[i];
                charsA[i] = charsA[charsA.length - i - 1];
                charsA[charsA.length - i - 1] = obj;
            }
        }
        String strC = new String(charsA);
        System.out.println(strC);   // a21cb3
    }
}
String 与 字节数组的转换
  • 字节数组 -> 字符串

    String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。

    String(byte[],int offset,int length) :用指定的字节数组的一部分, 即从数组起始位置 offset 开始取 length 个字节构造一个字符串对象。

  • 字符串 -> 字节数组

    public byte[] getBytes() :使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

    public byte[] getBytes(String charsetName) :使用指定的字符集将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。

案例
package com.laoyang.test.day02;

import org.junit.Test;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

/**
 * @ClassName StringConversion
 * @Description: String 类与其它结构之间的转换
 * @Author Laoyang
 * @Date 2021/9/9 10:35
 */
public class StringConversion {
    /**
     * String 与 byte[] (字节)之间的转换
     * 编码:String  ->  byte[](调用 String 类的 getByes() 方法)
     * 解码:byte[]  ->  String()
     *
     * 了解:
     * 1. 编码:字符串  ->  字节(简单来说就是 “把看得懂的数据转换成看不懂的二进制数据”)
     * 2. 解码:字节  ->  字符串(编码的逆过程,也就是把看不懂的二进制数据转换成看得懂的数据)
     *
     * 注意:转换成字符的那种方式并不是编码与解码,因为它不管是在编码还是解码的时候都是可以看得懂的数据!
     */
    @Test
    public void testThree() throws UnsupportedEncodingException {
        String str = "abc123嘿哈";
        // 使用默认的字符集进行转换(设置的是什么格式就是什么格式,我这里设置的是UTF-8)
        byte[] bytes = str.getBytes();
        // UTF-8 中中文占用三个字节
        System.out.println(Arrays.toString(bytes));     // [97, 98, 99, 49, 50, 51, -27, -104, -65, -27, -104, -65]

        /*
         手动设置对应的字符集进行转换
         使用 GBK 字符集进行编码(GBK 中中文占用两个字节)
         */
        byte[] gbks = str.getBytes("GBK");
        System.out.println(Arrays.toString(gbks));      // [97, 98, 99, 49, 50, 51, -70, -39, -71, -2]

        System.out.println("---------------------------------------");

        // 使用默认的字符集进行解码
        String strBytes = new String(bytes);
        System.out.println(strBytes);   // abc123嘿哈

        String gbkA = new String(gbks);
        // 出现乱码,原因:编码集和解码集不一致!
        System.out.println(gbkA);     // abc123�ٹ�

        // 使用指定的字符集进行解码
        String gbkB = new String(gbks, "GBK");
        System.out.println(gbkB);   // abc123嘿哈
    }
}

StringBuffer 类

介绍

  • java.lang.StringBuffer 代表可变的字符序列,JDK1.0 中声明,可以对字符串内容进行增删,并且不会产生新的对象
  • 作为参数传递时,方法内部可以改变值
  • 大部分方法与 String 相同

StringBuffer 类不同于String,其对象必须使用构造器生成。

StringBuffer 类中有三个构造器:

StringBuffer():初始容量为 16 的字符串缓冲区

StringBuffer(int size):构造指定容量的字符串缓冲区

StringBuffer(String str):将内容初始化为指定字符串内容

StringBuffer 源码分析

大家可以结合以下代码中的注释来看下面的部分源码

StringString strA = new String();   // new char[0]
String strB = new String("abc");   // new char[]{'a','b','c'}

StringBufferStringBuffer strbufferA = new StringBuffer();   // char[] value = new char[16];底层创建了一个长度为 16 的数组
strbufferA.append("a");    // value[0] = 'a';
strbufferA.append("b");    // value[1] = 'b';

StringBuffer strbufferB = new StringBuffer("abc");   // char[] value = new char["abc".length() + 16]
部分源码

StringBuffer 类

public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence{    
	/**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
        super(16);
    }
    
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
}

AbstractStringBuilder 类

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;
    
    int count;
    
    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
    
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    
    // 验证当前加入的字符是否超过默认空间
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }
    
    // 空间扩容
    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }
}

中文字幕是我自己手动加上去的,源码中都是英文注释

部分问题
  • 问题一:System.out.println(strbufferB.length()); // 最终返回的长度会是 16 吗?

    虽然 StringBuffer 会先开辟一个长度为 16 的数组空间,但是 length() 只会计算里面存在的值长度,并不会计算空间的大小

  • 问题二:扩容问题,如果要添加的数据底层数组装不下了,那么需要扩容底层的数组吗?

    是需要扩容的,在默认情况下,空间不够的时候 StringBuffer 会在底层将空间扩容为原本容量的 2倍 + 2,同时将原有数组中的元素复制到新的数组中

建议:在实际开发中,建议大家使用 StringBuffer(int capacity) 或 StringBuilder(int capacity) 进行扩容;尽量不去使用底层的扩容。

问题一案例
package com.laoyang.test.day03;

import org.junit.Test;

/**
 * @ClassName StringTools
 * @Description:StringBuffer 的使用
 * @Author Laoyang
 * @Date 2021/9/9 17:28
 */
public class StringTools {
    /**
     问题一:System.out.println(strbufferB.length());   // 3,只会计算里面存在的值,不会计算空间的大小
     */
    @Test
    public void testOne() {
        StringBuffer strbufferA = new StringBuffer();
        StringBuffer strbufferB = new StringBuffer("abc");
        System.out.println(strbufferA.length());    // 0
        System.out.println(strbufferB.length());    // 3
    }
}

问题二可以看上方的部分源码,我把需要用到的几个方法和变量都拿过来了,大家也可以自己去看源码。需要注意的就是不同版本的JDK,源码也会有所差异

StringBuffer 中的常用方法

方法作用
StringBuffer append(xxx)用于进行字符串拼接
StringBuffer delete(int start,int end)删除指定位置的内容
StringBuffer replace(int start, int end, String str)把 (start,end) 位置替换为 str
StringBuffer insert(int offset, xxx)在指定位置插入xxx
StringBuffer reverse()把当前字符序列逆转
public int indexOf(String str)查找当前字符串中的指定字符,返回索引值(未找到则返回 -1)
public String substring(int start,int end)返回一个从 start 开始到 end 索引结束的子字符串
public int length()当前字符长度
public char charAt(int n )返回指定索引位置的字符
public void setCharAt(int n ,char ch)替换指定位置的字符
案例
package com.laoyang.test.day03;

import org.junit.Test;

/**
 * @ClassName StringBufferTest
 * @Description: StringBuffer中的常用方法
 * @Author Laoyang
 * @Date 2021/9/13 10:04
 */
public class StringBufferTest {
    @Test
    public void testOne() {
        StringBuffer stringBuffer = new StringBuffer();
        // 添加数据
        stringBuffer.append(123);
        stringBuffer.append('q');
        stringBuffer.append("aa");
        System.out.println(stringBuffer);   // 123qaa

        // 删除指定开始位置至结束位置的数据
        StringBuffer delete = stringBuffer.delete(3, 4);
        System.out.println(delete);     // 123aa

        // 替换指定开始位置至结束位置的数据
        stringBuffer.replace(3, 5, "hello");
        System.out.println(stringBuffer);   // 123hello

        // 在指定位置插入对应的数据
        stringBuffer.insert(3, "ABC");
        System.out.println(stringBuffer);   // 123ABChello

        // 将当前数据进行反转
        stringBuffer.reverse();
        System.out.println(stringBuffer);   // ollehCBA321

        System.out.println("-----------------------------");

        StringBuffer stringBuffers = new StringBuffer("HelloWorld");
        //查找当前字符串中的指定字符,返回索引值(未找到则返回 -1)
        int hello = stringBuffers.indexOf("lo");
        System.out.println(hello);  // 3

        // 截取指定位置上的字符串进行返回
        String substringA = stringBuffers.substring(5);
        System.out.println(substringA);  // World
        String substringB = stringBuffers.substring(5, 10);
        System.out.println(substringB);  // World

        // 当前字符长度
        System.out.println(stringBuffers.length());     // 10

        // 返回指定索引位置的字符
        char c = stringBuffers.charAt(2);
        System.out.println(c);

        // 替换指定位置的字符
        stringBuffers.setCharAt(0, 'h');
        System.out.println(stringBuffers);   // helloWorld

        System.out.println("-----------------------------");

        /*
         了解:方法链
         因为在部分方法中,最后返回的还是当前的数据,所以我们可以在使用的时候连续调用,而不同一行调用一次
         */
        StringBuffer stringBufferC = new StringBuffer();
        stringBufferC.append("A").append("B").append("C");
        System.out.println(stringBufferC);  // ABC
    }
}

小总结

增:append(String str)

删:delete(int start, int end)

改:setCharAt(int n, char ch)、replace(int start, int end, String str)

查:charAt(int n)、indexOf(String str)

插:insert(int offset, String str)

长度:length()

遍历:toString()、for循环

String 与 StringBuffer、StringBuild 之间的转换

  • String -> StringBuffer、StringBuild:调用对应的构造器即可

  • StringBuffer、StringBuild -> String:

    方式一:调用 String 构造器

    方式二:调用 StringBuffer、StringBuilder 的 toString() 方法

package com.laoyang.test.day03;

import org.junit.Test;

/**
 * @ClassName StringTransition
 * @Description: Strting 与 StringBuffer、StringBuild 之间的转换
 * @Author Laoyang
 * @Date 2021/9/13 15:13
 */
public class StringTransition {
    /**
     * String -> StringBuffer、StringBuild:调用对应的构造器即可
     * StringBuffer、StringBuild -> String:
     * 方式一:调用 String 构造器
     * 方式二:调用 StringBuffer、StringBuilder 的 toString() 方法
     */
    @Test
    public void testOne() {
        String str = "abc";
        StringBuffer stringBufferA = new StringBuffer(str);
        System.out.println(stringBufferA);   // abc

        StringBuilder stringBuilderA = new StringBuilder(str);
        System.out.println(stringBuilderA);  // abc

        System.out.println("-------------------------");

        StringBuffer stringBufferB = new StringBuffer("ABC");
        StringBuilder stringBuilderB = new StringBuilder("QQ");

        // 方式一
        String strX = new String(stringBufferB);
        String strY = new String(stringBuilderB);
        System.out.println(strX);   // ABC
        System.out.println(strY);   // QQ

        // 方式二
        String strA = stringBufferB.toString();
        String strB = stringBuilderB.toString();
        System.out.println(strA);   // ABC
        System.out.println(strB);   // QQ
    }
}

String、StringBuffer、StringBuilder 的相同点和不同点

String:不可变的字符序列;底层使用 char[] 进行存储

StringBuffer:可变的字符序列;线程安全的(效率较低);底层使用 char[] 进行存储

StringBuilder:可变的字符序列;JDK5.0新增的,线程不安全的(效率较高);底层使用 char[] 进行存储

关于使用:如果当前代码没有多线程问题,则可以使用 StringBuilder,如果有,则使用 StringBuffer

对比 String、StringBuffer、StringBuilder 的效率

效率排列:StringBuilder > StringBuffer > String

package com.laoyang.test.day03;

import org.junit.Test;

/**
 * @ClassName StringBuilderTest
 * @Description: 对比 String、StringBuffer、StringBuilder 的效率
 * @Author Laoyang
 * @Date 2021/9/13 10:40
 */
public class StringBuilderTest {
    @Test
    public void testOne() {
        long startTime = 0L;
        long endTime = 0L;
        String text = "";
        StringBuffer buffer = new StringBuffer("");
        StringBuilder builder = new StringBuilder("");
        
        //开始对比
        // StringBuffer
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            buffer.append(String.valueOf(i));
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuffer的执行时间:" + (endTime - startTime));

        // StringBuilder
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            builder.append(String.valueOf(i));
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuilder的执行时间:" + (endTime - startTime));

        // String
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            text = text + i;
        }
        endTime = System.currentTimeMillis();
        System.out.println("String的执行时间:" + (endTime - startTime));
    }
}

JDK8 之前的日期和时间的 API 测试

这里先给大家一张时间格式的图给大家参考,刚开始学习的小伙伴可能不知道为什么,不过没关系,因为待会学着学着就会知道了!

在这里插入图片描述

System 类

System 类提供的 public static long currentTimeMillis() 用来返回当前时间与 1970年1月1日0时0分0秒 之间以毫秒为单位的时间差。

此方法适于计算时间差。

计算世界时间的主要标准:

  • UTC(Coordinated Universal Time)
  • GMT(Greenwich Mean Time)
  • CST(Central Standard Time

案例

package com.laoyang.test.day04;

import org.junit.Test;
import java.util.Date;

/**
 * @ClassName DataTimeTest
 * @Description: JDK8 之前日期和时间的 API 测试
 * @Author Laoyang
 * @Date 2021/9/13 11:16
 */
public class DataTimeTest {
    /**
     * System 类中的 currentTimeMillis()
     */
    @Test
    public void testOne() {
        // 返回当前时间与 1970年1月1日0时0分0秒 之间以毫秒为单位的时间差。
        long current = System.currentTimeMillis();
        System.out.println(current);    //返回具体的毫秒数(这种毫秒数被称为:时间戳)
    }
}

Date 类

Date 有两个类,分别是:

  • java.util.Date 类 和 java.sql.Date 类

    我们这里主要使用的是第一种

  • 两个构造器的使用

    Date():创建一个对应当前时间的 Date 对象

    Date(long date):创建指定毫秒数的 Date 对象(根据毫秒数返回对应的时间)

  • 两个方法的使用

    toString():显示当前的年、月、日、时、分、秒

    getTime():获取当前 Date 对象对应的毫秒数(时间戳)

  • java.sql.Date 对应着数据库中的日期类型的变量

问题一:java.sql.Date 如何实例化

问题二:如何把 java.util.Date 对象转换为 java.sql.Date 对象

案例

package com.laoyang.test.day04;

import org.junit.Test;
import java.util.Date;

/**
 * @ClassName DataTimeTest
 * @Description: JDK8 之前日期和时间的 API 测试
 * @Author Laoyang
 * @Date 2021/9/13 11:16
 */
public class DataTimeTest {
    /**
     java.util.Date 类
     java.sql.Date 类
     */
    @Test
    public void testTwo() {
        // 构造器一:Date():创建一个对应当前时间的 Date 对象
        Date dateA = new Date();
        // 方法一
        System.out.println(dateA.toString());  // Mon Sep 13 11:37:48 CST 2021
		// 方法二
        System.out.println(dateA.getTime());    // 毫秒数:1631504432056

        // 构造器二:创建指定毫秒数的 Date 对象
        Date dateB = new Date(1631504432056L);
        System.out.println(dateB);  // Mon Sep 13 11:40:32 CST 2021

        System.out.println("----------------------");

        /*
        问题一、java.sql.Date 如何实例化
         */
        // 创建 java.sql.Date 对象
        java.sql.Date dateC = new java.sql.Date(1631504432999L);
        System.out.println(dateC);   // 2021-09-13

        /*
        问题二、如何把 java.util.Date 对象转换为 java.sql.Date 对象
         */
        // 情况一
//        Date dateD = new java.sql.Date(1631504432999L);
//        java.sql.Date dateE = (java.sql.Date) dateD;
//        System.out.println(dateE);    // 2021-09-13

        // 情况二:如果直接创建一个 util 下的 Date 对象,那么就不能使用情况一中的方式进行转换
        Date dateF = new Date(1234567891234L);
        java.sql.Date dateG = new java.sql.Date(dateF.getTime());
        System.out.println(dateG);  // 2009-02-14
    }
}

SimpleDateFormat 类

Date 类的 API 不易于国际化,所以大部分被废弃了,java.text.SimpleDateFormat 类是一个不与语言环境有关的方式来格式化和解析日期的具体类。

SimpleDateFormat:对日期 Date 类格式化和解析

格式化:日期 -> 文本(调用 format(Date date) 方法)

解析:文本 -> 日期(调用 parse(String source) 方法)

SimpleDateFormat 实例化

  1. new SimpleDateFormat()
  2. new SimpleDateFormat(“yyyy.MM.dd HH:mm:ss”)

案例

package com.laoyang.test.day04;

import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @ClassName SimpleDateFormatTest
 * @Description SimpleDateFormat 的使用
 * @Author Laoyang
 * @Date 2021/9/14 9:37
 */
public class SimpleDateFormatTest {
    /**
     * SimpleDateFormat:对日期 Date 类格式化和解析
     * 两个操作:
     * 1. 格式化:日期  ->  字符串
     * 2. 解析:格式的逆过程,字符串  ->  日期
     */
    @Test
    public void testOne() throws ParseException {
        // 实例化 SimpleDateFormat,使用默认的构造器
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat();

        // 格式化:日期  ->  字符串
        Date date = new Date();
        String format = simpleDateFormat.format(date);
        System.out.println(date);       // Tue Sep 14 09:50:52 CST 2021
        System.out.println(format);     // 21-9-14 上午9:50

        // 解析:字符串  ->  日期
        //String strX = "2025-12-12";   // 使用无参构造器的时候无法解析默认格式以外的所有格式
        String str = "2025-12-12 下午2:50";   // 所以此处的格式必须和 format 一致,否则就会导致无法解析
        Date parse = simpleDateFormat.parse(str);
        System.out.println(parse);

        System.out.println("---------------------------------");

        /**
         * 按照指定的方式格式化和解析:调用带参的构造器
         * 年:y
         * 月:M
         * 日:d
         * 时:h
         * 分:m
         * 秒:s
         * 详情请看上面的那个图
         */
        SimpleDateFormat simpleDateFormatB = new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z");
        // 在实际开发中,我们一般都是使用自定义的格式
        SimpleDateFormat simpleDateFormatC = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
        Date dateB = new Date();

        // 格式化
        String formatB = simpleDateFormatB.format(dateB);
        System.out.println(formatB);    // 2021.09.14 公元 at 09:59:33 CST

        String formatC = simpleDateFormatC.format(dateB);
        System.out.println(formatC);   // 2021.09.14 10:01:51

        /*
         解析:要求字符串必须是符合 SimpleDateFormat 识别的格式(通过构造器参数进行设置)
         如果不符合,就会抛出异常!!
         */
        Date parseB = simpleDateFormatC.parse("2021.09.14 10:01:51");
        System.out.println(parseB);     // Tue Sep 14 10:01:51 CST 2021
    }

    /**
     * 练习一: 字符串 “2021-01-02” 转换为 java.sql.Date
     */
    @Test
    public void testTwo() throws ParseException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String str = "2021-01-02";
        Date parse = simpleDateFormat.parse(str);
        System.out.println(parse.getClass());   // class java.util.Date

        java.sql.Date date = new java.sql.Date(parse.getTime());
        System.out.println(date);    // 2021-01-02
        System.out.println(date.getClass());  // class java.sql.Date
    }

    /**
     * 练习二:三天打鱼两天晒网
     */
    @Test
    public void testThree() throws ParseException {
        String start = "1990-01-01";
        String end = "2021-07-15";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date parseA = simpleDateFormat.parse(start);
        Date parseB = simpleDateFormat.parse(end);

        /*
         总天数
         1000毫秒 = 1秒;60秒 = 1分钟;60分钟 = 1小时;一天有24小时  == 一天的毫秒数
         简单理解为:总天数的毫秒数 / 一天的毫秒数 = 总天数
         */
        long sum = (parseB.getTime() - parseA.getTime()) / (1000 * 60 * 60 * 24) + 1;

        if (sum % 5 == 1 || sum % 5 == 2 || sum % 5 == 3) {
            System.out.println(end + "在打鱼");
        }
        if (sum % 5 == 0 || sum % 5 == 4) {
            System.out.println(end + "在晒网");
        }
    }
}

Calendar 日历类

Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。

CalendarTest 实例化的两种方式

方式一:创建子类(GregorianCalendar)对象

方式二:调用 getInstance() 方法

常用方法

方法作用
public void set(int field,int value)设置指定的时间/日期
public void add(int field,int amount)在指定的时间/日期上添加时间/日期
public final Date getTime()根据当前的时间戳获取一个Date类型的对象
public final void setTime(Date date)根据指定的时间戳返回对应的时间/日期
public int get(int field)获取对应的时间信息

注意:

  • 获取月份时:一月是0,二月是1,以此类推,12月是11
  • 获取星期时:周日是1,周二是2 , 。。。。周六是7

案例

package com.laoyang.test.day04;

import org.junit.Test;
import java.util.Calendar;
import java.util.Date;

/**
 * @ClassName CalendarTest
 * @Description: Calendar 日历类(抽象类)的使用
 * @Author Laoyang
 * @Date 2021/9/14 10:48
 */
public class CalendarTest {
    /**
     Calendar 日历类(抽象类)的使用
     实例化
     方式一:创建子类(GregorianCalendar)对象
     方式二:调用 getInstance() 方法
     常用方法
     get()、set()、add()、getTime()、setTime()等...
     */
    @Test
    public void testOne() {
        // 实例化 Calendar
        Calendar calendar = Calendar.getInstance();

        // 常用方法
        // get() 方法的使用
        int days = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println(days);   // 返回当前月的第几天
        System.out.println(calendar.get(Calendar.DAY_OF_YEAR)); // 返回当前年的第几天

        /*
         set() 方法的使用
         calendar 可变性
         */
        calendar.set(Calendar.DAY_OF_MONTH, 18);
        days = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println(days);   // 修改后的天数,18

        // add() 方法的使用
        calendar.add(Calendar.DAY_OF_MONTH, -4);
        days = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println(days);   // 当前天数 - 4,比如 18 - 4 = 14

        // getTime() 方法的使用(日历类  ->  Date)
        Date time = calendar.getTime();
        System.out.println(time);   // Tue Sep 14 11:03:56 CST 2021

        // setTime() 方法的使用(Date  ->  日历类)
        calendar.setTime(new Date());
        days = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println(days);   // 返回修改后时间的月的第几天
    }
}

JDK8 之后的日期和时间 API 测试

JDK8 之前的日期时间类型的缺陷

  • 可变性:像日期和时间这样的类应该是不可变的。
  • 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
  • 格式化:格式化只对Date有用,Calendar则不行。
  • 此外,它们也不是线程安全的;不能处理闰秒等。

案例

package com.laoyang.test.day06;

import org.junit.Test;
import java.util.Date;

/**
 * @ClassName TheNewTimeTest
 * @Description: JDK8 的日期时间的使用
 * @Author Laoyang
 * @Date 2021/9/14 11:14
 */
public class TheNewTimeTest {
    @Test
    public void testOne() {
        /*
        因为偏移量的缘故,会导致我们最后得到的日期跟我们设置的日期是不同的
        如果想要得到相同的日期,那么就需要减去偏移量
        详情可看下方源码
         */
        Date dateA = new Date(2021, 2, 16);
        System.out.println(dateA);   // Wed Mar 16 00:00:00 CST 3921
        Date dateB = new Date(2021 - 1900, 2 - 1, 16);
        System.out.println(dateB);   // Tue Feb 16 00:00:00 CST 2021
    }
}

部分源码

public class Date implements java.io.Serializable, Cloneable, Comparable<Date> {
	/**
     * Allocates a <code>Date</code> object and initializes it so that
     * it represents midnight, local time, at the beginning of the day
     * specified by the <code>year</code>, <code>month</code>, and
     * <code>date</code> arguments.
     *
     * @param   year    the year minus 1900.
     * @param   month   the month between 0-11.
     * @param   date    the day of the month between 1-31.
     * @see     java.util.Calendar
     * @deprecated As of JDK version 1.1,
     * replaced by <code>Calendar.set(year + 1900, month, date)</code>
     * or <code>GregorianCalendar(year + 1900, month, date)</code>.
     */
    @Deprecated
    public Date(int year, int month, int date) {
        this(year, month, date, 0, 0, 0);
    }
}

大家可以看源码中的注释说明,然后理解上面代码想要表示的东西

LocalDate、LocalTime、LocalDateTime 的使用

本地日期(LocalDate):代表IOS格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期。

本地时间 (LocalTime):表示一个时间,而不是日期。

本地日期时间(LocalDateTime):用来表示日期和时间的,这是一个最常用的类之一。

Ps:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示 法,也就是公历。

说明

  1. LocalDateTime 在实际开发中,使用的频率会比 LocalDate、LocalTime 要高一些
  2. 类似于 Calendar

三者实例化的方式

  • 方式一:now():获取当前的日期、时间、日期 + 时间
  • 方式二:of():设置指定的年、月、日、时、分、秒是没有偏移量的

常用方法

方法作用
now() / * now(ZoneId zone)静态方法,根据当前时间创建对象/指定时区的对象
of()静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear()获得月份天数(1-31) /获得年份天数(1-366)
getDayOfWeek()获得星期几(返回一个 DayOfWeek 枚举值)
getMonth()获得月份, 返回一个 Month 枚举值
getMonthValue() / getYear()获得月份(1-12) /获得年份
getHour()/getMinute()/getSecond()获得当前对象对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()/ withMonth()/withYear()将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours()向当前对象添加几天、几周、几个月、几年、几小时
minusMonths() / minusWeeks()/ minusDays()/minusYears()/minusHours()从当前对象减去几月、几周、几天、几年、几小时

案例

package com.laoyang.test.day06;

import org.junit.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;

/**
 * @ClassName TheNewTimeTest
 * @Description: JDK8 的日期时间的使用
 * @Author Laoyang
 * @Date 2021/9/14 11:14
 */
public class TheNewTimeTest {
    /**
     * LocalDate、LocalTime、LocalDateTime 的使用
     * 实例化
     * 方式一:now():获取当前的日期、时间、日期 + 时间
     * 方式二:of():设置指定的年、月、日、时、分、秒是没有偏移量的
     *
     * 说明:
     * 1. LocalDateTime 在实际开发中,使用的频率会比 LocalDate、LocalTime 要高一些
     * 2. 类似于 Calendar
     */
    @Test
    public void testTwo() {
        // 实例化方式一:now():获取当前的日期、时间、日期 + 时间
        LocalDate localDate = LocalDate.now();
        LocalTime localTime = LocalTime.now();
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println(localDate);      // 2021-09-14
        System.out.println(localTime);      // 11:43:09.294
        System.out.println(localDateTime);  // 2021-09-14T11:43:09.294

        System.out.println("-------------------------------");

        /*
        实例化方式二:of():设置指定的年、月、日、时、分、秒是没有偏移量的
         */
//        LocalDate localDateA = LocalDate.of(2021, 1, 30);
//        LocalTime localTimeA = LocalTime.of(12, 1, 30);
        LocalDateTime localDateTimeA = LocalDateTime.of(2021, 2, 12, 12, 24, 48);
        System.out.println(localDateTimeA);  // 2021-01-12T12:24:48

        System.out.println("-------------------------------");

        /**
         * 这里的三个类型都可以使用以下方法,所以就不一一展示了,大家可根据需求进行使用
         * Ps:这里只演示部分方法,因为都是同样的方式进行修改、增加,减少等操作,所以大家可以自行举一反三
         */
        // getXxx():获取相关的属性
        System.out.println(localDateTimeA.getDayOfMonth()); // 12,当前月中的第几天
        System.out.println(localDateTimeA.getDayOfWeek());  // FRIDAY,周几
        System.out.println(localDateTimeA.getDayOfYear());  // 43,今天是今年的第几天
        System.out.println(localDateTimeA.getMonth());      // FEBRUARY,当前月份
        System.out.println(localDateTimeA.getMonthValue()); // 2,当前月份
        System.out.println(localDateTimeA.getMinute());     // 24,分钟

        System.out.println("-------------------------------");

        // 体现不可变性
        // withXxx():设置相关的属性
        LocalDate withDayOfMonth = localDate.withDayOfMonth(22);    // 设置当月的第几天
        System.out.println(localDate);          // 2021-09-17(今天的日期)
        System.out.println(withDayOfMonth);     // 2021-09-22(修改后的日期)

        LocalDateTime withHour = localDateTimeA.withHour(4);    // 设置当前的小时
        System.out.println(localDateTimeA);     // 2021-02-12T12:24:48
        System.out.println(withHour);           // 2021-02-12T04:24:48

        System.out.println("-------------------------------");

        // 不可变性
        // plusXxx():当前时间添加几天、几周、几个月、几年、几小时
        LocalDateTime plusMonths = localDateTimeA.plusMonths(3);    // 当前时间 + 3个月
        System.out.println(localDateTimeA);     // 2021-02-12T12:24:48
        System.out.println(plusMonths);         // 2021-05-12T12:24:48

        System.out.println("-------------------------------");

        // minusXxx():当前时间减去几月、几周、几天、几年、几小时
        LocalDateTime minusDays = localDateTimeA.minusDays(2);      // 当前日期 - 2天
        System.out.println(localDateTimeA);   // 2021-02-12T12:24:48
        System.out.println(minusDays);        // 2021-02-10T12:24:48
    }
}

这里只演示部分方法,因为都是同样的方式进行修改、增加,减少等操作,所以大家可以自行举一反三

Instant (瞬时)的使用

Instant:时间线上的一个瞬时点; 这可能被用来记录应用程序中的事件时间戳。

java.time 包通过值类型 Instant 提供机器视图,不提供处理人类意义上的时间单位。Instant 表示时间线上的一点,而不需要任何上下文信息,例如:时区。 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为 java.time 包是基于纳秒计算的,所以 Instant 的精度可以达到纳秒级!

常用方法

方法作用
now()静态方法,返回默认 UTC 时区的 Instant 类的对象
ofEpochMilli(long epochMilli)静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒 数之后的 Instant 类的对象
atOffset(ZoneOffset offset)结合即时的偏移来创建一个 OffsetDateTime
toEpochMilli()返回 1970-01-01 00:00:00 到当前时间的毫秒数,即为时间戳

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

本初子午线参考图

在这里插入图片描述

通过上图,大家可以看到,我们的北京时间属于 “东八区” 的时间线,接下来我们使用的方法中就会设计到时间线,所以先给大家了解一下

案例

package com.laoyang.test.day06;

import org.junit.Test;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

/**
 * @ClassName InstantTest
 * @Description: Instant 的使用
 * @Author Laoyang
 * @Date 2021/9/17 10:06
 */
public class InstantTest {
    /**
     * Instant 类似于 java.util.Date 类
     */
    @Test
    public void testOne() {
        // now():获取本初子午线对应的标准时间(也就是中时区的时间)
        Instant instant = Instant.now();
        System.out.println(instant);    // 2021-09-17T02:07:32.945Z(默认为中时区时间,可看时区表中国属于东八区,所以时间会有所差异)

        // atOffset():添加时间的偏移量
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
        System.out.println(offsetDateTime);  // 2021-09-17T10:12:13.614+08:00

        // toEpochMilli():获取从 1970年1月1日0时0分0秒 至今的毫秒数(类似 Date 中的 getTime() 方法)
        long epochMilli = instant.toEpochMilli();
        System.out.println(epochMilli);     // 1631844964944

        // ofEpochMilli():通过指定的毫秒数获取 Instant 实例(类似 Date 中的 Date(long millis))
        Instant milli = Instant.ofEpochMilli(1631844964944L);
        System.out.println(milli);   // 2021-09-17T02:16:04.944Z
    }
}

DateTimeFormatter 类的使用

DateTimeFormatter:格式化或解析日期、时间(类似于 SimpleDateFormat)

该类提供了三种格式化方法:

  • 预定义的标准格式。如: ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME

  • 本地化相关的格式。如:ofLocalizedDateTime() / ofLocalizedDate()

    FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:适用于LocalDateTime

    FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:使用于 LocalDate

  • 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

使用请看案例

常用方法

方法作用
ofPattern(String pattern)静态方法 , 返回一个指定字符串格式的 DateTimeFormatte
format(TemporalAccessor t)格式化一个日期、时间,返回字符串
parse(CharSequence text)将指定格式的字符序列解析为一个日期、时间

案例

package com.laoyang.test.day06;

import org.junit.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.TemporalAccessor;

/**
 * @ClassName DateTimeFormatterTest
 * @Description: DateTimeFormatter 的使用
 * @Author Laoyang
 * @Date 2021/9/17 10:49
 */
public class DateTimeFormatterTest {
    /**
     DateTimeFormatter:格式化或解析日期、时间(类似于 SimpleDateFormat)
     */
    @Test
    public void testOne() {
        // 方式一:预定义的标准格式。如: ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
        DateTimeFormatter dateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        // 格式化:日期转字符串
        LocalDateTime localDateTime = LocalDateTime.now();
        String format = dateTime.format(localDateTime);
        System.out.println(dateTime);   // ParseCaseSensitive(false)(Value(Year,4,10,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2))'T'(Value(HourOfDay,2)':'Value(MinuteOfHour,2)[':'Value(SecondOfMinute,2)[Fraction(NanoOfSecond,0,9,DecimalPoint)]])
        System.out.println(format);     // 2021-09-17T10:55:41.059

        // 解析:字符串转日期
        TemporalAccessor parse = dateTime.parse(format);
        System.out.println(parse);      // {},ISO resolved to 2021-09-17T10:57:31.648

        System.out.println("-------------------------------");

        /*
         方式二:本地化相关的格式。如:ofLocalizedDateTime()
         FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:适用于LocalDateTime
         */
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
        // 格式化
        LocalDateTime localDateTimeA = LocalDateTime.now();
        String toFormat = dateTimeFormatter.format(localDateTimeA);
        /*
        2021年9月17日 上午11时06分32秒(LONG 格式)
        21-9-17 上午11:07(SHORT 格式)
        2021-9-17 11:07:49(MEDIUM 格式)
         */
        System.out.println(toFormat);   // 2021年9月17日 上午11时09分50秒

        // 解析
        TemporalAccessor parse1 = dateTimeFormatter.parse(toFormat);
        System.out.println(parse1);  // {},ISO resolved to 2021-09-17T11:09:50

        /*
        本地化相关的格式。如:ofLocalizedDate()
        FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:使用于 LocalDate
         */
        DateTimeFormatter dateTimeFormatterA = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
        // 格式化
        LocalDate localDate = LocalDate.now();
        String format1 = dateTimeFormatterA.format(localDate);
        /*
        2021年9月17日 星期五(FULL 格式)
        2021年9月17日(LONG 格式)
        2021-9-17(MEDIUM 格式)
        21-9-17(SHORT 格式)
         */
        System.out.println(format1);    // 2021年9月17日 星期五

        // 解析
        TemporalAccessor parse2 = dateTimeFormatterA.parse(format1);
        System.out.println(parse2);     // {},ISO resolved to 2021-09-17

        System.out.println("-------------------------------");

        /*
         重点:方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
         一般情况下,使用自定义日期的频率是最高的
         注意:使用自定义格式的时候,不管是在解析还是在格式化,使用的数据都必须是符合格式的,否则就会出错
         */
        DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
        // 格式化
        String format2 = dateTimeFormatter1.format(LocalDateTime.now());
        System.out.println(format2);    // 2021-09-17 11:18:35

        // 解析
        TemporalAccessor parse3 = dateTimeFormatter1.parse(format2);
        System.out.println(parse3);   // {MicroOfSecond=0, MilliOfSecond=0, HourOfAmPm=11, SecondOfMinute=35, MinuteOfHour=18, NanoOfSecond=0},ISO resolved to 2021-09-17
    }
}

在实际开发中,大部分时候都会使用自定义格式的方式使用时间类型

其它时间 API

  • ZoneId:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris。
  • ZonedDateTime:一个在 ISO-8601 日历系统时区的日期时间,如 2007-12- 03T10:15:30+01:00 Europe/Paris。

    其中每个时区都对应着 ID,地区 ID 都为“{区域}/{城市}”的格式,例如: Asia/Shanghai 等。

  • Clock:使用时区提供对当前即时、日期和时间的访问的时钟。
  • 持续时间:Duration,用于计算两个“时间”间隔。
  • 日期间隔:Period,用于计算两个“日期”间隔。
  • TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下一个工作日”等操作。
  • TemporalAdjusters : 该类通过静态方法 (firstDayOfXxx()/lastDayOfXxx()/nextXxx()) 提供了大量的常用 TemporalAdjuster 的实现。

使用起来的方式其实都差不太多,就是理解一下每个 API 的实例化方式和常用的一些方法就好了,这里有一些示例,大家可以参考一下

案例

package com.laoyang.test.day06;

import org.junit.Test;
import java.time.*;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import java.util.Set;

/**
 * @ClassName OtherTimeTest
 * @Description: 其它时间API(时间格式化和解析的方式都差不多,所以这里就不一一演示了)
 * @Author Laoyang
 * @Date 2021/9/17 11:29
 */
public class OtherTimeTest {
    /**
     * ZoneId 的使用
     * 介绍:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris
     */
    @Test
    public void testOne() {
        //ZoneId:类中包含了所有的时区信息
        // ZoneId 的 getAvailableZoneIds():获取所有的 ZoneId
        Set<String> zoneIds = ZoneId.getAvailableZoneIds();
        for (String s : zoneIds) {
            System.out.println(s);
        }
        // ZoneId的of():获取指定时区的时间(不同时区的时间都不一致)Asia/Tokyo
        LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
        System.out.println(localDateTime);  // 2021-09-17T12:35:49.338
    }

    /**
     * ZonedDateTime 的使用
     * 介绍:一个在ISO-8601日历系统时区的日期时间,如 2007-12-03T10:15:30+01:00 Europe/Paris
     * > 其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如:Asia/Shanghai等
     */
    @Test
    public void testTwo() {
        //ZonedDateTime:带时区的日期时间
        // ZonedDateTime的now():获取本时区的ZonedDateTime对象
        ZonedDateTime zonedDateTime = ZonedDateTime.now();
        System.out.println(zonedDateTime);

        // ZonedDateTime的now(ZoneId id):获取指定时区的ZonedDateTime对象
        ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
        System.out.println(zonedDateTime1);
    }

    /**
     * Duration 的使用
     * 介绍:持续时间:Duration,用于计算两个“时间”间隔
     */
    @Test
    public void testThree() {
        //Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
        LocalTime localTime = LocalTime.now();
        LocalTime localTime1 = LocalTime.of(15, 23, 32);

        //between():静态方法,返回Duration对象,表示两个时间的间隔
        Duration duration = Duration.between(localTime1, localTime);
        System.out.println(duration);
        System.out.println(duration.getSeconds());  // 获取相隔秒数
        System.out.println(duration.getNano());     // 获取相隔纳米数

        LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
        LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
        Duration duration1 = Duration.between(localDateTime1, localDateTime);
        System.out.println(duration1.toDays());     // 获取相隔天数
    }

    /**
     * Period 的使用
     * 介绍:日期间隔:Period,用于计算两个“日期”间隔
     */
    @Test
    public void testFour() {
        //Period:用于计算两个“日期”间隔,以年、月、日衡量
        LocalDate localDate = LocalDate.now();
        LocalDate localDate1 = LocalDate.of(2028, 3, 18);

        Period period = Period.between(localDate, localDate1);
        System.out.println(period);     // P6Y6M1D(P6年6月1日)
        System.out.println(period.getYears());      // 获取相隔年份
        System.out.println(period.getMonths());     // 获取相隔月份
        System.out.println(period.getDays());       // 获取相隔日

        Period period1 = period.withYears(2008);    // 修改年份
        System.out.println(period1);    // P2008Y6M1D(P2008年6个月1天)
    }

    /**
     * TemporalAdjuster 的使用
     * 介绍:时间校正器。有时我们可能需要获取例如:将日期调整到“下一个工作日”等操作。
     */
    @Test
    public void testFive() {
        // TemporalAdjuster:时间校正器
        // 获取当前日期的下一个周日是哪天?
        TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
        LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
        System.out.println(localDateTime);   // 2021-09-19T11:48:33.487

        // 获取下一个工作日是哪天?
        LocalDate localDate = LocalDate.now().with(new TemporalAdjuster() {
            @Override
            public Temporal adjustInto(Temporal temporal) {
                LocalDate date = (LocalDate) temporal;
                // 获取当前是周几,然后if根据获取的星期几来进行判断
                if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) { // 周五
                    return date.plusDays(3);
                } else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {    // 周六
                    return date.plusDays(2);
                } else {    // 一、二、三、四、日
                    return date.plusDays(1);
                }
            }
        });
        System.out.println("下一个工作日是:" + localDate);
    }
}

Duration 和 Period 都是计算间隔的,一个是计算时间间隔,一个是计算日期间隔,是不一样的,大家记得搞清楚

Java 比较器

说明

Java 中的对象,正常情况下,只能进行 == 或 != 比较;不能使用 > 或 <;但是在开发场景中,我们偶尔也需要对多个对象进行排序,就需要比较对象的大小。

如何实现?

使用 Comparable 或 Comparator

Java实现对象排序的方式有两种:

  • 自然排序:java.lang.Comparable (默认都是从小到大排列的)
  • 定制排序:java.util.Comparator

Comparable (自然排序)接口的使用

  1. 像 String、包装类等实现了 Comparable 接口,重写了 compareTo(obj) 方法,给出了比较两个对象大小的方式

  2. 像 String、包装类重写 compareTo(obj) 方法以后,进行了从小到大的排列

  3. 重写 compareTo(obj) 的规则:

    如果当前对象 this 大于形参对象 obj,则返回正整数;

    如果当前对象 this 小于形参对象 obj,则返回负整数;

    如果当前对象 this 等于形参对象 obj,则返回零;

  4. 对于自定义类来说,如果需要排序,则可以让自定义类实现 Comparator 接口,并重写 compareTo(obj) 方法,在 compareTo() 方法中指明如何排序

案例

Arrays.sort 方法:
public static void sort(Object[] a) { ... }

Goods 类

package com.laoyang.test.day07;

/**
 * @ClassName Goods
 * @Description: 商品类
 * @Author Laoyang
 * @Date 2021/9/20 11:09
 */
public class Goods implements Comparable {
    private String name;
    private double price;

    public Goods() { }

    public Goods(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }

    /**
     * 指明商品比较大小的方式:按照价格从低到高排序
     */
    @Override
    public int compareTo(Object o) {
        System.out.println("执行compareTo()方法");	// 在运行时给大家看一下是否执行此方法
        if (o instanceof Goods) {
            Goods goods = (Goods) o;
            // 方式一
            if (this.price > goods.price) {
                return 1;
            } else if (this.price < goods.price) {
                return -1;
            } else {
                //return 0;
                /*
                 如果碰到价格一样的商品,那么我们可以先用价格排好序,然后在根据名称进行排序
                 Ps:如果没有这样的需求,则可以不使用
                 从高到低:-this.name.compareTo(goods.name)
                 从低到高:this.name.compareTo(goods.name)
                 */
                return this.name.compareTo(goods.name);
            }
            // 方式二
            //return Double.compare(this.price, goods.price);
        }
        throw new RuntimeException("传入的数据类型不一致!");
    }
}

测试类

package com.laoyang.test.day07;

import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;

/**
 * @ClassName ComparatorsTest
 * @Description: Java 比较器的使用
 * @Author Laoyang
 * @Date 2021/9/20 10:44
 */
public class ComparatorsTest {
    /**
     * String 类型的排序
     */
    @Test
    public void testOne() {
        String[] arr = new String[]{"TT", "BB", "JJ", "HH", "VV"};
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }

    /**
     * Comparable 接口的使用
     */
    @Test
    public void testComparable() {
        Goods[] goods = new Goods[5];
        goods[0] = new Goods("LiangXiang", 180);
        goods[1] = new Goods("PingGuo", 500);
        goods[2] = new Goods("HuaWei", 399);
        goods[3] = new Goods("SanXing",98);
        goods[4] = new Goods("HuaShuo",399);

		// 重写完 compareTo() 方法以后在调用 sort() 就会自动调用 compareTo()
        Arrays.sort(goods);
        System.out.println(Arrays.toString(goods));
    }
}

Comparator (定制排序)接口的使用

背景

当元素的类型没有实现 java.lang.Comparable 接口而又不方便修改代码,或者实现了 java.lang.Comparable 接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序。

重写 compare(Object o1,Object o2) 方法,比较 o1 和 o2 的大小:

  • 如果方法返回正整数,则表示o1大于o2;
  • 如果返回0,表示相等;
  • 返回负整数,表示o1小于o2。

可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort), 从而允许在排序顺序上实现精确控制。

案例

Arrays.sort 方法:
public static <T> void sort(T[] a, Comparator<? super T> c) { ... }

测试类

package com.laoyang.test.day07;

import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;

/**
 * @ClassName ComparatorsTest
 * @Description: Java 比较器的使用
 * @Author Laoyang
 * @Date 2021/9/20 10:44
 */
public class ComparatorsTest {
    /**
     * Comparator 接口的使用:定制排序
     */
    @Test
    public void testComparator() {
        String[] arr = new String[]{"TT", "BB", "JJ", "HH", "VV"};
        Arrays.sort(arr, new Comparator() {
            // 按照字符串从大到小的顺序排序
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Object && o2 instanceof Object) {
                    String strA = (String) o1;
                    String strB = (String) o2;
                    return -strA.compareTo(strB);
                }
                //return 0;
                throw new RuntimeException("输入的数据类型不一致!");
            }
        });
        System.out.println(Arrays.toString(arr));
    }

    /**
     * 将自然排序修改为定制排序
     */
    @Test
    public void test() {
        Goods[] goods = new Goods[6];
        goods[0] = new Goods("LiangXiang", 180);
        goods[1] = new Goods("PingGuo", 500);
        goods[2] = new Goods("HuaWei", 399);
        goods[3] = new Goods("HuaWei", 999);
        goods[4] = new Goods("SanXing",98);
        goods[5] = new Goods("HuaShuo",399);

        Arrays.sort(goods, new Comparator() {
            // 根据 Name 排序,如果 Name 相同,则价格高的排在前面
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Goods && o2 instanceof Goods) {
                    Goods goodsA = (Goods) o1;
                    Goods goodsB = (Goods) o2;
                    if (goodsA.getName().equals(goodsB.getName())){
                        return -Double.compare(goodsA.getPrice(), goodsB.getPrice());
                    } else {
                        return goodsA.getName().compareTo(goodsB.getName());
                    }
                }
                //return 0;
                throw new RuntimeException("输入的数据类型不一致!");
            }
        });
        System.out.println(Arrays.toString(goods));
    }
}

Comparable 和 Comparator 的对比

  1. Comparable 接口的方式一旦一定,保证 Comparable 接口实现类的对象在任何位置都可以比较大小
  2. Comparator 接口属于临时性的比较

其它常用类

System 类

说明

  • System类代表系统,系统级的很多属性和控制方法都放置在该类的内部; 该类位于 java.lang 包

  • 由于该类的构造器是 private 的,所以无法创建该类的对象,也就是无法实例化该类;其内部的成员变量和成员方法都是 static 的,所以也可以很方便的进行调用。

  • 成员变量

    System类内部包含 inouterr 三个成员变量,分别代表标准输入流 (键盘输入),标准输出流(显示器)和标准错误输出流(显示器)

  • 成员方法

    native long currentTimeMillis(): 该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和 GMT 时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。

    void exit(int status): 该方法的作用是退出程序;其中 status 的值为 0 代表正常退出,非零代表异常退出;使用该方法可以在图形界面编程中实现程序的退出功能等。

    void gc(): 该方法的作用是请求系统进行垃圾回收;至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。

    String getProperty(String key): 该方法的作用是获得系统中属性名为 key 的属性对应的值;系统中常见的属性名以及属性的作用如下表所示:

    属性名作用
    java.versionjava运行时环境版本
    java.homejava安装目录
    os.name操作系统的名称
    os.version操作系统的版本
    user.name用户的账户名称
    user.home用户的主目录
    user.dir用户的当前工作目录

案例

package com.laoyang.test.day08;

import org.junit.Test;

/**
 * @ClassName SystemTest
 * @Description: System类的使用
 * @Author Laoyang
 * @Date 2021/9/20 15:46
 */
public class SystemTest {
    /**
     * System 常用方法
     */
    @Test
    public void testSystem() {
        System.out.println(System.currentTimeMillis()); // 获取当前时间戳
        System.exit(0);     // 0 表示正常退出当前程序,非 0 表示异常退出
        System.gc();    // 请求系统进行垃圾回收(不一定会马上回收)
        String property = System.getProperty("user.name");  // 根据参数,获取系统对应的信息
        System.out.println(property);   // 返回用户名,更多请看 testTwo()
    }

    @Test
    public void testTwo() {
        String javaVersion = System.getProperty("java.version");
        System.out.println("java的version:" + javaVersion);

        String javaHome = System.getProperty("java.home");
        System.out.println("java的home:" + javaHome);

        String osName = System.getProperty("os.name");
        System.out.println("os的name:" + osName);

        String osVersion = System.getProperty("os.version");
        System.out.println("os的version:" + osVersion);

        String userName = System.getProperty("user.name");
        System.out.println("user的name:" + userName);

        String userHome = System.getProperty("user.home");
        System.out.println("user的home:" + userHome);

        String userDir = System.getProperty("user.dir");
        System.out.println("user的dir:" + userDir);
    }
}

Math 类

java.lang.Math 提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为 double 型。

常用方法

方法作用
double abs(double a)返回参数的绝对值
double sqrt(double a)求参数的算术平方根
double pow(double a, double b)返回第a的b次方
int max(int a, int b)返回两个参数中的最大值
double min(double a, double b)返回两个参数中的最小值
double random()返回一个随机数,默认范围:0-1
long round(double a)double 型数据 a 转换为 long 型(四舍五入)

更多方法可自行百度查看

案例

package com.laoyang.test.day08;

import org.junit.Test;

/**
 * @ClassName MathTest
 * @Description: Math 类的使用
 * @Author Laoyang
 * @Date 2021/9/20 15:50
 */
public class MathTest {
    /**
     * java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为 double 型。
     * 常用方法的使用
     */
    @Test
    public void testMath() {
        // abs(double a):返回绝对值,简单来说就是传进来什么值,返回的就是什么值
        System.out.println(Math.abs(3.1415926535));     // 3.1415926535

        // sqrt(double a):计算对应值的平方根
        System.out.println(Math.sqrt(25));  // 5.0

        // pow(double a, double b):计算 a 的 b 次幂
        System.out.println(Math.pow(3, 3));  // 27.0(3 * 3 * 3)

        // max(double a,double b):返回两个数字中的最大值
        System.out.println(Math.max(2, 6));     // 6

        // min(double a,double b):返回两个数字中的最小值
        System.out.println(Math.min(1.98, 1.38));   // 1.38

        // random():随机数,默认为 0 - 1 之间的值
        System.out.println(Math.random());
        int i = (int) (Math.random() * 10) + 1;  // 返回 1-10 之间的随机数
        System.out.println(i);

        // round(double a):double 型数据 a 转换为 long 型(四舍五入)
        System.out.println(Math.round(3.1415926535));  // 3
    }
}

BigInteger 与 BigDecimal 类

BigInteger 类

  • Integer 类作为 int 的包装类,能存储的最大整型值为 2³¹-1,Long 类也是有限的, 最大为 2的63次方-1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了。

  • java.math 包的 BigInteger 可以表示不可变的任意精度的整数。BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。

    另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、 位操作以及一些其他操作。

BigInteger 类常用方法
方法说明
public BigInteger abs()返回此 BigInteger 的绝对值的 BigInteger
BigInteger add(BigInteger val)返回其值为 (this + val) 的 BigInteger
BigInteger subtract(BigInteger val)返回其值为 (this - val) 的 BigInteger
BigInteger multiply(BigInteger val)返回其值为 (this * val) 的 BigInteger
BigInteger divide(BigInteger val)返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。
BigInteger remainder(BigInteger val)返回其值为 (this % val) 的 BigInteger
BigInteger[] divideAndRemainder(BigInteger val)返回包含 (this / val) 后跟 (this % val) 的两个 BigInteger 的数组
BigInteger pow(int exponent)返回其值为 (thisexponent) 的 BigInteger

BigDecimal 类

  • 一般的 Float 类和 Double 类可以用来做科学计算或工程计算,但在商业计算中, 要求数字精度比较高,所以就可以用到java.math.BigDecimal 类。
  • BigDecimal 类支持不可变的、任意精度的有符号十进制定点数。
BigDecimal 类常用方法
方法作用
BigDecimal add(BigDecimal augend)返回其值为 (this + augend) 的 BigDecimal
BigDecimal subtract(BigDecimal subtrahend)返回其值为 (this - subtrahend) 的 BigDecimal
BigDecimal multiply(BigDecimal multiplicand)返回其值为 (this * multiplicand) 的 BigDecimal

案例

package com.laoyang.test.day08;

import org.junit.Test;
import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * @ClassName BigIntegerTest
 * @Description: BigInteger 与 BigDecimal 的使用
 * @Author Laoyang
 * @Date 2021/9/20 15:51
 */
public class BigIntegerTest {
    @Test
    public void testBigInteger() {
        BigInteger bi = new BigInteger("12433241123124332411231243324112312433241123124332411231243324112312433241123124332411231243324112312433241123124332411231243324112312433241123124332411231243324112312433241123");
        BigDecimal bd = new BigDecimal("12435.351");
        BigDecimal bd2 = new BigDecimal("11");
        // 精确打印对应的值
        System.out.println(bi);

        // 除得尽的时候就可以正常打印,但是如果除不尽就会导致报错
        //System.out.println(bd.divide(bd2));

        // 保留当前长度尾数
        System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));

        // 保留指定长度的尾数
        System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));   // 1130.486454545454545
    }
}

常见笔试题

一、下列打印的结果是什么?

package com.laoyang.test.day01;

/**
 * @ClassName StringExercise
 * @Description: 下列打印的结果是什么?
 * @Author Laoyang
 * @Date 2021/9/8 9:47
 */
public class StringExercise {
    String str = new String("good");
    char[] ch = {'t', 'e', 's', 't'};

    public void change(String str, char ch[]) {
        str = "test ok";
        ch[0] = 'b';
    }

    public static void main(String[] args) {
        StringExercise ex = new StringExercise();
        ex.change(ex.str, ex.ch);
        System.out.print(ex.str + " and ");	// good and
        System.out.println(ex.ch);	//best
    }
}

解析:

  1. 第一处打印 good and,因为最开始的时候 str 已经被赋值了,然后根据 String 的不可变性,即使放进方法中表面进行了修改,最终其实也只是创建了一个新的值而已,并不会实质性的改变 new String 的值,所以最后打印的还是 good and
  2. 第二处打印 best,因为最开始创建的是一个 cahr[],然后我们将它的地址值传进方法中,在方法中就会根据对应的地址值找到某个需要修改的元素,所以最后打印的结果就是 best

Ps:String 是特别的,它的值是在常量池中的,而数组类型的数据是存在于堆空间中的(如果创建一个 String 类型的数组,那么最后的结果还是会有所改变的)

二、去除字符串两端的空格。

package com.laoyang.test.day02;

import org.junit.Test;
import java.util.Arrays;

/**
 * @ClassName InterviewQuestions
 * @Description: 常见笔试题
 * @Author Laoyang
 * @Date 2021/9/9 12:03
 */
public class InterviewQuestions {
    /**
     * 去除字符串两端的空格。
     */
    @Test
    public void testOne() {
        String name = "   小明   ";
        String trim = name.trim();
        System.out.println(trim);
    }
}

三、将一个字符串进行反转,将字符串中指定部分进行反转。

比如:“abcdefg”反转为”abfedcg”
package com.laoyang.test.day02;

import org.junit.Test;
import java.util.Arrays;

/**
 * @ClassName InterviewQuestions
 * @Description: 常见笔试题
 * @Author Laoyang
 * @Date 2021/9/9 12:03
 */
public class InterviewQuestions {
    /**
     * 将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反转为”abfedcg”
     */
    @Test
    public void testTwo() {
        String str = "abcdefg";
        if (str != null) {
            // 方式一:转换为 char[]
//        char[] chars = str.toCharArray();
//        System.out.println(Arrays.toString(chars));
//        // 将开始位置和结束位置设置好,然后使用数组反转的方式实现
//        for (int i = 2, j = 5; i < j; i++, j--) {
//            char team = chars[i];
//            chars[i] = chars[j];
//            chars[j] = team;
//        }
//        String strChar = new String(chars);
//        System.out.println(strChar);    // abfedcg

            // 方式二:使用 String 拼接
//            // 第一部分:不需要改变的数据,先截取出前半部分不需要反转的字符
//            String substring = str.substring(0, 2);
//            // 第二部分:需要改变的部分,从结束位置开始拼接,就可以完成反转的效果
//            for (int i = 5; i >= 2; i--) {
//                substring += str.charAt(i);
//            }
//            // 第三部分:因为最后的部分不需要进行反转,所以此处直接进行拼接即可
//            substring += str.substring(5 + 1);  // 结束位置 + 1(此处这样写是因为如果是使用传值的方式那么就要记得 +1)
//            System.out.println(substring);

            // 方式三:使用 StringBuffer、StringBuilder 替换 String(这种方式是在第二种方式上进行优化)
            StringBuilder stringBuilder = new StringBuilder(str.length());
            // 第一部分
            stringBuilder.append(str.substring(0, 2));
            // 第二部分
            for (int i = 5; i >= 2; i--) {
                stringBuilder.append(str.charAt(i));
            }
            // 第三部分
            stringBuilder.append(str.substring(5 + 1));  // 结束位置 + 1(此处这样写是因为如果是使用传值的方式那么就要记得 +1)
            System.out.println(stringBuilder);
        }
    }
}

四、获取一个字符串在另一个字符串中出现的次数。

比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数
package com.laoyang.test.day02;

import org.junit.Test;
import java.util.Arrays;

/**
 * @ClassName InterviewQuestions
 * @Description: 常见笔试题
 * @Author Laoyang
 * @Date 2021/9/9 12:03
 */
public class InterviewQuestions {
    /**
     * 获取一个字符串在另一个字符串中出现的次数。
     * 比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数
     */
    @Test
    public void testThree() {
        String str = "abkkcadkabkebfkabkskab";
        String ab = "ab";
        int count = 0;
        int index = 0;

        if (ab.length() < str.length()) {
            while (index != -1) {
                index = str.indexOf(ab, index + 1);
                count++;
            }
        }
        System.out.println(count);  // 4
    }
}

五、获取两个字符串中最大相同子串。

比如:
str1 = "abcwerthelloyuiodef“; str2 = "cvhellobnm"
提示:将短的那个串进行长度依次递减的子串与较长的串比较。
package com.laoyang.test.day02;

import org.junit.Test;
import java.util.Arrays;

/**
 * @ClassName InterviewQuestions
 * @Description: 常见笔试题
 * @Author Laoyang
 * @Date 2021/9/9 12:03
 */
public class InterviewQuestions {
    /**
     * 获取两个字符串中最大相同子串。
     * 比如:
     * str1 = "abcwerthelloyuiodef“; str2 = "cvhellobnm"
     * 提示:将短的那个串进行长度依次递减的子串与较长的串比较。
     */
    @Test
    public void testFour() {
        String strA = "abcwerthelloyuiodefabcde";
        String strB = "cvhellobnmabcde";

        // 获取当前参数的长度,然后依次赋值,将短的那个串进行长度依次递减的子串与较长的串比较。
        String maxStr = (strA.length() >= strB.length()) ? strA : strB;
        String minStr = (strA.length() < strB.length()) ? strA : strB;
        int length = minStr.length();
        String result = null;

        // 使用给循环取别名的方式跳出指定循环,如果是使用返回值的方法实现,则可以直接 return 返回结果并结束整个方法
        strFor:
        for (int i = 0; i < length; i++) {
            for (int j = 0, k = length - i; k <= length; j++, k++) {
                String substring = minStr.substring(j, k);
                if (maxStr.contains(substring)) {
                    /*
                     返回最终结果,如果有两个相同长度的子串,那么最终获取的就是第一次出现的子串
                     比如当前两个字符串中,hello 和 abcde 都存在,最后返回的结果也只会是 hello,因为它先出现的
                     */
                    result = substring;
                    break strFor;
                }
            }
        }
        System.out.println(result);
    }
}

六、对字符串中字符进行自然顺序排序。

提示
1)字符串变成字符数组。
2)对数组排序,选择,冒泡,Arrays.sort();
3)将排序后的数组变成字符串。
package com.laoyang.test.day02;

import org.junit.Test;
import java.util.Arrays;

/**
 * @ClassName InterviewQuestions
 * @Description: 常见笔试题
 * @Author Laoyang
 * @Date 2021/9/9 12:03
 */
public class InterviewQuestions {
    /**
     * 对字符串中字符进行自然顺序排序。
     * 提示:
     * 1)字符串变成字符数组。
     * 2)对数组排序,选择,冒泡,Arrays.sort();
     * 3)将排序后的数组变成字符串。
     */
    @Test
    public void testFive() {
        String str = "afagsuinvsdfui";
        char[] chars = str.toCharArray();
        // 方式一:Arrays.sort()
        Arrays.sort(chars);
        String strA = new String(chars);
        System.out.println(strA);

        // 方式二:冒泡排序
        for (int i = 0; i < chars.length - 1; i++) {
            for (int j = 0; j < chars.length - 1 - i; j++) {
                if (chars[j] > chars[j + 1]) {
                    char team = chars[j];
                    chars[j] = chars[j + 1];
                    chars[j + 1] = team;
                }
            }
        }
        String strB = new String(chars);
        System.out.println(strB);
    }
}

七、下列打印的结果是什么?

package com.laoyang.test.day03;

import org.junit.Test;

/**
 * @ClassName Exercise
 * @Description: 练习
 * @Author Laoyang
 * @Date 2021/9/13 10:47
 */
public class Exercise {
    /**
     * 笔试题:下列程序打印的是什么?
     */
    @Test
    public void testOne() {
        String str = null;
        StringBuffer sb = new StringBuffer();
        sb.append(str);

        System.out.println(sb.length());    // 4
        System.out.println(sb);   // “null”

        /*
         不能再创建对象的时候添加空值进去,如果要加入值,必须为非空的!(String 和 StringBuilder 也不能这样!!!)
         如果创建的时候不想添加值,而创建完之后又想要添加一个空值,那么就可以使用 append() 方法进行添加,使用方法添加不会出现异常
         */
        StringBuffer sb1 = new StringBuffer(str);	// 抛异常
        System.out.println(sb1);  // 因为上面报错了,所以这个就不会被执行
    }

    /**
     * 笔试题:下列程序打印的是什么?
     */
    @Test
    public void testTwo() {
        String a = "123";
        String b = "123";
        String c = new String("123");
        String d = new String("123");
        System.out.println(a.equals(b));   // true
        System.out.println(a == b);        // true
        System.out.println(c.equals(d));   // true
        System.out.println(c == d);        // false
        System.out.println(a.equals(c));   // true
        System.out.println(a == c);        // false
    }
}

因为题目都类似,所以这里我就把两个题放一起了,大家可以看一看

小复习

一、SimpleDateFormat:对日期 Date 类的格式化和解析

格式化:日期 -> 字符串
解析:字符串 -> 日期

实例化:new SimpleDateFormat()

日期标准格式:yyyy-MM-dd

二、Calendar:日历类、抽象类

Date 与 日历类的转换
Date -> Calendar(setTime(long time))
Calendar -> Date(getTime())

三、日期时间 API 的迭代

第一代:JDK1.0 Date类

第二代:JDK1.1 Calendar类,一定程度上替带了Date类

第三代:JDK1.8 提出新一套API,LocalDate/LocalTime/LocalDateTime

前两代的缺陷
  • 可变性:像日期和时间这样的类应该是不可变的。
  • 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
  • 格式化:格式化只对Date有用,Calendar则不行。
  • 此外,它们也不是线程安全的;不能处理闰秒等。
新时间日期API
  • java.time – 包含值对象的基础包
  • java.time.chrono – 提供对不同的日历系统的访问
  • java.time.format – 格式化和解析时间和日期
  • java.time.temporal – 包括底层框架和扩展特性
  • java.time.zone – 包含时区支持的类

说明:大多数开发者只会用到基础包和 format 包,也可能会用到 temporal 包。因此,尽管有 68 个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。

LocalDate/LocalTime/LocalDateTime 说明
  1. LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。
  2. LocalDateTime 相较于其它两个类型,使用的频率更高!

常用方法

方法作用
now() / * now(ZoneId zone)静态方法,根据当前时间创建对象/指定时区的对象
of()静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear()获得月份天数(1-31) /获得年份天数(1-366)
getDayOfWeek()获得星期几(返回一个 DayOfWeek 枚举值)
getMonth()获得月份, 返回一个 Month 枚举值
getMonthValue() / getYear()获得月份(1-12) /获得年份
getHour()/getMinute()/getSecond()获得当前对象对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()/ withMonth()/withYear()将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours()向当前对象添加几天、几周、几个月、几年、几小时
minusMonths() / minusWeeks()/ minusDays()/minusYears()/minusHours()从当前对象减去几月、几周、几天、几年、几小时

四、Instant(瞬时)

  1. 时间线上的一个瞬时点;概念上来说,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。
  2. 类似于 java.util.Date 类

常用方法

方法作用
now()静态方法,返回默认 UTC 时区的 Instant 类的对象
ofEpochMilli(long epochMilli)静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒 数之后的 Instant 类的对象
atOffset(ZoneOffset offset)结合即时的偏移来创建一个 OffsetDateTime
toEpochMilli()返回 1970-01-01 00:00:00 到当前时间的毫秒数,即为时间戳

五、DateTimeFormatter

  1. 格式化或解析日期、时间
  2. 类似于 SimpleDateFormat 类

实例化方式

  1. 预定义的标准格式;比如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
  2. 本地化相关的格式;比如:ofLocalizedDateTime(FormatStyle.LONG)
  3. 自定义的格式;比如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

重点:自定义的格式

常用方法

方法作用
ofPattern(String pattern)静态方法 , 返 回一个 指定字符串格式的 DateTimeFormatte
format(TemporalAccessor t)格式化一个日期、时间,返回字符串
parse(CharSequence text)将指定格式的字符序列解析为一个日期、时间

六、Java比较器

背景

Java 中的对象,正常情况下,只能进行比较:== 或 !=;不能使用 > 或 <;但是在开发场景中,我们需要对多个对象进行排序,也就需要比较对象的大小。

如何实现?
使用 Comparable 或 Comparator

Comparable 自然排序
  1. 像 String、包装类等实现了 Comparable 接口,重写了 compareTo(obj) 方法,给出了比较两个对象大小的方式
  2. 像 String、包装类重写 compareTo(obj) 方法以后,进行了从小到大的排列
  3. 重写 compareTo(obj) 的规则:
    • 如果当前对象this大于形参对象obj,则返回正整数;
    • 如果当前对象this小于形参对象obj,则返回负整数;
    • 如果当前对象this等于形参对象obj,则返回零;
  4. 对于自定义类来说,如果需要排序,则可以让自定义类实现 Comparator 接口,并重写 compareTo(obj) 方法,在 compareTo() 方法中指明如何排序
Comparator 定制排序
  1. 背景:当元素的类型没有实现 java.lang.Comparable 接口而又不方便修改代码,或者实现了 java.lang.Comparable 接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序
  2. 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
    • 如果方法返回正整数,则表示o1大于o2;
    • 如果返回0,表示相等;
    • 返回负整数,表示o1小于o2。
Comparable 和 Comparator 的对比
  • Comparable 接口的方式一旦一定,保证 Comparable 接口实现类的对象在任何位置都可以比较大小;
  • Comparator 接口属于临时性的比较;

七、其它常用类

System
  1. 位于 java.lang 包下
  2. 构造器全都是 private 的
  3. 内部的成员变量和成员方法都是 static 的

成员变量:out、in、err
成员方法

  1. native long currentTimeMillis()
  2. void exit(int status)
  3. void gc()
  4. String getProperty(String key)
Math

java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。

BigInteger

java.math包的BigInteger可以表示不可变的任意精度的整数。

BigDecimal

要求数字精度比较高,故用到java.math.BigDecimal类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值