包
相关概念
编译单元:简单来说,是一个java文件。
顶级类型声明:简单来说,是一个编译单元中最外层的声明的类或接口。顶级类型声明不可以用访问修饰符static
,protected
,private
修饰,只可用public
,或不修饰。
概述
程序是以一个个包的形式组织起来的,每个包都有自己的一批类型名称。
当且仅当一个顶层类或接口被声明为public
时才能在包外访问,包的命名结构是有层次的,包的成员是包内的所有(包括子包)的类或接口,包可以包含子包。
包可以存储在文件系统或是数据库中,文件系统中的包对应文件夹,
一个包由多个编译单元组成,编译单元可以自动访问(无需导入)包中声明的所有类型,以及自动访问java.lang
包中声明为public
的类型。
对于临时需求,可以不进行包声明,或者起个简单的名字,但如果代码要广泛分发,则应谨慎选择一个唯一的包名,以防止在两个开发组碰巧选择相同的包名导致使用时出现错误。
包成员
包的成员包括子包,以及包的编译单元中声明的所有顶级类和顶级接口。
如果P
是一个包的全称,Q
是P
的子包,则Q的全称为P.Q
。
- 一个包中的成员不能重名,即,顶级类与顶级接口与子包不可重名。
- 这里说子包不可重名,是指在子文件中,如果有java文件显式的用package 声明
虽然有子包的概念,但是本质上,java所有的包是平行的关系,不存在特殊的访问权限。
编译单元
对应一个java文件,由三部分组成,每部分都是可选的。
- package包声明
- import导入声明,用于导入其他包成员
- 顶级类型声明(类或接口)
包声明
未命名包
如果一个java文件不进行进行包声明,编译之后,会全部放在编译结果的classes文件夹下,
因此,即使位于不同的文件夹下,未进行package声明的两个java文件也不可同名。已命名包
包以package 包名
的形式在java文件中进行声明,可以对包进行注解。在同一包的多个java文件中,仅允许其中一个对包进行注解,package-info.java
对于基于文件系统的实现,建议采用以下方案:对包的注解放在该包下一个名为package-info.java
的文件中,这个文件不包含package-info
类的代码(毕竟package-info
并不是一个合法类名),通常这个文件仅仅包含package声明。尽管实际上可以在这个类上声明类,接口等,但是这样做是很糟糕的。包的可观察性
这个?资料很少,可观察性指的是什么,我的理解是编译器能否定位到包所在的位置Q&A
包名与文件夹名称的对应关系
正常而言,一个java文件的package
声明与自己所在的文件夹名是一致的,是一一对应的关系。如果有人突发奇想,让package
声明奇怪一点,比如某个java文件处于文件夹A
处,但是它声明package B;
,这样做可以通过编译,看编译结果的classes文件夹可以发现,会新建一个B文件夹,这个java文件对应的class文件处在B文件夹下。
而对于没有package
声明的java文件,其编译出的.class文件会直接放在编译结果的classes文件夹下,而不会被包所包裹。因此对于不同文件夹下的不同java文件,如果都不声明package,编译的结果会是在同一个包中,可以直接相互访问。导入声明
通过import,使得类型或者静态成员能通过一个简单名进行引用。如果不使用合适的import,那么只能通过全名进行引用。import java.util.Arrays;
public class ImportTest {
public static void main(String[] args) {
// 导入了java.util.Arrays,因此可以直接使用Arrays
Arrays.asList(new String[]{"a", "b", "c"}).forEach(System.out::println);
// 没有导入java.util.ArrayList,只能通过全名访问
java.util.ArrayList<String> list = new java.util.ArrayList<>();
}
}
导入语句的格式
导入单个类import TypeName;
按需导入类import TypeNameOrPackageName.*;
导入类的静态成员import TypeName.Identifier;
按需导入类的静态成员import static TypeName.*;
顶级类型声明
即一个java文件直接包含的类与接口,不包括类的内部类
在编译时,每一个顶级类型声明都会被编译成一个单独的class文件,因此在同一个文件中,顶级类型声明的先后顺序并不重要。
模块
在java9中引入了模块系统,
如果一组包具有足够的内聚力,则可以将这些包分组到一个模块中。模块将其部分或全部包分类为导出,这意味着可以从模块外部的代码访问其类型。如果包不是由模块导出的,则只有模块内的代码可以访问其类型。此外,如果模块中的代码希望访问另一个模块导出的包,则第一个模块必须显式依赖于第二个模块。因此,模块控制其包如何使用其他模块(通过指定依赖项)并控制其他模块如何使用其包(通过指定导出其哪些包)。
用模块声明去定义一个模块,定义的模块可以声明自己依赖的模块,以定义本模块中的代码可用的类和接口的范围,同时定义本模块中哪些包可以导出或完全开放,以方便其他模块依赖该模块。
依赖通过requires
进行声明,与是否存在具有指令指定名称的模块无关。
A “dependence” is what is expressed by a requires directive, independent of whether a module exists with the name specified by the directive. A “dependency” is the observable module enumerated by resolution (as described in the java.lang.module package specification) for a given requires directive. Generally, the rules of the Java programming language are more interested in dependences than dependencies.
模块声明引入了一个模块名称,该模块名称可以在其他模块声明中用于表示模块之间的关系。
有两种模块,普通模块和开放模块(用open修饰)
普通模块,在编译时和运行时仅向显式导出的那些包中的类型授予访问权限。
开放模块,在编译时仅向显式导出的包中的类型授予访问权限,但在运行时授予对其所有包中的类型的访问权限,就像导出所有包一样。
对于那些模块外的代码,在编译时或运行时授予模块导出包中的类型的访问权限专门针对,这些包中的public和protected类型以及这些类型的public和protected成员。
在编译时或运行时,不会授予对未导出的包中的类型或其成员的访问权限。
模块内的代码可以在编译时和运行时访问模块中所有包中的public和protected类型,以及这些类型的public和protected成员。
与编译时的访问和运行时的访问不同,Java SE 平台通过 Core Reflection API 提供反射访问。普通模块仅授予对显式导出或显式开放(或两者)的包中的类型的反射访问权限。开放模块授予对其所有包中类型的反射访问,就好像所有包都已开放一样。
对于普通模块之外的代码,授予模块导出(未打开)包中类型的反射访问权限专门针对这些包中的公共和受保护类型,以及这些类型的公共和受保护成员。
授予模块打开的包中的类型(无论是否导出)的反射访问权限是这些包中的所有类型以及这些类型的所有成员。
在未导出或打开的包中,不向类型或其成员授予反射访问权限。模块内的代码可以反射访问模块中所有包中的所有类型及其所有成员。
对于开放模块外部的代码,授予模块的开放包中的类型(即模块中的所有包)的反射访问权限是授予这些包中的所有类型以及这些类型的所有成员。模块内的代码可以反射访问模块中所有包中的所有类型及其所有成员。
模块声明的指令规定了模块对其他模块的依赖性(via requires
),它向其他模块提供的包(通过exports
和opens
),它使用的服务(via uses
),以及它提供的服务(via provideds
)。
dependence
requires指令指明当前模块依赖的模块
java.base包中不能有requires,因为他是顶级包,不依赖任何其他模块。
如果其他模块没有显式用requires声明依赖java.base,则隐含的依赖java.base。
requires可以用transitive修饰, 这导致任何其他依赖当前的模块也会有一个隐含的requires transitive依赖声明。
这会导致任何需要当前模块的模块都隐式声明依赖于requires transitive指令指定的模块。This causes any module which requires the current module to have an implicitly declared dependence on the module specified by the requires transitive directive.
说人话就是依赖传递,C依赖于B,B依赖于A,则C依赖于A
requires可以用static修饰,表明依赖项在编译时是必需的,但在运行时是可选的。
如果模块显式声明依赖java.base,那么不能用transitive和static关键字修饰
例如,如果requires指令指定了一个不可观察的模块,或者如果当前模块直接或间接表示对自身的依赖。
如果解析成功,则其结果指定当前模块读取的模块。当前模块读取的模块确定哪些普通编译单元对当前模块可见 (§7.3). 在那些普通编译单元(并且只有那些普通编译单元)中声明的类型可以被当前模块中的代码访问 (§6.6).
Java SE 平台区分显式声明的命名模块(即,使用模块声明)和隐式声明的命名模块(即自动模块)。但是,Java编程语言并没有显示出这种区别:指令引用命名模块而不考虑它们是显式声明的还是隐式声明的。requires
虽然自动模块便于迁移,但它们是不可靠的,因为它们的名称和导出的包在作者将它们转换为显式声明的模块时可能会更改。如果requires指令去依赖一个自动导入模块,我们鼓励java编译器报错,特别是用transitive修饰的要报更严重的错。
exports
exports,指令指定要由当前模块导出的包的名称,对于其他模块中的代码,编译时和运行时授予对包中的public和protected 类型以及这些类型的public和protected 成员的访问权限。它还授予对这些类型和成员的反射权限,以获取其他模块中的代码。
opens指令指定要由当前模块开放的包的名称,其他模块的代码可以在运行时(不包括编译时)对该模块开放包中的public和protected类型以及该类型public和protected成员进行访问。它还授予对包中所有类型及其所有成员的反射访问,以访问其他模块中的代码。
opens可以修饰模块,表明该模块所有包都开放
exports不可以导出与该模块不相关的包(导致编译错误),而open可以开放该模块中不存在的包,如果包恰好由与另一个模块关联的可观察编译单元声明,则opens指令对该另一个模块没有影响。
exports和open后面可以接上一个to子句,to子句后面声明,表明exports或opens只对指定的模块生效,通过to子句指定的模块称为当前模块的friends.
允许exports或opens指令的to子句指定不可观察的模块
服务消费
uses指令指定服务,当前模块可以通过java.util.ServiceLoader
发现服务提供者。
服务必须是类、接口、注解三者之一,不可是枚举类。
服务可以在当前模块或其他模块进行声明,如果服务不在当前模块声明,则必须保证服务可访问当前模块的代码。
服务提供
provides指令指定了一个服务,with子句为该服务指定了一个或多个javaUtilServiceLoader的服务提供者。
每个服务提供程序都必须是类类型或接口类型,即公共的、顶级的或嵌套的静态的,否则会发生编译时错误。
服务提供者必须在当前模块声明
如果服务提供者显式声明了一个没有正式参数的公共构造函数,或隐式声明了一个公共默认构造函数(§8.8.9),则该构造函数称为提供者构造函数。如果服务提供者显式声明了一个称为provider的公共静态方法,而没有形式参数,那么该方法称为provider方法。
如果服务提供者具有提供者方法,则其返回类型必须满足(i)在当前模块中声明,或者在另一个模块中声明,并且可以访问当前模块中的代码;(ii)是provides指令中指定的服务的子类型;
虽然必须在当前模块中声明由provides指令指定的服务提供者,但其provider方法的返回类型可能在另一个模块中声明。另外,请注意,当服务提供者声明提供者方法时,服务提供者本身不必是服务的子类型。
如果服务提供者没有提供者方法,则该服务提供者必须有提供构造函数,并且必须是在PROVICES指令中指定的服务的子类型,否则会发生编译时错误。
未命名模块
Java SE平台提供了未命名模块,这是因为Java SE 9之前开发的程序无法声明命名模块。此外,Java SE平台提供未命名包的原因也适用于未命名模块。
主机系统可以将命名包中的普通编译单元与未命名模块相关联。
未命名模块的规则旨在最大限度地实现与命名模块的互操作,如下所示:
- 未命名模块可以访问所有可观察模块
- 由于与未命名模块关联的普通编译单元是可观察的,因此关联的未命名模块是可观察的。因此,如果Java SE平台的实现支持多个未命名模块,则每个未命名模块都是可观察的;每个未命名模块读取包括其自身在内的每个未命名模块。
- 然而,要认识到,命名模块永远看不到未命名模块的普通编译单元,因为没有requires指令可以安排命名模块读取未命名模块。Java SE平台的核心反射API可用于安排命名模块在运行时读取未命名模块。
- 未命名模块导出其普通编译单元与该未命名模块关联的每个包。
-
模块的可观察性
当满足以下其一时,模块是可观察的
包含模块声明的模块化编译单元是可观察的。
- 与普通编译单元关联的模块是可观察的。