web-dev-qa-db-fra.com

Créer un Java.lang.String mutable

Tout le monde sait que Java Strings est immuable. Les cordes immuables sont un excellent ajout à Java depuis sa création. L'immuabilité permet un accès rapide et de nombreuses optimisations, beaucoup moins sujette aux erreurs que les chaînes de style C et contribue à appliquer le modèle de sécurité.

Il est possible de créer un mutable sans utiliser de hacks, à savoir

  • Java.lang.reflect
  • Sun.misc.Unsafe
  • Classes dans le chargeur de classe bootstrap
  • JNI (ou JNA comme il faut JNI)

Mais est-il possible simplement en Java, de sorte que la chaîne puisse être modifiée à tout moment? La question est Comment ?

48
bestsss

En créant un Java.lang.String avec le constructeur Charset, vous pouvez injecter votre propre Charset, qui apporte votre propre CharsetDecoder. CharsetDecoder obtient une référence à un objet CharBuffer dans la méthode decodeLoop. Le CharBuffer encapsule le caractère [] de l'objet String d'origine. Etant donné que CharsetDecoder contient une référence, vous pouvez modifier le caractère sous-jacent [] à l'aide de CharBuffer. Vous disposez ainsi d'une chaîne mutable.

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

}

Exécuter les impressions de code

abc
zzz

Je ne sais pas comment implémenter correctement decodeLoop (), mais ça m'est égal pour l'instant :)

79
mhaller

La question a reçu une bonne réponse de @mhaller. Je dirais que le soi-disant casse-tête était assez facile et qu'en regardant simplement les c-tors disponibles de String, on devrait pouvoir trouver le comment une partie, une 

Procédure pas à pas

C-tor d’intérêt est ci-dessous, si vous voulez pénétrer dans/casser/chercher une vulnérabilité de sécurité, cherchez toujours des classes arbitraires non finales. Le cas ici est 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;
}
Le c-tor offre un moyen soi-disant rapide de convertir byte[] en chaîne en transmettant le jeu de caractères, et non le nom du jeu de graphiques, afin d'éviter la recherche dans le fichier chartsetName-> charset. Il permet également de passer un objet Charset arbitraire pour créer String. L'acheminement principal du jeu de caractères convertit le contenu de Java.nio.ByteBuffer en CharBuffer. Le CharBuffer peut contenir une référence à char [] et il est disponible via array(); le CharBuffer est également entièrement 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);
    }

Pour éviter de modifier le char[], les développeurs Java copient le tableau de la même manière que toute autre construction String (par exemple, public String(char value[])). Cependant, il existe une exception: si aucun SecurityManager n'est installé, le caractère [] n'est pas copié.

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

Donc, s'il n'y a pas de SecurityManager, il est tout à fait possible d'avoir un CharBuffer/char [] modifiable qui soit référencé par une chaîne. 

Tout semble aller pour le moment - sauf que le byte[] est également copié (en gras ci-dessus). C’est Les développeurs Java sont devenus paresseux et ont eu tort.

La copie est nécessaire pour empêcher le jeu de caractères non autorisé (exemple ci-dessus) de modifier l’octet source []. Cependant, imaginons le cas d’avoir environ 512 Ko byte[] tampon contenant peu de chaînes. Tentative de création d’un seul petit nombre de graphiques - new String(buf, position, position+32,charset), générant une copie massive de 512 Ko []. Si la taille de la mémoire tampon était d'environ 1 Ko, l'impact ne sera jamais vraiment remarqué. Avec de grands tampons, la performance est vraiment énorme. La solution simple serait de copier la partie pertinente.

... ou bien les concepteurs de Java.nio ont réfléchi en introduisant des tampons en lecture seule. Il aurait suffi de simplement appeler ByteBuffer.asReadOnlyBuffer() (si Charset.getClassLoader ()! = Null) * Parfois, même les personnes travaillant sur Java.lang peuvent se tromper totalement.

* Class.getClassLoader () renvoie null pour les classes d’amorçage, c’est-à-dire celles qui viennent avec la JVM elle-même.

9
Mechanical snail

Je dirais StringBuilder (ou StringBuffer pour une utilisation multithread). Oui, à la fin, vous obtenez une chaîne immuable. Mais c'est la voie à suivre.

Par exemple, le meilleur moyen d'ajouter des chaînes dans une boucle est d'utiliser StringBuilder. Java lui-même utilise StringBuilder lorsque vous utilisez "fu" + variable + "ba".

http://docs.Oracle.com/javase/6/docs/api/Java/lang/StringBuilder.html

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

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

}
2
jitendrak

Ne réinventez pas la roue. Apache commons fournit justement cela.

MutableObject<String> mutableString = new new MutableObject<>();
0
Roland Ettinger