Содержание | Предыдущий | Следующий | Индекс Спецификация языка Java
Второй Выпуск


ГЛАВА 8

Классы


Объявления класса определяют новые ссылочные типы и описывают, как они реализуются (§8.1).

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

Эта глава обсуждает общую семантику всего верхнего уровня классов (§7.6) и вложенный (включая задействованные классы (§8.5, §9.5), локальные классы (§14.3) и анонимные классы (§15.9.5)). Детали, которые являются определенными для определенных видов классов, обсуждаются в разделах, выделенных этим конструкциям.

Именованный класс может быть объявлен abstract (§8.1.1.1) и должен быть объявлен abstract если это не полностью реализуется; такой класс нельзя инстанцировать, но может быть расширен подклассами. Класс может быть объявлен final (§8.1.1.2), когда у этого не может быть подклассов. Если класс объявляется public, тогда это может быть упомянуто от других пакетов.

Каждый класс кроме Object расширение (то есть, подкласс) единственный существующий класс (§8.1.3) и может реализовать интерфейсы (§8.1.4).

Тело класса объявляет элементы (поля и методы и вложенные классы и интерфейсы), экземпляр и статические инициализаторы, и конструкторы (§8.1.5). Контекст (§6.3) элемента (§8.2) является всем объявлением класса, которому принадлежит элемент. Поле, метод, задействованный класс, задействованный интерфейс, и объявления конструктора могут включать модификаторы доступа (§6.6) public, protected, или private. Элементы класса включают и объявленный и наследованные элементы (§8.2). Недавно объявленные поля могут скрыть поля, объявленные в суперклассе или суперинтерфейсе. Недавно объявленные элементы класса и интерфейсные элементы могут скрыть класс или соединить интерфейсом с элементами, объявленными в суперклассе или суперинтерфейсе. Недавно объявленные методы могут скрыть, реализовать, или переопределить методы, объявленные в суперклассе или суперинтерфейсе.

Полевые объявления (§8.3) описывают переменные класса, которые воплощаются однажды, и переменные экземпляра, которые недавно воплощаются для каждого экземпляра класса. Поле может быть объявлено final (§8.3.1.2), когда это может быть присвоено только однажды. Любое полевое объявление может включать инициализатор.

Задействованные объявления класса (§8.5) описывают вложенные классы, которые являются элементами окружающего класса. Задействованные классы могут быть статичными, когда у них нет никакого доступа к переменным экземпляра окружающего класса; или они могут быть внутренними классами (§8.1.2).

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

Объявления метода (§8.4) описывают код, который может быть вызван выражениями вызова метода (§15.12). Метод класса вызывается относительно типа класса; метод экземпляра вызывается относительно некоторого определенного объекта, который является экземпляром типа класса. Метод, объявление которого не указывает, как это реализуется, должен быть объявлен abstract. Метод может быть объявлен final (§8.4.3.3), когда это не может быть скрыто или переопределено. Метод может быть реализован зависимым от платформы native код (§8.4.3.4). A synchronized метод (§8.4.3.6) автоматически блокирует объект прежде, чем выполнить его тело и автоматически разблокировал объект по возврату, как будто при помощи a synchronized оператор (§14.18), таким образом позволяя его действия синхронизироваться с таковыми из других потоков (§17).

Имена методов могут быть перегружены (§8.4.7).

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

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

Конструкторы (§8.8) подобны методам, но не могут быть вызваны непосредственно вызовом метода; они используются, чтобы инициализировать новые экземпляры класса. Как методы, они могут быть перегружены (§8.8.6).

8.1 Объявление класса

Объявление класса определяет новый именованный ссылочный тип:

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

8.1.1 Модификаторы класса

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

Не все модификаторы применимы ко всем видам объявлений класса. Общественность модификатора доступа принадлежит только высокоуровневым классам (§7.6) и задействованным классам (§8.5, §9.5), и обсуждается в §6.6, §8.5 и §9.5. Модификаторы доступа, защищенные и частные, принадлежат только задействованным классам в пределах непосредственно объявления класса включения (§8.5) и обсуждаются в §8.5.1. Статичный модификатор доступа принадлежит только задействованным классам (§8.5, §9.5). Ошибка времени компиляции происходит, если тот же самый модификатор появляется не раз в объявлении класса.

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

8.1.1.1 abstract Классы

abstract класс является классом, который является неполным, или считаться неполным. Только abstract классы могут иметь abstract методы (§8.4.3.1, §9.4), то есть, методы, которые объявляются, но еще не реализуются. Если класс, который не является abstract содержит abstract метод, затем ошибка времени компиляции происходит. Класс C имеет abstract методы, если какое-либо следующее является истиной:

В примере:
abstract class Point {
	int x = 1, y = 1;
	void move(int dx, int dy) {
		x += dx;
		y += dy;
		alert();
	}
	abstract void alert();
}
abstract class ColoredPoint extends Point {
	int color;
}
class SimplePoint extends Point {
	void alert() { }
}
класс Point объявляется, который должен быть объявлен abstract, потому что это содержит объявление abstract метод называют alert. Подкласс Point именованный ColoredPoint наследовался abstract метод alert, таким образом, это должно также быть объявлено abstract. С другой стороны, подкласс Point именованный SimplePoint обеспечивает реализацию alert, таким образом, это не должно быть abstract.

Ошибка времени компиляции происходит, если попытка предпринимается, чтобы создать экземпляр abstract класс используя выражение создания экземпляра класса (§15.9).

Таким образом, продолжая пример, только показанный, оператор:

	Point p = new Point();
привел бы к ошибке времени компиляции; класс Point не может быть инстанцирован, потому что это abstract. Однако, a Point переменная могла правильно быть инициализирована со ссылкой на любой подкласс Point, и класс SimplePoint не abstract, так оператор:

	Point p = new SimplePoint();
было бы корректно.

Подкласс abstract класс, который не является самостоятельно abstract может быть инстанцирован, приводя к выполнению конструктора для abstract класс и, поэтому, выполнение полевых инициализаторов например переменные того класса. Таким образом, в примере, только данном, инстанцирование a SimplePoint вызывает конструктора по умолчанию и полевые инициализаторы для x и y из Point выполняться. Это - ошибка времени компиляции, чтобы объявить abstract тип класса так, что, не возможно создать подкласс, который реализует весь abstract методы. Эта ситуация может произойти, если класс имел бы как элементы два abstract методы, у которых есть та же самая сигнатура метода (§8.4.2), но различные типы возврата.

Как пример, объявления:

interface Colorable { void setColor(int color); }
abstract class Colored implements Colorable {
	abstract int setColor(int color);
}
результат в ошибке времени компиляции: это было бы невозможно для любого подкласса класса Colored обеспечить реализацию названного метода setColor, взятие одного параметра типа int, это может удовлетворить обоих abstract спецификации метода, потому что тот в интерфейсе Colorable требует, чтобы тот же самый метод не возвратил значения, в то время как тот в классе Colored требует, чтобы тот же самый метод возвратил значение типа int (§8.4).

Тип класса должен быть объявлен abstract только если намерение состоит в том, что подклассы могут быть созданы, чтобы завершить реализацию. Если намерение состоит в том, чтобы просто предотвратить инстанцирование класса, надлежащий способ выразить, это должно объявить конструктора (§8.8.8) никаких параметров, сделать это private, никогда не вызывайте это, и не объявляйте никаких других конструкторов. Класс этой формы обычно содержит методы класса и переменные. Класс Math пример класса, который нельзя инстанцировать; его объявление похоже на это:

public final class Math {
	private Math() { }		// never instantiate this class
	. . . declarations of class variables and methods . . .

}

8.1.1.2 final Классы

Класс может быть объявлен final если его определение полно, и никакие подклассы не требуются или требуются. Ошибка времени компиляции происходит если имя a final класс появляется в extends пункт (§8.1.3) другого class объявление; это подразумевает это a final у класса не может быть никаких подклассов. Ошибка времени компиляции происходит, если класс объявляется обоими final и abstract, потому что реализация такого класса никогда не могла завершаться (§8.1.1.1).

Поскольку a final у класса никогда нет подклассов, методов a final класс никогда не переопределяется (§8.4.6.1).

8.1.1.3 strictfp Классы

Эффект strictfp модификатор должен сделать все float или double выражения в пределах объявления класса быть явно строгим FP (§15.4). Это подразумевает, что все методы, объявленные в классе, и все вложенные типы, объявленные в классе, неявно strictfp.

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

8.1.2 Внутренние Классы и Экземпляры Включения

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

Чтобы иллюстрировать эти правила, рассмотрите пример ниже:

class HasStatic{
	static int j = 100;
}
class Outer{
	class Inner extends HasStatic{
		static final x = 3;		// ok - compile-time constant
		static int y = 4; 		// compile-time error, an inner class
	}
	static class NestedButNotInner{
		static int z = 5; 		// ok, not an inner class
	}
	interface NeverInner{}		// interfaces are never inner
}
Внутренние классы могут наследовать статические элементы, которые не являются константами времени компиляции даже при том, что они, возможно, не объявляют их. Вложенные классы, которые не являются внутренними классами, могут объявить статические элементы свободно, в соответствии с обычными правилами языка программирования Java. Задействованные интерфейсы (§8.5) всегда неявно статичны, таким образом, они, как никогда полагают, не являются внутренними классами.

Оператор или выражение происходят в статическом контексте, если и только если самый внутренний метод, конструктор, инициализатор экземпляра, статический инициализатор, полевой инициализатор, или явный оператор конструктора, включающий оператор или выражение, являются статическим методом, статическим инициализатором, переменным инициализатором статической переменной, или явным оператором вызова конструктора (§8.8.5).

Внутренний класс C является прямым внутренним классом класса O, если O является сразу лексически включающим классом C, и объявление C не происходит в статическом контексте. Класс C является внутренним классом класса O, если это - или прямой внутренний класс O или внутренний класс внутреннего класса O.

Класс O является нулевым лексически класс включения себя. Класс O является энным лексически класс включения класса C, если это - сразу класс включения n - 1-ый лексически класс включения C.

Экземпляр i из прямого внутреннего класса C класса O связывается с экземпляром O, известного как сразу экземпляр включения меня. Сразу экземпляр включения объекта, если таковые вообще имеются, определяется, когда объект создается (§15.9.2).

Объект o является нулевым лексически экземпляр включения себя. Объект o является энным лексически экземпляр включения экземпляра i, если это - сразу экземпляр включения n - 1-ый лексически экземпляр включения меня.

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

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

Кроме того, для каждого суперкласса S C, который является самостоятельно прямым внутренним классом класса Так, есть экземпляр НАСТОЛЬКО связанного со мной, известен как сразу экземпляр включения меня относительно S. Сразу экземпляр включения объекта относительно прямого суперкласса его класса, если таковые вообще имеются, определяется, когда конструктор суперкласса вызывается через явный оператор вызова конструктора.

Любая локальная переменная, формальный параметр метода или используемый параметр обработчика исключений, но не объявленный во внутреннем классе должны быть объявлены final, и должен быть определенно присвоен (§16) перед телом внутреннего класса.

Внутренние классы включают локальный (§14.3), анонимный (§15.9.5) и нестатические задействованные классы (§8.5). Вот некоторые примеры:

class Outer {
	int i = 100;
	static void classMethod() {
		final int l = 200;
		class LocalInStaticContext{
			int k = i; // compile-time error
			int m = l; // ok
		}
	}
	void foo() {
		class Local { // a local class
			int j = i;
		}
	}
}
Объявление класса LocalInStaticContext происходит в статическом контексте - в пределах статического метода classMethod. Переменные экземпляра класса Outer не доступны в пределах тела статического метода. В частности переменные экземпляра Outer не доступны в теле LocalInStaticContext. Однако, локальные переменные от окружающего метода могут быть упомянуты без ошибки (если они отмечаются final).

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

class WithDeepNesting{
	boolean toBe;
	WithDeepNesting(boolean b) { toBe = b;}
	class Nested {
		boolean theQuestion;
		class DeeplyNested {
			DeeplyNested(){
				theQuestion = toBe || !toBe;
			}
		}
	}
}
Здесь, каждый экземпляр WithDeepNesting.Nested.DeeplyNested имеет экземпляр включения класса WithDeepNesting.Nested (его сразу экземпляр включения) и экземпляр включения класса WithDeepNesting (его 2-ое лексически экземпляр включения).

8.1.3 Суперклассы и Подклассы

Дополнительное extends пункт в объявлении класса определяет прямой суперкласс текущего класса. Класс, как говорят, является прямым подклассом класса, который он расширяет. Прямой суперкласс является классом, из реализации которого получается реализация текущего класса. extends пункт не должен появиться в определении класса Object, потому что это - исконный класс и не имеет никакого прямого суперкласса. Если объявление класса для какого-либо другого класса имеет нет extends пункт, тогда у класса есть класс Object как его неявный прямой суперкласс.

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

ClassType должен назвать доступный (§6.6) тип класса, или ошибка времени компиляции происходит. Если указанный ClassType называет класс, который является final (§8.1.1.2), затем ошибка времени компиляции происходит; final классам не позволяют иметь подклассы.

В примере:

class Point { int x, y; }
final class ColoredPoint extends Point { int color; }
class Colored3DPoint extends ColoredPoint { int z; } // error
отношения следующие:

Объявление класса Colored3dPoint вызывает ошибку времени компиляции, потому что она пытается расшириться final класс ColoredPoint.

Отношение подкласса является переходным закрытием прямого отношения подкласса. Класс A является подклассом класса C, если любое из следующего является истиной:

Класс C, как говорят, является суперклассом класса A всякий раз, когда A является подклассом C.

В примере:

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
final class Colored3dPoint extends ColoredPoint { int z; }
отношения следующие:

Класс C непосредственно зависит от типа T, если T упоминается в extends или implements пункт C или как суперкласс или как суперинтерфейс, или как спецификатор в пределах суперкласса или суперинтерфейсного имени. Класс C зависит от ссылочного типа T, если какое-либо из следующих условий содержит:

Это - ошибка времени компиляции, если класс зависит от себя.

Например:

class Point extends ColoredPoint { int x, y; }
class ColoredPoint extends Point { int color; }
вызывает ошибку времени компиляции.

Если циркулярные объявленные классы обнаруживаются во время выполнения, поскольку классы загружаются (§12.2), то a ClassCircularityError бросается.

8.1.4 Суперинтерфейсы

Дополнительное implements пункт в объявлении класса перечисляет имена интерфейсов, которые являются прямыми суперинтерфейсами объявляемого класса:

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

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

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

Это - истина, даже если интерфейс называют по-разному; например, код:

