freyhe

导航

07.Java常用类 String、System、时间API、Object、比较器、Math等

String、StringBulider、StringBuffer

java.lang.String类的使用

详细参考:https://blog.csdn.net/ifwinds/article/details/80849184?ops_request_misc=&request_id=&biz_id=102&utm_term=string类&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-1-80849184.first_rank_v2_pc_rank_v29&spm=1018.2226.3001.4187#1__19

1.概述

String:字符串,使用一对""引起来表示。

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

2.String实现了Serializable接口:表示字符串是支持序列化的。

​ 实现了Comparable接口:表示String可以比较大小

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

4.通过字面量的方式 String str = " xx";(直接字面量赋值)

字面量赋值的字符串值直接声明在字符串常量池中,区别于new给一个字符串赋值

5.字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。

2.String的不可变性

2.1 说明

1.当对字符串重新赋值时,会重新指定内存区域赋值,而不是修改原内存区域的内容。

2.当对现的字符串进行拼接、replace()等修改时,也会重新指定内存区域赋值,而不是修改原内存区域的内容。

2.2 代码举例

String s1 = "abc";//字面量的定义方式
String s2 = "abc";
s1 = "hello";

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

System.out.println(s1);//hello
System.out.println(s2);//abc

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

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

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

String s4 = "abc";
String s5 = s4.replace('a', 'm');
System.out.println(s4);//abc
System.out.println(s5);//mbc

2.3 图示

image-20220310060724551

3.String实例化的不同方式

3.1 方式说明

String对象的创建
方式一:通过字面量定义的方式
String str = "hello";

方式二:通过new + 构造器的方式
//本质上在堆空间new了String对象  底层:this.value = new char[0]; 
String s1 = new String();

//this.value = original.value;

String s2 = new String(String original);

//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startInd=ex,int count);

4.面试题

1.String s = new String("abc");方式创建对象,在内存中创建了几个对象?

两个:一个是堆空间中new结构(char数组),另一个是char[]对应的常量池中的数据:"abc"

@Test
public void test01(){
    String s = "abc";
    exchange(s);
    System.out.println(s);// abc 此次打印的原来的s,final修饰char数组,不可变

    String ss = "abc";
    System.out.println(exchangeReturn(ss));// aaa 此次打印的是方法中返回的s(已被重新赋值)
}
private void exchange(String s){
    s = "aaa";
}
private String exchangeReturn(String s){
    s = "aaa";
    return s;
}

2.如下代码

public class StringTest {

    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) {
        StringTest ex = new StringTest();
        ex.change(ex.str, ex.ch);
        System.out.println(ex.str);//good  
        System.out.println(ex.ch);//best
    }
}

Sting底层char数组是final修饰的,不可变,即使传递给别人,别人修改了,自己也不会变

3.如下代码

@Test
public void test3(){
    String s1 = "javaEE";
    String s2 = "hadoop";

    String s3 = "javaEEhadoop";
    String s4 = "javaEE" + "hadoop";
    String s5 = s1 + "hadoop";
    String s6 = "javaEE" + s2;
    String s7 = s1 + s2;

    System.out.println(s3 == s4);//true
    System.out.println(s3 == s5);//false
    System.out.println(s3 == s6);//false
    System.out.println(s3 == s7);//false
    System.out.println(s5 == s6);//false
    System.out.println(s5 == s7);//false
    System.out.println(s6 == s7);//false

    String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
    System.out.println(s3 == s8);//true
}

1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
2.只要其中有一个是变量,结果就在堆中。
3.如果拼接的结果调用intern()方法,返回值就在常量池中

public native String intern();

intern()方法可以将String在堆中new出的字符串手动加入常量池 (一般用于字符串拼接操作之后,将拼接结果放入常量池)

-- String在进行拼接等修改操作时会在堆空间new StringBuilder对象来进行append最后toString的操作

而最后的toString操作也是new一个String对象(底层是copy char数组的value),所以最后拼接的结果不会放入常量池中

字符串"+"拼接原理

1.String字符串拼接底层会new一个StringBuilder对象,通过它的append方法实现拼接

2.null拼接会变成字符串"null"

因为StringBuilder对象append方法继承自父类AbstractStringBuilderappend的方法,其父类方法中对null判空后return append("null");

3.程序有大量字符串拼接时,建议考虑直接写StringBuilder实现,就不需要底层new很多临时StringBuilder对象了。

详见:https://blog.csdn.net/u012337114/article/details/81317992?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_antiscanv2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_antiscanv2

5.常用方法

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(不包含) --左闭右开

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

替换:

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):告知此字符串是否匹配给定的正则表达式。

split(String regex)方法有坑

切片:split(String regex)方法有坑,分隔符在最后会被忽略,而不是将结尾的空也切成一片,建议使用带重载方法split(String regex, int limit)

String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

详见:https://blog.csdn.net/weixin_43881375/article/details/117820786?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_default&utm_relevant_index=1

补充

String.join()// 也可用StringUtils.join() 和 String.join() 的入参顺序相反

List<String> list = new ArrayList<>();
list.add("111");
list.add("String");
list.add("join");
list.add("222");
String join = String.join("-",list);//传入String类型的List集合,使用"-"号拼接
System.out.println(join);// 111-String-join-222
 
String[] s = new String[]{"333","444"};//传入String类型的数组,使用"-"号拼接
String join2 = String.join("-",s);
System.out.println(join2);// 333-444

