методами доступа public и private.




Else

stck[++tos] = item;

}

 

int pop() {

if (tos < 0){

System. out. println("Стек пуст");

return 0;

}

Else

return stck[tos--];

}

}

public class TestStack {

public static void main(String[] args) {

Stack mystack1 = new Stack(5);

Stack mystack2 = new Stack(8);

 

for (int i=0; i<5; i++) mystack1.push(i);

for (int i=0; i<8; i++) mystack2.push(i);

 

System. out. println("Стек в mystack1:");

for (int i=0; i<5; i++)

System. out. println(mystack1.pop());

 

System. out. println("Стек в mystack2:");

for (int i=0; i<8; i++)

System. out. println(mystack2.pop());

}

}

 

Данные или переменные, определенные в классе, называются переменными экземпляра или экземплярными переменными. В данном примере это - stck[] и tos. Код содер­жится внутри методов (push, pop). Все вместе, методы и переменные, опреде­ленные внутри класса, называются членами класса. Каждый экземпляр класса (т. е. каждый объект класса - mystack1, mystack2) содержит свою собственную копию этих переменных. Таким образом, данные одного объекта отделены от данных другого.

В тексте данной программы содержится 2 класса: Stack и TestStack. Как в этом случае должен называться файл в котором данная программа храниться? Ответ: TestStack.java, т.к. класс TestStack содержит метод main() и имеет объявление public. В исходном файле может быть только один класс объявленный как public и любое кол-во классов без этого объявления.

 

Конструкторы

Конструктор (Stack)инициализирует объект после его создания. Он имеет такое же имя, как класс, в котором он постоянно находится. Если конструктор определен, то он автоматически вызывается сразу же после того, как объект создается, и прежде, чем завершается вы­полнение операции new. Конструкторы не имеют ни спецификатора возвращаемого типа, ни специфи­катора void. Неявным возвращаемым типом конструктора класса является тип самого класса. Работа конструктора за­ключается в том, чтобы инициализировать внутреннее состояние объекта.

Спецификаторы доступа

Инкапсуляция связывает данные с кодом, который манипу­лирует ими. Однако инкапсуляция обеспечивает другой важный атрибут: управление доступом. Через инкапсуляцию можно управлять доступом раз­личных частей программы к членам класса и предотвращать неправильное использование таких членов. Например, разрешая доступ к данным только через хорошо определенный набор методов, есть возможность предотвраще­ния неверного использования этих данных. Таким образом, при правильной реализации класс создает для использования "черный ящик", внутренняя работа которого не доступна для вмешательства.

Спецификаторы доступа Java: public (общий), private (частный) и protected (защищенный).

Начнем с определения спецификаторов public и private. Когда элемент класса модифицирован спецификатором public, то к этому элементу возмо­жен доступ из любой точки программы. Если член класса определен как private, к нему могут обращаться только члены этого класса. К элементам без спецификатора доступа можно получить доступ из этого же пакета (Понятие «пакет» будет рассмотрено позднее). Спецификатор protected применяется только при использовании наследования и будет рассмотрено позднее.

 

/* Эта программа демонстрирует различие между

методами доступа public и private.

*/

class Test {

int a; // доступ по умолчанию (public)

public int b; // общий (public) доступ

private int с; // частный (private) доступ

 

// методы для доступа к переменной с

void setc(int i) { // установить значение с

с = i;

}

 

int getc() { // получить значение с

return с;

}

}

 

class AccessTest {

public static void main(String args[]) {

Test ob = new Test();

ob.a = 10; ob.b = 20; //к а и b возможен прямой доступ.

 

// ob.c = 100; // Ошибка!

// к переменной c возможен доступ только через ее методы.

ob.setc(100);

 

System. out. println("a,b и с:"+ob.a+" "+ob.b+" "+ob.getc());

}

}

 

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

 

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

 

class Test2class {

int a,b;

public static void main(String args[]) {

Test2class ot_1 = new Test2class();

ot_1.a = 11;

ot_1.b = 22;

 

Test2class ot_2 = new Test2class();

ot_2 = ot_1;

// Теперь объект ot2 ссылается на ту же область памяти, что и объект ot1

 

System. out. println(ot_2.a);

ot_1.b = 33;

System. out. println(ot_2.b);

}

}

 

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

Назна­чение ot_1 переменной ot_2 не распределяет никакой памяти и не копирует ка­кую-либо часть первоначального объекта. Эта операция просто помещает в ot_2 ссылку из ot_1. Таким образом, любые изменения, сделанные в объекте через ot_2 затронут объект, на который ссылается ot_l, т. к. это один и тот же объект.

 

Ключевое слово this

Иногда у метода возникает необходимость обращаться к объекту, который его вызвал. Для этого Java определяет ключевое слово this. Его можно ис­пользовать внутри любого метода, чтобы сослаться на текущий объект. То есть this — это всегда ссылка на объект, метод которого был вызван. Вы можете использовать this везде, где разрешается ссылка на объект текущего класса.

Перегрузка методов

В Java в пределах одного класса можно определить два или более ме­тодов, которые совместно используют одно и то же имя, но имеют разное количество параметров. Когда это имеет место, методы называют перегру­женными, а о процессе говорят как о перегрузке метода. Перегрузка мето­дов — один из способов, с помощью которого реализуется полиморфизм. Когда происходит вызов перегружен­ного метода, выполняется тот метод, чьи параметры соот­ветствуют параметрам, используемым в вызове. В некоторых случаях определенную роль в вы­боре перегруженного метода могут сыграть автоматические преобразования типов Java (например, int и double). Java использует эти автоматические преобразования типов только тогда, когда никакого точного соответствия не находится. Рассмотрим пример.

 

class Stack {

private int stck[];

private double stckd[];

private int tos;

private boolean fl; // true if int false if double

 

Stack(int size) {

stck = new int [size];

stckd = new double [size];

tos = -1;

}

 

void push(int item) {

fl = true;

if (tos == stck.length-1)

System. out. println("Стек заполнен");

else {

stck[++tos] = item;

System. out. print("i");}

}

void push(double item) {

fl = false;

if (tos == stck.length-1)

System. out. println("Стек заполнен");

else {

stckd[++tos] = item;

System. out. print("d");}

}

double pop() {

if (tos < 0){

System. out. println("Стек пуст");

return 0;

}

else {

if (fl)

return stck[tos--];

Else

return stckd[tos--];

}

}

}

public class TestStack {

public static void main(String[] args) {

Stack mystack1 = new Stack(5);

Stack mystack2 = new Stack(8);

 

for (int i=0; i<5; i++) mystack1.push(i);

for (int i=0; i<8; i++) mystack2.push(i+1.1);

 

System. out. println("Стек в mystack1:");

for (int i=0; i<5; i++)

System. out. println(mystack1.pop());

 

System. out. println("Стек в mystack2:");

for (int i=0; i<8; i++)

System. out. println(mystack2.pop());

}

}

 

При заполнении стека mystack1 в качестве параметра при вызове метода push передается целое значение, что приводит к вызову перегруженного метода

void push(int item)

и использованию целочисленного массива stck.

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

void push(double item)

и использованию массива stckd.

 

Рассмотрим еще один пример.

 

/* Класс Box: три конструктора для разных способов

инициализации размеров блока. */

class Box {

double width;

double height;

double depth;

// конструктор для инициализации всех размеров

Box(double w, double h, double d) {

width = w;

height = h;

depth = d;

}

// конструктор для инициализации без указания размеров

Box () {

width = -1; // использовать —1 для указания

height = -1; // не инициализированного

depth = -1; // блока

}

// конструктор для создания куба

Box(double len) {

width = height = depth = len;

}

// вычислить и возвратить объем

double volume() {

return width * height * depth;

}

}

class OverloadCons {

public static void main(String args[]) {

// создать блоки, используя различные конструкторы

Box myboxl = new Box(10, 20, 15);

Box mybox2 = new Box();

Box mycube = new Box(7);

double vol;

// получить объем первого блока

vol = myboxl.volume();

System. out. println("Объем myboxl равен " + vol);

 

// получить объем второго блока

vol = mybox2.volume();

System. out. println("Объем mybox2 равен " + vol);

// получить объем куба

vol = mycube.volume();

System. out. println("Объем mycube равен " + vol); }

}

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

Скрытие переменной экземпляра

Как известно, в Java недопустимо объявление двух локальных переменных с одним и тем же именем внутри той же самой, или включающей области действия идентификаторов. Заметим, что вы можете иметь локальные пере­менные, включая формальные параметры для методов, которые перекрыва­ются с именами экземплярных переменных класса. Однако, когда локальная переменная имеет такое же имя, как переменная экземпляра, локальная пе­ременная скрывает переменную экземпляра. Вот почему width, height и depth не использовались как имена параметров конструктора Box() внутри класса Box. Если бы они были использованы для именования этих парамет­ров, то, скажем width, как формальный параметр, скрыл бы переменную экземпляра width. Хотя обычно проще указывать различные имена, сущест­вует другой способ обойти эту ситуацию. Поскольку this позволяет обра­щаться прямо к объекту, это можно применять для разрешения любых кон­фликтов пространства имен, которые могли бы происходить между экземплярными и локальными переменными. Ниже представлена другая версия Box(), которая использует width, height и depth для имен параметров и за­тем применяет this, чтобы получить доступ к переменным экземпляра с те­ми же самыми именами:

 

