22 июн. 2015 г.

Классы. Часть 4 – перегрузка конструкторов.

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

Box box1 = new Box();

Что здесь означают скобки? Похоже на то, что мы вызываем метод. Фактически это как раз то, что мы делаем. У каждого Java класса есть по крайней мере один конструктор, который является методом с таким же именем, как и имя класса. Его назначение – выполнить всю необходимую инициализацию нового объекта. Это конструктор по умолчанию, поскольку у него нет аргументов. Но как вы помните, что если мы определяем в классе свои конструкторы, то если хотим использовать конструктор по умолчанию, то должны определить и его.

N0008

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

Теперь можно создавать экземпляры класса Box и инициализировать их значения сразу в операторе new:

Box box1 = new Box();
Box box2 = new Box(10);
Box box3 = new Box(10, 20, 30);

Как видите создавать и инициализировать поля объектов Box стало проще и приятней.

N0009Вот так может выглядеть пример программы создающей объекты типа Box и сразу же инициализирующей их поля значениями.

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

N0010

Как видите, соответствующий перегруженный конструктор вызывается в зависимости от параметров, указанных при выполнении операции new.

Классы. Часть 3 - перегрузка методов.

Java разрешает определение внутри одного класса двух или более методов с одним именем, если только они содержат разные параметры.

В этом случае методы называют перегруженными, а процесс — перегрузкой (overloading) методов. Перегрузка методов — один из способов поддержки полиморфизма в Java.

На заметку! Не надо путать перегрузку методов с их переопределением (overriding). Переопределение мы рассмотрим когда будем изучать наследование.

При вызове перегруженного метода для определения нужной версии Java использует тип и/или количество аргументов метода. Следовательно, перегруженные методы должны различаться по типу и/или количеству их параметров. Хотя возвращаемые типы перегруженных методов могут быть различны, самого возвращаемого типа не достаточно для различения двух версий метода. Когда Java встречает вызов перегруженного метода, она просто выполняет ту его версию, параметры которой соответствуют аргументам, использованным в вызове.

Если помните, то в первой части посвященной классам, мы использовали два разных метода, для задания размеров нашего ящика. Для обратной совместимости, мы сохраним эти методы, но так же создадим перегруженный метод setData(), который будет принимать разное количество параметров и разный их тип. N0005

Слева приведен отрывок из кода класса Box в текущей его инкарнации. В котором добавлен перегруженный метод setData(). У него есть три варианта, которые принимают один параметр типа double, три double и одну строку, соответственно. И каждый из них выполняет свой код и задает значения надлежащим полям.

Как уже говорилось чтобы Java различала какой метод надо использовать они должны отличаться по типу и/или количеству передаваемых в метод аргументов.

Пример использования этих методов приведен ниже:

 

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

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

N0007

Стоит еще заметить что в данном случае происходит неявное преобразование типов данных, так как мы задаем размеры целочисленным литералом, а метод принимает double.

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

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

O001

O002

Я сразу привел код и вывод программы. В принципе здесь все очень просто.

 

У нас есть перегруженный метод multiplay(), который принимает разное количество аргументов разных типов, и так же возвращает разные типы.

Обратите внимание что в предпоследней строке кода происходит неявное преобразование типов из int в double.

Классы. Часть 2 – подробнее о создании экземпляров классов (объектов).

В прошлой статье мы рассмотрели лишь основные моменты о классах. Теперь рассмотрим более подробно создание объектов (инстанцирование).

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

Рассмотрим эту операцию более подробно, на примере программы из прошлого поста. Создать экземпляр нашего класса Box можно следующим образом:

Box mybox = new Box();

Этот оператор объединяет только что описанных два шага. Чтобы каждый из шагов был более очевидным, его можно было переписать следующим образом:

Box mybox; // объявление ссылки на объект
mybox = new Box(); // распределение памяти для объекта Box

В первой строке приведенного выше фрагмента кода переменная mybox объявляется как ссылка на объект типа Вох. В данный момент переменная mybox пока еще не ссылается на конкретный объект.

Grabli

