简体   繁体   中英

Accessing a final variable before constructor exit

Okay, I've been messing around with the excellent JodaTime library, attempting to get a general-case retail/fiscal (4-5-4) calendar implemented. I've already found the specific case for my company, but the general cases (mostly in determining start-of-year, and leap years) is killer; for instance, there's a set of dates whereby two fiscal years (364 days long, normally) will start during 1 ISO year.

In the process of determining year-start rules, I ended up with an abstract class and a couple of concrete classes for determining year starts, based on which side of the ISO leap-day they fall.

(pared-down) abstract class:

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

(pared down) concrete class (for dates in range 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);
    }

If you'll notice, I use leapYearCountOffset , which is defined (As a final variable) in the abstract super class, in getPreviousLeapYears() , which is then called from the super-class constructor. I don't want to repeat the formula in the super-class constructor - it's different for dates in the range 3/1-12/31; nor do I want to place the instance variable in the concrete subclass - the other calculation still needs leapYearCountOffset .

The Question Is: What is the state of leapYearCountOffset at the time the (subclass) method is called from the constructor? Is it guaranteed in any way, or is that something that could change at the whim of the compiler? And how the heck could I test that to find out? I'm already aware that the compiler is free to re-arrange some statements, but would (could?) that happen here?

One of the guarantees of final variables is that the compiler won't let you access them before they're assigned. So, if it compiles (which it should), you're good to go!

Since the getPreviousLeapYears is called after leapYearCountOffset is assigned, leapYearCountOffset will be properly initialized and getPreviousLeapYears will see the correct value.


Java leaves the burden of ensuring that final variables that are accessed by code called during the constructor are properly initialized before first access. If not properly initialized, code called during the constructor will see the zero value for that field's type.

The program

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

prints

0
1

since x is not initialized during the first call to foo , but as explained above, you don't have this problem.


The JLS says that every use of a final in the constructor must be after initialization but makes no such guarantee for other methods. Consider

abstract class C {
  public final int x;

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

  abstract int f();
}

For the language to ensure that x is initialized before every use, it would need to ensure that no subclass exists like

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

This requires global reasoning about classes which would be at odds with Java's dynamic linking and add a lot of complexity to the language specification.

While leapYearCountOffset is guaranteed to have its final value, this is still an accident waiting to happen. The method getPreviousLeapYears is executed before the subclass initialization has started, so any variables from the subclass will have their default values (0 or null).

Right now there is no danger, but if someone comes in and changes BeforeLeapYearEndPattern , maybe by adding a new final instance variable, which is then used in getPreviousLeapYears , you will get hurt.

It looks like this question is caused by confusion between intrathread and interthread semantics.

As long as you run the code in question in a single thread everything works as you would expect: there can be no visible effects of code reordering.

The same is true for final fields. final fields provide additional guarantees for concurrent access, and these guarantees only take effect after completion of constructors. That's why it's not recommended to make final fields accessible to other threads before completion of constructor. But it doesn't matter as long as you don't try to access the field in question from other threads.

However, I agree that calling subclass methods from constuctor of a superclass is a bad practice because subclass fields are not initialized at that point.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM