Перейти на главную   
  helloworld.ru - документация и книги по программированию  
helloworld.ru - документация и книги по программированию
    главная     хостинг    
Поиск по сайту:  
Смотрите также
Языки программирования
C#
MS Visual C++
Borland C++
C++ Builder
Visual Basic
Quick Basic
Turbo Pascal
Delphi
JavaScript
Java
PHP
Perl
Assembler
AutoLisp
Fortran
Python
1C

Интернет-технологии
HTML
VRML
HTTP
CGI
FTP
Proxy
DNS
протоколы TCP/IP
Apache

Web-дизайн
HTML
Дизайн
VRML
PhotoShop
Cookie
CGI
SSI
CSS
ASP
PHP
Perl

Программирование игр
DirectDraw
DirectSound
Direct3D
OpenGL
3D-графика
Графика под DOS

Алгоритмы
Численные методы
Обработка данных

Сис. программирование
Драйверы

Базы данных
MySQL
SQL

Другое

Хостинг


Друзья
demaker.ru
Реклама

Лучший хостинг. Аренда серверов




helloworld.ru

ГЛАВА 8

8.1.5 Тело класса и объявления членов

Тело класса может содержать объявления членов класса, то есть полей (§8.3) и методов (§8.4).Тело класса также может содержать статические инициализаторы (§8.5) и объявления конструкторов (§8.6) класса.

    ClassBody:
    
    	{ ClassBodyDeclarationsopt }
    
    ClassBodyDeclarations:
    
    	ClassBodyDeclaration
    
    	ClassBodyDeclarations ClassBodyDeclaration
    
    ClassBodyDeclaration:
    
    	ClassMemberDeclaration
    
    	StaticInitializer
    
    	ConstructorDeclaration
    
    ClassMemberDeclaration:
    
    	FieldDeclaration
    
    	MethodDeclaration
    

Область действия имени члена, объявленного в классовом типе или унаследованного им - все тело объявления классового типа.

8.2 Члены класса

Все следующее является членами классового типа:

  • Члены, унаследованные из непосредственного суперкласса (§8.1.3), за исключением класса Object, который не имеет никакого непосредственный суперкласса
  • Члены, унаследованные из любых непосредственных суперинтерфейсов (§8.1.4)
  • Члены, объявленные в теле класса (§8.1.5)

Члены класса, которые объявлены с модификаторами private , не наследуются подклассами этого класса. Только члены класса, объявленные как protected или public , наследуются подклассами, объявленными в другом пакете некоторого класса .

Конструкторы и статические инициализаторы - не являются членами и следовательно не наследуются.

Пример:

class Point {
	int x, y;
	private Point() { reset(); }
	Point(int x, int y) { this.x = x; this.y = y; }
	private void reset() { this.x = 0; this.y = 0; }
}


class ColoredPoint extends Point {
	int color;
	void clear() { reset(); }														// ошибка
}

class Test {
	public static void main(String[] args) {
		ColoredPoint c = new ColoredPoint(0, 0);													// ошибка
		c.reset();													// ошибка
	}
}

генерирует четыре ошибки времени компиляции:

  • Ошибка происходит потому, что ColoredPoint не имеет конструктора, объявленного с двумя целочисленными параметрами, как требуется при использовании main. Это иллюстрирует тот факт, что ColoredPoint не наследует конструкторы своего суперкласса Point.
  • Другая ошибка происходит, потому что ColoredPoint не объявляет конструкторов, и следовательно конструктор по умолчанию для него создается автоматически (§8.6.7). Этот конструктор эквивалентен: ColoredPoint() { super(); } , который вызывает конструктор без параметров для непосредственного суперкласса класса ColoredPoint. Ошибка состоит в том, что конструктор для Point , у которого нет никаких параметров объявлен как private, и следовательно не доступен снаружи классу Point, даже через вызов конструктора суперкласса (§8.6.5).

 

  • Еще две ошибки происходят, потому, что метод reset класса Point - private, и следовательно не унаследован классом ColoredPoint. Метод вызовов в методе clear класса ColoredPoint и в методе main класса Test следовательно не верен.

8.2.1 Примеры наследования

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

8.2.1.1 Пример: Наследование с заданным по умолчанию доступом

Рассмотрим пример, где в пакете points объявлены два модуля компиляции:

package points;

public class Point {
	int x, y;

	public void move(int dx, int dy) { x += dx; y += dy; }
}

и:

package points;

public class Point3d extends Point {
	int z;
	public void move(int dx, int dy, int dz) {
		x += dx; y += dy; z += dz;
	}
}

и третий модуль компиляции в другом пакете:

import points.Point3d;

class Point4d extends Point3d {
	int w;
	public void move(int dx, int dy, int dz, int dw) {
		x += dx; y += dy; z += dz; w += dw; // ошибки времени компиляции
	}
}

Здесь оба класса компилируются в пакете points . Класс Point3d наследует поля x и y класса Point, потому что они находятся в том же самом пакете что и Point. Класс Point4d, который находится в другом пакете, не наследует поля x и y класса Point или поля z   класса Point3d, и что приводит к сбою при компиляции.

Лучшим способом написания третьего модуля компиляции было бы:

import points.Point3d;

class Point4d extends Point3d {
	int w;
	public void move(int dx, int dy, int dz, int dw) {
		super.move(dx, dy, dz); w += dw;
	}
}
использовав метод move суперкласса Point3d , для обработки dx, dy, и dz. Если Point4d написан, таким образом, то компиляция произойдет без ошибок.

 

8.2.1.2 Наследование с public и protected

Пусть дан класс Point:

package points;

public class Point {

	public int x, y;


	protected int useCount = 0;


	static protected int totalUseCount = 0;


	public void move(int dx, int dy) {
		x += dx; y += dy; useCount++; totalUseCount++;
	}

}

public и protected поля x, y, useCount и totalUseCount унаследованы во всех подклассах Point. Следовательно, эта тестовая программа может успешно компилироваться и в другом пакете:

class Test extends points.Point {
	public void moveBack(int dx, int dy) {
		x -= dx; y -= dy; useCount++; totalUseCount++;
	}
}

8.2.1.3 Наследование с private

В примере:

class Point {

	int x, y;


	void move(int dx, int dy) {
		x += dx; y += dy; totalMoves++;
	}


	private static int totalMoves;


	void printMoves() { System.out.println(totalMoves); }

}


class Point3d extends Point {

	int z;


	void move(int dx, int dy, int dz) {
		super.move(dx, dy); z += dz; totalMoves++;
	}
}

переменная класса totalMoves может использоваться только внутри класса Point; она не унаследована подклассом Point3d. Ошибка времени компиляции происходит в том месте, где метод move класса Point3d пытается увеличить totalMoves.

 

8.2.1.4 Доступ к членам недоступных классов

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

Рассмотрим модуль компиляции:

package points;


public class Point {
	public int x, y;
	public void move(int dx, int dy) {
		x += dx; y += dy;
	}
}

и другой модуль компиляции другого пакета:

package morePoints;


class Point3d extends points.Point {
	public int z;
	public void move(int dx, int dy, int dz) {
		super.move(dx, dy); z += dz;
	}
}


public class OnePoint {
	static points.Point getOne() { return new Point3d(); }
}

При вызове morePoints.OnePoint.getOne, однако, третий пакет возвратил бы Point3d , который может использоваться как Point, даже если тип Point3d не доступен вне пакета morePoints. Метод move мог бы затем вызываться для объекта, который допустим, потому что метод move Point3d - public (поскольку, любой метод, отменяющий public метод, должен сам быть public , для того, чтобы в таких ситуациях он работал правильно). К полям x и y такого объекта можно было бы также обращаться из третьего пакета.

