Страницы

22 июл. 2015 г.

Интерфейсы. Часть 9 – фабричные методы.

Как я уже говорил тут, правильней рассматривать и понимать интерфейсы в соответствии с их названием (interface), то есть "протокол" или "договор" взаимодействия. Еще интерфейс можно представлять как "шлюз" ведущий к разным реализациям.
Типичным механизмом создания объектов, реализуемых посредством интерфейсов, является паттерн (шаблон) проектирования "Фабричный метод".
Суть паттерна практически полностью описывается его названием. Когда вам требуется получать какие-то объекты, например пакеты сока, вам совершенно не нужно знать как их делают на фабрике. Вы просто говорите «сделайте мне пакет апельсинового сока», а «фабрика» возвращает вам требуемый пакет. Как? Всё это решает сама фабрика, например «копирует» уже существующий эталон. Основное предназначение «фабрики» в том, чтобы можно было при необходимости изменять процесс «появления» пакета сока, а самому потребителю ничего об этом не нужно было сообщать, чтобы он запрашивал его как и прежде.
Как правило, одна фабрика занимается «производством» только одного рода «продуктов». Не рекомендуется «фабрику соков» создавать с учетом производства автомобильных покрышек. Как и в жизни, паттерн «фабрика» часто создается «одиночкой».
Фабричные методы избавляют проектировщиков от необходимости встраивать в код зависящие от приложения классы.
Ну и чтобы все стало более-менее понятно попрактикуемся…
В первом примере мы будем выпускать сервисы как продукт. И у нас будет фабрика которая выпускает эти продукты. Сервис и фабрика будут описаны как интерфейсы. И так же мы создадим по паре реализаций для сервиса и фабрики сервисов. Пример взят из книги "Философия Java". Правда я его немного изменил, чтобы он стал чуть более понятным на мой взгляд.




Вывод у программы следующий:
I0029
Все эти строки выводятся статическим методом serviceConsumer(), в который я так же добавил код приведения типов, причем очень интересного приведения, когда интерфейсная ссылка приводится к ссылке на объект класса реализующего этот интерфейс. Это делается в блоках if метода serviceConsumer().
Так же я добавил имя сервиса в код имплементации сервиса, а метод получения этого имени описан в классе, а не в интерфейсе.
И еще один примерчик из той же книги…

В нем создаются имплементации игр шашки и шахматы. Ну как бы шашки и шахматы :) Это же пример…

21 июл. 2015 г.

Интерфейсы. Часть 8 – обратные вызовы (callback).

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

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

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

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

Разумеется, таймер должен знать, какой метод он должен вызвать. Для этого таймеру нужно указать объект класса, реализующего интерфейс ActionListener из пакета java.awt.event. Этот интерфейс выглядит следующим образом:

public interface ActionListener extends EventListener {
     
public void actionPerformed(ActionEvent e);
}

По истечении заданного интервала времени таймер вызывает метод actionPerformed().

I0027Предположим, нам нужно каждые две секунды выводить на экран сообщение о текущем времени. Для этого необходимо определить класс, реализующий интерфейс ActionListener, а затем поместить операторы, которые нужно выполнить, в тело метода actionPerformed().

Обратите внимание на параметр ActionEvent метода actionPerformed(). Он содержит информацию о событии, например, об объекте, его породившем. В нашей программе детальная информация о событии не важна, поэтому можно просто проигнорировать этот параметр.

Затем следует создать объект данного класса и передать его конструктору класса Timer.

I0028

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

t.start() запускает таймер и на экране каждые две секунды будет появляться сообщение о текущем времени.

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

I0026

17 июл. 2015 г.

Интерфейсы. Часть 7 – практика.

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

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

По идее интерфейсы, как и следует из их названия, предоставляют API тем классам которые их имплементируют.

Интерфейсы это еще больший уровень абстракции чем абстрактные классы :)

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

