Смекни!
smekni.com

Общие представления о языке Java 5 (стр. 38 из 68)

Приведём ещё один аналогичный пример:

public void proc1(Double d1,Double d2,Double d3){

d3=d1+sin(d2);

}

Надежда на то, что в объект, передаваемый через параметр d3, возвратится вычисленное значение d3=d1+sin(d2), является ошибочной, так как при упаковке вычисленного результата создаётся новый объект.

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

public class UsableDouble{

Double value=0;

UsableDouble(Double value){

this.value=value;

}

}

Объект UsableDouble d можно передавать в подпрограмму по ссылке и без проблем получать возвращённое изменённое значение. Аналогичного рода оболочные классы легко написать для всех примитивных типов.

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

Заканчивая разговор о проблемах передачи параметров в подпрограмму, автор хочет выразить надежду, что разработчики Java либо добавят в стандартные оболочечные классы такого рода методы, либо добавят возможность передачи переменных в подпрограммы по ссылке, как, к примеру, это было сделано в Java-образном языке C#.

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

В объектном программировании принято использовать имеющиеся классы в качестве “заготовок” для создания новых классов, которые на них похожи, но обладают более сложной структурой и/или отличающимся поведением. Такие “заготовки” называются прародителями (ancestors), а основанные на них новые классы – потомками (descendants) или наследниками. Классы-потомки получают “в пользование” поля и методы, заданные в классах-прародителях, это называется наследованием (inheritance) полей и методов.

В C++ и Java вместо терминов “прародители” и “потомки” чаще используют неудачные названия “суперклассы” (superclasses) и “подклассы” (subclasses). Как уже говорилось, суперклассы должны быть примитивнее подклассов, но приставка “супер” подталкивает программиста к прямо противоположным действиям.

При задании класса-потомка сначала идут модификаторы, затем после ключевого слова class идёт имя декларируемого класса, затем идёт зарезервированное слово extends (“расширяет”), после чего требуется указать имя класса-родителя (непосредственного прародителя). Если не указывается, от какого класса идёт наследование, родителем считается класс Object. Сам класс-потомок называется наследником, или дочерним.

В синтаксисе Java словом extends подчёркивается, что потомок расширяет то, что задано в прародителе – добавляет новые поля, методы, усложняет поведение. (Но всё это делает класс более специализированным, менее общим).

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

Модификаторы, которые можно использовать:

- public – модификатор, задающий публичный (общедоступный) уровень видимости. Если он отсутствует, действует пакетный уровень доступа - класс доступен только элементам того же пакета.

- abstract – модификатор, указывающий, что класс является абстрактным, то есть у него не бывает экземпляров (объектов). Обязательно объявлять класс абстрактным в случае, если какой-либо метод объявлен как абстрактный.

- final – модификатор, указывающий, что класс является окончательным (final) , то есть что у него не может быть потомков.

Таким образом, задание класса-наследника имеет следующий формат:

Модификаторы class ИмяКласса extends ИмяРодителя {

Задание полей;

Задание подпрограмм - методов класса, методов объекта, конструкторов

}

Данный формат относится к классам, не реализующим интерфейсы (interfaces). Работе с интерфейсами будет посвящён отдельный раздел.

Рассмотрим в качестве примера наследование для классов описанной ранее иерархии фигур. Для простоты выберем вариант, в котором Figure - это класс-прародитель иерархии, Dot его потомок, а Circle - потомок Dot (то есть является “жирной точкой”). Напомним, что имена классов принято начинать с заглавной буквы.

Класс Figure опишем как абстрактный – объектов такого типа создавать не предполагается, так как фигура без указания конкретного вида – это, действительно, чистая абстракция. По той же причине методы show (“показать”) и hide (“скрыть”) объявлены как абстрактные. Напомним также, что если в классе хоть один метод является абстрактным, это класс обязан быть объявлен как абстрактный.

public abstract class Figure { //это абстрактный класс

int x=0;

int y=0;

java.awt.Color color;

java.awt.Graphics graphics;

java.awt.Color bgColor;

public abstract void show(); //это абстрактный метод

public abstract void hide(); //это абстрактный метод

public void moveTo(int x, int y){

hide();

this.x= x;

this.y= y;

show();

};

}

Поля x и y задают координаты фигуры, а color – её цвет. Соответствующий тип задан в пакете java.awt. Поле graphics задаёт ссылку на графическую поверхность, по которой будет идти отрисовка фигуры. Соответствующий тип также задан в пакете java.awt. В отличии от полей x, y и color для этого поля при написании класса невозможно задать начальное значение, и оно будет присвоено при создании объекта. То же относится к полю bgColor (от “background color”) – в нём мы будем хранить ссылку на цвет фона графической поверхности. Цветом фона мы будем выводить фигуру в методе hide для того, чтобы она перестала показываться на экране. Это не самый лучший, но зато самый простой способ скрыть фигуру. В дальнейшем при желании реализацию метода можно изменить – это никак не коснётся остальных частей программы. В параграфе, посвящённом конструкторам, в классе FilledCircle мы применим более совершенный способ отрисовки и “скрывания” фигур, основанный на использовании режима рисования XOR (“исключающее или”). Установка этого режима производится методом setXORMode. Такой режим можно использовать для всех наших фигур.

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

Рассмотрим теперь, как задаётся потомок класса Figure – класс Dot (“Точка”). Для Dot классы Object и Figure будут являться прародителями (суперклассами), причёт Figure будет непосредственным прародителем. Соответственно, для них класс Dot будет являться потомком (подклассом), причём для класса Figure – непосредственным потомком. Класс Dot расширяет (extends) функциональность класса Figure: хотя в нём и не появляется новых полей, зато пишется реализация для методов show и hide, которые в прародительском классе были абстрактными. В классе Figure мы использовали классы пакета java.awt без импорта этого пакета. В классе Dot используется импорт – обычно это удобнее, так как не надо много раз писать длинные имена.

package java_gui_example;

import java.awt.*;

/**

* @author В.В.Монахов

*/

public class Dot extends Figure{

/** Создаёт новый экземпляр типа Dot */

public Dot(Graphics graphics,Color bgColor) {

this.graphics=graphics;

this.bgColor=bgColor;

}

public void show(){

Color oldC=graphics.getColor();

graphics.setColor(Color.BLACK);

graphics.drawLine(x,y,x,y);

graphics.setColor(oldC);

}

public void hide(){

Color oldC=graphics.getColor();

graphics.setColor(bgColor);

graphics.drawLine(x,y,x,y);

graphics.setColor(oldC);

;

}

}

Отметим, что в классе Dot не задаются поля x, y, graphics и метод moveTo – они наследуются из класса Figure. А методы show и hide переопределяются (override) – для них пишется реализация, соответствующая тому, каким именно образом точка появляется и скрывается на экране.

Конструктор Dot(int x, int y, Graphics g) занимается созданием объекта типа Dot и инициализацией его полей. В методах show и hide используются методы объекта graphics. В методе show сначала во временной переменной oldC сохраняется информация о текущем цвете рисования. Затем в качестве текущего цвета устанавливается цёрный цвет (константа java.awt. Color.BLACK). Затем вызывается метод, рисующий точку, в качестве него используется рисование линии с совпадающими началом и концом. После чего восстанавливается первоначальный цвет рисования. Это необходимо для того, чтобы не повлиять на поведение других объектов, пользующихся для каких-либо целей текущим цветом. Такого рода действия являются очень характерными при пользовании разделяемыми (shared) ресурсами. Если вам при работе какого-либо метода требуется изменить состояние разделяемых внешних данных, сначала требуется сохранить информацию о текущем состоянии, а в конце вызова восстановить это состояние.

Термин override (“переопределить”) на русский язык часто переводят как “перекрыть”. Это может вводить в заблуждение, так как имеется ещё одно понятие – перекрытие области видимости (hiding). Такое перекрытие возникает в случае, когда в классе-потомке задаётся поле с тем же именем, что и в прародителе (но, возможно, другого типа). Для методов совпадение имён разрешено, в том числе с именами глобальных и локальных переменных.

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