Так как поле z класса Point3d - public, невозможно обратиться к этому полю из кода вне пакета morePoints, доступна только ссылка на экземпляр класса Point3d в переменной p типа Point. Выражение p.z неправильно, поскольку p имеет тип Point , а класс Point не имеет поля z; выражение ((Point3d)p).z также неправильно, потому что класс типа Point3d , не может иметь ссылку вне пакета morePoints. Однако, объявление поля z как public не бесполезно. Если бы в пакете morePoints, был public подкласс Point4d класса Point3d:

package morePoints;


public class Point4d extends Point3d {
	public int w;
	public void move(int dx, int dy, int dz, int dw) {
		super.move(dx, dy, dz); w += dw;
	}
}

тогда класс Point4d наследовал бы поле z, являющееся public, и тогда можно было бы обращаться к коду в пакетах отличных от morePoints, через переменные и выражения public типа Point4d.

8.3 Объявления полей

Переменные классового типа представляются в соответствии с объявлениями поля:

    FieldDeclaration:
    
    	FieldModifiersopt Type VariableDeclarators ;
    
    VariableDeclarators:
    
    	VariableDeclarator
    
    	VariableDeclarators , VariableDeclarator
    
    VariableDeclarator:
    
    	VariableDeclaratorId
    
    	VariableDeclaratorId = VariableInitializer
    
    VariableDeclaratorId:
    
    	Identifier
    
    	VariableDeclaratorId [ ]
    
    VariableInitializer:
    
    	Expression
    
    	ArrayInitializer
    

FieldModifiers описаны в §8.3.1. Identifier в FieldDeclarator может использоваться в имени, для обращения к полю. Областью действия имени поля (§6.3) является все тело объявления класса, в котором оно объявлено. Более чем одно поле можно объявить в одиночном объявлении поля, используя более чем один описатель FieldDeclarator и Type обращающийся ко всем описателям в объявлении. Объявления переменных, включая типы массив, обсуждаются в §10.2.

Ошибка времени компиляции произойдет, если тело объявления класса, содержит объявления двух полей с одинаковым именем. Методы и поля могут иметь одинаковое имя, так как они используются в различных контекстах, и неоднозначность устраняется различными процедурами поиска (§6.5).

Если класс объявляет поле с некоторым именем, тогда говорят, что объявление этого поля скрывает (§6.3.1) любые и все доступные объявления полей с тем же самым именем в суперклассах и суперинтерфейсах класса.

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

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

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

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

К скрытому полю можно обращаться, используя составное имя (если оно статическое) или, используя выражение (§15.10) доступа к полю, которое содержит ключевое слово super или приведение к типу суперкласса. Для подробного обсуждения см §15.10.2 и пример.

8.3.1 Модификаторы поля

    FieldModifiers:
    
    	FieldModifier
    
    	FieldModifiers FieldModifier
    
    FieldModifier: один из
    
    	public protected private
    
    	final static transient volatile
    

Модификаторы доступа public, protected, и private обсуждены в §6.6. Происходит ошибка во время компиляции, если один и тот же модификатор появляется более чем один раз в объявлении поля, или если объявление поля имеет более чем один из модификаторов доступа public, protected и private. Если два или более (отличных) модификатора поля появляются в объявлении поля, то обычно, хотя не обязательно, они появляются в порядке, непротиворечивом с показанным выше в продукции для FieldModifier.

8.3.1.1 Статические поля

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

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

Программа- пример:


class Point {
	int x, y, useCount;
	Point(int x, int y) { this.x = x; this.y = y; }
	final static Point origin = new Point(0, 0);
}


class Test {
	public static void main(String[] args) {
		Point p = new Point(1,1);
		Point q = new Point(2,2);
		p.x = 3; p.y = 3; p.useCount++; p.origin.useCount++;
		System.out.println("(" + q.x + "," + q.y + ")");
		System.out.println(q.useCount);
		System.out.println(q.origin == Point.origin);
		System.out.println(q.origin.useCount);
	}
}
напечатает:

(2,2)
0
true
1

показывает что замена полей x, y, и useCount у p не воздействует на поля q, потому что эти поля - переменные экземпляра в различных объектах. В этом примере, переменная класса origin класса Point вызвана и использованием имени класса как спецификатор в Point.origin, и использованием переменных типа класса в поле доступном для выражений (§15.10), как в p.origin и q.origin. Эти два пути вызова переменной класса origin обращаются к одному тому же объекту, это доказано фактом равенства значений ссылок выражений (§15.20.3) :

q.origin==Point.origin

равно true. Следующим доказательством является то, что оператор:

p.origin.useCount++;

делает значение q.origin.useCount равным 1; это, потому что p.origin и q.origin ссылаются на одну и ту же переменную.

8.3.1.2 Поля final

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

Любая попытка приведения к полю final кончается ошибкой во время компиляции. Следовательно, как только поле final было инициализировано, оно всегда содержит одно и то же значение. Если поле final содержит ссылку на объект, тогда состояние объекта может быть изменено операциями над объектом, но поле будет всегда ссылаться на тот же самый объект. Это применяется также к массивам, потому что массивы - объекты; если поле final содержит ссылку на массив, тогда компоненты массива могут быть изменены операциями над массивом, но поле будет всегда ссылаться на тот же самый массив.

Объявление поля final может служить в качестве полезной документации: то что его значение не будет изменяться, может помочь уйти от ошибок при программировании, и может облегчить работу транслятора, в генерации эффективного кода.

В примере:


class Point {
	int x, y;
	int useCount;
	Point(int x, int y) { this.x = x; this.y = y; }
	final static Point origin = new Point(0, 0);
}

класс Point объявляет final-переменную класса origin. Переменная origin содержит ссылку на объект, являющийся экземпляром класса Point, чьи координаты - (0, 0). Значение переменной Point.origin не может измениться, так что она всегда ссылается на тот же самый объект Point , созданный инициализатором. Однако, операция на объекте Point могла бы изменить состояние - например, при изменении useCount или даже, неявно, координаты x или y.

8.3.1.3 Переходные поля

Переменные могут быть отмечены transient для указания , что они - не часть постоянного состояния объекта. Если экземпляр класса Point:


class Point {
	int x, y;
	transient float rho, theta;
}

был сохранен в постоянной памяти обслуживающей системой, тогда будут сохранены только поля x и y . Эта спецификация еще не определяет детали таких услуг; но мы предполагаем обеспечивать их в будущей версии этой спецификации.

8.3.1.4 Поля volatile

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

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

Если, в следующем примере, один поток неоднократно вызывает метод one (но не более, чем Integer.MAX_VALUE (§20.7.2) раз), и другой поток неоднократно вызывает метод two:


class Test {

	static int i = 0, j = 0;


	static void one() { i++; j++; }

	static void two() {
		System.out.println("i=" + i + " j=" + j);
	}

}

тогда метод twoмог бы иногда печатать значение j , являющееся большим чем значение i, потому что пример не включает никакую синхронизацию и, по правилам, объясненными в §17, разделяемые значения i и j мог бы модифицироваться не по порядку.

Один способ предотвратить такое неверное поведение состоит в том, чтобы объявить методы one и two synchronized (§8.4.3.5):


class Test {

	static int i = 0, j = 0;


	static synchronized void one() { i++; j++; }

	static synchronized void two() {
		System.out.println("i=" + i + " j=" + j);
	}

}

Это позволяет методам one и two выполнятся одновременно, и кроме того гарантирует, что разделяемые значения i и j изменяются перед окончанием метода one . Следовательно метод two никогда не наблюдает значение j большее чем i; действительно, он всегда наблюдает то же самое значение i и j.

Другой подход - объявить i и j как volatile:


class Test {

	static volatile int i = 0, j = 0;


	static void one() { i++; j++; }

	static void two() {
		System.out.println("i=" + i + " j=" + j);
	}

}

Это позволяет методам one и two выполняться одновременно, но гарантирует, что доступ к разделяемым значениям i иj происходит точно столько раз, и точно в том же самом порядке, сколько раз к ним обращаются в течение выполнения текста программы каждым потоком. Следовательно, метод two никогда не наблюдает значениеj большего чем i, потому что каждая модификация i должна быть отражена в общедоступном значении iпрежде, чем происходит модификация j . Возможно, однако, что любой данный вызов метода two мог бы наблюдать значение j , являющиеся намного большим чем наблюдаемое значение i , потому что каждый метод мог бы быть выполнен много раз между моментом когда метод two вызывает значение i и моментом когда метод two вызывает значение j.

См. §17 для подробного обсуждения и примеров.

Происходит ошибка времени компиляции, если переменная final также объявлена как volatile.

8.3.2 Инициализация полей

Если описатель поля содержит инициализатор переменной, тогда оно имеет семантику присваивания (§15.25) объявленной переменной, и:

  • Если описатель - для переменной класса (то есть статическое поле), тогда инициализатор переменной вычислен и присваивание выполняется только однажды, когда класс - initialized (§12.4).
  • Если описатель - для переменной экземпляра (то есть поле нестатическое), тогда инициализатор переменной экземпляра и присваивание выполняется, каждый раз когда создается экземпляр класса (§12.5).

Пример:


class Point {
	int x = 1, y = 5;
}

class Test {
	public static void main(String[] args) {
		Point p = new Point();
		System.out.println(p.x + ", " + p.y);
	}
}

выводит:

1, 5

потому что присваивания x и y происходят всякий раз, когда создается новый Point .

Инициализаторы переменных также используются в объявлениях локальных переменных (§14.3), где инициализатор вычислен и присваивание выполняется, каждый раз при объявлении локальной переменной.

Ошибка времени компиляции произойдет, если вычисление инициализатора переменной для поля класса (или интерфейса) может неожиданно завершаться с контролируемым исключением (§11.2).

8.3.2.1 Инициализаторы для переменных класса

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


class Test {
	static float f = j;							// ошибка времени компиляции: опережающая ссылка
	static int j = 1;
	static int k = k+1;							// ошибка времени компиляции: опережающая ссылка
}

вызывает две ошибки времени компиляции, потому что j упоминается в инициализации f прежде, чем j объявлен и потому что инициализация k ссылается на k непосредственно.

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

Если ключевое слово this (§15.7.2) или ключевое слово super (§15.10.2, §15.11) имеется в выражении инициализации для переменной класса, тогда происходит ошибка во время компиляции.

(Здесь есть одна тонкость - то, что во время выполнения статические переменные, являющиеся final и инициализированные с постоянными во время компиляции значениями, инициализируются первыми. Это также применяется к таким полям в интерфейсах (§9.3.1). Эти переменные - “константы”, которые никогда не будут наблюдаться, чтобы иметь заданные по умолчанию начальные значения (§4.5.4), даже программами дальнего вызова. См. §12.4.2 и §13.4.8 для более подробного обсуждения.)

8.3.2.2 Инициализаторы переменных экземпляров

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


class Test {
	float f = j;
	int j = 1;
	int k = k+1;
}

вызывает две ошибки времени компиляции, потому что j упоминается в инициализации f прежде, чем j  объявлен и потому что инициализация k ссылается на k непосредственно.

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


class Test {
	float f = j;
	static int j = 1;
}

компилируется без ошибки; он инициализирует j как 1, тогда класс Test инициализирован, и инициализирует f в текущее значение j каждый раз при создании экземпляра класса Test .

В выражениях инициализации переменных экземпляров разрешаются ссылки на текущий объект this (§15.7.2) и использование ключевого слова super (§15.10.2, §15.11).

8.3.3 Примеры объявлений полей

Следующие примеры иллюстрируют(возможно искусственно) несколько пунктов относительно объявления поля.

8.3.3.1 Пример: Скрытие переменных класса

Пример:


class Point {
	static int x = 2;
}


class Test extends Point {
	static double x = 4.7;
	public static void main(String[] args) {

		new Test().printX();
	}
	void printX() {
		System.out.println(x + " " + super.x);
	}
}

выводит:

4.7 2

потому что объявление x в классе Test скрывает область действия x в классе Point, так как класс Test не наследует поле x из суперкласса Point. Внутри объявления класса Test, простое имя x ссылается на поле, объявленное внутри класса Test. Код в классе Testможет ссылаться на поле x класса Point как super.x (или так как x — статический, как Point.x). Если объявление Test.x удалено:


class Point {
	static int x = 2;
}


class Test extends Point {
	public static void main(String[] args) {
		new Test().printX();
	}
	void printX() {
		System.out.println(x + " " + super.x);
	}
}

тогда поле x класса Point больше не скрыто внутри класса Test; но простое имя x теперь ссылается на поле Point.x. Код в классе Test может еще ссылаться на то же самое поле как super.x. Следовательно, вариант этой программы выводит:

2 2

8.3.3.2 Пример: Скрытие переменных экземпляра

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


class Point {
	int x = 2;
}


class Test extends Point {
	double x = 4.7;
	void printBoth() {
		System.out.println(x + " " + super.x);
	}
	public static void main(String[] args) {
		Test sample = new Test();
		sample.printBoth();
		System.out.println(sample.x + " " + 

												((Point)sample).x);
	}
}

выводит:


4.7 2
4.7 2

потому что объявление x в классе Test скрывает область действия x в классе Point, так как класс Test не наследует поле x из суперкласса Point. Однако, нужно отметить, что поле x класса Point не унаследованно классом Test, оно тем не менее реализуется экземпляром класса Test. Другими словами, каждый экземпляр класса Test содержит два поля, одно типа int и одно типа float. Оба поля несут имя x, но внутри объявления класса Test, простое имя x всегда ссылается на поле, объявленное внутри класса Test. Код в методах экземпляра класса Testможет ссылаться на экземпляр переменной x класса Point как super.x.

Код, использующий выражение доступа к полю, для обращения к полю x , обратится к полю, именованному x в классе, выражения ссылочного типа. Таким образом, выражение sample.x обращается к значению типа float , переменная экземпляра объявлена в классе Test, потому что тип переменной sample- Test, но выражение ((Point)sample).x обращается к значению типа int , переменной экземпляра, объявленной в классе Point, из-за приведения к типу Point.

Если объявление x удалено из класса Test, как в программе:


class Point {
	static int x = 2;
}


class Test extends Point {
	void printBoth() {
		System.out.println(x + " " + super.x);
	}
	public static void main(String[] args) {
		Test sample = new Test();
		sample.printBoth();
		System.out.println(sample.x + " " +

												((Point)sample).x);
	}
}

тогда поле x класса Point больше не скрыто внутри класса Test. Внутри метода экземпляра в объявлении класса Test, простое имя x теперь ссылается на поле, объявленное внутри класса Point . Код в классе Test может еще ссылаться на то же самое поле как super.x. Выражение sample.x еще ссылается на поле x внутри типа Test, но это поле теперь является унаследованым полем, и ссылается на поле x объявленное в классе Point . Вариант данной программы выводит:


2 2
2 2

8.3.3.3 Пример: Множественное наследование полей

Класс может наследовать два или более поля с тем же самым именем, или от двух интерфейсов или из суперкласса и интерфейса. Происходит ошибка во время компиляции при любой попытке ссылки на любое двусмысленно унаследованное поле его простым именем. Составное имя или выражение доступа к полю, содержащее ключевое слово super (§15.10.2) может использоваться, для обращения к таким полям недвусмысленно. В примере:

interface Frob { float v = 2.0f; }


class SuperTest { int v = 3; }


