String真的不可变吗

前言

提到JavaString,都会提起String是不可变的。可是这点是真的吗?String的不可变是否能够破坏呢?面试

在验证以前,首先仍是须要介绍一下String的不可变特性。数组

PS:这里还要提到本身遇到的面试题:ide

String str = "abc";


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

String不可变特性

String的不可变指的是学习

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

因此String是不可变的。.net

破坏String的不可变

String的不可变其实主要是围绕value是一个值不可修改的char数组来实现的,可是利用Java的反射彻底能够破坏这个特性。code

关键代码以下:对象

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()方法),内容以下:blog

/**
     * 打印对象地址
     * @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提供的特性,那么被人拿来使用就很难避免。get

当时遇到前面提到的面试题的时候,还一直认为此题无解,可是随着本身不断学习后才发现,不少时候换个角度就能发现不一样的办法。

相关文章
相关标签/搜索