Создание собственных классов в Java: свойства, методы, конструкторы

Создание класса: свойства и методы

Рассмотрим пример создания простейшего класса. Давайте с его помощью смоделируем окружности на координатной плоскости.

Каждая такая окружность, как известно, будет определяться своим центром (т.е. точкой с двумя числовыми координатами) и радиусом (т.е. его длиной, представляемой в виде числа). Таким образом, окружность на координатной плоскости характеризуют 3 вещественных числа. Значит в нашем классе должно быть три соответствующих свойства.

Пока не будем пытаться решать серьёзных задач с помощью класса, а наделим его следующими возможностями: созданную на основе класса окружность должно быть возможно выводить на экран (в виде описания её характеристик), перемещать (т.е. совершать преобразование движения, меняя координаты её центра) и масштабировать (т.е. совершать преобразование подобия, меняя радиус окружности).

// описываем отдельный новый класс
class Circle {
    // свойства класса
    public double x; // абсцисса центра
    public double y; // ордината центра
    public double r; // радиус
    // методы класса
    // выводит на экран параметры окружности
    public void printCircle() {
        System.out.println("Окружность с центром ("+x+";"+y+") и радиусом "+r);
    }    
    // перемещает центр, движение окружности
    public void moveCircle(double a, double b) {
        x = x + a;
        y = y + b;
    }
    // масштабируем, выполняем преобразование подобия с коэффициентом k
    public void zoomCircle(double k) {
        r = r * k;
    }    
}

// описываем основной класс, содержащий метод main
public class Main {
    public static void main(String[] args) {
        // Создаём объект (окружность класса Circle), у неё будет нулевой
        // радиус и центр в (0.0;0.0), поскольку все свойства получат
        // значения по умолчанию
        Circle o1 = new Circle();
        // выводим на экран параметры окружности
        o1.printCircle();
        // Меняем абсциссу центра, обращааясь к свойству x
        o1.x = 3;
        // Меняем радиус, обращааясь к свойству r
        o1.r = 12.3;
        // выводим на экран обновлённые параметры окружности
        o1.printCircle();
        // Создаём другой объект того же класса
        Circle o2 = new Circle();
        o2.r = 3.14;
        o2.zoomCircle(1.66);
        o2.printCircle(); // Окружность с центром (0.0;0.0) и радиусом 5.2124
    }
}

Конструкторы

Когда мы создаём объект командой Circle o1 = new Circle(); используется так называемый конструктор по умолчанию (или конструктор без параметров) — это специальный метод класса, мы его не определяли явно, но даже если его не определить он создаётся автоматически, выполняется при создании каждого нового объекта и присваивает первоначальные значения его свойствам (инициализирует их). Значения по умолчанию для свойств зависят от их типа (0 или 0.0 для чиловых типов, false для логического типа и т.д.). 

Конструктор по умолчанию можно описать явно и при этом задать началльные значения для свойств нового объекта, отличные от значений по умолчанию.

От остальных методов конструктор отличается тем, что имеет то же самое имя, что и весь класс, а также не имеет типа возвращаемого значения (по сути, в результате своей работы конструктор возвращает новый объект нужного класса).

class Circle {
    public double x; // абсцисса центра
    public double y; // ордината центра
    public double r; // радиус

    public void printCircle() {
        System.out.println("Окружность с центром ("+x+";"+y+") и радиусом "+r);
    }    
    public void moveCircle(double a, double b) {
        x = x + a;
        y = y + b;
    }
    public void zoomCircle(double k) {
        r = r * k;
    }
    // конструктор по умолчанию, теперь сразу после создания объекта будем
    // получать окружность единичного радиуса с центром в начале координат
    public Circle() {
        x = 0.0;
        y = 0.0;
        r = 1.0;
    }
}

public class Main {
    public static void main(String[] args) {
        Circle o1 = new Circle();
        o1.printCircle(); // Окружность с центром (0.0;0.0) и радиусом 1.0
    }
}

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

Описать подобный конструктор можно в дополнение к основному следующим образом:

    public Circle(double a, double b, double s) {
        x = a;
        y = b;
        r = s;
    }

Теперь при создании объектов можно пользоваться любым конструктором на выбор:

        Circle o1 = new Circle();
        o1.printCircle(); // Окружность с центром (0.0;0.0) и радиусом 1.0
        Circle o2 = new Circle(1,-1,14);
        o2.printCircle(); // Окружность с центром (1.0;-1.0) и радиусом 14.0

Нужно учитывать следующий факт: если в классе описан явно хотя бы один конструктор с параметрами, то конструктор по умолчанию (без параметров) создаваться автоматические уже не будет (его в такой ситуации надо описывать явно). Хотя, если вам требуется только конструктор с параметрами (как второй из нашего примера), то можно обойтись и совсем без конструктора по умолчанию (описать в классе только один конструктор с параметрами).