Тут начинается один интересный момент, где можно наступить на грабельки. Поэтому стоит еще раз напомнить, что если переменная (объекта или примитива) объявляется в методе, то перед ее использованием она должна быть проинициализирована каким-либо значением. Для объектов это может быть и null. Так после первой строки примера, в mybox не находится даже null, т.к. он был объявлен в методе. Если же он был бы объявлен как поле, то после создания объекта оператором new, который вызовет конструктор по умолчанию, там был бы null.

Важно понимать, что для переменных объявленных в методе, память выделается в стеке, а для полей класса в куче (heap'e). В стеке может быть любой "мусор" (не путать с ментом), поэтому перед использованием переменную необходимо инициализировать каким либо значением. Поле же класса создается в хипе (не путать с хиппи), и конструктор по умолчанию инициализирует все поля класса значениями по умолчанию для их типов данных.

В второй строке примера выделяется память для конкретного объекта оператором new, а переменной mybox присваивается ссылка на этот объект. После выполнения второй строки кода переменную mybox можно использовать так, как если бы она была объектом типа Вох. Но в действительности переменная mybox просто содержит адрес памяти (ссылку) конкретного объекта типа Вох.

N0001

Чтобы стало понятней рассмотрим пример слева и вывод этой программы ниже.

N0002

Как видим у нас есть поле filedBox класса Box и так же мы объявили переменную типа Box – mybox в методе main().

Так вот, поскольку поле filedBox было проинициализировано конструктором по умолчанию для класса Classes002, там уже находится null – значение по умолчанию для объектов. И поэтому мы можем вывести его на печать сразу же, что и делает первый оператор в методе main(). А вот с переменной mybox такой номер уже не пройдет, так как она была объявлена внутри метода, следовательно мы не сможем сразу же ее использовать, например вывести на печать, так как компилятор нам даже не позволит скомпилировать программу и выдаст ошибку, поэтому строка вывода на печать mybox сразу после его объявления закомментирована.

А уже после того как мы инициализируем mybox хотя бы значением null, то тогда уже можно использовать оператор вывода на печать.

Когда же мы уже создали полноценный объект типа Box в памяти при помощи оператора new и присвоили ссылку на него переменной mybox, то переменную mybox можно уже использовать как объект (на самом деле ссылку на объект) и вызвать методы класса, а так же обращаться к его полям (если они открыты).

N0003Теперь еще раз стоит вспомнить разницу между ссылочными и примитивными типами данных. И чтобы освежить это в памяти рассмотрим тут еще один пример.

В строке 29 на первый взгляд, переменной b2 присваивается ссылка на копию объекта, на которую ссылается переменная b1. Таким образом, может показаться, что переменные b1 и b2 ссылаются на совершенно разные объекты, но это совсем не так. После выполнения данного фрагмента кода обе переменные, b1 и b2, будут ссылаться на один и тот же объект. Присваивание переменной b2 значения переменной b1 не привело к выделению области памяти или копированию какой-нибудь части исходного объекта. Такое присваивание приводит лишь к тому, что переменная b2 ссылается на тотже объект, что и переменная b1 . Таким образом, любые изменения, внесенные в объекте по ссылке в переменной b2 , окажут влияние на объект, на который ссылается переменная b1 , поскольку это один и тот же объект. Что собственно и видно из вывода программы:

N0004

Как видно изменения сделанные в строке 30, как казалось бы над другим объектом b2, на самом деле были сделаны над объектом на который ссылаются b1 и b2, то есть над одним и тем же объектом. Это видно из вывода команды в строке 31. Но после того как мы в строке 32 создали новый объект и вернули ссылку на него в b2, b2 стало ссылаться на другой объект, а b1 сохранил свою ссылку на старый.

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

Создание и использование объектов в Java

18 июн. 2015 г.

Классы. Часть 1 – Введение.

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

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

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

Еще раз напомню, что класс — это шаблон объекта, а объект — это экземпляр класса. Поскольку объект является экземпляром класса, термины объект и экземпляр часто используются попеременно.

Объявление класса

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

Чтобы все было более понятно, приведу процесс объявления класса в нескольких простых слайдах…

Для объявления класса служит ключевое слово class.

C00001

Тут представлена самая простоя форма создания класса. В нем пока отсутствуют данные (поля класса – fields) и код (методы класса – methods). Тело класса, то есть поля и методы должны быть заключены между фигурными скобками.

Имя файла, в котором храниться текст класса, должно в точности совпадать с именем класса учитывая регистр букв. То есть, в нашем примере класс Example должен находится в файле Example.java.

Модификаторы (modifiers) могут отсутствовать при объявлении класса. О модификаторах мы поговорим чуть позже.

Как уже говорилось, класс может содержать данные (поля – fields) и код (методы – methods).

Рассмотрим добавление полей в класс.

C00002

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

Полей в классе может быть сколько угодно.

Ну и сразу же рассмотрим пример создания простого класса, где будут только поля данных но не будет методов. Чтобы это продемонстрировать нам понадобятся два класса, один с методом main() и другой который мы создадим сами, например класс Box.

C00004

Вот мы и создали наш первый класс Box, весь такой из себя маленький и красивый. Как видим все его поля находятся между фигурными скобками, а имя файла в котором он хранится – Box.java.

Как мы помним для создания объектов используется оператор new, который выделяет в heap'e (куче) память под объект, где так же размещаются все поля (данные) класса. При выделении памяти происходит инициализация полей.

Если нет конструкторов в классе (о которых мы поговорим чуть позже) которые инициализируют эти поля какими-то значениями, то поля инициализируются значениями по умолчанию. Это делает конструктор по умолчанию.

Еще раз акцентирую внимание что поля класса создаются в куче (heap'e), поэтому, даже если вы их не проинициализировали какими-либо значениями, их сразу же можно использовать в коде программы, так как дефолтный конструктор присваивает им значения по умолчанию при создании объекта. Переменные же в методах класса создаются в стеке, поэтому их необходимо инициализировать значениями перед использованием, так как там могут быть неизвестно какие значения.

Как уже было сказано, класс определяет новый тип данных. В данном случае новый тип данных назван Box. Это имя будет использоваться для объявления объектов типа Box. Важно понимать, что объявление class создает только шаблон, но не действительный объект. Таким образом, приведенный код не приводит к появлению никаких объектов типа Box.

C00003

Как я уже упоминал, чтобы создать объект типа Box нужно использовать оператор new. В данном примере слева, в строке 7 мы как раз и создаем новый объект Box и размещаем ссылку на него в переменную box1. Далее мы можем использовать этот объект и обратиться к его полям через ссылку на него – box1.

Для обращения к полям объекта используется точка (.) после имени объекта (ссылки на объект), например – box1.widht.

C00005

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

 

C00006При объявлении полей класса, их, так же как и обычные переменные, можно сразу инициализировать значениями, как показано на примере слева. Чтобы вывести поле label для объекта box1, добавим следующую строку в код класса Classes001:

C00007println("box1.label = " + box1.label);

Вывод измененного класса Classes001 представлен справа.

 

C00008К полям экземпляра, можно не только обращаться для чтения их значений, но и для изменения, хотя обычно так делать не рекомендуют. Поскольку обычно поля лучше менять через методы класса и именно так и рекомендуется делать – это и есть инкапсуляция, ну в самом простом ее понимании. На примере слева, в строке 14 мы изменили значение поля label для объекта box1.

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

C00009

Как вы уже должны были понять, наша программа состоит из двух классов: Classes001 и Box.

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

C00010Каждый объект содержит собственные копии полей экземпляра. Это означает, что при наличии двух объектов Box каждый из них будет содержать собственные копии полей depth, width, height и label. Важно понимать, что изменения переменных экземпляра одного объекта не влияют на переменные экземпляра другого.

На примере слева, в строке 17 мы создали еще одни экземпляр класса Box с именем box2 и в строке 18 присвоили полю label значение b0x.

Вывод у программы теперь такой:

C00011

Поля box2.label и box1.label имеют разные значения.

Чтобы лучше понять чем отличаются классы от объектов и как экземпляры классов (объекты) создаются в памяти, очень рекомендую посмотреть следующее видео:

Создание экземпляра класса (объекта)

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

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

C00012

Как видно из текста класса Classes001, в строка 25 и 30, для вычисления объема ящика, нам каждый раз необходимо обращаться ко всем трем полям экземпляра класса, что не очень то удобно, если таких ящиков будут тысячи.

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

Так же для вывода значений высоты, ширины и глубины, нам опять каждый раз надо обращаться к каждому из полей экземпляра класса Box. А что если создать метод, который будет выводить их все сразу? Ведь это куда удобней!

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

И так! Погнали дальше!

Объявление методов в классе, обычно делают после объявления переменных. Например, объявление метода может быть таким:

C00013

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

Кроме того, метод может возвращать какое-либо значение, как в нашем примере возвращает значение типа int через оператор return, а может не возвращать, и тогда тип возвращаемого значения указывается как void.

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

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

C00014

Здесь тип указывает тип данных, возвращаемых методом. Он может быть любым допустимым типом, в том числе типом класса, созданным программистом. Если метод не возвращает значение, типом его возвращаемого значения должен быть void. Имя служит для указания имени метода. Оно может быть любым допустимым идентификатором, кроме тех, которые уже используются другими элементами в текущей области определения. Список_параметров — последовательность пар “тип-идентификатор”, разделенных запятыми. По сути, параметры — это переменные, которые принимают значения аргументов, переданных методу во время его вызова. Если метод не имеет параметров, список параметров будет пустым.

Методы, тип возвращаемого значения которых отличается от void, возвращают значение вызывающей процедуре с помощью следующей формы оператора return:

C00015

Здесь значение — это возвращаемое значение. Но оператор return может использоваться и без возвращаемого значения если тип метода void, в этом случае оператор return просто прерывает выполнение метода.

C00016Ну и теперь попрактикуемся. Создадим методы в классе Box, что на примере слева.

Метод printVolume() не возвращает ни какого значения, поэтому возвращаемый тип указан как void. Данный метод просто выводит на консоль значение объема.

Метод printSizes() тоже не возвращает ни какого значения, а просто выводит на консоль значения полей.

Метод getVolume() возвращает значение типа double, которое вычисляется перемножением полей размеров и возвращается оператором return.

Метод setSameSize(double size), хотя не возвращает ни какого значения, но напротив устанавливает значения полей равными переданному аргументу в переменной size, имеющей тип double.

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

Обращение к методам объекта делается так же, как и обращение к полям объекта, то есть через точку (.), что мы и увидим в классе Classes001.

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

 

C00017

Теперь в классе Classes001 мы можем внести изменения, показанные на примере слева.

В строках 10-12 мы задали размеры для box1 так же как и прежде.

Но в строке 14 для объекта box2 мы уже использовали метод setSameSize() с параметром равным 15, который мы передали в метод.

Метод setSameSize() принял этот параметр и установил значения всех полей размеров равным этому значению (см. код класса Box выше).

Затем в строках 17 и 23 мы использовали метод printSizes() для каждого из объектов, который вывел на консоль их размеры.

В строках 18 и 24 мы использовали метод getVolume() для получения значения объема объектов.

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

Вывод у нашей программы теперь такой:

C00018

C00019Ну что же, нам пора продвинуться и создать метод, который будет задавать сразу все три размера.  На скрине слева я привел только этот метод, без всего остального кода класса Box, так как он остался неизменным. Мы просто добавили этот метод.

Обратите внимание, что в метод передаются три параметра.

Вызов этого метода для объекта box1 осуществляется следующим образом:

box1.setSizes(10, 20, 30);

Вывод программы не изменился, зато код класса Calsses001 сократился на две строки, так как теперь все три значения мы задаем в одной строке с помощью вызова метода setSizes(). На Bitbucket можно посмотреть как теперь выглядят классы Classess001 и Box.

Еще раз прошу обратить внимание на то каким образом метод определен, как заданы параметры передающиеся в него и как называются  эти переменные (w, h и d). Это нам скоро понадобится.

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

Конструктор инициализирует объект непосредственно во время создания. Его имя совпадает с именем класса, в котором он находится, а синтаксис аналогичен синтаксису метода. Как только он определен, конструктор автоматически вызывается непосредственно после создания объекта, перед завершением выполнения операции new.

Конструкторы выглядят несколько непривычно, поскольку не имеют ни возвращаемого типа, ни даже типа void. Это обусловлено тем, что неявно заданный возвращаемый тип конструктора класса — это тип самого класса. Именно конструктор инициализирует внутреннее состояние объекта так, чтобы код, создающий экземпляр, с самого начала содержал полностью инициализированный, пригодный к использованию объект.

Если не объявлен ни один конструктор, автоматически создается конструктор по умолчанию (без параметров).

Объявление конструктора может выглядеть так:

C00020

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

C00021Добавим в наш класс Box, конструктор, который будет инициализировать все размеры.

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

Не находите, что он очень похож на метод setSizes()?

Отличия в том, что не указан тип возвращаемого значения, а так же то что имя совпадает с именем класса.

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

 

Теперь выполнить создание и инициализацию объекта типа Box можно следующим образом:

Box box1 = new Box(10, 20, 30);
Box box2 = new Box(15, 15, 15);

Вывод данной программы не поменялся, но зато класс Classes001 сократился еще на несколько строк и теперь выглядит так:

C00022

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

Чтобы было лучшее понимание происходящего, то лучше посмотреть текущее состояние кода классов Classes001 и Box.

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

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

Box box1 = new Box();

Если мы попробуем сделать так, то получим ошибку что конструктор Box() не определен. Поэтому, если мы добавляем свои конструкторы в класс, то, если хотим использовать конструктор по-умолчанию (без аргументов), то должны его тоже объявить.

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

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

Метод finalize()

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

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

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

Короче! Хоть метод finalize() и существует, пользоваться им категорически не рекомендуется!

Все! На этом введение в классы закончено! :) Но это только начало :) Самое начало :)

