'상수'공유를위한 자바의 정적 필드와 인터페이스


116

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

}

15
참고 : static final필요하지 않으며 인터페이스에 대해 중복됩니다.
ThomasW

또한주의 platformNames할 수있다 public, static그리고 final, 그러나 확실히 일정하지 않습니다. 유일한 상수 배열은 길이가 0 인 배열입니다.
Vlasec

@ThomasW 나는 이것이 몇 년 된 것을 알고 있지만 귀하의 의견에서 오류를 지적해야했습니다. static final반드시 중복되는 것은 아닙니다. final키워드 만있는 클래스 또는 인터페이스 필드 는 클래스 또는 인터페이스의 개체를 만들 때 해당 필드의 별도 인스턴스를 만듭니다. 를 사용 static final하면 각 개체가 해당 필드에 대한 메모리 위치를 공유하게됩니다. 즉, MyClass 클래스에 필드가있는 final String str = "Hello";경우 MyClass의 N 인스턴스에 대해 메모리에 필드 str의 N 인스턴스가 있습니다. static키워드를 추가하면 인스턴스가 1 개만 생성됩니다.
Sintrias

답변:


160

일반적으로 나쁜 습관으로 간주됩니다. 문제는 상수가 구현 클래스의 공용 "인터페이스"(더 나은 단어를 원하는 경우)의 일부라는 것입니다. 이는 구현 클래스가 내부적으로 만 필요한 경우에도 이러한 모든 값을 외부 클래스에 게시 함을 의미합니다. 상수는 코드 전체에서 확산됩니다. 예를 들어 Swing 의 SwingConstants 인터페이스는 모든 상수 (사용하지 않는 상수도 포함)를 자체적으로 "다시 내보내는"수십 개의 클래스에 의해 구현됩니다 .

그러나 내 말을 받아들이지 마십시오. Josh Bloch는 또한 그것이 나쁘다고 말합니다 .

상수 인터페이스 패턴은 인터페이스를 제대로 사용하지 않는 것입니다. 클래스가 내부적으로 일부 상수를 사용한다는 것은 구현 세부 사항입니다. 상수 인터페이스를 구현하면이 구현 세부 사항이 클래스의 내 보낸 API로 유출됩니다. 클래스가 상수 인터페이스를 구현하는 것은 클래스 사용자에게 중요하지 않습니다. 사실, 혼란 스러울 수도 있습니다. 더 나쁜 것은 약속을 나타냅니다. 향후 릴리스에서 더 이상 상수를 사용할 필요가 없도록 클래스가 수정되는 경우에도 이진 호환성을 보장하기 위해 인터페이스를 구현해야합니다. 최종 클래스가 아닌 클래스가 상수 인터페이스를 구현하는 경우 모든 하위 클래스는 인터페이스의 상수에 의해 네임 스페이스가 오염됩니다.

열거 형이 더 나은 방법 일 수 있습니다. 또는 인스턴스화 할 수없는 클래스의 공용 정적 필드로 상수를 간단히 넣을 수 있습니다. 이를 통해 다른 클래스가 자체 API를 오염시키지 않고 액세스 할 수 있습니다.


8
열거 형은 여기서 붉은 청어 또는 적어도 별도의 질문입니다. 물론 열거 형을 사용해야하지만 구현자가 필요로하지 않는 경우에는 숨겨야합니다.
DJClayworth

12
BTW : 인스턴스가없는 열거 형을 인스턴스화 할 수없는 클래스로 사용할 수 있습니다. ;)
Peter Lawrey 2010

5
하지만 왜 처음부터 이러한 인터페이스를 구현할까요? 상수 저장소로 사용하지 않는 이유는 무엇입니까? 전 세계적으로 공유되는 어떤 종류의 상수가 필요하다면 "더 깨끗한"방법이 보이지 않습니다.
shadox

2
@DanDyer 예, 그러나 인터페이스는 일부 선언을 암시 적으로 만듭니다. 공개 정적 결승 과 마찬가지로 기본입니다. 수업에 왜 귀찮게? Enum-글쎄, 그것은 다릅니다. 열거 형은 다른 항목에 대한 값 모음이 아니라 항목에 대해 가능한 값 모음을 정의해야합니다.
shadox

