When a constant is not true constant

zhaozj2021-02-16  28

When a constant is not true constant

Author: Vladimir Roubtsov

Q: What negative effects will be generated using "cyclic definitions" in Java?

Note: Cycle definition (such as A = B, B = C, c = a)

A: Typically, Java compile results are dynamic output: You can only recompile a class, and the rest will automatically get this change. This is because .CLASS uses dynamic links in the form of dynamic links when performing classes and classes, and links will only be determined when the class is loaded.

In this article, I will tell you about a well-known and very meaningful abnormality, but it can produce a very subtle and difficult to discover errors. The "loop definition" operation will result in some difficulty in determination in the back program.

Inlined Static Final Conntants

Consider the following two examples

Public class

Main

Implements Interfacea

{

Public static void main (string [] args)

{

System.out.println (a);

}

} // End of class

Public Interface Interfacea

{

Public Static Final Int A = 1;

} // end of interface

In accordance with the Java specification, all Static Final Domain (Field) is not initialized by the expression when compiled, but first, the expression value is initialized after calculating the expression. In other words, at runtime, class main will not dynamically get the value of the A in InterfaceA. Instead, "1" is used instead of "a" to compile into main.main (). You can check the javap dump with the following method to confirm this:

Method void main (java.lang.string [])

0 getStatic # 23

3 iconst_1

4 Invokevirtual # 29

7 return

The above ICONST_1 is in calling system.out.println () before the integer "1" push into the JVM operation stack. The value is embedded in the byhenon code without using Interface.a. If you recompile Interfacea.java, change A to "2", but don't recompile main.java, then the value of main.main () output is still the same as before.

Any experienced Java programmer knows the above content. This is just some of the slightly insignificant features of Java during call. When we use a Java compilation tool that can be increted based on the source file modification time, you need to pay special attention to this feature. (See Note1) Because this feature sometimes causes the compiler to compile an unmembacted version.

It looks like a constant, but it is not

Note that there may be different effects in some cases as shown in some cases. For example, when the expression of the initial field can only be evaluated at runtime, there is no precedent as shown.

Public Interface Interfacea

{

Public static final int a = new java.util.random () .NextINT ();

} // end of interface

After InterfaceA changes, the bytecode of main.main () will become:

Method void main (java.lang.string [])

0 getStatic # 27

3 getStatic # 31 6 Invokevirtual # 37

9 return

Note that a dynamic reference for A occurs in the bytecode.

The above Interface.a change is very obvious. However, imagine what is the case of using the following three interfaces in an application:

Public Interface Interfacea

{

Public Static Final Int A = 2 * interfaceb.b;

} // end of interface

Public Interface InterfaceB

{

Public Static Final Int B = InterfaceC.c 1;

} // end of interface

Public Interface InterfaceC Extends Interfacea

{

Public Static Final INT C = A 1;

} // end of interface

Try this version of main ():

Public class

Main

Implements Interfacea, Interfaceb, InterfaceC

{

Public static void main (string [] args)

{

System.out.println (A B C);

}

} // End of class

The print result is 7. For the time being, forget this value and change the place in main () looks not important:

Public class

Main

Implements Interfacea, Interfaceb, InterfaceC

{

Public static void main (string [] args)

{

System.out.Println (C B a); // The Sum is Still The Same, Right?

}

} // End of class

The result is 6. The result appears to be changed after the order of re-arrangement. Do you want to see this result? Let us analyze why this will be like this.

Although, A, B, and C see it seems to be initialized by expressions when compiling, it is not the case, because this is a "loop definition", where A is dependent B, and B is dependent on C, and final C is dependent on A.

As a result, the compiler cannot do any behavior of replacing the static initialization code when loading / initialize three domains. The reason is that the various domains in the three domains depends on the red-duty of another domain to seek values. (See Note2) After calculating the first main () result, I noticed that interfacea is the first loaded (the order of the addition calculation rules is added from left to right), so InterfaceC is the first complete initialization. (Depending on the dependencies between the three). InterfaceC relies on InterfaceA, and at this time A has not been initialized, (therefore the value of A is 0). At this time, the value of C is 1, and the value of B is 2, and the value of A is 4, and the result is obtained 7. The second version is the practice of the reader. (Tip: Three values ​​will change)

Perhaps my example is not good enough. However, imagine what kind of case is the same as the same three interfaces in a huge code base: "Cycle Definition" is not simple. It seems that everything is already defined inside, and it doesn't look at any problem. However, these issues will result in a problem that it is difficult to exclude: Although some expressions can be replied in your application version, it and may result in different classes in somewhere and cannot be foreseen. Execution order. For example, unpredictable changes in concurrent line programs may result in different class load order. Unfortunately, most compilers do not consider these errors and even have no warnings for programmers. About the author: Vladimir Roubtsov came into contact with him since 1995 Java, there are more than 13 years of experience in multi-language programming now, he is Trilogy's senior engineer, the main work is the development of enterprise software.

Resources:

Note 1: The .class format is extendible, and it is possible to preserve all compilation dependencies between classes via a new .class attribute, including dependencies through static final constants Rumor has it this is precisely what Sun is considering for future Java versions,. Although I Have No Data About WHETHER IT WILL HAPPEN IN JAVA 1.5.

Note 2: Yes, Java Interfaces CAN Contain Static Initializers. Althought The Language Syntax Prohibits this, It is allowed at the byte code level.

Java Language Specification's Section 13.4.8 discusses the effects of declaring a field static final and the justification for the current behavior: http://java.sun.com/docs/books/jls/second_edition/html/binaryComp.doc.html# 45139

WANT MORE? See The Java Q & a Index Page for the full q & a catalog: http://www.javaworld.com/columns/jw-qna-index.shtml

For more Than 100 Insightful Java Tips, Visit JavaWorld's Java Tips Index Page: http://www.javaworld.com/columns/jw-tips-index.shtml

Browse the core java section of javaworld's topical index: http://www.javaworld.com/channel_content/jw-core-index.shtml

Get more of Your Questions Answered IN Our Java Beginner Discussion: http://forums.devworld.com/webx?50@@.ee6b804

Sign Up for JavaWorld's Free Weekly Email Newsletters: http://www.javaworld.com/subscribeyou'll Find A Wealth Of It-Related Articles from Our Sister Publications At IDG.Net

转载请注明原文地址:https://www.9cbs.com/read-27729.html

New Post(0)