Chapter:Classes - Defining methods

From Juneday education
Jump to: navigation, search

Meta information about this chapter

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

Introduction

In this lecture, we take a closer look at instance methods. We remind the student about objects' having both state and behavior and explain that the behavior is coded using instance methods. So far, we've seen that classes define instance variables to hold the state of the object and that such variables often are declared private. We can't use a private variable from a different class but we could set the initial value of a private variable using arguments passed to the constructor at construction time. But once the object is created and initialized, how do we access the instance variables or the state of the object that they represent? Enter instance methods.

Purpose

The purpose of this chapter is to give the last piece of the puzzle surrounding the state and behavior of objects and how this is coded in class definitions.

In doing so, we look at the code for an instance method and its parts; access modifier, return type (or void), method name, parameter list (which can be empty) and the block with the body of the method. For methods with a return type, we also want the students to be comfortable and competent regarding the return statement and how it corresponds with both the method return type and the control flow of a program - that return not only returns a value, but also returns control to the code where the call of the method took place.

Another purpose is to expand the understanding of expressions, by extending the definition of expressions to also include method calls. This means that an expression can contain a method call, which is important for the students to understand.

This lecture also teaches the students how a method can change the state of an object (by affecting the private instance variables).

Goal

After this chapter, the student shall understand:

  • That classes can define instance methods
  • The anatomy of a method
    • access modifier
    • return type (or void)
    • method name
    • parameter list (which could be empty)
    • the block with the method body
    • return statements (mandatory for non-void methods, optional for void methods)
  • That calls to methods with a return type can be considered an expression or part of an expression
  • That an instance method can have a side-effect which changes the state of an object
  • That an instance method can be used to access the state (or part of the state) of an object

Instructions to the teacher

Common problems

Note that the definition and explanation of instance methods is not complete at this stage. We haven't gotten to the parts of the course material concerning exceptions (throws/throw) yet, and we haven't learned about inheritance yet either (final, @Override, protected etc)

When talking about methods, try to manage two perspectives. The first perspective is the code of the method which will be executed when the method is called, which is defined in the class when the class was written. Talking about the method and its code concerns compile-time. It is simply the way the method is coded and defined to work. The other perspective is the actual calling of the method. This takes place in run-time. For instance, calling a method which has return type int will yield an int value at runtime. This is why we can consider a method call an expression, or use it as part of an expression.

Stress that a method is typically called from code inside another method (an easy example is a small main method which creates an object and calls an instance method via a reference to the object). This ties in to our previous discussion of the different roles a Java programmer can play. One role is to design (and write) classes describing common treats for similar objects. Another role is the application programmer, which writes code that - using classes and objects - actually solves some problem. It is in the former role you write classes and instance methods. And it is in the latter role you write code that calls methods.

An exception to the above is that a method in a class can call another method in the same class. That would be coded by the API programmer which designs and writes classes for creating objects. But it is important to stress that, even if you write a class where an instance method calls a second method in the same class, this is not "active code" - nothing happens by itself. The "active code" would be when the first method is called from another class when the application is running.

A simple example would be that you write a code for a class representing a bank account, so that application programmers can create Account objects. In the Account class, you may have a method public void deposit(BigDecimal amount) for depositing money into the account. The deposit method, might call an auxiliary method in the same class, void logTransaction(Transaction t) for logging the deposit. Writing this class and compiling it, doesn't run any code. It is only when the application programmer (which may be also yourself, but typically someone else in the team) writes the main application which creates Account objects and calls methods on them (e.g. calls deposit when money is being deposited into the account) compiles the program and runs it that the code in the methods of the Account class will be executed. Another example would be that you write the Account class and then a small program which tests that the Account class behaves correctly. Running the tests will result in the test program creating Account objects, calling methods on them and verifying that the result is correct. It is the test program that triggers the code in the Account class' method to be executed. By itself, the Account class is just "passive code" waiting for someone to create an object (which later can have methods being called on it).

We've seen that these two perspectives on code is confusing to students an encourage you to talk about it and to verify that the students understand when code is just code and when it is later executed.

