Привет! Слово “модификатор” тебе уже знакомо. Неизменное в Java: final, константы и Immutable - 1Как минимум, ты сталкивался с модификаторами доступа (public, private) и с модификатором static. Сегодня поговорим о специальном модификаторе final. Он, можно сказать, “цементирует” те участки нашей программы, где нам нужно постоянное, однозначное, не меняющееся поведение. Его можно применять на трех участках нашей программы: в классах, методах и переменных. Неизменное в Java: final, константы и Immutable - 2 Пройдемся по ним по очереди. Если в объявлении класса стоит модификатор final, это значит, что от данного класса нельзя наследоваться. В прошлых лекциях мы видели простой пример наследования: у нас был родительский класс Animal, и два класса-потомка — Cat и Dog
public class Animal {
}

public class Cat extends Animal {
   //..поля и методы класса Cat
}

public class Dog extends Animal {

   //..поля и методы класса Dog
}
Однако, если мы укажем для класса Animal модификатор final, унаследовать классы Cat и Dog от него не получится.
public final class Animal {

}

public class Cat extends Animal {

   //ошибка! Cannot inherit from final Animal
}
Компилятор сразу же выдает ошибку. В Java уже реализовано много final-классов. Наиболее известный из тех, которыми ты постоянно пользуешься — String. Кроме того, если класс объявлен как final, все его методы тоже становятся final. Что это значит? Если для метода указан модификатор final — этот метод нельзя переопределить. Например, у нас есть класс Animal, в котором определен метод voice(). Однако собаки и кошки явно “разговаривают” по-разному. Поэтому в каждом из классов — Cat и Dog — мы создадим метод voice(), но реализуем его по-разному.
public class Animal {

   public void voice() {
       System.out.println("Голос!");
   }
}

public class Cat extends Animal {

   @Override
   public void voice() {
       System.out.println("Мяу!");
   }
}

public class Dog extends Animal {

   @Override
   public void voice() {
       System.out.println("Гав!");
   }
}
В классах Cat и Dog мы переопределили метод родительского класса. Теперь животное будет подавать голос в зависимости от того, объектом какого класса оно является:
public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       Dog dog = new Dog();

       cat.voice();
       dog.voice();
   }
}
Вывод: Мяу! Гав! Однако, если в классе Animal мы объявим метод voice() как final, переопределить его в других классах будет нельзя:
public class Animal {

   public final void voice() {
       System.out.println("Голос!");
   }
}


public class Cat extends Animal {

   @Override
   public void voice() {//ошибка! final-метод не может быть переопределен!
       System.out.println("Мяу!");
   }
}
Тогда наши объекты будут вынуждены пользоваться методом voice() так, как он определен в родительском классе:
public static void main(String[] args) {

   Cat cat = new Cat();
   Dog dog = new Dog();

   cat.voice();
   dog.voice();
}
Вывод: Голос! Голос! Теперь по поводу final-переменных. По-другому они называются константами. Во-первых (и в-главных), первое значение, присвоенное константе, нельзя изменить. Оно присваивается один раз и навсегда.
public class Main {

   private static final int CONSTANT_EXAMPLE = 333;

   public static void main(String[] args) {

       CONSTANT_EXAMPLE = 999;//ошибка! Нельзя присвоить новое значение final-переменной!
   }
}
Константу необязательно инициализировать сразу же. Это можно сделать и позже. Но значение присвоенное первым так и останется навсегда.
public static void main(String[] args) {

   final int CONSTANT_EXAMPLE;

   CONSTANT_EXAMPLE = 999;//так делать можно
}
Во-вторых, обрати внимание на название нашей переменной. Для констант в Java принято иное соглашение об именовании. Это не привычный нам camelCase. В случае с обычной переменной мы бы назвали ее constantExample, но названия констант пишется капсом, а между словами (если их несколько) ставится нижнее подчеркивание — “CONSTANT_EXAMPLE”. Зачем нужны константы? Например, они пригодятся, если ты постоянно используешь какое-то неизменное значение в программе. Скажем, ты решил войти в историю и в одиночку написать игру “Ведьмак 4”. В игре явно будет постоянно использоваться имя главного героя — “Геральт из Ривии”. Эту строку и имена других героев лучше выделить в константу: нужное тебе значение будет храниться в одном месте, и ты точно не ошибешься, печатая его в миллионный раз.
public class TheWitcher4 {

   private static final String GERALT_NAME = "Геральт из Ривии";
   private static final String YENNEFER_NAME = "Йеннифэр из Венгерберга";
   private static final String TRISS_NAME = "Трисс Меригольд";

   public static void main(String[] args) {

       System.out.println("Ведьмак 4");
       System.out.println("Это уже четвертая часть Ведьмака, а " + GERALT_NAME + " никак не определится кто ему" +
               " нравится больше: " + YENNEFER_NAME + " или " + TRISS_NAME);

       System.out.println("Но если вы никогда не играли в Ведьмака - начнем сначала.");
       System.out.println("Главного героя зовут " + GERALT_NAME);
       System.out.println(GERALT_NAME + " - ведьмак, охотник на чудовищ");
   }
}
Вывод: Ведьмак 4 Это уже четвертая часть Ведьмака, а Геральт из Ривии никак не определится, кто ему нравится больше: Йеннифэр из Венгерберга или Трисс Меригольд. Но если вы никогда не играли в Ведьмака — начнем сначала. Главного героя зовут Геральт из Ривии Геральт из Ривии — ведьмак, охотник на чудовищ Мы выделили имена героев в константы, и теперь совершенно точно не опечатаемся, и не будет нужды каждый раз писать их руками. Еще один плюс: если нам в итоге все-таки нужно будет изменить значение переменной во всей программе, достаточно сделать это в одном месте, а не переделывать вручную во всем коде :)

