We’ve seen a lot of methods so far. Now it’s time to examine them in detail.

As you know, the executable code is enclosed in braces and preceded by a declaration. The declaration has four parts:

  • An optional access modifier (public, private, protected, or the default), which have the same meanings as they do for classes and fields.
  • The optional static keyword if the method does not apply to an instance, but to the class as a whole.
  • A return type: the kind of field returned by the method, or void if it returns nothing.
  • The name of the method.
  • An argument list enclosed in parentheses. The parentheses are required even if the method has no arguments.

All About Arguments

An argument list is a series of placeholders, separated by commas, for values passed to the method by the caller. Each argument is a field type (int, String, or what have you) followed by a name by which the method can refer to it. For example,

public Integer findSum(Integer addend1, Integer addend2) {
    Integer sum = addend1.getIntValue() 
       + addend2.getIntValue();
    return sum;
}

might be called by a series of statements like this:

Integer theFirstNumber = 1;
Integer theSecondNumber = 3;
Integer mySum = findSum(theFirstNumber, theSecondNumber);

Notice that the names in the method declaration don’t have to match the names in the method call.

Another thing: when calling a method that returns a value, there’s no obligation to use that value. You could call findSum() in this manner if you wanted to:

findSum(theFirstNumber, theSecondNumber);

It’d be pretty silly, of course. But there are many cases where it makes sense. For example, the File class lets you delete a file, and returns an indication of whether the file had actually existed before the call. 99 times out of 100, you don’t care; it’s enough that the file doesn’t exist now.

Variable-Length Argument Lists

You can declare a method with variable number of arguments by appending three periods (Unicode has an ellipsis character, but this isn’t it) to the last field type in the list:

public void howeverMany(String who, String what,
       String... where) {
   for (String w : where) {
      System.out.println(w);
   }
}

In this example, where is now an array of Strings, and the method howeverMany() is iterating over it.

We can call howeverMany() in any of these ways:

howeverMany("George", "President", "Mount Vernon",
        "Independence Hall", "Trenton");
