Использование абстрактных классов

В ряде ситуаций нужно будет определять суперкласс, который объявляет структуру определенной абстракции без предоставления полной реализации каждого метода. То есть иногда придется создавать суперкласс, определяющий только обобщенную форму, которую будут совместно использовать все его подклассы, добавляя необходимые детали. Такой класс определяет сущность методов, которые должны реализовать подклассы. Например, такая ситуация может возникать, когда суперкласс не в состоянии создать полноценную реализацию метода. Именно такая ситуация имела место в классе Figure в предыдущем примере. Определение метода area () — просто шаблон. Он не будет вычислять и отображать площадь объекта какого-либо типа.

Как вы убедитесь в процессе создания собственных библиотек классов, отсутствие полного определения метода в контексте суперкласса — не столь уж редкая ситуация. Эту проблему можно решать двумя способами. Один из них, как было показано в предыдущем примере — просто вывод предупреждающего сообщения. Хотя этот подход и полезен в определенных ситуациях — например, при отладке — обычно он не годится. Могут существовать методы, которые должны быть переопределены подклассом, чтобы подкласс имел какой-либо смысл. Рассмотрим класс Triangle. Он лишен всякого смысла, если метод area () не определен. В этом случае необходим способ убедиться в том, что подкласс действительно переопределяет все необходимые методы. В Java для этого служит абстрактный метод.

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

abstract ТИП имя(список_параметров);

Как видите, в этой форме тело метода отсутствует.

Любой класс, который содержит один или более абстрактных методов, должен быть также объявлен как абстрактный. Для этого достаточно поместить ключевое слово abstract перед ключевым словом class в начале объявления класса. Абстрактный класс не может содержать какие-то объекты. То есть абстрактный класс не может быть непосредственно конкретизирован с помощью операции new. Такие объекты были бы бесполезны, поскольку абстрактный класс определен не полностью. Нельзя также объявлять абстрактные конструкторы или абстрактные статические методы. Любой подкласс абстрактного класса должен либо реализовать все абстрактные методы суперкласса, либо также быть объявлен абстрактным.

Ниже приведен простой пример класса, содержащего абстрактный метод, и класса, который реализует этот метод.

// Простой пример применения абстракции.
abstract class А {
abstract void callme ();
// абстрактные классы все же могут содержать конкретные методы
void callmetoo() {
System.out.println("Это конкретный метод.");
}
}
class В extends А {
void callme() {
System.out.println("Реализация метода callme класса В.");
}
}
class AbstractDemo {
public static void main(String args[]) {
В b = new В () ;
b.callme();
b.callmetoo();
}
}

Обратите внимание, что в этой программе класс А не содержит объявлений каких-либо объектов. Как уже было сказано, конкретизация абстрактного класса невозможна. И еще один нюанс: класс А реализует конкретный метод callmetoo (). Это вполне допустимо. Абстрактные классы могут содержать любое необходимое количество конкретных реализаций.

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

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

// Использование абстрактных методов и классов.
abstract class Figure {
double diml;
double dim2;
Figure(double a, double b) {
diml = a;
dim2 = b;
}
// теперь метод area является абстрактным
abstract double area();
}
class Rectangle extends Figure {
Rectangle(double a, double b) {
super(a, b);
}
// переопределение метода area для четырехугольника
double area() {
System.out.println("В области четырехугольника.");
return diml * dim2;
}
}
class Triangle extends Figure {
Triangle(double a, double b) {
super(a, b);
}
// переопределение метода area для четырехугольника double area() {
System.out.println ("В области треугольника.");
return diml * dim2 12;
}
}
class AbstractAreas {
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("Площадь равна " + f igref. area ()) ;
figref = t;
System.out.println("Площадь равна " + figref.area());
}
}

Как видно из комментария внутри метода main (), объявление объектов типа Figure более недопустимо, поскольку теперь этот класс является абстрактным. И все подклассы класса Figure должны переопределять метод area (). Чтобы убедиться в этом, попытайтесь создать подкласс, который не переопределяет метод area (). Это приведет к ошибке времени компиляции.

Хотя создание объекта типа Figure недопустимо, можно создать ссылочную переменную типа Figure. Переменная figref объявлена как ссылка на Figure — т.е. ее можно использовать для ссылки на объект любого класса, производного от Figure. Как мы уже поясняли, разрешение переопределенных методов во время выполнения осуществляется путем ссылки на суперкласс.

ex1