Chapter:Classes - Defining methods - Exercises

From Juneday education
Jump to: navigation, search

Exercises on declaring methods

Let's go back to our Member class.

Q1

Imagine you would like to get hold of the name of a certain member object (an object of class Member which represents a real-life member in some club or organization). 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() which returns the name
  • Write a method public String email() which 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.

Let's look at the description of the two methods above:

public String name() - access modifier public, return type String

We want the method to give the caller information back. The caller has a reference to some Member, but wants to get hold of the name (as a String) of that particular Member. So the method is definitely a so called "instance method" - you need a reference to a particular Member instance in order to call it. And the sole purpose of the method is to return to the caller the name of the Member in question. So the method cannot be declared void, since it has to return a value of type "reference to String". This, in turn, means that the method needs to declare a return type String and also have a return statement which actually returns a String reference (preferably a reference to the Member in question's name variable).

The same goes for:

public String email() - access modifier public, return type String

The sole purpose for that method, is to allow callers that have a reference to some Member object to query that object for its email address (as a String). It too, needs to declare a return type String and have a return statement which returns the reference for the object in question's email variable. It cannot be declare void, because if it did, it wouldn't return a value and the caller wouldn't get a String back (referring to the Member in question's email address String).

Now, copy your Member class from previous chapter exercises and add the two methods above.

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

Suggested solution:

  public String name() {
    return name;
  }

  public String email() {
    return email;
  }

Q2

Write some simple tests of these methods in your MemberTest class. You can copy the MemberTest.java file from the previous chapter exercises, and continue with it here.

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

And some tests:

      System.out.println("Ada's name:     " + ada.name());
      System.out.println("Ada's email:    " + ada.email());
      System.out.println("Charles' name:  " + charles.name());
      System.out.println("Charles' email: " + charles.email());

If you still have the code where your created two instances referred to by the variables ada and charles and passed in their names and addresses to the constructor of Member, you should be able to verify that the tests show that the methods do their jobs.

Q3

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 to the Member class, setEmail(string email) to set an object's email address. Make sure that the email address is valid.

This method should just do something, not calculate something. For such methods, we usually use the void keyword where we usually put the return-type.

Oh, really, void, you say? Please explain!

Well, the purpose of this method is to allow anyone with a reference to some Member instance, to change that Member's email address. The caller shouldn't expect a value back, since the caller is merely asking the Member instance to change its email address. Calling the method has a side-effect of the Member instance's email address being changed, but that's a task that really doesn't have any value which can be returned to the caller. The caller of the method expects the email address for this member to be changed to whatever the caller gave as an argument to the method, but that's merely a side-effect.

What could the method return, if we decided to make the method non-void and return a value of some type? It's hard to tell. The name says setEmail, which is the message we (or the caller of the method) send to the Member instance, and the caller passes along with this message the new email address. As the name of the message hints, we ask the Member to change its email address, but we don't expect any value back.

These kinds of methods which mutate (change) the state of an object are typically called "mutator methods" - methods which change the state of the instance. They are methods which simply does something and don't give any value back. This it the hallmark of methods declared void - methods which don't calculate or fetches any value for us, they simply do something.

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

Suggested solution:

  public void setEmail(String email) {
    if (email.contains("@")) {
      this.email = email;
    }
  }

Or possibly, using a more defensive style:

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

Q4

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 place with code to check the email validity. This is of course better than having two places (or more), if we later decide to change the way we validate email addresses.

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

Here's the constructor:

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

Alternatively, you could write a private boolean method, private boolean isValidEmail(String email) and call that one from the setEmail(String email) method:

  private boolean isValidEmail(String email) {
    return email != null && email.contains("@");
  }
  
  public void setEmail(String email) {
    if (isValidEmail(email)) {
      this.email = email;
    }
  }

One nice feature about boolean methods, is that they fit well into IF statements, if you give them a reasonable name like isValidEmail. Reading the code with the IF statement out loud sounds almost understandable:

"if is valid email". Using a name like "isValidEmail" also signals what the method does and when it returns true and when it returns false. If it "is valid email" then that's true. If it NOT is valid email, then the claim "isValidEmail" of course is a false claim, and we intuitively understand that the method would return false.

Q5

Let's say that we don't like the look of the String returned by the toString() method. For some reason we would like the values (of name and email) to be separated by ";" instead. Change the toString method to do this.

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

Here's a suggested solution:

    /*
     * Returns a String representation of this Member on the format:
     * <name>;<email>
     * where
     *  <name>  is this Member's name and
     *  <email> is this Member's email
     *
     * Example:
     * Peter Fonda;easyrider@hollywood.com
     */
    public String toString() {
        return name + ";" + email;
     }

