Статическая переменная инициализируется 2 раза

в 15:01, , рубрики: Action Script, class, const, Flash-платформа, flex, static, variable, метки: , , , ,

Играясь с синглтонами-статикой-константами во флексе, вот на что напоролся:


Есть файлы ( github.com/radistao/test-super-and-static/commit/1f0a6 ):

ChatModel.as
package
{
	public class ChatModel
	{
		
		public var someConst : String = getTrace("someConst");
		public var someVar : String = getTrace("someVar");
		
		public static const someStaticConst : String = getTrace("someStaticConst");
		public static var someStaticVar : String = getTrace("someStaticVar");
		
		public const menu : ChatMenuStructure = new ChatMenuStructure();
		
		private static var _instance : ChatModel;
		
		public static function get instance() : ChatModel {
			trace("Get instance start");
			if (!_instance) {
				_instance = new ChatModel(new T());
			}
			
			trace("Get instance end");
			
			return _instance;
		}
		
		public function ChatModel(t : T)
		{
			trace("ChatModel contructor 1");
		}
		
		private static var getTraceCount : int = 0;
		
		public static function getTrace(str : String) : String {
			getTraceCount++;
			trace("getTrace #" + getTraceCount, str);
			return str;
		}
	}
}

internal class T{
	
}

ChatMenuStructure.as

package
{
	public class ChatMenuStructure
	{
		
		public const chatMenuStructureConst : String = getTrace("chatMenuStructureConst");
		
		public var chatMenuStructureVar : String = getTrace("chatMenuStructureVar"); 
		
		public function ChatMenuStructure()
		{
			trace("ChatMenuStructure constructor");
		}
		
		public static function getTrace(str : String) : String {
			trace("ChatMenuStructure getTrace", str);
			return str;
		}
		
		public function toString() : String {
			return "ChatMenuStructure instance";
		}
			
	}
}

testSuperCall.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"
			   creationComplete="creationCompleteHandler()">
	<fx:Script>
		<![CDATA[
			
			public var chatModel : ChatModel = ChatModel.instance;
			
			public const menu : ChatMenuStructure = chatModel.menu;
			
			protected function creationCompleteHandler():void {
				trace("menu is "" + String(menu) + """);
			}
			
		]]>
	</fx:Script>
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
	</fx:Declarations>
</s:Application>

вот какие трейсы вышли ( gist.github.com/radistao/5478426 ):

getTrace #1 someStaticConst
getTrace #2 someStaticVar
Get instance start
getTrace #1 someConst
getTrace #2 someVar
ChatMenuStructure getTrace chatMenuStructureConst
ChatMenuStructure getTrace chatMenuStructureVar
ChatMenuStructure constructor
ChatModel contructor 1
Get instance end
menu is «ChatMenuStructure instance»

Коротко выводы по трейсам:

  • статические переменные класса не будут инициализироваться до первого вызова инстанса этого класса или первого обращения к статической переменной, как и в яве (КО не ошибался)
  • до того, как вызовется констуктор — вызовутся все статические «инициализаторы» (если это первое обращение к классу), а за ними — все локальные «инициализаторы». КО подсказывает, что каждый следующий вызов конструктора уже не будет вызывать статические инициализаторы.
  • наличие-отсутствие <b>super()</b> в конструкторе не меняет порядок инициализации (последовательности-результаты трейсов: коммит github.com/radistao/test-super-and-static/commit/7f930 и трейсы gist.github.com/radistao/5478435)

НО!!!
пока я все это делал — я в вызов статического метод getTrace(), которым инициализировал константы и переменные, решил вписать счетчик getTraceCount сколько раз он вызывался (строка 32 файла ChatModel.as). Все что я с ним делал — просто инкрементил и трейсил при каждом вызове getTrace()

А теперь обратите внимание на трейсы:

getTrace #1 someStaticConst
getTrace #2 someStaticVar
Get instance start
getTrace #1 someConst
getTrace #2 someVar

статическая переменная getTraceCount проинициализировалась 2 раза! WAT?

image

Оклимавшись и собрав остатки моего разорванного моска я начал дальше пилить и искать, WTF! Ну, вобщем, упустив все перебраные варианты, танцы с бубном и вызовы духов умерших флексеров, я понял в чем трабла:

private static var getTraceCount : int = 0;

находится в коде ниже первых двух вызовов getTrace(). Переместив это объявление в самый «верх» класса (кода) — все заработало аки часы ( github.com/radistao/test-super-and-static/commit/a0dcd ):

ChatMenuStructure.as

package
{
	public class ChatModel
	{
		
		private static var getTraceCount : int = 0;
		
		public var someConst : String = getTrace("someConst");
		public var someVar : String = getTrace("someVar");
		
		public static const someStaticConst : String = getTrace("someStaticConst");
		public static var someStaticVar : String = getTrace("someStaticVar");
		
		public const menu : ChatMenuStructure = new ChatMenuStructure();
		
		private static var _instance : ChatModel;
		
		public static function get instance() : ChatModel {
			trace("Get instance start");
			if (!_instance) {
				_instance = new ChatModel(new T());
			}
			
			trace("Get instance end");
			
			return _instance;
		}
		
		public function ChatModel(t : T)
		{
			trace("ChatModel contructor before super()");
			super();
			trace("ChatModel contructor after super()");
		}
		
		public static function getTrace(str : String) : String {
			getTraceCount++;
			trace("getTrace #" + getTraceCount, str);
			return str;
		}
	}
}

internal class T{
	
}

Трейсы ( gist.github.com/radistao/5478443 ):

Трейсы

getTrace <b>#1</b> someStaticConst
getTrace <b>#2</b> someStaticVar
Get instance start
getTrace <b>#3</b> someConst
getTrace <b>#4</b> someVar
ChatMenuStructure getTrace chatMenuStructureConst
ChatMenuStructure getTrace chatMenuStructureVar
ChatMenuStructure constructor
ChatModel contructor before super()
ChatModel contructor after super()
Get instance end
menu is "ChatMenuStructure instance"

Вобщем, происходит примерно такая картина:

  1. вызывается getTrace для инициализации первого статического объекта someStaticConst. В этом getTrace() дергается getTraceCount, инициализируется и инкрементится до 1
  2. вызывается getTrace для инициализации второго статического объекта someStaticVar. В этом getTrace() дергается уже проинициализированный getTraceCount и инкрементится до 2
  3. и тут приходит очередь инициализации третьего статического объекта — объявления private static var getTraceCount: int = 0; И в этом месте getTraceCount совершенно безболезненно сетится снова в 0 и начинается все с начала!

image

Таким образом, нельзя полагаться, что если статическая переменна находится «внизу кода» и будет дергаться раньше, то при этом она будет правильно проинициализирована.

Вот мне теперь интересно, кто виноват (и что делать): компилятор, ява-машина или флеш-плеер?

Ради интереса по-экспериментировал еще с непримитивными типами ( github.com/radistao/test-super-and-static/commit/08894 ):

GetTracesClass.as

package
{
	public class GetTracesClass
	{
		private static var constructorCalls : int = 1;
		
		private var instanceNumber : int;
		
		public function GetTracesClass()
		{
			trace("GetTracesClass constructor called " + String(constructorCalls) + " time(s)");
			instanceNumber = constructorCalls;
			constructorCalls++;
		}
		
		public function toString() : String {
			return "GetTracesClass instance #" + String(instanceNumber);
		}
	}
}
ChatModel.as

package
{
	public class ChatModel
	{
		
		public var someConst : String = getTrace("someConst");
		public var someVar : String = getTrace("someVar");
		
		public static const someStaticConst : String = getTrace("someStaticConst");
		public static var someStaticVar : String = getTrace("someStaticVar");
		
		public const menu : ChatMenuStructure = new ChatMenuStructure();
		
		private static var _instance : ChatModel;
		
		public static function get instance() : ChatModel {
			trace("Get instance start");
			if (!_instance) {
				_instance = new ChatModel(new T());
			}
			
			trace("Get instance end");
			
			return _instance;
		}
		
		public function ChatModel(t : T)
		{
			trace("ChatModel contructor before super()");
			super();
			trace("ChatModel contructor after super()");
		}
		
		private static var getTraceCount : GetTracesClass = new GetTracesClass();
		
		public static function getTrace(str : String) : String {
			trace("getTrace #"" + getTraceCount + """, str);
			return str;
		}
	}
}

internal class T{
	
}

Трейсы ( gist.github.com/radistao/5478479 ):

getTrace #«null» someStaticConst
getTrace #«null» someStaticVar
GetTracesClass constructor called 1 time(s)
Get instance start
getTrace #«GetTracesClass instance #1» someConst
getTrace #«GetTracesClass instance #1» someVar
ChatMenuStructure getTrace chatMenuStructureConst
ChatMenuStructure getTrace chatMenuStructureVar
ChatMenuStructure constructor
ChatModel contructor before super()
ChatModel contructor after super()
Get instance end
menu is «ChatMenuStructure instance»

Там уже все становится более прозрачно, хотя все равно тупо: первые 2 вызова getTrace() вообще не сетят getTraceCount, хотя и обращаются к нем, а после его инициализации все дальше идет нормально. Если инициализировать getTraceCount в начала — все идет нормально ( gist.github.com/radistao/5478482 )

image
(У меня уже не было информации для разрыва моска, поэтому эту картинку я вставил просто так!)

Потом еще попробовал с Number — та же картина, как и с int.
Потом еще попробовал инициализировать getTraceCount не нулем, а 1 (т.е., не дефолтным значением): тогда в трейсах выпало "2, 3, 2, 3" (т.е., в первый раз, все-таки, было взято не дефолтное значение для int, а именно то, которое ему сетится в инициализаторе).

На что это может повлиять? Я точно не знаю, но подозреваю, что, вероятно, может повлиять на какие-нибудь статические счетчики инстансов определенного класса и ему подобные шаблоны.

Автор: radistao

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js