30 июн. 2015 г.

Пакеты в Java.

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

Зачем нужны пакеты в Java и что это такое

  • Задание пространства имен, предотвращение коллизий имен классов
  • Логическая группировка связанных классов
  • Инкапсуляция - сокрытие деталей реализации за счет модификаторов доступа

Платформа Java содержит пакеты, имена которых начинаются на com, java, javax и org. Посмотрите это в сырцах, не поленитесь. Основные классы языка входят в пакет java.lang. Различные вспомогательные классы находятся в java.util. Классы для ввода и вывода входят в java.io, а классы для работы в сети – в java.net. Некоторые их этих пакетов содержат подпакеты. Например, java.lang содержит два специализированных пакета java.lang.reflect и java.lang.ref, а java.util содержит подпакет java.util.zip, который в свою очередь содержит классы для работы с ZIPархивами.

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

В стандартную библиотеку Java API входят сотни классов. Каждый программист в ходе работы добавляет к ним десятки своих классов. Множество классов растет и становится необозримым. Уже давно принято отдельные классы, решающие какую-то одну определенную задачу, объединять в библиотеки классов. Но библиотеки классов, кроме стандартной библиотеки, не являются частью языка.

Разработчики Java включили в язык дополнительную конструкцию — пакеты (packages). Все классы Java распределяются по пакетам. Кроме классов пакеты могут содержать интерфейсы и вложенные подпакеты (subpackages). Образуется древовидная структура пакетов и подпакетов.

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

Каждый пакет создает одно пространство имен (namespace). Это означает, что все имена классов, интерфейсов и подпакетов в пакете должны быть уникальны. Имена в разных пакетах могут совпадать, но это будут разные программные единицы. Таким образом, ни один класс, интерфейс или подпакет не может оказаться сразу в двух пакетах. Если надо в одном месте программы использовать два класса с одинаковыми именами из разных пакетов, то имя класса уточняется именем пакета: пакет.Класс. Такое уточненное имя называется полным именем класса (fully qualified name).

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

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

Определение пакета

Чтобы определить пакет для какого-либо класса, следует использовать директиву package. Если в Java-коде присутствует ключевое слово package, оно должно быть первой лексемой кода в файле Java, то есть должно следовать сразу после комментариев и пробелов. После ключевого слова должно стоять имя требуемого пакета и точка с запятой. Например:

package pro.java.pkg001;

Все файлы с исходными текстами и тем-более откомпилированные файлы с байт-кодом (расширение .calss) должны находится в соответствующих под-каталогах, как, например в данном случае в подкаталоге pro\java\pkg001. Все современные IDE при создании пакета, сразу же создают соответствующую файловую структуру в каталоге проекта, так что об этом можно особо не беспокоиться, но знать необходимо.

Поскольку строка package имя; только одна и это обязательно первая строка файла, каждый класс попадает только в один пакет или подпакет.

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

Компилятор всегда создает для таких классов безымянный пакет (unnamed package), которому соответствует текущий каталог (current working directory) файловой системы.

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

Имя пакета может быть любым, главное чтобы оно было уникальным. Соглашение "Code Conventions" рекомендует записывать имена пакетов строчными буквами. Тогда они не будут совпадать с именами классов, которые, по соглашению, начинаются с прописной буквы. Кроме того, соглашение советует использовать в качестве имени пакета или подпакета доменное имя своего сайта, записанное в обратном порядке, например:

com.oracle.developer

Самое главное правило – это то, что имя пакета должно быть уникальным.

Одной из важных функций пакетов является разделение пространства имен Java и предотвращение конфликта имен между классами. Например, классы java.util.List и java.awt.List можно различить только по именам их пакетов. Однако важно, чтобы различались и имена самих пакетов.

Как работают пакеты

Использование класса из пакета:

  • Классы текущего пакета и пакета java.lang всегда видны
  • Классы других пакетов доступны по полному имени с пакетом, но если они имеют соответствующие модификаторы доступа
  • Можно использовать директиву import

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

А вот классы и их поля и методы принадлежащих другим пакетам, если у них нет ни каких модификаторов доступа, уже не доступны даже по полному имени пакета и класса.

Чтобы отдохнуть от теории и понять ее получше немного попрактикуемся и рассмотрим на примере первый пункт использование класса из пакета. Создадим пакет pro.java.pkg001 и в нем два класса:

P0001

P0002

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

P0003

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

Очень важно заметить, что при компиляции файлов находящихся в пакетах (за исключением пакета по умолчанию), в class-файл попадает полное имя класса, то есть имя_пакета.имя_класса. Поэтому и запускать класс содержащий метод main() надо по полному имени. Кроме того, все другие пакеты и классы, используемые вашей программой должны быть доступны через classpath. Более подробно мы поговорим об этом в конце данной статьи.

