Иногда суперкласс требуется определить таким образом, чтобы объявить в нем структуру заданной абстракции, не предоставляя полную реализацию каждого метода
Это означает создать суперкласс, определяющий только обобщенную форму для совместного использования всеми его подклассами, в каждом из которых могут быть добавлены требующиеся детали.
В таком классе определяется характер методов, которые должны быть реализованы в подклассах. Подобная ситуация может, например, возникнуть, когда в суперклассе не удается полностью реализовать метод.
Например, посмотрите на следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
class Figure { double dim1; double dim2; Figure(double a, double b) { dim1 = a; dim2 = b; } double area() { System.out.println("Площадь фигуры не определена."); return 0; } } class Rectangle extends Figure { Rectangle(double a, double b) { super(a, b); } // переопределить метод area() для четырехугольника double area() { System.out.println("В области четырехугольника."); return dim1 * dim2; } } class Triangle extends Figure { Triangle(double a, double b) { super(a, b); } // переопределить метод area() для прямоугольного треугольника double area() { System.out.println("В области треугольника."); return dim1 * 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() в классе Figure служит лишь в качестве шаблона, не позволяя рассчитать и вывести площадь объекта какого-нибудь типа.
В процессе создания собственных библиотек классов вы сами убедитесь, что отсутствие полного определения метода в контексте суперкласса — не такая уж и редкая ситуация.
Выйти из этой ситуации можно двумя способами. Один из них, состоит в том, чтобы просто вывести предупреждающее сообщение. Такой способ удобен в определенных случаях, например, при отладке, но, как правило, он не годится.
Ведь могут быть и такие методы, которые должны быть переопределены в подклассе, чтобы подкласс имел хотя бы какой-то смысл.
Рассмотрим в качестве примера класс Triangle. Он лишен всякого смысла, если метод area() не определен. В таком случае требуется каким-то образом убедиться, что в подклассе действительно переопределяются все необходимые методы. И для этой цели в Java служит абстрактный метод.
Для того чтобы некоторые методы переопределялись в подклассе, достаточно объявить их с модификатором типа abstract.
Иногда они называются методами под ответственностъю подкласса, поскольку в суперклассе для них никакой реализации не предусмотрено.
Следовательно, эти методы должны быть переопределены в подклассе, где нельзя просто воспользоваться их вариантом, определенным в суперклассе.
Для объявления абстрактного метода используется приведенная ниже общая форма. Как видите, в этой форме тело метода отсутствует.
1 |
abstract тип имя(список_параметров); |
Любой класс, содержащий один или больше абстрактных методов, должен быть также объявлен как абстрактный.
Для этого достаточно указать ключевое слово abstract перед ключевым словом class в начале объявления класса.
У абстрактного класса не может быть никаких объектов. Это означает, что экземпляр абстрактного класса не может быть получен непосредственно с помощью оператора new.
Такие объекты были бы бесполезны, поскольку абстрактный класс определен не полностью. Кроме того, нельзя объявлять абстрактные конструкторы или абстрактные статические методы.
Любой подкласс, производный от абстрактногокласса, должен реализовать все абстрактные методы из своего суперкласса или же сам быть объявлен абстрактным.
Ниже приведен простой пример класса, содержащего абстрактный метод, и класса, реализующего этот метод.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// Простой пример абстракции abstract class A { abstract void callme(); // абстрактные классы все же могут содержать конкретные методы void callMeToo() { System.out.println("Это конкретный метод."); } } class B extends A { void callme() { System.out.println("Реализация метода callme() из класса B."); } } class AbstractDemo { public static void main(String args[]) { B b = new B(); b.callme(); b.callMeToo(); } } |
Вывод программы следующий:
1 2 3 4 5 |
pro-java.ru@admin:~$ javac abstract2.java pro-java.ru@admin:~$ java AbstractDemo Реализация метода callme() из класса B. Это конкретный метод. pro-java.ru@admin:~$ |
Обратите внимание на то, что в этой программе объекты класса А не объявляются. Как отмечалось ранее, получить экземпляр абстрактного класса нельзя.
И еще одно: в классе А реализуется конкретный метод callMeToo(), что вполне допустимо. В абстрактные классы может быть включена реализация какого угодно количества конкретных методов.
Несмотря на то, что абстрактные классы не позволяют получать экземпляры объектов, их все же можно применять для создания ссылок на объекты, поскольку в Java полиморфизм во время выполнения реализован с помощью ссылок на суперкласс.
Поэтому должна быть возможность создавать ссылку на абстрактный класс для указания на объект подкласса. В приведенном ниже примере показано, как воспользоваться такой возможностью.
Используя абстрактный класс, можно усовершенствовать созданный ранее класс Figure. Понятие площади неприменимо к неопределенной двумерной фигуре, поэтому в приведенной ниже новой версии программы метод area() объявляется в классе Figure как abstract.
Это, конечно, означает, что метод area() должен быть переопределен во всех классах, производных от класса Figure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
// Применение абстрактных методов и классов abstract class Figure { double dim1; double dim2; Figure(double a, double b) { dim1 = 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 dim1 * dim2; } } class Triangle extends Figure { Triangle(double a, double b) { super(a, b); } // переопределить метод area() для прямоугольного треугольника double area() { System.out.println("В области треугольника."); return dim1 * dim2 / 2; } } 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("Площадь равна " + figref.area()); figref = t; System.out.println("Площадь равна " + figref.area()); } } |
Вывод будет следующим:
1 2 3 4 5 6 7 |
pro-java.ru@admin:~$ javac abstraction.java pro-java.ru@admin:~$ java AbstractAreas В области четырехугольника. Площадь равна 45.0 В области треугольника. Площадь равна 40.0 pro-java.ru@admin:~$ |
Как следует из комментариев в теле метода main(), объявление объектов типа Figure больше не допускается, поскольку теперь этот класс является абстрактным.
И во всех подклассах, производных от класса Figure, должен быть переопределен метод area(). Чтобы убедиться в этом, попытайтесь создать подкласс, в котором метод area() не переопределяется. Это приведет к ошибке во время компиляции.
Если создать объект типа Figure нельзя, то можно хотя бы создать ссылочную переменную типа Figure.
Переменная figref объявлена как ссылка на класс Figure, т.е. ее можно использовать для ссылки на объект любого класса, производного от класса Figure.
Как пояснялось ранее, вызовы переопределенных методов разрешаются во время выполнения с помощью ссылочных переменных из суперкласса.