Создайте переменную java.lang.String
Общеизвестно, что Java String
неизменяемы. Неизменяемые строки - отличное дополнение к java с момента его создания. Невосприимчивость обеспечивает быстрый доступ и большую оптимизацию, значительно меньше подверженности ошибкам по сравнению с строками в стиле C и помогает обеспечить безопасность модели.
Возможно создание изменчивого без использования хаков, а именно
-
java.lang.reflect
-
sun.misc.Unsafe
- Классы в bootstrap classloader
- JNI (или JNA, поскольку он требует JNI)
Но возможно ли это только в простой Java, так что строка может быть изменена в любое время? Вопрос: Как?
Ответы
Ответ 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
.