# Thinking in Java

<<< Previous Reusing Classes:Composition syntax, Combining composition and inheritance Next >>>

other languages (especially C) and are used to accessing everything
without restriction. By the end of this chapter you should be convinced of
the value of access control in Java.
The concept of a library of components and the control over who can
access the components of that library is not complete, however. There's
still the question of how the components are bundled together into a
cohesive library unit. This is controlled with the package keyword in
Java, and the access specifiers are affected by whether a class is in the
same package or in a separate package. So to begin this chapter, you'll
learn how library components are placed into packages. Then you'll be
able to understand the complete meaning of the access specifiers.
package: the library unit
A package is what you get when you use the import keyword to bring in
an entire library, such as
import java.util.*;
This brings in the entire utility library that's part of the standard Java
distribution. Since, for example, the class ArrayList is in java.util, you
can now either specify the full name java.util.ArrayList (which you can
do without the import statement), or you can simply say ArrayList
(because of the import).
If you want to bring in a single class, you can name that class in the
import statement
import java.util.ArrayList;
Now you can use ArrayList with no qualification. However, none of the
other classes in java.util are available.
The reason for all this importing is to provide a mechanism to manage
"name spaces." The names of all your class members are insulated from
each other. A method f( ) inside a class A will not clash with an f( ) that
has the same signature (argument list) in class B. But what about the class
names? Suppose you create a stack class that is installed on a machine
that already has a stack class that's written by someone else? With Java
on the Internet, this can happen without the user knowing it, since classes
244
Thinking in Java
program.
This potential clashing of names is why it's important to have complete
control over the name spaces in Java, and to be able to create a completely
unique name regardless of the constraints of the Internet.
So far, most of the examples in this book have existed in a single file and
have been designed for local use, and haven't bothered with package
names. (In this case the class name is placed in the "default package.")
This is certainly an option, and for simplicity's sake this approach will be
used whenever possible throughout the rest of this book. However, if
you're planning to create libraries or programs that are friendly to other
Java programs on the same machine, you must think about preventing
class name clashes.
When you create a source-code file for Java, it's commonly called a
compilation unit (sometimes a translation unit). Each compilation unit
must have a name ending in .java, and inside the compilation unit there
can be a public class that must have the same name as the file (including
capitalization, but excluding the .java filename extension). There can be
only one public class in each compilation unit, otherwise the compiler
will complain. The rest of the classes in that compilation unit, if there are
any, are hidden from the world outside that package because they're not
public, and they comprise "support" classes for the main public class.
When you compile a .java file you get an output file with exactly the same
name but an extension of .class for each class in the .java file. Thus you
can end up with quite a few .class files from a small number of .java
files. If you've programmed with a compiled language, you might be used
to the compiler spitting out an intermediate form (usually an "obj" file)
that is then packaged together with others of its kind using a linker (to
create an executable file) or a librarian (to create a library). That's not
how Java works. A working program is a bunch of .class files, which can
be packaged and compressed into a JAR file (using Java's jar archiver).
Chapter 5: Hiding the Implementation
245
these files1.
A library is also a bunch of these class files. Each file has one class that is
public (you're not forced to have a public class, but it's typical), so
there's one component for each file. If you want to say that all these
components (that are in their own separate .java and .class files) belong
together, that's where the package keyword comes in.
When you say:
package mypackage;
at the beginning of a file (if you use a package statement, it must appear
as the first noncomment in the file), you're stating that this compilation
unit is part of a library named mypackage. Or, put another way, you're
saying that the public class name within this compilation unit is under
the umbrella of the name mypackage, and if anyone wants to use the
name they must either fully specify the name or use the import keyword
in combination with mypackage (using the choices given previously).
Note that the convention for Java package names is to use all lowercase
letters, even for intermediate words.
For example, suppose the name of the file is MyClass.java. This means
there can be one and only one public class in that file, and the name of
that class must be MyClass (including the capitalization):
package mypackage;
public class MyClass {
// . . .
Now, if someone wants to use MyClass or, for that matter, any of the
other public classes in mypackage, they must use the import keyword
to make the name or names in mypackage available. The alternative is
to give the fully qualified name:
mypackage.MyClass m = new mypackage.MyClass();
1 There's nothing in Java that forces the use of an interpreter. There exist native-code Java
compilers that generate a single executable file.
246
Thinking in Java
The import keyword can make this much cleaner:
import mypackage.*;
// . . .
MyClass m = new MyClass();
It's worth keeping in mind that what the package and import keywords
allow you to do, as a library designer, is to divide up the single global
name space so you won't have clashing names, no matter how many
people get on the Internet and start writing classes in Java.
Creating unique package names
You might observe that, since a package never really gets "packaged" into
a single file, a package could be made up of many .class files, and things
could get a bit cluttered. To prevent this, a logical thing to do is to place all
the .class files for a particular package into a single directory; that is, use
This is one way that Java references the problem of clutter; you'll see the
other way later when the jar utility is introduced.
Collecting the package files into a single subdirectory solves two other
problems: creating unique package names, and finding those classes that
might be buried in a directory structure someplace. This is accomplished,
as was introduced in Chapter 2, by encoding the path of the location of the
.class file into the name of the package. The compiler enforces this, but
by convention, the first part of the package name is the Internet domain
name of the creator of the class, reversed. Since Internet domain names
are guaranteed to be unique, if you follow this convention it's guaranteed
that your package name will be unique and thus you'll never have a
name clash. (That is, until you lose the domain name to someone else who
starts writing Java code with the same path names as you did.) Of course,
if you don't have your own domain name then you must fabricate an
unlikely combination (such as your first and last name) to create unique
package names. If you've decided to start publishing Java code it's worth
the relatively small effort to get a domain name.
The second part of this trick is resolving the package name into a
directory on your machine, so when the Java program runs and it needs to
load the .class file (which it does dynamically, at the point in the program
Chapter 5: Hiding the Implementation
247
where it needs to create an object of that particular class, or the first time
you access a static member of the class), it can locate the directory where
the .class file resides.
The Java interpreter proceeds as follows. First, it finds the environment
variable CLASSPATH (set via the operating system, sometimes by the
installation program that installs Java or a Java-based tool on your
machine). CLASSPATH contains one or more directories that are used as
roots for a search for .class files. Starting at that root, the interpreter will
take the package name and replace each dot with a slash to generate a
path name from the CLASSPATH root (so package foo.bar.baz
becomes foo\bar\baz or foo/bar/baz or possibly something else,
depending on your operating system). This is then concatenated to the
various entries in the CLASSPATH. That's where it looks for the .class
file with the name corresponding to the class you're trying to create. (It
also searches some standard directories relative to where the Java
interpreter resides).
To understand this, consider my domain name, which is
bruceeckel.com. By reversing this, com.bruceeckel establishes my
unique global name for my classes. (The com, edu, org, etc., extension was
formerly capitalized in Java packages, but this was changed in Java 2 so
the entire package name is lowercase.) I can further subdivide this by
deciding that I want to create a library named simple, so I'll end up with
a package name:
package com.bruceeckel.simple;
Now this package name can be used as an umbrella name space for the
following two files:
//: com:bruceeckel:simple:Vector.java
// Creating a package.
package com.bruceeckel.simple;
public class Vector {
public Vector() {
System.out.println(
"com.bruceeckel.util.Vector");
}
} ///:~
248
Thinking in Java
When you create your own packages, you'll discover that the package
statement must be the first noncomment code in the file. The second file
looks much the same:
//: com:bruceeckel:simple:List.java
// Creating a package.
package com.bruceeckel.simple;
public class List {
public List() {
System.out.println(
"com.bruceeckel.util.List");
}
} ///:~
Both of these files are placed in the subdirectory on my system:
C:\DOC\JavaT\com\bruceeckel\simple
If you walk back through this, you can see the package name
com.bruceeckel.simple, but what about the first portion of the path?
That's taken care of in the CLASSPATH environment variable, which is,
on my machine:
CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT
You can see that the CLASSPATH can contain a number of alternative
search paths.
There's a variation when using JAR files, however. You must put the name
of the JAR file in the classpath, not just the path where it's located. So for
a JAR named grape.jar your classpath would include:
CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar
Once the classpath is set up properly, the following file can be placed in
any directory:
//: c05:LibTest.java
// Uses the library.
import com.bruceeckel.simple.*;
public class LibTest {
Chapter 5: Hiding the Implementation
249
public static void main(String[] args) {
Vector v = new Vector();
List l = new List();
}
} ///:~
When the compiler encounters the import statement, it begins searching
at the directories specified by CLASSPATH, looking for subdirectory
com\bruceeckel\simple, then seeking the compiled files of the
appropriate names (Vector.class for Vector and List.class for List).
Note that both the classes and the desired methods in Vector and List
must be public.
Setting the CLASSPATH has been such a trial for beginning Java users (it
was for me, when I started) that Sun made the JDK in Java 2 a bit
smarter. You'll find that, when you install it, even if you don't set a
CLASSPATH you'll be able to compile and run basic Java programs. To
compile and run the source-code package for this book (available on the
CD ROM packaged with this book, or at ), however,
you will need to make some modifications to your CLASSPATH (these are
explained in the source-code package).
Collisions
What happens if two libraries are imported via * and they include the
same names? For example, suppose a program does this:
import com.bruceeckel.simple.*;
import java.util.*;
Since java.util.* also contains a Vector class, this causes a potential
collision. However, as long as you don't write the code that actually causes
the collision, everything is OK--this is good because otherwise you might
end up doing a lot of typing to prevent collisions that would never happen.
The collision does occur if you now try to make a Vector:
Vector v = new Vector();
Which Vector class does this refer to? The compiler can't know, and the
reader can't know either. So the compiler complains and forces you to be
explicit. If I want the standard Java Vector, for example, I must say:
250
Thinking in Java
java.util.Vector v = new java.util.Vector();
Since this (along with the CLASSPATH) completely specifies the location
of that Vector, there's no need for the import java.util.* statement
unless I'm using something else from java.util.
A custom tool library
With this knowledge, you can now create your own libraries of tools to
reduce or eliminate duplicate code. Consider, for example, creating an
alias for System.out.println( ) to reduce typing. This can be part of a
package called tools:
//: com:bruceeckel:tools:P.java
// The P.rint & P.rintln shorthand.
package com.bruceeckel.tools;
public class P {
public static void rint(String s) {
System.out.print(s);
}
public static void rintln(String s) {
System.out.println(s);
}
} ///:~
You can use this shorthand to print a String either with a newline
(P.rintln( )) or without a newline (P.rint( )).
You can guess that the location of this file must be in a directory that
starts at one of the CLASSPATH locations, then continues
com/bruceeckel/tools. After compiling, the P.class file can be used
anywhere on your system with an import statement:
//: c05:ToolTest.java
// Uses the tools library.
import com.bruceeckel.tools.*;
public class ToolTest {
public static void main(String[] args) {
P.rintln("Available from now on!");
P.rintln("" + 100); // Force it to be a String
Chapter 5: Hiding the Implementation
251
P.rintln("" + 100L);
P.rintln("" + 3.14159);
}
} ///:~
Notice that all objects can easily be forced into String representations by
putting them in a String expression; in the above case, starting the
expression with an empty String does the trick. But this brings up an
interesting observation. If you call System.out.println(100), it works
without casting it to a String. With some extra overloading, you can get
the P class to do this as well (this is an exercise at the end of this chapter).
So from now on, whenever you come up with a useful new utility, you can
add it to the tools directory. (Or to your own personal util or tools
directory.)
Using imports to change behavior
A feature that is missing from Java is C's conditional compilation, which
allows you to change a switch and get different behavior without changing
any other code. The reason such a feature was left out of Java is probably
because it is most often used in C to solve cross-platform issues: different
portions of the code are compiled depending on the platform that the
code is being compiled for. Since Java is intended to be automatically
cross-platform, such a feature should not be necessary.
However, there are other valuable uses for conditional compilation. A very
common use is for debugging code. The debugging features are enabled
during development, and disabled in the shipping product. Allen Holub
(www.holub.com) came up with the idea of using packages to mimic
conditional compilation. He used this to create a Java version of C's very
useful assertion mechanism, whereby you can say "this should be true" or
"this should be false" and if the statement doesn't agree with your
assertion you'll find out about it. Such a tool is quite helpful during
debugging.
Here is the class that you'll use for debugging:
//: com:bruceeckel:tools:debug:Assert.java
// Assertion tool for debugging.
package com.bruceeckel.tools.debug;
252
Thinking in Java
public class Assert {
private static void perr(String msg) {
System.err.println(msg);
}
public final static void is_true(boolean exp) {
if(!exp) perr("Assertion failed");
}
public final static void is_false(boolean exp){
if(exp) perr("Assertion failed");
}
public final static void
is_true(boolean exp, String msg) {
if(!exp) perr("Assertion failed: " + msg);
}
public final static void
is_false(boolean exp, String msg) {
if(exp) perr("Assertion failed: " + msg);
}
} ///:~
This class simply encapsulates Boolean tests, which print error messages
if they fail. In Chapter 10, you'll learn about a more sophisticated tool for
dealing with errors called exception handling, but the perr( ) method
will work fine in the meantime.
The output is printed to the console standard error stream by writing to
System.err.
When you want to use this class, you add a line in your program:
import com.bruceeckel.tools.debug.*;
To remove the assertions so you can ship the code, a second Assert class
is created, but in a different package:
//: com:bruceeckel:tools:Assert.java
// Turning off the assertion output
// so you can ship the program.
package com.bruceeckel.tools;
public class Assert {
Chapter 5: Hiding the Implementation
253
public final static void is_true(boolean exp){}
public final static void is_false(boolean exp){}
public final static void
is_true(boolean exp, String msg) {}
public final static void
is_false(boolean exp, String msg) {}
} ///:~
Now if you change the previous import statement to:
import com.bruceeckel.tools.*;
The program will no longer print assertions. Here's an example:
//: c05:TestAssert.java
// Demonstrating the assertion tool.
// Comment the following, and uncomment the
// subsequent line to change assertion behavior:
import com.bruceeckel.tools.debug.*;
// import com.bruceeckel.tools.*;
public class TestAssert {
public static void main(String[] args) {
Assert.is_true((2 + 2) == 5);
Assert.is_false((1 + 1) == 2);
Assert.is_true((2 + 2) == 5, "2 + 2 == 5");
Assert.is_false((1 + 1) == 2, "1 +1 != 2");
}
} ///:~
By changing the package that's imported, you change your code from the
debug version to the production version. This technique can be used for
any kind of conditional code.
Package caveat
It's worth remembering that anytime you create a package, you implicitly
specify a directory structure when you give the package a name. The
package must live in the directory indicated by its name, which must be a
directory that is searchable starting from the CLASSPATH.
Experimenting with the package keyword can be a bit frustrating at first,
because unless you adhere to the package-name to directory-path rule,
254
Thinking in Java
you'll get a lot of mysterious run-time messages about not being able to
find a particular class, even if that class is sitting there in the same
directory. If you get a message like this, try commenting out the package
statement, and if it runs you'll know where the problem lies.
Java access specifiers
When used, the Java access specifiers public, protected, and private
are placed in front of each definition for each member in your class,
whether it's a field or a method. Each access specifier controls the access
for only that particular definition. This is a distinct contrast to C++, in
which the access specifier controls all the definitions following it until
another access specifier comes along.
One way or another, everything has some kind of access specified for it. In
the following sections, you'll learn all about the various types of access,
starting with the default access.
"Friendly"
What if you give no access specifier at all, as in all the examples before
this chapter? The default access has no keyword, but it is commonly
referred to as "friendly." It means that all the other classes in the current
package have access to the friendly member, but to all the classes outside
of this package the member appears to be private. Since a compilation
unit--a file--can belong only to a single package, all the classes within a
single compilation unit are automatically friendly with each other. Thus,
friendly elements are also said to have package access.
Friendly access allows you to group related classes together in a package
so that they can easily interact with each other. When you put classes
members; e.g., making them "friends") you "own" the code in that
package. It makes sense that only code you own should have friendly
access to other code you own. You could say that friendly access gives a
meaning or a reason for grouping classes together in a package. In many
languages the way you organize your definitions in files can be willy-nilly,
but in Java you're compelled to organize them in a sensible fashion. In
Chapter 5: Hiding the Implementation
255
addition, you'll probably want to exclude classes that shouldn't have
The class controls which code has access to its members. There's no magic
way to "break in." Code from another package can't show up and say, "Hi,
I'm a friend of Bob's!" and expect to see the protected, friendly, and
private members of Bob. The only way to grant access to a member is to:
1.
Make the member public. Then everybody, everywhere, can access
it.
2.
Make the member friendly by leaving off any access specifier, and
put the other classes in the same package. Then the other classes
can access the member.
3.
As you'll see in Chapter 6, when inheritance is introduced, an
inherited class can access a protected member as well as a public
member (but not private members). It can access friendly
members only if the two classes are in the same package. But don't
4.
Provide "accessor/mutator" methods (also known as "get/set"
methods) that read and change the value. This is the most civilized
approach in terms of OOP, and it is fundamental to JavaBeans, as
you'll see in Chapter 13.
public: interface access
When you use the public keyword, it means that the member declaration
that immediately follows public is available to everyone, in particular to
the client programmer who uses the library. Suppose you define a package
dessert containing the following compilation unit:
// Creates a library.
package c05.dessert;
}
256
Thinking in Java
void bite() { System.out.println("bite"); }
} ///:~
Remember, Cookie.java must reside in a subdirectory called dessert, in
a directory under c05 (indicating Chapter 5 of this book) that must be
under one of the CLASSPATH directories. Don't make the mistake of
thinking that Java will always look at the current directory as one of the
starting points for searching. If you don't have a .' as one of the paths in
your CLASSPATH, Java won't look there.
Now if you create a program that uses Cookie:
//: c05:Dinner.java
// Uses the library.
import c05.dessert.*;
public class Dinner {
public Dinner() {
System.out.println("Dinner constructor");
}
public static void main(String[] args) {
//! x.bite(); // Can't access
}
} ///:~
you can create a Cookie object, since its constructor is public and the
class is public. (We'll look more at the concept of a public class later.)
However, the bite( ) member is inaccessible inside Dinner.java since
bite( ) is friendly only within package dessert.
The default package
You might be surprised to discover that the following code compiles, even
though it would appear that it breaks the rules:
//: c05:Cake.java
// Accesses a class in a
// separate compilation unit.
class Cake {
public static void main(String[] args) {
Chapter 5: Hiding the Implementation
257
Pie x = new Pie();
x.f();
}
} ///:~
In a second file, in the same directory:
//: c05:Pie.java
// The other class.
class Pie {
void f() { System.out.println("Pie.f()"); }
} ///:~
You might initially view these as completely foreign files, and yet Cake is
able to create a Pie object and call its f( ) method! (Note that you must
have .' in your CLASSPATH in order for the files to compile.) You'd
typically think that Pie and f( ) are friendly and therefore not available to
Cake. They are friendly--that part is correct. The reason that they are
available in Cake.java is because they are in the same directory and have
no explicit package name. Java treats files like this as implicitly part of the
"default package" for that directory, and therefore friendly to all the other
files in that directory.
private: you can't touch that!
The private keyword means that no one can access that member except
that particular class, inside methods of that class. Other classes in the
same package cannot access private members, so it's as if you're even
insulating the class against yourself. On the other hand, it's not unlikely
that a package might be created by several people collaborating together,
so private allows you to freely change that member without concern that
it will affect another class in the same package.
The default "friendly" package access often provides an adequate amount
of hiding; remember, a "friendly" member is inaccessible to the user of the
package. This is nice, since the default access is the one that you normally
use (and the one that you'll get if you forget to add any access control).
Thus, you'll typically think about access for the members that you
explicitly want to make public for the client programmer, and as a result,
you might not initially think you'll use the private keyword often since
258
Thinking in Java
it's tolerable to get away without it. (This is a distinct contrast with C++.)
However, it turns out that the consistent use of private is very important,
especially where multithreading is concerned. (As you'll see in Chapter
14.)
Here's an example of the use of private:
//: c05:IceCream.java
// Demonstrates "private" keyword.
class Sundae {
private Sundae() {}
static Sundae makeASundae() {
return new Sundae();
}
}
public class IceCream {
public static void main(String[] args) {
//! Sundae x = new Sundae();
Sundae x = Sundae.makeASundae();
}
} ///:~
This shows an example in which private comes in handy: you might want
to control how an object is created and prevent someone from directly
accessing a particular constructor (or all of them). In the example above,
you cannot create a Sundae object via its constructor; instead you must
call the makeASundae( ) method to do it for you2.
Any method that you're certain is only a "helper" method for that class
can be made private, to ensure that you don't accidentally use it
elsewhere in the package and thus prohibit yourself from changing or
removing the method. Making a method private guarantees that you
retain this option.
2 There's another effect in this case: Since the default constructor is the only one defined,
and it's private, it will prevent inheritance of this class. (A subject that will be introduced
in Chapter 6.)
Chapter 5: Hiding the Implementation
259
The same is true for a private field inside a class. Unless you must expose
the underlying implementation (which is a much rarer situation than you
might think), you should make all fields private. However, just because a
reference to an object is private inside a class doesn't mean that some
other object can't have a public reference to the same object. (See
Appendix A for issues about aliasing.)
protected: "sort of friendly"
The protected access specifier requires a jump ahead to understand.
First, you should be aware that you don't need to understand this section
to continue through this book up through inheritance (Chapter 6). But for
completeness, here is a brief description and example using protected.
The protected keyword deals with a concept called inheritance, which
takes an existing class and adds new members to that class without
touching the existing class, which we refer to as the base class. You can
also change the behavior of existing members of the class. To inherit from
an existing class, you say that your new class extends an existing class,
like this:
class Foo extends Bar {
The rest of the class definition looks the same.
If you create a new package and you inherit from a class in another
package, the only members you have access to are the public members of
the original package. (Of course, if you perform the inheritance in the
same package, you have the normal package access to all the "friendly"
members.) Sometimes the creator of the base class would like to take a
particular member and grant access to derived classes but not the world
in general. That's what protected does. If you refer back to the file
Cookie.java, the following class cannot access the "friendly" member:
//: c05:ChocolateChip.java
// Can't access friendly member
// in another class.
import c05.dessert.*;
public class ChocolateChip extends Cookie {
public ChocolateChip() {
260
Thinking in Java
System.out.println(
"ChocolateChip constructor");
}
public static void main(String[] args) {
ChocolateChip x = new ChocolateChip();
//! x.bite(); // Can't access bite
}
} ///:~
One of the interesting things about inheritance is that if a method bite( )
exists in class Cookie, then it also exists in any class inherited from
Cookie. But since bite( ) is "friendly" in a foreign package, it's
unavailable to us in this one. Of course, you could make it public, but
then everyone would have access and maybe that's not what you want. If
we change the class Cookie as follows:
}
protected void bite() {
System.out.println("bite");
}
}
then bite( ) still has "friendly" access within package dessert, but it is
also accessible to anyone inheriting from Cookie. However, it is not
public.
Interface and
implementation
Access control is often referred to as implementation hiding. Wrapping
data and methods within classes in combination with implementation
hiding is often called encapsulation3. The result is a data type with
characteristics and behaviors.
3 However, people often refer to implementation hiding alone as encapsulation.
Chapter 5: Hiding the Implementation
261
Access control puts boundaries within a data type for two important
reasons. The first is to establish what the client programmers can and
can't use. You can build your internal mechanisms into the structure
without worrying that the client programmers will accidentally treat the
internals as part of the interface that they should be using.
This feeds directly into the second reason, which is to separate the
interface from the implementation. If the structure is used in a set of
programs, but client programmers can't do anything but send messages to
the public interface, then you can change anything that's not public
(e.g., "friendly," protected, or private) without requiring modifications
to client code.
We're now in the world of object-oriented programming, where a class is
actually describing "a class of objects," as you would describe a class of
fishes or a class of birds. Any object belonging to this class will share these
characteristics and behaviors. The class is a description of the way all
objects of this type will look and act.
In the original OOP language, Simula-67, the keyword class was used to
describe a new data type. The same keyword has been used for most
object-oriented languages. This is the focal point of the whole language:
the creation of new data types that are more than just boxes containing
data and methods.
The class is the fundamental OOP concept in Java. It is one of the
keywords that will not be set in bold in this book--it becomes annoying
with a word repeated as often as "class."
For clarity, you might prefer a style of creating classes that puts the
public members at the beginning, followed by the protected, friendly,
and private members. The advantage is that the user of the class can
then read down from the top and see first what's important to them (the
public members, because they can be accessed outside the file), and stop
reading when they encounter the non-public members, which are part of
the internal implementation:
public class X {
public void pub1( ) { /* . . . */ }
public void pub2( ) { /* . . . */ }
262
Thinking in Java
public void pub3( )
{
/* .
.
.
*/ }
private void priv1(
)
{ /*
.
.
. */ }
private void priv2(
)
{ /*
.
.
. */ }
private void priv3(
)
{ /*
.
.
. */ }
private int i;
// . . .
}
This will make it only partially easier to read because the interface and
implementation are still mixed together. That is, you still see the source
code--the implementation--because it's right there in the class. In
Chapter 2) lessens the importance of code readability by the client
programmer. Displaying the interface to the consumer of a class is really
the job of the class browser, a tool whose job is to look at all the available
classes and show you what you can do with them (i.e., what members are
available) in a useful fashion. By the time you read this, browsers should
be an expected part of any good Java development tool.
Class access
In Java, the access specifiers can also be used to determine which classes
within a library will be available to the users of that library. If you want a
class to be available to a client programmer, you place the public
keyword somewhere before the opening brace of the class body. This
controls whether the client programmer can even create an object of the
class.
To control the access of a class, the specifier must appear before the
keyword class. Thus you can say:
public class Widget {
Now if the name of your library is mylib any client programmer can
access Widget by saying
import mylib.Widget;
or
import mylib.*;
Chapter 5: Hiding the Implementation
263
However, there's an extra set of constraints:
1.
There can be only one public class per compilation unit (file). The
idea is that each compilation unit has a single public interface
represented by that public class. It can have as many supporting
"friendly" classes as you want. If you have more than one public
class inside a compilation unit, the compiler will give you an error
message.
2.
The name of the public class must exactly match the name of the
file containing the compilation unit, including capitalization. So for
Widget, the name of the file must be Widget.java, not
widget.java or WIDGET.java. Again, you'll get a compile-time
error if they don't agree.
3.
It is possible, though not typical, to have a compilation unit with no
public class at all. In this case, you can name the file whatever you
like.
What if you've got a class inside mylib that you're just using to
accomplish the tasks performed by Widget or some other public class in
mylib? You don't want to go to the bother of creating documentation for
the client programmer, and you think that sometime later you might want
to completely change things and rip out your class altogether, substituting
a different one. To give you this flexibility, you need to ensure that no
client programmers become dependent on your particular
implementation details hidden inside mylib. To accomplish this, you just
leave the public keyword off the class, in which case it becomes friendly.
(That class can be used only within that package.)
Note that a class cannot be private (that would make it accessible to no
one but the class), or protected4. So you have only two choices for class
access: "friendly" or public. If you don't want anyone else to have access
to that class, you can make all the constructors private, thereby
4 Actually, an inner class can be private or protected, but that's a special case. These will
be introduced in Chapter 7.
264
Thinking in Java
preventing anyone but you, inside a static member of the class, from
creating an object of that class5. Here's an example:
//: c05:Lunch.java
// Demonstrates class access specifiers.
// Make a class effectively private
// with private constructors:
class Soup {
private Soup() {}
// (1) Allow creation via static method:
public static Soup makeSoup() {
return new Soup();
}
// (2) Create a static object and
// return a reference upon request.
// (The "Singleton" pattern):
private static Soup ps1 = new Soup();
public static Soup access() {
return ps1;
}
public void f() {}
}
class Sandwich { // Uses Lunch
void f() { new Lunch(); }
}
// Only one public class allowed per file:
public class Lunch {
void test() {
// Can't do this! Private constructor:
//! Soup priv1 = new Soup();
Soup priv2 = Soup.makeSoup();
Sandwich f1 = new Sandwich();
Soup.access().f();
}
} ///:~
5 You can also do it by inheriting (Chapter 6) from that class.
Chapter 5: Hiding the Implementation
265
Up to now, most of the methods have been returning either void or a
primitive type, so the definition:
public static Soup access() {
return ps1;
}
might look a little confusing at first. The word before the method name
(access) tells what the method returns. So far this has most often been
void, which means it returns nothing. But you can also return a reference
to an object, which is what happens here. This method returns a reference
to an object of class Soup.
The class Soup shows how to prevent direct creation of a class by
making all the constructors private. Remember that if you don't
explicitly create at least one constructor, the default constructor (a
constructor with no arguments) will be created for you. By writing the
default constructor, it won't be created automatically. By making it
private, no one can create an object of that class. But now how does
anyone use this class? The above example shows two options. First, a
static method is created that creates a new Soup and returns a reference
to it. This could be useful if you want to do some extra operations on the
Soup before returning it, or if you want to keep count of how many Soup
objects to create (perhaps to restrict their population).
The second option uses what's called a design pattern, which is covered in
This particular pattern is called a "singleton" because it allows only a
single object to ever be created. The object of class Soup is created as a
static private member of Soup, so there's one and only one, and you
can't get at it except through the public method access( ).
As previously mentioned, if you don't put an access specifier for class
access it defaults to "friendly." This means that an object of that class can
be created by any other class in the package, but not outside the package.
(Remember, all the files within the same directory that don't have explicit
package declarations are implicitly part of the default package for that
directory.) However, if a static member of that class is public, the client
programmer can still access that static member even though they cannot
create an object of that class.
266
Thinking in Java
Summary
In any relationship it's important to have boundaries that are respected by
all parties involved. When you create a library, you establish a
relationship with the user of that library--the client programmer--who is
another programmer, but one putting together an application or using
your library to build a bigger library.
Without rules, client programmers can do anything they want with all the
members of a class, even if you might prefer they don't directly
manipulate some of the members. Everything's naked to the world.
This chapter looked at how classes are built to form libraries; first, the
way a group of classes is packaged within a library, and second, the way
It is estimated that a C programming project begins to break down
somewhere between 50K and 100K lines of code because C has a single
"name space" so names begin to collide, causing an extra management
overhead. In Java, the package keyword, the package naming scheme,
and the import keyword give you complete control over names, so the
issue of name collision is easily avoided.
There are two reasons for controlling access to members. The first is to
keep users' hands off tools that they shouldn't touch; tools that are
necessary for the internal machinations of the data type, but not part of
the interface that users need to solve their particular problems. So making
methods and fields private is a service to users because they can easily
see what's important to them and what they can ignore. It simplifies their
understanding of the class.
The second and most important reason for access control is to allow the
library designer to change the internal workings of the class without
worrying about how it will affect the client programmer. You might build
a class one way at first, and then discover that restructuring your code will
provide much greater speed. If the interface and implementation are
clearly separated and protected, you can accomplish this without forcing
the user to rewrite their code.
Chapter 5: Hiding the Implementation
267
Access specifiers in Java give valuable control to the creator of a class. The
users of the class can clearly see exactly what they can use and what to
ignore. More important, though, is the ability to ensure that no user
becomes dependent on any part of the underlying implementation of a
class. If you know this as the creator of the class, you can change the
underlying implementation with the knowledge that no client
programmer will be affected by the changes because they can't access that
part of the class.
When you have the ability to change the underlying implementation, you
can not only improve your design later, but you also have the freedom to
make mistakes. No matter how carefully you plan and design you'll make
mistakes. Knowing that it's relatively safe to make these mistakes means
you'll be more experimental, you'll learn faster, and you'll finish your
project sooner.
The public interface to a class is what the user does see, so that is the most
important part of the class to get "right" during analysis and design. Even
that allows you some leeway for change. If you don't get the interface right
the first time, you can add more methods, as long as you don't remove any
that client programmers have already used in their code.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from .
1.
Write a program that creates an ArrayList object without
explicitly importing java.util.*.
2.
In the section labeled "package: the library unit," turn the code
fragments concerning mypackage into a compiling and running
set of Java files.
3.
In the section labeled "Collisions," take the code fragments and
turn them into a program, and verify that collisions do in fact
occur.
4.
Generalize the class P defined in this chapter by adding all the
overloaded versions of rint( ) and rintln( ) necessary to handle
all the different basic Java types.
268
Thinking in Java
5.
Change the import statement in TestAssert.java to enable and
disable the assertion mechanism.
6.
Create a class with public, private, protected, and "friendly"
data members and method members. Create an object of this class
and see what kind of compiler messages you get when you try to
access all the class members. Be aware that classes in the same
directory are part of the "default" package.
7.
Create a class with protected data. Create a second class in the
same file with a method that manipulates the protected data in
the first class.
8.
Change the class Cookie as specified in the section labeled
"protected: sort of friendly.'" Verify that bite( ) is not public.
9.
In the section titled "Class access" you'll find code fragments
describing mylib and Widget. Create this library, then create a
Widget in a class that is not part of the mylib package.
10.
Create a new directory and edit your CLASSPATH to include that
new directory. Copy the P.class file (produced by compiling
com.bruceeckel.tools.P.java) to your new directory and then
change the names of the file, the P class inside and the method
how it works.) Create another program in a different directory that
11.
Following the form of the example Lunch.java, create a class
called ConnectionManager that manages a fixed array of
Connection objects. The client programmer must not be able to
explicitly create Connection objects, but can only get them via a
static method in ConnectionManager. When the
ConnectionManager runs out of objects, it returns a null
reference. Test the classes in main( ).
12.
Create the following file in the c05/local directory (presumably in
///: c05:local:PackagedClass.java
package c05.local;
Chapter 5: Hiding the Implementation
269
class PackagedClass {
public PackagedClass() {
System.out.println(
"Creating a packaged class");
}
} ///:~
Then create the following file in a directory other than c05:
///: c05:foreign:Foreign.java
package c05.foreign;
import c05.local.*;
public class Foreign {
public static void main (String[] args) {
PackagedClass pc = new PackagedClass();
}
} ///:~
Explain why the compiler generates an error. Would making the
Foreign class part of the c05.local package change anything?
270
Thinking in Java
6: Reusing Classes
One of the most compelling features about Java is code
reuse. But to be revolutionary, you've got to be able to do
a lot more than copy code and change it.
That's the approach used in procedural languages like C, and it hasn't
worked very well. Like everything in Java, the solution revolves around
the class. You reuse code by creating new classes, but instead of creating
them from scratch, you use existing classes that someone has already built
and debugged.
The trick is to use the classes without soiling the existing code. In this
chapter you'll see two ways to accomplish this. The first is quite
straightforward: You simply create objects of your existing class inside the
new class. This is called composition, because the new class is composed
of objects of existing classes. You're simply reusing the functionality of the
code, not its form.
The second approach is more subtle. It creates a new class as a type of an
existing class. You literally take the form of the existing class and add code
to it without modifying the existing class. This magical act is called
inheritance, and the compiler does most of the work. Inheritance is one of
the cornerstones of object-oriented programming, and has additional
implications that will be explored in Chapter 7.
It turns out that much of the syntax and behavior are similar for both
composition and inheritance (which makes sense because they are both
ways of making new types from existing types). In this chapter, you'll
learn about these code reuse mechanisms.
Composition syntax
Until now, composition has been used quite frequently. You simply place
object references inside new classes. For example, suppose you'd like an
object that holds several String objects, a couple of primitives, and an
271
object of another class. For the nonprimitive objects, you put references
inside your new class, but you define the primitives directly:
//: c06:SprinklerSystem.java
// Composition for code reuse.
class WaterSource {
private String s;
WaterSource() {
System.out.println("WaterSource()");
s = new String("Constructed");
}
public String toString() { return s; }
}
public class SprinklerSystem {
private String valve1, valve2, valve3, valve4;
WaterSource source;
int i;
float f;
void print() {
System.out.println("valve1 = " + valve1);
System.out.println("valve2 = " + valve2);
System.out.println("valve3 = " + valve3);
System.out.println("valve4 = " + valve4);
System.out.println("i = " + i);
System.out.println("f = " + f);
System.out.println("source = " + source);
}
public static void main(String[] args) {
SprinklerSystem x = new SprinklerSystem();
x.print();
}
} ///:~
One of the methods defined in WaterSource is special: toString( ).
You will learn later that every nonprimitive object has a toString( )
method, and it's called in special situations when the compiler wants a
String but it's got one of these objects. So in the expression:
System.out.println("source = " + source);
272
Thinking in Java
the compiler sees you trying to add a String object ("source = ") to a
WaterSource. This doesn't make sense to it, because you can only "add"
a String to another String, so it says "I'll turn source into a String by
calling toString( )!" After doing this it can combine the two Strings and
pass the resulting String to System.out.println( ). Any time you want
to allow this behavior with a class you create you need only write a
toString( ) method.
At first glance, you might assume--Java being as safe and careful as it is--
that the compiler would automatically construct objects for each of the
references in the above code; for example, calling the default constructor
for WaterSource to initialize source. The output of the print statement
is in fact:
valve1 =
null
valve2 =
null
valve3 =
null
valve4 =
null
i=0
f = 0.0
source =
null
Primitives that are fields in a class are automatically initialized to zero, as
noted in Chapter 2. But the object references are initialized to null, and if
you try to call methods for any of them you'll get an exception. It's actually
pretty good (and useful) that you can still print them out without
throwing an exception.
It makes sense that the compiler doesn't just create a default object for
every reference because that would incur unnecessary overhead in many
cases. If you want the references initialized, you can do it:
1.
At the point the objects are defined. This means that they'll always
be initialized before the constructor is called.
2.
In the constructor for that class.
3.
Right before you actually need to use the object. This is often called
lazy initialization. It can reduce overhead in situations where the
object doesn't need to be created every time.
Chapter 6: Reusing Classes
273
All three approaches are shown here:
//: c06:Bath.java
// Constructor initialization with composition.
class Soap {
private String s;
Soap() {
System.out.println("Soap()");
s = new String("Constructed");
}
public String toString() { return s; }
}
public class Bath {
private String
// Initializing at point of definition:
s1 = new String("Happy"),
s2 = "Happy",
s3, s4;
Soap castille;
int i;
float toy;
Bath() {
System.out.println("Inside Bath()");
s3 = new String("Joy");
i = 47;
toy = 3.14f;
castille = new Soap();
}
void print() {
// Delayed initialization:
if(s4 == null)
s4 = new String("Joy");
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
System.out.println("s3 = " + s3);
System.out.println("s4 = " + s4);
System.out.println("i = " + i);
System.out.println("toy = " + toy);
System.out.println("castille = " + castille);
274
Thinking in Java
}
public static void main(String[] args) {
Bath b = new Bath();
b.print();
}
} ///:~
Note that in the Bath constructor a statement is executed before any of
the initializations take place. When you don't initialize at the point of
definition, there's still no guarantee that you'll perform any initialization
before you send a message to an object reference--except for the
inevitable run-time exception.
Here's the output for the program:
Inside Bath()
Soap()
s1 = Happy
s2 = Happy
s3 = Joy
s4 = Joy
i = 47
toy = 3.14
castille = Constructed
When print( ) is called it fills in s4 so that all the fields are properly
initialized by the time they are used.
Inheritance syntax
Inheritance is an integral part of Java (and OOP languages in general). It
turns out that you're always doing inheritance when you create a class,
because unless you explicitly inherit from some other class, you implicitly
inherit from Java's standard root class Object.
The syntax for composition is obvious, but to perform inheritance there's
a distinctly different form. When you inherit, you say "This new class is
like that old class." You state this in code by giving the name of the class
as usual, but before the opening brace of the class body, put the keyword
extends followed by the name of the base class. When you do this, you
Chapter 6: Reusing Classes
275
automatically get all the data members and methods in the base class.
Here's an example:
//: c06:Detergent.java
// Inheritance syntax & properties.
class Cleanser {
private String s = new String("Cleanser");
public void append(String a) { s += a; }
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public void print() { System.out.println(s); }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub();
x.print();
}
}
public class Detergent extends Cleanser {
// Change a method:
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() { append(" foam()"); }
// Test the new class:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
x.print();
System.out.println("Testing base class:");
Cleanser.main(args);
}
} ///:~
276
Thinking in Java
This demonstrates a number of features. First, in the Cleanser
append( ) method, Strings are concatenated to s using the += operator,
which is one of the operators (along with +') that the Java designers
Second, both Cleanser and Detergent contain a main( ) method. You
can create a main( ) for each one of your classes, and it's often
recommended to code this way so that your test code is wrapped in with
the class. Even if you have a lot of classes in a program, only the main( )
for the class invoked on the command line will be called. (As long as
main( ) is public, it doesn't matter whether the class that it's part of is
public.) So in this case, when you say java Detergent,
Detergent.main( ) will be called. But you can also say java Cleanser
to invoke Cleanser.main( ), even though Cleanser is not a public
class. This technique of putting a main( ) in each class allows easy unit
testing for each class. And you don't need to remove the main( ) when
you're finished testing; you can leave it in for later testing.
Here, you can see that Detergent.main( ) calls Cleanser.main( )
explicitly, passing it the same arguments from the command line
(however, you could pass it any String array).
It's important that all of the methods in Cleanser are public. Remember
that if you leave off any access specifier the member defaults to "friendly,"
which allows access only to package members. Thus, within this package,
anyone could use those methods if there were no access specifier.
Detergent would have no trouble, for example. However, if a class from
some other package were to inherit from Cleanser it could access only
public members. So to plan for inheritance, as a general rule make all
fields private and all methods public. (protected members also allow
particular cases you must make adjustments, but this is a useful guideline.
Note that Cleanser has a set of methods in its interface: append( ),
dilute( ), apply( ), scrub( ), and print( ). Because Detergent is
derived from Cleanser (via the extends keyword) it automatically gets
all these methods in its interface, even though you don't see them all
explicitly defined in Detergent. You can think of inheritance, then, as
Chapter 6: Reusing Classes
277
reusing the interface. (The implementation also comes with it, but that
part isn't the primary point.)
As seen in scrub( ), it's possible to take a method that's been defined in
the base class and modify it. In this case, you might want to call the
method from the base class inside the new version. But inside scrub( )
you cannot simply call scrub( ), since that would produce a recursive
call, which isn't what you want. To solve this problem Java has the
keyword super that refers to the "superclass" that the current class has
been inherited from. Thus the expression super.scrub( ) calls the base-
class version of the method scrub( ).
When inheriting you're not restricted to using the methods of the base
class. You can also add new methods to the derived class exactly the way
you put any method in a class: just define it. The method foam( ) is an
example of this.
In Detergent.main( ) you can see that for a Detergent object you can
call all the methods that are available in Cleanser as well as in
Detergent (i.e., foam( )).
Initializing the base class
Since there are now two classes involved--the base class and the derived
class--instead of just one, it can be a bit confusing to try to imagine the
resulting object produced by a derived class. From the outside, it looks
like the new class has the same interface as the base class and maybe
some additional methods and fields. But inheritance doesn't just copy the
interface of the base class. When you create an object of the derived class,
it contains within it a subobject of the base class. This subobject is the
same as if you had created an object of the base class by itself. It's just
that, from the outside, the subobject of the base class is wrapped within
the derived-class object.
Of course, it's essential that the base-class subobject be initialized
correctly and there's only one way to guarantee that: perform the
initialization in the constructor, by calling the base-class constructor,
which has all the appropriate knowledge and privileges to perform the
base-class initialization. Java automatically inserts calls to the base-class
278
Thinking in Java
constructor in the derived-class constructor. The following example shows
this working with three levels of inheritance:
//: c06:Cartoon.java
// Constructor calls during inheritance.
class Art {
Art() {
System.out.println("Art constructor");
}
}
class Drawing extends Art {
Drawing() {
System.out.println("Drawing constructor");
}
}
public class Cartoon extends Drawing {
Cartoon() {
System.out.println("Cartoon constructor");
}
public static void main(String[] args) {
Cartoon x = new Cartoon();
}
} ///:~
The output for this program shows the automatic calls:
Art constructor
Drawing constructor
Cartoon constructor
You can see that the construction happens from the base "outward," so
the base class is initialized before the derived-class constructors can
access it.
Even if you don't create a constructor for Cartoon( ), the compiler will
synthesize a default constructor for you that calls the base class
constructor.
Chapter 6: Reusing Classes
279
Constructors with arguments
The above example has default constructors; that is, they don't have any
arguments. It's easy for the compiler to call these because there's no
question about what arguments to pass. If your class doesn't have default
arguments, or if you want to call a base-class constructor that has an
argument, you must explicitly write the calls to the base-class constructor
using the super keyword and the appropriate argument list:
//: c06:Chess.java
// Inheritance, constructors and arguments.
class Game {
Game(int i) {
System.out.println("Game constructor");
}
}
class BoardGame extends Game {
BoardGame(int i) {
super(i);
System.out.println("BoardGame constructor");
}
}
public class Chess extends BoardGame {
Chess() {
super(11);
System.out.println("Chess constructor");
}
public static void main(String[] args) {
Chess x = new Chess();
}
} ///:~
If you don't call the base-class constructor in BoardGame( ), the
compiler will complain that it can't find a constructor of the form
Game( ). In addition, the call to the base-class constructor must be the
first thing you do in the derived-class constructor. (The compiler will
remind you if you get it wrong.)
280
Thinking in Java
Catching base constructor exceptions
As just noted, the compiler forces you to place the base-class constructor
call first in the body of the derived-class constructor. This means nothing
else can appear before it. As you'll see in Chapter 10, this also prevents a
derived-class constructor from catching any exceptions that come from a
base class. This can be inconvenient at times.
Combining composition
and inheritance
It is very common to use composition and inheritance together. The
following example shows the creation of a more complex class, using both
inheritance and composition, along with the necessary constructor
initialization:
//: c06:PlaceSetting.java
// Combining composition & inheritance.
class Plate {
Plate(int i) {
System.out.println("Plate constructor");
}
}
class DinnerPlate extends Plate {
DinnerPlate(int i) {
super(i);
System.out.println(
"DinnerPlate constructor");
}
}
class Utensil {
Utensil(int i) {
System.out.println("Utensil constructor");
}
}
Chapter 6: Reusing Classes
281
class Spoon extends Utensil {
Spoon(int i) {
super(i);
System.out.println("Spoon constructor");
}
}
class Fork extends Utensil {
Fork(int i) {
super(i);
System.out.println("Fork constructor");
}
}
class Knife extends Utensil {
Knife(int i) {
super(i);
System.out.println("Knife constructor");
}
}
// A cultural way of doing something:
class Custom {
Custom(int i) {
System.out.println("Custom constructor");
}
}
public class PlaceSetting extends Custom {
Spoon sp;
Fork frk;
Knife kn;
DinnerPlate pl;
PlaceSetting(int i) {
super(i + 1);
sp = new Spoon(i + 2);
frk = new Fork(i + 3);
kn = new Knife(i + 4);
pl = new DinnerPlate(i + 5);
System.out.println(
"PlaceSetting constructor");
282
Thinking in Java
}
public static void main(String[] args) {
PlaceSetting x = new PlaceSetting(9);
}
} ///:~
While the compiler forces you to initialize the base classes, and requires
that you do it right at the beginning of the constructor, it doesn't watch
over you to make sure that you initialize the member objects, so you must
remember to pay attention to that.
Guaranteeing proper cleanup
Java doesn't have the C++ concept of a destructor, a method that is
automatically called when an object is destroyed. The reason is probably
that in Java the practice is simply to forget about objects rather than to
destroy them, allowing the garbage collector to reclaim the memory as
necessary.
Often this is fine, but there are times when your class might perform some
activities during its lifetime that require cleanup. As mentioned in
Chapter 4, you can't know when the garbage collector will be called, or if it
will be called. So if you want something cleaned up for a class, you must
explicitly write a special method to do it, and make sure that the client
programmer knows that they must call this method. On top of this--as
described in Chapter 10 ("Error Handling with Exceptions")--you must
guard against an exception by putting such cleanup in a finally clause.
Consider an example of a computer-aided design system that draws
pictures on the screen: