![]() |
Spec-Zone .ru
спецификации, руководства, описания, API
|
Содержание | Предыдущий | Следующий | Индекс | Спецификация языка 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.4) и может реализовать интерфейсы (§8.1.5). Классы могут быть универсальными, то есть, они могут объявить переменные типа (§4.4), чья привязка может отличаться среди различных экземпляров класса.
Классы могут быть украшены аннотациями (§9.7) точно так же как любой другой вид объявления.
Тело класса объявляет элементы (поля и методы и вложенные классы и интерфейсы), экземпляр и статические инициализаторы, и конструкторы (§8.1.6). Контекст (§6.3) элемента (§8.2) является всем телом объявления класса, которому принадлежит элемент. Поле, метод, задействованный класс, задействованный интерфейс, и объявления конструктора могут включать модификаторы доступа (§6.6) public
, protected
, или private
. Элементы класса включают и объявленный и наследованные элементы (§8.2). Недавно объявленные поля могут скрыть поля, объявленные в суперклассе или суперинтерфейсе. Недавно объявленные элементы класса и интерфейсные элементы могут скрыть класс или соединить интерфейсом с элементами, объявленными в суперклассе или суперинтерфейсе. Недавно объявленные методы могут скрыть, реализовать, или переопределить методы, объявленные в суперклассе или суперинтерфейсе.
Полевые объявления (§8.3) описывают переменные класса, которые воплощаются однажды, и переменные экземпляра, которые недавно воплощаются для каждого экземпляра класса. Поле может быть объявлено final
(§8.3.1.2), когда это может быть присвоено только однажды. Любое полевое объявление может включать инициализатор.
Задействованные объявления класса (§8.5) описывают вложенные классы, которые являются элементами окружающего класса. Задействованные классы могут быть статичными, когда у них нет никакого доступа к переменным экземпляра окружающего класса; или они могут быть внутренними классами (§8.1.3).
Задействованные объявления интерфейса (§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.19), таким образом позволяя его действия синхронизироваться с таковыми из других потоков (§17).
Имена методов могут быть перегружены (§8.4.9).
Инициализаторы экземпляра (§8.6) являются блоками исполняемого кода, который может использоваться, чтобы помочь инициализировать экземпляр, когда он создается (§15.9).
Статические инициализаторы (§8.7) являются блоками исполняемого кода, который может использоваться, чтобы помочь инициализировать класс.
Конструкторы (§8.8) подобны методам, но не могут быть вызваны непосредственно вызовом метода; они используются, чтобы инициализировать новые экземпляры класса. Как методы, они могут быть перегружены (§8.8.8).
Правила в этом разделе применяются ко всем объявлениям класса, если эта спецификация явно не утверждает иначе. Во многих случаях специальные ограничения применяются к перечислимым объявлениям. Перечислимые объявления описываются подробно в §8.9.ClassDeclaration: NormalClassDeclaration EnumDeclaration NormalClassDeclaration: ClassModifiersopt class Identifier TypeParametersopt Superopt Interfacesopt ClassBody
Идентификатор в объявлении класса определяет имя класса. Ошибка времени компиляции происходит, если у класса есть то же самое простое имя как какой-либо из его классов включения или интерфейсов.
Не все модификаторы применимы ко всем видам объявлений класса. Модификатор доступаClassModifiers: ClassModifier ClassModifiers ClassModifier ClassModifier: one of Annotation public protected private abstract static final strictfp
public
принадлежит только высокоуровневым классам (§7.6) и задействованным классам (§8.5, §9.5), и обсуждается в §6.6, §8.5 и §9.5. Модификаторы доступа protected
и private
принадлежите только задействованным классам в пределах непосредственно объявления класса включения (§8.5), и обсуждаются в §8.5.1. Модификатор доступа static
принадлежит только задействованным классам (§8.5, §9.5). Ошибка времени компиляции происходит, если тот же самый модификатор появляется не раз в объявлении класса.
Если аннотация на объявлении класса соответствует типу T аннотации, и T имеет (мета-) аннотация м., который соответствует annotation.Target
, тогда у м. должен быть элемент, значение которого annotation.ElementType.TYPE
, или ошибка времени компиляции происходит. Модификаторы аннотации описываются далее в §9.7.
Если два или больше модификатора класса появляются в объявлении класса, то это общепринято, хотя не требуемый, что они появляются в порядке, непротиворечивом с показанным выше в производстве для ClassModifier.
abstract
класс является классом, который является неполным, или считаться неполным. Нормальные классы могут иметь abstract
методы (§8.4.3.1, §9.4), который является методами, которые объявляются, но еще не реализуются, только если они abstract
классы. Если нормальный класс, который не является abstract
содержит abstract
метод, затем ошибка времени компиляции происходит. Перечислимые типы (§8.9) не должны быть объявлены кратким обзором; выполнение так приведет к ошибке времени компиляции. Это - ошибка времени компиляции для перечислимого типа E, чтобы иметь абстрактный метод м. как элемент, если E не имеет один или более перечислимые константы, и у всех перечислимых констант Э есть тела класса, которые обеспечивают конкретные реализации м. Это - ошибка времени компиляции для тела класса перечислимой константы, чтобы объявить абстрактный метод.
Класс C имеет abstract
методы, если какое-либо следующее является истиной:
abstract
метод (§8.4.3).
abstract
метод и C ни не объявляют, ни наследовали метод, который реализует (§8.4.8.1) это.
abstract
) и C ни не объявляет, ни наследовал метод, который реализует его.
В примере:
класс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.10) никаких параметров, сделать это private
, никогда не вызывайте это, и не объявляйте никаких других конструкторов. Класс этой формы обычно содержит методы класса и переменные. Класс Math
пример класса, который нельзя инстанцировать; его объявление похоже на это:
public final class Math { private Math() { } // never instantiate this class . . . declarations of class variables and methods . . .}
final
если его определение полно, и никакие подклассы не требуются или требуются. Ошибка времени компиляции происходит если имя a final
класс появляется в extends
пункт (§8.1.4) другого class
объявление; это подразумевает это a final
у класса не может быть никаких подклассов. Ошибка времени компиляции происходит, если класс объявляется обоими final
и abstract
, потому что реализация такого класса никогда не могла завершаться (§8.1.1.1).Поскольку a final
у класса никогда нет подклассов, методов a final
класс никогда не переопределяется (§8.4.8.1).
strictfp
модификатор должен сделать все float
или double
выражения в пределах объявления класса быть явно строгим FP (§15.4). Это подразумевает, что все методы, объявленные в классе, и все вложенные типы, объявленные в классе, неявно strictfp. Отметьте также что все float
или double
выражения в пределах всех переменных инициализаторов, инициализаторов экземпляра, статических инициализаторов и конструкторов класса также будут явно строги FP.
Обсуждение
Например, выполнение кода
приведет к переменнойVector<String> x = new Vector<String>(); Vector<Integer> y = new Vector<Integer>(); boolean b = x.getClass() == y.getClass();
b
содержание значения true
.
Это - ошибка времени компиляции, если универсальный класс является прямым или косвенным подклассомTypeParameters ::= < TypeParameterList > TypeParameterList ::= TypeParameterList , TypeParameter | TypeParameter
Throwable
.Обсуждение
Это ограничение необходимо начиная сcatch
механизм виртуальной машины Java работает только с неуниверсальными классами. Контекст параметра типа класса является всем объявлением класса включая раздел параметра типа непосредственно. Поэтому, введите параметры, может появиться как части их собственных границ, или как границы других параметров типа, объявленных в том же самом разделе.
Это - ошибка времени компиляции, чтобы обратиться к параметру типа класса C где угодно в объявлении статического элемента C или объявлении статического элемента любого описания типа, вложенного в пределах C. Это - ошибка времени компиляции, чтобы обратиться к параметру типа класса C в пределах статического инициализатора C или любого класса, вложенного в пределах C.
Обсуждение
Пример: границы переменной Взаимно рекурсивного типа.
interface ConvertibleTo<T> { T convert(); } class ReprChange<T implements ConvertibleTo<S>, S implements ConvertibleTo<T>> { T t; void set(S s) { t = s.convert(); } S get() { return t.convert(); } }
Параметризованные объявления класса могут быть вложены в других объявлениях.
Обсуждение
Это иллюстрируется в следующем примере:
class Seq<T> { T head; Seq<T> tail; Seq() { this(null, null); } boolean isEmpty() { return tail == null; } Seq(T head, Seq<T> tail) { this.head = head; this.tail = tail; } class Zipper<S> { Seq<Pair<T,S>> zip(Seq<S> that) { if (this.isEmpty() || that.isEmpty()) return new Seq<Pair<T,S>>(); else return new Seq<Pair<T,S>>( new Pair<T,S>(this.head, that.head), this.tail.zip(that.tail)); } } } class Pair<T, S> { T fst; S Snd; Pair(T f, S s) {fst = f; snd = s;} } class Client { { Seq<String> strs = new Seq<String>("a", new Seq<String>("b", new Seq<String>())); Seq<Number> nums = new Seq<Number>(new Integer(1), new Seq<Number>(new Double(1.5), new Seq<Number>())); Seq<String>.Zipper<Number> zipper = strs.new Zipper<Number>(); Seq<Pair<String,Number>> combined = zipper.zip(nums); } }
static
. Внутренние классы, возможно, не объявляют статические инициализаторы (§8.7) или задействованные интерфейсы. Внутренние классы, возможно, не объявляют статические элементы, если они не время компиляции постоянные поля (§15.28).Чтобы иллюстрировать эти правила, рассмотрите пример ниже:
Внутренние классы могут наследовать статические элементы, которые не являются константами времени компиляции даже при том, что они, возможно, не объявляют их. Вложенные классы, которые не являются внутренними классами, могут объявить статические элементы свободно, в соответствии с обычными правилами языка программирования Java. Задействованные интерфейсы (§8.5) всегда неявно статичны, таким образом, они, как никогда полагают, не являются внутренними классами.class HasStatic{ static int j = 100; } class Outer{ class Inner extends HasStatic{ static final int 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 }
Оператор или выражение происходят в статическом контексте, если и только если самый внутренний метод, конструктор, инициализатор экземпляра, статический инициализатор, полевой инициализатор, или явный оператор вызова конструктора, включающий оператор или выражение, являются статическим методом, статическим инициализатором, переменным инициализатором статической переменной, или явным оператором вызова конструктора (§8.8.7).
Внутренний класс C является прямым внутренним классом класса O, если O является сразу лексически включающим классом C, и объявление C не происходит в статическом контексте. Класс C является внутренним классом класса O, если это - или прямой внутренний класс O или внутренний класс внутреннего класса O.
Класс O является нулевым лексически класс включения себя. Класс O является энным лексически класс включения класса C, если это - сразу класс включения n-1st, лексически включающего класс C.
Экземпляр i из прямого внутреннего класса C класса O связывается с экземпляром O, известного как сразу экземпляр включения меня. Сразу экземпляр включения объекта, если таковые вообще имеются, определяется, когда объект создается (§15.9.2).
Объект o является нулевым лексически экземпляр включения себя. Объект o является энным лексически экземпляр включения экземпляра i, если это - сразу экземпляр включения n-1st, лексически включающего экземпляр меня.
Когда внутренний класс обращается к переменной экземпляра, которая является элементом лексически класса включения, переменной соответствия, лексически экземпляр включения используется. Пустой финал (§4.12.4) поле лексически класса включения не может быть присвоен в пределах внутреннего класса.
У экземпляра внутреннего класса I, объявление которого происходит в статическом контексте, нет никаких лексически экземпляров включения. Однако, если я был сразу объявлен в пределах статического метода или статического инициализатора тогда, у меня действительно есть блок включения, который является самым внутренним оператором блока, лексически включающим объявление меня.
Кроме того, для каждого суперкласса S C, который является самостоятельно прямым внутренним классом класса Так, есть экземпляр НАСТОЛЬКО связанного со мной, известен как сразу экземпляр включения меня относительно S. Сразу экземпляр включения объекта относительно прямого суперкласса его класса, если таковые вообще имеются, определяется, когда конструктор суперкласса вызывается через явный оператор вызова конструктора.
Любая локальная переменная, формальный параметр метода или используемый параметр обработчика исключений, но не объявленный во внутреннем классе должны быть объявлены финалом. Любая локальная переменная, используемая, но не объявленный во внутреннем классе, должна быть определенно присвоена (§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-ое лексически экземпляр включения).extends
пункт в нормальном объявлении класса определяет прямой суперкласс текущего класса.
Следующее повторяется от §4.3, чтобы сделать представление здесь более четким:Super: extends ClassType
Класс, как говорят, является прямым подклассом своего прямого суперкласса. Прямой суперкласс является классом, из реализации которого получается реализация текущего класса. Прямой суперкласс перечислимого типаClassType: TypeDeclSpecifier TypeArgumentsopt
E
Enum<E>
. extends
пункт не должен появиться в определении класса Object
, потому что это - исконный класс и не имеет никакого прямого суперкласса.
Данный (возможно универсальный) объявление класса для C <F1..., Fn>, n0, CObject
, прямой суперкласс типа класса (§4.5) C <F1..., Fn> является типом, поданным, расширяет пункт объявления C, если расширяется, пункт присутствует, или
Object
иначе.
Позвольте C <F1..., Fn>, n> 0, будьте универсальным объявлением класса. Прямой суперкласс параметризованного типа класса C <T1..., Tn>, где Ti, 1 дюйм
, является типом, является D <тета U1..., Британская тета>, где D <U1..., Великобритания> являются прямым суперклассом C <F1..., Fn>, и тета является заменой [F1: = T1..., Fn: = Tn].
ClassType должен назвать доступный (§6.6) тип класса, или ошибка времени компиляции происходит. Если указанный ClassType называет класс, который является final
(§8.1.1.2), затем ошибка времени компиляции происходит; final
классам не позволяют иметь подклассы. Это - ошибка времени компиляции, если ClassType называет класс Enum
или любой вызов этого. Если TypeName сопровождается какими-либо параметрами типа, это должен быть корректный вызов описания типа, обозначенного TypeName, и ни один из параметров типа не может быть подстановочными параметрами типа, или ошибка времени компиляции происходит.
В примере:
отношения следующие:class Point { int x, y; } final class ColoredPoint extends Point { int color; } class Colored3DPoint extends ColoredPoint { int z; } // error
Point
прямой подкласс Object
.
Object
прямой суперкласс класса Point
.
ColoredPoint
прямой подкласс класса Point
.
Point
прямой суперкласс класса ColoredPoint
. Colored3dPoint
вызывает ошибку времени компиляции, потому что она пытается расшириться final
класс ColoredPoint
.Отношение подкласса является переходным закрытием прямого отношения подкласса. Класс A является подклассом класса C, если любое из следующего является истиной:
В примере:
отношения следующие:class Point { int x, y; } class ColoredPoint extends Point { int color; } final class Colored3dPoint extends ColoredPoint { int z; }
Point
суперкласс класса ColoredPoint
.
Point
суперкласс класса Colored3dPoint
.
ColoredPoint
подкласс класса Point
.
ColoredPoint
суперкласс класса Colored3dPoint
.
Colored3dPoint
подкласс класса ColoredPoint
.
Colored3dPoint
подкласс класса Point
. extends
или implements
пункт C или как суперкласс или как суперинтерфейс, или как спецификатор суперкласса или суперинтерфейсного имени. Класс C зависит от ссылочного типа T, если какое-либо из следующих условий содержит:
Например:
вызывает ошибку времени компиляции.class Point extends ColoredPoint { int x, y; } class ColoredPoint extends Point { int color; }
Если циркулярные объявленные классы обнаруживаются во время выполнения, поскольку классы загружаются (§12.2), то a ClassCircularityError
бросается.
implements
пункт в объявлении класса перечисляет имена интерфейсов, которые являются прямыми суперинтерфейсами объявляемого класса:
Следующее повторяется от §4.3, чтобы сделать представление здесь более четким:Interfaces: implements InterfaceTypeList InterfaceTypeList: InterfaceType InterfaceTypeList , InterfaceType
Данный (возможно универсальный) объявление класса для C <F1..., Fn>, n0InterfaceType: TypeDeclSpecifier TypeArgumentsopt
Позвольте C <F1..., Fn>, n> 0, будьте универсальным объявлением класса. Прямые суперинтерфейсы параметризованного типа класса C <T1..., Tn>, где Ti, 1 дюйм
, является типом, являются всеми типами I <тета U1..., Британская тета>, где я <U1..., Великобритания> является прямым суперинтерфейсом C <F1..., Fn>, и тета является заменой [F1: = T1..., Fn: = Tn].
Каждый InterfaceType должен назвать доступное (§6.6) интерфейсный тип, или ошибка времени компиляции происходит. Если TypeName сопровождается какими-либо параметрами типа, это должен быть корректный вызов описания типа, обозначенного TypeName, и ни один из параметров типа не может быть подстановочными параметрами типа, или ошибка времени компиляции происходит.
Ошибка времени компиляции происходит, если тот же самый интерфейс упоминается как прямой суперинтерфейс два или больше раза в сингле implements
имена пункта.
Это - истина, даже если интерфейс называют по-разному; например, код:
результаты в ошибке времени компиляции, потому что имена java.lang.class Redundant implements java.lang.Cloneable, Cloneable { int x; }
Cloneable
и Cloneable
обратитесь к тому же самому интерфейсу.Интерфейсный тип я - суперинтерфейс типа класса C, если какое-либо следующее является истиной:
В примере:
отношения следующие:public interface Colorable { void setColor(int color); int getColor(); } public enum Finish {MATTE, GLOSSY} public interface Paintable extends Colorable { void setFinish(Finish finish); Finish 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 { Finish finish; public void setFinish(Finish finish) { this.finish = finish; } public Finish getFinish() { return finish; } }
Paintable
суперинтерфейс класса PaintedPoint
.
Colorable
суперинтерфейс класса ColoredPoint
и класса PaintedPoint
.
Paintable
подынтерфейс интерфейса Colorable
, и Colorable
суперинтерфейс Paintable
, a
s определенный в §9.1.3. 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.8).Класс, возможно, одновременно не подтип двух интерфейсных типов, которые являются различными вызовами того же самого универсального интерфейса (§9.1.2), или вызовом универсального интерфейса и необработанного типа, называя тот же самый универсальный интерфейс.
Обсуждение
Вот пример недопустимого множественного наследования интерфейса:
class B implements I<Integer> class C extends B implements I<String>
Это требование было представлено, чтобы поддерживать преобразование стиранием типа (§4.6).
Контекст объявления элемента м. объявил в или наследовался типом класса C, все тело C, включая любые объявления вложенного типа.ClassBody: { ClassBodyDeclarationsopt } ClassBodyDeclarations: ClassBodyDeclaration ClassBodyDeclarations ClassBodyDeclaration ClassBodyDeclaration: ClassMemberDeclaration InstanceInitializer StaticInitializer ConstructorDeclaration ClassMemberDeclaration: FieldDeclaration MethodDeclaration ClassDeclaration InterfaceDeclaration ;
Если сам C является вложенным классом, могут быть определения того же самого вида (переменная, метод, или тип) и назвать как м. во включении контекстов. (Контексты могут быть блоками, классами, или пакетами.) Во всех таких случаях элемент м. объявил или наследовал в тенях C (§6.3.1) другие определения того же самого вида и имени.
Object
, у которого нет никакого прямого суперкласса
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
не имеет никакого конструктора, объявленного с двумя целочисленными параметрами, согласно просьбе использованием в main
. Это иллюстрирует факт это ColoredPoint
не наследовал конструкторов его суперкласса Point
.
ColoredPoint
не объявляет конструкторов, и поэтому конструктор по умолчанию для этого автоматически создается (§8.8.9), и этот конструктор по умолчанию эквивалентен: ColoredPoint() { super(); }
который вызывает конструктора, без параметров, для прямого суперкласса класса ColoredPoint
. Ошибка состоит в том что конструктор для Point
это не берет параметров, private
, и поэтому не доступно вне класса Point
, даже через вызов конструктора суперкласса (§8.8.7).
Еще две ошибки происходят потому что метод reset
из класса Point
private
, и поэтому не наследован классом ColoredPoint
. Вызовы метода в методе clear
из класса ColoredPoint
и в методе main
из класса Test
поэтому не корректны.
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
пишется таким образом, это скомпилирует без ошибок.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++; } }
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
.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
.
FieldModifiers описываются в §8.3.1. Идентификатор в FieldDeclarator может использоваться на имя, чтобы обратиться к полю. Поля являются элементами; контекст (§6.3) полевого объявления определяется в §8.1.6. Больше чем одно поле может быть объявлено в единственном полевом объявлении при использовании больше чем одного оператора объявления; FieldModifiers и Тип применяются ко всем операторам объявления в объявлении. Объявления переменной, включающие типы массива, обсуждаются в §10.2.FieldDeclaration: FieldModifiersopt Type VariableDeclarators ; VariableDeclarators: VariableDeclarator VariableDeclarators , VariableDeclarator VariableDeclarator: VariableDeclaratorId VariableDeclaratorId = VariableInitializer VariableDeclaratorId: Identifier VariableDeclaratorId [ ] VariableInitializer: Expression ArrayInitializer
Это - ошибка времени компиляции для тела объявления класса, чтобы объявить два поля с тем же самым именем. У методов, типов, и полей может быть то же самое имя, так как они используются в различных контекстах и снимаются неоднозначность различными процедурами поиска (§6.5).
Если класс объявляет поле с определенным именем, то объявление того поля, как говорят, скрывает любого и все доступные объявления полей с тем же самым именем в суперклассах, и суперинтерфейсы класса. Полевое объявление также тени (§6.3.1) объявления любых доступных полей во включении классов или интерфейсов, и любых локальных переменных, формальных параметров метода, и параметров обработчика исключений с тем же самым именем в любых блоках включения.
Если полевое объявление скрывает объявление другого поля, у этих двух полей не должно быть того же самого типа.
Класс наследовал от его прямого суперкласса и прямых суперинтерфейсов все нечастные поля суперкласса и суперинтерфейсов, которые и доступны, чтобы кодировать в классе и не скрытые объявлением в классе.
Отметьте, что частное поле суперкласса могло бы быть доступным для подкласса (например, если оба класса являются элементами того же самого класса). Однако, частное поле никогда не наследовано подклассом.
Для класса возможно наследовать больше чем одно поле с тем же самым именем (§8.3.3.3). Такая ситуация сам по себе не вызывает ошибку времени компиляции. Однако, любая попытка в пределах тела класса, чтобы обратиться к любому такому полю его простым именем приведет к ошибке времени компиляции, потому что такая ссылка неоднозначна.
Могло бы быть несколько путей, которыми то же самое полевое объявление могло бы быть наследовано от интерфейса. В такой ситуации поле, как полагают, наследовано только однажды, и это может быть упомянуто его простым именем без неоднозначности.
К скрытому полю можно получить доступ при использовании полностью определенного имени (если это static
) или при использовании выражения доступа к полю (§15.11), который содержит ключевое слово super
или бросок к супертипу класса. См. §15.11.2 для обсуждения и примера.
Значение сохранено в поле типа float
всегда элемент набора значений плавающего (§4.2.3); точно так же значение сохранено в поле типа double
всегда элемент двойного набора значений. Это не разрешается для поля типа float
чтобы содержать элемент набора значений "пускают в ход расширенную экспоненту", которая не является также элементом набора значений плавающего, ни для поля типа double
чтобы содержать элемент набора значений "удваивают расширенную экспоненту", которая не является также элементом двойного набора значений.
Модификаторы доступаFieldModifiers: FieldModifier FieldModifiers FieldModifier FieldModifier: one of Annotation public protected private static final transient volatile
public
, protected
, и private
обсуждаются в §6.6. Ошибка времени компиляции происходит, если тот же самый модификатор появляется не раз в полевом объявлении, или если у полевого объявления есть больше чем один из модификаторов доступа public
, protected
, и private
.
Если аннотация на полевом объявлении соответствует типу T аннотации, и T имеет (мета-) аннотация м., который соответствует annotation.Target
, тогда у м. должен быть элемент, значение которого annotation.ElementType.FIELD
, или ошибка времени компиляции происходит. Модификаторы аннотации описываются далее в §9.7.
Если два или больше (отличных) полевых модификатора появляются в полевом объявлении, это общепринято, хотя не требуемый, что они появляются в порядке, непротиворечивом с показанным выше в производстве для FieldModifier.
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
обратитесь к той же самой переменной.final
(§4.12.4). И класс и переменные экземпляра (static
и не -static
поля), может быть объявлен final
.Это - ошибка времени компиляции, если пустой финал (§4.12.4) переменная класса определенно не присваивается (§16.8) статическим инициализатором (§8.7) класса, в котором это объявляется.
Пустая заключительная переменная экземпляра должна быть определенно присвоена (§16.9) в конце каждого конструктора (§8.8) класса, в котором это объявляется; иначе ошибка времени компиляции происходит.
transient
указать, что они не часть постоянного состояния объекта. Если экземпляр класса Point
:
были сохранены к персистентному хранению системной службой, тогда только поляclass Point { int x, y; transient float rho, theta; }
x
и y
был бы сохранен. Эта спецификация не определяет детали таких служб; см. спецификацию java.io.Serializable
для примера такой службы.Язык программирования Java обеспечивает второй механизм, энергозависимые поля, который более удобен чем блокировка в некоторых целях.
Поле может быть объявленоvolatile
, когда модель памяти Java (§17) гарантирует, что все потоки видят непротиворечивое значение для переменной.Если в следующем примере один поток неоднократно вызывает метод 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
.
static
поле), тогда переменный инициализатор оценивается и присвоение, выполняемое точно однажды, когда класс инициализируется (§12.4).
static
), тогда переменный инициализатор оценивается и присвоение, выполняемое каждый раз, когда экземпляр класса создается (§12.5).
Пример:
производит вывод:class Point { int x = 1, y = 5; } class Test { public static void main(String[] args) { Point p = new Point(); System.out.println(p.x + ", " + p.y); } }
потому что присвоения на1, 5
x
и y
происходите всякий раз, когда новое Point
создается.Переменные инициализаторы также используются в операторах объявления локальной переменной (§14.4), где инициализатор оценивается и присвоение, выполняемое каждый раз, когда оператор объявления локальной переменной выполняется.
Это - ошибка времени компиляции если оценка переменного инициализатора для a static
поле именованного класса (или интерфейса) может завершиться резко с проверенным исключением (§11.2).
Это - ошибка времени компиляции, если инициализатор переменной экземпляра именованного класса может выдать проверенное исключение, если то исключение или один из его супертипов явно не объявляются в пункте бросков каждого конструктора его класса, и у класса есть по крайней мере один явно объявленный конструктор. Инициализатор переменной экземпляра в анонимном классе (§15.9.5) может выдать любые исключения.
Если ключевое слово this
(§15.8.3) или ключевое слово super
(§15.11.2, §15.12), происходит в выражении инициализации для переменной класса, затем ошибка времени компиляции происходит.
Одна тонкость здесь то, что во время выполнения, static
переменные, которые являются final
и это инициализируется с постоянными величинами времени компиляции, инициализируются сначала. Это также применяется к таким полям в интерфейсах (§9.3.1). Эти переменные являются "константами", у которых, как никогда будут наблюдать, не будет их начальных значений по умолчанию (§4.12.5), даже окольными программами. См. §12.4.2 и §13.4.9 для большего количества обсуждения.
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 для точных правил, управляющих ссылкой вперед на переменные экземпляра.
static
) поле класса или интерфейса C и всех следующих условий содержит:
static
) переменный инициализатор C или в экземпляре (соответственно static
) инициализатор C.
Это означает, что ошибка времени компиляции следует из тестовой программы:
тогда как следующий пример компилирует без ошибки:class Test { int i = j; // compile-time error: incorrect forward reference int j = 1; }
даже при том, что конструктор (§8.8) дляclass Test { Test() { k = 2; } int j = 1; int i = j; int k; }
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.12.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; }
производит вывод: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
производит вывод: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
и один из типа double
. Оба поля носят имя x
, но в пределах объявления класса Test
, простое имя x
всегда обращается к полю, объявленному в пределах класса Test
. Код в методах экземпляра класса Test
может обратиться к переменной экземпляра x
из класса Point
как super.x
.Код, который использует выражение доступа к полю, чтобы получить доступ к полю x
получит доступ к названному полю x
в классе, обозначенном типом ссылочного выражения. Таким образом, выражение sample.x
доступы a double
значение, переменная экземпляра объявляется в классе 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
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
окажись, иметь тот же самый тип, и то же самое неизменное значение не влияет на это суждение.
поля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
.
MethodModifiers описываются в §8.4.3, пункте TypeParameters метода в §8.4.4, пункте Бросков в §8.4.6, и MethodBody в §8.4.7. Объявление метода или определяет тип имеющий значение, что метод возвращает или использует ключевое словоMethodDeclaration: MethodHeader MethodBody MethodHeader: MethodModifiersopt TypeParametersopt ResultType MethodDeclarator Throwsopt ResultType: Type void MethodDeclarator: Identifier ( FormalParameterListopt )
void
указать, что метод не возвращает значение.Идентификатор в MethodDeclarator может использоваться на имя, чтобы обратиться к методу. Класс может объявить метод с тем же самым именем как класс или поле, задействованный класс или задействованный интерфейс класса, но этому обескураживают как syle.
Для совместимости с более старыми версиями платформы Java форме объявления для метода, который возвращает массив, позволяют поместить (некоторые или весь из) пустых пар скобки, которые формируют объявление типа массива после списка параметров. Это поддерживается устаревающим производством:
но не должен использоваться в новом коде.MethodDeclarator: MethodDeclarator [ ]
Это - ошибка времени компиляции для тела класса, чтобы объявить как элементы два метода с эквивалентными переопределению подписями (§8.4.2) (имя, число параметров, и типы любых параметров). У методов и полей может быть то же самое имя, так как они используются в различных контекстах и снимаются неоднозначность различными процедурами поиска (§6.5).
final
модификатор и/или одна или более аннотаций (§9.7)) и идентификатор (дополнительно сопровождаемый скобками), который определяет имя параметра. Последний формальный параметр в списке является особенным; это может быть переменный параметр арности, обозначенный elipsis после типа:
Следующее повторяется от §8.3, чтобы сделать представление здесь более четким:FormalParameterList: LastFormalParameter FormalParameters , LastFormalParameter FormalParameters: FormalParameter FormalParameters , FormalParameter FormalParameter: VariableModifiers Type VariableDeclaratorId VariableModifiers: VariableModifier VariableModifiers VariableModifier VariableModifier: one of final Annotation LastFormalParameter: VariableModifiers Type...opt VariableDeclaratorId FormalParameter
Если у метода или конструктора нет никаких параметров, только пустая пара круглых скобок появляется в объявлении метода или конструктора.VariableDeclaratorId: Identifier VariableDeclaratorId [ ]
Если у двух формальных параметров того же самого метода или конструктора, как объявляют, есть то же самое имя (то есть, их объявления упоминают тот же самый Идентификатор), то ошибка времени компиляции происходит.
Если аннотация на формальном параметре соответствует типу T аннотации, и T имеет (мета-) аннотация м., который соответствует annotation.Target
, тогда у м. должен быть элемент, значение которого annotation.ElementType.PARAMETER
, или ошибка времени компиляции происходит. Модификаторы аннотации описываются далее в §9.7.
Это - ошибка времени компиляции, если параметр метода или конструктора, который объявляется финалом, присваивается в пределах тела метода или конструктора.
Когда метод или конструктор вызываются (§15.12), значения фактических выражений параметра инициализируют недавно создаваемые переменные параметра, каждый объявленный Тип, перед выполнением тела метода или конструктора. Идентификатор, который появляется в DeclaratorId, может использоваться в качестве простого имени в теле метода или конструктора, чтобы обратиться к формальному параметру.
Если последний формальный параметр является переменным параметром арности типа T, это, как полагают, определяет формальный параметр типа T []. Метод является тогда переменным методом арности. Иначе, это - фиксированный метод арности. Вызовы переменного метода арности могут содержать более фактические выражения параметра чем формальные параметры. Все фактические выражения параметра, которые не соответствуют формальным параметрам, предшествующим переменному параметру арности, будут оценены и результаты, сохраненные в массив, который передадут к вызову метода (§15.12.4.2).
Контекст параметра метода (§8.4.1) или конструктор (§8.8.1) является всем телом метода или конструктора.
Эти названия параметра не могут быть повторно объявлены как локальные переменные метода, или как параметры исключения пунктов выгоды в операторе попытки метода или конструктора. Однако, параметр метода или конструктора может быть затенен где угодно в объявлении класса, вложенном в пределах того метода или конструктора. Такое вложенное объявление класса могло объявить или локальный класс (§14.3) или анонимный класс (§15.9).
Формальные параметры не упоминаются только использование простых имен, никогда при использовании полностью определенных имен (§6.6).
Метод или параметр конструктора типа float
всегда содержит элемент набора значений плавающего (§4.2.3); точно так же метод или параметр конструктора типа double
всегда содержит элемент двойного набора значений. Это не разрешается для метода или параметра конструктора типа float
чтобы содержать элемент набора значений "пускают в ход расширенную экспоненту", которая не является также элементом набора значений плавающего, ни для параметра метода типа double
чтобы содержать элемент набора значений "удваивают расширенную экспоненту", которая не является также элементом двойного набора значений.
Где фактическое выражение параметра, соответствующее переменной параметра, не строго FP (§15.4), оценке того фактического выражения параметра разрешают использовать промежуточные значения, оттянутые из соответствующих наборов значений расширенной экспоненты. До того, чтобы быть сохраненным в переменной параметра результат такого выражения отображается на самое близкое значение в соответствующем стандартном наборе значений преобразованием вызова метода (§5.3).
У двух методов есть та же самая подпись, если у них есть то же самое имя и типы параметра.
Два метода или объявления конструктора, у М. и N есть те же самые типы параметра, если все следующие условия содержат:
Обсуждение
Понятие подподписи, определенной здесь, разрабатывается, чтобы выразить отношение между двумя методами, подписи которых не идентичны, но в котором может переопределить другой.Определенно, это позволяет метод, подпись которого не использует универсальные типы, чтобы переопределить любую generified версию того метода. Это важно так, чтобы разработчики библиотеки могли свободно generify методы независимо от клиентов, которые определяют подклассы или подынтерфейсы библиотеки.
Теперь, предположите, что этот код был записан перед введением genericity, и теперь автором классаclass CollectionConverter { List toList(Collection c) {...} } class Overrider extends CollectionConverter{ List toList(Collection c) {...} }
CollectionConverter
решает к generify код, таким образом:
Без специального разрешения,class CollectionConverter { <T> List<T> toList(Collection<T> c) {...} }
Overrider.toList()
больше не переопределил бы CollectionConverter.toList()
. Вместо этого код был бы недопустим. Это значительно запретило бы использование genericity, так как писатели библиотеки будут смущаться перемещать существующий код.
Два сигнатур методов m1 и m2 являются эквивалентной переопределению эквивалентностью или m1 является подподписью m2 или m2, подподпись m1.
Пример:
вызывает ошибку времени компиляции, потому что она объявляет два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
.Модификаторы доступаMethodModifiers: MethodModifier MethodModifiers MethodModifier MethodModifier: one of Annotation public protected private abstract static final synchronized native strictfp
public
, protected
, и private
обсуждаются в §6.6. Ошибка времени компиляции происходит, если тот же самый модификатор появляется не раз в объявлении метода, или если у объявления метода есть больше чем один из модификаторов доступа public
, protected
, и private
. Ошибка времени компиляции происходит, если объявление метода, которое содержит ключевое слово abstract
также содержит любое из ключевых слов private
, static
, final
, native
, strictfp
, или synchronized
. Ошибка времени компиляции происходит, если объявление метода, которое содержит ключевое слово native
также содержит strictfp
.
Если аннотация на объявлении метода соответствует типу T аннотации, и T имеет (мета-) аннотация м., который соответствует annotation.Target
, тогда у м. должен быть элемент, значение которого annotation.ElementType.METHOD
, или ошибка времени компиляции происходит. Аннотации обсуждаются далее в §9.7.
Если два или больше модификатора метода появляются в объявлении метода, это общепринято, хотя не требуемый, что они появляются в порядке, непротиворечивом с показанным выше в производстве для MethodModifier.
abstract
объявление метода представляет метод, поскольку элемент, обеспечивая его подпись (§8.4.2), возвращает тип, и throws
пункт (если кто-либо), но не обеспечивает реализацию. Объявление abstract
метод м. должен появиться непосредственно в пределах abstract
класс (вызывают это A), если это не происходит в пределах перечисления (§8.9); иначе ошибка времени компиляции заканчивается. Каждый подкласс, который не является abstract
должен обеспечить реализацию для м., или ошибка времени компиляции происходит как определено в §8.1.1.1.
Это - ошибка времени компиляции для a private
метод, который будет объявлен abstract
.
Для подкласса было бы невозможно реализовать a private
abstract
метод, потому что private
методы не наследованы подклассами; поэтому такой метод никогда не мог использоваться.
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 { public 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 } }
static
вызывается методом класса. Метод класса всегда вызывается независимо от определенного объекта. Попытка сослаться на текущий объект, используя ключевое слово this
или ключевое слово super
или ссылаться на параметры типа любого окружающего объявления в теле метода класса приводит к ошибке времени компиляции. Это - ошибка времени компиляции для a static
метод, который будет объявлен abstract
.
Метод, который не объявляется static
вызывается методом экземпляра, и иногда вызывается не -static
метод. Метод экземпляра всегда вызывается относительно объекта, который становится текущим объектом к который ключевые слова this
и super
отнеситесь во время выполнения тела метода.
final
препятствовать тому, чтобы подклассы переопределили или скрыли это. Это - ошибка времени компиляции, чтобы попытаться переопределить или скрыть a final
метод.
A private
метод и все методы, объявленные сразу в пределах a final
класс (§8.1.1.2) ведет себя, как будто они 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
будет также обновлен.
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;}
strictfp
модификатор должен сделать все float
или double
выражения в пределах тела метода быть явно строгим FP (§15.4).synchronized
метод получает монитор (§17.1) прежде, чем он выполнится. Для класса (static)
метод, монитор, связанный с Class
объект для класса метода используется. Для метода экземпляра, монитор, связанный с this
(объект, для которого был вызван метод) используется. Они - те же самые блокировки, которые могут использоваться synchronized
оператор (§14.19); таким образом, код:
имеет точно тот же самый эффект как: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
имеет переменную экземпляра boxContents
это может содержать ссылку на любой объект. Можно поместить объект в a Box
вызывая put
, который возвращается false
если поле уже полно. Можно вытащить что-то из a Box
вызывая get
, который возвращает нулевую ссылку, если поле пусто.Если put
и get
не были synchronized
, и два потока выполняли методы для того же самого экземпляра Box
одновременно, тогда код мог неправильно себя вести. Это могло бы, например, потерять след объекта потому что два вызова к put
произошедший одновременно.
Контекст параметра типа метода является всем объявлением метода, включая раздел параметра типа непосредственно. Поэтому, введите параметры, может появиться как части их собственных границ, или как границы других параметров типа, объявленных в том же самом разделе.
Параметры типа универсальных методов не должны быть обеспечены явно, когда универсальный метод вызывается. Вместо этого они почти всегда выводятся как определено в §15.12.2.7
void
. Объявление метода d1 с типом R1 возврата является return-type-substitutable для другого метода d2 с типом R2 возврата, если и только если следующие условия содержат:
void
тогда R2 void
.
Обсуждение
Понятие замещаемости типа возврата суммирует пути, по которым типы возврата могут измениться среди методов, которые переопределяют друг друга.
Отметьте, что это определение поддерживает ковариантные возвраты - то есть, специализация типа возврата к подтипу (но только для ссылочных типов).
Также отметьте, что преобразования непроверенные позволяются также. Это необоснованно, и требует предупреждения непроверенного всякий раз, когда оно используется; это - специальный допуск, делается позволить гладкую миграцию от неуниверсального до универсального кода.
Ошибка времени компиляции происходит если любой ExceptionType, упомянутый в aThrows: throws ExceptionTypeList ExceptionTypeList: ExceptionType ExceptionTypeList , ExceptionType ExceptionType: ClassType TypeVariable
throws
пункт не является подтипом (§4.10) Throwable
. Это разрешается, но не требуется упоминать другие исключения (непроверенные) в a throws
пункт.
Для каждого проверенного исключения, которое может следовать из выполнения тела метода или конструктора, происходит ошибка времени компиляции, если тот тип исключения или супертип того типа исключения не упоминаются в a throws
пункт в объявлении метода или конструктора.
Требование, чтобы объявить проверенные исключения позволяет компилятору гарантировать, что код для того, чтобы обработать такие состояния ошибки был включен. Методы или конструкторы, которые не в состоянии обработать исключительные условия, брошенные как проверенные исключения, будут обычно приводить к ошибке времени компиляции из-за нехватки надлежащего исключения, вводят a throws
пункт. Язык программирования Java таким образом поощряет стиль программирования, где редкий и иначе действительно исключительные условия документируются таким образом.
Error
, например OutOfMemoryError
, бросаются из-за отказа в или виртуальной машины. Многие из них являются результатом отказов редактирования и могут произойти в непредсказуемых точках в выполнении программы. Сложные программы могут все же хотеть поймать и попытаться восстановиться с некоторых из этих условий.
RuntimeException
, например NullPointerException
, следствие выполненного - проверки целостности времени и бросается или непосредственно из программы или в библиотечных подпрограммах. Это выходит за рамки языка программирования Java, и возможно вне состояния искусства, чтобы включать достаточную информацию в программу, чтобы уменьшить до управляемого числа места, где они, как могут доказывать, не происходят. abstract
методы, определенные в интерфейсах, как могут объявлять, не выдают более проверенные исключения чем переопределенный или скрытый метод.
Более точно предположите, что B является классом или интерфейсом, и A является суперклассом или суперинтерфейсом B, и объявление метода n в B переопределяет или скрывает объявление метода м. в A. Если у n есть a throws
у пункта, который упоминает любые проверенные типы исключения, тогда м., должен быть a throws
пункт, и для каждого проверенного типа исключения, перечисленного в throws
пункт n, того же самого класса исключений или одного из его супертипов должен произойти в стирании throws
пункт м.; иначе, ошибка времени компиляции происходит.
Если нестертый throws
пункт м. не содержит супертип каждого исключения, вводят throws
пункт n, предупреждение непроверенное должно быть выпущено.
Обсуждение
См. §11 для получения дополнительной информации об исключениях и большом примере.Переменные типа позволяются в списках бросков даже при том, что им не позволяют в пунктах выгоды.
interface PrivilegedExceptionAction<E extends Exception> { void run() throws E; } class AccessController { public static <E extends Exception> Object doPrivileged(PrivilegedExceptionAction<E> action) throws E { ... } } class Test { public static void main(String[] args) { try { AccessController.doPrivileged( new PrivilegedExceptionAction<FileNotFoundException>() { public void run() throws FileNotFoundException {... delete a file ...} }); } catch (FileNotFoundException f) {...} // do something } }
abstract
(§8.4.3.1) или native
(§8.4.3.4).
Ошибка времени компиляции происходит, если объявление метода такжеMethodBody: Block ;
abstract
или native
и имеет блок для его тела. Ошибка времени компиляции происходит, если объявление метода ни один не abstract
ни native
и имеет точку с запятой для ее тела.Если реализация должна быть обеспечена для объявленного метода void
, но реализация не требует никакого исполняемого кода, тело метода должно быть записано как блок, который не содержит операторов:"{ }
".
void
, тогда его тело не должно содержать никого return
оператор (§14.17), у которого есть Выражение.
Если у метода, как объявляют, есть тип возврата, то каждый return
у оператора (§14.17) в его теле должно быть Выражение. Ошибка времени компиляции происходит, если тело метода может обычно завершаться (§14.1).
Другими словами метод с типом возврата должен возвратиться только при использовании оператора возврата, который обеспечивает возврат значения; не позволяется "понизиться конец своего тела."
Отметьте, что для метода возможно иметь объявленный тип возврата и все же не содержать операторы возврата. Вот один пример:
class DizzyDean { int pitch() { throw new RuntimeException("90 mph?!"); } }
abstract
или не) суперкласса и суперинтерфейсов, которые общедоступны, защищаются или объявленные с доступом по умолчанию в том же самом пакете как C и ни не переопределяются (§8.4.8.1), ни скрываются (§8.4.8.2) объявлением в классе.
abstract
, тогда m1, как говорят, реализует любого и все объявления abstract
методы, которые это переопределяет.
Обсуждение
Подпись метода переопределения может отличаться от переопределенного, если у формального параметра в одном из методов есть необработанный тип, в то время как у соответствующего параметра в другом есть параметризованный тип.
Правила позволяют подписи метода переопределения отличаться от переопределенного, размещать миграцию существующего ранее кода, чтобы использовать в своих интересах genericity. См. раздел §8.4.2 для дальнейшего анализа.
Ошибка времени компиляции происходит, если метод экземпляра переопределяет a static
метод.
В этом отношении переопределение методов отличается от сокрытия полей (§8.3), поскольку это допустимо для переменной экземпляра, чтобы скрыть a static
переменная.
К переопределенному методу можно получить доступ при использовании выражения вызова метода (§15.12), который содержит ключевое слово super
. Отметьте, что полностью определенное имя или бросок к супертипу класса не эффективны при попытке получить доступ к переопределенному методу; в этом отношении переопределение методов отличается от сокрытия полей. См. §15.12.4.9 для обсуждения и примеров этой точки.
strictfp
модификатор не имеет абсолютно никакого эффекта на правила для того, чтобы переопределить методы и реализовать абстрактные методы. Например, это разрешается для метода, который не строг FP, чтобы переопределить строгий FP метод, и разрешается для строгого FP метода переопределить метод, который не строг FP.static
м. метода, тогда объявление, м., как говорят, скрывает любой метод м.', где подпись м. является подподписью (§8.4.2) подписи м.', в суперклассах и суперинтерфейсах класса, который иначе был бы доступен, чтобы кодировать в классе. Ошибка времени компиляции происходит если a static
метод скрывает метод экземпляра.В этом отношении сокрытие методов отличается от сокрытия полей (§8.3), поскольку это допустимо для a static
переменная, чтобы скрыть переменную экземпляра. Сокрытие также отлично от затенения (§6.3.1) и затеняющий (§6.3.2).
К скрытому методу можно получить доступ при использовании полностью определенного имени или при использовании выражения вызова метода (§15.12), который содержит ключевое слово super
или бросок к супертипу класса. В этом отношении сокрытие методов подобно сокрытию полей.
У объявления метода не должно быть a throws
пункт, который конфликтует (§8.4.6) с тем из любого метода, который это переопределяет или скрывает; иначе, ошибка времени компиляции происходит.
Обсуждение
Правила выше учитывают ковариантные типы возврата - совершенствование типа возврата метода, переопределяя это.
Например, следующие объявления являются законными, хотя они были недопустимы в предыдущих версиях языка программирования Java:
Ослабленное правило для того, чтобы переопределить также позволяет ослаблять условия на абстрактных классах, реализовывая интерфейсы.class C implements Cloneable { C copy() { return (C)clone(); } } class D extends C implements Cloneable { D copy() { return (D)clone(); } }
Обсуждение
Рассмотреть
и предположите, что кто-то разделяет на подклассыclass StringSorter { // takes a collection of strings and converts it to a sortedlist List toList(Collection c) {...} }
StringCollector
Теперь, в некоторый момент авторclass Overrider extends StringSorter{ List toList(Collection c) {...} }
StringSorter
решает к generify код
Предупреждение непроверенное было бы дано, компилируяclass StringSorter { // takes a collection of strings and converts it to a list List<String> toList(Collection<String> c) {...} }
Overrider
против нового определения StringSorter
потому что тип возврата Overrider.toList()
List
, который не является подтипом типа возврата переопределенного метода, List<String
.
В этих отношениях переопределение методов отличается от сокрытия полей (§8.3), поскольку это допустимо для поля, чтобы скрыть поле другого типа.
Это - ошибка времени компиляции, если описание типа T имеет задействованный метод m1 и там существует метод m2, объявленный в T или супертипе T так, что, все следующие условия содержат:
Обсуждение
Эти ограничения необходимы, потому что обобщения реализуются через стирание. Правило выше подразумевает, что у методов, объявленных в том же самом классе с тем же самым именем, должны быть различные стирания. Это также подразумевает, что описание типа не может реализовать или расширить два отличных вызова того же самого универсального интерфейса. Вот некоторые дальнейшие примеры.
Класс не может иметь двух задействованных методов с тем же самым именем и ввести стирание.
Это недопустимо с тех порclass C<T> { T id (T x) {...} } class D extends C<String> { Object id(Object x) {...} }
D.id(Object)
элемент D
, C<String>.id(String)
объявляется в супертипе D
и:
id
C<String>.id(String
) доступно для D
D.id(Object)
не подподпись того из C<String>.id(String)
Обсуждение
Два различных метода класса, возможно, не переопределяют методы с тем же самым стиранием.
Это также недопустимо с тех порclass C<T> { T id (T x) {...} } interface I<T> { Tid(T x); } class D extends C<String> implements I<Integer> { String id(String x) {...} Integer id(Integer x) {...} }
D.id(String)
элемент D
, D.id(Integer)
объявляется в D
и:
id
D.id(Integer)
доступно для D
D.id(String)
переопределения C<String>.id(String)
и D.id(Integer)
переопределения I.id(Integer)
все же у двух переопределенных методов есть то же самое стирание
public
, тогда переопределение или сокрытие метода должны быть public
; иначе, ошибка времени компиляции происходит.
protected
, тогда переопределение или сокрытие метода должны быть protected
или public
; иначе, ошибка времени компиляции происходит.
private
; иначе, ошибка времени компиляции происходит. Отметьте это a private
метод не может быть скрыт или переопределен в техническом смысле тех сроков. Это означает, что подкласс может объявить метод с той же самой подписью как a private
метод в одном из его суперклассов, и нет никакого требования что тип возврата или throws
пункт такого метода имеет любое отношение к таковым private
метод в суперклассе.
Это - ошибка времени компиляции, если класс C наследовал конкретный метод, подписи которого подподпись другого конкретного метода, наследованного C.
Обсуждение
Это может произойти, если суперкласс является параметрическим, и у него есть два метода, которые были отличны в универсальном объявлении, но имеют ту же самую подпись в определенном используемом вызове.
Иначе, есть два возможных случая:
abstract
, тогда есть два подслучая: abstract
static
, ошибка времени компиляции происходит.
abstract
как полагают, переопределяет, и поэтому реализует, все другие методы от имени класса, который наследовал это. Если подпись неабстрактного метода не является подподписью каждого из других наследованных методов, предупреждение непроверенное должно быть выпущено (если не подавлено (§9.6.1.5)). Ошибка времени компиляции также происходит, если тип возврата неабстрактного метода не является типом возврата substitutable (§8.4.5) для каждого из других наследованных методов. Если тип возврата неабстрактного метода не является подтипом типа возврата какого-либо из других наследованных методов, предупреждение непроверенное должно быть выпущено. Кроме того ошибка времени компиляции происходит, если наследованный метод, который не является abstract
имеет a throws
пункт, который конфликтует (§8.4.6) с тем из любых других из наследованных методов. abstract
, тогда класс обязательно abstract
класс и, как полагают, наследовался весь abstract
методы. Ошибка времени компиляции происходит, если для каких-либо двух таких наследованных методов один из методов не является типом возврата substitutable для другого ( throws
пункты не вызывают ошибки в этом случае.) throws
пункты двух методов с тем же самым именем, если их подписи не эквивалентны переопределению.Методы переопределяются на основе подписи подписью.
Если, например, класс объявляет два public
методы с тем же самым именем, и подкласс переопределяют одного из них, подкласс все еще наследовал другой метод.
Когда метод вызывается (§15.12), число фактических параметров (и любых явных параметров типа) и типы времени компиляции параметров используется, во время компиляции, чтобы определить подпись метода, который будет вызван (§15.12.2). Если метод, который должен быть вызван, будет методом экземпляра, то фактический метод, который будет вызван, будет определен во время выполнения, используя динамический поиск метода (§15.12.4).
класс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
.
класс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.
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
.
Здесь методы переопределения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
, независимо от того, что тип переменной мы можем использовать, чтобы содержать ссылку на объект. Таким образом мы видим, что поля и методы ведут себя по-другому: сокрытие отличается от переопределения.
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
, выяснять, во время выполнения, который метод экземпляра вызвать.
Этот пример производит вывод: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.
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
.
Если класс объявляет тип элемента с определенным именем, то объявление того типа, как говорят, скрывает любого и все доступные объявления типов элемента с тем же самым именем в суперклассах и суперинтерфейсах класса.
В пределах класса C, объявления d типа элемента, названного n тенями объявления любых других типов, названных n, которые находятся в контексте в точке, где d происходит.
Если задействованный класс или интерфейс, объявленный с простым именем C, непосредственно включаются в пределах объявления класса с полностью определенным именем N, то у задействованного класса или интерфейса есть полностью определенное имя Северная Каролина. Класс наследовал от его прямого суперкласса и прямых суперинтерфейсов все типы нечлена парламента, не занимающего официального поста суперкласса и суперинтерфейсов, которые и доступны, чтобы кодировать в классе и не скрытые объявлением в классе.
Класс может наследовать два или больше описания типа с тем же самым именем, или от двух интерфейсов или от его суперкласса и интерфейса. Ошибка времени компиляции происходит на любой попытке обратиться к любому двусмысленно наследованному классу или интерфейсу его простым именем
Если то же самое описание типа наследовано от интерфейса разнообразными путями, класс или интерфейс, как полагают, наследованы только однажды. Это может быть упомянуто его простым именем без неоднозначности.
public
, protected
, и private
обсуждаются в §6.6. Ошибка времени компиляции происходит, если у объявления типа элемента есть больше чем один из модификаторов доступа public
, protected
, и private
. У объявлений типа элемента может быть аннотация modifers точно так же как любой тип или задействованное объявление.
static
ключевое слово может изменить объявление типа элемента C в пределах тела невнутреннего класса T. Его эффект состоит в том, чтобы объявить, что C не является внутренним классом. Так же, как у статического метода T нет никакого текущего экземпляра T в его теле, C также не имеет никакого текущего экземпляра T, и при этом у этого нет никаких лексически экземпляров включения.
Это - ошибка времени компиляции если a static
класс содержит использование не -static
элемент класса включения.
Задействованные интерфейсы всегда неявно static
. Это разрешается, но не требуется для объявления задействованного интерфейса явно перечислять static
модификатор.
Это - ошибка времени компиляции, если инициализатор экземпляра именованного класса может выдать проверенное исключение, если то исключение или один из его супертипов явно не объявляются вInstanceInitializer: Block
throws
у пункта каждого конструктора его класса и класса есть по крайней мере один явно объявленный конструктор. Инициализатор экземпляра в анонимном классе (§15.9.5) может выдать любые исключения. Правила выше различают инициализаторы экземпляра в именованных и анонимных классах. Это различие является преднамеренным. Данный анонимный класс только инстанцируют в единственной точке в программе. Поэтому возможно непосредственно распространить информацию о том, какие исключения могли бы быть повышены инициализатором экземпляра анонимного класса до окружающего выражения. Названный классами, с другой стороны, может быть инстанцирован во многих местах. Поэтому единственный способ распространить информацию о том, какие исключения могли бы быть повышены инициализатором экземпляра именованного класса, через пункты бросков его конструкторов. Из этого следует, что более либеральное правило может использоваться в случае анонимных классов. Подобные комментарии применяются к инициализаторам переменной экземпляра.
Это - ошибка времени компиляции, если инициализатор экземпляра не может обычно завершаться (§14.21). Если оператор возврата (§14.17) появляется где-нибудь в пределах инициализатора экземпляра, то ошибка времени компиляции происходит.Использование переменных экземпляра, объявления которых появляются дословно после использования, иногда ограничивается, даже при том, что эти переменные экземпляра находятся в контексте. См. §8.3.2.3 для точных правил, управляющих ссылкой вперед на переменные экземпляра.
Инициализаторам экземпляра разрешают отослать к текущему объекту этот (§15.8.3) к любым переменным типа (§4.4) в контексте и использовать ключевое слово super
(§15.11.2, §15.12).
Это - ошибка времени компиляции для статического инициализатора, чтобы быть в состоянии завершиться резко (§14.1, §15.6) с проверенным исключением (§11.2). Это - ошибка времени компиляции, если статический инициализатор не может обычно завершаться (§14.21).StaticInitializer: static Block
Статические инициализаторы и инициализаторы переменной класса выполняются в текстовом порядке.
Использование переменных класса, объявления которых появляются дословно после использования, иногда ограничивается, даже при том, что эти переменные класса находятся в контексте. См. §8.3.2.3 для точных правил, управляющих ссылкой вперед на переменные класса.
Если a return
оператор (§14.17) появляется где угодно в пределах статического инициализатора, затем ошибка времени компиляции происходит.
Если ключевое слово this
(§15.8.3) или любая переменная типа (§4.4) определенный вне инициализатора или ключевого слова super
(§15.11, §15.12), появляется где угодно в пределах статического инициализатора, затем ошибка времени компиляции происходит.
SimpleTypeName в ConstructorDeclarator должен быть простым именем класса, который содержит объявление конструктора; иначе ошибка времени компиляции происходит. Во всех других отношениях, взгляды объявления конструктора точно так же как объявление метода, у которого нет никакого типа результата.ConstructorDeclaration: ConstructorModifiersopt ConstructorDeclarator Throwsopt ConstructorBody ConstructorDeclarator: TypeParametersopt SimpleTypeName ( FormalParameterListopt )
Вот простой пример:
Конструкторы вызываются выражениями создания экземпляра класса (§15.9) преобразованиями и связями, вызванными оператором конкатенации строк + (§15.18.1), и явными вызовами конструктора от других конструкторов (§8.8.7). Конструкторы никогда не вызываются выражениями вызова метода (§15.12).class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } }
Доступом к конструкторам управляют модификаторы доступа (§6.6).
Это полезно, например, в предотвращении инстанцирования, объявляя недоступного конструктора (§8.8.10).
Объявления конструктора не являются элементами. Они никогда не наследованы и поэтому не подвергаются сокрытию или переопределению.Модификаторы доступаConstructorModifiers: ConstructorModifier ConstructorModifiers ConstructorModifier ConstructorModifier: one of Annotation public protected private
public
, protected
, и private
обсуждаются в §6.6. Ошибка времени компиляции происходит, если тот же самый модификатор появляется не раз в объявлении конструктора, или если у объявления конструктора есть больше чем один из модификаторов доступа public
, protected
, и private
.
Если никакой модификатор доступа не определяется для конструктора нормального класса, у конструктора есть доступ по умолчанию. Если никакой модификатор доступа не определяется для конструктора перечислимого типа, конструктор private
. Это - ошибка времени компиляции, если конструктор перечислимого типа (§8.9) объявляется public
или protected
.
Если аннотация на конструкторе соответствует типу T аннотации, и T имеет (мета-) аннотация м., который соответствует annotation.Target
, тогда у м. должен быть элемент, значение которого annotation.ElementType.CONSTRUCTOR
, или ошибка времени компиляции происходит. Аннотации далее обсуждаются в §9.7.
В отличие от методов, конструктор не может быть abstract
, static
, final
, native
, strictfp
, или synchronized
. Конструктор не наследован, таким образом нет никакой потребности объявить это final
и abstract
конструктор никогда не мог реализовываться. Конструктор всегда вызывается относительно объекта, таким образом, не имеет никакого смысла для конструктора быть static
. Нет никакой практической потребности в конструкторе быть synchronized
, потому что это заблокировало бы объект, в стадии строительства, который обычно не делается доступным для других потоков, пока все конструкторы для объекта не завершили свою работу. Нехватка native
конструкторы являются произвольным проектным решением языка, которое облегчает для реализации виртуальной машины Java проверять, что конструкторы суперкласса всегда должным образом вызываются во время объектного создания.
Отметьте, что ConstructorModifier не может быть объявлен strictfp
. Этим различием в определениях для ConstructorModifier и MethodModifier (§8.4.3) является намеренное проектное решение языка; это эффективно гарантирует, что конструктор строг FP (§15.4), если и только если его класс строг FP.
Контекст параметра типа конструктора является всем объявлением конструктора, включая раздел параметра типа непосредственно. Поэтому, введите параметры, может появиться как части их собственных границ, или как границы других параметров типа, объявленных в том же самом разделе.
Параметры типа универсального конструктора не должны быть обеспечены явно, когда универсальный конструктор вызывается. Когда им не обеспечивают, они выводятся как определено в §15.12.2.7.
throws
пункт для конструктора идентичен в структуре и поведении к throws
пункт для метода (§8.4.6).
Это - ошибка времени компиляции для конструктора, чтобы прямо или косвенно вызвать себя через серию одного или более явного включения вызовов конструктораConstructorBody: { ExplicitConstructorInvocationopt BlockStatementsopt }
this
. Если конструктор является конструктором для перечислимого типа (§8.9), это - ошибка времени компиляции для этого, чтобы вызвать конструктора суперкласса явно.
Если тело конструктора не начинается с явного вызова конструктора, и объявляемый конструктор не является частью исконного класса Object
, тогда тело конструктора, как неявно предполагает компилятор, начинается с вызова конструктора суперкласса"super();
", вызов конструктора его прямого суперкласса, который не берет параметров.
За исключением возможности явных вызовов конструктора, тело конструктора походит на тело метода (§8.4.7). A return
оператор (§14.17) может использоваться в теле конструктора, если это не включает выражение.
В примере:
первый конструктор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 описывают создание и инициализацию новых экземпляров класса.
ExplicitConstructorInvocation: NonWildTypeArgumentsopt this ( ArgumentListopt ) ; NonWildTypeArgumentsopt super ( ArgumentListopt ) ; Primary. NonWildTypeArgumentsopt super ( ArgumentListopt ) ; NonWildTypeArguments: < ReferenceTypeList > ReferenceTypeList: ReferenceType ReferenceTypeList , ReferenceType
Явные операторы вызова конструктора могут быть разделены на два вида:
super
(возможно снабженный предисловием с явными параметрами типа).
Вот пример квалифицированного вызова конструктора суперкласса:
Явный оператор вызова конструктора в теле конструктора, возможно, не обращается ни к каким переменным экземпляра или методам экземпляра, объявленным в этом классе или никаком суперклассе, или использовании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); }
Явный оператор вызова конструктора может бросить эквивалентность типа E исключения также:
Если выражение создания экземпляра анонимного класса появляется в пределах явного оператора вызова конструктора, то анонимный класс, возможно, не обращается ни к одному из экземпляров включения класса, конструктор которого вызывается.
Например:
Позвольте C быть классом, который инстанцируют, позволять S быть прямым суперклассом C, и позволять мне быть создаваемым экземпляром. Оценка явного вызова конструктора продолжается следующим образом: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 } } }
.super
"оценивается. Если основное выражение оценивает к null
, a NullPointerException
повышается, и вызов конструктора суперкласса завершается резко. Иначе, результатом этой оценки является сразу экземпляр включения меня относительно S. Позвольте O быть сразу лексически включающим классом S; это - ошибка времени компиляции, если тип p не является O или подклассом O.
this
.
this
.
Object
, тогда у конструктора по умолчанию есть пустое тело.
Конструктор по умолчанию имеет нет throws
пункт.
Из этого следует, что, если у nullary конструктора суперкласса есть a throws
пункт, затем ошибка времени компиляции произойдет.
private
. Иначе, если класс объявляется public
, тогда конструктору по умолчанию неявно дают модификатор доступа public
(§6.6); если класс объявляется protected
, тогда конструктору по умолчанию неявно дают модификатор доступа protected
(§6.6); если класс объявляется private
, тогда конструктору по умолчанию неявно дают модификатор доступа private
(§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
доступно, его конструктор по умолчанию не.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
, в котором это объявляется.
Тело перечислимого типа может содержать перечислимые константы. Перечислимая константа определяет экземпляр перечислимого типа. У перечислимого типа нет никаких экземпляров кроме определенных его перечислимыми константами.EnumDeclaration: ClassModifiersopt enum Identifier Interfacesopt EnumBody EnumBody: { EnumConstantsopt ,opt EnumBodyDeclarationsopt }
Обсуждение
Это - ошибка времени компиляции, чтобы попытаться явно инстанцировать перечислимого типа (§15.9.1). Заключительный метод клона в Перечислении гарантирует, что перечислимые константы никогда не могут клонироваться, и специальный режим механизмом сериализации гарантирует, что двойные экземпляры никогда не создаются в результате десериализации. Отражающее инстанцирование перечислимых типов запрещается. Вместе, эти четыре вещи гарантируют, что никакие экземпляры перечислимого типа не существуют вне определенных перечислимыми константами.
Поскольку есть только один экземпляр каждой перечислимой константы, допустимо использовать == оператор вместо equals
метод, сравнивая две ссылки на объект, если известно, что по крайней мере один из них обращается к перечислимой константе. ( equals
метод в Enum
заключительный метод, который просто вызывает super.equals
на его параметре и возвратах результат, таким образом выполняя сравнение идентификационных данных.)
Перечислимой константе может предшествовать аннотация (§9.7) модификаторы. Если аннотация на перечислимой константе соответствует типу T аннотации, и T имеет (мета-) аннотация м., который соответствуетEnumConstants: EnumConstant EnumConstants , EnumConstant EnumConstant: Annotations Identifier Argumentsopt ClassBodyopt Arguments: ( ArgumentListopt ) EnumBodyDeclarations: ; ClassBodyDeclarationsopt
annotation.Target
, тогда у м. должен быть элемент, значение которого annotation.ElementType.FIELD
, или ошибка времени компиляции происходит.
Перечислимая константа может сопровождаться параметрами, которые передают конструктору перечислимого типа, когда константа создается во время инициализации класса как описано позже в этом разделе. Конструктор, который будет вызван, выбирается, используя нормальные правила перегрузки (§15.12.2). Если параметры опускаются, пустой список параметров принимается. Если у перечислимого типа нет никаких объявлений конструктора, parameterless конструктор по умолчанию обеспечивается (который соответствует неявный пустой список параметров). Этот конструктор по умолчанию private
.
Дополнительное тело класса перечислимой константы неявно определяет объявление анонимного класса (§15.9.5), который расширяет сразу тип перечисления включения. Телом класса управляют обычные правила анонимных классов; в особенности это не может содержать конструкторов.
Обсуждение
Методы экземпляра, объявленные в этих телах класса, может быть вызван вне типа перечисления включения, только если они переопределяют доступные методы в типе перечисления включения.
Перечислимые типы (§8.9) не должны быть объявлены кратким обзором; выполнение так приведет к ошибке времени компиляции. Это - ошибка времени компиляции для перечислимого типа E, чтобы иметь абстрактный метод м. как элемент, если E не имеет один или более перечислимые константы, и у всех перечислимых констант Э есть тела класса, которые обеспечивают конкретные реализации м. Это - ошибка времени компиляции для тела класса перечислимой константы, чтобы объявить абстрактный метод.
Перечислимый тип неявно final
если это не содержит по крайней мере одну перечислимую константу, у которой есть тело класса. В любом случае это - ошибка времени компиляции, чтобы явно объявить, что перечислимый тип final
.
Вложенные перечислимые типы неявно static
. Это - permissable, чтобы явно объявить, что вложенный перечислимый тип static
.
Обсуждение
Это подразумевает, что невозможно определить локальное (§14.3) перечисление, или определить перечисление во внутреннем классе (§8.1.3).
Любой конструктор или задействованные объявления в пределах перечислимого объявления применяются к перечислимому типу точно, как будто они присутствовали в теле класса нормального объявления класса если явно не утверждено иначе.
Прямой суперкласс перечислимого типа по имени E Enum<E>
. В дополнение к элементам это наследовалось от Enum<E>
, для каждой объявленной перечислимой константы с именем n у перечислимого типа есть неявно объявленная общественность static
final
поле, названное n типа E. Эти поля, как полагают, объявляются в том же самом порядке как соответствующие перечислимые константы перед любыми статическими полями, явно объявленными в перечислимом типе. Каждое такое поле инициализируется к перечислимой константе, которая соответствует ему. Каждое такое поле, как также полагают, аннотируется теми же самыми аннотациями как соответствующая перечислимая константа. Перечислимая константа, как говорят, создается, когда соответствующее поле инициализируется.
Это - ошибка времени компиляции для перечисления, чтобы объявить финализатор. Экземпляр перечисления никогда не может завершаться.
Кроме того, если E является именем перечислимого типа, то у того типа есть следующие неявно объявленные статические методы:
/*** Returns an array containing the constants of this enum * type, in the order they're declared. This method may be * used to iterate over the constants as follows: * * for(E c : E.values()) * System.out.println(c); * * @return an array containing the constants of this enum * type, in the order they're declared */ public static E[] values(); /** * Returns the enum constant of this type with the specified * name. * The string must match exactly an identifier used to declare * an enum constant in this type. (Extraneous whitespace * characters are not permitted.) * * @return the enum constant with the specified name * @throws IllegalArgumentException if this enum type has no * constant with the specified name */ public static E valueOf(String name);
Обсуждение
Из этого следует, что перечислимые описания типа не могут содержать поля, которые конфликтуют с перечислимыми константами, и не могут содержать методы, которые конфликтуют с автоматически сгенерированными методами (values()
и valueOf(String)
) или методы, которые переопределяют заключительные методы в Enum
: (equals(Object)
, hashCode()
, clone()
, compareTo(Object)
, name()
, ordinal()
, и getDeclaringClass()
).
Это - ошибка времени компиляции, чтобы сослаться на статическое поле перечислимого типа, который не является временем компиляции, постоянным (§15.28) от конструкторов, блоков инициализатора экземпляра, или выражений инициализатора переменной экземпляра того типа. Это - ошибка времени компиляции для конструкторов, блоков инициализатора экземпляра, или выражений инициализатора переменной экземпляра перечислимого постоянного e, чтобы обратиться к себе или к перечислимой константе того же самого типа, который объявляется направо от e.
Обсуждение
Без этого правила очевидно разумный код перестал бы работать во время выполнения из-за зацикливания инициализации, свойственного от перечислимых типов. (Зацикливание существует в любом классе с "самовведенным" статическим полем.) Вот пример вида кода, который перестал бы работать:
Статическая инициализация этого перечислимого типа бросила бы NullPointerException, потому что карта цветов статической переменной является неинициализированной когда конструкторы для перечислимых выполненных констант. Ограничение выше гарантирует, что такой код не будет компилировать.enum Color { RED, GREEN, BLUE; static final Map<String,Color> colorMap = new HashMap<String,Color>(); Color() { colorMap.put(toString(), this); } }
Отметьте, что примером может легко быть refactored, чтобы работать должным образом:
enum Color { RED, GREEN, BLUE; static final Map<String,Color> colorMap = new HashMap<String,Color>(); static { for (Color c : Color.values()) colorMap.put(c.toString(), c); } }
refactored версия ясно корректна, поскольку статическая инициализация происходит от начала до конца.
Обсуждение
Вот программа с вложенным перечислимым объявлением, которое использует улучшенный для цикла, чтобы выполнить итерации по константам в перечислении:
public class Example1 { public enum Season { WINTER, SPRING, SUMMER, FALL } public static void main(String[] args) { for (Season s : Season.values()) System.out.println(s); } }
Выполнение этой программы производит следующий вывод:
WINTER SPRING SUMMER FALL
Вот программа, иллюстрирующая использование EnumSet
работать с поддиапазонами:
import java.util.*; public class Example2 { enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } public static void main(String[] args) { System.out.print("Weekdays: "); for (Day d : EnumSet.range(Day.MONDAY, Day.FRIDAY)) System.out.print(d + " "); System.out.println(); } }
Выполнение этой программы производит следующий вывод:
Weekdays: MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY
EnumSet
содержит богатое семейство статических фабрик, таким образом, этот метод может быть обобщен, чтобы работать подмножества состоящие из нескольких несмежных участков так же как поддиапазоны. На первый взгляд могло бы казаться расточительным генерировать EnumSet
для единственной итерации, но они настолько дешевы, что это - рекомендуемая идиома для итерации по поддиапазону. Внутренне, EnumSet
представляется с единственным долгим принятием, что у перечислимого типа есть 64 или меньше элементов. Вот немного более сложное перечислимое объявление для перечислимого типа с явным полем экземпляра и средством доступа для этого поля. У каждого элемента есть различное значение в поле, и значения передают на пути конструктор. В этом примере поле представляет значение, в центах, американской монеты. Отметьте, однако, что их не ограничения на тип или число параметров, которые можно передать перечислимому конструктору.
Операторы переключения полезны для моделирования добавления метода к перечислимому типу снаружи типа. Этот пример "добавляет" цветной метод кpublic enum Coin { PENNY(1), NICKEL(5), DIME(10), QUARTER(25); Coin(int value) { this.value = value; } private final int value; public int value() { return value; } }
Coin
введите, и печатает таблицу монет, их значений, и их цветов.
Выполнение печатных изданий программы:public class CoinTest { public static void main(String[] args) { for (Coin c : Coin.values()) System.out.println(c + ": "+ c.value() +"¢ " + color(c)); } private enum CoinColor { COPPER, NICKEL, SILVER } private static CoinColor color(Coin c) { switch(c) { case PENNY: return CoinColor.COPPER; case NICKEL: return CoinColor.NICKEL; case DIME: case QUARTER: return CoinColor.SILVER; default: throw new AssertionError("Unknown coin: " + c); } } }
PENNY: 1¢ COPPER NICKEL: 5¢ NICKEL DIME: 10¢ SILVER QUARTER: 25¢ SILVER
В следующем примере класс игральной карты создается на двух простых перечислимых типах. Отметьте, что каждый перечислимый тип был бы пока весь пример в отсутствие перечислимого средства:
Вот небольшая программа, которая тренируетсяimport java.util.*; public class Card implements Comparable<Card>, java.io.Serializable { public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN,JACK, QUEEN, KING, ACE } public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES } private final Rank rank; private final Suit suit; private Card(Rank rank, Suit suit) { if (rank == null || suit == null) throw new NullPointerException(rank + ", " + suit); this.rank = rank; this.suit = suit; } public Rank rank() { return rank; } public Suit suit() { return suit; } public String toString() { return rank + " of " + suit; } // Primary sort on suit, secondary sort on rank public int compareTo(Card c) { int suitCompare = suit.compareTo(c.suit); return (suitCompare != 0 ? suitCompare : rank.compareTo(c.rank)); } private static final List<Card> prototypeDeck = new ArrayList<Card>(52); static { for (Suit suit : Suit.values()) for (Rank rank : Rank.values()) prototypeDeck.add(new Card(rank, suit)); } // Returns a new deck public static List<Card> newDeck() { return new ArrayList<Card>(prototypeDeck); } }
Card
класс. Требуется два целочисленных параметра на командной строке, представляя число рук, чтобы иметь дело и число карт в каждой руке:
Выполнение программы приводит к результатам как это:import java.util.*; class Deal { public static void main(String args[]) { int numHands = Integer.parseInt(args[0]); int cardsPerHand = Integer.parseInt(args[1]); List<Card> deck = Card.newDeck(); Collections.shuffle(deck); for (int i=0; i < numHands; i++) System.out.println(dealHand(deck, cardsPerHand)); } /** * Returns a new ArrayList consisting of the last n elements of * deck, which are removed from deck. The returned list is * sorted using the elements' natural ordering. */ public static <E extends Comparable<E>> ArrayList<E> dealHand(List<E> deck, int n) { int deckSize = deck.size(); List<E> handView = deck.subList(deckSize - n, deckSize); ArrayList<E> hand = new ArrayList<E>(handView); handView.clear(); Collections.sort(hand); return hand; } }
Следующий пример демонстрирует использование постоянно-специфичных тел класса, чтобы присоединить поведения к константам. (Ожидается, что потребность в этом будет редка.):java Deal 4 5 [FOUR of SPADES, NINE of CLUBS, NINE of SPADES, QUEEN of SPADES, KING of SPADES] [THREE of DIAMONDS, FIVE of HEARTS, SIX of SPADES, SEVEN of DIAMONDS, KING of DIAMONDS] [FOUR of DIAMONDS, FIVE of SPADES, JACK of CLUBS, ACE of DIAMONDS, ACE of HEARTS] [THREE of HEARTS, FIVE of DIAMONDS, TEN of HEARTS, JACK of HEARTS, QUEEN of HEARTS]
Выполнение этой программы производит следующий вывод:import java.util.*; public enum Operation { PLUS { double eval(double x, double y) { return x + y; } }, MINUS { double eval(double x, double y) { return x - y; } }, TIMES { double eval(double x, double y) { return x * y; } }, DIVIDED_BY { double eval(double x, double y) { return x / y; } }; // Perform the arithmetic operation represented by this constant // abstract double eval(double x, double y); public static void main(String args[]) { double x = Double.parseDouble(args[0]); double y = Double.parseDouble(args[1]); for (Operation op : Operation.values()) System.out.println(x + " " + op + " " + y + " = " + op.eval(x, y)); } }
Вышеупомянутый образец является подходящим для умеренно сложных программистов. Это по общему признанию немного хитро, но это намного более безопасно чем использование оператора выбора в базовом типе (Работа), поскольку образец устраняет возможность упущения добавить поведение для новой константы (Вы получили бы ошибку времени компиляции).java Operation 2.0 4.0 2.0 PLUS 4.0 = 6.0 2.0 MINUS 4.0 = -2.0 2.0 TIMES 4.0 = 8.0 2.0 DIVIDED_BY 4.0 = 0.5
Содержание | Предыдущий | Следующий | Индекс | Спецификация языка Java Третий Выпуск |
Авторское право © 1996-2005 Sun Microsystems, Inc. Все права защищены
Пожалуйста, отправьте любые комментарии или исправления через нашу