简体   繁体   English

在构造函数退出之前访问最终变量

[英]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. 好吧,我一直在搞乱优秀的JodaTime库,试图实现一般案例零售/财政(4-5-4)日历。 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. 例如,有一组日期,其中两个会计年度(通常为364天)将在1年ISO期间开始。

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. 在确定年度开始规则的过程中,我最终得到了一个抽象类和几个具体的类来确定年份的开始,这取决于它们落在哪个ISO跳跃日。

(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): (减少)具体类(适用于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. 如果您注意到,我使用leapYearCountOffset ,它在抽象超类中定义(作为最终变量),在getPreviousLeapYears() ,然后从超类构造函数调用。 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; 我不想在超类构造函数中重复公式 - 它对于3 / 1-12 / 31范围内的日期不同; nor do I want to place the instance variable in the concrete subclass - the other calculation still needs leapYearCountOffset . 我也不想将实例变量放在具体的子类中 - 另一个计算仍然需要leapYearCountOffset

The Question Is: What is the state of leapYearCountOffset at the time the (subclass) method is called from the constructor? 问题是:从构造函数调用(子类)方法时leapYearCountOffset的状态什么? 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. final变量的一个保证是编译器在分配之前不允许您访问它们。 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. 由于getPreviousLeapYears之后被称为leapYearCountOffset分配, leapYearCountOffset将正常初始化, getPreviousLeapYears将看到正确的值。


Java leaves the burden of ensuring that final variables that are accessed by code called during the constructor are properly initialized before first access. Java承担了确保在第一次访问之前正确初始化构造函数期间调用的代码访问的final变量的负担。 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. 因为在第一次调用foo没有初始化x ,但如上所述,你没有这个问题。


The JLS says that every use of a final in the constructor must be after initialization but makes no such guarantee for other methods. JLS表示构造函数中每个final的使用必须在初始化之后,但对其他方法没有这样的保证。 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 对于确保在每次使用之前初始化x的语言,需要确保不存在类似的子类

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. 这需要对类的全局推理,这与Java的动态链接不一致,并为语言规范增加了很多复杂性。

While leapYearCountOffset is guaranteed to have its final value, this is still an accident waiting to happen. 虽然leapYearCountOffset保证有最终值,但这仍然是一个等待发生的事故。 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). 方法getPreviousLeapYears在子类初始化开始之前执行,因此子类中的任何变量都将具有其默认值(0或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. 现在没有危险,但是如果有人进来并改变BeforeLeapYearEndPattern ,可能通过添加一个新的final实例变量,然后在getPreviousLeapYears ,你会受到伤害。

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字段也是如此。 final fields provide additional guarantees for concurrent access, and these guarantees only take effect after completion of constructors. final字段为并发访问提供了额外的保证,这些保证仅在构造函数完成后生效。 That's why it's not recommended to make final fields accessible to other threads before completion of constructor. 这就是为什么不建议在构造函数完成之前使其他线程可以访问final字段。 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. 但是,我同意从超类的constuctor调用子类方法是一种不好的做法,因为子类字段在那时没有初始化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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