Чому ми не повинні використовувати захищені статики в Java


122

Я переглядав це запитання. Чи є спосіб зміни перемінних класів на Java? Перший коментар із 36 оновленнями:

Якщо ви коли-небудь бачите protected static, запустіть.

Хтось може пояснити, чому protected staticнахмурився?


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

6
@VGR, finalне означає, що поле є незмінним. Ви завжди можете змінювати objectпосилання за допомогою finalдовідкової змінної.
Зеешан

@VGR Я не згоден. ТІЛЬКО, чому ви зробите статичну змінну, - це мати доступ до неї з іншого пакету лише за спадковим принципом, а доступ до одного поля НЕ повинен бути причиною успадкування. Це хибний дизайн, IMO, і якщо ви вдаєтеся до цього, ви, ймовірно, повинні переосмислити структуру програми. Але це лише моя думка.
Діоксин

@LoneRider Ти маєш рацію. Я думав про непорушність, і остаточний, безумовно, цього не гарантує.
VGR

Навіть я прийшов сюди з того самого питання.
Raj Rajeshwar Singh

Відповіді:


86

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

Подумайте, що staticозначає:

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

Подумайте, що protectedозначає:

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

Два значення не зовсім взаємовиключні, але вони досить близькі.

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

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

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

public class Program {
    public static void main (String[] args) throws java.lang.Exception {
        System.out.println(new Test2().getTest());
        Test.test = "changed";
        System.out.println(new Test2().getTest());
    }
}

abstract class Test {
    protected static String test = "test";
}

class Test2 extends Test {
    public String getTest() {
        return test;
    }
}

Ви побачите результати:

test
changed

Спробуйте самі: https://ideone.com/KM8u8O

Клас Test2може отримати доступ до статичного члена testз того, що Testне потрібно кваліфікувати ім'я, але він не успадковує та не отримує власну копію. Він дивиться на той самий об’єкт у пам’яті.


4
Ви, хлопці, повішені на спадщину. Типовий випадок, коли це спостерігається, це те, що хтось хоче отримати пакет, не оприлюднюючи його (наприклад, тестовий модуль).
spudone

3
@spudone, але одиничні тести, як правило, розміщуються в одній упаковці. Щоб надати їм доступ, ви просто використовуєте стандартний (пакетний) рівень доступу. Захищений також дає доступ до підкласів, що не потрібно або стосується тестування одиниць.
Тім Б,

1
@Kamafeather Protected означає, що діти можуть бачити вас із різних класів, а будь-які класи в одному пакеті можуть бачити вас. Отже, програма "Програма" знаходиться в одному пакеті, і тому вона може бачити захищеного учасника.
Тім Б

2
" Клас, що розширюється, може змінити поведінку ". Це порушило б принцип Лісковської субституції. Підклас не повинен змінювати поведінку його супертипу. Це підпадає під весь аргумент "квадрат не прямокутник": коригування надкласу ( Rectangle) для регулювання ширини / висоти для забезпечення рівності (для Square) призведе до небажаних результатів, якщо ви замінювали кожен супертип на екземпляр його підтипу.
Діоксин

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

32

Це нахмурено, тому що це суперечливо.

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

Створення змінної staticробить її членом класу, виключаючи наміри успадкувати її . Це залишає лише намір використовуватись у пакеті , і у нас package-privateдля цього (без модифікатора).

Єдина ситуація , я міг би знайти це корисно, якщо ви оголосити клас , який повинен використовуватися для запуску програми (наприклад , JavaFX - х Application#launch, і тільки хотів , щоб мати можливість запуску з підкласу. Якщо робити це, переконайтеся , що метод також finalдля Disallow приховування . Але це не «норма», і, ймовірно , реалізується , щоб запобігти додавання більше складності, додавши новий спосіб запуску додатків.

Щоб переглянути рівні доступу кожного модифікатора, див. Це: Підручники Java - Контроль доступу до членів класу


4
Я не розумію, як би staticусунути наміри успадкування. Оскільки мій підклас в іншому пакеті все ще вимагає доступу поля супер protected, навіть якщо це static. package-privateне можу допомогти
Аобо Ян

@AoboYang Ви маєте рацію, тому деякі користуються protected static. Але це кодовий запах, звідси і « пробіжна » частина. Модифікатори доступу та успадкування - це дві різні теми. Так, ви не зможете отримати доступ до статичного члена з суперкласу, якщо він був package-private. Але вам не слід сподіватися на спадкування staticв першу чергу посилальних полів; це ознака поганого дизайну. Ви помітите, що спроби переосмислення staticметодів не дають результатів, що є чіткою ознакою того, що успадкування не базується на класах. Якщо вам потрібен доступ поза класом або пакетом, він повинен бутиpublic
Діоксин

3
private staticСпочатку у мене є клас з деякими функціями утиліти, але я думаю, що хтось може захотіти вдосконалити або налаштувати з мого класу, і ці функції утиліти можуть також забезпечити їм зручність. publicможе не підходити, оскільки утилітні методи не призначені для користувачів екземплярів мого класу. Не могли б ви допомогти мені зрозуміти гарний дизайн замість protected? Дякую
Аобо Ян

У цьому посиланні написано: "Використовуйте найбільш обмежуючий рівень доступу, який має сенс для певного учасника. Використовуйте приватне, якщо у вас немає вагомих причин цього не робити. " , що суперечить цьому питанню, будь-яким думкам?
Сесілія

13

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

(Відредаговано для розбиття на окремі пакети, щоб зробити protectedзрозуміліше використання )

package a;
import java.util.List;

public abstract class BaseClass
{
    public Integer compute(List<Integer> list)
    {
        return computeDefaultA(list)+computeDefaultB(list);
    }

    protected static Integer computeDefaultA(List<Integer> list)
    {
        return 12;
    }
    protected static Integer computeDefaultB(List<Integer> list)
    {
        return 34;
    }
}

Похідне від цього:

package a.b;

import java.util.List;

import a.BaseClass;

abstract class ExtendingClassA extends BaseClass
{
    @Override
    public Integer compute(List<Integer> list)
    {
        return computeDefaultA(list)+computeOwnB(list);
    }

    private static Integer computeOwnB(List<Integer> list)
    {
        return 56;
    }
}

Ще один похідний клас:

package a.b;

import java.util.List;

import a.BaseClass;

abstract class ExtendingClassB extends BaseClass
{
    @Override
    public Integer compute(List<Integer> list)
    {
        return computeOwnA(list)+computeDefaultB(list);
    }

    private static Integer computeOwnA(List<Integer> list)
    {
        return 78;
    }
}

Тут protected staticмодифікатор, безумовно, може бути виправданий:

  • Методи можуть бути static, оскільки вони не залежать від змінних екземплярів. Вони не призначені для використання безпосередньо як поліморфний метод, а скоріше є "корисними" методами, які пропонують реалізацію за замовчуванням, що є частиною більш складного обчислення, і служать "будівельними блоками" фактичної реалізації.
  • Методів не повинно бути public, оскільки вони є деталізацією реалізації. І вони не можуть бути, privateтому що їх слід викликати розширеними класами. Вони також не можуть мати видимість "за замовчуванням", оскільки тоді вони не будуть доступні для класів, що розширюються, в інших пакетах.

(EDIT: Можна припустити, що початковий коментар стосувався лише полів , а не методів - тоді, проте, це було занадто загально)


У цьому випадку слід визначити реалізацію за замовчуванням як protected final(оскільки ви не хочете, щоб їх переосмислювали), а не static. Те, що метод не використовує змінні екземпляри, не означає, що він повинен бути static(хоча він може ).
barfuin

2
@Thomas Звичайно, і в цьому випадку це було б можливо. Взагалі: Це, безумовно, частково суб'єктивно, але моє правило: Коли метод не призначений для використання поліморфно, і він може стати статичним, то я роблю це статичним. На відміну від цього final, він не тільки дає зрозуміти, що метод не призначений для переопрацювання, він також дає зрозуміти читачеві, що метод не використовує змінні екземпляри. Так лаконічно: Просто немає причин не робити це статичним.
Marco13

1
Коли метод не призначений для використання поліморфно, і він може бути статичним, то я роблю це статичним. - Це призведе до виникнення проблем, коли ви почнете використовувати макетні рамки для тестування одиниць. Але це призводить нас до іншої теми ...
barfuin

@ Marco13 також є випадок, коли ви щось робите protected, щоб не показувати спадщину, а зберігати його під єдиним пакетом - не піддаватися. Я думаю, що запечатані інтерфейси стануть корисними таким чином, коли вони будуть в java
Євген

@Eugene Цей коментар мене трохи дратує. Видимість пакета може бути досягнута без будь-якого модифікатора, тоді як protectedозначає "Успадковування класів (навіть у різних пакетах)" ...
Marco13

7

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


1
Чи можете ви надати джерело першої заяви? У швидкому тесті захищений статичний int був успадкований та повторно використаний у підкласі без проблем.
hiergiltdiestfu

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

2
Гм ... ні. пакет приватний і protectedне однаковий. Якщо ви просто скажете static, поле видно лише підкласам у тому ж пакеті.
Аарон Дігулла

1
@AaronDigulla protected staticдозволяє отримати доступ до пакету або з підкласу . Створення змінної статики видаляє наміри підкласифікації, залишаючи єдиним наміром доступ зсередини пакету.
Діоксин

@VinceEmigh: Вибачте, я розмовляв з богемським. Треба було зробити це більш зрозумілим.
Аарон Дігулла

5

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

Деякі люди, як правило, уникають використання protectedз різних причин, а деякі вважають, що не остаточних staticзмінних слід уникати будь-якими способами (я особисто певною мірою співчуваю останнім), тому я гадаю, що комбінація protectedі staticповинна виглядати погано ^ 2 для тих, що належать до обох груп.


3

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


1

Ну, як відповіла більшість людей:

  • protectedозначає - " пакет-приватний + видимість для підкласів - властивість / поведінка НЕБЕЗПЕЧЕНО "
  • staticозначає - " протилежний екземпляру - це властивість / поведінка КЛАСУ, тобто НЕ БЕЗПЕЧЕНО "

Тому вони трохи суперечливі та несумісні.

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

package X;
public abstract class AbstractType {
    protected Object field1;
    protected Object field2;
    ...
    protected Object fieldN;

    protected static abstract class BaseBuilder<T extends BaseBuilder<T>> {
        private Object field1; // = some default value here
        private Object field2; // = some default value here
        ...
        private Object fieldN; // = some default value here

        public T field1(Object value) { this.field1 = value; return self();}
        public T field2(Object value) { this.field2 = value; return self();}
        ...
        public T fieldN(Object value) { this.fieldN = value; return self();}
        protected abstract T self(); // should always return this;
        public abstract AbstractType build();
    }

    private AbstractType(BaseBuilder<?> b) {
        this.field1 = b.field1;
        this.field2 = b.field2;
        ...
        this.fieldN = b.fieldN;
    }
}

А чому protected static? Тому що я хочу, щоб неабразивний підтип, AbstactTypeякий реалізує свій власний не абстрактний Builder і розташований назовні, package Xщоб мати змогу отримати доступ та повторно використовувати його BaseBuilder.

package Y;
public MyType1 extends AbstractType {
    private Object filedN1;

    public static class Builder extends AbstractType.BaseBuilder<Builder> {
        private Object fieldN1; // = some default value here

        public Builder fieldN1(Object value) { this.fieldN1 = value; return self();}
        @Override protected Builder self() { return this; }
        @Override public MyType build() { return new MyType(this); }
    }

    private MyType(Builder b) {
        super(b);
        this.fieldN1 = b.fieldN1;
    }
}

Звичайно, ми можемо BaseBuilderоприлюднити, але тоді ми прийдемо до чергових суперечливих тверджень:

  • Ми маємо неіснуючий клас (реферат)
  • Ми надаємо для цього громадського будівельника

Таким чином, в обох випадках protected staticі з publicконструкторомabstract class ми поєднуємо суперечливі твердження. Це питання особистих уподобань.

Однак я все ще віддаю перевагу publicконструктору,abstract class оскільки protected staticмені в світі OOD та OOP вважається більш неприродно!


0

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

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