Java SE 8 Documentation
Exercises

Recap from the Previous Lab

$ javac HelloWorld.java
$ java HelloWorld
Hello World!

Defining and Instantiating Classes

As we have seen previously, types in Java can be split into two main groups: primitive and object types. The former ones are technically defined by the language itself and the compiler has built-in support for them (for example int that represents integers), and the latter ones are object classes that are defined in the standard libraries. New elements to the set of object classes can be (and should be) added by the programmer through class definitions.

For example, let us assume that we want to represent two-dimensional points and their associated operations from mathematics.

// A class that represents two-dimensional points.
class Point2D {
    // Coordinates as fields.
    double x;
    double y;

    // Translate a point by a vector.
    void translate(double dx, double dy) {
        x += dx;
        y += dy;
    }

    // Scale a point to the origin by a scalar value.
    void scale(double c) {
        x *= c;
        y *= c;
    }

    // Rotate a point by an angle.
        void rotate(double phi) {
        double nx = x * Math.cos(phi) - y * Math.sin(phi);
        double ny = x * Math.sin(phi) + y * Math.cos(phi);
        x = nx;
        y = ny;
    }

    // Obtain the textual representation of a point.
    String show() {
        return String.format("{ x = %.3f, y = %.3f }", x, y);
    }

    // Distance of two points.
    static double distance(Point2D p0, Point2D p1) {
        double xd = p0.x - p1.x;
        double yd = p0.y - p1.y;
        return (Math.sqrt((xd * xd) + (yd * yd)));
    }

    // Mirror a point to a another one.
    static Point2D mirror(Point2D c, Point2D p) {
        Point2D result = new Point2D();
        result.x = (2 * c.x) - p.x;
        result.y = (2 * c.y) - p.y;
        return result;
    }
}

// Utility functions for working with angles.
class Angle {
    static double degreeToRadian(double x) {
        return x * Math.PI / 180;
    }

    static double radianToDegree(double x) {
        return x * 180 / Math.PI;
    }
}

// "Main program"
public class Point2DDemo {
    public static void main(String[] args) {
        Point2D p0 = new Point2D();
        p0.x = Math.E;
        p0.y = Math.PI;
        p0.translate(0.1, 12.34);
        p0.scale(0.78);
        p0.rotate(Angle.degreeToRadian(90));
        System.out.println("p0 = " + p0.show());

        Point2D p1 = new Point2D();
        p1.x = 2 * Math.PI;
        p1.y = Math.E / 2;
        p1.scale(1.445);
        p1.rotate(Angle.degreeToRadian(-33));
        System.out.println("p1 = " + p1.show());

        System.out.println("distance(p0, p1) = " + Point2D.distance(p0, p1));

        Point2D p2 = Point2D.mirror(p1, p0);
        System.out.println("mirror(p1, p0) = " + p2.show());
    }
}

Note that it is sufficient to compile the main class only, which is Point2DDemo. That is because the compiler tries to compile all the other referenced classes as part of this process.

Functions (methods)

The source code example above also shows that operations in classes are given by (member) functions, or as they are called in Java, methods. Their format in general is as follows.

<modifiers> <return type> <name>( <list of parameters> )
        [ throws <list of thrown exceptions> ] {
    <statement1>;
    <statement2>;
    ...
}

Remarks:

  • Modifiers are to change the behavior of the method. There are many different modifiers, we will go through them during the semester. In the example, only static was used that tells that the given method is for the whole class. Such methods do not require any object in order to be called. Basically, they act like regular function definitions.

  • The return type may be either an arbitrary (though existing) type or void. When void is used, that is a procedure without a return value.

  • The name of the method is the name of operation, it is commonly written in lowerCamelCase.

  • List of parameters: A list that contains all the formal parameters to the method, with their types and names. Elements of this list are always passed by their values, through the stack.

  • List of thrown exceptions: All the checked exceptions that could be raised by the method, to be discussed later.

Packages

A package is a collection of classes and interfaces. It is recommended to use packages in the following cases:

  • Modularize the program.
  • Resolve name conflicts with introducing name spaces.
  • Restrict access to certain classes.

Packages must have names, they are often composed of a sequence of identifiers, separated by dots. It is a generic naming convention that package names are unique at the top (world-wide) level, so their structure resembles to domain names on the Internet, such as org.ietf.jgss.

The standard packages in Java usually have the java. prefix, and during the lab sessions, we are also going to work with a simplified package naming scheme.

Importing Packages

