Смекни!
smekni.com

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

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

В классе нашего приложения создадим на экранной форме панель, и будем вести отрисовку по ней. Зададим с помощью редактора свойств белый (или какой-нибудь другой) цвет панели – свойство background. Затем зададим переменную dot, которой назначим объект в обработчике нажатия на кнопку:

Dot dot=new Dot(jPanel1.getGraphics(),jPanel1.getBackground());

После создания объекта-точки с помощью переменной dot можно вызывать методы show и hide:

dot.show();

dot.hide();

Создадим на форме пункты ввода/редактирования текста jTextField1 и jTextField2. В этом случае становится можно вызывать метод moveTo, следующим образом задавая координаты, куда должна перемещаться точка:

int newX=Integer.parseInt(jTextField1.getText());

int newY=Integer.parseInt(jTextField2.getText());

dot.moveTo(newX,newY);

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

Рассмотрим теперь класс ScalableFigure (“Масштабируемая фигура”), расширяющий класс Figure. Он очень прост.

package java_gui_example;

public abstract class ScalableFigure extends Figure{

int size;

public void resize(int size) {

hide();

this.size=size;

show();

}

}

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

Зато появилось поле size (“размер”), и метод resize (“изменить размер”), расширяющий этот класс по сравнению с прародителем. Для того, чтобы изменить размер фигуры, отрисовываемой на экране, надо не только присвоить полю size новое значение, но и правильно перерисовать фигуру. Сначала надо её скрыть, затем изменить значение size, после чего показать на экране – уже нового размера. Следует обратить внимание, что мы пишем данный код на уровне абстракций, для нас не имеет значения, какого типа будет фигура – главное, чтобы она была масштабируемая, то есть являлась экземпляром класса-потомка ScalableFigure. О механизме, позволяющем такому коду правильно работать, будет рассказано далее в параграфе, посвящённом полиморфизму.

Опишем класс Circle (“Окружность”), расширяющий класс ScalableFigure.

package java_gui_example;

import java.awt.*;

public class Circle extends ScalableFigure {

Circle(Graphics g,Color bgColor, int r){ //это конструктор

graphics=g;

this.bgColor=bgColor;

size=r;

}

public void show(){

Color oldC=graphics.getColor();

graphics.setColor(Color.BLACK);

graphics.drawOval(x,y,size,size);

graphics.setColor(oldC);

}

public void hide(){

Color oldC=graphics.getColor();

graphics.setColor(bgColor);

graphics.drawOval(x,y,size,size);

graphics.setColor(oldC);

}

};

В классе Circle не задаётся новых полей – в качестве радиуса окружности используется поле size, унаследованное от класса ScalableFigure. Зато введён конструктор, позволяющий задавать радиус при создании окружности.

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

Таким образом, усложнение структуры Circle по сравнением со ScalableFigure в основном связано с появлением реализации у методов, которые до этого были абстрактными. Очевидно, класс Circle является более специализированным по сравнению со ScalableFigure, не говоря уж о Figure.

Поля x, y, color, bgColor, graphics и метод moveTo наследуется в Circle из класса Figure. А из ScalableFigure наследуются поле size и метод resize.

Следует особо подчеркнуть, что наследование относится к классам, а не к объектам. Можно говорить, что один класс является наследником другого. Но категорически нельзя – что один объект является наследником другого объекта. Иногда говорят фразы вроде “объект circle является наследником Figure ”. Это не страшно, если подразумевается, что “объект circle является экземпляром класса-наследника Figure”. Слишком долго произносить правильную фразу. Но следует чётко понимать, что имеется в виду, и злоупотреблять такими оборотами не следует.

Класс Circle является непосредственным (прямым) потомком ScalableFigure , а ScalableFigure – непосредственным (прямым) прародителем класса Circle . То есть для ScalableFigure класс Circle является подклассом, а для Circle класс ScalableFigure является суперклассом. Аналогично, для Figure подклассами являются и ScalableFigure, и Circle. А для Circle суперклассами являются и ScalableFigure, и Figure.

Поскольку в Java все классы— потомки класса Object, то Object является прародителем и для Figure, и для ScalableFigure, и для Circle. Но непосредственным прародителем он будет только для Figure.

Наследование и правила видимости. Зарезервированное слово super

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

Поля и методы, помеченные как private (“закрытый, частный”) наследуются, но в классах-наследниках недоступны. Это сделано в целях обеспечения безопасности. Пусть, например, некий класс Password1 обеспечивает проверку правильности пароля, и у него имеется строковое поле password (“пароль”), в котором держится пароль и с которым сравнивается введённый пользователем пароль. Если оно имеет тип public, такое поле общедоступно, и сохранить его в тайне мы не сможем. При отсутствии модификатора видимости или модификаторе protected на первый взгляд имеется необходимое ограничение доступа. Но если мы напишем класс Password2, являющийся наследником от Password1, в нём легко написать метод, “вскрывающий” пароль:

public String getPass(){

return password;

};

Если же поставить модификатор private, то в потомке до прародительского поля password не добраться!

То, что private-поля наследуются, проверить достаточно просто: зададим класс

public class TestPrivate1 {

private String s=”Значение поля private”;

public String get_s(){

return s;

}

}

и его потомок, который просто имеет другое имя, но больше ничего не делает:

public class TestPrivate2 extends TestPrivate1 {

}

Если из объекта, являющегося экземпляром TestPrivate2, вызвать метод get_s(), мы получим строку =”Значение поля private”:

TestPrivate2 tst=new TestPrivate2();

System.out.println(tst.get_s());

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

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

Существует “правило хорошего тона”: поля данных принято помечать модификатором private, а доступ к этим полям обеспечивать с помощью методов с тем же именем, но префиксом get (“получить” - доступ по чтению) и set (“установить” - доступ по записи). Эти методы называют “геттерами” и “сеттерами”. Такие правила основаны на том, что прямой доступ по записи к полям данных может разрушить целостность объекта.

Рассмотрим следующий пример: пусть у нас имеется фигура, отрисовываемая на экране. Изменение её координат должно сопровождаться отрисовкой на новом месте. Но если мы напрямую изменили поле x или y, фигура останется на прежнем месте, хотя поля имеют новые значения! Если же доступ к полю осуществляется через методы setX и setY, кроме изменения значений полей будут вызваны необходимые методы, обеспечивающие перерисовку фигуры в новом месте. Также можно обеспечить проверку вводимых значений на допустимость.

Возможен и гораздо худший случай доступа к полям напрямую: пусть у нас имеется объект-прямоугольник, у которого заданы поля x1,y1- координаты левого верхнего угла,x2,y2- координаты правого нижнего угла,w - ширина, h – высота, s- площадь прямоугольника. Они не являются независимыми: w=x2-x1, h=y2-y1, s=w*h. Поэтому изменение какого-либо из этих полей должно приводить к изменению других. Если же, скажем, изменить только x2, без изменения w и s, части объекта станут несогласованными. Предсказать, как поведёт себя в таких случаях программа, окажется невозможно!

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

Указанные выше правила хорошего тона программирования нашли выражение в среде NetBeans при установленном пакете NetBeans Enterprise Pack. В ней при разработке UML-диаграмм добавление в класс поля автоматически приводит к установке ему модификатора private и созданию двух public-методов с тем же именем, но префиксами get и set. Эти типы видимости в дальнейшем, конечно, можно менять, как и удалять ненужные методы.

Иногда возникает необходимость вызвать поле или метод из прародительского класса. Обычно это бывает в случаях, когда в классе-потомке задано поле с таким же именем (но, обычно, другим типом) или переопределён метод. В результате видимость прародительского поля данных или метода в классе-потомке утеряна. Иногда говорят, что поле или метод затеняются в потомке. В этих случаях используют вызов super.имяПоля или super.имяМетода(список параметров). Слово super в этих случаях означает сокращение от superclass. Если метод или поле заданы не в непосредственном прародителе, а унаследованы от более далёкого прародителя, соответствующие вызовы всё равно будут работать. Но комбинации вида super.super.имя не разрешены.

Использовать вызовы с помощью слова super разрешается только для методов и полей данных объектов. Для методов и переменных класса (то есть объявленных с модификатором static) вызовы с помощью ссылки super запрещены.