Chapter:Classes - Defining constructors - Exercises

From Juneday education
Jump to: navigation, search

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.


Q1

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

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

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;


  }
}

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

Q2

Now we're leaving the Greeter class and will move on to another task. Write a Member class like the one below. Note that we haven't added a constructor, yet, event though that's the topic of this chapter. The plot thickens!

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

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

The Member class should look a bit like this:

package net.supermegacorp.orgmanager;
 
  public class Member {
 
    public String name;
    public String email;

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

And you compile it like this:

$ javac net/supermegacorp/orgmanager/Member.java

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 }

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

The MemberTest class should now look like below, and the highlighted line is the call of the constructor in class Member. But wait! There was no constructor declared in the Member class? Do you remember that a class without any explicit constructor declaration gets one inserted into the class file by the compiler? That's why the class below works anyway!

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

}

Q4

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

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

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

Q5

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

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

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

  public Member() {
  }

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

Q6

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

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

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. Just like the parameter-less constructor you wrote above, with a seemingly empty body.

Q7

Fixing the constructor in Member to actually do some work...

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.

Try to recompile your classes and run the MemberTest program again. Why does the compilation of the MemberTest class fail?

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

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.

Q8

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

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

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

}

Q9

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?

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

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.

So, we need a way to distinguish the parameters from the instance variables, since they have the same names. Do you remember how to do that? Hint: check previous chapter text and the further reading links.

Q10

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?

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

It will work now, since we're setting the instance variable name to the value of the parameter name.

Without the this keyword, Java (and the compiler) would think that we wanted to reset the parameter named name to its own value. Now we can distinguish between the instance variable this.name and the parameter name. Isn't that just great?

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

Q11

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

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

The new, extra, 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? The method will print null since this is the value of the email variable. We could solve this in several ways., e g in the toString method we could print the email address only if we have a valid value (using an if statement or the ternary operator) or we could set the email to "" (the empty string).

Did you know that when you have more than one constructor (differing in arguments), it is called "overloading"? We have now two, overloaded constructors in Member. The clients (users) of this class, can now choose which overloaded constructor they want to use.

Q12

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

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

Nothing to do in this exercise.

Q13

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

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

Suggested solution:

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

Lets analyse 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:

    }

Q14

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. And besides, you cannot return anything from a constructor - so exceptions is the only way to send instant feedback to the caller of the constructor.

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

This is what the creation of the second Member instance looks like in MemberTest:

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

Q15

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.

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

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.

A defensive way to program and be safe from harm in this case would be to write the code like this instead:

    if (email != null && email.contains("@")) {

Q16

In some previous exercise (from the chapter on variables) you wrote a Passport class and a class to test it. 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, which saves the parameters in the corresponding instance variables

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

If you can't find the previous exercise code, you may sneak-peek below.

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

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

Q17

Try to compile the "old" PassportTest class. Do you think it will work? Check and see.

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

The PassportTest class calls a parameter-less constructor (remember that a constructor with no arguments automatically created by the Java compiler if (and only if) there are no constructors defined by the developers in the class source code).

Since there no longer is such a constructor - we have defined a constructor by our own and didn't get that free service from the compiler - the compilation will fail.

Make a mental (or better still, on paper) note about the compiler error message. Try to understand it. You will probably run in to similar compiler messages in your career as a Java programmer. It is good to get to know the compiler and everything it complains about. Knowing your error messages will save you a lot of time.

Q18

Change the TestpassportTest class so that it creates a Passport using the new constructor.

Output the Passport object using System.out.println.

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

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

  }

}

Q19

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

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

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

Q20

Let's fix Passport so that it uses a LocalDate instead of a String for storing the birth state of Passport objects.

We'll do this internally in the Passport class, but accept three ints in the constructor for creating a passport.

This will show you (hopefully) that there mustn't be a one-to-one mapping between the types of variables and the way people creates an object.

In order to use LocalDate, we need to import java.time.LocalDate in the Passport class. Otherwise the compiler and java won't know what we mean by "LocalDate".

Change the birth variable to be of type LocalDate instead of String:

  private LocalDate birth;

This will be the constructor:

  public Passport(String name, int year, int month, int day) {
    this.name = name;
    this.birth = LocalDate.of(year, month, day);
  }

The point here, is to show you how (and why) you need to import java.time.LocalDate, and also to show you that the constructor accepts the String for the name, and three int values for the birth date. But internally, the class stores the birth date in one single variable, of type LocalDate. A LocalDate can be constructed using the LocalDate.of() method which accepts exactly three ints, representing year, month and day.

So, even if users of this Passport class don't know it, the Passport class internally uses a LocalDate to store the birth date.

Try this out and see if you can compile the Passport class. Change the PassportTest class, to reflect the change of the constructor. That is, you need to create the new Passport by giving the name and three ints for the date as arguments. Verify that it works.

In the next chapter, we'll see how to get information from objects, or making objects do stuff for us, by "calling methods" (or sending messages, as it is sometimes called in various literature).

Links

Solutions (source code)

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.

Further reading

Where to go next

Next page is: Classes_-_Defining_methods

« PreviousBook TOCNext »