class Redundant implements java.lang.Cloneable, Cloneable {
	int x;
}
результаты в ошибке времени компиляции, потому что имена java.lang.Cloneable и Cloneable обратитесь к тому же самому интерфейсу.

Интерфейсный тип я - суперинтерфейс типа класса C, если какое-либо следующее является истиной:

Класс, как говорят, реализует все свои суперинтерфейсы.

В примере:

public interface Colorable {
	void setColor(int color);
	int getColor();
}
public interface Paintable extends Colorable {
	int MATTE = 0, GLOSSY = 1;
	void setFinish(int finish);
	int getFinish();
}
class Point { int x, y; }
class ColoredPoint extends Point implements Colorable {
	int color;
	public void setColor(int color) { this.color = color; }
	public int getColor() { return color; }
}
class PaintedPoint extends ColoredPoint implements Paintable 
{
	int finish;
	public void setFinish(int finish) {
		this.finish = finish;
	}
	public int getFinish() { return finish; }
}
отношения следующие:

У класса может быть суперинтерфейс больше чем одним способом. В этом примере, классе PaintedPoint имеет Colorable как суперинтерфейс оба, потому что это - суперинтерфейс ColoredPoint и потому что это - суперинтерфейс Paintable.

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

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

interface Colorable {
	void setColor(int color);
	int getColor();
}
class Point { int x, y; };
class ColoredPoint extends Point implements Colorable {
	int color;
}
вызывает ошибку времени компиляции, потому что ColoredPoint не abstract класс, но это не в состоянии обеспечить реализацию методов setColor и getColor из интерфейса Colorable.

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

interface Fish { int getNumberOfScales(); }
interface Piano { int getNumberOfScales(); }
class Tuna implements Fish, Piano {
	// You can tune a piano, but can you tuna fish?
	int getNumberOfScales() { return 91; }
}
метод getNumberOfScales в классе Tuna имеет имя, подпись, и тип возврата, который соответствует метод, объявленный в интерфейсе Fish и также соответствует метод, объявленный в интерфейсе Piano; это, как полагают, реализует обоих.

С другой стороны, в ситуации, такой как это:

interface Fish { int getNumberOfScales(); }
interface StringBass { double getNumberOfScales(); }
class Bass implements Fish, StringBass {
	// This declaration cannot be correct, no matter what type is used.
	public ??? getNumberOfScales() { return 91; }
}
Невозможно объявить названный метод getNumberOfScales с той же самой подписью и возвратом вводят как таковые из обоих методы, объявленные в интерфейсе Fish и в интерфейсе StringBass, потому что у класса может быть только один метод с данной подписью (§8.4). Поэтому, для единого класса невозможно реализовать оба интерфейса Fish и интерфейс StringBass (§8.4.6).

8.1.5 Тело класса и Задействованные Объявления

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

Контекст объявления элемента м. объявил в или наследовался типом класса C, все тело C, включая любые объявления вложенного типа.

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

8.2 Элементы класса

Элементы типа класса являются всем следующим:

Элементы класса, которые объявляются 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(); }		// error
}
class Test {
	public static void main(String[] args) {
		ColoredPoint c = new ColoredPoint(0, 0);	// error
		c.reset();				// error
	}
}
причины четыре ошибки времени компиляции:

	ColoredPoint() { super(); }
который вызывает конструктора, без параметров, для прямого суперкласса класса ColoredPoint. Ошибка состоит в том что конструктор для Point это не берет параметров, private, и поэтому не доступно вне класса Point, даже через вызов конструктора суперкласса (§8.8.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; // compile-time errors
	}
}
Здесь оба класса в 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 Наследование с общественностью и защищенный

Учитывая класс 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 Наследование с частным

В примере:

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, экземпляры класса могли бы быть доступными во время выполнения, чтобы кодировать вне пакета, в котором объявляется, есть ли у этого a public суперкласс или суперинтерфейс. Экземпляр класса может быть присвоен переменной такого public ввести. Вызов a 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 void move(int dx, int dy) {
		move(dx, dy, 0);
	}
}
public class OnePoint {
	public static points.Point getOne() { 
		return new Point3d(); 
	}
}
Вызов morePoints.OnePoint.getOne() во все же третьем пакете возвратил бы a Point3d это может использоваться в качестве a Point, даже при том, что тип Point3d не доступно вне пакета morePoints. Две версии параметра метода move мог тогда быть вызван для того объекта, который допустим потому что метод move из Point3d public (поскольку это должно быть для любого метода, который переопределяет a 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, a 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 Полевые Объявления

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

FieldModifiers описываются в §8.3.1. Идентификатор в FieldDeclarator может использоваться на имя, чтобы обратиться к полю. Поля являются элементами; контекст (§6.3) полевого объявления определяется в §8.1.5. Больше чем одно поле может быть объявлено в единственном полевом объявлении при использовании больше чем одного оператора объявления; FieldModifiers и Тип применяются ко всем операторам объявления в объявлении. Объявления переменной, включающие типы массива, обсуждаются в §10.2.

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

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

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

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

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

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

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

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

Значение сохранено в поле типа float всегда элемент набора значений плавающего (§4.2.3); точно так же значение сохранено в поле типа double всегда элемент двойного набора значений. Это не разрешается для поля типа float чтобы содержать элемент набора значений "пускают в ход расширенную экспоненту", которая не является также элементом набора значений плавающего, ни для поля типа double чтобы содержать элемент набора значений "удваивают расширенную экспоненту", которая не является также элементом двойного набора значений.

8.3.1 Полевые Модификаторы

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

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

8.3.1.1 static Поля

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

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

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.11), как в p.origin и q.origin. Эти два способа получить доступ origin переменная класса получает доступ к тому же самому объекту, свидетельствуемому фактом что значение ссылочного выражения равенства (§15.21.3):