16 июн. 2015 г.

Объектно-ориентированное программирование. Основные понятия.

Ponatiya_OOP

Grady__BoochНу вот мы и добрались до ООП :) И теперь надо определиться с основными понятиями, так как жить и кодить надо  по понятиям :)

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

Теперь же рассмотрим понятие объекта от авторитета – Гради Буча (мужик на фото справа).

 

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

Объектно-ориентированное программирование  — парадигма программирования, в которой программа строится из взаимодействующих объектов.

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

Объект является экземпляром класса. Классы описывают объекты, как бы являются шаблонами, на основе которых создаются объекты.

Чтобы понять как представить (описать) объект в классе надо уяснить еще несколько понятий. Этим понятиям в книгах по программированию обычно посвящены целые главы. У меня же нет ни какого особого и не особого желания описывать все это так же долго, нудно и подробно. Для этого есть книги и полно материалов в сети, в том числе и видео. Поэтому, для порядка, я приведу очень короткие определения данных понятий.

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

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

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

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

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

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

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

Ну или Google и Youtube вам в помощь.

Хотя приведу несколько не плохих видео по данной теме:

ООП. Часть 1.

 

ООП. Часть 2.

 

Понятие ООП

 

Введение в ООП

11 июн. 2015 г.

Строки. Часть 9 – форматирование строк (практика).

