Показаны сообщения с ярлыком implements. Показать все сообщения
Показаны сообщения с ярлыком implements. Показать все сообщения

28 авг. 2015 г.

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

Анонимный класс – это локальный класс без имени. Можно объявить анонимный (безымянный) класс, который может расширить (extends) другой класс или реализовать (implements) интерфейс. Объявление такого класса выполняется одновременно с созданием его объекта посредством оператора new.

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

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

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

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

Теперь рассмотрим на практике простой пример:

AN0001

На примере слева красными рамками обозначены фрагменты кода где объявляются анонимные классы. Первый имплементирует интерфейс Iout, второй расширяет класс External. Обратите внимание на двоеточие после фигурных скобок закрывающих объявление класса, оно является обязательным, так как по существу объявление анонимного класса представляет собой выражение. Это означает, что его можно записать как часть большего выражения, например вызова метода. Я привел достаточно простые примеры, хотя синтаксис все равно может показаться сложным. И начал я с объявления анонимного класса реализующего интерфейс Iout, так как на этом примере более легко понять почему анонимные классы называются анонимными – так как у них нет имени. Как вы помните невозможно создать экземпляр интерфейса, поскольку он является полностью абстрактным классом, а тут мы создаем класс реализующий интерфейс, но у этого класса нет имени и ссылку на этот анонимный класс мы присваиваем интерфейсной переменной iout. Надеюсь все просто? Ни кто не запутался?

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

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

Первую строку выводит метод outPrint(), другие – extPrint().

Как видите, синтаксис определения анонимного класса включает так же и создание экземпляра этого класса используя для этого ключевое слово new, за которым следует имя супер класса или интерфейса и затем определение самого анонимного класса в фигурных скобках за которыми следует двоеточие. Если имя, следующее за ключевым словом new, это имя класса, то анонимный класс является подклассом этого класса. Если имя, следующее за ключевым словом new, представляет собой интерфейс, то анонимный класс реализует этот интерфейс и расширяет класс Object. Данный синтаксис не позволяет указать секции extends, implements или имя класса. В следствии этого анонимный класс может реализовать только один интерфейс.

AN0003

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

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

AN0004

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

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

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

Анонимные классы, так же как и локальные, имеют доступ к локальным переменным своего блока кода которые объявлены как final или они должны быть effectively final.

Анонимные классы имеют доступ ко всем членам своего вешнего класса.

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

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

В анонимном классе вы не можете объявить статические инициализационные блоки.

В анонимном классе вы не можете объявить интерфейс.

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

В анонимном классе вы можете объявить:

  • Поля
  • Дополнительные методы (даже если этих методов нет в классе родителе)
  • Инициализационные блоки экземпляра
  • Локальные классы

Ну и теперь рассмотрим примеры всех вышеприведенных утверждений.

AN0006

Начнем пожалуй с этого :) то есть с .this. В Принципе это можно было еще на первом примере показать, но чет запамятовал. В общем первый пример мутировал в текущий. Я добавил строку str в класс Outer и затем обратился к ней из анонимного класса. Эти две строки подсвечены желтым.

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

AN0007

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

А теперь попробуем смоделировать конструктор анонимного класса :)

AN0008

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

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

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

Вывод программы представлен ниже:

AN0009

Как видно из вывода программы аргумент i переданный в конструктор суперкласса Base доступен как в конструкторе суперкласса, так и в инициализаторе и методах анонимного класса, который в данном случае является и наследником класса Base и так же внутренним его классом. Именно поэтому ему доступно private static поле i. Я умышленно дал одинаковые названия аргументу и полю, чтобы продемонстрировать области видимости переменных и полей.

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

AN0010

AN0011

AN0012

 

 

 

 

 

 

 

В это программе не используются статические методы и поля для того чтобы продемонстрировать доступ к полям экземпляров в классах Base и External. По существу у нас есть два анонимных класса. Анонимный класс в классе Base является и вложенным в него и его же наследником, а в классе External анонимный класс является наследником класса Base и внутренним для класса External. Именно по этому в первом случае анонимный класс имеет доступ к private полю str класса Base, а во втором не имеет и использует для доступа к нему унаследованный метод getStr(). Собственно из вывода программы видно как она работает :)

AN0013Я немного изменил предыдущий пример, чтобы продемонстрировать наследование. Теперь класс External является наследником класса Base. И поэтому от туда был убран метод getThis() и добавлены конструкторы. Класс Main тоже претерпел небольшие изменения, туда была добавлена одна строка.

println("e.getStr = " + e.getStr());

