Чому статичні змінні вважаються злими?


635

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

Я вважаю статичні змінні зручнішими для використання. І я припускаю, що вони теж ефективні (будь-ласка, виправте мене, якщо я помиляюся), тому що якби мені довелося робити 10 000 дзвінків до функції в межах класу, я був би радий зробити метод статичним і використовувати його прямо, Class.methodCall()а не просто захаращуючи пам'ять 10 000 екземплярів класу, правда?

Більше того, статика зменшує взаємозалежності від інших частин коду. Вони можуть виступати як досконалі державці. Додаючи до цього, я вважаю, що статистика широко застосовується в деяких мовах, таких як Smalltalk та Scala . То чому цей пригнічення для статики є поширеним серед програмістів (особливо в світі Яви)?

PS: Будь ласка, виправте мене, якщо мої припущення щодо статики помилкові.


43
Справедливо кажучи, на Smalltalk або Scala немає статичних змінних чи методів, саме тому, що статичні методи та змінні суперечать принципам OOP.
Маурісіо Лінхарес

87
Принаймні одне зроблене вами твердження є досить цікавим: "статика зменшує взаємозалежності від інших частин коду". Загалом вони посилюють залежності. Код, за яким здійснюється дзвінок, дуже щільно пов'язаний з кодом, що викликається. Ніякої абстракції між, пряма залежність.
Arne Deutsch

11
Хороше запитання ... Більше про програмістів.
WernerCD

26
Ваш другий абзац стосується зовсім іншого предмета, а саме статичних методів .
Павло

8
Функціональне програмування також насувається на глобальний стан. Якщо ви коли-небудь (і вам слід ) потрапити в ПП одного дня, будьте готові викинути поняття глобальної держави.
new123456

Відповіді:


689

Статичні змінні представляють глобальний стан. Це важко міркувати і важко перевірити: якщо я створю новий екземпляр об'єкта, я можу розмірковувати про його новий стан у межах тестів. Якщо я використовую код, який використовує статичні змінні, він може бути в будь-якому стані - і все, що може змінити його.

Я міг би тривати довгий час, але чим більше думати про те, що чим жорсткіше сфера дії, тим простіше міркувати. Ми добре роздумуємо над дрібними речами, але важко міркувати про стан системи мільйонів ліній, якщо немає модульності. Це стосується всіляких речей, до речі - не лише статичних змінних.


57
Це останнім часом здається аргументом, чи перевіряється код чи ні. Це досить хибні міркування. Аргументом має бути «хороший дизайн», і зазвичай хороший дизайн є перевіреним. Але не навпаки: "Я не можу перевірити це на це, це повинен бути поганий дизайн". Не зрозумійте мене неправильно, я згоден з вашим дописом загалом.
M Platvoet

144
@M Platvoet: Я б сказав, що якщо вибирати між двома інакше однаково допустимими конструкціями, тестовий варіант є кращим. Бути перевіреним, звичайно, не означає, що він добре розроблений, але я рідко стикаюся з непереборними хорошими конструкціями, і я думаю, що вони є досить рідкісними, що у мене немає проблем із тим, щоб перевірити загальну мету, що сприяє хорошому дизайну.
Джон Скіт

9
@M Platvoet - Заповідність впливає як на ремонтопридатність, так і на надійність, і я вважаю ці основні фактори якості дизайну. Вони, безумовно, не єдині фактори, але IMHO вартість будь-якого даного коду - це поєднання машинних циклів, циклів розробника та циклів користувачів. Заповітність вражає два з цих трьох.
Джастін Морган

5
@M Platvoet - Тестабільність також має тенденцію впливати на повторне використання, оскільки роздільний клас, як правило, простіше використовувати.
TrueWill

13
M Platvoet - Я не згоден з вашим першим коментарем тут. Я думаю, якщо щось неможливо перевірити, то це поганий дизайн; тому що якщо я не можу перевірити його, я не можу знати, що він працює. Ви б придбали машину, якби продавець сказав вам: "Конструкція цієї моделі не дозволяє її перевірити, тому я не знаю, чи справді вона працює"? Тестабельність настільки важлива для програмного забезпечення (як і для автомобілів), що грамотний дизайн вимагає, щоб він був включений.
Dawood ibn Kareem

277

Її не дуже об’єктно-орієнтована: одна з причин статики деякими людьми може вважатися «злою» - це те, що вона суперечить об'єктно-орієнтованій парадигмі . Зокрема, це порушує принцип того, що дані інкапсулюються в об'єкти (які можуть бути розширені, приховування інформації тощо). Статистика, як ви описуєте їх використання, по суті полягає в тому, щоб використовувати їх як глобальну змінну, щоб уникнути вирішення таких питань, як сфера застосування. Однак глобальні змінні є однією з визначальних характеристик парадигми програмного чи імперативного програмування, а не характеристикою "хорошого" об'єктно-орієнтованого коду. Це не означає, що процедурна парадигма погана, але я складаю враження, що ваш керівник очікує, що ви пишете "хороший об'єктно-орієнтований код", і ви справді хочете написати "

