Заполнение лакун в рассказе

Инициализация

Инициализация объекта

Инициализатор – специальный блок кода, заключенный в фигурные скобки. Чтобы понять, где он может размещаться, в какой момент исполняется и как он вписывается в процесс инициализации объекта, рассмотрим пример.

class A {
  int x = 1; // 1.1

  { // 1.2
    …
  }

  int y = 2; // 1.3

  { // 1.4
    …
  }
  …
  A (…) { // 1.5
    …
  }
  …
}

и

class B extends A {
  double z = 1.0; // 2.1

  {
    …
  }

  int Y = 2; // 2.2

  { // 2.3
    …
  }
  …
  B (…) { // 2.4
    …
  }
  …
}

Рассмотрим, что происходит при исполнении new B();

В начале происходит инициализация полей и выполнение инициализаторов расширяемого класса A (1.1–1.4) в порядке следования их в файле с исходным кодом.

Далее выполняется конструктор класса А с соответствующей сигнатурой (1.5).

Затем сверху вниз выполняются инициализаторы и инициализируются поля класса B (2.1–2.3).

И, наконец, выполняется конструктор с соответствующей сигнатурой класса B (2.4).

Естественным образом эта логика распространяется и на случаи наличия более одного предка у класса.

Поля и инициализаторы, отмеченные ключевым словом static, в момент инициализации объекта игнорируются.

Константные поля

Рассмотрим

class SquareMatrix {
  …
  final int myDimension;
  …
}

Ключевое слово final требует обязательного и единственного изменения соответствующего поля в процессе инициализации. То есть к концу выполнения любого конструктора поле final должно быть проинициализировано и только один раз. Возможность иного сценария выполнения вызовет ошибку еще на стадии компиляции.

Например, поле myDimension класса SquareMatrix логично проинициализировать в конструкторе:

public SquareMatrix(int dimension) {
  myDimension = dimension;
  …
}

Инициализация класса

Существует момент загрузки класса (обычно до первого использования класса). При этом происходит инициализация класса в порядке аналогичном порядку инициализации объекта (см. пункт 1.1 данной лекции), но только с полями (статическими) и инициализаторами (класса) отмеченными ключевым словом static.

Рассмотрим простой пример:

class С {
  static int x = 1; // 1
  static { // 2
    …
  }
  static int y = 2; // 3
  static { // 4
    …
  }
  …
}

Порядок действий при инициализации класса С:

  1. Инициализация статической переменной х единицей
  2. Выполнение первого инициализатора класса
  3. Инициализация статической переменной y двойкой.

Статические поля можно использовать, например, для подсчета количества экземпляров данного класса

static int ourCounter = 0;

При именовании статических полей принято использовать префикс «our»

Инициализаторы класса в среднем используются чаще чем простые инициализаторы

Cуществуют «настоящие константы». Например, в классе Float определено:

public static final float MIN_VALUE = 1.4E-45f;

static final поле должно быть проинициализировано при инициализации класса один и только один раз. Возможность осуществления иного сценария вызовет ошибку еще на стадии компиляции.

Принято имя static final поля писать прописными буквами.

Финализация. «finalize»

В классе Object существует метод

public void finalize()

Его, естественно, можно перекрывать.

Метод finalize будет вызван при уничтожении объекта. Но то, что до конкретного объекта когда-нибудь дойдет сборщик мусора, не гарантируется. Это определяется, в том числе, внутренней логикой работы сборщика мусора. То есть может такое быть, что программа завершится раньше, чем объект будет обработан garbage collector’ом.

Обычно предполагается, что метод finalize если и будет вызван, то только один раз. Если вызвать finalize вручную, то повторный вызов finalize при уничтожении объекта сборщиком мусора может привести к поломке программы.

finalize изредка используется в стандартной библиотеке.

«final»

Использование final создает ограничения на этапе компиляции.

Ключевое слово final может применяться к 4 видам сущностей языка java.

  • К полям (см. выше)

  • К локальным переменным.

    Результат аналогичен применению const в C++.

    Локальные final переменные обязаны инициализироваться сразу.

    Пример:

    final Int n = new Int(); 

    final относится только к ссылке «n». При этом, естественно, сам объект можно менять, используя, к примеру, метод

    n.setValue(10);

    Но оператор

    n = n2;

    вызовет ошибку на этапе компиляции.

  • К методам

    final методы нельзя перекрывать (но можно перегружать). При компиляции выражению будет сопоставлена не сигнатура, а конкретная функция. Поэтому final методы определяют в основном только для оптимизации.

  • К классам

    У final класса не может быть наследников. final классы, так же как и final методы используются в основном только для ускорения работы программы, так как все методы final класса - тоже final.

Сравнение объектов

Рассмотрим

Int a = new Int();
Int b = new Int();

(a == b) – false, так как, естественно, в данном случае «==» - это сравнение непосредственно переменных a и b, то есть ссылок. А они указывают на разные объекты.

Аналогично со строками и массивами – специальными объектами java.

В классе Object существует специальный, используемый, в том числе, в стандартной библиотеке, не final метод

public boolean equals(Object o)

equals возвращает true, если по сути сравниваемые объекты равны, и false – в ином случае.

Реализованный в Object метод equals сравнивает непосредственно ссылки. Часто необходимо перекрывать его. Например, при использовании Collection Framework, так как, в том числе, класс Set определяет эквивалентность объектов, используя equals.

В Java существует ужасная с точки зрения ООП вещь instanceof: (<object> instanceof <Class>) возвращает true, только если объект является экземпляром указанного класса.

Элементом хорошего стиля является как можно более редкое использование instanceof.

Пример разумного перекрытия equals в классе Int:

public boolean equals(Object o) {
  if (o instanceof Int) {
    return (((Int) o).myValue == myValue);
  } else {
    return false;
  }
}

Packages

Для организации хранения файлов классов существует система packages.

Допустим, классу Main требуется для работы класс Matrix, лежащий в каталоге с относительным путем «math/matrixes/». Тогда существуют два варианта действий:

  • везде, где требуется вместо имени класса «Matrix» писать «math.matrixes.Matrix», что не очень удобно.
  • В начале файла Main.java вставить строку «import math.matrixes.Matrix», после чего можно будет использовать короткое имя «Matrix».

Package – это каталог, со всеми и только с ними содержащимися в нем классами. Имя package совпадает с его относительным путем. Существуют вложенные пакеты.

Чтобы использовать короткие имена для всех классов package matrixes, можно написать «import math.matrixes.*»

Пример файла Main.java:

package org.borland;

import math.matrixes.*;

public class Main {
  …
}

Package также используются для избежания совпадения имен. Но даже длинные имена классов иногда совпадают. Чтобы избегать этого, сформулировано несколько правил:

  • default package должен быть всегда пустой
  • все коммерческие организации создают классы в package com
  • а все остальные – в org
  • Имена package начинаются с маленькой буквы

Пример организации папок:

org/
  borland/
    Main.java («import math.matrixes.*»)
    math/
      matrixes/
        Matrix.java
        MatrixException.java