Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



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

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

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

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

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

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

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

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

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

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

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

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

Переменные

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

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

*        здесь указано минимальное положительное число, буква f в конце числа означает, что число типа float ** здесь указано минимальное положительное число

имени и типа переменной.

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 5

сложения

 + y = 2 + 3 5

вычитания

 – y = 7 – 2 5

умножения

* y = 2 * 2 4

деления на целое

 % y = 9 % 2 1

деления

 / y = 9 / 2 y = 9. / 2 4 4.5

сложение с присваиванием 

 += y = 5; y += 2 7

вычитание с присваиванием

-= y = 5; y – = 2 3

умножение с присваиванием

 *= y = 5; y *= 2 10

деление с присваиванием

 /= y = 5; y /= 2 2

деление на целое с присваиванием

%= y = 5; y %= 2 1

унарное сложение

 ++ 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 < = 4 false

больше или равно

 > = 5 > = 4 true

не равно

 ! = 5! = 4 true

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

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

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

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

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

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

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

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

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

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

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

– не возвращает никакого значения, даже типа 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), не нарушая работоспособности изменений клиентского кода).

Спецификаторы доступа

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

public — член класса является открытым, т.е. доступен программисту-

клиенту.

private — доступ к члену класса не предоставляется никому, кроме методов этого класса. Другие классы того же пакета также не могут обращаться к private-членам.

protected — член класса является закрытым (private) для пользователя класса, но для всех, кто наследует от класса, и для соседей по пакету он доступен. (В Java protected автоматически предоставляет доступ в пределах пакета.)

Доступ в пределах пакета (по умолчанию) — член класса доступен для всех остальных классов текущего пакета.

Любой закрытый (private) метод в классе косвенно является неизменным (final). Добавление к такому методу ключевого слова final ничего не изменит.

Пакеты

Файл с исходным текстом на Java часто называют компилируемым модулем . Имя каждого компилируемого модуля должно завершаться суффиксом.java, а внутри него может находиться только один открытый (public) класс, имеющий то же имя, что и файл (с заглавной буквы, но без расширения.java).

В результате компиляции для каждого класса, определенного в файле.java, создается файл с тем же именем, но с расширением.class. Рабочая программа представляет собой набор однородных файлов.class, которые объединяются в пакет и сжимаются в файл JAR (утилитой Java jar). Интерпретатор Java отвечает за поиск, загрузку и интерпретацию этих файлов.

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

Стандартная библиотека Java содержит большое количество пакетов; в качестве примеров можно привести java.lang, java.util, java.net и т. д. Стандартные пакеты языка Java представляют собой иерархические структуры. Подобно каталогам на диске компьютера, пакеты могут быть вложены один в другой. Все стандартные пакеты принадлежат иерархиям java и javax.

В основном пакеты используются для обеспечения уникальности имен классов. Предположим, два программиста напишут класс с одинаковым именем. Если эти классы будут находиться в разных пакетах, то конфликта имен не возникнет. Для того, чтобы обеспечить уникальность имени пакета, рекомендуется использовать ваше доменное имя в сети Интернет, записанное в обратном порядке (например, для компании google пакет назывался бы com.google). Далее можно создавать различные подпакеты для гарантии уникальности имен. С точки зрения компилятора между вложенными пакетами нет никакой связи, например, для него пакеты java.util и java.util.jar никак не связаны друг с другом. Каждый из них представляет собой независимую коллекцию классов.

Предположим, у вас есть набор из нескольких файлов, которые вы хотите логически объединить в один пакет. Чтобы объявить, что все эти файлы (с расширениями.java и.class) связаны друг с другом, воспользуйтесь ключевым словом package. Тем самым вы объединяете все файлы в пакет.

             Директива       package       должна       находиться в     первой

незакомментированной строке файла. Так, команда package access;

означает, что данный компилируемый модуль входит в пакет с именем access. Иначе говоря, вы указываете, что открытый класс в этом компилируемом модуле принадлежит имени access и, если кто-то захочет использовать его, ему придется полностью записать или имя класса, или директиву import с access. Заметьте, что по правилам Java имена пакетов записываются только строчными буквами.

Доступ к классам из других пакетов можно получить двумя путями.

Во-первых, можно указывать полное имя пакета перед именем каждого класса. Например:

java.util.Date today = new java.util.Date();

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

Импортировать можно как один конкретный класс, так и весь пакет. Оператор import следует поместить в начало исходного файла (после всех директив package). Например, все классы из пакета java.util можно импортировать следующим образом:

import java.util.*;

После этого в коде можно не указывать имя пакета:

Date today = new Date();

Можно также импортировать отдельный класс из пакета: import java.util.Date;

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

Заметим, что оператор import со звездочкой можно применять для импортирования классов только одного пакета. Нельзя использовать обозначение import java.* или import java.*.*, чтобы импортировать все пакеты, имена которых содержат префикс java.

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

import java.util.*; /* оба этих пакета

import java.sql.*;   содержат классы с именем Date */

Чтобы избежать этого, следует добавить еще один конкретный оператор импорт. Тогда будет использоваться именно тот класс, который конкретно определен.

import java.util.*; import java.sql.*;

import java.util.Date;

Но что делать, если вам на самом деле нужно использовать оба класса с одним именем? В этом случае может помочь только использование полного имени пакета для каждого имени класса в коде.

java.util.Date now = new java.util.Date(); java.sql.Date today = new java.sql.Date();

Статический импорт

Начиная с пятой версии, в Java появилась возможность импортировать не только классы, но также статические методы и поля. Предположим, что в начало файла вы поместили следующую строку кода:

import static java.lang.System.*;

Это позволит использовать статические методы и поля, определенные в классе System, не указывая имени класса:

out.println(“Hi”); exit(0);

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

import static java.lang.System.out;

Однако, полученный в результате код становится более трудным для восприятия, поэтому статический импорт обычно используется только в двух случаях:

1) При использовании математических функций (импорт

статических функций класса Math);

2) При использовании констант из других классов.

Добавление классов в пакеты

Мы уже говорили, что для того, чтобы поместить класс в пакет, достаточно в начале файла указать имя пакета. Если директива package в исходном файле не указана, то классы, описанные в этом файле, помещаются в пакет по умолчанию. Пакет по умолчанию не имеет имени.

Пакеты следует помещать в подкаталог, путь к которому соответствует полностью самому имени пакета. Например, все файлы классов в пакете com.kozhura.javacourse должны находиться в подкаталоге com/kozhura/javacourse (в системе Windows слэши следует повернуть в другую сторону). Компиллятор помещает файлы классов в ту же структуру каталогов.

Таким образом, если у меня в пакете по умолчанию есть класс Default, а в пакете com.kozhura.javacourse есть класс MyClass, то структура каталогов должна выглядеть следующим образом:

. (базовый каталог)

! -- Default.java

! -- Default.class

! -- com/

! -- kozhura/

! -- javacourse/

! -- MyClass.java

! -- MyClass.class

Программу в Java следует запускать из базового каталога, причем как в случае, если точка входа в программу находится в классе Default, так и в случае, если она расположена более глубоко – в нашем случае в классе MyClass.

в первом случае запуск программы в консоли выглядел бы так:

javac Default.java java. Default во втором случае – так:

javac com/kozhura/javacourse/MyClass.java java com.kozhura.javacourse.MyClass

Заметьте, что компилятор (javac) работает с файлами (т. е. при указании файла задается путь и расширение файла), а интерпретатор (java) — с классами (для класса указывается именно пакет). Точка обозначает пакет по умолчанию (в этом случае после точки надо поставить обязательный пробел).

Порядок инициализации

Внимание!!! Хотя ключевое слово static и не используется явно, конструктор в действительности является статическим методом.

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

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

Сначала инициализируются static-члены, если они еще не были проинициализированы, и только затем нестатические объекты.

Итак, порядок создания объекта класса следующий:

