Страницы

Поиск по вопросам

пятница, 29 ноября 2019 г.

private + final = “Нельзя изменить”

#java


В некоторых источниках, в литературе пишут именно такое высказывание. 
У меня такой вопрос: "Является ли это утверждение абсолютным?" Или это сделано специально
для общего понимания?

Я здесь на форуме прочитала, что существует рефлексия и всё таки можно повлиять на
это. Может существуют другие возможности?
    


Ответы

Ответ 1



Все зависит от того, для чего использовать данные модификаторы: Если модификатор final используется для поля класса, тогда поле необходимо определить в конструкторе класса (причем единожды за весь конструктор), либо, непосредственно, в месте объявления.Совокупность модификаторов private final для поля накладывает запрет на изменение (определить данное поле возможно либо в конструкторе, либо в месте объявления за счет final) и видимость данного поля ограничивается методами данного класса (за счет private). Если модификатор final используется для метода класса, то он запрещает переопределение данного метода в классах-наследниках.Использование же пары модификаторов private final для метода класса лишено смысла, так как private ограничит доступ к данному методу пределами класса, а final запретит его переопределение в наследниках. т.е. потомки этот метод не увидят, а, следовательно, не смогут переопределить. При использовании рефлексии значение поля с модификатором private можно изменить, но при этом для final поля такое недопустимо (никакого исключения/предупреждения сгенерировано не будет, но поле не изменится). Контрольный пример: class WithPrivateFinalField { private int i = 1; private final String s = "String S"; private String s2 = "String S2"; public String toString() { return "i = " + i + ", " + s + ", " + s2; } } public class ModifyngPrivateFields { public static void main(String[] args) throws Exception { WithPrivateFinalField pf = new WithPrivateFinalField(); Field f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); f.setInt(pf, 47); System.out.println(pf); // i = 47, String S, String S2 f = pf.getClass().getDeclaredField("s"); f.setAccessible(true); f.set(pf, "MODIFY S"); System.out.println(pf); // i = 47, String S, String S2 f = pf.getClass().getDeclaredField("s2"); f.setAccessible(true); f.set(pf, "MODIFY S2"); System.out.println(pf); // i = 47, String S, MODIFY S2 } }

Ответ 2



private + final != “Нельзя изменить” Небольшое дополнение к соседнему ответу: изменить значение final поля всё-таки можно. Нужно всего лишь с использованием той же рефлексии снять этот модификатор с поля. Можно написать для этого отдельный метод, например, такой: private static void setAbsolutelyAccessible(Field field) { try { field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); } catch (NoSuchFieldException | IllegalAccessException e) { // ignored exceptions } } В классе Field имеется приватное поле modifiers, в котором в виде битовой маски хранится список модификаторов поля. Выражением field.getModifiers() & ~Modifier.FINAL просто "снимаем" модификатор final с поля. Пример: Предположим есть класс A: public static class A { private final Integer i = 100; @Override public String toString() { return i.toString(); } } Далее используем наш метод и переопределяем значение: A a = new A(); System.out.println(a); // 100 Field field = A.class.getDeclaredField("i"); setAbsolutelyAccessible(field); field.set(a, 10_000); System.out.println(a); // 10000 Ограничения Однако, имеется следующее ограничение: поля примитивных типов или строковые литералы (простая строка в кавычках), инициализированные значением в месте определения, а не в конструкторе, скорее всего (зависит от вендора и настроек компилятора), будут заинлайнены в местах использования и изменить их уже точно не удастся. Например: public static class B { private final String s = "hello"; @Override public String toString() { return s; } } При попытке поменять значение такого поля ничего не произойдёт: B b = new B(); System.out.println(b); // "hello" Field field = B.class.getDeclaredField("s"); setAbsolutelyAccessible(field); field.set(b, "world"); System.out.println(b); // "hello" Однако, если поле иницилазировано в конструкторе, (или не примитивом и не литералом, даже, например, через new String("hello")) - всё уже можно изменить: public static class B { private final String s; public B() { this.s = "hello"; } @Override public String toString() { return s; } } И результат: B b = new B(); System.out.println(b); // "hello" Field field = B.class.getDeclaredField("s"); setAbsolutelyAccessible(field); field.set(b, "world"); System.out.println(b); // "world"

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

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