6.String与其它结构的转换

6.1 与基本数据类型、包装类之间的转换

​ String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(String str) xxx.valueOf(String str)

​ 基本数据类型、包装类 --> String:调用String重载的String.valueOf(xxx)

@Test
public void test1(){
    String str1 = "123";
    //    int num = (int)str1;//错误的
    int num = Integer.parseInt(str1);

    String str2 = String.valueOf(num);//"123"
    String str3 = num + "";
    System.out.println(str1 == str3);

}

6.2 与字符数组之间的转换

​ String --> char[ ]:调用String的toCharArray()

​ char[ ] --> String:调用String的构造器

@Test
public void test2(){
    String str1 = "abc123";  //题目: a21cb3
    char[] charArray = str1.toCharArray();
    for (int i = 0; i < charArray.length; i++) {
        System.out.println(charArray[i]);
    }

    char[] arr = new char[]{'h','e','l','l','o'};
    String str2 = new String(arr);
    System.out.println(str2);
}

6.3 与字节数组之间的转换

编码:String --> byte[]:调用String的getBytes()

解码:byte[] --> String:调用String的构造器

编码:字符串 -->字节 (看得懂 --->看不懂的二进制数据)

解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 ---> 看得懂

注意:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码

@Test
public void test3() throws UnsupportedEncodingException {
    String str1 = "abc123中国";
    byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
    System.out.println(Arrays.toString(bytes));
    byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
    System.out.println(Arrays.toString(gbks));

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

    String str2 = new String(bytes);//使用默认的字符集,进行解码。
    System.out.println(str2);

    String str3 = new String(gbks);
    System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!
    String str4 = new String(gbks, "gbk");
    System.out.println(str4);//没出现乱码。原因:编码集和解码集一致!
}

6.4 与StringBuffer、StringBuilder之间的转换

​ String -->StringBuffer、StringBuilder:调用StringBuffer、StringBuilder构造器

​ StringBuffer、StringBuilder -->String:①调用String构造器;②StringBuffer、StringBuilder的toString方法

7.JVM中字符串常量池存放位置说明

​ jdk 1.6 (jdk 6.0 ,java 6.0):字符串常量池存储在方法区(永久区)

​ jdk 1.7:字符串常量池存储在堆空间

​ jdk 1.8:字符串常量池存储在方法区(元空间)

StringBuffer与StringBuilder

1.概述

String 类型的字符串是一个不可变的数据类型,内部保存了一个被final修饰的char数组

StringBuilder:一个可变的字符序列,用于单线程,不保证线程安全,效率高(通常情况都使用StringBuilder)

StringBuffer: 一个可变的字符序列,保证线程的同步,用于多线程,效率低

2.StringBuffer、StringBuilder的常用方法

增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length();
遍历:for() + charAt() / toString()

StringBuffer/StringBuilder append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer/StringBuilder delete(int start,int end):删除指定位置的内容
StringBuffer/StringBuilder replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer/StringBuilder insert(int offset, xxx):在指定位置插入xxx
StringBuffer/StringBuilder reverse() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)

3.String/StringBuffer/StringBuilder三者对比

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

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

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

1.StringBuffer 和 StringBuilder 都继承自 AbstractStringBuilder

2.他们内部方法(包括构造方法)基本都是调用父类的方法,StringBuffer的方法都加了synchronized关键字,所以是线程安全的,但执行效率低

3.AbstractStringBuilder 构造方法与扩容机制(StringBuffer 和 StringBuilder 同理)

​ 空参构造方法默认创建长度是16的char数组,

​ 扩容机制:原长度放不下,自动扩容为原来容量的2倍 + 2

对比String、StringBuffer、StringBuilder三者的执行效率

​ 从高到低排列:StringBuilder > StringBuffer > String

System类

详见:https://blog.csdn.net/qq_37212210/article/details/89304251?utm_medium=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromMachineLearnPai2default-2.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromMachineLearnPai2default-2.control

System的构造方法私有,不允许外部创建实例对象

System类中的方法都是静态方法且没有方法能返回一个实例对象

不能实例化 System 对象,只能通过类名调用静态方法,访问静态变量

System全是静态的属性和方法

成员变量:in、out、err

3个成员变量,分别代表标准输入流(in),标准输出流(out)和标准错误输出流(err)。

在initializeSystemClass方法中,分别调用setIn0,setOut0,setErr0这几个native方法来初始化对应的成员变量。

1.out: 对应的是一个 PrintStream 类型的对象,默认会将内容输出到控制台。

public final static PrintStream out = null

//将“你好”内容打印到项目文件夹路径下的"xxx.txt里
PrintStream out = new PrintStream("xxx.txt");
System.setOut(out);
System.out.println("你好");

2.err: 对应的是一个 PrintStream 类型的对象,默认会将异常内容输出到控制台。

public final static PrintStream err = null

//将异常信息打印到项目文件夹路径下的error.txt里
PrintStream error = new PrintStream("error.txt");
System.setErr(error);
System.out.println(1 / 0);

3.in: 对应的是一个 InputStream 类型的对象,接收输入,默认是键盘。

public final static InputStream in = null

System类中常用的方法

currentTimeMillis()

获取系统当前时间的时间戳:System类中的currentTimeMillis()

返回1970-01-01 00:00:00 距离现在的毫秒数时间差

long time = System.currentTimeMillis();
System.out.println(time);

arraycopy()方法

System.arraycopy为 JVM 内部固有方法(native方法),它通过手工编写汇编或其他优化方法来进行 Java 数组拷贝,这种方式比起直接在 Java 上进行 for 循环或 clone 是更加高效的。数组越大体现地越明显

此功能要求:1.源的起始位置+长度不能超过末尾 2.目标起始位置+长度不能超过末尾 3.所有的参数不能为负数

发生存储类型转换,部分成功的数据会被复制过去(详见下面代码)

java.lang.System.arraycopy() 与java.util.Arrays.copyOf()的区别:https://www.cnblogs.com/CherishFX/p/4730796.html

/**
 * @param src 源数组
 * @param srcPos 从源数组中复制的起始位置
 * @param dest 目标数组
 * @param destPos 要复制到的目标数组的起始位置
 * @param length 拷贝的长度
 * @exception  IndexOutOfBoundsException  如果在复制的过程中发生索引溢界异常
 * @exception  ArrayStoreException  如果源数组中的元素因为类型不匹配不能被复制到目标数组中
 * @exception  NullPointerException 如果源数组为null或者目标数组为null
 * 将源数组部分元素复制到目标数组的指定位置(替换目标数组指定位置的内容)
 */
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

示例:

public class ArrayCopyExample {
    public static void main(String[] args) {
        //原数组
        Object[] destArray = {1,2,3,4,5,6,7,8};
        //目标数组
        Object[] destPosArray = {"a","b","c","d","e"};
        System.arraycopy(destArray,0,destPosArray,0,3);
        for (int i = 0; i < destPosArray.length; i++) {
            System.out.println(destPosArray[i]);
        }
    }
}

数组拷贝操作

在 Java 编程中经常会遇到数组拷贝操作,一般会有如下四种方式对数组进行拷贝。

  • for遍历,遍历源数组并将每个元素赋给目标数组。
  • clone方法,原数组调用clone方法克隆新对象赋给目标数组。
  • System.arraycopy,JVM 提供的数组拷贝实现。
  • Arrays.copyof,实际也是调用System.arraycopy

System.arraycopy使用案例

import java.util.Arrays;
/**
 * 从指定源数组中复制一个数组,复制从指定的位置开始,<br>
 * 到目标数组的指定位置结束
 */
public class LessionSystemArraycopy {
  public static void main(String[] args) {
    // 此方位为native方法。
    // 此功能要求
    // 源的起始位置+长度不能超过末尾
    // 目标起始位置+长度不能超过末尾
    // 且所有的参数不能为负数
    try {
      System.arraycopy(ids, 0, ids2, 0, ids.length + 1);
    } catch (IndexOutOfBoundsException ex) {
      // 发生越界异常,数据不会改变
      System.out.println("拷贝发生异常:数据越界。");
    }
    System.out.println(Arrays.toString(ids2)); // [2, 3, 1, 0, 0, 0]
    // 如果是类型转换问题
    Object[] o1 = { 1, 2, 3, 4.5, 6.7 };
    Integer[] o2 = new Integer[5];
    System.out.println(Arrays.toString(o2)); // [null, null, null, null, null]
    try {
      System.arraycopy(o1, 0, o2, 0, o1.length);
    } catch (ArrayStoreException ex) {
      // 发生存储类型转换,部分成功的数据会被复制过去
      System.out.println("拷贝发生异常:数据转换错误,无法存储。");
    }
    // 从结果看,前面3个可以复制的数据已经被存储了。剩下的则没有
    System.out.println(Arrays.toString(o2)); // [1, 2, 3, null, null]
  }
}

System其他不常用方法

  • public static void setOut(PrintStream out):用来修改标准输出位置。
  • public static void setIn(InputStream in):用来修改标准输入位置。
  • public static void gc():通知JVM,希望进行一次垃圾回收,JVM收到消息后,会在适当的时间进行垃圾回收。
  • public static void exit(int status):以指定的退出码结束当前系统。
  这是唯一一个能够退出程序并不执行finally的情况

​ 参数传入一个数字即可。通常传入0记为正常状态,其他为异常状态方法)

示例:

