与java中的静态字段接口,用于共享';常数';

与java中的静态字段接口,用于共享';常数';,java,Java,我正在研究一些开源Java项目来进入Java,并注意到其中许多项目都有某种“常量”接口 例如,有一个名为的接口,大多数其他核心类都实现这个接口。该接口充满了静态成员。这种做法是有原因的,还是被认为是不好的做法?为什么不在有意义的地方使用枚举或静态类呢 我觉得使用接口来允许某种伪“全局变量”是很奇怪的 public interface PConstants { // LOTS OF static fields... static public final int SHINE = 31;

我正在研究一些开源Java项目来进入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

}

这源于Java1.5出现之前的一段时间,它为我们带来了枚举。在此之前,没有定义一组常量或约束值的好方法

在很多项目中,这仍然被使用,大部分时间是为了向后兼容,或者是因为需要进行大量重构才能摆脱。在Java 1.5+中,您可以使用静态导入从另一个类/接口导入常量/静态方法,而不是实现“常量接口”:

import static com.kittens.kittenpolisher.KittenConstants.*;
这避免了让类实现没有功能的接口的尴尬

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

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

例如:


这通常被认为是坏习惯。问题是常量是实现类的公共“接口”(因为缺少更好的词)的一部分。这意味着实现类正在将所有这些值发布到外部类,即使它们仅在内部需要。常数在整个代码中不断增加。Swing中的接口就是一个例子,它由几十个类实现,这些类都“重新导出”其所有常量(甚至是它们不使用的常量)

但不要相信我的话,这很糟糕:

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


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

鉴于事后诸葛亮的优势,我们可以看到Java在许多方面被破坏了。Java的一个主要缺陷是将接口限制为抽象方法和静态final字段。较新的、更复杂的面向对象语言(如Scala)将接口包含在特征中,这些特征可以(而且通常确实)包含具体的方法,这些方法可能具有算术零(常量!)。有关特性作为可组合行为单元的说明,请参见。有关Scala中的特性与Java中的接口的比较的简短描述,请参阅。在教授OO设计的背景下,简单的规则,比如断言接口永远不应该包含静态字段是愚蠢的。许多trait自然包含常量,这些常量是trait支持的公共“接口”的适当部分。在编写Java代码时,没有干净、优雅的方式来表示特性,但在接口中使用静态final字段通常是一个很好的解决方法。

我并不假装正确,但让我们看看这个小示例:

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);
      }
}
丰田汽车对福特汽车一无所知,福特汽车也不知道丰田汽车。原则常数应该改变,但是

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

public interface InnovativeCarConstants {

          static final String ENGINE = "electronic";
          static final String WHEEL  = "flat";
          // ...
}
现在我们可以改变我们的抽象:

public interface ToyotaCar extends CarConstants

现在如果我们需要改变引擎或轮子的核心价值,我们可以在抽象层上改变丰田汽车的接口,而不涉及实现

我知道这不安全,
但是我仍然想知道,您是否考虑过这个问题,根据JVM规范,接口中的字段和方法只能有Public、Static、Final和Abstract

默认情况下,接口中的所有方法都是抽象的,即使您没有明确提到它也很困难


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

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

Pleerock,您创建了一个完美的示例来说明为什么这些常量应该独立于接口和继承。对于应用程序的客户端来说,cars实现之间的技术差异并不重要。客户也一样,只有汽车。因此,客户希望从这个角度来看待它们,这是一个类似I_Someca的接口
public interface ToyotaCar extends CarConstants
public interface ToyotaCar extends InnovativeCarConstants 
public List<Decision> compareCars(List<I_Somecar> pCars);
public interface SyntaxExtensions {
     // query type
     String NEAR_TO_QUERY = "nearTo";

     // params for query
     String POINT = "coordinate";
     String DISTANCE_KM = "distanceInKm";
}