Reputation: 4215
I've read the Kotlin doc (https://kotlinlang.org/docs/packages.html), and I understood that, when importing a package, the package name does not need to match the folder's path that stores the package (unlike what happens in Java).
I don't have issues creating a package and importing it into other classes.
What I'd like to understand, is how the compiler can find the file to import?
For example:
if a file import animals.mammals.cats.*
:
import animals.mammals.cats.*
...
the entities to import do not need to be stored in the file /animals/mammals/cats.kt
, as long as the package name is "animals.mammals.cats":
package animals.mammals.cats
...
This Kotlin file could be stored in src/animals/kittens
for example.
In other words, how import
can locate the file/s to load since the package name does not help?
Thanks!
Upvotes: 3
Views: 1256
Reputation: 37680
TL;DR: the compiler is given the paths to all files to compile and to all dependencies, and therefore knows about all available packages and the declarations they contain.
First, note that the import
statement itself is not really the most important. It's just a convenient syntax to avoid having to specify the package everywhere throughout the file. But technically you don't need to import anything to be able to use declarations from outside the current file - you can just use their fully qualified name (a.k.a FQN) which is the package name + .
+ the name of the declaration.
Now, on to your question. When you run the compiler, you provide the paths to the complete set of files to be compiled at the same time: you compile a module, not a single file. Therefore, it has access to all declarations in all those files and maintains its own data structures about the available classes and top-level functions, and all symbols in general. So it can store and find declarations using just their 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.)
If the declaration you use is not in the set of files being compiled, it must be in one of your dependencies. You can tell the compiler about the available dependencies by specifying the list of jars containing their already compiled classes. This list of all classes available at compile time is called the compile classpath. This is why the tool you use to build your project (for instance, Gradle or your IDE) needs to know about those dependencies, so it can put their declarations on the compile classpath when calling the compiler for you. Then, just like declarations that are being compiled, the ones from the compile classpath can be easily looked up by the compiler (the path has been given to the compiler as an argument).
Now, when you actually run your compiled program, at least on the JVM, the classes required must be placed on the runtime classpath - a set of classes given to the java
program. Finding those declarations while the program runs is done by classloaders. There are multiple classloaders, organized in a hierarchy, but there is no need to go into details here. Basically, each time a class is used for the first time while your program is running, one classloader will be asked to load that class into memory. There are different implementations of classloaders, but one of the most common is the URLClassLoader
which is given the URLs of some jars that contain classes, and knows how to read classes from these jars into memory on-demand.
Upvotes: 6