Str00049

Разомнёмся на небольшом и простом примере слева.

Первый printf() выводит число 108 в десятичном и шестнадцатеричном виде.

Второй printf() выводит переменные i2 и i3 в различных вариантах со знаком и без него, а так же при разной ширине полей и если эта ширина больше количества символов (учитывая скобки и знаки + и ), то дополнительные поля заполняются пробелами.

Вариант printf() для double делает почти все то же самое, что и для int, но стоит обратить внимание на округление.

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

Str00050

Третий printf(), выводящий строки a, b, c и d в обратном порядке тоже интересный. Он использует индексы параметров (строк), чтобы вывести и их в противоположном порядке. Обратите внимание что индексы (перед знаком доллара - $) идут от 4 до 1.

И еще один пример простой программы, которая заполняет массив случайными числами типа double и сперва выводит массив в неформатированном виде, а затем в форматированном (без сортировки).

Str00051

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

Str00052

Если хочется увидеть отсортированный массив, то допишите программу сами :)

Строки. Часть 8 – форматирование строк (теория).

В JDK 5 был введен механизм форматирования строк, подобный тому, что есть в операторе printf языка C. Этот механизм реализует метод format() класса String и многие другие методы, например System.out.printf().

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

Str00044Число x можно вывести на консоль с помощью выражения System.out.println(x). В результате на экране отобразится число с максимальным количеством значащих цифр, допустимых для данного типа. Например, в результате выполнения приведенного ниже фрагмента кода на экран будет выведено число 3333.3333333333335.

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


