These notes discuss the basics of the Java programming language and its environment. No attempt is made to cover all of Java.
Java is designed for use in interactive computing, and it is relatively easy to write applications that respond to events, such as mouse clicks, caused by the computer user.
The original definition of Java, version 1.0, went through a major revision in 1997, primarily to provide support for improved event handling. Java 1.1 differs from Java 1.0 in ways that are quite significant for event-handling programs, but that are benign for the level that we will explore Java. You will need to be aware of the difference, however, if you attempt to compile a Java 1.1 program that does event-handling using a Java 1.0 compiler.
The Java library, a large part of which is concerned with graphics and event handling, was given a major overhaul with Java 1.1. In some cases, function names were changed to provide a more uniform naming convention.
Java 1.2 introduced new library classes, and Java 2 introduced even more, including entire collections of library packages. The language was not changed, only the library.
The designers of Java wanted to make it easy for programmers familiar with C to learn Java. Many parts of Java are borrowed directly from C. You must be careful, however. C is not a subset of Java, and Java is not a subset of C. The picture is more like this.
/\ /\ / \/ \ / /\ \ C/ / \ \Java \ \ / / \ \/ / \ /\ / \/ \/Here are some of the C features that are also part of Java. (In some cases, the syntax is not exactly like C, but very similar.)
boolean | |
byte | (8 bits, signed) |
char | (16 bit unicode) |
short | (16 bit signed integer) |
int | (32 bit signed integer) |
long | (64 bit signed integer) |
float | (32 bit floating point) |
double | (64 bit floating point) |
Note that the definition of Java says that a long integer MUST have 64 bits, regardless of the architecture of the machine on which it runs. The same kind of requirement holds for other types.
+ | addition or unary + |
- | subtraction or unary - |
* | multiplication |
/ | division |
% | mod |
<< | left shift (bring in 0 on right) |
>> | right shift (sign bit extension) |
>>> | right shift (bring in 0 on left) |
& | bitwise 'and' of binary integers or booleans |
| | bitwise 'or' of binary integers or booleans |
^ | bitwise exclusive-or of binary integers or booleans |
&& | short-circuit 'and'. |
|| | short-circuit 'or'. |
==, != | equality and inequality |
<, >, <=, >= | order tests |
Be careful with division. When you divide two integers, the result is rounded toward 0 to an integer. So expression 1/3 has value 0.
The precedence is as follows, from high to low. Operators on the same line have the same precedence.
++ -- unary operators * / % + - << >> >>> < <= > >= == != & ^ | && ||
You may write
int[] x;This says that x refers to an array of integers, but DOES NOT ACTUALLY CREATE THE ARRAY. To create the array, you would write something like
x = new int[n];as was done in the initialization. The size of an array can be given by any expression, not just a constant.
int f(int x, int y) { return x + y; }
You may declare local variables at any point within the body of a function.
To write a function that returns no value, declare the function to have return-type void, and do not include a return statement. (Or, say return;)
if(condition) { statements } else { statements }
The condition must have type boolean.
The else part can be omitted if nothing is to be done when the condition is false.
Expression a ? b : c is the C/Java conditional expression. It is equivalent to the Astarte expression If a then b else c %If
while(condition) { statements }
do { statements } while(condition)
for(s1,e2,s3) { statements }The for loop shown is equivalent to
s1; while(e2) { statements s3;}
class Name { ... things in the class }where Name is the name of the class. Typically, Name is capitalized. The word class is reserved, and all reserved words must be lower case.
Classes are used for packaging related data and functions, and for controlling access to global names. So a class is a kind of module, and we can discuss the module associated with a class.
Within a class, you can define variables and functions. The Java compiler makes two passes over the class definition, so that you can use a symbol before it is defined.
Each variable or function has an access mode that tells where it is visible. Modes are as follows.
private | Visible only within this class definition. |
public | Visible to every other class. |
protected | This mode is discussed below. It indicates visibility to this class and all that inherit from this class. |
(none) | Visible to every class defined in the same package. |
Variables and functions have two kinds, static (or class) variables and functions, and nonstatic (or instance) variables and functions. The nonstatic ones are discussed below. Static variables and functions are simply things that are associated with the module of the class.
For example, suppose that we want to implements the concept of
a stack. If we want to implement just one stack of integers,
then we might define a class Stack1 as follows.
class Stack1 {
private static int top = 0; // The number of things in the stack.
private static int[] mems = new int[100];
public static void push(int x)
{
mems[top++] = x; // ignores overflow possibility.
}
public static int pop()
{
return mems[--top]; // ignores underflow possibility.
}
}
Now there is one stack, and it is called Stack1.
Within the class, you can refer to the variables and functions
that are defined in the class by their names. Outside the class,
you must add the name of the class, with a dot. So to push n
onto the stack, and then pop it, you can write
Stack1.push(n);
int p = Stack1.pop();
class PairOfInts { public int first, second; }has an associated type, called PairOfInts. Each individual of this type has a representation that consists of two integers. Variables first and second are called instance variables, since they are associated with an instance of the type PairOfInts.
You can create a variable of type PairOfInts in the usual way.
PairOfInts p;
But be careful. In Java, all variables that refer to non-primitive
types such as PairOfInts are references. When variable p is created,
it refers to the null object, not to a pair of integers. To make it
refer to a new pair of integers, you must say
p = new PairOfInts();
Typically, you declare a variable and make it refer to a new
individual in a single line, such as
PairOfInts p = new PairOfInts();
You can refer to the parts of p using dot notation. p.first is the 'first' field of the pair referred to by p.
If two variables refer to the same object, then changing what one
refers to will change what the other refers to. For example, after
PairOfInts p = new PairOfInts();
PairOfInts q = p;
q.first = 1;
q.second = 2;
you will find that p.first is 1 and p.second is 2.
You can mix static and nonstatic definitions in a class definition. The static parts belong to the module, and the nonstatic parts belong to the type.
Every instance method has an implicit first parameter, which you can
refer to as 'this'. Within an instance method, you can refer to
the nonstatic variables of a class (as well as to the static variables).
You are implicitly referring to those fields in 'this'. For example,
here is a definition of stacks that, instead of creating a single,
fixed stack, creates an entire class of stacks.
class Stack2 {
private int top;
private int[] mems;
public void push(int x) {
mems[top++] = x; // ignores overflow possibility
}
public int pop() {
return mems[--top]; // ignores underflow possibility
}
public void init() {
top = 0;
mems = new int[100];
}
}
Remember that the implicit parameter is called 'this'.
The definition of function push might have been written
as follows, but the shorter definition above is preferred.
public void push(int x) {
this.mems[this.top++] = x; // ignores overflow possibility
}
Before using this kind of stack, you must create and initialize the stack.
Stack2 s = new Stack2();
s.init();
s.push(2);
s.push(3);
int n = s.pop();
int m = s.pop();
leaves stack s empty, with n = 3 and m = 2. You can create many stacks
this way. Each call to the push or pop function must have its implicit
first parameter given, using dot notation.
What you have seen now are the beginnings of object-oriented
programming in Java. Stack s is an object. It responds to
requests, and can change its internal state. When you write
s.push(3), you are asking s to change itself by pushing 3 onto its
internal stack. Note that you can refer to the same object by more
than one name. Writing
Stack2 s = new Stack2();
Stack2 r = new Stack2();
Stack2 t = r;
creates two stack objects, but allows you to refer to one of them
as either r or t. If you make a request to t, you are making a
request to the very same object as that referred to by r. But if
you write
t = s;
you do not change r. This just makes variable t refer to the
same stack as s, leaving r unchanged.
class Stack2 { private int top = 0; private int[] mems - new int[100]; ... }
The need to call a separate initializer is unpleasant. In fact, it is worse than unpleasant, it is dangerous. If the initializer is forgotten, then the stack will not work correctly.
Java provides a way around the initializer, by making it
possible to create an implicit initializer, called a
constructor. The constructor has the same name as the class,
and is defined without mentioning a result type. Here is a
new version of the stack class, with a constructor.
class Stack3 {
private int top;
private int[] mems;
public Stack3() {
top = 0;
mems = new int[100];
}
public void push(int x) {
mems[top++] = x; // ignores overflow possibility
}
public int pop() {
return mems[--top]; // ignores underflow possibility
}
}
Now, when you write
p = new Stack3();
the constructor is automatically run to initialize the stack.
You can have a constructor that has parameters, and you can have
more than one constructor.
For example, you might want the size of the stack to be a parameter.
class Stack4 {
private int top;
private int[] mems;
public Stack4(int size) {
top = 0;
mems = new int[size];
}
public Stack4() {
this(100);
}
public void push(int x) {
mems[top++] = x; // ignores overflow possibility
}
public int pop() {
return mems[--top]; // ignores underflow possibility
}
}
Notice that the constructor without parameters wants to run the constructor that has a parameter. It does so by calling the pseudo-function this.
Now
p = new Stack4(200);
q = new Stack4();
creates a stack p with 200 cells and another stack q with 100 cells.
The type of a class is always a member of the genus of the class.
When you define a class, you can indicate that its genus is a
subset of the genus of one other class, by using an 'extends' phrase.
For example, suppose that we want to create a new type of stack,
but one that is a special kind of Stack4. You can define
Class Stack4ext extends Stack4 {
...
}
We say that class Stack4ext is a subclass of class Stack4,
and class Stack4 is called the base class of Stack4ext.
You get two important things from this.
Instance variables are also inherited. Every instance of type Stack4ext implicitly has instance variables top and mems, which are defined in class Stack4. If you define any more instance variables in class Stack4ext, then an object of class Stack4ext has those variables as well as top and mems.
Note that possessing certain instance variables or methods is not
the same as making those variables or methods visible. For example,
consider the following definition of class Stack4ext, which provides
a new method isEmpty that tells whether the stack is empty.
Class Stack4ext extends Stack4 {
public boolean isEmpty()
{
return top == 0;
}
}
This will not work with the previous definition of class Stack4. The
problem is that, although objects of type Stack4ext have variables top
and mems, those variables were declared private in class Stack4, so
they are invisible in class Stack4ext. This situation can be remedied
by making variables mems and top 'protected'. This indicates that
they are visible inside class Stack4 and in any class that
extends class Stack4, but not to general classes. So we write
class Stack4 {
protected int top;
protected int[] mems;
public Stack4(int size) {
top = 0;
mems = new int[size];
}
public Stack4() {
this(100);
}
public void push(int x) {
mems[top++] = x; // ignores overflow possibility
}
public int pop() {
return mems[--top]; // ignores underflow possibility
}
}
class Stack4ext extends Stack4 {
public boolean isEmpty()
{
return top == 0;
}
}
This is almost ready to use. There is still one problem. When
a Stack4ext is created, we need to call the initializer for Stack4.
That has not been done in the preceding definition.
A modified definition of Stack4ext is as
follows. It uses the 'super' call to call the
constructor in its base class.
Class Stack4ext extends Stack4 {
public Stack4ext(int size) {
super(size);
}
public boolean isEmpty()
{
return top == 0;
}
}
Now you can do the following.
Stack4ext s = new Stack4ext(100);
int k;
s.push(2);
k = s.pop();
if(s.isEmpty()) {
...
}
You can use the pop method with an object of type Stack4ext because that method is inherited by Stack4ext from Stack4.
What would happen if you did the following? Notice that s is created
as an object of type Stack4ext, but is declared to be an object of
type Stack4.
Stack4 s = new Stack4ext(100);
int k;
s.push(2);
k = s.pop();
if(s.isEmpty()) {
...
}
The first line is fine, since s is a special kind of Stack4.
When you write
Stack4 s;
you do not mean that s must refer to something of type Stack4. Rather,
you mean that s can refer to any object that is of a type that
is in the genus Stack4. (We only have one name for the class, so
that name must be used to refer to the module, the type and the
genus.)
Expression s.isEmpty(), however, causes problems. Since s has been declared to refer to something in class Stack4, you cannot use operation isEmpty, which is only defined for class Stack4ext.
class C { ... String className() { return "C"; } ... }But in class D, which extends C, you would like to replace the definition of function className. You can do so, just by giving a new definition.
class D extends C { ... String className() { return "D"; } ... }
abstract class Expression { public abstract int evaluate(); }
Indicating that class Expression is abstract says that it is not
allowed to create an Expression that is ONLY an Expression. You must
create an object using a subclass of Expression. So
Expression e = new Expression();
is not allowed. Class Expression is abstract because some of its
functions are abstract, so there is no known way to compute them. To create
a kind of expression, you would extend class Expression. So an expression
that is just a number might go as follows.
class Number extends Expression {
private int val;
public Number(int v) {
val = v;
}
public int evaluate() {
return val;
}
}
and a class of expressions that are sums of other expressions
would look like this.
class Sum extends Expression {
private Expression e1, e1;
public Sum(Expression a, Expression b) {
e1 = a;
e2 = b;
}
public int evaluate() {
return e1.evaluate() + e2.evaluate();
}
}
Java provides something called an interface that is very similar
to an abstract class. An interface is not allowed to have instance
variables. It can only have functions. All of the functions of
an interface are implicitly abstract, even if you don't say so.
For example, the standard Java interface Runnable is defined as follows.
interface Runnable {
public void run();
}
You can use an interface as if it is an abstract class. For example,
you can create a variable that is a reference to a Runnable object,
as in
Runnable r;
You cannot, of course, use expression new Runnable(), since Runnable is abstract.
You can indicate that a class is an implementation (similar to a subclass)
of an interface in the heading of the class definition. For example,
if class Rabbit is to be an implementation of Runnable, and also extends
class Mammal, you write
class Rabbit extends Mammal implements Runnable {
...
public void run() {
...
}
}
Because class Rabbit implements Runnable, it takes on the responsibility
of implementing abstract method run. Having created class Rabbit, you
can write
Runnable r = new Rabbit();
since any instance of class Rabbit is also an instance of
Runnable.
A class can implement any number of interfaces.
class Example { public static final double pi = 3.1415926536; public static final double e = 2.7182818285; ... }
class MyApplet extends java.applet.AppletYou can avoid the need to write these long names by using an import. Write
import java.applet.Appletto allow using the short name Applet for jaba.applet.Applet, or write
import java.applet.*to allow short names for everything in package java.applet.
Be careful about imports. You can only import a package, not a class.
For example, there is a standard Java class called Math. It contains
a collection of useful functions, including function abs that computes
the absolute value of a number. To set y to the absolute value of x, you
must write
y = Math.abs(x);
You might be tempted to shorten this by writing
import Math;
but that is not allowed, since math is a class, not a package.
To compile a Java program, use the Java compiler, javac. On stymie, it is in /export/stu/4510/java/bin. For example, type
javac myprog.java
When you compile a Java package, you get "compiled" versions
that are in a special byte-code for the Java interpreter.
You get a file for each class, with extension .class.
To run a Java program, you use the Java interpreter, called java,
on the main class. It must have a method called main, with
the following heading.
public static void main(String[] args)
See file RobotSim.java for an example of an applet. File Robotsim.html runs that applet. It is a fairly crude simulation, and has not been provided with a way to start and stop it. Just back out of the page to stop it.