• При создании первого объекта класса или при первом вызове статического метода-обращения к статическому полю класса Dog, интерпретатор Java должен найти класс Dog.class. Поиск осуществляется в стандартных каталогах, перечисленных в переменной окружения СLASSPATH.

• После загрузки файла < имя класса>.class (с созданием особого объекта Class, о котором узнаем позже) производится инициализация статических элементов. Таким образом, инициализация статических членов проводится только один раз, при первой загрузке объекта Class.

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

• Выделенная память заполняется нулями, при этом все примитивные поля объекта Dog автоматически инициализируются значениями по умолчанию (ноль для чисел, его эквиваленты для типов boolean и char, null для ссылок).

• Выполняются все действия по инициализации, происходящие в точке определения полей класса.

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

Циклы

Конструкции while, do-while и for управляют циклами и иногда называются циклическими командами. Команда повторяется до тех пор, пока управляющее логическое выражение не станет ложным.

Форма цикла while следующая:

while (логическое выражение) команда или блок команд

Логическое выражение вычисляется перед началом цикла, а затем каждый раз перед выполнением очередного повторения оператора.

Форма конструкции do-while такова:

do команда или несколько команд

while (логическое выражение);

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

Цикл for проводит инициализацию перед первым шагом цикла. Затем выполняется проверка условия цикла, и в конце каждой итерации осуществляется некое «приращение» (обычно изменение управляющей переменной). Цикл for записывается следующим образом:

for (инициализация; логическое выражение; шаг) команда или блок команд

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

Пример:

for(int i = 1, j = i + 10; i < 5; i++, j = i * 2) {

//...

}

Для того, чтобы были видны отличия между циклами, приведем решение одной и той же задачи разными способами.

Задача. Вывести 5 раз на экран в столбик цифру 20.

int i = 0; while (i < 5) { System.out.println(20); i++; } int i = 0; do System.out.println(20); i++; while (i < 5); for (int i = 0; i < 5; i++) System.out.println(20); /* область действия переменной i – тело цикла */

Foreach

В Java SE5 появилась новая, более компактная форма for для перебора элементов массивов и контейнеров (см. далее). Эта упрощенная форма, называемая синтаксисом foreach, не требует ручного изменения служебной переменной для перебора последовательности объектов — цикл автоматически представляет очередной элемент.

Следующая программа создает массив float, после чего перебирает все его элементы:

import java util.*; public class ForEachFloat { public static void main(String[] args) {

Random rand = new Random(47); float f[] = new float[10]; for(int i = 0; i < 10; i++) f[i] = rand.nextFloat(); for(float x: f)

System.out.println(x);

}

}

Return

У ключевого слова return имеется два предназначения: оно указывает, какое значение возвращается методом (если только он не возвращает тип void), а также используется для немедленного выхода из метода.

Break и continue

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

Метки

Метка представляет собой идентификатор с последующим двоеточием:

label1:

Единственное место, где в Java метка может оказаться полезной, — прямо перед телом цикла. Причем никаких дополнительных команд между меткой и телом цикла быть не должно. Причина помещения метки перед телом цикла может быть лишь одна — вложение внутри цикла другого цикла или конструкции выбора. Обычные версии break и continue прерывают только текущий цикл, в то время как их версии с метками способны досрочно завершать циклы и передавать выполнение в точку, адресуемую меткой:

label1: внешний-цикл { внутренний-цикл {

//...

break; // 1

//...

continue; // 2

//...

continue label1; // 3

//... break label1; // 4

}

}

В первом случае (1) команда break прерывает выполнение внутреннего цикла, и управление переходит к внешнему циклу. Во втором случае (2) оператор continue передает управление к началу внутреннего цикла. Но в третьем варианте (3) команда continue label1 влечет выход из внутреннего и внешнего циклов и возврат к метке label1. Далее выполнение цикла фактически продолжается, но с внешнего цикла. В четвертом случае (4) команда break label1 также вызывает переход к метке label1, но на этот раз повторный вход в итерацию не происходит. Это действие останавливает выполнение обоих циклов.

Конструкция выбора

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

switch(выражение) { case значение1: команда; break; case значение2: команда; break, case значениеЗ: команда; break; case значение4: команда; break; case значение5: команда; break;

//... default: оператор;

}

Здесь выражение — выражение, в результате вычисления которого получается число типа int, символ char или, начиная с версии Java 7, строка String. Команда switch сравнивает результат выражения с каждым последующим значением. Если обнаруживается совпадение, исполняется соответствующая команда (простая или составная). Если же совпадения не находится, исполняется команда после ключевого слова default.

Нетрудно заметить, что каждая секция case заканчивается командой break, которая передает управление к концу команды switch. Такой синтаксис построения конструкции switch считается стандартным, но команда break не является строго обязательной. Если она отсутствует, при выходе из секции будет выполняться код следующих секций case, пока в программе не встретится очередная команда break. Пример:

switch(c) { case 'a': case 'e':

case 'i': case 'о':

case 'u': System.out.print(" гласная" ); break;

case 'y':

case 'w': System.out.print(" Условно гласная" ); break;

default: System.out.print(" согласная" );

}

Инициализация массивов

Массив представляет собой последовательность объектов или примитивов, относящихся к одному типу, обозначаемую одним идентификатором.

Массивы определяются и используются с помощью оператора индексирования [ ]. Чтобы объявить массив, вы просто. указываете вслед за типом пустые квадратные скобки: int[] a;

Квадратные скобки также могут размещаться после идентификатора, эффект будет точно таким же: int a[];

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

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

a = new int[3];

/* здесь создается массив из трех чисел типа integer

* Обратите внимание, оператор new неприменим для

* создания примитивов вне массива

 */

Компиллятор автоматически заполнит такой массив значениями по умолчанию. Для чисел это 0, для символов '0', для boolean – false.

Массив можно инициализировать сразу в точке объявления:

либо так: int[] a = { 23, 12, 99 }; либо чуть сложнее: int[] a = new int[]{ 23, 12, 99 };

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

При создании массива непримитивных объектов вы фактически создаете массив ссылок:

Integer[] a = new Integer[10];

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

for(int i = 0; i < a.length; i++) a[i]=rand.nextInt(500); //заполняем массив случайными числами

Здесь length – это поле, которое есть в любом массиве, которое содержит количество элементов массива. Это поле можно прочитать, но нельзя изменить.

Если в момент объявления вы не знаете, сколько элементов будет в массиве, но вам нужен уже инициализированный массив, то может выручить такая форма инициализации:

int[] а = new int[rand.nextInt(20)];


[1] Обратите внимание на то, что здесь нет точки с запятой. Это связано с тем, что в Java точка с запятой не ставится после определений функций (методов) или классов.

[2] Здесь применены два вида комментариев в Java (однострочный и многострочный).

[3] На самом деле это слишком сильное утверждение, поскольку объекты могут существовать на разных компьютерах и адресных пространствах, а также храниться на диске. В таких случаях для идентификации объекта приходится использовать не адрес памяти, а что-то другое.


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

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

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

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

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

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

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

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

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

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

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

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

Переменные

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

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

*        здесь указано минимальное положительное число, буква f в конце числа означает, что число типа float ** здесь указано минимальное положительное число

имени и типа переменной.

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 5

сложения

 + y = 2 + 3 5

вычитания

 – y = 7 – 2 5

умножения

* y = 2 * 2 4

деления на целое

 % y = 9 % 2 1

деления

 / y = 9 / 2 y = 9. / 2 4 4.5

сложение с присваиванием 

 += y = 5; y += 2 7

вычитание с присваиванием

-= y = 5; y – = 2 3

умножение с присваиванием

 *= y = 5; y *= 2 10

деление с присваиванием

 /= y = 5; y /= 2 2

деление на целое с присваиванием

%= y = 5; y %= 2 1

унарное сложение

 ++ 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 < = 4 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), не нарушая работоспособности изменений клиентского кода).


Поделиться:



Последнее изменение этой страницы: 2019-06-19; Просмотров: 183; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.404 с.)
Главная | Случайная страница | Обратная связь