【问题标题】:Why we should not use protected static in java为什么我们不应该在 java 中使用 protected static
【发布时间】:2014-08-08 22:57:45
【问题描述】:

我正在回答这个问题Is there a way to override class variables in Java? 第一条获得 36 票的评论是:

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

谁能解释为什么protected static 不受欢迎?

【问题讨论】:

  • 受保护的静态字段没有任何问题,只要它是final。跨类共享的可变静态字段绝对令人担忧。更新静态字段的多个类不太可能可靠或易于遵循,特别是因为任何受保护字段或方法的存在意味着该类旨在由其他包中的类扩展,可能是不受控制的类包含受保护字段的类的作者。
  • @VGR, final 并不意味着该字段是不可变的。您始终可以修改由final 引用变量引用的object
  • @VGR 我不同意。您制作静态变量的唯一原因是只能通过继承从另一个包中访问它,并且访问单个字段不应该是继承的原因。这是一个有缺陷的设计,IMO,如果你求助于这个,你可能应该重新考虑你的应用程序的结构。不过,这只是我的看法。
  • @LoneRider 你是对的。我在想是不可变的,而 final 当然不能保证。
  • 即使我也是从同一个问题来到这里的。

标签: java oop inheritance static protected


【解决方案1】:

与其说是直接的问题,不如说是风格上的问题。这表明你没有正确思考课堂上发生了什么。

想想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 而无需限定名称 - 但它不会继承或获取自己的副本。它正在查看内存中完全相同的对象。

【讨论】:

  • 你们对继承问题束手无策。看到这种情况的典型情况是有人想要在不公开的情况下访问包(例如单元测试)。
  • @spudone 但单元测试通常放在同一个包中。要授予他们访问权限,您只需使用默认(包)访问级别。受保护的也可以访问不需要或与单元测试相关的子类。
  • @Kamafeather Protected 意味着孩子们可以从不同的班级看到你,并且同一个包中的任何班级都可以看到你。所以是的,程序在同一个包中,因此可以看到受保护的成员。
  • "扩展类然后可以修改行为" 这将违反 Liskov 替换原则。子类不应修改其超类型的行为。这属于整个“正方形不是矩形”的论点:调整超类 (Rectangle) 以调整宽度/高度以确保相等将(对于Square)如果您要将每个超类型替换为它的子类型的一个实例。
  • "我能看到你可以同时使用这两者的唯一情况是,如果你有一个被设计为扩展的抽象类,扩展类可以修改行为使用原始定义的常量" 有点隐藏,但它就在那里。代码示例也表达了语句
【解决方案2】:

静态成员不被继承,受保护的成员只对子类(当然还有包含的类)可见,所以protected staticstatic 具有相同的可见性,这表明编码器存在误解。

【讨论】:

  • 您能否提供您的第一个声明的来源?在快速测试中,受保护的静态 int 被继承并在子类中重用,没有问题。
  • 受保护的静态与包私有静态具有相同的可见性,而不是私有静态。除此之外,如果您使用受保护的和静态的,最好删除访问修饰符以使其成为包私有(如果您打算使其在包中可访问)
  • 嗯...不。包私有和protected 不一样。如果只是说static,则该字段仅对同一包中的子类可见。
  • @AaronDigulla protected static 允许在包内从子类访问。将变量设为静态会消除子类化的意图,而唯一的意图是从包内访问。
  • @VinceEmigh:对不起,我在和 Bohemian 说话。应该更清楚地说明这一点。
【解决方案3】:

我看不出有什么特别的理由不应该对此表示不满。可能总是有实现相同行为的替代方法,并且这些替代方法是否比受保护的静态方法“更好”取决于实际架构。但是,受保护的静态方法至少是合理的一个示例可能如下:

(编辑拆分成单独的包,使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,因为它们应该被扩展类调用。它们也不能具有“默认”可见性,因为这样其他包中的扩展类将无法访问它们。

(编辑:可以假设原始评论仅涉及 fields,而不是 methods - 但是,它太笼统了)

【讨论】:

  • 在这种情况下,您应该将默认实现定义为protected final(因为您不希望它们被覆盖),而不是static。一个方法不使用实例变量这一事实并不意味着它应该是static(尽管它可以)。
  • @Thomas 当然,在这种情况下,这也是可能的。一般来说:这当然是部分主观的,但我的经验法则是:当一个方法不打算用于多态时,它可以是静态的,然后我将它设为静态。与将其设为final 相比,它不仅清楚地表明该方法不打算被覆盖,它另外 向读者清楚地表明该方法不使用实例变量。简而言之:根本没有理由将其设为静态。
  • 当一个方法不打算以多态方式使用时,它可以是静态的,然后我将其设为静态。 - 当你开始使用 mock 时,这会给你带来麻烦单元测试框架。但这将我们引向另一个话题……
  • @Marco13 还有一种情况是当你做某事protected,不是为了显示继承,而是将它保存在一个单独的包中——不暴露。我想当它们在 java 中时,密封接口会以这种方式变得有用
  • @Eugene 这个评论让我有点恼火。无需任何修饰符即可实现包可见性,而protected 表示“继承类(即使在不同的包中)”...
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    不赞成,因为它自相矛盾。

    创建一个变量protected 意味着它将被在包中使用,或者它将被在子类中继承

    使变量static 使其成为类的成员,消除了继承它的意图。这仅留下了在包中使用的意图,我们为此提供了package-private(无修饰符)。

    我觉得这很有用的唯一情况是,如果您声明了一个应该用于启动应用程序的类(如 JavaFX 的 Application#launch,并且只想能够从子类启动。如果这样做,确保该方法也是 final 以禁止 hiding。但这不是“规范”,可能是为了防止通过添加新的启动应用程序方式来增加复杂性而实现的。

    要查看每个修饰符的访问级别,请参阅:The Java Tutorials - Controlling Access to Members of a Class

    【讨论】:

    • 我不明白static 将如何消除继承它的意图。因为我在另一个包中的子类仍然需要超级字段为protected 才能访问,即使它是staticpackage-private 帮不上忙
    • @AoboYang 你是对的,这就是为什么有些人使用protected static。但这是一种代码味道,因此是“run”部分。访问修饰符和继承是两个不同的主题。是的,如果它是package-private,您将无法从超类访问静态成员。但是您不应该首先依赖继承来引用static 字段;这是设计不佳的标志。您会注意到覆盖static 方法的尝试没有给出任何结果,这清楚地表明继承不是基于类的。如果你需要在类或包之外访问,它应该是public
    • 我有一个类,一开始有一些private static util 函数,但我想有人可能想改进或自定义我的类,这些util 函数也可能为他们提供方便。 public 可能不合适,因为 util 方法不适用于我的类实例的用户。你能帮我找出一个好的设计来代替protected吗?谢谢
    • 在该链接中,它显示“使用对特定成员有意义的最严格的访问级别。除非您有充分的理由不这样做,否则请使用私人。 ,这与这个问题相矛盾,有什么想法吗?
    【解决方案6】:

    实际上protected static 并没有根本性的问题。如果你真的想要一个对包和声明类的所有子类可见的静态变量或方法,那么继续并使其成为protected static

    有些人出于各种原因一般避免使用protected,有些人认为非final的static变量应该尽量避免使用(我个人在一定程度上同情后者),所以我猜protectedstatic 对于属于这两个群体的人来说必须看起来糟糕^2

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

        好吧,正如大多数人所回答的那样:

        • protected 表示 - 'package-private + 对子类的可见性 - 属性/行为是继承的'
        • static 表示 - '与实例相反 - 它是 CLASS 属性/行为,即它不是继承的'

        因此它们有些矛盾和不相容。

        但是,最近我想出了一个用例,将这两者结合使用可能是有意义的。想象一下,您想创建一个 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 的非抽象子类型,它实现了自己的非抽象生成器,并且位于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 的构建器这两种情况下,我们结合了相互矛盾的陈述。这是个人喜好问题。

        但是,我仍然更喜欢 abstract classpublic 构建器,因为 protected static 在 OOD 和 OOP 世界中对我来说感觉更不自然!

        【讨论】:

          猜你喜欢
          • 2012-07-25
          • 2020-06-12
          • 1970-01-01
          • 2016-07-23
          • 2011-03-15
          • 2011-04-22
          • 2011-05-16
          • 2020-10-09
          • 2018-01-15
          相关资源
          最近更新 更多