JavaRush /Blog Java /Random-VI /Final, Константы и Immutable в Java

Final, Константы и Immutable в Java

Xuất bản trong nhóm
Hello! Слово “модификатор” тебе уже знакомо. Как минимум, ты сталкивался с модификаторами доступа (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. Кроме того, если класс объявлен How final, все его методы тоже становятся final. What это значит? Если для метода указан модификатор 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 мы переопределor метод родительского класса. Теперь животное будет подавать голос в зависимости от того, an objectом Howого класса оно является:

public class Main {

   public static void main(String[] args) {

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

public class Animal {

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


public class Cat extends Animal {

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

public static void main(String[] args) {

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

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

public class Main {
  
   private static final int CONSTANT_EXAMPLE = 333;

   public static void main(String[] args) {

       CONSTANT_EXAMPLE = 999;//ошибка! Нельзя присвоить новое meaning final-переменной!
   }
}
Константу необязательно инициализировать сразу же. Это можно сделать и позже. Но meaning присвоенное первым так и останется навсегда.

public static void main(String[] args) {

   final int CONSTANT_EXAMPLE;

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

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 + " ниHow не определится кто ему" +
               " нравится больше: " + YENNEFER_NAME + " or " + TRISS_NAME);

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

Immutable-типы

За время работы на Java ты уже, наверное, привык к тому, что программист практически fully управляет состоянием всех an objectов. Захотел — создал an object Cat. Захотел — переименовал его. Захотел — поменял возраст, or еще что-нибудь. Но в Java есть несколько типов данных, которые отличаются особым состоянием. Они являются неизменяемыми, or Immutable. Это значит, что если класс неизменяемый, состояние его an objectов изменить невозможно. Примеры? Возможно ты удивишься, но самый известный пример Immutable - класса — String! Казалось бы, разве мы не можем изменить meaning строки? Ну, давай попробуем:

public static void main(String[] args) {

   String str1 = "I love Java";

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

   str1 = "I love Python";//но поведение str1 ниHow не влияет на str2
   System.out.println(str2);//str2 продолжает указывать на строку "I love Java", хотя str1 уже указывает на другой an object
}
Вывод: I love Java I love Java После того, How мы написали:

str1 = "I love Python";
an object со строкой "I love Java" не изменился и никуда не делся. Он благополучно существует и имеет внутри себя ровно тот же текст, что и раньше. Код:

str1 = "I love Python";
просто создал еще один an object, и теперь переменная str1 указывает на него. Но на an object "I love Java" мы ниHow не можем повлиять. Так, ладно, давай попробуем по-другому! В классе 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 Ничего не поменялось. И не должно было. Как мы и сказали — an objectы String неизменяемые. А что же тогда все эти методы класса String? Они же могут обрезать строку, изменить в ней символы и прочее. Зачем они тогда нужны, если ничего не происходит? Могут! Но они при этом каждый раз возвращают новый an object строки. Бесполезно писать:

str1.replace("Java", "Python");
— ты не изменишь исходный an object. Но если ты запишешь результат работы метода в новую переменную-ссылку, сразу увидишь разницу!

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 и работают. С an objectом "I love Java" ничего сделать нельзя. Только создать новый an object, и написать: “Новый an object = результат Howих-то манипуляций с an objectом "I love Java"”. Какие типы еще относятся к Immutable? Из того, что тебе железобетонно нужно запомнить уже сейчас — все классы-обертки над примитивными типами — неизменяемые. Integer, Byte, Character, Short, Boolean, Long, Double, Float — все эти классы создают Immutable an objectы. Сюда же относятся и классы, используемые для создания больших чисел — BigInteger и BigDecimal. Мы недавно проходor исключения и затрагивали StackTrace. Так вот: an objectы класса java.lang.StackTraceElement тоже неизменяемые. Это логично: если бы кто-то мог изменять данные нашего стэка, это могло бы свести на нет всю работу с ним. Представь, что кто-нибудь заходит в StackTrace и меняет OutOfMemoryError на FileNotFoundException. А тебе с этим стеком работать и искать причину ошибки. А программа при этом вообще не использует файлы :) Поэтому от греха подальше эти an objectы сделали неизменяемыми. Ну, со StackTraceElement более-менее понятно. А зачем кому-то понадобилось делать неизменяемыми строки? В чем проблема, если бы можно было менять их значения. Наверное, даже удобнее бы было :/ Причин тут несколько. Во-первых, экономия памяти. Неизменяемые строки можно помещать в String Pool и использовать каждый раз одну и ту же instead of создания новых. Во-вторых, безопасность. Например, большинство логинов и паролей в любой программе — строки. Возможность их изменения могла бы повлечь проблемы с авторизацией. Есть и другие причины, но пока что мы не дошли к ним в изучении Java — вернемся попозже.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION