Почему статические переменные считаются злыми?

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

Я считаю, статические переменные более удобны в использовании. И я предполагаю, что они тоже эффективны (пожалуйста, исправьте меня, если я ошибаюсь), потому что, если бы мне пришлось сделать 10000 вызовов функции внутри класса, я был бы рад сделать метод статичным и использовать прямолинейное Class.methodCall() на нем вместо того, чтобы загромождать память 10000 экземплярами класса, верно?

Более того, статика уменьшает взаимозависимости других частей кода. Они могут выступать в качестве идеальных государственных держателей. В дополнение к этому я обнаружил, что статика широко применяется в некоторых языках, таких как Smalltalk и Scala. Так почему же это угнетение статики распространено среди программистов (особенно в мире Java)?

PS: пожалуйста, поправьте меня, если мои предположения о статике неверны.

34 ответа

Решение

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

Я мог бы продолжать в течение довольно долгого времени, но большая идея, о которой стоит подумать, заключается в том, что чем теснее рамки чего-либо, тем легче рассуждать. Мы хорошо думаем о маленьких вещах, но трудно рассуждать о состоянии системы с миллионами линий, если нет модульности. Кстати, это относится ко всем видам вещей, а не только к статическим переменным.

Он не очень объектно-ориентирован: одна из причин, по которой статика может считаться "злой" некоторыми людьми, заключается в том, что они противоречат объектно-ориентированной парадигме. В частности, это нарушает принцип инкапсуляции данных в объекты (которые могут быть расширены, скрытие информации и т. Д.). Статика, как вы описываете, используя их, по сути, должна использовать их как глобальную переменную, чтобы избежать решения таких проблем, как область действия. Однако глобальные переменные являются одной из определяющих характеристик парадигмы процедурного или императивного программирования, а не характеристикой "хорошего" объектно-ориентированного кода. Это не значит, что процедурная парадигма плохая, но у меня сложилось впечатление, что ваш супервайзер ожидает, что вы пишете "хороший объектно-ориентированный код", и вы действительно хотите написать "хороший процедурный код".

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

Время жизни объекта. Кроме того, у статики есть время жизни, которое соответствует всему времени выполнения программы. Это означает, что даже когда вы закончили использовать свой класс, память от всех этих статических переменных не может быть собрана сборщиком мусора. Если, например, вместо этого вы сделали свои переменные нестатическими, а в своей функции main() вы сделали один экземпляр вашего класса, а затем попросили ваш класс выполнить определенную функцию 10000 раз после выполнения этих 10000 вызовов и вы удаляете ссылки на один экземпляр, все ваши статические переменные могут быть собраны и использованы повторно.

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

Другие варианты: если эффективность является вашей главной задачей, могут быть другие более эффективные способы решения проблемы скорости, чем рассмотрение только преимущества вызова, заключающегося в большей скорости, чем создание. Подумайте, нужны ли где-нибудь временные или энергозависимые модификаторы. Чтобы сохранить возможность встраивания, метод может быть помечен как final, а не static. Параметры метода и другие переменные могут быть помечены как окончательные, чтобы разрешить определенные оптимизации компилятора на основе предположений о том, что может изменить эти переменные. Объект экземпляра можно использовать повторно несколько раз, вместо того, чтобы каждый раз создавать новый экземпляр. Там могут быть переключатели оптимизации компилятора, которые должны быть включены для приложения в целом. Возможно, дизайн должен быть настроен так, чтобы 10000 прогонов могли быть многопоточными и использовать преимущества многопроцессорных ядер. Если переносимость не имеет значения, возможно, нативный метод даст вам лучшую скорость, чем ваша статика.

Если по какой-то причине вы не хотите иметь несколько копий объекта, шаблон одноэлементного проектирования обладает преимуществами по сравнению со статическими объектами, такими как безопасность потоков (при условии, что ваш синглтон хорошо закодирован), что позволяет выполнять отложенную инициализацию, гарантируя, что объект был правильно инициализируется, когда он используется, подклассификация, преимущества в тестировании и рефакторинге вашего кода, не говоря уже о том, что если в какой-то момент вы передумали о том, что хотите только один экземпляр объекта, НАМНОГО проще удалить код, чтобы предотвратить дублирование экземпляров. чем рефакторинг всего кода статических переменных для использования переменных экземпляра. Я должен был сделать это раньше, это не весело, и вам в конечном итоге приходится редактировать намного больше классов, что увеличивает ваш риск появления новых ошибок... намного лучше, чтобы все было "правильно" в первый раз, даже если кажется, что у него есть свои недостатки. Для меня необходимая доработка, если вы решите, что вам понадобится несколько копий чего-либо, вероятно, является одной из наиболее веских причин использовать статику как можно реже. И поэтому я также не согласен с вашим утверждением о том, что статика уменьшает взаимозависимости, я думаю, что в итоге вы получите код, который будет более взаимосвязанным, если у вас есть много статик, к которым можно получить прямой доступ, а не объект, который "знает, как делать". что-то "на себя".

Зло это субъективный термин.

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

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

Вот две основные проблемы, которые у меня есть с ними.

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

Глобальные состояния абсолютно необходимы. Мы не можем избежать глобальных государств. Мы не можем избежать рассуждений о глобальных государствах. - Если мы хотим понять семантику нашего приложения.

Люди, которые пытаются избавиться от глобальных государств ради этого, неизбежно оказываются в гораздо более сложной системе - и глобальные государства все еще там, умело / идиотски замаскированные под многими слоями косвенных указаний; и мы все еще должны рассуждать о глобальных государствах, развернув все косвенности.

Как и люди Spring, которые щедро объявляют глобальные состояния в XML и считают, что это лучше.

@ Джон Скит if I create a new instance of an object Теперь у вас есть две причины: состояние внутри объекта и состояние среды, в которой размещается объект.

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

Я бы оценил где-то в 85% случаев, когда вижу "статичный" без "финала", это НЕПРАВИЛЬНО. Часто я нахожу странные обходные пути, чтобы замаскировать или скрыть эти проблемы.

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

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

Если вы хотите получить более подробную информацию, пожалуйста, читайте дальше...

Почему бы не использовать статику?

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

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

Если вы используете статику, невозможно поменять реализацию класса, чтобы протестировать компоненты более высокого уровня. Например, представьте статический CustomerDAO, который возвращает объекты Customer, которые он загружает из базы данных. Теперь у меня есть класс CustomerFilter, которому требуется доступ к некоторым объектам Customer. Если CustomerDAO статический, я не могу написать тест для CustomerFilter без предварительной инициализации моей базы данных и заполнения полезной информации.

А заполнение и инициализация базы данных занимает много времени. По моему опыту, ваша структура инициализации БД со временем будет меняться, а это значит, что данные будут изменяться, и тесты могут прерваться. IE, представьте, что Customer 1 раньше был VIP, но структура инициализации БД изменилась, и теперь Customer 1 больше не VIP, но ваш тест был жестко запрограммирован для загрузки Customer 1…

Лучшим подходом является создание экземпляра CustomerDAO и передача его в CustomerFilter при его создании. (Еще лучшим подходом было бы использование Spring или другой платформы Inversion of Control.

Как только вы это сделаете, вы можете быстро смоделировать или заглушить альтернативный DAO в вашем CustomerFilterTest, что позволит вам лучше контролировать тест,

Без статического DAO тест будет более быстрым (без инициализации db) и более надежным (потому что он не потерпит неудачу при изменении кода инициализации db). Например, в этом случае обеспечение того, чтобы Клиент 1 был и всегда будет VIP, насколько это касается теста.

Выполнение тестов

Статика вызывает реальную проблему при совместном запуске комплектов модульных тестов (например, с вашим сервером Continuous Integration). Представьте себе статическую карту сетевых объектов Socket, которая остается открытой от одного теста к другому. Первый тест может открыть Socket на порту 8080, но вы забыли очистить карту, когда тест будет сорван. Теперь, когда запускается второй тест, он может потерпеть крах, когда попытается создать новый сокет для порта 8080, поскольку порт все еще занят. Представьте также, что ссылки на сокеты в вашей статической коллекции не удаляются и (за исключением WeakHashMap) никогда не могут быть подвергнуты сборке мусора, что приводит к утечке памяти.

Это слишком обобщенный пример, но в больших системах эта проблема возникает ВСЕ ВРЕМЯ. Люди не думают о модульных тестах, запускающих и останавливающих свое программное обеспечение повторно в одной и той же JVM, но это хороший тест вашего программного обеспечения, и если у вас есть стремление к высокой доступности, это то, о чем вы должны знать.

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

Если конфигурация системы, которая применяется к этим компонентам инфраструктуры, изменяется между модульными тестами, и среда модульного тестирования не разрушает и не перестраивает компоненты, эти изменения не могут вступить в силу, и когда тест полагается на эти изменения, они потерпят неудачу,

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

Хуже того, сбой может быть основан на порядке, в котором выполнялись тесты.

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

Тонкие ошибки

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

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

Когда поток умирает, статический объект не сбрасывается и не собирается. Представьте, что у вас есть поток с именем "EmailCustomers", и когда он запускается, он заполняет статическую коллекцию строк списком адресов электронной почты, а затем начинает отправлять электронные письма на каждый из адресов. Допустим, поток каким-то образом прерван или отменен, поэтому среда высокой доступности перезапускает поток. Затем, когда поток запускается, он перезагружает список клиентов. Но поскольку коллекция является статической, она может сохранить список адресов электронной почты из предыдущей коллекции. Теперь некоторые клиенты могут получить дубликаты писем.

В сторону: статический финал

Использование "static final" фактически является Java-эквивалентом C #define, хотя есть технические различия в реализации. A C/C++ #define выгружается из кода препроцессором перед компиляцией. Java "статический финал" в конечном итоге останется в стеке. Таким образом, она больше похожа на переменную "static const" в C++, чем на #define.

Резюме

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

Есть 2 основных проблемы со статическими переменными:

  • Потокобезопасность - статические ресурсы по определению не являются потокобезопасными
  • Непрозрачность кода - вы не знаете, когда создается экземпляр статической переменной и будет ли он создан перед другой статической переменной

Обобщая несколько основных преимуществ и недостатков использования статических методов в Java:

Преимущества:

  1. Доступен глобально, т.е. не связан с каким-либо конкретным экземпляром объекта.
  2. Один экземпляр на JVM.
  3. Доступ к нему можно получить с помощью имени класса (объект не требуется).
  4. Содержит одно значение, применимое ко всем экземплярам.
  5. Загрузитесь при запуске JVM и умирает, когда JVM выключается.
  6. Они не изменяют состояние объекта.

Недостатки:

  1. Статические члены всегда являются частью памяти о том, используются они или нет.
  2. Вы не можете контролировать создание и уничтожение статической переменной. Полезно, что они были созданы при загрузке программы и уничтожены при выгрузке программы (или когда JVM завершает работу).
  3. Вы можете сделать статический поток безопасным, используя синхронизацию, но вам потребуются дополнительные усилия.
  4. Если один поток изменяет значение статической переменной, это может нарушить функциональность других потоков.
  5. Вы должны знать "статический", прежде чем использовать его.
  6. Вы не можете переопределить статические методы.
  7. Сериализация не работает с ними.
  8. Они не участвуют в полиморфизме во время выполнения.
  9. Существует проблема с памятью (в некоторой степени, но не так много, я думаю), если используется большое количество статических переменных / методов. Потому что они не будут GC, пока программа не закончится.
  10. Статические методы тоже сложно проверить.

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

При этом важно различать обычные статические переменные (обычно считается плохими) и конечные статические переменные (константы АКА; не так уж плохо).

Поскольку никто не упомянул об этом: параллелизм. Статические переменные могут вас удивить, если у вас есть несколько потоков, читающих и записывающих в статическую переменную. Это часто встречается в веб-приложениях (например, ASP.NET) и может вызывать довольно сумасшедшие ошибки. Например, если у вас есть статическая переменная, которая обновляется страницей, и страница запрашивается двумя людьми "почти в одно и то же время", один пользователь может получить результат, ожидаемый другим пользователем, или хуже.

Статика уменьшает взаимозависимости от других частей кода. Они могут выступать в качестве идеальных государственных держателей

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

* На самом деле, Прит Сангха упомянул об этом.

если бы мне нужно было сделать 10 000 вызовов функции внутри класса, я был бы рад сделать метод статичным и использовать простой метод class.methodCall() вместо того, чтобы загромождать память 10 000 экземплярами класса, верно?

Вы должны сбалансировать необходимость инкапсуляции данных в объект с состоянием, а не просто вычислять результат функции для некоторых данных.

Более того, статика уменьшает взаимозависимости других частей кода.

Как и инкапсуляция. В больших приложениях статика имеет тенденцию создавать код спагетти и не позволяет легко проводить рефакторинг или тестирование.

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

На мой взгляд, это почти никогда не связано с производительностью, а с дизайном. Я не считаю использование статических методов неправильным, как применение статических переменных (но я полагаю, что вы на самом деле говорите о вызовах методов).

Это просто о том, как изолировать логику и дать ей хорошее место. Иногда это оправдывает использование статических методов, java.lang.Math хороший пример Я думаю, когда вы называете большинство ваших классов XxxUtil или же Xxxhelper вам лучше пересмотреть свой дизайн.

Я только что подытожил некоторые моменты, высказанные в ответах. Если вы нашли что-то не так, пожалуйста, исправьте это.

Масштабирование. У нас есть ровно один экземпляр статической переменной для каждой виртуальной машины Java. Предположим, что мы разрабатываем систему управления библиотекой, и мы решили присвоить названию книги статическую переменную, поскольку в каждой книге только одна переменная. Но если система растет и мы используем несколько JVM, то у нас нет способа выяснить, с какой книгой мы имеем дело?

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

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

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

Сериализация: Сериализация также не работает с ними.

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

Но что, если они нам действительно нужны?

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

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

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

Для получения дополнительной информации прочитайте это Спасибо.

Мне кажется, что вы спрашиваете о статических переменных, но вы также указываете статические методы в своих примерах.

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

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

В качестве примера отличного Java-проекта, который использует много статики и делает это правильно, пожалуйста, посмотрите на Play! рамки. Об этом также идет обсуждение в SO.

Статические переменные / методы в сочетании со статическим импортом также широко используются в библиотеках, которые облегчают декларативное программирование в java, например: упростить или Hamcrest. Это было бы невозможно без большого количества статических переменных и методов.

Так что статические переменные (и методы) хороши, но используйте их с умом!

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

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

Еще одна причина: хрупкость.

Если у вас есть класс, большинство людей ожидают, что смогут его создать и использовать по желанию.

Вы можете задокументировать, что это не тот случай, или защититься от него (синглтон / фабричный шаблон) - но это дополнительная работа и, следовательно, дополнительные расходы. Даже тогда, в большой компании, есть вероятность, что кто-то в какой-то момент попытается использовать ваш класс, не обращая должного внимания на все приятные комментарии или фабрику.

Если вы часто используете статические переменные, это сломается. Ошибки стоят дорого.

Между улучшением производительности на 0,0001% и устойчивостью к изменениям со стороны потенциально невежественных разработчиков, во многих случаях надежность является хорошим выбором.

Преимущества:

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

Недостатки:

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

То, что нужно запомнить:

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

Как статическая ломает инкапсуляцию:

Их техническая реализация позволяет поддерживать состояние во всех экземплярах класса. Проблема в том, что это по сути не ООП, потому что он игнорирует инкапсуляцию. Если переменная может быть изменена каким-либо экземпляром класса, тогда фундаментальный принцип, лежащий в основе инкапсуляции / сокрытия информации, полностью теряется: объект больше не контролирует свое состояние. Его состояние теперь зависит от переменных, которые по сути являются глобальными. Что мы знаем, это плохо. Даже частные статические переменные поддерживают состояние на глобальном уровне, но просто ограничивают его доступ. Любой экземпляр объекта может изменить статическую переменную, которая вызывает неоднозначность, поскольку отдельные экземпляры объекта больше не имеют контроля над своим собственным состоянием. Изменения состояния могут произойти произвольно без знания объекта, который зависит от этого состояния, что является проблематичным, поскольку объект может работать неправильно, когда это происходит. Как часто говорят, статика "Наследование нарушает инкапсуляцию" делает это гораздо более суровым образом: не только раскрывая внутреннюю реализацию, но и раскрывая внутреннее состояние.

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

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

Я считаю, статические переменные более удобны в использовании. И я предполагаю, что они также эффективны (пожалуйста, исправьте меня, если я ошибаюсь), потому что, если бы мне пришлось сделать 10 000 вызовов функции внутри класса, я был бы рад сделать метод статическим и использовать простой class.methodCall() на нем вместо того, чтобы загромождать память 10 000 экземплярами класса, верно?

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

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

например:

public class WaterContainer {
    private int size;
    private int brand;
    ...etc

    public static int convertToGallon(int liters)...

    public static int convertToLiters(int gallon)...

}

Все (может:) имеет свое предназначение, если у вас есть куча потоков, которые должны обмениваться / кэшировать данные, а также вся доступная память (так что вы не разбиваетесь на контексты внутри одной JVM), статический - лучший выбор

-> Конечно, вы можете вызвать только один экземпляр, но почему?
я нахожу некоторые комментарии в этой теме злыми, а не статиками;)

Вопрос о том, что "Статика - это зло", больше относится к глобальному состоянию. Подходящее время для того, чтобы переменная была статичной, если у нее никогда не было более одного состояния; Инструменты IE, которые должны быть доступны для всей среды и всегда возвращать одинаковые результаты для одних и тех же вызовов методов, никогда не являются "злыми", как статические. Что касается вашего комментария:

Я считаю, статические переменные более удобны в использовании. И я предполагаю, что они тоже эффективны

Статика - идеальный и эффективный выбор для переменных / классов, которые никогда не меняются.

Проблема с глобальным состоянием - это внутреннее несоответствие, которое оно может создать. Документация о модульных тестах часто решает эту проблему, так как в любое время, когда существует глобальное состояние, к которому могут обращаться более чем несколько несвязанных объектов, ваши модульные тесты будут неполными, а не "модульными". Как упоминалось в этой статье о глобальном состоянии и синглетонах, если объект A и B не связаны (так как в одном явно не указана ссылка на другой), то A не должен иметь возможности влиять на состояние B.

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

В вашем посте два основных вопроса.

Сначала о статических переменных. Статические переменные совершенно не нужны, и их легко избежать. В языке ООП в целом, и в частности в Java, параметры функции передаются по ссылке, то есть, если вы передаете объект в функцию, вы передаете указатель на объект, поэтому вам не нужно определять статические переменные, так как Вы можете передать указатель на объект в любую область, которая нуждается в этой информации. Даже если это подразумевает, что yo заполнит вашу память указателями, это не обязательно будет означать низкую производительность, потому что реальные системы разбивки памяти оптимизированы для работы с этим, и они будут сохранять в памяти страницы, на которые ссылаются указатели, которые вы передали новому. объем; использование статических переменных может привести к тому, что система загрузит страницу памяти, где они хранятся, когда к ним необходимо получить доступ (это произойдет, если страница не была принята в течение длительного времени). Хорошей практикой является объединение всех этих статических компонентов в несколько маленьких "пунктов конфигурации", это гарантирует, что система поместит все это на одну и ту же страницу памяти.

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

