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

當時遇到前面提到的面試題的時候,還一直認爲此題無解,可是隨着本身不斷學習後才發現,不少時候換個角度就能發現不一樣的辦法。

相關文章
相關標籤/搜索