// Используйте этот вариант конструктора

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

Box(double width, double height, double depth) {

this. width = width;

this. height = height;

this. depth = depth;

}

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

 

Передача аргументов

Существуют два способа, с помощью которых машинный язык мо­жет передавать аргумент подпрограмме. Первый способ — передача аргу­мента по значению. Этот метод копирует значение аргумента в формальный параметр подпрограммы. Поэтому любые изменения этого параметра под­программой не имеют никакого влияния на соответствующий аргумент вы­зова. Второй способ — передача аргумента по ссылке. В этом методе ссылка на параметр (а не его значение) передается параметру. Внутри подпрограммы она используется, чтобы обратиться к фактическому параметру, указанному в вызове. Это означает, что изменения параметра будут влиять на аргумент, использованный для вызова подпрограммы. Java использует оба метода, в зависимости от того, что передается во время вызова подпрограмме.

Когда методу передается простой тип, он передается по значению. Таким об­разом, то, что происходит с параметром, который принимает аргумент, ни­как не влияет на сам аргумент (т. е. аргумент при этом не изменяется). На­пример, рассмотрим следующую программу:

 

// Простые типы передаются по значению

class Test {

void meth(int i, int j) {

i *= 2; j /= 2;

}

}

class CallByValue {

public static void main(String args[]) {

Test ob = new Test();

int a = 15, b = 20;

System. out. println("а и b перед вызовом: "+a+" "+b);

ob.meth(a, b);

System. out. println("а и b после вызова: "+a+" "+b);

}

}

 

Когда вы передаете методу объект, ситуация изменяется, по­тому что объекты передаются по ссылке. При создании переменной типа "класс", создается только ссылка на объект. Таким обра­зом, когда вы передаете эту ссылку методу, принимающий ее параметр будет ссылаться на тот же самый объект, что и аргумент. Это и означает, что объ­екты передаются методам по ссылке. Все изменения объекта внутри метода затрагивают объект, используемый как аргумент. Например, рассмотрим следующую программу:

 

// Объекты передаются по ссылке.

class Test{

int a, b;

 

Test(int i, int j) {

a = i;

b = j;

}

// передать объект

void meth(Test o) {

o.a *= 2;

o.b /= 2;

}

}

class CallByRef {

public static void main(String args []) {

Test ob = new Test(15, 20);

System. out. println("ob.а и ob.b перед вызовом: " + ob.a + " " + ob.b);

ob.meth(ob);

System. out. println("ob.a и ob.b после вызова: " + ob.a + " " + ob.b);

}

}

 

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

Статические элементы

Иногда возникает необходимость определить элемент (член) класса так, чтобы появилась возможность пользоваться им независимо от какого-либо объекта этого класса. Обычно к элементам класса нужно обращаться только через объект этого класса. Однако можно создать элемент для использова­ния без ссылки на определенный объект. Чтобы это сделать, укажите в на­чале его объявления ключевое слово static. Когда элемент объявляется как static, к нему можно обращаться до того, как создаются какие-либо объек­ты его класса, и без ссылки на какой-либо объект. Статическими мож­но объявлять как методы, так и переменные. Наиболее общим примером static-элемента является метод main (). Он объявляется как static, потому что должен вызваться прежде, чем будут созданы какие-либо объекты.

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

Методы, объявленные как static имеют несколько ограничений:

  • могут вызывать только другие static-методы;
  • должны обращаться только к static-данным;
  • никогда не могут ссылаться на this или super. (Ключевое слово super касается наследования и описано в дальнейшем.)

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

 

class UseStatic {

static int a = 3;

static int b;

 

static void meth(int x) {

System. out. println("x= " + x);

System. out. println("a= " + a);

System. out. println("b= " + b);

}

 

static {

System. out. println("Статический блок инициализирован");

b = a * 4;

}

public static void main(String args[]) {

meth (42);

}

}

Bce static-инструкции выполняются сразу же после загрузки класса usestatic. Сначала, в а устанавливается 3, затем выполняется static -блок (печатая сообщение «Статический блок инициализирован.»). Затем вызывается метод main(), который обращается к meth(), передавая 42 параметру х. Три оператора println() обращаются к двум static-перемен­ным (а и b) и к локальной переменной х.

Внутри статического метода недопустимо обращаться к любым экземплярным переменным.

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

Classname.methodname()

Например так можно вызвать методы класса Math, для вычисления различных математических функций: Math.sin(x), Math.cos(x), Math.sqrt(x), ….

Наследование

Наследование позволяет создавать иерархические клас­сификации. Используя наследование, можно создать главный класс, кото­рый определяет свойства, общие для набора связанных элементов. Затем этот класс может быть унаследован другими, более специфическими клас­сами, каждый из которых добавляет те свойства, которые являются уни­кальными для него. В терминологии Java класс, который унаследован, назы­вается суперклассом (superclass). Класс, который выполняет наследование, называется подклассом (subclass). Поэтому подкласс — это специализирован­ная версия суперкласса. Он наследует все переменные экземпляра и методы, определенные суперклассом, и прибавляет свои собственные уникальные элементы. Чтобы наследовать класс, нужно просто включить определение одного клас­са в другое, используя ключевое слово extends.

Рассмотрим пример.

 

// Программа использует наследование для расширения Box.

class Box {

double width;

double height;

double depth;

// конструктор, используемый, когда указаны все размеры

Box(double w, double h, double d) {

width = w;

height = h;

depth = d;

}

// конструктор, используемый, когда размеры не указаны

Box() {

width = -1; // использовать -1 для указания

height = -1; // неинициализированного

depth = -1; // блока

}

// конструктор, используемый для создания куба

Box(double len) {

width = height = depth = len;

}

// вычислить и вернуть объем

double volume() {

return width * height * depth;

}

}

// Box расширяется для включения веса.

class BoxWeight extends Box {

double weight; // вес блока

// конструктор для BoxWeight

BoxWeight(double w, double h, double d, double m)

{

width = w;

height = h;

depth = d;

weight = m;

}

}

class DemoBoxWeight {

public static void main(String args[]) {

BoxWeight myboxl = new BoxWeight(10, 20, 15, 34.3);

BoxWeight mybox2 = new BoxWeight(2, 3, 4, 0.076);

double vol;

vol = myboxl.volume();

System. out. println("Объем myboxl равен " + vol);

System. out. println("Beс myboxl равен " + myboxl.weight);

System. out. println();

vol = mybox2.volume();

System. out. println("Объем mybox2 равен " + vol);

System. out. println("Bec mybox2 равен " + mybox2.weight);

}

}

 

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

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

 

Хотя подкласс включает все элементы (члены) своего суперкласса, он не может обращаться к тем элементам суперкласса, которые были объявлены как private.

 

Рассмотрим пример.

 

/* В иерархии классов private-члены остаются private для ее классов.

Эта программа содержит ошибку и не будет компилироваться. */

// Создать суперкласс.

class А {

int i; // public по умолчанию

private int j; // private для A

void setij(int x, int y) {

i = x;

j = y;

}

}

 

// j класса А здесь не доступна.

class В extends А {

int total;

void sum () {

total = i + j; // ОШИБКА, j здесь не доступна

}

}

class Access {

public static void main(String args[]) {

В subOb = new В();

subOb.setij(10, 12);

subOb.sum();

System. out. println("Всего " + subOb.total);

}

}

 

В переменную суперкласса можно поместить ссылку на объект подкласса.

Рассмотрим пример.

 

class RefDemo {

public static void main(String args[]) {

BoxWeight weightbox = new BoxWeight(3, 5, 7, 8.37);

Box plainbox = new Box();

double vol;

vol = weightbox.volume();

System. out. println("Объем weightbox равен " + vol);

System. out. println("Beс weightbox равен"+ weightbox.weight);

System. out. println();

 

// назначить ссылку на BoxWeight ссылке на Box

plainbox = weightbox;

 

vol = plainbox.volume(); // OK, volume() определена в Box

System. out. println("Объем plainbox равен " + vol);

 

/* Следующее утверждение не верно, потому что plainbox не определяет член weight. */

//System.out.println("Вес plainbox равен"+plainbox.weight);

}

}

 

Ключевое слово super.

В случае, если подкласс должен обратиться к своему непосредственному суперклассу, он может сделать это при помощи ключевого слова super.