q.origin==Point.origin
true. Новые доказательства то, что приращение:

p.origin.useCount++;
вызывает значение q.origin.useCount быть 1; это так потому что p.origin и q.origin обратитесь к той же самой переменной.

8.3.1.2 final Поля

Поле может быть объявлено final (§4.5.4). И класс и переменные экземпляра (static и не -static поля), может быть объявлен final.

Это - ошибка времени компиляции, если пустой финал (§4.5.4) переменная класса определенно не присваивается (§16.7) статическим инициализатором (§8.7) класса, в котором это объявляется.

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

8.3.1.3 transient Поля

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

Если экземпляр класса Point:

class Point {
	int x, y;
	transient float rho, theta;
}
были сохранены к персистентному хранению системной службой, тогда только поля x и y был бы сохранен. Эта спецификация не определяет детали таких служб; см. спецификацию java.io. Сериализуемый для примера такой службы.

8.3.1.4 volatile Поля

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

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

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

Если в следующем примере один поток неоднократно вызывает метод one (но не больше, чем Integer.MAX_VALUE времена всего), и другой поток неоднократно вызывает метод 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.6):

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 происходите точно так много раз, и в точно том же самом порядке, как они, кажется, происходят во время выполнения текста программы каждым потоком. Поэтому, совместно используемое значение для j никогда не больше чем это для i, потому что каждое обновление к i должен быть отражен в совместно используемом значении для i перед обновлением к j происходит. Возможно, однако, что любой данный вызов метода two мог бы наблюдать значение для j это намного больше чем значение, наблюдаемое для i, потому что метод one мог бы быть выполнен много раз между моментом когда метод two выбирает значение i и момент, когда метод two выбирает значение j. См. §17 для большего количества обсуждения и примеров.

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

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

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

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.4), где инициализатор оценивается и присвоение, выполняемое каждый раз, когда оператор объявления локальной переменной выполняется.

Это - ошибка времени компиляции если оценка переменного инициализатора для a static поле или для переменной экземпляра именованного класса (или интерфейса) может завершиться резко с проверенным исключением (§11.2).

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

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

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

Одна тонкость здесь то, что во время выполнения, static переменные, которые являются final и это инициализируется с постоянными величинами времени компиляции, инициализируются сначала. Это также применяется к таким полям в интерфейсах (§9.3.1). Эти переменные являются "константами", у которых, как никогда будут наблюдать, не будет их начальных значений по умолчанию (§4.5.5), даже окольными программами. См. §12.4.2 и §13.4.8 для большего количества обсуждения. Использование переменных класса, объявления которых появляются дословно после использования, иногда ограничивается, даже при том, что эти переменные класса находятся в контексте. См. §8.3.2.3 для точных правил, управляющих ссылкой вперед на переменные класса.

8.3.2.2 Инициализаторы например Переменные

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

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

class Test {
	float f = j;
	static int j = 1;
}
компиляции без ошибки; это инициализирует j к 1 когда класс Test инициализируется, и инициализирует f к текущей стоимости j каждый раз экземпляр класса Test создается.

Выражениям инициализации например переменные разрешают обратиться к текущему объекту this (§15.8.3) и использовать ключевое слово super (§15.11.2, §15.12).

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

8.3.2.3 Ограничения на использование Полей во время Инициализации

Объявление элемента должно появиться прежде, чем оно будет использоваться, только если элемент является экземпляром (соответственно static) поле класса или интерфейса C и всех следующих условий содержит:

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

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

	class Test {
		int i = j;	// compile-time error: incorrect forward reference
		int j = 1;
	}
тогда как следующий пример компилирует без ошибки:

	class Test {
		Test() { k = 2; }
		int j = 1;
		int i = j;
		int k;
	}
даже при том, что конструктор (§8.8) для Test обращается к полю k это объявляется тремя строками позже.

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

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

class Z {
	static { i = j + 2; }
	static int i, j;
	static { j = 4; }
}
результат в ошибках времени компиляции. Доступы методами не проверяются таким образом, таким образом:

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.5).

Более тщательно продуманный пример:

class UseBeforeDeclaration {
	static {
		x = 100; // ok - assignment
		int y = x + 1; // error - read before declaration
		int v = x = 3; // ok - x at left hand side of assignment
		int z = UseBeforeDeclaration.x * 2;
	// ok - not accessed via simple name
		Object o = new Object(){ 
			void foo(){x++;} // ok - occurs in a different class
			{x++;} // ok - occurs in a different class
    		};
  }
	{
		j = 200; // ok - assignment
		j = j + 1; // error - right hand side reads before declaration
		int k = j = j + 1; 
		int n = j = 300; // ok - j at left hand side of assignment
		int h = j++; // error - read before declaration
		int l = this.j * 3; // ok - not accessed via simple name
		Object o = new Object(){ 
			void foo(){j++;} // ok - occurs in a different class
			{ j = j + 1;} // ok - occurs in a different class
		};
	}
	int w = x= 3; // ok - x at left hand side of assignment
	int p = x; // ok - instance initializers may access static fields
	static int u = (new Object(){int bar(){return x;}}).bar();
	// ok - occurs in a different class
	static int x;
	int m = j = 4; // ok - j at left hand side of assignment
	int o = (new Object(){int bar(){return j;}}).bar(); 
	// ok - occurs in a different class
	int j;
}

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 static, как 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 доступы a float значение, переменная экземпляра объявляется в классе Test, потому что тип переменной выборки 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.11.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 Объявления метода

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

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

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

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

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

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

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

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

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

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

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

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

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

Контекст параметра метода (§8.4.1) или конструктор (§8.8.1) является всем телом метода или конструктора.

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

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

Метод или параметр конструктора типа float всегда содержит элемент набора значений плавающего (§4.2.3); точно так же метод или параметр конструктора типа double всегда содержит элемент двойного набора значений. Это не разрешается для метода или параметра конструктора типа float чтобы содержать элемент набора значений "пускают в ход расширенную экспоненту", которая не является также элементом набора значений плавающего, ни для параметра метода типа double чтобы содержать элемент набора значений "удваивают расширенную экспоненту", которая не является также элементом двойного набора значений.

Где фактическое выражение параметра, соответствующее переменной параметра, не строго FP (§15.4), оценке того фактического выражения параметра разрешают использовать промежуточные значения, оттянутые из соответствующих наборов значений расширенной экспоненты. До того, чтобы быть сохраненным в переменной параметра результат такого выражения отображается на самое близкое значение в соответствующем стандартном наборе значений преобразованием вызова метода (§5.3).

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 Модификаторы метода

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

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

8.4.3.1 abstract Методы

abstract объявление метода представляет метод, поскольку элемент, обеспечивая его подпись (имя и номер и тип параметров), возвращает тип, и throws пункт (если кто-либо), но не обеспечивает реализацию. Объявление abstract метод м. должен появиться непосредственно в пределах abstract класс (вызывают это A); иначе ошибка времени компиляции заканчивается. Каждый подкласс, который не является abstract должен обеспечить реализацию для м., или ошибка времени компиляции происходит как определено в §8.1.1.1.

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

Для подкласса было бы невозможно реализовать a private abstract метод, потому что private методы не наследованы подклассами; поэтому такой метод никогда не мог использоваться.

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

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

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