class Test extends SuperTest implements Frob {
	public static void main(String[] args) {
		new Test().printV();
	}
	void printV() { System.out.println(v); }
}

класс Test наследует два поля, именованные v, одно из суперкласса SuperTest и одно из суперинтерфейса Frob. Это само по себе разрешено, но происходит ошибка во время компиляции из-за использования простого имени v в методе printV: нельзя определить, которое поле v имеется в виду.

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

interface Frob { float v = 2.0f; }


class SuperTest { int v = 3; }


class Test extends SuperTest implements Frob {
	public static void main(String[] args) {
		new Test().printV();
	}
	void printV() {
		System.out.println((super.v + Frob.v)/2);
	}
}

Она компилируется и выводит:

2.5

Даже если два отличных унаследованных поля имеют одинаковое значение, и оба описаны как final, любая ссылка на любое поле простым именем рассматривается как неоднозначная и приводит к ошибке во время компиляции. В примере:

interface Color { int RED=0, GREEN=1, BLUE=2; }


interface TrafficLight { int RED=0, YELLOW=1, GREEN=2; }


class Test implements Color, TrafficLight {
	public static void main(String[] args) {
		System.out.println(GREEN);										// compile-time error
		System.out.println(RED);										// compile-time error
	}
}

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

8.3.3.4 Пример: Перенаследование полей

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


public interface Colorable {
	int RED = 0xff0000, GREEN = 0x00ff00, BLUE = 0x0000ff;
}


public interface Paintable extends Colorable {
	int MATTE = 0, GLOSSY = 1;
}


class Point { int x, y; }


class ColoredPoint extends Point implements Colorable {
	. . .
}


class PaintedPoint extends ColoredPoint implements Paintable 
{
	. . .       RED       . . .
}

поля, RED, GREEN, и BLUE унаследованы классом PaintedPoint и через непосредственный суперкласс ColoredPoint и через непосредственный суперинтерфейс Paintable. Простые имена, RED, GREEN, и BLUE могут, однако, использоваться без неоднозначности внутри класса PaintedPoint ссылаясь на поля, объявленные в интерфейсе Colorable.

8.4 Объявления метода

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

    MethodDeclaration:
    
    	MethodHeader MethodBody
    
    MethodHeader:
    
    	MethodModifiersopt ResultType MethodDeclarator Throwsopt
    
    ResultType:
    
    	Type
    
    	void
    
    MethodDeclarator:
    
    	Identifer ( FormalParameterListopt )
    

MethodModifiers описаны в §8.4.3, Throws объяснен в §8.4.4, а MethodBody в §8.4.5. Объявление метода или определяет тип значения, которое метод возвращает или использует ключевое слово void, для указания того, что метод не возвращает значений.

Identifier в MethodDeclarator может использоваться в имени, для ссылки на метод. Класс может объявлять метод с тем же самым именем что и класс или поле класса.

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

    MethodDeclarator:
    
    	MethodDeclarator [ ]
    

но не должно использоваться в новом коде языка Ява.

Ошибка времени компиляции произойдет, если тело класса имеет как члены два метода с одинаковой сигнатурой (§8.4.2) (именем, числом параметров, и типами всех параметров). Методы и поля могут иметь одинаковою сигнатуру, с того момента как они используются в различных контекстах и в них устранены неоднозначности различным процедурами поиска (§6.5).

8.4.1 Формальные параметры

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

    FormalParameterList:
    
    	FormalParameter
    
    	FormalParameterList , FormalParameter
    
    FormalParameter:
    
    	Type VariableDeclaratorId
    

Для большей ясности следующее повторено из §8.3:

    VariableDeclaratorId:
    
    	Identifier
    
    	VariableDeclaratorId [ ]
    

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

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

При вызове метода (§15.11), значения фактического параметра выражения инициализируют вновь созданные параметры-переменные, каждый из которых объявлен как Type, перед выполнением тела метода. Identifier который появляется в DeclaratorId, может использоваться как простое имя в теле метода для ссылки на формальной параметр.

Область действия имен формальных параметров - все тело метода. Эти имена параметров не могут быть повторно объявлены как локальные переменные или параметры исключения внутри метода; то есть не разрешено скрывать имя параметра.

Формальные параметры упоминаются только при использовании простых имен, и никогда при использовании составных имен (§6.6).

8.4.2 Сигнатура метода

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

Пример:


class Point implements Move {
	int x, y;
	abstract void move(int dx, int dy);
	void move(int dx, int dy) { x += dx; y += dy; }
}

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

8.4.3 Модификаторы метода

    MethodModifiers:
    
    	MethodModifier
    
    	MethodModifiers MethodModifier
    
    MethodModifier: один из
    
    	public protected private
    
    	abstract static final synchronized native
    

Модификаторы доступа public, protected и private обсуждены в §6.6. Происходит ошибка во время компиляции, если один и тот же модификатор появляется более чем однажды в объявлении метода, или объявление метода имеет более чем один модификатор доступа public, protected и private. Происходит ошибка во время компиляции, если объявление метода, которое содержит ключевое слово abstract также содержит любое из ключевых слов private, static, final, native или synchronized.

Если в объявлении метода появляются два или более модификатора метода, обычно, хотя не и всегда, они появляются в порядке непротиворечивом с показанным в продукции для МодификатораМетода.

8.4.3.1 Методы abstract.

Объявление abstract метода представляет метод как член, задаёт сигнатуру (имя, число и тип параметров), тип результата и методы throws (если существуют), но не обеспечивает реализацию. Объявление abstract метода m должно встречаться внутри abstract класса (назовём его A); иначе произойдет ошибка времени компиляции. Всякий подкласс A не является abstract, так как должен обеспечить реализацию m, а иначе случается ошибка времени компиляции. Более корректно, для каждого подкласса C abstract класса A, если C не является abstract, то должен существовать некий класс B такой, что все следующее является истинным:

  • B - это суперкласс C или же - это сам C.
  • B - это подкласс A.
  • B обеспечивает объявление метода m, метод m не abstract, и это объявление унаследовано классом C, таким образом обеспечивается выполнение метода m, который объявлен в C.

Если не существует такого класса B, то происходит ошибка времени компиляции.

Это ошибка времени компиляции private-метода, который объявлен как abstract. Так как невозможно реализовать этот private-метод для данного подкласса, потому что private-методы не доступны другим подклассам; поэтому такой метод никогда не может быть использован.

Это ошибка времени компиляции для static-метода, который объявлен abstract.

Это ошибка времени компиляции для final-метода, который объявлен abstract.

Abstract класс может замещать abstract метод, обеспечивая другое объявление abstract метода. Это может быть полезно, например, чтобы поместить комментарий документации (§18), или объявить ряд проверяемых исключений (§11.2), которые могут быть сгенерированы этим методом, когда он будет реализован подклассами, чтобы быть более ограниченным. Например, рассмотрите этот код:


class BufferEmpty extends Exception {
	BufferEmpty() { super(); }
	BufferEmpty(String s) { super(s); }
}


class BufferError extends Exception {
	BufferError() { super(); }
	BufferError(String s) { super(s); }
}


public interface Buffer {
	char get() throws BufferEmpty, BufferError;
}


public abstract class InfiniteBuffer implements Buffer {
	abstract char get() throws BufferError;
}

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

Метод экземпляра, который - не является abstract, может быть замещен abstract методом. Например, мы можем объявить abstract класс Point, которому требуются подклассы, чтобы реализовать String, они должны быть полными экземлярами классов:


abstract class Point {
	int x, y;
	public abstract String toString();
}

Данное abstract объявление toString замещает не abstract метод toString класса Object (§20.1.2). (Класс Object неявный прямой суперкласс класса Point.) Фрагмент кода


class ColoredPoint extends Point {
	int color;
	public String toString() {
		return super.toString() + ": color " + color; // error
	}
}