howeverMany("George", "General");
howeverMany("Thomas", "Vice President", "Washington DC";

Not surprisingly, the argument that varies in number of occurrences has to be the last one in the argument list.

Call By Value

Some languages pass arguments as pointers to where the values can be found–known as call by reference. With call by reference, the method is free to substitute different values for an argument, and the new values will be retained by the caller.

Java, however, uses call by value. Instead of a pointer to the argument value, it makes a replica of the value and passes that instead.

Call by reference vs. call by value

In the figure above, the boxes (i.e. variables) connected by red lines to the method header are free for the method to change. In languages like COBOL that pass by reference, the method can change the original contents of the boxes, which are the very same variables the caller is using. In languages like Java that pass by value, the method is free to change the copies of the original values; the values known to the caller are intact.

HOWEVER–and this is a big HOWEVER–remember that the box containing an object doesn’t really hold the object itself; it holds a pointer to it. When a copy of that pointer is passed to a method, the method might replace it with the pointer to some other object in place of the copy. But as long as it has access to the original pointer value, even though it is merely a replica of what the caller provided, the method is free to muck about with whatever object the pointer is pointing at!! For example, if the object is, say, a student grade, the method can’t replace it in the caller with a different student grade object, but it can, if the object allows it, call methods in that student grade and change values there.

Try This

This code illustrates some of what we’ve just talked about. Copy it into your workspace, run it, then follow the logic.

public class MarxBrother {
    private String birthName;
    private String stageName;
    private String createdName;

    public MarxBrother(String birthName, String stageName) {
        super();
        this.birthName = birthName;
        this.stageName = stageName;
        this.createdName = stageName;
    }

    @Override
    public String toString() {
        return "(" + createdName + ") " + getBirthName() + "'s stage name is " + getStageName();
    }

    public String getBirthName() {
        return birthName;
    }

    public void setBirthName(String birthName) {
        this.birthName = birthName;
    }

    public String getStageName() {
        return stageName;
    }

    public void setStageName(String stageName) {
        this.stageName = stageName;
    }

    public static void main(String[] args) {
        MarxBrother groucho = new MarxBrother("Julius", "Groucho");
        System.out.println("Before calling replaceBrother: " + groucho.toString());
        replaceBrother(groucho, "Adolph", "Harpo");
        System.out.println("After calling replaceBrother: " + groucho.toString());

        System.out.println("");
        System.out.println("Before calling modifyBrother: " + groucho.toString());
        modifyBrother(groucho, "Leonard", "Chico");
        System.out.println("After calling modifyBrother: " + groucho.toString());
    }


    private static void replaceBrother(MarxBrother brother, String birthName, String stageName) {
        String originalCreatedName = brother.getStageName();
        
        System.out.println("Inside method replaceBrother before replacement: " + brother.toString());
        brother = new MarxBrother(birthName, stageName);
        
        // Replace the createdName in the new instance with the value
        // of the replaced instance.
        brother.createdName = originalCreatedName;
    }

    private static void modifyBrother(MarxBrother brother, String birthName, String stageName) {
        System.out.println("Inside method modifyBrother before modification: " + brother.toString());
        brother.setBirthName(birthName);
        brother.setStageName(stageName);
        System.out.println("Inside method modifyBrother after modification: " + brother.toString());
    }
}

Notice the two methods replaceBrother() and modifyBrother(). The first creates a new MarxBrother and replaces the incoming brother with it. The second modifies the attributes of the brother it was given. When you run this code, you’ll notice that although modifyBrother displays the attributes of the replacement brother it’s created, thanks to call by value it has not replaced groucho in the main() method. On the other hand, modifyBrother() changes the birthName and stageName within groucho itself; it’s the same groucho but with changed field values. The result of running this code is:

Before calling replaceBrother: (Groucho) Julius's stage name is Groucho
Inside method replaceBrother before replacement: (Groucho) Julius's stage name is Groucho
Inside method replaceBrother after replacement: (Groucho) Adolph's stage name is Harpo
After calling replaceBrother: (Groucho) Julius's stage name is Groucho

Before calling modifyBrother: (Groucho) Julius's stage name is Groucho
Inside method modifyBrother before modification: (Groucho) Julius's stage name is Groucho
Inside method modifyBrother after modification: (Groucho) Leonard's stage name is Chico
After calling modifyBrother: (Groucho) Leonard's stage name is Chico

return

The return statement exits the method.

Every method that returns a value (i.e., has a field type instead of void in the header) requires at least one return statement that names the value being returned to the caller. Take, for example, the getStageName() method above:

    public String getStageName() {
         return stageName;
    }

In methods that don’t return values, return to the caller is automatic once the last statement in the method is executed. Return from other points, however, is allowed, by coding return without an operand.

Getters and Setters

You may have noticed that there are methods with names beginning “get” and “set”. These are called (surprise!) getters and setters. One of the requirements of encapsulation is to never let outsiders have direct access to your fields (except for constants); always keep each field private or protected, and write a public getter and setter to retrieve and (if desired) to replace the field’s value. Always.

And notice how the setters use the qualifier this to distinguish between the field within the object instance and the field being passed as an argument? This way of writing setters (and getters) is standard practice. But most especially, so is their naming: “get” or “set” followed by the capitalized name of the field in question with one exception: getters for boolean fields start with “is” instead of “get”. This naming convention is so important that in some frameworks like Spring and Hibernate, the developer provides field names and the environment calls getters and setters whose names are assumed to follow this pattern.

Constructors

Another method you’ll see in MarxBrother is a constructor: a method that’s called in order to instantiate an object. MarxBrother has one constructor:

    public MarxBrother(String birthName, String stageName) {
        super();
        this.birthName = birthName;
        this.stageName = stageName;
        this.createdName = stageName;
    }

and a MarxBrother is created like this:

MarxBrother groucho = new MarxBrother("Julius", "Groucho");

Notice that a constructor’s declaration doesn’t specify a return type (not even void), and the constructor’s name is the name of the class. Other things to know about constructors are:

  • The constructor may call a constructor of its superclass with the statement, super();. (In our constructor the superclass is Object). If your class has a superclass with a constructor that accepts arguments, you would code it as, say, super(arg1, arg2);. If you do call a superclass constructor, the call must be the first statement in the current constructor.
  • If you don’t need to do any special processing in a constructor, you don’t have to write one. A default constructor like this (one that accepts no arguments and does nothing) will be provided implicitly:
    public MyClass() {
    super();
    }

    If you want the default constructor to do more than this, just code it explicitly.
  • But if you do write any constructor, the default constructor is no longer implicit. (see Benevolent Overloads below.) If you still want other classes to be able to use a default constructor that does nothing more than the one in the previous bullet point, you have to code it explicitly.
  • You can call one constructor in your class from another one (see Benevolent Overloads below) by coding a call like this:
    this(arg1, arg2);
    As with a call to super, it must be the first statement in the calling constructor.

Benevolent Overlords Overloads

In the previous section, we mentioned the possibility of multiple constructors in a given class. And you’ve probably noticed that when you type System.out.println into the Eclipse editor, you’re shown a long list of possible argument configurations that might be passed to that method. These alternatives are called overloads. As mentioned above, they can be used for constructors as well as for other methods.

Overloads let you provide multiple methods that accomplish the same thing without the need to have a distinct name for each one. Imagine if you needed a separate method name for what System.out.println() does for each type of argument you can pass to it: one name for String, one name for int, one name for char, and so on.

Two methods are overloaded if:

  • They have the same name;
  • They have argument lists that differ in number, type, or both.

Of course, like System.out.println(), overloads might take in arguments of different types with the intention of treating them differently based on those types. Another common use for overloads is to fill in default values for existing methods without requiring that the developer provide them in a call. For example:

public Integer overloaded(String arg1);
   return overloaded(arg1, null);
}

