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.

Every piece of Java code you will write will be contained in a class like this.

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.
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 GaussianIntegers 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

In the file GaussianAddition.java

    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);
        }
    }

and in GaussianInteger.java

    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 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 lie 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, the visibility of fields and methods only becomes important when you are managing a large amount of code or sharing it with others. In this case, you will want to use the smallest practical visibility. 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

  1. Compile and run the program WelcomeToJava on your system.

  2. What happens if you give an argument to our Squaring program that cannot be parsed to an int? For example,

        $ java Squaring abcdefg 
    

  3. Modify the Squaring program to allow for inputs that are doubles; that is, your program should respond to

        $ java Squaring 10.1
    

    with something like

        10.1 squared is 102.00999999999999
    

    To parse the argument into a double, use Double.parseDouble(arguments[0])}.

  4. Write a program to approximate e2 by n terms of the Taylor series:

  5. Create a program that prints out all the prime numbers smaller than n using the sieve of Erastothenes. Begin by creating an array of n boolean values and initializing them to be true. Then set the values indexed by multiplies of primes to false.

  6. Remember that the Taylor series

    is valid for |x|<1. Moreover, for a given value of x, this gives an alternating series. Create a method

        public static double arctan(double x, double tolerance)
    

    that uses the Taylor series to compute arctan x with an error less than tolerance. Also, remembering that

    create a program using your method to compute with an error less than 10-n where n is input into the program. If n=20, does your program give the correct result? Explain why or why not.

  7. Add class methods

        public static GaussianInteger add(GaussianInteger a, GaussianInteger b)
        public static GaussianInteger subtract(GaussianInteger a, GaussianInteger b)
    

  8. Add an instance method

        public double getModulus()
    

    that returns the modulus |a| of a Gaussian integer a. You will need to use the method Math.sqrt(double x) to compute the square root. (You should be able to understand this syntax now: it refers to a static method called sqrt in a class Math.)

  9. There is a Division Algorithm for the Gaussian integers: that is, given Gaussian integers a and b where , there are (not necessarily unique) Gaussian integers q and r such that

    In fact, to find q you may view b as a complex number and find the closest Gaussian integer to the complex number a/b. Add an instance method

        public static GaussianInteger[] dividedBy(GaussianInteger b)
    
    that returns an array with two GaussianIntegers, q and r, that result from dividing the Gaussian integer represented by the instance by b. You will want to use the expression (int) Math.round(double x) to find the closest integer to a real number.

  10. Implement the Euclidean Algorithm to find the greatest common divisor of two Gaussian integers by creating a class method

        public static GaussianInteger gcd(GaussianInteger a, GaussianInteger b)
    

  11. Remember that a Gaussian integer p is prime if it is not a unit and if p=ab then one of a and b is a unit. Write an instance method

        public boolean isPrime()
    
    that determines whether a GaussianInteger represents a prime. You may find it convenient to add a class field

        public static final GaussianInteger[] UNITS
    
    where the array contains the four units . Modify GaussianArithmetic to test the methods you have constructed.

  12. Design a class called Rational that represents a rational number and provides methods for doing arithmetic with rationals, determining when one Rational is equal to another and comparing whether one Rational is larger than another. Use care to deal properly with division by 0.

  13. Design a class that represents polynomials in one variable with rational coefficients. Include an instance method that reports the degree of the polynomial and class methods to implement the Division Algorithm and the Euclidean Algorithm.

  14. Write a method that converts a String into a new one with the first character upper case and each remaining character lower case.

  15. Read about the char primitive data type. Write a method that implements the Cesear cipher in which the letters of the alphabet are cyclically shifted by three characters.

  16. A Java variable name is valid if it begins with a letter or underscore and is followed by any number of letters, digits or underscores. Write a method that determines whether a String satisfies this condition.

  17. Explain why there are only class methods in the class {\tt Math}.

  18. Explain the syntax of the instruction

        System.out.println("Hello");
    

  19. What is the result of the following code:

        GaussianInteger x = new GaussianInteger(2, 1);
        GaussianInteger y = new GaussianInteger(2, 1);
        if (x == y) System.out.println(``x equals y'');
        else System.out.println(``x does not equal y'');
    

  20. A stack is another kind of data structure, similar to a queue, in which objects are added to and removed from the same side of the list. Write a new class MyStack that extends Vector to implement this behavior. You might note that there is a class Stack already provided in java.util

  21. Explain the meaning of every word of the class WelcomeToJava that we met at the beginning of this lecture.