В заключение: избегайте использования статических переменных и находите правильное равновесие производительности при работе со статическими или нестатическими методами.

PS: извините за мой английский.

Мои $.02 в том, что некоторые из этих ответов сбивают с толку проблему, вместо того, чтобы сказать "статика плохая", я думаю, что лучше говорить о масштабах и случаях.

Я бы сказал, что статические переменные - это "классовые" переменные - они представляют значение, которое является общим для всех экземпляров этого класса. Как правило, он должен быть ограничен таким же образом (защищенным или закрытым для класса и его экземпляров).

Если вы планируете использовать поведение на уровне класса и предоставлять его другому коду, то единственное приложение может быть лучшим решением для поддержки изменений в будущем (как предложила @Jessica). Это потому, что вы можете использовать интерфейсы на уровне экземпляра / синглтона способами, которые вы не можете использовать на уровне класса - в частности наследование.

Некоторые мысли о том, почему я думаю, что некоторые аспекты других ответов не являются ключевыми для вопроса...

Статика не "глобальная". В Java область видимости контролируется отдельно от static/instance.

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

Управление жизненным циклом - интересный аргумент, но я думаю, что он менее важен. Я не понимаю, почему так сложно управлять парой методов класса, таких как init()/clear(), чем создание и уничтожение экземпляра синглтона. На самом деле, некоторые могут сказать, что синглтон немного сложнее из-за GC.