4
개인적으로 조쉬가 공을 잘못 치고 있다고 느낍니다. 어떤 유형의 객체를 넣었는지에 관계없이 상수가 누출되는 것을 원하지 않으면 내 보낸 코드의 일부가 아닌지 확인해야합니다. 인터페이스 또는 클래스, 둘 다 내보낼 수 있습니다. 따라서 질문해야 할 올바른 질문은 아닙니다. 어떤 유형의 물건에 넣어야하는데이 물건을 어떻게 구성해야하는지입니다. 내 보낸 코드에서 상수를 사용하는 경우에도 내 보낸 후에 사용할 수 있는지 확인해야합니다. 그래서 "나쁜 관행"이라는 주장은 내 겸손한 견해로는 유효하지 않습니다.
Lawrence

99

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;
}

따라서 정적 가져 오기 때문에 이전과 동일한 오류를 다시 수행하려면 인터페이스 대신 클래스를 사용해야한다고 설명하는 것입니다. 어리 석다!
거시기

11
아니, 그건 내가 말하는 게 아니야. 나는 두 가지 독립적 인 것을 말하고 있습니다. 1 : 상속을 남용하는 대신 정적 가져 오기를 사용합니다. 2 : 상수 저장소가 있어야하는 경우 인터페이스 대신 최종 클래스로 만듭니다.
Zarkonnen

상속의 일부로 설계되지 않은 "상수 인터페이스"는 절대로 아닙니다. 따라서 정적 가져 오기는 구문 설탕에 대한 것이며 이러한 인터페이스에서 상속하는 것은 끔찍한 오류입니다. 나는 썬이 이것을했다는 것을 알고있다. 그러나 그들은 또한 수많은 다른 기본적인 오류를 만들었다. 그것은 그들을 모방 할 변명이 아니다.
gizmo

3
질문에 게시 된 코드의 문제점 중 하나는 인터페이스 구현이 상수에 더 쉽게 액세스 할 수 있도록 사용된다는 것입니다. 내가 FooInterface를 구현하는 것을 보았을 때 그 기능에 영향을 미칠 것으로 예상하고 위의 내용은 이것을 위반합니다. 정적 가져 오기는이 문제를 해결합니다.
Zarkonnen

2
기즈모-나는 정적 가져 오기의 팬이 아니지만 그가하고있는 일은 클래스 이름, 즉 ConstClass.SOME_CONST를 사용하는 것을 피하는 것입니다. 정적 가져 오기를 수행하는 것은 Z.가 인터페이스에서 상속한다고 말하지 않는 클래스에 해당 멤버를 추가하지 않습니다. 그는 사실 그 반대라고 말합니다.
mtruesdell

8

나는 옳은 척하지 않지만이 작은 예를 봅시다.

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이 기계식이기 때문에 상수를 변경해서는 안되지만 ... 미래에 Toyota의 연구 엔지니어는 전자 엔진과 평면 바퀴를 발명했습니다! 우리의 새로운 인터페이스를 보자

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 년에 여러분의 아이디어를 알고 싶습니다. 저에게 인터페이스 필드는 일부 개체간에 공유되어야합니다.
Raining

나는 당신의 아이디어와 관련된 답변을 썼습니다 : stackoverflow.com/a/55877115/5290519
Raining

이것은 PMD의 규칙이 얼마나 제한적인지 보여주는 좋은 예입니다. 관료주의를 통해 더 나은 코드를 얻으려는 시도는 헛된 시도로 남아 있습니다.
bebbo

6

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"으로 변경할 수 있으며 모든 인덱스 구현에서 새 구문이 존중된다는 것을 알 수 있습니다.

추신 :이 예제의 영감은 Neo4j 공간 코드에서 가져 왔습니다.


5