Форматирование строк делает их восприятие более приятным и понятным. Посмотрим какой вывод генерирует данная программа:

Str00045Как видите такой вывод более приятен и понятен. Например, первый оператор printf форматирует число x таким образом (%.2f), что длина общего поля равна размеру всего числа, а десятичная часть занимает от этого только два числа (поля).

Второй оператор printf уже форматирует значение x в виде числа, размер поля которого составляет 8 цифр, а дробная часть равна двум цифрам (%8.2f). Можете посчитать и убедиться. Кроме того обратите внимание что между символом равно (=) и числом появился пробел, поскольку для всего поля числа отведено 8 символов и все не занятые поля заполняются пробелами и происходит выравнивание по правому краю. Следующий printf уже отвел под поле числа 16 символов (%16.2f), что тоже видно на выводе программы. И последним я продемонстрировал работу метода format() класса String. Они имеют абсолютно одинаковый синтаксис, так как их функционал основан на классе Formatter.

Теперь будем разбираться более подробно с синтаксисом форматирования строк.

В методах printf() и format() можно задавать произвольное количество параметров. Пример вызова с несколькими параметрами приведен ниже.

System.out.printf("%s, в следующем году вам будет %d", name, age);

В данном случае значение переменной (параметра) name будет подставлено вместо спецификатора формата %s, а значение переменной (параметра) age – вместо спецификатора формата %d.