Обратите внимание что анонимный класс находящийся внутри класса External так же является наследником класса Base.

AN0014

AN0015

 

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

17 авг. 2015 г.

Внутренние классы. Часть 2 – статические вложенные классы.

Статические вложенные классы (nested) очень похожи на классы верхнего уровня. Их обычно используют если необходимо логически связать два класса – внешний и внутренний. Интерфейсы так же могут быть статическими вложенными интерфейсами располагающимися внутри внешнего класса или интерфейса.

Статический вложенный класс определяется внутри другого внешнего класса. Это может выглядеть так:

NC0001

Как видно из кода, внешним классом является класс OuterClass, статическим вложенным классом является StaticNesterdClass, статическим вложенным интерфейсом является StaticNestedInterface.

Обратите внимание на границы внешнего класса, а так же вложенного статического класса и интерфейса.

По существу внешний класс для статического вложенного класса является как бы мини пакетом.

 

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

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

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

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

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

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

Ну и теперь немного практики…

NC0002

NC0003

NC0004

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

nst.getName() выводит private поле nestedName. Заметьте что оно не статическое и private. nst.getOuterName() выводит статическое private поле outerName внешнего класса Outer. Далее выводиться статическое поле out внешнего класса Outer. Мы смогли это сделать так как оно статическое. Далее выводится статическое поле nst вложенного класса Nested через уточнение именем класса Outer.

Затем мы создаем экземпляр класса Outer и получаем через него доступ к private static полю outerName через вызов метода getName() на экземпляре класса Outer. И последняя строка выводит private static поле вложенного класса через метод внешнего класса getStnst(), который имеет доступ даже к private static полям вложенного класса. Ну как не запутались? :) По идее пока все не очень сложно :)

NC0005

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

В коде, расположенном вне окружающего класса, на статический вложенный класс или интерфейс ссылаются по имени внешнего класса с последующим добавлением имени внутреннего класса (например, Outer.Nested). Для импорта статических классов-членов можно применять директиву import:

import pro.java.nested.Outer.Nested; // импорт вложенного класса Nested
import pro.java.nested.Outer.*; // импорт всех вложенных классов из класса Outer

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

Nested nst = new Nested("MyNested");

То есть в данном случае мы уже не видим что класс Nested является вложенным классом класса Outer, как это было явно видно до импорта:

Outer.Nested nst = new Outer.Nested("MyNested");

 

NC0007Теперь еще немного поговорим о вложенных интерфейсах. Я добавил в класс Outer вложенный интерфейс IGetNames и вложенный класс GetNames в котором реализовал этот интерфейс, а так же в класс Main добавил создание экземпляра класса GetNames и использование его методов.

NC0008

Здесь я привел лишь отрывки кода который был добавлен. В этом примере класс GetNames является вложенным в класс Outer, но ни что не мешает вложить его в интерфейс IGetNames.

 

Но тогда уже придется уточнять имя класса вложенного в интерфейс через имя внешнего класса и имя вложенного интерфейса:

Outer.IGetNames.GetNames gn = new Outer.IGetNames.GetNames();

NC0009

Для наглядности, так же приведу отрывки измененного кода в классе Outer. в классе Main была изменена лишь строка создания экземпляра класса GetNames, которая уже была приведена выше.

Оба два предыдущих примера генерируют следующий вывод:

NC0010

Три последние строки генерируются вызовом методов на экземпляре класса GetNames.

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

Интересно так же посмотреть на те .class файлы которые получились в результате наших изменений в коде классов.

NC0011

Как видим у нас получилось пять .class файлов. По одному на каждый из наших классов и интерфейсу. Особое внимание следует обратить на имена вложенных классов и интерфейса. Уровень вложенности классов разделяется знаком $. Знак доллара является валидным символов в именах классов, но предназначен для использования только компилятором и JVM.

Кстати сказать избавиться от длинной строки создания экземпляра класса GetNames можно все тем же импортом. Для этого нам надо импортировать этот класс по его полному имени:

import pro.java.nested.Outer.IGetNames.GetNames;

Тогда строка создания экземпляра класса GetNames будет выглядеть так:

GetNames gn = new GetNames();

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

NC0012Вложенные классы можно размещать и просто в интерфейсах. Любой класс, помещенный в интерфейс, автоматически объявляется как открытый (public) и статический (static). Так как класс объявляется как static, он не нарушает правил обращения с интерфейсом – этот вложенный класс всего лишь размещается в пространстве имен интерфейса. И как я уже показывал, вы даже можете реализовать окружающий интерфейс во внутреннем классе. Хитрый пример показан слева. И если вы его просто попробуете запустить из Eclipse, то ни чего не выйдет, хотя этот файл без проблем и ошибок скомпилируется. И он даже работает и его даже можно запустить, но используя магию правильное понимание. Это понимание можно почерпнуть здесь. Но все же его стоит чуть обновить до нынешних наших знаний.

