Кафедра ЮНЕСКО по НИТ Объектная модель в Java Часть 2. Интерфейсы и внутренние классы, параметры метода
Кафедра ЮНЕСКО по НИТ Параметры метода Вызов по значению (call by value)– метод получает значение, переданное ему вызывающим модулем. Вызов по ссылке (call by reference) – метод получает от вызывающего модуля адрес переменной. Метод может модифицировать значение, хранящееся в переменной, переданной по ссылке, но не может изменять значение переменной, переданной по значению. В Java всегда используется только вызов по значению. Это значит, что метод получает копии значений всех параметров.
Кафедра ЮНЕСКО по НИТ Пример метода для основного типа данных 1. Переменная x инициализируется копией значения параметра percent= Значение переменной x утраивается – теперь оно равно 30. Значение переменной percent остается равным Метод завершает свою работу, и параметр x больше не используется. // метод утраивает значение аргумента public static void tripleValue (double x){ x = 3*x; } // вызовем этот метод double percent = 10; tripleValue(percent);
Кафедра ЮНЕСКО по НИТ Пример метода для объектного типа данных 1. Переменная x инициализируется копией значения переменной harry, т.е. ссылкой на объект. 2. Метод raiseSalary применяется к этой ссылке на объект. Объект класса Employee, к которому относятся ссылки x и harry, увеличивает зарплату сотрудников на 200%. 3. Метод завершает свою работу, и параметр x больше не используется. Объектная переменная harry продолжает ссылаться на объект, в котором зарплата увеличена втрое. // метод утраивает значение поля у объекта public static void tripleSalary (Employee x){ x.raiseSalary(200) ; } // вызовем этот метод harry = new Employee(…); tripleSalary(harry);
Кафедра ЮНЕСКО по НИТ Метод, выполняющий обмен 2 объектов Метод не может изменять параметры основных типов. Метод может изменять состояние объекта, передаваемого как параметр. Метод не может изменять ссылки и переназначать их на новые объекты. public static void swap (Employee x, Employee y){ Employee temp=x; x=y; y=temp;} // вызовем этот метод Employee a = new Employee(Алиса…); Employee b = new Employee(Боб…); swap(a,b); Относится ли ссылка a к Бобу, а ссылка b – к Алисе? Изначально, ссылка x относится к Алисе, y – к Бобу. После окончания работы метода ссылка x – относится к Бобу. Все усилия по созданию метода были напрасны!
Кафедра ЮНЕСКО по НИТ Интерфейсы Интерфейс – множество требований, предъявляемых к классу. Объявление интерфейсов похоже на упрощенное объявление классов. От интерфейсов нельзя порождать объектов (из-за абстрактности методов), но можно их реализовывать.
Кафедра ЮНЕСКО по НИТ Объявление интерфейсов Объявление интерфейса может начинается с модификатора public (по умолч. default ). Ключевое слово abstract не требуется, т.к. все интерфейсы абстрактные. Затем слово interface и имя интерфейса, после которого может стоять extends и список интерфейсов наследования. public interface Drawble extends Colorable, Resizable {тело интерфейса} Все поля интерфейса должны быть public final static, все методы интерфейса public abstract. Данные модификаторы рекомендуется не писать в программе. public interface Moveable{ int right=1; int left=2; void moveLeft(); void moveRight(); }
Кафедра ЮНЕСКО по НИТ Интерфейсы. Пример Метод sort класса Array упорядочивает массив объектов при одном условии: объекты должны принадлежать классам, реализующим интерфейс Comparable: Это значит, что любой класс, реализующий интерфейс Comparable, должен иметь метод compareTo, получающий объект Object и возвращающий целое число. Существует и неявное требование: при вызове x.compareTo(y) метод compareTo должен уметь сравнивать два объекта и возвращать признак того, что один из них больше другого. Отрицательное значение – x меньше y, положительное – x больше y, равное 0 – объекты равны. public interface Comparable { int compareTo(Object other);}
Кафедра ЮНЕСКО по НИТ Интерфейсы. Пример Для сортировки объектов класса Employee нужно реализовать метод sort класса Array. В этом случае класс Employee должен реализовать интерфейс Comparable. Для того чтобы класс реализовывал интерфейс, нужно выполнить 2 действия: объявить, что класс предназначен для реализации интерфейса: поместить в класс определение всех методов, указанных в интерфейсе: class Employee implements Comparable {…} public int compareTo (Object otherObject){ Employee other = (Employee) otherObject; if(salary < other.salary) return -1; if(salary > other.salary) return 1; return 0;}
Кафедра ЮНЕСКО по НИТ Свойства интерфейсов 1. Интерфейсы – это не классы! С помощью операции new() невозможно создать экземпляр интерфейса. 2. Но несмотря на это, можно объявлять переменные, ссылающиеся на интерфейсы. 3. С помощью операции instanceof можно проверять, реализует ли данный объект интерфейс. 4. Аналогично классам, интерфейсы могут реализовывать иерархию наследования от интерфейсов. x = new Comparable(…); //не правильно Comparable x; x=new Employee(…);
Кафедра ЮНЕСКО по НИТ Выводы 1. Каждый класс может реализовывать любые доступные интерфейсы, при этом в классе должны быть реализованы все абстрактные методы 2. Если из разных источников наследуются методы с одинаковыми сигнатурами, то достаточно один раз описать метод 3. Интерфейс – полностью абстрактный класс. Но с помощью его механизмов можно реализовать пример множественного наследования.
Кафедра ЮНЕСКО по НИТ Некоторые «полезные» примеры применения интерфейсов Обратные вызовы, клонирование объектов, многопоточные приложения
Кафедра ЮНЕСКО по НИТ Интерфейсы и обратные вызовы При обратном вызове программист задает действия, которые должны выполняться всякий раз, когда происходит некоторое событие. Например, действие, которое должно быть выполнено при нажатии кнопки мыши. Класс javax.swing.Timer можно использовать для отсчета времени. Устанавливая таймер, необходимо указать, что должно произойти по истечении интервала времени. Программист должен передать таймеру объект некоторого класса, метод которого будет вызван. Класс объекта должен реализовывать интерфейс ActionListener из java.awt.event: public interface ActionListener{ void actionPerformed(ActionEvent event); }
Кафедра ЮНЕСКО по НИТ Интерфейсы и обратные вызовы import java.awt.*; import java.awt.event.*; import javax.swing.Timer; public class TimerTest{ public static void main(String[] args){ ActionListener listener=new TimePrinter(); Timer t=new Timer(10000, listener); t.start(); JOptionPane.showMessageDialog(null, Выход?); System.exit(0); } } class TimePrinter implements ActionListener{ public void actionPerformed(ActionEvent event){ Date now=new Date(); System.out.println(Текущее время+now); Toolkit.getDefaultToolkit().beep(); }
Кафедра ЮНЕСКО по НИТ Клонирование объектов После создания копии переменной и оригинал и копия представляют собой один и тот же объект. Изменение одной переменной повлечет изменение другой. оригинал копия Employee name = John salary = Копирование Employee original = new Employee(John, 50000); Employee copy = original; copy.raiseSalary(10); // Оригинал тоже изменился
Кафедра ЮНЕСКО по НИТ Клонирование объектов Если нужно, чтобы переменная copy представляла собой новый объект, в первый момент своего существования идентичный объекту original, но совершенно не зависимый от него, используется метод clone(). оригинал копия Employee name = John salary = Клонирование Employee name = John salary = Employee original = new Employee (John, 50000); Employee copy = (Employee) original.clone(); copy.raiseSalary(10); // Оригинал не изменился
Кафедра ЮНЕСКО по НИТ Клонирование объектов Метод clone() является защищенным методом класса Object. Т.е. только класс Employee может клонировать объекты своего класса. Если все поля класса имеют основной тип, то в двойнике они просто копируются. Если объект содержит ссылку на подобъект, то и оригинал, и клонированные объекты будут совместно использовать один и тот же объект – поверхностное клонирование. оригинал копия Employee name = John salary = payDay = Клонирование Employee name = John salary = payDay = GregorianCalendar
Кафедра ЮНЕСКО по НИТ Клонирование объектов Поверхностное копирование используется в случае, когда подобъект является неизменяемым (например, String). Если подобъект является изменяемым, то необходимо переопределять метод clone(), чтобы выполнить глубокое копирование, которое позволяет клонировать подобъекты наряду с содержащими их объектами. Для этого необходимо: реализовать интерфейс Cloneable. переопределить метод clone() с модификатором доступа public. При реализации интерфейса Cloneable необходимо перехватывать исключительную ситуацию CloneNotSupportedException.
Кафедра ЮНЕСКО по НИТ Клонирование объектов class Employee implements Cloneable { … public Object clone(){ // повышает уровень видимости try{ // вызов метода Object.clone() Employee cloned = (Employee) super.clone(); // клонирование изменяемых полей cloned.payDay = (GregorianCalendar)payDay.clone(); return cloned; } catch(CloneNotSupportedException e){ return null;} } Чтобы реализовать глубокое копирование, нужно приложить дополнительные усилия и организовать клонирование изменяемых полей экземпляра!
Кафедра ЮНЕСКО по НИТ Многопоточность и интерфейс Runnable Применение процедуры квантования времени (time slicing) на однопроцессорных компьютерах позволило запускать многопоточные приложения. Применение многопоточности: в задачах, где необходимо одновременно выполнять несколько действий; активные игры или подобные приложения.
Кафедра ЮНЕСКО по НИТ Многопоточность и интерфейс Runnable Поток выполнения в Java представляется экземпляром класса Thread. Необходимо от него наследоваться и перекрыть метод run(). Метод run() содержит действия, которые должны исполняться в новом потоке исполнения. public class mythread extends Thread{ … //создание конструкторов и методов public void run(){ //некоторое долгое действие, вычисление } public static void main(String[] args){ mythread mt = new mythread(); mt.start();// унаследованный метод, вызывает метод run() }
Кафедра ЮНЕСКО по НИТ Многопоточность и интерфейс Runnable Описанный выше подход обладает одним недостатком – отсутствием в Java множественного наследования. На помощь приходят интерфейсы: public class myrun implements Runnable{ … //создание конструкторов и методов public void run(){ //некоторое долгое действие, вычисление } public static void main(String[] args){ myrun mrun = new myrun(); Thread t = new Thread(mrun); t.start();// вызывает метод run() }
Кафедра ЮНЕСКО по НИТ Внутренние классы Внутренний (вложенный) класс – класс, определенный внутри другого класса. Зачем он нужен: Объект внутреннего класса имеет доступ к реализации объекта, который его создал, включая закрытые данные. Внутренний класс можно скрыть от других классов того же пакета. Безымянный (анонимный) внутренний класс удобен, если нужно «на лету» уточнить обратные вызовы. Внутренние классы очень удобны при создании событийно-управляемых программ.
Кафедра ЮНЕСКО по НИТ Пример Создать программу, в которой банковский счет управляется по таймеру: каждую секунду на счет добавляются проценты. Не будем использовать открытые методы, поскольку любой сможет вызвать их и изменить состояние счета. Применим внутренние классы, методы которых могут непосредственно манипулировать балансом. class BankAccount{ public BankAccount (double initialBalance){…} public void start (double rate){…} private double balance; private class InterestAdder implements ActionListener{ //внутренний класс … } }
Кафедра ЮНЕСКО по НИТ Продолжение примера Класс InterestAdder расположен внутри BankAccount. НО из этого не следует, что каждый объект BankAccount содержит поле типа InterestAdder! Класс InterestAdder является закрытым внутренним классом, что гарантирует безопасность. private class InterestAdder implements ActionListener{ private double rate; public InterestAdder (double aRate){ rate=aRate;} public void actionPerformed (ActionEvent event){ double interest=balance*rate/100; balance += interest; System.out.println(Баланс=+balance); } }
Кафедра ЮНЕСКО по НИТ Продолжение примера import java.awt.event.*; import javax.swing.*; public class InnerClassTest{ public static void main(String[] args){ BankAccount account=new BankAccount(1000); account.start(10); JOptionPane.showMessageDialog(null,Выход?); System.exit(0); } } class BankAccount{ private double balance; public BankAccount (double initialBalance){ balance = initialBalance; } public void start (double rate){ ActionListener adder = new InterestAdder(rate); Timer t = new Timer(1000, adder); t.start(); } private class InterestAdder implements … }
Кафедра ЮНЕСКО по НИТ Локальные внутренние классы Класс определяется локально в отдельном методе. Локальные классы никогда не объявляются с помощью модификаторов доступа. Область видимости класса – блок. Классы полностью скрыты от внешнего мира. Имеют доступ не только к полям своего внешнего класса, но и к локальным переменным метода, в котором класс объявлен. public void start (double rate){ class InterestAdder implements ActionListener{ private double rate; public InterestAdder (double aRate){ rate=aRate;} public void actionPerformed (ActionEvent event) { double interest=balance*rate/100; balance += interest; System.out.println(Баланс=+balance); } } ActionListener adder = new InterestAdder(rate); Timer t = new Timer(1000, adder); t.start(); }
Кафедра ЮНЕСКО по НИТ Анонимные вложенные классы Создается новый объект класса, реализующего интерфейс ActionListener, в котором в {} определен требуемый метод actionPerformed(). public void start (int interval, final boolean beep){ ActionListener listener = new ActionListener(){ public void actionPerformed(ActionEvent event){ Date now = new Date(); System.out.println(Звонок, время: + now); if(beep) Toolkit.getDefaultToolkit().beep(); } }; Timer t = new Timer(1000, listener); t.start(); }