前言

提到JavaString,都会提起String是不可变的。但是这点是真的吗?String的不可变是否可以破坏呢?

在验证之前,首先还是需要介绍一下String的不可变特性。

PS:这里还要提到自己遇到的面试题:

String str = "abc";


// 输出def
System.out.println(str);

String不可变特性

String的不可变指的是

  • String内部是使用一个被final修饰char数组value存储字符串的值
  • 数组value的值在对象构造的时候就已经进行了赋值
  • String不提供方法对数组value中的值进行修改
  • String中需要对value进行修改的方法(例如replace)则是直接返回一个新的String对象

所以String是不可变的。

破坏String的不可变

String的不可变其实主要是围绕value是一个值不可修改的char数组来实现的,但是利用Java的反射完全可以破坏这个特性。

关键代码如下:

        String str="test";
        // str对象的引用地址
        printAddresses("str",str);
        try {
            Field field=str.getClass().getDeclaredField("value");
            //
            char[] newChars={'h','e','l','l','o'};
            // 使private属性的值可以被访问
            field.setAccessible(true);
            // 替换value数组的值
            field.set(str,newChars);
            // 替换后的值
            System.out.println("str value:"+str);
            // 替换后,str对象的引用地址
            printAddresses("str",str);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

上面打印对象地址的方法是引用了如何获取到Java对象的地址的示例代码(原本是准备使用 System.identityHashCode()方法),内容如下:

    /**
     * 打印对象地址
     * @param label
     * @param objects
     */
    public void printAddresses(String label, Object... objects) {
        System.out.print(label + ":0x");
        long last = 0;
        Unsafe unsafe=getUnsafe();
        int offset = unsafe.arrayBaseOffset(objects.getClass());
        int scale = unsafe.arrayIndexScale(objects.getClass());
        switch (scale) {
            case 4:
                // 64位JVM
                long factor = 8;
                final long i1 = (unsafe.getInt(objects, offset) & 0xFFFFFFFFL) * factor;
                System.out.print(Long.toHexString(i1));
                last = i1;
                for (int i = 1; i < objects.length; i++) {
                    final long i2 = (unsafe.getInt(objects, offset + i * 4) & 0xFFFFFFFFL) * factor;
                    if (i2 > last)
                        System.out.print(", +" + Long.toHexString(i2 - last));
                    else
                        System.out.print(", -" + Long.toHexString(last - i2));
                    last = i2;
                }
                break;
            case 8:
                throw new AssertionError("Not supported");
        }
        System.out.println();
    }

    /**
     * 通过反射获取Unsafe对象
     * @return
     */
    private static Unsafe getUnsafe() {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            return (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

运行结果

str:0x76b4136e0
str value:hello
str:0x76b4136e0

从运行结果可以得知,使用反射可以对String对象的值进行修改,同时不会修改这个对象的对象地址。

总结

其实使用反射来破坏String的不可变存在取巧成分,但是实际上反射也是Java提供的特性,那么被人拿来使用就很难避免。

当时遇到前面提到的面试题的时候,还一直认为此题无解,但是随着自己不断学习后才发现,很多时候换个角度就能发现不同的办法。


Null
137 声望31 粉丝

免费的东西是最贵的,好走的只是下坡路