Классы
Наследование
class Triangle extends TwoDShape { }Для каждого создаваемого подкласса можно указать только один суперкласс. Множественное наследование в Java не поддерживается.
Главное преимущество наследования заключается в следующем: создав суперкласс, в котором определены общие для множества объектов свойства, вы можете использовать его для создания любого числа более специализированных подклассов. Каждый подкласс добавляет собственный набор специфических для него атрибутов в соответствии с конкретной необходимостью.
В каких случаях переменную экземпляра следует объявлять закрытой (private)?
Следует лишь придерживаться двух общих принципов.
* Во-первых, если переменная экземпляра используется только методами, определенными в классе, то она должна быть закрытой. * И во-вторых, если значение переменной экземпляра не должно выходить за определенные границы, ее следует объявить как закрытую, а обращение к ней организовать с помощью специальных методов доступа. Подобным образом можно предотвратить присваивание недопустимых значений переменной.
Доступ
protected - достуно всем только в пределах пакета, или подклассам в др.пакетах;
private - доступно только в экземпляре текущего класса;
public - доступно всем (включая др.пакеты);
По умолчанию - поля и методы класса доступны во всех других классах в рамках текущего пакета.
Конструкторы
Конструктор суперкласса используется для построения родительской части объекта, а конструктор подкласса - для остальной его части.
Для вызова конструктора суперкласса из подкласса используется следующая общая форма ключевого слова super:
super (список_ параметров) ;
Вызов конструктора super() всегда должен быть первым оператором в теле конструктора подкласса.
Пример:
class Triangle extends TwoDShape { private String style; Triangle(String s, double w, double hl { super(w, h); // вызвать конструктор суперкласса (TwoDShape) style = s; ... каието действия конструктора подкаласса - Triangle } }Вызов super() позволяет вызвать любую форму конструктора, определенную в суперклассе Для выполнения выбирается тот вариант конструктора, который соответствует указанным аргументам (т.е. определяется соответствие сигнатуре).
Доступ к членам суперкласса
Существует еще одна общая форма ключевого слова super, которая применяется подобно ключевому слову this, но ссылается на суперкласс данного класса. Эта общая форма обращения к члену суперкласса имеет следующий вид:
suреr.член_класса
-где член_класса - обозначает название метода или переменной суперкласса
Данная форма ключевого слова super используется в тех случаях, когда член подкласса скрывает член суперкласса.
Многоуровневая иерархия классовИтак, имея в своем распоряжении суперкласс, определяющий общие свойства некоторых объектов, можно создать на его основе специализированные подклассы. Каждый подкласс дополняет свойства суперкласса собственными уникальными свойствами. В этом и состоит сущность наследования.
Java позволяет строить иерархии, состоящие из любого количества уровней наследования. Как уже упоминалось выше, многоуровневая иерархия идеально подходит для использования одного подкласса в качестве суперкласса для другого подкласса. Так, если имеются три класса, А, B и C, то класс с может наследовать все характеристики класса В, а тот, в свою очередь, может наследовать все характеристики класса А.
Очередность вызова конструктороввызов super() всегда означает обращение к конструктору ближайшего суперкласса!!!
В иерархии классов конструкторы вызываются в порядке наследования, начиная с суперкласса и кончая подклассом. Более того, метод super() должен быть первым оператором в конструкторе подкласса, и поэтому порядок, в котором вызываются конструкторы, остается неизменным, независимо от того, используется ли вызов super() или нет. Если вызов super() отсутствует, то выполняется конструктор каждого суперкласса по умолчанию (т.е. конструктор без параметров).
Ссылки на суперкласс и объекты подклассов
Ссылочной переменной суперкласса может быть присвоена ссылка на объект любого подкласса, производного от данного суперкласса. Таким образом, ссылку на объект суперкласса можно использовать для обращения к объектам соответствующих подклассов.
Пример:Если ссылка на объект подкласса присваивается ссылочной переменной суперкласса, то последняя может быть использована для доступа только к тем частям данного объекта, которые определяются суперклассом.
// Создать один объект на основе другого Triangle(Triangle оb} { // передача объекта конструктору класса TwoDShape super (оb); style = ob.style; }
В качестве параметра данному конструктору передается объект Triangle, который затем с помощью вызова super() передается конструктору TwoDShape, как показано ниже:
// Создать один объект на основе другого TwoDShape(TwoDShape оb) { width = ob.width; height = ob.height; }Следует заметить, что конструктор TwoDshape() должен получить объект типа TwoDShape, но конструктор Triangle() передает ему объект типа Triangle. Тем не менее никаких недоразумений не возникает. Ведь, как пояснялось ранее, ссылочная переменная суперкласса может ссылаться на объект подкласса. Следовательно, конструктору TwoDShape() можно передать ссылку на экземпляр подкласса, производного от класса TwoDShape. Конструктор TwoDShape() инициализирует лишь те части передаваемого ему объекта подкласса, которые являются членами класса TwoDShape, и поэтому не имеет значения, содержит ли этот объект дополнительные члены, добавленные в производных подклассах.
Перегрузка методов
Перегрузка методов является одним из способов реализации принципа полиморфизма в Java. Для того чтобы перегрузить метод, достаточно объявить его новый вариант, отличающийся от уже существующих, а все остальное сделает компилятор. Нужно лишь соблюсти одно условие: тип и/или число параметров в каждом из перегружаемых методов должны быть разными. Одноrо лишь различия в типах возвращаемых значений для этой цели недостаточно. (Информации о возвращаемом типе не всегда будет хватать Java для принятия решения о том, какой именно метод должен использоваться.) Конечно, перегружаемые методы могут иметь разные возвращаемые типы, но при вызове метода выполняется лишь тот его вариант, в котором параметры соответствуют передаваемым аргументам.
// Возможен лишь один вариант метода ovlDemo(int) void ovlDemo(int а) { System.out.println("One parameter: " + а); } /* Ошибка! Невозможно существование двух версий перегруженного метода ovlDemo(int), отличающихся лишь типом возвращаемого значения. */ int void ovlDemo (int а) { System.out.println("Oдин параметр: " + а); return а * а; }
В Java применяется автоматическое приведение типов. Это приведение распространяется и на типы параметров перегружаемых методов.Как поясняется в комментариях к приведенному выше фрагменту кода, отличия возвращаемых типов недостаточно для перегрузки методов.
(Пример в классе: ms.learning.classes.TypeConv)Важно понимать, что автоматическое преобразование типов выполняется лишь в отсутствие прямого соответствия типов параметра и аргумента.
Перегрузка методов поддерживает полиморфизм, поскольку она является одним из способов реализации парадигмы "один интерфейс - множество методов". Для того чтобы стало понятнее, как и для чего это делается, необходимо принять во внимание следующее соображение: в языках программирования, не поддерживающих перегрузку методов, каждый метод должен иметь уникальное имя. Но в ряде случаев требуется выполнять одну и ту же последовательность операций над разными типами данных. В качестве примера рассмотрим функцию, определяющую абсолютное значение. В языках, не поддерживающих перегрузку методов, приходится создавать несколько вариантов данной функции с именами, отличающимися хотя бы одним символом. Например, в языке Си функция abs() возвращает абсолютное значение числа типа int, функция labs() - абсолютное значение числа типа long, а функция fabs() - абсолютное значение числа с плавающей точкой. Объясняется это тем, что в Си не поддерживается перегрузка, и поэтому каждая из функций должна обладать своим собственным именем, несмотря на то что все они выполняют одинаковые действия. Это приводит к неоправданному усложнению процесса написания программ.
Программисты на Java употребляют термин сигнатура. Что это такое?
Переопределение методов (Override)Применительно к Java сигнатура обозначает имя метода и список его параметров. При перегрузке методов действует следующее правило: никакие два метода из одного класса не могут иметь одинаковые сигнатуры. Следует, однако, иметь в виду, что сигнатура не включает в себя тип возвращаемого значения, поскольку он не используется в Java при принятии решения о перегрузке.
В иерархии классов часто присутствуют методы с одинаковой сигнатурой и одинаковым возвращаемым значением как в суперклассе, так и в подклассе.В этом случае говорят, что метод суперкласса переопределяется в подклассе.
Если требуется обратиться к исходной версии переопределяемого метода, то есть той, которая определена в суперклассе, следует воспользоваться ключевым словом super.Если переопределяемый метод вызывается из подкласса, то он всегда будет ссылаться на версию метода, определенную в подклассе. Версия метода, определенная в суперклассе, скрывается.
Пример:
void show() { super.show(); // это вызов метода show(), определенного в суперклассе // ... тут могут быть какие-то действия метода show() подкласса }
Переопределение метода происходит только в том случае, если сигнатуры переопределяемого и переопределяющего методов совпадают. В противном случае происходит обычная перегрузка методов. (если в переопределяющем методе изменить только возвращаемое значение, то будет ошибка компиляции)
Вложенные классы
Вложенный класс не может существовать независимо от класса, в который он вложен. Следовательно, область действия вложенного класса ограничена его внешним классом. Если вложенный класс объявлен в пределах области действия внешнего класса, то он становится членом последнего. Имеется также возможность объявить вложенный класс, который станет локальным в пределах блока.
Существуют два типа вложенных классов. Одни вложенные классы объявляются с помощью модификатора доступа static, а другие - без него. В этой статье будет рассматриваться только нестатический вариант вложенных классов. Классы такого типа называются внутренними. Внутренний класс имеет доступ ко всем переменным и методам внешнего класса, в который он вложен, и может обращаться к ним непосредственно, как и все остальные нестатические члены внешнего класса. Иногда внутренний класс используется для предоставления ряда услуг внешнему классу, в котором он содержится.
Вопрос: Чем статический вложенный класс отличается от нестатического?
Ответ: Статический вложенный класс объявляется с помощью модификатора static. Являясь статическим, он может непосредственно обращаться к любому статическому члену своего внешнего класса. Другие члены внешнего класса доступны ему посредством ссылки на объект.
Переменное число аргументов
Такое решение было предложено в JDK 5. Новое средство, которое позволило избавиться от явного формирования массива аргументов перед вызовом метода, получило название varargs (сокращение от variable-length arguments - список аргументов переменной длины).
Соответствующие методы называют методами с переменным числом аргументов (другое название - методы переменной арности)
static void vaTest (int ... v) { System.out.println("Чиcлo аргументов: " + v.length); Systern.out.println("Coдepжимoe: "); for(int i=0; i < v.length; i++) System.out.println(" arg " + i + ": " + v[i]); System.out.println(); }Обратите внимание на синтаксис объявления параметра v:
int ... v
Это объявление сообщает компилятору, что метод vaTest() может вызываться с указанием произвольного количества аргументов, в том числе и вовсе без них. Более того, оно означает неявное объявления аргумента v как массива типа int[]. Таким образом, в теле метода vaTest() доступ к параметру v осуществляется с помощью обычного синтаксиса обращения к массивам.
Помимо списка параметров переменной длины, в объявлении метода могут указываться и обычные параметры, но при одном условии: массив параметров переменной длины должен быть указан последним. Например, приведенное ниже объявление метода является вполне допустимым.
int doit(int а, int b, double с, int ... vals) { }
Поддержка полиморфизма в переопределяемых методах
(динамическая диспетчеризация методов)
Механизм переопределения методов лежит в основе одного из наиболее эффективных языковых средств Java - динамической диспетчеризации методов, обеспечивающей возможность поиска подходящей версии переопределенного метода во время выполнения программы (а не во время ее компиляции).
Вспомним очень важный принцип: ссылочная переменная суперкласса может ссылаться на объект подкласса. В Java этот принцип используется для вызова переопределяемых методов во время выполнения. Если вызов переопределенного метода осуществляется с использованием ссылки на суперкласс, то исполняющая система Java выбирает нужную версию метода на основании типа объекта, на который эта ссылка указывает в момент вызова. Ссылкам на различные типы объектов будут соответствовать вызовы различных версий переопределенного метода. Иными словами, во время выполнения версия переопределенного метода выбирается в зависимости от типа обьекта ссылки (а не типа ссылочной переменной). Следовательно, если суперкласс содержит метод, переопределенный в подклассе, будет вызываться метод, соответствующий тому объекту, на который указывает ссылочная переменная суперкласса.
Пример: //Демонстрация динамической диспетчеризации методов public class DynDispDemo { public static void main(String[] args) { DynDispDemo d = new DynDispDemo(); Sup superOb = d.new Sup(); Sub1 subOb = d.new Sub1(); Sub2 subOb2 = d.new Sub2(); // так тоже работает // Sup superOb = d.new Sup(); // Sup subOb = d.new Sub1(); // Sup subOb2 = d.new Sub2(); Sup supRef; supRef = superOb; supRef.who(); supRef = subOb; supRef.who(); supRef = subOb2; supRef.who(); } //суперкласс class Sup { void who() { System.out.println("who() в Sup"); } } //подкласс 1 class Sub1 extends Sup { void who() { System.out.println("who() в Sub1"); } } //подкласс 2 class Sub2 extends Sup { void who() { System.out.println("who() в Sub2"); } } }В данном примере программы определяются суперкласс Sup и два ero подкласса Sub1 и Sub2. В классе Sup объявляется метод who(), переопределяемый в подклассах. В методе main() создаются объекты типа Sup, Sub и Sub2. Там же объявляется переменная supRef, ссылающаяся на объект типа Sup. Затем переменной supRef в методе main() поочередно присваиваются ссылки на объекты разного типа, и далее эти ссылки используются для вызова метода who() . Как следует из результата выполнения данной программы, вызываемая версия метода who() определяется типом объекта, на который указывает переменная supRef в момент вызова, а не типом самой переменной.
Использование абстрактных классов
Иногда требуется создать суперкласс, в котором определяется лишь самая общая форма для всех его подклассов, а наполнение ее деталями предоставляется каждому из этих подклассов. В таком классе определяется лишь суть методов, которые должны быть конкретно реализованы в подклассах, а не в самом суперклассе.Подобная ситуация возникает, например, в связи с невозможностью полноценной реализации метода в суперклассе.
Для определения абстрактного метода используется следующий общий синтаксис:
abstract тип имя(список_параметров);
Использование ключевого слова: final
Предотвращение переопределения методов:
Для тоrо чтобы предотвратить переопределение метода, в начале ero объявления нужно указать модификатор final.
class А { final void meth() { System.out.println("Этo метод final."); } }
Предотвращение наследования:
final class А { // . . . }
Константы объявляются с помощью ключевого слова final:
final int OUTERR = О; final int INERR = 1; final int DISKERR = 2; final int INDEXERR = З;