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

635

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

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

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

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

Вамси Эмани
источник
43
Просто ради того, чтобы сказать, что на Smalltalk или Scala нет статических переменных или методов, именно потому, что статические методы и переменные противоречат принципам ООП.
Маурисио Линьярес
87
По крайней мере одно высказывание, которое вы делаете, довольно любопытно: «статика уменьшает взаимозависимости в других частях кода». В целом они ужесточают зависимости. Код, по которому выполняется вызов, очень тесно связан с вызываемым кодом. Нет абстракции между, прямая зависимость.
Арне Дойч
11
Хороший вопрос ... больше программистов. Вопросы То?
WernerCD
26
Ваш второй абзац посвящен совершенно другой теме, а именно статическим методам .
Пол
8
Функциональное программирование также сводит на нет глобальное состояние. Если вы когда-нибудь (и вы должны ) попасть в FP однажды, будьте готовы отказаться от понятия глобального состояния.
new123456

Ответы:

689

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

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

Джон Скит
источник
57
В последнее время это кажется аргументом, независимо от того, тестируется код или нет. Это довольно ошибочное рассуждение. Аргумент должен быть «хороший дизайн», и обычно хороший дизайн поддается тестированию. Но не наоборот: «Я не могу проверить это, потому что это плохой дизайн». Не поймите меня неправильно, я согласен с вашим постом в целом.
М Платвоет
144
@M Platvoet: Я бы сказал, что при выборе между двумя одинаково действительными дизайнами, тестируемый лучше. Быть тестируемым, конечно, не значит быть хорошо спроектированным, но я редко сталкиваюсь с непроверяемыми хорошими проектами, и я думаю, что они достаточно редки, так что у меня нет проблем с тем, чтобы сделать тестируемость универсальным показателем, способствующим хорошему дизайну.
Джон Скит
9
@M Platvoet - Тестируемость влияет как на ремонтопригодность, так и на надежность, и я бы рассмотрел эти основные факторы качества дизайна. Они, конечно, не единственные факторы, но ИМХО стоимость любого данного кода - это сочетание машинных циклов, циклов разработки и пользовательских циклов. Тестируемость поражает двух из этих трех.
Джастин Морган
5
@M Platvoet - Тестируемость также влияет на возможность многократного использования, поскольку разъединенный класс обычно проще использовать повторно.
TrueWill
13
М Платвоет - я не согласен с вашим первым комментарием здесь. Я думаю, что если что-то не может быть проверено, то это плохой дизайн; потому что, если я не могу проверить это, я не могу знать, что это работает. Купите ли вы автомобиль, если продавец скажет вам: «Дизайн этой модели не позволяет ее протестировать, поэтому я не знаю, действительно ли она работает»? Тестируемость настолько важна для программного обеспечения (как и для автомобилей), что компетентный дизайн ТРЕБУЕТ, что он будет включен.
Дауд ибн Карим
277

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

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

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

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

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

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

Джессика Браун
источник
11
Мне нравится ваш ответ, я думаю, что он сосредоточен на правильных компромиссах, которые следует учитывать применительно к статике, а не к каким-то «красным селедкам», таким как параллелизм и объем. И +1 для синглетонов, лучший вопрос, действительно, мог бы быть, когда использовать статические переменные / методы против синглетонов ...
studgeek
2
Даже если сам синглтон может быть поточно-ориентированным (например, с помощью synchronizedметодов), это не означает, что в вызывающем коде нет условий гонки по отношению к синглтонному состоянию.
Андре Карон
8
Кроме того, статика не против парадигмы ООП. Многие фанатики ООП скажут вам, что класс - это объект, а статический метод - это метод объекта класса, а не его экземпляры. Это явление менее присутствует в Java. Другие языки, такие как Python, позволяют использовать классы в качестве переменных, а статические методы можно использовать как методы этого объекта.
Андре Карон
4
Последняя строка третьего абзаца должна читать все ваши нестатические переменные , если я не ошибаюсь.
Стив
1
Object Lifetime, это один очень важный момент, который упоминал @jessica.
Абидемон
93

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

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

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

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

Прит Сангха
источник
59

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

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

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

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

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

irreputable
источник
10
«У меня есть две причины рассуждать». Нет, если я сделаю свой тест зависимым только от состояния объекта. Чем проще, тем менее глобальное состояние у меня.
DJClayworth
2
Внедрение зависимостей не имеет ничего общего с глобальным состоянием или глобальной видимостью, даже сам контейнер не является глобальным. По сравнению с «нормальным» кодом, единственная дополнительная вещь, которой виден объект, управляемый контейнером, - это сам контейнер. На самом деле, DI очень часто используется, чтобы избежать Singleton Pattern.
Флёгипоки
31

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

  • Потокобезопасность - статические ресурсы по определению не являются потокобезопасными
  • Непрозрачность кода - вы не знаете, когда создается экземпляр статической переменной и будет ли он создан перед другой статической переменной
sternr
источник
Я думаю, что Джон Скит ссылался на тот же комментарий, который вы опубликовали.
RG-3
13
Я не получаю точку безопасности потока, я думаю, что ничто не является потокобезопасным, если вы не сделаете это так. Похоже, это вообще не связано со статическими вещами, поправьте меня, если я что-то упустил.
Zmaster
1
@Zmaster - Хотя верно, что потокобезопасность не является проблемой, исключающей статические переменные, потому что по их определению они должны вызываться из разных контекстов, они более урезаны для них
sternr
2
@sternr Я понимаю, что вы имеете в виду, если событие "разные контексты" не обязательно равно "различным потокам". Но это правда, что безопасность потоков часто нужно учитывать при использовании статических ресурсов. Вы должны рассмотреть вопрос о прояснении предложения.
Zmaster
Например, существуют допустимые потоки с безопасным использованием статических ресурсов. приватная статическая финальная Logger LOG = Logger.getLogger (Foo.class); приватная статическая финальная AtomicInteger x = new AtomicInteger (0); Насколько я понимаю, статические назначения таких ресурсов гарантируют многопоточность загрузчиком классов. Экземпляр Logger является или не является потокобезопасным независимо от того, где вы назначаете ему указатель. Сохранение состояния в статике, вероятно, не очень хорошая идея, но нет никаких причин, по которым оно не должно быть поточно-ориентированным.
Текнопауль
29

Если вы используете ключевое слово «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, хотя существуют технические различия в реализации. AC / C ++ #define выгружается из кода препроцессором перед компиляцией. Java «статический финал» в конечном итоге останется в стеке. Таким образом, она больше похожа на переменную «static const» в C ++, чем на #define.

Резюме

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

JBCP
источник
15

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

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

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

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

Джастин М. Кейс
источник
5
Переменные экземпляра не имеют преимуществ безопасности потоков по сравнению со статикой, все они являются незащищенными переменными. Вместо этого все сводится к тому, как вы защищаете код, который обращается к этим переменным.
Studgeek
2
Я не совсем это утверждал, но ради обсуждения: разделение - это форма защиты. Состояния потоков разделены; глобального состояния нет . Переменная экземпляра не нуждается в защите, если она явно не разделена между потоками; статическая переменная всегда используется всеми потоками процесса.
Джастин М. Киз
Хотелось бы, чтобы переменные-статические переменные были скорее первоклассной концепцией, поскольку они могут быть очень полезны для безопасного предоставления информации, доступной для вызова встроенной подпрограммы, без необходимости передавать эту информацию через каждый уровень упаковки. Например, если у объекта есть методы для его визуализации в текущем графическом контексте потока, и существуют методы для сохранения / восстановления текущего графического контекста, их использование часто может быть чище, чем необходимость передавать графический контекст через каждый вызов метода.
суперкат
15

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

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

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

Недостатки:

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

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

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

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

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

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

Жером Верстринг
источник
13

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

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

Джек Эдмондс
источник
4
«Статические переменные представляют состояние между классами» ... Я думаю, вы имеете в виду «статические переменные представляют состояние между экземплярами»? +1 за "окончательные статические константы АКА, не так уж плохо". Поскольку значение не может измениться, все, что зависит от него в определенный момент времени, не может неявно изменить его поведение позднее - значение остается тем же.
Джаред Апдайк
«Статические переменные представляют состояние в разных экземплярах» - гораздо лучший способ заявить об этом. Я отредактировал свой ответ.
Джек Эдмондс
9

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

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

М Платвоет
источник
3
Чистые побочные эффекты без статических методов идеально подходят ИМО. Но глобальное изменчивое состояние редко встречается, и я интерпретирую ОП как говорящий о глобальном состоянии.
CodesInChaos
1
@CodeInChaos полностью согласен. Я считаю, что OP не совсем ясно о разнице между статическими методами и переменными.
М Платвоет
8

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

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

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

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

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

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

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

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

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

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

akhil_mittal
источник
7

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

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

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

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

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

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

