Проверка объектов на равенство

Где хранятся данные

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

1. Регистры. Это самое быстрое хранилище, потому что данные хранятся прямо внутри процессора. Однако количество регистров жестко ограничено, поэтому регистры используются компилятором по мере необходимости. У вас нет прямого доступа к регистрам, вы не сможете найти и малейших следов их поддержки в языке. (С другой стороны, языки С и С++ позволяют порекомендовать компилятору хранить данные в регистрах.)

2. Стек. Эта область хранения данных находится в общей оперативной памяти (RAM), но процессор предоставляет прямой доступ к ней с использованием указателя стека. Указатель стека перемещается вниз для выделения памяти или вверх для ее освобождения. Это чрезвычайно быстрый и эффективный способ размещения данных, по скорости уступающий только регистрам. Во время обработки программы компилятор Java должен знать жизненный цикл данных, размещаемых в стеке. Это ограничение уменьшает гибкость ваших программ, поэтому, хотя некоторые данные Java хранятся в стеке (особенно ссылки на объекты), сами объекты Java не помещаются в стек.

3. Куча. Пул памяти общего назначения (находится также в RAM), в котором размещаются все объекты Java. Преимущество кучи состоит в том, что компилятору не обязательно знать, как долго просуществуют находящиеся там объекты. Таким образом, работа с кучей дает значительное преимущество в гибкости. Когда вам нужно создать объект, вы пишете код с использованием ключевого слова new, и память выделяется из кучи во время выполнения программы. Конечно, за гибкость приходится расплачиваться: выделение памяти из кучи занимает больше времени, чем в стеке (даже если бы вы могли явно создавать объекты в стеке, как в С++).

4. Постоянная память. Значения констант часто встраиваются прямо в код программы, так как они неизменны. Иногда такие данные могут размещаться в постоянной памяти (ROM), если речь идет о «встроенных» системах.

5. Не-оперативная память. Если данные располагаются вне программы, они могут существовать и тогда, когда она не выполняется. Два основных примера: потоковые объекты (streamed objects), в которых объекты представлены в виде потока байтов, обычно используются для посылки на другие машины, и долгоживущие (persistent) объекты, которые запоминаются на диске и сохраняют свое состояние даже после окончания работы программы. Особенностью этих видов хранения данных является возможность перевода объектов в нечто, что может быть сохранено на другом носителе информации, а потом восстановлено в виде обычного объекта, хранящегося в оперативной памяти.

Примитивные типы

Создание объекта в куче для маленькой простой переменной — недостаточно эффективно, а ключевое слово new создает объекты именно в куче. В таких случаях вместо создания переменной с помощью new создается «автоматическая» переменная, не являющаяся ссылкой. Переменная напрямую хранит значение и располагается в стеке, так что операции с ней гораздо производительнее. Такие переменные относят к так называемым «примитивным» типам данных.

В Java размеры всех примитивных типов жестко фиксированы. Они не меняются с переходом на иную машинную архитектуру, как это происходит во многих других языках. Незыблемость размера — одна из причин улучшенной переносимости Java-nporpaмм.

Примитивный тип Размер, бит Минимум Максимум Тип упаковки
boolean (логические значения) Boolean
char (символьные значения) Unicode 0 Unicode 216-1 Character
byte (байт) -128 +127 Byte
short (короткое целое) -215 +215-1 Short
int (целое) -231 +231-1 Integer
long (длинное целое) -263 +263-1 Long
float (число с плавающей запятой) 1.40239846e-45f* 3.40282347e+38f Float
double (число с повышенной точностью) 4.94065645841246544e-32** 1.79769313486231570e+308 Double
Void («пустое» значение) Void

Размер типа boolean явно не определяется; указывается лишь то, что этот тип может принимать значения true и false.

Переменные

Переменная — это именованная область памяти, в которой хранятся данные определённого типа.У переменной есть имя и значение. Имя служит для обращения к области памяти, в которой хранится значение. Во время выполнения программы значение переменной можно изменять. Перед использованием любая переменная должна быть объявлена и инициализирована.

