首先,请注意,import
语句本身并不是最重要的.这只是一种方便的语法,可以避免在整个文件中到处指定包.但从技术上讲,您不需要导入任何内容来使用当前文件外部的声明--您只需使用它们的fully qualified name(也称为FQN),即包名+声明名.
现在,让我们来回答你的问题.当您运行编译器时,您将同时提供要编译的完整文件集的路径:您将编译一个模块,而不是单个文件.因此,它可以访问所有这些文件中的所有声明,并维护自己关于可用类和顶级函数以及所有符号的数据 struct .因此它可以仅使用声明的FQN来存储和查找声明.(DISCLAIMER: I'm no expert and I don't actually know how it's done internally, but I'm just guessing that conceptually it's like storing a big mapping between FQN and the information about the corresponding declaration.)个
如果您使用的声明不在正在编译的文件集中,则它必须位于dependencies个文件中的一个文件中.您可以通过指定包含已编译类的JAR列表来告诉编译器可用的依赖项.编译时所有可用类的列表称为compile classpath.这就是为什么用来构建项目的工具(例如Gradle或IDE)需要知道这些依赖关系,以便在为您调用编译器时可以将它们的声明放在compile classpath上.然后,就像正在编译的声明一样,编译器可以很容易地查找来自编译类路径的声明(路径已作为参数提供给编译器).
现在,当您实际run编译的程序时,至少在JVM上,所需的类必须放在runtime classpath上--这是提供给java
程序的一组类.在程序运行时查找这些声明是在classloaders完成的.有多个按层次 struct 组织的类加载器,但这里不需要详细说明.基本上,每次在程序运行时第一次使用类时,都会要求一个类加载器将该类加载到内存中.类加载器有不同的实现,但最常见的是URLClassLoader
,它被提供了一些包含类的JAR的URL,并且知道如何按需将类从这些JAR读取到内存中.