Подтвердить что ты не робот

Создайте переменную java.lang.String

Общеизвестно, что Java String неизменяемы. Неизменяемые строки - отличное дополнение к java с момента его создания. Невосприимчивость обеспечивает быстрый доступ и большую оптимизацию, значительно меньше подверженности ошибкам по сравнению с строками в стиле C и помогает обеспечить безопасность модели.

Возможно создание изменчивого без использования хаков, а именно

  • java.lang.reflect
  • sun.misc.Unsafe
  • Классы в bootstrap classloader
  • JNI (или JNA, поскольку он требует JNI)

Но возможно ли это только в простой Java, так что строка может быть изменена в любое время? Вопрос: Как?

4b9b3361

Ответ 1

Создав java.lang.String с помощью конструктора Charset, вы можете ввести свой собственный Charset, который приносит ваш собственный CharsetDecoder. CharsetDecoder получает ссылку на объект CharBuffer в методе decodeLoop. CharBuffer обертывает char [] исходного объекта String. Поскольку CharsetDecoder имеет ссылку на него, вы можете изменить базовый char [] с помощью CharBuffer, таким образом, у вас есть изменяемая строка.

public class MutableStringTest {


    // http://stackoverflow.com/questions/11146255/how-to-create-mutable-java-lang-string#11146288
    @Test
    public void testMutableString() throws Exception {
        final String s = createModifiableString();
        System.out.println(s);
        modify(s);
        System.out.println(s);
    }

    private final AtomicReference<CharBuffer> cbRef = new AtomicReference<CharBuffer>();
    private String createModifiableString() {
        Charset charset = new Charset("foo", null) {
            @Override
            public boolean contains(Charset cs) {
                return false;
            }

            @Override
            public CharsetDecoder newDecoder() {
                CharsetDecoder cd = 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;
                    }
                };
                return cd;
            }

            @Override
            public CharsetEncoder newEncoder() {
                return null;
            }
        };
        return new String("abc".getBytes(), charset);
    }
    private void modify(String s) {
        CharBuffer charBuffer = cbRef.get();
        charBuffer.position(0);
        charBuffer.put("xyz");
    }

}

Выполнение печати кода

abc
zzz

Я не знаю, как правильно реализовать decodeLoop(), но мне все равно сейчас:)

Ответ 2

Вопрос получил хороший ответ от @mhaller. Я бы сказал, что так называемая головоломка была довольно простой, и, просто взглянув на доступные c-tors String, вы должны выяснить, как часть,

Пошаговое

Представляющий интерес C-tor ниже, если вы хотите взломать/взломать/искать уязвимость безопасности, всегда искать не конечные произвольные классы. Случай здесь java.nio.charset.Charset


//String
public String(byte bytes[], int offset, int length, Charset charset) {
    if (charset == null)
        throw new NullPointerException("charset");
    checkBounds(bytes, offset, length);
    char[] v = StringCoding.decode(charset, bytes, offset, length);
    this.offset = 0;
    this.count = v.length;
    this.value = v;
}
The c-tor offers supposedly-fast way to convert byte[] to String by passing the Charset not the chartset name to avoid the lookup chartsetName- > charset. It also allows passing an arbitrary Charset object to create String. Charset main routing converts the content of java.nio.ByteBuffer to CharBuffer. The CharBuffer may hold a reference to char[] and it available via array(), also the CharBuffer is fully modifiable.

    //StringCoding
    static char[] decode(Charset cs, byte[] ba, int off, int len) {
        StringDecoder sd = new StringDecoder(cs, cs.name());
        byte[] b = Arrays.copyOf(ba, ba.length);
        return sd.decode(b, off, len);
    }

    //StringDecoder
    char[] decode(byte[] ba, int off, int len) {
        int en = scale(len, cd.maxCharsPerByte());
        char[] ca = new char[en];
        if (len == 0)
            return ca;
        cd.reset();
        ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
        CharBuffer cb = CharBuffer.wrap(ca);
        try {
            CoderResult cr = cd.decode(bb, cb, true);
            if (!cr.isUnderflow())
                cr.throwException();
            cr = cd.flush(cb);
            if (!cr.isUnderflow())
                cr.throwException();
        } catch (CharacterCodingException x) {
            // Substitution is always enabled,
            // so this shouldn't happen
            throw new Error(x);
        }
        return safeTrim(ca, cb.position(), cs);
    }

