среда, 14 декабря 2011 г.

Возможность изменения строк java и их кэширование

Поговорим об изменении неизменяемых классов Java и немного о кэшировании строк.
Для тех, кто знает эти механизмы здесь не будет ничего нового. Для тех, кто не задумывался о том, как они уживаются вместе - немного уличной магии.
Итак, есть код:
public class SecureClass {
    
    private String msg = "Fail";
    
    public String getMessage() {
        return msg;
    }    
}
public class Main {

    public static void main(String[] args) {
        SecureClass secureClass = new SecureClass();        
        System.out.println(secureClass.getMessage());
    }   
}
После выполнения в консоли по вполне очевидным причинам будет:
>>Fail
Но мы то хотим выиграть, а не проиграть?
При этом внести исправления в SecureClass не можем или не хотим? Тогда немного уличной магии для класса Main:
public class Main {

    public static void main(String[] args) throws Exception {
        SecureClass secureClass = new SecureClass();  
        Field valueField = String.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set("Fail", new char[] {'W', 'i', 'n', '!'});
        System.out.println(secureClass.getMessage());
    }   
}
Для большей наглядности и чтобы не перегружать заметку обработкой всех исключений, которые нужно обрабатывать при использовании reflect, сократил код, написав throws Exception. Запускаем ещё раз и видим:
>>Win!
Ну прекрасно, так и должно быть!

Небольшие пояснения:
В локальной безымянной переменной метода main с помощью reflect меняем значение внутреннего поля "value". Таким образом эта локальная переменная уже содержит в себе другую строку (вот вам и невозможность изменить класс String). Эти изменения отражаются и на переменной из класса SecureClass, потому что java старается хранит строки в пуле, когда это возможно и когда программист этому явно не воспрепятствовал. Получается, что в обоих классах строковые переменные "смотрели" на один и тот же объект. Попытаемся намекнуть java, что мы не хотим своими reflect-операциями изменять строки в пуле, указав new String("Fail") вместо "Fail":
public class Main {

    public static void main(String[] args) throws Exception {
        SecureClass secureClass = new SecureClass();  
        Field valueField = String.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set(new String("Fail"), new char[] {'W', 'i', 'n', '!'});
        System.out.println(secureClass.getMessage());
    }   
}
Запускаем и видим:
>>Fail
Потому что теперь переменные указывают на разные объекты. Меняя один - не оказываем никакого влияния на другой.

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

Комментариев нет:

Отправить комментарий