2012-04-12 2 views
3

그래, 나는 일반 JostTime 라이브러리를 사용하여 소매/회계 (4-5-4) 일정을 구현하려고 시도했다. 나는 이미 내 회사에 대한 구체적인 사례를 찾았지만 일반적인 경우 (주로 시작 연도와 윤년을 결정할 때)가 살인자입니다. 예를 들어 1 ISO 연도 동안 2 회계 연도 (보통 364 일, 보통)가 시작되는 날짜 집합이 있습니다.생성자 종료 전에 최종 변수에 액세스

올해 시작 규칙을 결정하는 과정에서 나는 ISO 시작일의 어느 시점에 기초하는지에 따라 연도 시작을 결정하기위한 추상 클래스와 몇 가지 구체적인 클래스를 작성했습니다.

(깎았 다운) 추상 클래스 (범위의 1/7에 날짜 - 2/28)

private static abstract class SimpleFiscalYearEndPattern implements FiscalYearEndPattern { 

    protected final int leapYearCountOffset; 
    protected final int doomsdayOffset; 

    private final int startingDayOfWeek; 
    private final int yearOffset; 
    private final long millisFromEpochToFiscalYearStart; 
    private final long millisElapsedToEpochDividedByTwo; 

    /** 
    * Restricted constructor 
    * @param fiscalYear 
    * @param startingOn 
    * @param inFirstWeek 
    */ 
    protected SimpleFiscalYearEndPattern(final int fiscalYear, final LocalDate startingOn, final MonthDay inFirstWeek) { 
     this.yearOffset = fiscalYear - startingOn.getYear(); 
     this.doomsdayOffset = getDoomsdayOffset(inFirstWeek); 
     this.startingDayOfWeek = startingOn.getDayOfWeek(); 

     final int startingDoomsday = getDoomsdayOffset(new MonthDay(startingOn, REFERENCE_CHRONOLOGY)); 
     // If the starting doomsday is a later day-of-week, it needs to become negative. 
     this.leapYearCountOffset = calculateLeapYearCountOffset(startingDoomsday : doomsdayOffset, doomsdayOffset); 

     final int leapYearsBefore = getPreviousLeapYears(fiscalYearBeforeEpoch); 
    } 
} 

(아래 깎았) 콘크리트 클래스 :

private static final class BeforeLeapYearEndPattern extends SimpleFiscalYearEndPattern { 

    private static final int FIRST_YEAR_LEAP_YEAR_OFFSET = -1; 

    private BeforeLeapYearEndPattern(final int fiscalYear, final LocalDate startingOn, final MonthDay onOrBefore) { 
     super(fiscalYear, startingOn, onOrBefore); 
    } 

    public static final BeforeLeapYearEndPattern create(final int fiscalYear, final LocalDate startingOn, final MonthDay onOrBefore) { 
     return new BeforeLeapYearEndPattern(fiscalYear, startingOn, onOrBefore); 
    } 

    /* (non-Javadoc) 
    * @see ext.site.time.chrono.FiscalYearEndPatternBuilder.SimpleFiscalYearEndPattern#getPreviousLeapYears(int) 
    */ 
    @Override 
    protected int getPreviousLeapYears(final int isoYear) { 
     // Formula gets count of leap years, including current, so subtract a year first. 
     final int previousYear = isoYear - 1; 
     // If the doomsday offset is -1, then the first year is a leap year. 
     return (previousYear + leapYearCountOffset + (previousYear/4) - (previousYear/100) + (previousYear/400))/7 + (leapYearCountOffset == FIRST_YEAR_LEAP_YEAR_OFFSET ? 1 : 0); 
    } 

이 있을지 예를 들어, 추상 수퍼 클래스에서 (최종 변수로) getPreviousLeapYears()에 정의되어있는 수퍼 클래스 생성자에서 호출 된 leapYearCountOffset을 사용합니다. 수퍼 클래스 생성자에서 수식을 반복하고 싶지는 않습니다. 3/1-12/31 범위의 날짜에서는 다릅니다. 구체적인 하위 클래스에 인스턴스 변수를 배치하고 싶지는 않습니다. 다른 계산에는 여전히 leapYearCountOffset이 필요합니다.

질문 : (하위 클래스) 메서드가 생성자에서 호출 될 때 leapYearCountOffset의 상태는 무엇입니까? 그것은 어떤 방식 으로든 보장됩니까? 아니면 컴파일러의 어딘가에서 변할 수있는 무언가입니까? 그리고 그것을 어떻게 알아낼 수 있을까요? 나는 이미 컴파일러가 일부 문장을 자유롭게 배열 할 수 있다는 것을 알고 있지만, 여기서 일어날 것인가?

답변

2

, leapYearCountOffset 적절히 초기화되며 getPreviousLeapYears 올바른 값을 참조한다.


자바는 생성자 중에 호출 코드에 의해 액세스 final 변수가 제대로 처음 액세스하기 전에 초기화 보장의 부담을 떠난다. 제대로 초기화되지 않으면 생성자 중에 호출되는 코드는 해당 필드의 유형에 대한 0 값을 보게됩니다.

프로그램

public class Foo { 
    protected final int x; 
    Foo() { 
    foo(); 
    this.x = 1; 
    foo(); 
    } 
    void foo() { System.out.println(this.x); } 
    public static void main(String[] argv) { new Foo(); } 
} 

인쇄 x 이후

0 
1 

foo의 최초의 호출시에 초기화되지 않지만, 위에서 설명한대로,이 문제가 없습니다.


는 JLS는 생성자에서 최종 의 모든 사용은 초기화 이후 여야 있다고하지만, 다른 방법에 대한 이러한 보증을하지 않습니다.x 모든 사용하기 전에 초기화 될 수 있도록 언어에 대한

abstract class C { 
    public final int x; 

    C() { 
    this.x = f(); 
    } 

    abstract int f(); 
} 

을 고려, 그것은이와 상충 될 클래스에 대한 글로벌 추론을 필요로

class D extends C { 
    int f() { 
    return this.x; 
    } 
} 

처럼 어떤 서브 클래스가 존재하지 않음을 확인해야합니다 Java의 동적 인 연결은 언어 사양에 많은 복잡성을 추가합니다.

+0

그러나 성능 고려 사항을 위해 (구현에 따라) 컴파일러가 배정을 재조정 할 수 있습니까? 그래서, 내 순서가 그 순서대로 평가되도록 보장 받는가? (내가 원하는 것을 잡을만큼 '똑똑하다')? 아니면 대신'0'으로 끝낼 수 있을까요? –

+0

@ X-Zero, 컴파일러는 프로그램 의미를 변경하지 않는 재배정 만 허용됩니다. –

+0

아, 그럼 나는 잘되어야합니다. @ Joni의 관심사도 생각하고 있습니다. –

4

final 변수 중 하나는 컴파일러가 할당되기 전에 컴파일러에서 액세스하지 못하도록하는 것입니다. 그래서, 그것이 컴파일되면 (어느 쪽이 좋을까요), 여러분은 가야합니다! leapYearCountOffset 할당 후 getPreviousLeapYears 호출되므로

+0

메서드 호출에는 해당되지 않습니다. 이유를 설명하는 프로그램에 대한 내 대답을 참조하십시오. –

+0

매혹적인 ... 오늘 새로운 것을 배우기위한 +1. – stevevls

0

leapYearCountOffset은 최종 값이 보장되지만 이는 여전히 우연히 발생하는 사고입니다. 메소드 getPreviousLeapYears은 서브 클래스 초기화가 시작되기 전에 실행되므로 서브 클래스의 모든 변수는 기본값 (0 또는 null)을가집니다.

지금은 위험하지 않지만 누군가가 들어 와서 BeforeLeapYearEndPattern을 변경하면 getPreviousLeapYears에 사용되는 final 인스턴스 변수를 새로 추가하면 상처를 입을 수 있습니다.

+0

그래서, 익명 메소드를 사용하여 수퍼 클래스 생성자 (또는 유사)로 전달 했습니까? 이제는 함수를 일류 객체로 사용할 수 없도록하는 것입니다. –

+0

일반적인 해결책은 사물을 리팩터링하여 값이 실제로 필요할 때 하위 클래스 메소드를 지연 호출하는 것입니다. 클로저는 도움이되지만 필요하지 않습니다. – Joni

+0

흠, 내가하고있는 일을 조정할 필요가 있다고 생각합니다. 게으른 로딩은 좋지만, 나는 그 값을 꽤 많이 필요로하므로 ... –

0

이 질문은 intrathread와 interthread 의미 사이의 혼동으로 인해 발생합니다.

단일 스레드에서 문제의 코드를 실행하는 한 모든 것은 예상대로 작동합니다. 코드 재정렬의 가시적 인 효과는 없을 수 있습니다.

final 필드에 대해서도 마찬가지입니다. final 필드는 동시 액세스에 대한 추가 보장을 제공하며 이러한 보장은 생성자 완료 후에 만 ​​적용됩니다. 그렇기 때문에 생성자가 완료되기 전에 final 필드를 다른 스레드가 액세스 할 수 있도록하지 않는 것이 좋습니다. 그러나 다른 스레드에서 해당 필드에 액세스하려고 시도하지 않는 한 중요하지 않습니다.

그러나 서브 클래스 필드가 해당 지점에서 초기화되지 않았기 때문에 슈퍼 클래스의 constuctor에서 서브 클래스 메서드를 호출하는 것은 좋지 않은 방법입니다.

+0

무엇? 기본 클래스 부분은 파생 클래스보다 먼저 초기화해야합니다. –