Skip to content

Latest commit

 

History

History
1175 lines (649 loc) · 679 KB

09-接口.md

File metadata and controls

1175 lines (649 loc) · 679 KB

第九章 接口

接口声明引入了一种新的引用类型,其成员是类、接口、常量和方法。此类型没有实例变量,且通常声明一个或多个 abstract 方法;另外,不相关的类可以通过提供它的 abstract 方法的实现来实现接口。接口不可以直接实例化。

嵌套接口是声明出现在另一个类或接口的 body 中的任何接口。

顶层接口是不是嵌套接口的接口。

我们区分两种接口 - 标准接口和注解类型。

本章讨论了所有接口的通用语义 - 标准接口,包括顶层(7.6)和嵌套(8.5,9.5),和注解类型(9.6)。特定于具体种类接口的详情信息将在专门用于这些概念的部分中讨论。

程序可以使用接口,使相关类不必共享公共 abstract 父类或向 Object 添加方法。

接口可以声明为一个或多个其它接口的直接扩展,这意味着它继承其扩展的的接口的所有成员类型、实例方法和常量,除它可能重写或隐藏的任何成员之外。

类可以声明为直接实现一个或多个接口,这意味着类的任何实例都实现由接口指定的所有 abstract 方法。类必须实现其直接父类和直接父接口所那样做的所有接口。此(多)接口继承允许对象在不共享父类的情况下支持(多个)公共行为。

声明类型是接口类型的变量可以将其值作为实现指定接口的类的任何实例的引用。类恰好实现了接口的所有 abstract 方法,这是不够的;类或其父类之一必须实际声明为实现接口,否则类不被视为实现此接口。

9.1. 接口声明

接口声明指定一种新的命名引用类型。有两种接口声明 - 标准接口声明和注解类型声明(9.6)。

InterfaceDeclaration:
    NormalInterfaceDeclaration
    AnnotationTypeDeclaration

NormalInterfaceDeclaration:
    {InterfaceModifier} interface Identifier [TypeParameters] [ExtendsInterfaces] InterfaceBody

接口声明中的 Identifier 指定接口的名称。

如果接口具有与任何其封闭类或接口相同的简单名称,则这是一个编译时错误。

接口声明的作用域和遮蔽在 6.3 和 6.4 中指定。

9.1.1. 接口修饰符

接口声明可以包含接口修饰符。

InterfaceModifier:
    (one of)
    Annotation public protected private
    abstract static strictfp

接口声明上注解修饰符规则在 9.7.4 和 9.7.5 中指定。

访问修饰符 public(6.6)适合每种接口声明。

访问修饰符 protected 和 private 仅适合成员接口,其声明由类声明(8.5.1)直接封闭。

修饰符 static 仅适合成员接口(8.5.1,9.5),不适合顶层接口(7.6)。

如果相同的关键字作为接口声明的修饰符出现多于一次,则这是一个编译时错误。

如果两个或更多个(不同)的接口修饰符出现在接口声明中,则通常,尽管不是必须的,它们以以上 InterfaceModifier 产生式中显示的一致的顺序出现。

9.1.1.1. abstract 接口

每个接口都是隐式 abstract。
此修饰符已过时,不应在新程序中使用。

9.1.1.2. strictfp 接口

strictfp 修饰符的作用是,使接口声明中的所有 float 或 double 表达式都是显式 FP-strict(15.4)。

这意味着接口中声明的所有方法和接口中声明的所有嵌套类型都是隐式 strictfp。

9.1.2. 泛型接口和类型参数

接口是泛型的,如果它声明了一个或多个类型变量(4.4)。

这些类型变量被称为接口的类型参数。类型参数部分跟在接口名称后面,并用尖括号分隔。

为了方便,以下来自 8.1.2 和 4.4 的产品在这显示:

TypeParameters:
    < TypeParameterList >

TypeParameterList:
    TypeParameter {, TypeParameter}

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 中指定。

如果在接口 I 的字段或类型成员的声明中的任何位置引用接口 I 的类型参数,则这是一个编译时错误。

泛型接口声明定义了一组参数化类型(4.5),一个通过类型参数用于每个类型参数部分的可能参数化。所有这些参数化类型在运行时共享相同的接口。

9.1.3. 父接口和子接口

如果提供了 extends 子句,则声明的接口扩展每个其它命名接口,并因此继承每个其它命名接口的成员类型、方法和常量。

这些其它命名接口是声明的接口的值集父接口。

任何实现被声明的接口的类也被视为实现此接口扩展的所有接口。

ExtendsInterfaces:
    extends InterfaceTypeList

为了方便,以下来自 8.1.5 的产生式在这显示:

InterfaceTypeList:
    InterfaceType {, InterfaceType}

接口声明的 extends 子句中的每个 InterfaceType 必须命名一个可访问的接口类型(6.6),否则发生一个编译时错误。

如果 InterfaceType 有类型参数,则它必须表示一个格式良好的参数化类型(4.5),且没有类型参数可以是通配符类型参数,否则发生一个编译时错误。

给定一个泛型接口声明 I<F1,...,Fn> (n > 0),参数化接口类型 I<T1,...,Tn> 的直接父接口,其中 Ti (1 ≤ i ≤ n) 是一个类型,是所有类型 J<U1 θ,...,Uk θ>,其中 J<U1,...,Uk> 是 I<F1,...,Fn> 的一个直接父接口,且 θ 是替代 [F1:=T1,...,Fn:=Tn]。

父接口关系是直接父接口关系的传递闭包。接口 K 是接口 I 的父接口,如果以下两者之一为 true:

    * K 是 I 的直接父接口。

    * 存在一个接口 J,K 是 J 的父接口,且 J 是 I 的父接口,递归地应用此定义。

接口 I 被称为是接口 K 的一个子接口,只要 K 是 I 的父接口。

虽然每个类都是类 Object 的扩展,但没有一个接口是所有接口的扩展。

接口 I 直接依赖类型 T,如果 T 在 I 的 extends 子句中作为父接口或父接口名称的完全限定形式中的限定符提及。

接口 I 依赖引用类型 T,如果以下任何一种情况是 true:

    * I 直接依赖 T。

    * I 直接依赖类 C,C 依赖 T(8.1.5)。

    * I 直接依赖接口 J,J 依赖 T(递归地使用此定义)。

如果接口依赖自身,则这是一个编译时错误。

如果运行时检测到循环地声明接口,则在加载接口时,抛出一个 ClassCircularityError(12.2.1)。

9.1.4. 接口 body 和成员声明

接口 body 可以声明接口的成员,即,字段(9.3)、方法(9.4)、类(9.5)和接口(9.5)。

InterfaceBody:
    { {InterfaceMemberDeclaration} }

InterfaceMemberDeclaration:
    ConstantDeclaration
    InterfaceMethodDeclaration
    ClassDeclaration
    InterfaceDeclaration
    ;

由接口类型 I 在接口中声明的或继承自接口的成员 m 的声明的作用域在 6.3 中指定。

9.2. 接口成员

接口类型的成员是:

    * 在接口(9.1.4)的 body 中声明的成员。

    * 继承自任何直接父接口(9.1.3)的成员。

    * 如果接口没有直接父接口,则此接口隐式声明一个 public abstract 成员方法 m,该方法具有签名 s、返回类型 r 和 throws 子句 t,对应于声明在 Object 中的每个 public 实例方法 m,其具有签名 s、返回类型 r 和 throws 子句 t,除非具有相同签名、相同返回类型和兼容的 throws 子句的 abstract 方法被此接口显式声明。

      如果在 m 在 Object 中被声明为 final 的情况下,接口显式声明这样一个方法 m,则这是一个编译时错误。

      如果接口显式声明一个方法,其签名与 Object 的一个 public 方法是签名等效的,但其具有不同的返回类型,或不兼容的 throws 子句,或不是 abstract 的,则这是一个编译时错误。

