Все о ключевых словах static и final

Что такое ключевое слово static?

Чтобы получить доступ к членам класса в Java, нужно сначала создать экземпляр класса, а затем вызвать членов класса с помощью переменной экземпляра. Но иногда нужно получить доступ к членам класса, не создавая никаких переменных.

В таком случае можно воспользоваться ключевым словом static, то есть объявить членов класса статическими. В Java большинство членов служебного класса являются статическими. Вот несколько примеров.

  • java.util.Objects содержит статические служебные операции для метода объекта.
  • java.util.Collections состоит исключительно из статических методов, которые работают с коллекциями или возвращают их.

Где можно употреблять ключевое слово static?

Мы можем использовать это ключевое слово в четырех контекстах:

  • статические методы;
  • статические переменные;
  • статические вложенные классы;
  • статические блоки.

Рассмотрим подробнее каждый из перечисленных пунктов.

Статические методы

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

public class StaticExample {
	public static void add(int a, int b) {
		System.out.println(a+b);
	}
	
	public void multiply(int a, int b) {
		System.out.println(a*b);
	}
	public static void main(String[] args) {
		/** Вызов статического метода **/
		StaticExample.add(1, 2);
		
		/** Вызов не-статического метода**/
		StaticExample staticExample = new StaticExample();
		staticExample.multiply(1, 2);
	}
}

В приведенном выше примере метод add  —  статический, поэтому его можно вызывать напрямую с именем класса. Нет необходимости создавать новый экземпляр класса StaticExample. Но метод multiply не является статическим. Таким образом, для нестатического метода необходимо создать новый экземпляр класса StaticExample.

Статические переменные

При создании объектов класса в Java каждый из них содержит собственную копию всех переменных класса.

Однако, если мы объявим переменную статической, все объекты класса будут использовать одну и ту же статическую переменную. Это связано с тем, что, как и статические методы, статические переменные также связаны с классом. И объекты класса для доступа к статическим переменным создавать не нужно. Например:

public class VariableExample {
public String normalVariable = null;
public static String staticVariable = null;
public static void main(String[] args) {
VariableExample firstExample = new VariableExample();
firstExample.normalVariable = "Hello";
firstExample.staticVariable = "Hello";//Это то же самое, что VariableExample.staticVariable = "Hello"
VariableExample secondExample = new VariableExample();
System.out.println("normalVariable: "+ secondExample.normalVariable);
System.out.println("staticVariable: "+ secondExample.staticVariable);
}
}

В приведенном выше примере normalVariable  —  переменная класса, а staticVariable  —  статическая переменная. Если вы объявите переменную, как показано ниже:

firstExample.staticVariable = “Hello”

Это похоже на доступ к статической переменной через имя класса:

VariableExample.staticVariable = “Hello”

Здесь статические переменные  —  переменные уровня класса. Поэтому, если вы обращаетесь к статическим переменным через переменную экземпляра объекта, то это отражается на соответствующей статической переменной уровня класса. Таким образом, статические переменные всегда имеют одно и то же значение. Но переменная экземпляра сохраняет отдельное значение в каждом экземпляре объекта. Таким образом, приведенный выше фрагмент кода выведет:

normalVariable: null

staticVariable: Hello

Статические переменные  —  редкость в Java. Вместо них применяют статические константы. Они определяются ключевым словом static final и представлены в верхнем регистре. Вот почему некоторые предпочитают использовать верхний регистр и для статических переменных.

Статические блоки

Здесь мы видим статический блок с синтаксисом:

static {

}

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

public class VariableExample {
public static String staticVariable = null;

static {
staticVariable = "Hello Variable";
}

public static void main(String[] args) {
System.out.println(staticVariable);
}
}

Класс может содержать несколько статических блоков, а каждый из них выполняется в той же последовательности, в которой они написаны в коде.

public class VariableExample {
public static String staticVariable = null;

static {
staticVariable = "Hello Variable";
}

static {
staticVariable = "Hello Again";
}

public static void main(String[] args) {
System.out.println(staticVariable);
}
}

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

Hello Again

Вложенный статический класс

В Java можно объявить класс внутри другого класса. Такие классы называются вложенными классами. Они бывают двух типов: статические и нестатические.

Вложенный класс является членом заключающего его класса. Нестатические вложенные классы (внутренние классы) имеют доступ к другим членам заключающего класса, даже если они объявлены приватными. Статические вложенные классы не имеют доступа к другим членам заключающего класса.

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

Со статическим внутренним классом все иначе. Можно сказать, что если у внутреннего класса нет причин для доступа к внешнему, вы должны сделать его статическим по умолчанию.

public class InnerClassExample {
	public String outerElement;
	private String outerPrivateElement;
	private void outerMethod() {
		
	}
	public static class MyStaticInnerClass{
		public void myFunction() {
			System.out.println("calling static class inner function");
		}		
	}
	public class MyInnerClass {
		public void myAnotherFunction() {
			outerElement = "sample";
			outerPrivateElement ="Hello Private";
			outerMethod();
		}	
	}
	public static void main(String[] args) {
		/**Вызов статического внутренего класса **/
		InnerClassExample.MyStaticInnerClass staticInner = new InnerClassExample.MyStaticInnerClass();
		staticInner.myFunction();

		/*Вызов внутреннего класса **/
		InnerClassExample.MyInnerClass innerClass = new InnerClassExample().new MyInnerClass();
		innerClass.myAnotherFunction();		
	}
}

