Элементы типов
Элементы или члены (members) пользовательских типов могут быть методами, полями (в классах) и вложенными типами. В классе можно также объявлять конструкторы, служащие для создания объектов этого класса. В обоих языках описание конструктора похоже на описание метода, только тип результата не указывается, а вместо имени метода используется имя самого класса.
В обоих языках поля можно только перекрывать в наследниках, а методы можно и перегружать. Вложенные типы, как и поля, могут быть перекрыты.
У каждого элемента класса могут присутствовать модификаторы, определяющие доступность этого элемента из разных мест программы, а также его контекст — относится ли он к объектам этого класса (нестатический элемент) или к самому классу (статический элемент, помечается как static).
Для указания доступности в обоих языках могут использоваться модификаторы public, protected и private, указывающие, соответственно, что данный элемент доступен везде, где доступен содержащий его тип, доступен только в описаниях типов-наследников содержащего типа или только в рамках описания самого содержащего типа. Доступность по умолчанию, без указания модификатора, трактуется в рассматриваемых языках различно.
Нестатические методы в обоих языках (а также свойства, индексированные свойства и события в C#) могут быть объявлены абстрактными (abstract), т.е. не задающими реализации соответствующей операции. Такие методы (а также свойства, индексированные свойства и события в C#) помечаются модификатором abstract. Вместо кода у абстрактного метода сразу после описания полной сигнатуры идет точка с запятой.
Методы (свойства, индексированные свойства и события в C#), которые не должны быть перегружены в наследниках содержащего их класса, помечаются в Java как final, а в C#, как sealed.
В обоих языках можно использовать операции, реализованные на других языках. Для этого в C# используются стандартные механизмы .NET — класс реализуется на одном из языков, поддерживаемых .NET, с учетом ограничений на общие библиотеки этой среды, и становится доступен из любого другого кода на поддерживаемом .NET языке.
В Java для этого предусмотрен механизм Java Native Interface, JNI [6,7]. Класс Java может иметь ряд внешних методов, помеченных модификатором native. Вместо кода у таких методов сразу после описания полной сигнатуры идет точка с запятой. Они по определенным правилам реализуются в виде функций на языке C (или на другом языке, если можно в результате компиляции получить библиотеку с интерфейсом на C). Внешние методы, а также свойства, индексированные свойства, события и операторы, привязываемые по определенным правилам к функциям, которые имеют интерфейс на C и не вложены в среду .NET, есть и в C# — там такие операции помечаются как extern.
В Java, помимо перечисленных членов типов, имеются инициализаторы. Их описание приведено ниже. Инициализаторы относятся только к тому классу, в котором они определены, их нельзя перегрузить. | В C# члены типов, помимо методов, полей, конструкторов и вложенных типов, могут быть константами, свойствами, индексированными свойствами, событиями или операторами. Кроме этого, в типе можно определить деструктор и статический конструктор. Нестатические свойства, индексированные свойства и события можно перегружать в наследниках. Остальные из перечисленных элементов относятся только к тому классу, в котором описаны. |
Константы в Java принято оформлять в виде полей с модификаторами final static. Модификатор final для поля означает, что присвоить ему значение можно только один раз и сделать это нужно либо в статическом инициализаторе класса (см. ниже), если поле статическое, либо в каждом из конструкторов, если поле нестатическое: | Константы являются единожды вычисляемыми и неизменными далее значениями, хранящимися в классе или структуре. Пример объявления константы приведен ниже. |
public class A2 { public static final double PHI = 1.61803398874989; } | public class A2 { public const double Phi = 1.61803398874989; } |
Компонентная модель JavaBeans определяет свойство (property) класса A, имеющее имя name и тип T, как набор из одного или двух методов, декларированных в классе A — T getName() и void setName(T), называемых методами доступа (accessor methods) к свойству. Свойство может быть доступным только для чтения, если имеется лишь метод get, и только для записи, если имеется лишь метод set. Если свойство имеет логический тип, для метода чтения этого свойства используется имя isName(). Эти соглашения широко используются в разработке Java-программ, и такие свойства описываются не только у классов, предназначенных стать компонентами JavaBeans. Они и стали основанием для введения специальной конструкции для описания свойств в C#. | Свойства (properties) представляют собой "виртуальные" поля. Каждое свойство имеет один или оба метода доступа get и set, которые определяют действия, выполняемые при чтении и модификации этого свойства. Оба метода доступа описываются внутри декларации свойства. Метод set использует специальный идентификатор value для ссылки на устанавливаемое значение свойства. Обращение к свойству — чтение (возможно, только если у него есть метод get) или изменение значения свойства (возможно, только если у него есть метод set) — происходит так же, как к полю. При перегрузке свойства в наследниках перегружаются методы доступа к нему. Пример объявления свойств и их использования приведен ниже. |
public class MyArrayList { private int[] items = new int[10]; private int size = 0; public int getSize() { return size; } public int getCapacity() { return items.Length; } public void setCapacity(int value) { int[] newItems = new int[value]; System.arraycopy (items, 0, newItems, 0, size); items = newItems; } public static void main(String[] args) { MyArrayList l = new MyArrayList(); System.out.println(l.getSize()); System.out.println(l.getCapacity()); l.setCapacity(50); System.out.println(l.getSize()); System.out.println(l.getCapacity()); } } | using System; public class MyArrayList { private int[] items = new int[10]; private int size = 0; public int Size { get { return size; } } public int Capacity { get { return items.Length; } set { int[] newItems = new int[value]; Array.Copy (items, newItems, size); items = newItems; } } public static void Main() { MyArrayList l = new MyArrayList(); Console.WriteLine( l.Size ); Console.WriteLine( l.Capacity ); l.Capacity = 50; Console.WriteLine( l.Size ); Console.WriteLine( l.Capacity ); } } |
JavaBeans определяет индексированное свойство (indexed property) класса A, имеющее имя name и тип T, как один или пару методов T getName(int) и void setName(int, T). Свойства могут быть индексированы только одним целым числом. В дальнейшем предполагалось ослабить это ограничение и разрешить индексацию несколькими параметрами, которые могли бы иметь разные типы. Однако с 1997 года, когда появилась последняя версия спецификаций JavaBeans [9], этого пока сделано не было. | Индексированное свойство или индексер (indexer) — это свойство, зависящее от набора параметров. В C# может быть определен только один индексер для типа и данного набора типов параметров. Т.е. нет возможности определять свойства с разными именами, но одинаковыми наборами индексов. Обращение к индексеру объекта (или класса, т.е. статическому) производится так, как будто этот объект (класс) был бы массивом, индексированным набором индексов соответствующих типов. При перегрузке индексера в наследниках перегружаются методы доступа к нему. Индексеры должны быть нестатическими. Обращение к индексеру класса-предка в индексере наследника организуется с помощью конструкции base[…]. Пример декларации и использования индексера приведен ниже. |
public class MyArrayList { int[] items = new int[10]; int size = 0; public int getItem(int i) { if (i < 0 || i >= 10) throw new IllegalArgumentException(); else return items[i]; } public void setItem(int i, int value) { if (i < 0 || i >= 10) throw new IllegalArgumentException(); else items[i] = value; } public static void main(String[] args) { MyArrayList l = new MyArrayList(); l.setItem(0, 23); l.setItem(1, 75); l.setItem(1, l.getItem(1)-1); l.setItem(0, l.getItem(0) + l.getItem(1)); System.out.println (l.getItem(0)); System.out.println (l.getItem(1)); } } | using System; public class MyArrayList { int[] items = new int[10]; int size = 0; public int this[int i] { get { if (i < 0 || i >= 10) throw new IndexOutOfRangeException(); else return items[i]; } set { if (i < 0 || i >= 10) throw new IndexOutOfRangeException(); else items[i] = value; } } public static void Main() { MyArrayList l = new MyArrayList(); l[0] = 23; l[1] = 75; l[0] += (--l[1]); Console.WriteLine(l[0]); Console.WriteLine(l[1]); } } |
События (events) в модели JavaBeans служат для оповещения набора объектов-наблюдателей (listeners) о некоторых изменениях в состоянии объекта-источника (source). При этом класс EventType объектов, представляющих события определенного вида, должен наследовать java.util.EventObject. Все объекты-наблюдатели должны реализовывать один интерфейс EventListener, в котором должен быть метод обработки события (обычно называемый так же, как и событие) с параметром типа EventType. Интерфейс EventListener должен наследовать интерфейсу java.util.EventListener. Класс источника событий должен иметь методы для регистрации наблюдателей и их удаления из реестра. Эти методы должны иметь сигнатуры public void addEventListener (EventListener) public void removeEventListener (EventListener). Можно заметить, что такой способ реализации обработки событий воплощает образец проектирования "Подписчик". В приведенном ниже примере все public классы и интерфейсы должны быть описаны в разных файлах. | Событие (event) представляет собой свойство специального вида, имеющее делегатный тип. У события, в отличие от обычного свойства, методы доступа называются add и remove и предназначены они для добавления или удаления обработчиков данного события, являющихся делегатами (это аналоги различных реализаций метода обработки события в интерфейсе наблюдателя в JavaBeans) при помощи операторов += и =. Событие может быть реализовано как поле делегатного типа, помеченное модификатором event. В этом случае декларировать соответствующие методы add и remove необязательно — они автоматически реализуются при применении операторов += и = в к этому полю как к делегату. Если же программист хочет реализовать какое-то специфическое хранение обработчиков события, он должен определить методы add и remove. В приведенном ниже примере одно из событий реализовано как событие-поле, а другое — при помощи настоящего поля и методов add и remove, дающих совместно тот же результат. При перегрузке события в наследниках перегружаются методы доступа к нему. |
public class MouseEventArgs { ... } public class MouseEventObject extends java.util.EventObject { MouseEventArgs args; MouseEventObject (Object source, MouseEventArgs args) { super(source); this.args = args; } } public interface MouseEventListener extends java.util.EventListener { void mouseUp(MouseEventObject e); void mouseDown(MouseEventObject e); } import java.util.ArrayList; public class MouseEventSource { private ArrayList<MouseEventListener> listeners = new ArrayList <MouseEventListener >(); public synchronized void addMouseEventListener (MouseEventListener l) { listeners.add(l); } public synchronized void removeMouseEventListener (MouseEventListener l) { listeners.remove(l); } protected void notifyMouseUp (MouseEventArgs a) { MouseEventObject e = new MouseEventObject(this, a); ArrayList<MouseEventListener> l; synchronized(this) { l = (ArrayList<MouseEventListener>) listeners.clone(); for(MouseEventListener el : l) el.mouseUp(e); } } protected void notifyMouseDown (MouseEventArgs a) { MouseEventObject e = new MouseEventObject(this, a); ArrayList<MouseEventListener> l; synchronized(this) { l = (ArrayList<MouseEventListener>) listeners.clone(); for(MouseEventListener el : l) el.mouseDown(e); } } } public class HandlerConfigurator { MouseEventSource s = new MouseEventSource(); MouseEventListener listener = new MouseEventListener() { public void mouseUp (MouseEventObject e) { ... } public void mouseDown (MouseEventObject e) { ... } }; public void configure() { s.addMouseEventListener(listener); } } | public class MouseEventArgs { ... } public delegate void MouseEventHandler (object source, MouseEventArgs e); public class MouseEventSource { public event MouseEventHandler MouseUp; private MouseEventHandler mouseDown; public event MouseEventHandler MouseDown { add { lock(this) { mouseDown += value; } } remove { lock(this) { mouseDown -= value; } } } protected void OnMouseUp(MouseEventArgs e) { MouseUp(this, e); } protected void OnMouseDown(MouseEventArgs e) { mouseDown(this, e); } } public class HandlerConfigurator { MouseEventSource s = new MouseEventSource(); public void UpHandler (object source, MouseEventArgs e) { ... } public void DownHandler (object source, MouseEventArgs e) { ... } public void Configure() { s.MouseUp += UpHandler; s.MouseDown += DownHandler; } } |
Методы доступа к свойствам, индексерам или событиям в классах-наследниках могут перегружаться по отдельности, т.е., например, метод чтения свойства перегружается, а метод записи — нет. В C# 2.0 введена возможность декларации различной доступности у таких методов. Например, метод чтения свойства можно сделать общедоступным, а метод записи — доступным только для наследников. Для этого можно описать свойство так: public int Property { get { … } protected set { … } } | |
В Java никакие операторы переопределить нельзя. Вообще, в этом языке имеются только операторы, действующие на значениях примитивных типах, сравнение объектов на равенство и неравенство, а также оператор + для строк (это объекты класса java.lang.String), обозначающий операцию конкатенации. Оператор + может применяться и к другим типам аргументов, если один из них имеет тип String. При этом результатом соответствующей операции является конкатенация его и результата применения метода toString() к другому операнду в порядке следования операндов. | Некоторые операторы в C# можно переопределить (перекрыть) для данного пользовательского типа. Переопределяемый оператор всегда имеет модификатор static. Переопределяемые унарные операторы (их единственный параметр должен иметь тот тип, в рамках которого они переопределяются, или объемлющий тип): +, -, !, ~ (в качестве типа результата могут иметь любой тип), ++,-- (тип их результата может быть только подтипом объемлющего), true, false (тип результата bool). Переопределяемые бинарные операторы (хотя бы один из их параметров должен иметь объемлющий тип, а возвращать они могут результат любого типа): +, -, *, /, %, &, |, ^, <<, >>, ==, !=, <, >, <=, >=. Для операторов сдвига << и >> ограничения более жесткие — первый их параметр должен иметь объемлющий тип, а второй быть типа int. Можно определять также операторы приведения к другому типу или приведения из другого типа, причем можно объявить такое приведение неявным с помощью модификатора implicit, чтобы компилятор сам вставлял его там, где оно необходимо для соблюдения правил соответствия типов. Иначе надо использовать модификатор explicit и всегда явно приводить один тип к другому. Некоторые операторы можно определять только парами — таковы true и false, == и !=, < и >, <= и >=. Операторы true и false служат для неявного преобразования объектов данного типа к соответствующим логическим значениям. Если в типе T определяются операторы & и |, возвращающие значение этого же типа, а также операторы true и false, то к объектам типа можно применять условные логические операторы && и ||. Их поведение в этом случае может быть описано соотношениями (x && y) = (T.false(x)? x : (x & y)) и (x || y) = (T.true(x)? x : (x | y)). Аналогичным образом автоматически переопределяются составные операторы присваивания, если переопределены операторы +, -, *, /, %, &, |, ^, << или >>. Ниже приведен пример переопределения и использования операторов. Обратите внимание, что оператор приведения типа MyInt в int действует неявно, а для обратного перехода требуется явное приведение. Другая тонкость — необходимость приведения объектов типа MyInt к object в методе AreEqual — если этого не сделать, то при обращении к оператору == возникнет бесконечный цикл, поскольку сравнение i1 == null тоже будет интерпретироваться как вызов этого оператора. using System; public class MyInt { int n = 0; public MyInt(int n) { this.n = n; } public override bool Equals(object obj) { MyInt o = obj as MyInt; if (o == null) return false; return o.n == n; } public override int GetHashCode() { return n; } public override string ToString() { return n.ToString(); } public static bool AreEqual (MyInt i1, MyInt i2) { if ((object)i1 == null) return ((object)i2 == null); else return i1.Equals(i2); } public static bool operator == (MyInt i1, MyInt i2) { return AreEqual(i1, i2); } public static bool operator != (MyInt i1, MyInt i2) { return !AreEqual(i1, i2); } public static bool operator true (MyInt i) { return i.n > 0; } public static bool operator false (MyInt i) { return i.n <= 0; } public static MyInt operator ++ (MyInt i) { return new MyInt(i.n + 1); } public static MyInt operator -- (MyInt i) { return new MyInt(i.n - 1); } public static MyInt operator & (MyInt i1, MyInt i2) { return new MyInt(i1.n & i2.n); } public static MyInt operator | (MyInt i1, MyInt i2) { return new MyInt(i1.n | i2.n); } public static implicit operator int (MyInt i) { return i.n; } public static explicit operator MyInt (int i) { return new MyInt(i); } public static void Main() { MyInt n = (MyInt)5; MyInt k = (MyInt)(n - 3 * n); Console.WriteLine("k = " + k + " , n = " + n); Console.WriteLine("n == n : " + (n == n)); Console.WriteLine("n == k : " + (n == k)); Console.WriteLine( "(++k) && (n++) : " + ((++k) && (n++))); Console.WriteLine("k = " + k + " , n = " + n); Console.WriteLine( "(++n) && (k++) : " + ((++n) && (k++))); Console.WriteLine("k = " + k + " , n = " + n); } } |
Аналогом деструктора в Java является метод protected void finalize(), который можно перегрузить для данного класса. Так же, как и деструктор в C#, этот метод вызывается на некотором шаге уничтожения объекта после того, как тот был помечен сборщиком мусора как неиспользуемый. | Деструктор предназначен для освобождения каких-либо ресурсов, связанных с объектом и не освобождаемых автоматически средой .NET, либо для оптимизации использования ресурсов за счет их явного освобождения. Деструктор вызывается автоматически при уничтожении объекта в ходе работы механизма управления памятью .NET. В этот момент объект уже должен быть помечен сборщиком мусора как неиспользуемый. Деструктор оформляется как особый метод, без возвращаемого значения и с именем, получающимся добавлением префикса ‘~’ к имени класса |
public class MyFileReader { java.io.FileReader input; public MyFileReader(String path) throws FileNotFoundException { input = new java.io.FileReader (new java.io.File(path)); } protected void finalize() { System.out.println("Destructor"); try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } | using System; public class MyFileStream { System.IO.FileStream input; public MyFileStream(string path) { input = System.IO.File.Open (path, System.IO.FileMode.Open); } ~MyFileStream() { Console.WriteLine("Destructor"); input.Close(); } } |
Инициализаторы представляют собой блоки кода, заключенные в фигурные скобки и расположенные непосредственно внутри декларации класса. Эти блоки выполняются вместе с инициализаторами отдельных полей — выражениями, которые написаны после знака = в объявлениях полей — при построении объекта данного класса, в порядке их расположения в декларации. | |
Статическиеинициализаторы — такие же блоки, помеченные модификатором static — выполняются вместе с инициализаторами статических полей по тем же правилам в момент первой загрузки класса в Java-машину. | Статический конструктор класса представляет собой блок кода, выполняемый при первой загрузке класса в среду .NET, т.е. в момент первого использования этого класса в программе. Это аналог статического инициализатора в Java. Оформляется он как конструктор с модификатором static. |
public class A { static { System.out.println("Loading A"); } static int x = 1; static { System.out.println("x = " + x); x++; } static int y = 2; static { y = x + 3; System.out.println("x = " + x); System.out.println("y = " + y); } public static void main(String[] args) {} } | using System; public class A { static A() { Console.WriteLine("Loading A"); Console.WriteLine("x = " + x); x++; y = x + 3; Console.WriteLine("x = " + x); Console.WriteLine("y = " + y); } static int x = 1; static int y = 2; public static void Main() {} } |
Приведенный выше код выдает результат Loading A x = 1 x = 2 y = 5 | Приведенный выше код выдает результат Loading A x = 1 x = 2 y = 5 |
В Java нестатические вложенные типы трактуются очень специфическим образом — каждый объект такого типа считается привязанным к определенному объекту объемлющего типа. У нестатического вложенного типа есть как бы необъявленное поле, хранящее ссылку на объект объемлющего типа. Такая конструкция используется, например, для определения классов итераторов для коллекций — объект-итератор всегда связан с объектом-коллекцией, которую он итерирует. В то же время, пользователю не нужно знать, какого именно типа данный итератор, — достаточно, что он реализует общий интерфейс всех итераторов, позволяющий проверить, есть ли еще объекты, и получить следующий объект. Получить этот объект внутри декларации вложенного типа можно с помощью конструкции ClassName.this, где ClassName — имя объемлющего типа. При создании объекта такого вложенного класса необходимо указать объект объемлющего класса, к которому тот будет привязан. | В C# модификатор static у класса, все равно, вложенного в другой или нет, обозначает, что этот класс является контейнером набора констант и статических операций. Все его элементы должны быть декларированы как static. |
public class ContainingClass { static int counter = 1; static int ecounter = 1; int id = counter++; class EmbeddedClass { int eid = ecounter++; public String toString() { return "" + ContainingClass.this.id + '.' + eid; } } public String toString() { return "" + id; } public static void main (String[] args) { ContainingClass c = new ContainingClass() , c1 = new ContainingClass(); System.out.println(c); System.out.println(c1); EmbeddedClass e = c.new EmbeddedClass() , e1 = c.new EmbeddedClass() , e2 = c1.new EmbeddedClass(); System.out.println(e); System.out.println(e1); System.out.println(e2); } } | |
В C# класс может определить различные реализации для операций (методов, свойств, индексеров, событий) с одинаковой сигнатурой, если они декларированы в различных реализуемых классом интерфейсах. Для этого при определении таких операций нужно указывать имя интерфейса в качестве расширения их имени. using System; public interface I1 { void m(); } public interface I2 { void m(); } public class A : I1, I2 { public void m() { Console.WriteLine("A.m() called"); } void I1.m() { Console.WriteLine ("I1.m() defined in A called"); } void I2.m() { Console.WriteLine ("I2.m() defined in A called"); } public static void Main() { A f = new A(); I1 i1 = f; I2 i2 = f; f.m(); i1.m(); i2.m(); } } Результат работы приведенного выше примера следующий. A.m() called I1.m() defined in A called I2.m() defined in A called |
Последовательность выполнения инициализаторов полей и конструкторов классов-предков и наследников при построении объектов в Java и C# различается достаточно сильно.
О правилах, определяющих эту последовательность, можно судить по результатам работы следующих примеров.
public class A { static { System.out.println ("Static initializer of A"); } { System.out.println ("Initializer of A"); } static int sinit() { System.out.println ("Static field initializer of A"); return 0; } static int init() { System.out.println ("Field initializer of A"); return 0; } static int sf = sinit(); int f = init(); public A() { System.out.println ("Constructor of A"); } } public class B extends A { static int sf = sinit(); int f = init(); static { System.out.println ("Static initializer of B"); } { System.out.println ("Initializer of B"); } static int sinit() { System.out.println ("Static field initializer of B"); return 0; } static int init() { System.out.println ("Field initializer of B"); return 0; } public B() { System.out.println ("Constructor of B"); } } public class C { public static void main(String[] args) { B b = new B(); } } | using System; public class A { static A() { Console.WriteLine ("Static constructor of A"); } static int sinit() { Console.WriteLine ("Static field initializer of A"); return 0; } static int init() { Console.WriteLine ("Field initializer of A"); return 0; } static int sf = sinit(); int f = init(); public A() { Console.WriteLine ("Constructor of A"); } } public class B : A { static int sf = sinit(); int f = init(); static B() { Console.WriteLine ("Static constructor of B"); } static int sinit() { Console.WriteLine ("Static field initializer of B"); return 0; } static int init() { Console.WriteLine ("Field initializer of B"); return 0; } public B() { Console.WriteLine ("Constructor of B"); } } public class C { public static void Main() { B b = new B(); } } |
Результат работы приведенного примера такой. Static initializer of A Static field initializer of A Static field initializer of B Static initializer of B Initializer of A Field initializer of A Constructor of A Field initializer of B Initializer of B Constructor of B | Результат работы приведенного примера такой. Static field initializer of B Static constructor of B Field initializer of B Static field initializer of A Static constructor of A Field initializer of A Constructor of A Constructor of B |
В Java элемент типа, помимо доступности, указываемой с помощью модификаторов private, protected и public, может иметь пакетную доступность. Такой элемент может использоваться в типах того же пакета, к которому относится содержащий его тип. Именно пакетная доступность используется в Java по умолчанию. protected элементы в Java также доступны из типов того пакета, в котором находится содержащий эти элементы тип, т.е. protected-доступность шире пакетной. Типы, не вложенные в другие, могут быть либо public (должно быть не более одного такого типа в файле), либо иметь пакетную доступность. | В C# доступность элемента типа по умолчанию — private. Имеется дополнительный модификатор доступности — internal. Элемент, имеющий такую доступность, может быть использован всюду в рамках того же файла, где находится определение этого элемента. Кроме того, в C# можно использовать комбинированный модификатор protected internal для указания того, что к данному элементу имеют доступ как наследники содержащего его типа, так и типы в рамках содержащего его файла. Типы, не вложенные в другие, могут быть либо public, либо иметь доступность по умолчанию — private, что означает, что они могут использоваться только в рамках содержащего их пространства имен. В C# все элементы типов могут быть помечены модификатором new для точного указания на то, что такой элемент — новый и никак не соотносится с элементами предков данного типа с тем же именем или той же сигнатурой. |
В Java для полей классов дополнительно к модификаторам доступности и контекста могут использоваться модификаторы final, transient и volatile. | Поля классов или структурных типов в C# могут иметь в качестве дополнительных модификаторов readonly и volatile. |
Модификатор final обозначает, что такое поле не может быть изменено во время работы, но сначала должно быть инициализировано: статическое — в одном из статических инициализаторов, нестатическое — к концу работы каждого из конструкторов. В инициализаторах или конструкторах такое поле может модифицироваться несколько раз. Модификатор final у локальных переменных и параметров методов может использоваться примерно в том же значении — невозможность модификации их значений после инициализации. | Модификатор readonly по своему значению аналогичен модификатору final в Java. readonly поля должны инициализироваться к концу работы конструкторов и дальше не могут быть изменены. Такие поля используются для представления постоянных в ходе работы программы объектов и значений, типы которых не допустимы для поддерживаемых языком констант, а также таких, значение которых не может быть вычислено во время компиляции. |
Поля, помеченные модификатором transient, считаются не входящими в состояние объекта или класса, подлежащее хранению или передаче по сети. В Java имеются соглашения о том, как должен быть оформлен интерфейс класса, объекты которого могут быть сохранены или переданы по сети — эти соглашения можно найти в документации по интерфейсу java.io.Serializable, который должен реализовываться таким классом. Имеются и другие подобные наборы соглашений, привязанные к определенным библиотекам или технологиям в рамках Java. Стандартный механизм Java, обеспечивающий сохранение и восстановление объектов, реализующих интерфейс java.io.Serializable, по умолчанию сохраняет значения всех полей, кроме помеченных модификатором transient. Методы классов в Java могут быть дополнительно помечены модификаторами strictfp (такой же модификатор могут иметь инициализаторы) и synchronized. Значение модификатора strictfp описано в Лекции 10, в разделе о типах с плавающей точкой. Значение модификатора synchronized описывается ниже, в разделе, посвященном многопоточным приложениям. |