与java中的静态字段接口以共享“常量”

Interfaces with static fields in java for sharing 'constants'

我正在研究一些开源Java项目以进入Java,并注意到许多Java项目具有某种"常量"接口。

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

我发现使用一个接口来允许某种伪"全局变量"是很奇怪的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

}


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

但不要只是相信我的话,乔希·布洛赫也说这很糟糕:

The constant interface pattern is a poor use of interfaces. That a class uses some constants internally is an implementation detail. Implementing a constant interface causes this implementation detail to leak into the class's exported API. It is of no consequence to the users of a class that the class implements a constant interface. In fact, it may even confuse them. Worse, it represents a commitment: if in a future release the class is modified so that it no longer needs to use the constants, it still must implement the interface to ensure binary compatibility. If a nonfinal class implements a constant interface, all of its subclasses will have their namespaces polluted by the constants in the interface.

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


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

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

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

至于让一个类只存储常量的实践,我认为有时是必要的。有些常量在类中没有自然位置,所以最好将它们放在"中性"位置。

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

如:

1
2
3
4
5
6
7
8
/** 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
      }
}

丰田章男对福特汽车一无所知,福特汽车对丰田章男一无所知。应更改主carconstants,但…

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

1
2
3
4
5
6
public interface InnovativeCarConstants {

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

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

1
public interface ToyotaCar extends CarConstants

1
public interface ToyotaCar extends InnovativeCarConstants

现在,如果我们需要改变核心价值,如果引擎或轮子,我们可以在抽象级别上改变Toyotacar接口,不要触及实现

不安全,我知道,但我还是想知道你想过这个吗


在爪哇,人们非常讨厌这种模式。然而,静态常量的接口有时确实有值。您需要基本满足以下条件:

  • 这些概念是几个课程。

  • 它们的值在未来的版本中可能会改变。

  • 所有实现都使用相同的值是至关重要的。
  • 例如,假设您正在编写假设查询语言的扩展。在这个扩展中,您将使用一些索引支持的新操作来扩展语言语法。例如,您将拥有一个支持地理空间查询的R-Tree。

    因此,使用静态常量编写公共接口:

    1
    2
    3
    4
    5
    6
    7
    8
    public interface SyntaxExtensions {
         // query type
         String NEAR_TO_QUERY ="nearTo";

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

    现在,一个新的开发人员认为他需要构建一个更好的索引,所以他来构建一个R*实现。通过在他的新树中实现这个接口,他保证不同的索引在查询语言中具有相同的语法。此外,如果您后来决定"nearto"是一个混淆的名称,您可以将其更改为"withIndistanceink",并知道新语法将受到所有索引实现的尊重。

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


    考虑到后见之明的好处,我们可以看到Java在很多方面都被破坏了。Java的一个主要缺点是对抽象方法和静态最终字段的接口限制。新的、更复杂的OO语言,如scala子系统接口,其特征可以(通常也可以)包括具体的方法,这些方法可能具有arity zero(常量!).有关作为可组合行为单位的特征的说明,请参阅http://scg.unibe.ch/archive/papers/scha03atraits.pdf。对于Scala中与Java中的接口相比的特性的简短描述,请参见HTTP://www. COFRITMIT.COM/BLUG/Scala/Scala-Java-难民-PAR-5。在教OO设计的上下文中,断言接口不应该包含静态字段这样简单的规则是愚蠢的。许多特性自然包括常量,这些常量是特性所支持的公共"接口"的适当部分。在编写Java代码时,没有一种简洁、优雅的方式来表示特性,但是在接口中使用静态最终字段通常是一种好的解决方案的一部分。


    我没有足够的声誉给普莱洛克一个评论,因此我必须创造一个答案。我很抱歉,但他很努力,我想回答他。

    Pleerock,您创建了一个完美的例子来说明为什么这些常量应该独立于接口和继承。对于应用程序的客户来说,在实现Cars之间存在技术差异并不重要。客户也一样,只有汽车。因此,客户希望从这个角度来看待它们,这是一个类似于i-somecar的界面。在整个应用程序中,客户将只使用一个透视图,而不针对每个不同的汽车品牌使用不同的透视图。

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

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

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


    根据JVM规范,接口中的字段和方法只能有公共的、静态的、最终的和抽象的。来自Java虚拟机内部的REF

    默认情况下,接口中的所有方法都是抽象的,甚至很难明确地提到。

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


    这是在Java 1.5存在之前,给我们带来了枚举。在此之前,没有好的方法来定义一组常量或约束值。

    在许多项目中,这仍然被使用,大多数时候要么是为了向后兼容性,要么是因为需要进行大量的重构来摆脱。