Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Java Programer&Developer. Выпуск 3 - Основы языка (окончание)


Выпуск 3

Здравствуйте, дорогие читатели!

Этот выпуск завершает знакомство с базовыми понятиями языка Java.

1. Основы языка (окончание)

1.7. Передача параметров

Параметр = Аргумент (argument)

При передаче параметров при вызове функции в Java, на самом деле передаётся копия параметра. Рассмотрим фрагмент кода:

1. double radians = 1.2345;
2. System.out.println("Синус от " + radians + " = " + Math.sin(radians));

Переменная radians состоит из битового шаблона, который представляет собой число 1.2345. Во второй строке копия этого битового шаблона передаётся в аппарат вызывания методов Виртуальной Java Машины (Java Virtual Machine, JVM).

Когда параметр передаётся в метод, изменения значения параметра методом не отражаются на исходном значении параметра вне метода. Для наглядности рассмотрим пример:

1. public void bumper(int bumpMe) {
2.     bumpMe += 15;
3. }

Метод bumper вызывается в следующем коде:

1. int xx = 12345;
2. bumper(xx);
3. System.out.println("Сейчас xx равен " + xx);

Во второй строке xx копируется и передаётся в bumper(), который изменяет значение 12345 на 12360 (+15). Но в третьей строке выводится всё равно 12345, потому что вне метода bumper() значение xx не изменяется.

Это также имеет место, когда передаваемый параметр объект, а не переменная примитивного типа. Однако эффект совсем другой. Для понимания этого, рассмотрим концепцию ссылки на объект. Java программы не работают непосредственно с объектами. Когда конструктор создаёт объект, то возвращает некое значение, битовый шаблон, который уникальным образом идентифицирует объект. Это значение называется ссылка на объект. Например, рассмотрим следующий код:

1. Button btn;
2. btn = new Button("Ok");

Во второй строке конструктор Button возвращает ссылку на только что созданнную кнопку посредством переменной btn. Это не сам объект и не его копия, но ссылка на объект. Например, в некоторых имплементациях JVM ссылка - это адрес объекта.

В большинстве JVM, на самом деле, значение ссылки - адрес адреса, где второй адрес - адрес объекта. Этот подход называется двойная адресация и используется сборщиком мусора (описан далее в этом выпуске) для перераспределения объектов с целью уменьшения дефрагментации памяти.

Рассмотрим следующий фрагмент кода:

1. Button btn;
2. btn = new Button("Pink");
3. replacer(btn);
4. System.out.println(btn.getLabel());
5.
6. public void replacer(Button replaceMe) {
7.     replaceMe = new Button("Blue");
8. }

Во второй строке создаётся кнопка, ссылка на которую сохраняется в переменной btn. В третьей строке копия ссылки на кнопку Pink передаётся в метод replacer(), который (в строке 7) создаёт вторую кнопку Blue, ссылку на которую сохраняет в переданном параметре replaceMe. Но вне метода replacer() ссылка на кнопку Pink остаётся той же самой, поэтому строка 4 напечает Pink.

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

7.     replaceMe.setLabel("Blue");

то, хоть переданная ссылка btn и та же самая, что и в предыдущей версии примера, но сам объект изменён. Ссылка на объект не изменяется, но используется для доступа к самому объекту. В результате четвёртая строка выдаст Blue в выходном потоке.

Массивы - это объекты, а значит, программы работают со ссылками на них, а не непосредственно с ними. То есть при передаче ссылки на массив в некоторый метод, возможно изменить содержимое массива, хранимого в вызывающем методе.

Как создать ссылку на переменную примитивного вида

Это полезная техника для для создания эффекта передачи переменных примитивного вида по ссылке (когда вызываемый метод может изменять значение переменной). В вызываемый метод достаточно просто передать массив, состоящий из одного элемента, которым является заданная переменная. В коде это будет выглядеть, например, так:

1.  public class PrimitiveReference {
2.      public static void main(String args[]) {
3.          int [] myValue = { 1 };
4.          modifyIt(myValue);
5.          System.out.println("myValue contains " + myValue[0]);
6.      }

7.      public static void modifyIt(int [] value) {
8.          value[0]++;
9.      }
10. }

1.8. Сборщик мусора (Garbage Collection)

Большинство современных языков программирования позволяют выделять память во время выполнения программы. В Java это делается явно тогда, когда используется оператор new для создания объекта, и неявно - когда вызывается метод, который имеет локальные переменные или параметры. В случае неявного выделения памяти используется стековое пространство, которое освобождается, как только совершается выход из метода. В случае явного выделения памяти (под объекты) используется пространство кучи (heap). Освобождение этой памяти не так тривиально, но об этом поговорим ниже.

Пространство кучи используется для объектов даже тогда, когда они создаются в вызываемом методе. Например, в

public void aMethod() {
    MyClass mc = new MyClass();
}

локальная переменная mc - это ссылка, память для которой выделяется в стеке. Но память для объекта типа MyClass, на который ссылается mc, выделяется в куче.

В этой части мы рассмотрим освобождение памяти в куче, выделенной для объектов. Мы должны ответить на вопрос: когда мы можем освободить эту память? Некоторые языки программирования обязывают программиста явно освобождать память, когда он заканчивает с ней работать. На практике оказалось, что при таком подходе возникает много ошибок, поскольку программист мог или освободить память раньше, чем нужно (вызывая потерю данных), или забыть совсем об её освобождении (провоцируя недостаток памяти). Сборщик мусора в Java полностью решает первую проблему и значительно упрощает вторую.

В Java вы никогда не освобождаете память явно. Во время выполнения программы выделяемая память и её использование отслеживается. Это выполняется в фоновом режиме потоком (Thread, который называется сборщик мусора), имеющим низкую приоритетность. Как только сборщик мусора находит память, которая не достижима ни из одного из действующих потоков, он освобождает её обратно в кучу для последующего использования.

Сбор мусора может быть осуществлён разными способами, каждый из которых имеет свои недостатки и преимущества в зависимости от типа выполняемой программы. Для систем контроля в реальном времени, например, необходимо обеспечивать беспрепятственную реакцию на прерывания. Сборка муссора для таких приложений должна производиться часто, в коротких временных интервалах и должна быть легко прерываема. Программы, которые интенсивно используют память, работают лучше, если сборка муссора проводится редко и быстро. В настоящее время сборщик мусора "зашит" (hardcoded) в систему выполнения Java программ и болшинство его алгоритмов используют компромис между выгрузкой и реактивностью (способность к реагированию). В будущем, наверное, будет возможно подключать различные сборщики мусора (или JVM) с различными алгоритмами в зависимости от наших нужд. По крайней мере, мы себе этого желаем.

Однако мы до сих пор не ответили явно на вопрос, поставленный вначале. Самый лучший ответ: память не освобождается до тех пор, пока она используется. Даже если объект больше не используется, нельзя уверенно сказать, когда память, выделенная под него, будет освобождена. Это может случиться через 1 милисекунду, или через 100 милисекунд, или не случиться вовсе. Методы System.gc() и Runtime.gc() выглядят, как "запустить сборщик мусора", но и на них нельзя полагаться, поскольку другие более приоритетные потоки могут отложить их выполнение. И на самом деле, документация для методов gc() гласит: "Вызов этих методов подразумевает, что JVM приложит усилия для переработки неиспользуемых объектов".

Сама природа автоматического сбора мусора имеет важное последствие: утечка памяти (ведущая к её нехватке) всё ещё может иметь место. Достижимые ссылки на неиспользуемые объекты позволяют последним занимать память и не быть обработанными сборщиком мусора. Решением этой проблемы может быть явное присвоение null значения ссылке на объект, который вам уже не нужен. Это особенно просто в случае имплементации коллекции. Например, пусть массив storage используется для реализации стека. Типичная имплементация метода pop() такова:

1. public Object pop() {
2.     return storage[index--];
3. }

Если метод, вызывающий pop(), не позаботится о вытолкнутом объекте после его использования, память для этого объекта не будет освобождена до тех пор, пока не произойдёт выход из вызывающего метода или пока ссылка на этот объект (находящаяся в массиве storage) не будет перезаписана. Это может занять много времени. Для ускорения процесса, метод pop() может быть модифицирован следующим образом:

1. public Object pop() {
2.     Object returnValue = storage[index];
3.     storage[index--] = null;
4.     return returnValue;
5. }

1.9. Итоги первой главы

  • Элементы исходного файла должны идти в следующем порядке:
    1. Декларация пакетов
    2. Утверждения импортов
    3. Определения классов
  • В исходном файле должно быть хотя бы одно общедоступное (public) определение класса, имя которого должно совпадать с именем файла.
  • Идентификатор должен начинаться с буквы, символа доллара или подчёркивания; остальными символами могут быть буквы, цифры, символы доллара или подчёркивания.
  • В Java имеются четыре примитивных целочисленных типа со знаком: byte, short, int, long.
  • Два примитивных типа с плавающей точкой: float, double.
  • Тип char беззнаковый и используется для представления символов Unicode.
  • Тип boolean принимает только два значения: true или false.
  • Массивы должны быть (в порядке перечисления):
    1. Объявлены
    2. Созданы
    3. Инициализированы
  • Инициализация по умолчанию может быть применена и к переменным членам класса, и к элементам массивов, но не к автоматическим переменным (создаются в методе или передаются в него параметром). Значения по умолчанию: 0 - для численных типов, null - для ссылок на объекты и для типа char, false - для boolean.
  • Член length массивов возвращает количество элементов в массиве.
  • Класс с методом main() может быть вызван из командной строки, как Java приложение. Сигнатура для этого метода: public static void main(String[] args), где аргумент args содержит все параметры входной строки, которые идут следом за именем класса.
  • Параметры метода - копии, а не оригиналы. Для параметров примитивных типов это означает, что модификации переменных внутри вызываемого метода не видны методу вызывающему. Для параметров объектого типа (в том числе и для массивов) это тоже имеет место, но модификация объекта или массива, на который ссылается параметр, видна в вызывающем методе.
  • Сборщик мусора может освободить память, если ясно, что она больше не используется.
  • Достоверная сборка мусора невозможна.
  • Невозможно предсказать, когда неиспользуемая память будет освобождена.
  • Сборка мусора не гарантирует, что не случится утечка памяти, которая может иметь место, если ссылки на неиспользуемые объекты не установлены в null или не уничтожены (например, с выходом из вызываемого метода).

1.10. Тест для самопроверки

  1. Данные типов со знаком имеют равное количество положительны ненулевых и отрицательных значений.
    1. Истинно
    2. Ложно
  2. Выберите все идентификаторы, которые допускаются в Java программе.
    1. BigOlLongStringWithMeaninglessName
    2. $int
    3. bytes
    4. $1
    5. finallist
  3. Какие из нижеследующих сигнатур могут быть использованы для объявления методаmain()? (Выберите все допустимые.)
    1. public static void main()
    2. public static void main(String arg[])
    3. public void main(String [] arg)
    4. public static void main(String[] args)
    5. public static int main(String [] arg)
  4. Если исходый файл включает в себя все три высокоуровневых элемента, какова их последовательность?
    1. Импорты, декларация пакета, классы
    2. Классы, импорты, декларация пакета
    3. Декларация пакета идёт первой, а порядок импортов и классов не существенен
    4. Декларация пакета, импорты, классы
    5. Импорты идёт первыми, а порядок декларации пакета и определений классов несущественен
  5. Рассмотрим следующую строку кода: int[] x = new int[25];. После её выполения, какие утверждения истинны? (Выберите все истинные утверждения.)
    1. x[24] = 0
    2. x[24] не определено
    3. x[25] = 0
    4. x[0] = null
    5. x.length = 25
  6. Рассмотрим следующий код:

    1.  class Q6 {
    2.      public static void main(String args[]) {
    3.          Holder h = new Holder();
    4.          h.held = 100;
    5.          h.bump(h);
    6.          System.out.println(h.held);
    7.      }
    8.  }
    9.
    10. class Holder {
    11.     public int held;
    12.     public void bump(Holder theHolder) {
    13.         theHolder.held++;
    14.     }
    15. }

    Что печатается в строке 6?
    1. 0
    2. 1
    3. 100
    4. 101
  7. Рассмотрим следующий код:

    1.  class Q7 {
    2.      public static void main(String args[]) {
    3.          double d = 12.3;
    4.          Decrementer dec = new Decrementer();
    5.          dec.decrement(d);
    6.          System.out.println(d);
    7.      }
    8.  }
    9.
    10. class Decrementer {
    11.     public void decrement(double decMe) {
    12.         decMe = decMe - 1.0;
    13.     }
    14. }

    Что печатается в строке 6?
    1. 0.0
    2. -1.0
    3. 12.3
    4. 11.3
  8. Как можно заставит сборщик мусора освободить память, выделенную под некий объект?
    1. Никак
    2. Вызвав System.gc()
    3. Вызвав System.gc(), и передав параметром ссылку на объект, память под который хотим освободить
    4. Вызвав Runtime.gc()
    5. Установив ссылку на объект в null
  9. Каков диапазон значений, которые могут быть присвоены перемнной типа short?
    1. Зависит от аппаратного обеспечения компьютера, на котором запускаетя программа
    2. От 0 до 216-1
    3. От 0 до 232-1
    4. От -215 до 215-1
    5. От -231 до 231-1
  10. Каков диапазон значений, которые могут быть присвоены перемнной типа byte?
    1. Зависит от аппаратного обеспечения компьютера, на котором запускаетя программа
    2. От 0 до 28-1
    3. От 0 до 216-1
    4. От -27 до 27-1
    5. От -215 до 215-1

Ответы на тест будут даны в следующем выпуске рассылки.


Дорогие читатели! Если у вас возникают вопросы по тексту рассылки, я с удовольствием на них отвечу. Мой почтовый адрес идёт последней строкой в данном выпуске (at означает @). Кроме того, присылайте мне интересующие вас темы, которые я могла бы осветить в рассылке, делитесь опытом или просто комментируйте. Я также буду благодарна вам, если вы проголосуете за этот выпуск. Форма голосования находится внизу выпуска.

Всего доброго,
Наталия Македа
natalia.macheda at gmail.com


В избранное