Chapter: Classes II

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 II - classes continued

Declaring variables

Description

Let's pretend we should develop a system to manage Passports in a country. The system will become big enough to divide into smaller parts. We call these smaller parts sub systems. One of these sub systems could be an abstraction of Passport - the software representation of the physical passport. Each individual passport need to hold information, in some way, associated with the person for which the Passport is made. The information could be birth date, height, color of hair etc. To store this kind of information in an object (a Passport instance) we need to have variables in the objects. These variables have values specific to each object - just as in real life - if one person is 190 cm long another person may not be 190 cm long.

Variables declared inside a class generally serve two purposes. The most common purpose is that instances of the class (objects created from the class, that is object whose type is the class) must have a way to save their state. A class can be seen as a complex type describing a group of similar objects (such as for instance files) and the operations these objects can perform. A file object, for instance, can have an absolute path and a name. This is true for all files (they all have names etc). So the class can be used to describe common treats for these objects. The name is stored in a variable, and since we want to allow for many instances of files to have different names, this type of variable is called "instance variable".

So, instance variables exist to allow many objects to have different values (state) for a common treat. The other type of variable which can be declared in the class block, is "class variable" (also known as "static" variables). These variables exist to store information about the type (or class) itself, which is the same for all instances. For instance, elaborating on the File class, all file objects in a file system use the same character as "file separator" (on our systems it is / ). There is no point in storing this common value in the memory space of every object we create (since it is the same value for all objects, and it will probably not change). Using the keyword static we can declare a variable in a class which has these characteristics: It will only be stored in one place when the program is running, and it is not specific to any particular one object. In fact, static variables are available even before any object of a class has been created.

A file class could declare a variable like this (leaving out the rest of the class declaration and focusing only on the variables):

1     int nrStudents;

If you do not assign a variable (in a class block) a value Java assigns them a value, a so called default value. For more information about default values read this: Default values in Java. Local variables (variables declared in e.g. a method) are not given default values and need to be initialized before use.

Videos

Exercises on variable declaration

We need to keep track of the members in the organisation, anything from a sport club to a car pool organisation. To do this we need the class Member which we're going to use as an abstraction of the actual (physical) members. Each physical member will have a corresponding Member instance (an object) in our system. We don't have knowledge yet on how to store many things of one kind in a good way, but that doesn't matter since the focus right now is to discuss and develop the Member class, so let's begin.

  1. Create a class, Member in the package net.supermegacorp.orgmanager. Putting your class in a package also means you have to have a corresponding directory structure. So your file Member should be located in the directory structure like this net/supermegacorp/orgmanager
  2. Create a test class MemberTest, with a main method. You should use the package name in the package net.supermegacorp.orgmanager.test. In the main method you should create a Member instance.
  3. Compile and run the MemberTest class. Don't expect anything to happen.
  4. Output, using println, the instance/object in the MemberTest class' main method (after you create the object of course).
  5. Compile and run the code. You should see a printout that contains your class name Member followed by @ and some digits. We will look at how this printout will change through this lecture and in the coming lecture about inheritance we will reveal how the printout is done. What we can tell you right now is that there is no magic or philosophy behind this.
  6. Add, to the Member class, a instance variable, name with the type String. Make sure to use the public keyword. The keyword public makes it possible for everyone to read/write the variable.
  7. Compile and run the test class again. Nothing should have changed from above.
  8. In the test class' main method you should output the name variable of the Member instance.
    To access a public member (like the name variable) of an object, use the reference variable and a dot:
    myMember.name (if the reference variable is called myMember)
    You can put that between the parentheses of println (you give it as argument to println).
  9. Compile and run the test class again. What value of name was printed?
    the variable name seems to have gotten, or actually really gotten, a value (null) even if we didn't assign it that. See Default values in Java for more information
  10. Set the value of the name variable in the object to "Ada". Do this in the main method of MemberTest.
    To access a public member (like the name variable) of an object, use the reference variable and a dot:
    myMember.name = "Ada"; (if the reference variable is called myMember)
  11. Compile and run the test class again. What value of name was printed?
  12. Remove the code that outputted the value of name.
  13. Now, we will ask you to just do as we tell you - without really fully understanding what happens. You will get a nice result and in the coming lecture on Inheritance you will get an explanation on what's going on. Add the following code to your Member class' definition:
    1     public String toString() {
    2        return name;
    3     }
    
  14. Compile your code and run the test class again. What happened to the initial output of the Member object?
  15. Now add another instance variable, email, also of the type String to the Member class.
  16. Compile your code and run the test class again. What happened to the output of the Member object?
  17. Change the toString method in the Member class to look something like:
    1     public String toString() {
    2        return name + " " + email;
    3     }
    
  18. Compile your code and run the test class again. What happened to the output of the Member object?
  19. In your main method, change the value of the email address to "ada@lovelace.net"
  20. Compile your code and run the test class again. What happened to the output of the Member object?

We now have a class Member that can be used in our Member management system. Objects creating from that class will have two instance variables, name and email. This means we (potentially) could continue writing our system. But to write such a system we need more knowledge, so in this course we will do a thing similar to the Member class, to continue practicing on the newly learnt concepts.

We will continue practicing by developing a class for a system for the police to keep track of the passport of a country's citizens.

An interesting difference between a Member and a passport is that in the former case, the Member, it feels natural to be able to update variables in an object. If, for example, a Member change email address it is still the same member so it feels natural that the system shall allow that as well. If we, on the other hand, want to update a passport, let's say we need to change the social security number of a person (to which the passport belongs) we'll need to create a new physical passport (not an updated). Should this be reflected in the code of the class? This is a question we (the authors) would love to spend more time on but right now we don't have the tools so we (the authors) will skip it.

  1. Create a class, Passport in a package called org.police.passportsystem The class should have a public String variable, name, to represent the person's (to which the passport belongs) name and a String variable, birth, which represents the birth date of the person. Note: it is a good practice to use specialised classes to store dates, such as the Calendar class. We don't do this at the moment since we want you to be able to feel familiar. We also want you to see later on that the choice of String is not the best approach. Some authors and developers use Strings even when storing dates in a database. This is wrong for may reasons, the most obvious being that we can't trust the data in our database doing so. We aim at making you aware of the danger of doing so and give you an alternative solution.
  2. Write a test class, PassportTest in the package org.police.passportsystem.test, that creates a Passport instance and sets the name of the Passport to "Adam" and the birth date to "1994-01-01".

  3. Now, compile both classes.
  4. It is "rather" unlikely, actually it would not make sense at all, that we need to change the birth date of a person, so we suggest that you change this variable to be private instead. And while we're at it, change the name variable to be private as well. Compile the class Passport (only that class).
  5. Now, compile the PassportTest class as well. Will it work?

Not being able to assign or read values from an object makes the keyword private useless, doesn't it? On the contrary. We recommend setting as many variables as possible as private. How to set them then? One way would be a method many authors and teachers like called Getters and Setters. We do not recommend this at all - at least not in the way it is most commonly taught. Instead we recommend setting the values in a constructor, which is, by pure coincident, the subject of the next section.

Solutions to variable declaration

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


