Почему мы не должны использовать защищенную статику в java

122

Я задавал этот вопрос Есть ли способ переопределить переменные класса в Java? Первый комментарий, набравший 36 голосов, был:

Если вы когда-нибудь увидите protected static , бегите.

Кто-нибудь может объяснить, почему не protected staticодобряют?

Zeeshan
источник
6
Нет ничего плохого в защищенном статическом поле, если оно есть final. Изменяемое статическое поле, разделяемое всеми классами, определенно вызывает беспокойство. Множественные классы, обновляющие статическое поле, вряд ли будут надежными или легкими для отслеживания, тем более, что присутствие любого защищенного поля или метода подразумевает, что класс предназначен для расширения классами в других пакетах, возможно, классами, не находящимися под контролем автор класса, содержащего защищенное поле.
VGR
6
@VGR finalне означает, что поле неизменяемо. Вы всегда можете изменить objectссылку, на которую finalссылается ссылочная переменная.
Zeeshan
@VGR Я не согласен. ЕДИНСТВЕННАЯ причина, по которой вы должны создать статическую переменную, - это иметь доступ к ней из другого пакета только по наследству, а доступ к одному полю НЕ должен быть причиной наследования. Это ошибочный дизайн, ИМО, и если вы прибегнете к нему, вам, вероятно, следует переосмыслить структуру своего приложения. Но это только мое мнение.
Dioxin
@LoneRider Вы правы. Я думал о неизменном, и final, конечно, не гарантирует этого.
VGR
Даже я пришел сюда из-за того же вопроса.
Радж Раджешвар Сингх Ратхор

Ответы:

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, но модульные тесты обычно помещаются в один и тот же пакет. Чтобы предоставить им доступ, вы просто используете уровень доступа по умолчанию (пакет). Protected также дает доступ к подклассам, которые не требуются или не относятся к модульному тесту.
Tim B
1
@Kamafeather Protected означает, что дети могут видеть вас из разных классов, и любые классы в одном пакете могут видеть вас. Итак, да, программа находится в том же пакете и, следовательно, может видеть защищенный член.
Тим Би
2
" расширяющийся класс мог бы затем изменить поведение " Это нарушит принцип подстановки Лискова. Подкласс не должен изменять поведение своего супертипа. Это подпадает под весь аргумент «квадрат не прямоугольник»: настройка суперкласса ( Rectangle) для регулировки ширины / высоты, чтобы гарантировать, что равенство (для Square) приведет к нежелательным результатам, если вы замените каждый супертип экземпляром его подтипа.
Dioxin
1
« Единственный случай, когда я вижу, где вы могли бы использовать их вместе, - это если бы у вас был абстрактный класс, который был разработан для расширения, а расширяющийся класс мог бы затем изменить поведение, используя константы, определенные в оригинале » Немного скрыто, но там. Пример кода также выражает утверждение
Dioxin
32

Это неодобрительно, потому что это противоречиво.

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

Создание переменной staticделает ее членом класса, что исключает намерение наследовать ее . Остается только намерение использоваться в пакете , и у нас есть package-privateдля этого (без модификатора).

Единственная ситуация, в которой я мог найти это полезным, - это если вы объявляли класс, который должен использоваться для запуска приложения (например, JavaFX Application#launch, и хотели иметь возможность запускаться только из подкласса. В этом случае убедитесь, что метод также finalдолжен запретить скрытие . Но это не «норма» и, вероятно, было реализовано, чтобы предотвратить добавление дополнительных сложностей путем добавления нового способа запуска приложений.

Чтобы увидеть уровни доступа для каждого модификатора, см. Это: Учебники по Java - Управление доступом к членам класса

диоксин
источник
4
Я не понимаю, как staticустранить намерения наследовать его. Поскольку мой подкласс в другом пакете по-прежнему требует, чтобы поле super было protectedдля доступа, даже если оно static. package-privateне могу помочь
Аобо Ян 02
@AoboYang Вы правы, поэтому некоторые люди используют protected static. Но это запах кода, отсюда и « запуск ». Модификаторы доступа и наследование - это две разные темы. Да, вы не сможете получить доступ к статическому члену суперкласса, если бы он был package-private. Но вы не должны staticв первую очередь полагаться на наследование для ссылки на поля; это признак плохого дизайна. Вы заметите, что попытки переопределения staticметодов не дают результатов, что является явным признаком того, что наследование не основано на классах. Если вам нужен доступ за пределами класса или пакета, он должен бытьpublic
Диоксин
3
private staticСначала у меня есть класс с некоторыми служебными функциями, но я думаю, что кто-то может захотеть улучшить или настроить мой класс, и эти служебные функции также могут обеспечить им удобство. publicможет не подходить, поскольку методы util не предназначены для пользователей экземпляров моего класса. Не могли бы вы помочь мне придумать хороший дизайн вместо protected? Спасибо
Aobo Yang
В этой ссылке говорится: «Используйте наиболее ограничительный уровень доступа, который имеет смысл для конкретного участника. Используйте приват, если у вас нет веской причины не делать этого ». , что противоречит этому вопросу, любые мысли?
Сесилия
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потому что они должны вызываться расширяющимися классами. Они также не могут иметь видимость по умолчанию, потому что тогда они не будут доступны для расширяющихся классов в других пакетах.

(РЕДАКТИРОВАТЬ: можно было предположить, что исходный комментарий относился только к полям , а не к методам - тогда, однако, он был слишком общим)

Marco13
источник
В этом случае вы должны определить реализации по умолчанию как protected final(поскольку вы не хотите, чтобы они переопределялись), not static. Тот факт, что метод не использует переменные экземпляра, не означает, что это должно быть static(хотя может ).
barfuin
2
@Thomas Конечно, в этом случае это тоже возможно. В общем: это, конечно, частично субъективно, но мое практическое правило таково: когда метод не предназначен для использования полиморфно и может быть статическим, я делаю его статическим. В отличие от этого final, он не только проясняет, что метод не предназначен для переопределения, но и дополнительно дает понять читателю, что метод не использует переменные экземпляра. Итак, кратко: просто нет причин не делать его статичным.
Marco13
1
Когда метод не предназначен для использования в полиморфном режиме и может быть статическим, я делаю его статическим. - У вас возникнут проблемы, когда вы начнете использовать фиктивные фреймворки для модульного тестирования. Но это подводит нас к другой теме ...
barfuin
@ Marco13 есть также случай, когда вы создаете что-то protected, но не для демонстрации наследования, а для того, чтобы хранить это в одном пакете, не раскрывая его. Я думаю, что запечатанные интерфейсы станут полезными в этом случае, когда они появятся в java
Евгений
@Eugene Этот комментарий меня немного раздражает. Видимость пакетов может быть достигнута без каких-либо модификаторов, тогда как это protectedозначает «Наследование классов (даже в разных пакетах)» ...
Marco13
7

Статические члены не наследуются, а защищенные члены видны только подклассам (и, конечно, содержащему их классу), поэтому a protected staticимеет такую ​​же видимость, что staticи указывает на недоразумение со стороны кодировщика.

Богемный
источник
1
Не могли бы вы предоставить источник для вашего первого заявления? В быстром тесте защищенный статический тип int был унаследован и повторно использован в подклассе без проблем.
hiergiltdiestfu
Защищенный статический вид имеет ту же видимость, что и частный статический пакет, а не частный статический. Чтобы добавить к этому, если вы используете protected и static, лучше всего просто удалить модификатор доступа, чтобы сделать его закрытым для пакета (если вы намеревались сделать его доступным в пакете)
Dioxin
2
Гм ... нет. пакет частный и protectedне то же самое. Если вы просто скажете static, поле видно только подклассам в том же пакете.
Аарон Дигулла
1
@AaronDigulla protected staticразрешает доступ внутри пакета или из подкласса . Создание статической переменной удаляет намерения наследования подклассов, оставляя единственное намерение - доступ изнутри пакета.
Dioxin
@VinceEmigh: Извините, я разговаривал с Bohemian. Надо было прояснить это.
Аарон Дигулла
5

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

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

x4u
источник
3

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

Митхун Каннот
источник
1

Ну, как ответило большинство людей:

  • protectedозначает - ' package-private + видимость для подклассов - свойство / поведение НАСЛЕДУЕТ '
  • 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!

egelev
источник
0

Нет ничего плохого в том, чтобы иметь protected static . Многие упускают из виду одну вещь: вы можете захотеть написать тестовые примеры для статических методов, которые вы не хотите раскрывать в обычных обстоятельствах. Я заметил, что это особенно полезно для написания тестов для статических методов в служебных классах.

Aelphaeis
источник