Чтобы предотвратить изменение char[], разработчики java копируют массив так же, как и любую другую конструкцию String (например, public String(char value[])). Однако существует исключение - если SecurityManager не установлен, char [] не копируется.


    //Trim the given char array to the given length
    //
    private static char[] safeTrim(char[] ca, int len, Charset cs) {
        if (len == ca.length 
                && (System.getSecurityManager() == null
                || cs.getClass().getClassLoader0() == null))
            return ca;
        else
            return Arrays.copyOf(ca, len);
    }

Поэтому, если нет SecurityManager, абсолютно возможно иметь модифицируемый CharBuffer/ char [], на который ссылается String.

Теперь все хорошо выглядит - кроме того, скопировано byte[] (выделено полужирным). Это где разработчики java стали ленивыми и массово ошибочными.

Копия необходима, чтобы предотвратить изъятие Charset (пример выше), чтобы изменить исходный байт []. Однако представьте себе случай наличия буфера 512KB byte[], который содержит несколько строк. Попытка создать одну маленькую, несколько диаграмм - new String(buf, position, position+32,charset), что приводит к массивной копии в 512 Кбайт []. Если буфер был 1 КБ или около того, удар никогда не будет действительно замечен. Тем не менее, при использовании больших буферов удар производительности действительно огромен. Простое исправление было бы скопировать соответствующую часть.

... или хорошо проектировщики java.nio думали о введении только для чтения буферов. Просто называть ByteBuffer.asReadOnlyBuffer() было бы достаточно (если Charset.getClassLoader()!= Null) * Иногда даже ребята, работающие над java.lang, могут полностью ошибиться.

* Class.getClassLoader() возвращает значение null для классов начальной загрузки, то есть те, которые идут непосредственно с JVM.

Ответ 3

Я бы сказал, что StringBuilder (или StringBuffer для многопоточного использования). Да, в конце вы получаете неизменяемую String. Но это путь.

Например, лучший способ добавить строки в цикле - использовать StringBuilder. Сама Java использует StringBuilder, когда вы используете "fu" + variable + "ba".

http://docs.oracle.com/javase/6/docs/api/java/lang/StringBuilder.html

добавить (реветь).append(5).appen( "dfgdfg" ) ToString();.

Ответ 4

// How to achieve String Mutability

import java.lang.reflect.Field; 

public class MutableString {

    public static void main(String[] args) { 
        String s = "Hello"; 

        mutate(s);
        System.out.println(s); 

    } 

    public static void mutate(String s) {
        try {

            String t = "Hello world";
            Field val = String.class.getDeclaredField("value"); 
            Field count = String.class.getDeclaredField("count"); 
            val.setAccessible(true); 
            count.setAccessible(true); 

            count.setInt (s, t.length ());
            val.set (s, val.get(t));
        } 
        catch (Exception e) { e.printStackTrace(); }
    } 

}

Ответ 5

Упрощенный способ обмена курсором класса начальной загрузки java и javac

1) Перейдите к установке jdk и скопируйте ее в отдельную папку rt.jar и src.zip

2) Распакуйте String.java из источника zip и измените его значение частного поля  внутренний массив char для публики

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    public final char value[];

3) Скомпилируйте измененную строку String.java с помощью javac:

javac String.java

4) Переместите скомпилированный String.class и другие скомпилированные классы в rt.jar в этом каталоге

5) Создайте тестовый класс, который использует частное поле String

package exp;

    class MutableStringExp { 

        public static void main(String[] args) {
            String letter = "A";
            System.out.println(letter);
            letter.value[0] = 'X';
            System.out.println(letter);
        }
    }

6) Создать пустой каталог target и скомпилировать тестовый класс

javac -Xbootclasspath:rt.jar -d target MutableStringExp.java

7) Запустите его

java -Xbootclasspath:rt.jar -cp "target" exp.MutableStringExp

:

A
X

P.S это будет работать только с измененным rt.jar и использовать этот параметр для переопределения rt.jar является нарушением лицензии jre.