You can find complete source code to the suggested solutions below in the declaring-variables directory in this zip file or in the git repository.

  1. Since the class name should be Member the following code must be stored in a file called Member.java. The content can be:
    1 package net.supermegacorp.orgmanager;
    2 
    3 public class Member {
    4 
    5 }
    
  2. Since the class name should be </code>Member</code> the following code must be stored in a file called Member.java. The content can be:
     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 
     9         Member m = new Member();
    10     }
    11 
    12 }
    
  3. To compile: javac net/supermegacorp/orgmanager/Member.java net/supermegacorp/orgmanager/test/MemberTest.java
    To execute: java net.supermegacorp.orgmanager.test.MemberTest
    If you want to do the above on one line and execute only if compilation succeeds: javac net/supermegacorp/orgmanager/Member.java net/supermegacorp/orgmanager/test/MemberTest.java && java net.supermegacorp.orgmanager.test.MemberTest
  4. Add a normal println with the object as argument.
     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 
     9         Member m = new Member();
    10         System.out.println(m);
    11 
    12     }
    13 
    14 }
    
  5. The printout looks like this:
    net.supermegacorp.orgmanager.Member@717da562
  6. Add the String declaration in the class scope, like this:
    1 package net.supermegacorp.orgmanager;
    2 
    3 public class Member {
    4 
    5     public String name;
    6 
    7 }
    
  7. To compile: javac net/supermegacorp/orgmanager/Member.java net/supermegacorp/orgmanager/test/MemberTest.java
    To execute: java net.supermegacorp.orgmanager.test.MemberTest
    .. and no changes in the printout.
  8. To access the name variable in an object we use the . like this m.name. Printing it out will make the main code look like this:
    1     public static void main(String[] args) {
    2 
    3         Member m = new Member();
    4         System.out.println(m);
    5         System.out.println(m.name);
    6 
    7     }
    
  9. null can be seen on the screen. Since we have not assigned a value to name the default value (null) is still in use. When printing a reference with the value null, java simply outputs null.
  10. An example of how this can be done:
    1     public static void main(String[] args) {
    2 
    3         Member m = new Member();
    4         m.name = "Ada";
    5         System.out.println(m);
    6         System.out.println(m.name);
    7 
    8     }
    
  11. Not so surprisingly "Ada" was printed.
  12. An example of how this can be done is to turn the code into a comment:
    1     public static void main(String[] args) {
    2 
    3         Member m = new Member();
    4         m.name = "Ada";
    5         System.out.println(m);
    6         //System.out.println(m.name);
    7 
    8     }
    
  13. The class now looks like this
     1 package net.supermegacorp.orgmanager;
     2 
     3 public class Member {
     4 
     5     public String name;
     6 
     7     public String toString() {
     8         return name;
     9     }
    10 
    11 }
    
  14. The output now looks like this: Ada
    Note that the same output before was net.supermegacorp.orgmanager.Member@717da562
    With the method toString an object defines its own String representation.
    Note that toString does not print anything. It returns a string.
  15. The class now looks like this
     1 package net.supermegacorp.orgmanager;
     2 
     3 public class Member {
     4 
     5     public String name;
     6     public String email;
     7 
     8     public String toString() {
     9         return name;
    10     }
    11 
    12 }
    
  16. The output now looks like this: Ada. The value of email is not part of the String returned by the toString method.
  17. The Member class now looks like this:
     1 package net.supermegacorp.orgmanager;
     2 
     3 public class Member {
     4 
     5     public String name;
     6     public String email;
     7 
     8     public String toString() {
     9         return name + " " + email;
    10     }
    11 
    12 }
    
  18. The output now looks like this: Ada null. Since the variable email has not been assigned a value, the default value null is still assigned and thus printed.
  19. The MemberTest code now looks like:
     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 
     9         Member m = new Member();
    10         m.name = "Ada";
    11         m.email = "ada@lovelace.net";
    12         System.out.println(m);
    13         //System.out.println(m.name);                                           
    14 
    15     }
    16 
    17 }
    
  20. The output now looks like this: Ada ada@lovelace.net. Since the variable email has been assigned the value "ada@lovelace.net" which is printed alongside the value of name.
  21. The passport class will look something like this:
    1 package org.police.passportsystem;
    2 
    3 public class Passport {
    4 
    5     public String name;
    6     public String birth;
    7 
    8 }
    
    The PassportTest class will look something like
     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         Passport p = new Passport();
    10 
    11 	p.name  = "Adam";
    12 	p.birth = "1994-01-01";
    13 
    14     }
    15 
    16 }
    
  22. Compile and execute like this: javac org/police/passportsystem/Passport.java org/police/passportsystem/test/PassportTest.java && java org.police.passportsystem.test.PassportTest
  23. The Passport class will now look something like
    1 package org.police.passportsystem;
    2 
    3 public class Passport {
    4 
    5     private String  name;
    6     private String  birth;
    7 
    8 }
    
    Compilation of the Passport class (javac org/police/passportsystem/Passport.java) succeeds.
  24. Compilation of the PassportTest class (javac org/police/passportsystem/test/PassportTest.java) fails with the following message:
    1 org/police/passportsystem/test/PassportTest.java:12: error: name has private access in Passport
    2         p.name  = "Adam";
    3          ^
    4 org/police/passportsystem/test/PassportTest.java:13: error: birth has private access in Passport
    5         p.birth = "1994-01-01";
    6          ^
    7 2 errors
    
    We are not allowed to change the values of private variables.
    Note that the error message may differ between different compilers and versions

