Chapter: Classes III

From Juneday education
Jump to: navigation, search

THIS PAGE IS REPLACED BY:

Meta information about this chapter

See introduction in previous chapter Chapter:Classes for meta information.

Chapter Videos

See below for individual links to the videos.

Classes III - classes conclusion

Static variables and methods

Description

So far, we have focused on instance variables, i.e. variables which every instance hold their own copy of. Now it's time to introduce variables that rather belong to the class itself and that every instance of the class "share". Such variables are called "class variables" or "static variables". The latter name comes from the fact that we use the modifier static when we declare such variables.

If you think about it, there are values which are not unique to every instance of a class, but rather shared. One example would be the interest rate of a bank account of some type. It is common that every bank account of the same type (e.g. a savings account) have the same interest rate. If every savings account have the same interest rate, say 0,01%, and if this interest rate would change for every bank account, should the bank decide to change it, then it is practical to have a mechanism for expressing that the variable holding the current interest rate is shared between all instances of the class SavingsAccount.

The modifier static has precisely this meaning when applied to a variable declared in the block of a class. An example with the interest rate could look something like:

public class SavingsAccount{
  private static double interestRate = 0.01; // percent, shared by all instances
  // instance variable declarations like balance etc...
  // constructors...
  // methods...
}

An indication that a variable should be static is that the variable holds some value which is relevant to talk about without any relation to specific instances of the class. Another indication is that it is safe and natural that the variable has the same impact on every instance of a class, in the sense that if the variable's value changes, every instance will be effected by the change.

If you compare this to an instance variable such as the balance for an instance of SavingsAccount, you will see that the opposite holds for instance variables. It is not desirable that when one instance of a bank account changes the amount of the balance, every other instance of a bank account should also be affected. And it is not relevant to talk about the balance without the context of a particular instance of a bank account. The balance is the balance of one particular bank account! Therefore the balance is an instance variable which every bank account object will have its own copy of (so that they can have different balance). But the interest rate of some class of bank account, like a SavingsAccount, is relevant to talk about, even before any accounts have been created (the first customer enters the bank and decides she wants a savings account, because she agrees with the interest rate).

What about static methods, then (aka class methods)? Like with static variables, a static method is declared using the modifier static, and like static variables, static methods exist independent of any instances of the class it belongs to. Going back to the SavingsAccount class, there could be a static method for changing the static variable interestRate, something like this:

public class SavingsAccount{
  private static double interestRate = 0.01; // percent
  public static void changeInterestRate(double newRate){
    // Some logic to check that it is a feasible value...
    interestRate = newRate;
  }
  // instance variables...
  // constructors...
  // instance methods...
}

Another potential case for static methods are convenience methods which only calculate something from its parameters, like the many static methods declared in java.lang.Math, for instance public double sqrt(double d) which calculate the square root of its parameter (given as argument in the calling code). Such a method only operates on its parameters and can therefore exist totally independent of any instances. In fact, you cannot create instances of the class Math!

So how do we call a public static method? We use the class name (where the method is declared) and a dot followed by the method and arguments:

  /*
  
   |\
 A | \ C
   |__\
     B
  
  Calculate the length of C when we know the lengths of
  A (4)
  B (3)
  */
  double a = 4.0;
  double b = 3.0;
  double c = Math.sqrt(3*3+4*4); // Pythagoras...

A class method, or as it's also called, a static method, can only operate on static variables and its parameters. Why? Because we can call it regardless of any instances, even before any instances of the class is constructed. It is not possible to use instance variables inside a static method, because we don't know if there is any instances or which instance they would belong to. This is because we can call a static method via the class name so there is no reference to any specific instance known to the method.

While instance methods always have the keyword this as a reference to the instance for which the instance method was called, there is no such thing for a static method, since they are called using the class name.

Videos

Exercises on static variables and methods

Let's go back to our Member class. We added a variable separator that we used to store the separator between values in our printout. We also discussed if we needed to have one separator variable per object. We don't. Instead we can let the class have a variable common for every object. This means that this variable belongs to (is associated with) the class instead of a separate object.

  1. Change the separator variable into a class variable, using the keyword static.
  2. Let's assume someone wants to know what separator is used when calling toString on Member objects. To let people get this value we can either:
    * make the variable public. A drawback of this is that people can change it. This makes it not usable for us.
    * keep the variable private and add a public method, getSeparator.
    Your job is to write the method as described last. Should this method be public or private? Should it be static or non-static?.
  3. Now, say that we want to change the value of the separator variable. Add a method, setSeparator(String separator), to change it.
  4. Can you motivate keeping the separator variable private when everyone can change it using the set method?
  5. Write a new class, StaticConfusion. Don't use any packages in this class (which makes the class get the nameless package). Write a main method that invokes the method email(). The class can look something like this:
    import net.supermegacorp.orgmanager.Member;
    
    public class StaticConfusion {
    
        public static void main(String[] args) {
    
            Member.email();
    
        }
    
    }
    
    Compile. Did it work? Why?

Solutions to static variables and methods

Expand using link to the right to see the full content.

  1. Suggested solution (parts of the class):
    package net.supermegacorp.orgmanager;
    
    public class Member {
    
      private String name;
      private String email;
      private static String separator = ";";
    
      public String toString() {
        return name + separator + email;
      }
    }
    
  2. Suggested solution (parts of the class):
    package net.supermegacorp.orgmanager;
    
    public class Member {
    
      private String name;
      private String email;
      private static String separator = ";";
    
      public static String getSeparator() {
        return separator;
      }
    
      public String toString() {
        return name + separator + email;
      }
    }
    
    We need to make the method public since we want others to be able to use it. It does not have to be static since all objects can reach the class, but we suggest making it static to reflect that the variable is static.
  3. Suggested solution:
        public static void setSeparator(String separator) {
            Member.separator = separator;
        }
    
  4. The authors can not motivate Getters and Setters like this. But we will use the method in the next section so let's keep it for now.
  5. The class StaticConfusion will not compile. You'll get an error message similar to this:
    StaticConfusion.java:7: error: non-static method email() cannot be referenced from a static context
            Member.email();
                  ^
    1 error
    
    The method email() is an instance method which means it is associated with an object. Calling the method with an association to the class and no object does not make sense. This is similar to asking the blueprint of a mobile phone what phone number is has - it is instances of the mobile phone blueprint that has phone numbers.
    We have noticed that a common solution to this, even among teachers and supervisors, to make the method email() static. Try doing this and you'll find that you also need to make the instance variable email static. Et voila, it works. The "only" problem now is that all Member instances will share the same email address. So we urge you not to fall in to the common pattern we (the authors) are calling 'the static swamp anti-pattern'. Think about if asking the class for an email address is wise in the first place and you'll directly remove or replace the call to Member.email(). Another solution is to create an object of the type Member, which is probably you wanted in the first place.

Section: final variables

Description

Final variables, when used as global constants, are mentioned in the video for static variables.

Note: a method and a class can also be final but this does not make sense before we introduce inheritance so we'll skip it for now

Videos

  • No videos for this small section!

Exercises on final variables and methods

Let's go back to our Member class. We added a variable separator that we turned into a static variable, in the previous section. Why should anyone, including ourselves, be able to change it? We (the authors) see no reason to be able to change it. So let's make the variable "unchangeable".

  1. Make it impossible to change the separator variable. Do this by using the keyword final. Also make sure that the method setSeparator(String separator) is present in the code. Compile. Does it work?
  2. Remove the setSeparator(String separator) and compile again. Compile and explain why this happens.
  3. There is a coding convention which says that final static variables (constants) should be named with all upper case letters with underscore as word separator if the name consists of several words. What should you rename separator to? (No underscore is needed, since it consists of only one word)

Solutions to final variables and methods

Expand using link to the right to see the full content.

  1. Change the separator declaration to look something like this:
      private static final String separator = ";";
    
    It does not compile since we have a method setSeparator(String separator) that alters (changes) the values of separator which we have made final.
  2. Now it compiles again. So we can't change, or even provide a method with which we can change, the value of a final variable.
  3. SEPARATOR

About Java packages

Description

Just as we like to organize our files on our computer in a hierarchical structure using directories, we want to maintain such order in our Java programs as well. Programmers tend to organize the classes in the system in directories with a hierarchical structure with descriptive names of the directories. This has many benefits. One is that the classes get a "name space" using the package names as part of the full class name. This, in turn, allows us to have two or more classes in our system with the same name, if they belong to different packages.

The package name corresponds to the relative path to the class file (and source code file). This allows us not only to have descriptive names of classes where the package names are part of the full (qualified) class name, but also to have the same name of the actual class, since the class files are kept in separate directories:

org.somecompany.net.facebook.LoginCredentials;
org.somecompany.net.instagram.LoginCredentials;
org.somecompany.net.google.LoginCredentials;

All the classes declared above are declared with the name LoginCredentials, but the qualified names show us the context. Just by looking at the full, qualified, names of the classes we understand that they are representations of the login credentials for different social network services.

Another benefit from using packages is that we can group classes together according to some criteria. For instance, we could have all the classes dealing with networking in a directory (and package!) and all classes which deal with the graphical user interface in another directory (and package!). This way, it is easy for the programmers working on the source code to find the source code files. If a programmer has to fix a problem with the internet connectivity of the system, she would be able to look at the directory names and figure out where the relevant source code files for the classes probably are located: "They are probably somewhere under org/somecompany/net...". And when another programmer needs to work on the graphical interface of the system, she would similarly probably think: "The classes for this are probably somewhere under org/somecompany/gui/..." (gui often stands for "Graphical User Interface").

The perhaps most striking reason for using packages (and the corresponding directory structure) is that the alternative would mean that we keep all source code files (and after compilation, all class files) in one big directory. For a large project with thousands of files, this would of course be a great mess.

Finally, a less obvious benefit for using packages, is that we could actually use the "default" or "package private" access level for variables and methods. This access level is what we get when we don't use any of "public", "private" or "protected". The absence of an access modifier keyword gives us this level which is called "package private" (or simply "default"). Such variables and methods are accessible within the class itself and from classes in the same package.

It is not uncommon that we would give some methods visibility only in related classes, so that those classes can use the method but no other classes in the system. Related classes, as we said above, should be put in the same directory (and package), so the default access level works fine in such a case. Let's go back to the SavingsAccount example. Perhaps we have a class responsible for changing the interest rate of the different bank account classes. If we put this class in the same package as the classes for the accounts, we could give the static method changeInterestRate(double) default access level. The benefit of this approach could be that we effectively prevent "unauthorized" classes (unrelated classes) to mess with the interest rate values of the different bank account classes. We could then centralize the security measures for deciding if an interest rate could be changed to the class which has access to the various changeInterestRate(double) methods of the respective classes.

But from a foreign (unrelated) class, like org.somecompany.main.Main, the various changeInterestRate methods are not accessible, since this class belongs to a different package.

Videos

Exercises on grouping stuff in packages

This exercise aims to make you reflect on how one could divide classes into packages.

Given the following classes for some project, how could you divide the classes into directories (and packages) which better describe their role in the project? Be creative! There is not one single answer to this question, you have to use your imagination here.

$ ls -1
Book.java
Main.java
MathStuff.java
Newspaper.java
TestMathStuff.java
TextUtilities.java

Solutions to grouping stuff in packages

Expand using link to the right to see the full content.

One possible division could be the following:

.
|-- literature
|   |-- Book.java
|   `-- Newspaper.java
|-- main
|   `-- Main.java
|-- math
|   `-- MathStuff.java
|-- test
|   `-- TestMathStuff.java
`-- text
    `-- TextUtilities.java

Testing if your classes work

How do we know a class working the way we want it to? We need to test it. There are several test strategies and methods which could be the basis a separate course. In this course we'll provide some tips and tricks for you when writing code.

Let's look at a version of our Member class again:

package net.supermegacorp.orgmanager;

public class Member {

  private String name;
  private String email;
  private static final String separator = ";";

  public Member(String name, String email) {
    this(name);
    setEmail(email);
  }

 
  public Member(String name) {
    this.name = name;
  }

  public void setEmail(String email) {
    if (email.contains("@")) {
      this.email = email;
    }
  }
  
  public String toString() {
    return name + separator + email;
  }
    
  public String name() {
    return name;
  }
  
  public String email() {
    return email;
  }


  public static String getSeparator() {
    return separator;
  }
}

What do we need to test (or verify) in this class? In the first constructor we have two parameters. The second argument is only valid if there's an "@" in it so let's write code to:

  • create a Member object with a valid email address
  • create a Member object with an invalid email address
  • create a Member object with null as an email address

let's do this first and then write code to make sure the values were set accordingly.

So let's start with the following test class:

 1 package net.supermegacorp.orgmanager.test;
 2 
 3 import net.supermegacorp.orgmanager.Member;
 4 
 5 public class MemberTest {
 6 
 7     public static void main(String[] args) {
 8       Member ada     = new Member("Ada", "ada@lovelace.edu");
 9       Member charles = new Member("Charles", "charles __AT__ babbage.net");
10       Member bert    = new Member("Bert", null);
11     }
12 
13 }

Compile and execute these tests. Wooops, we can compile but get an error when executing it:

Exception in thread "main" java.lang.NullPointerException
	at net.supermegacorp.orgmanager.Member.setEmail(Member.java:20)
	at net.supermegacorp.orgmanager.Member.<init>(Member.java:11)

----
	at net.supermegacorp.orgmanager.test.MemberTest.main(MemberTest.java:11)

Already we have seen a positive effect of our tests and we have just begun. At line 20 in the Member.class something goes wrong. Since Java says it is a NullPointerException we can deduce that emails is null. So we need to change the method setEmail() in our member class slightly. Let's write it like this instead:

1   public void setEmail(String email) {
2     if (email != null && email.contains("@")) {
3       this.email = email;
4     }
5   }

The change here is that we make sure that the email address is not null before we invoke the contains method. Let's compile and execute again.

Nice, it seems to work or at least not crash. So let's continue writing code to check that the values were set accordingly. We give an example on how to do this below:

1     public static void main(String[] args) {
2       Member ada     = new Member("Ada", "ada@lovelace.edu");
3       Member charles = new Member("Charles", "charles __AT__ babbage.net");
4       Member bert    = new Member("Bert", null);
5 
6       System.out.println("ada:      " + ada.email());
7       System.out.println("charles:  " + charles.email());
8       System.out.println("bert:     " + bert.email());
9     }

This is ok, but it is a bit tedious that the developer has to manually check the email addresses. Since we're (or at least trying to become) developers we want to automate this, so let's write the following text code to verify that the constructor does its job:

 1       // Let's check that we have managed to store "ada@lovelace.edu" 
 2       if ( !ada.email().equals("ada@lovelace.edu")) {
 3           System.err.println("Ada's email " + ada.email() + ") is faulty: ");
 4       }
 5 
 6       // Passing the email address "charles __AT__ babbage.net" should result
 7       // in the email (instance) variable being assigned null.
 8       //
 9       // Let's verify this (that the email address is null).
10       //
11       // Let's check that the constructor works (i.e assignes email to null), 
12       //    so, if not null (!=null) then we should output an error message
13       if ( charles.email() != null ) {
14           System.err.println("charles' email is faulty. Current value: \"" + charles.email + "\"   should be null");
15       } 
16       // We could potentially have added this, but we want a silent check
17       // - that is, don't print anything if "ok"
18       /*else {
19           System.out.println("charles' email is null, which is ok");
20       } 
21       */
22 
23       // Similar to the above
24       if ( bert.email() != null ) {
25           System.err.println("bert's email  is faulty: ");
26       }

Now we get an error message if something is not ok. If all is well, Nothing is printed. This is a better approach but still not good. Let's use assertions (Programming With Assertions). Assertion is a statement that let's you test your code in a structured way. If we for example want to verify that a variable email has the value ada@lovelace.net we write

1    assert email.equals("ada@lovelace.net");

Think of this as "saying" to Java: I am assuming that the variable email has the value "ada@lovelace.net". Can you check that for me. If I am wrong, force an error to occur.

If we translate the above (rather short) introduction to our MemberTest class we could write:

 1   public static void main(String[] args) {
 2     Member ada     = new Member("Ada", "ada@lovelace.edu");
 3     Member charles = new Member("Charles", "charles __AT__ babbage.net");
 4     Member bert    = new Member("Bert", null);
 5 
 6     assert ada.email().equals("ada@lovelace.edu");
 7     System.out.println("ada passed the email equals test.");
 8     assert charles.email()==null;
 9     System.out.println("charles passed the email is null test.");
10     assert bert.email()==null;
11     System.out.println("bert passed the email is null test.");
12   }

Note that the print statements about tests having passed will not be printed unless the tests pass! This is because the assert statements will crash the code before anythings is printed if the tests do not pass.

To compile the classes: javac net/supermegacorp/orgmanager/Member.java net/supermegacorp/orgmanager/test/MemberTest.java

To execute the class java -ea net.supermegacorp.orgmanager.test.MemberTest

Note: to enable the assertions you have to use -ea (enable assertions)

It is important to discuss tests a bit. What is it we've verified? Have we made sure our code (the Member class) always works as it should? Are we sure that our test code is correct? These are all questions that require, as we said above, a separate course or book so we will settle with some short remarks. With the tests above you have made sure the code does not crash when the tests were executed

You cannot say that the code always will work. But the tests above are at least better than nothing since they made us find some bugs.

Exercises on testing your classes

This exercise aims to make you familiar with how to write test code to test your classes.

You should Write test code to test the Passport class below

package org.police.passportsystem;

public class Passport {

  private String  name;
  private String  birth;

  public Passport(String name, String birth) {
    this.name  = name;
    this.birth = birth;
  }

  public String toString() {
    return name + " " + birth;
  }

  public String name() {
    return name;
  }

  public String birth() {
    return birth;
  }
}


1 Create a class PassportTest in the package org.police.passportsystem.test The class should have a main method.

2 Add, to the main method in the test class above, code to create a passport object with the following arguments "Ada Lovelace" "1815-12-10". It really seems a bit odd to create a passport for someone who died many years ago but we couldn't resist to give a bit of history lesson. Make sure that the name and birth is stored correctly in the object.

Hint: you could check if the name in the Passport object has the same content as the text you passed as argument. Do the same with email. It is easier to do this if you save the arguments in two String reference variables.

3 Verify (or test) that the toString method returns the correct string.

Hint: create a string yourself with the expected result and compare that string with the actual string returned from the method.

Solutions to Testing your classes

Expand using link to the right to see the full content.

1 Suggested solution

1 package org.police.passportsystem.test;
2 
3 public class PassportTest {
4 
5     public static void main(String[] args) {
6 
7 
8     }
9 }

To compile and execute the tests: javac org/police/passportsystem/test/PassportTest.java && java -ea org.police.passportsystem.test.PassportTest

2 Suggested solution

 1 package org.police.passportsystem.test;
 2 
 3 import org.police.passportsystem.Passport;
 4 
 5 public class PassportTest {
 6 
 7   public static void main(String[] args) {
 8 
 9     String name  = "Ada Lovelace";
10     String birth = "1815-12-10";
11     Passport p        = new Passport(name, birth);
12 
13     assert p.name().equals(name);
14     assert p.birth().equals(birth);
15   }
16 }

To compile and execute the tests: javac org/police/passportsystem/test/PassportTest.java && java -ea org.police.passportsystem.test.PassportTest

3 Suggested solution

 1 package org.police.passportsystem.test;
 2 
 3 import org.police.passportsystem.Passport;
 4 
 5 public class PassportTest {
 6 
 7   public static void main(String[] args) {
 8 
 9     String name  = "Ada Lovelace";
10     String birth = "1815-12-10";
11     Passport p        = new Passport(name, birth);
12     String expected   = name + " " + birth;
13 
14     assert p.name().equals(name);
15     assert p.birth().equals(birth);
16     assert p.toString().equals(expected);
17   }
18 }

To compile and execute the tests: javac org/police/passportsystem/test/PassportTest.java && java -ea org.police.passportsystem.test.PassportTest

Using our classes

Now that we have seen the rules on how to write classes with variables, constructors and methods, we'll look a little at how we use these classes in programs which actually do something. How do we write programs using our own classes?

  • Typically there is only one main in a separate class (not counting tests).
  • typically the Main class (class with the main) is in a separate package
    • typically you need a package statement here too because of that
    • typically you need import statements in the Main class (also because of that)
  • Other normal classes (classes we write that don't have the main method) often also uses other classes
    • If we write a class, it probably needs more classes which we also write ourselves
    • Related classes are in the same package and can "use" eachother without import (otherwise we need that)
    • One use can be as a return type for a method
    • Another use can be as the type of an instance variable

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) you will find some files which will be the basis for this exercise. The structure of the files looks like this:

.
`-- net
    `-- supermegacorp
        `-- orgmanager
            |-- main
            |   `-- Main.java
            |-- Member.java
            |-- test
            |   `-- MemberTest.java
            `-- ui
                `-- CLI.java

A short introduction to the files:

Member.java - a class representing our members

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

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

Main.java - a command line program to manage members


1 Read the source code in Main.java. In the source code you will find comments on 1) what's going on in the ode and 2) what you should add.


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

3 Compile and run the program Main. 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.

4 Add a loop to the main method in the Main class. The loop should make it possible for a user to continue adding or listing members.

5 Add an ArrayList at the beginning of the main method. This ArrayList should be used to store the members added by the user. What is a good type to use when declaring and creating the ArrayList?

Hint: Creating an ArrayList for storing String objects is done like this: ArrayList<String> strings = new ArrayList<>();. Your job is to do a similar thing for Members

6 Each time the user adds a member (mennu item: '1 - to add member') you should add that member to the list.

Hint: Adding a String, let's call it s, to a ArrayList, let's call it strings, for storing String objects is done like this: strings.add(s);. Your job is to do a similar thing for Members

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

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
  • we can write a program using the member class

Let's continue. We should now make our Main class a bit more object oriented and structured.

8 Declare the CLI reference as an instance variable instead of, as now, a local variable in the main method. Do the same thing with the ArrayList (or List). The code will not compile anymore, why?

Hint: Right now you have a local CLI reference and you create (using new) CLI object/instance in the main method. This should be removed. Move the declaration and creation/construction of the CLI object

9 Move the rest of the main method's code in to a new method, an instance method, called mainLoop(). The code compiles again - great! Try to execute it. Nothing happens, why?

10 Try to make this new method public at first and then private. Do both ways work? Why?

11 Create a Main object in the main method. Invoke the mainLoop() on that object. Try to execute it. Why does it work now?

There's a bit too much code in the mainLoop() method. Let's split it into smaller pieces.

12 Put the code that outputs the welcome message in a separate method. Invoke that method accordingly. This method can be private. Why?

13 Put the code that handles/manages the user command in a separate method. Invoke that method accordingly.

The main method is now easy to read. The mainLoop method is easier to read and understand. The new methods you've written are short and easy to understand. By the above steps we've turned our program into a program

  • easier to understand
  • easier to maintain
  • and actually easier to test

It is now possible to write tests, so let's do that


The program surely needs some more work. But for now we're happy with it and it's about time we continue with the next section.

Solutions to using your own classes

Expand using link to the right to see the full content.

1 not applicable

2 not applicable

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

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/Main.java && java net.supermegacorp.orgmanager.main.Main

4 Suggested solution:

        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();                                                                                                                               
                /*                                                                                                                                                              
                 * add the member to the ArrayList                                                                                                                              
                 */                                                                                                                                                             
            } 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 */                                                                                          
            } else if (command.equals("3")) {                                                                                                                                   
                cli.msgln("Good bye...");                                                                                                                                       
                System.exit(0);                                                                                                                                                 
            } else {                                                                                                                                                            
                cli.msgln("What do you want with your life?");                                                                                                                  
            }                                                                                                                                                                   
        }

5 Suggested solution

        /*                                                                                                                                                                      
         * Create an ArrayList to store Members                                                                                                                                 
         */
        ArrayList<Member> members = new ArrayList<Member>();

6 Suggested solution

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

7 Suggested solution

            } 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) {
                    System.out.println(m);
                }

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

net.supermegacorp.orgmanager.main;

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

public class Main{

    public static void main(String[] args) {
        /*                                                                                                                                                                                                                                                                                                                   
         * Create a CLI object to use when communicating with user                                                                                                                                                                                                                                                           
         */
        CLI    cli = new CLI();

        /*                                                                                                                                                                                                                                                                                                                   
         * Create an ArrayList to store Members                                                                                                                                                                                                                                                                              
         */
	ArrayList<Member> members = new ArrayList<Member>();

        /*                                                                                                                                                                                                                                                                                                                   
         * Give some starting message to the user                                                                                                                                                                                                                                                                            
         */
        cli.msgln("Starting Member manager");


        /*                                                                                                                                                                                                                                                                                                                   
         * 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();
                /*                                                                                                                                                                                                                                                                                                           
                 * 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) {
                    System.out.println(m);
                }
            } else if (command.equals("3")) {
                cli.msgln("Good bye...");
                // If the uses choses to quit, we might as well do that                                                                                                                                                                                                                                                      
                System.exit(0);
            } else {
                cli.msgln("What do you want with your life?");
            }
        }
    }
}

8 Suggested solution

public class Main{                                                                                                                                                              
                                                                                                                                                                                
    /*                                                                                                                                                                          
     * Create a CLI object to use when communicating with user                                                                                                                  
     */                                                                                                                                                                         
    CLI    cli = new CLI();                                                                                                                                                     
                        
    /*                                                                                                                                                                          
     * Create an ArrayList to store Members                                                                                                                                     
     */
    List<Member> members = new ArrayList<Member>();

The code does not compile since our main method refers to a CLI that does not exist. There used to be a local variable in the main method.

9 In the main method there's no code that does anything.

10 public or private does not matter when we're declaring methods. It matters when we're using the methods.

11 The main method now have that actually do something. The code creates a Main instance (object) and invokes the mainLoop() method which is basically the same code that was located directly in the main method before.

12 It can be private since we're invoking it from the same class. Suggested solutions

    private void showMenu() {
            /*                                                                                                                                                                  
             * 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");
    }


     .......

    	while(true) {

            showMenu();

      .....

13

    private void handleUserCommand(String command) {
        /*                                                                                                                                                                      
         * Based on command, take different actions                                                                                                                             
         */
        if (command.equals("1")) {
            Member m   = cli.readNewMember();
            /*                                                                                                                                                                  
             * 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) {
                System.out.println(m);
            }
        } else if (command.equals("3")) {
            cli.msgln("Good bye...");
            // If the uses choses to quit, we might as well do that                                                                                                             
            System.exit(0);
        } else {
            cli.msgln("What do you want with your life?");
        }
    }

    ......

       private void mainLoop() {
        /*                                                                                                                                                                      
         * Give some starting message to the user                                                                                                                               
         */
        cli.msgln("Starting Member manager");

	while(true) {

            showMenu();

            /*                                                                                                                                                                  
             * Read command from user                                                                                                                                           
             */
            String command = cli.askUser("Enter your choice");

            /*                                                                                                                                                                  
             * Handle user command                                                                                                                                              
             */
            handleUserCommand(command);
        }
    }


The complete class now looks a bit like this (please note that this is a suggested solution - you might have a different solution).

package net.supermegacorp.orgmanager.main;

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




public class Main{

    /*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
     * Create a CLI object to use when communicating with user                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
     */
    CLI    cli = new CLI();

    /*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
     * Create an ArrayList to store Members                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
     */
    List<Member> members = new ArrayList<Member>();


    private void showMenu() {
            /*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
             * 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");
    }


    private void handleUserCommand(String command) {
        /*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
         * Based on command, take different actions                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
         */
        if (command.equals("1")) {
            Member m   = cli.readNewMember();
            /*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
             * 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) {
                System.out.println(m);
            }
        } else if (command.equals("3")) {
            cli.msgln("Good bye...");
            // If the uses choses to quit, we might as well do that                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
            System.exit(0);
        } else {
            cli.msgln("What do you want with your life?");
        }
    }


    private void mainLoop() {
        /*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
         * Give some starting message to the user                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
         */
        cli.msgln("Starting Member manager");

        while(true) {

            showMenu();

            /*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
             * Read command from user                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
             */
            String command = cli.askUser("Enter your choice");

            /*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
             * Handle user command                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
             */
            handleUserCommand(command);
        }
    }

    public static void main(String[] args) {

        Main m = new Main();
        m.mainLoop();

    }


}

Looking at the source code for some API classes

Description

To demystify source code for classes we show briefly the source code for some classes in the API. We also show how to find the source code in src.zip (or how to get it).

  • java.lang.String (quick glance)
  • java.lang.System (quick glance)
  • java.lang.Math (quick glance - focus on private constructor and all the static methods)

Videos

  • No video for this section

Exercises on the source code for API classes

Locate the file src.zip on your system. It should be located in the Java installation directory, for instance under the JDK1.8XXX directory (the exact location might vary a little from computer system to computer system).

Copy this zip file to your home directory and some directory under it (we suggest that you start by creating a directory somewhere under your home directory and copy the zip file to that place).

Enter the directory where you put your copy of src.zip. Unzip the zip file (this will create a lot of new files and directories, which is why we strongly urge you to do this in a new directory dedicated for this exercise).

$ unzip src.zip

Let's study the directory structure from this zip file a little. It is the directory tree under the java directory we are most interested in. List the contents of the java directory.

  1. What are the folder names under the java directory?
  2. Do you recognize any of the folder names from package names?
    Change directory to the relative path java/lang/. List the file String.java:
    $ cd java/lang
    $ ls String.java
    
  3. Does the file java/lang/String.java exist?
    Let's look inside the file. You may open String.java in an editor, or if you think it is easier to stay in the terminal, you can use a pager, such as less (to move around a file in less, use the arrow keys, to quit you press "q").
  4. What is the package declaration in the source code for String?
  5. How many methods called toUpperCase can you find in the source code for String?
  6. Locate the definition of the method toUpperCase() (with no arguments). What is the return type of that method?
  7. Go to the online API documentation for String (use a search engine if you don't know the address by heart). How many methods called toUpperCase does the documentation list?
  8. Also in the online API documentation, lookup toUpperCase() (with no arguments). Can you see that the return type is the same as in the source code?
  9. Now, look at the source code for java.lang.System. Are there any constructors?
  10. What is the access modifier for the constructor(s)?
  11. Can you create an instance of System? (If you don't know the answer from looking at the constructors, try to write a small program and to the following in the main method: System sys = new System();)
  12. Locate the class variable with the name out. What is the full declaration of out?
  13. Can we use out from another class?
  14. Have you used out before?
  15. How would we use out from another class?
  16. What is the full class name (the qualified name) of the class which out is a reference to?
  17. How can the System class use the name PrintStream which is defined in another class in another package?
  18. Next, look at the source code for java.lang.Math. Can you create an instance of Math? Why?
  19. Are there any instance methods in java.lang.Math?
  20. How many methods are called max?
  21. What are the differences between the various versions of max()?

Solutions to looking at the source code for API classes

Expand using link to the right to see the full content.

For Java 7, the online API is located at [1] and for Java 8, it is located at [2]

  1. The folder names under the java directory are applet beans lang net rmi sql time awt io math nio security text util
  2. You might recognize lang, and util which are part of the packages java.lang and java.util. Remember, package names are relative paths, so the package java.lang correspond to the path java/lang and the package java.util correspsyith the java installation in the file rt.jar).
  3. Yes, it exists!
  4. package java.lang;
  5. 2 methods exist with the name toUpperCase
  6. The return type of the toUpperCase() method is String
  7. The documentation also lists two methods called toUpperCase
  8. Yes, the return type is listed as String also in the documentation
  9. Yes, there is one constructor in java.lang.System, private System(){}
  10. The access modifier of the only constructor in System is private
  11. It is not possible to call a private member of a class, and since the only constructor is private, it is not possible to create an instance of java.lang.System!
  12. The full declaration of out in System is: public final static PrintStream out = null;
  13. Yes, out is declared public, so we can use it from another class!
  14. out is declared public static, so the syntax from another class would be System.out
  15. Yes, you have used it many times, for printing to the standard out stream like this: System.out.println("hello");, for instance. The variable out is of type "reference to PrintStream" and PrintStream has an instance method called println which you have used many times.
  16. The out is of type reference to java.io.PrintStream
  17. The class System has an import statement saying import java.io.*; which allows System to use the shorter class name PrintStream for java.io.PrintStream
  18. No, you cannot create an instance of Math, because the only constructor is private.
  19. No, there are only static (class) methods in java.lang.math
  20. There are four methods in java.lang.Math called max
  21. The parameters differ. Max returns the greater of two numbers, and is defined for comparing:
    1. Two int numbers
    2. Two long numbers
    3. Two float numbers
    4. Two double numbers

Other keywords related to elements declared in classes

Description

The following keywords also exist in the Java programming language:

  • volatile (variable modifier)
  • native (method modifier)
  • transient (variable modifier)
  • synchronized (method modifier)
  • protected (access modifier - both variables and methods)
  • strictfp (method, class and interface modifier)
  • and more...

We will not use any of the ones listed above in this course/book. If you want to read about them, we refer to the Java Language Specification If you want the full list of java keywords, please see here.

Strings are immutable

Videos for immutable Strings

Description

Some objects are impossible to change once they are created. Such objects are said to be "immutable". Strings are examples of such objects. Once you have created a String, you cannot change it.

There are instance methods in the class String which might lead you to think that you can change a String object, such as toLowerCase(), concat(), append() etc.

But every such instance method in the class String actually returns a reference to a brand new String object with a state as if the previous object would have been changed!

Some examples might be in place:

// Change all letters to lower case:
String city = "Liverpool";
String lowerCaseCity = city.toLowerCase();

In the above example code, we must save the result of calling toLowerCase() in a variable if we are to use it. Simply calling city.toLowerCase() doesn't change the textual contents of the String referred to by city. So, this code below would compile but not change anything:

// call toLowerCase()
String city = "Liverpool";
city.toLowerCase();
// city is still representing "Liverpool" with a capital "L"

The append() method works in a similar way:

String city = "Liverpool";
String cityAndCountry = city.concat(", Great Britain");

Simply calling concat() on a String reference doesn't change the String referred to by the reference. It actually creates a new String object with the contents produced from concatenating the original String with the argument, and returns a reference to this new String. All instance methods in String works this way.

By the way, how do we check if two reference variables refer to the same instance/object? We can use the == operator!

When applied to reference variables, the == operator compares the "address" (reference) of the two variables, which means "do they refer to the exact same object in memory?".

Consider the following snippets:

 Member first  = new Member("James");
 Member second = first;
 Member third  = new Member("James");
 System.out.println(first == second); // true  - same object!
 System.out.println(first == third);  // false - same name but different objects!
 System.out.println(second == second);// true  - same object!
 System.out.println(second == third); // false - same name but different objects!

And, using Strings:

 String name = "James";
 String fullName = name.concat(" Brown");
 System.out.println(name==fullName); // false - concat creates a new object!
 System.out.println( name==name.concat(" Stewart") ); // false - concat creates a brand new object!

If we want to check whether two different objects should be considered "equal" to each other, we must use a method called equals() instead. String has a functioning version of this method:

 String name = new String("James");
 String otherName = new String("James");
 System.out.println( name.equals(otherString) ); // true - different objects, but the same contents!

Bonus information (you may skip this part if you want, it is only for the keen students who want to know the whole truth):

There is actually something special with some immutable classes like String. Java maintains a pool of String objects created with the special syntax with double quotes: String s = "Some text";. Since Strings are immutable and will never change, they can be reused by the runtime system. So creating two Strings using the double quote style with the same text, actually only creates one String object! So this would print true:

 String one = "Hello";
 String two = "Hello";
 System.out.println(one==two); // true - actually only one object is created and reused...

But using the operator new when creating a String really creates a brand new object, regardless of the pool of reusable existing String objects. So the following will print "false":

 String one = "Hello";
 String two = new String("Hello");
 System.out.println(one==two); // false - the operator new forces the creation of a new object!

Exercises on immutable Strings

  1. What is printed from this small snippet?
    String name = "Anton";
    name.toUpperCase();
    System.out.println(name);
    
  2. Change the snippet above so that it prints ANTON instead, with the help of toUpperCase().
  3. A private instance variable of type reference to String exists in a class Passport. The variable is called passportNumber. A public instance method passportNumber() returns a reference to the variable. Can code from a different class change the value of the passportNumber instance variable of any Passport object?
  4. You want to make a class immutable. How would you change the changeColor() method? The only constructor for the class is included.
    // The only constructor
    public SomeClass(Color c){
      this.color = c;
    }
    // The method to change color:
    public void changeColor(Color c){
      this.color = c;
    }
    

Solutions to immutable Strings

Expand using link to the right to see the full content.

  1. "Anton" is printed. toUpperCase() doesn't change the string, it creates a new one
  2. Suggestion: System.out.println(name.toUpperCase());
  3. No, even if code in other classes can get a reference to the instance variable, it cannot change it. Strings are immutable, and since the variable is private, no code can re-assign it to a new String either.
  4. This is one solution, make the method return a new instance with the new color as argument to the only constructor:
    public SomeClass changeColor(Color c){
      return new SomeClass(c); // don't change the object, return a reference to a new one instead!
    }
    

Chapter Links

External links

Books this chapter is a part of

Programming with Java book

Book TOC | previous chapter | next chapter