Каждый спецификатор формата, начинающийся с символа %, заменяется соответствующим параметром. Символ преобразования, которым завершается спецификатор формата, задает тип форматируемого значения: f — число с плавающей точкой; s — строка; d — десятичное число; и т.п. Символы преобразования описаны в таблице ниже:

Str00046
На заметку! Преобразование s можно использовать для форматирования любого объекта. Если этот объект реализует интерфейс Formattable, вызывается метод formatTo(). В противном случае для преобразования объекта в строку применяется метод toString().

Для даты и времени параметр х может быть представлен следующими символами преобразования:

DATE2
В составе спецификатора формата могут присутствовать флаги, управляющие форматом выходных данных. Назначение всех флагов описано в таблице ниже:

Str00047
Например, запятая, используемая в качестве флага, формирует разделитель групп, который зависит от локали (формата) установленной в вашей ОС. Так, в результате выполнения на Windwos 8.1 в русской локали, приведенного ниже оператора на экран будет выведена строка 3 333,33.

System.out.printf("%,.2f", 10000.0 / 3.0);

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

На заметку! Ряд правил форматирования зависит от специфических условий конкретной страны или региона.

В одном спецификаторе формата можно использовать несколько флагов, например последовательность символов "%, (.2f" указывает на то, что при выводе будут использованы разделители групп, а отрицательные числа будут помещены в скобки). Например строка:


System.out.printf("%, (.2f", -10000.0 / 3.0);

Сгенерирует вот такой вывод: (3 333,33)

Для создания форматированной строки без вывода ее можно использовать статический метод String.format(). Например:


String message = String.format("%s, в следующем году вам будет %d", name, age);

В строке, определяющей формат, может задаваться индекс форматируемого параметра. Индекс должен следовать непосредственно за символом % и завершаться знаком $. Пример использования индекса приведен ниже.


System.out.printf("%1$s %2$tB %2$te, %2$tY", "Дата:", new Date());

Данный код сформирует такой вывод: Дата: июня 11, 2015

Внимание! Индексы начинаются с единицы.

Вы также можете использовать флаг <, который означает, что форматированию подлежит тот же параметр, который был сформатирован последним. Так, приведенный ниже оператор дает тот же результат, что и рассмотренное ранее.


System.out.printf("%s %tB %<te, %<tY", "Дата:", new Date());

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

%[аргумент_индекс][флаги][ширина][.точность]символ_преобразования

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

В заключение теории приведу диаграмму которая показывает синтаксис спецификатора формата:

Str00048
P.S. В следующем разделе будет больше практики. Я разделил теорию и практику чтобы было удобней смотреть на таблицы символов преобразования и примеры на разных вкладках, а не мотать одну страницу вверх и вниз.