Section: defining constructors

Description

This section deals with constructors and the syntax and semantics involved. As constructors are one of the basic building blocks for (many) classes, it is important to learn and understand how to declare them.

The role of constructors is to define how objects can be created from the class. Constructors are run (the code in the constructor executes) when client code creates an instance from a class using the operator called new as we have seen in previous chapters. It is often in the constructor objects are given their initial state. It is common that a constructor declares parameters so that client code can pass the initial values for the initial state of the object.

To allow for flexibility, a class can declare more than one constructor where the constructors differ in the type and number of arguments which can be passed to the constructor. This can provide means to create objects passing along varying information to the initial state. To declare variations of constructors or methods in this way, is sometimes called overloading.

The class java.lang.String serves as an example. It declares many overloaded constructors, which gives us flexibility in how we can construct a String object. There is one constructor without any parameters at all which simply creates an "empty string". Then there is one constructor which takes a reference to another String object as the only parameter, which allows us to create a new String as a copy of some other existing String object. And then there's one constructor which takes a reference to an array of char as the argument, which allows us to create a String object representing the characters found in the array.

If we don't declare any constructors at all, the compiler will actually provide one for us (with no arguments). This is a kind of convenience feature, which allows us to create new objects (with no data provided for initial state) without having to write code for a constructor which seemingly does nothing in the body of the constructor. If we declare at least one constructor, the compiler won't, however, provide any constructor for us.

When we say that the compiler provides a constructor for us, it is important to know that the constructor will be inserted in the resulting class file from the source code file. The compiler will not ever insert any code in our source code files!

The syntax of a constructor is:

<access-modifier> ClassName(<parameter-list-if-any>){
  <code-for-initialization>
}

Typically, the access modifier is the keyword public which means that the constructor is available in client code in any class in a program. It is common to have public constructors, since many classes exist for client code to create objects which they need to solve some task. As you will see in sections below, there are more access modifiers than public which limit what client code is allowed to use the constructor. The "ClassName" in the simplified syntax above, means that the constructors "have the same name" as the class. Alternatively you could think of the class name as the "type" created by the constructor. Regardless, it is a strict syntax rule that the constructor must have the class name before the parentheses around the parameter list.

The parameter list may be empty. If the constructor has parameters, which are placeholders for any values provided as arguments when creating a new object, they are a comma separated list of the form type variableName. Here's an example of a no-arguments constructor:

public Greeter(String greeting){
  // do something with greeting
}

In the example above, we show a constructor for the class Greeter, with the access level public (the constructor can be used in code from any other class), and a parameter list of one single argument, a reference to a String which will be referred to in the body of the constructor as greeting. When you declare arguments, the arguments become local variables which may be used only in the body of the constructor (or method). Let's explain that with other words! The purpose of parameters is to allow for data to be passed from one place in a program (perhaps in the main method) to code in some other place (perhaps a constructor in a class in some package imported from main). The calling code "passes data as arguments" to, for instance, code for a constructor in another class. The code for the constructor must have a way to reference (talk about) the data sent to it. This is done by declaring parameters with a type and a name, like String greeting in the constructor example above. The parameter greeting will work like a variable local to the constructor code. It will get it's value when and if some other code calls the constructor using the operator new. A local variable, as we've discussed in previous chapters, is a variable that is known only in some block, and as a result only can be used in that block.

A closer look at the Greeter example. The purpose of a constructor, as the one above, is to give the object being created by the constructor, an initial state. This is often done via parameters whose values are stored in an instance variable for the class. See the section about instance variables for information on the syntax and semantics of instance variables. Let's say that an instance of class Greeter is capable of responding to the action (or message) greet(). The object could for instance play a sound with a greeting phrase or output some greeting to standard out. But where is this greeting phrase stored, and how does it get initialized?