Ну ладно, от слов к делу. Попрактикуемся на хорошем примере видео, которое я уже предлагал для вашего просмотра.

Интерфейсы в Java

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

I0024Во второй мутации данного проекта я добавил интерфейс IReplacer и его имплементацию в классе DefaultReplacer.

Код интерфеса IReplacer:

package pro.java.smile2;
public interface IReplacer {
String replace(IReader reader, String from, String to);
}

Код класса DefaultReplacer:

package pro.java.smile2;

public class DefaultReplacer implements IReplacer {
@Override
public String replace(IReader reader, String from, String to) {
return reader.read().replace(from, to);
}
}

Как видно метод объявленный в IRepalcer использует в качестве входного аргумента объект типа IReader, через который получает строку для замены, а так же подстроки для замены – с какой (from) на какую (to).

 

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

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

Кроме того я добавил в этот пример метод печати стека, дабы было видно что происходит.

Интерфейсы. Часть 6 – интерфейсы-маркеры.

Иногда полезно определить пустой интерфейс. Класс может реализовать этот интерфейс, указав его в секции implements. При этом нет необходимости реализовывать методы. Любой экземпляр класса становится экземпляром интерфейса. С помощью оператора instanceof Javaкод может проверить, является ли объект экземпляром интерфейса. Таким образом, эта техника полезна для предоставления дополнительной информации об объекте. Интерфейс Cloneable из пакета java.lang является примером интерфейса-маркера (marker interface). Он не определяет методов, но идентифицирует класс, внутреннее состояние которого можно клонировать методом clone() класса Object.

Пусть дан произвольный объект. Наличие у него работающего метода clone() можно определить с помощью следующего кода:

int[] aint = new int[10];
int[] bint;
if (aint instanceof Cloneable) bint = aint.clone();
else bint = null;

Еще одним примером интерфейса-маркера является интерфейс java.io.Serializable.

Интерфейсы. Часть 5 – вложенные интерфейсы.

Интерфейс может быть объявлен членом класса или другого интерфейса. Такой интерфейс называется интерфейсом-членом или вложенным интерфейсом. Вложенный в класс интерфейс может быть объявлен как public, private или protected. Это отличает его от интерфейса верхнего уровня или интерфейса вложенного в другой интерфейс, который должен быть либо объявлен как public, либо, как уже было отмечено, должен использовать уровень доступа, заданный по умолчанию.

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

Вложенный в класс интерфейс объявленный с модификатором private не может быть имплементирован каким-либо классом. Он может использоваться только внутри того класса где был объявлен.

Ну и небольшой примерчик, который демонстрирует все вышеописанное…

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

I0023

Первая строка выводится статическим методом aintPrt2() вложенного в класс А интерфейса Aint1. Вывод генерирует строка 80. Вторая строка выводится при помощи некоторый танцев с бубном, а именно при помощи метода AaPrint() вложенного класса Aa. Вложенные классы мы пока не изучали, поэтому данный бубен может быт не понятен. Далее уже все просто :) ну и по правде сказать лень расписывать подробно. Если чё, то пишите комменты :)

Интерфейсы. Часть 4 – статические методы.

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

имя_интерфейса.имя_статического_метода

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

I0020

Как видим на примере слева в интерфейсе MyInt определен статический метод prt1() с реализацией, а так же объявлен метод prt2() без реализации.

Так же мы создали класс Prt, который имплементирует интерфейс MyInt, но он не унаследовал метод prt1(), так как он является статическим.

Класс Interf01 у нас так же реализует интерфейс MyInt и кроме того он содержит метод main(). Поскольку он реализует интерфейс MyInt, так же возникла необходимость определения метода prt2().

Этот метод prt2() не является статическим и поэтому его без танцев с бубном из статического метода main() вызвать нельзя.

Танцы с бубном продемонстрированы в последних двух строках метода main().

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

I0021 

Первая строка выводится вызовом статического метода prt1() на интерфейсе. Вторая строка выводится вызовом метода prt2() на экземпляре класса Prt, а третья на экземпляре класса Interf01.

16 июл. 2015 г.

Интерфейсы. Часть 3 – наследование в интерфейсах.

Ключевое слово extends позволяет одному интерфейсу наследовать другой. Синтаксис определения такого наследования аналогичен синтаксису наследования классов, но в отличие от них интерфейсы могут наследоваться от любого множества интерфейсов, а не от одного как классы.
Интерфейс, который расширяет (наследуется) более одного интерфейса, наследует все методы и константы от каждого родителя и может определять собственные дополнительные абстрактные методы и константы.
Когда класс реализует интерфейс, который наследует другие интерфейсы, он должен предоставлять реализации всех методов, определенных внутри цепочки наследования интерфейса.
Приведем простой пример. На котором, я кстати покажу, что интерфейсы и классы могут содержаться в одном java файле, но в таком случае только один класс может быть public, остальные должны быть с пакетным доступом. Эффект ничем не отличается от создания отдельного java-файла для каждого класса и интерфейса. После компиляции получаются отдельные class файлы для каждого интерфейса и класса.

В этом примере интерфейс В наследуется от интерфейса А, в котором определены два метода с реализациями по умолчанию. В интерфейса В определен один метод без реализации по умолчанию.
Как видим в классе Ext1 мы реализовали только метод methB1(), поскольку для него нет реализации по умолчанию. Другие же методы – methA1() и methA2() были унаследованы в своей реализации по умолчанию.
В классе Ext2 мы реализовали все методы интерфейсов А и В переопределив их по своему.
Внимание стоит обратить на строки 54 и 58, где мы создаем объектные интерфейсные ссылки ext2 и ext3 соответственно. Но ссылка ext3 имеет интерфейсный тип А, именно поэтому на ней нельзя вызывать метод methB2(), хотя он у нас и реализован в классе Ext2.
Вывод у программы следующий:
I0018
GrabliДалее в наследовании интерфейсов начинаются грабельки. А что если в каком либо из родителей и потомков объявлены методы с одинаковой сигнатурой? А что если параметры методов совпадают, но отличается тип возвращаемого значения? И т.д и т.п.
Попробуем со всем этим разобраться… Хотя, возможно, все случаи тут и не перечислю, так как их достаточно много, но основные идеи постараюсь донести. А на остальные случаи грабли вам в помощь :)
  • Если класс реализует интерфейсы в которых есть методы с одинаковой сигнатурой и возвращаемым значением, но сам не переопределяет реализацию этих одинаковых методов, то тогда возникнет ошибка компиляции. Если же он переопределяет эти методы, то тогда все нормально.
  • Если в интерфейсах наследуемых один от другого есть одинаковые методы с реализациями по умолчанию, то предпочтение отдается реализации самого нижнего метода по умолчанию в цепочке, так как действует правило переопределения методов. Но если в классе переопределена реализация этого метода, то предпочтение отдается реализации метода в классе. Это естественное правило. Но благодаря новой форме ключевого слова super, можно обратиться к реализации метода по умолчанию родительского интерфейса из метода интерфейса наследника. Эта форма ключевого слова super выглядит следующим образом:

    имя_интерфейса.super.имя_метода();
    Если, допустим, из метода с реализацией по умолчанию интрефейса В, который унаследовался от интерфейса А, надо обратиться к методу по умолчанию интерфейса А, то это может выглядеть например так:

    A.super
.method();
  • Если наследуемые методы интерфейсов имеют одинаковые параметры, но различный тип возвращаемого значения, то возникает ошибка компиляции.

  • Теперь посмотрим это на примере:

    Программа генерирует следующий вывод:
    I0019
    Первые четыре строки генерируются имплементацией метода по умолчанию meth() интерфейса D, вызванные на объекте класса Dd. Последняя строка выводится имплементацией метода meth() в классе Cb вызванного на объекте этого класса. Опять же, обратите внимание на строки 47 и 50, на то каким образом определены типы ссылок.

    Перечисления. Часть 1 - знакомство.

    В предыдущем примере вы, наверное, заметили, что создавать интерфейс только для записи констант не совсем удобно. Начиная с версии Java SE 5 для этой цели в язык введены перечисления (enumerations). Создавая перечисление, мы сразу же указываем константы, входящие в него.

    Вместо интерфейса SharedConstants, описанного в предыщем примере, можно воспользоваться перечислением, сделав такую запись:

    public enum SharedConstants {NO, YES, MAYBE, LATER, SOON, NEVER}

    Как видите, запись сильно упростилась. Мы записываем только константы, не указывая их характеристики. Каков же, в таком случае, их тип? У них тип перечисления SharedConstants.

    Перечисления в языке Java образуют самостоятельные типы, что указывается словом enum в описании перечисления, но все они неявно наследуют абстрактный класс java.lang.Enum. Это наследование не надо указывать словом extends, как мы обычно делаем, определяя классы. Оно введено только для того, чтобы включить перечисления в иерархию классов Java API. Тем не менее мы можем воспользоваться методами класса Enum для получения некоторых характеристик перечисления, как показано на примере ниже:

    En001

    Обратите внимание, во-первых, на то, как задается цикл для перебора всех значений перечисления SharedConstants. В заголовке цикла определяется переменная scont типа перечисления SharedConstants. Метод values(), имеющийся в каждом перечислении, дает ссылку на его значения. Эти значения получает последовательно, одно за другим, переменная scont.

    Во-вторых, посмотрите, как можно узнать тип значений перечисления. Его возвращает метод getDeclaringClass() класса Enum. В нашем случае мы получим тип SharedConstants (с полным именем пакета).

    En002

    В-третьих, у каждой константы, входящей в перечисление, есть свой порядковый номер 0, 1, 2 и т. д. Его можно узнать методом ordinal() класса Enum.

     

    En004

    En003

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

    Обратите внимание, что статический импорт класса SharedConstants понадобился только для метода  ask(), так как в методе answer() данный тип указан явно. Попробуйте закомментировать строку импорта данного класса и посмотрите на результат. А так же попытайтесь понять почему так происходит. Хотя я, в принципе, уже объяснил это :)

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

    Интерфейсы. Часть 2 – константы в интерфейсах.

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

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

    Чтобы это лучше понять рассмотрим пример…

    I0015

    I0016

    В приведенном примере программы два класса Question и AskMe реализуют интерфейс SharedConstants, в котором определены константы NO, YES, MAYBE, SOON, LATER и NEVER.

    I0017

    I0014

    Как видно в самом интерфейсе определены только константы.

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

    И может быть например таким:

    I0013

    15 июл. 2015 г.

    Интерфейсы. Часть 1 – введение.

    Чтобы было проще понять интерфейсы в Java, можно их представлять как полностью абстрактные классы, то есть классы все методы которых не имеют реализации, а только лишь объявлены. Но так было до Java 8, в которой появилась возможность создавать в интерфейсе реализацию метода по умолчанию. Интерфейсы так же могут содержать поля, но все они будут объявлены как final и static, то есть по существу являются константами. Кроме того, интерфейсы служат для реализации подобия множественного наследования в Java, которое в чистом виде в Java не поддерживается.

    Как уже говорилось, унаследоваться (extends) в Java можно только от одного класса, каждый класс В или С происходит из неполной семьи, как показано на рисунке а ниже. Все классы происходят только от "Адама", от класса Object. Но часто возникает необходимость породить класс D от двух классов B и С, как показано на рисунке б. Это называется множественным наследованием (multiple inheritance). В множественном наследовании нет ничего плохого. Трудности возникают, если классы B и C сами порождены от одного класса А, как показано на рисунке в. Это так называемое "ромбовидное" наследование.

    I0001

    В самом деле, пусть в классе А определен метод f(), к которому мы обращаемся из некоторого метода класса D. Можем мы быть уверены, что метод f() выполняет то, что написано в классе А, т. е. это метод A.f()? Может, он переопределен в классах B и С? Если так, то каким вариантом мы пользуемся: В.f() или C.f()? Конечно, допустимо определить экземпляры классов и обращаться к методам этих экземпляров, но это совсем другая ситуация.

    В различных языках программирования этот вопрос решается по-разному, главным образом уточнением имени метода f(). Но при этом всегда нарушается принцип KISS. Вокруг множественного наследования всегда много споров, есть его ярые приверженцы и столь же ярые противники. Не будем встревать в эти споры, наше дело — наилучшим образом использовать средства языка для решения своих задач.

    Создатели языка Java после долгих споров и размышлений поступили радикально — запретили множественное наследование классов вообще. При расширении класса после слова extends можно написать только одно имя суперкласса. С помощью уточнения super можно обратиться только к членам непосредственного суперкласса.

    Но что делать, если все-таки при порождении надо использовать несколько предков? Например, у нас есть общий класс автомобилей Automobile, от которого можно породить класс грузовиков Truck и класс легковых автомобилей Car. Но вот надо описать пикап Pickup. Этот класс должен наследовать свойства и грузовых, и легковых автомобилей.

    В таких случаях используется еще одна конструкция языка Java — интерфейс. Внимательно проанализировав ромбовидное наследование, теоретики ООП выяснили, что проблему создает только реализация методов, а не их описание.

    Интерфейс (interface), в отличие от класса, содержит только константы, заголовки методов, и с Java 8 может содержать реализацию методов по умолчанию.

    Интерфейсы тоже размещаются в файлах с расширением .java, которые в свою очередь могут находится в пакетах и подпакетах, часто в тех же самых, что и классы, и тоже компилируются в class-файлы.

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

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

    Затем в фигурных скобках записываются в любом порядке константы, заголовки методов и реализации методов по умолчанию. Можно сказать, что в интерфейсе все методы абстрактные (те у которых нет реализации по умолчанию), но слово abstract писать не надо. Константы всегда статические, но слова static и final указывать не нужно. Все эти модификаторы принимаются по умолчанию. Все константы и методы в интерфейсах всегда открыты, не обязательно даже указывать модификатор public.

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

    interface Automobile{ . . . }
    interface Car extends Automobile{ . . . }
    interface Truck extends Automobile{ . . . }
    interface Pickup extends Car, Truck{ . . . }

    Таким образом, интерфейс — это только набросок, эскиз. В нем указано, что делать, но не указано, как это делать (если нет реализации по умолчанию).

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

    Как же тогда использовать интерфейсы, если невозможно создать объекты на их основе?

    Использовать нужно не интерфейс, а его реализацию (implementation). Реализация интерфейса — это класс, в котором расписываются методы одного или нескольких интерфейсов. В заголовке класса после его имени или после имени его суперкласса, если он есть, записывается слово implements и, через запятую, перечисляются имена интерфейсов.

    Вот как можно реализовать иерархию автомобилей:

    interface Automobile{ . . . }
    interface Car extends Automobile{ . . . }
    class Truck implements Automobile{ . . . }
    class Pickup extends Truck implements Car{ . . . }

    или так

    interface Automobile{ . . . }
    interface Car extends Automobile{ . . . }
    interface Truck extends Automobile{ . . . }
    class Pickup implements Car, Truck{ . . . }

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

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

    Методы, которые реализуют интерфейс, должны быть объявлены как public.

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

    I0006

    I0005

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

    Стоит обратить внимание что в классе AnimalSound мы создали массив объектных ссылок типа Soud (интерфейсный тип) и поместили туда ссылки на объекты, реализующие данный интерфейс.

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

    I0007

     

    И теперь посмотрим на классы Cow, Cat и Dog:

    I0002

    I0003

    I0004

    Как видно наши классы Cow, Cat и Dog которые имплементируют интерфейс Sound имеют каждый свою реализацию метода getSound().

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

    I0010

    I0011

    Как видно из примера класс MaxMin реализует два интрефейса.

     

    I0008

    I0009

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

    I0012

     

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

    Интерфейсы в Java
    Интерфейсы в Java
    Интерфейсы в Java
    Интерфейсы в Java. Но тут автор делает несколько ошибочных заявлений

    9 июл. 2015 г.

    Абстрактные классы. Практика.

    Abs01

    Abs02

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

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

    Совсем заезженный пример с фигурами уже надоел.

    Конечно это не ахти какой пример, но он просто напомнил о статических методах и полях в купе с абстрактным классом.

    Наследование. Часть 2 – абстрактные классы и методы.

    Абстрактный класс – это класс экземпляр которого нельзя создать. Ну и зачем же он тогда может быть нужен? А вот представьте себе нужен таки :)

    В ряде ситуаций нужно будет определять суперкласс, который объявляет структуру определенной абстракции без предоставления полной реализации каждого метода. То есть иногда придется создавать суперкласс, определяющий только обобщенную форму, которую будут совместно использовать все его подклассы, добавляя необходимые детали. Такой класс определяет сущность методов, которые должны реализовать подклассы. Например, такая ситуация может возникать, когда суперкласс не в состоянии создать полноценную реализацию метода. Примером может служить класс Number из стандартной библиотеки Java. Посмотрите его на досуге :)

    Абстрактные классы объявляются при помощи модификатора abstract. И как можно догадаться они могут содержать абстрактные методы, которые объявляются при помощи этого же модификатора.

    Java позволяет определить метод без реализации, объявив его с модификатором abstract. У абстрактного метода нет тела; у него есть только заголовок, заканчивающийся точкой с запятой.

    A0001

    Вот правила для абстрактных методов и абстрактных классов, которые их содержат:

    • Любой класс с абстрактным методом автоматически становится абстрактным и должен быть объявлен как abstract.
    • Нельзя создать ни одного экземпляра абстрактного класса.
    • Экземпляры подклассов абстрактного класса, можно создавать только в том случае, если все методы, объявленные как abstract, замещены и реализованы (то есть имеют тело). Такие классы часто называют реальными, чтобы подчеркнуть тот факт, что они не абстрактные.
    • Если подкласс абстрактного класса, не реализует всех методов, объявленных как abstract, то этот подкласс сам является абстрактным.
    • Методы, объявленные как static, private и final, не могут быть объявлены как abstract, поскольку такие методы не могут быть замещены подклассами. Точно так же класс, объявленный как final, не может содержать методов, объявленных как abstract.
    • Класс может быть объявлен как abstract, даже если он не содержит ни одного абстрактного метода. Такое объявление означает, что реализация класса все еще не закончена и класс будет служить родителем для одного или нескольких подклассов, которые завершат реализацию. Экземпляр такого класса не может быть создан.
    • Можно создавать объектные переменные абстрактных классов, однако такие переменные должны ссылаться на объект неабстрактного подкласса.

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

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

    А так же, чтобы знать что за животное издает этот звук мы создадим нормальный (не абстрактный) метод getType(), который будет возвращать тип животного.

    В классах наследниках мы уже сделаем для каждого свою реализацию абстрактного метода getSound().

    A0003

    A0004

    A0005

    A0006

    Ну и собственно сводим теперь все в одну работающую кучу :)

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

    Каждому из элементов массива мы присваиваем ссылку на объект подкласса класса Animal.

    Затем мы в цикле обходим массив и вызываем на каждом его элементе методы, которые выводят тип животного и его звук.

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

    A0007

    Как видим звуки соответствуют животным :)

    6 июл. 2015 г.

    Полиморфизм и наследование. Практика на понимание переопределения методов.

    Если какой-либо из методов базового класса Java был перегружен несколько раз, переопределение имени этого метода в производном классе не скроет ни одну из базовых версий (в отличие от C++). Поэтому перегрузка работает вне зависимости от того, где был определен метод — на текущем уровне или в базовом классе. Чтобы все стало понятнее попрактикуемся на простом примере и уже классическом в этом блоге примере. Есть три класса А, В и С. В наследуется от А, а С наследуется от В. Посмотрим что с ними будет на этот раз…

    SS004

    SS005

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

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

    Как видно из данного примера у нас есть только объект класса С, но он содержит все поля и методы унаследованные от своих предков, классов А и В.

    Полиморфизм – ковариантность возвращаемых типов.

    В JDK 5 появилась концепция ковариантности возвращаемых типов; этот термин означает, что переопределенный метод производного класса может вер­нуть тип, производный от типа, возвращаемого методом базового класса. Например:

    Co001

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

    Co002

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

    В примере я специально добавил аннотацию @Override, чтобы было явно, что метод build() переопределен в подклассе.

    Ковариантность, в данном примере, позволяет возвращать более специализированный тип Circle.

    В этой программе, так же, есть один интересный момент: мы переопределили метод toString(), наследуемый от класса Object.

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

    Классы. Практика на понимание создания объектов.

    Теперь, на основе предыдущих знаний еще чуть расширим свое сознание о том как и в какой очередности происходит создание объектов в памяти и инициализация полей значениями. Рассмотрим простой пример:

    CC001Вывод этой программы уже должен быть вам понятен:

    CC002

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

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

    Как видно из вывода программы при создании объекта Sub, сперва были вызваны инициализационные блоки и инициализатор поля суперкласса. Затем был запущен конструктор суперкласса, но поскольку метод display() переопределен в подклассе Sub, то он нам вывел значение поля set объекта данного подкласса. Данный вывод подсвечен желтым в скрине. Далее происходит вывод значения поля set суперкласса. Затем отрабатывают инициализационные блоки подкласса и инициализация поля set подкласса. Далее отрабатывает конструктор подкласса в котором выводится значение поля set подкласса.

    Полиморфные методы при вызове из конструктора.

    Работы по расширению сознания полиморфизма продолжаются! Разберем один очень интересный пример. Но сперва надо вспомнить о том как вызываются конструкторы суперклассов.

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

    В принципе, мы можем в конструкторе суперкласса вызвать переопределенный метод подкласса. Но тут кроется коварная ошибка, ведь конструктор подкласса еще не был вызван. На момент такого вызова известно только то, что объекты суперкласса были проинициализированы. Но конструктор суперкласса является лишь очередным шагом на пути по­строения объекта подкласса, поля которого еще не были инициализированы его конструктором на момент вызова переопределенного метода подкласса из конструктора суперкласса. Поэтому переопределенный метод может перейти во «внешнюю» часть иерархии, то есть обратиться к данным подкласса, которые еще не были проинициализированы его конструктором, а этом может привести к манипуляции с неинициализированными данными, что это наверняка приведет к катастрофе. Хотя мы знаем что поля класса инициализируются значениями по умолчанию, но не известно на какие побочные эффекты мы можем напороться. Тут все зависит от звезд и их расположения по отношению к вашему коду :)

    Мне кажется что предыдущий абзац уже вызвал побочный эффект в вашем мозгу? Да? Абзац полный? :)

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

    PC001

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

    PC002

    Как видно из вывода программы, метод Cricle.drow() был вызван еще до вызова конструктора Crircle(). И можно было бы напороться на ошибку NullPointerException.

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

    У происходящего есть и положительная сторона — по крайней мере, данные инициализируются значениями по умолчанию, а не каким-то мусором. Отрицательная это то, что компилятор молчал как партизан.

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

    Но если очень хочется, то почему бы и нет :)

    Grabli1