目录
引言
如何在idea中查看类的Diagram(类图)
(1)按住Alt后点击想要查看的类定位到类的源文件;
(2)在类的源文件中右键单击Diagrams后选择show Diagrams。
以 String类 为示例:
类图只是 UML 图的一种,UML图包括九种类型,分静态图和动态图两种,其中常用的静态图5种,动态图4种。
① 常用的静态图:用例图、类图、包图、对象图、部署图
② 常用的动态图:顺序图,通信图(UML1.x 时称为协作图),状态机图,活动图
idea中类图的各部分
debug 中 Step Into 和 Step Out
若想查看某个语句在底层具体是如何执行的,可以 dubug
某个语句,并利用 Step Into
和 Step Out
来查看底层是如何一步一步执行的。
String
String 类的理解和创建对象
(1) String 对象用语保存字符串,也就是一组字符序列
(2) 字符串常量对象是双引号括起的字符序列。例如:“你好”,“123”,“boy” 等
(3) 字符串的字符在底层使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节
(4) String类较常用构造器(其它看手册):
- String s1 = new String();
- String s2 = new String(String original);
- String s3 = new String(char[] a);
- String s4 = new String(char[] a,int startIndex,int count);
解读:
*① String 实现了 Serializable 说明 String对象 可以串行化(可以串行化的意思就是说可以在网络传输也可以保存在文件中)
*② String 实现了 Comparable 接口,说明 String对象 可以比较(根据compareTo方法中的规则进行比较)
③ String 实现了 CharSequence接口,CharSequence接口 就是一个字符序列接口
④ 继承父类 Object类
代码示例:
public class String01 {
public static void main(String[] args) {
//1.String 对象用于保存字符串,也就是一组字符序列
//2. "jack" 字符串常量, 双引号括起的字符序列;("jack"是字符串常量,是个具体的值;但name是变量,可以对name再次进行赋值,如:name="tom")
//3. 字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节
//4. String 类有很多构造器,构造器的重载
// 常用的有 String s1 = new String(); //
//String s2 = new String(String original);
//String s3 = new String(char[] a);
//String s4 = new String(char[] a,int startIndex,int count)
//String s5 = new String(byte[] b)
//5. String 类实现了接口 Serializable【String 可以串行化:可以在网络传输】
// 接口 Comparable [String 对象可以比较大小]
//6. String 是 final 类,不能被其他的类继承
//7. String 有属性 private final char value[]; 用于存放字符串内容
//8. 一定要注意:value 是一个 final 类型, 不可以修改:即 value 不能指向新的地址,但是单个字符内容是可以变化
String name = "jack";
name = "tom";
final char[] value = {'a','b','c'};
char[] v2 = {'t','o','m'};
value[0] = 'H';
//value = v2; 不可以修改 value 地址
}
}
补充:
7. String 有属性 private final char value[]; 用于存放字符串内容
翻译:该值用于字符存储
解读:也就是说我们在保存一个字符串时,其实它真正保存到了value字符数组这个属性中去了。也就是说,在底层它仍然是一个char数组;所以这也是为什么说“字符串的本质其实还是char数组”
补充:
8. 一定要注意:value 是一个 final 类型, 不可以修改(需要功力):即 value 不能指向新的地址,但是单个字符内容是可以变化
回顾被final、static修饰的属性初始化时间段
① final修饰的属性的初始化可以在编译期 [1],也可以在运行期 [2],初始化后不能被改变;不是类共享的,每个对象都有专属自己的一个,可以用this指代(解读:这里的编译期是指进行类加载 [*] 时,也就是将final与static一起连用或写入代码块或写构造器中时,会在类加载的初始化阶段进行初始化;而运行期是指编译完成后进行类加载并执行到该对应初始化语句时进行初始化【比如在方法中定义一个局部变量并用final修饰】)
② static修饰的属性的初始化在编译期 [3](类加载的时候),初始化后能改变;是类共享的,就一个,因此不能用this指代(解读:即在进行类加载时,就会对静态属性和方法、代码块、构造器等进行初始化)
[*] 在 Java 中,类的加载主要分为以下三个步骤:加载、连接(验证,准备;解析)、初始化
小结:
① String类底层最重要的就是它真正存放字符序列的是它的一个属性value(字符数组),而且这个属性是被final修饰的;
② String类是final类;
③ String类实现了两个重要接口:Serializable 和 Comparable 接口,意味着它可以串行化同时可以进行比较大小。
创建 String 对象的两种方式与区别
- 方式一:直接赋值 String s1 = “hsp”;
先从常量池查看是否有 “hsp” 数据空间,如果有,直接指向;如果没有则重新创建,然后指向。s1 最终指向的是常量池的空间地址。
- 方式二:调用构造器 String s2 = new String(“hsp”);
先在堆中创建空间,里面维护了 value 属性,指向常量池的 hsp 空间。如果常量池没有 “hsp” ,重新创建,如果有,直接通过 value 指向。最终指向的是堆中的空间地址。
由第二张图可以看出来字符串常量池中的字符串也是带有字符数组属性的,因此,若是将字符串常量直接付给一个String对象,就形如图中original变量一样,此String引用直接指向字符串常量池中的String对象;而若是通过构造器new出来的一个对象,就如s1,s2变量一样,先在堆中开辟一个String对象内含属性字符数组value,然后字符数组value再指向 与匹配到的字符串常量池中的字符 ① 中的属性字符数组 ② 指向的同一片内存区域 ③ 。( ①②③位置如上图所示 )
测试题
1)
public class StringExercise01 {
public static void main(String[] args) {
String a = "abc";
String b ="abc";
System.out.println(a.equals(b));//T
System.out.println(a==b); //T
}
}
equals:比较引用类型默认也是比较地址值是否相同,注意:String类重写了equals()方法,比较的是内容是否相同
==:比较引用类型比较的是地址值是否相同
3)
public class StringExercise03 {
public static void main(String[] args) {
String a = "hsp"; //a 指向 常量池的 “hsp”
String b =new String("hsp");//b 指向堆中对象
System.out.println(a.equals(b)); //T
System.out.println(a==b); //F
//b.intern() 方法返回常量池地址
System.out.println(a==b.intern()); //T
System.out.println(b==b.intern()); //F
}
}
知识点:当调用.intern()方法时,如果池已经包含一个等于此 String 对象的字符串(底层用equals(Object)方法来确定的),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用
4)
public class StringExercise04 {
public static void main(String[] args) {
String s1 = "hspedu"; //指向常量池”hspedu”
String s2 = "java"; //指向常量池”java”
String s4 = "java";//指向常量池”java”
String s3 = new String("java");//指向堆中对象
System.out.println(s2 == s3); // F
System.out.println(s2 == s4); //T
System.out.println(s2.equals(s3));//T
System.out.println(s1 == s2); //F
}
}
5)
public class StringExercise05 {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "hspedu";
Person p2 = new Person();
p2.name = "hspedu";
System.out.println(p1.name.equals(p2.name));//比较内容: T
System.out.println(p1.name == p2.name); //T
System.out.println(p1.name == "hspedu"); //T
String s1 = new String("bcde");
String s2 = new String("bcde");
System.out.println(s1==s2); //F
}
}
class Person {
public String name;
}
总结:“System.out.println(s1==s2); //False” ===> 只要是new String(),则栈中的地址都是指向最新的new出来的堆中的地址
内存图:
字符串的特性
(1)String 是一个 final 类,代表不可变的字符序列
(2)字符串是不可变的。一个字符串对象一旦被分配,期内容是不可变的(即在字符串常量池中,一个字符串对象一旦被分配,它所在的地址和内容都不能再变了)
测试题
1)以下语句创建了几个对象?画出内存布局图。
String s1 = "hello";
s1 = "haha";
答:创建了2个对象
2)String a = “hello” + “abc”;创建了几个对象?
答:创建了1个对象
分析
1.编译器不傻,做一个优化,判断创建的常量池对象是否 有引用指向(这里的“是否有引用指向”意思就是说编译器会判断若要在字符串常量池生成一个对象,是否有引用去指向它;在本例中显然 hello 和 abc 都没有引用去指向它,只有 helloabc 有引用 a 去指向,故编译器不去生成 hello 和 abc 而是直接生成 helloabc )
2.Stirng a = “hello” + “abc”; => Stirng a = “helloabc”;
Kim:总的来说就是编译器会优化,优化的原因是若将 hello 和 abc
都单独创建一份儿,再生成一个helloabc,那么就会造成内存浪费。因为实际上 hello 和 abc
并没有被引用指向,通俗的讲就是:压根没用。
3)
String a = "hello"; //创建 a对象
String b = "abc";//创建 b对象
String c = a + b;创建了几个对象?画出内存图?
答:创建了3个对象
public class StringExercise08 {
public static void main(String[] args) {
String a = "hello"; //创建 a对象
String b = "abc";//创建 b对象
//老韩解读
//1. 先 创建一个 StringBuilder sb = StringBuilder()
//2. 执行 sb.append("hello");
//3. 执行 sb.append("abc");
//4. String c= sb.toString()
//最后其实是 c 指向堆中的对象(String) -> 堆中的对象(String)中的属性value[] -> 池中 "helloabc"
String c = a + b;
String d = "helloabc";
System.out.println(c == d);//真还是假? 是false
String e = "hello" + "abc";//直接看池, e指向常量池
System.out.println(d == e);//真还是假? 是true
}
}
注释配套图:
1.
2.
3.
4.
//最后其实是 c 指向堆中的对象(String) -> 堆中的对象(String)中的属性value[] -> 池中 “helloabc” 内存布局图如下:
小技巧:这里学到了一下debug某个语句时,如何利用Step Into和Step Out来查看底层是如何完成这个语句的
小结::底层是StringBuilder sb = new StringBuilder (); ab.append(a); ab.append(b); sb是在堆中,并且append是在原来字符串的基础上追加的。
规则+结论:常量相加,看的是池;变量相加,是在堆中。||| 如果拼接的字符串中 包含变量 ,则在编译时编译器采用StringBuilder对其进行优化,即自动创建StringBuilder实例并调用其append()方法,将这些字符串拼接在一起
扩展思考:String a = “hello”; String b = “abc”; String c = a + b; System.out.println((a+b)==c); // 输出false
理由:String c = a + b; 中的a+b会new一个新对象返回给c;而System.out.println((a+b)==c); 中的a+b会再次new一个新对象与c的地址进行比较。故就相当于new了两次,而new出来的引用指向的地址又肯定不同,所以会输出false
4)下面的代码输出什么,并说明原因
public class StringExercise09 {
public static void main(String[] args) {
String s1 = "hspedu"; //s1 指向池中的 “hspedu”
String s2 = "java"; // s2 指向池中的 “java”
String s5 = "hspedujava"; //s5 指向池中的 “hspedujava”
String s6 = (s1 + s2).intern();//s6 指向池中的 “hspedujava”
System.out.println(s5 == s6); //T
System.out.println(s5.equals(s6));//T
}
}
5)下列程序运行的结果是什么,尝试画出内存布局图
答:输出 “hsp and hava”
class Test1 {
String str = new String("hsp");
final char[] ch = {'j', 'a', 'v', 'a'};
public void change(String str, char ch[]) {
str = "java"; // 这里改变的是形参,若此处为 this.str = "java"; 则改变的就是成员变量,故输出结果就是 "java and hava"
ch[0] = 'h';
}
public static void main(String[] args) {
Test1 ex = new Test1();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");
System.out.println(ex.ch);
}
}
在栈中的change解读:只要调一个方法,就会产生一个新栈(方法栈(存放方法的局部变量等))
上述题目与下列代码进行比较,可以看出String类和普通类对象在内存进行操作时是有所不同的。
public class Jxl {
public A a = new A();
public void change(A a) {
a.num = 4;
}
public static void main(String[] args) {
Jxl jxl = new Jxl();
jxl.change(jxl.a);
System.out.println(jxl.a.num);
}
}
class A {
int num = 7;
}
String 类的常见方法
虽然String类的拼接效率比较低 [*] ,但是它在某些情况下还是非常实用的。比如做一些配置文件或表示一些常量的值。下面介绍一下String类的常用方法,这些方法不必要去背,只要知道怎么去使用即可。到后面用的比较久了,用的时间比较长了,自然也就熟悉了。
[*] 例子,如下面这段代码:
String s = new String("");
for(int i=0;i<80000;i++) {
s += "hello";
}
1.equals 比较内容是否相同,区分大小写
2.equalsIgnoreCase 忽略大小写的判断内容是否相等
3.length 获取字符的个数,字符串的长度
4.indexOf 获取字符(子字符串)在字符串对象中第一次出现的索引,索引从 0 开始,如果找不到,返回 -1【若为子字符串且找到了,则返回的是子字符串第一个字符的索引位置】
5.lastIndexOf 获取字符(子字符串)在字符串中最后一次出现的索引,索引从 0 开始,如果找不到,返回 -1【若为子字符串且找到了,则返回的是子字符串第一个字符的索引位置】
6.substring 截取指定范围的子串
7.trim 去前后空格
8.charAt 获取某索引处的字符,注意不能使用Str[index] 这种方式
9.toUpperCase 转换成大写
10.toLowerCase 转换成小写
11.concat 拼接字符串
源码如下:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len); // 将str中的字符从len的位置开始复制到buf中
return new String(buf, true);
}
/**
* getChars(char dst[], int dstBegin)
* Copy characters from this string into dst starting at dstBegin.
* This method doesn't perform any range checking.
* 翻译:将此字符串中的字符从dstBegin开始复制到dst中。此方法不执行任何范围检查。
*/
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
12.replace 替换字符串中的字符(参数列表中前面的替换后面的)
注意:9、10、11、12方法均对原来字符串没有影响,返回的是一个新的字符串
13.split 分割字符串
14.compareTo 比较两个字符串的大小,如果前者大,则返回正数,后者大,则返回负数,如果相等,返回 0
15.toCharArray 转换成字符数组
16.toCharArray 转换成字符数组
17.format 格式字符串(%s , %d , %.2f,%c 称为占位符)
(1)这些占位符由后面变量来替换
(2)%s 表示后面由 字符串来替换
(3)%d 是整数来替换
(4)%.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
详解:%f:浮点类型,一个%f对应一个浮点类型
// %f 浮点型输出,默认精确到小数点后六位
// %.5f 浮点型输出,保留小数点后5位,不够5位的零来补
System.out.println(String.format("%f %.2f %.5f", 7.0 , 8.0 , 9.99));
// 输出
7.000000 8.00 9.99000
(5)%c 使用 char 类型来替换
public class StringMethod01 {
public static void main(String[] args) {
//1. equals 前面已经讲过了. 比较内容是否相同,区分大小写
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));//
// 2.equalsIgnoreCase 忽略大小写的判断内容是否相等
String username = "johN";
if ("john".equalsIgnoreCase(username)) {
System.out.println("Success!");
} else {
System.out.println("Failure!");
}
// 3.length 获取字符的个数,字符串的长度
System.out.println("韩顺平".length());
// 4.indexOf 获取字符在字符串对象中第一次出现的索引,索引从0开始,如果找不到,返回-1
String s1 = "wer@terwe@g";
int index = s1.indexOf('@');
System.out.println(index);// 3
System.out.println("weIndex=" + s1.indexOf("we"));//0
// 5.lastIndexOf 获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1
s1 = "wer@terwe@g@";
index = s1.lastIndexOf('@');
System.out.println(index);//11
System.out.println("ter的位置=" + s1.lastIndexOf("ter"));//4
// 6.substring 截取指定范围的子串
String name = "hello,张三";
//下面name.substring(6) 从索引6开始截取后面所有的内容
System.out.println(name.substring(6));//截取后面的字符
//name.substring(0,5)表示从索引0开始截取,截取到索引 5的位置(编程遵从左闭右开)
System.out.println(name.substring(2,5));//llo
}
}
public class StringMethod02 {
public static void main(String[] args) {
// 1.toUpperCase转换成大写
String s = "heLLo";
System.out.println(s.toUpperCase());//HELLO
// 2.toLowerCase
System.out.println(s.toLowerCase());//hello
// 3.concat拼接字符串
String s1 = "宝玉";
s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together");
System.out.println(s1);//宝玉林黛玉薛宝钗together
// 4.replace 替换字符串中的字符
s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
//在s1中,将 所有的 林黛玉 替换成薛宝钗
// 老韩解读: s1.replace() 方法执行后,返回的结果才是替换过的.
// 注意对 s1没有任何影响
String s11 = s1.replace("宝玉", "jack");
System.out.println(s1);//宝玉 and 林黛玉 林黛玉 林黛玉
System.out.println(s11);//jack and 林黛玉 林黛玉 林黛玉
// 5.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等
String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
//老韩解读:
// 1. 以 , 为标准对 poem 进行分割 , 返回一个数组
// 2. 在对字符串进行分割时,如果有特殊字符,需要加入 转义符 \
String[] split = poem.split(",");
poem = "E:\\aaa\\bbb";
split = poem.split("\\\\");
System.out.println("==分割后内容===");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
// 6.toCharArray 转换成字符数组
s = "happy";
char[] chs = s.toCharArray();
for (int i = 0; i < chs.length; i++) {
System.out.println(chs[i]);
}
// 7.compareTo 比较两个字符串的大小,如果前者大,
// 则返回正数,后者大,则返回负数,如果相等,返回0
// 老韩解读
// (1) 如果长度相同,并且每个字符也相同,就返回 0
// (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小
// 就返回 if (c1 != c2) {
// return c1 - c2;
// }
// (3) 如果前面的部分都相同,就返回 str1.len - str2.len
String a = "jcck";// len = 3
String b = "jack";// len = 4
System.out.println(a.compareTo(b)); // 返回值是 'c' - 'a' = 2的值
// 8.format 格式字符串
/* 占位符有:
* %s 字符串 %c 字符 %d 整型 %.2f 浮点型
*
*/
String name = "john";
int age = 10;
double score = 56.857;
char gender = '男';
//将所有的信息都拼接在一个字符串.
String info =
"我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我!";
System.out.println(info);
//老韩解读
//1. %s , %d , %.2f %c 称为占位符
//2. 这些占位符由后面变量来替换
//3. %s 表示后面由 字符串来替换
//4. %d 是整数来替换
//5. %.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
//6. %c 使用char 类型来替换
String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!";
String info2 = String.format(formatStr, name, age, score, gender);
System.out.println("info2=" + info2);
}
}
StringBuilder类
基本介绍
1. 一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API ,但不保证同步(StringBuilder 不是线程安全)。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区单个线程使用的时候。如果可以,建议有限采用该类,因为在大多数实现中,它比 StringBuffer 要快
2.在StringBuilder 上的主要操作是 append 和 insert 方法,可以重载这些方法,以接收任意类型的数据
public class StringBuilder01 {
public static void main(String[] args) {
//老韩解读
//1. StringBuilder 继承 AbstractStringBuilder 类
//2. 实现了 Serializable ,说明StringBuilder对象是可以串行化(对象可以网络传输,可以保存到文件)
//3. StringBuilder 是final类, 不能被继承
//4. StringBuilder 对象字符序列仍然是存放在其父类 AbstractStringBuilder的 char[] value;
// 因此,字符序列是堆中
//5. StringBuilder 的方法,没有做互斥的处理,即没有synchronized 关键字,因此在单线程的情况下使用
// 同样的,由于 StringBuilder 的方法都没有实现线程安全,所以在多线程的情况下,用 StringBuilder 是有风险的
StringBuilder stringBuilder = new StringBuilder();
}
}
日期时间
以下类的介绍中,内容大多是方法,所见即所得,会用即可
第一代日期类
(1)Date:精确到毫秒,代表特定的瞬间
(2)SimpleDateFormat:格式和解析日期的类(和Date配套使用的)
SimpleDateFormat:格式化和解析日期的具体类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化
示例代码:
public class Date01 {
public static void main(String[] args) throws ParseException {
//老韩解读
//1. 获取当前系统时间
//2. 这里的Date 类是在java.util包(java.sql包下也有一个Date,但是这个是和数据库相关的,不要引这个)
//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
Date d1 = new Date(); //获取当前系统时间
System.out.println("当前日期=" + d1);
// 输出:当前日期=Thu Sep 14 12:21:39 CST 2023
// 是国外的方式,这里的CST是美国中央时区的缩写
Date d2 = new Date(9234567); //通过指定毫秒数得到时间
// 计算方法就是通过ms&s&m&h之间转换算出结果加到1970年1月1日 00:00:00 000上
System.out.println("d2=" + d2); //获取某个时间对应的毫秒数
//
//老韩解读
//1. 创建 SimpleDateFormat对象,可以指定相应的格式
//2. 这里的格式使用的字母是规定好,不能乱写
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
System.out.println("当前日期=" + format);
//老韩解读
//1. 可以把一个格式化的String 转成对应的 Date
//2. 得到Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
//3. 在把String -> Date , 使用的 sdf 格式需要和你给的String的格式一样,否则会抛出转换异常(throws ParseException)( parse 译为解析)
String s = "1996年01月01日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println("parse=" + sdf.format(parse));
}
}
知识点:
① 计算机开始计算时间:是从1970年1月1号0分0秒开始
② SimpleDateFormat类格式化符号
只要记住符号即可,具体格式(即谁前谁后)都可以,由程序员自己决定。如下面的例子。
③ SimpleDateFormat类的具体使用
1.Date 转 String 可使用 format(Object obj),String转Date可使用parse(String source);
2.时间字串String转Date时,parse时如果时间字串与模式字串不匹配会报ParseException异常。
Date d = new Date();
System.out.println(d);
// 要转换的格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
SimpleDateFormat sdf1 = new SimpleDateFormat(
"一年中的第 D 天 一年中第w个星期 一月中第W个星期 E a 在一天中K时 z时区");
// 格式化日期,日期->字符串
String formatDate = sdf.format(d);
String formatDate1 = sdf1.format(d);
System.out.println(formatDate);
System.out.println(formatDate1);
String s = "2022-07-13 15:12:34 000";
try {
Date date = sdf.parse(s);//格式化日期,字符串->日期
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
第二代日期类
(1)第二代日期类,主要就是 Calendar 类( calendar 译为日历)
(2)Calendar 类是一个抽象类,它为特定瞬间与一组诸如YEAR、MONTH、DAY_OF_MONTH、HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。
示例代码:
public class Calendar_ {
public static void main(String[] args) {
//老韩解读
//1. Calendar是一个抽象类, 并且构造器是private
//2. 可以通过 getInstance() 来获取实例
//3. 提供大量的方法和字段提供给程序员
//4. Calendar没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
//5. 如果我们需要按照 24小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由
System.out.println("c=" + c);
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
// 这里为什么要 + 1, 因为Calendar 返回月时候,是按照 0 开始编号
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:" + c.get(Calendar.HOUR));
// Calender 拿到的是12进制的小时数
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));
//Calender 没有专门的格式化方法,所以需要程序员自己来组合显示 优点:比较灵活;缺点:比较麻烦
System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH) +
" " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) );
}
}
详解注释1&2:1. Calendar是一个抽象类, 并且构造器是private;2. 可以通过 getInstance() 来获取实例
由源码可知“Calendar是一个抽象类, 并且构造器是private”,故无法通过new来实例化一个对象,但Calendar类提供了一个静态方法getInstance()可以返回一个实例,这个方法内调用了另一个静态方法createCalendar(TimeZone zone, Locale aLocale)。
而静态方法createCalendar(TimeZone zone, Locale aLocale)会返回一个Calendar类的子类实例。
详解获取日历对象的某个日历字段:需要用.get加字段才能获取所需信息,(要通过get方法来获取,不能直接获取),因为 Calendar 是个类,并没有和对象关联起来,应该用对象.get然后指定要获取 Calendar 类中的哪个字段(这些字段本身都是静态的,即类共享的,需要通过get方法中逻辑处理后才能返回对象对应的字段值),源码如下:
第三代日期
第三代日期出现的原因:前两代日期类的不足
JDK1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK1.1引入 Calendar 类之后被弃用了。而 Calendar 也存在的问题是(知道即可,若在面试中被问到 前两代日期类 有什么问题,只要能简单回答一下即可):
1)可变性:像日期和时间这样的类应该是不可变的。
2)偏移性:Date中的年份是从1900开始的,而月份都从0开始。
3)格式化:格式化只对Date有用,Calendar 则不行。
4)此外,它们也不是线程安全的(若有多线程操作,那么会存在线程安全问题);不能处理闰秒问题(闰秒问题:每个2天,多出1s)
第三代日期类常见方法
1)LocalDate(日期/年月日)、LocalTime(时间/时分秒)、LocalDateTime(日期时间/年月日时分秒) JDK8加入
public class LocalDate_ {
public static void main(String[] args) {
//第三代日期
//老韩解读
//1. 使用now() 返回表示当前日期时间的 对象
LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now()
System.out.println(ldt);
//2. 使用DateTimeFormatter 对象来进行格式化
// 创建 DateTimeFormatter对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);
System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());
LocalDate now = LocalDate.now(); //可以获取年月日
LocalTime now2 = LocalTime.now();//获取到时分秒
//提供 plus 和 minus方法可以对当前时间进行加或者减
//看看890天后,是什么时候 把 年月日-时分秒
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890天后=" + dateTimeFormatter.format(localDateTime));
//看看在 3456分钟前是什么时候,把 年月日-时分秒输出
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
}
}
LocalDateTime new后输出的为:2023-09-14T14:11:03.519(其中T表示时间戳)
“时间戳是一种时间表示方式,它是以秒为单位表示从1970年1月1日0时0分0秒(GMT)开始流逝的时间,按秒计算是一个32位整数,也可用缩写为Timestamps(TS)。” 【32位有符号整数是一种整数类型,它可以表示范围为-231到231-1之间的整数值,其中2^31表示2的31次方,即2的31次幂】
DateTimeFormatter格式日期类
类似于SimpleDateFormat
DateTimeFormatter dtf= DateTimeFormatter.ofPattern(格式);
String str = dtf.format(日期对象);
Instant 时间戳
类似于Date
提供了一系列和Date类转换的方式
public class Instant_ {
public static void main(String[] args) {
//1.通过 静态方法 now() 获取表示当前时间戳的对象
Instant now = Instant.now();
System.out.println(now);
//2. 通过 from 可以把 Instant转成 Date
Date date = Date.from(now);
//3. 通过 date的toInstant() 可以把 date 转成Instant对象
Instant instant = date.toInstant();
}
}
LocalTimeDate类能不能解决闰秒问题?
答:可以,它知道闰年,比如2020-02-29 减一年的话会变成2019-02-28