Создание новых типов данных
вполне разумно. Впрочем, с давних времен повелось, что большинство объект¬но-ориентированных языков использовали ключевое слово class в смысле «Я собираюсь описать новый тип объектов». За ключевым словом class следует имя нового типа. Например:
class ATypeName { /* Тело класса */ }
Эта конструкция вводит новый тип, и поэтому вы можете теперь создавать объект этого типа ключевым словом new:
ATypeName а = new ATypeNameO;
Впрочем, объекту нельзя «приказать» что-то сделать (то есть послать ему не¬обходимые сообщения) до тех пор, пока для него не будут определены методы.
Поля и методы
При определении класса (строго говоря, вся ваша работа на Java сводится к оп¬ределению классов, созданию объектов этих классов и посылке сообщений этим объектам) в него можно включить две разновидности элементов: поля (fields) (иногда называемые переменными класса) и методы (methods) (еще на¬зываемые функциями класса). Поле представляет собой объект любого типа, с которым можно работать по ссылке, или объект примитивного типа. Если ис¬пользуется ссылка, ее необходимо инициализировать, чтобы связать с реаль¬ным объектом (ключевым словом new, как было показано ранее).
Каждый объект использует собственный блок памяти для своих полей дан¬ных; совместное использование обычных полей разными объектами класса не¬возможно. Пример класса с полями:
class DataOnly { int i, double d; boolean b;
}
Такой класс ничего не делает, кроме хранения данных, но вы можете соз¬дать объект этого класса:
DataOnly data = new DataOnlyO;
Полям класса можно присваивать значения, но для начала необходимо уз¬нать, как обращаться к членам объекта. Для этого сначала указывается имя ссыл¬ки на объект, затем следует точка, а далее — имя члена, принадлежащего объекту:
ссылка.член Например:
data i = 47;
data.d =1.1,
data.b = false;
Также ваш объект может содержать другие объекты, данные которых вы хоте¬ли бы изменить. Для этого просто продолжите «цепочку из точек». Например:
55
myPlane.leftTank.capacity = 100;
Класс DataOnly не способен ни на что, кроме хранения данных, так как в нем отсутствуют методы. Чтобы понять, как они работают, необходимо разобраться, что такое аргументы и возвращаемые значения. Вскоре мы вернемся к этой теме.
Значения по умолчанию для полей примитивных типов
Если поле данных относится к примитивному типу, ему гарантированно при¬сваивается значение по умолчанию, даже если оно не было инициализировано явно (табл. 2.2).
Таблица 2.2. Значения по умолчанию для полей примитивных типов
Примитивный тип Значение по умолчанию
boolean false
char ЛиОООО' (null)
byte (byte)O
short (short)O
int 0
long OL
float O.Of
double O.Od
Значения по умолчанию гарантируются Java только в том случае, если пере¬менная используется как член класса. Тем самым обеспечивается обязательная инициализация элементарных типов (что не делается в С++), которая умень¬шает вероятность ошибок. Однако значение по умолчанию может быть невер¬ным или даже недопустимым для вашей программы. Переменные всегда лучше инициализировать явно.
Такая гарантия не относится к локальным переменным, которые не являются полями класса. Допустим, в определении метода встречается объявление пере¬менной
int х;
Переменной х будет присвоено случайное значение (как в С и С++); она не будет автоматически инициализирована нулем. Вы отвечаете за присвоение правильного значения перед использованием х. Если же вы забудете это сде¬лать, в Java существует очевидное преимущество в сравнении с С++: компиля¬тор выдает ошибку, в которой указано, что переменная не была инициализиро¬вана. (Многие компиляторы С++ предупреждают о таких переменных, но в Java это считается ошибкой.)
Методы, аргументы и возвращаемые значения
Во многих языках (таких как С и С++) для обозначения именованной подпро¬граммы употребляется термин функция. В Java чаще предпочитают термин метод, как бы подразумевающий «способ что-то сделать». Если вам хочется, вы можете продолжать пользоваться термином «функция». Разница только
57
Методы, аргументы и возвращаемые значения
в написании, но в дальнейшем в книге будет употребляться преимущественно термин «метод».
Методы в Java определяют сообщения, принимаемые объектом. Основные части метода — имя, аргументы, возвращаемый тип и тело. Вот примерная форма:
возвращаемыйТип ИмяМетодаС /* список аргументов */ ) { /* тело метода */
}
Возвращаемый тип — это тип объекта, «выдаваемого» методом после его вы¬зова. Список аргументов определяет типы и имена для информации, которую вы хотите передать в метод. Имя метода и его список аргументов (объединяе¬мые термином сигнатура) обеспечивают однозначную идентификацию метода.
Методы в Java создаются только как части класса. Метод может вызываться только для объекта , и этот объект должен обладать возможностью произвести такой вызов. Если вы попытаетесь вызвать для объекта несуществующий ме¬тод, то получите ошибку компиляции. Вызов метода осуществляется следую¬щим образом: сначала записывается имя объекта, за ним точка, за ней следуют имя метода и его список аргументов:
имяОбъекта.имяМетода(арг1. арг2, аргЗ)
Например, представьте, что у вас есть метод f(), вызываемый без аргументов, который возвращает значение типа int. Если у вас имеется в наличии объект а, для которого может быть вызван метод f(), в вашей власти использовать сле¬дующую конструкцию:
int х = a.f(),
Тип возвращаемого значения должен быть совместим с типом х.
Такое действие вызова метода часто называется посылкой сообщения объекту. В примере выше сообщением является вызов f(), а объектом — а. Объект¬но-ориентированное программирование нередко характеризуется обобщающей формулой «посылка сообщений объектам».
Список аргументов
Список аргументов определяет, какая информация передается методу. Как лег¬ко догадаться, эта информация — как и все в Java — воплощается в форме объ¬ектов, поэтому в списке должны быть указаны как типы передаваемых объек¬тов, так и их имена. Как и в любой другой ситуации в Java, где мы вроде бы работаем с объектами, на самом деле используются ссылки2. Впрочем, тип ссыл¬ки должен соответствовать типу передаваемых данных. Если предполагается,
что аргумент является строкой (то есть объектом String), вы должны передать именно строку, или ожидайте сообщения об ошибке.
Рассмотрим метод, получающий в качестве аргумента строку (String). Сле¬дующее определение должно размещаться внутри определения класса, для ко¬торого создается метод:
int storage(String s) {
return s.lengthO * 2;
}
Метод указывает, сколько байтов потребуется для хранения данных опреде¬ленной строки. (Строки состоят из символов char, размер которых — 16 бит, или 2 байта; это сделано для поддержки набора символов Unicode.) Аргумент имеет тип String и называется s. Получив объект s, метод может работать с ним точно так же, как и с любым другим объектом (то есть посылать ему сообще¬ния). В данном случае вызывается метод length(), один из методов класса String; он возвращает количество символов в строке.
Также обратите внимание на ключевое слово return, выполняющее два дей¬ствия. Во-первых, оно означает: «выйти из метода, все сделано». Во-вторых, если метод возвращает значение, это значение указывается сразу же за коман¬дой return. В нашем случае возвращаемое значение — это результат вычисления s.length() * 2.
Метод может возвращать любой тип, но, если вы не хотите пользоваться этой возможностью, следует указать, что метод возвращает void. Ниже приведе¬но несколько примеров:
boolean flagO { return true: }
float naturalLogBaseO { return 2.718, }
void nothingO { return; }
void nothing2() {}
Когда выходным типом является void, ключевое слово return нужно лишь для завершения метода, поэтому при достижении конца метода его присутствие необязательно. Вы можете покинуть метод в любой момент, но если при этом указывается возвращаемый тип, отличный от void, то компилятор заставит вас (сообщениями об ошибках) вернуть подходящий тип независимо от того, в ка¬ком месте метода было прервано выполнение.