为什么我们不应该在Java中使用受保护的静态


122

我正在解决这个问题,有没有一种方法可以覆盖Java中的类变量? 关于36个投票的第一条评论是:

如果您看到protected static,请运行。

谁能解释为什么protected static皱眉呢?


6
只要是,受保护的静态字段就没有问题final。跨类共享的可变静态字段绝对令人担忧。更新静态字段的多个类不太可能可靠或易于遵循,特别是因为任何受保护的字段或方法的存在都意味着该类应由其他包中的类扩展,可能是不受其控制的类。包含保护字段的类的作者。
VGR 2014年

6
@VGR,final并不意味着该字段是不变的。您始终可以object通过final引用变量来修改引用。
Zeeshan 2014年

@VGR我不同意。您要生成静态变量的唯一原因是只能通过继承来从另一个包中对其进行访问,而对单个字段的访问不应成为继承的原因。IMO,这是一个有缺陷的设计,如果您诉诸于此,则可能应该重新考虑应用程序的结构。不过那只是我的意见。
Dioxin

@LoneRider你是对的。我一直在想一成不变,最终肯定不能保证这一点。
VGR

甚至我也来自同一个问题。
拉吉(Raj Rajeshwar Singh Rathore)

Answers:


86

这是一个风格上的问题,而不是直接的问题。这表明您没有正确考虑班上正在发生的事情。

考虑一下什么static意思:

该变量存在于类级别,在每个实例中并不单独存在,并且在扩展me的类中没有独立存在

考虑一下什么protected意思:

该变量可以在此类,同一包中的类以及扩展我的类中看到。

这两种含义并不完全互斥,但非常接近。

我唯一可以看到的是可以将两者一起使用的情况是,如果您有一个设计成可扩展的抽象类,然后扩展类可以使用原始对象中定义的常量来修改行为。这种安排很可能最终会变得非常凌乱,并表明类的设计存在缺陷。

在大多数情况下,最好将常量设为public,因为这样会使所有内容更清洁,并使子类的子类具有更大的灵活性。在许多情况下,组合比继承要好得多,而抽象类则强制继承。

要查看一个示例,说明如何破坏事物,并说明变量没有独立存在的含义,请尝试以下示例代码:

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是能够访问静态成员testTest无需限定名称-但它没有继承或让自己的副本。它正在查看内存中完全相同的对象。


4
你们都对继承挂了电话。看到这种情况的典型情况是某人想要不公开访问软件包(例如,单元测试)。
spudone

3
@spudone,但单元测试通常放在同一程序包中。要授予他们访问权限,您只需使用默认(程序包)访问级别。受保护也可以访问子类,而子类不是必需的,也不与单元测试有关。
Tim B

1
@Kamafeather保护意味着孩子可以从不同的班级看到您,并且同一包中的任何班级都可以看到您。因此,是的,程序位于同一软件包中,因此可以看到受保护的成员。
Tim B

2
扩展类然后可以修改行为 ”,这将违反Liskov替换原则。子类不应修改其超类型的行为。这属于整个“正方形而不是矩形”参数:如果您要用其子类型的实例替换每个超类型,则调整超类(Rectangle)以调整宽度/高度以确保相等(对于Square)将产生不良结果。
Dioxin

1
唯一可以看到的是可以将两者一起使用的情况是,如果您有一个被设计为可扩展的抽象类,然后扩展类可以使用原始对象中定义的常量来修改行为 ”那里。该代码示例还表达了以下语句
Dioxin

32

人们对此不屑一顾,因为它是矛盾的。

生成变量protected意味着它将在包中使用在子类中继承

使变量static成为类的成员,消除了继承它的意图。这只剩下要在package中使用的意图,而我们有这个意图package-private(没有修饰符)。

我认为这对您有用的唯一情况是,如果您声明了一个应用于启动应用程序的类(例如JavaFX的类Application#launch,并且只希望能够从子类中启动。如果这样做,请确保该方法也可以final用于不允许隐藏,但是,这不是“规范”,并可能实施,以防止通过向应用推出一种新的方式增加更多的复杂性。

要查看每个修饰符的访问级别,请参见:Java教程-控制对类成员的访问


4
我不明白该如何static消除继承它的意图。因为我在另一个包中的子类仍然需要protected访问super字段,即使它是staticpackage-private不禁
奥博洋

@AoboYang是的,这就是为什么有人使用的原因protected static。但这是代码的味道,因此是“ 运行 ”部分。访问修饰符和继承是两个不同的主题。是的,如果是,您将无法从超类访问静态成员package-private。但是,您首先不应该依赖继承来引用static字段。这是不良设计的标志。您会注意到尝试覆盖static方法不会产生任何结果,这清楚表明继承不是基于类的。如果您需要访问课程或课程包之外的地方,应该是public
Dioxin

3
private static最初有一个带有一些util函数的类,但是我认为有人可能想从我的类中进行改进或自定义,这些util函数也可能为他们提供便利。public可能不适合,因为util方法不适用于我的类的实例的用户。您能帮我找出一个好的设计protected吗?谢谢
杨波

在该链接中,它说“使用对特定成员有意义的最严格的访问级别。除非有充分理由不使用,否则请使用private。” ,与这个问题相矛盾,有什么想法吗?
塞西莉亚

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因为扩展类应该调用它们。它们也不能具有“默认”可见性,因为那样一来,其他软件包中的扩展类将无法访问它们。

(编辑:可以假定原始注释仅涉及字段,而不涉及方法 -但是,它太笼统了)


在这种情况下,您应该将默认实现定义为protected final(因为您不希望它们被覆盖),而不是static。方法不使用实例变量的事实并不意味着它应该是static(尽管可以)。
barfuin 2014年

2
@Thomas当然,在这种情况下,这也是可能的。一般而言:这当然是部分主观的,但是我的经验法则是:当某个方法不打算使用多态时,并且它可以是静态的,那么我将其设为静态。与之相反final,它不仅使该方法无意被重写,而且使读者清楚该方法未使用实例变量。如此简洁:根本没有理由使其保持静态。
Marco13 2014年

1
当一种方法不打算被多态使用时,并且它可以是静态的,那么我将其设为静态。-当您开始使用模拟框架进行单元测试时,这将给您带来麻烦。但是,这引出了另一个话题……
barfuin14年

@ Marco13还有一种情况,当您做某事时protected,不是显示继承,而是将其保留在单个包中-不公开。我猜想密封接口在Java中使用时会以这种方式变得有用
Eugene

@Eugene该评论使我有些恼火。无需任何修饰符即可实现包可见性,而protected意味着“继承类(即使在不同的包中)” ...
Marco13,18年

7

静态成员不会被继承,并且受保护的成员仅对子类(当然是包含的类)protected static可见,因此,a 具有与相同的可见性static,表明编码人员存在误解。


1
您能否提供第一份声明的出处?在快速测试中,受保护的静态int可以毫无问题地继承到子类并在子类中重用。
hiergiltdiestfu 2014年

受保护的静态具有与程序包专用静态(而不是专用静态)相同的可见性。此外,如果您使用的是受保护的和静态的,则最好删除access修饰符以使其成为包私有(如果您打算使其在包内可访问)
Dioxin 2014年

2
嗯...不 包的私有和protected不一样。如果您只是说static,则该字段仅对同一包中的子类可见。
亚伦·迪古拉

1
@AaronDigulla protected static允许在包内从子类访问。将变量设为静态将消除子类化的意图,而唯一的意图是从包内部进行访问。
二恶英2014年

@VinceEmigh:抱歉,我在跟波希米亚人说话。应该更清楚地说明这一点。
亚伦·迪古拉

5

实际上,根本没有什么错protected static。如果您确实想要包和声明类的所有子类可见的静态变量或方法,请继续进行操作protected static

有些人通常避免使用protected各种原因,有些人认为非最终static变量应该通过各种手段来避免(我个人后者在一定程度上同情),所以我猜的组合protectedstatic必须看坏^ 2到那些属于这两个群体。


3

使用Protected以便可以在子类中使用它。在具体类的上下文中使用时,定义受保护的静态变量是没有逻辑的,因为您可以访问相同的变量是一种静态方式,但是编译器会发出警告以静态方式访问超类静态变量。


1

好吧,因为大多数人都回答了:

  • protected意味着-' 包私有+子类的可见性-属性/行为是继承的 '
  • static意味着-' 与实例相反-这是CLASS属性/行为,即不是INHERITED '

因此,它们有点矛盾且不兼容。

但是,最近我想到了一个用例,可以将两者一起使用。想象一下,您想创建一个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 staticpublic建设者的abstract class我们结合自相矛盾的说法。这是个人喜好问题。

但是,我仍然更喜欢publicanabstract class构建器,因为protected static在OOD和OOP世界中,对我而言,感觉更不自然!


0

拥有没有错protected static。很多人忽略的一件事是,您可能想为静态方法编写测试用例,而这些情况在正常情况下是不希望公开的。我注意到这对于在实用程序类中为静态方法编写测试特别有用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.