1. 引言

本教程将探讨在 Java 中创建可变 String 的几种方法。⚠️ 注意:Java 中字符串默认不可变,强行修改可能引发严重问题。

2. 字符串的不可变性

与 C/C++ 等语言不同,Java 中的 String不可变的。这意味着:

  • 任何字符串修改都会在内存中创建新对象
  • 原始字符串引用将指向新对象
  • Java 提供了 StringBufferStringBuilder 处理可变文本

核心要点:字符串不可变性是 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. 结论

本文探讨了创建可变字符串的三种方法:

  1. 反射(已过时且危险)
  2. 字符集编解码(复杂但有控制)
  3. 自定义字符集(高级技巧)

重要提醒:除非特殊需求,否则始终使用 StringBuilder/StringBuffer。可变字符串属于"屠龙之技",了解即可,慎用为妙。

本文代码示例已托管在 GitHub


原始标题:Create a “Mutable” String in Java