돌이켜 보면 Java가 여러면에서 망가 졌음을 알 수 있습니다. Java의 주요 실패 중 하나는 추상 메서드 및 정적 최종 필드에 대한 인터페이스 제한입니다. Scala와 같은 새롭고 더 정교한 OO 언어는 특성에 따라 인터페이스를 포함 할 수 있으며 일반적으로 인수가 0 (상수!) 일 수있는 구체적인 메서드를 포함 할 수 있습니다. 구성 가능한 동작의 단위로서 특성에 대한 설명은 http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf를 참조 하십시오 . Scala의 특성이 Java의 인터페이스와 어떻게 비교되는지에 대한 간략한 설명은 http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5를 참조하십시오 .. OO 디자인을 가르치는 맥락에서 인터페이스가 정적 필드를 포함해서는 안된다고 주장하는 것과 같은 단순한 규칙은 어리 석습니다. 많은 특성은 자연스럽게 상수를 포함하며 이러한 상수는 특성이 지원하는 공용 "인터페이스"의 일부입니다. Java 코드를 작성할 때 특성을 표현하는 깔끔하고 우아한 방법은 없지만 인터페이스 내에서 정적 최종 필드를 사용하는 것은 종종 좋은 해결 방법의 일부입니다.


12
끔찍하게 허식 적이며 요즘은 구식입니다.
Esko

1
훌륭한 통찰력 (+1)이지만 Java에 대해 너무 비판적 일 수 있습니다.
peterh - 분석 재개 모니카

0

JVM 사양에 따라 인터페이스의 필드와 메서드는 Public, Static, Final 및 Abstract 만 가질 수 있습니다. 내부 Java VM의 참조

기본적으로 인터페이스의 모든 메소드는 명시 적으로 언급하지 않았더라도 추상적입니다.

인터페이스는 사양 만 제공하기위한 것입니다. 어떤 구현도 포함 할 수 없습니다. 따라서 사양을 변경하는 클래스 구현을 피하기 위해 최종적으로 작성됩니다. Interface는 인스턴스화 할 수 없기 때문에 인터페이스 이름을 사용하여 필드에 액세스하기 위해 정적으로 만들어집니다.


0

나는 Pleerock에 코멘트를 할만큼 평판이 충분하지 않기 때문에 답을 만들어야합니다. 미안하지만 그는 그것에 대해 좋은 노력을 기울 였고 나는 그에게 대답하고 싶습니다.

Pleerock, 당신은 왜 이러한 상수가 인터페이스로부터 독립적이고 상속으로부터 독립적이어야하는지 보여주는 완벽한 예제를 만들었습니다. 응용 프로그램의 클라이언트에게는 이러한 자동차 구현간에 기술적 차이가 있다는 것이 중요하지 않습니다. 클라이언트도 마찬가지입니다. 자동차 만 있습니다. 그래서 클라이언트는 I_Somecar와 같은 인터페이스 인 그 관점에서 그것들을보고 싶어합니다. 응용 프로그램 전체에서 클라이언트는 각기 다른 자동차 브랜드에 대해 다른 관점이 아닌 하나의 관점 만 사용합니다.

고객이 구매하기 전에 자동차를 비교하려는 경우 다음과 같은 방법을 사용할 수 있습니다.

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

인터페이스는 행동에 대한 계약이며 한 관점에서 다른 객체를 보여줍니다. 당신이 디자인하는 방식에 따라 모든 자동차 브랜드는 고유 한 상속을 받게됩니다. 실제로는 매우 정확하지만, 자동차는 완전히 다른 유형의 물체를 비교하는 것과 같을 수 있다는 점에서 차이가있을 수 있기 때문에 결국에는 서로 다른 자동차 사이의 선택이 있습니다. 이것이 모든 브랜드가 공유해야하는 인터페이스의 관점입니다. 상수의 선택이 이것을 불가능하게해서는 안됩니다. Zarkonnen의 대답을 고려하십시오.


-1

이것은 Java 1.5가 존재하기 전부터 우리에게 열거 형을 가져 왔습니다. 그 이전에는 상수 또는 제한된 값 세트를 정의하는 좋은 방법이 없었습니다.

이것은 대부분의 경우 이전 버전과의 호환성이나 많은 프로젝트에서 제거하는 데 필요한 리팩토링의 양으로 인해 여전히 사용됩니다.


2
Java 5 이전에는 type-safe enum 패턴을 사용할 수있었습니다 ( java.sun.com/developer/Books/shiftintojava/page1.html 참조 ).
Dan Dyer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.