Java Online Tutorial
BASIC SYNTAX AND FUNDAMENTAL CONCEPTS - Part II
This chapter covers the following topics and is essential
reading before tackling the other chapters.
MODIFIER
FLOW CONTROL
MODIFIER
Modifier Overview
The most common modifiers are the access modifiers:
public, protected, and private. The access
modifiers are covered in the next section. The remaining
modifiers do not fall into clear categories. They are:
- final
- abstract
- static
- native
- transient
- synchronized
- volatile
Each of these modifiers is discussed in its own section.
Back to TOP
The Access Modifiers
Access modifiers control which classes may use a feature.
A class' features are
- The class itself
- Its class variables
- Its methods and constructors
Note that, with rare exceptions, the only variables
that may be controlled by access modifiers are class-level
variables. The variables that you declare and use within
a class' methods may not have access modifiers. This
makes sense - a method variable can only be used within
its method.
The access modifiers are
The only access modifier permitted to non-inner classes
is public. There is no such thing as a protected or
private top level class.
A feature may have at most one access modifier. If
a feature has no access modifier, its access defaults
to friendly. Be aware that friendly is not a
Java keyword. It is just the colloquial name that we
humans use for the type of access a feature gets if
no modifier is specified.
The following declarations are all legal, provided
they appear in valid contexts.
class Parser { ... }
public class EightDimensionalComplex { ... }
private int i;
Graphics offScreenGraphics;
protected double getChiSquared() { ... }
private class Horse { .... }
The following declarations are illegal.
public protected int x;// at most one access modifier
friendly Button getBtn(){ ... }// 'friendly' is not a modifier
Back to TOP
public
The most generous access modifier is public. A public
class, variable, or method may be used in any Java program
without restriction. An applet (i.e. a custom subclass
of java.applet.Applet) is declared as a public class
so that it may be instantiated by browsers. An application
declares its main() method to be public so that main()
may be invoked from any Java runtime environment.
To summarize - public features may be accessed from
anywhere by anyone. External classes,
subclasses and classes in other packages all have access
to public features without any restriction at all.
Back to TOP
private
The least generous access modifier is private. Top
level classes may not be declared private. A private
variable or method may only be used by an instance of
the class that declares the variable or method.
As an example, consider the following code.
class Complex {
private double real, imaginary;
public complex(double r, double i) {
real=r;
imaginary=i;
}
public Complex add(Complex c) {
return new Complex(real+c.real, imaginary+c.imaginary);}
}
class Test {
void useThem() {
Complex c1 = new Complex(1, 2);
Complex c2 = new Complex(3, 4);
Complex c3 = c1.add(c2);
double d = c3.real; // illegal !
}
}
Here again, the last line causes a compiler error similar
to the previous one: "Variable real in class Complex
not accessible from SubComplex." The private nature
of variable real prevents an instance of SubComplex
from accessing one of its own variables !
The main purpose of the private access modifier is
hiding data . Here the SubComplex class can be
made to compile by accessing variable real through an
instance of its class.
Friendly
Friendly is the name of the default access of
classes, variables, and methods, if you don't specify
an access modifier. A class' data and methods may be
friendly, as well as the class itself. A class' friendly
features are accessible to any class in the same package
as the class in question.
Friendly is not a Java keyword. It is simply a name
given to the access level that results from not specifying
an access modifier.
To summarize: only classes that are in the package
may access friendly features of the classes that are
in the package. Friendly is sometimes also called package-access
Back to TOP
protected
The name protected is a bit misleading. From
the sound of it, you might guess that protected access
is extremely restrictive -- perhaps the next closest
thing to private access. In fact, protected features
are even more accessible than friendly features.
Only variables, methods and inner-classes may be declared
protected. A protected feature of a class is available
to all classes in the same package, just like a friendly
feature. Moreover, a protected feature of a class is
available to all subclasses of the class that owns the
protected feature. This access is provided even to subclasses
that reside in a different package from the class that
owns the protected feature.
As an example consider the following source:
package sportinggoods;
class Ski {
void applyWax() { ... }
}
The applyWax() methods defaults to friendly access. Now consider the following subclass.
package sportinggoods;
class DownhillSki extends Ski {
void tuneup() {
applyWax();
}
}
The subclass calls the inherited method applyWax().
This is not a problem as long as Ski and DownhillSki
reside in the same package. However if one of the classes
were to be moved to a different package, DownhillSki
would no longer have access to the inherited method
applyWax() and compilation would fail. The problem can
be fixed by making applyWax protected like this.
package adifferentpackage;
class Ski {
protected void applyWax() { ... }
}
Back to TOP
Subclasses
and Method privacy
Java specifies that methods may not be overridden to
be more private. For example, most applets provide an
init() method, which overrides the do nothing version
in java.applet.Applet. The original version is declared
public so declaring the subclass version private, friendly
or protected would result in the compiler error which
gives a message like, "Methods cannot be overridden
to be more private."
private ---> friendly ---> protected --->
public
The rules for overriding may be summarized as follows.
- A private method may be overridden by a private,
friendly, protected or public method.
- A friendly method may be overridden by a friendly,
protected or public method.
- A protected method may be overridden by a protected
or public method.
- A public method may only be overridden by a public
method.
Back to TOP
Other Modifiers
The rest of
this section covers the other Java modifiers - final,
abstract, static, native, transient, synchronized
and volatile .
Java does not
care about order of appearance of modifiers. Declaring
a class to be public final is no different from
declaring it as final public . Declaring a method
to be protected static is the same as declaring
it static protected .
Not every modifier can be applied to every feature.
A table at the end of the section summarizes which modifiers
apply to which features.
Back to TOP
final
The final modifier applies to classes, methods and
variables. Final features may not be changed, in other
words final has the same meaning as constant.
A final class may not be subclassed. For example the
following statement will not compile, because the java.lang.Math
class is final.
class SubMath extends java.lang.Math { ... }
The compiler says, "Can't subclass final classes."
A final variable may not be modified once it has been
assigned a value. For example java.lang.Math class has
a final variable, of type double, called PI. Obviously
pi cannot be changed during the execution of a program.
If a final variable is a reference to an object, it
is the reference that must stay the same, not the object.
You cannot alter the reference of a final object, but
you can change the data it contains. For example:
class Walrus {
int weight;
Walrus(int w) { weight = w; }
}
class Tester {
final Walrus w1 = new Walrus(1500);
void test() {
w1 = new Walrus(1400); // illegal
w1.weight = 1800; // legal
}
}
A final method may not be overridden. For example the code below will not compile.
class Mammal {
final void getAround() { .. }
}
class Dolphin extends Mammal {
void getAround() { ... }
}
The compiler error one would
get from attempting to compile the above code would be: "Final
methods cannot be overridden."
Back to TOP
abstract
The abstract modifier can be applied to classes and
methods. A class that is abstract may not be instantiated
( i.e. you may not call its constructor).
The signature of an abstract method looks like this:
abstract void travel();
You will notice that the method declaration has no
method body! The implementation of an abstract method
is found in its subclasses. The superclass only provides
the method name and signature.
If a class contains one or more abstract methods, the
compiler insists that the class must be declared abstract.
This is a great convenience to programmers who only
have to look in one place (the class declaration) to
find out if they are allowed to instantiate that class
or if they have to build a subclass.
In fact, the compiler insists that a class must be
declared abstract if any of the following conditions
is true.
- The class has one or more abstract methods
- The class inherits one or
more abstract methods (from an abstract parent) for which it does not
provide implementations.
- The class declares that it implements an interface but does not provide
implementations from every method of that interface.
In a way, abstract is the
opposite of final. A final class cannot be subclassed whereas an
abstract class must be subclassed.
Back to TOP
static
The static modifier can be
applied to variables, methods and even a strange kind of code that is
not part of a method. You can think of static features as belonging to
a class rather than to a specific instance of the class.
Static means there is only
one copy of the variable, no matter how many specific instances of the
class might exist at any particular moment. You can reference a static
variable through its class name or through a reference to any instance
of the class. Since there is only one copy, no matter how many
specific instances of the class, it makes better sense to reference it
via the class name rather than through a specific instance.
Methods as well as data can
be declared static. Static methods are not allowed to call the
non-static features of their class (although they are free to call the
static methods and static data of their class).
Consider an example:
class SomeClass {
static int i = 45;
int j = 2;
public static void main (String args[]) {
i += 120;
j *= 10; //error: will not compile!
}
}
Non-static methods have an implicit variable named
this , which is a reference to the object executing
the method. For example:
class NoThisInStatic {
int w;
void bumpW() {
w++;
}
}
On the last line, the programmer has not specified
which object's w is to be incremented. The compiler
assumes the line to be an abbreviation for
this.w++;
For static methods, there is no this, If you try to
access it you will get an error message which says,
"Undefined variable: this." As mentioned earlier,
the concept of a specific instance of a class executing
the current method does not mean anything, static belongs
to the whole class in general.
A static method may not be overridden to be non-static.
The code below will not compile.
class Cattle {
static void foo() { ... }
}
class Sheep extends Cattle {
void foo() { ... } //compiler error !
}
The compiler objects to the last line because a static method is
overridden to be non-static. It can be made to compile
if we change the signature of the over-ridden method
to:
static void foo() { ... }
To summarize static methods.
- A static method may only access the static data
of its class. It may not access non-static data.
- A static method may only call the static methods
of its class. It may not call non-static methods.
- A static method has no this.
- A static method may not be overridden to be non-static.
Static Initializers
It is legal for a class to contain static code that does not exist
within a method body. A class may have a block of initializer
code that is simply surrounded by curly braces and labeled
static. For example:
public class StaticDemo {
static int i = 5;
static {
System.out.println("Static code: i = " + i++);
}
public static void main(String args[]) {
System.out.println("main: i = " + i++ );
}
}
This kind of construction is perfectly legal. The code inside the curlies is executed exactly once, at the time the class
is loaded. At class load time, all static initializer code are executed in order of appearance within the
class definition.
Free floating initializer code should be used with caution as it can
easily lead to obfuscated code. The compiler supports
multiple initializer blocks within a class, but there
is never a good reason to have more than one such block.
Back to TOP
native
The native modifier can refer only to methods. Like the abstract
keyword, native indicates that the body of the method
is to be found elsewhere. In the case of abstract methods,
the body is in a subclass. With native methods, the
body lies entirely outside the Java Virtual Machine,
in a library.
Native code is written in a non-java language, typically C or C++
and compiled for single target machine type.
The signature of a native method is like this:
native void doSomething();
Again, there
is no method body! As mentioned above the implementation
resides in a library which is made available by the
statement:
System.loadLibrary("SomeLibrary");
Native methods
are called just like ordinary methods and callers needn't
know that the method is native. Many common methods
are native, including all the number crunching methods
of the Math class and the clone() and notify() methods
of the Object class.
Back to TOP
transient
The transient modifier applies to variables only. A transient variable
is not stored as part of its object's persistent state.
Many objects (specifically those that implement either the Externalizable
or Serializable interfaces) can have their state serialized
and written to some destination outside the Java Virtual
Machine. This is done by passing the object to the writeObject()
method of the File Output Stream, then the object's
state is written to a file. If the stream is chained
to a socket's Output Stream, then the object's state
is written to the network. In both cases, the object
can be reconstituted by reading it from the Object Input
Stream.
There will be times when an object will contain extremely sensitive
information. Consider the following class:
class WealthyCustomer extends Customer implements Serializable {
private float wealth;
private String accessCode;
}
Once an object is written to a destination outside the JVM, none of
Java's elaborate security mechanisms is in effect. If
an instance of this class were to be written to a file
or to the Internet, somebody could snoop the access
code. To prevent this the variable should be made transient
by marking it with the transient keyword like this.
class WealthyCustomer extends Customer implements Serializable {
private float wealth;
private transient String accessCode;
}
Now the access code will not be written out during serialization.
Note: Transient variables may not be final or static.
Back to TOP
synchronized
Only methods may be synchronized. The synchronized keyword means
that only one thread can access the method at a particular
time.
Back to TOP
volatile
Only variables may be volatile. Declaring them so indicates that such
methods may be modified asynchronously, so the compiler
takes special precautions. Volatile variables are of interest
in multiprocessor environments.
Back to TOP
In Summary
pNot all modifiers can be applied to all features. Top level classes may
not be private or protected. Methods may not be transient.
Static is so general that you can apply it to free-floating
blocks of code.
The table below summarizes the possible combinations of features and
modifiers.
|
Modifier
|
Class
|
Variable
|
Method/Constructor
|
Free-floating block
|
|
public
|
yes
|
yes
|
yes
|
no
|
|
protected
|
no
|
yes
|
yes
|
no
|
|
friendly
|
yes
|
yes
|
yes
|
no
|
|
private
|
no
|
yes
|
yes
|
no
|
|
final
|
yes
|
yes
|
yes
|
no
|
|
abstract
|
yes
|
no
|
yes
|
no
|
|
static
|
no
|
yes
|
yes
|
yes
|
|
native
|
no
|
no
|
yes
|
no
|
|
transient
|
no
|
yes
|
no
|
no
|
|
synchronized
|
no
|
no
|
yes
|
no
|
Back to TOP
FLOW CONTROL
The Loop Constructs
Java provides three loop constructions. Taken from
C and C++, these are the while(), do() and for() constructs.
Each provide the facility for repeating execution of
a block of code until some condition occurs. The while()
loop is discussed first.
The while() Loop
The general form of the while() loop is:
while(boolean_condition) {
repeated_statement;
}
The element boolean_condition can be any expression
that returns a boolean result. The repeated_statement
will be executed again and again until the boolean_condition
becomes false. If the condition never becomes false,
then the loop will repeat forever. In practice this
means that the loop will repeat until the program is
stopped or the machine is turned off.
When there is only one statement following a while()
statement then it is not necessary to put the braces
(the curly brackets), but it is a good idea to put the
braces anyway because they make code more readable.
Observe that if the boolean_condition is already false
when the loop is first encountered then the loop will
never be executed. This is the main feature that
distinguishes it from the do() loop, discussed next.
The do() Loop
The general form of the do loop is:
do {
repeated_statement(s)
} while(boolean_condition);
Again, just like the while() loop, the execution terminates
when the boolean_condition becomes false. The significant
difference is that this loop always executes the body
of the loop at least one, since the test is performed
at the end of the body. The third type of loop, by far
the most common is the for() loop discussed next.
The for() loop
The general form of the for() loop is shown below.
for (init_statement ; boolean_condition ; iter_expression) {
statement_one;
statement_two;
}
The keys to this loop are in the three parts contained in the brackets
following the for keyword.
- The init_statement is executed immediately before the loop is itself
started. It is often used to set up starting conditions.
You will see shortly that it can also contain variable
declarations.
- The boolean_condition is treated exactly the same as in the while() and
do() loops. The body of the loop will be executed
repeatedly until the condition ceases to be true.
As in the while() loop, it is possible that the body
of the for() loop might never be executed. This occurs
when the boolean_condition is already false at the
start of the loop.
- The iter_expression (short for "iteration expression") is executed
immediately after the execution of the loop, just
before the test is performed again. Commonly this
is used to increment a loop counter.
In fact, because for() loops commonly need a counting variable, you are
allowed to declare variables in the init_statement part.
The scope of such a variable is restricted to the statement
or block following the for() statement. This results
in code which looks like this:
for(int x=0; x<10; x++) {
System.out.println("Value of x is:" + x);
}
It might be useful to look at the equivalent of this code implemented
using a while() loop.
{
int x=0;
while (x<10) {
System.out.println("Value of x is:" + x);
x++;
}
}
The for() loop and the Comma Separator
The init_statement and the iter_expression parts described previously can
contain a sequence of expressions rather than a single
one. The expressions should be separated by a comma,
instead of a semi-colon. This is illustrated below.
for ( j=3, k=6; j+k < 20; j++, k+=2) {
System.out.println("J is: " + j + "K is: " + k);
}
Note that while you can use the comma to separate several expressions,
you cannot mix expressions with variable declarations.
So the following would be illegal:
int i;
for (i=7, int j=0; i < 20; j++) { // illegal !
System.out.println("i is: " + i + "j is: " + j);
}
Back to TOP
The
break and continue Statements in Loops
Sometimes you need to abandon the execution of the
body of a loop - or perhaps a number of nested loops.
The Java development team recognized this as a legitimate
use for a goto statement. Java provides two statements,
break and continue, which can be used instead of goto
to achieve this effect.
Using continue
Suppose you have a loop that is processing an array of items that
contains two String references. The first String is
always non-null, but the second might not be present.
To shift the control in a loop, we can use continue,
like this:
for (int i=0; i<array.length; i++) {
//process first string
if (array[i].secondString == null) {
continue;
}
}
The real strength of continue is that it is able to skip out of multiple
levels of loops. Consider another example. Suppose our
example, instead of using two Strings, used two arrays
of char values. We need to nest these loops like this:
mainLoop: for (int i=0; i<array.length; i++) {
// process first array
for (int j=0; j<array[i].secondArray.length; j++) {
if (array[i].secondArray[j] == '\u0000') {
continue mainLoop;
}
}
}
Notice that a label has been applied to the opening for() loop.
Here, when the processing of the second array comes
across a zero value, it abandons the whole processing
not just for the second array, but for the current object
in the main array. This is equivalent to jumping to
the statement i++ in the first for() statement.
To achieve this without using continue is possible
if we set a flag in the inner loop and use that to abandon
the outer loop processing, which is rather messy.
Using break
The break statement, when applied to a loop, is somewhat similar to the continue
statement. However, instead of prematurely completing
the current iteration of a loop, break causes the entire
loop to be abandoned. Consider this example.
for (int j=0; j<array.length; j++ ) {
if (array[j] == null) {
break; // breaks out of inner loop
}
// process array[i]
}
In this case, instead of simply skipping processing for array[j] and
proceeding directly to processing array[j+1], this version
quits the entire inner loop as soon as the null element
is found.
You can also use labels on break statements, and as before, you must
place a matching label on one of the enclosing blocks.
Back to TOP
The Selection
Statements
Java provides a choice of two selection constructs.
These are the if()/else and switch() mechanisms. If
there are two sub paths an execution can travel, you
can use the if()/else statement which is controlled
by a boolean expression. If you want multiple execution
paths, then you can use the switch() construct.
The if()/else Construct
The most important thing to remember about an if()/else statement is that
it takes a boolean argument. For example:
if (x>5) {
System.out.println("x is more than 5");
}
else {
System.out.println("x is not more than 5");
}
The else part can be thought of as optional since an
if() statement can exist by itself. And you can also
use this in a nested fashion.
The switch() Construct
If you need to make a choice between multiple alternative execution
paths, and the choice can be based on an int value,
you can use the switch() construct. Consider this example:
switch (x) {
case 1:
System.out.println("Got a One");
break;
case 2:
System.out.println("Got a Two");
break;
case 3:
case 4:
System.out.println("Got a Four");
break;
default:
System.out.println("Something other than 1,2,3 or 4");
break;
}
There are a few important things to remember while using switch()
and they are:
- One of the important things to remember about switch() is that variable x must be either byte,
short, char or int . It cannot be anything else.
- Another is the arguments to the case labels must be constants, or at least constant expressions
that can be fully evaluated at compile time. You cannot use a variable
or an expression involving variables.
- Each case label takes only a single argument, but when execution jumps to one of these labels, it
continues downward until it reaches a break statement. If the break
statement is not there at the end of each block of statements it would
execute all the cases following the one that was matched. So it is
essential to place the break statements.
- The default statement can be
compared to the else part of an if()/else construction. Execution
jumps to the default statement if there are no matching cases in the
switch(). And although the default statement is shown at the end of
the switch construct there is no rule that requires this placement.
Default can be placed anywhere in a switch().
Back to TOP
Now on to the next chapter Your first Java Applet and Applications
All questions and comments can be addressed to the
author.
All material appearing within this website is copyright
protected and may not be reproduced elsewhere without
the express written permission of the author (Sanjeev
Dasgupta)
|