PS В терминах Smalltalk многие из его диалектов имеют переменные класса, но в Smalltalk классы на самом деле являются экземплярами Metaclass, поэтому они действительно являются переменными в экземпляре Metaclass. Тем не менее, я бы применил то же правило. Если они используются для общего состояния между экземплярами, тогда хорошо. Если они поддерживают публичную функциональность, вам стоит взглянуть на Singleton. Вздох, я действительно скучаю по Smalltalk....

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

а) Причина о программах.

Если у вас есть программа для малого и среднего размера, в которой есть доступ к статической переменной Global.foo, вызов к ней обычно происходит из ниоткуда - нет пути и, следовательно, нет временной шкалы, как переменная попадает в то место, где она используется. Теперь, как я узнаю, кто установил его действительное значение? Как я узнаю, что произойдет, если я изменю это прямо сейчас? У меня есть grep для всего источника, чтобы собрать все доступы, чтобы узнать, что происходит.

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

б) Вам действительно нужен только один?

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

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

Я много играл со статикой, и могу ли я дать вам немного другой ответ - или, может быть, немного другой взгляд на это?

Когда я использовал статику в классе (как члены, так и методы), я, в конце концов, начал замечать, что мой класс на самом деле состоит из двух классов, разделяющих ответственность - есть "Статическая" часть, которая действует как синглтон, и есть не -статическая часть (нормальный класс). Насколько я знаю, вы всегда можете полностью разделить эти два класса, просто выбрав все статики для одного класса и нестатики для другого.

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

Теперь давайте немного реорганизуем проблему: если вы разделите свой класс на один класс, где все статично, и другой, который является просто "нормальным классом", и забудьте о "нормальном классе", тогда ваш вопрос станет чисто статическим классом против синглтона, подробно рассматривается здесь (и, вероятно, с дюжиной других вопросов).

Здесь есть много хороших ответов, добавляя к этому,

Память: Статические переменные живут до тех пор, пока загрузчик классов живет [в общем, до тех пор, пока не умрет VM], но это только в случае массовых объектов / ссылок, хранящихся как статические.

Модуляризация: рассмотрите такие понятия, как IOC, dependencyInjection, прокси и т. Д. Все они полностью против тесно связанных / статических реализаций.

Прочие доводы против: безопасность потоков, тестируемость

Статические поля де-факто являются корнями сборщика мусора (см. Раздел «Как работает сборка мусора» ранее в этой главе), что означает, что они никогда не собираются сборщиком мусора! Только для удобства статические поля и коллекции часто используются для хранения кешей или совместного использования состояния между потоками. Изменяемые статические поля необходимо очистить явно. Если разработчик не учтет все возможности (почти наверняка), очистка не состоится, что приведет к утечке памяти. Такое неосторожное программирование означает, что статические поля и коллекции стали наиболее частой причиной утечек памяти!

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

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

Я думаю, что чрезмерное использование глобальных переменных со статическим ключевым словом также приведет к утечке памяти в какой-то момент в приложении

Другие вопросы по тегам