Java基础学习——第九章 Java常用类
一、String类及其常用方法
1. String的特性:不可变的字符序列(不可变性)
- String类:表示字符串,使用一对双引号引起来表示。所有字符串字面量(如 “abc”),都是String类的实例对象
- String类声明为final,不可被继承
- String类对象的字符内容存储在一个char型数组中的,该数组声明为final(常量),代表**不可变的字符序列(不可变性):char型数组的引用类型变量value不能被重新赋值,且数组中的元素也不可被修改**
- 当对String对象重新赋值时,需要重新指定内存区域创建一个新的char型数组存储新的字符串常量,而不能修改原有char型数组中的元素
- 当对现有字符串进行连接操作时,也需要重新指定内存区域创建一个新的char型数组存储连结后的字符串常量,而不能修改原有char型数组中的元素
- 当调用String类的replace()方法替换String对象的指定字符或字符串时,也需要重新指定内存区域创建一个新的char型数组存储替换后的字符串常量,而不能修改原有char型数组中的元素
- String类实现了Serializable接口:表示字符串是可序列化的,即String对象可以进行传输(io流再详细讲);实现了Comparable接口:表示String对象可以比较大小
/*
******** 体会字符串的不可变性 ********
注:通过字面量的方式实例化String对象:此时String对象的引用类型变量指向的字符串常量存储在方法区字符串常量池中
字符串常量池中不会存在相同内容的字符串常量
*/
public class StringTest {
@Test
public void test1() {
//两个String对象的字面量定义相同,由于字符串常量池中不会存在相同内容的字符串常量
//因此String对象的引用s1和s2指向方法区字符串常量池中同一个字符串常量,故地址值相同
String s1 = "abc"; //字面量的定义方式
String s2 = "abc";
System.out.println(s1 == s2); //true
//当对String对象重新赋值时,需要重新指定内存区域创建一个新的char型数组存储新的字符串常量,
//而不能修改原有char型数组中的元素,故此时String对象的引用s1和s2指向不同的字符串常量,地址值不同
s1 = "hello";
System.out.println(s1); //hello
System.out.println(s2); //abc
System.out.println(s1 == s2); //false
//当对现有字符串进行连接操作时,需要重新指定内存区域创建一个新的char型数组存储连结后的字符串常量,
//而不能修改原有char型数组中的元素,故此时String类型的引用s2和s3指向不通的字符串常量,地址值不同
String s3 = "abc";
s3 += "def";
System.out.println(s3); //abcdef
System.out.println(s2); //abc
System.out.println(s2 == s3); //false
//当调用String类的replace()方法替换String对象的指定字符或字符串时,
//需要重新指定内存区域创建一个新的char型数组存储替换后的字符串常量,不能修改原有char型数组中的元素
String s4 = "abc";
String s5 = s4.replace('a', 'm');
System.out.println(s4); //abc
System.out.println(s5); //mbc
System.out.println(s4 == s5); //false
}
}
2. String类的对象实例化方式
2.1 方式一:通过字面量的方式
- 通过字面量的方式实例化String对象:此时String类型的引用类型变量指向的是方法区字符串常量池中的字符串常量(一个引用,一个对象,一个地址值)
- 字符串常量池中不会存在相同内容的字符串常量
String str = "hello";
2.2 方式二:通过new + 构造器的方式
- 通过new + 构造器的方式实例化String对象:此时String类型的引用类型变量指向的是堆空间中new出来的String对象(字符串非常量对象);而堆空间中的String对象又加载了value属性(char型数组的引用类型变量),指向字符串常量池中的字符串常量(两个引用,两个对象,两个地址值)
- 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 startIndex,int count);
2.3 两种String对象实例化方式的区别
2.3.1 例一
public class StringTest {
@Test
public void test2() {
/*
通过字面量的方式实例化String对象:此时引用类型变量s1和s2指向的是方法区字符串常量池中的字符串常量
(一个引用,一个对象,一个地址值)
*/
String s1 = "javaEE";
String s2 = "javaEE";
/*
通过new + 构造器的方式实例化String对象:此时引用类型变量s3和s4指向的是堆空间中new出来的String
对象(字符串非常量对象);而堆空间中的String对象又加载了value属性(char型数组的引用),指向字符串常
量池中的字符串常量(两个引用,两个对象,两个地址值)
*/
String s3 = new String("javaEE");
String s4 = new String("javaEE");
//字符串常量池中不会存储相同内容的字符串常量
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false
System.out.println(s1 == s4); //false
System.out.println(s3 == s4); //false
}
}
2.3.2 例二
public class StringTest {
@Test
public void test3() {
Person p1 = new Person("Tom", 12);
Person p2 = new Person("Tom", 12);
//String类重写了equals方法,比较的是两个String对象的实际内容
System.out.println(p1.name.equals(p2.name)); //true
/*
== 比较的是两个String类型引用的地址值,这里是字面量定义,由于字符串常量池中不会存储相同内容的字符串
常量,因此二者指向字符串常量池中同一个字符串常量,地址值相同
*/
System.out.println(p1.name == p2.name); //true
/*
当对String对象重新赋值时,需要重新指定内存区域创建一个新的char型数组存储新的字符串常量,而不能修改
原有char型数组中的元素,故此时String对象的引用p1.name和p2.name指向不同的字符串常量,地址值不同
*/
p1.name = "Jerry";
System.out.println(p2.name); //Tom
}
}
2.3.3 例三
@Test
public void test3() {
String a = "123";
String b = "123";
String c = new String("123");
String d = new String("123");
//String类重写的equals方法比较实际字面量内容
System.out.println(a.equals(b)); //true
//二者都是字面量方式定义,指向方法区字符串常量池中同一个字符串常量(字符串常量池中不会存在相同内容的常量)
System.out.println(a == b); //true
//String类重写的equals方法比较实际字面量内容
System.out.println(c.equals(d)); //true
//二者都是new+构造器的方式定义,指向堆空间中两个不同的对象实体
System.out.println(c == d); //false
//String类重写的equals方法比较实际字面量内容
System.out.println(a.equals(c)); // true
//一个是字面量定义,直接指向字符串常量池中的字符串常量;一个是new+构造器定义,指向堆空间中的对象实体
//而堆空间中的对象又加载了value属性(char型数组的引用类型变量),指向字符串常量池中的字符串常量
System.out.println(a == c); //false
}
2.4 【面试题】new + 构造器的方式实例化String对象,在内存中创建了几个对象?
- 两个。通过new + 构造器的方式实例化String对象:此时String类型的引用类型变量指向的是==堆空间中new出来的String对象(字符串非常量对象);而堆空间中的String对象又加载了value属性(char型数组的引用类型变量),指向字符串常量池中的字符串常量(两个引用,两个对象,两个地址值==)
2.5 String不同拼接操作的对比
- (字面量)字符串常量与**(字面量)字符串常量**的拼接结果直接存储在字符串常量池,且字符串常量池中不会存在相同内容的字符串常量(一个引用,一个对象,一个地址值)
- 在String拼接操作中,只要其中有一个是变量(基本数据类型变量或引用类型变量),拼接后就**相当于在堆空间中new了一个String对象(字符串非常量对象)**,而堆空间中的String对象又加载了value属性(char型数组的引用类型变量),指向字符串常量池中的字符串常量(两个引用,两个对象,两个地址值)
- 如果拼接的结果调用==intern()方法,方法的返回值,即给引用类型变量赋的值是字符串常量池中的地址值==
2.5.1 例一
public class StringTest {
@Test
public void test4() {
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
/*
(字面量)字符串常量与(字面量)字符串常量的拼接结果直接存储在字符串常量池,且字符串常量池中不会存在相同
内容的字符串常量(一个引用,一个对象,一个地址值)
*/
String s4 = "javaEE" + "hadoop";
/*
在String拼接操作中,只要其中有一个是变量(基本数据类型变量或引用类型变量),拼接后就相当于在堆空间中
new了一个String对象(字符串非常量对象),而堆空间中的String对象又加载了value属性(char型数组的引
用类型变量),指向字符串常量池中的字符串常量(两个引用,两个对象,两个地址值)
*/
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
//如果拼接的结果调用intern()方法,方法的返回值,即给引用类型变量赋的值是字符串常量池中的地址值
String s8 = (s1 + s2).intern();
//两个引用的值都是字符串常量池中同一个字符串常量的地址
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
//两个引用的值都是字符串常量池中同一个字符串常量的地址
System.out.println(s3 == s8); //true
}
}
2.5.2 例二
2.5.3 例三
分析下列程序运行的结果:
public class StringTest1 {
String str = new String("good"); //引用str指向堆空间中的String对象
char[] ch = { 't', 'e', 's', 't' };
public void change(String str, char ch[]) {
/*
通过字面量的方式给String对象赋值:需要重新指定内存区域创建一个新的char型数组存储新的字符串常量,而
不能修改原有char型数组中的元素,这里只是让该引用类型的形参指向了方法区字符串常量池中另一个字符串常量
*/
str = "test ok"; //String的不可变性
//对于数组对象而言,其引用类型的形参和实参都指向堆空间中同一个数组实体
ch[0] = 'b'; //数组没有不可变性
}
public static void main(String[] args) {
StringTest1 ex = new StringTest1();
ex.change(ex.str, ex.ch);
System.out.println(ex.str); //good:String的不可变性
System.out.println(ex.ch); //best:数组或普通对象没有不可变性
}
}
2.5.4 例四
public void test4() {
String s1 = "javaEEhadoop"; //字面量定义:引用类型变量s1直接指向字符串常量池中的字符串常量
String s2 = "javaEE";
String s3 = s2 + "hadoop"; //字符串常量与变量拼接,引用类型变量s3的值是堆空间中的地址值
System.out.println(s1 == s3); //false
final String s4 = "javaEE"; //s4是一个常量:final
//常量与常量的拼接结果直接存储在字符串常量池,引用类型变量s5的值是字符串常量池中的地址值
String s5 = s4 + "hadoop";
//字符串常量池中不会存在相同内容的字符串常量,故此时s1和s5中保存的是常量池中同一个字符串常量的地址值
System.out.println(s1 == s5); //true
}
3. String类的常用方法
3.1 高频常用
- int length(): 返回字符串(存放字符内容的char型数组)的长度: return value.length
- char charAt(int index): 返回某索引处的字符return value[index]
- boolean isEmpty(): 判断是否是空字符串(判断char型数组的长度是否为0): return value.length == 0
- String toLowerCase(): 使用默认语言环境, 将 String 中的所有字符转换为小写
- String toUpperCase(): 使用默认语言环境, 将 String 中的所有字符转换为大写
- String trim(): 返回字符串的副本, 忽略前导空白和尾部空白
- boolean equals(Object obj):比较字符串的实际内容是否相同
- boolean equalsIgnoreCase(String anotherString):比较字符串的实际内容是否相同,忽略大小写
- String concat(String str): 将指定字符串连接到此字符串的结尾。 等价于用“+”
- int compareTo(String anotherString): 比较两个字符串的大小(逐字符比较ASCII码的大小),返回正数表示调用该方法的字符串大,返回负数表示作为形参的字符串大,可用于字符串排序
- String substring(int beginIndex):返回当前字符串从beginIndex开始截取到最后的一个字符的子字符串
- String substring(int beginIndex, int endIndex):返回当前字符串从beginIndex到endIndex(不包含)的子字符串(左闭右开)
public class StringMethodTest {
@Test
public void test1() {
String s1 = "HelloWorld";
//1. int length(): 返回字符串(存放字符内容的char型数组)的长度: return value.length
System.out.println(s1.length()); //10
//2. char charAt(int index): 返回某索引处的字符return value[index]
System.out.println(s1.charAt(0)); //H
//3. boolean isEmpty(): 判断是否是空字符串(判断char型数组的长度是否为0): return value.length == 0
System.out.println(s1.isEmpty()); //false
//4. String toLowerCase(): 使用默认语言环境,将 String 中的所有字符转换为小写
//5. String toUpperCase(): 使用默认语言环境,将 String 中的所有字符转换为大写
String s2 = s1.toLowerCase();
System.out.println(s1); //HelloWorld:String的不可变性
System.out.println(s2); //helloworld
//6. String trim(): 返回字符串的副本, 忽略前导空白和尾部空白
String s3 = " hello world ";
String s4 = s3.trim();
System.out.println(s3); // hello world :前后都有空格
System.out.println(s4); //hello world:前后没有空格
//7. boolean equals(Object obj): 比较字符串的实际内容是否相同
//8. boolean equalsIgnoreCase(String anotherString): 比较字符串的实际内容是否相同, 忽略大小写
String s5 = "HelloWorld";
String s6 = "helloworld";
System.out.println(s5.equals(s6)); //false
System.out.println(s5.equalsIgnoreCase(s6)); //true
//9. String concat(String str): 将指定字符串连接到此字符串的结尾。 等价于用“+”
System.out.println(s6.concat("fff")); //helloworldfff
System.out.println(s6 + "fff"); //helloworldfff
//10. int compareTo(String anotherString): 比较两个字符串的大小(逐字符比较ASCII码的大小),
//返回正数表示调用该方法的字符串大,返回负数表示作为形参的字符串大,可用于字符串排序
String s7 = "abc";
String s8 = new String("abe");
System.out.println(s7.compareTo(s8)); //-2
//11. String substring(int beginIndex):返回当前字符串从beginIndex开始截取到最后的一个字符的子字符串
//12. String substring(int beginIndex, int endIndex):返回当前字符串从beginIndex到endIndex(不包含)的子字符串
String s9 = "今天天气不错";
System.out.println(s9.substring(2)); //天气不错
System.out.println(s9.substring(2, 4)); //天气
}
}
3.2 查找
- 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
- 问题:什么情况下,**indexOf(str)和lastIndexOf(str)**的返回值相同?
- ① 调用方法的字符串中只有唯一的一个str子字符; ② 未找到都返回-1
public class StringMethodTest {
@Test
public void test2() {
String s1 = "helloworld";
//13. boolean endsWith(String suffix): 测试此字符串是否以指定的后缀结束(没有要求要写几个字符)
System.out.println(s1.endsWith("ld")); //true
//14. boolean startsWith(String prefix): 测试此字符串是否以指定的前缀开始
System.out.println(s1.startsWith("Hell")); //false
//15. boolean startsWith(String prefix, int toffset): 测试此字符串从指定索引开始的子字符串是否以指定前缀开始
System.out.println(s1.startsWith("ll", 2)); //true
//16. boolean contains(CharSequence s): 当且仅当此字符串包含指定的 char 值序列时,返回 true
String s2 = "wo";
System.out.println(s1.contains(s2)); //true
//17. int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
System.out.println(s1.indexOf("lo")); //3
//18. int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始搜索
System.out.println(s1.indexOf("lo", 1)); //3
System.out.println(s1.indexOf("lo", 4)); //-1
//19. int lastIndexOf(String str):返回指定子字符串在此字符串中最后一次出现处的索引
String s3 = "hororhhh";
System.out.println(s3.lastIndexOf("or")); //3
//20. int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
System.out.println(s3.lastIndexOf("or", 4)); //3
System.out.println(s3.lastIndexOf("or", 2)); //1
System.out.println(s3.lastIndexOf("or", 0)); //-1
//注:indexOf和lastIndexOf方法如果未找到都返回-1
}
}
3.3 替换
- 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 替换此字符串匹配给定的正则表达式的第一个子字符串
3.4 匹配
- boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
3.5 切片
- String[] split(String regex):根据给定正则表达式的匹配拆分此字符串
- String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
public class StringMethodTest {
@Test
public void test3() {
//********************* 替换 *********************
String s1 = "今天天气不错,今天适合出去春游";
//21. String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的s
String s2 = s1.replace("今", "明");
System.out.println(s1); //今天天气不错,今天适合出去春游:String的不变性
System.out.println(s2); //明天天气不错,明天适合出去春游
//22. String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串
String s3 = s1.replace("今天", "清明节");
System.out.println(s3); //清明节天气不错,清明节适合出去春游
//23. String replaceAll(String regex, String replacement):使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串
String str = "12hello34world5java7891mysql456";
//把字符串中的数字替换成",",如果结果中开头和结尾有","的话去掉
String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
System.out.println(string); //hello,world,java,mysql
//********************* 匹配 *********************
//25. boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
String str1 = "12345";
//判断str字符串中是否全部由数字组成,即有1-n个数字组成
boolean matches = str1.matches("\\d+");
System.out.println(matches); //true
String tel = "0571-4534289";
//判断这是否是一个杭州的固定电话
boolean result = tel.matches("0571-\\d{7,8}");
System.out.println(result); //true
//********************* 切片 *********************
//26. String[] split(String regex):根据给定正则表达式的匹配拆分此字符串
String str2 = "hello|world|java";
String[] strs = str2.split("\\|");
for (int i = 0; i < strs.length; i++) {
System.out.print(strs[i] + " "); //hello world java
}
System.out.println();
String str3 = "hello.world.java";
String[] strs2 = str3.split("\\.");
for (int i = 0; i < strs2.length; i++) {
System.out.print(strs2[i] + " "); //hello world java
}
}
}
4. String与其他数据结构的转换
4.1 String与基本数据类型、包装类的转换
- String类型 --> 基本数据类型、包装类
- 调用包装类的静态方法parseXxx(String s),返回值为基本数据类型
- 基本数据类型、包装类 --> String类型
- 字符串的连接运算:+ “”,基本数据类型可以直接和字符串做连接运算;包装类的对象通过自动拆箱也可以直接和字符串做连接运算
- 调用String类重载的静态方法valueOf(),形参列表可以是基本数据类型,也可以是包装类的对象
- 调用包装类重写的toString()方法
public class StringTest2 {
@Test
public void test1() {
//1. String类型 --> 基本数据类型、包装类
//- 调用包装类的静态方法parseXxx(String s),返回值为基本数据类型
String str1 = "123"; //字符串常量池
int i = Integer.parseInt(str1);
System.out.println(i); //123
//2. 基本数据类型、包装类 --> String类型
//- 字符串的连接运算:+ "",基本数据类型可以直接和字符串做连接运算;包装类的对象通过自动拆箱也可以直接和字符串做连接运算
//- 调用String类重载的静态方法valueOf(),形参列表可以是基本数据类型,也可以是包装类的对象
//- 调用包装类重写的toString()方法
String s = String.valueOf(i);
System.out.println(s); //"123"
String str2 = i + ""; //堆空间
System.out.println(str1 == str2); //false
}
}
4.2 String与char型(字符型)数组的转换
- char型数组 --> String
- String 类的构造器: String(char[]) 和 String(char[], int offset, int length) 分别用字符数组中的全部字符和部分字符创建字符串对象
- String --> char型数组
- public char[] toCharArray(): 将字符串中的全部字符存放在一个字符数组中的方法
- public void getChars(int srcBegin, int srcEnd, char[] dst,int dstBegin): 提供了将指定索引范围内的字符串存放到数组中的方法
public class StringTest2 {
@Test
public void test2() {
//1. char型数组 --> String
//- String 类的构造器: String(char[]) 和 String(char[], int offset, int length) 分别用字符数组中的全部字符和部分字符创建字符串对象
char[] arr = new char[]{'h', 'e', 'l', 'l', 'o'};
String s = new String(arr);
System.out.println(s); //hello
//2. String --> char型数组
//- public char[] toCharArray(): 将字符串中的全部字符存放在一个字符数组中的方法
String str1 = "abc123";
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.print(charArray[i]);
}
}
}
4.3 String与byte型(字节型)数组的转换:编码&解码
- String --> byte型数组:编码(看得懂到看不懂)
- public byte[] getBytes():使用平台的默认字符集将String编码为byte序列,并将结果存储到一个新的byte型数组中
- public byte[] getBytes(String charsetName):使用指定的字符集将String编码为byte序列,并将结果存储到新的byte型数组中
- byte型数组 --> String:解码(看不懂到看得懂)
- String(byte[]):String的构造器,使用默认字符集解码指定的byte型数组,构造一个新的String对象
- String(byte[], int offset, int length):String类的构造器,用指定的byte型数组的一部分,即从数组起始位置offset开始取length个字节进行解码,构造一个新的String对象
- 注意:编码和解码使用的字符集必须要一致,否则在解码时会出现乱码
public class StringTest2 {
@Test
public void test3() {
//1. String --> byte型数组:编码
//- public byte[] getBytes():使用平台的默认字符集将String编码为byte序列,并将结果存储到一个新的byte型数组中
//- public byte[] getBytes(String charsetName):使用指定的字符集将String编码为byte序列,并将结果存储到新的byte型数组中
String str1 = "abc123中国";
byte[] bytes = str1.getBytes(); //使用默认字符集,进行编码
System.out.println(Arrays.toString(bytes)); //[97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]
byte[] gbks = null;
try {
gbks = str1.getBytes("gbk"); //使用指定的字符集(gkb),进行编码
System.out.println(Arrays.toString(gbks)); //[97, 98, 99, 49, 50, 51, -42, -48, -71, -6]
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//2. byte型数组 --> String:解码
//- String(byte[]):String类的构造器,使用平台的默认字符集解码指定的byte型数组,构造一个新的String对象
//- String(byte[], int offset, int length):String类的构造器,用指定的byte型数组的一部分,即从数组起始位置offset开始取length个字节进行解码,构造一个新的String对象
String str2 = new String(bytes); //使用默认字符集,进行解码
System.out.println(str2); //abc123中国
String str3 = new String(gbks);
System.out.println(str3); //abc123�й� 出现乱码:解码和编码使用的字符集不一致
try {
String str4 = new String(gbks, "gbk"); //使用指定的字符集,进行解码
System.out.println(str4); //abc123中国
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
5. String常见算法题
例1:trim()方法:去除字符串两端空格
- 模拟一个trim方法,去除字符串两端的空格
public class StringExer {
public static void main(String[] args) {
StringExer test = new StringExer();
String str1 = " a b c ";
System.out.println(test.trim(str1)); //a b c
}
public String trim(String str) {
if (str != null) {
int start = 0; //记录从前往后首个不是空格字符的索引
int end = str.length() - 1; //记录从后往前首个不是空格字符的索引
while (start < end && str.charAt(start) == ' ') {
start++;
}
//全空字符串的情况
if (str.charAt(start) == ' ') {
return "";
}
while (start < end && str.charAt(end) == ' ') {
end--;
}
return str.substring(start, end + 1);
}
return null;
}
}
例2:字符串指定部分反转
- 将一个字符串中指定部分进行反转。比如“abcdefg”反转为”abfedcg”
public class StringExer {
public static void main(String[] args) {
StringExer test = new StringExer();
String str2 = "abcdefg";
System.out.println(test.reverse2(str2, 2, 6)); //abfedcg
}
//方式一:for循环遍历一个i
public String reverse1(String str, int start, int end) {
if (str != null) {
char[] arr = str.toCharArray();
//数组反转
for (int i = start; i < (end + start) / 2; i++) {
char temp = arr[i];
arr[i] = arr[start + end - 1 - i];
arr[start + end - 1 - i] = temp;
}
return new String(arr);
}
return null;
}
//方式二:for循环遍历一个i一个j
public String reverse2(String str, int start, int end) {
if (str != null) {
char[] arr = str.toCharArray();
//数组反转
for (int i = start, j = end - 1; i < j; i++, j--) {
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return new String(arr);
}
return null;
}
//方式三:使用String的拼接
public String reverse3(String str, int start, int end) {
if (str != null) {
//第一部分:ab
String newStr = str.substring(0, start);
//第二部分:abfedc
for (int i = end - 1; i >= start; i--) {
newStr += str.charAt(i);
}
//第三部分:abfedcg
return newStr + str.substring(end);
}
return null;
}
//方式四:使用StringBuffer / StringBuilder替换方式二中的String
public String reverse4(String str, int start, int end) {
if (str != null) {
StringBuilder s = new StringBuilder(str.length());
//第一部分:ab
s.append(str.substring(0, start));
//第二部分:abfedc
for (int i = end - 1; i >= start; i--) {
s.append(str.charAt(i));
}
//第三部分:abfedcg
return s.append(str.substring(end)).toString();
}
return null;
}
}
例3:子字符串出现的次数
- 获取一个字符串在另一个字符串中出现的次数
- 比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数
public class StringExer {
public static void main(String[] args) {
StringExer test = new StringExer();
String str31 = "abkkcadkabkebfkabkskab";
String str32 = "ab";
System.out.println(test.getCount(str31, str32)); //4
}
public int getCount(String str1, String str2) {
if (str1 != null && str2 != null) {
if (str1.length() >= str2.length()) {
int count = 0;
int index = 0;
while (str1.indexOf(str2, index) != -1) {
index = str1.indexOf(str2, index) + str2.length();
count++;
}
return count;
}
}
return 0;
}
}
例4:两个字符串中最大相同子串
- 获取两个字符串中最大相同子串。比如:str1 = "abcwerthelloyuiodef“;str2 = “cvhellobnm”
- 提示:将短的那个串进行长度依次递减的子串与较长的串比较
public class StringExer {
public static void main(String[] args) {
StringExer test = new StringExer();
String str41 = "abcwerthelloyuiodef";
String str42 = "cvhellobnm";
System.out.println(test.maxSubString(str41, str42)); //hello
}
//前提:两个字符串中只有一个最大相同子串
public String maxSubString(String str1, String str2) {
if (str1 != null && str2 != null) {
String maxStr;
String minStr;
if (str1.length() >= str2.length()) {
maxStr = str1;
minStr = str2;
} else {
maxStr = str2;
minStr = str1;
}
//String maxStr = (str1.length() >= str2.length()) ? str1 : str2;
//String minStr = (str1.length() >= str2.length()) ? str2 : str1;
int length = minStr.length();
//此层循环决定较短字符串要去掉几个字符
for (int i = 0; i < length;i++) {
for (int j = 0, k = length - i; k <= length ;j++, k++) {
String subStr = minStr.substring(j, k);
if (maxStr.contains(subStr)) {
return subStr;
}
}
}
}
return null;
}
}
例5:字符串排序
- 对字符串中字符进行自然顺序排序
- 提示:
1)字符串变成字符数组
2)对数组排序,选择,冒泡,Arrays.sort();
3)将排序后的数组变成字符串
public class StringExer {
public static void main(String[] args) {
StringExer test = new StringExer();
String str5 = "abcwerthelloyuiodef";
System.out.println(test.testSort(str5)); //abcdeeefhilloortuwy
}
public String testSort(String str) {
char[] arr = str.toCharArray();
Arrays.sort(arr);
return new String(arr);
}
}
二、StringBuffer & StringBuilder
1. StringBuffer类的特性:可变的字符序列
- java.lang.StringBuffer代表可变的字符序列,在JDK1.0中声明。可以对字符串内容进行增删,且不会产生新的对象
- 很多方法与String类相同
- 当StringBuffer对象作为参数传递时,方法内部可以改变该对象的字符内容(字面量)
2. StringBuffer类的对象实例化方式
- StringBuffer类不同于String类,其对象不能通过字面量的方式创建,只能通过 new + 构造器 的方式创建。StringBuffer类有三个==构造器==:
- **StringBuffer():**空参构造器。创建一个长度为16的char型数组(初始容量为16的字符串缓冲区)
- StringBuffer(int capacity):创建一个指定长度的char型数组(指定容量的字符串缓冲区)
- **StringBuffer(String str):**创建一个长度为(指定字符串长度 + 16)的char型数组,并将char型数组的元素初始化为指定字符串的字面量内容
String s = new String("我喜欢学习");
StringBuffer buffer = new StringBuffer("我喜欢学习");
buffer.append("数学");
3. StringBuffer类的常用方法
- StringBuffer append(xxx):提供了很多的重载的append()方法,用于字符串拼接
- StringBuffer delete(int start,int end):删除原字符串中从索引start到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):返回指定子字符串在此字符串中第一次出现处的索引(和String中的类似)
- public String substring(int start,int end):返回当前字符串从beginIndex到endIndex的子字符串(左闭右开)
- public int length():返回char型数组中有效字符的个数,即count的值
- public char charAt(int n): 返回某索引处的字符 return value[n]
- public void setCharAt(int n ,char ch): 将某索引处的字符修改为新的字符ch
- 总结
- StringBuilder中的方法与StringBuffer类似,只是StringBuffer类中的方法大多为同步方法
- 增: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();遍历:toString()
- 当String类的对象作为方法的参数进行值传递时,其实际内容不会在方法内部改变, 而StringBuffer和StringBuilder类的对象的实际内容一般都会在方法内部改变,且返回该改变后的对象
public class StringBufferBuilderTest {
@Test
public void test2() {
StringBuffer s1 = new StringBuffer("abc");
//1. StringBuffer append(xxx):提供了很多的重载的append()方法,用于字符串拼接
s1.append(1);
s1.append("1");
System.out.println(s1); //abc11
//2. StringBuffer delete(int start,int end):删除原字符串中从索引start到end的字符(左闭右开)
s1.delete(2, 4);
System.out.println(s1); //ab1
//3. StringBuffer replace(int start, int end, String str):将索引start到end的子字符串替换为str(长度可以不同)
s1.replace(1, 3, "fff");
System.out.println(s1); //afff
//4. StringBuffer insert(int offset, xxx):在指定位置插入xxx(可以是多种数据类型,长度不限),并转化成字符串
s1.insert(2, false);
System.out.println(s1); //affalseff
//5. StringBuffer reverse():把当前字符序列逆转
s1.reverse();
System.out.println(s1); //ffeslaffa
//7. public String substring(int start,int end):返回当前字符串从beginIndex到endIndex的子字符串(左闭右开)
String s2 = s1.substring(2, 5);
System.out.println(s1); //ffeslaffa
System.out.println(s2); //esl
}
}
4. String、StringBuffer、StringBuilder的对比
4.1 从特性上对比
- String(JDK1.0):
- 不可变的字符序列;
- String类的对象的字符内容存储在一个char型数组中,且声明为final(常量,不可变的);
- StringBuffer(JDK1.0):
- 可变的字符序列;
- 线程安全的(StringBuffer类中的方法大多为同步方法),效率低;
- StringBuffer类的对象的字符内容存储在一个char型数组中,但没有声明为final(可变的)
- StringBuilder(JDK5.0):
- 可变的字符序列;
- 线程不安全的,效率高;
- StringBuilder类的对象的字符内容存储在一个char型数组中,但没有声明为final(可变的)
- StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列, 而且提供相关功能的方法也一样
- 当String类的对象作为方法的参数进行值传递时,其实际内容不会在方法内部改变, 而StringBuffer和StringBuilder类的对象的实际内容一般都会在方法内部改变,且返回该改变后的对象
- 局部变量考虑使用StringBuilder类的方法,因为局部变量不存在线程安全问题
4.2 从构造器对char[]数组初始化的角度理解(不)可变的字符序列
- String类的构造器(存放字符内容的char型数组声明为final,不可以改变数组元素的值)
//本质上this.value = new char[0];底层创建一个长度为0的char型数组
String s1 = new String();
//return this.value.length();
System.out.println(s1.length()); //0
//本质上 this.value = new char[]{'a', 'b', 'c'};底层创建一个长度为3的char型数组
String s2 = new String("abc");
//return this.value.length();
System.out.println(s2.length()); //3
- StringBuffer或StringBuilder类的构造器(存放字符内容的char型数组为非final,可以改变数组元素的值)
- 问题1:System.out.println(sb2.length()); 输出什么?
- 不同于==String类中的length()方法实际返回的是存储字符串内容的char型数组的长度,StringBuffer类中的length()方法返回的是char型数组中有效字符的个数,即count的值==
- 问题2:当append和insert时,如果原char型数组的长度(字符串缓冲区的容量)不够是怎么处理的?
- 对原char型数组进行==扩容==:默认情况下,新建一个长度(容量)为原来的2倍+2的char型数组,并将原数组中的元素复制到新数组中
- 问题1:System.out.println(sb2.length()); 输出什么?
//本质上this.value = new char[16];底层创建一个长度为16的char型数组(初始容量为16的字符串缓冲区)
StringBuffer sb1 = new StringBuffer();
//return count; 返回的是有效字符的个数
System.out.println(sb1.length()); //0
//本质上this.value[0] = 'a';
sb1.append('a');
//本质上this.value[1] = 'b';
sb1.append('b');
//return count; 返回的是有效字符的个数
System.out.println(sb1.length()); //2
//本质上this.value = new char["abc".length() + 16];
//底层创建一个长度为(指定字符串长度 + 16)的char型数组,并将char型数组的元素初始化为指定字符串的字面量内容
StringBuffer sb2 = new StringBuffer("abc");
//return count; 返回的是有效字符的个数
System.out.println(sb2.length()); //3
4.3 从效率上对比
- 从高到低:StringBuilder > StringBuffer > String
5. String与StringBuffer、StringBuilder的转换
- String --> StringBuffer、StringBuilder:调用StringBuffer、StringBuilder的构造器
- StringBuffer、StringBuilder --> String:① 调用String的构造器;② 调用StringBuffer、StringBuilder重写的toString()方法
三、JDK8.0之前的日期时间API
1. System中的currentTimeMillis()
- java.lang.System类提供的==静态方法 public static long currentTimeMillis()== 用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差(时间戳)
- 此方法适于计算时间差
public class DateTimeTest {
@Test
public void test1() {
//静态方法currentTimeMillis(),返回时间戳,返回值类型为long
long time = System.currentTimeMillis();
System.out.println(time);
}
}
2. Date类
2.1 java.util.Date类
- java.util.Date类表示特定的瞬间,精确到毫秒
2.1.1 两个构造器
- Date():使用无参构造器,创建一个对应当前时间戳的Date对象
- Date(long date):创建一个指定时间戳的Date对象
2.1.2 两个常用方法
- getTime():返回自1970年1月1日00:00:00 GMT 以来当前Date对象对应的毫秒数(时间戳),相当于调用了System.currentTimeMillis()
- toString():把此Date对象转换为以下形式的String:dow mon dd hh:mm:ss zzz yyyy 其中: dow是一周中的某一天(Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准
- 其它很多方法都过时了
@Test
public void test2() {
//Date():使用无参构造器,创建一个对应当前时间戳的Date对象
Date date1 = new Date();
System.out.println(date1.toString()); //Sun Apr 03 23:33:57 CST 2022
//java.util.Date类重写了toString()方法
System.out.println(date1); //Sun Apr 03 23:33:57 CST 2022
//getTime():返回自1970年1月1日00:00:00 GMT 以来当前Date对象对应的毫秒数(时间戳)
System.out.println(date1.getTime()); //1649000037742
//Date(long date):创建一个指定毫秒数时间戳的Date对象
Date date2 = new Date(1649000037742L);
System.out.println(date2); //Sun Apr 03 23:33:57 CST 2022
}
2.2 java.sql.Date类
- java.sql.Date类是java.util.Date类的子类,对应数据库中的日期类型的变量
2.2.1 构造器
- Date(long date)
2.2.2 java.util.Date对象转换为java.sql.Date对象
- 先调用java.util.Date对象的getTime()方法获取该对象的时间戳,然后将此时间戳作为参数传递给java.sql.Date类的构造器,new一个新的java.sql.Date对象
@Test
public void test3() {
//创建java.sql.Date类的对象:构造器为Date(long date)
java.sql.Date date1 = new java.sql.Date(1649000037742L);
//java.sql.Date类重写了toString()方法
System.out.println(date1.toString()); //2022-04-03
System.out.println(date1); //2022-04-03
//java.sql.Date类的getTime()方法
System.out.println(date1.getTime()); //1649000037742
//如何将java.util.Date对象转换为java.sql.Date对象
java.util.Date date2 = new java.util.Date();
//先调用java.util.Date对象的getTime()方法获取该对象的时间戳,
//然后将此时间戳作为参数传递给java.sql.Date类的构造器,new一个新的java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(date2.getTime());
}
3. SimpleDateFormat类
- java.text.SimpleDateFormat类:对日期Date类的格式化和解析
- 它允许进行 ① 格式化:日期(Date) → 字符串(String);② 解析:字符串(String) → 日期(Date)
3.1 构造器
- SimpleDateFormat():空参构造器,默认的模式和语言环境创建对象
- public SimpleDateFormat(String pattern): 该构造器可以用参数pattern指定的格式创建一个对象
3.2 格式化:日期(Date) → 字符串(String)
- public String format(Date date):格式化Date类的对象,转化成字符串
3.3 解析:字符串(String) → 日期(Date)
- public Date parse(String source):对给定字符串进行解析,生成一个Date类的对象
- 要求传入的字符串必须是符合SimpleDateFormat构造器指定的格式,否则就会抛异常
public class DateTimeTest {
@Test
public void testSimpleDateFormat() {
//实例化SimpleDateFormat:空参构造器,默认的模式和语言环境创建对象
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化:日期(Date) → 字符串(String)
//public String format(Date date):格式化Date类的对象,转化成字符串
Date date = new Date();
System.out.println(date); //Mon Apr 04 23:37:15 CST 2022
String format = sdf.format(date);
System.out.println(format); //22-4-4 下午11:37
//解析:字符串(String) → 日期(Date)
//public Date parse(String source):对给定字符串进行解析,生成一个Date类的对象
try {
Date parse = sdf.parse("22-4-4 下午11:39");
System.out.println(parse); //Mon Apr 04 23:39:00 CST 2022
} catch (ParseException e) {
e.printStackTrace();
}
//********* 按照指定的方式格式化和解析:调用带参的构造器 *********
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化:日期(Date) → 字符串(String)
//public String format(Date date):格式化Date类的对象,转化成字符串
String format1 = sdf1.format(date);
System.out.println(format1); //2022-04-04 11:48:24
//解析:字符串(String) → 日期(Date)
//public Date parse(String source):对给定字符串进行解析,生成一个Date类的对象
try {
Date parse = sdf1.parse("2022-04-04 11:48:24");
System.out.println(parse); //Mon Apr 04 11:48:24 CST 2022
} catch (ParseException e) {
e.printStackTrace();
}
}
}
3.4 SimpleDateFormat例题
例1:指定格式字符串转换为java.sql.Date
- 字符串"2020-09-08"转换为java.sql.Date
public class DateTimeTest {
@Test
public void test1() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date date1 = null;
try {
date1 = sdf.parse("2020-09-08");
System.out.println(date1); //Tue Sep 08 00:00:00 CST 2020
} catch (ParseException e) {
e.printStackTrace();
}
//先调用java.util.Date对象的getTime()方法获取该对象的时间戳,
//然后将此时间戳作为参数传递给java.sql.Date类的构造器,new一个新的java.sql.Date对象
java.sql.Date date2 = new java.sql.Date(date1.getTime());
System.out.println(date2); //2020-09-08
}
}
4. Calendar(日历)类
- java.util.Calendar(日历)类是一个抽象基类,用于完成日期字段之间相互操作的功能
4.1 实例化方法
- 方式一:调用其静态方法 Calendar.getInstance(),返回对应当前时间的子类GregorianCalendar的对象
- 方式二:调用子类GregorianCalendar的构造器,创建对应当前时间的子类GregorianCalendar的对象
4.2 常用方法
- public int get(int field):获取指定时间信息:如YEAR, MONTH, DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND(静态常量)
- public void set(int field,int value):修改指定时间信息
- public void add(int field,int amount):增加指定时间信息
- public final Date getTime():Calendar对象 --> Date类对象
- public final void setTime(Date date):Date对象 --> Calendar类对象
- 月份:一月是0,二月是1,以此类推,12月是11;星期:周日是1,周二是2,以此类推,周六是7
public class DateTimeTest {
@Test
public void testCalendar() {
//1.Calendar的实例化方法
//方式一:调用其静态方法Calendar.getInstance(),返回对应当前时间的子类GregorianCalendar对象
Calendar calendar = Calendar.getInstance();
//方式二:调用子类GregorianCalendar的构造器
Calendar calendar1 = new GregorianCalendar();
//2.常用方法
//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days); //5
//set()
calendar.set(Calendar.DAY_OF_MONTH, 22);
System.out.println(calendar.get((Calendar.DAY_OF_MONTH))); //22
//add()
calendar.add(Calendar.DAY_OF_MONTH, 3);
System.out.println(calendar.get((Calendar.DAY_OF_MONTH))); //25:22+3
//getTime():Calendar对象 --> Date类对象
Date date = calendar.getTime();
System.out.println(date); //Mon Apr 25 00:50:52 CST 2022
//setTime():Date对象 --> Calendar类对象
Date date1 = new Date();
calendar.setTime(date1);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); //5
}
}
四、JDK8.0新的日期时间API:java.time
- JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是:
- 可变性:像日期和时间这样的类应该是不可变的
- 偏移性: Date中的年份是从1900开始的,而月份都从0开始
- 格式化:格式化只对Date有用, Calendar则不行
- 此外,它们也不是线程安全的;不能处理闰秒等
- 第三次引入的API是成功的, 并且Java 8中引入的java.time API 已经纠正了过去的缺陷。新的 java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理
1. LocalDate、 LocalTime、 LocalDateTime类
LocalDate、 LocalTime、 LocalDateTime类的实例是==不可变的对象==,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息
- LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。
- LocalTime表示一个时间,而不是日期。
- LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一
- LocalDate、 LocalTime、 LocalDateTime类的功能类似于Calendar类
public class JDK8DateTimeTest {
@Test
public void test1() {
//now():静态方法,根据当前日期、时间、日期+时间创建对象
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate); //2022-04-05
System.out.println(localTime); //01:42:57.859
System.out.println(localDateTime); //2022-04-05T01:42:57.859
//of():静态方法,根据指定年、月、日、时、分、秒创建对象,没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
System.out.println(localDateTime1); //2020-10-06T13:23:43
//getXxx():获取相关属性
System.out.println(localDateTime.getDayOfMonth()); //5
System.out.println(localDateTime.getDayOfWeek()); //TUESDAY
System.out.println(localDateTime.getDayOfYear()); //95
//withXxx():设置相关属性
LocalDate localDate1 = localDate.withDayOfMonth(22);
System.out.println(localDate); //2022-04-05:不可变性
System.out.println(localDate1); //2022-04-22
//plusXxx():增加相关属性的值
LocalDateTime localDateTime2 = localDateTime.plusMonths(3);
System.out.println(localDateTime); //2022-04-05T02:25:49.914:不可变性
System.out.println(localDateTime2); //2022-07-05T02:25:49.914
//minusXxx():减少相关属性的值
LocalDateTime localDateTime3 = localDateTime.minusMonths(3);
System.out.println(localDateTime); //2022-04-05T02:26:48.673:不可变性
System.out.println(localDateTime3); //2022-01-05T02:26:48.673
}
}
2. Instant(瞬时)类
- Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳
- java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。 Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲, 它只是简单的表示自1970年1月1日0时0分0秒( UTC)开始的秒数。 因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级
- Instant类的功能类似于java.util.Date类
public class JDK8DateTimeTest {
@Test
public void test2() {
//now():静态方法,返回默认UTC时区的Instant类的对象
Instant instant = Instant.now();
System.out.println(instant); //2022-04-04T18:36:33.207Z
//atOffset():结合即时的偏移来创建一个 OffsetDateTime
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime); //2022-04-05T02:36:33.207+08:00
//toEpochMilli():获取时间戳 --> Date类的getTime()
System.out.println(instant.toEpochMilli()); //1649097524850
System.out.println(new Date().getTime()); //1649097524905
//ofEpochMilli(long epochMilli):静态方法,返回指定时间戳的Instant类对象 --> Date类的Date(long millis)
Instant instant1 = Instant.ofEpochMilli(1649097524850L);
System.out.println(instant1); //2022-04-04T18:38:44.850Z
}
}
3. DateTimeFormatter类
- java.time.format.DateTimeFormatter 类:用来格式化或解析日期、时间
- 该类提供了三种格式化方法
- 预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
- 本地化相关的格式。如: ofLocalizedDateTime(FormatStyle.LONG)
- 自定义的格式。如: ofPattern(“yyyy-MM-dd hh:mm:ss”)
- DateTimeFormatter类的功能类似于SimpleDateFormat类
public class JDK8DateTimeTest {
@Test
public void test3() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String format = dateTimeFormatter.format(LocalDateTime.now());
System.out.println(format); //2022-04-05 02:54:02
//解析
TemporalAccessor parse = dateTimeFormatter.parse("2022-04-05 02:54:02");
System.out.println(parse);
}
}
4. 其它API
五、Java比较器(对象比较大小或对象数组排序)
- 在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题
- Java中的对象,正常情况下只能使用 ‘’==‘’ 或 ‘’!=‘’ 进行比较,比较对象的引用类型变量保存的地址值是否相同;但在实际开发场景中,我们需要对多个对象进行比较排序,即比较对象的“大小”
- Java实现对象排序的方式有两种:
- 自然排序: java.lang.Comparable接口
- 定制排序: java.util.Comparator接口
1. 自然排序:java.lang.Comparable接口
- Comparable接口强行对其实现类的对象进行整体排序。这种排序被称为类的自然排序
- Comparable接口的实现类必须实现其抽象方法==compareTo(Object obj)==。像String类、包装类等实现了Comparable接口的compareTo(Object obj)方法,给出了比较两个对象大小的方式
- Comparable接口的实现类类型的==对象列表(或数组)可以通过Collections.sort(或Arrays.sort)==进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器
1.1 compareTo(Object obj)方法的重写规则
- 对于自定义类,如果需要对其类型的对象数组进行排序(从小到大),可以让其实现Comparable接口,并重写compareTo(Object obj)方法,在其方法体内指明如何比较对象的大小(如按照对象的某个属性值比较大小)
- Comparable接口的实现类对象通过compareTo(Object obj)方法的返回值来比较大小:
- 如果当前对象this大于形参对象obj,则返回正整数
- 如果当前对象this小于形参对象obj,则返回负整数
- 如果当前对象this等于形参对象obj,则返回零
- 对于自定义实现类C的对象e1和e2来说,当且仅当 e1.compareTo(e2) == 0 与 e1.equals(e2) 具有相同的boolean值时,类C的自然排序才叫做与 equals() 一致。建议最好使自然排序与 equals() 一致
//Comparable接口的实现类
public class Goods implements Comparable {
private String name;
private double price;
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
//重写compareTo(Object obj)方法,指明如何比较两个Goods类对象的大小
@Override
public int compareTo(Object o) {
//如何比较:先按照商品价格从小到大排列(相等情况再按商品名称从小到大排列)
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;
//如果price属性的值相等,则再比较name属性
return this.name.compareTo(goods.name);
}
//方式二:
//return Double.compare(this.price, goods.price);
}
//运行时异常,这里无需处理
throw new RuntimeException("输入的数据类型不一致");
}
}
import org.junit.Test;
import java.util.Arrays;
public class CompareTest {
/*
Comparable接口的使用:自然排序
*/
@Test
public void test1() {
String[] arr = new String[]{"AA", "CC", "KK", "MM", "GG", "JJ", "DD"};
//String类实现了Comparable接口的compareTo(Object obj)方法
//Comparable接口的实现类类型的对象列表(或数组)可以通过Collections.sort(或Arrays.sort)进行自动排序
//实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); //[AA, CC, DD, GG, JJ, KK, MM]
}
/*
自定义类对象数组排序:从小到大
*/
@Test
public void test2() {
//自定义类Goods实现Comparable接口,并重写compareTo(Object obj)方法,在其方法体内指明如何比较对象的大小
Goods[] arr = new Goods[5];
arr[0] = new Goods("lenovoMouse", 34);
arr[1] = new Goods("dellMouse", 43);
arr[2] = new Goods("xiaomiMouse", 12);
arr[3] = new Goods("huaweiMouse", 65);
arr[4] = new Goods("microsoftMouse", 43);
//Comparable接口实现类类型的对象列表(或数组)可以通过Collections.sort(或Arrays.sort)进行自动排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
1.2 Comparable的典型实现类(默认从小到大排列):
- String:按照字符串中字符的Unicode值进行比较
- Character:按照字符的Unicode值来进行比较
- 数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值大小进行比较
- Boolean:true对应的包装类实例大于 false 对应的包装类实例
- Date、Time等:后面的日期时间比前面的日期时间大
2. 定制排序:java.util.Comparator接口
- 当对象数组的元素类型没有实现Comparable接口,或实现了Comparable接口但其重写的compareTo()方法中的排序规则不适合当前操作时,可以使用Comparator接口的实现类对象进行定制排序排序
- 使用方法:将Comparator接口实现类的对象作为参数传递给 Collections.sort() 或 Arrays.sort() 方法,从而允许在排序顺序上实现精确控制,Comparator接口实现类的对象是作为sort方法的形参使用的
- 还可以使用Comparator接口实现类的对象来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序
2.1 compare(Object o1,Object o2)方法的重写规则
- 如果需要对某个类的对象数组进行不同于自然排序的定制排序,可以声明一个Comparator接口的实现类,并重写compare(Object o1,Object o2)方法,在其方法体内指明如何比较两个对象的大小
- Comparator接口的实现类通过重写compare()方法,比较对象o1和o2的大小(以从小到大排序为例):
- 如果对象o1大于对象o2,则返回正整数
- 如果对象o1小于对象o2,则返回负整数
- 如果对象o1等于对象o2,则返回零
2.2 定制排序的实现举例
- 使用Comparator接口的实现类对象实现String类型对象数组的**从大到小定制排序**
import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;
public class CompareTest {
/*
Comparator接口的使用:定制排序
*/
@Test
public void test3() {
String[] arr = new String[]{"AA", "CC", "KK", "MM", "GG", "JJ", "DD"};
//将Comparator接口实现类的对象传递给sort方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制
//这里实现了Comparator接口的匿名实现类的匿名对象
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String) {
String s1 = (String) o1;
String s2 = (String) o2;
//定制排序:从大到小的顺序排列
return -s1.compareTo(s2);
}
//运行时异常,这里无需处理
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr)); //[MM, KK, JJ, GG, DD, CC, AA]
}
}
- 自定义Goods类对象数组的定制排序
import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;
public class CompareTest {
@Test
public void test4() {
//自定义类Goods实现Comparable接口,并重写compareTo(Object obj)方法,在其方法体内指明如何比较对象的大小
Goods[] arr = new Goods[5];
arr[0] = new Goods("lenovoMouse", 34);
arr[1] = new Goods("dellMouse", 43);
arr[2] = new Goods("xiaomiMouse", 12);
arr[3] = new Goods("huaweiMouse", 65);
arr[4] = new Goods("microsoftMouse", 43);
//将Comparator接口实现类的对象传递给sort方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制
//这里实现了Comparator接口的匿名实现类的匿名对象
Arrays.sort(arr, 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("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
}
3. Comparable接口和Comparator接口的对比
- 在比较对象的大小或对对象列表(或数组)进行排序时,实现Comparable接口的方式是一种==长期性==的比较:让当前对象或对象数组元素所属的类实现Comparable接口,并实现抽象方法compareTo();一旦compareTo()方法中指定排序规则,则在任何情况下都可以比较当前类的对象的大小,也可以直接通过Collections.sort(或Arrays.sort)对当前类的对象列表(或数组)进行自然排序
- 实现Comparator接口的方式是一种==临时性的比较:实现一个临时的Comparator接口实现类,并实现其抽象方法compare(),声明定制的排序规则;将该实现类的对象(比较器的对象)==作为参数传递给Collections.sort(或Arrays.sort)方法,对当前类的对象列表(或数组)进行定制排序
六、System类
- java.lang.System类代表系统,系统级的很多属性和控制方法都放置在该类的内部
- 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的, 所以也可以很方便的进行调用
1. 静态成员变量
- in:标准输入流(键盘输入)
- out:标准输出流(显示器)
- err:标准错误输出流(显示器)
2. 静态成员方法
- native long currentTimeMillis():返回当前时间戳
- void exit(int status):退出程序。其中status的值为0代表正常退出,非零代表异常退出。 使用该方法可以在图形界面编程中实现程序的退出功能等
- void gc():请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况
- String getProperty(String key):获得系统中属性名为key的属性对应的值。系统中常见
的属性名以及属性的作用如下表所示:
七、Math类
八、BigInteger与BigDecimal
1. BigInteger
- java.math包的BigInteger可以表示==不可变的、任意精度的整数==。BigInteger 提供所有Java的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、位操作以及一些其他操作
- 构造器:
- BigInteger(String val):根据字符串构建BigInteger对象
- 常用方法:
2. BigDecimal
- 一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到java.math.BigDecimal类
- BigDecimal类支持==不可变的、任意精度的有符号十进制定点数==
- 构造器:
- public BigDecimal(double val):根据double类型数据构建BigDecimal对象
- public BigDecimal(String val):根据字符串构建BigDecimal对象
- 常用方法:
- public BigDecimal add(BigDecimal augend)
- public BigDecimal subtract(BigDecimal subtrahend)
- public BigDecimal multiply(BigDecimal multiplicand)
- public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
public void testBigInteger() {
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi); //12433241123
// System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP)); //1130.486
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP)); //1130.486454545454545
}