[Chapter 5] 5.2 Methods

#

| Chapter 5 Objects in Java |

## 5.2 Methods

Methods appear inside class bodies. They contain local variable declarations and other Java statements that are executed by a calling thread when the method is invoked. Method declarations in Java look like ANSI C-style function declarations with two restrictions:

- A method in Java always specifies a return type (there's no default). The returned value can be a primitive numeric type, a reference type, or the type `void`, which indicates no returned value.

- A method always has a fixed number of arguments. The combination of method overloading and true arrays removes most of the need for a variable number of arguments. These techniques are type-safe and easier to use than C's variable argument list mechanism.

Here's a simple example:

    class Bird {
        int xPos, yPos;

        double fly ( int x, int y ) {
            double distance = Math.sqrt( x*x + y*y );
            flap( distance );
            xPos = x;
            yPos = y;
            return distance;
        }
        ...
    }

In this example, the class `Bird` defines a method, `fly()`, that takes as arguments two integers: `x` and `y`. It returns a `double` type value as a result.

### Local Variables

The `fly()` method declares a local variable called `distance` that it uses to compute the distance flown. A local variable is temporary; it exists only within the scope of its method. Local variables are allocated and initialized when a method is invoked; they are normally destroyed when the method returns. They can't be referenced from outside the method itself. If the method is executing concurrently in different threads, each thread has its own copies of the method's local variables. A method's arguments also serve as local variables within the scope of the method.

An object created within a method and assigned to a local variable may or may not persist after the method has returned. As with all objects in Java, it depends on whether any references to the object remain. If an object is created, assigned to a local variable, and never used anywhere else, that object will no longer be referenced when the local variable is destroyed, so garbage collection will remove the object. If, however, we assign the object to an instance variable, pass it as an argument to another method, or pass it back as a return value, it may be saved by another variable holding its reference. We'll discuss object creation and garbage collection in more detail shortly.

### Shadowing

If a local variable and an instance variable have the same name, the local variable shadows or hides the name of the instance variable within the scope of the method. In the following example, the local variables `xPos` and `yPos` hide the instance variables of the same name:

    class Bird {
        int xPos, yPos;
        int xNest, yNest;
        ...
        double flyToNest() {
            int xPos = xNest;
            int yPos = yNest:
            return ( fly( xPos, yPos ) );
        }
        ...
    }

When we set the values of the local variables in `flyToNest()`, it has no effect on the values of the instance variables.

#### this

The special reference `this` refers to the current object. You can use it any time you need to refer explicitly to the current object instance. Often, you don't need to use `this` because the reference to the current object is implicit; this is the case with using instance variables and methods inside of a class. But we can use `this` to refer explicitly to instance variables in the object, even if they are shadowed.

The subsequent example shows how we can use `this` to allow us argument names that shadow instance variable names. This is a fairly common technique, as it saves your having to deliberately make up alternate names (as we'll try to emphasize in this book, names are important). Here's how we could implement our `fly()` method with shadowed variables:

    class Bird {
        int xPos, yPos;

        double fly ( int xPos, int yPos ) {
            double distance = Math.sqrt( xPos*xPos + yPos*yPos );
            flap( distance );
            this.xPos = xPos;
            this.yPos = yPos;
            return distance;
        }
        ...
    }

In this example, the expression `this.xPos` refers to the instance variable `xPos` and assigns it the value of the local variable `xPos`, which would otherwise hide its name. The only reason we need to use `this` in the above example is because we've used argument names that hide our instance variables, and we want to refer to the instance variables.

### Static Methods

Static methods (class methods), like static variables, belong to the class and not to an individual instance of the class. What does this mean? Well, foremost, a static method lives outside of any particular class instance. It can be invoked by name, through the class name, without any objects around. Because it is not bound to a particular object instance, a static method can only directly access other static members of classes. It can't directly see any instance variables or call any instance methods, because to do so we'd have to ask: "on which instance?" Static methods can be called from instances, just like instance methods, but the important thing is that they can also be used independently.

Our `fly()` method uses a static method: `Math.sqrt()`. This method is defined by the `java.lang.Math` class; we'll explore this class in detail in Chapter 7, Basic Utility Classes. For now, the important thing to note is that `Math` is the name of a class and not an instance of a `Math` object (you can't even make an instance of `Math`). Because `static` methods can be invoked wherever the class name is available, class methods are closer to normal C-style functions. Static methods are particularly useful for utility methods that perform work that might be useful either independently of instances of the class or in creating instances of the class.

For example, in our `Bird` class we can enumerate all types of birds that can be created:

    class Bird {
        ...
        static String [] getBirdTypes( ) {
        String [] types;
        // Create list...
            return types;
        }
        ...
    }

Here we've defined a `static` method `getBirdTypes()` that returns an array of strings containing bird names. We can use `getBirdTypes()` from within an instance of `Bird`, just like an instance method. However, we can also call it from other classes, using the `Bird` class name as a reference:

    String [] names = Bird.getBirdTypes();

Perhaps a special version of the `Bird` class constructor accepts the name of a bird type. We could use this list to decide what kind of bird to create.

### Local Variable Initialization

In the `flyToNest()` example, we made a point of initializing the local variables `xPos` and `yPos`. Unlike instance variables, local variables must be initialized before they can be used. It's a compile-time error to try to access a local variable without first assigning it a value:

    void myMethod() {
        int foo = 42;
        int bar;

        // bar += 1;       // Compile time error, bar uninitialized

        bar = 99;
        bar += 1;       // ok here
    }

Notice that this doesn't imply local variables have to be initialized when declared, just that the first time they are referenced must be in an assignment. More subtle possibilities arise when making assignments inside of conditionals:

    void myMethod {
        int foo;

        if ( someCondition ) {
            foo = 42;
            ...
        }

        foo += 1;                  // Compile time error
                                   // foo may not have been initialized

In the above example, `foo` is initialized only if `someCondition` is `true`. The compiler doesn't let you make this wager, so it flags the use of `foo` as an error. We could correct this situation in several ways. We could initialize the variable to a default value in advance or move the usage inside of the conditional. We could also make sure the path of execution doesn't reach the uninitialized variable through some other means, depending on what makes sense for our particular application. For example, we could return from the method abruptly:

    int foo;
    ...
    if ( someCondition ) {
        foo = 42;
        ...
    } else
        return;

    foo += 1;

In this case, there's no chance of reaching `foo` in an unused state and the compiler allows the use of `foo` after the conditional.

Why is Java so picky about local variables? One of the most common (and insidious) sources of error in C or C++ is forgetting to initialize local variables, so Java tries to help us out. If it didn't, Java would suffer the same potential irregularities as C or C++.[2]

> [2] As with `malloc`'ed storage in C or C++, Java objects and their instance variables are allocated on a heap, which allows them default values once, when they are created. Local variables, however, are allocated on the Java virtual machine stack. As with the stack in C and C++, failing to initialize these could mean successive method calls could receive garbage values, and program execution might be inconsistent or implementation dependent.

### Argument Passing and References

Let's consider what happens when you pass arguments to a method. All primitive data types (e.g., `int`, `char`, `float`) are passed by value. Now you're probably used to the idea that reference types (i.e., any kind of object, including arrays and strings) are used through references. An important distinction (that we discussed briefly in Chapter 4) is that the references themselves (the pointers to these objects) are actually primitive types, and are passed by value too.

Consider the following piece of code:

    // somewhere
        int i = 0;
        SomeKindOfObject obj = new SomeKindOfObject();
        myMethod( i, obj );
        ...
    void myMethod(int j, SomeKindOfObject o) {
        ...
    }

The first chunk of code calls `myMethod()`, passing it two arguments. The first argument, `i`, is passed by value; when the method is called, the value of `i` is copied into the method's parameter `j`. If `myMethod()` changes the value of `i`, it's changing only its copy of the local variable.

In the same way, a copy of the reference to `obj` is placed into the reference variable `o` of `myMethod()`. Both references refer to the same object, of course, and any changes made through either reference affect the actual (single) object instance, but there are two copies of the pointer. If we change the value of, say, `o.size`, the change is visible through either reference. However, if `myMethod()` changes the reference `o` itself--to point to another object--it's affecting only its copy. In this sense, passing the reference is like passing a pointer in C and unlike passing by reference in C++.

What if `myMethod()` needs to modify the calling method's notion of the `obj` reference as well (i.e., make `obj` point to a different object)? The easy way to do that is to wrap `obj` inside some kind of object. A good candidate would be to wrap the object up as the lone element in an array:

    SomeKindOfObject [] wrapper = { obj };

All parties could then refer to the object as wrapper[0] and would have the ability to change the reference. This is not very asthetically pleasing, but it does illustrate that what is needed is the level of indirection. Another possibility is to use `this` to pass a reference to the calling object.

Let's look at another piece of code that could be from an implementation of a linked list:

    class Element {
        public Element nextElement;

        void addToList( List list ) {
            list.addToList( this );
        }
    }

    class List {
        void addToList( Element element ) {
            ...
            element.nextElement = getNextElement();
        }
    }

Every element in a linked list contains a pointer to the next element in the list. In this code, the `Element` class represents one element; it includes a method for adding itself to the list. The `List` class itself contains a method for adding an arbitrary `Element` to the list. The method `addToList()` calls `addToList()` with the argument `this` (which is, of course, an `Element`). `addToList()` can use the `this` reference to modify the `Element`'s `nextElement` instance variable. The same technique can be used in conjunction with interfaces to implement callbacks for arbitrary method invocations.

### Method Overloading

Method overloading is the ability to define multiple methods with the same name in a class; when the method is invoked, the compiler picks the correct one based on the arguments passed to the method. This implies, of course, that overloaded methods must have different numbers or types of arguments. In a later section we'll look at method overriding, which occurs when we declare methods with identical signatures in different classes.

Method overloading is a powerful and useful feature. It's another form of polymorphism (ad-hoc polymorphism). The idea is to create methods that act in the same way on different types of arguments and have what appears to be a single method that operates on any of the types. The Java `PrintStream`'s `print()` method is a good example of method overloading in action. As you've probably deduced by now, you can print a string representation of just about anything using the expression:

    System.out.print( argument )

The variable `out` is a reference to an object (a `PrintStream`) that defines nine different versions of the `print()` method. They take, respectively, arguments of the following types: `Object`, `String`, `char[]`, `char`, `int`, `long`, `float`, `double`, and `boolean`.

    class PrintStream {
        void print( Object arg ) { ... }
        void print( String arg ) { ... }
        void print( char [] arg ) { ... }
        ...
    }

You can invoke the `print()` method with any of these types as an argument, and it's printed in an appropriate way. In a language without method overloading, this would require something more cumbersome, such as a separate method for printing each type of object. Then it would be your responsibility to remember what method to use for each data type.

In the above example, `print()` has been overloaded to support two reference types: `Object` and `String`. What if we try to call `print()` with some other reference type? Say, perhaps, a `Date` object? The answer is that since `Date` is a subclass of `Object`, the `Object` method is selected. When there's not an exact type match, the compiler searches for an acceptable, assignable match. Since `Date`, like all classes, is a subclass of `Object`, a `Date` object can be assigned to a variable of type `Object`. It's therefore an acceptable match, and the `Object` method is selected.

But what if there's more than one possible match? Say, for example, we tried to print a subclass of `String` called `MyString`. (Of course, the `String` class is `final`, so it can't be subclassed, but allow me this brief transgression for purposes of explanation.) `MyString` is assignable to either `String` or to `Object`. Here the compiler makes a determination as to which match is "better" and selects that method. In this case it's the `String` method.

The intuitive explanation is that the `String` class is closer to `MyString` in the inheritance hierarchy. It is a more specific match. A more rigorous way of specifying it would be to say that a given method is more specific than another method with respect to some arguments it wants to accept if the argument types of the first method are all assignable to the argument types of the second method. In this case, the `String` method is more specific to a subclass of `String` than the `Object` method because type `String` is assignable to type `Object`. The reverse is obviously not true.

If you're paying close attention, you may have noticed I said that the compiler resolves overloaded methods. Method overloading is not something that happens at run-time; this is an important distinction. It means that the selected method is chosen once, when the code is compiled. Once the overloaded method is selected, the choice is fixed until the code is recompiled, even if the class containing the called method is later revised and an even more specific overloaded method is added. This is in contrast to overridden (virtual) methods, which are located at run-time and can be found even if they didn't exist when the calling class was compiled. We'll talk about method overriding later in the chapter.

One last note about overloading. In earlier chapters, we've pointed out that Java doesn't support programmer-defined overloaded operators, and that `+` is the only system-defined overloaded operator. If you've been wondering what an overloaded operator is, I can finally clear up that mystery. In a language like C++, you can customize operators such as `+` and `*` to work with objects that you create. For example, you could create a class `Complex` that implements complex numbers, and then overload methods corresponding to `+` and `*` to add and multiply `Complex` objects. Some people argue that operator overloading makes for elegant and readable programs, while others say it's just "syntactic sugar" that makes for obfuscated code. The Java designers clearly espoused the later opinion when they chose not to support programmer-defined overloaded operators.

Classes |  | Object Creation