Ключевое слово super имеет две общие формы. Первая вызывает конструк­тор суперкласса. Вторая используется для доступа к элементу суперкласса, который был скрыт элементом подкласса.

 

//BoxWeight теперь использует super для инициализации Box-атрибутов.

class BoxWeight extends Box {

double weight; // вес блока

 

// инициализировать width, height и depth, используя super()

BoxWeight(double w, double h, double d, double m) {

super (w, h, d); // вызвать конструктор суперкласса

weight = m;

}

}

 

Рассмотрим реализацию класса BoxWeight с использованием вызова конструктора суперкласса.

 

// Полная реализация BoxWeight.

class Box {

private double width;

private double height;

private double depth;

// построить клон объекта

Box(Box ob) { // передать объект конструктору

width = ob.width;

height = ob.height;

depth = ob.depth;

}

// конструктор, используемый для всех размеров

Box(double w, double h, double d) {

width = w;

height = h;

depth = d;

}

// конструктор, используемый без размеров

Box() {

width = -1; // использовать -1 для указания

height = -1; // неинициализированного

depth = -1; // блока

}

// конструктор, используемый для создания куба

Box(double len) {

width = height = depth = len;

}

// вычислить и возвратить объем

double volume() {

return width * height * depth;

}

}

 

// BoxWeight теперь полностью реализует все конструкторы.

class BoxWeight extends Box {

double weight; // вес блока

// построить клон объекта

BoxWeight(BoxWeight ob) { // передать объект конструктору

super (ob);

weight = ob.weight;

}

// конструктор, используемый для всех размеров

BoxWeight(double w, double h, double d, double m) {

super (w, h, d); // вызвать конструктор суперкласса

weight = m;

}

// конструктор по умолчанию

BoxWeight() {

super ();

weight = -1;

}

// конструктор, используемый для создания куба

BoxWeight(double len, double m) {

super (len);

weight = m;

}

}

class DemoSuper {

public static void main(String args[]) {

BoxWeight myboxl = new BoxWeight(10, 20, 15, 34.3);

BoxWeight mybox2 = new BoxWeight(2, 3, 4, 0.076);

BoxWeight mybox3 = new BoxWeight(); //по умолчанию

BoxWeight mycube = new BoxWeight(3, 2);

BoxWeight myclone = new BoxWeight(myboxl);

 

double vol;

vol = myboxl.volume();

System. out. println("Объем myboxl равен " + vol);

System. out. println("Bec myboxl равен " + myboxl.weight);

System. out. println();

 

vol = mybox2.volume();

System. out. println("Объем mybox2 равен " + vol);

System. out. println("Bec mybox2 равен " + mybox2.weight);

System. out. println();

vol = mybox3.volume();

System. out. println("Объем mybox3 равен " + vol);

System. out. println("Вес mybox3 равен " + mybox3.weight);

System. out. println();

vol = myclone.volume();

System. out. println("Объем myclone равен " + vol);

System. out. println("Beс myclone равен " + myclone.weight);

System. out. println();

vol = mycube.volume();

System. out. println("Объем mycube равен " + vol);

System. out. println("Bec mycube равен " + mycube.weight);

System. out. println();

}

}

 

Вторая форма super действует в чем-то подобно ссылке this, за исключени­ем того, что она всегда обращается к суперклассу подкласса, в котором ис­пользуется. Общий формат такого использования super имеет вид:

Super.member

где member может быть либо методом, либо переменной экземпляра.

Вторая форма super больше всего применима к ситуациям, когда имена элементов (членов) подкласса скрывают элементы с тем же именем в супер­классе.

 

// Использование super для преодоления скрытия имен.

class А {

int i;

}

// Создание подкласса В расширением класса А.

class В extends А {

int i; // этот i скрывает i в А

В(int a, int b) {

super. i = a; // i из A

i = b; // i из В

}

void show() {

System. out. println("i из суперкласса: " + super. i);

System. out. println("i из подкласса: " + i);

}

}

class UseSuper {

public static void main(String args[]) {

В subOb = new В(1, 2);

subOb.show();

}

}

 

Порядок вызова конструкторов

Рассмотрим пример (проект ConstSeq файл CallingCons):

 

// Демонстрирует порядок вызова конструкторов.

//

// Создать суперкласс А.

class A {

A() {

System. out. println("Внутри А-конструктора.");

}

}

// Создать подкласс В расширением А.

class B extends A {

B() {

System. out. println("Внутри В-конструктора.");

}

}

// Создать другой класс (С), расширяющий В.

