Содержание
| Предыдущая | СледующаяГЛАВА 4
Ява - строго типизированный язык, это означает, что каждая переменная и каждое выражение имеет тип, который известен во времени компиляции. Типы ограничивают значения, которые может принимать переменная, (§4.5) и значения, которые могут быть результатом выражения, ограничивают набор операций, применимых к этим значениям, и определяют смысл операций. Строгая типизация помогает обнаруживать ошибки времени компиляции.
Типы языка Ява разделены на две категории: примитивные типы и ссылки. Примитивные типы (§4.2) это тип boolean и числовые типы. Числовые типы - целочисленные типы: byte, short, int, long, и char, а также вещественные типы float и double. Ссылочные типы (§4.3) - классовые типы, интерфейсные типы, и типы массивов. Имеется также специальный тип null. Объект (§4.3.1) в языке Ява - это динамически созданный экземпляр классового типа или динамически созданный массив. Значения ссылочного типа - это ссылки на объекты. Все объекты, включая массивы, поддерживают методы класса Object (§4.3.2).Строковые литералы представляются объектами класса String (§4.3.3).
Типы считаются одинаковыми (§4.3.4), если они имеют одни и те же квалифицированные имена и были загружены одним и тем же классовым загрузчиком. Имена типов используются (§4.4) в объявлениях, в приведениях, в выражениях создающих экземпляр класса, в выражениях создания массива, и в выражениях с операцией instanceof.
Переменная (§4.5) - это определенное место в памяти. Переменная простого типа всегда имеет значение именно этого типа. A переменная классового типа T, может содержать ссылку типа null или ссылку на элемент классового типа T или любого класса, являющегося подклассом T. Переменная интерфейсного типа может содержать ссылку типа null или ссылку на любой элемент любого класса интерфейса. Если T - простой тип, то переменная типа "array of T " может содержать ссылку типа null или ссылку на любой массив типа "array of T "; если T - ссылочный тип, то переменная типа "array of T" может содержать ссылку типа null или ссылку на любой массив типа " array of S " такой, что тип S совместим (§5.2) с типом T. Переменная типа Object может содержать ссылку типа null или ссылку на любой объект, независимо является ли он элементом классового типа или массивогого типа.
Имеются два вида типов в языке Ява: примитивные типы (§4.2) и ссылочные типы (§4.3). Имеется соответственно два вида значений данных, которые могут быть сохранены в переменных, переданы как параметры, возвращаемые методами, и вычислены: примитивные значения (§4.2) и значения U ссылки (§4.3).
Type: PrimitiveType ReferenceType
Имеется также специальный тип null, тип null-выражения, которое не имеет никакого имени. Так как тип null не имеет никакого имени, невозможно объявить переменную типа null или привести к типу null. Ссылка типа null является единственным возможным значением выражения типа null. Ссылка типа null может быть всегда приведена к любому из ссылочных типов. Практически, программист на языке Ява может игнорировать тип null и только признавать, что null - есть просто специальный литерал, который может иметь любой ссылочный тип.
Примитивный тип предопределен языком Ява и назван зарезервированным ключевым словом (§3.9):
PrimitiveType: NumericTypeboolean
NumericType: IntegralType FloatingPointType IntegralType: one ofbyte short int long char
FloatingPointType: one offloat double
Разные простые значения не могут иметь одну и ту же переменную. Переменная, чей тип - примитивный тип всегда имеет примитивное значение того же самого типа. Значение переменной примитивного типа может быть изменено только операцией присваивания.
Числовые типы - это целочисленные типы и вещественные типы.
Целочисленные типы: byte, short, int, и long, значения данных типов являются 8-битовыми, 16-битовыми, 32-битовыми и 64-битовыми, целыми числами со знаком, соответственно, и char, его значения имеют 16-битовое представление целыми числами без знака, являющимися Unicode-символами.
Вещественные типы: float, значениями являются - 32-разрядные IEEE 754 числа с плавающей точкой и double, значениями являются - 64-разрядные IEEE 754 числа с плавающей точкой.
Тип boolean имеет ровно два значения: true(истина) и false(ложь).
Значения целочисленных типов - целые числа в следующих диапазонах:
Язык Ява обеспечивает ряд операций, с целочисленными значениями:
Другие полезные конструкторы, методы и константы предопределены в классах Integer (§20.7), Long (§20.8), и Character (§20.5).
Если целочисленная операция, отличная от операции сдвига, имеет по крайней мере один операнд типа long, то операция выполняется используя 64-разрядное представление, и результат числовой операции имеет тип long. Если тип одного из операндов отличен от long, то он сначала расширяется (§5.1.2) чтобы иметь тип long (§5.6). Иначе, будет выполнена операция, использующая 32-разрядное представление, и результат целочисленной операции будет иметь тип int. Если же тип операнда не int, то сначала он будет расширен, чтобы иметь тип int.
Встроенные целочисленные операции никогда не дают переполнения. Единственные числовые операции, которые генерируют исключительные ситуации (§11) - операция целочисленного деления / (§15.16.2) и операция целочисленного остатка % (§15.16.3), которые выдают ошибку ArithmeticException, если правый операнд - ноль.
Пример:
class Test { public static void main(String[] args) { int i = 1000000; System.out.println(i * i); long l = i; System.out.println(l * l); System.out.println(20296 / (l - i)); } }
Выведет на экран:
-727379968 1000000000000
и затем сталкивается с ArithmeticException в делении на 1-i, потому что 1-i - это ноль. Первое умножение выполняется в 32-разрядном представлении, в то время как второе умножение - умножение типа long.
Значение -727379968 это десятичное значение младших 32 битов математического результата, 1000000000000, который является значением, слишком большим для типа int.
Любое значение любого целочисленного типа может быть приведено к любому числовому типу или быть получено из любого числового типа. Нет никакой связи между целочисленными типами и типом boolean.
Вещественные типы - это float и double, представляющие в одиночном – 32 битном или в двоичном – 64 битном формате IEEE 754 значения и операции, как определено в IEEE Standard for Binary Floating-Point Arithmetic, ANSI/IEEE Standard 754-1985 (IEEE, New York).
IEEE 754 стандарт не только для положительных и отрицательных чисел, но также для положительных и отрицательных нолей, положительных и отрицательных бесконечностей, и специальных Not-a-Number (“не число”, сокращенно NaN). Значение NaN используется, чтобы представить результат некоторых операций, таких как деление ноля на ноль. Типы NaN констант типа float и double предопределены как Float.NaN (§20.9.5) и Double.NaN (§20.10.5).
Конечные отличные от нуля значения типа float имеют форму, где s - это или +1, или -1, m - положительное целое число меньшее и e - целое число между -149 и 104, включительно. Те формы, в которых m–положительно, но меньше и e равно -149, как говорится, являются денормализованными.
Конечные отличные от нуля значения типа double имеют форму, где s или +1, или -1, m - положительное целое число меньшее , и e - целое число между -1075 и 970, включительно. Те формы, в которых m–положительно, но меньше и e равно -1075, как говорится, являются денормализованными.
За исключением NaN, вещественные значения имеют двоичное представление; NaN в порядке возрастания : отрицательная бесконечность, отрицательные конечные значения отличные от нуля, отрицательный ноль, положительный ноль, положительные конечные значения отличные от нуля, и положительная бесконечность.
Положительный ноль и отрицательный ноль равны; таким образом результат выражения 0.0 == –0.0 есть true, а результат 0.0 > –0.0 есть false. Но другие операции могут отличать положительный и отрицательный ноль; например, 1.0/0.0 имеет значение положительной бесконечности, в то время как значение 1.0/-0.0 - отрицательная бесконечность. Операции Math.min и Math.max также отличают положительный ноль и отрицательный ноль.
NaN не имеют двоичного представления, таким образом, числовые операции сравнения <, <=, >, и >= возвращают false если оба операнда - NaN (§15.19.1). Операция равенства == возвращает false, если один из операндов - NaN, и операция неравенства != возвращает true, если один из операндов - NaN (§15.20.1). В частности x != x true тогда и только тогда, когда x - NaN, и (x < y) ==! (x >=y) будет false, если x или y - NaN.
Любое значение вещественного типа может быть приведено к любому значению числового типа или быть получено из любого числового типа. Не существует никакой связи между вещественными типами и типом boolean.
Язык Ява обеспечивает ряд операций над вещественными значениями:
Другие полезные конструкторы, методы и константы определены в классах Float (§20.9), Double (§20.10), и Math (§20.11).
Если по крайней мере один из операндов в двухместной операции имеет вещественный тип, то операция является вещественной операцией, даже если один из операндов целочисленный.
Если по крайней мере один из операндов числовой операции имеет тип double, то операция выполняется с использованием 64-разрядной вещественной арифметики, и результатом числовой операции будет значение типа double. (Если тип одного из операндов не double, то его сначала расширяют, чтобы он имел тип double (§5.6). Иначе, операция выполняется, используя 32-разрядную вещественную арифметику, и результат числовой операции имеет тип float. Если один из операндов имеет тип отличный от float, то его сначала расширяют, чтобы он имел тип float.
Операции над значениями вещественного типа ведут себя в соответствии с IEEE 754. В частности, язык Ява требует поддержки IEEE 754 денормализованных чисел с плавающей точкой и постепенной потери значимых разрядов, которые упрощают выполнение некоторых числовых алгоритмов. Вещественные операции в языке Ява не ”округляют результат к нулю”, если он - денормализованное число.
При вычислении вещественных арифметических выражений языка Ява результат каждой операции округляется с точностью результата всего выражения. Неточные результаты должны быть округлены до значения, самого близкого к точному; если два самых близких представления значений - сравнительно близки, то выбирается представление с меньшим значащим нолем. Этот режим округления является установленным по умолчанию в стандарте IEEE 754, и известен как округление до ближайшего.
Язык Ява использует округление до нуля при преобразовании вещественного значения к целочисленному (§5.1.3), которое действует, в этом случае, отсекая у числа биты мантиссы. Округление до нуля выбирает в качестве своего результата ближайшее и не превосходящее его точное значение.
В языке Ява в результате операций с вещественными числами не возникает исключительных ситуаций (§11). Результатом операции, приводящей к переполнению, является бесконечность со знаком; результатом операции, приводящей к потере значащих разрядов, является ноль со знаком; и результатом операции, приводящей к неопределенному результату, является NaN. Все числовые операции с NaN в качестве операнда имеют NaN в результате. Как уже было сказано, NaN не имеет численного представления, таким образом операция проверки на равенство, включающая один или два NaN возвращает всегда false, а операция !=, включающая NaN, возвращает true, даже для случая x != x, когда x - NaN.
Пример программы:
class Test { public static void main(String[] args) { // пример переполнения: double d = 1e308; System.out.print("overflow produces infinity: "); System.out.println(d + "*10==" + d*10); // пример пополнения: d = 1e-305 * Math.PI; System.out.print("gradual underflow: " + d + "\n "); for (int i = 0; i < 4; i++) System.out.print(" " + (d /= 100000)); System.out.println(); // пример NaN: System.out.print("0.0/0.0 is Not-a-Number: "); d = 0.0/0.0; System.out.println(d); // пример неточных результатов и округления: System.out.print("inexact results with float:"); for (int i = 0; i < 100; i++) { float z = 1.0f / i; if (z * i != 1.0f) System.out.print(" " + i); } System.out.println(); // другой пример неточных результатов и округления: System.out.print("inexact results with double:"); for (int i = 0; i < 100; i++) { double z = 1.0 / i; if (z * i != 1.0) System.out.print(" " + i); } System.out.println(); // пример целочисленного округления: System.out.print("cast to int rounds toward 0: "); d = 12345.6; System.out.println((int)d + " " + (int)(-d)); } }
Выведет на экран:
overflow produces infinity: 1.0e+308*10==Infinity gradual underflow: 3.141592653589793E-305 3.1415926535898E-310 3.141592653E-315 3.142E-320 0.0 0.0/0.0 is Not-a-Number: NaN inexact results with float: 0 41 47 55 61 82 83 94 97 inexact results with double: 0 49 98 cast to int rounds toward 0: 12345 -12345
Этот пример показывает, что потеря значимости может приводить к потере точности.
Когда i = 0 (вызывает деление на ноль), то z становится положительной бесконечностью, и z*0 становится NaN, уже не равным 1.0.
Тип boolean позволяет представить логическую величину, которая может принимать два значения true (истинный) или false (ложный) (§3.10.3). К типу boolean применимы следующие операции:
Логические выражения определяют поток управления в некоторых видах операторов:
Логическое выражение также устанавливает, которое из подвыражений вычисляется условной операцией ?: (§15.24).
В управляющих операторах, и в качестве первого операнда условной операции ?: могут использоваться только логические выражения. Значение x целочисленного типа может быть преобразовано к типу boolean, следуя соглашениям языка Си, где любое отличное от нуля значение есть true (истина), с помощью выражения x!=0. Ссылка на объект obj может быть преобразована к логическому типу boolean, следуя соглашениям языка Си, где любая ссылка тип которой не null есть true (истина), с помощью выражения obj!=null.
Допускается приведение значений типа boolean в тип boolean (§5.1.1); никакое другое приведение на типе boolean не позволяется. Логический тип может быть преобразован в строку строковым преобразованием (§5.4).
Существует три вида ссылочных типов: классовый тип (§8), интерфейсный тип (§9), и тип массив (§10).
ReferenceType:
ClassOrInterfaceType
ArrayType
ClassOrInterfaceType:
ClassType
InterfaceType
ClassType:
TypeName
InterfaceType:
TypeName
ArrayType:
Type [ ]
Имена описаны в §6; имена типов в §6.5 и более подробно в §6.5.4.
Простой пример:
class Point { int[] metrics; } interface Move { void move(int deltax, int deltay); }
объявляет классовый тип Point, интерфейсный тип Move, и использует тип массив int[] (элементы массива имеют тип int) чтобы объявить поле metrics класса Point.
Объект это экземпляр класса или массив.
Ссылочные значения (часто называемые ссылками) - это указатели на объекты, и специальная ссылка типа null, которая не ссылается ни на какой объект.
Экземпляр класса создается явно выражением, создающим экземпляр (§15.8), или, вызовом метода newInstance класса Class (§20.3.8). Массив создается явно выражением, создающим массив (§15.8).
Новый экземпляр класса создается неявно, когда в выражении использована операция конкатенации строк + (§15.17.1), результатом которой будет новый объект типа String (§4.3.3, §20.12). Новый объект типа массив создается неявно, когда вычисляется новое инициализирующее массив выражение (§10.6); это может происходить, когда инициализируется класс или интерфейс (§12.4), когда создается новый экземпляр класса (§15.8) или когда выполнен оператор объявления локальной переменной (§14.3).
Многие из этих случаев иллюстрируются в следующем примере:
class Point { int x, y; Point() { System.out.println("default"); } Point(int x, int y) { this.x = x; this.y = y; } // Экземпляр Point создан явно во время инициализаци класса: static Point origin = new Point(0,0); // Строка может быть создана неявно операцией +: public String toString() { return "(" + x + "," + y + ")"; } } class Test { public static void main(String[] args) { // Point создан явно, используя newInstance: Point p = null; try { p = (Point)Class.forName("Point").newInstance(); } catch (Exception e) { System.out.println(e); } // Массив создан неявно с помощью конструктора массива: Point a[] = { new Point(0,0), new Point(1,1) }; // Строки созданы неявно операциями +: System.out.println("p: " + p); System.out.println("a: { " + a[0] + ", " + a[1] + " }"); // Массив создан явно выражением создания массива: String sa[] = new String[2]; sa[0] = "he"; sa[1] = "llo"; System.out.println(sa[0] + sa[1]); } }
который выводит на экран:
default p: (0,0) a: { (0,0), (1,1) } hello
Операции со ссылками на объекты:
Может существовать много ссылок на один и тот же объект. Большинство объектов имеет структуру, хранящуюся в полях объектов, которые являются экземплярами классов или в переменных, которые являются компонентами объекта-массива. Если две переменные содержат ссылки на один и тот же объект, то состояние объекта может быть изменено используя одну переменную-ссылку на объект, и тогда измененное состояние может наблюдаться через ссылку в другой переменной.
Пример программы:
class Value { int val; }
class Test { public static void main(String[] args) { int i1 = 3; int i2 = i1; i2 = 4; System.out.print("i1==" + i1); System.out.println(" but i2==" + i2); Value v1 = new Value(); v1.val = 5; Value v2 = v1; v2.val = 6; System.out.print("v1.val==" + v1.val); System.out.println(" and v2.val==" + v2.val); } }
Выводит результат:
i1==3 but i2==4 v1.val==6 and v2.val==6
потому что v1.val и v2.val ссылаются на одну и ту же переменную экземпляра (§4.5.3) в одном из объектов Value, созданного единственным выражением new, хотя i1 и i2 являются различными переменными.
Примеры создания и использования массивов смотрите в §10 и §15.9.
Каждый объект имеет связанный с ним замок (§17.13), который используется (синхронизированными) synchronized-методами (§8.4.3) и оператором synchronized (§14.17), чтобы обеспечить управление параллельным доступом к его состоянию различными потоками (§17.12, §20.20).
Стандартный класс Object - суперкласс (§8.1) всех других классов. Переменная типа Object может содержать ссылку на любой объект, который является экземпляром класса или массив (§10). Все классы и массивы наследуют методы класса Object, которые перечислены здесь и полностью определены в §20.1:
package java.lang;
public class Object { public final Class getClass() { . . . } public String toString() { . . . } public boolean equals(Object obj) { . . . } public int hashCode() { . . . } protected Object clone() throws CloneNotSupportedException { . . . } public final void wait() throws IllegalMonitorStateException, InterruptedException { . . . } public final void wait(long millis) throws IllegalMonitorStateException, InterruptedException { . . . } public final void wait(long millis, int nanos) { . . . } throws IllegalMonitorStateException, InterruptedException { . . . } public final void notify() { . . . } throws IllegalMonitorStateException public final void notifyAll() { . . . } throws IllegalMonitorStateException protected void finalize() throws Throwable { . . . } }
Члены Object следующие:
Экземпляр класса String (§20.12) представляется последовательностями Unicode-символов. Строковый объект имеет постоянное (неизменное) значение. Строковые литералы (§3.10.5) - ссылки на экземпляры класса String. Операция конкатенации строк + (§15.17.1) неявно создает новый строковый объект.
Два ссылочных типа называются одинаковыми типами, если:
Типы используются, когда они появляются в описаниях или в некоторых выражениях.
Следующий фрагмент содержит один или несколько случаев каждого вида употребления типа:
import java.util.Random;
class MiscMath { int divisor; MiscMath(int divisor) { this.divisor = divisor; } float ratio(long l) { try { l /= divisor; } catch (Exception e) { if (e instanceof ArithmeticException) l = Long.MAX_VALUE; else l = 0; } return (float)l; } double gausser() { Random r = new Random(); double[] val = new double[2]; val[0] = r.nextGaussian(); val[1] = r.nextGaussian(); return (val[0] + val[1]) / 2; } }
В этом примере, типы используются в следующих описаниях:
и в выражениях следующих видов:
Переменная размещается в памяти и имеет тип, иногда этот тип называется типом времени компиляции, который является или примитивным типом (§4.2), или ссылочным типом (§4.3). Переменная всегда содержит значение, которое совместимо по присваиванию (§5.2) с типом. Значение переменной изменяется либо с помощью присваивания (§15.25), либо с помощью операций ++ (инкремента) или -- (декремента) в префиксной или постфиксной форме (§15.13.2, §15.13.3, §15.14.1, §15.14.2).
Совместимость значения переменной с типом гарантируется проектом языка Ява. По умолчанию значения совместимы (§4.5.4) и все присваивания переменной проверяются для совместимости присваивания (§5.2), обычно во время компиляции, но, в отдельных случаях во время выполнения (§10.10).
Переменная примитивного типа всегда содержит значение только примитивного типа.
Переменная ссылочного типа может содержать каждую из следующих ссылок:
Имеется семь видов переменных:
Это не только для одной исключительной ситуации, локальная переменная могла бы всегда рассматриваться после того как она создается, когда выполнено описание оператора этой локальной переменной . Исключительная ситуация содержит оператор switch (§14.9), где это возможно для контроля выхода из блока, но при этом обходит выполнение описания оператора локальной переменной. Из-за ограничений, наложенных правилами определяющего присваивания (§16), локальная переменная, объявленная с помощью такого обойденного оператора описания локальной переменной не может использоваться прежде, чем ей будет присвоено значение с помощью присваивания выражения (§15.25).
Следующий пример содержит несколько различных видов переменных:
class Point { static int numPoints; // numPoints - переменная класса int x, y; // x и y - переменные экземпляра int[] w = new int[10]; // w[0] - компонент массива int setX(int x) { // x - параметр метода int oldx = this.x; // oldx - локальная переменная this.x = x; return oldx; } }
Каждая переменная в программе на языке Ява должна иметь некоторое значение прежде, чем это значение будет использовано:
Пример программы:
class Point { static int npoints; int x, y; Point root; } class Test { public static void main(String[] args) { System.out.println("npoints=" + Point.npoints); Point p = new Point(); System.out.println("p.x=" + p.x + ", p.y=" + p.y); System.out.println("p.root=" + p.root); } }
напечатает:
npoints=0 p.x=0, p.y=0 p.root=null
иллюстрирующий инициализацию npoints по умолчанию, которая происходит когда класс Point подготавливается (§12.3.2), и инициализацию по умолчанию x, y и root, которая происходит, когда создан новый экземпляр класса new Point. Смотрите §12 для полного описания всех аспектов загрузки, компоновки и инициализации классов и интерфейсов, плюс описание обработки классов при создании новых экземпляров класса.
Каждый объект принадлежит некоторому специфическому классу: т.е. классу, упомянутому в выражении создания, которое произвело данный объект, классу, чей класс объекта использовался для вызова нового экземпляра метода (§20.3.6), чтобы произвести объект, или класс String для объектов, неявно созданных строковой операцией конкатенации + (§15.17.1). Этот класс назван классом объекта. (Массивы также имеют класс, как описано в конце этого раздела.) Упомянутый объект является экземпляром данного класса и всех суперклассов этого класса.
(Иногда говорят, что переменная или выражение имеет "тип времени выполнения", но это - злоупотребление терминологией; они ссылаются на класс объекта обращаясь к значению переменной или выражения во время выполнения (в предположении, что значение - не null). Короче говоря, тип - это понятие времени компиляции. Переменная или выражение имеют тип; объект или массив не имеют никакого типа, но принадлежат классу.)
Тип переменной всегда объявляется, а тип выражения может быть установлен во время компиляции. Тип ограничивает значения, которые может принимать переменная или иметь выражение во время выполнения. Если значение во время выполнения - это ссылка, не равная null, и она указывает на объект или массив, который имеет класс (не тип), то этот класс обязательно будет совместимым с типом времени компиляции.
Хотя переменная или выражение могут иметь тип времени компиляции, который является типом интерфейса, но не существует экземпляров интерфейсов. Переменная или выражение, чей тип - это тип интерфейса, может ссылаться на любой объект, если класс объекта реализует (§8.1.4) данный интерфейс.
Пример, иллюстрирующий создание новых объектов и различия между типом переменной и классом объекта:
public interface Colorable { void setColor(byte r, byte g, byte b); } class Point { int x, y; } class ColoredPoint extends Point implements Colorable { byte r, g, b; public void setColor(byte rv, byte gv, byte bv) { r = rv; g = gv; b = bv; } } class Test { public static void main(String[] args) { Point p = new Point(); ColoredPoint cp = new ColoredPoint(); p = cp; Colorable c = cp; } }
В этом примере:
Каждый массив также имеет класс; метод getClass (§20.1.1), когда он вызван для объекта массив, возвратит класс объекта (класса Class), который представляет класс массива. Классы для массивов имеют непривычные имена, которые не являются идентификаторами языка Ява; например, класс для массива компонент типа int имеет имя " [I " так что значение выражения:
new int[10].getClass().getName()
это строка " [I "; для получения более подробной информации смотрите §20.1.1.
Содержание
| Предыдущая | Следующая