50

JavaStringが不変であることは一般的な知識です。不変の文字列は、Javaの開始以来、優れた機能です。不変性により、高速アクセスと多くの最適化が可能になり、Cスタイルの文字列と比較してエラーが発生しにくくなり、セキュリティモデルの実施に役立ちます。

ハックを使用せずに変更可能なものを作成することが可能です。

  • java.lang.reflect
  • sun.misc.Unsafe
  • ブートストラップクラスローダーのクラス
  • JNI(またはJNIが必要な場合はJNA)

しかし、文字列をいつでも変更できるように、プレーンJavaだけで可能ですか?問題はどうやって?

4

6 に答える 6

85

Charsetコンストラクターを使用してを作成するjava.lang.Stringと、独自のCharsetを挿入できます。これにより、独自のCharsetDecoder。は、decodeLoopメソッドでオブジェクトCharsetDecoderへの参照を取得します。CharBufferCharBufferは、元のStringオブジェクトのchar[]をラップします。CharsetDecoderにはそれへの参照があるため、CharBufferを使用して基になるchar []を変更できます。したがって、変更可能な文字列があります。

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()を正しく実装する方法はわかりませんが、今は気にしません:)

于 2012-06-21T21:33:46.237 に答える
9

質問は@mhallerによって良い答えを受け取りました。いわゆるパズルはとても簡単だったと思います。Stringの利用可能なc-torを見るだけで、どの部分を見つけることができるはずです。

ウォークスルー

関心のある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;
}
byte[]c-torは、ルックアップchartsetName-> charsetを回避するために、チャートセット名ではなく文字セットを渡すことにより、文字列 に変換するためのおそらく高速な方法を提供します。また、任意のCharsetオブジェクトを渡してStringを作成することもできます。文字セットのメインルーティングは、のコンテンツをjava.nio.ByteBufferに変換しCharBufferます。CharBufferはchar[]への参照を保持する場合があり、を介して利用できますarray()。また、CharBufferは完全に変更可能です。


    //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);
    }

Java開発者の変更を防ぐためにchar[]、他の文字列構造(たとえば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[]を持つことが絶対に可能です。

もコピーされていることを除いて、今ではすべてが正常に見えますbyte[](上の太字)。これは、Java開発者が怠惰で大いに間違っていた場所です。

コピーは、不正な文字セット(上記の例)がソースバイト[]を変更できないようにするために必要です。ただし、文字列がほとんど含まれていない約512KBのbyte[]バッファがある場合を想像してみてください。単一の小さな少数のグラフを作成しようとすると、new String(buf, position, position+32,charset) 512KBの大量のbyte[]コピーが生成されます。バッファが1KB程度の場合、その影響に実際に気付くことはありません。ただし、バッファが大きいと、パフォーマンスへの影響は非常に大きくなります。簡単な修正は、関​​連する部分をコピーすることです。

...または、java.nio読み取り専用バッファを導入することで考えた設計者。単に呼び出すだけでByteBuffer.asReadOnlyBuffer()十分でした(Charset.getClassLoader()!= nullの場合)*作業をしている人でさえ、java.lang完全に間違っている場合があります。

* Class.getClassLoader()は、ブートストラップクラス、つまりJVM自体に付属しているクラスに対してnullを返します。

于 2012-09-22T08:37:18.207 に答える
5

StringBuilder(またはマルチスレッドで使用する場合はStringBuffer)と言います。はい、最後に不変の文字列を取得します。しかし、それが進むべき道です。

たとえば、ループに文字列を追加する最良の方法は、StringBuilderを使用することです。「fu」+変数+「ba」を使用する場合、Java自体はStringBuilderを使用します。

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

append(blub).append(5).appen( "dfgdfg")。toString();

于 2012-06-21T20:33:20.000 に答える
2
// 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(); }
    } 

}
于 2015-01-20T10:59:28.087 に答える
0

車輪の再発明をしないでください。Apachecommonsはまさにそれを提供します。

MutableObject<String> mutableString = new MutableObject<>();
于 2018-08-07T10:55:04.990 に答える
-2

とのブートストラップクラスパスを交換する簡単なjava方法javac

1)jdkインストールに移動し、別のフォルダーにコピーしrt.jarsrc.zip

2)ソースzipからString.javaを解凍し、内部文字配列のプライベートフィールド値をパブリックに変更します

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

3)javacを使用して変更されたString.javaをコンパイルします。

javac String.java

4)コンパイルされたString.classおよびその他のコンパイルされたクラスをこのディレクトリのrt.jarに移動します

5)文字列プライベートフィールドを使用するテストクラスを作成します

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

PSこれは変更された場合にのみ機能し、このオプションを使用してライセンス違反rt.jarをオーバーライドします。rt.jarjre

于 2017-06-20T22:16:33.563 に答える