cetnar
источник
6

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

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

Ануй Патель
источник
6

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

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

Чарльз Гудвин
источник
5

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

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

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

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

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

ptyx
источник
4

Я считаю, статические переменные более удобны в использовании. И я предполагаю, что они тоже эффективны (пожалуйста, исправьте меня, если я ошибаюсь), потому что, если бы мне пришлось сделать 10000 вызовов функции внутри класса, я был бы рад сделать метод статическим и использовать простой 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)...

}
Cygnusx1
источник
Классический синглтон (то есть тот, к которому обращается Class.Instance) чуть лучше, чем статическая переменная. Это немного более тестируемо, но все же намного хуже, чем проекты, в которых вы просто создаете один экземпляр, а не строите свой код, исходя из предположения, что есть только один.
CodesInChaos
Не уверен, что понимаю ваш комментарий! я отвечал на ФП о том, что он выделил курсивом на создание 10 000 объектов. Я не понимаю, почему вы сравниваете синглтон и статическую переменную? Из того, что вы написали, я понимаю, что синглтон плохой дизайн ...! Думаю, я вас неправильно понял, так как Spring Framework по умолчанию делает все компоненты Singleton ;-)
Cygnusx1
Классический синглтон (который имеет Class.Instance), который несет изменяемое состояние, является плохим дизайном IMO. В этом случае я настоятельно предпочитаю проект, в котором я получаю синглтоны, которые мне нужно использовать, передаваемые в качестве параметра в класс, который их использует (обычно с помощью DI). Логически неизменные классические синглтоны - это хорошо, IMO.
CodesInChaos
@ Cygnusx1 В случае, если неясно, почему синглтон Class (синглтон, в котором класс обеспечивает единственную копию) не был легко тестируемым, он тесно связывает существование класса с жизненным циклом программы. Чтобы проверить это, вы должны придерживаться запуска и завершения программы, что часто имеет побочные эффекты, неважные для тестирования класса. Если бы он был фактически единичным (одна копия в программе, но без принудительного применения), вы могли бы создать несколько копий во время тестирования без программы, проверяя, что поведение в классе является «как должно быть» для каждого сценария тестирования.
Эдвин Бак
4

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

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

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

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

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

Брайан Эйджи
источник
«Время глобально» - есть и другие способы моделирования времени в вычислительных системах, кроме того, чтобы оно было неявным, глобальным, которое меняется само по себе. ср этот опрос: «Моделирование времени в вычислительной технике: таксономия и сравнительное исследование» @ arxiv.org/abs/0807.4132
Джаред Апдайк,
4

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

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

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

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

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

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

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

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

studgeek
источник
4

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

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

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

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

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

jrodriguez
источник
4

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

Зденек Павлас
источник
3

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

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

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

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

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

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

Пользователь неизвестен
источник
3

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

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

tomasb
источник
3

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

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

Andres
источник
2

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

болван
источник
1
Но делает ли строгое рассмотрение вашего кода следование какой-то произвольной стандартной парадигме на самом деле, делает код лучше или мы жалуемся, чтобы не писать просто работающий код?
Зои
Да, это делает его лучше, потому что это делает его более управляемым в будущем, более легким для понимания и более явным.
болван
1
Почему плохо не писать ОО-код? и почему Бьярн Страуструп не соглашается с тобой? назвать только одного ...
Маркиз Лорн
2
Я не сказал, что это зло - не писать ОО-код. Я сказал, что плохо думать, что вы пишете ОО-код, когда все, что вы делаете, маскирует глобальные переменные за статическими методами и свойствами. Пожалуйста, перечитайте то, что я написал.
болван
2

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

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

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

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

Санкарганеш Ишваран
источник
0

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

seinta
источник
0

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

Рахул Ананд
источник
0

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

Например, у нас есть пользовательский интерфейс какого-либо проекта, у нас есть список стран, языков, ролей пользователей и т. Д. И у нас есть класс для организации этих данных. мы абсолютно уверены, что приложение не будет работать без этих списков. поэтому первое, что мы делаем в приложении init, это проверка этого списка на наличие обновлений и получение этого списка из API (при необходимости). Поэтому мы согласны с тем, что эти данные «всегда» присутствуют в приложении. Это практически данные только для чтения, поэтому нам не нужно заботиться о их состоянии - думая об этом случае, мы действительно не хотим иметь много экземпляров этих данных - этот случай выглядит идеальным кандидатом на статичность .

qiAlex
источник
0

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

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

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

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

Билл К
источник