JavaSE基础——第十一章 常用类(一)
本专题主要为观看韩顺平老师《零基础30天学会Java》课程笔记,同时也会阅读其他书籍、学习其他视频课程进行学习笔记总结。如有雷同,不是巧合!
一、包装类(Wrapper Class)
- 定义:针对八种基本数据类型,包装成的引用类型,位于
java.lang
包中,主要用于在需要对象的上下文中使用基本数据类型。 - 作用:
- 使基本数据类型能够以对象形式存在:某些 Java 集合类(如 ArrayList、HashMap)只能存储对象,不能存储基本数据类型
- 提供更多操作方法:包装类提供了很多实用的方法,如类型转换、数值比较等
- 支持 null 值:基本数据类型不能为 null,而包装类可以
(1)类型转换
手动装箱和拆箱(JDK5前)
// 手动装箱
Integer i = new Integer(10);
Double d = new Double(3.14);
////////
Integer i = Integer.valueOf(10);
Double d = Double.valueOf(3.14);
Boolean b = Boolean.valueOf(true);
// 手动拆箱
int ii = i.intValue();
自动装箱和拆箱(JDK5后)
// 自动装箱:基本类型 -> 包装类
Integer i = 10; // 底层使用 Integer.valueOf(10),返回new Integer(10);// 自动拆箱:包装类 -> 基本类型
int num = i; // 相当于 i.intValue()
但是int[]数组和Integer[]数组不能互相转换:
-
例题:
🗯️Q:如何理解
Object obj1 = true;
A:自动装箱+向上转型
等效代码:
boolean primitive = true; // 基本类型 Boolean wrapper = Boolean.valueOf(primitive); // 装箱 Object obj1 = wrapper; // 向上转型
-
不可变性:包装类是不可变的,一旦创建就不能改变其值。任何看似"修改"的操作实际上都创建并返回新对象,原对象保持不变。(因为值不可变,才能安全地重用缓存对象)
-
示例:
Integer num = Integer.valueOf(10); // 创建值为10的Integer对象// 尝试"修改"值 Integer newNum = num + 5; // 实际过程:// 1. num自动拆箱为int 10// 2. 10 + 5 = 15// 3. 15自动装箱为新的Integer对象// 4. newNum引用新对象System.out.println(num); // 输出10 - 原对象未改变 System.out.println(newNum); // 输出15 - 是新对象
-
与String类型相互转换
+方式3:Integer j3 = Integer.parseInt(s);
// 方法返回int类型数据,使用自动装箱
// 字符串转数值
int num = Integer.parseInt("123");
double d = Double.parseDouble("3.14");// 数值转字符串
String s1 = Integer.toString(123);
String s2 = Double.toString(3.14);
(2)Integer类和Character类的常用方法
(3)缓存机制
Java 的包装类缓存机制是一种性能优化手段,它通过缓存常用范围内的包装类对象,减少对象创建的开销,提高内存使用效率。当使用 valueOf()
方法(或自动装箱)创建这些范围内的对象时,会返回缓存中的同一对象引用,而不是每次都创建新对象。
可以通过JVM参数调整Integer的缓存上限【下限固定,不可配置 】,设置后,Integer的缓存范围将变为-128~500:
-Djava.lang.Integer.IntegerCache.high=500
-
例题:
(4)比较方法
Integer a = 100;
Integer b = 100;// 比较值
boolean equal = a.equals(b); // true// 比较引用(注意缓存范围)
boolean sameRef = (a == b); // true(在缓存范围内)
- 使用
equals()
比较值【Objects
类中该方法默认与==
相同,但一些包装类中已经重写该方法,即比较对象内容是否相等】 - 使用
==
比较引用(注意缓存范围) - 当
Integer
对象与int
进行比较时(==
),Java 会自动将Integer
对象拆箱为int
基本类型,然后进行基本类型的值的比较;如果使用当Integer
对象的equals()
方法,则会将int
数据自动装箱为Integer
再比较值
二、String类
String类用于表示和操作字符串,每次更新都需要重新开辟空间,效率较低。
- 实现Serializable接口:代表String对象可以串行化,即可以在网络传输
- 实现Comparable接口:代表String对象可以自然排序(相互比较)
String类的构造函数
(1)基本特性
-
不可变性(Immutable):String 对象一旦创建,其地址就不能被修改,也不能通过给str.charAt(index)重新赋值更改;并且不能被继承。所有看似修改字符串的操作(如concat、replace等)实际上都是创建新的 String 对象。
String name = "jack"; name = "tom";
- "jack"对象本身(例如0x100)没有被修改,它仍然完整存在于内存中
- 改变的是name变量的引用(从指向0x100改为指向0x200)
-
存储在字符串常量池:Java 使用字符串常量池来优化字符串存储,相同的字符串字面量会指向池中的同一个对象。字符串的本质是char数组,对象中的属性char value[]用于存放字符串数组。
(2)创建String对象
// 方式1:使用字面量(推荐)
String s1 = "Hello";// 方式2:使用构造方法
String s2 = new String("Hello");// 方式3:从字符数组创建
char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String s3 = new String(charArray);// 方式4:从字节数组创建
byte[] byteArray = {72, 101, 108, 108, 111};
String s4 = new String(byteArray);
不同创建方式的区别
1. 使用字面量方式(直接赋值)
String s1 = "hello";
String s2 = "hello";
特点:
- 字符串存储在字符串常量池中(位于方法区)
- 相同内容的字符串字面量会指向同一个对象(复用机制)
s1 == s2
返回true
(地址相同)- 最节省内存的方式
- 编译时就能确定字符串内容
2. 使用 new
关键字
String s3 = new String("hello");
String s4 = new String("hello");
特点:
- 每次都会在堆内存中创建新的对象
- 即使内容相同,也会创建不同对象
s3 == s4
返回false
(地址不同)【指向堆中不同的value数组地址】- 会先在常量池创建"hello"(如果不存在),然后在堆中创建新对象
- 性能开销较大,通常不推荐
3. 从字符数组创建
char[] arr = {'h', 'e', 'l', 'l', 'o'};
String s5 = new String(arr)
特点:
- 根据字符数组内容创建新字符串
- 字符串内容在运行时确定
- 每次调用都会创建新对象
- 适用于需要动态构建字符串的场景
4. 从字节数组创建
byte[] bytes = {104, 101, 108, 108, 111};
String s6 = new String(bytes);
特点:
- 将字节数组按指定字符集解码为字符串
- 可以指定字符集:
new String(bytes, "UTF-8")
- 适用于网络传输或文件读取后的字节转换
5. 使用 intern()
方法
String s7 = new String("hello").intern();
特点:
-
会检查字符串是否已在常量池中存在
-
如果存在则返回常量池中的引用,否则将字符串添加到常量池
-
s7 == "hello"
会返回true
-
适用于需要确保字符串被共享的场景
-
intern()
方法介绍用于手动将字符串对象添加到字符串常量池中,或返回已存在的常量池中的字符串引用。
1. 方法定义
public native String intern();
- 这是一个本地方法(native method),其实现由 JVM 提供
- 返回值为规范化的字符串表示(即常量池中的字符串)
2. 核心作用
intern()
方法的主要作用是:- 如果字符串常量池中已经包含一个等于此 String 对象的字符串(用
equals()
方法确定),则返回池中的字符串 - 如果不包含,则将此 String 对象添加到池中,并返回此 String 对象的引用
3. 使用示例
基本使用
String s1 = new String("hello"); // 在堆中创建新对象 String s2 = s1.intern(); // 返回常量池中的"hello"System.out.println(s1 == s2); // false System.out.println("hello" == s2); // true
更复杂的例子
String s3 = new String("world") + new String("!"); // 在堆中创建"world!"对象 String s4 = "world!"; // 在常量池创建"world!"对象 String s5 = s3.intern(); // 发现常量池已有"world!",返回常量池引用System.out.println(s3 == s4); // false System.out.println(s4 == s5); // true
4. 内存行为分析
情况1:常量池中已存在相同字符串
String a = "java"; String b = new String("java").intern();System.out.println(a == b); // true
内存变化:
- 字面量"java"先在常量池创建
new String("java")
在堆中创建新对象intern()
发现常量池已有"java",返回常量池引用
情况2:常量池中不存在相同字符串
String c = new String("python").intern(); String d = "python";System.out.println(c == d); // true
内存变化:
new String("python")
先在堆中创建对象intern()
将堆中的"python"添加到常量池- 字面量"python"直接使用常量池中的引用
5. 重要特性
- 自动入池:使用字面量创建的字符串会自动放入常量池
- 手动控制:
intern()
提供了手动控制字符串入池的能力 - 性能影响:频繁调用
intern()
可能导致常量池过大,影响性能 - JVM 差异:不同 JVM 实现可能有不同的字符串常量池策略
6. 性能考虑
- 优点:可以减少重复字符串的内存占用,使字符串比较更快(
==
代替equals()
) - 缺点:
- 常量池大小有限(受限于方法区/元空间大小)
intern()
调用本身有性能开销- 可能导致 Full GC 时间变长
7. 现代 JVM 的优化
在较新的 JVM 版本中(如 Java 7+),字符串常量池被移到了堆内存中,这使得:
- 常量池大小不再受限于永久代大小
- 可以像普通对象一样被垃圾回收
- 减少了内存溢出的风险
内存分配对比
创建方式 | 存储位置 | 是否复用相同内容 | 适用场景 |
---|---|---|---|
字面量 | 字符串常量池 | 是 | 已知的固定字符串 |
new String() | 堆内存 | 否 | 需要独立对象的场景 |
字符数组构造 | 堆内存 | 否 | 动态构建字符串 |
字节数组构造 | 堆内存 | 否 | 字节到字符串的转换 |
intern() | 可能常量池 | 是 | 需要共享字符串的场景 |
性能建议
- 优先使用字面量方式:最节省内存,性能最好
- 避免不必要的
new String()
:除非确实需要独立对象 - 大量字符串拼接使用
StringBuilder
:比直接+
更高效 - 谨慎使用
intern()
:虽然可以节省内存,但常量池大小有限
示例验证
String a = "hello";
String b = "hello";
String c = new String("hello");
String d = new String("hello");
String e = c.intern();System.out.println(a == b); // true (同一常量)
System.out.println(a == c); // false (常量池 vs 堆)
System.out.println(c == d); // false (两个不同堆对象)
System.out.println(a == e); // true (intern后与常量池相同)
-
例题1:
-
例题2:
-
例题3:String a = "hello" + "abc"创建了几个对象?
这个语句实际上只创建了 1 个 String 对象,原因如下:
- 编译期常量折叠(Constant Folding):编译器会在编译阶段将字符串字面量的连接运算优化为一个完整的字符串常量
- 源代码:
"hello" + "abc"
- 编译后等价于:
"helloabc"
- 源代码:
- 字符串常量池存储:优化后的完整字符串
"helloabc"
会被放入字符串常量池- 只会在常量池中创建这一个对象
- 不会创建中间的
"hello"
和"abc"
的临时对象
验证代码:
public class StringTest {public static void main(String[] args) {String a = "hello" + "abc";String b = "helloabc";System.out.println(a == b); // 输出 true,证明是同一个对象} }
- 情况1:纯字面量连接(编译期确定)
String a = "hello" + "abc"; // 1个对象 ("helloabc")
- 情况2:包含变量的连接(运行期确定)
String b = "hello"; String c = b + "abc"; // 会创建新对象,不享受编译期优化
- 情况3:final 变量的连接(仍可优化)
final String d = "hello"; String e = d + "abc"; // 1个对象 ("helloabc"),因为d是final
- 纯字面量连接:编译器会优化为单个字符串常量,只创建1个对象
- 包含变量连接:会在运行时创建新对象(通常通过StringBuilder实现)
- final变量连接:仍可享受编译期优化,视为常量表达式
- 编译期常量折叠(Constant Folding):编译器会在编译阶段将字符串字面量的连接运算优化为一个完整的字符串常量
-
例题4:String a = "hello"; String b = "abc"; String c = a + b;// String b = a + “abc”;创建了几个对象?
-
初始化字符串常量,在常量池创建
”hello”
对象,和”abc”
对象 -
字符串连接操作:
String c = new StringBuilder().append(a).append(b).toString();
-
创建
StringBuilder
对象:用于执行连接操作 -
StringBuilder.toString()
创建的新String
对象
-
-
最后
c
指向堆中的对象(String) value[]→池中的字符串常量”helloabc”
常量相加,看的是池;变量相加,看的是堆。
-
-
例题5:
(3)常用方法
1 获取信息
length()
:返回字符串长度charAt(int index)
:返回指定索引处的字符【注意不能用Str[index]方式获取】indexOf(String str)
:返回子字符串/单个字符首次出现的位置,从0开始,找不到返回-1lastIndexOf(String str)
:返回子字符串最后一次出现的位置
2 比较操作
-
equals(Object anObject)
:区分大小写,判断内容是否相等 -
equalsIgnoreCase(String anotherString)
:忽略大小写,比较内容 -
compareTo(String anotherString)
:字典序比较 -
startsWith(String prefix)
:检查是否以指定前缀开头 -
endsWith(String suffix)
:检查是否以指定后缀结尾
3 子字符串操作
substring(int beginIndex)
:从指定位置到末尾的子字符串substring(int beginIndex, int endIndex)
:指定区间的子字符串[)concat(String str)
:字符串连接【可以一直在后面.concat(strx)】(通常使用+运算符更方便)
4 转换操作
-
toLowerCase()
:转换为小写 -
toUpperCase()
:转换为大写 -
trim()
:去除前后空白字符 -
replace(char oldChar, char newChar)
:【不支持正则表达式,只能替换单个字符】替换字符串中所有出现的指定字符;区分大小写;返回新字符串 -
replace(CharSequence target, CharSequence replacement)
:【不支持正则表达式】替换字符串中所有出现的指定字符序列;参数可以是 String、StringBuilder 或 StringBuffer -
replaceAll(String regex, String replacement)
:使用正则表达式匹配并替换所有符合的子字符串;功能最强大但也最复杂;简单替换优先使用非正则版本String str = "Phone: 123-456-7890"; String result = str.replaceAll("\\\\d", "*"); System.out.println(result); // 输出 "Phone: ***-***-****"
-
replaceFirst(String regex, String replacement)
:只替换第一个匹配的子字符串,使用正则表达式匹配String str = "apple banana apple"; String result = str.replaceFirst("apple", "orange"); System.out.println(result); // 输出 "orange banana apple"
5 其他实用方法
-
split(String regex)
:按正则表达式分割字符串,返回一个String数组 -
matches(String regex)
:检查是否匹配正则表达式 -
contains(CharSequence s)
:检查是否包含指定字符序列 -
isEmpty()
:检查是否为空字符串 -
isBlank()
:检查是否为空白字符串(Java 11+) -
public char[] toCharArray()
:将字符串转换为一个新的字符数组,包含字符串中的所有字符;对返回数组的修改不会影响原字符串【需要修改字符串内容时(先转为char数组,修改后再转为String)】 -
public static String format(String format, Object... args)
:使用指定的格式字符串和参数返回一个格式化字符串 // 占位符:%s
- 字符串;%c
-字符;%d
- 十进制整数;%f
- 浮点数;%n
- 换行符;%%
- 百分号本身// 基本格式化 String formatted = String.format("Name: %s, Age: %d", "Alice", 25); System.out.println(formatted); // 输出 "Name: Alice, Age: 25"// 数字格式化 String price = String.format("Price: %.2f", 19.99); // .2 - 精度控制,保留2位小数 System.out.println(price); // 输出 "Price: 19.99"// 多参数格式化 String message = String.format("%s scored %d/%d (%.2f%%)", "Bob", 85, 100, 85.0); System.out.println(message); // 输出 "Bob scored 85/100 (85.00%)"// 日期格式化 import java.time.LocalDate; LocalDate date = LocalDate.now(); String dateStr = String.format("Today is %tF", date); System.out.println(dateStr); // 输出 "Today is 2023-11-15"(示例日期)// 控制宽度和对齐 String table = String.format("|%-10s|%10d|", "Item", 123); // 总宽度10,左对齐;总宽度10,右对齐 System.out.println(table); // 输出 "|Item | 123|"// 填充零 String id = String.format("ID: %05d", 42); System.out.println(id); // 输出 "ID: 00042" System.out.println(String.format("%010.2f", 123.456)); // "0000123.46"
System.out.printf()
内部使用的就是String.format()
,只是直接输出而不是返回字符串。
三、StringBuffer类
java.lang.StringBuffer
是 Java 中一个重要的可变字符串类,和String的大多数方法相同,它允许在不创建新对象的情况下修改字符串内容,可变长度,当空间容量不够时扩展并更新地址;是一个容器。但是不能被继承。
父类中的属性char[] value
,该数组存放字符串内容;因为不是final
修饰,所以直接存放在堆中。
(1)构造器
StringBuffer()
构造器逐步调试经历,创建一个大小为16的char[],用于存放字符序列:
StringBuffer(String str)
构造器扩容数组:
如果str为空,则调用str.length()会抛出空指针异常
(2)和String的相互转换
(3)常用方法
1 追加内容
可以用.append()一直追加
StringBuffer sb = new StringBuffer();
sb.append("Hello"); // 追加字符串
sb.append(' '); // 追加字符
sb.append(123); // 追加数字
sb.append(true); // 追加布尔值
System.out.println(sb); // 调用toString()
// 结果: "Hello 123true"
①追加:
如果append(null),会把null转换为字符串”null”,并且长度+4
②输出:
-
例题1:
-
例题2:
输入商品名称和商品价格,要求打印效果示例: 商品名 商品价格 手机 123,564.59 要求:价格的小数点前面每三位用逗号隔开
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.print("商品名:");String commodity = scanner.nextLine(); // 商品名System.out.print("价格:");String price = scanner.nextLine(); // 商品价格scanner.close();StringBuffer price_buffer = new StringBuffer(price);for (int i = price_buffer.lastIndexOf(".") - 3; i > 0; i=i-3) {price_buffer.insert(i, ',');}System.out.println("商品名\\t商品价格");System.out.println(commodity + "\\t" + price_buffer);} }
运行结果:
2 插入内容
StringBuffer sb = new StringBuffer("World");
sb.insert(0, "Hello "); // 在索引0处插入,原来的内容自动后移
// 结果: "Hello World"
3 删除内容
StringBuffer sb = new StringBuffer("Hello World");
sb.delete(5, 11); // 删除索引[5, 11)的内容
// 结果: "Hello"
sb.deleteCharAt(4);
4 替换内容
StringBuffer sb = new StringBuffer("Hello World");
sb.replace(6, 11, "Java"); // 替换索引6-10的内容
// 结果: "Hello Java"
5 反转字符串
StringBuffer sb = new StringBuffer("Hello");
sb.reverse();
// 结果: "olleH"
6 容量相关方法
StringBuffer sb = new StringBuffer();
sb.capacity(); // 返回当前容量
sb.ensureCapacity(50); // 确保最小容量
sb.trimToSize(); // 将容量调整为实际大小
(4)重要特点
-
自动扩容:当内容超过当前容量时,自动按 (旧容量*2)+2 的规则扩容
-
线程安全:所有公共方法都有 synchronized 修饰
-
性能优化:适合频繁修改字符串的场景
-
方法链:支持方法链式调用
String result = new StringBuffer().append("Hello").append(" ").append("World").toString();
四、StringBuilder类
- 一个可变的字符序列,提供一个与StringBuffer兼容的API,但不保证同步,不保证线程安全(没有synchronized修饰)。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer快(在单线程环境下性能更高)
- 在StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据
- 不能被继承
- 字符序列存放在父类AbstractStringBuilder的char[] value中,因此存放在堆中
与StringBuffer相同
-
String、StringBuffer、StringBuilder比较:
- String复用率高,指的是不同的对象都指向常量池的同一个地址
构造方法、常用方法与StringBuffer类相同。