1. 引言
本教程将探讨在 Java 中创建可变 String
的几种方法。⚠️ 注意:Java 中字符串默认不可变,强行修改可能引发严重问题。
2. 字符串的不可变性
与 C/C++ 等语言不同,Java 中的 String
是不可变的。这意味着:
- 任何字符串修改都会在内存中创建新对象
- 原始字符串引用将指向新对象
- Java 提供了
StringBuffer
和StringBuilder
处理可变文本
✅ 核心要点:字符串不可变性是 Java 的核心设计,强行打破可能导致灾难性后果。
3. 通过反射创建可变字符串
使用 Java 反射机制可以尝试创建可变字符串。反射允许运行时检查和修改对象结构,但极其危险:
String myString = "Hello World";
String otherString = new String("Hello World");
// 获取 String 的 value 字段并修改访问权限
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
f.set(myString, "Hi World".toCharArray());
执行后观察:
System.out.println(otherString); // 输出: Hi World
❌ 踩坑警告:
- 所有引用该字面量的字符串都会被修改
- 现代版本 Java(JDK 9+)已修复此漏洞
- 可能导致整个程序崩溃
4. 字符集与字符串
4.1 字符集基础
更可控的方案是实现自定义字符集(Charset)。字符集是字符与二进制数据的映射字典:
- ASCII 仅支持 128 个字符
- UTF-8/UTF-16 支持多语言
- Java 内置支持多种编码(US-ASCII, ISO-8859-1 等)
✅ 关键概念:字符集确保文本在数字系统中正确解析。
4.2 使用字符集编解码
以 UTF-8 为例演示字符串编解码:
String inputString = "Hello, दुनिया";
// 获取 UTF-8 编码器
Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();
// 准备缓冲区
CharBuffer charBuffer = CharBuffer.wrap(inputString);
ByteBuffer byteBuffer = ByteBuffer.allocate(64);
// 编码
encoder.encode(charBuffer, byteBuffer, true);
// 解码方法
private static String decodeString(ByteBuffer byteBuffer) {
Charset charset = Charset.forName("UTF-8");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer decodedCharBuffer = CharBuffer.allocate(50);
decoder.decode(byteBuffer, decodedCharBuffer, true);
decodedCharBuffer.flip();
return decodedCharBuffer.toString();
}
验证代码:
String inputString = "hello दुनिया";
String result = ch.decodeString(ch.encodeString(inputString));
Assertions.assertEquals(inputString, result);
4.3 创建自定义字符集
通过继承 Charset
实现可变字符串:
private final Charset myCharset = new Charset("mycharset", null) {
private final AtomicReference<CharBuffer> cbRef = new AtomicReference<>();
@Override
public CharsetDecoder newDecoder() {
return new CharsetDecoder(this, 1.0f, 1.0f) {
@Override
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
cbRef.set(out); // 保存共享缓冲区引用
while (in.remaining() > 0) {
out.append((char) in.get());
}
return CoderResult.UNDERFLOW;
}
};
}
@Override
public CharsetEncoder newEncoder() {
return new CharsetEncoder(this, 1.0f, 1.0f) {
@Override
protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
while (in.hasRemaining()) {
if (!out.hasRemaining()) return CoderResult.OVERFLOW;
char c = in.get();
if (c > 127) return CoderResult.unmappableForLength(1);
out.put((byte) c);
}
return CoderResult.UNDERFLOW;
}
};
}
};
4.4 通过自定义字符集修改字符串
利用共享 CharBuffer
实现字符串修改:
// 创建可修改字符串
public String createModifiableString(String s) {
return new String(s.getBytes(), myCharset);
}
// 修改字符串内容
public void modifyString() {
CharBuffer cb = cbRef.get();
cb.position(0);
cb.put("something");
}
验证测试:
String s = createModifiableString("Hello");
Assert.assertEquals("Hello", s);
modifyString();
Assert.assertEquals("something", s); // ✅ 修改成功
5. 字符串可变性的最终思考
虽然技术上可行,但强烈不建议在生产环境使用可变字符串:
- 违背 Java 核心设计原则
- 可能导致不可预测的副作用
- 现代版本 Java 已加强防护
✅ 推荐方案:
- 使用
StringBuilder
(单线程) - 使用
StringBuffer
(多线程) - 这两个类专为可变字符序列设计
6. 结论
本文探讨了创建可变字符串的三种方法:
- 反射(已过时且危险)
- 字符集编解码(复杂但有控制)
- 自定义字符集(高级技巧)
❌ 重要提醒:除非特殊需求,否则始终使用 StringBuilder
/StringBuffer
。可变字符串属于"屠龙之技",了解即可,慎用为妙。
本文代码示例已托管在 GitHub