public class SystemExample {
    public static void main(String[] args) {
        System.out.println("Hello World !");
        System.exit(0);

日期和时间

Java里表示以及操作日期和时间的主要有这么几个类:

  • java.util.Date类,用来表示特定的瞬间,精确到毫秒。
  • java.text.DateFormat类,用来实现字符串(String)和日期(Date)之间相互转换。
  • java.util.Calendar类,替换了很多Date类里的功能,这个类将能用到的时间信息都封装成为静态成员,方便获取。

1. Date类

Date类用来表示日期,它里面有很多的方法都被Calendar类取代了。

​ Date 对象的 toString 方法,得到的字符串和我们的习惯不一样

​ 可以考虑使用 DateFormat 对时间的输出格式进行调整

java.util.Date 年月日时分秒毫秒

java.sql.Date 年月日 (java.util.Date 的子类)

java.sql.Time 时分秒毫秒

java.sql.Timestamp 时间戳 年月日时分秒毫秒

1. 构造方法

  • public Date():创建一个当前时间的日期对象。

  • public Date(long date):分配Date对象并初始化此对象,以表示自从标准基准时间以来的指定毫秒数。

    标准基准时间:称为“历元(epoch)”,即1970年1月1日00:00:00 GMT

创建java.util.Date对象

//构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//Sat Feb 16 16:35:31 GMT+08:00 2019 是当前时间
System.out.println(date1.getTime());//1550306204104

//构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(155030620410L);
System.out.println(date2.toString());

创建java.sql.Date对象

java.sql.Date date3 = new java.sql.Date(35235325345L);
System.out.println(date3);//1971-02-13

将java.util.Date对象转换为java.sql.Date对象

Date date = new Date();
java.sql.Date sqlDate = new java.sql.Date(date.getTime());

2. 常用方法

  • public long getTime() :把日期对象转换成对应的时间毫秒值。
  • public String toString():显示当前的年、月、日、时、分、秒 //默认格式:Sat Feb 16 16:35:31 GMT+08:00 2019
  • public boolean after(Date when):判断是否在when时间之后。
  • public boolean before(Date when):判断是否在when时间之前。
  • public int compareTo(Date anotherDate):判断时间和指定时间的大小关系。

2. DateFormat类

java.text.DateFormat 是Format类的抽象子类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。

  • 格式化:按照指定的格式,从Date对象转换为String对象。//String format = sdf.format(date)
  • 解析:按照指定的格式,从String对象转换为Date对象。//Date date = sdf.parse(str);

1. 构造方法

DateFormat是一个抽象类,不能直接创建对象,通常我们会选择创建一个SimpleDateFormat类型的对象。

同一个SimpleDateFormat对象并发场景下会产生异常,推荐使用 java.time.format.DateTimeFormatter(@since 1.8)

public SimpleDateFormat(String pattern):通常会传入一个格式化字符串参数。

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd HH:MM:SS");

参数pattern是一个字符串,代表日期时间的自定义格式。

年月日时分秒:yyyy-MM-dd hh:mm:ss

image-20220310215032573

image-20220312132448270

2. 常见方法

  • public String format(Date date):将Date对象格式化为字符串。

  • public Date parse(String source):将字符串解析为Date对象。

    // 创建一个 SimpleDateFormat 对象  //空参构造器格式化时有默认格式 :21-6-4 下午7:47   
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd HH:MM:SS");
    // 创建一个 Date 对象
    Date date = new Date();
    
    // 将当前时间以指定的格式转换成为字符串
    String str1 = sdf.format(date);
    System.out.println(str1);  // 2020-03-15 11:22:35
    
    // 按照格式创建一个字符串对象
    String dateStr = "2008-08-08 12:34:45";
    
    // 将字符串加载成为一个 Date 对象
    // 解析要求:字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),否则,抛异常
    Date newDate = sdf.parse(dateStr);
    
    

3.SimpleDateFormat 线程不安全的原因和解决方案

线程不安全原因 :

​ SimpleDateFormat 使用成员变量 Calendar 来保存数据, format 方法和 parse 方法在并发场景下会修改 Calendar 的值

​ 参考: https://mp.weixin.qq.com/s/2uzr800WYtu4R0hycfGruA

https://www.jianshu.com/p/d9977a048dab

解决方案:

  1. 将 SimpleDateFormat 定义为局部变量;(每次调用时都new一个,而不是设为static变量来共享)
  2. 使用 synchronized 或者 Lock 加锁执行;(确保每次调用 SimpleDateFormat 对象时都只有一个线程在使用)
  3. 使用 ThreadLocal;(每个线程自己独有一个 SimpleDateFormat 对象)
  4. 使用 JDK 8 中提供的 java.time.format.DateTimeFormatter(@since 1.8) --推荐使用

调用DateTimeFormatter类的静态方法ofPattern传入一个字符串格式,就可以获取到一个日期时间格式化器。

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分ss秒");
LocalDateTime ldt = LocalDateTime.parse("2021年01月08日 20点12分34秒", dtf);
System.out.println(ldt.get(ChronoField.DAY_OF_MONTH));

System.out.println(dtf.format(LocalDateTime.now()));

3. Calendar类

java.util.Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能,在Date后出现,替换掉了许多Date的方法。

该类将所有可能用到的时间信息封装为静态成员变量,方便获取。日历类就是方便获取各个时间属性的。

1. 构造方法

Calendar为抽象类,由于语言敏感性,Calendar类在创建对象时并非直接创建,而是通过静态方法创建,返回子类对象。

  • public static Calendar getInstance():使用默认时区和语言环境获得一个日历。
  • 调用它的子类GregorianCalendar的构造器。

一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想 要的时间信息。

比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、 MINUTE、SECOND

2. 常见方法

Calendar类里通常使用两个方法:

  • public int get(int field):获取指定字段(年月日等)的值。
  • public void set(int filed, int vale):给指定的字段设置值。
  • public void add(int field,int amount) :根据日历的规则将 Calendar 的日历字段 field 添加或减去给定的 amount
  • public final Date getTime() :将 Calendar 转换为 java.util.Date

注意:

获取月份时:一月是0,二月是1,以此类推,12月是11

获取星期时:周日是1,周二是2 , 。。。。周六是7

Calendar类中提供很多成员常量,代表给定的日历字段:

字段值 含义
YEAR
MONTH 月(从0开始,可以+1使用)
DAY_OF_MONTH 月中的天(几号)
HOUR 时(12小时制)
HOUR_OF_DAY 时(24小时制)
MINUTE
SECOND
DAY_OF_WEEK 周中的天(周几,周日为1,可以-1使用)

3.示例代码

//1.实例化
//方式一:创建其子类(GregorianCalendar的对象
//方式二:调用其静态方法getInstance()
Calendar calendar = Calendar.getInstance();
//    System.out.println(calendar.getClass());
 
//2.常用方法
//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,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);

//add()
calendar.add(Calendar.DAY_OF_MONTH,-3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days); 

//getTime():日历类---> Date
Date date = calendar.getTime();
System.out.println(date);

//setTime():Date ---> 日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);

4.JDK8中新日期时间API(替代旧的API)

详见:https://mp.weixin.qq.com/s/FNsyV4skO4NauynR2igp5A

1.概述

1.日期时间API的迭代:

​ 第一代:jdk 1.0 Date类

​ 第二代:jdk 1.1 Calendar类,一定程度上替换Date类

​ 第三代:jdk 1.8 提出了新的一套API (吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API)

2.前两代存在的问题举例:

​ 可变性:像日期和时间这样的类应该是不可变的。

​ 偏移性:Date中的年份是从1900开始的,而月份都从0开始。

​ 格式化:格式化只对Date用,Calendar则不行。

​ 此外,它们也不是线程安全的;不能处理闰秒等。

3.新的 java.time 中包含:

本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime) 、持续时间(Duration)

历史悠久的 Date 类新增了 toInstant() 方法, 用于把 Date 转换成新的表示形式。

这些新增的本地化时间日期 API 大大简 化了日期时间和本地化的管理。

新时间日期API

java.time – 包含值对象的基础包

java.time.chrono – 提供对不同的日历系统的访问

java.time.format – 格式化和解析时间和日期 -- DateTimeFormatter就在其包下

java.time.temporal – 包括底层框架和扩展特性

java.time.zone – 包含时区支持的类

说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽

管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。

2.LocalDate、LocalTime、LocalDateTime

都是final修饰,线程安全的类

LocalDate表示在ISO格式(YYYY-MM-DD)下的不带具体时间的日期,可以存储 生日、纪念日等日期。

​ LocalTime在本地时间表示不带日期的时间,表示一个时间,而不是日期。

​ LocalDateTime用于表示日期和时间的组合,这是一个最常用的类之一。

说明:

1.LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象

2.分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。

3.它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息

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

4.这三个类的构造方法都是私有的,不能直接创建对象,都是通过静态方法now来获取到当前的日期和时间。

常用方法:

image-20220312145401925

LocalDate ld = LocalDate.now(); // 2022-03-13 20:45
System.out.println(ld.get(ChronoField.DAY_OF_WEEK)); // 7
System.out.println(ld.get(ChronoField.DAY_OF_YEAR)); // 72

LocalTime lt = LocalTime.now();
System.out.println(lt.get(ChronoField.HOUR_OF_DAY)); // 20
System.out.println(lt.get(ChronoField.MINUTE_OF_HOUR)); // 45

LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt.get(ChronoField.MINUTE_OF_HOUR)); // 45
System.out.println(ldt.get(ChronoField.DAY_OF_MONTH)); // 13