接口,从它扩展的接口,继承那些接口的所有成员,除它隐藏的字段、类和接口之外;它重写(9.4.1)的 abstract 或 default 方法;以及 static 方法。

接口类型的字段、方法和成员类型可能具有相同的名称,因为它们用在不同的上下文中,并且不会被不同的查找过程混淆。但是,作为一种风格问题而不推荐这样做。

9.3. 字段(常量)声明

ConstantDeclaration:
    {ConstantModifier} UnannType VariableDeclaratorList ;

ConstantModifier:
    (one of)
    Annotation public
    static final

有关 UnannType,请参见 8.3。为了方便,以下来自 4.3 和 8.3 的产生式在这显示:

VariableDeclaratorList:
    VariableDeclarator {, VariableDeclarator}

VariableDeclarator:
    VariableDeclaratorId [= VariableInitializer]

VariableDeclaratorId:
    Identifier [Dims]

Dims:
    {Annotation} [ ] {{Annotation} [ ]}

VariableInitializer:
    Expression
    ArrayInitializer

接口字段声明上注解修饰符规则在 9.7.4 和 9.7.5 中指定。

接口 body 中的每个字段声明是隐式 public、static 和 final。允许冗余地为这些字段指定任何或所有修饰符。

如果相同的关键字作为字段声明的修饰符出现多于一次,则这是一个编译时错误。

如果两个或更多个(不同的)字段修饰符出现在字段声明中,则通常,但是不是必须的,它们以与以上 ConstantModifier 产品中显示的相一致的顺序出现。

如果 UnannType 和 VariableDeclaratorId 中没有出现括号对,则字段的声明类型由 UnannType 表示,并由 10.2 指定。

接口字段声明的作用域和遮蔽在 6.3 和 6.4 中指定。

如果接口声明的 body 声明了两个具有相同名称的字段,则这是一个编译时错误。

如果接口声明了特定名称的字段,则该字段的声明被称为隐藏此接口的父接口中任何和所有可访问的具有相同名称的字段声明。

接口可能继承多个具有相同名称的字段。在这种情况下,其本身不会导致编译时错误。但是,接口 body 中任何试图通过其简单名称引用此类字段都会产生一个编译时错误,因为此类引用是模糊不清的。

可能有几种路径,通过其可能从接口继承相同的字段声明。在这种情况下,字段被视为仅继承一次,并且可以通过其简单名称来引用它,而不产生歧义。

avatar

9.3.1. 接口字段的初始化

接口字段声明中每个声明符必须具有变量初始化器,否则发生一个编译时错误。

初始化器不必是常量表达式(15.28)。

如果接口字段的初始化器使用同一字段或另一字段的简单名称,这些字段的声明在原文中随后出现在同一接口中,则这是一个编译时错误。

如果关键字 this(15.8.3)或关键字 super(15.11.2,15.12)出现在接口字段的初始化器中,除非此出现是在匿名类(15.9.5)的 body 中,则这是一个编译时错误。

在运行时,计算初始化器,字段赋值仅运行一次,当初始化(12.4.2)接口时。

注意,是常变量(4.12.4)的接口字段在其它接口字段之前初始化。这也适用于类中那些是常变量的 static 字段。此类字段永远不会被观察到它们的默认初始值(4.12.5),即使通过不正当的程序。

avatar

9.4. 方法声明

InterfaceMethodDeclaration:
    {InterfaceMethodModifier} MethodHeader MethodBody

InterfaceMethodModifier:
    (one of)
    Annotation public
    abstract default static strictfp

为了方便,以下来自 8.4、8.4.5 和 8.4.7 的产生式在这显示:

MethodHeader:
    Result MethodDeclarator [Throws]
    TypeParameters {Annotation} Result MethodDeclarator [Throws]

Result:
    UnannType
    void

MethodDeclarator:
    Identifier ( [FormalParameterList] ) [Dims]

MethodBody:
    Block
    ;

接口方法声明上注解修饰符规则在 9.7.4 和 9.7.5 中指定。

接口 body 中的每个方法声明是隐式 public(6.6)。允许为接口中的方法声明冗余地指定 public 修饰符,但作为一种风格问题而不推荐。

默认方法是在接口中声明的具有 default 修饰符的方法,其 body 总是由块表示。它为实现此接口的任何类提供了一个默认实现,而不需要重写方法。默认方法与类中声明的具体方法(8.4.3.1)不同。

接口可以声明 static 方法,调用它不需要引用特定的对象。

在接口的 static 方法的 header 或 body 中使用任何周围声明的类型参数的名称,是一个编译时错误。

strictfp 修饰符的作用是使 default 或 static 方法的 body 中的所有 float 或 double 表达式是显式 FP-strict(15.4)。

缺少 default 修饰符或 static 修饰符的接口方法是隐式 abstract,因此其 body 由分号表示,而不是块。允许为此类方法声明冗余地指定 abstract 修饰符,但作为一种风格问题而不推荐。

如果相同的关键字作为接口中方法声明的修饰符出现多于一次,则这是一个编译时错误。

如果使用修饰符 abstract、default 或 static 中的多个声明方法,则这是一个编译时错误。

如果 abstract 方法声明包含关键字 strictfp,则这是一个编译时错误。

如果接口 body 显式或隐式地声明两个具有重写等效签名(8.4.2)的方法,则这是一个编译时错误。但是,接口可以继承几个具有此类签名(9.4.1)的 abstract 方法。

接口中的方法可以是泛型的。接口中泛型方法的类型参数规则与类(8.4.4)中泛型方法的相同。

9.4.1. 继承和重写

接口 I 从其直接父接口继承所有 abstract 和 default 方法 m,这些方法满足以下所有:

    * m 是 I 的直接父接口 J 的成员。

    * I 中声明的方法的签名不是 m 的签名的子签名(8.4.2)。

    * 不存在是 I 的直接父接口 J' 的成员的方法 m'(m 不同于 m',J 不同于 J'),这样,m' 重写来自 J' 的方法 m 的声明。

注意,方法以签名为基础被重写。例如,如果接口声明了两个具有相同名称(9.4.2)的 public 方法,而子接口重写其中之一,则子接口仍然继承另一个方法。

上面的第三个子句防止子接口重新继承已经被其子接口中的另一个重写的方法。例如,在该程序中:

avatar

Right 从 Top 继承 name(),但 Bottom 从 Left 继承 name()。这是因为来自 Left 的 name() 重写了 Top 中的 name() 的声明。

接口不从其父接口继承 static 方法。

如果接口 I 声明了一个 static 方法 m,m 的签名是 I 的子接口中的实例方法 m' 的子签名,m' 对 I 中的代码来说是可访问的,则发生一个编译时错误。

本质上,接口中的 static 方法无法“隐藏”父接口中的实例方法。这类似于 8.4.8.2 中的规则,即类中的 static 方法无法隐藏父类或父接口中的实例方法。注意,8.4.8.2 中的规则说的是“声明或继承 static 方法”的类,而上面的规则仅说的是“声明 static 方法”的接口,因为接口无法继承 static 方法。还请注意,8.4.8.2 中的规则允许隐藏父类/父接口中的实例和 static 方法,而上面的规则仅考虑父接口中的实例方法。

9.4.1.1. 重写(通过实例方法)

由接口 I 声明的或继承的实例方法 m1 从 I 中重写另一个在接口 J 中声明的实例方法 m2,当且仅当以下为 true:

    * I 是 J 的子接口。

    * m1 的签名是 m2 的签名的子签名(8.4.2)。

strictfp 修饰符的存在或缺失对重写方法规则没有任何影响。例如,允许不是 FP-strict 的方法重写 FP-strict 方法,也允许 FP-strict 方法重写不是 FP-strict 的方法。

被重写的 default 方法可以通过使用包含被父接口名称限定的关键字 super 的方法调用表达式(15.12)访问。

9.4.1.2. 重写要求

接口方法的返回类型和任何被重写的接口方法的返回类型之间的关系在 8.4.8.3 中指定。

接口方法的 throws 子句和任何被重写的接口方法的 throws 子句之间的关系在 8.4.8.3 中指定。

接口方法签名和被重写的接口方法签名之间的关系在 8.4.8.3 中指定。

如果 default 方法与类 Object 的非 private 方法是重写等效的,则这是一个编译时错误,因为任何实现此接口的类将继承它自己的方法的实现。

禁止将 Object 方法之一作为 default 方法声明可能是令人吃惊的。毕竟,已经存在一些像 java.util.List 的情况,其中 toString 和 equals 的行为是明确定义的。但是,当理解一些更广泛的设计决策时,动机变得更加清晰:

    * 首先,允许从父类继承的方法重写从父接口(8.4.8.1)继承的方法。因此,每个实现类将自动地重写接口的 toString 默认。这是 Java 编程语言的长期行为。这不是我们希望通过 default 方法的设计来改变的东西,因为这会与接口渐渐进化的目标相冲突,仅当类通过类层次结构不具有默认行为时才提供默认行为。

    * 其次,接口不继承 Object,而是隐式声明许多与 Object(9.2)相同的方法。因此,在 Object 中声明的 toString 和在接口中声明的 toString 没有共同的祖先。充其量,如果两者都是一个类的继承的候选人,它们会冲突。解决此问题需要对类和接口的继承树进行笨拙的混合。

    * 第三,在接口中声明 Object 方法的用例通常假设一个线性的接口层次结构;该特征不能很好地推广到多继承方案。

    * 第四,Object 方法是如此基础,以致于允许任意父接口悄悄地添加改变它们行为的 default 方法这一行为看起来非常危险。

但是,接口可以自由地定义另一个方法,该方法为重写 Object 方法的类提供有用的行为。例如,java.util.List 接口可以声明一个 elementString 方法,其生成由 toString 的协定描述的字符串;然后,类中 toString 的实现者可以委托给这个方法。

9.4.1.3. 具有重写等效签名的继承方法

接口可以继承几个具有重写等效签名(8.4.2)的方法。

如果接口 I 继承一个 default 方法,其签名与 I 继承的另一个方法是重写等效的,则发生一个编译时错误。(这是其它方法是 abstract 还是 default 的情况。)

否则,所有被继承的方法是 abstract,且接口被视为继承所有方法。

继承的方法之一必须是其它所有继承的方法的返回类型替代,否则发生一个编译时错误。(在这种情况下,throws 子句不会导致错误。)

可能由几种路径,通过其可以从接口继承同一方法声明。这一事实本身不会造成任何困难,也不会产生编译时错误。

自然地,如果两个不同的具有匹配签名的 default 方法被子接口继承,就会出现行为冲突。我们积极检测此类冲突并通知开发者错误,而不是在编译具体类时产生问题。可以通过声明一个重写的新方法避免此错误,从而防止所有冲突方法的继承。

类似地,当具有匹配签名的 abstract 和 default 方法被继承时,我们产生一个错误。在这种情况下,可以优先考虑一个或另一个 - 也许我们假定 default 方法为 abstract 方法提供合理的实现。但这是有风险的,因为除了巧合的名称和签名,我们没有理由相信 default 方法与 abstract 方法的协定表现一致 - 当起初开发子接口时,default 方法可能还不存在。在这种情况下,要求用户积极地断言默认实现是适当的(通过重写声明)是比较安全的。

相比之下,类中继承具体方法的长期行为是,它们重写在接口(请参见 8.4.8)中声明的 abstract 方法。关于潜在的协定违反的同一论点也适用于此,但在这种情况下,类和接口之间存在固有的不平衡。为了保持类层次结构的独立性质,我们更喜欢通过简单地给定具体方法优先级来最小化类接口冲突。

9.4.2. 重载

如果接口的两个方法(无论两者是否是声明在同一接口中,或两者是被同一接口继承,或一个是声明的和一个是继承的)具有相同的名称但不同的不是重写等效(8.4.2)的签名,则方法名称被称为是被重载。

这一事实不会导致任何问题,其本身也不会产生编译时错误。两个具有相同名称但具有不同的不是重写等效的签名的方法的返回类型之间或 throws 子句之间不必存在关系。

avatar

9.4.3. 接口方法 body

default 方法具有块 body。此代码块提供了此方法的实现,在类实现此接口的事件中,但不提供其自己的此方法的实现。

static 方法也具有块 body,其提供了此方法的实现。

如果接口方法声明是 abstract(显式或隐式)并具有块作为它的 body,则这是一个编译时错误。

如果接口方法声明是 default 或 static 并具有冒号作为它的 body,则这是一个编译时错误。

如果 static 方法试图使用关键字 this 或关键字 super 引用当前对象,则这是一个编译时错误。

方法 body 中 return 语句规则在 14.17 中指定。

如果声明一个有返回类型(8.4.5)的方法,则如果方法 body 无法正常完成(14.1),发生一个编译时错误。

9.5. 成员类型声明

接口可以包含成员类型声明(8.5)。

接口中成员类型声明是隐式 public 和 static 的。允许冗余地指定这些修饰符中一个或两者。

如果相同的关键字作为接口中成员类型声明的修饰符出现多于一次,则这是一个编译时错误。

如果接口声明一个具有特定名称的成员类型,则该类型声明被称为隐藏任何和所有可访问的在此接口的父接口中具有相同名称的成员类型的声明。

接口从其直接父接口继承父接口的所有非 private 成员类型,这些父接口对接口中代码来说是可访问的,且未被接口中的声明隐藏。

接口可以继承两个或更多个具有相同名称的类型声明。试图通过简单名称引用任何引起歧义地被继承的类或接口,是一个编译时错误。

如果通过多个路径从一个接口继承相同的类型声明,则类或接口被视为仅继承一次;可以通过其简单名称引用它,而不引起歧义。

9.6. 注解类型

注解类型声明指定一个新的注解类型,一种特殊的接口类型。为了从标准接口声明中区分注解类型声明,关键字 interface 前面有一个 at-sign(@)。

AnnotationTypeDeclaration: {InterfaceModifier} @ interface Identifier AnnotationTypeBody

注意,at-sign(@)和关键字 interface 是不同的标记。可以使用空格分隔它们,但作为一种风格问题而不推荐这样做。

注解类型声明上注解修饰符规则在 9.7.4 和 9.7.5 中指定。

注解类型声明中的 Identifier 指定了注解类型的名称。

如果注解类型具有与任何其封闭类或接口相同的简单名称,则这是一个编译时错误。

每个注解类型的直接父接口是 java.lang.annotation.Annotation。

根据 AnnotationTypeDeclaration 语法,注解类型声明无法是泛型的,并且不允许 extends 子句。

注解类型无法显式声明父类或父接口的事实的结果是,注解类型的子类或子接口本身永远不是注解类型。类似地,java.lang.annotation.Annotation 本身也不是注解类型。

注解类型从 java.lang.annotation.Annotation 继承几个成员,包括隐式声明的对应于 Object 的实例方法的方法,但这些方法未定义注解类型(9.6.1)的元素。

因为这些方法未定义注解类型的元素,所以在该类型(9.7)的注解中使用它们是非法的。如果没有这个规则,我们不能确保元素是注解中可表示的类型,或者它们的访问器方法是可用的。

除非再次进行显式修改,否则所有适用于标准接口声明的规则都将适用于注解类型声明。

例如,注解类型与正常类和接口类型共享相同的命名空间;只要接口声明是合法的,则注解类型声明也是合法的,并且具有相同的作用域和可访问性。

9.6.1. 注解类型元素

注解类型的 body 可以包含方法声明,每个定义注解类型的一个元素。除了由它显式声明的方法定义的元素之外,注解类型没有任何其它元素。

AnnotationTypeBody:
    { {AnnotationTypeMemberDeclaration} }

AnnotationTypeMemberDeclaration:
    AnnotationTypeElementDeclaration
    ConstantDeclaration
    ClassDeclaration
    InterfaceDeclaration
    ;

AnnotationTypeElementDeclaration:
    {AnnotationTypeElementModifier} UnannType Identifier ( ) [Dims] [DefaultValue] ;

AnnotationTypeElementModifier:
    (one of)
    Annotation public
    abstract

根据 AnnotationTypeElementDeclaration 产生式,注解类型声明中的方法声明无法具有形式参数、类型参数或 throws 子句。为了方便,以下来自 4.3 的产生式在这显示:

Dims:
    {Annotation} [ ] {{Annotation} [ ]}

根据 AnnotationTypeElementModifier 产品,注解类型声明中的方法声明不能是 default 或 static。因此,注解类型无法声明与标准接口类型相同种类的方法。注意,注解类型仍然可能从其隐式父接口 java.lang.annotation.Annotation 继承 default 方法,尽管没有像 Java SE 8 这样的 default 方法。

按照约定,在注解类型上应存在的唯一的 AnnotationTypeElementModifiers 是注解。

注解类型中声明的方法的返回类型必须是以下之一,否则发生一个编译时错误:

    * 基元类型

    * String

    * Class 或 Class(4.5)的调用

    * 枚举类型

    * 注解类型

    * 数组类型,其组件类型是前面的类型之一(10.1)。

avatar

允许返回数组的方法声明在空的形式参数列表之后放置表示数组类型的括号对。为了兼容早期版本的 Java 编程语言而支持此语法。强烈建议不要在新代码中使用此语法。

如果注解类型中声明的任何方法具有与类 Object 中或接口 java.lang.annotation.Annotation 中声明的任何 public 或 protected 方法重写等效的签名,则这是一个编译时错误。

如果注解类型声明 T 包含类型 T 的元素,直接或间接,则这是一个编译时错误。

avatar

没有元素的注解类型被称为标记注解类型。

具有一个元素的注解类型被称为单元素注解类型。

按照约定,单元素注解类型中的唯一元素名称是 value。对此约定的语言支持由单元素注解(9.7.3)提供。

avatar

avatar

avatar

9.6.2. 注解类型元素的默认值

注解类型元素可以具有一个默认值,用跟在元素(空的)的参数列表之后的关键字 default 和 ElementValue(9.7.1)指定。

DefaultValue:
    default ElementValue

如果元素的类型与指定的默认值是不相称的(9.7),则这是一个编译时错误。

默认值不编译进注解中,而是在读取注解时动态地应用。因此,更改默认值会影响注解,即使在更改发生之前被编译的类中的注解(假定这些逐渐诶缺少默认元素的显式值)。

avatar

9.6.3. 可重复的注解类型

注解类型 T 是可重复的,如果其声明是用一个其 value 元素表示一个包含 T 的注解类型的 @Repeatable 注解(9.6.4.8)(元)标注的。

注解类型 TC 是一个包含 T 的注解类型,如果以下所有为 true:

    1. TC 声明一个 value() 方法,其返回类型是 T[]。

    2. 任何由 TC 声明的除 value() 以外的方法具有默认值。

    3. TC 的保留时间至少与 T 相同,在这种情况下,保留用 @Retention 注解(9.6.4.2)显式或隐式地表示。特别地:

    * 如果 TC 的保留是 java.lang.annotation.RetentiionPolicy.SOURCE,则 T 的保留是 java.lang.annotation.RetentiionPolicy.SOURCE。

    * 如果 TC 的保留是 java.lang.annotation.RetentiionPolicy.CLASS,则 T 的保留是 java.lang.annotation.RetentiionPolicy.CLASS 或 java.lang.annotation.RetentiionPolicy.SOURCE。

    * 如果 TC 的保留是 java.lang.annotation.RetentiionPolicy.RUNTIME,则 T 的保留是 java.lang.annotation.RetentiionPolicy.SOURCE、java.lang.annotation.RetentiionPolicy.CLASS 或 java.lang.annotation.RetentiionPolicy.RUNTIME。

    4. T 适用于至少与 TC 一样的相同的种类的程序元素(9.6.4.1)。特别地,如果其中适用 T 的程序元素的种类由集合 m1 表示,其中适用 TC 的程序元素的种类由集合 m2 表示,则 m2 中的每种必须在 m1 中出现,除非:

    * 如果 m2 中的种类是 java.lang.annotation.ElementType.ANNOTATION_TYPE,则至少 java.lang.annotation.ElementType.ANNOTATION_TYPE 或 java.lang.annotation.ElementType.TYPE 或 java.lang.annotation.ElementType.TYPE_USE 中的一个必须在 m1 中出现。

    * 如果 m2 中的种类是 java.lang.annotation.ElementType.TYPE,则至少 java.lang.annotation.ElementType.TYPE 或 java.lang.annotation.ElementType.TYPE_USE 中的一个必须在 m1 中出现。

    * 如果 m2 中的种类是 java.lang.annotation.ElementType.TYPE_PARAMETER,则至少 java.lang.annotation.ElementType.TYPE_PARAMETER 或 java.lang.annotation.ElementType.TYPE_USE 中的一个必须在 m1 中出现。

    此子句实现的策略是,注解类型只能适用的某些种类的程序元素上是可重复的。

    5. 如果 T 的声明具有与 java.lang.annotation.Documented相对应的(元)注解,则 TC 的声明必须具有与 java.lang.annotation.Documented 相对应的(元)注解。

    注意,允许 TC 是 @Documented,而 T 不是 @Documented。

    6. 如果 T 的声明具有与 java.lang.annotation.Inherited 相对应的(元)注解,则 TC 的声明必须具有与 java.lang.annotation.Inherited 相对应的(元)注解。

    注意,允许 TC 是 @Inherited,而 T 不是 @Inherited。

如果注解类型 T 是用 @Repeatable 注解(元)标注的,而此 @Repeatale 注解的 value 元素表示不是 T 的包含注解类型的类型,则这个一个编译时错误。

avatar

@Repeatable 注解无法重复,因此只能由可重复注解类型指定一个包含注解类型。

允许指定多个包含注解类型会在编译时导致不希望的选择,当可重复注解类型的多个注解逻辑上被容器注解(9.7.5)替换时。

注解类型可以是至多一个注解类型的包含注解类型。

这由如下要求所暗示,如果注解类型 T 的声明指定 TC 的包含注解类型,则 TC 的 value() 方法具有涉及 T 的返回类型,特别是 T[]。

注解类型无法指定他自己作为它的包含容器类型。

这是由包含注解类型的 value() 方法上的要求所隐含的。具体地说,如果注解类型 A 指定自身(通过 @Repeatable)作为它的包含容器类型,则 A 的 value() 方法的返回类型必须是 A[];这将导致编译时错误,因为注解类型无法在其元素(9.6.1)中引用自身。通常,两个注解类型无法互相指定为对方的包含容器类型,因为循环注解类型声明是非法的。

注解类型 T 可能是某些注解类型 T 的包含注解类型,同时也具有其自己的包含注解类型 TC '。即,包含注解类型本身可能是可重复的注解类型。

avatar

avatar

avatar

9.6.4 预定义的注解类型

Java SE 平台的库中预定义了几种注解类型。这些预定义的注解类型的一些有特殊的语义。这些语义在本节指定。本节不提供在这包含的预定义的注解的完整规范;那是适当的 API 规范的作用。只有那些在 Java 编译器或Java 虚拟机实现部分上需要特殊行为的语义才在这指定。

9.6.4.1 @Target

java.lang.annotation.Target 注解类型用在注解类型 T 上,以指定 T 适用的上下文。java.lang.annotation.Target 具有一个值为 java.lang.ElementType[] 类型的元素,以指定上下文。

注解类型可能适用于声明上下文,其中注解适用于声明,或类型上下文,其中注解适用于在声明和表达式中使用的类型。

有八种声明上下文,每个与一个 java.lang.annotation.ElementType 枚举常量对应。

    1. 包声明
        与 java.lang.annotation.ElementType.PACKAGE 对应

    2. 类型声明:class、interface、enum 和 annotation type declaration(8.1.1,9.1.1,8.5,9.5,8.9,9.6)
        与 java.lang.annotation.ElementType.TYPE 对应
        此外,与 java.lang.annotation.ElementType.ANNOTATION_TYPE 对应的注解类型声明

    3. 方法声明(包括注解类型元素)(8.4.3,9.4,9.6.1)
        与 java.lang.annotation.ElementType.METHOD 对应

    4. 构造器声明(8.8.3)
        与 java.lang.annotation.ElementType.CONSTRUCTOR 对应

    5. 泛型类、接口、方法和构造器的类型参数声明(8.1.2,9.1.2,8.4.4,8.8.4)
        与 java.lang.annotation.ElementType.TYPE_PARAMETER 对应

    6. 字段声明(包括枚举常量)(8.3.1,9.3,8.9.1)
        与 java.lang.annotation.ElementType.FIELD 对应

    7. 形参和异常参数声明(8.4.1,9.4,14.20)
        与 java.lang.annotation.ElementType.PARAMETER 对应

    8. 局部变量声明(包括语句的循环变量和 try-with-resources 语句的 resource 变量)(14.4,14.14.1,14.14.2,14.20.3)
        与 java.lang.annotation.ElementType.LOCAL_VARIABLE 对应

有 16 个类型上下文(4.11),都由 java.lang.annotation.ElementType 的枚举常量 TYPE_USE 表示。

如果相同的枚举常量在 java.lang.annotation.Target 类型的注解元素值上出现多次,则这是一个编译时错误。

如果一个注解类型 T 的声明上不存在 java.lang.annotation.Target 类型的注解,则 T 适用于除了类型参数声明以外的所有声明上下文,并且不适用于任何类型上下文。

这些上下文是在 Java SE 7 中允许的注解的句法位置。

9.6.4.2 @Retention

注解可能仅存在于源代码中,或者它们也可能存在于类或接口的二进制形式中。在二进制形式中存在的注解通过 Java SE 平台的反射库可能或可能不在运行时可用。注解类型 java.lang.annotation.Retention 用于在这些可能中选择。

如果注解 a 与类型 T 对应,并且T具有与 java.lang.annotatioin.Retention 对应的(元)注解 m,则:

    * 如果m具有值为 java.lang.annotation.RetentionPolicy.SOURCE 的元素,则Java编译期必须确保出现 a 的类或接口的二进制表示中不存在 a。

    * 如果 m 具有值为 java.lang.annotation.RetentionPolicy.CLASS 或 java.lang.annotation.RetentiionPolicy.RUNTIME 的元素,则 Java 编译器必须确保出现 a 的类或接口的二进制表示中存在 a,除非 m 标注局部变量声明。

      局部变量声明上的注解从不在二进制表示中保留。

      此外,如果m具有值为 java.lang.annotation.RetentionPolicy.RUNTIME 的元素,则 Java SE 平台的反射库必须在运行时可用。

如果 T 没有与 java.lang.annotation.Retention 对应的(元)注解 m,则 Java 编译器必须将 T 视为这样一个具有值为 java.lang.annotation.RetentionPolicy.CLASS 的元素的元注解 m。

9.6.4.3 @Inherited

注解类型 java.lang.annotation.Inherited 用于指示与给定注解类型对应的类 C 上的注解有 C 子类继承。

9.6.4.4 @Override

编程者偶尔会重载方法声明,当他们想要重写它的时候,会导致微妙的问题。注解类型 Override 支持早期检测此类问题。

avatar

如果用注解 @Override 来标注方法声明,但该方法不重写或实现在父型中声明的方法,或者不是重写等效于 Object 的 public 方法,则发生一个编译时错误。

此行为不同于 Java SE 5.0,其中 @Override 仅当应用于实现来自父接口的在父类中不存在的方法的方法时,才导致编译时错误。

avatar

9.6.4.5. @SuppressWarnings

Java 编译器越来越有能力发布有用的“类似皮棉”警告。为了鼓励适用此类警告,当程序员知道警告不合适时,应该由某种方法可以禁用程序的某个部分中的警告。

注解类型 SuppressWarnings 支持程序员控制其它由 Java 编译器发出的警告。它包含单个元素,其是一个字符串数组。

如果一个程序声明用注解 @SuppressWarnings(value = {S1, ..., Sk})标注,则 Java 编译器不能报告任何由 S1 ... Sk 之一标识的警告,如果该警告将作为标注的声明或其任何部分的结果生成。

未检查警告由字符串“unchecked”标识。

编译器供应商应该与注解类型一起记录它们支持的警告名称。鼓励供应商合作,以确保同一名称可以跨多个编译器工作。

9.6.4.6. @Deprecated

标注有 @Deprecated 的程序元素是不鼓励程序员使用的元素,通常因为它是危险的,或因为存在更好的替代。

当在显式或隐式声明的结构中使用(通过名称重写、调用或引用)声明用 @Deprecated 标注的类型、方法、字段或构造器时,Java 编译器必须产生一个反对警告,除非:

    * 使用是在本身是用注解 @Deprecated 标注的实体中;或

    * 使用是在用注解 @SuppressWarnings("deprecation") 标注的以抑制警告的实体中;或

    * 使用和声明都在同一最外层类中。

在局部变量声明或参数声明上使用 @Deprecated 注解没有任何作用。

唯一隐式声明的可以导致反对警告的结构是容器注解(9.7.5)。即,如果 T 是可重复的注解类型,TC 是其包含注解类型,且 TC 被舍弃,则重复 @T 注解会导致反对警告。该警告是由于隐式的 @TC 容器注解。强烈不建议舍弃包含注解类型,而不同时舍弃相应的可重复的注解类型。

9.6.4.7. @SafeVarargs

具有非具体化元素类型(4.7)的可变参数可能导致堆污染(4.12.2),并引发编译时未检查警告(5.1.9)。如果此可变数量的方法在可变参数上表现良好,则此类警告是言之无物的。

注解类型 SafeVarargs,在用于标注方法或构造器声明时,使程序员断言,防止 Java 编译器为可变数量的方法或构造器的声明或调用报告未检查的警告,其中编译器这样做是由于可变参数具有非具体化的元素类型。

注解 @SafeVarargs 具有非局部效果,因为除了有关可变数量的方法本身(8.4.1)声明的未检查警告之外,它还抑制方法调用表达式中的未检查警告。相反,注解 @SuppressWarnings("unchecked") 具有局部效果,因为它仅抑制有关方法声明的未检查警告。

avatar

如果用注解 @SafeVarargs 标注固定数量的方法或构造器声明,则这是一个编译时错误。

如果用注解 @SafeVarargs 标注既不是 static 也不是 final 的可变数量的方法,则这是一个编译时错误。

因为 @SafeVarargs 仅适用于 static 方法、final 实例方法和构造器,此注解在发生方法重写的地方是不可用的。注解继承仅在类(不是方法、接口或构造器)上有作用,因此,@SafeVarargs 风格的注解不能通过类中的实例方法或通过接口传递。

9.6.4.8. @Repeatable

注解类型 java.lang.annotation.Repeatable 用在可重复的注解类型的声明上,以指示它的包含注解类型(9.6.3)。

注意,T 的声明上的 @Repeatable 元注解不足以产生 T 的包含注解类型 TC。由很多可被视为 T 的包含注解类型的用于 TC 的格式良好的规则。

9.6.4.9. @FunctionalInterface

注解类型 FunctionalInterface 用于指示,接口是函数式接口(9.8)。它便于检测出在接口中出现的或由接口继承的不适当的方法声明,该接口是函数式的。

如果用 @FunctionalInterface 标注接口声明,但事实上该接口不是函数式接口,则这是一个编译时错误。

因为有些接口恰好是函数式的,所以用 @FunctionalInterface 标注所有函数式接口声明,这是不必要的或可取的。

9.7. 注解

注解是一种标记,其将信息与程序结构关联起来,但在运行时没有任何作用。注解表示注解类型(9.6)的具体调用,通常为该类型的元素提供值。

有三种注解。第一种是最普遍的,而其它两种仅仅是第一种的简化。

Annotation:
    NormalAnnotation
    MarkerAnnotation
    SingleElementAnnotation

标准注解在 9.7.1 中描述,标记注解在 9.7.2 中,而单元素注解在 9.7.3 中。注解可以出现在程序中的多种句法位置上,如 9.7.4 中所述。同一类型注解在一个位置上出现的数量由它们的类型决定,如 9.7.5 中所述。

9.7.1. 标准注解

标准注解指定了注解类型的名称和一个可选的逗号分隔的元素值对列表。每对包含一个与注解类型(9.6.1)的元素相关联的元素值。

NormalAnnotation:
    @ TypeName ( [ElementValuePairList] )

ElementValuePairList:
    ElementValuePair {, ElementValuePair}

ElementValuePair:
    Identifier = ElementValue

ElementValue:
    ConditionalExpression
    ElementValueArrayInitializer
    Annotation

ElementValueArrayInitializer:
    { [ElementValueList] [,] }

ElementValueList:
    ElementValue {, ElementValue}

注意,at-sign(@)是对自身(3.11)的标记。可以在它和 TypeName 之间放置空格,但作为一种风格问题,不推荐这样做。

TypeName 指定与注解对应的注解类型。注解被称为“具有”该类型。

如果 TypeName 不指定在注解的地方可访问(6.6)的注解类型,则这是一个编译时错误。

元素值对中的 Identifier 必须是注解类型的元素(即,方法)之一的简单名称,否则发生一个编译时错误。

方法的返回类型定义元素值对的元素类型。

如果元素类型是数组类型,则不需要使用大括号来指定元素值对的元素值。如果元素值不是 ElementValueArrayInitializer,则唯一元素是元素值的数组值与元素相关联。如果元素值是 ElementValueArrayInitializer,则由 ElementValueArrayInitializer 表示的数组值与元素相关联。

如果元素类型与元素值是不相称的,则这是一个编译时错误。元素类型 T 与元素值 V 是相称的,当且仅当以下之一为 true:

    * T 是数组类型 E[],且两者之一:

        如果 V 是 ConditionalExpression 或 Annotation,则 V 与 E 是相称的;或

        如果 V 是 ElementValueArrayInitializer,则 V 包含的每个元素值与 E 是相称的。

ElementValueArrayInitializer 类似于标准数组初始化器(10.6),除了 ElementValueArrayInitializer 在句法上像表达式和嵌套初始化器一样可以包含注解之外。但是,句法上嵌套初始化器在 ElementValueArrayInitializer 中是不合法的,因为它们与注解类型声明(不允许嵌套数组类型)中的数组类型元素永远是不相称的。

    * T 不是数组类型,V 的类型与 T 是赋值兼容(5.2)的,且:

        如果 T 是基元类型或 String,则 V 是常量表达式(15.28)。

        如果 T 是 Class 或 Class(4.5)的调用,则 V 是类字面量(15.8.2)。

        如果 T 是枚举类型(8.9),则 V 是枚举常量(8.9.1)。

        V 不是 null。

注意,如果 T 不是数组类型或注解类型,则元素值必须是 ConditionalExpression(15.25)。ConditionalExpression 的使用而不是更普遍的像 Expression 的产品,是一个语法技巧,以防止赋值表达式作为元素值。因为赋值表达式不是常量表达式,所以它无法与基元或 String 类型的元素的元素值是相称的。

正是地说,说 ElementValue 是 FP-strict 的是无效的,因为它可能是注解或类字面量。不过,我们可以非正式地说 ElementValue 是 FP-strict 的,当它是常量表达式或常量表达式数组或元素值被发现(递归地)是常量表达式的注解;毕竟,每个常量表达式是 FP-strict 的。

标准注解必须为相应注解类型的每个元素包含一个元素值对,除那些具有默认值的元素之外,否则发生一个编译时错误。

标准元素可能,但不必要,为具有默认值的元素包含元素值对。

通常,尽管不必要,注解中的元素值对以与注解类型声明中对应的元素相同的顺序存在。

注解类型声明上的注解被称为元注解。

类型 T 的注解可以作为类型 T 本身声明上的元注解出现。一般地说,允许在“注解”的传递闭包中循环。

例如,用类型 T 的元注解标注注解类型 S 的声明和用类型 S 的元注解标注 T 自己的声明是合法的。预定义的注解类型包括几种这样的循环。

avatar

9.7.2. 标记注解

标记注解是一种设计用于使用标记注解类型(9.6.1)的简写。

MarkerAnnotation:
    @ TypeName

它是标准注解的简写:

@TypeName()

将具有元素的注解类型用作标记注解是合法的,只要所有元素具有默认值(9.6.2)。

avatar

9.7.3. 单元素注解

单元素注解是一种简写,设计用于使用单元素注解类型(9.6.1)。

SingleElementAnnotation:
    @ TypeName ( ElementValue )

它是标准注解的简写:

@TypeName(value = ElementValue)

将具有多个元素的注解类型用作单元素注解是合法的,只要其中一个元素名为 value,所有其它元素都具有默认值(9.6.2)

avatar avatar

9.7.4. 注解可以出现在哪

声明注解是应用于声明的注解,其自己的类型可应用在由该声明表示的声明上下文(9.6.4.1)中。

类型注解是应用于类型(或类型的任何部分)的注解,其自己的类型可应用在类型上下文(4.11)中。

avatar

通常,尽管不是必须的,在所有其它修饰符之前写声明注解,类型注解直接在它们应用于的类型之前。

注解可能出现在程序的句法位置上,其中这些注解可以合理地应用于声明或类型或两者。这可能发生在任何五种上下文中,其中修饰符紧跟在声明的实体的类型之前:

    * 方法声明(包括注解类型的元素)

    * 构造器声明

    * 字段声明(包括枚举常量)

    * 形式和异常参数声明

局部变量声明(包括 for 语句的循环变量和 try-with-resources 语句的 resource 变量)

Java 编程语言的语法明确地将这些位置上的注解视为声明(8.3)修饰符,但这纯粹是一个句法问题。注解是应用于声明还是声明的实体的类型 - 因此,注解是声明注解还是类型注解 - 依赖于注解类型的适用性:

    * 如果注解的类型适用于与声明对应的声明上下文,且不适用于类型上下文,则此注解被视为仅应用于此声明。

    * 如果注解的类型适用于类型上下文,且不适用于与声明对应的声明上下文,则此注解被视为仅应用于最靠近注解的类型。

    * 如果注解的类型适用于与声明对应的声明上下文和类型上下文,则此注解被视为应用于声明和最靠近注解的类型。

在以上第二和第三个情况下,最靠近注解的类型是在源代码中为声明的实体编写的类型;如果该类型是数组类型,则元素类型被视为最靠近注解。

例如,在字段声明 @Foo public static String f; 中,最靠近 @Foo 的类型是 String。(如果此字段声明的类型被写作 java.lang.String,则 java.lang.String 将是最靠近 @Foo 的类型,后面的规则禁止将类型注解应用于包名称 java。)在泛型方法声明 @Foo int[] m() {...} 中,为声明的实体编写的类型是 int[],所以 @Foo 应用于元素类型 int。

局部变量声明类似于 lambda 表达式的形式参数声明,同时在源代码中允许声明注解和类型注解的情况下,只有类型注解可以存储在 class 文件中。

有两种涉及方法/构造器声明的特殊情况:

    * 如果注解出现构造器声明之前,并被视为应用于最靠近注解的类型,则该类型是新构造的对象的类型。新构造的对象的类型是直接封闭构造器声明的类型的完全限定名称。在该完全限定名称中,注解应用于由构造器声明表示的简单类型名称。

    * 如果注解出现在 void 方法声明之前,并被视为仅应用于最靠近注解的类型,则发生一个编译时错误。

这是一个编译时错误,如果类型 T 的注解句法上是满足以下情况的修饰符:

    * 包声明,但 T 不适用于包声明。

    * 类、接口或枚举声明,但 T 不适用于类型声明或类型上下文;或注解类型声明,但 T 不适用于注解类型声明或类型声明或类型上下文。

    * 方法声明(包括注解类型的元素),但 T 不适用于方法声明或类型上下文。

    * 构造器声明,但 T 不适用于构造器声明或类型上下文。

    * 泛型类、接口、方法或构造器的类型参数声明,但 T 不适用于类型参数声明或类型上下文。

    * 字段声明(包括枚举常量),但 T 不适用于字段声明或类型上下文。

    * 形式或异常参数声明,但 T 不适用于形式和异常参数声明或类型上下文。

    * 接收者参数,但 T 不适用于类型上下文。

    * 局部变量声明(包括 for 语句的循环变量或 try-with-resources 语句的 resource 变量),但 T 不适用于局部变量声明或类型上下文。

注意,以上大多数子句都提及 "... or type contexts",因为即使注解不适用于该声明,它还可能适用于声明的实体的类型。

类型注解是可接受的,如果以下两种情况都为 true:

    * 注解最靠近的简单名称被归类为 TypeName,而不是 PackageName。

    * 如果注解最靠近的简单名称后跟 "." 和另一个 TypeName - 即,注解作为 @Foo T.U 出现 - 则,U 表示 T 的内部类。

第二个子句背后的直觉是,如果 Outer.this 在由 Outer 封闭的嵌套类中是合法的,则 Outer 可以被标注,因为它表示运行时某个对象的类型。另一方面,如果 Outer.this 不是合法的 - 因为它出现在的类运行时没有 Outer 的封闭实例 - 则 Outer 不可以被标注,因为它逻辑上仅仅是一个名称,类似于完全限定的类型名称中的包名称组件。

avatar

avatar

如果类型 T 的注解应用于类型上下文中类型的最外层,并且 T 不适用于占用相同语法位置的类型上下文或声明上下文(如果有),则这是一个编译时错误。

如果类型 T 的注解应用于类型上下文中类型的一部分(即,不是最外层),并且 T 不适用于类型上下文,则这是一个编译时错误。

如果类型 T 的注解应用于类型上下文中的类型(或类型的任何部分),并且 T 不适用于类型上下文,且此注解是不可接受的,则这是一个编译时错误。

例如,假定用 @Traget(ElementType.TYPE_USE) 元标注的注解类型 TA。术语 @TA java.lang.Object 和 java.@TA lang.Object 是非法的,因为最靠近 @TA 的简单名称被归类为包名称。另一方面,java.lang.@TA Object 是合法的。

注意,非法术语在任何位置都是“非法的”。注解包名称的禁止广泛地:应用于为唯一类型上下文的位置处,例如 class ... extends @TA java.lang.Object {...},和应用于同时是声明和类型上下文的位置处,例如 @TA java.lang.Object f;。(没有这样的位置,其仅仅是声明上下文,其中可以标注包名称,因为类、包和类型参数声明只使用简单名称。)

如果额外地用 @Target(ElementType.FIELD) 元标注 TA,则术语 @TA java.lang.Object 在同时是声明和类型上下文的位置中是合法的,例如字段声明 @TA java.lang.Object f;。在这里,@TA 被视为应用于 f 的声明(而不是类型 java.lang.Object),因为 TA 适用于字段声明上下文。

9.7.5. 同一类型的多注解

如果同一类型 T 的多注解出现在声明上下文或类型上下文中,除 T 是可重复的(9.6.3)且 T 和 T 的包含注解类型适用于声明上下文或类型上下文(9.6.4.1)之外,则这是一个编译时错误。

通常,尽管不是必须的,同一类型的多注解连续地出现。

如果声明上下文或类型上下文具有可重复的注解类型 T 的多注解,则它就像,上下文没有类型 T 的显式声明的注解,具有一个 T 的包含注解类型的隐式声明的注解,一样。

隐式声明的注解被称为容器注解,并且出现在上下文中的类型 T 的多注解被称为基本注解。容器注解的(数组类型的)value 元素的元素是在上下文中以从左到右的顺序出现的所有基本注解。

如果,在声明上下文或类型上下文中,有可重复的注解类型 T 的多注解和 T 的包含注解类型的任何注解,则这是一个编译时错误。

换言之,不可能在同一类型的注解也作为它们的容器出现的位置处重复注解。这禁止类似以下的不鲜明的代码:

@Foo(0) @Foo(1) @FooContainer({@Foo(2)}) class A {}

如果此代码是合法的,则需要多层包容性:首先,类型 Foo 的注解将由类型 FooContainer 的隐式声明的容器注解包含,然后该注解和类型 FooContainer 的显式声明的注解将包含在另一个隐式声明的注解中。这种复杂性在 Java 编程语言的设计者的判断中是不可取的。另一个方法是,将类型 Foo 的注解视为它们出现在显式 @FooContainer 注解中的 @Foo(2) 旁边一样,不可取的,因为它可能会改变反射程序解释 @FooContainer 注解的方式。

如果,在声明上下文或类型上下文中,有一个可重复的注解类型 T 的注解和 T 的包含注解类型的多注解,则这是一个编译时错误。

此规则旨在允许以下代码:

@Foo(1) @FooContainer({@Foo(2)}) class A {}

只有一个可重复注解类型 Foo 的注解,没有隐式声明的容器注解,即使 FooContainer 是 Foo 的包含注解类型。但是,重复类型 FooContainer 的注解,如下中所述:

@Foo(1) @FooContainer({@Foo(2)}) @FooContainer({@Foo(3)}) class A {}

是禁止的,即使 FooContainer 具有其自身的包含注解类型的可重复性。当底层的可重复类型的注解存在时,重复自身就是容器的注解是不鲜明的。

9.8. 函数式接口

函数式接口是仅有一个 abstract 方法(除 Object 方法之外)的接口,因此表示单个函数协定。此“单个”方法可以表现为从父接口继承的多个具有重写等效签名的 abstract 方法;在这种情况下,继承的方法逻辑上表示单个方法。

对于接口 I,让 M 是 abstract 方法集合,它们是 I 的成员,I 不具有与类 Object 的任何 public 实例方法相同的签名。然后,I 是函数式接口,如果 M 中存在方法 m,满足以下两者都为 true:

    * m 的签名是 M 中每个方法的签名的子签名(8.4.2)。

    * m 是 M 中每个方法的返回类型替代(8.4.5)。

除通过声明和实例化类来创建接口实例的通常处理之外,可以使用方法引用表达式和 lambda 表达式(15.13,15.27)来创建函数式接口的实例。

函数式接口的定义排除了接口中那些也是 Object 中的 public 方法的方法。这是为了允许对像 java.util.Comparator 这样的接口的函数式处理,此类接口声明了多个 abstract 方法,其中只有一个是真正的“新的” - int compare(T,T) 方法。另一个方法 - boolean equals(Object) - 是以其他方式被隐式声明的 abstract 方法的显式声明,并且将被实现此接口的每个类自动地实现。

注意,如果在接口中声明 Object 的非 public 方法,例如 clone(),它们不会被实现此接口的每个类自动地实现。继承自 Object 的实现是 protected,而接口方法必须是 public。实现此类接口的唯一方法是,类使用 public 方法重写非 public Object 方法。

avatar

avatar

avatar

avatar

avatar

函数式接口的声明允许在程序中使用函数式接口类型。有四种函数式接口类型:

    * 非泛型(6.1)函数式接口类型

    * 参数化类型,其是泛型函数式接口的参数化(4.5)

    * 泛型函数式接口的原始类型

    * 交集类型(4.9),其引起一个概念上的函数式接口

在特殊情况下,将交集类型视为函数式接口类型是有用的。通常,这看起来就像一个具有一个或多个标记接口类型的函数式接口类型的交集,例如 Runnable & java.io.Serializable。此类交集可用于强迫 lambda 表达式符合某种类型的强制转换。如果交集中的接口类型之一是 java.io.Serializable,则触发序列化的特殊运行时支持(15.27.4)。

9.9. 函数类型

函数式接口 I 的函数类型是可以用于重写(8.4.8)I 的 abstract 方法的方法类型。

让 M 是为 I 定义的 abstract 方法集合。I 的函数类型由以下组成:

    * 类型参数,形式参数,和返回类型:

      让 m 是 M 中的方法,其具有:

          1. 签名是 M 中每个方法的签名的子签名的签名;且

          2. 返回类型是 M 中每个方法的返回类型的子类型的返回类型(在适应任何类型参数之后(8.4.4))。

      如果不存在这样的方法,则让 m 是 M 中的方法,其:

          1. 具有签名是 M 中每个方法的签名的子签名的签名;且

          2. 是 M 中每个方法的返回类型替代(8.4.5)。

      函数类型的类型参数、形式参数类型和返回类型由 m 给定。

    * throws 子句:

      函数类型的 throws 子句派生自 M 中方法的 throws 子句。如果函数类型是泛型的,则首先调整这些子句以适应函数类型(8.4.4)的类型参数。如果函数类型不是泛型的但 M 中至少一个方法是泛型的,则首先擦除这些子句。然后,函数类型的 throws 子句包括满足以下约束的每个类型 E:

          E 是 throws 子句之一提及的。

          对于每个 throws 子句,E 是在该子句中命名的某些类型的子类型。

当 M 中的某些返回类型是原始的,而另一些不是,则如果可能,函数类型的定义尝试选择最具体的类型。例如,如果返回类型是 LinkedList 和 LinkedList ,则直接选择后者作为函数类型的返回类型。当没有最具体的类型时,则定义通过查找最可替代的返回类型进行补偿。例如,如果有第三个返回类型,List,则它不是这种情况,返回类型之一是其它每个的子类型(因为原始 LinkedList 不是 List 的子类型);相反,选择 LinkedList 作为函数类型的返回类型,因为它是 LinkedList 和 List<?> 两者的返回类型替代。

驱动函数类型的被抛出的异常类型的定义的目标是支持不变量,具有作为结果的 throws 子句的方法可以重写函数式接口的每个 abstract 方法。依照 8.4.6,这意味着函数类型无法比集合 M 中的任何单个方法抛出“更多”异常,因此,我们寻求由每个方法的 throws 子句“覆盖”的尽可能多的异常。

函数式接口类型的函数类型由以下指定:

    * 非泛型函数式接口 I 的类型的函数类型仅仅是函数式接口 I 的函数类型,定义如上。

    * 参数化函数式接口类型 I<A1...An> 的函数类型,其中 A1...An 是类型,I 的相应的类型参数是 P1...Pn,是通过将替代 [P1:=A1, ..., Pn:=An] 应用于泛型函数式接口 I<P1...Pn> 的函数类型来派生的。

    * 参数化函数式接口类型 I<A1...An> 的函数类型,其中 A1...An 的一个或多个是通配符,是 I 的非通配符参数化的函数类型 I<T1...Tn>。非通配符参数化按如下确定。

      让 P1...Pn 是 I 的具有相应边界 B1...Bn 的类型参数。对于 i (1 ≤ i ≤ n) 所有,根据 Ai 的形式派生 Ti:

          如果 Ai 是类型,则 Ti=Ai。

          如果 Ai 是通配符,并且相应的类型参数的边界 Bi 提及 P1...Pn 之一,则 Ti 是未定义的,并且没有函数类型。

          否则:

              如果 Ai 是无边界的通配符 ?,则 Ti=Bi。

              如果 Ai 是上边界通配符 ? extends Ui,则 Ti=glb(Ui, Bi)(5.1.10)。

              如果 Ai 是下边界通配符 ? super Li,则 Ti=Li。

    * 泛型函数式接口 I<...> 的原始类型的函数类型是泛型函数式接口 I<...> 的函数类型。

    * 引起概念上函数式接口的交集类型的函数类型是概念上函数式接口的函数类型。

avatar

avatar

函数式接口的函数类型的定义是不确定的:虽然 M 中的签名是“相同的”,它们可能在句法上是不同的(例如,HashMap.Entry 和 Map.Entry);返回类型可能是其它每个返回类型的子类型,但可能还有其它也是子类型的返回类型(List<?> 和 List<? extends Object>);而抛出的类型的顺序是未指定的。这些区别是很微妙的,但它们有时会很重要。但是,在 Java 编程语言中不以不确定性重要的方式使用函数类型。注意,“最具体的方法”的返回类型和 throws 子句的定义也是不确定的,当有多个 abstract 方法时(15.12.2.5)。

当通过通配符参数化泛型函数式接口时,有许多不同的实例可以满足通配符并生成不同的函数类型。例如,Predicate(函数类型 Integer -> boolean)、Predicate(函数类型 Number -> boolean)和 Predicate(函数类型 Object -> boolean)中的每个是 Predicate<? super Integer>。有时,可以从上下文知道,例如 lambda 表达式的参数类型,哪个函数类型是想要的(15.27.3)。其它时候,必须选择一个;在这些情况下,使用边界。(这一简单策略无法保证结果类型将满足某些复杂的边界,因此,不支持所有复杂的情况。)

avatar