Shmuel H.
Shmuel H.

Reputation: 2546

Package-relative identifier path in Java

Introduction

Moving from C++, one of the big differences was the difference in scoping: In C++ every identifier is relative to the current scope (class or namespace) and you could write an absolute path with the scope resolution operator :: in the beginning.

However, in Java I could not find a way to access to an identifier from other package without importing it to the current scope or writing its full path.

It could be a problem if I have a few classes with the same name (then, in C++ I would address them as Module1::Foo and Module2::Foo, which is not possible in Java as long as I want to save my sanity since com.company.project.module1.Foo is too long for my taste).

Code!

Here's an example for accessing the class Tools.Useless.Foo from Java and C++ (with no import or using)

Tools/Useless/Foo.hpp:

namespace Tools {
namespace Useless {
class Foo {         
};
}
}

Tools/Bar.hpp

namespace Tools {
...
// Use Foo with a relative identifier
Useless::Foo foo;
// Use Foo with an absolute identifier.
::Tools::Useless::Foo bar;
...
} 

And that's how it would look in Java:

com/company/project/Tools/Useless/Foo.java:

package com.company.project.Tools.Useless;

public class Foo { }

com/company/project/Tools/Bar.java:

...
// Use Foo with a relative identifier
???
// Use Foo with an absolute identifier.
com.company.project.Tools.Useless.Foo foo;
...

The question(s)

  1. Is there a way to access Foo from package Tools.Useless without specifying Tools.Useless full package name (and importing it; since importing it will bind it to the current scope)?
  2. Am I doing it right? How should I use a few classes with the same name? Should I just avoid that or just import them with "package.*" to bypass it?

Solutions

  1. Using more package-descriptive class names (e.g. ToolsUselessFoo instead of just Foo).
  2. Importing everything with import path.to.module.* instead of import path.to.module.Foo and then access Foo the the needed packages to resolve any ambiguity. The problem is that sometimes the package names has a meaning (e.g. Tools.Useless.Foo and Tools.Useful.Foo).

Upvotes: 1

Views: 1715

Answers (1)

Holger
Holger

Reputation: 298103

Java packages are not nested. While their names may have common prefixes, that has no meaning within the Java programming language.

Also the fact that their class files are stored in nested directories when the storage is a file system, has no additional meaning. When classes are stored in a jar file, they are stored with entry names matching their qualified names without actually forming directories at all (though a lot of tools like to present them as-if being in a hierarchical structure to mimic a file system).

So the packages com.company.project.tools.useless and com.company.project.tools have no relationship at all. While we humans tend to organize code in such a way, that a semantic relationship can be assumed here (which is a good thing), there is none on a technical level. There is no relative addressing between them and there are no additional access rights, compared to any other two packages. In fact, both packages could be part of two different modules (starting with Java 9), with even less access permission compared to two other packages with less similar names.

The standard approach to use the class from another package, is to use import com.company.project.Tools.Useless.Foo;, followed by using Foo within the class.

It’s not quiet clear, which problem you see with that, i.e. what “since importing it will bind it to the current scope” is supposed to mean. The mere existence of an import statement, has no effect on the code. All it tells the compiler, is how to resolve occurrences of the simple name Foo, if no other Foo is in scope.

In other words, local scopes still have precedence, even including inherited members. Further, at places where variables and types could appear, variables have precedence. E.g. for an occurrence of Foo.bar(), a variable called Foo would have precedence, a local variable before member variables within the same type, an outer class or inherited ones. Otherwise, member types, outer types or inherited member types would be used. Only if none of them exist, the import statement would be used to resolve Foo.

Needless to say, you should avoid having so many items with the same simple name that you would have to think about the details of the resolving process. That’s why the naming conventions suggest to start variable names with a lowercase letter and class names with an upper case letter.

And yes, avoid giving classes the same simple name. Once you have to deal with both classes within the same compilation unit, there is no way around accessing one of them with its (fully¹) qualified name throughout the entire compilation unit. (Using a * in import doesn’t help you in any way with such a scenario)


As explained, the term “fully” is obsolete in Java, as qualified names are always complete.

Upvotes: 3

Related Questions