目录
7.7、Object
7.7.1、Object类
Object类是所有类的超类,它是继承体系的顶端,所有的类都间接或直接的继承了Object类,所以java中任何一个对象都可以被视作Object类型,在定义类时,如果没有为类显示的指定父类,那么它就默认继承Object类。
它内部有几个常用的方法:
1、public final native Class<?> getClass():用于获得运行时的类型,返回的是此Object对象的类对象/运行时类对象(反射时使用)
2、public native int hashCode():返回对象的哈希码值,通常配合equals 一起使用(两者一起被重写)
3、public boolean equals(Object obj):本质上是用==,子类对其重写后,比较引用数据类型在堆中的值
4、protected native Object clone():特定的克隆操作。需实现接口Cloneable,否则抛出CloneNotSupportedException
5、public String toString():返回对象的字符串表示形式,类名和它的引用地址(重写后的toString会输出内容实体)
6、public final native void notify():唤醒正在此对象的监视器上等待的单个线程。
7、public final native void notifyAll():唤醒正在此对象的监视器上等待的所有线程
8、public final void wait():使当前线程等待直到它被唤醒
9、wait(long timeout):使当前线程等待直到它被唤醒或超过指定的时间量
10、protected void finalize():当垃圾收集确定不再有对该对象的引用时,由对象上的垃圾收集器调用
1、==和equals的比较
==在比较基本数据类型时比较的是保存的值是否相同,而比较引用数据类型时比较存放的引用地址是否相同。
equals在底层也是使用==来比较的,但是一般都会对其进行重写,重写后的equals方法可以比较两个对象在堆中存放的值是否相同
equals 的逻辑需要重写来决定,所以严格来说equals 的功能是根据业务逻辑来决定的,所以equals 中的代码不能随便写。
子类中如果重写了equals方法,在比较时,会先调用超类的equals方法,如果检测不相等,对象就会直接判定为不相等,否则再继续使用子类中重写的equals方法。
2、equals 方法具有下述性质:
- 自反性:对于任何非 null的引用 x,x.equals(x) 应该返回 ture
- 对称性:对于任何引用 x 和 y, 当且仅当 y.equals(x) 返回 true 时,x.equals(y) 也返回 true
- 传递性;对于任何引用x、 y 、 z, 如果 x.equals(y) 返回 true,y.equals(z) 返回 true,则x.equals(z) 也应该返回 true
- 一致性:如果 x 和 y 引用的对象没有发生变化,则反复调用 x.equals(y) 应该返回同样的结果。
- 对于任意非 null引用 x, x.equals(null) 都应该返回 false。
3、equals和hashCode的比较
- hashcode:主要是用来获取hash码,返回的是一个int,通常用来确定对象在hash表的位置。
- equals:在发生hash冲突时(两个对象有相同的hash码),使用equals进行判断两个对象是否真的相同,返回值为Boolean
两者需要同时重写,在存入Hash数据时,先对key的hashcode进行判断,确定在hash表中的位置,如果此位置已经存在数据,再使用equals进行判断,如equals判断两者相同会将value替换,如果不同则创建新节点存放数据。这样会减少equals的使用频率,大大提高执行效率。
7.7.2、Objects类
从Java7开始,引入的Objects 工具类提供了一些工具方法来操作对象,这些方法大多是 “空指针” 安全的。比如我们在使用Object 中的toString 方法时,可能会出现空指针异常,但是使用Objects 中的 toString 方法时,就不会出现空指针的异常,但是它会返回一个 “null” 的字符串。
Objects
类的方法经常被用在需要进行对象比较、生成哈希码、返回字符串表示等场景中。
1、equals(Object a, Object b):比较两个对象的相等性。这个方法是 null-safe 的,如果两个对象都是 null,则返回 true;如果其中一个对象是 null,则返回 false;否则,调用第一个对象的 equals() 方法进行比较。
2、hashCode(Object o):生成对象的哈希码。这个方法也是 null-safe 的,如果对象是 null,则返回 0;否则,调用对象的 hashCode() 方法。
3、toString(Object o):返回对象的字符串表示。如果对象是 null,则返回字符串 "null";否则,调用对象的 toString() 方法。
4、toString(Object o, String nullDefault):返回对象的字符串表示,但允许为 null 对象指定默认的字符串表示。
5、requireNonNull(T obj):检查指定的对象引用不是 null。如果是 null,则抛出 NullPointerException。
6、requireNonNull(T obj, String message):检查指定的对象引用不是 null,并在抛出 NullPointerException 时提供自定义的错误消息。
7、compare(T a, T b, Comparator<? super T> c):使用提供的 Comparator 比较两个对象。这个方法是 null-safe 的,可以优雅地处理 null 值。
8、deepEquals(Object a, Object b):对两个对象进行深度比较。这对于比较可能包含数组的对象尤其有用,因为它会考虑数组内部的元素是否相等。
7.8、字符串相关类
7.8.1、介绍
在Java操作字符串的除了String外,还有两个类,分别是:StringBuffer、StringBuilder。它们创建的字符串其值是可以改的,这得益于内部的动态数组。
-
StringBuffer:JDK1.0出现,字符串类,其值可修改,线程安全,速度相对StringBuilder慢。
-
StringBuilder:JDK 5.0出现,字符串类,其值可修改,线程不安全,但是速度最快。
//三种构造方法 StringBuffer():初始容量为16的字符串缓冲区 StringBuffer(int size):构造指定容量的字符串缓冲区 StringBuffer(String str):将内容初始化为指定字符串内容
StringBuilder和StringBuffderde都继承了AbstractStringBuilder类,它和String一样(java9之前的版本),其底层是一个字符数组。每个字符占2个字节。而Java9之后String采用 byte[] 和 encoding-flag字段来保存字符,每个字符占一个字节,所以Java9之后的字符串更加节省空间。
底层的字符数组,初始大小是 16 个字符(除非在构造时指定了其他大小),这意味着如果不进行扩容,它可以存储 16 个字符。
当添加的字符超出了当前数组容量时,就会发生扩容。扩容过程如下:
如果添加字符串过程中超出数组范围,就会对底层数组进行扩容,首先创建一个新的数组大小为原来的2倍加2,将原数组中的内容复制到新数组中,再将指针指向新创建的数组地址。
-
jdk8中的扩容方法:
private int newCapacity(int minCapacity) { // 计算新容量,当前容量的两倍再加2 int newCapacity = (value.length << 1) + 2; // 如果计算得到的新容量仍然小于所需最小容量,直接使用所需最小容量 if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } // 检查新容量是否溢出或超过数组最大限制 return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; } private int hugeCapacity(int minCapacity) { if (minCapacity < 0) // 溢出 throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
-
jdk17中的扩容方法
private int newCapacity(int minCapacity) { int oldLength = value.length; // 当前数组长度 int newLength = minCapacity << coder; // 根据编码调整最小容量需求 int growth = newLength - oldLength; // 计算增长量 // 计算新长度,确保至少能容纳增长量,同时预留一些额外空间 int length = ArraysSupport.newLength(oldLength, growth, oldLength + (2 << coder)); // 如果计算结果为 Integer.MAX_VALUE,抛出内存溢出错误 if (length == Integer.MAX_VALUE) { throw new OutOfMemoryError("Required length exceeds implementation limit"); } return length >> coder; // 根据编码调整计算后的长度,并返回 } //计算新数组的长度 public static int newLength(int oldLength, int minGrowth, int prefGrowth) { // 计算首选长度。首选长度是旧长度加上最小增长和首选增长中的较大值。 // 这里可能会发生整数溢出 int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // 如果计算出的首选长度是正数且不超过软最大数组长度(SOFT_MAX_ARRAY_LENGTH),则直接返回这个长度 if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) { return prefLength; } else { // 如果首选长度是负数或超过软最大长度,调用hugeLength方法来处理这种极端情况 // 这部分代码因为很少执行,所以被放在一个单独的方法中以优化性能 return hugeLength(oldLength, minGrowth); } }
- Coder影响:
coder
的值决定了字符数组中每个字符的字节大小。因此,minCapacity << coder
实际上是根据编码对最小容量需求进行调整。 - 动态扩容策略:
ArraysSupport.newLength
方法用于动态决定数组的新长度。它考虑了当前长度、需要增加的长度以及一个预期的最大长度。这里oldLength + (2 << coder)
表示预期增长的长度,这个增长是基于当前编码方式的。 - 内存溢出检查:如果新的长度计算结果为
Integer.MAX_VALUE
,则抛出OutOfMemoryError
,说明请求的数组长度超过了Java数组能够支持的最大限制。 - 返回调整后的长度:返回值
length >> coder
是将计算出来的长度根据编码方式进行调整,确保它反映的是字符数而非字节数。
- Coder影响:
7.8.2、常用方法
StringBuilder和StringBuffder提供的方法基本类似:
StringBuilder和StringBuffder常用方法:
1、增:追加字符串 .append("").append("").....;
2、删:删除指定范围的内容[start,end) .delete(int start,int end)
3、改:替换指定范围的内容[start,end) .replace(int start, int end, String str)
修改指定索引处的字符:public void setCharAt(int n ,char ch)
4、查:获取指定索引处的字符 public char charAt(int n )
5、在指定位置插入字符串:在指定位置插入指定的内容 .insert(int offset, "")
6、返回指定子字符串在当前字符串中第一次出现处的索引:public int indexOf(String str)
7、截取字符串[start,end):public String substring(int start,int end)
8、返回字符串的长度:public int length()
9、把当前字符序列逆转:.reverse()
public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
//增
builder.append("hello").append("world");
System.out.println("追加字符串:"+builder);
//删
builder.delete(0,2);
System.out.println("删除字符串:"+builder);
//改
builder.replace(3,7,"java");
System.out.println("替换字符串:"+builder);
//查、
System.out.println("索引3的位置:"+builder.charAt(3));
builder.setCharAt(3,'a');
System.out.println("修改字符串:"+builder);
//插入字符串
builder.insert(0,"he");
System.out.println(builder);
//返回指定子字符串在当前字符串中第一次出现处的索引
System.out.println(builder.indexOf("a"));
//返回指定子字符串在当前字符串中最后一次出现处的索引
System.out.println(builder.lastIndexOf("a"));
//截取字符串[start,end)
System.out.println(builder.substring(0,3));
//把当前字符序列逆转
System.out.println(builder.reverse());
//判断字符串是否为空
System.out.println(builder.isEmpty());
}
7.8.3、速度对比
对比一下使用String和StringBuilder在速度上的区别
计算一下追加 100,000,0次字符所用的时间
-
使用String
public static void main(String[] args) { long start = System.currentTimeMillis(); String str = ""; for(int i=0; i<1000000; i++){ str += 1; } long end = System.currentTimeMillis(); System.out.printf("使用String用时:%d",end-start); }
-
换成StringBuilder
public static void main(String[] args) { long start = System.currentTimeMillis(); StringBuilder builder = new StringBuilder(); for(int i=0; i<1000000; i++){ builder.append(1); } long end = System.currentTimeMillis(); System.out.printf("使用StringBuilder用时:%d",end-start); }
-
使用StringBuffder
public static void main(String[] args) { long start = System.currentTimeMillis(); StringBuffer buffer = new StringBuffer(); for(int i=0; i<1000000; i++){ buffer.append(1); } long end = System.currentTimeMillis(); System.out.printf("使用StringBuffer用时:%d",end-start); }
从上面可以明显的看出来使用String的时候相比较后两种速度慢的不是一点半点的,而是很多倍的差距了,而StringBuilder相比较StringBuffder也快了一些。
7.9、时间处理
Java原本提供的时间处理类为 Date 和 Calendar两个类,但是Date不仅无法实现国际化,且它对不同属性也使用了前后矛盾偏移量,如月份小时从0开始,天数从1开始。而Calendar操作过于复杂,所以再Java8之后,提供了一套全新的时间、日期库。
7.9.1、JDK8之前
-
计算时间差:
System类提供的
public static long currentTimeMillis()
用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。通常用来计算某个程序的运行时间,进行快慢的对比 -
java.util.Date类
Date中既包含日期,也包含时间。目前Date中大多数构造器都不再推荐使用,仅仅留下2个构造器。
-
SimpleDateFormat类:用于格式化和解析日期的一个类,属于
java.text
包。 -
Calendar类:是一个抽象类,用于表示日历,是一个日历类的模板,提供一些日历相关的通用方法。在Java中提供一个其子类GregorianCalendar就是公历。主要用于操作日历字段,如年、月、日、时、分、秒等。
//创建方式 1、创建子类GregorianCalendar的的对象:Calendar calendar = new GregorianCalendar(); 2、通过静态方法:Calendar calendar = Calendar.getInstance(); (底层也是创建GregorianCalendar对象)
-
设置日期:set(int field, int value)、setTimeInMillis(long millis)
calendar.set(Calendar.YEAR, 2023); //设置年份,2023年 calendar.set(Calendar.MONTH, Calendar.JANUARY); //设置月份 1月 calendar.set(Calendar.DAY_OF_MONTH, 1); //设置日期
-
获取日期:get(int field)、getTimeInMillis()
int year = calendar.get(Calendar.YEAR); //获取年份 int month = calendar.get(Calendar.MONTH); //获取月份 int day = calendar.get(Calendar.DAY_OF_MONTH); //获取日期
-
添加或减少时间:add(int field, int amount)
calendar.add(Calendar.YEAR, 1); // 在当前年份上加1年 calendar.add(Calendar.DAY_OF_MONTH, -10); // 当前日期减10天
-
设置时间到最大或最小值:getActualMaximum(int field)和 getActualMinimum(int field)
getActualMaximum(int field):用于确定一个月可以有的最大天数等 getActualMinimum(int field):通常用于确定一个月可以有的最小天数等
-
比较时间:
before(Object when):用于比较两个日期,查看当前实例是否在参数表示的日期之前 after(Object when):用于比较两个日期,查看当前实例是否在参数表示的日期之后。 equals(Object obj):用于比较两个日历对象是否代表同一时刻。
Calendar类中常用的常量字段如下:
常量字段 说明 Calendar.ERA 表示纪元,只能为0或1。0表示BC(“before Christ”,即公元前);1表示AD(拉丁语“Anno Domini”,即公元)。 Calendar.YEAR 表示年份 Calendar.MONTH 表示月份,0表示1月,1表示2月……… Calendar.DAY_OF_MONTH 表示一个月中的某天字段 Calendar.WEEK_OF_YEAR` 即本年中第几个星期 Calendar.WEEK_OF_MONTH 当前月中的星期数,即本月中第几个星期 Calendar.DAY_OF_YEAR 一年中第几天 Calendar.DAY_OF_WEEK 一周中第几天,注意,周日是1,周一是2,… Calendar.DAY_OF_WEEK_IN_MONTH 表示当前月份中的星期几 Calendar.DATE 一月中第几天,同DAY_OF_MONTH的值是一样的 Calendar.HOUR 小时(12小时制) Calendar.AM_PM 用于指示HOUR是在中午之前还是在中午之后。在中午12点之前返回0,在中午12点(包括12点)之后返回1 Calendar.HOUR_OF_DAY 一天中第几个小时(24小时制) Calendar.MINUTE 分钟 Calendar.SECOND 秒 Calendar.MILLISECOND 毫秒 Calendar.JANUARY 1月 Calendar.FEBRUARY 2月 Calendar.MARCH 3月 Calendar.APRIL 4月 Calendar.MAY 5月 Calendar.JUNE 6月 Calendar.JULY 7月 Calendar.AUGUST 8月 Calendar.SEPTEMBER 9月 Calendar.OCTOBER 10月 Calendar.NOVEMBER 11月 Calendar.DECEMBER 12月 public static void main(String[] args) throws ParseException { Calendar calendar = Calendar.getInstance(); //查看是公元前(0)还是公元后(1) System.out.println("判断公元:"+calendar.get(Calendar.ERA)); //获取月份:返回值+1 System.out.println("获取月份:"+calendar.get(Calendar.MONTH)); //将年份设置为1999 calendar.set(Calendar.YEAR,1999); System.out.println("修改年份为1999:"+calendar.get(Calendar.YEAR)); //将年份+1 calendar.add(Calendar.YEAR,1); System.out.println("将年份+1:"+calendar.get(Calendar.YEAR)); //返回Date格式日期 System.out.println("获取date格式日期:"+calendar.getTime()); //设置时间 calendar.setTime(new Date("Tue Feb 08 18:29:23 CST 2222")); System.out.println("设置时间:"+calendar.getTime());; }
-
7.9.2、JDK8后
1、时间日期类
在JDK8以前,不论是Date类、还是Calendar类,都不是特别好用,所以时期时间问题一直都是一个让人头疼的问题。所以在JDK8中第三次引入了关于日期时间的API,新的 java.time包 中包含了如下类:
-
LocalDate:表示一个没有时区的日期(年月日),用于处理日期。
LocalDate localDate = LocalDate.now(); //获取当前日期 System.out.println(localDate); LocalDate localDate2 = LocalDate.of(2000,1,1); //指定日期 System.out.println(localDate2); int year = localDate2.getYear();//获取年 2000 Month month = localDate2.getMonth();//获取月 JANUARY int dayOfMonth = localDate2.getDayOfMonth(); //获取日 1 DayOfWeek week = localDate2.getDayOfWeek();//获取星期几 SATURDAY int dayOfYear = localDate2.getDayOfYear();//获取一年中的第几天 1 LocalDate plussedDays = localDate2.plusDays(10);//增加天数 2000-01-21 LocalDate minusedDays = localDate2.minusDays(10);//减少天数 1999-12-22 LocalDate plussedMonths = localDate2.plusMonths(10);//增加月 2000-11-01 LocalDate minusedMonths = localDate2.minusMonths(10);//减少月 1999-03-01 LocalDate plussedYears = localDate2.plusYears(10);//增加年 2010-01-01 LocalDate minusedYears = localDate2.minusYears(10);//减少年 1990-01-01 LocalDate withYear = localDate2.withYear(2020);//设置年 2020-01-01
-
LocalTime:表示一个没有日期的时间(时分秒和纳秒),用于处理时间。
System.out.println(LocalTime.now()); //获取当前时间 LocalTime localTime2 = LocalTime.of(10,10,10); //指定时间 System.out.println(localTime2); int hour = localTime2.getHour();//获取时 10 int minute = localTime2.getMinute();//获取分 10 int second = localTime2.getSecond();//获取秒 10 LocalTime plusHours = localTime2.plusHours(1);//增加小时 11:10:10 LocalTime minusHours = localTime2.minusHours(1);//减少小时 09:10:10 LocalTime plusMinutes = localTime2.plusMinutes(1);//增加分钟 10:11:10 LocalTime minusMinutes = localTime2.minusMinutes(1);//减少分钟 10:09:10 LocalTime plusSeconds = localTime2.plusSeconds(1);//增加秒 10:10:11 LocalTime minusSeconds = localTime2.minusSeconds(1);//减少秒 10:10:09 LocalTime withHour = localTime2.withHour(20);//设置时 20:10:10 LocalTime withMinute = localTime2.withMinute(20);//设置分 10:20:10 LocalTime withSecond = localTime2.withSecond(20);//设置秒 10:10:20
-
LocalDateTime:表示没有时区的日期和时间,是
LocalDate
和LocalTime
的组合,适用于需要同时处理日期和时间但不涉及时区的情况。System.out.println(LocalDateTime.now()); //获取当前日期时间 LocalDateTime localDateTime2 = LocalDateTime.of(2000,1,1,10,10,10);//指定日期时间 System.out.println(localDateTime2); int year = localDateTime2.getYear();//获取年 2000 Month month = localDateTime2.getMonth();//获取月 JANUARY int dayOfMonth = localDateTime2.getDayOfMonth();//获取日 1 DayOfWeek dayOfWeek = localDateTime2.getDayOfWeek();//获取星期几 SATURDAY int dayOfYear = localDateTime2.getDayOfYear();//获取一年中的第几天 1 int hour = localDateTime2.getHour();//获取时 10 int minute = localDateTime2.getMinute();//获取分 10 int second = localDateTime2.getSecond();//获取秒 10 LocalDateTime plusDays = localDateTime2.plusDays(10);//增加天数 2000-01-21T10:10:10 LocalDateTime minusDays = localDateTime2.minusDays(10);//减少天数 2000-01-01T10:10:10 LocalDateTime plusMonths = localDateTime2.plusMonths(10);//增加月 2000-11-01T10:10:10 LocalDateTime minusMonths = localDateTime2.minusMonths(10);//减少月 2000-03-01T10:10:10 LocalDateTime plusYears = localDateTime2.plusYears(10);//增加年 2010-01-01T10:10:10 LocalDateTime minusYears = localDateTime2.minusYears(10);//减少年 1990-01-01T10:10:10 LocalDateTime withYear = localDateTime2.withYear(2020);//设置年 2020-01-01T10:10:10 LocalDateTime withHour = localDateTime2.withHour(20);//设置时 20:10:10 LocalDateTime withMinute = localDateTime2.withMinute(20);//设置分 10:20:10 LocalDateTime withSecond = localDateTime2.withSecond(20);//设置秒 10:10:20
-
ZoneId:代表一个时区
-
ZonedDateTime:代表一个时区化的日期、时间
System.out.println(ZonedDateTime.now()); //获取当前带时区的日期和时间。 ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime2,ZoneId.of("Asia/Shanghai"));//根据给定的 LocalDateTime 和时区创建 ZonedDateTime。 System.out.println(zonedDateTime2); int year = zonedDateTime2.getYear();//获取年 2000 Month month = zonedDateTime2.getMonth();//获取月 JANUARY int dayOfMonth = zonedDateTime2.getDayOfMonth();//获取日 1 DayOfWeek dayOfWeek = zonedDateTime2.getDayOfWeek();//获取星期几 SATURDAY int dayOfYear = zonedDateTime2.getDayOfYear();//获取一年中的第几天 1 int zonedDateTime2Hour = zonedDateTime2.getHour();//获取时 10 int zonedDateTime2Minute = zonedDateTime2.getMinute();//获取分 10 int second = zonedDateTime2.getSecond();//获取秒 10
-
Duration :类用于处理时间的持续性,以秒和纳秒为单位。它主要用于计算两个时间点之间的差异,并且是不可变且线程安全的。
1、between(Temporal startInclusive, Temporal endExclusive) //计算两个时间点之间的持续时间。 2、ofDays(long days) //创建表示指定天数的持续时间。 3、ofHours(long hours) //创建表示指定小时数的持续时间。 4、ofMinutes(long minutes) //创建表示指定分钟数的持续时间。 5、ofSeconds(long seconds):创建表示指定秒数的持续时间。 6、plus(Duration durationToAdd) //返回一个新的 Duration,将当前 Duration 与另一个 Duration 相加。 7、minus(Duration durationToSubtract) //返回一个新的 Duration,从当前 Duration中减去另一个 Duration。
-
Period:用于处理日期之间的期间,以年、月、日为单位。它也是不可变和线程安全的,适用于日期单位的计算。
1、between(LocalDate startDateInclusive, LocalDate endDateExclusive) //计算两个日期之间的期间。 2、ofYears(int years) //创建表示指定年数的期间。 3、ofMonths(int months) //创建表示指定月数的期间。 4、ofWeeks(int weeks) //创建表示指定周数的期间。 5、ofDays(int days) //创建表示指定天数的期间。 6、plus(Period periodToAdd) //返回一个新的 Period,将当前 Period 与另一个 Period 相加。 7、minus(Period periodToSubtract) //返回一个新的 Period,从当前 Period 中减去另一个 Period。
-
InstantSource:Java17中引入的规范性接口,是一个可以获取时刻(Instant)的通用接口。Clock就是其实现类。
- Clock:获取指定时区的当前日期、时间,可取代currentTimeMillis() 方法。
-
Instant:表示一个具体的时刻,可精确到纳秒。其静态方法now获取当前时刻,now(Clock)获取Clock对应的时刻。还可以通过一系列的minusXxx()方法在当前时刻基础上减去一段时间,也可以通过plusXxx()方法加上一段时间。
-
Monthday:概念代表日月。其静态方法now获取当前月份和日期,now(Clock)获取Clock对应的月份和日期。
-
Year:代表年,其静态方法now获取当前的年份,now(Clock)获取Clock对应的年份。还可以通过minusYears()方法在当前年份基础上减去几年,也可以通过plusYears()方法加上几年。
-
YearMonth:代表年月,其静态方法now获取当前的年月,now(Clock)获取Clock对应的年月。
-
DayOfWeek:是一个枚举类,定义了周日到周六的枚举值
-
Month:一个枚举类,定义了一到十二月的枚举值
-
TemporalAdjuster :时间校正器。
2、格式化日期
java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:
-
预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
-
本地化相关的格式。
- 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss“)
//时间
LocalDateTime dateTime2 = LocalDateTime.of(1999, 12, 2, 12, 22, 22);
System.out.println(dateTime2);
//第一种格式化
DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
String format1 = dateTimeFormatter1.format(dateTime2);
System.out.println(format1);
//第二种格式化
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
String format2 = dateTimeFormatter2.format(dateTime2);
System.out.println(format2);
//第三种格式化
DateTimeFormatter dateTimeFormatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
String format3 = dateTimeFormatter3.format(dateTime2);
System.out.println(format3);
7.10、对象比较器
在Java中,对对象进行排序和比较是一项常见的需求,尤其是在处理集合时。Java提供了两种主要方式来比较对象:实现Comparable
接口和使用Comparator
接口。
7.10.1、Comparable
java.lang.Comparable接口,用于定义对象自然排序的方式。类通过实现Comparable
接口的compareTo
方法来定义其对象的排序顺序,默认从小到大。
public interface Comparable<T> {
int compareTo(T o);
}
实现Comparable
接口的类可以被Java的任何基于比较的标准排序方法使用,如Collections.sort()
和Arrays.sort()
。此外,这些对象可以用于自动排序的数据结构,如TreeSet
和TreeMap
。
-
类实现Comparable接口
-
重写接口的compareTo(Object obj) 方法,通过此方法对比两个对象的大小(如果当前对象this大于形参对象,则返回正整数,如果当前对象this小于形参对象,则返回负整数,如果当前对象this等于形参对象,则返回零。)
public class Person implements Comparable<Person> { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public int compareTo(@NotNull Person p) { //(x < y) ? -1 : ((x == y) ? 0 : 1); 此处xy为age的值 return Integer.compare(this.age, p.age); } @Override public String toString() { return "Person{" + "name='" + name + '\'' +", age=" + age +'}'; } }
public static void main(String[] args) { List<Person> list =new ArrayList<>(); list.add(new Person("张三", 25)); list.add(new Person("李四", 19)); list.add(new Person("王五", 32)); list.add(new Person("赵六", 18)); //排序 Collections.sort(list); list.forEach(System.out::println); }
7.10.2、Comparator
定制排序:java.util.Comparator接口,用于定义对象比较规则的工具,尤其是当这些对象的类没有实现Comparable
接口或你需要除类的自然排序之外的其他排序方式时。Comparator
提供了一种灵活的策略,使你能够控制多种类型的比较逻辑。
Comparator
是一个功能接口(functional interface),它定义了一个比较两个对象的方法和一些静态方法,其中compare方法的用法和compareTo一样,需要子类重写。
除了compare外,还可以直接使用Comparator的静态方法来进行排序。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// 其他默认和静态方法
}
重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
这是一个功能接口,内部定义了一些比较规则:
-
comparing():一个静态辅助方法,用于生成一个比较器,该比较器根据提供的函数对对象的某个属性进行比较。
Comparator<Student> byName = Comparator.comparing(Student::getName);
-
comparingInt()、comparingLong()、comparingDouble():用于处理基本类型字段的比较,避免了装箱操作,提高性能。
Comparator<Student> byScore = Comparator.comparingInt(Student::getScore);
-
thenComparing():进行多级排序,如果主比较器认为两个对象相等,那么可以使用另一个比较器来进行进一步的比较。
Comparator<Student> byNameThenByScore = Comparator.comparing(Student::getName) .thenComparingInt(Student::getScore);
-
reversed():逆序排序
Comparator<Student> reverseByName = Comparator.comparing(Student::getName).reversed();
-
nullsFirst() 和 nullsLast():用于处理比较中的
null
值,可以生成一个新的比较器,该比较器将null
视为小于或大于非null
值。Comparator<Student> byNameNullFirst = Comparator.comparing(Student::getName, Comparator.nullsFirst(String::compareTo));
-
naturalOrder()和 reverseOrder():提供了对自然排序和其逆序的快捷访问。
Comparator<Integer> natural = Comparator.naturalOrder(); Comparator<Integer> reverse = Comparator.reverseOrder();
类除了实现Comparator的compare方法外,还能直接使用其静态方法,这样类可以不用实现Comparator也能进行排序。
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//get、set、toString等方法省略
}
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("张三", 25));
list.add(new Person("李四", 10));
list.add(new Person("王五", 21));
list.add(new Person("赵六", 32));
//使用Comparator提供的静态方法,按年龄进行排序
Comparator<Person> comparator = Comparator.comparingInt(Person::getAge);
list.sort(comparator);
list.forEach(System.out::println);
//使用Comparator提供的静态方法,按名字逆序排序
System.out.println("按名字排序:");
Comparator<Person> comparator1 = Comparator.comparing(Person::getName);
list.sort(comparator1.reversed()); //逆序
list.forEach(System.out::println);
}