На Яві є багато готчей, коли ви починаєте використовувати статику, яка не завжди є очевидною. Наприклад, якщо у вас є дві копії вашої програми, що працюють в одній віртушній машині, чи вони будуть порушувати значення статичної змінної і псуватися зі станом один одного? Або що станеться, коли ви розширите клас, чи можете ви перемогти статичний член? У вашого VM не вистачає пам'яті, оскільки у вас є божевільна кількість статики, і ця пам'ять не може бути відновлена ​​для інших необхідних об'єктів екземпляра?

Термін експлуатації об'єкта: Крім того, статики мають тривалість життя, що відповідає всьому часу виконання програми. Це означає, що навіть після того, як ви закінчите роботу з класом, пам'ять з усіх цих статичних змінних неможливо зібрати сміття. Наприклад, якщо ви замість цього зробили свої змінні нестатичними, а в своїй головній () функції ви зробили один екземпляр свого класу, а потім попросили ваш клас виконати певну функцію 10000 разів, як тільки ці 10 000 викликів були виконані , і ви видаляєте свої посилання на єдиний екземпляр, всі ваші статичні змінні можуть бути зібрані та повторно використані.

Не допускає певного повторного використання: Також статичні методи не можна використовувати для реалізації інтерфейсу, тому статичні методи можуть запобігти застосуванню певних об'єктно-орієнтованих функцій.

Інші параметри: Якщо ефективність є вашою основною проблемою, можуть бути інші кращі способи вирішити проблему швидкості, ніж врахувати лише те, що перевагу виклику зазвичай швидше, ніж створення. Поміркуйте, чи потрібні дещо перехідні або мінливі модифікатори. Щоб зберегти здатність до накреслення, метод може бути позначений як остаточний замість статичного. Параметри методу та інші змінні можуть бути позначені остаточними, щоб дозволити певні оптимізації компілятора на основі припущень щодо того, що може змінити ці змінні. Об'єкт екземпляра можна повторно використовувати кілька разів, а не створювати новий екземпляр кожен раз. Можливо, існують перемикачі оптимізації компілятора, які слід увімкнути для додатка взагалі. Можливо, дизайн повинен бути розроблений таким чином, щоб 10000 пробіг могли бути багатопотоковими та використовувати переваги багатопроцесорних ядер. Якщо портативність isn '

Якщо з якихось причин ви не хочете декількох копій об'єкта, схему дизайну однотонних, має переваги перед статичними об'єктами, такими як безпека потоків (припускаючи, що ваш одиночний код добре закодований), що дозволяє ліні-ініціалізації, гарантування належного ініціалізації об'єкта при його використанні, підкласифікації, переваг тестування та рефакторингу коду, не кажучи вже про те, якщо ви в якийсь момент передумаєте про те, що хочете отримати лише один екземпляр об'єкта, набагато простіше видалити код для запобігання повторюваних екземплярів, ніж перефактурувати весь ваш статичний код змінної для використання змінних екземплярів. Мені довелося це робити раніше, це не цікаво, і вам, нарешті, доведеться редагувати набагато більше класів, що збільшує ризик введення нових помилок ... набагато краще, щоб налаштувати речі "правильно" з першого разу, навіть якщо здається, що у нього є свої недоліки. Для мене, необхідна повторна робота, якщо ви вирішите вниз по дорозі, вам потрібні кілька копій чогось, мабуть, одна з найбільш переконливих причин використовувати статику якомога рідше. І, таким чином, я також не погоджуюся з вашим твердженням про те, що статика зменшує взаємозалежності, я думаю, ви закінчите кодом, який є більш сполученим, якщо у вас є багато статики, до якої можна отримати прямий доступ, а не об'єкт, який "знає, як робити щось "на собі".


11
Мені подобається ваша відповідь, я думаю, що вона зосереджена на правильних компромісах, щоб врахувати статику, а не деякі червоні оселедці, як паралельність і розмах. І +1 для одиночних, справді краще питання, коли використовувати статичні змінні / методи проти одиночних ...
studgeek

2
Навіть незважаючи на те, що сам сингл може бути безпечним для потоків (наприклад, за допомогою synchronizedметодів), це не означає, що викликовий код не містить перегонових умов щодо стану одиночного.
Андре Карон

8
Також статика не проти парадигми ООП. Багато фанатиків OOP скажуть вам, що клас є об'єктом, а статичний метод - це метод об'єкта класу, а не екземпляри. Це явище рідше присутнє на Яві. Інші мови, такі як Python, дозволяють використовувати класи як змінні, і ви можете отримувати доступ до статичних методів як методів цього об'єкта.
Андре Карон

4
Останній рядок третього абзацу повинен читати всі ваші нестатичні змінні , якщо я не помиляюся.
Стів

1
Object Lifetime, це один дуже важливий момент, про який згадував @jessica.
Абхідемон

93

Зло - це суб'єктивний термін.

Ви не контролюєте статику щодо створення та знищення. Вони живуть за бажанням завантаження та вивантаження програми.

Оскільки статика живе в одному просторі, всі потоки, які бажають їх використовувати, повинні пройти контроль доступу, яким ви повинні керувати. Це означає, що програми є більш сполученими, і цю зміну важче передбачити та керувати (як каже J Skeet). Це призводить до проблем виділення впливу змін і, таким чином, впливає на те, як керується тестуванням.

Це два основних питання, які у мене є з ними.


59

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

Глобальні держави - абсолютна необхідність. Ми не можемо уникнути глобальних держав. Ми не можемо уникнути міркувань про глобальні держави. - Якщо нам важливо зрозуміти нашу семантику додатків.

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

Як і люди весни, які пишно декларують глобальні держави в xml і думають, що це якось вище.

@ Jon Skeet if I create a new instance of an objectтепер у вас є два причини - стан усередині об'єкта та стан середовища, що розміщує об'єкт.


10
"У мене є два моменти на те". Не, якщо я роблю свій тест лише залежним від стану об'єкта. Що простіше, тим менш глобальний стан у мене.
DJClayworth

2
Введення залежності не має нічого спільного з глобальним станом або глобальною видимістю - навіть сам контейнер не є глобальним. Порівняно з "звичайним" кодом, єдине, що бачить об'єкт, керований контейнером, - це сам контейнер. Насправді, ДІ дуже часто використовується, щоб уникнути єдиного малюнка.
Floegipoky

31

Є дві основні проблеми зі статичними змінними:

  • Безпека нитки - статичні ресурси за визначенням не є безпечними для потоків
  • Неявність коду - Ви не знаєте, коли статичні змінні інстанціюються і чи буде вона інстанціюватися перед іншою статичною змінною

Я думаю, що Джон Скіт переглядав той самий коментар, який ви розмістили.
RG-3

13
У мене немає точки безпеки теми, я думаю, що нічого не є безпечним для ниток, якщо ви не зробите це так. Здається, це взагалі не пов’язано зі статичними речами, будь ласка, виправте мене, якщо я щось пропускаю.
Zmaster

1
@ Zmaster - Хоча це правда, що безпека потоків не є винятком лише для статичних змінних, оскільки за їх визначенням вони повинні викликатись з різних контекстів, вони є більш
прирізними

2
@sternr Я розумію, що ви маєте на увазі, подія, якщо "різні контексти" не обов'язково дорівнюють "різним потокам". Але це правда, що безпеку ниток потрібно часто враховувати зі статичними ресурсами. Вам слід розглянути уточнення речення.
Zmaster

Наприклад, є дійсними потокові безпечні способи використання статичних ресурсів, наприклад. приватний статичний кінцевий Logger LOG = Logger.getLogger (Foo.class); приватний статичний кінцевий AtomicInteger x = новий AtomicInteger (0); Як я розумію, статичний розподіл таких ресурсів гарантується завантаженням класів завантаженням. Екземпляр Logger є безпечним для потоків або не є незалежним від того, де ви призначите йому вказівник. Зберігати стан у статиці, мабуть, не є хорошою ідеєю, але немає причин, щоб воно не було безпечним для потоків.
текнопаул

29

Якщо ви використовуєте "статичне" ключове слово без "остаточного" ключового слова, це повинно бути сигналом для ретельного розгляду вашого дизайну. Навіть наявність "остаточного" не є вільним пропуском, оскільки змінений статичний кінцевий об'єкт може бути настільки ж небезпечним.

Я б оцінив десь у 85% часу, коли я бачу "статику" без "остаточного", це WRONG. Часто мені трапляються дивні способи вирішення проблем, щоб замаскувати або приховати ці проблеми.

Будь ласка, не створюйте статичні мутабелі. Особливо колекції. Загалом, колекції повинні бути ініціалізовані, коли об'єкт, що містить вміст, ініціалізовані та розроблені таким чином, щоб вони були скинуті або забуті про те, коли забутий їх об'єкт забутий.

Використання статики може створити дуже тонкі помилки, що спричинить тривалий день інженерів. Я знаю, бо я одночасно створював і полював на цих клопів.

Якщо ви хочете отримати більше деталей, будь ласка, читайте далі ...

Чому б не використовувати статику?

Існує багато проблем зі статикою, включаючи написання та виконання тестів, а також тонких помилок, які не відразу очевидні.

Код, який спирається на статичні об'єкти, не може бути легко перевірений одиницею, і статику не можна легко глузувати (як правило).

Якщо ви використовуєте статику, неможливо замінити реалізацію класу, щоб перевірити компоненти більш високого рівня. Наприклад, уявіть статичний CustomerDAO, який повертає клієнтські об'єкти, які він завантажує з бази даних. Тепер у мене є клас CustomerFilter, який потребує доступу до деяких об'єктів клієнта. Якщо CustomerDAO статичний, я не можу написати тест для CustomerFilter, попередньо не ініціалізуючи свою базу даних і не заповнивши корисну інформацію.

І популяція бази даних, і ініціалізація займають багато часу. На мій досвід, ваша ініціалізація бази даних з часом зміниться, тобто дані перетворяться, і тести можуть зламатися. IE, уявіть, що Customer 1 раніше був VIP, але структура ініціалізації БД змінилася, і тепер Замовник 1 вже не є VIP, але ваш тест був жорстко кодований для завантаження клієнта 1…

Кращим підходом є створення екземпляра CustomerDAO та передавання його у CustomerFilter під час його створення. (Ще кращим підходом було б використання весни чи іншої структури інверсії управління.

Після цього ви зможете швидко знущатись або заглушувати альтернативний DAO у своєму CustomerFilterTest, що дозволяє вам більше контролювати тест,

Без статичного DAO тест буде швидшим (без ініціалізації db) та надійнішим (оскільки він не вийде з ладу при зміні коду ініціалізації db). Наприклад, у цьому випадку забезпечення Замовника 1 є та завжди буде VIP, що стосується тесту.

Виконання тестів

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

Це надто узагальнений приклад, але у великих системах ця проблема трапляється ВСЕ ЧАС. Люди не замислюються над тестуванням одиниць, які запускають та зупиняють своє програмне забезпечення неодноразово в одному JVM, але це хороший тест вашої розробки програмного забезпечення, і якщо у вас є прагнення до високої доступності, це вам потрібно знати.

Ці проблеми часто виникають із рамними об'єктами, наприклад, вашими шарами доступу до БД, кешування, обміну повідомленнями та ведення журналів. Якщо ви використовуєте Java EE або якісь найкращі рамки породи, вони, ймовірно, керують цим для вас багато, але, як і я, ви маєте справу зі спадковою системою, у вас може бути багато спеціальних рамок для доступу до цих шарів.

Якщо конфігурація системи, що застосовується до цих компонентів рамки, змінюється між тестовими одиницями, і рамка тестового блоку не руйнує та не відновлює компоненти, ці зміни не можуть набути чинності, і коли тест покладається на ці зміни, вони не зможуть .

Навіть не рамкові компоненти є предметом цієї проблеми. Уявіть статичну карту під назвою OpenOrders. Ви пишете один тест, який створює кілька відкритих замовлень, і перевіряє, чи всі вони в правильному стані, потім тест закінчується. Інший розробник пише другий тест, який вносить необхідні замовлення на карту OpenOrders, а потім стверджує, що кількість замовлень є точною. Запускатись індивідуально, ці тести обидва проходили б, але якщо працювати разом у наборі, вони не зможуть.

Гірше, помилка може базуватися на порядку, в якому проводились тести.

У цьому випадку, уникаючи статики, ви уникаєте ризику збереження даних у тестових випадках, забезпечуючи кращу надійність тесту.

Тонкі помилки

Якщо ви працюєте в середовищі з високою доступністю або в будь-якому іншому місці, де потоки можуть бути запущені та зупинені, те саме, що було зазначено вище, для тестових наборів модулів може застосовуватися і тоді, коли ваш код працює на виробництві.

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

Коли нитка відмирає, статичний об'єкт не буде скинутий або зібраний сміття. Уявіть, що у вас є нитка під назвою "EmailCustomers", і коли вона запускається, вона заповнює статичну колекцію String зі списком адрес електронної пошти, а потім починає надсилати електронну пошту кожної з адрес. Скажімо, нитка перервана або скасована якось, тому ваша рамка високої доступності перезавантажує потік. Потім, коли нитка запускається, вона перезавантажує список клієнтів. Але оскільки колекція статична, вона може зберігати список адрес електронної пошти з попередньої колекції. Тепер деякі клієнти можуть отримати копії електронних листів.

Вбік: Статичний фінал

Використання "статичного остаточного" є фактично Java-еквівалентом C #define, хоча є технічні відмінності в реалізації. AC / C ++ #define вимикається з коду попередньо процесором перед компіляцією. Java "статичний фінал" в кінцевому підсумку залишатиметься резидентом пам'яті. Таким чином, він більше схожий на змінну "статичний const" в C ++, ніж на #define.

Підсумок

Я сподіваюся, що це допоможе пояснити декілька основних причин, чому статистика є проблематичною. Якщо ви використовуєте сучасну систему Java, на зразок Java EE або Spring тощо, можливо, ви не стикаєтесь із багатьма з цих ситуацій, але якщо ви працюєте з великим кодом застарілого коду, вони можуть стати набагато частішими.


15

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

статика зменшує взаємозалежності від інших частин коду. Вони можуть виступати як досконалі державці

Сподіваюся, ви готові використовувати замки та вирішувати суперечки.

* Власне, Преет Сангга згадував про це.


5
Змінні екземпляра не мають переваг щодо безпеки потоку перед статичними, вони всі незахищені змінні. Натомість все зводиться до того, як ви захищаєте код, який отримує доступ до цих змінних.
studgeek

2
Я не дуже висловив це твердження, але заради обговорення: розлука - це форма захисту. Стани нитки розділені; глобальна держава не є . Змінна примірника не потребує захисту, якщо вона явно не поділяється між потоками; статична змінна завжди поділяється всіма потоками в процесі.
Джастін М. Кіз

Я хочу, щоб потокові статичні змінні більше були першокласним поняттям, оскільки вони можуть бути дуже корисними для безпечного надання інформації, доступної для завершеного виклику підпрограми, без необхідності передавати цю інформацію через кожен шар обгортання. Наприклад, якщо об’єкт мав методи рендерингу його в поточний графічний контекст потоку, і існували методи збереження / відновлення поточного графічного контексту, використовуючи ці, часто можна бути більш чистими, ніж необхідність передавати графічний контекст через кожен виклик методу.
supercat

15

Узагальнення декількох основних переваг та недоліків використання статичних методів на Java:

Переваги:

  1. Глобально доступний, тобто не пов'язаний з будь-яким конкретним екземпляром об'єкта.
  2. Один примірник на JVM.
  3. Доступ можна отримати за допомогою назви класу (не потрібен жоден об'єкт).
  4. Містить єдине значення, застосовне до всіх примірників.
  5. Завантажте під час запуску JVM і помирає, коли JVM вимкнеться.
  6. Вони не змінюють стан об'єкта.

Недоліки:

  1. Статичні члени завжди є частиною пам’яті, використовуються вони чи ні.
  2. Ви не можете контролювати створення та знищення статичної змінної. Корисно вони були створені при завантаженні програми та знищені під час завантаження програми (або коли JVM вимикається).
  3. Ви можете зробити безпеку потоків статики за допомогою синхронізації, але вам потрібні додаткові зусилля.
  4. Якщо один потік змінює значення статичної змінної, що, можливо, може порушити функціональність інших потоків.
  5. Ви повинні знати "статичний" перед його використанням.
  6. Ви не можете перекрити статичні методи.
  7. Серіалізація не справляється з ними.
  8. Вони не беруть участі в поліморфізмі виконання.
  9. Існує проблема пам'яті (певною мірою, але я не дуже здогадуюсь), якщо використовується велика кількість статичних змінних / методів. Тому що вони не будуть збирати сміття до завершення програми.
  10. Статичні методи теж важко перевірити.

Недоліками 6, 7, 8 і 10 є недоліки використовуваних мов / фреймворків, а не недоліки статичних змінних загалом. Недоліки 1, 4 і 5 існують і для інших рішень, як-от одного однотонного шаблону, передбаченого деяким фреймворком. (Я не проголосував за відповідь, тому що я згоден на решту, і це приємна колекція.)
peterh - Відновіть Моніку

13

якби мені довелося робити 10 000 дзвінків до функції в класі, я був би радий зробити метод статичним і використовувати на ньому прямий class.methodCall (), а не захаращувати пам'ять 10 000 екземплярів класу, правда?

Ви повинні збалансувати необхідність інкапсуляції даних у об'єкт із станом, проти потреби простого обчислення результату функції на деяких даних.

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

Так само робиться інкапсуляція. У великих програмах статики, як правило, створюють код спагетті і не дозволяють легко переробляти чи тестувати.

Інші відповіді також дають вагомі причини проти надмірного використання статики.


13

Статичні змінні, як правило, вважаються поганими, оскільки вони представляють глобальний стан, і тому їх набагато складніше міркувати. Зокрема, вони порушують припущення об'єктно-орієнтованого програмування. У об'єктно-орієнтованому програмуванні кожен об’єкт має свій стан, представлений екземплярами (нестатичними) змінними. Статичні змінні представляють стан у всіх екземплярах, що може бути набагато складнішим для тестування одиниць. Це головним чином через те, що важче виділити зміни статичних змінних в один тест.

При цьому важливо розрізняти звичайні статичні змінні (як правило, вважаються поганими), і кінцеві статичні змінні (константи AKA; не так вже й погано).


4
"Статичні змінні представляють стан у класах" ... Я думаю, ви маєте на увазі "статичні змінні представляють стан у всіх екземплярах"? +1 для "кінцевих статичних констант AKA, не так вже й погано". Оскільки значення не може змінитися, все, що від нього залежить в один момент часу, не може неявно змінити свою поведінку в більш пізній час - значення те саме.
Jared Updike

"Статичні змінні представляють стан у всіх екземплярах" - це набагато кращий спосіб констатувати його. Я відредагував свою відповідь.
Джек Едмондс

9

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

Це просто про те, як виділити логіку і дати їй гарне місце. Іноді це виправдовує використання статичних методів, які java.lang.Mathє хорошим прикладом. Я думаю, коли ви називаєте більшість своїх класів XxxUtilабо Xxxhelperвам краще переглянути свій дизайн.


3
Статичні методи без побічних ефектів - це прекрасний ІМО. Але глобальна змінна держава рідко є, і я трактую ОП як розмову про глобальну державу.
CodesInChaos

1
@CodeInChaos повністю згоден. Я вважаю, що ОП не зовсім зрозуміло в різниці між статичними методами і варами.
M Platvoet

8

Я щойно підсумував деякі моменти, зроблені у відповідях. Якщо ви виявите щось не так, будь ласка, сміливо виправляйте це.

Масштабування: У нас рівно один екземпляр статичної змінної на JVM. Припустимо, ми розробляємо систему управління бібліотекою, і ми вирішили поставити ім’я книги статичною змінною, оскільки в книзі існує лише одна. Але якщо система зростає, і ми використовуємо декілька JVM, то у нас немає способу зрозуміти, з якою книгою ми маємо справу?

Безпека нитки: І примірник змінної, і статична змінна повинні контролюватися при використанні в багатопотоковому середовищі. Але у випадку змінної екземпляра вона не потребує захисту, якщо вона явно не поділяється між потоками, але у випадку статичної змінної вона завжди поділяється всіма потоками в процесі.

Тестування: Хоча тестова конструкція не дорівнює хорошому дизайну, але ми рідко спостерігатимемо хороший дизайн, який не піддається контролю. Оскільки статичні змінні являють собою глобальний стан, і їх дуже важко перевірити.

Обґрунтування стану: Якщо я створю новий примірник класу, ми можемо міркувати про стан цього примірника, але якщо він має статичні змінні, то він може бути в будь-якому стані. Чому? Тому що можливо, що статична змінна була модифікована деяким іншим екземпляром, оскільки статична змінна поділяється між примірниками.

Серіалізація: серіалізація також не справляється з ними.

Створення та руйнування: Створення та знищення статичних змінних неможливо контролювати. Зазвичай вони створюються та знищуються під час завантаження та розвантаження програми. Це означає, що вони погані для управління пам'яттю, а також додають час ініціалізації при запуску.

Але що робити, якщо вони нам справді потрібні?

Але іноді ми можемо мати справжню потребу в них. Якщо ми справді відчуваємо потребу в багатьох статичних змінних, які спільно використовуються в додатку, тоді одним із варіантів є використання шаблону дизайну Singleton, який матиме всі ці змінні. Або ми можемо створити якийсь об'єкт, який буде мати цю статичну змінну і може бути переданий навколо.

Крім того, якщо статична змінна позначена як остаточна, вона стає постійною і значення, присвоєне їй один раз, неможливо змінити. Це означає, що він позбавить нас від усіх проблем, з якими ми стикаємося через його незмінність.


7

Мені здається, ви запитуєте про статичні змінні, але ви також вказуєте статичні методи у своїх прикладах.

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

Статичні методи, також корисні методи. Зазвичай це не погана практика їх використання, але головна проблема полягає в тому, що вони можуть перешкоджати тестуванню.

Як приклад чудового проекту Java, який використовує багато статики та робить це правильно, будь ласка, подивіться на Play! рамки . Існує також обговорення про це в SO.

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

Тож статичні змінні (та методи) хороші, але використовуйте їх розумно!


6

Статичні змінні найголовніше створюють проблеми із захищеністю даних (будь-який час змінюється, будь-хто може змінити, прямий доступ без об’єкта тощо)

Для отримання додаткової інформації читайте цю подяку.


6

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

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


5

Ще одна причина: крихкість.

Якщо у вас є клас, більшість людей очікують, що зможете створити його та використовувати його за бажанням.

Ви можете задокументувати, що це не так, або захистити від нього (однотонний / заводський зразок) - але це додаткова робота, а отже, додаткові витрати. Навіть тоді, у великій компанії, є ймовірність, що хтось спробує в якийсь момент використати ваш клас, не повністю звертаючи увагу на всі приємні коментарі чи заводи.

Якщо ви багато використовуєте статичні змінні, це порушиться. Клопи дорогі.

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


4

Я вважаю статичні змінні зручнішими для використання. І я припускаю, що вони також ефективні (Будь ласка, виправте мене, якщо я помиляюся), тому що якби мені довелося робити 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)...

}