Импорт классов и пакетов

Класс пакета pro.java.pkg001 может ссылаться на любой другой класс в pro.java.pkg001 по его простому имени. А поскольку классы пакета java.lang являются базовыми для языка Java, любой Java-код может ссылаться на любой класс этого пакета по его простому имени. Значит, всегда можно набирать String вместо java.lang.String. Однако по умолчанию для всех других классов нужно указывать полные имена. Поэтому, чтобы применить класс File пакета java.io, следует набрать java.io.File.

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

import java.io.File; // Теперь можно набирать File вместо java.io.File

Чтобы импортировать целый пакет классов, введите после import имя пакета, символы .* и точку с запятой. Например, чтобы использовать класс File вместе с несколькими другими классами пакета java.io, нужно просто импортировать целый пакет:

import java.io.*; // Теперь простые имена можно применять для всех классов из java.io

Этот синтаксис импорта пакета не относится к подпакетам. Даже если я импортирую пакет java.util, мне все еще придется обращаться к классу java.util.zip.ZipInputStream по его полному имени. Если два класса, импортированные из разных пакетов, называются одинаково, к ним нельзя обращаться по простому имени; во избежание двусмысленности к обоим классам следует обращаться по их полным именам.

Начиная с версии Java 5 в язык введена еще одна форма оператора import, предназначенная для импорта статических полей и методов класса — оператор import static. Например, можно написать оператор:

import static java.lang.Math.*;

После этого все статические поля и методы класса Math можно использовать без указания имени класса. Вместо записи:

double r = Math.cos(Math.PI * alpha);

можно записать просто

double r = cos(PI * alpha);

Теперь разгаданы и другие строчки, которые были в наших исходниках, например:

import static pro.java.util.Print.*;
import static pro.java.util.Strings.*;

Подчеркнем, что оператор import вводится только для удобства программистов и слово "импортировать" не означает никаких перемещений классов.

Директивы import позволяют компилятору получить полные имена всех используемых классов, полей и методов по их коротким именам. В class-файл попадают полные имена, подстановка содержимого не происходит. Поэтому при запуске программы все используемые классы должны присутствовать в classpath.

Структура файла Java

Теперь можно описать структуру исходного файла с текстом программы на языке Java.

  • В первой строке файла может быть необязательный оператор package.
  • В следующих строках могут быть необязательные операторы import.
  • Далее идут описания классов и интерфейсов (интерфейсы будут рассмотрены позже).

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

Директив import может быть сколько угодно.

Существует еще несколько важных ограничений по файлам Java. Во-первых, каждый файл может содержать максимум один класс, объявленный как public. Такой класс предназначен для использования другими классами в других пакетах. Более подробно public классы и связанные с ними модификаторы мы рассмотрим в следующей статье. Данное ограничение по открытым классам относится только к классам верхнего уровня; как вы скоро узнаете, класс может содержать любое количество вложенных, или внутренних, классов, объявленных как public.

Второе ограничение касается имени файла в Java. Если файл Java содержит класс public, то имя файла должно представлять собой имя public класса, к которому присоединено расширение .java. Например, если Point является public классом, его исходный код должен находиться в файле Point.java.

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

Соглашение "Code Conventions" рекомендует открытый класс, если он имеется в файле, описывать первым.

Даже если ваши классы не являются public, полезно определять только по одному классу на файл и давать файлу имя класса. Для технологии Java характерно записывать исходный текст каждого класса в отдельном файле. В конце концов, компилятор всегда создает class-файл для каждого класса.

Исполнение классов в пакетах и CLASSPATH

GrabliС запуском нашей простой программы из Eclipse, что была в самом начале, нет ни каких проблем, а вот при запуске из командной строки они могут возникнуть.

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

Давайте запустим и посмотрим (обращаем внимание на пути файлов):

P0004

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

javap спасет отца русской демократии! Давайте посмотрим что там у нас скомпилилось?

P0005

Как видим наш класс сейчас называется pro.java.pkg001.Main, то есть наш класс содержит полное имя, включая имя пакета. И кстати обратите внимание на то как объявлен массив String[].

Для контраста рассмотрим любой класс, где мы не использовали пакеты.

P0006

Рассмотрим откомпилированный класс Classes001.

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

Давайте попробуем запустить наш класс Main по полному имени и посмотрим что получиться.

 

P0007

Как видим у нас опять ошибка.

Чтобы наша программа запустилась, должно выполняться одно из следующих двух условий. Либо программа должна выполняться из каталога, расположенного непосредственно над каталогом pro, то есть в нашем случае запуск должен происходить из каталога bin, либо переменная среды CLASSPATH должна содержать путь к каталогу pro, либо параметр –classpath должен указывать путь к каталогу pro во время выполнения программы с помощью java.

P0008

P0009

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

Ну и на последок можно глянуть хорошее видео по этой теме

Пакеты (pagkages) в Java

29 июн. 2015 г.

Классы. Практика: паттерн проектирования Builder.

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

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

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

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

B0001

Допустим у нас есть класс Contact представленный слева. В нем несть несколько однотипных строковых полей.

Конечно, когда мы захотим создать объект этого класса в среде разработки, то она нам может подсказать названия полей, но даже в этом случае легко ошибиться. Например, перепутать name с surname.

Паттерн Builder как раз и предназначен для уменьшения вероятности таких ошибок.

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

Для этого класса создание объекта будет выглядеть так:

Contact c = new Contact("Петров", "Вася", "p@v.ru", "1234", "Уфа");

Здесь я намеренно допустил ошибку, перепутав имя и фамилию местами.

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

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

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

B0002

B0003

B0004

И вывод данной программы:

B0005

Сразу обратите внимание на то, что в классе Contact все поля final, а в классе ContactBuilder все поля не имеют этого модификатора. Это является общераспространённой практикой при создании паттерна Builder, хотя это можно и изменить исходя из нужд вашего приложения.

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

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

Это мы рассмотрели самый простой вариант данного паттерна. Как видно из приведенного выше примера, используя данную реализацию паттерна, можно создать такой объект Contact все поля которого могут получить значения по умолчанию, то есть null. Но можно создать такую реализацию данного паттерна, что некоторые поля будут обязательны к заполнению, а некоторые опциональные могут получать значения по умолчанию, которые мы для них определим.

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

Сделаем поле surname обязательным, а остальные как и было опциональными, но присвоим им наши значения по умолчанию. Для  этого нам надо изменить класс ContactBuilder и, соответственно, его применение в  Classes014.

B0006

Как видим в классе ContactBuilder мы добавили конструктор и убрали метод установки значения surname, которое теперь устанавливается в конструкторе и является final, то есть обязательным полем.

А использование ContactBuilder выглядит теперь вот так:

B0009

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

B0008

На этом с паттерном Builder мы не прощаемся. Мы еще встретимся с ним на практике после изучения абстрактных и внутренних классов.

Классы. Практика: строки и массивы как аргументы.

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

По существу это практика на понимание ссылочных типов данных и нацелена она на то чтобы понять разницу между объектом и ссылкой на объект. Поехали!

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

Далее вывод данной программы:

CP0001

Кратенько поясню данный вывод. В строке 8 основной программы создается объект str1, который содержит строку Str1. В 9 строке создается строковая переменная str2 которая так же содержит строку Str1. НО! Хотя эти строки и создаются по разному, это один и тот же объект, что видно из вывода программы. С выводом str3 и str4 все должно уже быть более мнее вам понятно. А затем при помощи метода intern() в строке 20 мы заставляем str4 указывать на тот же объект (строку) что и str1 с str2. С массивами еще интереснее. Мы создаем класс dim1, внутри которого уже формируется массив из переданных аргументов. И далее на эти аргументы мы воздействуем из Class013 через массив strArray. А затем делаем наоборот. Это возможно поскольку мы работаем со ссылочными типами.

На эти грабли частенько наступают начинающие программисты. Если что-то не понятно, то пишите комменты.

Классы. Часть 13 - модификатор private для полей и методов.

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

P002

P003

Закомментированные строки кода вызывают ошибку компиляции, так как private поля и методы доступны только внутри класса. Получить доступ к ним можно только через открытые методы класса. Это сделано для того, чтобы не было возможности как угодно и кому угодно модифицировать поля класса и получать доступ к методам.

В Java существует такое понятие как сеттеры и геттеры (set, get). Это методы, соответственно, для установки или получений значений полей класса.

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

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

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

  • литералы
  • переменные
  • объекты
  • методы (с соответствующим аргументу возвращаемым значением)

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

M0001

M0002

 

Вывод у этой программы простой:

M0003

Как видим ни чего сложного тут нет. Главное чтобы методы, передаваемые как аргументы, возвращали тот же тип данных, что и аргумент. Хотя иначе компилятор даже не скомпилирует программу, а среда разработки покажет ошибку.

Если что-то не понятно, то очень советую посмотреть видео ниже:

Основы понимания методов классов

И особенно второе видео из этой серии, где как раз и говориться о том, что мы только что обсуждали…

Передача аргументов в методы

Классы. Часть 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

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