Объявление переменной — сообщение компилятору (интерпретатору) имени и типа переменной.

int a;

float b;

Инициализация — присваивание переменной начального значения.

а = 7;

b = 8.1;

Инициализация может также происходить непосредственно при объявлении:

int a = 10;

float b = 12.4;

Классы

Класс является абстрактным типом данных, определяемым пользователем, и представляет собой модель реального объекта в виде данных и функций для работы с ними (Павловская).

Данные класса называются полями, а функции класса — методами. Поля и методы называются членами класса.

Метод в общем виде задается так:

() { }

Описание (определение) класса в простейшем виде выглядит так:

class { }

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

class Man { // это определение класса

String hairColor = brown; //это задание поля

String getHairColor() { //это определение метода

return hairColor;

}

}

Конкретные переменные типа «класс» называются экземплярами класса, или объектами(Павловская).

Допустим, что у нас есть класс А

class A {

int x = 0;

void f(){}

} [1]

Тогда его экземпляр (объект класса), можно создать так:

A obj = new A();

В этом случае доступ к полям и методам класса можно осуществить так:

obj.x; // доступ к полю класса

obj.f(); /* доступ к методу класса */[2]

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

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

Проверка объектов на равенство

Light It = new Light();

It.on();

Интерфейс определяет, какие запросы вы вправе делать к определённому объекту. Однако где-то должен существовать и код, выполняющий запросы. Этот код, наряду со скрытыми данными, составляет реализацию.

Операторы

Оператор Обозначение Пример
Выражение Результат
Арифметические
присваивания = y = 5
сложения + y = 2 + 3
вычитания y = 7 – 2
умножения * y = 2 * 2
деления на целое % y = 9 % 2
деления / y = 9 / 2 y = 9. / 2 4.5
сложение с присваиванием += y = 5; y += 2
вычитание с присваиванием -= y = 5; y – = 2
умножение с присваиванием *= y = 5; y *= 2
деление с присваиванием /= y = 5; y /= 2
деление на целое с присваиванием %= y = 5; y %= 2
унарное сложение ++ x = 7; y = x++ x = 7; y = ++x y=7, x=8 y=8, x=8
унарное вычитание x = 9; y = x— x = 9; y = —x y=9, x=8 y=8, x=8
Логические
не (лог. отрицание) ! y = true; !у false
и 5 0 false
или || 5 || 0 true
Сравнения
равно = = 5 = = 5 true
меньше 5 4 false
больше 5 4 true
меньше или равно 5 false
больше или равно = 5 = 4 true
не равно != 5 != 4 true

Здесь стоит обратить внимания на операторы, выполняющие одновременно действие и присваивание. Разберем к примеру выражение

y += 2

Оно означает, что к значению переменной у прибавляется 2 и полученное новое значение записывается в переменную у. Аналогично действуют и другие подобные выражения.

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

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

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

Операция деление на целое дает в результате целочисленный остаток от деления целого числа на целое.

Все операции сравнения возвращают результат типа boolean.

Проверка объектов на равенство

Операции отношений == и != также работают с любыми объектами, но их смысл нередко сбивает с толку начинающих программистов на Java. Пример:

public class Equivalence {

public static void main(String[] args) {

Integer nl = new Integer(47);

Integer n2 = new Integer(47);

int n3 = 47;

int n4 = 47;

System.out.println(nl == n2);

System out println(n3 == n4);

}

} /* Output.

false

true

Результаты просто ошеломляют. Почему в первом случае мы получаем ЛОЖЬ при сравнении?

Тут дело вот в чем. Если в первом случае мы имеем дело с объектами из кучи, то n1 и n2 есть ни что иное, как ссылки на эти объекты в куче. Получается в данном случае оператор == сравнивает между собою ссылки, а не значения!!! Естественно, ссылки на эти объекты разные, поэтому и результат false.

Во втором случае мы имеем дело с примитивными типами, которые, как мы знаем, хранятся не в куче, а в системном стеке, и обращение к которым происходит не по ссылке, а напрямую к их значению. Естественно, и результат мы получаем ожидаемый – true.

Как тогда сравнивать между собой объекты из кучи? Для этого все объекты Java имеют метод equals(). По умолчанию метод equals() сравниваем между собой тоже ссылки, но его можно переопределить. Кстати, у подавляющего большинства классов стандартной библиотеки Java этот метод уже переопределен так, чтобы сравнивать именно значения, так что в большинстве случаев переопределять его самостоятельно не понадобится (обычно это требуется для самостоятельно созданных классов).

Итак, если в предыдущем примере мы заменим строку

System.out.println(nl == n2);

на

System.out.println(n1.equals(n2));

то теперь в результат получим true, как мы и ожидали изначально.

Инициализация

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

Конструктор всегда:

— имеет то же имя, что и класс;

— не возвращает никакого значения, даже типа void;

— конструктор, вызываемый без параметров, называется конструктором по умолчанию;

— если программист не указал ни одного конструктора, компилятор создает его автоматически;

— конструкторы не наследуются.

class A { // Это и есть конструктор

A { System.out.print(Выполняется конструктор ); }

}

При создании объекта

new A();

выполняется инициализация, т.е. выделяется память и вызывается конструктор, поэтому в выводе мы увидим:

Выполняется конструктор

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

class A {

A(int i){System.out.print(Выполняется конструктор + i);}

}

Теперь после создания объекта

new A(1);

сообщение будет выглядеть так:

Выполняется конструктор 1

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

Если конструктор с аргументами будет единственным конструктором класса, то будет выполняться именно он (просто компилятор не позволит создавать объекты этого класса каким-либо другим способом ).

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

Подробнее о классах

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

Причины использования static:

1) когда некоторые данные должны храниться «в единственном числе» независимо от того, сколько было создано объектов класса.

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

final — ключевое слово, означающее «Это нельзя изменить»;

Причины использования final:

1) когда нужна константа времени компиляции, которая никогда не меняется (используется только для примитивных типов);

2) когда надо задать значение, инициализируемое во время работы программы, которое нельзя изменять.

Поле, одновременно объявленное с ключевыми словами static и final, существует в памяти в единственном экземпляре и не может быть изменено.

Для примитивов final делает постоянным значение, но для ссылки на объект постоянной становится ссылка. После того как такая ссылка будет связана с объектом, она уже не сможет указывать на другой объект. Впрочем, сам объект при этом может изменяться; в Java нет механизмов, позволяющих сделать произвольный объект неизменным , можно только самому спроектировать свой код так, чтобы объект стал фактически неизменным.

Пустые константы — поля, объявленные как final, которым, однако, не было присвоено начальное значение.

Любую константу надо обязательно инициализировать перед использованием (пустые не исключение).

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

Неизменные методы. Если метод объявлен с ключевым словом final, то это значит, что содержимое метода нельзя изменить (даже из производного класса).

Неизменные классы. Если класс объявлен с ключевым словом final, то это означает, что его нельзя использовать в качестве базового.

При этом поля класса могут быть, а могут и не быть неизменными — по вашему выбору . А вот методы неизменного класса будут в любом случае неизменными (т.к. наследование запрещено). Так что прибавление слова final к методам в таком классе ничего не изменит).

Управление доступом

Контроль над доступом(сокрытие реализации).

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

Причины сокрытия реализации:

1) позволить программисту-клиенту знать, что он может использовать, а что нет.

2)разделение интерфейса и реализации. Это позволит Вам изменять все, что не объявлено как public (члены с доступом в пределах пакета, protected и private), не нарушая работоспособности изменений клиентского кода) .

Java уроки для начинающих, часть 11 — Создание и использование объектов


Похожие статьи.

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