class C extends B {

C() {

System. out. println("Внутри С-конструктора.");

}

}

class CallingCons {

public static void main(String args[]) {

C c = new C();

}

}

 

Результатом работы этой программы будет:

 

Внутри А-конструктора.

Внутри В-конструктора.

Внутри С-конструктора.

 

При создании объекта подкласса происходит цепочка вызовов конструкторов классов данной иерархии. Конструкторы вызываются в порядке подчиненности классов.

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

 

Переопределение методов

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

Рассмотрим следующий фрагмент (проект Override файл Override.java):

 

// Переопределение методов.

class A {

int i, j;

A(int a, int b) {

i = a;

j = b;

}

// показать i и j на экране

void show() {

System. out. println("i и j: " + i + " " + j);

}

}

class В extends A {

int k;

В(int a, int b, int c) {

super (a, b);

k = c;

}

// Показать на экране k (этот show() переопределяет show() из A)

void show() {

System. out. println("k: " + k);

}

}

class Override {

public static void main(String args[]) {

В subOb = new В(1, 2, 3);

subOb.show(); // здесь вызывается show() из В

}

}

 

Переопределение метода происходит только тогда, когда имена, возвращаемые значения и параметры этих двух методов идентичны. Если это не так, то оба метода просто перегружены. Например, рассмотрим следующую модифицированную вер­сию предыдущего примера (проект Override файл Override2.java):

 

// Методы с различными сигнатурами типов перегружаются,

// а не переопределяются.

class A {

int i, j;

A(int a, int b) {

i = a;

j = b;

}

// Показать i и j

void show() {

System.out.println("i и j: " + i + " " + j);

}

}

// Создать подкласс В расширением класса А.

class B extends A {

int k;

B(int a, int b, int c) {

super (a, b);

k = c;

}

// Перегруженный show()

void show(String msg) {

System. out. println(msg + k);

}

}

class Override2 {

public static void main(String args[]) {

B subOb = new B(1, 2, 3);

subOb.show("Это к: "); // вызов show() класса В

subOb.show(); // вызов show() класса A

}

}

 

Полиморфизм времени выполнения

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

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

Например (проект Dispatch файл Dispatch.java):

 

// Динамическая диспетчеризация методов.

class A {

void callme () {

System. out. println("Внутри А метод callme");

}

}

class B extends A {

// переопределить callme()

void callme() {

System. out. println("Внутри В метод callme");

}

}

class C extends A {

// переопределить callme()

void callme() {

System. out. println("Внутри С метод callme");

}

}

class Dispatch {

public static void main(String args[]) {

A a = new A(); // объект типа A

B b = new B(); // объект типа В

C с = new C(); // объект типа С

A r; // определить ссылку типа А

r = a; // r на А-объект

r.callme(); // вызывает А-версию callme

r = b; // r указывает на В-объект

r.callme(); // вызывает В-версию callme

r = с; // r указывает,на С-объект

r.callme(); // вызывает С-версию callme

}

}

 

Еще один пример (проект FindAreas файл FindAreas.java):

 

// Использование полиморфизма времени выполнения.

class Figure {

double diml;

double dim2;

Figure(double a, double b) {

diml = a;

dim2 = b;

}

double area() {

System. out. println("Площадь Figure не определена.");

return 0;

}

}

class Rectangle extends Figure {

Rectangle(double a, double b) {

super (a, b);

}

// переопределить area для прямоугольника

double area() {

System. out. println("Внутри Area для Rectangle.");

return diml * dim2;

}

}

class Triangle extends Figure {

Triangle(double a, double b) {

super (a, b);

}

// переопределить area для прямоугольного треугольника

double area() {

System. out. println("Внутри Area для Triangle.");

return diml * dim2 / 2;

}

}

class FindAreas {

public static void main(String args[]) {

Figure f = new Figure(10, 10);

Rectangle r = new Rectangle(9, 5);

Triangle t = new Triangle(10, 8);

Figure figref;

figref = r;

System. out. println("Площадь равна " + figref.area());

figref = t;

System. out. println("Площадь равна " + figref.area ());

figref = f;

System. out. println("Площадь равна " + figref.area ());

}

}

 

Результат работы:

 

Внутри Area для Rectangle.

Площадь равна 45.0

Внутри Area для Triangle.

Площадь равна 40.0

Площадь Figure не определена.

Площадь равна 0.0

 

Полиморфизм – это замечательная возможность сделать Ваши программы более короткими и изящными, хотя без этого можно обойтись (см. пример FindAreas2.java).



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: