java学习--String

在 Java 中,String 是最常用的核心类之一,用于表示不可变的字符序列,属于 java.lang 包(无需手动导入)。以下从核心特性、常用操作、内存原理、常见陷阱等维度全面解析:

一、核心特性

1. 不可变性(Immutable)

String 对象一旦创建,其内部的字符数组(char[] value,Java 9+ 改为 byte[] 以节省内存)就无法修改。所有看似修改的操作(如 substringreplace)都会返回新的 String 对象,原对象保持不变。

String s = "hello";
s += " world"; // 实际创建了新对象,原"hello"仍存在
System.out.println(s); // 输出:hello world

不可变的好处

  • 线程安全:多线程访问无需同步;
  • 可哈希:hashCode 缓存(计算一次后永久不变),适合作为 HashMap 键;
  • 字符串常量池复用。
2. 字符串常量池(String Pool)

JVM 为优化内存,在方法区(元空间) 中维护一个字符串常量池:

  • 直接赋值(String s = "abc"):先检查常量池,存在则复用引用,不存在则创建新对象放入常量池;
  • new String("abc"):会创建两个对象(常量池的 "abc" + 堆中的 String 对象),堆对象引用常量池的字符数组。
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");

System.out.println(s1 == s2); // true(常量池同一对象)
System.out.println(s1 == s3); // false(堆 vs 常量池)
System.out.println(s1.equals(s3)); // true(内容相等)
3. 常用创建方式
方式说明示例
直接赋值优先使用常量池,效率最高String s = "java";
new String()堆中创建新对象,避免使用(除非特殊场景)String s = new String("java");
String.valueOf()安全转换基本类型 / 对象(避免 null)String s = String.valueOf(123);
字符数组构造从字符数组创建char[] arr = {'j','a','v','a'}; String s = new String(arr);

二、常用方法(高频)

方法功能示例
length()获取字符串长度"java".length(); // 4
charAt(int index)获取指定索引字符"java".charAt(1); // 'a'
equals(Object obj)比较内容(区分大小写)"Java".equals("java"); // false
equalsIgnoreCase()忽略大小写比较"Java".equalsIgnoreCase("java"); // true
contains(CharSequence s)判断是否包含子串"hello".contains("ell"); // true
indexOf(String s)查找子串首次出现索引(无则返回 -1)"hello".indexOf("l"); // 2
lastIndexOf(String s)查找子串最后出现索引"hello".lastIndexOf("l"); // 3
substring(int start)从 start 截取到末尾"hello".substring(2); // "llo"
substring(int start, int end)截取 [start, end) 区间"hello".substring(1,3); // "el"
trim()去除首尾空白(Java 11+ 用 strip() 更通用)" java ".trim(); // "java"
replace(CharSequence old, CharSequence new)替换所有匹配子串"java".replace("a", "o"); // "jovo"
split(String regex)按正则分割字符串"a,b,c".split(","); // ["a","b","c"]
toLowerCase()/toUpperCase()转小写 / 大写"Java".toLowerCase(); // "java"
isEmpty()/isBlank()判断空字符串(Java 11+ isBlank() 包含空白)" ".isBlank(); // true
concat(String str)拼接字符串(等价于 +,但效率低)"a".concat("b"); // "ab"

三、内存与性能优化

1. 避免频繁拼接(+ 号)

循环中使用 + 拼接字符串会创建大量临时对象,效率极低。推荐使用 StringBuilder(非线程安全)或 StringBuffer(线程安全)

// 低效
String s = "";
for (int i = 0; i < 1000; i++) {
    s += i; // 每次创建新 String
}

// 高效
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i); // 仅操作一个字符数组
}
String result = sb.toString();
2. intern() 方法

手动将堆中的 String 对象加入常量池,复用引用:

String s1 = new String("java");
String s2 = s1.intern(); // 将 s1 内容加入常量池并返回常量池引用
String s3 = "java";
System.out.println(s2 == s3); // true

适用场景:大量重复字符串(如数据库返回的重复字段),减少内存占用。

3. Java 9 优化:byte[] 替代 char[]

Java 9 前,String 用 char[] 存储(每个字符占 2 字节);Java 9 后,根据字符编码自动选择 byte[](Latin-1 占 1 字节,UTF-16 占 2 字节),节省内存。

四、常见陷阱

1. == vs equals()
  • ==:比较对象引用(是否指向同一内存地址);
  • equals():比较字符串内容(String 重写了 equals() 方法)。
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false(不同对象)
System.out.println(s1.equals(s2)); // true(内容相同)
2. 空指针风险

调用 null 的 String 方法会抛出 NullPointerException,建议先判空:

String s = null;
// 错误:NPE
// s.equals("abc");

// 正确:常量在前,或先判空
"abc".equals(s); // false
Objects.nonNull(s) && s.equals("abc"); // 更严谨
3. substring() 内存泄漏(Java 7 前)

Java 7 前,substring() 会复用原字符串的 char[],即使截取小片段,也会持有原大字符串的引用,导致内存泄漏。Java 7+ 已修复(创建新的 char[])。

五、总结

  1. String 是不可变的,所有修改操作返回新对象;
  2. 优先使用直接赋值(常量池),避免 new String()
  3. 拼接字符串用 StringBuilder(单线程)/StringBuffer(多线程);
  4. 比较内容用 equals(),比较引用用 ==
  5. 大量重复字符串用 intern() 优化内存。
### Java String 类官方文档及相关方法示例 #### JDK API 文档的重要性 Java 提供了全面的 JDK API 文档,这是学习 `String` 类以及其他核心功能的重要资源。通过查阅这些文档,开发者能够深入了解各种方法的功能及其使用方式[^1]。 #### String 的基本概念与常用方法 以下是关于 `String` 类的一些重要知识点以及其常见方法: - **字符串不可变性** 在 Java 中,`String` 是不可变对象 (immutable object),这意味着一旦创建了一个字符串实例,它的值就不能被更改。任何修改操作都会返回一个新的字符串对象。 - **substring() 方法** `String.substring()` 方法用于提取子字符串,它有两种重载形式: - `public String substring(int beginIndex)`:从指定索引位置开始截取到结尾。 - `public String substring(int beginIndex, int endIndex)`:从起始索引到结束索引前一位进行截取[^2]。 示例代码如下所示: ```java public class SubstringExample { public static void main(String[] args) { String str = "HelloWorld"; System.out.println(str.substring(5)); // 输出: World System.out.println(str.substring(0, 5)); // 输出: Hello } } ``` - **Boolean 解析** 使用静态方法 `Boolean.parseBoolean(String s)` 可以将字符串转换成布尔值。只有当输入字符串不为空且忽略大小写等于 `"true"` 时才返回 `true`;其他情况下均返回 `false`[^4]。 示例代码如下: ```java public class BooleanParseExample { public static void main(String[] args) { System.out.println(Boolean.parseBoolean("True")); // true System.out.println(Boolean.parseBoolean("FALSE")); // false System.out.println(Boolean.parseBoolean(null)); // false } } ``` #### 接口的作用于规范定义 虽然本问题是针对 `String` 类的具体实现细节展开讨论,但值得一提的是,在更广泛的上下文中,接口作为 Java 编程语言的一部分起到了至关重要的角色——它们不仅限定了行为契约还促进了模块间解耦合设计思路的发展方向之一即面向接口编程理念的应用实践过程中的具体体现形式表现为多个类共同遵循某个特定类型的约束条件从而达到提高程序灵活性的目的同时也有助于增强系统的可维护性和扩展能力等方面表现出色之处在于能够让不同的开发人员专注于各自负责的部分而无需关心对方内部是如何工作的只要满足约定即可正常协作完成整个项目的构建工作流程图展示如下内容结构层次清晰明了便于理解和记忆掌握要点快速上手实战演练效果显著提升效率减少错误发生几率等等优点值得推荐给广大初学者朋友们尝试体验一下看看是否适合自己当前阶段的学习需求情况再做进一步考虑决定吧[^3]!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值