результатом будет ошибка времени компиляции, потому что при вызове super.toString () происходит обращение к методу toString класса Point, который является abstract и поэтому не может быть вызван. Метод toString класса Object может быть сделан доступным для класса ColoredPoint только, если класс Point явно делает его доступным через некоторый другой метод, как в:


abstract class Point {
	int x, y;
	public abstract String toString();
	protected String objString() { return super.toString(); }
}

class ColoredPoint extends Point {
	int color;
	public String toString() {
		return objString() + ": color " + color;														// correct
	}
}

8.4.3.2 Методы static

Метод, который объявлен static, назван методом класса. Метод класса всегда вызывается независимо от объекта. Попытка сослаться на текущий объект, использующий ключевое слово this или ключевое слово super в теле метода класса заканчивается ошибкой времени компиляции. Это ошибка времени компиляции для static-метода, который объявлен abstract.

Метод, который объявлен не как static, назван методом экземпляра, и иногда называется не-static методом). Метод экземпляра всегда вызывается относительно объекта, который становится текущим, и на который ключевые слова this и super указывают в течение выполнения тела метода.

8.4.3.3 Методы final

Метод может быть объявлен final, чтобы предотвратить подклассы от переполнения или скрыть его. Ошибка времени компиляции произойдет, если сделать попытку заместить или скрыть final-метод.

Private-метод и все остальные методы, объявленные в final-классе (§8.1.2.2) неявно будут final-методами, потому что невозможно замещать их. Хотя это и разрешено, но не требуется включать в описание таких методов ключевое слово final.

Ошибка времени компиляции для final-метода происходит, если он объявлен abstract.

Во время выполнения, генератор кода машины или оптимизатор может легко и благополучно "внутреннее" тело final-метода, заменить вызовом метода с кодом в теле метода, как показано в примере:


final class Point {
	int x, y;
	void move(int dx, int dy) { x += dx; y += dy; }
}


class Test {
	public static void main(String[] args) {
		Point[] p = new Point[100];
		for (int i = 0; i < p.length; i++) {
			p[i] = new Point();
			p[i].move(i, p.length-1-i);
		}
	}
}

Здесь, "внутреннее" тело метода move класса Point в методе main, преобразуется к циклу в форме:


		for (int i = 0; i < p.length; i++) {
			p[i] = new Point();
			Point pi = p[i];
			pi.x += i;
			pi.y += p.length-1-i;
		}

цикл может быть подчинен дальнейшей оптимизации.

Такая подстановка кода не может быть выполнена в момент компиляции, поскольку не гарантируется, что Test и Point будут всегда перекомпилироваться вместе, так, чтобы всякий раз, когда Point определен, метод move - изменяется, и код для Test.main будет также обновлен.

8.4.3.4 Методы native

Метод, который является native, осуществлен в виде кода, зависящего от платформы, который обычно написан на другом языке программирования типа Cи, Cи ++, ФОРТРАН или ассемблере. Тело native метода задается как точка с запятой вместо блока, содержащего тело метода.

Ошибка времени компиляции происходит, если native-метод объявлен как abstract.

Hапример, класс RandomAccessFile стандартного пакета java.io может быть объявлен при помощи следующих native-методов:

package java.io;


public class RandomAccessFile

	implements DataOutput, DataInput
{	. . .
	public native void open(String name, boolean writeable)
		throws IOException;
	public native int readBytes(byte[] b, int off, int len)
		throws IOException;
	public native void writeBytes(byte[] b, int off, int len)
		throws IOException;
	public native long getFilePointer() throws IOException;
	public native void seek(long pos) throws IOException;
	public native long length() throws IOException;
	public native void close() throws IOException;
}

8.4.3.5 Методы sychronized

Synchronized-метод приобретает замок (§17.1) прежде, чем он выполняется. Для метода класса (static) используется замок, связанный с объектом Class (§20.3). Для метода экземпляра используется замок, связанный с this (объект для которого метод был вызван). Вот некоторые замки, которые могут использоваться sichronized оператором (§14.17); фрагмент кода:


class Test {
	int count;
	synchronized void bump() { count++; }
	static int classCount;
	static synchronized void classBump() {
		classCount++;
	}
}

имеет точно тот же результат как:


class BumpTest {
	int count;
	void bump() {
		synchronized (this) {
			count++;
		}
	}
	static int classCount;
	static void classBump() {
		try {
			synchronized (Class.forName("BumpTest")) {
				classCount++;
			}
		} catch (ClassNotFoundException e) {
				...
		}
	}
}

более сложный пример:


public class Box {

	private Object boxContents;


	public synchronized Object get() {
		Object contents = boxContents;
		boxContents = null;
		return contents;
	}


	public synchronized boolean put(Object contents) {
		if (boxContents != null)
			return false;
		boxContents = contents;
		return true;
	}

}

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

Если put и get, не были бы объявлены как synchronized, и два потока выполняли одни и те же методы для одного и того же экземпляра Box в одно и то же время, тогда код мог бы выполняться некорректно. В связи с этим могли бы, например, теряться данные отслеживания объекта, потому что два вызова put произошли одновременно.

См. §17 для более детального обсуждения потоков и замков.

8.4.4 Метод throws

Предложение throws используется, чтобы указать проверяемые исключения (§11.2), которые могут быть результатом реализации метода или конструктора:

    Throws:
    
    	throws ClassTypeList
    
    ClassTypeList:
    
    	ClassType
    
    	ClassTypeList , ClassType
    

Ошибка времени компиляции происходит, если любой КлассовыйТип, упомянутый в предложении throws не является классом или подклассом Throwable (§20.22). Это разрешено, но не требуется, чтобы упомянуть другие (непроверяемые) исключения в методе throws.

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

Требование объявлять проверяемые исключения позволяет компилятору гарантировать,что код для обработки таких условий ошибки был включен. Методы или конструкторы, которые не в состоянии обрабатывать исключительные события, сгенерированные как проверяемые, исключения будут обычно заканчиваться ошибкой времени компиляции из-за отсутствия соответствующего типа исключения в предложении throws. Язык Ява таким образом поощряет cтиль программирования, где редко встречающиеся и в то же время верные исключительные события могут могут быть зарегистрированы.

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

  • Исключения, которые представлены подклассами класса Error, для примера OutOfMemoryError, сгенерированы из-за их отсутствия в виртуальной машине. Многие из них - являются результатом неудачного редактирования и поэтому могут происходить в непредсказуемых местах при выполнении программы на языке Ява. Сложные программы могут все же при желании отлавливать и пытаться избавляться от некоторых из этих условий.
  • Исключения, которые представлены подклассами класса RuntimeException, например NullPointerException, являются следствием контроля целостности во время выполнения и генерации как непосредственно самой программы на языке Ява так и библиотек стандартных программ. Такая проверка вне возможностей языка Ява и возможно на сегодняшний день нигде не реализуется, включая достаточную информацию в программу предварительно преобразовав к управляемому номеру места, где они заведомо не происходят.

Метод, который замещает или скрывает другой метод (§8.4.6), включая методы, реализующие abstract-методы, определенные в интерфейсах, может не быть объявлен генерируя более проверяемые исключения, чем перегруженный или скрытый метод.

Более корректно, предположим, что B - класс или интерфейс, и A - суперкласс или суперинтерфейс B, и объявления метода n в B перегружает или скрывает объявление метода m в A. Если n содержит throws и ссылается на любые проверяемые типы исключений, тогда m должен также содержать метод throws для каждого проверяемого типа исключений, внесенного в список в throws метода n, который схож с классом исключений или одним из суперклассов объявленных в предложении throws метода m; иначе происходит ошибка времени компиляции.

См. §11 для получения большей информации относительно исключений и больших примеров.

8.4.5 Tело метода

Тело метода является или блоком кода, который осуществляет метод или просто точка с запятой, указывающая на отсутствие реализации. Тело метода должно содержать точку с запятой тогда и только тогда, когда метод объявлен abstract (§8.4.3.1) или native (§8.4.3.4).

    MethodBody:
    
    	Block 
    
    	;
    

Ошибка времени компиляции происходит, если метод является abstract или native и имеет блок для тела. Ошибка времени компиляции также происходит, если метод объявлен ни как abstract и ни как native, но содержит точку с запятой для тела.

Если реализация должна быть предусмотрена методом, но она не требует никакого выполнимого кода, тело метода должно быть написано как блок, не содержащий операторы: " { } ".

Если метод объявлен void, тогда тело не должно содержать оператор return (§14.15), который имеет Выражение.

Если метод, при объявлении, имеет тип результата, тогда каждый оператор return (§14.15) в теле должен иметь Выражение. Ошибка времени компиляции происходит, если тело метода может заканчиваться нормально (§14.1). Другими словами, метод с типом результата должен возвращаться только используя оператор возврата, который обеспечивает возвращение значения; это не позволяется " снижение от конца тела."

Заметьте, что это возможно для метода, в котором объявлен тип возвращения, но в то же самое время не содержится никаких операторов возврата. Пример:

class DizzyDean {

	int pitch() { throw new RuntimeException("90 mph?!"); }

}

8.4.6 Наследование, замещение и сокрытие.

Класс наследует от прямых суперклассов и прямых суперинтерфейсов все методы (abstract или нет) этих суперклассов и суперинтерфейсов, которые доступны для кода класса и которые не перегружены и (§8.4.6.1) не скрыты (§8.4.6.2) в объявлении данного класса.

8.4.6.3 Требования к замещению и сокрытию.

Если объявление метода перегружает или скрывает объявление другого метода, тогда происходит ошибка времени компиляции, если они имеют различные типы возвращения или если один имеет тип возвращения, а другой не возвращает никаких данных (т.е. в его объявлении содержится слово "void"). Более того, объявление метода не должно содержать предложение throws, которое противоречит (§8.4.4) с этим методом, и которое его замещает или скрывает; иначе происходит ошибка времени компиляции. В этом аспекте замещение методов отличается от сокрытия полей (§8.3), для него допустимо для поля скрыть поле другого типа.

Модификатор доступа (§6.6) замещенного или скрытого метода должен обеспечивать максимальный доступ к замещенным или скрытым методам, иначе происходит ошибка времени компиляции. Более подробно:

  • Если замещаемый или скрываемый метод объявлен как public, тогда замещение или сокрытие метода должно быть public; иначе происходит ошибка времени компиляции.
  • Если замещемый или скрываемый метод объявлен как protected, тогда замещение или сокрытие метода должно быть protected или public; иначе происходит ошибка времени компиляции.
  • Если замещенный или скрытый метод имеет доступ по умолчанию (пакета), тогда замещение или сокрытие метода не должно быть private; иначе присходит ошибка времени компиляции.

Заметьте, что private-метод никогда не доступен для подклассов и поэтому не может быть скрытым или замещенным в техническом смысле этого понятия. Такой способ, что подкласс может объявлять метод с той же самой сигнатурой как private-метод в одном из суперклассов, и не существует ограничения, чтобы тип возвращения или метод throws такого метода имел какую-либо взаимосвязь с private-методами в суперклассе.

8.4.6.4 Наследование методов с той же самой сигнатурой.

Наследование методов разрешено для класса, чтобы унаследовать более одного метода с тот же самой сигнатурой (§8.4.6.4). Такая ситуация сама по себе не приводит к ошибкам времени компиляции. Однако существуют два возможных случая:

  • Если один из унаследованных методов - не abstract, тогда имеется два варианта:
  • Если метод, который - не является abstract - static, происходит ошибка времени компиляции.
  • Иначе, метод, который - не является abstract, рассматривается как замещенный, и поэтому реализует, все методы класса, который наследует его. Ошибка времени компиляции происходит при, сравнивании не abstract метода с каждым из унаследованных методов, для любой такой пары справедливо: либо они имеют различные типы возвращения, либо один имеет тип возвращения, а другой объявлен со словом "void". Кроме того, ошибка времени компиляции происходит, если унаследованный неabstract метод содержит метод throws, что нельзя (§8.4.4) для данного и любого другого из унаследованных методов.

  • Если ни один из унаследованных методов не является abstract, тогда класс обязательно должен быть abstract и учитывать все наследуемые abstract-методы. Ошибка времени компиляции происходит если, для любых двух таких унаследованных методов, причем либо они имеют различные типы возвращения, либо один имеет тип возвращения, а другой объявлен со словом "void". (Методы throws в этом случае не приводят к ошибкам.)

Это не возможно для двух или более унаследованных методов с одинаковой сигнатурой и не abstract, потому что методы, которые - не abstract наследуются только от прямого суперкласса, а не от суперинтерфейсов.

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

8.4.7 Перегрузка

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

Методы перегруженные на сигнатурно-сигнатурной основе. Если, например, класс объявляет два public-метода с одним и тем же именем, и подкласс перегружает один из них, то подкласс унаследует другой метод. В этом отношении язык Ява отличается от Cи ++.

Когда метод вызывается (§15.11), число фактических аргументов и аргументов типов времени компиляции используются, во время компиляции, для определения сигнатуры метода, который будет вызван (§15.11.2). Если метод, который должен быть вызван является методом экземпляра, то фактически вызываемый метод будет определен во время выполнения, используя динамический поиск метода (§15.11.4).

8.4.8 Примеры объявлений метода

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

8.4.8.1 Пример: замещение

В примере:


class Point {

	int x = 0, y = 0;


	void move(int dx, int dy) { x += dx; y += dy; }

}


class SlowPoint extends Point {

	int xLimit, yLimit;


	void move(int dx, int dy) {
		super.move(limit(dx, xLimit), limit(dy, yLimit));
	}


	static int limit(int d, int limit) {
		return d > limit ? limit : d < -limit ? -limit : d;
	}

}

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

8.4.8.2 Пример: Перегрузка, замещение и сокрытие

В примере:


class Point {

	int x = 0, y = 0;


	void move(int dx, int dy) { x += dx; y += dy; }


	int color;

}


class RealPoint extends Point {

	float x = 0.0f, y = 0.0f;


	void move(int dx, int dy) { move((float)dx, (float)dy); }


	void move(float dx, float dy) { x += dx; y += dy; }

}

класс RealPoint скрывает объявления int переменных экземпляра x и y класса Point с собственными float переменными экземпляра x и y, и замещает метод move класса Point с собственным методом move. Он также перегружает имя move другим методом с другой сигнатурой (§8.4.2).

В этом примере, элементы класса RealPoint включают экземпляр переменной color, унаследованные от класс Point float переменные экземпляра x и y, объявленны в RealPoint, и два метода move, объявленные в RealPoint.

Которыt из этих двух перегруженных методов move класса RealPoint будут выбраны для любого специфического вызова метода будет определено во время компиляции процедурой разрешения перегрузки, описанной в §15.11.

8.4.8.3 Пример: некорректного замещения

Этот пример - расширенный вариант фрагмента кода из предшествующего пункта:


class Point {

	int x = 0, y = 0, color;


	void move(int dx, int dy) { x += dx; y += dy; }


	int getX() { return x; }


	int getY() { return y; }

}


class RealPoint extends Point {

	float x = 0.0f, y = 0.0f;


	void move(int dx, int dy) { move((float)dx, (float)dy); }


	void move(float dx, float dy) { x += dx; y += dy; }


	float getX() { return x; }


	float getY() { return y; }

}

