JavaRush/Java блог/Архив info.javarush/Модификатор Static в Java
Автор
Pavlo Plynko
Java-разработчик в CodeGym

Модификатор Static в Java

Статья из группы Архив info.javarush
участников
Модификатор static в Java напрямую связан с классом. Если поле статично, значит оно принадлежит классу, если метод статичный, то аналогично: он принадлежит классу. Исходя из этого, можно обращаться к статическому методу или полю, используя имя класса. Например, если поле count статично в классе Counter, значит, вы можете обратиться к переменной запросом вида: Counter.count.

Кратко

  • static привязывает поле, метод, блок или вложенный класс к самому классу, а не к его объектам.
  • Статическое поле существует в одном экземпляре на весь класс: изменил его через один объект, изменилось для всех.
  • Обращаться к статическим членам принято по имени класса (Counter.count), объект для этого создавать не нужно.
  • Из статического метода нельзя напрямую обратиться к нестатическому полю: объекта, которому оно принадлежит, может и не быть.
  • Статический метод нельзя переопределить. Одноименный метод в наследнике не переопределяет родительский, а скрывает его, и выбор идет по типу переменной еще на этапе компиляции.
  • Статический блок выполняется один раз при загрузке класса и инициализирует статические поля.
Давайте вспомним (а может быть, узнаем), что такое static и что может быть статическим в Java. Static это модификатор, применяемый к полю, блоку, методу или внутреннему классу. Данный модификатор указывает на привязку субъекта к текущему классу.Светлый лабрадор идет по дороге, держа в зубах поводок второй собаки

Где применяется static

Статические поля

При обозначении переменной уровня класса мы указываем на то, что это значение относится к классу. Если этого не делать, то значение переменной будет привязываться к объекту, созданному по этому классу. Что это значит? Долгопят с огромными удивленными глазами сидит на ветке А то, что если переменная не статическая, то у каждого нового объекта данного класса будет свое значение этой переменной, меняя которое мы меняем его исключительно в одном объекте: Например, у нас есть класс Car с нестатической переменной:
public class Car {
  int km;
}
Тогда в main:
Car orangeCar = new Car();
orangeCar.km = 100;

Car blueCar = new Car();
blueCar.km = 85;

System.out.println("Orange car - " + orangeCar.km);
System.out.println("Blue car - " + blueCar.km);
Вывод мы получим:

Orange car - 100
Blue car - 85
Как видим, у каждого объекта своя переменная, изменение которой происходит только для этого объекта. Ну а если у нас переменная статическая, то это глобальное значение, одно для всех: Теперь мы имеем Car со статической переменной:
public class Car {
  static int km;
}
Тогда тот же код в main будет выдавать в консоль:

Orange car - 85
Blue car - 85
Ведь переменная у нас одна на всех, и каждый раз мы меняем именно ее. К статическим переменным, как правило, обращаются не по ссылке на объект (orangeCar.km), а по имени класса: Car.km

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

Есть два блока инициализации: обычный и статический. Блок предназначен для инициализации внутренних переменных. Если блок обычный, то им инициализируют внутренние переменные объекта, если же статический, соответственно, им задают статические переменные (то есть переменные класса). Пример класса со статическим блоком инициализации:
public class Car {
  static int km;

  static {
     km = 150;
  }
}

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

Статические методы отличаются от обычных тем, что они также привязаны к классу, а не к объекту. Важным свойством статического метода является то, что он может обратиться только к статическим переменным/методам. В качестве примера давайте рассмотрим класс, который у нас будет неким счетчиком, ведущим учет вызовов метода:
public class Counter {
  static int count;

  public static void invokeCounter() {
     count++;
     System.out.println("Текущее значение счетчика - " + count);
  }
}
Вызовем его в main:
Counter.invokeCounter();
Counter.invokeCounter();
Counter.invokeCounter();
И получим вывод в консоль:

Текущее значение счетчика - 1
Текущее значение счетчика - 2
Текущее значение счетчика - 3

Статический класс в Java

Статическим классом может быть только внутренний класс. Опять же, этот класс привязан к внешнему классу, и если внешний наследуется другим классом, то этот не будет наследован. При этом данный класс можно наследовать, как и он может наследоваться от любого другого класса и имплементировать интерфейс. По сути статический вложенный класс ничем не отличается от любого другого внутреннего класса за исключением того, что его объект не содержит ссылку на создавший его объект внешнего класса. Тем не менее, благодаря этому статический класс наиболее похож на обычный не вложенный, ведь единственное различие состоит в том, что он упакован в другой класс. В некоторых случаях для нас это преимущество, так как с него у нас есть доступ к приватным статическим переменным внешнего класса. Пример вложенного статического класса:
public class Vehicle {

  public static class Car {
     public int km;
  }
}
Создание экземпляра данного класса и задание значения внутренней переменной:
Vehicle.Car car = new Vehicle.Car();
car.km = 90;

Доступ к статическим членам без объекта

Для использования статических методов/переменных/класса нам не нужно создавать объект данного класса. Конечно, следует учитывать модификаторы доступа. Например, поля private доступны только внутри класса, в котором они объявлены. Поля protected доступны всем классам внутри пакета (package), а также всем классам-наследникам вне пакета. Для более подробной информации ознакомьтесь со статьей “private vs protected vs public”. Предположим, существует статический метод increment() в классе Counter, задачей которого является инкрементирование счетчика count. Для вызова данного метода можно использовать обращение вида Counter.increment(). Нет необходимости создавать экземпляр класса Counter для доступа к статическому полю или методу. Это фундаментальное отличие между статическими и НЕ статическими объектами (членами класса). Еще раз напомню, что статические члены класса напрямую принадлежат классу, а не его экземпляру. То есть, значение статической переменной count будет одинаковое для всех объектов типа Counter. Далее в этой статье мы рассмотрим основополагающие аспекты применения модификатора static в Java, а также некоторые особенности, которые помогут понять ключевые концепции программирования.

Статический импорт

static import позволяет импортировать статические члены класса. Это дает возможность обращаться к ним напрямую, без указания имени класса. Пример:
import static java.lang.Math.PI;
import static java.lang.Math.cos;

public class Test {
    public static void main(String[] args) {
        // Вместо Math.cos(Math.PI)
        System.out.println(cos(PI));
    }
}
Эта возможность улучшает читаемость кода, если используется умеренно. Она очень популярна в JUnit-тестах для импорта assert-методов (assertEquals, assertTrue и т.д.).

static и обычные члены класса: в чем разница

Сведем оба варианта из примеров выше в одну таблицу:
СвойствоОбычный член классаЧлен с модификатором static
Кому принадлежитконкретному объектусамому классу
Сколько копий в памятисвоя у каждого объектаодна на все объекты
Как принято обращатьсяorangeCar.kmCar.km
Нужен ли объектда, сначала newнет
Доступ к нестатическим членаместьнет, только к статическим

Что должен знать каждый программист о модификаторе Static в Java

