Chapter:Classes - Using your classes - Exercises

From Juneday education
Jump to: navigation, search

Exercises on using your own classes

This exercise aims to make you familiar with how to write programs which make use of the classes you define yourself. The idea is to write a small program to add and list members. In the using-our-classes folder of the classes repository (download zip of whole repository) you will find some files which will be the basis for this exercise. The structure of the files looks like this:

.
|-- net
|   `-- supermegacorp
|       `-- orgmanager
|           |-- domain
|           |   `-- Member.java
|           |-- main
|           |   `-- MemberSystemMain.java
|           |-- Member.java
|           |-- test
|           |   `-- MemberTest.java
|           `-- ui
|               |-- CLI.java
|               `-- MemberManager.java
|-- test
|   `-- user_input.txt
`-- test.sh

If you don't want to download the whole Classes repository, you can download a zip file of only the using-our-classes dirctory on the page here. Please open the link in a new tab, wait for the download link to appear and download the zip from that page.

If you are on windows and are running Cygwin, then you will find your zip file probably here: /cygdrive/c/Users/<your-user-name>/Downloads/using-classes.zip

If you are on Mac OS, you will probably find your zip file here: /Users/<your-user-name>/Downloads/using-classes.zip or simply: ~/Downloads/using-classes.zip

A short introduction to the files:

Member.java - a class representing our members

MemberTest.java - a small class to test the Member class

MemberManager.java - a class providing a menu and coresponding code to manage user input

CLI.java - a class to help us communicate with the user

MemberSystemMain.java - simply creating a MemberManager instance and invoking the menu system

user_input.txt - a text file with example user interaction

test.sh - a test script faking user interaction


Running the program

$ java net.supermegacorp.orgmanager.main.MemberSystemMain

As usual, you must stand in the directory above net/ for the above line to work, or use a class path.

Running the program you will presented with a menu. Try to add a member and leave the program and start the program again. The members are missing. This is something that will work once we've finished the exercises.

The curious may ask how was the member stored. We use something called Serializable Objects (using Serializable). This is a bit outside the scope of this course and leave it to the curious students to check this out further.

Testing the program

We've written some tests in a script. You can use these to check that the changes and additions to the code you've made in all the exercises are correct. To run the script you type:

$ ./test.sh

and when running you should see a listing like this: Removing existing 'database' members.data: OK

$ ./test.sh 
Compiling java files: OK
Automated user session adding 3 members: OK
Automated user session checking 3 members exist: OK
Automated user session adding 20000 more members: OK
Automated user session checking 20003 members exist: OK

Note: the tests above only work if you've finished all the exercises. So this scripts is useful to verify that all the final code works.

Is it possible to automate the task of adding 20000 members? Well of course it is. Fact is, the scripts we're providing are part of a strategy from our side to introduce you to (perhaps) a new way of thinking. WHo on earth would like to add 200000, or even worse 2 millions, of members manually. Doing this should send a chill up your spine and think of some clever way of making sure you can add the members (getting the job done) and have a nice time (not being bored adding the members manually). You'll be saving a lot (we mean A LOT!) of time when automating stuff. In short what we're doing is to use redirects and some loops.

Expand using link to the right to see an explanation to all this.

A short example. Let's analyse an easy user session with the system:

Starting Member manager
Menu
 1 - to add member
 2 - to list members
 3 - to quit
Enter your choice1
Enter values for new Member
* name: Ada Lovelace
* email: ada@lovelace.net
Created member: Ada Lovelace;ada@lovelace.net
Menu
 1 - to add member
 2 - to list members
 3 - to quit
Enter your choice2
No members?
Member: Ada Lovelace;ada@lovelace.net
Menu
 1 - to add member
 2 - to list members
 3 - to quit
Enter your choice3
Good bye...
Good bye

The user (actually the author of this page) interacted with the system by typing:

1
Ada Lovelace
ada@lovelace.net
2
3

So these are the commands needed to add a member, list the members and finally quit the program. We could use [[echo]] to print the same thing:

$ echo -n -e "1\nAda Lovelace\nada@lovelace.net\n2\n3\n"
1
Ada Lovelace
ada@lovelace.net
2
3

With the trick below we, not properly explained but good for now, replace the keyboard as input source and use the printout from the echo command as input (stdin). Now, we're goind to create another user (Dennis M. Ritchie, dennis@c.org).

$ echo -n -e "1\nDennis M. Ritchie\ndennis@c.org\n2\n3\n" | java net.supermegacorp.orgmanager.main.MemberSystemMain 
Starting Member manager
Menu
 1 - to add member
 2 - to list members
 3 - to quit
 4 - to remove a member
Enter your choiceEnter values for new Member
* name: * email: Created member: Dennis M. Ritchie;dennis@c.org
Menu
 1 - to add member
 2 - to list members
 3 - to quit
 4 - to remove a member
Enter your choiceNo members?
Member: Ada Lovelace;ada@lovelace.net
Member: Dennis M. Ritchie;dennis@c.org
Menu
 1 - to add member
 2 - to list members
 3 - to quit
 4 - to remove a member
Enter your choiceGood bye...
Good bye

So we've managed to automate this. How about this

$ for i in $(seq 1 10) ; do echo -n -e "1\nDennis M. Ritchie Nr $i\ndennis$i@c.org\n2\n" ; done | java net.supermegacorp.orgmanager.main.MemberSystemMain

The above will add 10 members with almost the same name. We could make the program end in a better way (no java.util.NoSuchElementException). Actually the members are not stored. So we really should fix it.

What would happen if we wrote (note that we're removed the command (2) to skip the listing between adding members) and add "3" to tell the program to quit and save the members to file:

$ rm members.data; COUNT=1000000 ; for i in $(seq 1 $COUNT) ; do echo -n -e "1\nDennis M. Ritchie Nr $i\ndennis$i@c.org\n" ; if [ $i -eq $COUNT ]; then echo -e "3\n" ; fi ; done  | java net.supermegacorp.orgmanager.main.MemberSystemMain > /dev/null

You're right, we would add one million members. And this in 18 seconds on the author's computer. Think about drinking coffe instead of doing this manually.....and still getting the job done. >/dev/null? It's a nice trick to make the printout disappear.

How to make sure we succeeded in adding 1 million members? Manually count them? Let some tools do that for us.

$ echo -e "2\n3\n" | java net.supermegacorp.orgmanager.main.MemberSystemMain  | grep -c "Member:"
1000000
$ du -sm members.data 
55	members.data

The first command verified that one million members were in the file (which was removed before adding the members so we now there should be one million members). THe second command simply shows us the file size of the file containing the stored members. It is 55 MB big.

Are we supposed to know all this? Are we supposed to learn all this scripting? Are we getting examined on this? No, no and no. But we hope it inspires you to think about (not) repeating things - let the computer do that for you.

Q1

Read the source code in MemberSystemMain.java. In the source code you will find comments on 1) what's going on in the code. Hopefully you wil see that a MemberManager object is created and used. This class is really the program and we will investigate this class in the following exercises. In the class (MemberManager) you wll find instruction what you should add and change to the entire program work better.

Expand using link to the right to see a suggested solution/answer.

not applicable

Q2

Check the source code of the Member class and make sure it is quite similar to the one you've written yourself in earlier sections. If it is not similar ask your supervisor/teacher.

Expand using link to the right to see a suggested solution/answer.

not applicable

Q3

Compile and run the program MemberSystemMain. You will see that you (as a user) get a chance to do one of the alternatives presented to you and then the program ends. This is not much of a program. We need to add a couple of things to make it (a bit) more useful.

Ok, now we're ready to start with the actual exercises.

Expand using link to the right to see a suggested solution/answer.

To compile you type: javac net/supermegacorp/orgmanager/main/MemberSystemMain.java and to execture the program you type: java net.supermegacorp.orgmanager.main.MemberSystemMain

You can of course do it as a one-liner, where the execution is done only of the compilation succeeds: javac net/supermegacorp/orgmanager/main/MemberSystemMain.java && java net.supermegacorp.orgmanager.main.MemberSystemMain

Q4

First of all, we need to make sure that the program runs, that is keep asking the user what she/he wants to to, until the user explicityl tells the program to quit. So let's start by asking how you would ask a person to do such a thing. Probalbly you would ask a person something like "keep asking the customer what to do until the customer tells you good bye". So let's look at how we can do this with cone. We will controlling the flow of the program. A good idea here would be to introduce a while loop. So let's add a while loop to make the menu/user interaction code keep repeating, making it possible for the user to enter choices in sequence.

Expand using link to the right to see a suggested solution.

Suggested solution:

  public void menuLoop() {
    /*
     * Add some kind of loop here - hint, use while
     */
    while (true) {

      /*
       * Present options to the user
       */
      cli.msgln("Menu");
      cli.msgln(" 1 - to add member");
      cli.msgln(" 2 - to list members");
      cli.msgln(" 3 - to quit");

      ....... snip

    }

With snip we mean that we snipped away parts of the code. We did this to make the code listing a bit shorter.

Q5

There's something in the MemberManager class that is used to store members in a list and some code that saves member to file. What part of the code do you think it is?

Expand using link to the right to see a suggested solution/answer.

The following code creates an ArrayList that is used to store member while the program us running.

        /*
         * An instance variable for a list of members:
         */
        private List<Member> members;
...
...
and in the constructor we have:
        members = new ArrayList<Member>();
because we want the members instance variable to be initialized to a new (empty) list!

The following methods store the list of members to file: saveToFile The following methods reads the list of members, previously stored to file, back to a list: readFromFile

Why do you think that the type on the left hand on the assignment is a bit different from the type on the right side?

Expand using link to the right to see a suggested solution/answer.

Suggested solution Let's look at the code again.

        List<Member> members = new ArrayList<Member>();

If we, some time in the future, would like to change to using another of the concrete List implementations (like for instance LinkedList) we have available in Java's API, this is easier done when using this very common idiom. You will learn a lot more about this in the coming parts of the course, but for now compare it to a requirement of owning a Vehicle. If you state that you have a Vehicle, and some one asks what it is, you could answer a Car if that is what you own. If you later sell your Car and buy a Motorcycle, you can still say that you have a Vehicle. When some one asks what it is, you can tell them it is a Motorcycle.

So, List is the abstraction of the object used here. The actual object is a kind of List called ArrayList. But since we limit the type of our variable to the more abstract type List, we can later change to another kind of List like LinkedList and all other code using our variable will still work. Because both ArrayList and LinkedList are kinds of List, just like a Car and a Motorcycle in the example are both sorts of Vehicle.

Q6

In the menuLoop() method you will find a couple of if statements managing the input from the user. Your job is to find what part of the code it is that handles the case where the user wants to add a member (the menu option: '1 - to add member').

This code is not finished. It is your job to add code that adds the new member to the list. See below for how to add an element to a List.

If you had a List<String>, called names, you would add a String reference s like this: names.add(s);. You should do a similar thing with the list members which is a List<Member> and the m reference variable to the new Member.

Expand using link to the right to see a suggested solution/answer.

Suggested solution

            if (command.equals("1")) {
                Member m = cli.readNewMember();
                /*
                 * add the member to the ArrayList
                 */
                members.add(m);

Q7

When the user choses to list the members ('2 - to list members') you should loop through the content of the ArrayList and print the items out.

Hint: To loop through and output an ArrayList of strings like strings above, we simple write:

  for (String s : strings) {   // for each string s in strings
     System.out.println(s);    // output the object
  }

We encourage you to use the CLI to output the Member information instead of using Systemout.println. In the CLI class you will find an instance method (a method associated with an instance) which can output a member with a prefix of Member: followed by the member as a String. The method is called outputMember.


Now we have a rather simple program. We miss some parts for sure but we now have seen that:

  • we can create a new class to represent a member (our Member class)
  • we can write a program using the Member class

Expand using link to the right to see a suggested solution/answer.

Suggested solution

    } else if (command.equals("2")) {
      for (Member m : members) {
        cli.outputMember(m);
      }

If you want to write something if the list is empty (there are no saved members), you could do that like this:

    } else if (command.equals("2")) {
      if (members.size() == 0) {
        cli.msgln("No members");
      }
      for (Member m : members) {
        cli.outputMember(m);
      }

The loop will not write anything if the members list is empty.

The complete source code of the class now looks a bit like this:

package net.supermegacorp.orgmanager.ui;

import net.supermegacorp.orgmanager.domain.Member;
import net.supermegacorp.orgmanager.ui.CLI;
import java.util.List;
import java.util.ArrayList;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.ObjectInputStream;


public class MemberManager{

  private CLI cli;
  private List<Member> members;
  private String membersFile="members.data";

  
  public MemberManager() {
    cli = new CLI();
    members = new ArrayList<Member>();
    readFromFile();
  }

  public void welcome() {
    cli.msgln("Starting Member manager");
  }
  
  public void goodbye() {
    cli.msgln("Good bye");
  }
  
  public void menuLoop() {
    /* 
     * Add some kind of loop here - hint, use while 
     */
    while(true) {

      /*
       * Present options to the user
       */
      cli.msgln("Menu");
      cli.msgln(" 1 - to add member");
      cli.msgln(" 2 - to list members");
      cli.msgln(" 3 - to quit");
    
      /*
       * Read command from user
       */
      String command = cli.askUser("Enter your choice");
    
      /*
       * Based on command, take different actions
       */
      if (command.equals("1")) {
        Member m   = cli.readNewMember();
        cli.msgln("Created member: " + m);
        /*
         * add the member to the ArrayList
         */

        members.add(m);

      } else if (command.equals("2")) {
        cli.msgln("No members?");
        /* loop through the ArrayList and print the members, 
           hint: the CLI class has an (instance) method called outputMember
        */
      
        for (Member m : members) {
          cli.outputMember(m);
        }        
      } else if (command.equals("3")) {
        cli.msgln("Good bye...");
        saveToFile();
        /* break out of the loop */
        /* Two ways (chose one):
           1. use the keyword break
           2. loop (above) over a boolean variable which you (here) se to false
        */
        
      } else {
        cli.msgln("Invalid choice?");
      }
    } // Here, you end your loop
    
  }

private void saveToFile() {
    try {
      // First time the application is run, create a file      
      File f = new File("first_run");
      if (!f.exists()) {
        f.createNewFile();
      }
      FileOutputStream fos = new FileOutputStream(membersFile);
      ObjectOutputStream oos = new ObjectOutputStream(fos);
      oos.writeObject(members);
      oos.close();
      oos.flush();
      fos.flush();
      fos.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  @SuppressWarnings({"unchecked"})
  private void readFromFile(){
    try {
      FileInputStream fis = new FileInputStream(membersFile);
      ObjectInputStream ois = new ObjectInputStream(fis);
      Object readObject = ois.readObject();
      members = (ArrayList) readObject;
      ois.close();
      fis.close();
    } catch(java.io.FileNotFoundException c){
      cli.msgln("File not found");
      return;
    }catch(IOException ioe){
      ioe.printStackTrace();
      return;
    }catch(ClassNotFoundException c){
      cli.msgln("Class not found");
      c.printStackTrace();
      return;
    }
    
  }
  
}

Q8 (Challenge)

Add a menu alternative that makes it possible to remove a member. Let's make the menu look like this:

Menu
 1 - to add member
 2 - to list members
 3 - to quit
 4 - to remove a member

Let's keep (3) for quit - even though it feels a bit awkward. We ask you to do it this way since we want our test scripts to be able to test the classes with or without this new feature.

Expand using link to the right to see a suggested solution/answer.

      /*
       * Present options to the user
       */
      cli.msgln("Menu");
      cli.msgln(" 1 - to add member");
      cli.msgln(" 2 - to list members");
      cli.msgln(" 3 - to quit");
      cli.msgln(" 4 - to remove a member");

Q9 (Challenge)

Add code to ask the user for a name to remove, if the user chose (4),

We suggest you loop through the Member objects one by one and check if the specific object's name equals the name the user supplied. Using a classic for-loop is a bit problematic, as is using the for each-loop. Instead we will ask you to use Iterator (see the expand-link for the syntax).

Hint: name.equals("Adam"); returns true if the variable name is "Adam". The method equalsIgnoreCase(String) in String might also be worth looking up in the Java API.

If you want to remove matches on parts of the Member's names, rather than an exact match of the name, you can use the method contains(String) which is an instance method in java.lang.String.

Here are some options for matching a String against another String:

  • contains() (for substrings)
  • equals() (for exact matches)
  • equalsIgnoreCase() (for exact matches but regardless of the case of the String - doesn't care about upper or lower case letters)

A few words of warning. Note that if you allow the user to enter a partial name and remove all members whose name in part matches the user's input (using contains()), you should change the promt to the user so that the user understands that all members whose name partially matches the user's input will be removed. Also, be aware that if the user enters an empty String, all members will be removed, since all Strings will return true when asked if they contain("") - all Strings contain the empty String!

Expand using link to the right to see a suggested solution/answer. Note that we in the code below remove members with a name containing the text given by the user.

Suggested solution, using an iterator loop:

        /* Iterator */
        Iterator<Member> i = members.iterator();
        while (i.hasNext()) {
          Member m = i.next();
          if (m.name().contains(name)) {
            cli.msg("Removing member: " + m);
            i.remove();
          }
        }

Or, if you think it is easier, you can remove all members whose name contains a substring like this:

String name = cli.askUser("name of member to remove?: ");
members.removeIf(m -> m.name().contains(name));

The above is a so called lambda expression passed as an argument to the Collection instance method removeIf() which expects a Predicate. A predicate is an object of some class which declares one method, boolean test(T t).

We could thus also have written:

String name = cli.askUser("name of member to remove?: ");
members.removeIf(new Predicate<Member>()
 {
    public boolean test(Member m) {
      return m.name().contains(name);
    }
 });

The lambda expression above is a shortcut for the above, so

m -> m.name().contains(name)

is a shortcut for:

new Predicate<Member>()
 {
    public boolean test(Member m) {
      return m.name().contains(name);
    }
 }

If you would like to read about the problems with using the for-loop or the for each-loop we have some addtional text for you. Expand using link to the right to see text. Again, we will show code that remove members with a name cotaining (bot equal to) the text supplied by the user.

Suggested solution, using a classic for-loop:

      } else if (command.equals("4")) {
        String name = cli.askUser("name of member to remove?: ");
        // Here's a good place to look if the entered name is the empty String
        // which would remove all Members :-)

        for (int i=0; i<members.size(); i++) {
          Member m = members.get(i);
          if (m.name().contains(name)) {
            cli.msg("Removing member: " + m);
            members.remove(m);
          }
        }
      }

which when removing one of the last member added by the test script (test.sh) behaves like this:

Menu
 1 - to add member
 2 - to list members
 3 - to quit
 4 - to remove a member
Enter your choice4
name of member to remove?: Test19996
Removing member: Test19996 Testson;test19996@mega.comMenu

Notice that we wanted to remove the member with a name containing "Test19996". The code seem to work.

Let's rewrite the for loop a bit.

int size=members.size();
for (int i=0; i<size; i++) {

And try to remove a member from the list (not the last one!!) which seems to work.

Here's a suggested solution/answer for a for loop which loops over a size measued before entering the loop:

        int size = members.size();
        for (int i=0; i<size; i++) {
          Member m = members.get(i);
          if (m.name().contains(name)) {
            cli.msg("Removing member: " + m);
            members.remove(m);
          }
        }

which when removing the last member added by the test script (test.sh) behaves like this:

Menu
 1 - to add member
 2 - to list members
 3 - to quit
 4 - to remove a member
Enter your choice4 
name of member to remove?: Test19997
Removing member: Test19997 Testson;test19997@mega.comException in thread "main" java.lang.IndexOutOfBoundsException: Index: 20002, Size: 20002
	at java.util.ArrayList.rangeCheck(ArrayList.java:653)
	at java.util.ArrayList.get(ArrayList.java:429)
	at net.supermegacorp.orgmanager.ui.MemberManager.menuLoop(MemberManager.java:103)
	at net.supermegacorp.orgmanager.main.MemberSystemMain.main(MemberSystemMain.java:21)

Notice that we wanted to remove the member with a name containing "Test19997". The code fails because we're indexing the 20002th element in an array which is now (after removing an element) 20002 big (highest index 20001).

OK, so if we don't want to end up in that situation, we might want to try a solution using a classic for-each-loop:

      } else if (command.equals("4")) {
        String name = cli.askUser("name of member to remove?: ");
        /* 'New' for each loop */

        for (Member m : members) {
          if (m.name().contains(name)) {
            cli.msg("Removing member: " + m);
            members.remove(m);
          }
        }
      } else {
Menu
 1 - to add member
 2 - to list members
 3 - to quit
 4 - to remove a member
Enter your choice4
name of member to remove?: Test1994
Removing member: Test1994 Testson;test1994@mega.comException in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at net.supermegacorp.orgmanager.ui.MemberManager.menuLoop(MemberManager.java:100)
	at net.supermegacorp.orgmanager.main.MemberSystemMain.main(Main.java:21)

But why didn't this work either? Because, using a for-each-loop, we can't remove elements from the list while looping over the same list. At least not the last member of the list.

So what to do then? Well, the first solution, using Iterator, and the lambda, are the preferred way to iterate (loop) over a list which you want to modify (or remove elements from).

We wanted you to try some less safe ways and see what kind of troubles might occur, in order to motivate you to learn about the Iterator solution.

Extra challenge: What happens if you enter no characters at all when the menu asks you to enter a string to use for removing members matching a substring (using the method contains?

Starting Member manager
Menu
 1 - to add member
 2 - to list members
 3 - to quit
 4 - to remove a member
Enter your choice4
name of member to remove?:

The system will remove all members. Why you may ask. It is because all names match the string "". Try to write code to does not continue if the user enters an empty string.

Expand using link to the right to see a suggested solution.

      } else if (command.equals("4")) {
        String name = cli.askUser("Name of member to remove?: ");
        if (name.equals("")) {
          cli.msgln("Invalid name");
	} else {
	  /* Iterator */
          Iterator<Member> i = members.iterator();
          while (i.hasNext()) {
            Member m = i.next();
            if (m.name().contains(name)) {
              cli.msg("Removing member: " + m);
              i.remove();
            }
          }
        }

Links

Where to go next

Next page is: Classes_-_API_source_code

« PreviousBook TOCNext »