Another thing we'd like to give an heads-up on, is the concept of methods returning a value. We've mentioned this before, but this lecture on instance methods are bound to expose that this is confusing to some students. Again, a method printing something to the standard streams does not return anything by doing so. A print statement has nothing to do with returning a value, even if it communicates a value to the user. A method returning a value does so to other pieces of code, not the user. As we've stated before, we think this confusion is more likely to manifest itself if you expose the students to exercises where the task is worded in the lines of "Write a method which take two integers and print the sum of these integers to standard out". The more realistic way to solve the problem of adding two integer values would be to write a program which obtains two integer values from e.g. the user, calls a method which returns the sum of the integers and stores the returned value in a variable which is later printed out so that the user can see the result.

The worst exercises we've seen in teaching materials and literature are those which asks the student to write a method which both prints out e.g. the sum and returns the value. By mixing printing and returning a value, you are likely to confuse the students. At least that is our experience and what we've learned from discussing with students who express confusion about returning values from methods.

Full frontal - Code up-front!

Time for a code example. The point isn't that you should understand the code, but rather to show you the code for a method, since that's what this chapter is about - writing methods.

Example of a method for reading a file and converting the contents to a List<Contact>:

  /**
   * Loads the file .addressbook from the user's home directory
   * and converts it to an ArrayList<Contact> and assigns it to
   * the instance variable entries
  */ 
  public void load(){
    try{
      if (!new File(ADDRESS_FILE).exists()){
        System.out.println("INFO: There is no address book file.");
        return;
      }
      ObjectInputStream in = 
        new ObjectInputStream(new FileInputStream(ADDRESS_FILE) );
      // The file is created by this class, saving a List<Contact>
      // so it is safe to surpress warnings.
      @SuppressWarnings("unchecked") entries = (List<Contact>)in.readObject();
      in.close();
    } catch(IOException e) {
      // Write a user friendly error message, log the exception, and, 
      // rethrow the exception as a RuntimeException to be handle by the Main class
      // global error handler (the last resort).
      System.err.println("Could not load address book");
      logException(e);
      throw new RuntimeException("Your address book is unavailable, corrupted or missing.");
    }
  }

Classes: Defining methods

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 leave so called static/class methods to later sections. After this section you should be able to write a method with return value, parameters (or not).

Two purposes for methods

Calculating a value

A method either returns a value it calculates or otherwise gathers back to the calling code. Such methods have a return type which can be:

  • a primitive type (any of byte, short, int, long, float, double, boolean, char)
  • a reference type (a reference type is a class, interface, enum or array)

Such methods are called primarily to get the value it returns back to the place we call the method. Since those methods return a value and have a type, we can use calls to such methods as an expression or part of an expression:

//Calculate the side of a square, when we know the area is 81 square cm:
double area = 81;
double side = Math.sqrt(area);

The call to Math.sqrt() is used as an expression in the assignment statement for the side variable.

Anywhere where we can use an expression, we can put a call to a method which calculates and returns a value of the correct type.

Note that Math.sqrt() is a static method (a.k.a. a class method). We are going to focus on instance methods to begin with.

An example of an instance method that returns a value is:

  public int age() {
    return Period.between(birthDate, LocalDate.now()).getYears();
  }

The example is taken from a class for creating Student objects. The class represents a student. Each student has a name (of type String) and a birth date (of type java.time.LocalDate). The instance method age() returns the age of a student, by calculating how many years have passed since the birth date.

A call to age() can be used in an expression:

int totalAge = 0;
List<Student> students = readStudents(); // get students from somewhere (e.g. from a database)
for (Student s : students) { 
  totalAge += s.age(); // accumulate age from all students
}
double averageAge = totalAge / students.size();

Methods which simple does something and don't give a value back

The other purpose for writing a method is to group code which simply does some work together in one place, and give that procedure (that work) a name. Typically, we want to re-use code like sorting a list, so that we can sort a list many times without having to write the same code again. Such methods have a side-effect but they don't return any value back to the caller.