Immutable-типы

За время работы на Java ты уже, наверное, привык к тому, что программист практически полностью управляет состоянием всех объектов. Захотел — создал объект Cat. Захотел — переименовал его. Захотел — поменял возраст, или еще что-нибудь. Но в Java есть несколько типов данных, которые отличаются особым состоянием. Они являются неизменяемыми, или Immutable. Это значит, что если класс неизменяемый, состояние его объектов изменить невозможно. Примеры? Возможно ты удивишься, но самый известный пример Immutable - класса — String! Казалось бы, разве мы не можем изменить значение строки? Ну, давай попробуем:
public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;//обе переменные-ссылки указывают на одну строку.
   System.out.println(str2);

   str1 = "I love Python";//но поведение str1 никак не влияет на str2
   System.out.println(str2);//str2 продолжает указывать на строку "I love Java", хотя str1 уже указывает на другой объект
}
Вывод: I love Java I love Java После того, как мы написали:
str1 = "I love Python";
объект со строкой "I love Java" не изменился и никуда не делся. Он благополучно существует и имеет внутри себя ровно тот же текст, что и раньше. Код:
str1 = "I love Python";
просто создал еще один объект, и теперь переменная str1 указывает на него. Но на объект "I love Java" мы никак не можем повлиять. Так, ладно, давай попробуем по-другому! В классе String полно методов, и некоторые из них, похоже с виду меняют состояние строки! Вот, например, есть метод replace(). Давай поменяем слово “Java” на слово “Python” в нашей строке!
public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;//обе переменные-ссылки указывают на одну строку.
   System.out.println(str2);

   str1.replace("Java", "Python");//попробуем изменить состояние str1, заменив слово "Java" на “Python”
   System.out.println(str2);
}
Вывод: I love Java I love Java Снова не получилось! Может, метод кривой, не работает? Попробуем другой. Вот, например, substring(). Обрезает строку по номерам переданных символов. Давай обрежем нашу до первых 10 символов:
public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;//обе переменные-ссылки указывают на одну строку.
   System.out.println(str2);

   str1.substring(10);//обрезаем исходную строку
   System.out.println(str2);
}
Вывод: I love Java I love Java Неизменное в Java: final, константы и Immutable - 3 Ничего не поменялось. И не должно было. Как мы и сказали — объекты String неизменяемые. А что же тогда все эти методы класса String? Они же могут обрезать строку, изменить в ней символы и прочее. Зачем они тогда нужны, если ничего не происходит? Могут! Но они при этом каждый раз возвращают новый объект строки. Бесполезно писать:
str1.replace("Java", "Python");
— ты не изменишь исходный объект. Но если ты запишешь результат работы метода в новую переменную-ссылку, сразу увидишь разницу!
public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;//обе переменные-ссылки указывают на одну строку.
   System.out.println(str2);

   String str1AfterReplacement =  str1.replace("Java", "Python");
   System.out.println(str2);

   System.out.println(str1AfterReplacement);
}
Только так все эти методы String и работают. С объектом "I love Java" ничего сделать нельзя. Только создать новый объект, и написать: “Новый объект = результат каких-то манипуляций с объектом "I love Java"”. Какие типы еще относятся к Immutable? Из того, что тебе железобетонно нужно запомнить уже сейчас — все классы-обертки над примитивными типами — неизменяемые. Integer, Byte, Character, Short, Boolean, Long, Double, Float — все эти классы создают Immutable объекты. Сюда же относятся и классы, используемые для создания больших чисел — BigInteger и BigDecimal. Мы недавно проходили исключения и затрагивали StackTrace. Так вот: объекты класса java.lang.StackTraceElement тоже неизменяемые. Это логично: если бы кто-то мог изменять данные нашего стэка, это могло бы свести на нет всю работу с ним. Представь, что кто-нибудь заходит в StackTrace и меняет OutOfMemoryError на FileNotFoundException. А тебе с этим стеком работать и искать причину ошибки. А программа при этом вообще не использует файлы :) Поэтому от греха подальше эти объекты сделали неизменяемыми. Ну, со StackTraceElement более-менее понятно. А зачем кому-то понадобилось делать неизменяемыми строки? В чем проблема, если бы можно было менять их значения. Наверное, даже удобнее бы было :/ Причин тут несколько. Во-первых, экономия памяти. Неизменяемые строки можно помещать в String Pool и использовать каждый раз одну и ту же вместо создания новых. Во-вторых, безопасность. Например, большинство логинов и паролей в любой программе — строки. Возможность их изменения могла бы повлечь проблемы с авторизацией. Есть и другие причины, но пока что мы не дошли к ним в изучении Java — вернемся попозже.