Что такое статические методы
Статические методы. Плюсы и минусы.
Определение
Статическими методами в 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 ()).
- Когда методу требуется доступ лишь к статическим полям класса
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 методам, за исключением того, что нельзя их переопределить в реализуемых классах:
- Статические методы в интерфейсах хороши для методов — утилит, например, проверки на null, сортировки коллекций;
- Нельзя определять статические методы в интерфейсах для метода класса 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 );
- поведение статических методов не может стать полиморфным;
- статический метод не ссылается на статические поля;
- обеспечена потокозащищённость.
Документация
Статические методы сопоставлены с классом, но не с определенными экземплярами того класса. Эти методы не требуют объекта класса как входной параметр. Поэтому можно вызвать статические методы, не создавая объект класса.
Почему задают статические методы
Статические методы полезны, когда вы не хотите создавать экземпляр класса прежде, чем выполнить некоторый код. Например, предположите, что вы хотите настроить MATLAB ® среда или использование статический метод вычислить данные, требуемые создать экземпляры класса.
Предположим, что классу нужно значение для пи, вычисленного к конкретным допускам. Класс мог задать свою собственную версию встроенного pi функция для использования в классе. Этот подход обеспечивает инкапсуляцию внутренних работ класса, но не требует, чтобы экземпляр класса возвратил значение.
Определение статических методов
Чтобы задать метод как статический, установите блок Static методов припишите true . Например:
classdef MyClass methods(Static) function p = pi(tol) [n d] = rat(pi,tol); p = n/d; end end end
Вызов статических методов
Вызовите статические методы с помощью имени класса, сопровождаемого точкой ( . ), затем имя метода:
classname.staticMethodName(args. )
Вызов pi метод MyClass в предыдущем разделе потребовал бы этого оператора:
value = MyClass.pi(.001);
Можно также вызвать статические методы с помощью экземпляра класса, как любой метод:
obj = MyClass; value = obj.pi(.001);
Наследование статических методов
Подклассы могут переопределить статические методы если Sealed метода атрибут также установлен в true в суперклассе.
Похожие темы
Статические свойства и методы
Мы также можем присвоить метод самому классу. Такие методы называются статическими.
В объявление класса они добавляются с помощью ключевого слова static , например:
class User < static staticMethod() < alert(this === User); >> User.staticMethod(); // true
Это фактически то же самое, что присвоить метод напрямую как свойство функции:
class User < >User.staticMethod = function() < alert(this === User); >;
Значением this при вызове User.staticMethod() является сам конструктор класса User (правило «объект до точки»).
Обычно статические методы используются для реализации функций, которые будут принадлежать классу в целом, но не какому-либо его конкретному объекту.
Звучит не очень понятно? Сейчас все встанет на свои места.
Например, есть объекты статей Article , и нужна функция для их сравнения.
Естественное решение – сделать для этого статический метод Article.compare :
class Article < constructor(title, date) < this.title = title; this.date = date; >static compare(articleA, articleB) < return articleA.date - articleB.date; >> // использование let articles = [ new Article("HTML", new Date(2019, 1, 1)), new Article("CSS", new Date(2019, 0, 1)), new Article("JavaScript", new Date(2019, 11, 1)) ]; articles.sort(Article.compare); alert( articles[0].title ); // CSS
Здесь метод Article.compare стоит «над» статьями, как средство для их сравнения. Это метод не отдельной статьи, а всего класса.
Другим примером может быть так называемый «фабричный» метод.
Скажем, нам нужно несколько способов создания статьи:
- Создание через заданные параметры ( title , date и т. д.).
- Создание пустой статьи с сегодняшней датой.
- …или как-то ещё.
Первый способ может быть реализован через конструктор. А для второго можно использовать статический метод класса.
Такой как Article.createTodays() в следующем примере:
class Article < constructor(title, date) < this.title = title; this.date = date; >static createTodays() < // помним, что this = Article return new this("Сегодняшний дайджест", new Date()); >> let article = Article.createTodays(); alert( article.title ); // Сегодняшний дайджест
Теперь каждый раз, когда нам нужно создать сегодняшний дайджест, нужно вызывать Article.createTodays() . Ещё раз, это не метод одной статьи, а метод всего класса.
Статические методы также используются в классах, относящихся к базам данных, для поиска/сохранения/удаления вхождений в базу данных, например:
// предположим, что Article - это специальный класс для управления статьями // статический метод для удаления статьи по id: Article.remove();
Статические методы недоступны для отдельных объектов
Статические методы могут вызываться для классов, но не для отдельных объектов.
Например. такой код не будет работать:
// . article.createTodays(); /// Error: article.createTodays is not a function
Статические свойства
Новая возможность
Эта возможность была добавлена в язык недавно. Примеры работают в последнем Chrome.
Статические свойства также возможны, они выглядят как свойства класса, но с static в начале:
class Article < static publisher = "Илья Кантор"; >alert( Article.publisher ); // Илья Кантор
Это то же самое, что и прямое присваивание Article :
Article.publisher = "Илья Кантор";
Наследование статических свойств и методов
Статические свойства и методы наследуются.
Например, метод Animal.compare в коде ниже наследуется и доступен как Rabbit.compare :
class Animal < constructor(name, speed) < this.speed = speed; this.name = name; >run(speed = 0) < this.speed += speed; alert(`$бежит со скоростью $.`); > static compare(animalA, animalB) < return animalA.speed - animalB.speed; >> // Наследует от Animal class Rabbit extends Animal < hide() < alert(`$прячется!`); > > let rabbits = [ new Rabbit("Белый кролик", 10), new Rabbit("Чёрный кролик", 5) ]; rabbits.sort(Rabbit.compare); rabbits[0].run(); // Чёрный кролик бежит со скоростью 5.
Мы можем вызвать Rabbit.compare , при этом будет вызван унаследованный Animal.compare .
Как это работает? Снова с использованием прототипов. Как вы уже могли предположить, extends даёт Rabbit ссылку [[Prototype]] на Animal .
Так что Rabbit extends Animal создаёт две ссылки на прототип:
- Функция Rabbit прототипно наследует от функции Animal .
- Rabbit.prototype прототипно наследует от Animal.prototype .
В результате наследование работает как для обычных, так и для статических методов.
Давайте это проверим кодом:
class Animal <> class Rabbit extends Animal <> // для статики alert(Rabbit.__proto__ === Animal); // true // для обычных методов alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
Итого
Статические методы используются для функциональности, принадлежат классу «в целом», а не относятся к конкретному объекту класса.
Например, метод для сравнения двух статей Article.compare(article1, article2) или фабричный метод Article.createTodays() .
В объявлении класса они помечаются ключевым словом static .
Статические свойства используются в тех случаях, когда мы хотели бы сохранить данные на уровне класса, а не какого-то одного объекта.
class MyClass < static property = . ; static method() < . >>
Технически, статическое объявление – это то же самое, что и присвоение классу:
MyClass.property = . MyClass.method = .
Статические свойства и методы наследуются.
Для class B extends A прототип класса B указывает на A : B.[[Prototype]] = A . Таким образом, если поле не найдено в B , поиск продолжается в A .
Задачи
Класс расширяет объект?
важность: 3
Как мы уже знаем, все объекты наследуют от Object.prototype и имеют доступ к «общим» методам объекта, например hasOwnProperty .
class Rabbit < constructor(name) < this.name = name; >> let rabbit = new Rabbit("Rab"); // метод hasOwnProperty от Object.prototype alert( rabbit.hasOwnProperty('name') ); // true
Но что если мы явно напишем «class Rabbit extends Object» – тогда результат будет отличаться от обычного «class Rabbit» ?
Ниже пример кода с таким наследованием (почему он не работает? исправьте его):
class Rabbit extends Object < constructor(name) < this.name = name; >> let rabbit = new Rabbit("Кроль"); alert( rabbit.hasOwnProperty('name') ); // Ошибка
Сперва давайте разберёмся, почему код не работает.
Причина становится очевидна, если мы попытаемся запустить его. Унаследованный конструктор класса должен вызывать super() . В противном случае «this» будет не определён.
class Rabbit extends Object < constructor(name) < super(); // надо вызвать конструктор родителя, когда наследуемся this.name = name; >> let rabbit = new Rabbit("Кроль"); alert( rabbit.hasOwnProperty('name') ); // true
Но это ещё не все.
Даже после исправления есть важное различие между «class Rabbit extends Object» и class Rabbit .
Как мы знаем, синтаксис «extends» устанавливает 2 прототипа:
- Между «prototype» функций-конструкторов (для методов)
- Между самими функциями-конструкторами (для статических методов).
В случае с class Rabbit extends Object это значит:
class Rabbit extends Object <> alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true
Таким образом, Rabbit предоставляет доступ к статическим методам Object через Rabbit , например:
class Rabbit extends Object <> // обычно мы вызываем Object.getOwnPropertyNames alert( Rabbit.getOwnPropertyNames() ); // a,b
Но если явно не наследуем от объекта, то для Rabbit.__proto__ не установлено значение Object .
class Rabbit <> alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) false (!) alert( Rabbit.__proto__ === Function.prototype ); // как у каждой функции по умолчанию // ошибка - нет такой функции у Rabbit alert( Rabbit.getOwnPropertyNames() ); // Ошибка
Таким образом, в этом случае у Rabbit нет доступа к статическим методам Object .
Кстати, у Function.prototype также есть «общие» методы, такие как call , bind и т. д. Они в конечном итоге доступны в обоих случаях, потому что для встроенного конструктора Object Object.__proto__ === Function.prototype .
Пример на картинке:
Короче говоря, есть два отличия:
class Rabbit | class Rabbit extends Object |
---|---|
– | необходимо вызвать super() в конструкторе |
Rabbit.__proto__ === Function.prototype | Rabbit.__proto__ === Object |
Когда использовать статические методы
В обсуждениях к посту (перевод) о именованных конструкторах прозвучало мнение, что статические методы плохи и их вообще не стоит использовать. На мой взгляд, это слишком большое обобщение.
Статические методы по сути своей просто способ организации глобальных функций в пространства имен. Использование пространств имен, я думаю, вы согласитесь — хороший тон. Что касается глобальных функций — мы используем их всегда, встроенные функции PHP составляют основу нашего кода.
Основная проблема здесь — отсутствие совместно используемого глобального состояния. Вот пример из прошлого поста:
В данном примере возвращаемый результат свободен от побочных эффектов и вполне предсказуем, т.к. зависит только от аргументов, подаваемых на вход. Каждый раз при вызове метода вам будет возвращен идентичный результат (объект Time со значением 11:45), вне зависимости от состояния системы, контекста или чего-либо еще.
Другой пример:
И снова — результат предсказуем, Calculator::sum(1, 2); предоставляет нам сервис, не имеющий состояния, и не зависящий ни от чего, кроме аргументов. Более того, эта реализация не может быть полиморфной или иметь различные имплементации, т.к. любой результат кроме 3 будет ошибкой. Да, вы можете изменить внутреннюю реализацию метода, улучшив алгоритм сложения чисел, но это не должно никак отражаться на результате его использования.
Возьмем обратный пример, на этот раз с состоянием:
Пример элементарный, но в более сложных ситуациях это может быть не столь доходчиво. Представьте, что два разработчика используют в своем коде счетчики. Когда они тестируют свое решение изолированно — нет никаких проблем. Но после интеграции их решений счетчик начинает работать не так, как ожидалось, потому что используется глобальное состояние, вместо того, чтобы воспользоваться отдельным экземпляром счетчика.
Абстракция
Возможно, вы все еще чувствуете неприятие против кода, вроде Calculator::sum($x, $y) , т.к. мы не можем сымитировать или расширить его. Но не стоит забывать, что это довольно низкий уровень абстракции. Вы также не сможете сымитировать и расширить оператор + в PHP, но я не думаю, что вы когда-либо чувствовали потребность в этом. Если вам нужен более высокий уровень абстракции, то композиция — ваш верный спутник. Но хочу заметить, между Calculator::sum($x, $y) и + есть довольно интересное различие, первый может вот так, а второй нет:
Это все может показаться избыточным, но не стоит забывать про функции и статические методы, ведь они могут быть очень полезны при правильном их применении.
Часть 1: Как использовать именованные конструкторы в PHP
- Веб-разработка
- PHP
- Программирование