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

The course is maintained by Technische Informatica Breda


Generics

We speak about Generics in connection to the way classes can conserve objects of genric type. Generics is based on the generic type parameter which is used when we define a class, and which helps us to define the types that have to be chosen when an object is created. A class generics can be defined by setting up the number of type parameters we want. This number is written after the class name and between the greater-than and less-than signs. We now implement our own generic class Slot which be assigned whatever object.

public class Slot<T> {
    private T key;

    public void setValue(T key) {
        this.key = key;
    }

    public T getValue() {
        return key;
    }
}

The definition public class Slot<T> tells us that we have to give a type parameter to the constructor of the class Slot. After the constructor call the object variables have to be the same type as what established with the call. We now create a slot which memorizes strings.

Slot<String> string = new Slot<String>();
string.setValue(":)");

System.out.println(string.getValue());
:)

If we change the type parameter we can create different kinds of Slot ojects, whose purpose is to memorize objects. For instance, we can memorize an integer in the following way:

Slot<Integer> num = new Slot<Integer>();
num.setValue(5);

System.out.println(slot.getValue());
5

An important part of Java data structures are programmed to be generic. For instance, ArrayList receives one parameter, HashMap two.

List<String> string = new ArrayList<String>();
Map<String, String> keyCouples = new HashMap<String, String>();

In the future, when you see the type ArrayList<String>, for instance, you know that its internal structure makes use of a generic type parameter.

The Interface which Makes Use of Generics: Comparable

In addition to normal interfaces, Java has interfaces which make use of generics. The internal value types of generic interfaces are defined in the same way as for generic classes. Let us have a look at Java made-up Comparable interface. The Comparable interface defines the compareTo method, which returns the place of this object, in relation to the parameter object (a negative number, 0, or a positive number). If this object is placed before the parameter object in the comparison order, the method returns a negative value, whereas it returns a positive value if it is placed after the parameter object. If the objects are placed at the same place in the comparison order, the method returns 0. With comparison order we mean the object order of magnitude defined by the programmer, i.e. the object order, when they are sorted with the sort method.

One of the advantages of the interface Comparable is that it allows us to sort a list of Comparable type keys by using the standard library method Collections.sort, for instance. Collections.sort uses the method compareTo of a key list to define in which order these keys should be. We call Natural Ordering this ordering technique which makes use of the compareTo method.

We create the class ClubMember, which depicts the young people and children who belong to the club. The members have to eat in order of height, so the club members will implement the interface Comparable. The interface Comparable also takes as type parameter the class which it is compared to. As type parameter, we use the ClubMember class.

public class ClubMember implements Comparable<ClubMember> {
    private String name;
    private int height;

    public ClubMember(String name, int height) {
        this.name = name;
        this.height = height;
    }

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

    public int getHeight() {
        return this.height;
    }

    @Override
    public String toString() {
        return this.getName() + " (" + this.getHeight() + ")";
    }

    @Override
    public int compareTo(ClubMember clubMember) {
        if(this.height == clubMember.getHeight()) {
            return 0;
        } else if (this.height > clubMember.getHeight()) {
            return 1;
        } else {
            return -1;
        }
    }
}

The interface requires the method compareTo, which returns an integer that tells us the comparison order. Our method compareTo() has to return a negative number if this object is smaller than its parameter object, or zero, if the two members are equally tall. Therefore, we can implement the above method compareTo, in the following way:

@Override
public int compareTo(ClubMember clubMember) {
    return this.height - clubMember.getHeight();
}

Sorting club members is easy, now.

List<ClubMember> clubMembers = new ArrayList<ClubMember>();
clubMembers.add(new ClubMember("mikael", 182));
clubMembers.add(new ClubMember("matti", 187));
clubMembers.add(new ClubMember("joel", 184));

System.out.println(clubMembers);
Collections.sort(clubMembers);
System.out.println(clubMembers);
[mikael (182), matti (187), joel (184)]
[mikael (182), joel (184), matti (187)]

If we want to sort the members in descending order, we only have to switch the variable order in our compareTo method.

Exercise generics-1: Rich First, Poor Last

You find the pre-made class Person. People have got name and salary information. Make Person implement the interface Comparable, so that the compareTo method would sort the people according to their salary – rich first, poor last.

Exercise generics-2: Students Sorted by Name

You find the pre-made class Student. Students have got a name. Make Student implement the interface Comparable, so that the compareTo method would sort the students in alphabetic order.

Tip: student names are Strings, the class String is Comparable itself. You can use the String’s compareTo method when you implement your class Student . String.compareTo gives a different value to characters according to their case; because of this, String has also got the method compareToIgnoreCase which, in fact, ignores the case while comparing. You can use either of them, when you sort your students.

Exercise generics-3: Sorting Cards

Together with the exercise layout, you find a class whose objects represent playing cards. Cards have got a value and a suit. Card values are 2, 3, …, 10, J, Q, K and A, and the suits are Spades, Hearts, Diamonds and Clubs. Value and suit are however shown as integers in the objects. Cards have also got a toString method, which is used to print the card value and suit in a “friendly way”.

Four constants – that is public static final variables – are defined in the class, so that the user didn’t need to handle card’s suits as numbers:

public class Card {
   public static final int SPADES  = 0;
   public static final int DIAMONDS  = 1;
   public static final int HEARTS = 2;
   public static final int CLUBS   = 3;

   // ...
}

Now, instead of writing the number 1, we can use the constant Card.DIAMONDS in our program. In the following example, we create three cards and print them:

 Card first = new Card(2, Card.DIAMONDS);
 Card second = new Card(14, Card.CLUBS);
 Card third = new Card(12, Card.HEARTS);

 System.out.println(first);
 System.out.println(second);
 System.out.println(third);

Prints:

2 of Diamonds
A of Clubs
Q of Hearts

Note: using constants as shown above is not the best way deal with things. Later on in the course we learn a better way to show suits!

Exercise generics-3.1 Comparable Cards

Make your Cards class Comparable. Implement the compareTo method so that cards would be sorted in ascending order according to their value. If the value of two classes have got the same values, we compare them against their suit in ascending order: spades first, diamonds second, hearts third, and clubs last.

The smallest card would then be the two spades and the greatest would be the clubs ace.

Exercise generics-3.2: Hand

Next, let’s create the class Hand which represents the player hand set of cards. Create the following method to the hand:

  • public void add(Card card) adds a card to the hand
  • public void print() prints the cards in the hand following the below example pattern
Hand hand = new Hand();

hand.add( new Card(2, Card.SPADES) );
hand.add( new Card(14, Card.CLUBS) );
hand.add( new Card(12, Card.HEARTS) );
hand.add( new Card(2, Card.CLUBS) );

hand.print();

Prints:

2 of Spades
A of Clubs
Q of Hearts
2 of Clubs

Store the hand cards into an ArrayList.

Exercise generics-3.4 Sorting the Hand

Create the method public void sort() for your hand, which sorts the cards in the hand. After being sorted, the cards are printed in order:

Hand hand = new Hand();

hand.add( new Card(2, Card.SPADES) );
hand.add( new Card(14, Card.CLUBS) );
hand.add( new Card(12, Card.HEARTS) );
hand.add( new Card(2, Card.CLUBS) );

hand.sort();

hand.print();

Prints:

2 of Spades
2 of Clubs
Q of Hearts
A of Clubs

Exercise generics-3.5: Comparing Hands

In one card game, the most valuable hand, where the sum of the cards value is the biggest. Modify the class Hand so that it could be compared according to this criterion: make it implement the interface Comparable<Hand>.

Below, you find an example of a program where we compare hands:

Hand hand1 = new Hand();

hand1.add( new Card(2, Card.SPADES) );
hand1.add( new Card(14, Card.CLUBS) );
hand1.add( new Card(12, Card.HEARTS) );
hand1.add( new Card(2, Card.CLUBS) );

Hand hand2 = new Hand();

hand2.add( new Card(11, Card.DIAMONDS) );
hand2.add( new Card(11, Card.CLUBS) );
hand2.add( new Card(11, Card.HEARTS) );

int comparison = hand1.compareTo(hand2);

if ( comparison < 0 ) {
  System.out.println("the most valuable hand contains the cards");
  hand2.print();
} else if ( comparison > 0 ){
  System.out.println("the most valuable hand contains the cards");
  hand1.print();
} else {
  System.out.println("the hands are equally valuable");
}

Prints:

the most valuable hand contains the cards
J of Diamonds
J of Clubs
J of Hearts

Exercise generics-3.6: Sorting the Cards against Different Criteria

What about if we wanted to sort cards in a slightly different way, sometimes; for instance, what about if we wanted to have all same-suit cards in a raw? The class can have only one method compareTo, which means that we have to find out other ways to sort cards against different orders.

If you want to sort your cards in optional orders, you can make use of different classes which execute the comparison. These classes have to implement the interface Comparator<Card>. The object which determines the sorting order compares two cards it receives as parameter. There is only one method, a method compare(Card card1, Card card2) which has to return a negative value if card1 is before card2, a positive value if card2 is before card1, and 0 otherwise.

The idea is creating a specific comparison class for each sorting order; for instance, a class which places same suit cards together in a row:

import java.util.Comparator;

public class SortAgainstSuitAndValue implements Comparator<Card> {
   public int compare(Card card1, Card card2) {
       return card1.getSuit()-card2.getSuit();
   }
}

Sorting against suit works in the same way as the card method compareTo thought for suits, that is spades first, diamonds second, hearts third, clubs last.

Sorting is still possible through the method Collections.sort. The method now receives as second parameter an object of the class that determines the sorting order:

ArrayList<Card> cards = new ArrayList<Card>();

cards.add( new Card(3, Card.CLUBS) );
cards.add( new Card(2, Card.DIAMONDS) );
cards.add( new Card(14, Card.CLUBS) );
cards.add( new Card(12, Card.HEARTS) );
cards.add( new Card(2, Card.CLUBS) );

SortAgainstSuit suitSorter = new SortAgainstSuit();
Collections.sort(cards, suitSorter );

for (Card c : cards) {
  System.out.println( c );
}

Prints:

2 of Diamonds
Q of Hearts
3 of Clubs
A of Clubs
2 of Clubs

The sorting object can also be created directly together with the sort call:

 Collections.sort(cards, new SortAgainstSuit() );

Further information about comparator classes in here.

Create now the class SortAgainstSuitAndValue which implements the interface Comparator and sorts cards as it is done in the example above, plus same suit cards are also sorted according to their value.

Exercise generics-3.7: Sort Against Suit

Add the method public void sortAgainstSuit() to the class Hand; when the method is called the hand’s cards are sorted according to the comparator SortAgainstSuitAndValue. After sorting them, the cards are printed in order:

Hand hand = new Hand();

hand.add( new Card(12, Card.HEARTS) );
hand.add( new Card(4, Card.CLUBS) );
hand.add( new Card(2, Card.DIAMONDS) );
hand.add( new Card(14, Card.CLUBS) );
hand.add( new Card(7, Card.HEARTS) );
hand.add( new Card(2, Card.CLUBS) );

hand.sortAgainstSuit();

hand.print();

Prints:

2 of Diamonds
7 of Hearts
Q of Hearts
2 of Clubs
4 of Clubs
A of Clubs