类声明定义了新的引用类型,并描述了它们是如何实现的(8.1)。
顶层类是这样一种类,它们不是嵌套的类。
嵌套的类是其声明在另一个类或接口的 body 内出现的任何类。
这一章讨论了所有类的通用语义 - 顶层(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.5)。类可以是泛型的(8.1.2),即,它们可以声明类型变量,其绑定在类的不同实例之间不同。
类可以被注解(9.7)装饰,就像任何其它种类的声明。
类的 body 声明成员(字段和方法和嵌套的类和接口)、实例和静态初始化器,以及构造器(8.1.6)。成员(8.2)的作用域(6.3)是成员所属的类的声明的整个 body。字段、方法、成员类、成员接口和构造器声明可以包含访问修饰符(6.6)public、protected 或 private。类的成员包括声明的和继承的成员两者(8.2)。新声明的字段可以隐藏声明在父类或父接口中的字段。新声明的类成员和接口成员可以隐藏声明在父类或父接口中的类或接口成员。新声明的方法可以隐藏、实现或重写声明在父类或父接口中的方法。
字段声明(8.3)描述了类变量,其只具体化一次,和实例变量,其为类的每个实例重新具体化一次。字段可以声明为 final(8.3.1.2),在这种情况下,它只能被赋值一次。任何字段声明都可以包含一个初始化器。
成员类声明(8.5)描述了嵌套的类,其是包围它的类的成员。成员类可以是 static,在这种情况下,它们无法访问包围它的类的实例变量;或者它们可能是内部类(8.1.3)。
成员接口声明(8.5)描述了嵌套的接口,其是包围它的类的成员。
方法声明(8.4)描述了代码,可以通过方法调用表达式(15.12)来调用它们。类方法相对于类类型被调用;对于作为类类型的实例的某些特定对象,将调用实例方法。方法必须被声明为 abstract,其声明未指示它是如何实现的。方法可以声明为 final(8.4.3.3),在这种情况下,它不能被隐藏或重写。可以通过平台依赖的本地代码(8.4.3.4)来实现方法。同步方法(8.4.3.6)在执行其 body 之前自动地锁定一个对象,并在返回后自动地解锁这个对象,就像使用同步语句(14.19)一样,从而允许其活动与其它线程同步(17(Threads and Locks))。
方法名称可以重载(8.4.9)。
实例初始化器(8.6)是可执行代码块,可用于帮助初始化一个实例,在其创建时(15.9)。
静态初始化器(8.7)是可执行代码块,可用于帮助初始化一个类。
构造器(8.8)类似于方法,但不能通过方法调用直接调用;它们用于初始化新的类实例。像方法一样,它们可以重载(8.8.8)。
类声明指定了一个新命名的引用类型。
有两种类声明:标准类声明和枚举声明。
ClassDeclaration:
NormalClassDeclaration
EnumDeclaration
NormalClassDeclaration:
{ClassModifier} class Identifier [TypeParameters] [SuperClass] [SuperInterfaces] ClassBody
本节中的规则适用于所有类声明,包括枚举声明。但是,对于类修饰符、内部类和父类,特殊规则适用于枚举声明;这些规则在 8.9 中说明。
类声明中的 Identifier 指定了类的名称。
如果一个类与它的任何封闭类或接口具有相同的简单名称,则这是一个编译时错误。
作用域和类声明的遮蔽在 6.3 和 6.4 中指定。
类声明可以包含类修饰符。
ClassModifier:
(one of)
Annotation public protected private
abstract static final strictfp
类声明上的注解修饰符规则在 9.7.4 和 9.7.5 中指定。
访问修饰符 public(6.6)仅与顶层类(7.6)和成员类(8.5)有关,而与局部类(14.3)或匿名类(15.9.5)无关。
访问修饰符 protected 和 private 仅与直接封闭类声明(8.5)内的成员类有关。
修饰符 static 仅与成员类(8.5.1)有关,而与顶层或局部或匿名类无关。
如果相同的关键字作为类声明的修饰符出现多次,则这是一个编译时错误。
如果两个或更多个(不同的)类修饰符出现在类声明中,则通常是习惯的,尽管不是必需的,它们出现的顺序与上面的 ClassModifier 的产品中显示的次序一致。
抽象类是不完整的或被认为是不完整的类。
如果尝试使用类实例创建表达式(15.9.1)创建抽象类的实例,则这是一个编译时错误。
抽象类的子类,其本身不是抽象类,可以实例化,导致抽象类的构造器的执行,因此,执行该类的实例变量的字段初始化器。
标准类可能有抽象方法,即声明但未实现(8.4.3.1)的方法,只有当它是抽象类时。如果不是抽象的类具有抽象方法,则这是一个编译时错误。
如果一个两者之一为 true,则类 C 具有抽象方法:
* C 的任一成员方法(8.2) - 声明的或继承的 - 是抽象的。
* C 的任一父类具有包访问的抽象方法,并且 C 或 C 的父类没有方法重写该抽象方法。
声明一个抽象类类型,以致于不可能创建实现其所有抽象方法的子类,则这是一个编译时错误。如果类将具有相同方法签名(8.4.2)的两个抽象方法作为成员,但返回类型对每个方法来说,没有类型是两者返回类型可替代的(8.4.5),则可能发生这种用情况。
只有当目的是可以创建子类以完成实现时,类类型才应声明为抽象的。如果目的是简单地防止类实例化,正确地表达这个的方式是声明一个没有参数的构造器(8.8.10),使其私有,从不调用它,且不声明其它构造器。这种形式的类通常包含类方法和变量。
类可以声明为 final,如果其定义是完整的,并且不想要或不需要任何子类。
如果 final 类的名称出现在另一个类声明的 extends 子句(8.1.4)中,则这是一个编译时错误;这意味着 final 类无法有任何子类。
如果类同时声明为 final 和 abstract,则这是一个编译时错误,因为此种类的实现不可能是完整的(8.1.1.1)。
由于 final 类从不会有任何子类,final 类的方法从不被重写(8.4.8.1)。
strictfp 修饰符的作用是使类声明(包括变量初始化器、实例初始化器、静态初始化器和构造器中)中的所有 float 或 double 表达式成为显式 FP-strict(15.4)。
这意味着此类中声明的所有方法,和此类中声明的所有嵌套类型,都是隐式 strictfp。
如果类声明一个或多个类型变量(4.4),则此类是泛型的。
这些类型变量被称为类的类型参数。类型参数部分跟在类名称后面,并由尖括号分隔。
TypeParameter:
< TypeParameterList >
TypeParameterList:
TypeParameter {, TypeParameter}
为了方便以下来自 4.4 的产生式在这显示;
TypeParameter:
{TypeParameterModifier} Identifier [TypeBound]
TypeParameterModifier:
Annotation
TypeBound:
extends TypeVariable
extends ClassOrInterfaceType {AdditionalBound}
AdditionalBound:
& InterfaceType
类型参数上的注解修饰符规则在 9.7.4 和 9.7.5 中指定。
在类的类型参数部分中,类型变量 T 直接依赖类型变量 S,如果 S 是 T 的边界,而 T 依赖 S ,如果 T 直接依赖 S 或 T 直接依赖类型变量 U,而 U 依赖 S(递归地使用这个定义)。如果类的类型参数部分中的类型变量依赖它自己,则这是一个编译时错误。
作用域和类的类型参数的遮蔽在 6.3 和 6.4 中指定。
泛型类声明定义了一组参数化类型(4.5),一个用于类型参数部分的每个可能的参数化。所有这些参数化类型在运行时共享相同的类。
如果泛型类是 Throwable(11.1.1)的直接或间接子类,则这是一个编译时错误。
由于 Java 虚拟机的 catch 机制只在非泛型类上工作,因此这个限制是必需的。
在以下任何情况中,引用泛型类 C 的类型参数是一个编译时错误:
* C 的 static 成员的声明(8.3.1.1,8.4.3.2,8.5.1)。
* 在 C 中嵌套的任何类型声明的 static 成员的声明。
* C 的静态初始化器(8.7),或
* 在 C 中嵌套的任何类声明的静态初始化器。
内部类是一个嵌套类,其未显式地或隐式地被声明为 static。
内部类可以是非 static 的成员类(8.5)、局部类(14.3)或匿名类(15.9.5)。接口的成员类是隐式 static(9.5)的,因此从不被认为是内部类。
如果内部类声明了静态初始化器(8.7),则这是一个编译时错误。
如果内部类声明了显式或隐式 static 的成员,除非该成员是常变量(4.12.4),否则这是一个编译时错误。
内部类可以继承不是常变量的 static 成员,即使内部类无法声明它们。
不是内部类的嵌套类可以根据 Java 编程语言的通常规则自由地声明 static 成员。
当且仅当最内部的封闭语句或表达式的方法、构造器、实例初始化器、静态初始化器、字段初始化器或显式的构造器调用语句是 static 方法、static 初始化器、static 变量的变量初始化器或显式的构造器调用语句(8.8.7.1),则语句或表达式出现在静态上下文中。
内部类 C 是类或接口 O 的直接内部类,如果 O 直接封闭 C 的类型声明,并且 C 的声明不出现在静态上下文中。
直接封闭内部类的类型声明是接口,这是不常见的,但可能发生的。只有在默认方法 body(9.4)中声明类时才会出现此情况。具体地说,如果在默认方法 body 中声明了匿名或局部类,或默认方法 body 中声明的匿名类的 body 中声明了成员类,则会发生这种情况。
类或接口 O 是其本身的第零个词法封闭类型声明。
类 O 是类 C 的第 n 个词法封闭类型声明,如果它是 C 的第 n-1 个词法封闭类型声明的直接封闭类型声明。
类或接口 O 的直接内部类 C 的实例 i 与 O 的一个实例相关联,称为 i 的直接封闭实例。对象的直接封闭实例,如果有,在此对象创建(15.9.2)的时候确定。
对象 o 是其本身的第零个词法封闭实例。
对象 o 是实例 i 的第 n 个词法封闭实例,如果它是 i 的第 n-1 个词法封闭实例的直接封闭实例。
声明出现在静态上下文中的内部类 I 的实例没有词法封闭实例。但是如果 I 是在静态方法或静态初始化器中直接声明的,则 I 确实有一个封闭块,它是在词法上封闭 I 的声明的最内部的块语句。
对于 C ,其本身是类或接口 SO 的直接内部类,的每个父类 S,有一个与 i 相关联的 SO 的实例,称为 i 的关于 S 的直接封闭实例。关于其类的直接父类的对象的直接封闭实例,如果有,在父类构造器通过显式的构造器调用语句(8.8.7.1)调用时确定。
当内部类(其声明未出现在静态上下文中)引用作为词法封闭类型声明的成员的实例变量时,将使用相应的词法封闭实例的变量。
在内部类中使用但未声明的任何局部变量、形式参数或异常参数必须声明为 final 或 事实上 final(4.12.4)的,否则在尝试使用的地方发生一个编译时错误。
在内部类中使用但未声明的任何局部变量在内部类的 body 之前必须明确赋值(16(Definite Assignment)),否则发生一个编译时错误。
类似的变量使用上的规则适用于 lambda 表达式(15.27.2)的 body。
词法封闭类型声明的空 final 字段(4.12.4)不可以在内部类中赋值,否则发生一个编译时错误。
标准类声明中的可选的 extends 子句指定当前类的直接父类。
Superclass:
extends ClassType
extends 子句不能在类 Object 的定义中出现,否则发生一个编译时错误,因为它是原始的类,且没有直接父类。
ClassType 必须命名一个可访问的类类型(6.6),否则发生一个编译时错误。
如果 ClassType 命名一个 final 类,则这是一个编译时错误,因为 final 类不允许有子类(8.1.1.2)。
如果 ClassType 命名类 Enum 或 Enum的任何调用(8.9),则这是一个编译时错误。
如果 ClassType 有类型参数,则它必须格式良好的参数化类型(4.5),并且任何类型参数都不能是通配符类型参数,否则发生一个编译时错误。
给定一个(可能是泛型的)类声明 C<F1,...,Fn> (n ≥ 0, C ≠ Object),类类型 C<F1,...,Fn> 的直接父类是 C 的声明的 extends 子句中给定的类型,如果存在 extends 子句,否则是 Object。
给定一个泛型类声明 C<F1,...,Fn> (n > 0),参数化类类型 C<T1,...,Tn> 的直接父类,其中是 Ti (1 ≤ i ≤ n) 类型,是 D<U1 θ,...,Uk θ>,其中 D<U1,...,Uk> 是 C<F1,...,Fn> 的直接父类,而 θ 是 [F1:=T1,...,Fn:=Tn] 的代替。
一个类被称为其直接父类的直接子类。直接父类是从其实现派生当前类的实现的类。
子类关系直接子类关系的传递闭包。如果以下两者之一为 true,则类 A 是类 C 的子类:
* A 是 C 的直接子类
* 存在类 B,因此 A 是 B 的子类,B 是 C 的子类,递归地应用此定义。
类 C 被称为类 A 的父类,当 A 是 C 的子类时。
类 C 直接依赖于类型 T,如果 T 是在 C 的 extends 或 implements 子句中作为父类或父接口被提及,或在父类或父接口名称的完全限定形式中作为限定符被提及。
类 C 依赖于引用类型 T,如果以下任一为 true:
* C 直接依赖 T。
* C 直接依赖一个依赖(9.1.3)T 的接口。
* C 直接依赖一个依赖 T(递归地使用此定义)的类 D。
如果类依赖它自己,则这是一个编译时错误。
如果在运行时检测到循环声明的类,则当加载类时,将抛出 ClassCircularityError(12.2.1)。
类声明中的可选的 implements 子句列举了接口的名称,它们是被声明的类的直接父接口。
Superinterfaces:
implements InterfaceTypeList
InterfaceTypeList:
InterfaceType {, InterfaceType}
每个 InterfaceType 必须命名一个可访问的接口类型(6.6),否则发生一个编译时错误。
如果 InterfaceType 有类型参数,则它必须表示格式良好的参数化类型(4.5),并且任何类型参数都不能是通配符类型参数,否则发生一个编译时错误。
如果在单个 implements 子句中多次将同一接口作为直接父接口提及,则这是一个编译时错误。即使以不同的方式命名该接口,这也是 true。
给定一个(可能是泛型的)类声明 C<F1,...,Fn> (n ≥ 0, C ≠ Object),类类型 C<F1,...,Fn> 的直接父接口是在 C 的声明的 implements 子句中给定的类型,如果存在 implements 子句。
给定一个泛型类声明 C<F1,...,Fn> (n > 0),参数化类类型 C<T1,...,Tn> 的直接父接口,其中 Ti (1 ≤ i ≤ n) 是类型,是所有类型 I<U1 θ,...,Uk θ>,其中 I<U1,...,Uk> 是 C<F1,...,Fn> 的直接父接口,而 θ 是 [F1:=T1,...,Fn:=Tn] 的代替。
接口类型 I 是类类型 C 的父接口,如果以下任一为 true:
* I 是 C 的直接父接口。
* C 有某些直接父接口 J,对 J 来说 I 是父接口,使用 9.1.3 中给定的定义“接口的父接口”。
* I 是 C 的直接父接口的父接口。
类可以以多种方式有父接口。
类被称为 implement 所有它的父接口。
类不可以同时是两个接口类型的子类型,这两个接口类型是同一泛型接口(9.1.2)的不同参数化,或是一个泛型接口的参数化的子类型和命名同一泛型接口的原始类型,否则发生一个编译时错误。
引入此要求是为了支持通过类型擦除(4.6)进行转换。
除非类被声明为 abstract,否则必须实现每个直接父接口的所有 abstract 成员方法(8.4.8.1),要么通过这个类中的声明,要么通过继承自直接父类或直接父接口的现有的方法声明,因为非 abstract 的类不允许有 abstract 方法(8.1.1.1)。
类的父接口的每个默认方法(9.4.3)可以通过这个类中的方法重写;如果没有这样做,则默认方法通常被继承,它的行为由它的默认 body 指定。
允许在类中使用单个方法声明来实现多个父接口的方法。
类 body 可以包含类的成员的声明,即,字段(8.3)、方法(8.4)、类(8.5)和接口(8.5)。
类 body 也可以包含实例初始化器(8.6)、静态初始化器(8.7)和类的构造器(8.8)声明。
ClassBody:
{ {ClassBodyDeclaration} }
ClassBodyDeclaration:
ClassMemberDeclaration
InstanceInitializer
StaticInitializer
ConstructorDeclaration
ClassMemberDeclaration:
FieldDeclaration
MethodDeclaration
ClassDeclaration
InterfaceDeclaration
;
由类类型 C 声明的或继承的成员 m 的声明的作用域和遮蔽在 6.3 和 6.4 中指定。
如果 C 本身是嵌套类,则在封闭作用域内可能有相同种类的定义(变量、方法或类型),并命名为 m。(作用域可能是块、类或包。)在所有这种情况下,声明在或继承自 C 的成员 m 遮蔽了相同种类和名称的其它定义。
类类型的成员是以下所有:
* 继承自其直接父类(8.1.4)的成员,除了类 Object 以外,它没有直接父类
* 继承自任何直接父接口(8.1.5)的成员
* 声明在类 body 中的成员(8.1.6)
类的声明为 private 的成员不会被该类的子类继承。
只有类的声明为 protected 或 public 的成员才会被声明该类的包以外的包中声明的子类继承。
构造器、静态初始化器和实例初始化器不是成员,因此不会被继承。
我们使用短语一个成员的类型来表示:
字段,它的类型。
方法,一个有序的四元组由以下组成:
类型参数:方法成员的任何类型参数的声明。
参数类型:方法成员的参数的参数类型列表。
返回类型:方法成员的返回类型。
throws 子句:在方法成员的 throws 子句中声明的异常类型。
类类型的字段、方法和成员类型可能具有相同的名称,因为它们在不同的上下文中使用,并被不同的查找过程区分(6.5)。但是,这是一个不被鼓励的风格问题。
类类型的变量通过字段声明引入。
FieldDeclaration:
{FieldModifier} UnannType VariableDeclaratorList ;
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclarator [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
VariableInitializer:
Expression
ArrayInitializer
UnannType:
UnannPrimitiveType
UnannReferenceType
UnannPrimitiveType:
NumericType
boolean
UnannReferenceType:
UnannClassOrInterfaceType
UnannTypeVariable
UnannArrayType
UnannClassOrInterfaceType:
UnannClassType
UnannInterfaceType
UnannClassType:
Identifier [TypeArguments]
UnannClassOrInterfaceType . {Annotation} Identifier [TypeArgument]
UnannInterfaceType:
UnannClassType
UnannTypeVariable:
Identifier
UnannArrayType:
UnannPrimitiveType Dims
UnannClassOrInterfaceType Dims
UnannTypeVarible Dims
为了方便,以下来自 4.3 的产生式在这显示。
Dims:
{Annotation} [ ] {{Annotation} [ ]}
FieldDeclaration 中的每个声明符声明了一个字段。声明符中的 Identifier 可以用于在名称中引用该字段。
可以通过使用多个声明符来在单个 FieldDeclaration 中声明多个字段,FieldDeclaration 和 UnannType 适用于声明中的所有声明符。
FieldModifier 子句在 8.3.1 中描述。
如果未在 UnannType 和 VariableDeclaratorId 中出现括号对,则字段的声明类型由 UnannType 表示,并且由 10.2 指定。
字段声明的作用域和遮蔽在 6.3 和 6.4 中指定。
类声明的 body 声明两个具有相同名称的字段,则这是一个编译时错误。
如果类声明了一个具有特定名称的字段,则称该字段的声明将隐藏该类的父类和父接口中的具有相同名称的任何和所有可访问的字段声明。
在这方面,字段隐藏不同于方法隐藏(8.4.8.3),因为在字段隐藏中静态和非静态字段之间没有区别,而在方法隐藏中静态和非静态方法之间有区别。
隐藏的字段可以通过使用限定名称(6.5.6.2)来访问,如果它是静态的,或通过使用包含关键字 super(15.11.2)的字段访问表达式或到父类类型的强制转换。
在这方面,字段隐藏类似于方法隐藏。
如果字段声明隐藏了另一个字段声明,则这两个字段需要具有相同的类型。
类从其直接父类和字节父接口继承父类和父接口的所有非私有字段,父类和父接口对于此类中的代码来说是可访问的,并且没有被此类中的声明隐藏。
父类的私有字段对子类来说可能是可访问的 - 例如,如果两个类是同一类的成员。然而,私有字段永远不会被子类继承。
类可能继承多个具有相同名称的字段。此类情况其本身不会导致编译时错误。但是,任何尝试在类的 body 中通过简单名称引用此类字段将会产生编译时错误,因为此类引用是不明确的。
可能有几种路径,可以从接口继承相同的字段声明。在这种情况下,字段被视为只继承一次,可以通过其简单名称来引用它,而不存在歧义。
在 float 类型的字段中存储的值总是浮点值集(4.2.3)的元素;类似地,在 double 类型的字段中存储的值总是双精度值集的元素。不允许 float 类型的字段包含浮点扩展指数值集的元素,该元素也不是浮点值集的元素,也不允许 double 类型字段包含双精度扩展指数值集的元素,该元素也不是双精度值集的元素。
FieldModifier:
(one of)
Annotation public protected private
static final transient volatile
字段声明上注解修饰符的规则在 9.7.4 和 9.7.5 中指定。
如果相同的关键字作为字段声明的修饰符出现多次,则这是一个编译时错误。
如果字段声明中出现了两个或更多个字段修饰符,则通常,尽管不是必需的,它们出现的顺序与以上 FieldModifier 产品中显示的顺序一致。
如果字段声明为 static,则无论创建多少个类实例(可能零个),该字段只存在一个实例。static 字段,有时称为类变量,是在初始化(12.4)类时具体化的。
未声明为 static 的字段(有时称为非 static 字段)被称为实例变量。无论何时创建类的新的实例,为每个在该类或其父类中声明的实例变量创建一个与该实例相关联的新变量。
Code that uses a field access expression to access field x will access the field named x in the class indicated by the type of reference expression.
使用字段访问表达式访问字段 x 的代码将访问由引用表达式的类型指示的类中的名为 x 的字段。
字段可以声明为 final(4.12.4)。类和实例变量(static 和 非 static 字段)都可以声明为 final。
空 final 类变量必须被声明它的类的静态初始化器明确地赋值,否则发生一个编译时错误(8.7,16.8)。
空 final 实例变量必须在声明它的类的每个构造器的末尾处明确地赋值,否则发生一个编译时错误(8.8,16.9)。
变量可以标记为 transient,以表示它们不是对象的持久化状态的一部分。
Java 编程语言允许线程访问共享变量(17.1)。通常,为了确保共享变量一致地和可靠地更新,线程应该确保它具有对这些变量的独占使用,通过获取强制互斥访问这些共享变量的锁。
Java 编程语言提供了第二种机制,volatile 字段,它在实现某些目的时比锁更方便。
字段可以声明为 volatile,这种情况下,Java 内存模型确保所有线程可以看到该变量的一致的值(17.4)。
如果 final 变量也声明为 volatile,则这是一个编译时错误。
如果字段声明中的声明符具有变量初始化器,则该声明符具有赋值(15.26)声明的变量的语义。
如果是类变量的声明符(即,static 字段),则以下规则适用于它的初始化器:
* 如果由简单名称到任何实例变量的引用在初始化器中出现,则这是一个编译时错误。
* 如果关键字 this(15.8.3)或关键字 super(15.11.2,15.12)在初始化器中出现,则这是一个编译时错误。
* 在运行时,当初始化类时(12.4.2),计算初始化器,并且赋值恰好执行一次。
注意,那些是常变量(4.12.4)的 static 字段在其它 static 字段(12.4.2)之前初始化。这也适用于接口(9.3.1)。这些字段将永远不会被观察到有它们的默认初始值(4.12.5),即使通过不正当的程序。
如果声明符是实例变量的(即,不是 static 的字段),则以下规则适用于它的初始化器:
* 初始化器可以使用在类中声明的或继承子类的任何类变量的简单名称,即使其声明在原文中出现在该初始化器之后。
* 初始化器可以引用当前的对象 this(15.8.3),并且可以使用关键字 super(15.11.2,15.12)。
* 在运行时,计算初始化器,并且每次创建类的实例时(12.5)执行赋值。
字段声明中变量初始化器的异常检查在 11.2.3 中指定。
变量初始化器也用于局部变量声明语句(14.4),其中计算初始化器,并且每次执行局部变量声明语句时执行赋值。
声明在原文中出现在使用之后的类变量的使用有时受到限制,即使这些类变量在作用域内(6.3)。具体地说,如果以下所有为 true,则这是一个编译时错误:
* 类或接口 C 中的类变量的声明在原文中出现在该类变量的使用之后;
* 在 C 的类变量初始化器或 C 的静态初始化器中使用简单名称;
* 不是在赋值的左手边使用;
* C 是封装使用的最内部的类或接口。
声明在原文中出现在使用之后的实例变量的使用有时受到限制,即使这些实例变量在作用域内。具体地是,如果以下所有为 true,则这是一个编译时错误:
* 类或接口 C 中的实例变量的声明在原文中出现在该实例变量的使用之后;
* 在 C 的实例变量的初始化器或 C 的实例初始化器中使用简单名称;
* 不是在赋值的左手边使用;
* C 是封装使用的最内部的类或接口。
方法声明了可被调用的可执行代码,将规定数量的值作为参数传递。
MethodDeclaration:
{MethodModifier} MethodHeader MethodBody
MethodHeader:
Result MethodDeclarator [Throws]
TypeParameters {Annotation} Result MethodDeclaration [Throws]
MethodDeclarator:
Identifier ( [FormalParameterList] ) [Dims]
为了方便,在这显示以下来自 4.3 的产生式:
Dims:
{Annotation} [ ] { {Annotation} [ ] }
FormalParameterList 在 8.4.1 中描述,MethodModifier 子句在 8.4.3 中,TypeParameters 子句在 8.4.4 中,Result 子句在 8.4.5 中,Throws 子句在 8.4.6 中,MethodBody 在 8.4.7 中。
MethodDeclarator 中的 Identifier 可以用于引用方法的名称(6.5.7.1,15.12)。
如果类的 body 声明两个具有重写等效的签名(8.4.2)作为成员,则这是一个编译时错误。
方法声明的作用域和遮蔽在 6.3 和 6.4 中指定。
允许返回数组的方法声明在形式参数列表后面放置表示数组类型的部分或全部括号对。这个语法是为了兼容早期版本的 Java 编程语言。强烈建议不要在新代码中使用这个语法。
方法或构造器的形式参数,如果有,由逗号分隔的参数说明符列表指定。每个参数说明符由类型(可选地前面有 final 修饰符和/或一个或多个注解)和指定参数名称的标识符(可选地后跟括号)。
如果方法或构造器没有形式参数,只有一个空的圆括号对出现在方法或构造器的声明中。
FormalParameterList:
ReceiverParameter
FormalParameters , LastFormalParameter
LastFormalParameter
FormalParameters:
FormalParameter {, FormalParameter}
ReceiverParameter {, FormalParameter}
FormalParameter:
{VariableModifier} UnannType VariableDeclaratorId
VariableModifier:
(one of)
Annotation final
ReceiverParameter:
{Annotation} UnannType [Identifier .] this
LastFormalParameter:
{VariableModifier} UnannType {Annotation} ... VariableDeclaratorId
FormalParameter
为了方便,在这显示以下来自 4.3 和 8.3 的产生式:
VariableDeclaratorId:
Identifier [Dims]
Dims:
{Annotation} [ ] { {Annotation} [ ] }
方法或构造器的最后一个形式参数是特殊的:它可以是可变参数,由类型后面的省略号指示。
注意,省略号(...)是一个对自己的标记。可以在它和类型之间放置空格,但作为风格问题,不建议这样做。
如果最后一个形式参数是一个可变参数,则方法是可变方法。否则,它是一个固定数量的方法。
接收者参数是实例方法或内部类的构造器的可选语法手段。对于实例方法,接收者参数表示调用该方法的对象。对于内部类的构造器,接收者参数表示新构造的对象的直接封闭实例。无论哪种方式,接收者参数都只存在于允许在源代码中表示的表示对象的类型,以便可以对类型进行注解。接收者参数不是形式参数;更确切地说,它不是任何种类变量的声明(4.12.3),它从不绑定到作为方法调用表达式或限定的类实例创建表达式中的参数传递的任何值,并且它在运行时没有任何影响。
形式参数和接收者参数上注解修饰符的规则在 9.7.4 和 9.7.5 中指定。
如果 final 作为形式参数声明的修饰符出现多次,则这是一个编译时错误。
对可变参数使用混合数组表示法(10.2)是一个编译时错误。
形式参数的作用域和遮蔽在 6.3 和 6.4 中指定。
方法或构造器声明两个具有相同名称形式参数是一个编译时错误。(即,它们的声明提及相同的 Identifier。)
如果声明为 final 的形式参数在方法或构造器的 body 内赋值,则这是一个编译时错误。
接收者参数仅可以出现在实例方法或内部类的构造器的 FormalParameterList 中;否则,发生一个编译时错误。
在允许接收者参数的地方,按如下指定它的类型和名称:
* 在实例方法中,接收者参数的类型必须是声明方法的类或接口,并且接收者参数的名称必须是 this;否则,发生一个编译时错误。
* 在内部类的构造器中,接收者参数的类型必须是直接封闭内部类的类型声明的类或接口,并且接收者参数的名称必须是 Identifier . this,其中 Identifier 是直接封闭内部类的类型声明的类或接口的简单名称;否则,发生一个编译时错误。
形式参数的声明类型依赖于它是否是可变参数:
* 如果形式参数不是可变参数,则如果 UnannType 和 VariableDeclaratorId 中未出现括号对,声明类型由 UnannType 表示,另外由 10.2 指定。
* 如果形式参数是可变参数,则声明类型由 10.2 指定。(注意,可变参数不允许“混合表示法”。)
如果可变参数的声明类型有非具体化的元素类型(4.7),则由于可变方法的声明出现编译时未检查警告,除非用 @SafeVarargs(9.6.4.7)注解了方法或未检查警告被 @SuppressWarnings(9.6.4.5)抑制。
当调用方法或构造器(15.12)时,实际参数表达式的值初始化新创建的参数变量,每个声明类型,在方法或构造器的 body 执行之前。在 DeclaratorId 中出现的 Identifier 可以用作方法或构造器的 body 中引用形式参数的简单名称。
可变方法的调用可能包含比形式参数更多的实际参数表达式。所有与形式参数不对应的在可变参数后面的实际参数表达式都将被计算,并且结果都将存储在一个传递给方法调用(15.12.4.2)的数组中。
float 类型的方法或构造器参数总是包含浮点值集的元素(4.2.3);类似地,double 类型的方法或构造器参数总是包含双精度值集的元素。不允许 float 类型的方法或构造器参数包含浮点扩展指数值集的元素,其也不是浮点值集的元素,也不允许 double 类型的方法参数包含双精度扩展指数值集的元素,其也不是双精度值集的元素。
在与参数变量相对应的实际参数表达式不是 FP-strict(15.4)的地方,允许该实际参数表达式的计算使用从适当的扩展指数值集得到的中间值。先于被存储在参数变量中,此类表达式的结果被映射到受调用转换(5.3)影响的相应的标准值集中的最近值。
两个方法或构造器,M 和 N,具有相同的签名,如果它们具有相同的名称、相同的类型参数(如果有)(8.4.4),并且在将 N 的形式参数调整为 M 的类型参数后,相同的形式参数类型。
方法 m1 的签名是方法 m2 的签名的子签名,如果满足以下两者之一:
m2 具有与 m1 相同的签名,或
m1 的签名与 m2 的签名的擦除(4.6)相同。
两个方法签名 m1 和 m2 是重写等效的,如果 m1 是 m2 的子签名,或 m2 是 m1 的子签名。
在一个类中声明两个具有重写等效签名的方法是一个编译时错误。
子签名的概念旨在两个签名不相同的方法之间的关系,但是其中一个可以重写另一个。具体地说,它允许签名未使用泛型类型的方法重写该方法的任何泛型化的版本。这很重要,以致于库设计者可以自由地独立于定义该库的子类或子接口的客户端使方法泛型化。
MethodModifier:
(one of)
Annotation public protected private
abstract static final synchronized native strictfp
方法声明上注解修饰符规则在 9.7.4 和 9.7.5 中指定。
如果相同的关键字作为方法声明的修饰符出现多次,则这是一个编译时错误。
如果包含关键字 abstract 的方法声明也包含关键字 private、static、final、native、strictfp 或 synchronized 中的任何一个,则这是一个编译时错误。
如果包含关键字 native 的方法声明也包含 strictfp,则这是一个编译时错误。
如果两个或更多个(不同的)方法修饰符出现在方法修饰符中,则通常,尽管不是必需的,它们出现的顺序与以上在 MethodModifier 产品中显示的顺序一致。
abstract 方法声明引入方法作为成员,假定它的签名(8.4.2)、结果(8.4.5)和 throws 子句存在(8.4.6),但不提供实现(8.4.7)。非 abstract 的方法可以称为具体方法。
abstract 方法 m 的声明必须直接出现在 abstract 类(称它为 A)中,除非它出现在枚举声明(8.9)中;否则,发生一个编译时错误。
A 的每个非 abstract(8.1.1.1)的子类必须提供 m 的实现,否则,发生一个编译时错误。
abstract 方法可以通过提供另一个 abstract 方法声明来重写 abstract 方法。
这可以提供一个位置,以放置文档注释、细化返回类型或声明可以由该方法抛出的已检查异常集合,当它被子类实现时,将会更有限。
非 abstract 的实例方法可以被 abstract 方法重写。
声明为 static 的方法被称为类方法。
在类方法的 header 或 body 中使用任何周围声明的类型参数的名称,是一个编译时错误。
类方法总是在不引用具体的对象的情况下被调用。尝试使用关键字 this(15.8.3)或关键字 super(15.11.2)来引用当前对象,是一个编译时错误。
未声明为 static 的方法被称为实例方法,有时称为非 static 方法。
实例方法调用时总是相对于一个对象,其是关键字 this 和 super 在方法 body 执行期间引用的当前对象。
<br
方法可以被声明为 final,以防止子类重写或隐藏它。
尝试重写或隐藏 final 方法,是一个编译时错误。
private 方法和在 final 类(8.1.1.2)中直接声明的所有方法表现得就像它们是 final 一样,因为不可能重写它们。
在运行时,机器代码生成器或优化器可以“内联”final 方法的 body,用其 body 中的代码替换该方法调用。内联过程必须保留方法调用的语义。特别是,如果实例方法调用的目标是 null,则必须抛出 NullPointerException,即使该方法是内联的。Java 编译器必须确保,异常将在正确的点抛出,这样,方法的实际参数将被视为在方法调用之前按正确的顺序已计算的。
native 方法以平台依赖的代码实现,通常以另一种编程语言如 C 编写。native 方法的 body 仅作为分号给出,指示省略实现,而不是块(8.4.7)。
strictfp 修饰符的作用是使方法 body 中的所有 float 或 double 类型表达式显式地是 FP-strict(15.4)。
synchronized 方法在其执行前获取一个监视器(17.1)。
对于类(static)方法,将使用与方法的类的 Class 对象相关联的监视器。
对于实例方法,将使用与 this(调用方法的对象)相关联的监视器。
方法是泛型的,如果它声明了一个或多个类型变量(4.4)。
这些类型变量被称为方法的类型参数。泛型方法的类型参数部分的形式与泛型类(8.1.2)的类型参数部分相同。
泛型方法声明定义了一组方法,一个用于通过类型参数对类型参数部分的每个可能调用。当调用泛型方法时,不需要显式地提供类型参数,因为通常可以推断出它们(18(Type Inference))。
方法的类型参数的作用域和遮蔽在 6.3 中指定。
两个方法或构造器 M 和 N 具有相同的类型参数,如果以下都为 true:
* M 和 N 具有相同数量的类型参数(可能为零)。
* 其中 A1, ..., An 是 M 的类型参数,而 B1, ..., Bn 是 N 的类型参数,让 θ=[B1:=A1, ..., Bn:=An]。然后,对于所有 i (1 ≤ i ≤ n), Ai 的边界与应用于 Bi 的边界 θ 是相同的类型。
当两个方法或构造器 M 和 N 具有相同的类型参数时,N 中提到的类型可以通过应用上面定义 θ 的类型来适应 M 的类型参数。
方法声明的结果要么声明了方法返回(返回类型)的值的类型,要么使用关键字 void 来表示方法不返回值。
Result:
UnannType
void
如果结果不是 void,则方法的返回类型由 UnannType 表示,如果形式参数列表后面没有出现括号对,并由 10.2 指定。
如果返回类型为引用类型,则返回类型在相互重写的方法之间可能不同。返回类型替代的概念支持协变返回,即,返回类型到子类型的特化。
具有返回类型 R1 的方法声明 d1 是另一个具有返回类型 R2 的方法 d2 的返回类型替代,当且仅当以下任一为 true:
* 如果 R1 是 void,则 R2 是 void。
* 如果 R1 是基元类型,则 R2 与 R1 相等。
* 如果 R1 是引用类型,则以下之一为 true:
R1,适应 d2 的类型参数,是 R2 的子类型。
可以通过未检查转换(5.1.9)将 R1 转换为 R2 的子类型。
d1 不具有与 d2 (8.4.2)相同的签名,且 R1 = |R2|。
定义中允许未检查转换,尽管它不健全,作为允许非泛型到泛型代码的平滑迁移的特殊让步。如果未检查转换用于确定,R1 是 R2 的返回类型替代,则 R1 必须不是 R2 的子类型,并且重写(8.4.8.3,9.4.1)规则将要求编译时未检查警告。
throws 子句用于声明方法或构造器 body 中的语句可以抛出(11.2.2)的任何已检查异常类(11.1.1)。
Throws:
throws ExceptionTypeList
ExceptionTypeList:
ExceptionType {, ExceptionType}
ExceptionType:
ClassType
TypeVariable
如果在 throws 子句中提及的 ExceptionType 不是 Throwable 的子类型(4.10),则这是一个编译时错误。
throws 子句中允许类型变量,即使 catch 子句(14.20)中不允许它们。
允许但不必要在 throws 子句中提及未检查异常类(11.1.1)。
throws 子句和方法或构造器 body 的异常检查之间的关系在 11.2.3 中指定。
本质上,对于可能因执行方法或构造器的 body 而导致每个已检查异常,发生一个编译时错误,除非该方法或构造器的声明的 throws 子句中提到了它的异常类型或它的异常类型的子类型。
声明已检查异常的要求允许 Java 编译器确保已包含处理此类错误条件的代码。在其 body 中未正确处理作为已检查异常抛出的异常条件的方法或构造器通常会导致编译时错误。因此,Java 编程语言鼓励一种编程风格,在这种风格中,很少有其它真正特殊的条件以这种方式记录下来。
方法的 throws 子句和重写的或隐藏的方法的 throws 子句之间的关系在 8.4.8.3 中指定。
方法 body 是实现方法的代码块,或仅仅是一个分号,表示缺失实现。
MethodBody:
Block
;
方法 body 必须是分号,如果方法是 abstract 或 native(8.4.3.1,8.4.3.4)。更确切地:
* 如果方法声明是 abstract 或 native,且有自己的 body 块,则这是一个编译时错误。
* 如果方法声明既不是 abstract,也不是 native,且有冒号作为其 body,则这是一个编译时错误。
如果要为声明为 void 的方法提供实现,但实现不需要可执行代码,则应将方法 body 编写为不包含任何语句的块:“{}”。
方法 body 中 return 语句规则在 14.17 中指定。
如果方法被声明为有返回类型(8.4.5),则如果方法的 body 可以正常完成(14.1),这是一个编译时错误。
换言之,有返回类型的方法必须通过使用提供值返回的 return 语句来返回;不允许此方法“结束其 body”。有关方法 body 中 return 语句的精确规则,请参见 14.17。
类 C 从它的直接父类继承父类的所有具体化方法 m(包括 static 和实例),只要以下所有为 true:
* m 是 C 的直接父类的成员。
* m 是 public、protected 或声明为与 C 同一包的包访问。
* C 中声明的方法都不具有这样的签名,此签名是 m 的签名的子签名(8.4.2)。
类 C 从其直接父类和直接父接口继承所有 abstract 和 default(9.4)方法 m,只要以下所有为 true:
* m 是 C 的直接父类或直接父接口 D 的成员。
* m 是 public、protected 或声明为与 C 同一包下的包访问。
* C 中声明的方法都不具有这样的的签名,此签名是 m 的签名的子签名(8.4.2)。
* 由 C 从其直接父类继承的所有具体方法都不具有这样的签名,此签名是 m 的签名的子签名。
* 不存在是 C 的直接父类或直接父接口 D 的成员的方法 m,这样,‘来自 D'的 m 重写方法 m 的声明。
类不继承来自其父接口的 static 方法。
注意,继承的具体方法可能阻止 abstract 或 default 方法的继承。(稍后,我们将断言,具体方法重写“来自 C”的 abstract 或 default 方法。)而且,一个父类型方法可能阻止另一个父类型方法的继承,如果前者“已经”重写了后者 - 这与接口规则(9.4.1)相同,并防止冲突,其中继承多个 default 方法,且一个实现显然意味着取代另一个。
注意,方法在按签名的基础上被重写或隐藏。例如,如果类声明了两个具有相同名称(8.4.9)的 public 方法,而子类重写了它们中的一个,则子类仍然继承另一个方法。
在类 C 中声明的或被类 C 继承的实例方法 mC,重写了来自 C 的另一个在类 A 中声明的方法 mA,当且仅当以下所有为 true:
* A 是 C 的父类。
* C 不继承 mA。
* mC 的签名是 mA 的签名的子签名(8.4.2)。
* 以下之一为 true:
mA 是 public。
mA 是 protected。
mA 被声明为与 C 同一包下的包访问,并且 C 声明,mC 或 mA 是 C 的直接父类的成员。
mA 被声明为包访问,并且 mC 重写来自 C 的某些父类的 mA。
mA 被声明为包访问,并且 mC 重写来自 C 的方法 m'(m' 与 mC 和 mA 不同),这样 m' 重写来自 C 的某些父类的 mA。
如果非 abstract 方法 mC 重写来自类 C 的 abstract 方法 mA,则 mC 被称为 implement 来自 C 的 mA。
在类 C 中声明的或被类 C 继承的实例方法 mC,重写来自 C 的另一个在接口 I 中声明的方法 mI,当且仅当以下所有为 true:
* I 是 C 的父接口。
* mI 是 abstract 或 default 方法。
* mC 的签名是 mI 的签名的子签名(8.4.2)。
重写方法的签名可能不同于被重写的,如果方法之一中的形式参数是原始类型,而另一个中的相应参数是参数化类型。这容许早已存在的代码的迁移,以充分利用泛型的优势。
重写的概念还包括这些方法,它们重写来自声明它们的类的某些子类的另一个。这可能发生在两个方面:
* 泛型父类中的具体方法,在某些参数化下,可以具有与该类中的 abstract 方法相同的签名。在这种情况下,具体方法被继承,而 abstract 方法不会(如上文所述)。然后,应认为被继承的方法重写了其来自 C 的 abstract 对等方。(此方案因包访问而复杂化:如果 C 在不同的包中,则无论如何都不会继承 mA,也不应被视为重写。)
* 继承自类的方法可以重写父接口方法。(幸运地是,这里不用关心包访问。)
如果实例方法重写 static 方法,则这是一个编译时错误。
在这方面,方法重写不同于字段隐藏(8.3),因为允许实例变量隐藏 static 变量。
可以通过使用包含关键字 super 的方法调用表达式(15.12)来访问被重写的方法。在尝试访问被重写的方法时,限定名称或到父类类型的强制转换是无效的。
在这方面,方法重写不同于字段隐藏。
strictfp 修饰符的存在或缺失对重写方法和实现 abstract 方法的规则绝对没有影响。例如,允许非 FP-strict 方法重写 FP-strict 方法,并且允许 FP-strict 方法重写非 FP-strict 方法。
如果类 C 声明或继承 static 方法 m,则 m 被称为隐藏了任何方法 m',其中 m 的签名是 m' 的签名的子签名(8.4.2),在 C 的父类和父接口中,其对 C 中的代码来说是可访问的。
如果 static 方法隐藏实例方法,则这是一个编译时错误。
在这方面,方法隐藏与字段隐藏(8.3)不同,因为允许 static 变量隐藏实例变量。隐藏也不同于遮蔽(6.4.1)和模糊(6.4.2)。
可以通过使用限定名称或通过使用包含关键字 super 或强制转换到父类类型的方法调用表达式(15.12)来访问被隐藏的方法。
在这方面,方法隐藏类似于字段隐藏。
如果带有返回类型 R1 的方法声明 d1 重写或隐藏另一个带有返回类型 R2 的方法 d2 的声明,则 d1 必须是 d2 的返回类型替代(8.4.5),否则发生一个编译时错误。
此规则允许协变返回类型 - 细化方法的返回类型在重写它时。
如果 R1 不是 R2 的子类型,则发生一个编译时未检查警告,除非被 SuppressWarnings 注解(9.6.4.5)抑制。
重写或隐藏另一个方法的方法,包括 implement 接口中定义的 abstract 方法的方法,不可以被声明比被重写的或被隐藏的方法抛出更多的已检查异常。
在这方面,方法重写与字段隐藏(8.3)不同,因为允许字段隐藏另一个类型的字段。
更精确地说,假设 B 是类或接口,而 A 是 B 的父类或父接口,B 中的方法声明 m2 重写或隐藏 A 中的方法声明 m1。则:
* 如果 m2 有 throws 子句,其提及了任何已检查异类型,则 m1 必须有 throws 子句,否则发生一个编译时错误。
* 对于在 m2 的 throws 子句中列出的每个已检查异常类型,相同的异常类或其子类型之一必须出现在 m1 的 throws 子句的擦除(4.6)中;否则,发生一个编译时错误。
* 如果擦除过的 m1 的 throws 子句不包含 m2 的 throws 子句中的每个异常类型的父类型(如果需要,应修改为 m1 的类型参数),则发生一个编译时未检查警告。
如果类型声明 T 有成员方法 m1,并且存在在 T 或 T 的父类型中声明的方法 m2,这样以下所有为 true,则这是一个编译时错误:
* m1 和 m2 具有相同的名称。
* m2 从 T 中是可访问的。
* m1 的签名不是 m2 的签名的子签名(8.4.2)。
* m1 的签名或某些方法 m1 的重写(直接或间接)具有与 m2 的签名或某些方法 m2 的重写(直接或间接)相同的擦除。
这些限制是必需的,因为泛型是通过擦除实现的。上述规则意味着在同一类中声明的具有相同名称的方法必须具有不同的擦除。它还意味着类型声明不能实现或扩展同一泛型接口的两个不同调用。
重写的或隐藏的方法的访问修饰符(6.6)必须至少提供与被重写的或被隐藏的方法相同的访问,如下所示:
* 如果被重写的或被隐藏的方法是 public,则重写或隐藏方法必须是 public;否则,发生一个编译时错误。
* 如果被重写的或被隐藏的方法是 protected,则重写或隐藏方法必须是 protected 或 public;否则,发生一个编译时错误。
* 如果被重写或被隐藏的方法具有包访问,则重写或隐藏方法不能是 private;否则,发生一个编译时错误。
注意,private 方法不能在这些术语的技术意义上被隐藏或重写。这意味着子类可以声明与其父类之一中的 private 方法签名相同的方法,且此类方法的返回类型或 throws 子句不需要承担任何与父类中的 private 方法的返回类型或 throws 子句之间的关系。
类可能继承多个具有重写等价签名(8.4.2)的方法。
如果类 C 继承具体方法,其签名与 C 继承的另一个方法是重写等价的,则这是一个编译时错误。
如果类 C 继承 default 方法,其签名与 C 继承的另一个方法是重写等价的,除非存在在 C 的父类中声明的和被 C 继承的与这两个方法是重写等价的 abstract 方法,否则这是一个编译时错误。
当 abstract 方法在父类中声明时,将产生严格的 default-abstract 和 default-default 冲突规则的异常:来自父类层次结构的抽象性的断言实质上超过 default 方法,从而使 default 方法看起来像 abstract 方法。但是,来自类的 abstract 方法不重写 default 方法,因为仍然允许接口改善来自类层次结构的 abstract 方法的签名。
注意,如果所有由 C 继承的重写等价 abstract 方法声明在接口中,则该异常不适用。
否则,重写等价方法集合由至少一个 abstract 方法和零个或多个 default 方法组成;然后此类必须是 abstract 类,且被视为继承所有方法。
被继承的方法之一必须是每个其它被继承的方法的返回类型代替;否则,发生一个编译时错误。(这种情况下,throws 子句不会导致错误。)
有几种路径,通过它们从接口继承相同的方法声明。这一事实不会造成任何困难,也不会导致编译时错误。
如果类的两个方法(不管是在同一类中声明的,或是继承自一个类,或一个是声明的,一个是继承的)具有相同的名称,但签名不是重写等价的,则此方法名称被称为是重载的。
这一事实不会造成任何困难,也不会导致编译时错误。具有相同名称的两个方法不要求返回类型或 throws 子句之间的关系,除非它们的签名是重写等价的。
当调用方法(15.12)时,实际参数(以及任何显式的类型参数)的数量和参数的编译时类型用于,在编译时,确定将被调用(15.12.2)的方法的签名。如果被调用的方法是实例方法,被调用的实际方法将在运行时确定,使用动态方法查找(15.12.4)。
成员类是其声明直接封闭在另一个类或接口声明(8.1.6,9.1.4)的 body 中的类。
成员接口是其声明直接封闭在另一个类或接口声明(8.1.6,9.1.4)的 body 中的接口。
类或接口声明中成员类型的可访问性在 6.6 中指定。
如果同一关键字作为类中成员类型的修饰符出现不止一次,则这是一个编译时错误。
成员类型的作用域和遮蔽在 6.3 和 6.4 中指定。
如果类声明了具有特定名称的成员类型,则该类型声明被称为隐藏此类的父类和父接口中具有相同名称的任何和所有可访问的成员类型声明。
在这方面,成员类型隐藏类似于字段隐藏(8.3)。
类从其直接父类和直接父接口继承父类和父接口的所有非 private 成员类型,两者对此类中的代码来说都是可访问的,且未被此类中的声明隐藏。
类可以继承两个或更多个具有相同名称的类型声明,来自两个接口或来自其父类和一个接口。试图通过简单名称模糊不清地引用任何继承的类或接口,是一个编译时错误。
如果通过多个路径从一个接口继承相同的类型声明,则类或接口被视为仅被继承一次。可以通过它的简单名称来引用它,而不引起歧义。
static 关键字可以修改非内部类或接口 T 的 body 中的成员类型 C 的声明。它的作用是,声明 C 是不是内部类。就像 T 的 static 方法在其 body 中没有 T 当前实例一样,C 也没有 T 的当前实例,它也没有任何词法封闭实例。
如果 static 类包含封闭类的非 static 成员的使用,则这是一个编译时错误。
成员接口隐式地是 static(9.1.1)。允许成员接口声明冗余地指定 static 修饰符。
当创建类的实例(12.5,15.9,8.8.7.1)时,执行类中声明的实例初始化器。
InstanceInitializer:
Block
如果实例初始化器无法正常完成(14.21),则这是一个编译时错误。
如果实例初始化器内出现了 return 语句(14.17),则这是一个编译时错误。
允许实例初始化器通过关键字 this(15.8.3)引用当前对象,使用关键字 super(15.11.2,15.12),使用作用域中的任何类型变量。
在原文中声明出现在使用后面的实例变量的使用有时受到限制,即使这些实例变量在作用域中。有关控制转发引用到实例变量的精确规则,请参见 8.3.3。
当初始化类时(12.4.2),执行声明在类中的 static 初始化器。与任何类变量(8.3.2)的字段初始化器一起,可以使用 static 初始化器来初始化类的类变量。
StaticInitializer:
static Block
如果 static 初始化器无法正常完成(14.21),则这是一个编译时错误。
如果 static 初始化器中出现了 return 语句(14.17),则这是一个编译时错误。
如果关键字 this(15.8.3)或关键字 super(15.11,15.12)或 static 初始化器外声明的任何类型变量,出现在 static 初始化器中,则这是一个编译时错误。
在原文中声明声明出现在使用后面的类变量的使用有时受到限制,即使这些类变量在作用域中。有关控制转发引用到类变量的精确规则,请参见 8.3.3。
static 初始化器的异常检查在 11.2.3 中指定。
构造器在是类(12.5,15.9)的实例的对象创建中使用。
ConstructorDeclaration:
{ConstructorModifier} ConstructorDeclarator [Throws] ConstructorBody
ConstructorDeclarator:
[TypeParameters] SimpleTypeName ( [FormalParameterList] )
SimpleTypeName:
Identifier
本节中的规则适用于所有类声明中的构造器,包括枚举声明。但是,特殊规则适用于有关构造器修饰符、构造器函数体和默认构造器的枚举声明;这些规则列于 8.9.2。
ConstructorDeclarator 中的 SimpleTypeName 必须是包含构造器声明的类的简单名称,否则发生一个编译时错误。
在所有其它方面,构造器声明看起来就像没有结果(8.4.5)的方法声明。
通过类实例创建表达式(15.9)、由字符串串联运算符 +(15.18.1)引起的转换和串联,和来自其它构造器(8.8.7)的显式的构造器调用来调用构造器。通过访问修饰符(6.6)来控制构造器的访问,因此可以通过声明不可访问的构造器(8.8.10)来阻止实例化。
从不通过方法调用表达式(15.12)来调用构造器。
构造器的形式参数在语法和语义上与方法的(8.4.1)等价。
非 private 的内部成员类的构造器隐式声明,作为第一个形式参数,表示类的直接封闭实例的变量(15.9.2,15.9.3)。
为什么只有这种类具有隐式声明的构造器参数的理由是微妙的。下面的解释可能有帮助:
1. 非 private 内部成员类的类实例创建表达式中,15.9.2 指定了成员类的直接封闭实例。此成员类可能是由编译器发出的,它与类实例创建表达式的编译器不同。因此,必须有一个标准的方式来让创建表达式的编译器将引用(表示直接封闭实例)传递给成员类的构造器。因此,Java 编程语言在本节中认为,非 private 的内部成员类的构造器隐式声明直接封闭实例的初始参数。15.9.3 指定将该实例传递给构造器。
2. 在局部类(不在 static 上下文中)或匿名类的类实例创建表达式中,15.9.2 指定局部/匿名类的直接封闭实例。局部/匿名类必须由类实例创建表达式相同的编译器发出。该编译器可以表示它希望的值集封闭实例。Java 编程语言不需要在局部/匿名类的构造器中隐式声明参数。
3. 在匿名类的类实例创建表达式中,以及匿名类的父类是内部的或局部的(不在 static 上下文中),15.9.2 指定了与父类有关的匿名类的直接封闭实例。此实例必须从匿名类传输到其父类,其中它充当直接封闭实例。由于父类可能是由与类实例创建表达式的编译器不同的编译器发出的,因此必须以标准方式传输实例,通过将它作为第一个参数传递给父类构造器。注意,匿名类本身必须是由与类实例创建表达式相同的编译器发出的,所以编译器可能将有关父类的直接封闭实例传输到它希望的匿名类,在匿名类将实例传递给父类构造器之前。但是,为了保持一致性,Java 编程语言在 15.9.5.1 中认为,在某些情况下,匿名类的构造器隐式声明了有关父类的直接封闭实例的初始参数。
事实上,非 private 内部成员类可以通过与编译它的不同的编译器来访问,而局部或匿名类总是通过编译它的同一编译器访问,这解释了为什么非 private 内部成员类的二进制名称被定义成可预见的,但局部或匿名类的二进制名称却不是(13.1)。
声明两个在类中具有重写等价签名(8.4.2)的构造器,是一个编译时错误。
声明两个构造器,其签名在类中具有相同的擦除(4.6),是一个编译时错误。
ConstructorModifier:
(one of)
Annotation public protected private
构造器声明上注解修饰符的规则在 9.7.4 和 9.7.5 中指定。
如果相同的关键字在构造器声明中出现多于一次,则这是一个编译时错误。
在标准类声明中,没有访问修饰符的构造器声明具有包访问。
如果两个或更多个(不同的)方法修饰符出现在方法声明中,则通常,尽管不是必须的,它们出现的顺序与以上在 MethodModifier 产品中显示的顺序一致。
与方法不同,构造器不能是 abstract、static、final、native、strictfp 或 synchronized:
构造器不能继承,因此不需要将它声明为 final。
abstract 构造器永远不能被实现。
构造器调用始终有关一个对象,因此构造器是 static 的是没有意义的。
没有实际需要同步构造器,因为它会锁定正在构造中的对象,直到该对象的所有构造器在完成它们的工作之前,通常都不会将它提供给其它线程。
缺少 native 构造器是主观的语言设计选择,这使 Java 虚拟机的实现易于判定父类构造器在对象创建期间总是正确调用。
无法将构造器声明为 strictfp(与方法相比(8.4.3))是有意的语言设计选择;它有效地确保了构造器是 FP-strict 的,当且仅当类是 FP-strict(15.4)的。
构造器是泛型的,如果它声明一个或多个类型变量(4.4)。
这些类型变量被称为构造器的类型参数。泛型构造器的类型参数部分的形式与泛型类(8.1.2)的类型参数部分相等。
构造器可以单独地是泛型的,而不管声明该构造器的类本身是否是泛型的。
泛型构造器声明定义了一组构造器,每个都是通过类型参数的类型参数部分的可能调用。当调用泛型构造器时,可以不需要显式地提供类型参数,因为通常可以推断出它们(18(Type Inference))。
构造器类型参数的作用域和遮蔽在 6.3 和 6.4 中指定。
构造器的 throws 子句在结构和行为上与方法(8.4.6)的 throws 子句相等。
构造器的类型由其签名和其 throws 子句给定的异常类型组成。
构造器 body 的第一个语句可以是同一类或直接父类(8.8.7.1)的另一个构造器的显式调用。
ConstructorBody:
{ [ExplicitConstructorInvocation] [BlockStatements] }
如果构造器通过一系列包含一个或多个显式涉及 this 的构造器调用来直接或间接地调用自身,则这是一个编译时错误。
如果构造器 body 不以显式的构造器调用开头,并且声明的构造器不是原始的类 Object 的一部分,则构造器 body 隐式地以父类构造器调用“super();”开头,其直接父类的无参的构造器调用。
除了可能的显式的构造器调用和禁止显式返回值(14.17)以外,构造器 body 类似于方法 body(8.4.7)。
return 语句(14.17)可以用在构造器 body 中,如果它不包含表达式。
ExplicitConstructorInvocation: [TypeArguments] this ( [ArgumentList] ) ; [TypeArguments] super ( [ArgumentList] ) ; ExpressionName . [TypeArguments] super ( [ArgumentList] ) ; Primary . [TypeArguments] super ( [ArgumentList] ) ;
为了方便,以下来自 4.5.1 和 15.12 的产生式在这显示:
TypeArguments: < TypeArgumentList >
ArgumentList: Expression {, Expression}
显式的构造器调用语句分为两类:
* 以关键字 this(可能以显式类型参数开头)开头的备用构造器调用。它们用于调用同一类的备用构造器。
* 以关键字 super(可能以显式类型参数开头)或 Primary 表达式 或 ExpressionName 开头的父类构造器调用。它们用于调用直接父类的构造器。它们进一步分为:
以关键字 super(可能以显式类型参数开头)开头的未限定的父类构造器调用。
以 Primary 表达式或 ExpressionName 开头的限定的父类构造器调用。它们允许子类构造器显式地指定新创建对象的直接封闭实例,与直接父类(8.1.3)有关。当父类是内部类时,这可能是必需的。
构造器 body 中的显式构造器调用语句不可以引用任何实例变量或实例方法或此类中的内部类或任何父类,或在任何表达式中使用 this 或 super;否则,发生一个编译时错误。
此禁止使用当前实例解释了为什么显式构造器调用语句被视为在 static 上下文(8.1.3)中发生。
如果 this 或 super 的左侧存在 TypeArguments,则如果任何类型参数是通配符(4.5.1),这是一个编译时错误。
让 C 是将被实例化的类可,让 S 是 C 的直接父类。
如果父类构造器调用语句是未限定的,则:
* 如果 S 是内部类,但 S 不是 C 的词法封闭类型声明的成员,则发生一个编译时错误。
如果父类构造器调用语句是限定的,则:
* 如果 S 不是内部类,或如果 S 的声明出现在 static 上下文中,则发生一个编译时错误。
* 否则,让 p 是直接出现在“.super”前面的 Primary 表达式或 ExpressionName,让 O 是 S 的直接封闭类。如果 p 的类型不是 O 或 O 的父类,或如果 p 的类型不是可访问的(6.6),则这是一个编译时错误。
显式构造器调用语句可以抛出的异常在 11.2.2 中指定。
备用构造器调用语句的计算将首先从左向右计算构造器的参数进行,就像普通的方法调用中一样;然后调用构造器。
父类构造器调用语句的计算按如下进行:
-
让 i 是被创建的实例。存在 i 的有关 S(如果有)的直接封闭实例必须被确定:
1.1 如果 S 不是内部类,或如果 S 的声明出现 static 上下文中,则不存在 i 的有关 S 的直接封闭实例。
1.2 如果父类构造器调用是未限定的,则 S 必须是局部类或内部成员类。
让 O 是 S 的直接封闭类,让 n 是一个整数,这样 O 是 C 的第 n 个词法封闭类型声明。 i 的有关 S 的直接封闭实例是 this 的第 n 个词法封闭实例。
1.3 如果父类构造器调用是限定的,则直接出现在“.super”前面的 Primary 表达式或 ExpressionName,p,被计算。
如果 p 计算为 null,则抛出一个 NullPointerException,父类构造器调用突然完成。 否则,此计算的结果是有关 S 的 i 的直接封闭实例。
-
在确定有关 S (如果有)的 i 的直接封闭实例之后,父类构造器调用语句的计算通过从左向右计算构造器的参数进行,就像普通方法调用中一样;然后调用构造器。
-
最后,如果父类构造器调用语句正常完成,则 C 的所有实例变量初始化器和 C 的所有实例初始化器被执行。如果原文中实例初始化器或实例变量初始化器 I 在实例初始化器或实例变量初始化器 J 之前,则 I 在 J 之前被执行。
不管父类构造器调用实际作为显式构造器调用语句出现还是隐式提供,实例变量初始化器和实例初始化器的执行被运行。(备用构造器调用不运行额外的隐式执行。)
构造器重载在行为上与方法(8.4.9)重载一致。在编译时通过每个类实例创建表达式(15.9)来解析重载。
如果类不包含任何构造器声明,则隐式声明一个默认构造器。顶层类、成员类或局部类的默认构造器的形式如下:
* 默认构造器具有与类(6.6)相同的可访问性。
* 默认构造器没有形式参数,除了在非 private 内部成员类中,其中默认构造器隐式声明一个形式参数,表示类(8.8.1,15.9.2,15.9.3)的直接封闭实例。
* 默认构造器没有 throws 子句。
* 如果声明的类是原始的类 Object,则默认构造器没有 body。否则,默认构造器简单地调用没有参数的父类构造器。
匿名类的默认构造器的形式在 15.9.5.1 中指定。
如果隐式声明默认构造器,但父类没有可访问的没有参数和 throws 子句的构造器,则这是一个编译时错误。
通过声明至少一个构造器,并将所有构造器声明为 private,类可以设计为防止类声明以外的代码创建类的实例,以防止默认构造器的创建。
public 类同样可以防止它的包以外的实例的创建,通过声明至少一个构造器,为防止具有 public 访问的默认构造器的创建,同时将所有构造器都不声明为 public。
枚举声明指定新的枚举类型,一种特殊的类类型。
EnumDeclaration:
{ClassModifier} enum Identifier [Superinterfaces] EnumBody
如果枚举声明具有修饰符 abstract 或 final,则这是一个编译时错误。
枚举声明是隐式 final 的,除非它包含至少一个具有类 body(8.9.1)的枚举常量。
嵌套枚举类型是隐式 static 的。允许嵌套枚举类型声明冗余地指定 static 修饰符。
这意味着,可以在内部类 body 中声明枚举类型,因为内部类无法具有除了常变量以外的 static 成员。
如果相同的关键字作为枚举声明的修饰符出现多次,则这是一个编译时错误。
枚举类型 E 的直接父类是 Enum(8.1.4)。
除了那些由枚举常量定义的实例以外,枚举类型没有任何其它实例。如果试图显式地实例化枚举类型(15.9.1),则这是一个编译时错误。
除了编译时错误之外,还有三种机制确保,没有枚举类型的实例存在于那些由其枚举常量定义的范围之外:
Enum 中的 final clone 方法确保,永远不能克隆枚举常量。
禁止枚举类型的反射实例化。
序列化机制的特殊处理确保,副本实例永远不会因为反序列化而创建。
枚举声明的 body 可以包含枚举常量。一个枚举常量定义枚举类型的一个实例。
EnumBody:
{ [EnumConstantList] [,] [EnumBodyDeclarations] }
EnumConstantList:
EnumConstant {, EnumConstant}
EnumConstant:
{EnumConstantModifier} Identifier [ ( [ArgumentList] ) ] [ClassBoyd]
EnumConstantModifier:
Annotation
为了方便,以下来自 15.12 的产生式在这显示:
ArgumentList:
Expression {, Expression}
枚举常量声明上注解修饰符规则在 9.7.4 和 9.7.5 中指定。
EnumConstant 中的 Identifier 可以在引用枚举常量的名称中使用。
枚举常量的作用域和遮蔽在 6.3 和 6.4 中指定。
枚举常量可以后跟参数,将其传递给枚举的构造器,当在类初始化期间创建常量时,如本节后面所述。使用重载解析(15.12.2)的标准规则来选择调用的构造器。如果忽略参数,则假设一个空的参数列表。
枚举常量的可选的类 body 隐式地定义了一个扩展直接封闭枚举类型的匿名类声明(15.9.5)。类 body 受匿名类通常规则的控制;特别是它们不能包含任何构造器。在这些类的 bodies 中声明的实例方法可以在封闭枚举类型之外调用,仅当它们重写了封闭枚举类型(8.4.8)中的可访问方法时。
如果枚举常量的类 body 声明 abstract 方法,则这是一个编译时错误。
因为每个枚举常量仅对应一个实例,因此如果知道它们中至少一个引用枚举常量,当比较两个对象引用时,允许使用 == 运算符代替 equals 方法。
Enum 中的 equals 方法是 final 方法,它仅仅在它的参数上调用 super.equals,因此运行一个身份比较。
除了枚举常量之外,枚举声明的 body 可以包含构造器和成员声明,以及实例和 static 初始化器。
EnumBodyDeclaration:
; {ClassBodyDeclaration}
为了方便,以下来自 8.1.6 的产品在这显示:
ClassBodyDeclaration:
ClassMemberDeclaration
InstanceInitializer
StaticInitializer
ConstructorDeclaration
ClassMemberDeclaration:
FieldDeclaration
MethodDeclaration
ClassDeclaration
InterfaceDeclaration
;
枚举声明的 body 中的任何构造器或成员声明都完全像标准类声明的 body 中已经存在的一样适用于枚举类型,除非另有明确说明。
如果枚举声明中的构造器声明是 public 或 protected(6.6),则这个一个编译时错误。
如果枚举声明中的构造器声明包含父类构造器调用语句(8.8.7.1),则这是一个编译时错误。
从枚举类型的构造器、实例初始化器或实例变量初始化器表达式中引用枚举类型的 static 字段,是一个编译时错误,除非此字段是常变量(4.12.4)。
在枚举声明中,无访问修饰符的构造器声明是 private。
在无构造器声明的枚举声明中,隐式声明默认构造器。默认构造器是 private,没有形式参数,且没有 throws 子句。
实际上,编译器可能会通过在枚举类型的默认构造器中声明 String 和 int 参数来反映 Enum 类型。但是,这些参数未指定为“隐式声明”,因为不同的编译器不需要就默认构造器的形式达成一致。只有枚举类型的编译器知道如何实例化枚举常量;其它编译器可以简单地依赖隐式声明的枚举类型(8.9.3)的 public static 字段,而不考虑这些字段是如何初始化的。
如果枚举声明 E 具有 abstract 方法 m 作为成员,则除非 E 具有至少一个枚举常量,并且 E 的枚举常量具有类 bodies,其提供 m 的具体实现,否则这个一个编译时错误。
如果枚举声明声明终结器(12.6),则这个一个编译时错误。枚举类型的实例永不终结。
枚举类型 E 的成员如下所有:
* E 的声明的 body 中声明的成员。
* 继承自 Enum 的成员。
* 对于在 E 的声明 body 中声明的每个枚举常量 c,E 具有隐式声明的类型 E 的 public static final 字段,它们具有与 c 相同的名称。这些字段具有由 c 组成的变量初始化器,并且被与 c 相同的注解注解。
这些字段以与相应枚举常量相同的顺序隐式声明的,在任何显式地声明在 E 的声明 body 中的 static 字段之前。
当初始化相应的隐式声明的字段时,表示将创建枚举常量。
* 以下隐式声明的方法:
因此,枚举类型 E 的声明不能包含与隐式声明到的对应 E 的枚举常量的字段相冲突的字段,也不能包含与隐式声明的方法相冲突或重写类 Enum 的 final 方法。