NullPointerException with static variables

I just hit the very strange (to me) java behavior. I have the following classes:

public abstract class Unit { public static final Unit KM = KMUnit.INSTANCE; public static final Unit METERS = MeterUnit.INSTANCE; protected Unit() { } public abstract double getValueInUnit(double value, Unit unit); protected abstract double getValueInMeters(double value); } 

and

 public class KMUnit extends Unit { public static final Unit INSTANCE = new KMUnit(); private KMUnit() { } //here are abstract methods overriden } public class MeterUnit extends Unit { public static final Unit INSTANCE = new MeterUnit(); private MeterUnit() { } ///abstract methods overriden } 

And my test case:

 public class TestMetricUnits extends TestCase { @Test public void testConversion() { System.out.println("Unit.METERS: " + Unit.METERS); System.out.println("Unit.KM: " + Unit.KM); double meters = Unit.KM.getValueInUnit(102.11, Unit.METERS); assertEquals(0.10211, meters, 0.00001); } } 
  • MKUnit and MeterUnit are both singleton statically initialized, so during class loading. Constructors are private, so they cannot be initialized elsewhere.
  • The class class contains static final references to MKUnit.INSTANCE and MeterUnit.INSTANCE

I would expect that:

  • The KMUnit class is loaded and an instance is created.
  • The MeterUnit class is loaded and an instance is created.
  • The module class is loaded and the KM and METERS variables are initialized, they are final, therefore they cannot be changed.

But when I run my test case in the console with maven, my result is:

  TESTS Running de.audi.echargingstations.tests.TestMetricUnits<br/> Unit.METERS: m<br/> Unit.KM: null<br/> Tests run: 3, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.089 sec <<< FAILURE! - in de.audi.echargingstations.tests.TestMetricUnits<br/> testConversion(de.audi.echargingstations.tests.TestMetricUnits) Time elapsed: 0.011 sec <<< ERROR!<br/> java.lang.NullPointerException: null<br/> at <br/>de.audi.echargingstations.tests.TestMetricUnits.testConversion(TestMetricUnits.java:29) <br/> Results : Tests in error: TestMetricUnits.testConversion:29 NullPointer 

And the funny part is that when I run this test from eclipse via the JUnit runner, everything is fine, I don't have a NullPointerException , but in the console I have:

 Unit.METERS: m Unit.KM: km 

So the question is: what could be the reason that the KM variable in Unit is null (and at the same time, METERS is not null)

+6
source share
2 answers

Static initialization can be complicated. You have an interdependence between A → B and B → A. The reason this is a bad idea is because the JVM starts loading statics from top to bottom in the class - if it falls into a new class that has not yet been initialized, it waits, until it initializes this class and its dependencies recursively, until everything is ready, and then continues.

Except when it is already loading a class. If A refers to B and B refers to A, it cannot start loading A a second time, or it will be an infinite loop (since A will load B again, which loads A). Thus, in this case it basically says: "The download has already begun, which you no longer need to do, continuing."

Moral of the story: depending on the order in which classes are loaded, KMUnit.INSTANCE cannot be initialized when this line is pressed:

public static final Unit KM = KMUnit.INSTANCE;

Imagine that you are a JVM and you start downloading KMUnit. He would have to load the Unit, the first time she sees it, so that, for example, create an object that is a subclass of Unit, when we get to the first creation (or, perhaps, before that - I am in a fog when statically loading the JVM ) But this, in turn, causes the static to be initialized in Unit, including this:

 public static final Unit KM = KMUnit.INSTANCE; public static final Unit METERS = MeterUnit.INSTANCE; 

Ok Now Unit completed the download, and we finished creating KMUnit for KMUnit.INSTANCE ... but wait - we already set KM = KMUnit.INSTANCE , which was null at that time. Therefore, it remains zero. Unfortunately.

On the other hand, if Unit loads first, then it expects KMUnit to load before initialization, so KMUnit.INSTANCE is set when we actually start the initializer.

I think. I am a little sleep deprived and I am not a specialist in the field of downloads.

+4
source

I would expect that:

 - KMUnit class is loaded and instance is created. - MeterUnit class is loaded and instance is created. - Unit class is loaded and both KM and METERS variable are initialized, they are final so they cant be changed. 

Without even going into the language specification, it's easy to see why the above sequence is not possible.

KMUnit continues to Unit . To create a static field KMUnit.INSTANCE , its class KMUnit must be created. And to create KMUnit , its superclass Unit must be created. Finally, to create the Unit class, its static fields KM and METERS must be assigned.

But we came here trying to create the KMUnit class, and we have not yet loaded the METERS class. Therefore, it is impossible for the static fields of the superclass to be assigned the correct values ​​(i.e., References to a fully constructed object).

There are two problems in the steps you describe:

The error of the steps described is that you delay the loading of Unit , which is impossible.

Hope this helps. Static initializers are not easy to understand, and their specifications are even less. It may be easier to rationalize why something cannot be done, hence my informal answer.

0
source

Source: https://habr.com/ru/post/957662/


All Articles