И поможет нам в этом магия утилиты javap

NC0013

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

NC0014

Но можно сделать это и из Eclipse указав класс где находится метод main():

NC0015

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

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

NC0017

NC0016

На примерах слева и сверху представлены два класса Main, содержащий метод main() и SomeClass, содержащий вложенный класс Test, который так же содержит метод main().

После компиляции получается соответственно три файла с расширением .class:

NC0018

Если посмотреть на содержимое файлов SomeClass.class и SomeClass$Test.class при помощи javap, то увидим следующее:

NC0019

Как видим в байт коде класса SomeClass не присутствует код вложенного в него класса Test, поскольку он находится в отдельном своем .class файле, в котором есть метод main(). И этот .class файл мы можем запустить отдельно от класса Main. Так же мы можем запустить на исполнение и класс Main, и даже если мы удалим откомпилированный .class файл класса Test, то наша программа все равно будет работать правильно.

NC0020

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

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

NC0021

NC0022

Что тут стоить отметить, так это то что в классе Human, а так же в его вложенном классе Relations используется идентификатор relations во множестве мест и при этом не происходит ни какой путаницы. Кроме того, в отличие от примера на видео, я сперва создал объект статического вложенного класса, а уже только потом создал объект внешнего класса, чтобы подчеркнуть независимость одного от другого.

Теперь еще стоит поговорить о наследовании в статических вложенных классах.

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

И теперь опять попрактикуемся…

NC0023

NC0024

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

NC0025

Как видим в этом примере у нас есть два класса наследника вложенного класса Nested. Это классы Ext1 и Ext2. Причем Ext1 является внутренним классом для класса Nested и соответственно имеет доступ ко всем полям внешнего класса Outer. Обращаю внимание что класс Ext1 не статический, а внутренний, то есть inner (это наша следующая тема). Класс же Ext2 не является внутренним классом Nested хотя и наследует его и поэтому он не имеет доступа к полям класса Nested. Следует обратить внимание на интересный синтаксис создания экземпляра класса Ext1. Поскольку класс Ext1 не статический, то он не может быть создан без привязки к экземпляру его внешнего класса.

Если же класс Ext1 сделать статическим, то строка создания его экземпляра будет выглядеть для нас уже привычным образом:

Outer.Nested.Ext1 ext1 = new Outer.Nested.Ext1();

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

Можно так же привести примеры использования статических вложенных классов из реальной жизни. Что может быть более реального чем стандартная библиотека java в которой существует класс java.awt.geom.Rectangle2D который имеет два вложенных класса, Float и Double. Это очень простые классы форм и было бы просто ни к чему умножать количество высокоуровневых классов в этом пакете еще на два.

NC0026

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

NC0027

NC0028

Тут представлен окончательный вариант с реализацией интерфейса IStack двумя классами FixedStack и DynStack. Причем класс DynStack является вложенным в класс FixedStack, который в свою очередь вложен в интерфейс IStack. Класс DynStack наследуется от класса FixedStack и поэтому там переопределен только один метод push(), который увеличивает размер стека в два раза при его заполнении.

Стоит обратить внимание на то что для класса FixedStack мы не писали слово static, хотя он таковым и является поскольку является вложенным в интерфейс классом. А вот для класса DynStack нам уже пришлось написать слово static, так как если бы мы этого не сделали, то этот класс был бы внутренним, то есть inner – не статическим.

Ну и на последок длинная портянка вывода данной программы:

NC0029

Первая строка выводится сразу же после создания объекта fix. Тогда еще стек пустой.

Затем мы добавили в стек единицу и вывели его состояние.

После добавили строку и опять вывели состояние.

Затем добавили значение типа double 55.55 и вывели состояние.

Затем попытались добавить ссылку null, что вполне легитимно для Object, но нам выдалось сообщение что стек полон.

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

После этого напечатали звездочки и создали объект динамического стека.

Добавили туда 10 вывели значение.

Добавили другую строку и снова вывели значение.

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

После этого значение 77.77 было изъято из стека и содержимое стека было выведено на экран.

За сим все! Кино про статические вложенные классы закончено :)

Далее в программе изучение внутренних inner классов.

Да и вообще далее еще много чего :)

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

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