3.其他

Instant

除了上述三个类以外,JDK8还提供了Instant类来表示时间,它是精确到纳秒的。

说明:

​ ① 时间线上的一个瞬时点。 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC开始的秒数。)

​ ② 类似于 java.util.Date类

1秒 = 1000毫秒 =106微秒=109纳秒 (1 ns = 10 ^ -9 s)

// 获取到当前时间
Instant instant = Instant.now();
System.out.println(instant);

// 使用指定的数字创建时间
Instant day = Instant.ofEpochSecond(1000000);
System.out.println(day);

// 进行日期计算
System.out.println(day.plus(Duration.ofDays(5)));

// 将字符串加载成为 Instant 对象
Instant ins = Instant.parse("2021-01-01T12:00:10.123Z");
System.out.println(ins);

常用方法:

image-20220310215509963

DateTimeFormatter

1 说明:

① 格式化或解析日期、时间

② 类似于SimpleDateFormat

2 常用方法:

① 实例化方式:

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

​ 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)

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

② 常用方法:

image-20220310215621480

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

// 重点:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");

//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);// 2022-03-13 09:06:07

//解析
TemporalAccessor accessor = formatter3.parse("2022-03-13 09:06:07");
System.out.println(accessor);// {MilliOfSecond=0, NanoOfSecond=0, SecondOfMinute=7, MinuteOfHour=6, HourOfAmPm=9, MicroOfSecond=0},ISO resolved to 2022-03-13

ZonedDateTime / ZoneId

带时区的日期时间

举例:

// ZoneId:类中包含了所的时区信息
@Test
public void test1(){
    //getAvailableZoneIds():获取所的ZoneId
    Set<String> zoneIds = ZoneId.getAvailableZoneIds();
    for(String s : zoneIds){
        System.out.println(s);
    }
    System.out.println();

    //获取“Asia/Tokyo”时区对应的时间
    LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
    System.out.println(localDateTime);
}

//ZonedDateTime:带时区的日期时间
@Test
public void test2(){
    //now():获取本时区的ZonedDateTime对象
    ZonedDateTime zonedDateTime = ZonedDateTime.now();
    System.out.println(zonedDateTime);
    //now(ZoneId id):获取指定时区的ZonedDateTime对象
    ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
    System.out.println(zonedDateTime1);

}

Duration

时间间隔:用于计算两个“时间”间隔,以秒和纳秒为基准

image-20220310215827449

举例:

@Test
public void test3(){
    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());

}

7.3 日期间隔:Period --用于计算两个“日期”间隔,以年、月、日衡量

image-20220310220007412

举例:

@Test
public void test4(){
    LocalDate localDate = LocalDate.now();
    LocalDate localDate1 = LocalDate.of(2028, 3, 18);

    Period period = Period.between(localDate, localDate1);
    System.out.println(period);

    System.out.println(period.getYears());
    System.out.println(period.getMonths());
    System.out.println(period.getDays());

    Period period1 = period.withYears(2);
    System.out.println(period1);

}

TemporalAdjuster

日期时间校正器

举例:

@Test
public void test5(){
    //获取当前日期的下一个周日是哪天?
    TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
    LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
    System.out.println(localDateTime);

    //获取下一个工作日是哪天?
    LocalDate localDate = LocalDate.now().with(new TemporalAdjuster(){
        @Override
        public Temporal adjustInto(Temporal temporal) {
            LocalDate date = (LocalDate)temporal;
            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);
}

序列化处理

image-20220424194400715

image-20220424194423040

image-20220424194457837

image-20220424194515557

日期Util

Date、LocalDate、LocalDateTime间互相转化
// 数据准备
Date date = new Date();
LocalDate localDate = LocalDate.now();
LocalDateTime localDateTime = LocalDateTime.now();

// Date -> LocalDateTime
LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());

// LocalDateTime -> Date
Date d = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

// LocalDate -> LocalDateTime
LocalDateTime ldt2 = LocalDateTime.of(localDate, LocalTime.now());

// LocalDateTime -> LocalDate
LocalDate ld = localDateTime.toLocalDate();
获取两月之间的所有月份
/**获取两月之间的所有月份*/
public static List<String> getMonthBetween(String minDate, String maxDate){
    ArrayList<String> result = new ArrayList<String>();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");//格式化为年月

    Calendar min = Calendar.getInstance();
    Calendar max = Calendar.getInstance();

    try{
        min.setTime(sdf.parse(minDate));
        min.set(min.get(Calendar.YEAR), min.get(Calendar.MONTH), 1);

        max.setTime(sdf.parse(maxDate));
        max.set(max.get(Calendar.YEAR), max.get(Calendar.MONTH), 2);
    }catch (Exception e){
        throw new SSCException("分解月份出错");//注意:抛出的类型是自定义的
    }

    Calendar curr = min;
    while (curr.before(max)) {
        result.add(sdf.format(curr.getTime()));
        curr.add(Calendar.MONTH, 1);
    }
    min = null;max = null;curr = null;
    return result;
}
获取 t-1月的第一天到最后一天
DateTime lastMonth Dateutil.LastMonth();
String beginofLastMonth = DateUtil.format(DateUtil.beginOfMonth(lastMonth),"yyyy-MM-dd");//hutool工具类
String endofLastMonth = DateUtil.format(Dateutil.endOfMonth(lastMonth),"yyyy-MM-dd");

Object

1.概述

1.Object类是所Java类的根父类

2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类

3.Object类中的功能(属性、方法)就具通用性

​ 属性:无

​ 方法:9个

4.Object类只声明了一个空参的构造器

2.内部9个方法说明

getClass()、hashCode()、equals()、clone()、toString()、notify()、notifyAll()、wait() --重载3个方法 、finalize()

  1. getClass 方法
    public final native Class<?> getClass();
    final 方法、获取对象的运行时 class 对象,class 对象就是描述对象所属类的对象。这个方法通常是和 Java 反射机制搭配使用的。

  2. hashCode 方法
    public native int hashCode();
    该方法主要用于获取对象的散列值。Object 中该方法默认返回的是对象的堆内存地址。

  3. equals 方法
    public boolean equals(Object obj) { return (this == obj);}
    该方法用于比较两个对象,如果这两个对象引用指向的是同一个对象,那么返回 true,否则返回 false。一般 equals 和 == 是不一样的,但是在 Object 中两者是一样的。子类一般都要重写这个方法。

  4. clone 方法
    protected native Object clone() throws CloneNotSupportedException;

该方法是保护方法,只有实现了 Cloneable 接口才可以调用该方法,否则抛出 CloneNotSupportedException 异常。

对应引用数据类型的拷贝,默认的 clone 方法是浅拷贝(native方法,调用C/C++生成克隆对象)。

所谓浅拷贝,指的是只会拷贝对象的引用地址赋值给新的引用,新引用其实还是指向原来的对象

  1. toString 方法
    public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
    返回一个 String 对象,一般子类都有覆盖。默认返回格式如下:对象的 class 名称 + @ + hashCode 的十六进制字符串。

  2. notify 方法
    public final native void notify();
    final 方法,主要用于唤醒在该对象上等待的某个线程。

  3. notifyAll 方法
    public final native void notifyAll();
    final 方法,主要用于唤醒在该对象上等待的所有线程。

  4. wait(long timeout) 方法
    public final native void wait(long timeout) throws InterruptedException;
    wait 方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 方法一直等待,直到获得锁或者被中断。wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。

  5. wait(long timeout, int nanos) 方法
    public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && timeout == 0)) { timeout++; } wait(timeout); }
    参数说明
    timeout:最大等待时间(毫秒)
    nanos:附加时间在毫秒范围(0-999999)
    该方法导致当前线程等待,直到其他线程调用此对象的 notify() 方法或notifyAll()方法,或在指定已经过去的时间。此方法类似于 wait 方法的一个参数,但它允许更好地控制的时间等待一个通知放弃之前的量。实时量,以毫微秒计算,计算公式如下:
    1000000 * timeout + nanos
    在所有其他方面,这种方法与 wait(long timeout) 做同样的事情。特别是 wait(0, 0) 表示和 wait(0) 相同。

  6. wait 方法
    public final void wait() throws InterruptedException { wait(0);}
    可以看到 wait() 方法实际上调用的是 wait(long timeout) 方法,只不过 timeout 为 0,即不等待。

  7. finalize 方法
    protected void finalize() throws Throwable { }
    该方法是保护方法,主要用于在 GC 的时候再次被调用,如果我们实现了这个方法,对象可能在这个方法中再次复活,从而避免被 GC 回收。

