В языках Java и C# присутствуют статические члены класса. Для инициализации статических полей нам доступен механизм, называемый "static constructors" в C# и "static block" в Java.
Также, данный механизм возможно использовать если требуется только один раз что-либо выполнить. Но в этом случае стоит обратить внимание на то тогда вызывается код для статической инициализации и что в этот момент инициализировано и может быть использовано.
Не отличается от определения с инициализацией для не статических переменных.
class Plant {
public static long globalId = 0;
}
При наследовании статические поля инициализируются в следующей последовательности
class BasePlant {
public static long basicId = Note.printMessage("BasePlant: 'basicId' initialization");
}
class Plant extends BasePlant {
public static long globalId = Note.printMessage("Plant: 'globalId' initialization");
}
System.out.println("checkpoint 1");
System.out.println("basicId = " + BasePlant.basicId);
System.out.println("checkpoint 2");
System.out.println("globalId = " + Plant.globalId);
System.out.println("checkpoint 3");
// OUTPUT
// checkpoint 1
// BasePlant: 'basicId' initialization
// globalId = 77
// checkpoint 2
// Plant: 'globalId' initialization
// globalId = 77
// checkpoint 3
Изменение порядка обращения к полям приводит к предсказуемому результату
System.out.println("checkpoint 1");
System.out.println("globalId = " + Plant.globalId);
System.out.println("checkpoint 2");
System.out.println("basicId = " + BasePlant.basicId);
System.out.println("checkpoint 3");
// OUTPUT
// checkpoint 1
// BasePlant: 'basicId' initialization
// Plant: 'globalId' initialization
// globalId = 77
// checkpoint 2
// basicId = 77
// checkpoint 3
Статический блок – это блок кода, который используется для инициализации статических полей.
Пример использования:
class BasePlant {
public static long basicId = 0;
static {
System.out.println("BasePlant: static block #1");
basicId = 3;
}
}
class Plant extends BasePlant {
public static long globalId = 0;
static {
System.out.println("Plant: static block #1");
basicId = 7;
globalId = 7;
}
static {
System.out.println("Plant: static block #2");
basicId = 13;
globalId = 13;
}
}
System.out.println("checkpoint 1");
System.out.println("basicId = " + BasePlant.basicId);
System.out.println("checkpoint 2");
System.out.println("globalId = " + Plant.globalId);
System.out.println("checkpoint 3");
// OUTPUT
// checkpoint 1
// BasePlant: static block #1
// basicId = 3
// checkpoint 2
// Plant: static block #1
// Plant: static block #2
// globalId = 13
// checkpoint 3
Блоки будут выполняться в порядке их определения в классе.
Статические блоки будут выполнены только один раз при создании инстанса или при обращении к статическому полю класса.
System.out.println("checkpoint 1");
var plant = new Plant();
System.out.println("checkpoint 2");
// OUTPUT
// checkpoint 1
// BasePlant: static block #1
// Plant: static block #1
// Plant: static block #2
// checkpoint 2
При попытке использовать исключения в static block компилятор заругает нас: Initializer must be able to complete normally
. Возможно возникновение RuntimeException в процессе работы приложения.
Если RuntimeException
возникнет в static block, то это приведет к подобному результату
checkpoint 1
In the static block
Exception while initializing/ by zero
Exception in thread "main" java.lang.ExceptionInInitializerError
at Main.start(Main.java:46)
at Main.main(Main.java:38)
Caused by: java.lang.RuntimeException: / by zero
at Main$BasePlant.<clinit>(Main.java:20)
... 2 more
в этом примере использовалось деление на ноль, чтобы вызвать исключение
- статические переменные, в порядке определения
- "static block"(s), в порядке определения
- переменные экземпляра, в порядке определения
- "instance initializer"(s), в порядке определения
- конструктор
Следующий пример показывает что при обращении к потомку будут вызваны инициализаторы класса предка
System.out.println("checkpoint 1");
System.out.println("globalId = " + Plant.globalId);
System.out.println("checkpoint 2");
System.out.println("basicId = " + BasePlant.basicId);
System.out.println("checkpoint 3");
// OUTPUT
// checkpoint 1
// BasePlant: static block #1
// Plant: static block #1
// Plant: static block #2
// globalId = 13
// checkpoint 2
// basicId = 13
// checkpoint 3
Блоки будут выполняться в порядке их определения в классе. Сначала в базовом классе, потом в классе потомке.
Допускается инициализация статических полей в интерфейсе
interface PlantInterface {
long globalId = Note.printMessage("PlantInterface: initialization");
}
System.out.println("checkpoint 1");
System.out.println("globalId = " + PlantInterface.globalId);
System.out.println("checkpoint 2");
// OUTPUT
// checkpoint 1
// PlantInterface: initialization
// globalId = 77
// checkpoint 2
Но! запрещено использовать static block в интерфейсах.
Тут все аналогично Java
class Plant
{
public static long GlobalId = Note.PrintMessage("Plant: static initialization");
}
Console.WriteLine("checkpoint 1");
Console.WriteLine("GlobalId=" + Plant.GlobalId);
Console.WriteLine("checkpoint 2");
// OUTPUT
// checkpoint 1
// Plant: static initialization
// GlobalId=13
// checkpoint 2
Статический конструктор используется для инициализации любых статических данных или для выполнения определенного действия, которое необходимо выполнить только один раз. Он вызывается автоматически перед созданием первого экземпляра или ссылками на какие-либо статические элементы. Статический конструктор будет вызван не более одного раза.
class BasePlant
{
public static long basicId = 0;
static BasePlant()
{
Console.WriteLine("BasePlant: static block #1");
basicId = 3;
}
}
class Plant: BasePlant
{
public static long globalId = 0;
static Plant()
{
Console.WriteLine("Plant: static constructor");
basicId = 7;
globalId = 7;
}
}
Console.WriteLine("checkpoint 1");
Console.WriteLine("globalId = " + BasePlant.basicId);
Console.WriteLine("checkpoint 2");
Console.WriteLine("globalId = " + Plant.globalId);
Console.WriteLine("checkpoint 3");
// OUTPUT
// checkpoint 1
// BasePlant: static block #1
// globalId = 3
// checkpoint 2
// Plant: static constructor
// globalId = 7
// checkpoint 3
Необработанное исключение в статическом конструкторе вызовет что-то подобное
System.TypeInitializationException: 'The type initializer for 'ConsoleApp1.Plant' threw an exception.'
В отличии от Java компилятор C# не сопротивляется использованию исключений и пропускает например такое
class BasePlant
{
public static long basicId = 0;
static BasePlant()
{
Console.WriteLine("BasePlant: static block #1");
throw new ArgumentException();
basicId = 3;
}
}
- static constructor текущего класса
- static constructors базового класса
Console.WriteLine("checkpoint 1");
Console.WriteLine("globalId = " + Plant.globalId);
Console.WriteLine("checkpoint 2");
Console.WriteLine("globalId = " + BasePlant.basicId);
Console.WriteLine("checkpoint 3");
// OUTPUT
// checkpoint 1
// Plant: static constructor
// BasePlant: static block #1
// globalId = 7
// checkpoint 2
// globalId = 7
// checkpoint 3
Статический конструктор будет вызван перед созданием экземпляра класса или перед первым доступом к статическому члену. Сначала вызывается статический конструктор класса наследника а потом статический конструктор базового класса.
Пример с созданием инстанса
Console.WriteLine("checkpoint 1");
var plant = new Plant();
Console.WriteLine("checkpoint 2");
// OUTPUT
// checkpoint 1
// Plant: static constructor
// BasePlant: static block #1
// checkpoint 2
В C# допустимо использовать статический конструктор в интерфейсах для инициализации статических членов
interface IPlant
{
static long GlobalId { get; }
static IPlant()
{
GlobalId = 3;
}
}
Console.WriteLine("checkpoint 1");
Console.WriteLine("GlobalId = " + IPlant.GlobalId);
Console.WriteLine("checkpoint 2");
// OUTPUT
// checkpoint 1
// GlobalId = 3
// checkpoint 2
Так как статические члены интерфейса доступны только через интерфейс, то инициализация статических членов интерфейса не будет выполняться при создании объекта класса, который наследует интерфейс
class Plant: BasePlant, IPlant
{
...
}
Console.WriteLine("checkpoint 1");
var plant = new Plant();
Console.WriteLine("checkpoint 2");
Console.WriteLine("GlobalId=" + IPlant.GlobalId);
Console.WriteLine("checkpoint 3");
// OUTPUT
// checkpoint 1
// Plant: static constructor
// BasePlant: static block #1
// checkpoint 2
// IPlant: static constructor
// GlobalId=3
// checkpoint 3