29 июн. 2015 г.

Классы. Часть 11 – раскрываем тайну System.out.println()

Этот пост можно назвать небольшой практикой, которая может показать нам, что мы уже знаем в чем же тайна System.out.println(). Мы сейчас практически повторим эту же конструкцию сами. Тут просто приведу код двух классов, которые реализуют эту конструкцию и ее применение в третьем.

N0037

N0038

N0039

В Classes011 мы просто используем класс Syst в котором определена статическая переменная (ссылка) out ссылающаяся на класс PrtStr, в котором есть метод print().

Хотя, конечно же, в классе System все это реализовано немного по другому, но принцип остается тот же.

Поле out инициализируется в нативном коде JVM. А более подробно можно почитать тут.

26 июн. 2015 г.

Классы. Часть 10 – инициализация полей класса.

Мы уже видели как можно присваивать значения полям класса. Но чтобы подвести итог перечислим их:

  • Инициализация значениями по умолчанию
  • Инициализация при объявлении поля
  • Инициализация через конструктор
  • Инициализация в инициализационном блоке

Первые три способа мы уже видели, теперь рассмотрим четвертый.

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

Ну и теперь примеры…

N0034

N0035

Вывод у программы следующий:

N0036

Инициализационные блоки выполняются в порядке их следования в объявлении класса.

В этой программе продемонстрированы все четыре способа инициализации полей.

25 июн. 2015 г.

Классы. Часть 9 – модификатор static для полей и методов.

Во всех программах, которые мы до сих пор обсуждали, при объявлении метода main() использовался модификатор static. Рассмотрим действие этого модификатора для методов и полей.

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

Как уже обсуждалось, класс – это коллекция данных, хранимых в именованных полях, и кода, организованного в именованные методы, оперирующие этими данными. Поля и методы называются членами класса (members). В Java классы могут содержать другие классы. Эти классы-члены, или внутренние классы, предоставляют дополнительную функциональность которую мы обсудим чуть позже. Сейчас мы рассмотрим только поля и методы. Члены класса делятся на два различных типа: члены класса, связанные непосредственно с классом (статические), и члены экземпляра, связанные с каждым экземпляром класса (то есть с объектом). Не принимая во внимание классы-члены, мы получаем четыре типа членов:

  • Поля класса (статические поля – static fields)
  • Методы класса (статические методы – static methods)
  • Поля экземпляра (instance fields)
  • Методы экземпляра (instance methods)

Поля класса связаны с классом, в котором они определены, а не с экземпляром класса. То же касается и методов класса, как мы это уже отмечали.

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

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

Статические поля и методы объявляются при помощи модификатора static.

На методы, объявленные как static, накладывается ряд ограничений:

  • Они могут вызывать только другие статические методы.
  • Они могут получать доступ только к статическим переменным.
  • Они ни коим образом не могут ссылаться на члены типа this или super. (Ключевое слово super связано с наследованием и будет рассмотрено чуть позже)
  • Статические методы не могут быть абстрактными
  • Статические методы переопределяются в подклассах только как статические
  • При переопределении статических методов полиморфизм не действует, ссылки всегда направляются на методы класса, а не объекта

Статические поля так же могут использовать и модификатор final, что имеет тот же смысл что и для обычных полей – после инициализации, значение такого поля не может быть изменено.

Ну и теперь немного попрактикуемся… Вернемся к нашим баранам коровам…

N0029

N0030

Как видно в классе Cow мы определили статическую переменную count, а так же статический метод getCount(), который возвращает значение статической переменной count.

В классе Herd показано как можно использовать статические поля и методы класса, а так же пример, как делать не следует.

Программа генерирует следующий вывод:

N0032

Хотя это конечно очень простые примеры, но на них можно увидеть, что переменная count является общей для всех экземпляров класса Cow и обращение к ней из любого метода любого экземпляра этого класса будет возвращать одинаковый результат.

 

Рекомендую, так же, посмотреть хорошее видео на эту тему:

Что есть static

Классы. Часть 8 – Модификатор final для переменных и полей.

N0028Мы уже говорили о модификаторе final для переменных в методах, все тоже самое справедливо и для полей классов. Их значение должно быть присвоено ровно один раз к моменту завершения инициализации экземпляра.

Так же, на заметку, можно отметить, что модификатор final можно использовать и при объявлении аргументов в методе. Например:

N0033

Если аргумент а объявлен таким образом, то его невозможно изменить в методе.

 

На эту тему, я думаю, достаточно этих двух простых примеров.

Так что идем дальше!

Классы. Часть 7 – передача аргументов в метод.

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

Передаваемые значения копируются в переменные метода объявленные как аргументы. С примитивными типами все просто – аргументы метода получают их копии, поэтому метод ни как не может влиять на переменную переданную в него как аргумент, но может работать только с её копией. А вот с передачей объектов (верней сказать ссылок на объекты) как аргументы в метод все чуть хитрее. Они тоже передаются по значению, только это значение (ссылка) копируется в переменную объявленную как аргумент метода, но поскольку значение содержит ссылку на объект, то метод может изменять состояние переданного ему таким образом объекта. На саму же изначальную ссылку метод повлиять не может, он работает только с ее локальной копией. Чтобы все стало понятно давайте рассмотрим простой пример:

N0026

Вывод у программы следующий:

N0027

Как видите при вызове метода change() со значением типа int изменение переменной в методе ни как не повлияло на переменную переданную методу в качестве аргумента.

Что же касается объекта Box, то после передачи в метод change() его состояние было изменено несмотря на то что в метод была передана копия ссылки на объект. Копия ссылки все равно ссылается на тот же объект, поэтому он и был изменен. По существу произошло следующее b=box1. Но вот повлиять на саму ссылку box1 метод change() не может, что и продемонстрировано в методе chnge(), где присвоение переменой b ссылки на новый объект ни как не повлияло на ссылку box2.

В последнем варианте произошло следующее: во время передачи аргумента произошло b=box2, а затем b=new Box(). Надеюсь что все с передачей аргументов в метод понятно.

Если нет, то пишите письма и присылайте деньги комменты.

JA01

JA02

Я решил этот пример сделать более наглядным. Стоить обратить внимание на красные стрелки. В первом случае с box1 может показаться, что это та же самая ссылка передается в метод, но на самом деле это ее копия, что подтверждает пример с box2.

Из примера box2 видно, что хотя мы изменили ссылку box2 в методе chhge(), но это ни как не повлияло на ссылку box2 вне метода. Что и следовало доказать :) И ссылки и примитивные типы передаются в методы по значению.

24 июн. 2015 г.

Классы. Часть 6 – this is magic.

Каждый раз при вызове обычного (не статического) метода, кроме явных аргументов, если они имеются, туда еще передается и не явный аргумент – this.

this хранит ссылку на объект из которого был вызван метод

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

Чтобы эта тарабарщина стала более понятной давайте рассмотрим простой пример из двух классов:

N0018

N0019

У программы следующий вывод:

N0020

Как видно из кода класса Cow метод getThis() возвращает ссылку на текущий объект Cow, что и подтверждается выводом данной программы.

 

Поскольку this указывает на текущий объект, то его можно использовать для явного указания обращений к полям текущего экземпляра класса, например:

this.age = 5;

Чтобы стало понятней для чего это может быть нужно, рассмотрим еще один пример:

N0021

N0022

Вывод у программы следующий:

cow1 name = NoNaMe
cow1 name = Буренка

Мы чуток поменяли эти два класса. В классе Herd мы сперва вызываем метод setNoName() и передаем ему строку, но, как видно из вывода программы поле name не получает это значение, так как в этом методе про существу происходит бессмысленная операция – присвоение переменной name (объявленной в методе) значения самой себе. О чем нас, кстати, предупреждает Eclipse, показав значок предупреждения в строке 13 (вот как интересно совпала-то :) ).

Затем мы используем метод setName(), в 17 строке которого используется ключевое слово this и через точку имя поля, которому необходимо присвоить значение. По существу, в нашем случае, эту же операцию можно было сделать и следующим кодом в классе Herd:

cow1.name = "Буренка";

Разве эта строка не напоминает строку 17? this.name = name;

Те же самые яйца, только вид сбоку.

Кроме уже приведенного использования this, его можно использовать для вызова одних конструкторов класса из других. Вызвать один метод класса из другого метода очень просто – по имени. И даже если имена одинаковые, то работает правило перегрузки, а вот как вызывать конструкторы? У них же имя совпадает с именем класса.

Один метод из другого можно вызывать в любом месте метода, но вызов одного конструктора из другого может быть только в первой строке вызывающего конструктора. Например:

N0023

N0024

Вывод у программы следующий:

N0025

Как видно из вывода программы, конструкторы могут вызывать один другой, используя функционал вызываемого и добавляя свой.

Ну и в завершение предлагаю посмотреть очень хорошее видео на эту тему:

23 июн. 2015 г.

Классы. Часть 5 – рекурсивные методы.

В Java можно создавать рекурсивные методы, то есть, если сказать по простому,  это методы которые вызывают сами себя. Сразу же рассмотрим простой пример рекурсии на примере метода обратного отсчета, который будет выводить целочисленные значения в обратном порядке от переданного как аргумент целого значения.

N0011

N0012

В классе Recursion определен метод обратного отсчета countdown(). В классе Classes05 создается объект класса Recursion – rcn1 и на нем вызывается метод countdown().

Вывод у этой программы следующий: 10 9 8 7 6 5 4 3 2 1 0

В рекурсивных методах необходимо позаботиться о логике, которая прерывает их выполнение, иначе они будут выполнятся "вечно", то есть пока не израсходуют всю память стека и програ вылетит по ошибке нехватки памяти (StackOverflowError).

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

Из-за дополнительной перегрузки ресурсов, связанной с дополнительными вызовами функций, рекурсивные версии многих подпрограмм могут выполняться несколько медленнее их итерационных аналогов. Большое количество обращений к методу могут вызвать переполнение стека. Поскольку параметры и локальные переменные сохраняются в стеке, а каждый новый вызов создает новые копии этих значений, это может привести к переполнению стека. В этом случае система времени выполнения Java будет генерировать исключение.

Основное преимущество применения рекурсивных методов состоит в том, что их можно использовать для создания более понятных и простых версий некоторых алгоритмов, чем при использовании итерационных аналогов. Например, алгоритм быстрой сортировки достаточно трудно реализовать итерационным методом. А некоторые типы алгоритмов, связанных с искусственным интеллектом, легче всего реализовать именно с помощью рекурсивных решений.

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

N0013

N0014

В классе Recursion определены рекурсивные методы, а в классе Classes005 они вызываются.

Программа генерирует следующий вывод:

N0015

N0016Ну и совсем на закусь пример рекурсивного метода main(), который выполняет обратный отсчет от переданного в аргументе командной строки числа до единицы.

Вывод у программы следующий:

N0017

Вот мы постепенно и подошли к передаче в методы, как аргументы, объектов. В данных последних примерах – массивы. Но в методы, как аргументы, можно передавать любые объекты.