From a programming language designer's point of view, how is "symbolic" insertion implemented as opposed to "textual" insertion using pre-processor macros?
I understand the basics of how "textual" insertion works in C and C++. That is, the pre-processor might transform:
#include "person.hpp"
class employee
{
person p;
};
into:
#ifndef PERSON_HPP
#define PERSON_HPP
class person
{
public:
uint32_t height_;
};
#endif // PERSON_HPP
class employee
{
person p;
};
This is required because the size of the employee class can only be determined
if the compiler knows the size of the person class. One disadvantage of this is
slow compilation speed. A program consisting of a single source code file
main.cpp
might have thousands of lines inserted into it by the pre-processor
before compilation begins. This can be avoided in some cases by forward
declaring the class and using pointers. The employee class cannot use the
person class until it is defined, (which is why the implementation file will
still include "person.h") but it does remove header file dependencies and
increases compilation speed.
class person;
class employee
{
person* p;
};
For a language like Java, on the other hand, no textual insertion is required. Instead, "symbolic" insertion is performed. So the file below would not be "fleshed out" with the definitions of the dependent software modules.
import com.example.library.person;
class employee {
private person p;
};
I can't wrap my head around how the compiler accomplishes this. I imagine that
because Java is garbage collected, it doesn't need to know the size before it
is declared, in much the same way as classes can be forward declared in C++
without including the entire file. But how does the compiler know that
p->some_function_that_does_not_exist(23)
is valid?
This question was originally asked on stack overflow.