здесь класс Point обеспечивает методы getX и getY, которые возвращают значения из полей x и y; затем класс RealPoint замещает эти методы, объявляя их с той же самой сигнатурой. Результатом таких действий будут две ошибки времени компиляции, одна ошибка на каждый метод, потому что типы возвращения не соответствуют; методы в классе Point возвращают значения типа int, а замещенные методы в классе RealPoint возвращают значения типа float.

8.4.8.4 Пример: замещение против сокрытия

Этот пример исправляет ошибки примера в предшествующем пункте:


class Point {

	int x = 0, y = 0;


	void move(int dx, int dy) { x += dx; y += dy; }


	int getX() { return x; }


	int getY() { return y; }


	int color;

}


class RealPoint extends Point {

	float x = 0.0f, y = 0.0f;


	void move(int dx, int dy) { move((float)dx, (float)dy); }


	void move(float dx, float dy) { x += dx; y += dy; }


	int getX() { return (int)Math.floor(x); }


	int getY() { return (int)Math.floor(y); }

}

Здесь замещение методов getX и getY в классе RealPoint имеет тот же самый тип возвращения что и методы класса Point, который их перегружает, поэтому этот код может успешно компилироваться.

Рассмотрите, сейчас, этот фрагмент программы:


class Test {

	public static void main(String[] args) {
		RealPoint rp = new RealPoint();
		Point p = rp;
		rp.move(1.71828f, 4.14159f);
		p.move(1, -1);
		show(p.x, p.y);
		show(rp.x, rp.y);
		show(p.getX(), p.getY());
		show(rp.getX(), rp.getY());
	}


	static void show(int x, int y) {
		System.out.println("(" + x + ", " + y + ")");
	}


	static void show(float x, float y) {
		System.out.println("(" + x + ", " + y + ")");
	}

}

Эта программа напечатает:


(0, 0)
(2.7182798, 3.14159)
(2, 3)
(2, 3)

Первая строка результата работы программы иллюстрирует тот факт, что экземпляр RealPoint фактически содержит два целочисленных поля, объявленные в классе Point; эти поля и их имена скрыты от кода который осуществлен в пределах объявления класса RealPoint (и тех подклассов, которые могли бы его содержать). Когда ссылка на экземпляр класса RealPoint в переменной типа Point используется для доступа к полю x, целочисленное поля x объявленного в классе Point доступно. Тот факт, что значение является нолем, указывает на то, что вызов метода p.move (1, -1) не вызывал метод move класса Point; вместо этого вызывая перегруженный метод move класса RealPoint.

Вторая строка результатов показывает, что при доступе к полю rp.x происходит обращение к полю x, объявленному в классе RealPoint. Это поле имеет тип float, и эта вторая строка результатов соответственно показывает значения плавания - пункта. Кстати, это также иллюстрирует факт, что метод с именем show замещает; типы аргументов в вызове метода, указывая который из двух определений будет вызван.

Последние две строки результатов показывают, что при вызове методов p.getX () и Rp.getX () каждый из них вызывает метод getX, объявленный в классе RealPoint. Действительно, не существует способа вызвать метод getX класса Point для экземпляра класса RealPoint вне тела RealPoint, независимо от того что тип переменной мы можем использовать, чтобы содержать ссылку на объект. Таким образом, мы видим, что поля и методы ведут себя поразному: сокрытие отлично от замещения.

8.4.8.5 Пример: Вызов скрытых методов класса

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


class Super {
	static String greeting() { return "Goodnight"; }
	String name() { return "Richard"; }
}


class Sub extends Super {
	static String greeting() { return "Hello"; }
	String name() { return "Dick"; }
}


class Test {
	public static void main(String[] args) {
		Super s = new Sub();
		System.out.println(s.greeting() + ", " + s.name());
	}
}

печатает результат:

Goodnight, Dick

потому что вызов greeting использует тип s, именованный Super, вычисляя во время компиляции, какой метод класса вызван, принимая во внимание, что вызов name использует класс s, именованный Sub, вычисляя  во время работы, который метод экземпляра вызывать.

8.4.8.6 Большой пример замещения.

Замещение осуществляется легко для подклассов, чтобы расширить возможности существующего класса, как показано в этом примере:

import java.io.OutputStream;


import java.io.IOException;


class BufferOutput {

	private OutputStream o;


	BufferOutput(OutputStream o) { this.o = o; }


	protected byte[] buf = new byte[512];


	protected int pos = 0;


	public void putchar(char c) throws IOException {
		if (pos == buf.length)
			flush();
		buf[pos++] = (byte)c;
	}


	public void putstr(String s) throws IOException {
		for (int i = 0; i < s.length(); i++)
			putchar(s.charAt(i));
	}


	public void flush() throws IOException {
		o.write(buf, 0, pos);
		pos = 0;
	}

}


class LineBufferOutput extends BufferOutput {

	LineBufferOutput(OutputStream o) { super(o); }


	public void putchar(char c) throws IOException {
		super.putchar(c);
		if (c == '\n')
			flush();
	}

}


class Test {
	public static void main(String[] args)

		throws IOException

	{
		LineBufferOutput lbo =

			new LineBufferOutput(System.out);
		lbo.putstr("lbo\nlbo");
		System.out.print("print\n");
		lbo.putstr("\n");
	}
}

данный пример печатает:


lbo
print
lbo

Класс BufferOutput реализует очень простую версию OutputStream, сдвигая результат, когда буфер полон, или метод flush вызван. Подкласс LineBufferOutput объявляет только конструктор и единственный метод putchar, который замещает метод putchar в BufferOutput. Он наследует методы putstr и flush от класса Buffer.

В методе putchar объекта LineBufferOutput, если символьный аргумент новая строка, то он вызывает метод flush. Критический момент относительно замещения в этом примере - это то, что метод putstr, который объявлен в классе BufferOutput, вызывает метод putchar, определенный в текущем объекте с ключевым словом this, которое не обязательно при объявлении метода putchar в классе BufferOutput.

Таким образом, когда putstr вызывается в main используя объект LineBufferOutput lbo, вызов putchar в теле метода putstr - это вызов putchar объекта lbo, перегружающего объявление putchar, который проверяет для новой строки. Это позволяет подклассу BufferOutput изменять поведение метода putstr без того, чтобы повторно определить его.

Документация для класса типа BufferOutput, который предназначен, для расширения, должна ясно указывать на то, что - есть определенные соглашения между классом и подклассами, и должно ясно указать, на то что подклассы могут таким образом замещать метод putchar. Средство реализации BufferOutput класса не, поэтому, хотело бы изменить выполнение putstr в будущем выполнении BufferOutput, чтобы не использовать метод putchar, потому что это будет нарушать ранее определенные соглашения с подклассами. См. дальнейшее обсуждение двоичной совместимости в §13, и особенно в §13.2.

8.4.8.7 Пример: Некорректное замещение при использовании Trows.

Этот пример использует обычную стандартную форму для объявления нового типа исключения  в объявлении класса BadPointException:


class BadPointException extends Exception {
	BadPointException() { super(); }
	BadPointException(String s) { super(s); }
}

class Point {
	int x, y;
	void move(int dx, int dy) { x += dx; y += dy; }
}


class CheckedPoint extends Point {
	void move(int dx, int dy) throws BadPointException {
		if ((x + dx) < 0 || (y + dy) < 0)
			throw new BadPointException();
		x += dx; y += dy;
	}
}

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

Перенос предложения throws не помогает:


class CheckedPoint extends Point {
	void move(int dx, int dy) {
		if ((x + dx) < 0 || (y + dy) < 0)
			throw new BadPointException();
		x += dx; y += dy;
	}
}

Сейчас происходит другая ошибка времени компиляции, потому что тело метода move не может генерировать проверяемое исключение, а именно BadPointException, которое не объявлено в предложении throws метода move.