В приведенном выше примере видно, что внутренний класс MyInnerClass может получить доступ ко всем методам и переменным внешнего, включая приватные переменные. Статическому внутреннему классу, напротив, недоступны какие-либо методы или переменные внешнего класса.

Где в памяти Java хранятся статические классы и переменные?

Вплоть до 8-й версии Java статические методы и переменные хранились в пространстве permgen. Но потом было введено новое пространство памяти, называемое метапространством  —  в нем хранятся все эти имена и поля класса, методы класса с байт-кодом методов, пул констант, JIT-оптимизации и т. д. Причина удаления permgen в Java 8.0 в том, что очень сложно предсказать необходимый размер permgen.

Зачем нужно ключевое слово final?

В Java при объявлении сущности можно воспользоваться ключевым словом final. Оно предполагает, что значение не может быть изменено в будущем. Ключевое слово final может применяться для переменной, метода и класса.

  • Конечная переменная предназначена для создания постоянных значений.
  • Конечный метод предотвращает переопределение метода.
  • Конечный класс предотвращает наследование.

Что такое конечная переменная и когда ей стоит воспользоваться?

Когда переменная объявляется с помощью ключевого слова final, ее значение не может быть изменено. По сути, это константа. Это также означает, что конечную переменную необходимо инициализировать. Если конечная переменная является ссылкой, то ее нельзя будет повторно привязать к другому объекту, но внутреннее состояние объекта, на которое указывает эта ссылочная переменная, может быть изменено, т.е. вы можете добавлять или удалять элементы из конечного массива или конечной коллекции. Рекомендуется именовать конечные переменные целиком в верхнем регистре и с подчеркиванием для разделения слов.

Существует три способа инициализации конечной переменной.

  • Инициализировать конечную переменную, когда она будет объявлена. Это самый распространенный подход. Если конечная переменная не инициализирована при объявлении, она называется пустой конечной переменной. Ниже приведены два способа инициализации пустой конечной переменной.
  • Пустая конечная переменная может быть инициализирована внутри блока инициализатора экземпляра или внутри конструктора. Если в классе несколько конструкторов, то переменную необходимо инициализировать во всех из них, в противном случае во время компиляции появится ошибка.
  • Пустая конечная статическая переменная может быть инициализирована в статическом блоке.
public class FinalVariableExample {
	public final String MY_VARIABLE_1 = "Variable Initialized";
	public final static String MY_VARIABLE_2;
	public final String MY_VARIABLE_3;
	public final String MY_VARIABLE_4 ;
	
	static {
		MY_VARIABLE_2 = "Static Varible Initialized";
	}
	
	{
		MY_VARIABLE_3 = "block Initialized";
	}
	
	public FinalVariableExample() {
		MY_VARIABLE_4 = "Constructor Initialized";
	}		
}

Когда следует применять конечную переменную

Единственное различие между обычной и конечной переменной в том, что первой можно переприсвоить значение, а второй  —  нельзя. Следовательно, конечные переменные должны использоваться только для значений, которые необходимо сохранять постоянными на протяжении выполнения программы.

Где использовать конечные классы

Когда класс объявляется с ключевым словом final, он называется конечным. Конечный класс не может быть расширен (унаследован). У него есть два применения.

Первое, безусловно, заключается в предотвращении наследования, так как конечные классы не могут быть расширены. Например, все классы-оболочки, такие как Integer, Float и т. д.  —  конечные классы.

public final class MyFinalClass {

}

public class MyClass extends MyFinalClass{//Ошибка компиляции!!!

}

При попытке расширить конечный класс компилятор выдаст следующее исключение:

Другое применение final с классами заключается в создании неизменяемого класса, подобного предопределенному классу String. Нельзя сделать класс неизменяемым, не сделав его конечным.

Когда использовать конечные методы

Когда метод объявляется с ключевым словом final, он называется конечным методом. Такой метод не может быть переопределен. Это присутствует в классе Object  —  ряд его методов конечные. С ключевым словом final объявляются методы, для которых необходимо следовать одной и той же реализации во всех производных классах. Фрагмент ниже иллюстрирует метод с ключевым словом final:

public class MyParentClass {
public final void myFinalMethod() {
...
}
}

public class MyClass extends MyParentClass{
public final void myFinalMethod() {//Ошибка компиляции!!!
...
}
}

Здесь происходит попытка переопределить метод final из родительского класса. Java этого не позволяет и выдает следующее исключение:

Надеемся, теперь вы хорошо поняли ключевые слова static и final.

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Anish Antony: All about static and final keywords

Предыдущая статьяНаука о данных — что она изучает на самом деле?
Следующая статьяDjango REST Framework: REST API на Python с нуля