Introduction to Java


An example applet
What is Java?
How to construct and run a Java program
Anatomy of a simple program
Basic Syntax
Object-oriented programming
Java's pre-defined classes
Inheritance
Values versus references
Visibility
Exercises

Based on notes Introduction to Java and Object-oriented programming

What is Java?

A high-level programming language that offers, among other features,

  • sophisticated graphics,
  • easily constructed graphical user interfaces, and
  • easy delivery to a wide audience.

How to construct and run a Java program

Once Java is installed, the way a Java program is run varies from one operating system to the next. You can find details on running Java under Windows, Linux and MacOS.

However, the basic steps will be the same.

1. Use a text editor to write the program in a file called, say, WelcomeToJava.java.

$ ls
WelcomeToJava.java

2. Compile the program. This produces a file with a .class extension containing Java byte code.

$ javac WelcomeToJava.java
$ ls
WelcomeToJava.class  WelcomeToJava.java

3. Execute the program. This causes the Java virtual machine to execute the Java byte code.

$ java WelcomeToJava
Welcome to Java




Anatomy of a simple program

Here is what the file WelcomeToJava.java looks like.

    public class WelcomeToJava {
        public static void main(String[] args) {
            System.out.println("Welcome to Java");
        }
    }
Note: The Java compiler does not worry about the spacing of the code. However, the way in which lines are broken and indented help a human read the code more easily. It is wise to follow this or some other style of writing code. Many text editors will help you do it.
The definition of the public class WelcomeToJava must be in a file named WelcomeToJava.java.

The class declaration

    public class WelcomeToJava {
        public static void main(String[] args) {
            System.out.println("Welcome to Java");
        }
    }
Note: Curly brackets { and } delimit blocks of code.
The declaration public class WelcomeToJava says that the following block of code contains the definition of the class WelcomeToJava.

What's contained in the class WelcomeToJava?

    public static void main(String[] args) {
        System.out.println("Welcome to Java");
    }

One method whose name is main. In other programming languages, methods are sometimes known as procedures or subroutines.

The information contained in the declaration public static void main(String[] args) is called the signature of the method. The block of code which follows is the body of the method.

When we run the program WelcomeToJava, the Java virtual machine looks inside the class file WelcomeToJava.class for a method named main with this signature and executes it.

What's contained in the main method?

    System.out.println("Welcome to Java");

A single instruction that causes the phrase "Welcome To Java" to be printed.


Basic syntax

Using variables

    public class Squaring {
        public static void main(String[] arguments) {
            int n;
	    n = 10;
            int n2 = n * n;
            System.out.println(n + " squared is " + n2);
        }
    }

Note: The type of every variable must be declared. The instruction int n; declares that n is of type int, a 32-bit signed integer.
The variable n is then initialized with n = 10;
Declaration and initialization can be combined in a single instruction: int n2 = n*n;

Using variables

Some common data types are

  • boolean, whose value is either true or false,

  • int, a 32-bit signed integer,

  • float, a 32-bit floating point number, and

  • double, a 64-bit floating point number.
A variable is accessible within the smallest block of code containing its declaration.

Controlling the flow of execution

    public class Factorial {
        public static void main(String[] arguments) {
            int n = Integer.parseInt(arguments[0]);
	    if (n < 0) {
    	        System.out.println("Please enter a non-negative integer");
	        return;
	    }
   	    int factorial = 1;
	    for (int i = n; i >= 1; i = i - 1) {
	        factorial = factorial * i;
	    }
	    System.out.println(n + "! = " + factorial);
	}
    }

Note: The boolean expression n < 0 evaluates to either true or false.
The block of code following the conditional is executed if the value of the boolean expression is true.
This block of code may be followed with else { ... } .
The declaration of the for loop contains three statements: the first is executed at the beginning of the first loop, the second is a boolean expression to determine whether the loop should be executed and the third is executed at the end of each loop.

Arrays

    public class Fibonacci {
        public static void main(String[] args) {
	    int N = 10;
   	    int[] f;
	    f = new int[N];
 	    f[0] = 0;  f[1] = 1;
	    for (int i = 2; i < N; i++) {
	        f[i] = f[i-1] + f[i-2];
	    }
	    for (int i = 0; i < N; i++) System.out.println(f[i]);
	}
    }

Note: Arrays are declared with by adding square brackets [] to the data type.
To initialize the array, use the new keyword and specify the number of elements in the array.
Elements in the array are indexed by consecutive integers beginning at 0.

Methods

    public class PascalTriangle {
        public static void main(String[] args) {
	    int n = Integer.parseInt(args[0]);
	    for (int c = 0; c <= n; c++) {
	        int bc = factorial(n)/factorial(c)/factorial(n-c);
	        System.out.print(bc + " ");
    	    }
	    System.out.println();
	}

        public static int factorial(int k) {
   	    int factorial = 1;
	    for (int i = k; i >= 1; i--) {
	        factorial = factorial * i;
	    }
	    return factorial;
	}
    }

Note: A method called factorial is defined with signature int factorial(int k) .
The first occurrence of int in the signature declares that this method will return an int. type
Next comes the name of the method factorial.
(int k) declares this method to have one argument of type int.
We can use this method by saying, for instance, int fact = factorial(10);. This causes the block of code defined by factorial to be executed with 10 replacing k.


Object-oriented programming

The central feature of Java is that it is an object-oriented language. Simply said, this gives us the ability to define our own data types.

What is an object?

An object is simply a structure that contains (a) a collection of data and (b) some methods that change or reflect the data.

Suppose we wish to work with Gaussian integers; that is, complex numbers of the form

a + bi where a and b are integers.

Just as we have the data type int, it would be convenient to have a data type GaussianInteger. We will see how to construct such a data type and how to use it.

Data and methods of GaussianInteger

A Gaussian integer is defined by its real and imaginary parts, both of which are integers. Therefore, a GaussianInteger object will have two pieces of data: int real, imag;

For our purposes here, we will be interested in adding Gaussian integers; therefore, a GaussianInteger object will have a method by which we may add another GaussianInteger to it resulting in a third GaussianInteger containing the sum.

How can we use GaussianInteger?

We will see how to define GaussianInteger's in a moment. First, however, let's see how we can use them by constructing a program that adds the Gaussian integers x = 3-2i and y = 2 + i.

Let's first consider how it would look if we added two integers.

public class Addition {
    public static void main(String[] args) {
	int x = 3;
	int y = 2;
	int sum = x + y;
	System.out.println(sum);
    }
}
The variables x and y are first declared and initialized, their sum is computed and then printed.

And now with GaussianInteger

public class GaussianAddition {
    public static void main(String[] args) {
	GaussianInteger x = new GaussianInteger(3, -2);
	GaussianInteger y = new GaussianInteger(2, 1);
	GaussianInteger sum = x.add(y);
	System.out.println(sum);
    }
}

Note: Variables x and y are declared to be of the data type GaussianInteger.
They are initialized in a slightly different way using the new keyword.
The sum is computed using the add method of x.
The sum is then printed.

Defining the data type GaussianInteger

public class GaussianInteger {
    int real, imag;
    public GaussianInteger(int a, int b) {
	real = a;  imag = b;
    }
    public GaussianInteger add(GaussianInteger a) {
	return new GaussianInteger(real + a.real, imag + a.imag);
    }
    public String toString() {
	return real + " + " + imag + " i";
    }
}

You should think of this class file as defining the template for a GaussianInteger.

Instantiation

We create a specific GaussianInteger through the process of instantiation, initiated by the command

GaussianInteger x = new GaussianInteger(3, -2);

Several things happen:

  • Some space is set aside in memory for the GaussianInteger. In this space are locations for the data (called instance fields) and methods (called instance methods) specified by the template. We call this an instance of a GaussianInteger object.

  • The method
        public GaussianInteger(int a, int b) {
    	real = a;  imag = b;
        }
    
    is executed. This has the effect of initializing the data. This method is called a constructor.

  • The variable x contains the memory address of the object.

The result

Here is what is in the memory location addressed by x.

Accessing an object's data and methods

Now that we have the object x, we will want to access its data and its methods.

  • We can retrieve the value of the real field of x using the syntax x.real.

  • The add method of x is accessed in a similar way: x.add(y).

Another look

public class GaussianAddition {
    public static void main(String[] args) {
	GaussianInteger x = new GaussianInteger(3, -2);
	GaussianInteger y = new GaussianInteger(2, 1);
	GaussianInteger sum = x.add(y);
	System.out.println(sum);
    }
}

public class GaussianInteger {
    int real, imag;
    public GaussianInteger(int a, int b) {
	real = a;  imag = b;
    }
    public GaussianInteger add(GaussianInteger a) {
	return new GaussianInteger(real + a.real, imag + a.imag);
    }
    public String toString() {
	return real + " + " + imag + " i";
    }
}

Advantages

  • GaussianInteger may be easily reused in another program.

  • We can work with a GaussianInteger as we think about it: as an indecomposible entity.

  • The details of how GaussianInteger work are hidden away.

Class fields and methods

The GaussianInteger objects we have been working with represent specific elements, say x = 3-2i, in the larger set of all Gaussian integers. Generally speaking, objects should be thought of as a specific element in a larger set.

Oftentimes, there are fields and methods that belong, not to a specific element, but to the entire set. For instance, the additive identity is a special element in the set of Gaussian integers. In the same way, multiplication may be thought of as a method belonging to the entire set.

The static keyword allows us to define fields and methods belonging to the set rather than a specific instance. We call such fields and methods class fields and class methods.

Our next version of GaussianInteger

public class GaussianInteger {
    int real, imag;
    public GaussianInteger(int a, int b) {
	real = a;  imag = b;
    }
    public GaussianInteger add(GaussianInteger a) {
	return new GaussianInteger(real + a.real, imag + a.imag);
    }
    public String toString() {
	return real + " + " + imag + " i";
    }

    static final GaussianInteger ZERO = new GaussianInteger(0, 0);

    public static GaussianInteger multiply(GaussianInteger a, 
					   GaussianInteger b) {
	return new GaussianInteger(a.real*b.real - a.imag*b.imag,
				   a.real*b.imag + a.imag*b.real);
    }
}

The keyword final indicates that the field ZERO may not be reassigned.

How can we use class fields and methods?

We can access class field ZERO using the syntax GaussianInteger.ZERO.

Likewise, the class method multiply may be accessed using GaussianInteger.multiply(x, y).

There is no real reason to make add an instance method and multiply a class method. The example, as presented, is meant merely to illustrate the principle of class methods. It is possible to add another instance method, also called multiply, defined like this:

    public GaussianInteger multiply(GaussianInteger b) {
	return GaussianInteger.multiply(this, b);
    }

(The keyword this is how an object may refer to itself.)

In fact, different methods may share the same name as long as their argument lists are different. Likewise, we may have multiple constructors as long as their argument lists are different.


Java's pre-defined classes

We have now constructed a special class GaussianInteger to help us work with Gaussian integers.

Java 1.4 provides us with 2991 (!) classes. How can we learn about these?

  • Classes that work together for some common aim are grouped into 135 packages. A few packages are
    • java.lang, core classes in Java,
    • java.io, classes that handle input/output,
    • java.awt, classes that construct graphical interfaces and images (awt stands for Abstract Windowing Toolkit ), and
    • java.util, some basic utility classes.

  • In fact, some packages are sub-packages of others. For instance, java.awt.geom is a sub-package of java.awt that provides some classes lending geometrical assistance for the construction of images.

  • All classes are well documented in the Applications Programming Interface.

The import statement

If we use one of Java's pre-defined classes, the compiler needs to know which package contains the class. Unless otherwise directed, Java will only look through the package java.lang.

Suppose we want to use the class Vector in the package java.util.

We can do one of two things:

  • Declare the instance of Vector like this
     
    java.util.Vector vector = new java.util.Vector();
    
    or
  • include an import statement before the class declaration like this:
    import java.util.Vector;
    
    or
    import java.util.*;
    
    and declare the instance using
     
    Vector vector = new Vector();
    

Inheritance

It often happens that we have a class that we would like to modify or add some new functionality to. Java provides a convenient way to do this through inheritance.

For example, a typical data structure is a queue, a list of data into which new entries are added on one side and removed from the other.

An array is not useful here since the number of entries in the queue may grow arbitrarily large. However, a Vector allows us to maintain an array of objects of no fixed size. In other words, we would like our queue to have the features of a Vector, but also include some methods to add elements on one side and remove them on the other.

The Queue class

import java.util.Vector;

public class Queue extends Vector {
    public void insertElement(Object obj) {
	insertElementAt(obj, 0);
    }

    public boolean hasMoreElements() {
	return size() > 0;
    }

    public Object removeElement() {
	Object object = elementAt(size() - 1);
	removeElementAt(size() - 1);
	return object;
    }
}

The keyword extends is important here: it says that Queue inherits all of the fields and methods from Vector. To provide the functionality of a queue, we add two instance methods: insertElement and removeElement.

Using a Queue

public class QueueOfGaussianIntegers {
    public static void main(String[] args) {
	Queue queue = new Queue();
	queue.insertElement(new GaussianInteger(3, 2));
	queue.insertElement(new GaussianInteger(2, 0));

	GaussianInteger top = (GaussianInteger) queue.removeElement();
	System.out.println(top);

	queue.insertElement(new GaussianInteger(10, -1));

	top = (GaussianInteger) queue.removeElement();
	System.out.println(top);
	top = (GaussianInteger) queue.removeElement();
	System.out.println(top);
    }
}	

The result is

3 + 2 i
2 + 0 i
10 + -1 i

The hierarchy tree

If B extends A, we call B a subclass of A and A the superclass of B.

  • A class may only extend one class.

  • Every class implicitly extends java.lang.Object.

  • This means that the set of all classes forms a tree with Object at the root.

  • A class may redefine a method in a superclass to modify the behavior of that method. This is called method overriding. For instance, Object defines a method with signature
    public String toString()
    
    This method is called when an Object is printed. We overrode this method in GaussianInteger to print something of the form a+bi.

  • If a class overrides a method in a superclass, it may access the superclass method using the super keyword. For instance, if a GaussianInteger wanted to call the toString() of Object rather than its own toString() method, we could say super.toString().

    Likewise, we can call a constructor that has been overridden using super( ... ). For technical reasons, however, this must be the first instruction in the constructor.


Values versus references

The data types we saw at the beginning--int, float, double and boolean--are called primitive data types to distinguish them from objects. The behavior of the two classes of data types can be quite different.

If we say int n = 2, then the value of n is set to 2.

However, if we say GaussianInteger x = new GaussianInteger(2, -1), then the value of x is the memory address of the GaussianInteger object instantiated.

Compare the behavior of the following fragments of code:

int m = -3;
int n = 2;
n = m;
m = 4;
System.out.println(n + " " + m);

GaussianInteger x = new GaussianInteger(2, -1);
GaussianInteger y = new GaussianInteger(3, 2);
y = x;
x.real = 2;
System.out.println(x + " " + y);

Visibility

Sometimes we would like to hide some of the fields and methods in a class. For instance, we could add a field modulus to our GaussianInteger class by modifying the constructor.

    ...
    double modulus;
    public GaussianInteger(int a, int b) {
	real = a;  imag = b;
	modulus = Math.sqrt(real*real + imag*imag);
    }
    ...

A problem arises if we say something like this:

    ...
    GaussianInteger x = new GaussianInteger(2, 1);
    x.modulus = 10;
    ...

Visibility modifiers

To remedy this, we may add the modifier private in the declaration of modulus:

    private double modulus;

and add a method to allow others to access the modulus field:

    public double getModulus() {
	return modulus;
    }

The private modifier means that modulus may only be accessed from within its own instance. This is called a visibility modifier.

By contrast, the public modifier would allow any class to access a field or method.

There are also modifiers package and protected that lies somewhere between these two extremes.

If a field or method does not have a visibility modifier, it is set to package by default. This means that the field or method is visible only within the package containing the class.

What is now wrong with our GaussianInteger class?

The public keyword

  • Declaring fields to be public can be dangerous. In general, it is best to provide access to a field only through a pair of get/set methods. For instance, if we want to allow a programmer to modify the real and imaginary parts of a GaussianInteger, we can declare these fields to be private and define methods

    public void setReal(int r) {  real = r; }
    public int getReal() { return real; }
    public void setImag(int i) {  imag = i; }
    public int getImag() { return imag;}
    
    You will see this construction repeatedly throughout the API.

  • Loosely speaking, visibility of methods only becomes important when you are managing a large amount of code or sharing it with others. When you are beginning, it is usually safe to declare most methods to be public.

  • Several classes may be defined in a given .java file. However, only one of them may have the public modifier and this class must have the same name as the .java file.

Exercises