8.5 Статические инициализаторы

Любые статические инициализаторы, объявленные в классе, реализуются, когда класс инициализируется и для переменных класса вместе с некоторым полем инициализаторов (§8.3.2) , может использоваться, чтобы инициализировать переменные класса (§12.4).

    StaticInitializer:
    
    	static Block
    

Ошибка времени компиляции происходит, если статический инициализатор завершаться преждевременно (§14.1, §15.5) с проверенным исключением (§11.2).

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


class Z {
	static int i = j + 2; 
	static int j = 4;
}

и:


class Z {
	static { i = j + 2; }
	static int i, j;
	static { j = 4; }
}

результатом будет ошибка времени компиляции.

Доступы c помощью методов к переменным класса в следующем коде не регистрируются , итак:


class Z {
	static int peek() { return j; }

	static int i = peek();
	static int j = 1;
}


class Test {
	public static void main(String[] args) {
		System.out.println(Z.i);
	}

}

печатает результат:

0

поскольку переменная инициализации для i использует классовый метод peek для доступа к значению переменной j прежде, чем j будет инициализирована переменной инициализации, в которой точке это все еще имеет значение присваиваемое по умолчанию (§ 4.5.4).

Если оператор return (§14.15) появляется где-нибудь в пределах статического инициализатора, то ошибка времени компиляции происходит.

Если ключевое слово this (§15.7.2) или super (§15.10, §15.11) появляется где-нибудь в пределах статического инициализатора, то ошибка времени компиляции происходит.

8.6 Объявление конструктора

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

    ConstructorDeclaration:
    
    	ConstructorModifiersopt ConstructorDeclarator
    
    		Throwsopt ConstructorBody
    
    ConstructorDeclarator:
    
    	SimpleTypeName ( FormalParameterListopt )
    

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

Простой пример:


class Point {
	int x, y;
	Point(int x, int y) { this.x = x; this.y = y; }
}

Конструкторы вызваные классовыми выражениями создания экземпляра (§15.8), методом NewInstance класса Class (§20.3), преобразованиями и операцией конкатенации строк + (§15.17.1), и явными вызовами конструкторов из других конструкторов (§8.6.5).Конструкторы никогда не вызываются методом вызова выражения (§15.11).

Доступ к конструкторам управляется модификаторами доступа (§6.6). Это очень полезно, например, для предотвращения обработки объявления недоступного конструктора (§8.6.8).

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

8.6.1 Формальные параметры

Формальные параметры конструктора идентичны по своей структуре и характеру формальным параметрам метода (§8.4.1).

8.6.2 Сигнатура конструктора

Сигнатура конструктора идентична по своей структуре и характеру сигнатуре метода (§8.4.2).

8.6.3 Модификаторы конструктора

    ConstructorModifiers:
    
    	ConstructorModifier
    
    	ConstructorModifiers ConstructorModifier
    
    ConstructorModifier: one of
    
    	public protected private
    

Модификаторы доступа public, protected, private рассмотренны в §6.6. Ошибка времени компиляции происходит, если один и тот же модификатор появляется больше, чем однажды в объявлении конструктора, или если объявление конструктора имеет более одного из модификаторов доступа public, protected, private.

В отличие от методов, конструктор не может быть abstract, static, final, native или synchronized. Конструктор не наследуется, поэтому нет никакой потребности в объявлении его как final или abstract, конструктор никогда не может быть реализован. Конструктор всегда вызывается относительно объекта, поэтому нет смысла для конструктора быть static. Не существует никакой практической потребности для конструктора быть synchronized, потому что это заперло бы объект для конструирования, которое обычно не доступно другим потокам до тех пор пока все конструкторы объекта не закончили свою работу. Недостаток native-конструкторов - произвольный выбор проекта языка, который делает его легким для выполнения виртуальной машиной языка Ява чтобы проверить, что суперкласс конструкторов всегда должным образом вызывается в течение создания объекта.

8.6.4 Конструктор Throws

Предложение throws для конструктора идентичен структуре и поведению предложения throws для метода (§8.4.4).

8.6.5 Тело конструктора

Первый оператор тела конструктора может быть явным вызовом другого конструктора того же самого класса, записанных как this сопровождаемое списком аргументов заключенным в скобки, или явным вызовом конструктора прямого суперкласса, записанных как super сопровождаемое cписоком аргументов заключенным в скобки.

    ConstructorBody:
    
    	{ ExplicitConstructorInvocationopt BlockStatementsopt }
    
    ExplicitConstructorInvocation:
    
    	this ( ArgumentListopt ) ;
    
    	super ( ArgumentListopt ) ;
    

Ошибка времени-компиляции для конструктора прямо или косвенно вызывающего себя через ряд одного или более явных вызовов конструктора содержащих слово this.

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

Кроме возможности явного вызова конструктора, тело конструктора - подобно телу метода (§8.4.5). В теле конструктора может использоваться оператор return (§14.15), если он не содержит выражение.

В примере:


class Point {

	int x, y;


	Point(int x, int y) { this.x = x; this.y = y; }

}


class ColoredPoint extends Point {

	static final int WHITE = 0, BLACK = 1;


	int color;


	ColoredPoint(int x, int y) {
		this(x, y, WHITE);
	}


	ColoredPoint(int x, int y, int color) {
		super(x, y);

		this.color = color;

	}

}

первый конструктор ColoredPoint вызывает второй, задавая дополнительный аргумент; второй конструктор ColoredPoint вызывает конструктор из его суперкласса Point, передавая координаты.

Явный оператор вызова конструктора не может обратиться к каким-нибудь переменным экземпляра или методам экземпляра, объявленным в этом классе или каком-нибудь суперклассе, или использовать слова this или super в каком-нибудь выражении; иначе, происходит ошибка компиляции. Например, если первый конструктор ColoredPoint в примере раньше изменен:


	ColoredPoint(int x, int y) {
		this(x, y, color);
	}

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

Вызов конструктора непосредственного суперкласса, это либо фактическое появление явного оператора вызова конструктора либо обеспечивается автоматически (§8.6.7), исполняя дополнительное неявное действие после нормального возврата управления из конструктора: все переменные экземпляра, которые имеют инициализаторы инициализируются в это время, в текстовой команде, в которой они появляются в описании класса. Вызов другого конструктора в том же самом классе с использованием ключевого слова this не исполняет этого дополнительного неявного действия.

В §12.5 описывается создание и инициализация новых экземпляров класса.

8.6.6 Перегрузка конструктора

Перегрузка конструкторов идентична перегрузке методов. Перегрузка осуществляется во время компиляции, каждым экземпляром класса создающим выражение (§15.8).

8.6.7 Конструктор по умолчанию

Если класс не содержит описание конструктора, тогда конструктор по умолчанию не берет никакие параметры, автоматически предусматривая их:

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

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

Если класс объявлен как public, тогда конструктор по умолчанию неявно приобретает модификатор доступа public (§6.6); иначе, конструктор по умолчанию имеет доступ по умолчанию заключающий в себе доступ модификатора. Таким образом, пример:


public class Point {
	int x, y;
}

является эквивалентным объявлению:


public class Point {
	int x, y;
	public Point() { super(); }
}

где конструктор по умолчанию является public, потому что класс Point есть public.

8.6.8 Предотвращение создания экземпляров класса

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

Таким образом, в примере:


class ClassOnly {
	private ClassOnly() { }
	static String just = "only the lonely";
}

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

package just;


public class PackageOnly {
	PackageOnly() { }
	String[] justDesserts = { "cheesecake", "ice cream" };
}

класс PackageOnly может быть подвержен обработке только в пределах пакета just, в котором он объявлен.


[ Назад | Оглавление | Далее ]










helloworld.ru © 2001-2021
Все права защищены