Java 문화에 관해 말할 수있는 많은 것들이 있지만, 지금 당면한 상황에서는 몇 가지 중요한 측면이 있다고 생각합니다.
- 라이브러리 코드는 한 번 작성되지만 훨씬 더 자주 사용됩니다. 라이브러리 작성의 오버 헤드를 최소화하는 것이 좋지만 장기적으로는 라이브러리 사용의 오버 헤드를 최소화하는 방식으로 작성하는 것이 더 가치가 있습니다.
- 즉, 자체 문서화 유형이 훌륭하다는 것을 의미합니다. 메소드 이름을 사용하면 발생하는 일과 객체에서 발생하는 일을 명확하게 알 수 있습니다.
- 정적 타이핑은 특정 클래스의 오류를 제거하는 데 매우 유용한 도구입니다. 그것은 모든 것을 고치지는 않습니다 (Haskell에 대한 농담을 좋아하는 사람들은 일단 유형 시스템이 코드를 받아들이게되면 아마 정확할 것입니다). 그러나 특정 종류의 잘못된 것을 불가능하게 만드는 것은 매우 쉽습니다.
- 라이브러리 코드 작성은 계약 지정에 관한 것입니다. 인수 및 결과 유형에 대한 인터페이스를 정의하면 계약의 경계가보다 명확하게 정의됩니다. 어떤 것이 튜플을 받아들이거나 생산한다면, 그것이 실제로 수신하거나 생산 해야하는 튜플인지 여부는 없으며, 일반적인 유형에 대한 제약 방식이 거의 없습니다 (적당한 수의 요소가 있습니까?) 그들은 당신이 기대 한 유형입니까?).
필드가있는 "구조"클래스
다른 답변에서 언급했듯이 공개 필드가있는 클래스를 사용할 수 있습니다. 이것을 최종적으로 만들면 불변 클래스를 얻고 생성자로 클래스를 초기화합니다.
class ParseResult0 {
public final long millis;
public final boolean isSeconds;
public final boolean isLessThanOneMilli;
public ParseResult0(long millis, boolean isSeconds, boolean isLessThanOneMilli) {
this.millis = millis;
this.isSeconds = isSeconds;
this.isLessThanOneMilli = isLessThanOneMilli;
}
}
물론 이것은 특정 클래스에 묶여 있고 구문 분석 결과를 생성하거나 소비 해야하는 모든 것이이 클래스를 사용해야한다는 것을 의미합니다. 일부 응용 프로그램의 경우 문제가 없습니다. 다른 사람들에게는 약간의 고통이 생길 수 있습니다. 많은 Java 코드는 계약 정의에 관한 것이며 일반적으로 인터페이스로 연결됩니다.
또 다른 함정은 클래스 기반 접근 방식을 사용하면 필드를 노출하고 해당 필드 모두에 값이 있어야한다는 것입니다. 예를 들어 isLessThanOneMilli가 true 인 경우에도 isSeconds 및 millis는 항상 일부 값을 가져야합니다. isLessThanOneMilli가 true 일 때 millis 필드 값의 해석은 무엇이어야합니까?
인터페이스로서의 "구조"
인터페이스에서 정적 메소드를 허용하면 구문상의 오버 헤드없이 불변 유형을 작성하는 것이 상대적으로 쉽습니다. 예를 들어, 다음과 같은 종류의 결과 구조를 구현할 수 있습니다.
interface ParseResult {
long getMillis();
boolean isSeconds();
boolean isLessThanOneMilli();
static ParseResult from(long millis, boolean isSeconds, boolean isLessThanOneMill) {
return new ParseResult() {
@Override
public boolean isSeconds() {
return isSeconds;
}
@Override
public boolean isLessThanOneMilli() {
return isLessThanOneMill;
}
@Override
public long getMillis() {
return millis;
}
};
}
}
그것은 여전히 많은 상용구이며, 나는 완전히 동의하지만, 몇 가지 이점도 있습니다. 그리고 나는 그들이 당신의 주요 질문에 대답하기 시작한다고 생각합니다.
이 구문 분석 결과와 같은 구조를 사용하면 구문 분석기 의 계약 이 매우 명확하게 정의됩니다. 파이썬에서 한 튜플은 다른 튜플과 실제로 구별되지 않습니다. Java에서는 정적 입력을 사용할 수 있으므로 이미 특정 클래스의 오류를 배제했습니다. 예를 들어 Python에서 튜플을 반환하고 튜플 (millis, isSeconds, isLessThanOneMilli)을 반환하려는 경우 실수로 다음을 수행 할 수 있습니다.
return (true, 500, false)
당신이 의미 할 때 :
return (500, true, false)
이러한 종류의 Java 인터페이스를 사용하면 컴파일 할 수 없습니다.
return ParseResult.from(true, 500, false);
조금도. 너가해야되는:
return ParseResult.from(500, true, false);
이는 정적으로 유형이 지정된 언어의 이점입니다.
이 방법은 또한 얻을 수있는 값을 제한 할 수있는 기능을 제공하기 시작합니다. 예를 들어, getMillis ()를 호출 할 때 isLessThanOneMilli ()가 true인지 여부를 확인할 수 있으며,이 경우 의미있는 값 (millis)이 없으므로 IllegalStateException (예 : IllegalStateException)을 처리하십시오.
잘못된 일을하기 어렵게 만들기
위의 인터페이스 예에서 isSeconds 및 isLessThanOneMilli 인수는 동일한 유형을 갖기 때문에 실수로 바꿀 수있는 문제가 있습니다.
실제로 TimeUnit과 Duration을 실제로 사용하여 다음과 같은 결과를 얻을 수 있습니다.
interface Duration {
TimeUnit getTimeUnit();
long getDuration();
static Duration from(TimeUnit unit, long duration) {
return new Duration() {
@Override
public TimeUnit getTimeUnit() {
return unit;
}
@Override
public long getDuration() {
return duration;
}
};
}
}
interface ParseResult2 {
boolean isLessThanOneMilli();
Duration getDuration();
static ParseResult2 from(TimeUnit unit, long duration) {
Duration d = Duration.from(unit, duration);
return new ParseResult2() {
@Override
public boolean isLessThanOneMilli() {
return false;
}
@Override
public Duration getDuration() {
return d;
}
};
}
static ParseResult2 lessThanOneMilli() {
return new ParseResult2() {
@Override
public boolean isLessThanOneMilli() {
return true;
}
@Override
public Duration getDuration() {
throw new IllegalStateException();
}
};
}
}
그것은 훨씬 더 많은 코드 가 될 것입니다. 하지만 한 번만 작성하면되며 (문서를 올바르게 문서화했다고 가정하면) 코드 를 사용 하는 사람들 은 결과의 의미를 추측 할 필요가 없습니다. result[0]
그들이 뜻할 때 실수로 일을 할 수 없습니다 result[1]
. 여전히 간결하게 인스턴스 를 만들 수 있으며 인스턴스에서 데이터를 가져 오는 것도 그렇게 어려운 것은 아닙니다.
ParseResult2 x = ParseResult2.from(TimeUnit.MILLISECONDS, 32);
ParseResult2 y = ParseResult2.lessThanOneMilli();
실제로 클래스 기반 접근 방식으로 이와 같은 작업을 수행 할 수 있습니다. 다른 경우에 대해 생성자를 지정하십시오. 그래도 다른 필드를 초기화하는 것에 대한 문제가 있지만 필드에 대한 액세스를 막을 수는 없습니다.
또 다른 대답은 Java의 엔터프라이즈 유형 특성으로 인해 이미 존재하는 다른 라이브러리를 작성하거나 다른 사람들이 사용할 라이브러리를 작성한다는 의미입니다. 귀하의 공개 API는 그것을 피할 수있는 경우 결과 유형을 해독 문서를 컨설팅 많은 시간을 필요가 없습니다.
당신은 쓰기 한 번 이러한 구조를,하지만 당신은 만들 그들에게 여러 번, 그래서 당신은 여전히 (당신이 얻을)이 간결하게 만들 싶어. 정적 타이핑을 사용하면 데이터에서 얻는 데이터가 예상 한 것입니다.
이제는 간단한 튜플이나 목록이 많은 의미를 가질 수있는 곳이 여전히 있습니다. 무언가의 배열을 반환하는 데 오버 헤드가 적을 수 있으며 그 경우 (그리고 프로파일 링으로 결정한 오버 헤드가 중요한 경우) 내부적 으로 간단한 값 배열을 사용하면 많은 의미가 있습니다. 공개 API에는 여전히 명확하게 정의 된 유형이 있어야합니다.