Это может обеспечить место, чтобы поместить комментарий для документации, или объявить, что набор проверенных исключений (§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 никогда броски a BufferEmpty исключение, предполагаемо потому что это генерирует данные в буфере, и таким образом никогда не может исчерпывать данные. Метод экземпляра, который не является abstract может быть переопределен abstract метод.

Например, мы можем объявить abstract класс Point это требует, чтобы его подклассы реализовали toString если они должны быть полными, instantiable классы:

abstract class Point {
	int x, y;
	public abstract String toString();
}
Это abstract объявление toString переопределяет не -abstract toString метод класса Object. (Класс 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 в теле класса метод приводит к ошибке времени компиляции. Это - ошибка времени компиляции для a static метод, который будет объявлен abstract.

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

8.4.3.3 final Методы

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

A private метод и все методы объявляются в a final класс (§8.1.1.2) неявно final, потому что невозможно переопределить их. Это разрешается, но не требуется для объявлений таких методов избыточно включать final ключевое слово.

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

Во время выполнения генератор машинного кода или оптимизатор могут "встроить" тело a final метод, заменяя вызов метода с кодом в его теле. Процесс встраивания должен сохранить семантику вызова метода. В частности если цель вызова метода экземпляра null, тогда a NullPointerException должен быть брошен, даже если метод встраивается. Компилятор должен гарантировать, что исключение будет выдано в корректной точке, так, чтобы фактические параметры методу, как замечалось, были оценены в правильном порядке до вызова метода.

Рассмотрите пример:

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 цикл к форме:

		for (int i = 0; i < p.length; i++) {
			p[i] = new Point();
			Point pi = p[i];
			int j = p.length-1-i;
			pi.x += i;
			pi.y += j;
		}
Цикл мог бы тогда подвергнуться дальнейшей оптимизации.

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

8.4.3.4 native Методы

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

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

Например, класс 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 strictfp Методы

Эффект strictfp модификатор должен сделать все float или double выражения в пределах тела метода быть явно строгим FP (§15.4).

8.4.3.6 synchronized Методы

A synchronized метод получает блокировку (§17.1) прежде, чем он выполнится. Для класса (static) метод, блокировка, связанная с Class объект для класса метода используется. Для метода экземпляра, блокировка, связанная с this (объект, для которого был вызван метод) используется.

Они - те же самые блокировки, которые могут использоваться synchronized оператор (§14.18); таким образом, код:

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 имеет переменную экземпляра contents это может содержать ссылку на любой объект. Можно поместить объект в a Box вызывая put, который возвращается false если поле уже полно. Можно вытащить что-то из a Box вызывая get, который возвращает нулевую ссылку если box пусто.

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

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

8.4.4 Броски метода

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

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

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

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

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

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

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

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

8.4.5 Тело метода

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

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

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

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

Если у метода, как объявляют, есть тип возврата, то каждый return у оператора (§14.16) в его теле должно быть Выражение. Ошибка времени компиляции происходит, если тело метода может обычно завершаться (§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.1 Переопределение (Методами экземпляра)

Метод экземпляра m1, объявленный в классе C, переопределяет другой метод с той же самой подписью, m2, объявленный в классе A если оба
  1. C является подклассом A.
  2. Также
Кроме того, если m1 не abstract, тогда m1, как говорят, реализует любого и все объявления abstract методы, которые это переопределяет.

Ошибка времени компиляции происходит, если метод экземпляра переопределяет a static метод.

В этом отношении переопределение методов отличается от сокрытия полей (§8.3), поскольку это допустимо для переменной экземпляра, чтобы скрыть a static переменная.

К переопределенному методу можно получить доступ при использовании выражения вызова метода (§15.12), который содержит ключевое слово super. Отметьте, что полностью определенное имя или бросок к супертипу класса не эффективны при попытке получить доступ к переопределенному методу; в этом отношении переопределение методов отличается от сокрытия полей. См. §15.12.4.9 для обсуждения и примеров этой точки.

Присутствие или отсутствие strictfp модификатор не имеет абсолютно никакого эффекта на правила для того, чтобы переопределить методы и реализовать абстрактные методы. Например, это разрешается для метода, который не строг FP, чтобы переопределить строгий FP метод, и разрешается для строгого FP метода переопределить метод, который не строг FP.

8.4.6.2 Сокрытие (Методами Класса)

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

В этом отношении сокрытие методов отличается от сокрытия полей (§8.3), поскольку это допустимо для a static переменная, чтобы скрыть переменную экземпляра. Сокрытие также отлично от затенения (§6.3.1) и затеняющий (§6.3.2).

К скрытому методу можно получить доступ при использовании полностью определенного имени или при использовании выражения вызова метода (§15.12), который содержит ключевое слово super или бросок к супертипу класса. В этом отношении сокрытие методов подобно сокрытию полей.

8.4.6.3 Требования в Переопределении и Сокрытии

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

В этих отношениях переопределение методов отличается от сокрытия полей (§8.3), поскольку это допустимо для поля, чтобы скрыть поле другого типа.

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

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

8.4.6.4 Наследование Методов с Той же самой Подписью

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

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

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

8.4.7 Перегрузка

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

Методы переопределяются на основе подписи подписью.

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

Когда метод вызывается (§15.12), число фактических параметров и типы времени компиляции параметров используются, во время компиляции, чтобы определить подпись метода, который будет вызван (§15.12.2). Если метод, который должен быть вызван, будет методом экземпляра, то фактический метод, который будет вызван, будет определен во время выполнения, используя динамический поиск метода (§15.12.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.

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

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) метод может быть вызван при использовании ссылки, тип которой является классом, который фактически содержит объявление метода. В этом отношении сокрытие статических методов отличается от переопределения методов экземпляра. Пример:

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 от класса BufferOutput.

В putchar метод a 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 Пример: Неправильное Переопределение из-за Бросков

Этот пример использует обычную и стандартную форму для того, чтобы объявить новый тип исключения в его объявлении класса 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 не объявил. Если это не считали ошибкой, invoker метода 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 Объявления Типа элемента

Задействованный класс является классом, объявление которого непосредственно включается в другой класс или интерфейсное объявление. Точно так же задействованный интерфейс является интерфейсом, объявление которого непосредственно включается в другой класс или интерфейсное объявление. Контекст (§6.3) задействованного класса или интерфейса определяется в §8.1.5.

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

В пределах класса C, объявления d типа элемента, названного n тенями объявления любых других типов, названных n, которые находятся в контексте в точке, где d происходит.

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

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

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

8.5.1 Модификаторы доступа

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

8.5.2 Статические Объявления Типа элемента

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

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

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

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

Инициализатор экземпляра, объявленный в классе, выполняется, когда экземпляр класса создается (§15.9), как определено в §8.8.5.1.

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

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

Это - ошибка времени компиляции, если инициализатор экземпляра не может обычно завершаться (§14.20). Если оператор возврата (§14.16) появляется где-нибудь в пределах инициализатора экземпляра, то ошибка времени компиляции происходит.

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

Инициализаторам экземпляра разрешают отослать к текущему объекту этот (§15.8.3) и использовать ключевое слово супер (§15.11.2, §15.12).

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

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

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

Статические инициализаторы и инициализаторы переменной класса выполняются в текстовом порядке.

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

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

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

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

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

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

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

class Point {
	int x, y;
	Point(int x, int y) { this.x = x; this.y = y; }
}
Конструкторы вызываются выражениями создания экземпляра класса (§15.9) преобразованиями и связями, вызванными оператором конкатенации строк + (§15.18.1), и явными вызовами конструктора от других конструкторов (§8.8.5). Конструкторы никогда не вызываются выражениями вызова метода (§15.12).

Доступом к конструкторам управляют модификаторы доступа (§6.6).

Это полезно, например, в предотвращении инстанцирования, объявляя недоступного конструктора (§8.8.8). Объявления конструктора не являются элементами. Они никогда не наследованы и поэтому не подвергаются сокрытию или переопределению.

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

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

8.8.2 Подпись конструктора

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

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

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

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

Отметьте, что ConstructorModifier не может быть объявлен strictfp. Этим различием в определениях для ConstructorModifier и MethodModifier (§8.4.3) является намеренное проектное решение языка; это эффективно гарантирует, что конструктор строг FP (§15.4), если и только если его класс строг FP.

8.8.4 Броски конструктора

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

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

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

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

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

За исключением возможности явных вызовов конструктора, тело конструктора походит на тело метода (§8.4.5). A return оператор (§14.16) может использоваться в теле конструктора, если это не включает выражение.

В примере:

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, проведение координат.

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

8.8.5.1 Явные Вызовы Конструктора

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

class Outer {
	class Inner{}
}
class ChildOfInner extends Outer.Inner {
	ChildOfInner(){(new Outer()).super();}
}
Явный оператор вызова конструктора в теле конструктора, возможно, не обращается ни к каким переменным экземпляра или методам экземпляра, объявленным в этом классе или никаком суперклассе, или использовании this или super в любом выражении; иначе, ошибка времени компиляции происходит.

Например, если первый конструктор ColoredPoint в примере выше были изменены на:

ColoredPoint(int x, int y) {
	this(x, y, color);
}
тогда ошибка времени компиляции произошла бы, потому что переменная экземпляра не может использоваться в пределах вызова конструктора суперкласса.

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

Например:

class Top {
	int x;
	class Dummy {
		Dummy(Object o) {}
	}
	class Inside extends Dummy {
		Inside() {
			super(new Object() { int r = x; }); // error
		}
		Inside(final int y) {
			super(new Object() { int r = y; }); // correct
		}
	}
}
Позвольте C быть классом, который инстанцируют, позволять S быть прямым суперклассом C, и позволять мне быть создаваемым экземпляром. Оценка явного вызова конструктора продолжается следующим образом:

8.8.6 Конструктор, Перегружающийся

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

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

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

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

Конструктор по умолчанию имеет нет throws пункт.

Из этого следует, что nullary конструктор суперкласса, имеет a throws пункт, затем ошибка времени компиляции произойдет.

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

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

public class Point {
	int x, y;
}
эквивалентно объявлению:

public class Point {
	int x, y;
	public Point() { super(); }
}
где конструктор по умолчанию public потому что класс Point public.

Правило, что у конструктора по умолчанию класса есть тот же самый модификатор доступа как сам класс, просто и интуитивно. Отметьте, однако, что это не подразумевает, что конструктор доступен всякий раз, когда класс доступен. Рассмотреть

package p1;
public class Outer {
 	protected class Inner{}
}
package p2;
class SonOfOuter extends p1.Outer {
	void foo() {
 		new Inner(); // compile-time access error
	}
}

Конструктор для Inner защищается. Однако, конструктор защищается относительно Inner, в то время как Inner защищается относительно Outer. Так, Inner доступно в SonOfOuter, так как это - подкласс Outer. Inner's конструктор не доступно в SonOfOuter, потому что класс SonOfOuter не подкласс Inner! Следовательно, даже при том, что Inner доступно, его конструктор по умолчанию не.

8.8.8 Предотвращение Инстанцирования Класса

Класс может быть разработан, чтобы предотвратить код вне объявления класса от создания экземпляров класса, объявляя по крайней мере одному конструктору, предотвратить создание неявного конструктора, и объявляя, что все конструкторы private. A 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, в котором это объявляется.


Содержание | Предыдущий | Следующий | Индекс Спецификация языка Java
Второй Выпуск

Спецификация языка Java (HTML, сгенерированный Блинчиком "сюзет" Pelouch 19 мая 2000)
Авторское право © Sun Microsystems, Inc 2000 года. Все права защищены
Пожалуйста, отправьте любые комментарии или исправления к jls@java.sun.com



Spec-Zone.ru - all specs in one place



free hit counter