Какова цель изменения строки с помощью отражения? - PullRequest
3 голосов
/ 02 августа 2011

Я читал статью , в которой говорилось, что строки Java не являются полностью неизменяемыми. Однако в примере кода статьи, который изменяет строку, он вызывает string.toUpperCase (). ToCharArray (), который возвращает новую строку. Так, какова цель прохождения процесса изменения строки, если вы все равно вызываете toUpperCase ()? Вот код:

public static void toUpperCase(String orig)
{
 try
 {
    Field stringValue = String.class.getDeclaredField("value");
    stringValue.setAccessible(true);
    stringValue.set(orig, orig.toUpperCase().toCharArray());
 }
 catch (Exception ex){}
}

Также я заметил, что сама string.toUpperCase () не работает. Это должно быть string.toUpperCase (). ToCharArray (). Есть ли причина для этого?

Ответы [ 7 ]

12 голосов
/ 02 августа 2011

Что он делает:

Он получает массив символов, который, как он знает, имеет правильную длину (например, строковую версию в верхнем регистре) и помещает его в качестве резервного массива строки.(Резервный массив называется value внутри класса String.)

Почему он это делает:

Чтобы проиллюстрировать, что вы можете поместить туда любой массив символов, который захотите.

Почему это полезно:

Строка неизменна, и это позволяет вам обойти неизменность.Конечно, это делать не рекомендуется - КОГДА-ЛИБО .Напротив, я не удивлюсь, если он скажет: «Берегись, потому что люди потенциально могут сделать это с ВАШИМ кодом. Даже если ты думаешь, что твои вещи безопасны, это может быть не так!»

Последствияэто широко распространено.Неизменяемые переменные больше не являются неизменяемыми.Конечные переменные больше не являются окончательными.Потокобезопасные объекты больше не являются поточно-ориентированными.Контракты, на которые вы рассчитывали, вы больше не можете делать.Все потому, что у какого-то инженера где-то была проблема, которую он не мог исправить обычными средствами, поэтому он копается в раздумьях, чтобы ее решить. Не будь «тем парнем».

Вы также заметите, что теперь изменился хэш-код для этой строки.Итак, если вы никогда не вычисляли hashCode для этой строки, он все равно равен 0, так что все в порядке.С другой стороны, если вы рассчитали его, когда вы собираетесь поместить его в HashMap или HashSet, он не будет извлечен.

Примите во внимание следующее:

import java.util.*;
import java.lang.reflect.*;

class HashTest {        

    /** Results:
     C:\Documents and Settings\glowcoder\My Documents>java HashTest
        Orig hash: -804322678
        New value: STACKOVERFLOW
        Contains orig: true
        Contains copy: false
     */

    public static void main(String[] args) throws Exception {

        Set<String> set = new HashSet<String>();
        String str = "StackOverflow";
        System.out.println("Orig hash: " + str.hashCode());
        set.add(str);

        Field stringValue = String.class.getDeclaredField("value");
        stringValue.setAccessible(true);
        stringValue.set(str, str.toUpperCase().toCharArray()); // 

        System.out.println("New value: " + str);

        String copy = new String(str); // force a copy
        System.out.println("Contains orig: " + set.contains(str));
        System.out.println("Contains copy: " + set.contains(copy));
    }

}

IМогу поспорить, что он делает это как предупреждение против плохого поведения, а не показывает «крутой» трюк.

РЕДАКТИРОВАТЬ: Я нашел статью, на которую вы ссылаетесь, и статью, на которой он основан.В оригинальной статье говорится: «Это означает, что если класс в другом пакете« возится »с интернированной строкой, это может привести к хаосу в вашей программе. Это хорошо? (Вам не нужно отвечать ;-)« Ядумаю, это ясно дает понять, что это скорее руководство по защите, чем совет о том, как кодировать.

Так что, если вы уходите из этой цепочки только с одним фрагментом информации, то это отражение опасно, ненадежно,и не шутите!

6 голосов
/ 02 августа 2011

Не пытайтесь делать это дома!

Вы нарушаете неизменность String. нет веских причин для этого.

4 голосов
/ 02 августа 2011

Я думаю, что не могу добавить к уже предоставленным объяснениям, поэтому, возможно, я могу добавить к обсуждению, предложив, как это можно предотвратить.

Вы можете предотвратить вмешательство вашего кода в эти и другие непреднамеренные способыс помощью диспетчера безопасности.

public static void main(String args[]){

    System.setSecurityManager(new SecurityManager());
    String jedi1 = "jedi";

    toUpperCase(jedi1);
    System.out.println(jedi1);
} 

Это создаст исключение в методе toUpperCase при условии, что вы не предоставляете все привилегии всем базам кода в файлах политики по умолчанию.(В вашем текущем коде ваши исключения в настоящее время проглочены).

4 голосов
/ 02 августа 2011

Какова цель? Я не уверен, спросите, кто написал этот материал. Обычно вы не должны делать что-то подобное. Есть причина, по которой строка является неизменной.

Вот как бы выглядел этот метод, если бы поля были общедоступными, т.е. без отражения:

public static void toUpperCase(String orig) {
    orig.value = orig.toUpperCase().toCharArray();
}

Поскольку value имеет тип char[], вы не можете присвоить String этому полю - вот почему вам нужен вызов toCharArray после .toUpperCase(). Вы получите исключение, если попытаетесь это сделать (я полагаю, ClassCastException), но блок try-catch там съест его. (Это дает нам еще один урок: Никогда не используйте такие пустые блоки catch .)

Обратите внимание: этот код может работать неправильно, так как фактические данные исходной строки могут не начинаться с начала char[]. Поскольку вы не обновляете поле offset, вы получите IndexOutOfBoundsExceptions при использовании такой измененной строки. Кроме того, объект String кэширует свой hashCode, поэтому это тоже будет неправильно.

Вот правильный путь:

public static void toUpperCase(String orig) {
    orig.value = orig.toUpperCase().toCharArray();
    orig.offset = 0;
    orig.hash = 0; // will be recalculated on next `.hashCode()` call.
}

С отражением это выглядит так:

public static void toUpperCase(String orig)
{
  try
  {
    Field stringValue = String.class.getDeclaredField("value");
    stringValue.setAccessible(true);
    stringValue.set(orig, orig.toUpperCase().toCharArray());
    Field stringOffset = String.class.getDeclaredField("offset");
    stringOffset.setAccessible(true);
    stringOffset.setInt(orig, 0);
    Field stringHash = String.class.getDeclaredField("hash");
    stringHash.setAccessible(true);
    stringHash.setInt(orig, 0);
  }
  catch (Exception ex){
     // at least print the output
     ex.printStackTrace();
  }
}
2 голосов
/ 02 августа 2011

1.) Читать Bohemian ответ.

2.) Строки внутренне хранятся в массиве символов, поэтому вам нужно вызвать toCharArray для установки поля.

1 голос
/ 02 августа 2011

Вы изменяете финальную строку с отражением для тестирования. Иногда эта строка содержит путь к местоположению по умолчанию, используемому в производственной среде, но не пригодному для тестирования. Тем не менее, на эту переменную ссылаются несколько объектов / методов, которые вы запускаете в своем тесте, и, следовательно, во время ваших тестов вы можете установить для нее определенное значение.

Как уже говорили другие, это, вероятно, то, что вы не хотите делать (часто / когда-либо).

1 голос
/ 02 августа 2011

По умолчанию String.toUpperCase () оставляет исходную строку без изменений, в то время как возвращает новый строковый объект.

Функция, которую вы определили выше, редактирует содержимое исходного строкового объекта на месте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...