An object typically stores the data it needs to accomplish some task in so called instance variables declared in the class. Typically, it is the role of the constructor to accept the initial value of the data via some parameter. The block of the constructor then uses the parameter name as a variable and assigns the value to some suitable instance variable.

In order for the code in the constructor to be able to refer to instance variables, Java uses the keyword this and a dot and then the name of some instance variable. Let's take a closer look at how the Greeter class could accomplish this:

public class Greeter{
  private String greeting; // instance variable to store this object's greeting phrase

  // Constructor accepting a String reference to some greeting text
  public Greeter(String greeting){
    // store the parameter in the instance variable
    this.greeting = greeting;
  }
}

You may be surprised that the instance variable has the same name as the parameter. This would be very problematic if it wasn't for the keyword this! But using the keyword this, we have a way to refer to instance variables of the object being constructed by the constructor! You can read the code this.greeting out loud as "this object which is being constructed here has an instance variable called greeting". Now, the statement this.greeting = greeting; clearly (we hope) means that this object's instance variable "greeting" should be assigned the value of the parameter "greeting". Now, we also learn that when the name "greeting" is being used without the "this" keyword, it refers to the parameter.

One way to think about that is that when we use a name in some block of code, the name refers to the variable declared in the closest scope (or block). The parameter named "greeting" is declared in the constructor which must be the closest block/scope. The instance variable with the same name is declared in the enclosing block (the block of the class definition). So when we use the name "greeting" in the constructor, we mean the parameter. It is the "closest" variable declared with that name. If we want to use the instance variable with the same name, we had to use the "this" keyword since the instance variable is not declared in the closest scope.

Wouldn't choosing different names for the parameter and the instance variable solve all this without the need for the this keyword, you may ask. Of course, but this is a book about Java and the way Java works, so we don't want to give you the (false) idea that the parameter names must differ from the instance variables. In fact, we could use the this keyword anyway to refer to an instance variable with a different name. The this keyword always refers to "this object". Here's the same class with a different name of the parameter:

public class Greeter{
  private String greeting; // instance variable to store this object's greeting phrase

  // Constructor accepting a String reference to some greeting text
  public Greeter(String aGreeting){
    // store the parameter in the instance variable
    this.greeting = aGreeting;
  }
}

Here, the parameter is called "aGreeting" and the instance variable is called "greeting". So we don't have to use the this keyword, but we can anyway. Using this, even though we don't have to, could help to make the code clear actually. Now that we told you, you know that this.greeting must refer to an instance variable. There are other uses of this but this is not the place to talk about those. The important part here is that this always means "this object".

Note that in all the examples above, we didn't include a package statement, to keep our code focused on the constructor. Normally it would be the first line of code in the source code file and precede the class declaration public class Greeter{ . And, by all means, note that objects created from the stupid example class can't actually do anything with the greeting text, since we haven't written any methods with code that does something with the instance variable. The objects would only store some text but we could never access that text. We apologize for this.

Videos

Exercises on declaring constructors

As usual, start by creating a directory for these exercises, so that you keep your home directory organized. Use cd to enter that directory.


1 Let's look a bit again at the Greeter class we've used before. Now it is you who should write it. Create the following directory structure:

.
`-- org
    `-- progund
        |-- greet
        `-- main

You can create the whole structure using only two commands:

$ mkdir -p org/progund/greet/
$ mkdir -p org/progund/main/

In the "org/progund/greet/" directory, create a Java source code file with the declaration of the Greeter class. It should have a package declaration as the first line in the code declaring that the class belongs to the package "org.progund.greet". The class should be publi and have the name Greeter. This means, as usual, that the source code file must be called "Greeter.java". Write an instance variable declaration for a reference to a String containing a greeting text. You may use the same name for the variable as in the example.

2 Now we're leaving the Greeter class and will move on to another task. Write a Member class like the one below. Compile the class from the exercise directory (meaning that you need to give the compiler the relative path to the source code file). Make sure it compiles.

package net.supermegacorp.orgmanager;
 
 public class Member {
 
     public String name;
     public String email;
 
     public String toString() {
         return name + " " + email;
     }
 
}

3 Let's go back to your test class again. Now we're going to rewrite it - it's going to become smaller. Write your main method like this:

 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   
 9          Member m = new Member();
10          System.out.println(m);
11  
12     }
13 }

4 Compile the Member and MemberTest classes and run the MemberTest program again.

5 Write a public constructor with no arguments to your Member class. The instance variables should be made private.

6 Compile the Member and MemberTest classes and run the MemberTest program again. Did you see a change in the printout? Why?

7 In order to set the instance variables we need to pass information to the constructor. Add two parameters (String newname and String newEmail) to your member constructor. Use these parameters to set the corresponding instance variables. Recompile your classes and run the memberTest program again. Why does the compilation fail?

8 Add the arguments "Ada" "ada@lovelace.edu" in your MemberTest code, where your're creating the Member instance. Recompile your classes and run the memberTest program again

9 Make sure that the parameters to the Member constructor are called the exact same thing as the instance variables. That is the code should look something like this:

  public Member(String name, String email) {
    name  = name;
    email = email;
  }

Compile and execute the code again. Did it work?

10 Now change to the following in your constructor:

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

Do you see the word this? This word this is used to say to the compiler that we, the developers, mean the instance variable name and not something else. Do you think that the code will work now?

11 Ok, but what about email. If a Member has no email address, what do we do then? We suggest (for now) to add a constructor with only one parameter (the new name).

12 Nice. But some code is duplicated, see below:

1   public Member(String name, String email) {
2     this.name  = name;
3     this.email = email;
4   }
5 
6   public Member(String name) {
7     this.name = name;
8   }

Is there a way for us to reuse the code of one constructor in another? Yes, there is. We will show you how to do it and we ask you to think about it and in coming Passport exercise implement it yourself.

1   public Member(String name, String email) {
2     this(name);
3     this.email = email;
4   }
5 
6   public Member(String name) {
7     this.name = name;
8   }

On line 2 we call the other constructor with the name argument. What about the keyword this - wasn't it used to instruct Java that we (the developer) want to refer to an instance variable? Well, it is but think of this as something referring to the current object - in the case of a constructor it is the object being created and in the case of a method it is the object on which the method was invoked).

But if we use different name (e g newName and name) we will not need to use this? Well, you're right but we (the authors) believe it is good to use this to make it easy for someone reading our code that we for example mean to use the instance variable).

13 Email address - do we know something about email addresses? It always contain the @ sign. So an email address without @ is not an email address. Should we allow someone to set an invalid email address? No, we should not so let's add code to check if there is an @ present in the email address given as argument to the constructor (see below):

1   public Member(String name, String email) {
2     this(name);
3     // Check for valid email 
4     this.email = email;
5   }

Compile and execute the code again to make sure it works.

HINT: The String class has an instance method called contains(String) which checks if this String contains the String given as argument. So, "Liverpool".contains("p") returns (is evaluated to) true

14 In your TestMember class we want you to create one more Member with the name "Charles" and the (invalid) email address "charles _AT_ babbage.net". This code will check if your email checking code works - i e the new Member should not have an email address "stored" since the one above is invalid.

Ok, good we now have made sure that the emails address must be correct. But for a developer using our Member class it might be useful, or even crucial, to be notified if a method invocation (calling a method) went wrong. How do we do this? Some authors have a tendency to use return values for this but we will use the preferred way in Java, which is using something called Exceptions which is dealt with in coming chapters.

15 Try adding a new member (in your Membertest class, like this:

1       Member noone = new Member("Dummy", null);
2       System.out.println(noone);

Will it work? Why?

We will deal with writing robust code in future chapters. With robust code we mean code that will "always" work.

16 In the previous section you wrote a Passport class and a class to test that. We now want you to add the following to the Passport class:

  • make the variables (name and birth) private
  • a constructor with name and and birth as parameters

Compile the Passport class to make sure it is (syntactically) ok.

17 Try to compile the "old" PassportTest class. Do you think it will work? Check and see. 18 Change the TestpassportTest class so that it creates a Passport using the new constructor.

Output the Passport object using System.out.println.

19Add a method (public String toString()) like you did in Member. Compile both classes and make sure it compiles and executes properly.

Solutions to declaring constructors

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


You can find complete source code to the suggested solutions below in the defining-constructors directory in this zip file or in the git repository.

1 For instance, your class could look like this:

package org.progund.greet;

public class Greeter{

  //Instance variable to hold the greeting text:
  private String greeting;

  // Constructor accepting a String reference to some greeting text
  public Greeter(String greeting){
    // store the parameter in the instance variable
    this.greeting = greeting;
  }
}

Note that this stupid class can't do anything with the greeting text, because we have't written any methods yet!

2 The Member class should look a bit like this:

package net.supermegacorp.orgmanager;
 
 public class Member {
 
     public String name;
     public String email;
 
     public Member(String name) {
          this.name = name;
     }

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

3 The MemberTest class should now look like this:

package net.supermegacorp.orgmanager.test;

import net.supermegacorp.orgmanager.Member;

public class MemberTest {

    public static void main(String[] args) {

        Member m = new Member();
        System.out.println(m);
    }

}

4 No solution suggested needed. To compile and execute the MemberTest class you type the following:

javac net/supermegacorp/orgmanager/test/MemberTest.java net/supermegacorp/orgmanager/Member.java && java net.supermegacorp.orgmanager.test.MemberTest
null null

5 It is hard to find something meaningful in doing the below:

  public Member() {
  }

But we will use the results to "prove" a point.

6 The printouts are the same. We wanted to show you that if you do not write any constructor, Java creates a default constructor for you - a constructor with no arguments.

7 Your Member class should now look something like the following:

package net.supermegacorp.orgmanager;

public class Member {

  private String name;
  private String email;

  public Member(String newName, String newEmail) {
    name = newName;
    email = newEmail;
  }

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

}

The compilation fails since we're calling a constructor with no arguments and we have no such constructor. But before we added any constructor, in the earlier section about declaring variables, we could call such a constructor. Java is kind enough to add a constructor with no parameters for us if (and only if) we haven't written any constructors. This automatically added constructor is called default constructor. You will see in some literature and examples that you "must" write such a constructor yourself but don't be fooled by this. People claiming this do not know the rules of Inheritance well enough to teach it. But don't worry - WE DO ;) So how do we solve the failing compilation? Let's look at the next exercise.

8 The MemberTest class should look something like this:

package net.supermegacorp.orgmanager.test;

import net.supermegacorp.orgmanager.Member;

public class MemberTest {

    public static void main(String[] args) {

        Member m = new Member("Ada", "ada@lovelace.edu");
        System.out.println(m);
    }

}

9 It will compile but not function the way we want since we're setting the value of the instance variables to the values of the instance variables. Setting the value of a variable x to the value of just x will not change a thing Same here. Java uses the last "known" or "seen" declaration of a variable - and between the instance variable and the parameter the latter is the last one seen, so the parameter is used.

10 It will work now, since we're setting the value instance variable name (we're using this.name to name which, without the this keyword, refers to the parameter named name

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

11 The new Member constructor should look something like:

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

Note that this constructor only sets a value to name and leaves email unset. What happens with our nice toString method? We'll skip the discussion on this since we'll be doing that in the chapter on inheritance.


12 Nothing to do in this exercise.

13 Suggested solution:

  public Member(String name, String email) {
    this(name);
    if (email.contains("@")) {
      this.email = email;
    }
  }

Lets anaylse the following a bit further:

    if (email.contains("@")) {
      this.email = email;
    }

The code email.contains("@") invokes the contains method with "@" as argument. Doing this means we state that the email object contains "@". If the String email contains "@" the statement is true and if "@" was not present the statement is false.

So when we write

    if (email.contains("@")) {

We only reach inside the block if the email address really contains "@" (i e the statement is true), so we can now to the instance variable email assign the value of the argument, which is done here:

      this.email = email;

and of course we need to "close" the block:

    }

14 Suggested solution:

      Member charles = new Member("Charles", "charles __AT__ babbage.net");
      System.out.println(charles);

15 It will not work since the line

    if (email.contains("@")) {

will be executed. Since email is null we get a runtime error (actually a so called exception). More on this in coming chapters. We just wanted to give you a heads-up.

16 Suggested solution:

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

17 The PassportTest class calls the default constructor (a constructor with no arguments automatically created by Java if (and only if) there are no constructors defined by the developers in the class). Since there no longer is such a constructor - since we have defined a constructor by our own (and Java only creates a default constructor with no arguments if and only if there are no constructors as defined by the developer) - the compilation will fail.

18 Suggested solution:

package org.police.passportsystem.test;

import org.police.passportsystem.Passport;

public class PassportTest {

    public static void main(String [] args) {

      Passport p = new Passport("Adam", "1994-01-01");
      System.out.println(p);

    }

}

19 Suggested solution:

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

Defining methods

Description

Grouping together instructions, as a unit, and giving that group a name is really what writing methods is all about. Methods is a powerful construction that make it possible to split our code in to smaller pieces focusing on specific parts of a system which make our code easier to read as well as makes it possible to reuse code.

We will look more into the syntax and structure of methods and focus entirely on instance methods. We'll so called leave static/class methods to later sections. After this section you should be able to write a method with return value, parameters (or not).

Videos

Exercises on declaring methods

Let's go back to our Member class.

  1. Imagine you would like to get hold of the name of an object. Or the email. How do we do that now? We need to add methods (public methods so everyone can use them).
    * Write a method public String name() that returns the name
    * Write a method public String email() that returns the email address
    In some books and traditions it seems as if the name for such methods must be getName() and getEmail() and often with corresponding set methods setName(String name) and setEmail(String email). We have never been able to justify this so we're simply avoiding this. See Getters and Setters for more information.
  2. Write some simple tests of these methods in your MemberTest class.
  3. It would be nice to be able to update a Member's email address. We don't think it is necessary to change the name since that really doesn't happen that often so when it does we can create a new object instead and "throw away the old one". So add a method, setEmail(string email) to set an object's email address. Make sure that the email address is valid.
  4. The code to check the validity of the email address in the setEmail method and in the constructor are similar. Let's reuse the code. Make a call in the constructor to the method. This way you will have only one code to check the email validity.
  5. Let's say that we don't like the look of the String returned by toString. For some reason we would like the vales (of name and email) to be separated by ";" instead. Change the toString method to do this.
  6. The change was relatively easy in the previous exercise, wasn't it? Let's say we had three, four, five or even more instance variables (variables specific to a specific instance). It means that we need to change the " " (space) to ";" (semi colon) in quite a few places. How about keeping this separator in a variable instead... and that is your next task. Create a String variable called separator that has the value ";".
  7. In the previous exercise each object has its own variable separator with the value ";". This is not optimal if we consider memory space. One could also discuss if this really is a variable that belongs to an object. Perhaps the class should be responsible for storing this. Can a class store a variable? We'll continue this discussion under static variables and methods.
  8. In the previous section you wrote a Passport class and a class to test that. We now want you to add public methods to retrieve the birth and name variables to the Passport class.

Solutions to declaring methods

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


You can find complete source code to the suggested solutions below in the defining-methods directory in this zip file or in the git repository.

  1. Suggested solution:
      public String name() {
        return name;
      }
    
      public String email() {
        return email;
      }
    
  2. And some tests:
          System.out.println("Ada's name:     " + ada.name());
          System.out.println("Ada's email:    " + ada.email());
          System.out.println("Charle's name:  " + charles.name());
          System.out.println("Charle's email: " + charles.email());
    
  3. Suggested solution:
      public void setEmail(String email) {
        if (email.contains("@")) {
          this.email = email;
        }
      }
    
  4. Here's the constructor:
      public Member(String name, String email) {
        this(name);
        setEmail(email);
      }
    
  5. Here's a suggested solution:
        public String toString() {
            return name + ";" + email;
         }
    
  6. Here's a suggested solution:
    package net.supermegacorp.orgmanager;
    
    public class Member {
    
        private String name;
        private String email;
        private String separator = ";";
    
    .....
    
      public String toString() {
        return name + separator + email;
      }
    .....
    }
    
  7. No solution needed.
  8. Here's a suggested solution:
    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;
        }
    
    }
    

Chapter Links

External links

Books this chapter is a part of

Programming with Java book

Book TOC | previous chapter | next chapter