Доступ к членам класса из тела методов

Добавим в наш класс метод, вычисляющий площадь той окружности, к которой метод применён. Метод будет описывать так:

    public double squareCircle() {
        double s = Math.PI * r * r;
        return s;
    }

Результат работы метода можно увидеть следующим образом:

        System.out.println("Площадь круга o2: "+o2.squareCircle()); //615.75...

Обратите внимание: внутри каждого метода класса доступны свойства того объекта, для которого метод будет вызываться. То есть если мы вызываем метод для объекта o2, то внутри метода при его выполнении мы будем работать именно со свойствами объекта o2 (o2.x будет доступно x, o2.r будет доступно как r и т.д.).

Может возникнуть ситуация, когда для формальных параметров метода вы захотите использовать имена уже принадлежащие свойствам класса.

Например, можно было бы начать описание метода для масштабирования таким образом:

public void zoomCircle(double r) {…

Как же в таком случае обращаться к свойствам объекта (ведь имена этих свойств перекрываются формальным параметром)?

Решение такой неоднозначности существует: к любому свойству внутри метода можно обращаться не только по имени, но и через ссылку this. То есть внутри метода можно написать x=13;, а можно this.x=13; — эффект будет идентичный. Соответственно, когда имя формального параметра перекрывает имя свойства, к имени свойства нужно обращаться через ссылку this. Тогда метод можно переписать таким образом:

    public void zoomCircle(double r) {
        this.r = this.r * r;
    }

Понятно, что удобнее не допускать перекрывания имён свойств именами локальных параметров в методах. Иногда, впрочем, требуется внутри метода применить какой-то другой метод к текущему объекту, тогда без ссылки this не обойтись.

Добавим в класс метод, проверяющий, совпадают ли две окружности по площади.

В этом методе должны участвовать два объекта: тот, для которого метод вызван и второй участник сравнения, который может быть передан в метод через параметр. При этом параметр будет иметь соответствующий тип (не какой-то встроенный, а в виде класса Circle).

Метод можно описать так:

    public boolean equalsCircle(Circle cir) {
        if(this.squareCircle() == cir.squareCircle()) {
            return true;
        } else {
            return false;
        }
    }

Пример использования метода:

        if(o1.equalsCircle(o2)) {
            System.out.println("Круги o2 и o1 имеют равную площадь");
        } else {
            System.out.println("Круги o2 и o1 имеют различную площадь");
        }

Задачи

  1. Создайте в классе Circle метод, вычисляющий длину окружности.
  2. Создайте в классе Circle метод, перемещающий центр круга в случайную точку квадрата координатной плоскости с диагональю от [-99;-99] до [99;99]. Обратите внимание на то, что требуется создать обычный метод, применимый к уже существующему объекту, а не конструктор создающий новый объект.
  3. Измените в классе Circle конструктор по умолчанию так, чтобы в момент создания объекта с его помощью, координаты центра и радиус окружности пользователь вводил с клавиатуры.
  4. Создайте в классе Circle метод, вычисляющий расстояние между центрами двух окружностей.
  5. Создайте в классе Circle метод, проверяющий, касаются ли окружности в одной точке. Учтите, что возможен вариант, когда одна окружность содержится внутри другой и при этом всё равно возможно касание в одной точке.

Пример

class Circle {
    public double x; // абсцисса центра
    public double y; // ордината центра
    public double r; // радиус

    public void printCircle() {
        System.out.println("Окружность с центром ("+x+";"+y+") и радиусом "+r);
    }    
    public void moveCircle(double a, double b) {
        x = x + a;
        y = y + b;
    }
    public void zoomCircle(double r) {
        this.r = this.r * r;
    }
    public Circle() {
        x = 0.0;
        y = 0.0;
        r = 1.0;
    }
    public Circle(double a, double b, double s) {
        x = a;
        y = b;
        r = s;
    }  
    // метод вычисляющий площадь круга
    public double squareCircle() {
        double s = Math.PI * r * r;
        return s;
    }
    // метод проверяющий равны ли окружности по площадям
    public boolean equalsCircle(Circle cir) {
        if(this.squareCircle() == cir.squareCircle()) {
            return true;
        } else {
            return false;
        }
    }    
}

public class Main {
    public static void main(String[] args) {
        Circle o1 = new Circle();
        o1.printCircle(); // Окружность с центром (0.0;0.0) и радиусом 1.0
        Circle o2 = new Circle(1,-1,14);
        o2.printCircle(); // Окружность с центром (1.0;-1.0) и радиусом 14.0 
        System.out.println("Площадь круга o2: "+o2.squareCircle()); //615.75...
        o1.zoomCircle(14);
        if(o1.equalsCircle(o2)) {
            System.out.println("Круги o2 и o1 имеют равную площадь");
        } else {
            System.out.println("Круги o2 и o1 имеют различную площадь");
        }
    }
}