Статические методы. Плюсы и минусы.

Автор: Андрей Самойлов


Static methods

Определение

Статическими методами в Java называют такие методы, которые могут быть вызваны без создания экземпляра класса. Например, метод pow () из класса Math является статическим:

//Math.pow returns double, need cast, display 256
int result = (int) Math.pow(2, 8);
               

При вызове метода Math.pow (х , а) вычисляется степень x числа а. При выполнении этого метода не используется ни один из экземпляров класса Math. Иными словами, у него нет неявного параметра this. Это означает, что в статических методах не используется текущий объект по ссылке this. (А в нестатических методах неявный параметр this ссылается на текущий объект)

Когда следует использовать

Если поведение метода не зависит от состояния объекта(значений полей класса), метод следует объявить как статический. Статические методы следует применять в двух случаях:

  • Когда методу не требуется доступ к данным о состоянии объекта, поскольку все необходимые параметры задаются явно (например, в методе Math.pow ()).
  • Когда методу требуется доступ лишь к статическим полям класса
Поскольку статический метод можно вызвать, используя тип класса, в котором эти методы описаны, подобные методы как нельзя лучше подходят в качестве методов-фабрик (factory), и методов-утилит (utility). Класс java.lang.Math — замечательный пример, в котором почти все методы статичны, по этой же причине классы-утилиты в Java финализированы (final). Рассмотрим пример применения статических фабричных методов. В таких классах, как, например, LocalDate и NumberFormat, для построения объектов применяются статические фабричные методы. В следующем примере кода показано, каким образом в классе NumberFormat получаются форматирующие объекты для разных стилей вывода результатов:

NumberFormat currencyFormatter = NumberFormat.getCurrencylnstance();
NumberFormat percentFormatter = NumberFormat.getPercentlnstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x)); // выводит $0.10
System.out.println(percentFormatter.format(x)); // выводит 10%
                        

Почему же не использовать для этой цели конструктор? На то есть две причины.

  • Конструктору нельзя присвоить произвольное имя. Его имя всегда должно совпадать с именем класса. Так, в классе NumberFormat имеет смысл применять разные имена для разных типов форматирования.

  • При использовании конструктора тип объекта фиксирован. Если же применяются фабричные методы, они возвращают объект типа Decimal Format, наследующий свойства из класса NumberFormat.

Особенности применения

Важным моментом является то, что статические методы переопределять (Override) нельзя. Если объявить такой же метод в классе-наследнике (subclass), т.е. метод с таким же именем и сигнатурой, то лишь «спрячется» метод суперкласса (superclass) вместо переопределения. Это явление известно как сокрытие методов (hiding methods). Это означает, что при обращении к статическому методу, который объявлен как в родительском, так и в дочернем классе, во время компиляции всегда будет вызван метод исходя из типа переменной. В отличие от переопределения, такие методы не будут выполнены во время работы программы. Рассмотрим пример:

class Vehicle{
   public static void kmToMiles(int km) {
      System.out.println("Внутри родительского класса/статического метода");
   } 
}

class Car extends Vehicle{
public static void kmToMiles(int km) {
     System.out.println("Внутри дочернего класса/статического метода ");
   } 
}

public class Demo{
public static void main(String args[]) {
    Vehicle v = new Car();
    v.kmToMiles(10);
    }
}        
        

Вывод в консоль:
Внутри родительского класса/статического метода

Код наглядно демонстрирует: несмотря на то, что объект имеет тип Car, вызван статический метод из класса Vehicle, т.к. произошло обращение к методу во время компиляции. И при этом ошибки во время компиляции не возникло!

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

Статические методы и переменные хранились в области Permgen до 8-й версии java. Начиная с 8-й версии, они хранятся в новой область памяти, которая называется Metaspace

Примеры применения статических методов

  • Статическим является метод main - точка входа в Java-программе.Если метод main не объявлен как static, то у JVM при создании экземпляра класса возникает неопределённость, поскольку конструкторов у класса может быть несколько и непонятно, какой из них вызвать. Кроме того, в случае нестатического метода, jvm создаёт объект прежде чем вызвать метод main, что может привести к проблемам с дополнительным выделением памяти.

  • Начиная с версии Java 8, в интерфейсах можно писать статические методы, которые аналогичны default методам, за исключением того, что нельзя их переопределить в реализуемых классах:
    1. Статические методы в интерфейсах хороши для методов - утилит, например, проверки на null, сортировки коллекций;

    2. Нельзя определять статические методы в интерфейсах для метода класса Object, это приводит к сообщению об ошибке: "This static method cannot hide the instance method from Object".

Достоинства и недостатки

Достоинства

  • отсутствует необходимость каждый раз создавать новый объект для доступа к таким методам

  • удобны для создания методов-утилит

  • загружаются единственный раз при запуске JVM

Недостатки

  • В отличие от локальных переменных, статические методы НЕ потокобезопасны (Thread-safe) в Java. На практике это одна из наиболее частых причин возникновения проблем связанных с безопасностью мультипоточного программирования. Учитывая что каждый экземпляр класса имеет одну и ту же копию статической переменной, то такая переменная нуждается в защите — «залочивании» классом. Поэтому при использовании статических переменных необходимо убедиться, что они должным образом синхронизированы (synchronized), во избежание проблем, например таких как «состояние гонки» (race condition).

  • В отличие от локальных переменных, статические методы НЕ потокобезопасны (Thread-safe) в Java. На практике это одна из наиболее частых причин возникновения проблем связанных с безопасностью мультипоточного программирования. Когда метод синхронизирован, он блокирует объект, если метод статичен, он блокирует класс, поэтому всегда рекомендуется использовать синхронизированный блок для блокировки только тех разделов метода, которые требуют синхронизации.

  • Если статические методы ссылаются на статические переменные, они остаются в памяти всё время жизни загрузчика классов, что может привести к утечкам памяти.

  • Неудобно тестировать юнит-тестами. Нельзя создавать mock-объекты, потому что при вызове статического метода задаётся имя класса.

  • Поскольку статический метод нельзя переопределить, это приводит к нарушению полиморфизма. Это приводит к низкой гибкости в ситуациях, когда в результате изменений поведение статического метода становится полиморфным: Пример: HourlyPayCalculator.calculatePay(employee, overtimeRate) Данная статическая функция тоже выглядит вполне разумно. Она не работает ни с каким конкретным объектом и получает все данные из своих аргументов. Однако нельзя исключать, что эту функцию потребуется сделать полиморфной. Возможно, в будущем потребуется реализовать несколько разных алгоритмов для вычисления почасовой оплаты — скажем, OvertimeHourlyPayCalculator и StraightTimeHourlyPayCalculator. В этом случае данная функция не может быть статической. Ее следует оформить как нестатическую функцию Employee. Как следствие, что в случае реализации полиморфного поведения сложно управлять поведением по условию. Это возможно сделать при помощи двух подходов: передать флаг через параметр метода или установить статический флаг извне. Проблема с первым подходом в том, что приходится изменять сигнатуру для каждого вызывающего объекта, что приводит к усложнению кода по причине добавления новых и новых флагов. Применение второго подхода может привести к загромождению кода установкой и сбросом флагов:
    boolean oldFlag = MyUtils.getFlag(); 
    MyUtils.someMethod(); 
    MyUtils.setFlag( oldFlag ); 
                                
Вывод:

Необходимо с осторожностью подходить к принятию решения о применении статических методов в дизайне приложения. Их следует применять только в случаях если:

  • поведение статических методов не может стать полиморфным;
  • статический метод не ссылается на статические поля;
  • обеспечена потокозащищённость.