Instead of a return type, such methods have the void keyword where a return type normally would be. You've used such a method many times already! Look at the println() method of the PrintStream class. You've used the System.out variable (which is of type PrintStream) many times, just to call the println() method. You have never called System.out.println() to get a value back, or to use that call as part of an expression. You haven't, and we know you haven't, because the compiler wouldn't allow you to do so!

Think about it. If System.out.println really would return a value, what would the type be? And what would it mean?

Object o = System.out.println(false); // Won't compile, and if it did, what would the value be? What type?
double d = System.out.println("Cowabunga!"); // Won't compile either. What could it possibly return? What type?

You use System.out.println() solely for the side-effect of something being printed to standard out (usually the terminal where you run your Java program). You don't use it because it is great at calculating some value which will be handed back to you using a return statement.

So when do you write a method which is declared void? When you are not interested in any value returned by the method, but only interested in the fact that the method does some work for you (sort a list, print some text, change some state, etc).

What other things should we consider when writing a method?

Other than deciding about whether to make the method return a value of some type, or to be void and simply perform some task without returning any value back, you should decide if the method depends on the state of some object (instance methods) or if the method can stand on its own legs and do its thing without consulting with any particular instance or object's state. The latter are called static methods (or class methods). More on static methods versus instance methods in later chapters.

For now, we'll focus on instance methods. Methods that work on objects (instances) and depend on the state of the object we call them on. We use references to objects to call their instance methods.

Object oriented considerations

One thing to think about when designing a class for some objects, is to think about the objects as entities with a state and behavior. Early on, when learning Java, it is easy to get a feeling that behavior is just about reading some state or changing some state.

If we take Student as an example, one might think that the state of a Student object is just the name and the birth date. So, the behavior must be to return the name or birth date right? And to change the name and change the birth date?

No. First of all, in most situations where we need to represent students as objects in a system, like some administrative system for a university, it is not typical behavior of students to go and change their names (let alone their birth dates). So, we don't see any need for instance methods for this. Surely, we'd like to be able to query a Student object for some data relating to its state. But this does not have to be a one-to-one mapping between the instance variables birth and name. We could imagine many other behaviors of a Student object. For instance, consider the age() method above. That method was part of the behavior of a Student object. Internally, the state of the object is just a birth date and a name. But we might want to consider the age of a student for some reason.

Try to avoid to just write a method returning the birth date reference for the case that some user of your class Student would want to consider the age. Even if the student only keeps track of its birth date as part of its state in an instance variable, why shouldn't every Student object be able to tell its programmer users (the programmers using objects of Student type) how old they are? If you ask a flesh-and-blood real life student how old she is, surely, you wouldn't expect to get the birth date back so that you'll have to do the math yourself. Most people know how to calculate their age by taking the current date and calculate their age using also their birth date.

Another behavior that an object representing some kind of person might have, is the ability to tell us whether they are legally adults or not. So, if a system dealing with objects representing some kind of person needs to know whether the person is adult or not, we should think of this as a suitable behavior of the objects. In this case, we'd write a boolean instance method, perhaps named isAdult() which can return true if they are over some legal age and false otherwise.

This makes such objects much more convenient to use for our colleagues, than if they have to query the object for a birth date and then do the math themselves...

And here's perhaps the place to talk about some examples of state and behavior we've seen in various places (we might be guilty of this ourselves). Please don't make age an instance variable, because then you must update the age every time the object "has a birthday". Age is, and should be, a function of the birth date and the current date of today.

We will try to be better at coming up with behaviors for our examples than just a mapping between some private instance variable and the returning of that variable (or the value of that variable). We are working on it, but at least we have tried to address it here, and are frank about the fact that our example classes sometimes suffer from this lack of fantasy with regards to the behavior of our objects expressed as their instance variables.

Chapters about classes

Here are the chapters about classes in this book, so that you can keep a check on your progress:

Videos

Links

Further reading

Where to go next

Next page has exercises for this chapter: Classes_-_Defining_methods_-_Exercises

« PreviousBook TOCNext »