3.浅拷贝和深拷贝

详见:https://blog.csdn.net/u011289652/article/details/80722187

1.浅拷贝和深拷贝的含义

浅拷贝:对象的浅拷贝会对原对象内存地址进行拷贝,赋值给新的引用,而不是真的创建新对象,源对象引用和副本引用指向同一个对象

深拷贝:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存(真的创建了新对象)

​ 深拷贝相比于浅拷贝速度较慢并且花销较大。

2.浅拷贝的实现-调用Object.clone方法

如何实现浅拷贝? 调用clone方法, 重写 clone方法(内部还是调用Object的clone方法)

如果想要使一个类的对象能够调用clone方法 ,则需要实现Cloneable接口, 并重写 clone方法:

public class Student implements Cloneable{
    private int sno ;
    private String name;

    //getter ,setter 省略

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student s = null;
        try{
            s = (Student)super.clone();//调用Object的clone方法,即浅拷贝
        }catch (Exception e){
            e.printStackTrace();
        }
        return s;
    }

}

3.深拷贝的实现

1.多层嵌套调用Object.clone方法

将当前对象内部所用引用数据类型使用Object.clone方法拷贝一份,然后重新set进去

tip:对象内部属性的属性还包含引用数据类型,则接着嵌套使用Clone方法,直至最内侧的引用数据类型的内部属性全部是基本数据为止

@Data
class Student implements Cloneable {
    private String name;
    private int age;
    private Teacher teacher;

    @Override
    public Object clone() throws CloneNotSupportedException {
        // 浅拷贝时:
        //  Student student = (Student) super.clone();
        //  return student;

        // 改为深拷贝: -- 本来是浅拷贝,现在将Teacher对象复制一份并重新set进来
        // 此处是深拷贝的前提条件:Teacher内部属性全部都是基本数据
        Student student = (Student) super.clone();
        student.setTeacher((Teacher) student.getTeacher().clone());
        return student;
    }

}

2.使用序列化实现深拷贝

此处使用IO流中的对象流来作为示例

Student s1 = new Student(name:"张三", age:19, person:null);
// 序列化
ByteArrayOutputstream baos = new ByteArrayoutputstream();
ObjectOutputstream oos = new Objectoutputstream(baos);
oos.writeObject(s1);//将Person对象写入到ByteArrayOutputstream对象中
oos.flush();
// 反序列化
ByteArrayInputstream bai = new ByteArrayInputstream(baos.toByteArray());
ObjectInputstream ois new ObjectInputstream(bai);
Student s2 = (Student)ois.readobject();//从 ByteArrayOutputstream对象中读出Person对象,此时是个深拷贝出的对象
//修改s2的属性
s2.setName("李四");
s2.setAge(20);
s2.getPerson(new Person(name:"zhangsan",age:18));

System.out.println("s1:"+s1.tostring());// 在s2的属性被修改后,s1还是原址,并未被改变,所以是深拷贝

比较器

1.Java比较器的使用背景

​ Java中的对象,正常情况下,只能进行比较:== 或 != 。不能使用 > 或 < 的

​ 但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。

​ 如何实现?使用两个接口中的任何一个:Comparable 或 Comparator

2.自然排序:使用Comparable接口

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

重写compareTo(obj)的规则:(一般)

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

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

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

2.自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序
自定义类代码举例:

public class Goods implements  Comparable{

 private String name;
 private double price;

 //指明商品比较大小的方式:照价格从低到高排序,再照产品名称从高到低排序
 @Override
 public int compareTo(Object o) {
//        System.out.println("**************");
     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;
            return -this.name.compareTo(goods.name);
         }
         //方式二:
//           return Double.compare(this.price,goods.price);
     }
//        return 0;
     throw new RuntimeException("传入的数据类型不一致!");
 }
// getter、setter、toString()、构造器:省略
}

3.定制排序:使用Comparator接口

1.说明
当元素的类型没实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,

​ 那么可以考虑使用 Comparator 的对象来排序

2.一般用法:

​ 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:(类似compareTo的比较规则)

如果方法返回正整数,则表示o1大于o2;

如果返回0,表示相等;

返回负整数,表示o1小于o2。

还可以使用:
Arrays.sort(goods,com);
Collections.sort(coll,com);
new TreeSet(com);
已经实现Comparable接口,重写compareTo方法对象的 compareTo 方法    ...

代码举例:

Comparator com = new Comparator() {
 //指明商品比较大小的方式:照产品名称从低到高排序,再照价格从高到低排序
 @Override
 public int compare(Object o1, Object o2) {
     if(o1 instanceof Goods && o2 instanceof Goods){
         Goods g1 = (Goods)o1;
         Goods g2 = (Goods)o2;
         if(g1.getName().equals(g2.getName())){
             return -Double.compare(g1.getPrice(),g2.getPrice());
         }else{
             return g1.getName().compareTo(g2.getName());
         }
     }
     throw new RuntimeException("输入的数据类型不一致");
 }
}

3.内部的一些静态方法(使用lambda表达式定义比较规则)

​ Comparator.comparing 按xx比较

​ Comparator.reverseOrder Comparator.reversed Comparator.naturalOrder 按xx规则 取反/不取反 排序

​ Comparator.nullsFirst Comparator.nullsLast 将null对象排在集合的首位或尾部

​ comparator.thenComparing //默认方法 先按xx比较,再按此规则比较

使用代码示例:

@Data
public class User { 
    String username;    
    int age;    
    List<User> followers;
}

List<User> users = ...
  1. 按年龄从小到大排序
users.sort(Comparator.comparing(User::getAge));
// 上面采用了方法引用,等同于 users.sort((o1,o2)->o1.getAge().compareTo(o2.getAge()));

代码比较容易理解,取出age,根据age进行大小比较。

  1. 按年龄从大到小排序
users.sort(Comparator.comparing(User::getAge).reversed());

和示例1基本一样,只是在比较后,进行reverse操作(反向操作)。

  1. 按关注者数量从小到大排序
users.sort(Comparator.comparingInt(u -> u.getFollowers().size()));

我们不仅可以传 method reference,还可以自定义lambda。

  1. 按关注者从多到少排序,若关注者数量一样,按年龄从小到大排序
users.sort(Comparator.comparing((User u) -> u.getFollowers().size()).reversed()  .thenComparing(User::getAge));

当有多个条件时,使用 thenComparing 进行组合。

有时被排序的对象可能是null的,Comparator 也提供了 nullsFirst 和 nullsLast 方法,方便我们将null对象排在集合的首位或尾部。

另外要注意,对于复杂属性的排序,我们通常用 Comparator.comparing ,而对简单属性( primitive value,如 int、float ),建议用 Comparator.comparingInt、Comparator.comparingFloat 等,后者可避免自动装箱带来的性能损耗

更多的使用方法,大家可参考 Comparator 源码。

Math

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

​ Math.sqrt()//计算平方根
​ Math.cbrt()//计算立方根
​ Math.pow(a, b)//计算a的b次方
​ Math.max(a, b);//计算最大值
​ Math.min(a, b);//计算最小值
​ Math.abs();//取绝对值
​ Math.ceil();//舍小数,取偏大的值
​ Math.floor();//舍小数,取偏小值
​ Math.round();//四舍五入
​ Math.random();//取得一个大于或等于0但小于1的随机double数

public class MathTest{
    public static void main(String args[]){  

        //sqrt()&cbrt()
        System.out.println(Math.sqrt(16));   //4.0返回double值
        System.out.println(Math.cbrt(8));    //2.0返回double值

        //pow(a,b)
        System.out.println(Math.pow(2,2));     //4.0返回double值

        //max(a, b)&min(a, b)
        System.out.println(Math.max(2,1.0));//2.0有一个为浮点数就返回浮点数
        System.out.println(Math.min(2,1));//2

        //abs求绝对值   
        System.out.println(Math.abs(-1));    //1  
        System.out.println(Math.abs(10.1));     //10.1  

        //ceil取偏大值
        System.out.println(Math.ceil(-10.1));   //-10.0  
        System.out.println(Math.ceil(10.7));    //11.0  
        System.out.println(Math.ceil(-0.7));    //-0.0  
        System.out.println(Math.ceil(0.0));     //0.0  
        System.out.println(Math.ceil(-0.0));    //-0.0  

        //floor取偏小值
        System.out.println(Math.floor(-10.1));  //-11.0  
        System.out.println(Math.floor(10.7));   //10.0  
        System.out.println(Math.floor(-0.7));   //-1.0  
        System.out.println(Math.floor(0.0));    //0.0  
        System.out.println(Math.floor(-0.0));   //-0.0  

        //round四舍五入
        System.out.println(Math.round(10.1));   //10  
        System.out.println(Math.round(10.7));   //11  
        System.out.println(Math.round(10.5));   //11  
        System.out.println(Math.round(-10.5));  //-10 负数情况下.5会被舍去 
        System.out.println(Math.round(-10.51)); //-11  
        System.out.println(Math.round(-10.6));  //-11  
        System.out.println(Math.round(-10.2));  //-10  
    }  
}

BigInteger、BigDecimal

说明:

​ BigInteger:可以表示不可变的任意精度的整数。(byte、short、int、long都有长度限制)

BigInteger 一般用来表示超大整数.对于超大整数来说,使用字符来表示

​ BigDecimal:要求数字精度比较高,用到java.math.BigDecimal类(float、double在计算时会产生精度丢失)

(byte、short、int、long 和 float、double 的说明,详见笔记 02.基本语法 中 2.1 变量的分类)

代码举例:

@Test
public void test2() {
    BigInteger bi = new BigInteger("1243324112234324324325235245346567657653");
    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, 25, BigDecimal.ROUND_HALF_UP));

}

Java.util包

详见:https://iowiki.com/java/util/

posted on 2022-03-14 07:29  freyhe  阅读(58)  评论(0编辑  收藏  举报