【问题标题】:Interfaces with static fields in java for sharing 'constants'java中用于共享“常量”的静态字段接口
【发布时间】:2010-09-24 03:36:47
【问题描述】:

我正在研究一些开源 Java 项目以进入 Java,并注意到其中很多都有某种“常量”接口。

例如,processing.org 有一个名为PConstants.java 的接口,大多数其他核心类都实现了这个接口。该接口充满了静态成员。这种方法是否有原因,或者这被认为是不好的做法?为什么不使用枚举有意义的地方,或者静态类?

我觉得使用接口来允许某种伪“全局变量”很奇怪。

public interface PConstants {

  // LOTS OF static fields...

  static public final int SHINE = 31;

  // emissive (by default kept black)
  static public final int ER = 32;
  static public final int EG = 33;
  static public final int EB = 34;

  // has this vertex been lit yet
  static public final int BEEN_LIT = 35;

  static public final int VERTEX_FIELD_COUNT = 36;


  // renderers known to processing.core

  static final String P2D    = "processing.core.PGraphics2D";
  static final String P3D    = "processing.core.PGraphics3D";
  static final String JAVA2D = "processing.core.PGraphicsJava2D";
  static final String OPENGL = "processing.opengl.PGraphicsOpenGL";
  static final String PDF    = "processing.pdf.PGraphicsPDF";
  static final String DXF    = "processing.dxf.RawDXF";


  // platform IDs for PApplet.platform

  static final int OTHER   = 0;
  static final int WINDOWS = 1;
  static final int MACOSX  = 2;
  static final int LINUX   = 3;

  static final String[] platformNames = {
    "other", "windows", "macosx", "linux"
  };

  // and on and on

}

【问题讨论】:

  • 注意:static final 不是必须的,对于接口来说是多余的。
  • 还要注意platformNames可能是publicstaticfinal,但绝对不是常数。唯一的常量数组是长度为零的数组。
  • @ThomasW 我知道这已经有几年历史了,但我需要在您的评论中指出一个错误。 static final 不一定是多余的。当您创建类或接口的对象时,只有 final 关键字的类或接口字段将创建该字段的单独实例。使用static final 将使每个对象共享该字段的内存位置。换句话说,如果一个类 MyClass 有一个字段final String str = "Hello";,对于 MyClass 的 N 个实例,内存中将有 N 个字段 str 的实例。添加 static 关键字只会产生 1 个实例。
  • @Sintrias 是类而不是接口的情况。在接口中声明的任何字段都是隐式静态的。
  • @awgtek 感谢您的更正。我误解了 ThomasW 的评论,不知道接口在 Java 中是如何工作的。

标签: java


【解决方案1】:

这来自于 Java 1.5 还没有出现并将枚举带给我们之前的时间。在此之前,还没有定义一组常量或约束值的好方法。

这仍然在使用,大部分时间要么是为了向后兼容,要么是因为在很多项目中需要大量重构才能摆脱它。

【讨论】:

【解决方案2】:

在 Java 1.5+ 中,您可以使用静态导入从另一个类/接口导入常量/静态方法,而不是实现“常量接口”:

import static com.kittens.kittenpolisher.KittenConstants.*;

这避免了让你的类实现没有功能的接口的丑陋。

至于使用类来存储常量的做法,我认为有时是必要的。有些常量在类中没有自然的位置,因此最好将它们放在“中性”的位置。

但不要使用接口,而是使用带有私有构造函数的最终类。 (使该类无法实例化或子类化,发送一个强烈的信息,表明它不包含非静态功能/数据。)

例如:

/** Set of constants needed for Kitten Polisher. */
public final class KittenConstants
{
    private KittenConstants() {}

    public static final String KITTEN_SOUND = "meow";
    public static final double KITTEN_CUTENESS_FACTOR = 1;
}

【讨论】:

  • 那么,您是在解释,由于静态导入,我们应该使用类而不是接口来重新执行与以前相同的错误?!太傻了!
  • 不,这根本不是我要说的。我说的是两个独立的事情。 1:使用静态导入而不是滥用继承。 2:如果您必须有一个常量存储库,请将其设为最终类而不是接口。
  • “常量接口”不是设计成任何继承的一部分,永远不会。所以静态导入只是为了语法糖,从这样的接口继承是一个可怕的错误。我知道 Sun 这样做了,但他们也犯了很多其他基本错误,这不是模仿他们的借口。
  • 为该问题发布的代码的问题之一是接口实现仅用于更轻松地访问常量。当我看到实现 FooInterface 的东西时,我希望这会影响它的功能,而上面的内容违反了这一点。静态导入解决了这个问题。
  • gizmo - 我不喜欢静态导入,但他所做的是避免使用类名,即 ConstClass.SOME_CONST。进行静态导入不会将这些成员添加到您 Z 的类中。没有说要从接口继承,他说的实际上相反。
【解决方案3】:

这通常被认为是不好的做法。问题是常量是实现类的公共“接口”(为了更好的词)的一部分。这意味着实现类将所有这些值发布到外部类,即使它们仅在内部需要。常量在整个代码中激增。一个例子是 Swing 中的 SwingConstants 接口,它由几十个类实现,所有这些类都“重新导出”所有它的常量(甚至是它们不使用的常量)作为自己的.

但不要只相信我的话,Josh Bloch also says 这很糟糕:

常量接口模式是对接口的不良使用。一个类在内部使用一些常量是一个实现细节。实现一个常量接口会导致这个实现细节泄漏到类的导出 API 中。类实现一个常量接口对类的用户来说无关紧要。事实上,它甚至可能使他们感到困惑。更糟糕的是,它代表了一种承诺:如果在未来的版本中修改了类以使其不再需要使用常量,它仍然必须实现接口以确保二进制兼容性。如果一个非final类实现了一个常量接口,那么它的所有子类的命名空间都会被接口中的常量污染。

枚举可能是更好的方法。或者您可以简单地将常量作为公共静态字段放在无法实例化的类中。这允许另一个类访问它们而不会污染自己的 API。

【讨论】:

  • 枚举在这里是一个红鲱鱼 - 或者至少是一个单独的问题。当然应该使用枚举,但如果实现者不需要它们,也应该隐藏它们。
  • 顺便说一句:您可以使用没有实例的枚举作为无法实例化的类。 ;)
  • 但是为什么要首先实现这些接口呢?为什么不将它们用作常量存储库?如果我需要某种全局共享的常量,我看不到“更清洁”的方法。
  • @DanDyer 是的,但是接口使一些声明隐含。像 public static final 只是一个默认值。为什么要费心上课?枚举 - 好吧,这取决于。枚举应该定义一个实体的可能值集合,而不是不同实体的值集合。
  • 就我个人而言,我觉得乔什击球错误。如果你不希望你的常量泄漏——不管你把它们放在什么类型的对象中——你需要确保它们不是导出代码的一部分。接口或类,都可以导出。所以要问的正确问题不是:我将它们放在什么类型的对象中,而是如何组织这个对象。如果常量在导出的代码中使用,您仍然要确保它们在导出后可用。所以“不良做法”的说法在我看来是无效的。
【解决方案4】:

鉴于事后诸葛亮的优势,我们可以看到 Java 在很多方面都被破坏了。 Java 的一个主要缺点是将接口限制为抽象方法和静态最终字段。更新、更复杂的 OO 语言(如 Scala)通过特征包含接口,这些特征可以(并且通常确实)包括具体方法,这些方法可能具有零值(常量!)。有关将特征作为可组合行为单位的说明,请参阅http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf。有关 Scala 中的特征如何与 Java 中的接口进行比较的简短描述,请参阅http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5。在教授 OO 设计的上下文中,断言接口不应该包含静态字段之类的简单规则是愚蠢的。许多特性自然包含常量,这些常量是特性支持的公共“接口”的适当部分。在编写 Java 代码时,没有一种简洁、优雅的方式来表示特征,但在接口中使用静态 final 字段通常是一种很好的解决方法。

【讨论】:

  • 非常自命不凡,现在已经过时了。
  • 精彩的见解 (+1),虽然对 Java 可能有点过于挑剔。
【解决方案5】:

我不假装权利是正确的,但让我们看看这个小例子:

public interface CarConstants {

      static final String ENGINE = "mechanical";
      static final String WHEEL  = "round";
      // ...

}

public interface ToyotaCar extends CarConstants //, ICar, ... {
      void produce();
}

public interface FordCar extends CarConstants //, ICar, ... {
      void produce();
}

// and this is implementation #1
public class CamryCar implements ToyotaCar {

      public void produce() {
           System.out.println("the engine is " + ENGINE );
           System.out.println("the wheel is " + WHEEL);
      }
}

