Может ли статический метод вызывать не статический?
В книге «Полный справочник Java» Герберта Шилдта сказано: «На методы объявленные static, накладывается ряд ограничений.
— Они могут вызывать только другие статические методы.
.
public class Main < public String nonStaticF1() < return "inside non static method"; >public static void main(String[] args) < Main m1 = new Main(); System.out.println(m1.nonStaticF1()); >> //output: //inside non static method
я вижу нестатический метод вызваный из статического. Что я не так понимаю?
- Вопрос задан более трёх лет назад
- 11046 просмотров
1 комментарий
Оценить 1 комментарий
NEWOBJ.ru → Введение в ООП с примерами на C# →
Статический метод ( static ) класса – метод, не имеющий доступа к состоянию (полям) объекта, то есть к переменной this .
Для объявления статического метода используется ключевое слово static :
private static float SquareGeron (float a, float b, float c) < /* … */ >
Статический метод может быть вызван как через экземпляр класса, так и через имя класса. Например, из методов класса Triangle мы можем обратиться к статическому методу SquareGeron следующими способами:
class Triangle < public void SomeMethod () < // 1 - Неявно через объект, то есть через переменную this. SquareGeron (1,2,2); // 2 - Явно через объект, то есть через переменную this. this.SquareGeron (1,2,2); // 3 - Через имя класса, без использования объекта. Square.SquareGeron (1,2,2); >>
Аналогично, извне класса, при условии, что мы сделаем метод SquareGeron открытым:
Square sq = new Square(); // 2 - Через объект. sq.SquareGeron (1,2,2); // 3 - Через имя класса. Square.SquareGeron(1,2,3);
Подчеркнем, что во всех перечисленных случаях метод не имеет доступа к переменной this , то есть даже при вызове через экземпляр, ему не передается, как для обычных методов, параметр this , он не знает, какой именно объект его вызвал и, соответственно, у него нет доступа к полям объекта. Также, как следствие, из статического метода нельзя вызвать нестатический метод того же класса для вызванного объекта. При этом, статический метод, как и любой другой метод класса, имеет доступ к полям и методам объектов этого класса независимо от их уровня видимости:
class Vector < private float x; private float y; public Vector() < >private static float Length (float x0, float y0, float x1, float у1) < /* . */ >public float Length () < // Из нестатического метода можно вызывать статический метод. return Length (0, 0, x, y); >public static Vector Sum (Vector a, Vector b) < Vector sum = new Vector(); // Статические метод, как и нестатический, // имеет доступ к полям, в том числе закрытым (private) // объектов того же класса: sum.x = a.x + b.x; sum.y = a.y + b.y; return sum; >>
Слово «статический» используется в том смысле, что статические методы не относятся к динамике объекта, не используют и не меняют его состояния.
Вспомним, что мы уже использовали статический метод для вычисления квадратного корня Math.Sqrt . Класс System.Math реализует в виде статических методов и другие математические функции. Однако есть множество причин, почему в большинстве случаев следует избегать использования статических методов.
Программисты, не имеющие опыта ООП, часто начинают широко использовать статические методы как способ программировать на объектно-ориентированном языке в процедурном стиле. Действительно, для статических методов их класс – лишь способ синтаксической группировки. Более того, использование статических методов – это всегда в некотором смысле отход от ООП, так как он делает невозможным использование всех ключевых элементов объектно-ориентированного программирования: абстрактных типов данных, наследования, полиморфизма. Сформулируем следующее правило: в первом приближении статическими следует делать только 1) небольшие 2) вспомогательные 3) закрытые ( private ) методы класса. Практически всегда методы, не удовлетворяющие приведенному правилу и не обращающиеся к полям объекта, можно и нужно вынести в отдельный класс. Например, если бы метод SquareGeron был большим, то следовало бы создать класс SquareGeronCalculator и создать там открытый метод Calc .
§ 25. Статические поля. Аналогично тому, как статический метод не привязан к объекту, мы можем объявить статическое поле, не являющееся частью никакого объекта, которое будет создаваться в одном экземпляре и будет доступно из любого объекта класса или через имя класса.
Например, следующий код считает число объектов типа Point , созданных с момента запуска приложения:
class Point < private static long newCount; public Point() < // Эквивалентно newCount++, но корректно работает в многопоточном окружении. Interlocked.Increment(ref newCount); >>
Экземпляр статической переменной создается автоматически до первого ее использования (когда именно – не регламентируется), а при создании экземпляров класса память для статических переменных не выделяется. Таким образом, в приведенном примере в некоторый момент времени после запуска приложения и до вызова команды увеличения значения newCount будет создан ровно один экземпляр этой переменной, а каждый создаваемый объект Point будет увеличивать ее значение в своем конструкторе.
Применительно к статическим полям также можно сформулировать правило: в первом приближении следует избегать использовать статические полей.
Причин тут много, в частности, создаются неявные связи между объектами одного класса. Также статические переменные не обрабатываются сборщиком мусора и существуют до закрытия программы, поэтому в крупных приложениях статические поля могут «связать» ощутимый объем памяти, освобождаемый только при закрытии программы.
Тем не менее, статические поля, как и статические методы, используются достаточно широко. Рассмотрим следующий типичный пример.
Положим, мы хотим реализовать авторизацию пользователей, то есть управлять доступностью функциональных возможностей приложения в зависимости от роли текущего пользователя. Для этого в приложении фиксируется перечень возможностей ( features ) и при вызове соответствующих методов, выполняется проверка, разрешен ли текущему пользователю доступ к запрашиваемой функциональной возможности. В первом приближении мы могли бы задать уникальное название для каждой функциональной возможности и привязывать к текущему пользователю перечень строк – список разрешенных возможностей. Тогда авторизация могла бы выглядеть следующим образом:
public class ACL // access control list < // Список возможностей, доступных текущему пользователю. private string[] currentUserAllowedFeatures; public void Authorize (string feature) < // Если у пользователь нет запрашиваемой возможности… if (!currentUserAllowedFeatures.Contains (feature)) // …формируем исключение. throw new Exception (); >> // Использование: public class SomeClass < private ACL acl; // Объект acl создается и инициализируется // где-то при запуске программы. public SomeClass (ACL acl) < this.acl = acl; >public void DoSomeJob () < // Сначала проверяем, если ли у текущего пользователя доступ // к указанной возможноти (ADMINISTRATOR). // Если доступа нет, исключение прервет выполнение метода. acl.Authorize (“ADMINISTRATOR”); // . >>
Проблема этого решения в том, что легко допустить ошибку в написании названия функциональной возможности. Однако мы можем перечислить допустимые названия как открытые неизменяемые статические поля некоторого класса:
public Feature < public static readonly string Administrator = “ADMINISTRATOR”; public static readonly string Guest = “GUEST”; // . >// Фрагмент из предыдущего листинга: public void DoSomeJob ()
Такое решение лучше, но оно все еще позволяет нам передать в метод Authorize произвольную строку, не используя класс Feature . Проанализируйте следующий код:
public Feature < private string code; private Feature (string code) < this.code = code; >public string GetCode() < return code; >public static readonly Feature Administrator = new Feature (“ADMINISTRATOR); public static readonly Feature Guest = new Feature (“GUEST”); // . > //class ACL public void Authorize (Feature feature) < if (!currentUserAllowedFeatures.Contains (feature.GetCode())) throw new Exception (); >// class SomeClass public void DoSomeJob ()
Теперь экземпляры класса Feature представляют отдельные функциональные возможности, но так как единственный конструктор этого класса объявлен как закрытый ( private ), то они не могут быть созданы извне класса. В статических открытых полях сохраняем фиксированный перечень экземпляров этого же класса. Таким образом, в метод Authorize мы передаем объект типа Feature , но не можем создать его самостоятельно, а используем перечень фиксированных, «зашитых» в классе Feature объектов.
Мы можем пойти еще дальше, сохраняя в классе ACL не массив строк, а массив объектов Features :
class ACL < private Feature[] currentUserAllowedFeatures; public void Authorize (Feature feature) < for (int i = 0; i < currentUserAllowedFeatures.Length; i++) < if (currentUserAllowedFeatures[i].GetCode() == feature.GetCode()) return; >throw new Exception (); > >
Такое решение делает безопасным не только передачу параметра в Authorize , но и формирование списка currentUserAllowedFeatures , который теперь также гарантированно не будет содержать произвольных строк.
В заключение, повторимся, что хотя механизм статических полей и методов широко применяется в современной практике объектно-ориентированного программирования, использовать эти возможности следует крайне осмотрительно. Дополнительные аргументы против статических методов и полей мы рассмотрим в последующих главах.
Вопросы и задания
Что такое статические методы, статические поля?
Верно ли говорить о статических классах или статических объектов? В чем их отличие (если верно) от неизменяемых классов и объектов?
Статический метод не имеет доступ к полям объекта, но имеет ли он доступ к полям объекта того же класса, переданного в параметрах этого метода?
В каком порядке следует использовать ключевые слова public/private и static ?
Сравните следующие поля класса:
public class SomeClass < static int field1 = 1; const int field2 = 2; readonly int field3 = 3; static readonly int field4 = 4; int field5 = 5; >
Когда происходит выделение памяти, создаются ли экземпляры полей для каждого экземпляра объекта, возможно ли изменение значений полей? Какие еще есть отличия между ними?
Статические поля и методы часто применяются для реализации объектов-одиночек 35 . Объект-одиночка ( singleton ) – объект, который должен существовать в программе в одном экземпляре. К примеру, это может быть объект, хранящий глобальные параметры приложения. Разберите следующие реализации:
// 1 вариант public class Settings < public string TempWorkspacePath() < /* . */ >public static Settings Singleton = new Settings(); > // Использование: string path = Settings.Singleton.TempWorkspacePath(); // 2 вариант public class Settings < public string TempWorkspacePath() < /* . */ >private static Settings singleton; private Settings () < singleton = new Settings(); >public Settings Get () < if (singleton == null) singleton = new Settings(); return singleton; >> // Использование: string path = Settings.Get().TempWorkspacePath();
Объясните, почему вторая реализация лучше? Почему во втором варианте используется закрытый ( private ) конструктор? Почему не используется ключевое слово readonly для поля singleton ?
* Объясните почему использование newCount++ , вместо метода Interlocked.Increment может привести к ошибкам (неверном подсчету).
* Изучите механизм перечислений в C# ( enum ).
- 35. Отметим попутно, что шаблон «одиночка» в современной практике часто стремятся заменять на шаблон «инверсии управления».↩︎
Статические свойства и методы
Мы также можем присвоить метод самому классу. Такие методы называются статическими.
В объявление класса они добавляются с помощью ключевого слова 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 |
Python: статические методы, методы класса и экземпляра класса
Согласно модели данных Python, язык предлагает три вида методов: статические, класса и экземпляра класса. Давайте посмотрим, что же происходит за кулисами каждого из видов методов. Понимание принципов их работы поможет в создании красивого и эффективного кода. Начнём с самого простого примера, в котором демонстрируются все три вида методов.
class ToyClass:
def instancemethod(self):
return 'instance method called', self
@classmethod
def classmethod(cls):
return 'class method called', cls @staticmethod
def staticmethod():
return 'static method called'
Методы экземпляра класса
Это наиболее часто используемый вид методов. Методы экземпляра класса принимают объект класса как первый аргумент, который принято называть self и который указывает на сам экземпляр. Количество параметров метода не ограничено.
Используя параметр self , мы можем менять состояние объекта и обращаться к другим его методам и параметрам. К тому же, используя атрибут self.__class__ , мы получаем доступ к атрибутам класса и возможности менять состояние самого класса. То есть методы экземпляров класса позволяют менять как состояние определённого объекта, так и класса.
Встроенный пример метода экземпляра — str.upper() :
>>> "welcome".upper() # 'WELCOME'
Методы класса
Методы класса принимают класс в качестве параметра, который принято обозначать как cls . Он указывает на класс ToyClass, а не на объект этого класса. При декларации методов этого вида используется декоратор classmethod .
Методы класса привязаны к самому классу, а не его экземпляру. Они могут менять состояние класса, что отразится на всех объектах этого класса, но не могут менять конкретный объект.
Встроенный пример метода класса — dict.fromkeys() — возвращает новый словарь с переданными элементами в качестве ключей.
dict.fromkeys('AEIOU') #
Статические методы
Статические методы декларируются при помощи декоратора staticmethod . Им не нужен определённый первый аргумент (ни self , ни cls ).
Их можно воспринимать как методы, которые “не знают, к какому классу относятся”.
Таким образом, статические методы прикреплены к классу лишь для удобства и не могут менять состояние ни класса, ни его экземпляра.
С теорией достаточно. Давайте разберёмся с работой методов, создав объект нашего класса и вызвав поочерёдно каждый из методов: instancemethod, classmethod and staticmethod.
>>> obj = ToyClass()
>>> obj.instancemethod()
('instance method called', ToyClass instance at 0x10f47e7a0>)>>> ToyClass.instancemethod(obj)
('instance method called', ToyClass instance at 0x10f47e7a0>)
Пример выше подтверждает то, что метод instancemethod имеет доступ к объекту класса ToyClass через аргумент self . Кстати, вызов функции obj.instancemethod() используется лишь для удобства, то есть можно использовать и ToyClass.instancemethod(obj) .
Теперь давайте вызовем метод класса:
>>> obj.classmethod()
('class method called', )
Мы видим, что метод класса classmethod() имеет доступ к самому классу ToyClass, но не к его конкретному экземпляру объекта. Запомните, в Python всё является объектом. Класс тоже объект, который мы можем передать функции в качестве аргумента.
Заметьте, что self и cls — не обязательные названия и эти параметры можно называть иначе.
def instancemethod(self, . )
def classmethod(cls, . )-------то же самое, что и----------def instancemethod(my_object, . )
def classmethod(my_class, . )
Это лишь общепринятые обозначения, которым следуют все. Тем не менее они должны находиться первыми в списке параметров.
Вызовем статический метод:
>>> obj.staticmethod()
static method called
Да, это может вас удивить, но статические методы можно вызывать через объект класса. Вызов через точку нужен лишь для удобства. На самом же деле в случае статического метода никакие аргументы ( self или cls ) методу не передаются.
То есть статические методы не могут получить доступ к параметрам класса или объекта. Они работают только с теми данными, которые им передаются в качестве аргументов.
Теперь давайте вызовем те же самые методы, но на самом классе.
>>> ToyClass.classmethod()
('class method called', )>>> ToyClass.staticmethod()
'static method called'>>> ToyClass.instancemethod()
TypeError: unbound method instancemethod()
must be called with ToyClass instance as
first argument (got nothing instead)
Метод класса и статический метод работают, как нужно. Однако вызов метода экземпляра класса выдаёт TypeError, так как метод не может получить на вход экземпляр класса.
Теперь, когда вы знаете разницу между тремя видами методов, давайте рассмотрим реальный пример для понимания того, когда и какой метод стоит использовать. Пример взят отсюда.
from datetime import dateclass Person:
def __init__(self, name, age):
self.name = name
self.age = age@classmethod
def from_birth_year(cls, name, year):
return cls(name, date.today().year - year)@staticmethod
def is_adult(age):
return age > 18person1 = Person('Sarah', 25)
person2 = Person.from_birth_year('Roark', 1994)>>> person1.name, person1.age
Sarah 25>>> person2.name, person2.age
Roark 24>>> Person.is_adult(25)
True
Когда использовать каждый из методов?
Выбор того, какой из методов использовать, может показаться достаточно сложным. Тем не менее с опытом этот выбор делать гораздо проще.
Чаще всего метод класса используется тогда, когда нужен генерирующий метод, возвращающий объект класса. Как видим, метод класса from_birth_year используется для создания объекта класса Person по году рождения, а не возрасту.
Статические методы в основном используются как вспомогательные функции и работают с данными, которые им передаются.
Запомнить
- Методы экземпляра класса получают доступ к объекту класса через параметр self и к классу через self.__class__ .
- Методы класса не могут получить доступ к определённому объекту класса, но имеют доступ к самому классу через cls .
- Статические методы работают как обычные функции, но принадлежат области имён класса. Они не имеют доступа ни к самому классу, ни к его экземплярам.
- Даже если вы программируете только ради интереса, изучение ООП в Python поможет писать код так, чтобы в дальнейшем было легче искать ошибки и использовать его повторно.