Other ways to format a String using variables, without using the + operator include:

String.format("%s;%s", name, email);

and

new StringBuilder(name)
     .append(";")
     .append(email)
     .toString();

Q6

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 ";".

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

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

Q7

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.

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

No solution needed.

Q8

In a previous chapter exercise you wrote a Passport class and a class to test it. We now want you to add public methods to retrieve the birth and name variables to the Passport class.

Do it! You may copy the source codes and directories from the previous passport exercise and continue working on the copied files.

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

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

}

Q9

Next, we'll change the Passport class to use a LocalDate internally. But the birth() method should still return a String. Like in the last chapter, we want to show you that the private instance variables don't need to mimic the methods or constructors of the class. So we might use a LocalDate as the type of the instance variable birth, but expose a String via the birth() method.

Do the following:

  • import java.time.LocalDate in the Passport class
  • change the type of the birth instance variable to LocalDate
  • change the constructor so that it sets the birth instance variable like this: this.birth = LocalDate.parse(birth);
  • change the birth() method so that it returns birth.toString()

What do we gain from this? Actually, the parse method of LocalDate will make our program crash if someone calls the constructor with a malformed date string, which actually is good. We don't like bugs, and bugs should be discovered by us, the programmers, when we test our classes and program.

We could improve our class even more by adding a second constructor, which accepts a String for the name and a LocalDate as the birth date, as a service to those who prefer this version of creating a Passport. Now, users of our class can choose between the two. We could even add a third constructor which accepts a String for the name, and three int values for year, month and day (see previous chapters for how that could work).

Remember, just because we have an instance variable of some type, we don't have to expose the same type via the method for reading the Passports birth date.

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

Here's a suggested solution:

package org.police.passportsystem;

import java.time.LocalDate;

public class Passport {

  private String name;
  private LocalDate birth;

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

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

  public String name() {
    return name;
  }

  public String birth() {
    return birth.toString();
  }

}

Q10

Next, let's keep working with our Passport class. Since the Passport now uses a LocalDate internally, it is much more capable than it was when it was using a String. The class can take advantage of the methods in LocalDate in order to make Passport objects more capable. We can now add a method which returns a boolean value indicating whether the passport holder is an adult or not. Let's decide that the rules for being adult or not works on the age being over 17 (18 years old and older). So a passport holder with an age over 17 years would be adult, and otherwise not.

So, we need a new method boolean hasAdultHolder().

How would we use the LocalDate to figure out if we are over 17?

We can calculate the age of a person with a LocalDate for the birth date, by calculating the java.time.Period between the birth date and the current date, then taking the years part of that Period:

Period.between(birth, LocalDate.now()).getYears()

So, the method becomes:

  public boolean hasAdultHolder() {
    return Period.between(birth, LocalDate.now()).getYears() > 17;
  }

While we're at it, why not enhance the toString() method as well, to include information on the holder's being adult or child:

  public String toString() {
    return name + " " + birth +
      " (" + (hasAdultHolder() ? "adult)" : "child)");
  }

You need to import java.time.Period in the Passport class, because otherwise the compiler and runtime won't know what you mean with Period.

Update the TestPassport class to create a few Passports with child holders and adult holders (use suitable birth date strings to test this). Try both printing out the objects via their references, and also to call the hasAdultHolder() method.

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

Here's a suggested solution:

package org.police.passportsystem;

import java.time.LocalDate;
import java.time.Period;

public class Passport {

  private String name;
  private LocalDate birth;

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

  public String toString() {
    return name + " " + birth +
      " (" + (hasAdultHolder() ? "adult)" : "child)");
  }

  public String name() {
    return name;
  }

  public boolean hasAdultHolder() {
    return Period.between(birth, LocalDate.now()).getYears() > 17;
  }
  
  public String birth() {
    return birth.toString();
  }

}

If you wonder about the strange things in toString(), then we can tell you that we are using the ternary operator.

The ternary operator works like this. It is evaluated to one of two possible values, depending on a boolean test.

The structure is: <bolean expression> ? <value if true> : <value if false>

In the toString() method, the test is a call to hasAdultHolder() and if it evaluates to true, the value of the expression is "adult)" and if not, the value is "child)".

Links

Solutions (source code

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.

Further reading

Bonus: Some links on using Strings and StringBuilder for the exercises above:

Where to go next

Next page is: Classes_-_Static_members

« PreviousBook TOCNext »