В этом разделе мы рассмотрим основные моменты использования статических методов, полей и классов. Начнем с переменных.
  1. Вы НЕ можете получить доступ к НЕ статическим членам класса, внутри статического контекста, как вариант, метода или блока. Результатом компиляции приведенного ниже кода будет ошибка:
    public class Counter{
    private int count;
    public static void main(String args[]){
       System.out.println(count); //compile time error
    }}
    Это одна из наиболее распространенных ошибок, допускаемых программистами Java, особенно новичками. Так как метод main статичный, а переменная count нет, в этом случае метод println, внутри метода main выбросит “Compile time error”.
  2. В отличие от локальных переменных, статические поля и методы НЕ потокобезопасны (Thread-safe) в Java. На практике это одна из наиболее частых причин возникновения проблем, связанных с безопасностью мультипоточного программирования. Учитывая что каждый экземпляр класса имеет одну и ту же копию статической переменной, то такая переменная нуждается в защите, в «залочивании» классом. Поэтому при использовании статических переменных, убедитесь, что они должным образом синхронизированы (synchronized), во избежание проблем, например таких как «состояние гонки» (race condition).

  3. Статические методы имеют преимущество в применении, т.к. отсутствует необходимость каждый раз создавать новый объект для доступа к таким методам. Статический метод можно вызвать, используя тип класса, в котором эти методы описаны. Именно поэтому, подобные методы как нельзя лучше подходят в качестве методов-фабрик (factory), и методов-утилит (utility). Класс java.lang.Math это замечательный пример, в котором почти все методы статичны, по этой же причине классы-утилиты в Java финализированы (final).

  4. Другим важным моментом является то, что вы НЕ можете переопределять (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, т.к. произошло обращение к методу во время компиляции. И заметьте, ошибки во время компиляции не возникло!

  5. Объявить статическим также можно и класс, за исключением классов верхнего уровня. Такие классы известны как «вложенные статические классы» (nested static class). Они бывают полезными для представления улучшенных связей. Яркий пример вложенного статического класса это HashMap.Entry, который предоставляет структуру данных внутри HashMap. Стоит заметить, также как и любой другой внутренний класс, вложенные классы находятся в отдельном файле .class. Таким образом, если вы объявили пять вложенных классов в вашем главном классе, у вас будет 6 файлов с расширением .class. Еще одним примером использования является объявление собственного компаратора (Comparator), например компаратор по возрасту (AgeComparator) в классе сотрудники (Employee).

  6. Модификатор static также может быть объявлен в статичном блоке, более известным как «Статический блок инициализации» (Static initializer block), который будет выполнен во время загрузки класса. Если вы не объявите такой блок, то Java соберет все статические поля в один список и выполнит его во время загрузки класса. Однако, статичный блок НЕ может пробросить перехваченные исключения, но может выбросить не перехваченные. В таком случае возникнет «ExceptionInInitializerError». На практике, любое исключение, возникшее во время выполнения и инициализации статических полей, будет завернуто Java в эту ошибку. Это также самая частая причина ошибки «NoClassDefFoundError», т.к. класс не находился в памяти во время обращения к нему.

  7. Полезно знать, что статические методы связываются во время компиляции, в отличие от связывания виртуальных или не статических методов, которые связываются во время исполнения на реальном объекте. Следовательно, статические методы не могут быть переопределены в Java, т.к. полиморфизм во время выполнения не распространяется на них. Это важное ограничение, которое необходимо учитывать, объявляя метод статическим. В этом есть смысл, только тогда, когда нет возможности или необходимости переопределения такого метода классами-наследниками. Методы-фабрики и методы-утилиты хорошие образцы применения модификатора static. Джошуа Блох выделил несколько преимуществ использования статичного метода-фабрики перед конструктором, в книге «Effective Java», которая является обязательной для прочтения каждым программистом данного языка.

  8. Важным свойством статического блока является инициализация. Статические поля или переменные инициализируются после загрузки класса в память. Порядок инициализации сверху вниз, в том же порядке, в каком они описаны в исходном файле Java класса. Поскольку статические поля инициализируются на потокобезопасный манер, это свойство также используется для реализации паттерна Singleton. Если вы не используете список Enum как Singleton, по тем или иным причинам, то для вас есть хорошая альтернатива. Но в таком случае необходимо учесть, что это не «ленивая» инициализация. Это означает, что статическое поле будет проинициализировано еще ДО того как кто-нибудь об этом «попросит». Если объект ресурсоемкий или редко используется, то инициализация его в статическом блоке сыграет не в вашу пользу.

  9. Во время сериализации, также как и transient переменные, статические поля не сериализуются. Действительно, если сохранить любые данные в статическом поле, то после десериализации новый объект будет содержать его первичное (по-умолчанию) значение, например, если статическим полем была переменная типа int, то ее значение после десериализации будет равно нулю, если типа float, то 0.0, если типа Object, то null. Честно говоря, это один из наиболее часто задаваемых вопросов касательно сериализации на собеседованиях по Java. Не храните наиболее важные данные об объекте в статическом поле!

  10. И напоследок, поговорим о static import. Данный модификатор имеет много общего со стандартным оператором import, но в отличие от него позволяет импортировать один или все статические члены класса. При импортировании статических методов, к ним можно обращаться как будто они определены в этом же классе, аналогично при импортировании полей, мы можем получить доступ без указания имени класса. Данная возможность появилась в Java версии 1.5, и при должном использовании улучшает читабельность кода. Наиболее часто данная конструкция встречается в тестах JUnit, т.к. почти все разработчики тестов используют static import для assert методов, например assertEquals() и для их перегруженных дубликатов.

На этом все. Все вышеперечисленные пункты о модификаторе static в Java обязан знать каждый программист. В данной статье была рассмотрена базовая информация о статических переменных, полях, методах, блоках инициализации и импорте. В том числе некоторые важные свойства, знание которых является критичным при написании и понимании программ на Java. Я надеюсь, что каждый разработчик доведет свои навыки использования статических концептов до совершенства, т.к. это очень важно для серьезного программирования.

Вопросы и ответы

Чем статическое поле отличается от обычного?

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

Почему в статическом методе нельзя обратиться к нестатическому полю?

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

Можно ли переопределить статический метод?

Нет. Одноименный метод в классе-наследнике не переопределяет родительский, а скрывает его, это называется сокрытием методов. Какой из них вызовется, решается на этапе компиляции по типу переменной, а не по типу объекта.

Когда выполняется статический блок инициализации?

Один раз, при загрузке класса в память, и до того, как кто-то обратится к классу. Блоки и инициализаторы статических полей выполняются сверху вниз, в порядке их объявления в файле.

Чем статический вложенный класс отличается от обычного внутреннего?

Объект статического вложенного класса не хранит ссылку на объект внешнего класса, поэтому его можно создать без него: new Vehicle.Car(). Обычному внутреннему классу объект внешнего класса обязателен.

Что такое static import и когда он уместен?

Это импорт статических членов класса, после которого к ним обращаются без имени класса: cos(PI) вместо Math.cos(Math.PI). Возможность появилась в Java 5. Уместна в меру, чаще всего ее применяют в тестах JUnit для assert-методов. Источник: Oracle Java Tutorials: Understanding Class Members.

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

Комментарии (78)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Иван
Уровень 19
2 октября 2024, 06:07
Многие критикуют статью, однако мне понравилась, много информации, тем самым - пищи для размышления.
16 мая 2024, 14:51
в статье не хватает раскрытия информации почему нельзя обращаться к не статическим полям, методам. Вызывая метод по ссылке на объект, в вызывающий метод неявно передается ссылка на объект - this, а статические метод не имеет этой ссылки.
Anonymous #3346123
Уровень 15
1 апреля 2024, 08:58
"Пример вложенного статического класса : public class Vehicle { public static class Car { public int km; } } Создание экземпляра данного вложенного статического класса и задание значения внутренней переменной: Vehicle.Car car = new Vehicle.Car(); car.km = 90; Для использования статических методов/переменных/класса не нужно создавать объект данного класса. " А разве Vehicle.Car car = new Vehicle.Car(); это не объект ?
gypsy potassium
Уровень 4
Expert
30 января 2024, 18:32
Опять же, этот класс привязан к внешнему классу, и если внешний наследуется другим классом, то этот не будет наследован. не понятен смысл предложения, слишком много "этих" (ну как их там)
Евгений N
Уровень 23
17 января 2024, 13:36
"статические поля не сериализуются... после десериализации новый объект будет содержать его первичное (по-умолчанию) значение" а константы (public final static ) будут содержать свои значения?
Going
Уровень 15
Expert
13 ноября 2023, 15:13
Ничего не понятно, но ооочень интересно:)
p90x
Уровень 8
14 апреля 2023, 21:59
поорал "Если ничего не понятно – добро пожаловать за дополнительной информацией."
Anonymous #3232154
Уровень 37
15 марта 2023, 18:19
" По сути статический вложенный класс ничем не отличается от любого другого внутреннего класса за исключением того, что его объект не содержит ссылку на создавший его объект внешнего класса. " Может кто объяснить простым языком что это значит?
Oraz Janov Backend Developer
10 апреля 2023, 15:24
Для статического класса не надо объявлять экземпляр класса
class ProstoClass {
    static class StaticInnerClass{
        static void sout(){
            System.out.println("Что-то");
        }
    }
}
class Solution {
    public static void main(String[] args) {
        ProstoClass.StaticInnerClass.sout();
    }
}
Т.е. чтобы обратиться к статическому классу не нужен экземпляр класса по типу
ProstoClass prostoClass = new ProstoClass();
Pavel Tsygankov
Уровень 28
28 февраля 2023, 22:01
"Вы НЕ можете получить доступ к НЕ статическим членам класса, внутри статического контекста, как вариант, метода или блока.Это одна из наиболее распространённых ошибок допускаемых программистами Java, особенно новичками. Так как метод main статичный, а переменная count нет, в этом случае метод println, внутри метода main выбросит “Compile time error”." Нашел ответ почему моя прога не компилируется но что с этим делать?? Статическим может быть только внутренний класс, но если немного пошалить и вписать перед внешним классом модификатор static, все прекрасно работает
Andrey EliseevJava Developer в СберТех
24 февраля 2023, 13:51
v.kmToMiles(10); не корректное обращение к статическому методу в примере, просьба поправить)