Когда подкласс наследует абстрактный класс, в нем должны быть реализованы все абстрактные методы суперкласса. В противном случае подкласс должен быть также определен как abstract. Таким образом, атрибут abstract наследуется до тех пор, пока не будет достигнута полная реализация класса.
Используя абстрактный класс, мы можем усовершенствовать рассматривавшийся ранее класс TwoDShape-Для неопределенной двумерной геометрической фигуры понятие площади не имеет никакого смысла, поэтому в приведенной ниже версии предыдущей программы метод area () и сам класс TwoDShape объявляются как abstract. Это, конечно, означает, что во всех классах, производных от класса TwoDShape, должен быть переопределен метод area (). // Создание абстрактного класса. // Теперь класс TwoDShape является абстрактным. abstract class TwoDShape { private double width; private double height; private String name; // Конструктор no умолчанию. TwoDShape() { width = height = 0.0; name = "null"; } // Параметризированный конструктор. TwoDShape(double w, double h, String n) { width = w; height = h; name = n; } // построить объект с одинаковыми значениями // переменных экземпляра width и height TwoDShape(double х, String n) { width = height = x; name = n; } // построить один объект на основании другого объекта TwoDShape(TwoDShape ob) { width = ob.width; height = ob.height; name = ob.name; } // Методы доступа к переменным width и height, double getWidth() { return width; } double getHeightO { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; } String getName() { return name; } void showDim() { System.out.println("Width and height are " + width + " and " + height); } // Теперь метод area () является абстрактным. abstract double area(); } // Подкласс, производный от класса TwoDShape, // для представления треугольников, class Triangle extends TwoDShape { private String style; // Конструктор по умолчанию. Triangle() { super () ; style = "null"; } // Конструктор класса Triangle. Triangle(String s, double w, double h) { super(w, h, "triangle"); style = s; } // Конструктор с одним аргументом для построения треугольника. Triangle(double х) { super(х, "triangle"); // вызвать конструктор суперкласса style = "isosceles"; } // построить один объект на основании другого объекта Triangle(Triangle ob) { super(ob); // передать объект конструктору класса TwoDShape style = ob.style; } double area() { return getWidth() * getHeight() / 2; } void showStyle() { System.out.println("Triangle is " + style); } } // Подкласс, производный от класса TwoDShape, // для представления прямоугольников. class Rectangle extends TwoDShape { // Конструктор по умолчанию. Rectangle() { super(); } // Конструктор класса Rectangle. Rectangle(double w, double h) { super(w, h, "rectangle"); // вызвать конструктор суперкласса } // построить квадрат Rectangle(double х) { super(х, "rectangle"); // вызвать конструктор суперкласса } // построить один объект на основании другого объекта Rectangle(Rectangle ob) { super(ob); // передать объект конструктору класса TwoDShape } boolean isSquare() { if (getWidth () == getHeightO) return true; return false; } double area() { return getWidth () * getHeightO; } } class AbsShape { public static void main(String args[]) { TwoDShape shapes[] = new TwoDShape[4]; shapes[0] = new Triangle("right", 8.0, 12.0); shapes[1] = new Rectangle(10); shapes[2] = new Rectangle(10, 4); shapes[3] = new Triangle(7.0); for(int i=0; i < shapes.length; i++) { System.out.println("object is " + shapes[i].getName()); System.out.println("Area is " + shapes[i].area()); System.out.println(); } } }
Как показывает представленный выше пример программы, во всех подклассах, производных от класса TwoDShape, метод area () должен быть непременно переопределен. Убедитесь в этом сами, попробовав создать подкласс, в котором не переопределен метод area (). В итоге вы получите сообщение об ошибке во время компиляции. Конечно, возможность создавать ссылки на объекты типа TwoDShape по-прежнему существует, и это было сделано в приведенном выше примере программы, но объявлять объекты типа TwoDShape уже нельзя. Именно поэтому массив shapes сокращен в методе main () до 4 элементов, а объект типа TwoDShape для общей двумерной геометрической формы больше не создается.
И еще одно, последнее замечание. Обратите внимание на то, что в классе TwoDShape по-прежнему присутствуют определения методов showDim () и getName () и перед их именами нет модификатора abstract. В абстрактные классы вполне допускается (и часто практикуется) включать конкретные методы, которые могут быть использованы в своем исходном виде в подклассе. А переопределению в подклассах подлежат только те методы, которые объявлены как abstract. Использование ключевого слова final
Какие бы богатые возможности ни представляли механизмы наследования и переопределения методов, иногда требуется запретить их действие. Допустим, создается класс, в котором инкапсулированы средства управления некоторым устройством. Кроме того, в этом классе пользователю может быть разрешено инициализировать устройство, чтобы воспользоваться некоторой секретной информацией. В таком случае пользователи данного класса не должны иметь возможность переопределять метод инициализации устройства. Для этой цели в Java предоставляется ключевое слово final, позволяющее без труда запретить переопределение метода или наследование класса. Предотвращение переопределения методов
Для того чтобы предотвратить переопределение метода, в начале его объявления нужно указать модификатор доступа final. Переопределять объявленные подобным образом методы нельзя. Ниже приведен фрагмент кода, демонстрирующий использование ключевого слова final для подобных целей. class А { final void meth() { System.out.println("This is a final method."); } } class В extends А { void meth() { // Ошибка! Переопределить метод нельзя. System.out.println("Illegal!") ; } }
Поскольку метод meth () объявлен как final, его нельзя переопределить в классе В. Если вы попытаетесь сделать это, возникнет ошибка при компиляции программы. Предотвращение наследования
Предотвратить наследование класса можно, указав в определении класса ключевое слово final. В этом случае считается, что данное ключевое слово применяется ко всем методам класса. Очевидно, что не имеет никакого смысла применять ключевое слово final к абстрактным классам. Ведь абстрактный класс не завершен по определению, и объявленные в нем методы должны быть реализованы в подклассах.
Ниже приведен пример класса, подклассы которого создавать запрещено. final class А { // ... } // Следующее определение класса недопустимо. class В extends А { // Ошибка! Создать подкласс от класса А нельзя. // . . . }
Как следует из комментариев к данному примеру, недопустимо, чтобы класс В наследовал от класса А, так как последний определен как final. Применение ключевого слова final к переменным экземпляра
Помимо рассмотренных ранее примеров использования, ключевое слово final можно применять и к переменным экземпляра. Подобным способом создаются именованные константы. Если имени переменной предшествует модификатор final, то значение этой переменной не может быть изменено на протяжении всего времени выполнения программы. Очевидно, что подобным переменным нужно присваивать начальные значения. В главе 6 был рассмотрен простой класс ErrorMsg для обработки ошибок. В нем устанавливается соответствие между кодами ошибок и символьными строками сообщений об ошибках. Ниже приведен усовершенствованный вариант этого класса, в котором для создания именованных констант применяется модификатор final. Теперь, вместо того чтобы передавать методу getErrorMsg () числовое значение, например 2, достаточно указать при его вызове именованную целочисленную константу DISKERR. // Возврат объекта типа String, class ErrorMsg { // Коды ошибок. // Константы объявляются с помощью ключевого слова final. final int OUTERR = 0; final int INERR = 1; final int DISKERR = 2; final int INDEXERR = 3; String msgs[] = { "Output Error", "Input Error", "Disk Full", "Index Out-Of-Bounds" }; // возвратить сообщение об ошибке String getErrorMsg(int i) { if(i >=0 & i < msgs.length) return msgs[i]; else return "Invalid Error Code"; } } class FinalD { public static void main(String args[]) { ErrorMsg err = new ErrorMsg(); // При вызове метода используются константы, // объявленные с помощью ключевого слова final. System.out.println(err.getErrorMsg(err.OUTERR)); System.out.println(err.getErrorMsg(err.DISKERR)); } }