All the fundamental classes and interfaces in the Java language (for example Object, String, and so on) are contained by the java.lang package, whose elements are automatically available anywhere in the sources. Contents of any other package can be made accessed by calling them by their fully-qualified name. For instance, java.util.Scanner refers to the Scanner class in the java.util package.

public class Simple {
    public static void main(String args[]) {
        Scanner sc = new Scanner(System.in);
        System.out.print("? ");
        if (sc.hasNextInt()) {
            int x = sc.nextInt();
            System.out.println("x = " + x);
        }
        else System.out.println("No valid input.");
    }
}

If we do not want to use those fully-qualified names every time when a class from some external package is named, import statements shall be added to the beginning (prologue) of the source code. The import section instructs the compiler to import names from other packages to the name space of the package to be compiled.

import java.util.Scanner;   // Importing a specific class.
import java.math.*;         // Importing everything from a package.

The previous program with the import lines included:

import java.util.Scanner;

public class Simple {
    public static void main(String args[]) {
        Scanner sc = new Scanner(System.in);
        int x = sc.nextInt();
        System.out.println("x = " + x);
    }
}

Useful packages from Java standard libraries:

  • java.awt: Graphical user interfaces.
  • java.awt.event: Event handling for graphical user interfaces.
  • javax.swing: Another implementation of graphical user interfaces.
  • java.util: Data structures and other common utilities.
  • java.util.regex: Regular expressions.
  • java.io: Handling input/output.

Creating Packages

Of course, our own source may be structured through packages. In order to so, the following steps have to be made:

  • The identifier of the package must be placed in the sources. This can be done by using the package keyword, followed by the identifier. The identifier must be in the previously-mentioned fully-qualified format.

  • The sources must be organized into a directory structure that corresponds to the fully-qualified name of the package, and then both the compilation and running must be done at the package root. This will not work otherwise.

For illustration, let us partition our previous program on two-dimensional points into multiple packages. Move the Point2D class to the geo.basics package, move the Angle class to the geo.utils package, and finally, move the Point2DDemo class to the main package.

First we shall have to build up the corresponding directory layout, and place the source code files at the different parts of it:

$ md geo
$ md geo\basics
$ md geo\utils
$ md main
$ mv Angle.java geo\utils
$ mv Point2D.java geo\basics
$ mv Point2DDemo.java main

Or more visually:

 package root
   |
   |- geo
   |   |
   |   |- basics
   |   |   Point2D.java
   |   |
   |   '- utils
   |       Angle.java
   |
   '- main
       Point2DDemo.java

Once this is done, every source file must be tagged with the respective package identifier. For example, the following line must be added to the beginning of the Point2D.java file:

package geo.basics;

For Angle.java:

package geo.utils;

For Point2DDemo.java:

package main;

import geo.basics.Point2D;
import geo.utils.Angle;

In addition to that, all classes and their members (fields and methods) must be declared public with the public modifier. This makes it possible to access them from other packages. By default, they are only visible inside the package where they were defined.

Compiling and Running

The compiler does not support recursive compilation of the source files, but there can be a file name prefixed with @ passed to it that has all the files to be compiled.

$ dir /s /B *.java > sources.txt
$ javac @sources.txt

Applications with packages can be run from package root by giving the fully-qualified name of the main class (Point2DDemo).

$ java main.Point2DDemo
...

Debugging Programs

Sometimes we need to find mistakes in programs, or we need to understand how they work exactly. This process is called debugging or tracing. In order to be able to debug programs, the sources must be compiled with the -g flag of javac. This will instruct the compiler to add all the necessary meta information to the generated byte code.

$ javac -g Point2D.java
$ javac -g Angle.java
$ javac -g Point2DDemo.java

After the sources have been successfully compiled this way, the jdb (as in "Java DeBugger") utility may be used to work with the embedded debugging information. First, launch it from the command prompt, which is going to give us an interactive interface.

$ jdb
Initializing jdb ...
>

In the debugger, the available commands can be listed by the help command. Here, only the must important ones will be discussed.

> help
** command list **
[..]
run [class [args]]        -- start execution of application's main class
[..]
print <expr>              -- print value of expression
[..]
eval <expr>               -- evaluate expression (same as print)
set <lvalue> = <expr>     -- assign new value to field/variable/array element
locals                    -- print all local variables in current stack frame
[..]
stop at <class id>:<line> -- set a breakpoint at a line
[..]
step                      -- execute current line
[..]
cont                      -- continue execution from breakpoint
[..]
exit (or quit)            -- exit debugger
[..]

The first, and probably the most important command is stop at, which may be used to stop program execution at a given point. With this command, so-called breakpoints can be placed in the sources. When the program hits any of those breakpoints during execution (inside the debugger), it will stop and returns to the interactive interface.

> stop at Point2DDemo:3
Deferring breakpoint Point2DDemo:3.
It will be set after the class is loaded.

Breakpoints may be set without loading any class into the memory as they will be activated only once the execution reaches the respective part of the program. The run command may be used for launching the main() method of the loaded class.

> run Point2DDemo hello
run Point2DDemo hello
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint Point2DDemo:3

Breakpoint hit: "thread=main", Point2DDemo.main(), line=3 bci=0
3            Point2D p0 = new Point2D();

On hitting the breakpoint, the debugger will show the affected line, together with its number. In the meantime, the execution of the program is suspended. This gives us the opportunity to inspect the values for all the variables that are visible in the current scope. One can also evaluate expressions composed of those values.

The locals command lists the visible variables.

main[1] locals
Method arguments:
args = instance of java.lang.String[1] (id=386)
Local variables:

As we can see here, only the args variable can be accessed at this point. This variable has the command-line parameters for the program. The contents of args can be displayed with the print command.

main[1] print args
 args = instance of java.lang.String[1] (id=386)
main[1] print args.length
 args.length = 1
main[1] print args[0]
 args[0] = "hello"

The p0 variable in the source code will be allocated only if the corresponding statement was executed. The step commands lets us to execute the subsequent statements step by step, one at a time.

main[1] step
> 
Step completed: "thread=main", Point2D.<init>(), line=1 bci=0
1    class Point2D {

Then the control is passed to the initialization routine of the Point2D class, because that has be done on every instantiation. Step the execution until the value of the p0 variable is eventually created.

main[1] step
> 
Step completed: "thread=main", Point2DDemo.main(), line=3 bci=7
3            Point2D p0 = new Point2D();

main[1] step
> 
Step completed: "thread=main", Point2DDemo.main(), line=4 bci=8
4            p0.x = Math.E;

The value of p0 can be now inspected:

main[1] print p0
 p0 = "Point2D@faa824"

Or as we have already shown inside the program:

main[1] print p0.show()
 p0.show() = "{ x = 0.000, y = 0.000 }"

Variables in the program may be changed if needed. This is useful when some bad value has to be corrected or we want to experiment with something else.

main[1] print p0.x
 p0.x = 0.0
main[1] set p0.x = 1.0
 p0.x = 1.0 = 1.0
main[1] print p0.x
 p0.x = 1.0

If we want to continue with the program execution in the usual pace, use the cont command. In this way, the program will normally run until it hits a breakpoint or it terminates.

main[1] cont
> p0 = { x = -12.076, y = 2.198 }
p1 = { x = 8.684, y = -3.298 }
distance(p0, p1) = 21.474953930229827
mirror(p1, p0) = { x = 29.444, y = -8.794 }

The application exited

Exercises

  • Create the Gender class for representing genders. Add two class-level constants, with the names Gender.MALE (for males) and Gender.FEMALE (for females) as integer.

  • Create the Person class that is capable of holding information on persons, such as the following: first and last names (both of them should be of type String), gender (integer), and year of birth (integer).

  • Add a static method for the Person class named make() that needs to be called with the personal details (first name, last name, gender, year of birth) and it creates a new Person object.

    But before creating the object, verify that every parameter satisfies the corresponding constraints:

    • Neither the first name nor the last name can be empty, and both of them must be at least of size two, and both of them have to start with an uppercase letter. Use the java.lang.String class to formulate those conditions.

    • The year of birth must be in between 1880 and 2016.

    If the specified parameters do not satisfy the constraints above, the method shall return an empty, that is a null reference. (That is, no object shall be actually created in that case.)

  • Add a show() method for the Person class that converts the internal state of the object to a String value.

  • Add an isAdult() method to the Person class that returns with a Boolean value depending on the fact that the person represented by the object is over 18 or not.

  • Add an equals() method to the Person class. The purpose of it is to decide about its parameter, which is another Person object, if it is equal to current one. The result must be a Boolean value.

    Remark. Because a reference to an object is passed as a parameter, it might be even a null value. By definition, let the result be false in that case.

  • Place the classes into a person package. Write a main program for them where a Person object is created via reading the neccessary information from the standard input, and print it (by the show() method) to the standard output. Place class for the main program into the main package.

Related sources
Front page
Back