3

さて、私は優れたJodaTimeライブラリをいじり回して、一般的な小売/会計(4-5-4)カレンダーを実装しようと試みてきました。私はすでに私の会社の特定のケースを見つけましたが、一般的なケース(主に年の初めとうるう年の決定)はキラーです。たとえば、2会計年度(通常は364日)が1ISO年の間に開始される一連の日付があります。

年の始まりのルールを決定する過程で、私は、ISOの飛躍日のどちら側に該当するかに基づいて、年の始まりを決定するための抽象クラスといくつかの具体的なクラスに行き着きました。

(簡素化された)抽象クラス:

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

(簡素化された)具体的なクラス(1/7から2/28の範囲の日付の場合):

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

お気づきの方もいらっしゃると思いますがleapYearCountOffset、抽象スーパークラスで(最終変数として)定義されている、を使用します。これはgetPreviousLeapYears()、スーパークラスコンストラクターから呼び出されます。スーパークラスコンストラクターで式を繰り返したくありません。3/1〜12/31の範囲の日付では異なります。また、インスタンス変数を具象サブクラスに配置したくありません。他の計算にはまだが必要leapYearCountOffsetです。

質問は次のとおりです。leapYearCountOffset(サブクラス)メソッドがコンストラクターから呼び出されたときの状態はどうなっていますか?それは何らかの形で保証されていますか、それともコンパイラの気まぐれで変更される可能性があるものですか?そして、一体どうやってそれをテストして見つけることができますか?コンパイラーがいくつかのステートメントを自由に再配置できることはすでに知っていますが、それはここで起こりますか?

4

4 に答える 4

4

変数の保証の1つは、final変数が割り当てられる前にコンパイラーが変数にアクセスできないようにすることです。だから、それがコンパイルされれば(それはそうあるべきです)、あなたは行ってもいいです!

于 2012-04-12T18:26:11.100 に答える
2

が割り当てられgetPreviousLeapYearsた後に呼び出されるため、適切に初期化され、正しい値が表示されます。leapYearCountOffsetleapYearCountOffsetgetPreviousLeapYears


finalJavaは、コンストラクター中に呼び出されたコードによってアクセスされる変数が、最初のアクセスの前に適切に初期化されることを保証する負担を残します。適切に初期化されていない場合、コンストラクター中に呼び出されたコードは、そのフィールドの型のゼロ値を参照します。

プログラム

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

プリント

0
1

xの最初の呼び出しでは初期化されないためですfooが、上記で説明したように、この問題は発生しません。


JLSによると、コンストラクターでのfinalの使用はすべて初期化後に行う必要がありますが、他のメソッドについてはそのような保証はありません。検討

abstract class C {
  public final int x;

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

  abstract int f();
}

言語がx使用する前に必ず初期化されるようにするには、次のようなサブクラスが存在しないことを確認する必要があります。

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

これには、Javaの動的リンクと矛盾し、言語仕様に多くの複雑さを追加するクラスに関するグローバルな推論が必要です。

于 2012-04-12T18:26:22.417 に答える
0

最終的な価値がleapYearCountOffsetあることは保証されていますが、これはまだ起こるのを待っている事故です。このメソッドgetPreviousLeapYearsはサブクラスの初期化が開始される前に実行されるため、サブクラスの変数はすべてデフォルト値(0またはnull)になります。

今のところ危険はありませんが、誰かが入って変更した場合BeforeLeapYearEndPattern、おそらくで使用される新しいfinalインスタンス変数を追加することによってgetPreviousLeapYears、あなたは怪我をするでしょう。

于 2012-04-12T18:37:54.297 に答える
0

この質問は、スレッド内とスレッド間のセマンティクスの混同が原因のようです。

問題のコードを単一のスレッドで実行する限り、すべてが期待どおりに機能します。コードの並べ替えによる目に見える影響はありません。

同じことがフィールドにも当てはまりfinalます。finalフィールドは同時アクセスの追加の保証を提供し、これらの保証はコンストラクターの完了後にのみ有効になります。finalそのため、コンストラクターが完了する前に、他のスレッドがフィールドにアクセスできるようにすることはお勧めしません。ただし、他のスレッドから問題のフィールドにアクセスしようとしない限り、問題はありません。

ただし、スーパークラスのconstuctorからサブクラスメソッドを呼び出すことは、その時点でサブクラスフィールドが初期化されていないため、悪い習慣であることに同意します。

于 2012-04-12T18:48:14.163 に答える