Основы объектно-ориентированного программирования
Классы и объекты в языке Java
Для знакомства с базовым для объекно-ориентированного программирования понятием класса, рассмотрим класс R2Point, задающий точку на плоскости.
Пример программы
//Класс, описывающий точку (Point) на плоскости (R2). class R2Point { // Переменные экземпляра, задающие координаты точки. private double x, y; // Конструктор с параметрами. public R2Point(double x, double y) { this.x = x; this.y = y; } // Метод экземпляра - расстояние до начала координат. public double dist0() { return Math.sqrt(x*x + y*y); } }
Для того, чтобы создать экземпляр этого класса, в программе следует предварительно объявить переменную типа R2Point, а затем воспользоваться оператором new. Объявление переменной можно совмещать с созданием нового объекта, что демонстрируется в следующей программе, находящей расстояние от точки до начала координат.
R2Point p = new R2Point(1.,2.); double d = p.dist0();
Этот пример объясняет, почему такой стиль записи называют объектно-ориентированным: при вызове метода в центре внимания находится объект p, а не метод dist0. Этот метод не нуждается в аргументе — объект, над которым производится данное действие, уже указан в данной конструкции. Именно по этой причине внутри метода dist0 можно работать с величинами x и y, принадлежащими конкретному экземпляру объекта. Имена x и y в теле метода dist0 являются сокращениями от this.x и this.y соответственно, где ключевое слово this является указателем на тот экземпляр класса, с которым должен работать метод. Часто нет необходимости явно использовать этот неявный аргумент любого метода экземпляра, однако иногда это необходимо. Примером является конструктор класса R2Point.
Конструктор — это метод, имеющий имя, совпадающее с именем класса. Две основные задачи конструктора заключаются в выделении памяти под вновь создаваемый объект и его инициализация. В рассматриваемом нами примере точки на плоскости совершенно бессмысленно пытаться как-либо работать с объектом (точкой), у которого не заданы координаты. Поэтому конструктор объекта типа R2Point, который вызывается с помощью метода new, должен записать в переменные конкретного экземпляра его координаты. В том случае, если в качестве имен аргументов конструктора выбраны x и y (а это вполне естественный выбор), для обеспечения доступа к переменным экземпляра необходимо использование ключевого слова this. В объявлении конструктора не разрешается указывать возвращаемый тип (хотя неявно всегда возвращается объект this ), а в его теле нельзя использовать оператор return.
Если в некоторой задаче необходимо создавать новые объекты типа R2Point, вводя их координаты с клавиатуры, то может возникнуть желание реализовать это непосредственно в конструкторе.
Пример программы
//Класс, описывающий точку (Point) на плоскости (R2). class R2Point{ // Переменные экземпляра, задающие координаты точки. private double x, y; // Конструктор с параметрами. public R2Point(double x, double y) { this.x = x; this.y = y; } // Еще один конструктор, позволяющий вводить // координаты вновь создаваемой точки с клавиатуры. public R2Point() throws Exception { x = Xterm.inputDouble("x -> "); y = Xterm.inputDouble("y -> "); } // Метод класса - расстояние до начала координат. public static double dist0(R2Point a) { return Math.sqrt(a.x*a.x + a.y*a.y); } // Метод экземпляра - расстояние до начала координат. public double dist0() { return Math.sqrt(x*x + y*y); } }
Такой класс выглядит на первый взгляд весьма странно — в нем разные методы имеют одинаковые имена. Компилятор, однако, может различить их. Признак, по которому это происходит — количество и типы аргументов у различных методов. Так, если оператор new для объекта R2Point имеет два аргумента, то вызывается первый из конструкторов, а если аргументов нет — второй.
Вторым интересным моментом в этом примере является иллюстрация использования метода класса. Первый из двух методов dist0 описан с ключевым словом static, что и делает его методом класса. Такой метод не может быть вызван от конкретного объекта с помощью оператора "точка", ему не передается указатель this на экземпляр, а само это ключевое слово не может быть использовано в его теле. Приведенная ниже программа находит расстояние от точки p до начала координат двумя различными эквивалентными методами.
R2Point p = new R2Point(1.,2.); double d1 = p.dist0(); double d2 = R2Point.dist0(p);
Могут существовать и переменные класса — это такие переменные, которые всегда имеются ровно в одном экземпляре, независимо от того как много имеется объектов данного класса. Для их описания также применяется ключевое слово static.
Обычно класс содержит целый ряд методов, как экземпляра, так и класса, набор которых определяется конкретной задачей. Вот как выглядит описание класса R2Point, которое будет использовано в следующем параграфе в реальном проекте.
Пример программы
//Класс, описывающий точку (Point) на плоскости (R2). class R2Point { private double x, y; public R2Point(double x, double y) { this.x = x; this.y = y; } public R2Point() throws Exception { x = Xterm.inputDouble("x -> "); y = Xterm.inputDouble("y -> "); } public static double dist(R2Point a, R2Point b) { return Math.sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } public static double area(R2Point a, R2Point b, R2Point c) { return 0.5*((a.x-c.x)*(b.y-c.y)-(a.y-c.y)*(b.x-c.x)); } public static boolean equal(R2Point a, R2Point b) { return a.x==b.x && a.y==b.y; } public static boolean isTriangle(R2Point a, R2Point b, R2Point c) { return area(a, b, c) != 0.0; } public boolean inside(R2Point a, R2Point b) { return (a.x <= x && x <= b.x || a.x >= x && x >= b.x) && (a.y <= y && y <= b.y || a.y >= y && y >= b.y); } public boolean light(R2Point a, R2Point b) { double s = area(a, b, this); return s < 0.0 || ( s == 0.0 && ! inside(a, b)); } }