Класичний синглтон (тобто той, до якого отримують доступ Class.Instance) ледве краще, ніж статична величина. Це трохи більш перевірено, але все-таки набагато гірше, ніж проекти, де ви просто створили єдиний екземпляр замість того, щоб будувати свій код за припущенням, що існує лише один.
CodesInChaos

Не впевнений, я розумію ваш коментар! я відповідав ОП про те, що він курсивом заявив про примірник 10 000 об'єктів. Я не розумію, чому ти порівнюєш одиночну та статичну змінну? Що я розумію з написаного вами, це те, що Singleton - це поганий дизайн ...! Думаю, я вас неправильно розумію, оскільки Spring Framework за замовчуванням робить усі боби Singleton ;-)
Cygnusx1

Класичний синглтон (який має Class.Instance), який переносить змінне стан, - це поганий дизайн IMO. У такому випадку я дуже віддаю перевагу дизайну, де я отримую потрібні мені одиночні кнопки, передані як параметр, до класу, який їх використовує (як правило, за допомогою DI). Логічно незмінні класичні синглтони - це чудо ІМО.
CodesInChaos

@ Cygnusx1 Якщо не було зрозуміло, чому одиночний клас класу (синглтон, де клас забезпечує єдину копію) не легко перевірити, він щільно прив’язує існування класу до життєвого циклу програми. Щоб перевірити його, ви повинні дотримуватися запуску та відключення програми, що часто має побічні ефекти, не важливі для тестування класу. Якщо це було ефективно як однотонний (одна копія в програмі, але не примусово виконувати інше), ви можете створити кілька копій у тестовий час без програми, перевіривши, що поведінка в класі є такою, якою повинна бути, для кожного сценарію тестування.
Едвін Бак

4

Питання "Статика є злом" - це скоріше проблема глобального стану. Відповідний час, щоб змінна була статичною, це якщо вона ніколи не має більше ніж один стан; Інструменти IE, які повинні бути доступні всім фреймворкам і завжди повертати однакові результати для того ж виклику методу, ніколи не є «злими», ніж статика. Щодо Вашого коментаря:

Я вважаю статичні змінні зручнішими для використання. І я припускаю, що вони також ефективні

Статистика є ідеальним та ефективним вибором для змінних / класів, які ніколи не змінюються .

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

Є кілька винятків із заборони глобального стану в хорошому коді, наприклад, годинник. Час глобальний, і - у певному сенсі - він змінює стан об’єктів, не маючи закодованих відносин.


"Час є глобальним" - є й інші способи моделювання часу в обчислювальних системах, ніж мати якусь неявну глобальну річ, яка змінюється сама по собі. пор. це опитування: "Час моделювання в обчислювальній техніці: систематика та порівняльне опитування" @ arxiv.org/abs/0807.4132
Jared Updike

4

Мій $ .02 полягає в тому, що кілька з цих відповідей заплутують питання, а не говорять "статики погані". Я думаю, що краще поговорити про визначення рівня та випадки.

Що я б сказав, що статика є змінною "класу" - вона представляє значення, яке ділиться між усіма примірниками цього класу. Як правило, його слід також визначати таким чином (захищеним або приватним для класу та його примірників).

Якщо ви плануєте розмістити навколо нього поведінку на рівні класу та піддавати його іншому коду, то однократне рішення може бути кращим рішенням для підтримки змін у майбутньому (як запропонував @Jessica). Це пояснюється тим, що ви можете використовувати інтерфейси на рівні екземпляр / синглтон таким чином, який ви не можете використовувати на рівні класу, зокрема успадкування.

Деякі думки про те, чому я вважаю, що деякі аспекти в інших відповідях не є основними у питанні ...

Статистика не є "глобальною". У Java масштабування контролюється окремо від статичного / екземпляра.

Паралельність не менш небезпечна для статики, ніж методи екземплярів. Це все-таки держава, яку потрібно захистити. Звичайно, у вас може бути 1000 екземплярів зі змінною екземпляра кожною та лише однією статичною змінною, але якщо код, що звертається до одного, не записаний безпечним для потоків способом, ви все ще вкручуєтесь - це може зайняти трохи більше часу, щоб ви це зрозуміли .

Управління життєвим циклом - цікавий аргумент, але я думаю, що це менш важливий. Я не бачу, чому важче керувати парою класових методів, як init () / clear (), ніж створення та знищення екземпляра singleton. Насправді, дехто може сказати, що сингл є дещо складнішим через ГК.

PS, з точки зору Smalltalk, багато його діалектів мають змінні класи, але в Smalltalk класи фактично є прикладами Metaclass, тому вони дійсно є змінними в екземплярі Metaclass. Все-таки я застосував би те саме правило. Якщо вони використовуються для загального стану в усіх випадках, тоді добре. Якщо вони підтримують публічну функціональність, варто подивитися на Singleton. Зітхніть, я впевнено сумую за Smalltalk ....


4

У вашому дописі є два основних питання.

По-перше, про статичні змінні. Статичні змінні є абсолютно непотрібними, і його використання можна уникнути легко. У мові OOP в цілому, а в Java зокрема, параметри функції пасеровані за посиланням, це означає, що якщо ви передаєте об'єкт фунціанту, ви передаєте вказівник на об'єкт, тому вам не потрібно визначати статичні змінні, оскільки ви можете передати вказівник на об’єкт до будь-якої області, яка потребує цієї інформації. Навіть якщо це означає, що yo заповнить вашу пам’ять покажчиками, це не обов'язково представлятиме низьку ефективність, оскільки фактичні системи перемикання пам’яті оптимізовані для цього, і вони зберігатимуть у пам’яті сторінки, на які посилаються вказівники, які ви перейшли до нового сфера застосування; використання статичних змінних може призвести до того, що система завантажить сторінку пам'яті, де вони зберігаються, коли до них потрібно звернутися (це станеться, якщо дана сторінка не була доступна). Доброю практикою є складання всього цього статичного затиску в декілька маленьких "конфігураційних статей", це забезпечить, щоб система розмістила все це на одній сторінці пам'яті.

По-друге, про статичні методи. Статичні методи не такі вже й погані, але вони можуть швидко знизити продуктивність. Наприклад, подумайте про метод, який порівнює два об'єкти класу та повертає значення, що вказує, який із об'єктів більший (метод типічного порівняння) цей метод може бути статичним чи ні, але при виклику його нестатична форма буде більш ефективною оскільки для цього доведеться вирішити лише два посилання (по одному на кожен об’єкт) обличчям до трьох посилань, яким доведеться вирішувати статичну версію того ж методу (по одному для класу плюс два, по одному для кожного об’єкта). Але, як я кажу, це не так вже й погано, якщо ми подивимось на клас Math, ми зможемо знайти безліч математичних функцій, визначених статичними методами. Це дійсно більш ефективно, ніж розміщення всіх цих методів у класі, що визначає числа,

На завершення: уникайте використання статичних змінних і знаходите правильну рівновагу продуктивності при роботі зі статичними або нестатичними методами.

PS: Вибачте за мою англійську.


4

З самими статичними змінними немає нічого поганого. Просто зламаний синтаксис Java. Кожен клас Java фактично визначає дві структури - об'єкт одиночного типу, який інкапсулює статичні змінні, та екземпляр. Визначення обох в одному джерельному блоці - це чисте зло, і це призводить до важкого для читання коду. Скала зробила це правильно.


3

а) Розум щодо програм.

Якщо у вас є програма з невеликим розміром середнього розміру, де доступ до статичної змінної Global.foo доступний, виклик до неї зазвичай надходить нізвідки - шляху немає, а отже, немає часової шкали, як змінна приходить до місця, де вона використовується. Тепер як я можу знати, хто встановив його фактичне значення? Звідки я знаю, що станеться, якщо я його зараз модифікую? Я маю греп над усім джерелом, щоб зібрати всі звернення, щоб знати, що відбувається.

Якщо ви знаєте, як ви його використовуєте, оскільки ви щойно написали код, проблема непомітна, але якщо ви спробуєте зрозуміти іноземний код, ви зрозумієте.

б) Вам справді потрібен лише один?

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

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


3

все (може :) має своє призначення, якщо у вас є купа потоків, яким потрібно ділитися / кешувати дані, а також всю доступну пам'ять (щоб ви не розділилися на контексти в межах одного JVM) статика є найкращим вибором

-> звичайно, ви можете примусити лише один екземпляр, але чому?
Я знаходжу деякі коментарі в цій темі злі, а не статику;)


3

Статичні змінні не є ні добрими, ні злими. Вони представляють атрибути, які описують весь клас, а не конкретний екземпляр. Якщо вам потрібно встановити лічильник для всіх екземплярів певного класу, статична змінна буде правильним місцем для утримання значення.

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


2

Усі відповіді вище показують, чому статика погана. Причина, що вони злі, полягає в тому, що це створює помилкове враження, що ви пишете об'єктно-орієнтований код, а насправді ви цього не є. Це просто зло.


1
Але чи жорстко розглядаючи ваш код, щоб дотримуватися якоїсь довільної стандартної парадигми, насправді робить код кращим, чи ми скаржимось уникати просто написання коду, який працює?
Зої

Так, це робить його кращим, оскільки це робить його більш керованим у майбутньому, легшим для розуміння та явнішим.
Блокада

1
Чому зло не писати код OO? і чому Бджарн Струструп не погоджується з вами? назвати лише одну ...
Маркіз Лорн

2
Я не сказав, що зло не писати код OO. Я сказав, що зло, думаючи, що ви пишете код OO, коли все, що ви є, маскує глобалі за статичними методами та властивостями. Будь ласка, перечитайте те, що я написав.
Блокада

2

Тут є багато хороших відповідей,

Пам'ять: Статичні змінні живуть до тих пір, поки живе завантажувач класів [загалом, поки VM не помирає], але це лише у випадку масових об'єктів / посилань, що зберігаються як статичні.

Модуляризація: розгляньте такі поняття, як IOC, залежністьInjection, проксі тощо. Всі цілком проти жорсткого з'єднання / статичної реалізації.

Інші Con: Безпека нитки, заповідність


0

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


0

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


0

З моєї точки зору, staticзмінною слід читати лише дані або змінні, створені умовно .

Наприклад, у нас є призначений для користувача проект, у нас є список країн, мов, ролей користувача тощо. І у нас є клас для організації цих даних. ми абсолютно впевнені, що додаток не буде працювати без цих списків. тому перше, що ми робимо у програмі init - це перевірка цього списку на оновлення та отримання цього списку з api (якщо потрібно). Тож ми погоджуємось, що ці дані "завжди" присутні в додатку. Це практично лише читання даних, тому нам не потрібно дбати про його стан - думаючи про цей випадок, ми дійсно не хочемо мати багато примірників цих даних - цей випадок виглядає ідеальним кандидатом для статичності .


0

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

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

Це часто траплялося, коли я мав статичну колекцію всередині класу, яка містить екземпляри класу та деякі статичні методи управління колекцією. Після того, як ви задумаєтесь, очевидно, що ваш клас не робить "Просто одне", це колекція та щось зовсім інше.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.