Object-Oriented Programming with Java, part I + II

cc

This material is licensed under the Creative Commons BY-NC-SA license, which means that you can use it and distribute it freely so long as you do not erase the names of the original authors. If you make changes in the material and want to distribute this altered version of the material, you have to license it with a similar free license. The use of the material for commercial use is prohibited without a separate agreement.

Authors: Arto Hellas, Matti Luukkainen
Translators to English: Emilia Hjelm, Alex H. Virtanen, Matti Luukkainen, Virpi Sumu, Birunthan Mohanathas, Etiënne Goossens
Extra material added by: Etiënne Goossens, Maurice Snoeren, Johan Talboom
Adapted for Informatica by: Ruud Hermans

The course is maintained by Technische Informatica Breda


To static or not to static?

When we started using objects, the material advised to leave out the keyword ‘static’ when defining their methods. However, up until week 3 all of the methods included that keyword. So what is it all about?

The following example has a method resetArray, that works as its name implies; it sets all of the cells of an array that it receives as a parameter to 0.

public class Program {

    public static void resetArray(int[] table) {
        for ( int i=0; i < table.length; i++ )
            table[i] = 0;
    }

    public static void main(String[] args) {
        int[] values = { 1, 2, 3, 4, 5 };

        for ( int number : values ) {
            System.out.print( number + " " );  // prints 1, 2, 3, 4, 5
        }

        System.out.println();

        resetArray(values);

        for ( int number : values ) {
            System.out.print( number + " " );  // prints 0, 0, 0, 0, 0
        }
    }
}

We notice that the method definition now has the keyword static. The reason for that is that the method does not operate on any object, instead it is a class method or in other words static methods. In contrast to instance methods, static methods are not connected to any particular object and thus the reference this is not valid within static methods. A static method can operate only with data that is given it as parameter. The parameter of a static method can naturally be an object.

Since static methods are not connected to any object, those can not be called through the object name: objectName.methodName() but should be called as in the above example by using only the method name.

If the static method is called from a different class, the call is of the form ClassName.staticMethodName(). The below example demonstrates that:

public class Program {
    public static void main(String[] args) {
        int[] values = { 1, 2, 3, 4, 5 };

        for ( int value : values ) {
            System.out.print( value + " " );  // prints: 1, 2, 3, 4, 5
        }

        System.out.println();

        ArrayHandling.resetArray(values);

        for ( int value : values ) {
            System.out.print( value + " " );  // prints: 0, 0, 0, 0, 0
        }
    }
}
public class ArrayHandling {
    public static void resetArray(int[] array) {
        for ( int i=0; i < array.length; i++ ) {
            array[i] = 0;
        }
    }
}

The static method that has been defined within another class will now be called with ArrayHandling.resetArray(parameter);.

When static methods should be used

All object state-handling methods should be defined as normal object methods. For example, all of the methods of the Person, MyDate, Clock, Team, … classes we defined during the previous weeks should be defined as normal object methods, not as statics.

Lets get back to the Person class yet again. In the following is a part of the class definition. All of the object variables are referred to with the this keyword because we emphasize that we are handling the object variables ‘within’ the said object..

public class Person {
    private String name;
    private int age;

    public Person(String name) {
        this.age = 0;
        this.name = name;
    }

    public boolean isAdult(){
        if ( this.age < 18 ) {
            return false;
        }

        return true;
    }

    public void becomeOlder() {
        this.age++;
    }

    public String getName() {
        return this.name;
    }
}

Because the methods manipulate the object, they do not need to be defined as static, or in other words “not belonging to the object”. If we try to do this, the program won’t work:

public class Person {
    //...

    public static void becomeOlder() {
        this.age++;
    }
}

As a result we’ll get an error non-static variable age can not be referenced from static context, which means that a static method cannot handle an object method.

So when should a static method be used then? Let us inspect the object Person handling an example familiar from chapter 23:

public class Program {
    public static void main(String[] args) {
        Person pekka = new Person("Pekka");
        Person antti = new Person("Antti");
        Person juhana = new Person("Juhana");

        for ( int i=0; i < 30; i++ ) {
            pekka.becomeOlder();
            juhana.becomeOlder();
        }

        antti.becomeOlder();

        if ( antti.isAdult() ) {
            System.out.println( antti.getName() + " is an adult" );
        } else {
            System.out.println( antti.getName() + " is a minor" );
        }

        if ( pekka.isAdult() ) {
            System.out.println( pekka.getName() + " is an adult" );
        } else {
            System.out.println( pekka.getName() + " is a minor" );
        }

        if ( juhana.isAdult() ) {
            System.out.println( juhana.getName() + " is an adult" );
        } else {
            System.out.println( juhana.getName() + " is a minor" );
        }
    }
}

We’ll notice that the piece of code that reports the matureness of persons is copy-pasted twice in the program. It looks really bad!

Reporting the maturity of a person is an excellent candidate for a static method. Let’s rewrite the Program using that method:

public class Main {

    public static void main(String[] args) {
        Person pekka = new Person("Pekka");
        Person antti = new Person("Antti");
        Person juhana = new Person("Juhana");

        for ( int i=0; i < 30; i++ ) {
            pekka.becomeOlder();
            juhana.becomeOlder();
        }

        antti.becomeOlder();

        reportMaturity(antti);

        reportMaturity(pekka);

        reportMaturity(juhana);
    }

    private static void reportMaturity(Person person) {
        if ( person.isAdult() ) {
            System.out.println(person.getName() + " is an adult");
        } else {
            System.out.println(person.getName() + " is a minor");
        }
    }
}

The method reportMaturity is defined as static so it doesn’t belong to any object, but the method receives a Person object as a parameter. The method is not defined within the Person-class since even though it handles a Person object that it receives as a parameter, it is an assistance method of the main program we just wrote. With the method we’ve made main more readable.

Exercise static-1: The library information system

In this assignment we are implementing a simple information system prototype for a library. The prototype will have functionality for searching books by the title, publisher or publishing year.

The main building blocks of the system are the classes Book and Library. Objects of the class Book represent the information of a single book. Object of the class Library holds a set of books and provides various ways to search for the books within the library.

Exercise static-1.1: Book

Let us start with the class Book. The class has instance variables title for the book title, publisher for the name of the publisher, and year for the publishing year. The title and the publisher are of the type String and the publishing year is represented as an integer.

Now implement the class Book. The class should have the constructor public Book(String title, String publisher, int year) and methods public String title(), public String publisher(), public int year() and public String toString().

Example usage:

Book cheese = new Book("Cheese Problems Solved", "Woodhead Publishing", 2007);
System.out.println(cheese.title());
System.out.println(cheese.publisher());
System.out.println(cheese.year());

System.out.println(cheese);

The output should be:

Cheese Problems Solved
Woodhead Publishing
2007
Cheese Problems Solved, Woodhead Publishing, 2007

Exercise static-1.2: Library

Implement the class Library, with constructor public Library() and methods public void addBook(Book newBook) and public void printBooks()

Example usage below.

Library library = new Library();

Book cheese = new Book("Cheese Problems Solved", "Woodhead Publishing", 2007);
library.addBook(cheese);

Book nhl = new Book("NHL Hockey", "Stanley Kupp", 1952);
library.addBook(nhl);

library.addBook(new Book("Battle Axes", "Tom A. Hawk", 1851));

library.printBooks();

The output should be:

Cheese Problems Solved, Woodhead Publishing, 2007
NHL Hockey, Stanley Kupp, 1952
Battle Axes, Tom A. Hawk, 1851

Exercise static-1.3: Search functionality

Add to the class Library the methods public ArrayList<Book> searchByTitle(String title), public ArrayList<Book> searchByPublisher(String publisher) and public ArrayList<Book> searchByYear(int year). The methods return the list of books that match the given title, publisher or year.

Note: you are supposed to do a method that returns an ArrayList. Use the following skeleton as starting point:

public class Library {
   // ...

   public ArrayList<Book> searchByTitle(String title) {
     ArrayList<Book> found = new ArrayList<Book>();

     // iterate the list of books and add all the matching books to the list found

     return found;
   }

Note: when you do the search by a string (title or publisher), do not look for exact matches (with the method equals) instead use the method contains of the class String.

Example usage:

Library library = new Library();

library.addBook(new Book("Cheese Problems Solved", "Woodhead Publishing", 2007));
library.addBook(new Book("The Stinky Cheese Man and Other Fairly Stupid Tales", "Penguin Group", 1992));
library.addBook(new Book("NHL Hockey", "Stanley Kupp", 1952));
library.addBook(new Book("Battle Axes", "Tom A. Hawk", 1851));

ArrayList<Book> result = library.searchByTitle("Cheese");
for (Book book: result) {
    System.out.println(book);
}

System.out.println("---");
for (Book book: library.searchByPublisher("Penguin Group  ")) {
    System.out.println(book);
}

System.out.println("---");
for (Book book: library.searchByYear(1851)) {
    System.out.println(book);
}

The output should be:

Cheese Problems Solved, Woodhead Publishing, 2007
The Stinky Cheese Man and Other Fairly Stupid Tales, Penguin Group, 1992
---
---
Battle Axes, Tom A. Hawk, 1851

There are some minor problems with the implemented search functionality. One particular problem is that the search differentiates upper and lower case letters. In the above example the search by title with the search term “cheese” produced an empty list as answer. The example where the search term contained extra white spaces did not give the expected answer, either. We’d like the search functionality to be case insensitive and not disturbed by the extra white spaces at the start or at the end of the search terms. We will implement a small helper library StringUtils that will then be used in the Library for the more flexible search functionality.

Implement the class StringUtils with a static method public static boolean included(String word, String searched), which checks if the string searched is contained within the string word. As described in the previous paragraph, the method should be case insensitive and should not care about trailing and ending white spaces in the string searched. If either of the strings is null, the method should return false.

The methods trim and toUpperCase() of the class String might be helpful.

When you have completed the method, use it in the search functionality of the class Library.

Use the method as follows:

if(StringUtils.included(book.title(), searchedTitle)) {
    // Book found!
}

The improved library with the example:

Library library = new Library();

library.addBook(new Book("Cheese Problems Solved", "Woodhead Publishing", 2007));
library.addBook(new Book("The Stinky Cheese Man and Other Fairly Stupid Tales", "Penguin Group", 1992));
library.addBook(new Book("NHL Hockey", "Stanley Kupp", 1952));
library.addBook(new Book("Battle Axes", "Tom A. Hawk", 1851));

for (Book book: library.searchByTitle("CHEESE")) {
    System.out.println(book);
}

System.out.println("---");
for (Book book: library.searchByPublisher("PENGUIN  ")) {
    System.out.println(book);
}

should output the following:

Cheese Problems Solved, Woodhead Publishing, 2007
The Stinky Cheese Man and Other Fairly Stupid Tales, Penguin Group, 1992
---
The Stinky Cheese Man and Other Fairly Stupid Tales, Penguin Group, 1992