~ on C++ and Java classes here are three
properties that ~ can satisfy:
| Reflexive | A ~ A is true for all classes A |
| Symmetric | A ~ B implies B ~ A for all classes A and B |
| Transitive | A ~ B and B ~ C implies A ~ C for all classes A, B and C. |
The C++ Friendship Operator, henceforth known as is
reflexive but not symmetric or transitive because classes are friends
with themselves is the only thing you can say about a C++ program
without actually looking at the source code to see what class is
friends with what. Before we can look at the corresponding operator
in Java, we need to look at a sample diagram of the Java source files
and directories of a sample Java project. In the following diagram,
red dots represent Java source files and green
boxes represent directories:
~C++

The following graph shows how the classes of the above diagram relate
in terms of the Java Package Field Visibility Operator, henceforth
known as . Importantly, we are looking at package
visibility applied to fields (methods and properties) but not to
classes. Therefore we assume that all classes are declared with
public visibility and not package visibility. As shown below, each
distinct package is an island into itself because classes in
subdirectories cannot access package visible fields in parent
directories.
~Java

The operator is reflexive, symmetric and transitive so it is
less general than ~Java. Here is a possible way that ~C++ operator
could be generalised:
~Java

The Generalised Java Can Access Operator, henceforth referred to
as ~GJ is reflexive, weakly symmetric and transitive. By weakly
symmetric I mean A implies ~GJ BB only when ~GJ AA and
B are in the exact same package. The ~GJ operator gives rise to
Augmented Trees. By an Augmented Tree I mean a tree augmented
into a directed graph in such a way that if A and ~GJ BB then ~GJ
CA is in the graph.
~GJ C
The operators discussed above lie in the following order from least
general to most general: , ~Java, ~GJ as the
properties of ~C++ are a strict subset of the properties of
~GJ and the properties of ~Java are a strict subset of the
properties of ~C++. Therefore changing the way that Java package
visibility is implemented from ~GJ to ~Java would make the
language more expressive and closer to the expressiveness of C++.
~GJ
There are two downsides to friendship in C++. The first downside is
that to achieve a complicated friendship arrangement, many friend
declarations are needed. The second downside is that to achieve a
complicated friendship arrangement many source files need to be
touched (i.e. modified) by having friend declarations added to
them. The extra touching of files has poor ramifications for programs
like make, which generally recompiles files that include files
that have been touched since the last call to make. Even with
Java modified in the way previously discussed, it would still be
superior to C++ in respect of not needing any friend declarations.
Here is the second file:package alpha; public class A { //////////////////////////////////////////////////////////////////// /// /// PROPERTIES: /// //////////////////////////////////////////////////////////////////// public int property1; protected int property2; int property3; private int property4; public static int property5; protected static int property6; static int property7; private static int property8; //////////////////////////////////////////////////////////////////// /// /// METHODS: /// //////////////////////////////////////////////////////////////////// public void method1() { System.out.println("A's method1"); } protected void method2() { System.out.println("A's method2"); } void method3() { System.out.println("A's method3"); } private void method4() { System.out.println("A's method4"); } public static void method5() { System.out.println("A's method5"); } protected static void method6() { System.out.println("A's method6"); } static void method7() { System.out.println("A's method7"); } private static void method8() { System.out.println("A's method8"); } //////////////////////////////////////////////////////////////////// /// /// MAIN METHOD: /// //////////////////////////////////////////////////////////////////// public static void main(String[] args) { A a = new A(); alpha.beta.B b = new alpha.beta.B(); b.wibble(a); } }
Compiling the second file gives an error at the lines markedpackage alpha.beta; public class B { public void wibble(alpha.A a) { // Compiler error: method3() is not public in alpha.A; // cannot be accessed from outside package a.method3(); // *** // Compiler error: method7() is not public in alpha.A; // cannot be accessed from outside package a.method7(); // *** // Compiler error: property3 is not public in alpha.A; // cannot be accessed from outside package a.property3++; // *** // Compiler error: property7 is not public in alpha.A; // cannot be accessed from outside package a.property7++; // *** } }
***
because the package alpha.beta is considered to be different to
the package alpha. This is despite the fact that the second
file is stored in a subdirectory of the directory that the first file
is stored in. It is natural to follow the analogy established by the
directory structure and to think of package alpha.beta as being
inside package alpha. Taking this idea to its limit we
would expect that code in package alpha.beta could access methods
and properties in package alpha which have package
visibility. What follows is a design pattern that allows one to get
around this flaw so that sub-packages can access methods and properties
from parent packages.
Here is the second part:package alpha; public class AInner { //////////////////////////////////////////////////////////////////// /// /// PROPERTIES: /// //////////////////////////////////////////////////////////////////// public int property1; public int property2; public int property3; public int property4; public static int property5; public static int property6; public static int property7; public static int property8; //////////////////////////////////////////////////////////////////// /// /// METHODS: /// //////////////////////////////////////////////////////////////////// public void method1() { System.out.println("AInner's method1"); } public void method2() { System.out.println("AInner's method2"); } public void method3() { System.out.println("AInner's method3"); } public void method4() { System.out.println("AInner's method4"); } public static void method5() { System.out.println("AInner's method5"); } public static void method6() { System.out.println("AInner's method6"); } public static void method7() { System.out.println("AInner's method7"); } public static void method8() { System.out.println("AInner's method8"); } }
It is important in the above code that propertypackage alpha; public class AOuter { private AInner aInner = new AInner(); //////////////////////////////////////////////////////////////////// /// /// SETTERS AND GETTERS FOR PROPERTIES: /// //////////////////////////////////////////////////////////////////// public int getProperty1() { return aInner.property1; } protected int getProperty2() { return aInner.property2; } int getProperty3() { return aInner.property3; } private int getProperty4() { return aInner.property4; } public static int getProperty5() { return AInner.property5; } protected static int getProperty6() { return AInner.property6; } static int getProperty7() { return AInner.property7; } private static int getProperty8() { return AInner.property8; } public void setProperty1(int i) { aInner.property1 = i; } protected void setProperty2(int i) { aInner.property2 = i; } void setProperty3(int i) { aInner.property3 = i; } private void setProperty4(int i) { aInner.property4 = i; } public static void setProperty5(int i) { AInner.property5 = i; } protected static void setProperty6(int i) { AInner.property6 = i; } static void setProperty7(int i) { AInner.property7 = i; } private static void setProperty8(int i) { AInner.property8 = i; } //////////////////////////////////////////////////////////////////// /// /// METHODS: /// //////////////////////////////////////////////////////////////////// public void method1() { aInner.method1(); } protected void method2() { aInner.method2(); } void method3() { aInner.method3(); } private void method4() { aInner.method4(); } public static void method5() { AInner.method5(); } protected static void method6() { AInner.method6(); } static void method7() { AInner.method7(); } private static void method8() { AInner.method8(); } //////////////////////////////////////////////////////////////////// /// /// MAIN METHOD: /// //////////////////////////////////////////////////////////////////// public static void main(String[] args) { AOuter a = new AOuter(); alpha.beta.B b = new alpha.beta.B(); b.wibble(a.aInner); } }
AInner is private
so that only the AOuter class can access it. The AInner
property also has no getter method as having such would violate
encapsulation and allow a back door into the class by allowing
arbitrary outside access to the class AOuter's private methods and
properties. For a similar reason the AInner property has no
setter method. Here is the second file reworked to make it
compile:
package alpha.beta; public class B { public void wibble(alpha.AInner aInner) { // Demonstration of successful attempt // to access every method and property // of a given class. aInner.property1++; aInner.property2++; aInner.property3++; aInner.property4++; // these properties are static // so they could also be prefixed // by alpha.AInner aInner.property5++; aInner.property6++; aInner.property7++; aInner.property8++; aInner.method1(); aInner.method2(); aInner.method3(); aInner.method4(); // these methods are static // so they could also be prefixed // by alpha.AInner aInner.method5(); aInner.method6(); aInner.method7(); aInner.method8(); } }
The downside of this system is that only a method of the class
AOuter can call B's wibble method. Therefore to call a
method in a subpackage, you need to define and call a method in the
class AOuter that in turn calls the method in the subpackage. You
can think of the act of calling a method of a class X with a
reference to the internals of a different class Y as a kind of
friendship, as by passing a reference to the internals of Y you
are granting access to private methods and properties of Y to
methods in X which is outside of Y.
Note: Care must be taken to where the alpha.aInner reference
is used, for example if it is stored somewhere else than a local
variable of the wibble method, then this opens up access to the
internals of the AOuter class to everywhere the alpha.aInner
reference can be accessed.
In the above code, public access to thepackage alpha; public class A { // Defines a property with package visibility java.util.Vector vector = new java.util.Vector(); // Provides the point of entry for an application public static void main(String[] args) { A a = new A(); alpha.beta.B b = new alpha.beta.B(); b.wibble(a.vector); } }
java.util.Vector internals
of A has been granted by the call to the wibble method from
the main method of the A class. Here is the file that
contains the definition of the wibble method:
Note that under this restricted system thepackage alpha.beta; public class B { public void wibble(java.util.Vector vector) { System.out.println("B's wibble method"); // Successful attempt to access a property // in a parent package vector.addElement("apple"); } }
Vector inside the A
class cannot be re-bound to another Vector outside of the A
class. It can only be altered by calling the public methods of the
Vector class.
Extending the above-mentioned design pattern to methods that pass and return values of different types is left as an exercise for the reader. At the present time I haven't worked out how to apply this design pattern to private or package visibility methods in classes that inherit from and use properties of other classes.
If the developers of Java could be made aware of the limitation of the Java under the present system, then Java could be re-engineered so that it no longer has this limitation.
Adding support to the more general system of package visibility for fields would not change the behaviour of Java source files that presently compile. All it would do is make certain files that don't compile into files that do compile.
On the other hand if package visibility as applied to classes is to be
reinterpreted, then some compiler trickery will be needed to ensure
that files that compile under the old system give the same result as
under the new system. As an example of a potential problem is for
code that uses import statements to import a class from a from a
parent package. Here is a file called Vector.java:
Here is the second file, calledpackage alpha; class Vector { }
Main.java:
package alpha.beta; import java.util.*; import alpha.*; class Main { public static void main(String[] args) { Vector v = new Vector(); // *** chooses java.util.Vector rather // than alpha.Vector as alpha.Vector is // not considered to be in the same package System.out.println(v); } }
Because the Vector class has package visibility it is not
accessible to the alpha.beta package. Therefore the compiler
creates a new java.util.Vector at the line marked
***. Changing javac to get around this design flaw should
not change the behaviour of classes that do compile under the current
system. Therefore there needs to be a special case for classes
defined with package visibility.
I hope that the cost of this burden in terms of additional compiler complexity is less than the value of the benefit obtained by the programmer by extending Java in this way.
| Back to Research Projects |