// and this is implementation #2
public class MustangCar implements FordCar {

      public void produce() {
           System.out.println("the engine is " + ENGINE );
           System.out.println("the wheel is " + WHEEL);
      }
}

ToyotaCar 不知道 FordCar,FordCar 也不知道 ToyotaCar。原则 CarConstants 应该改变,但是...

常量不应该改变,因为轮子是圆的,egine 是机械的,但是... 未来丰田的研究工程师发明了电子发动机和平轮!让我们看看我们的新界面

public interface InnovativeCarConstants {

          static final String ENGINE = "electronic";
          static final String WHEEL  = "flat";
          // ...
}

现在我们可以改变我们的抽象了:

public interface ToyotaCar extends CarConstants

public interface ToyotaCar extends InnovativeCarConstants 

现在,如果我们需要更改 ENGINE 或 WHEEL 的核心值,我们可以在抽象级别更改 ToyotaCar 接口,不要触及实现

它不安全,我知道, 但我还是想知道你有没有想过这个

【讨论】:

  • 我想知道你现在在 2019 年的想法。对我来说,接口字段是为了在某些对象之间共享。
  • 我写了一个与你的想法相关的答案:stackoverflow.com/a/55877115/5290519
  • 这是一个很好的例子,说明 PMD 的规则非常有限。试图通过官僚机构获得更好的代码仍然是徒劳的尝试。
【解决方案6】:

根据 JVM 规范,接口中的字段和方法只能有 Public、Static、Final 和 Abstract。 Ref from Inside Java VM

默认情况下,接口中的所有方法都是抽象的,即使你没有明确提到它。

接口仅用于提供规范。它不能包含任何实现。所以为了避免实现类来改变规范,它是最终的。由于接口无法实例化,因此它们被设为静态以使用接口名称访问字段。

【讨论】:

    【解决方案7】:

    我没有足够的声望给 Pleerock 发表评论,因此我必须创建一个答案。对此我很抱歉,但他付出了一些努力,我想回答他。

    Pleerock,你创建了一个完美的例子来说明为什么这些常量应该独立于接口和继承。对于应用程序的客户来说,这些汽车的实现之间存在技术差异并不重要。它们对客户来说是一样的,只是汽车。所以,客户想从那个角度来看待它们,这是一个像 I_Somecar 这样的接口。在整个应用程序中,客户将只使用一个视角,而不是针对每个不同的汽车品牌使用不同的视角。

    如果客户想在购买前比较汽车,他可以采用这样的方法:

    public List<Decision> compareCars(List<I_Somecar> pCars);
    

    界面是关于行为的契约,从一个角度显示不同的对象。你设计它的方式,每个汽车品牌都会有自己的传承路线。虽然这在现实中是非常正确的,因为汽车可以有很大的不同,就像比较完全不同类型的物体一样,最终在不同的汽车之间存在选择。这就是所有品牌都必须共享的界面视角。常数的选择不应该使这成为不可能。请考虑 Zarkonnen 的回答。

    【讨论】:

      【解决方案8】:

      Java 中有很多人讨厌这种模式。但是,静态常量的接口有时确实有价值。您需要基本满足以下条件:

      1. 这些概念是几个公共接口的一部分 类。

      2. 它们的值可能会在未来的版本中发生变化。

      3. 所有实现都使用相同的值至关重要。

      例如,假设您正在编写一个假设查询语言的扩展。在这个扩展中,您将使用索引支持的一些新操作来扩展语言语法。例如。您将拥有一个支持地理空间查询的 R-Tree。

      所以你用静态常量写了一个公共接口:

      public interface SyntaxExtensions {
           // query type
           String NEAR_TO_QUERY = "nearTo";
      
           // params for query
           String POINT = "coordinate";
           String DISTANCE_KM = "distanceInKm";
      }
      

      现在,一位新开发人员认为他需要构建一个更好的索引,所以他来构建一个 R* 实现。通过在他的新树中实现这个接口,他保证不同的索引在查询语言中具有相同的语法。此外,如果您后来认为“nearTo”是一个令人困惑的名称,您可以将其更改为“withinDistanceInKm”,并知道您的所有索引实现都会遵守新语法。

      PS:这个例子的灵感来自于 Neo4j 空间代码。

      【讨论】:

        猜你喜欢
        • 2013-08-16
        • 1970-01-01
        • 1970-01-01
        • 2012-06-21
        • 1970-01-01
        • 2017-12-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多