4. Создание объектов. Конструктор.

1. Создание объектов

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

Box myBox;

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

Затем нужно создать конкретный физический объект и получить на него ссылку. Эти операции выполняются с помощью оператора new. Этот оператор динамически (то есть, во время выполнения программы) резервирует память для объекта, инициирует процесс создания объекта и возвращает ссылку на него (ссылка представляет собой адрес объекта в памяти). Далее нам необходимо сохранить ссылку в переменной.

Box myBox;
mybox = new Box();

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

После выполнения второй строки кода переменную mybox можно использовать так, как если бы она была объектом типа Box. Но в действительности переменная mybox просто содержит адрес памяти конкретного объекта типа Box. Результат выполнения этих двух строк кода показан на рисунке 4.3.

Присвоение переменным ссылок на объекты

Какие действия выполняет приведенный ниже фрагмент кода?

Box b1 = new Box();
Box b2 = b1;

На первый взгляд, переменной b2 присваивается ссылка на копию объекта, на которую ссылается переменная b1. Таким образом, может показаться, что переменные b1 и b2 ссылаются на совершенно разные объекты, но это не так. После выполнения данного фрагмента кода обе переменные, b1 и b2, будут ссылаться на один и тот же объект. Таким образом, любые изменения, внесенные в объекте по ссылке в переменную b2, окажут влияние на объект, на который ссылается переменная b1, поскольку это один и тот же объект (рис. 4.4)

Передача аргументов подпрограмме

В общем случае, для передачи аргументов подпрограмме (в данном случае, методу) в языках программирования имеются два способа.

Первым способом является передача по значению. В этом случае значение аргумента копируется в параметр метода. Следовательно, изменения, вносимые в параметр метода, не оказывают никакого влияния на аргумент.

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

Все аргументы в Java передаются по значению, но конкретный результат зависит от того, какой именно тип данных передается: примитивный или ссылочный.

Когда методу передается аргумент примитивного типа, его передача происходит по значению. В итоге создается копия аргумента, и все, что происходит с параметром, принимающим этот аргумент, не оказывает никакого влияния за пределами вызываемого метода.

int x = 10; // x равен 10

foo(x); // в метод копируется значение х

// работа метода не повлияет на переменную x
// она все равно будет равна 10

void foo(int x) {
    x = x * 2;
}

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

Box mybox = new Box();
mybox.width = 100;

foo(mybox);

// переменная width объекта mybox будет равна 200

void foo(Box mybox) {
    // В этом методе мы работаем с одним и тем же объектом
    mybox.width = mybox.width * 2;
}

2. Конструктор

Основной причиной чрезмерных затрат в программировании является "небезопасное" программирование.

Основные проблемы с безопасностью относятся к инициализации и завершению. Очень многие ошибки при программировании на языке C обусловлены неверной инициализацией переменных. Это особенно часто происходит при работе с библиотеками, когда пользователи не знают, как нужно инициализировать компонент библиотеки или забывают это сделать.

В языке C++ впервые появляется понятие конструктора - специального метода, который вызывается при создании нового объекта.

В Java разработчик класса может в обязательном порядке выполнить инициализацию каждого объекта при помощи специального метода, называемого конструктором. Если у класса имеется конструктор, Java автоматически вызывает его при создании объекта, перед тем как пользователи смогут обратиться к этому объекту. Таким образом, инициализация объекта гарантирована.

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

Синтаксис конструктора отличается от синтаксиса обычного метода. Его имя совпадает с именем класса, в котором он находится, и он не имеет возвращаемого типа.

Как было сказано выше, оператор newдинамически выделяет оперативную память для создания объекта. Общая форма использования оператора newвыглядит следующим образом

переменная_типа_класса = new имя_класса();

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

Инициализация всех переменных класса при каждом создании объекта – занятие довольно утомительное. В связи с этим, в Java разрешается выполнять собственную инициализацию при создании объектов. Такая инициализация осуществляется с помощью конструктора.

public class Box {

    double width;
    double height;
    double depth;

    public Box(double wd, double ht, double dt) {
        width = wd;
        height = ht;
        depth = dt;
    }
}

...

Box mybox = new Box(100, 200, 300);

Еще раз обратите внимание, что имя конструктора совпадает с именем класса, в котором он находится, а синтаксис аналогичен синтаксису метода. Также конструктор не имеет возвращаемого типа - даже типа void.

Большинство IDE для Java имеют механизм для генерации конструкторов. В IntelliJ IDEA нажмите комбинацию Alt+Insert находясь в окне редактирования java-файла. Откроется контекстное меню Generate, где вы можете выбрать генерацию конструктора, после чего указать поля для инициализации.

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

Оператор newвызывает конструктор Box(). Но мы ранее не создавали этот конструктор, почему компилятор не выдал ошибку, когда мы запускали приложение?

Если в классе не определен конструктор, то в Java будет автоматически предоставлен конструктор по умолчанию.

Конструктор не получающий аргументов, называется конструктором по умолчанию (в документации Java он называется конструктор без аргументов).

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

3. Ключевое слово this

Представим, что у нас есть два объекта одного класса и для этих двух объектов вызывается один и тот же метод:

public class Box {

    double width;
    double height;
    double depth;
    
    public double getArea() {
        return width * height * depth;
    }
}

...

Box box1 = new Box();
Box box2 = new Box();

box1.getArea();
box2.getArea();

Если существует один метод getArea(), как метод узнает, для какого объекта он вызывается – для box_1 или дляbox_2?

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

Box.getArea(box1);
Box.getArea(box2);

Передача дополнительного аргумента относится к внутреннему синтаксису. При попытке явно воспользоваться ею компилятор выдаст сообщение об ошибке.

Предположим, во время выполнения метода нам необходимо получить ссылку на текущий объект. Так как эта ссылка передается компилятором скрытно, идентификатора для нее не существует. Но для решения этой задачи существует ключевое слово this.

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

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

Ключевое слово this чаще всего используется в ситуации, когда локальная переменная скрывает поле класса. В Java не допускается объявление двух локальных переменных с одним и тем же именем в той же самой области действия. Однако, мы можем объявить локальные переменные, имена которых совпадают с именами полей класса.

public class Box {

    double width;
    double height;
    double depth;

    // Пример сокрытия поля класса
    void foo(double width) {
        double height = 100;

        // В консоль будет выведено значение локальных переменных
        System.out.println(width);
        System.out.println(height);
    }
}

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

Можно решить эту ситуацию путем изменения имен локальных переменных, но это некорректно с точки зрения хорошего стиля написания кода. Грамотным решением является использование ключевого слова this. Это позволит переменным иметь одинаковые названия, а к переменным экземпляра можно будет обратиться с помощью этого ключевого слова.

public class Box {

    double width;
    double height;
    double depth;

    // Пример сокрытия поля класса

    void foo(double width) {
        double height = 100;

        // В консоль будет выведено значение локальных переменных
        System.out.println(width);
        System.out.println(height);
        
        // Теперь мы обращаемся к переменным экземпляра
        System.out.println(this.width);
        System.out.println(this.height);
    }
}

Иногда, во время выполнения метода необходимо получить ссылку на текущий объект, для которого был вызван метод. Так как ссылка на него передается скрытно, идентификатора для нее нет. Но для этого существует специальное ключевое слово – this. Ключевое слово thisпредоставляет ссылку на объект, для которого был вызван метод. Обращаться с ней можно как и с любой другой ссылкой на объект.

Last updated