public Integer overloaded(String arg1, Integer arg2) {
    if (arg2 == null) {
       return 0;
    } else {...}
}

Static Initialization Blocks

There is yet one more type of method in Java: the static initialization block, which looks like this:

static {
    do something
    do something...
}

A static initialization block is executed whenever the class is loaded (as opposed to when an instance is created), which is usually once per execution of the Java Virtual Machine. Notice, by the way, that it has no name; it doesn’t need one.

Singletons

Recall the discussion of the Delegates Pattern in the previous lesson?

Another popular pattern–one used in many classes already provided with Java–is the singleton. This is a class which permits one and only one instance. Here’s an example of a singleton class.

public class Singleton {
    private Singleton instance;
    
    public Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
    private Singleton() {   
    }
}

Notice that the default constructor is marked private. Because of that, no other classes can call it. Instead, they call the public getInstance() method, which calls the constructor only if the instance doesn’t already exist.

You may be asking, “Can’t I accomplish the same thing with a class having only static methods, and a static initialization block instead of a constructor?” For the most part, you can. But there are two things a singleton’s getInstance() method can do that a static initialization block cannot:

  • It can accept arguments.
  • It can throw an Exception if anything goes wrong.

What You Need To Know

  • Methods begin with a declaration, and a method body of executable statements enclosed in braces.
  • The declaration contains an access modifier, the optional keyword static, a return type, a method name, and an argument list.
  • Argument lists can be variable-length.
  • Arguments are passed by value from caller to method, which insulates the caller from changes to the values of the arguments.
  • For this reason, an object instance passed by the caller is similarly insulated–but if the object class permits it, the called method can modify the instance itself.
  • Getters and setters are an essential part of encapsulation, and by convention, they are named after the fields they get or set.
  • Methods that return values must have at least one return statement that returns a value of the type specified in the method header.
  • The return statement is optional for methods that don’t return values.
  • A constructor is a method that allows code that is creating an object instance to provide arguments that can be used in initialization.
  • A constructor may call the constructor of its superclass.
  • A constructor may call an overloaded constructor in the same class.
  • Overloads allow the use of the same method name for several methods that accomplish the same task but which take different arguments. Both constructors and other method types may be overloaded.

Next: Arrays and Vectors