Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 7 of 7
The method's name identifies the method. Choose an identifier for the method name that is not a reserved word.
Preceding each method is a return data type, which either identifies the kind of data values that the method returns or indicates
that the method returns nothing. Apart from void, return_data_type is either:
boolean, byte, char, double, float, int, long, or shortIf a reference data type identifier is specified, the method returns a reference to an object created from that data type instead of a primitive value.
A pair of round brackets follows each method. You can optionally declare a comma-delimited list of variables between the brackets. Each variable in the declaration is known as a parameter. When the method is called, an optional comma-delimited list of expressions, known as arguments, passes to the method. The number of arguments and data type of each argument (in a left-to-right order) must match the number of parameters and data type of each parameter (in a left-to-right order). A method's parameter list is often referred to as a params operator.
To call a method, pass zero or more arguments to the method. The method accesses those arguments, processes the values in those parameters, and then (optionally) returns a value to the method's caller. For example, suppose you create a method designed to calculate a number's square root. Your code passes a value to the square root method and then calls that method. The method grabs that argument via the corresponding parameter; after some processing, the square root method returns the value's square root back to the caller.
The following example demonstrates a simple method with four compiler errors:
class MathUtilities
{
int sum (int limit)
{
System.out.println ("limit = " + limit);
System.out.println ("total = " + total); // Compiler error.
int total;
for (int i = 1; i <= limit; i++)
total += i; // Compiler error.
System.out.println ("i = " + i); // Compiler error.
return total; // Compiler error.
}
}
In the code above, MathUtilities declares a method named sum() with a single int parameter named limit. Method sum() calculates the sum of integers ranging from 1 to limit and returns that total. Let's examine sum() in detail.
Notice that limit is a parameter to sum(). The JVM creates the limit variable and assigns the argument value -- passed to sum() during the call -- to limit. Any statement in the block following sum()'s method signature can access the argument value contained in limit. In other words, the scope -- or visibility -- of limit is the entire block. However, the parameter cannot be accessed outside the block; another method declared in MathUtilities cannot access limit. Once a program's execution leaves a method in which a parameter is declared, the JVM destroys that parameter.
Also note the variable declaration statement int total;; it declares a local variable named total. Like a parameter, a local variable is a variable that is local to a method block. A local variable cannot be accessed from outside the block. Unlike a parameter,
a local variable's scope is not the entire block. Instead, its scope ranges from the point in the block where the local variable
is declared to the block's end. Suppose you attempt to access a local variable before it is declared. What happens? For an
answer, notice the // Compiler error. comment to the right of System.out.println ("total = " + total);. Never attempt to access a local variable prior to that variable's declaration.
Furthermore, the above code shows that blocks can be nested -- as the code's for loop statement illustrates. The total += i; simple statement following for (int i = 1; i <= limit; i++) is a block. That block nests within the method block. Local variable i is local to the total += i; block. If you try to access i from the enclosing method block, another compiler error results. Never attempt to access a local variable declared in an inner block from an outer block.
Local variables introduce another potential problem: They must be explicitly initialized before they are accessed. Otherwise,
the compiler reports an error. If total is not initialized, total += i; and return total; result in compiler errors. To correct those errors, change int total; to int total = 0;. Always initialize your local variables to avoid errors that arise from uninitialized local variables.
Examine the return total; statement at the end of sum() in the code above. That return statement returns a value to the method's caller. Use the following syntax to express a return
statement in source code:
'return' [ expression];
A return statement consists of keyword return optionally followed by expression. Unless a method's return data type is void, you must always follow return with an expression whose data type matches the method's return data type. Furthermore, if a method's return data type is
not void, execution must always exit from a method via a return statement. Otherwise, the compiler reports an error. (Stay tuned to
this article for more examples of return statements.)
After fixing the four compiler errors in the code above, how would you call sum()? Check out the following code fragment: MathUtilities mu = new MathUtilities (); int sumTotal = mu.sum (10);. That fragment creates an object from the MathUtilties class and then calls sum() via the mu object reference variable, with 10 as sum()'s sole argument. sum() calculates the sum of integers ranging from 1 to 10 and returns the result. The sum is then assigned to sumTotal.
Once a return statement executes, where does program execution continue? If a method call is not part of an expression, execution continues with the statement that immediately follows the method call. However, if the method call is an operand to some operator in an expression, execution continues with the operator that evaluates its other operand(s).
How does the JVM know the return location at which to continue program execution? Internally, the JVM maintains a data structure known as a method call stack. Before the JVM calls a method, it pushes the address of the next instruction following the method-call byte code instruction onto the method call stack. When execution leaves the method block -- either from a return statement or from falling off the method's bottom -- the JVM pops this address from the method call stack and execution picks up with the first byte code instruction following the original method-call byte-code instruction. (I explore data structures and stacks in a future article.)
You can declare a method with an access specifier keyword: public, private, or protected. The access specifier determines how accessible the method is to code in other classes (for calling purposes). Access ranges
from totally callable to totally uncallable.
If you do not declare a method with an access specifier keyword, Java assigns a default access level to the method, making the method callable within its class and to all classes within the same package. Any methods of any class not declared in the same package as the class that contains the method to be called cannot call that method. Examine the code below:
class Temperature
{
private int degrees;
void setDegrees (int d)
{
degrees = d;
}
int getDegrees ()
{
return degrees;
}
}
Only methods declared in Temperature and other classes declared in the same package as Temperature can call setDegrees() and getDegrees().
If you declare a method private, only code contained in its class can call that method. The method becomes inaccessible to every other class in every other
package. Consider the example below:
class MathUtilities
{
private int factorial (int n)
{
if (n == 0)
return 1; // 0! (0 factorial) returns 1
int product = 1;
for (int i = 2; i <= n; i++)
product *= i;
return product;
}
int permutation (int n, int r)
{
return factorial (n) / factorial (n - r);
}
}
Only code contained in the above example's MathUtilities class can call factorial(). The factorial() method is a helper method to permutation() because it helps permutation() calculate a permutation. As such, factorial() isn't designed to be called from outside its class. (A case could be made for not declaring factorial() private because that method is quite useful. However, for the purpose of the above example, let's just say that factorial() should be declared private.) When a method only exists to serve other methods in a class, that helper method should be declared private. Declaring helper methods private helps promote information hiding and keeps class design clean and easy to maintain.
If you declare a method public, code contained in its class and other packages' classes can call that method, as shown in the example below:
public class Color
{
private int color;
public int getColor () { return color; }
}
Declaring Color public makes Color accessible to other packages. However, getColor() is not callable from code in those packages unless it is also marked public.
Finally, if you declare a method protected, that method resembles a method with default access level. The only difference: subclasses in any package can access protected methods. Examine the following code:
class Employee
{
// Several fields are declared but not shown in this example.
protected double computeSalary ()
{
return hoursWorked * hourlyRate;
}
}
Only code contained in Employee, other classes declared in the same package as Employee, and all Employee's subclasses (declared in any package) can call computeSalary().
You can declare a method with a modifier keyword: abstract or final and/or static and/or native and/or synchronized.
If you declare a method abstract, it only consists of a method signature; there is no block. Furthermore, the class in which the abstract method is declared must also be declared abstract. Finally, an abstract method cannot be declared final, static, native, synchronized, or private at the same time.
If you declare a method final, the compiler ensures that the method cannot be overridden by subclasses.
If you declare a method static, that method is known as a class method because it can only access class fields. However, if static is not specified, the method is known as an instance method because it can access instance fields as well as class fields.
If you declare a method native, only a method signature is present. The method body is written in C++ and stored in a platform-specific library.
Finally, if you declare a method synchronized, only one thread at a time can execute method code.
Note: I will discuss abstract, native, and synchronized methods as well as method overriding in future articles.
An instance method accesses the instance fields and class fields declared in its class. To declare an instance method, do not specify the static keyword in the instance method signature. Check out the code below:
class Employee
{
private double salary;
void setSalary (double s)
{
salary = s;
}
}
setSalary() is an instance method; it accesses the salary instance field.
Suppose you have an instance method with a parameter that has the same name as an instance field. Is it possible to assign the parameter's value to the same-named field? For an answer, examine the following example:
class Employee
{
private double salary;
void setSalary (double salary)
{
this.salary = salary;
}
}
The setSalary() instance method declares a parameter named salary. That method's body assigns the parameter salary to the instance field salary. To accomplish that task, setSalary() uses keyword this.
One of the stranger keywords in Java and other object-oriented languages, this identifies the current object at the source code level. When you prefix a field name with this, you say, "Access the field belonging to this object."
Suppose you want to call an instance method. The manner in which you make that call depends on whether the instance method is being called from another instance method in the same class, from another instance method in another class, from a class method in the same class, or from a class method in another class.
Call an instance method from another instance method in the same class by specifying the method's name and argument list. The following example demonstrates that:
class MyClass
{
void first () { second (true); }
void second (boolean firstFlag)
{
if (firstFlag)
System.out.println ("First flag.");
else
System.out.println ("Second flag.");
}
}
The above example's first() instance method calls the second() instance method with a default argument value set to true. You don't have to prefix the method call with an object reference variable.
Call an instance method from another class's instance method by completing the following: Create an object from the former instance method's class, assign that object's reference to an object reference variable, and prefix the object reference variable to the instance method. This code demonstrates that:
class AnotherClass
{
void callSecond ()
{
MyClass mc = new MyClass ();
mc.second (false);
new MyClass ().second (false);
}
}
AnotherClass's callSecond() method creates a MyClass object and assigns its reference to mc. callSecond(), then uses the mc. prefix to call MyClass's second() instance method with a false argument value.
AnotherClass also demonstrates that you don't need an object reference variable to call an instance method. Instead, the new MyClass ().second (false); expression creates an object from MyClass and then implicitly uses that object's reference to call second().
Finally, to call an instance method from either a class method in the same class as the instance method or a different class's class method, follow the same approach as you did when you called the instance method from another class's instance method.
Instance method calls can be chained together using a technique called method chaining, which the following example demonstrates:
class HelloGoodBye
{
public static void main (String [] args)
{
new HelloGoodBye ().hello ().goodbye ();
}
HelloGoodBye hello ()
{
System.out.println ("Hello");
return this;
}
void goodbye ()
{
System.out.println ("Goodbye");
}
}
HelloGoodBye's main() method creates a new HelloGoodBye object and calls its hello() method. Because hello() returns a reference to that same object (via hello()'s return this; statement), a goodbye() method call is chained to the end of the hello() method call. (You will sometimes see method chaining used in Java source code, so it's good to know what's happening.)
A class method is a method that accesses the class fields declared in its class. To declare a class method, specify the static keyword in the class method signature. Check out this code:
class Employee
{
private static int numEmployees;
static void setEmployeeCount (int numEmp)
{
numEmployees = numEmp;
}
}
The above example's setEmployeeCount() class method assigns the value of its numEmp parameter to Employee's numEmployees class field. If the parameter had been named numEmployees, could you use this to assign the parameter to the same-named class field? For an answer, check out the example below:
class Employee
{
private static int numEmployees;
static void setEmployeeCount (int numEmployees)
{
this.numEmployees = numEmployees; // Compiler error.
}
}
You cannot use this within a class method because this refers to the current class instance, and a class method is not associated with any class instance.
The manner in which you call a class method depends on whether the class method is being called from another class method in the same class, from another class's class method, from an instance method in the same class, or from another class's instance method.
You can call a class method from another class method in the same class by specifying the method's name and argument list as in the following code:
class FirstAndSecond
{
static void first ()
{
System.out.println ("first");
second ();
}
static void second ()
{
System.out.println ("second");
}
}
The above example's first() class method calls the second() class method. You don't have to prefix the method call with a class name.
To call a class method from another class's class method, prefix the class method call with the name of its class and the dot operator:
class UseFirstAndSecond
{
public static void main (String [] args)
{
FirstAndSecond.first ();
FirstAndSecond.second ();
}
}
The above example's main() method calls the first() and second() methods in FirstAndSecond. The main() method accomplishes that task by prefixing FirstAndSecond. to each class method.
To call a class method from an instance method in the same class, specify the method's name and argument list. That is the same as calling the class method from another class method in the same class.
Finally, you call a class method from another class's instance method just as you do when calling the class method from another class's class method.
So far, I have said nothing about how to pass arguments to either instance or class methods during a method call. In the C++ world, there are two approaches to this task: call-by-value and call-by-reference. Call-by-value makes a copy of each method argument and passes that copy to the method. Because the method receives only a copy of the argument value, that method cannot modify the original value. In contrast, call-by-reference passes the address of an argument's variable to the method. That implies the method does not receive a copy of the argument value, but receives the address of that value's storage location -- and can modify the original value.
Java supports call-by-value and does not support call-by-reference. Therefore, only a copy of each primitive or reference argument passes to a Java method during a method call. That method can modify the copy's value, but cannot modify the original value. To put this idea into perspective, take a close look at Listing 3:
Listing 3. CBDemo.java
// CBDemo.java
class Employee
{
private double salary;
double getSalary () { return salary; }
void setSalary (double salary) { this.salary = salary; }
}
class CBDemo
{
public static void main (String [] args)
{
int v = 12;
Employee e = new Employee ();
e.setSalary (50000.0);
System.out.println ("v = " + v);
System.out.println ("e salary = " + e.getSalary ());
changeArgs (v, e);
System.out.println ("v = " + v);
System.out.println ("e salary = " + e.getSalary ());
}
static void changeArgs (int x, Employee y)
{
x = 30;
y.setSalary (30000.0);
}
}
CBDemo declares and initializes two variables in its main() method: v and e. Variable v is based on the integer primitive data type, whereas e is a reference variable that holds a reference to an object created from Employee. At the start of the changeArgs(), method call, the JVM allocates memory (on the method call stack) for temporary storage. From a source code perspective,
one of the temporary storage locations is named x, whereas the other temporary storage location is named y. The JVM then copies the value in v to x, and copies the value in e to y. Within changeArgs(), an assignment is made to x, and an attempt is made to change the salary field of the Employee object that y references. (The JVM destroys both x and y when execution leaves changeArgs().) Take a look at the following output:
v = 12 e salary = 50000.0 v = 12 e salary = 30000.0
Clearly, x = 30; did not change the value in v. Therefore, v must have been passed call-by-value to changeArgs(). But was e also passed call-by-value? The answer is yes. The JVM placed a copy of e's reference value in y. At that point, both e and y referenced the same Employee object, and could call that object's setSalary() method to manipulate its salary field. If the JVM had used call-by-reference, y would contain a reference to variable e, and not a reference to the object -- and y.setSalary (30000.0); would probably crash the JVM.
Are you still skeptical about e being passed call-by-value? If so, perform the following experiment: Add y = null; to the end of changeArgs() -- just after y.setSalary (30000.0); -- and recompile. Run the program. If you see the previous output, e was passed call-by-value. But if you see an error message stating something about NullPointerException, e was passed call-by-reference. That error message arises from the e.getSalary() method call that follows the changeArgs() method call. If e's value was passed call-by-reference, y = null; would assign null to e, and the attempt to call the getSalary() method, via e's null reference, would cause the JVM to terminate that program with a NullPointerException message. However, you will not see that message because e was passed call-by-value.
| Note |
|---|
| As with other object reference arguments, the JVM uses call-by-value to pass array reference arguments to a method. As a result, a method can modify an array's element values. However, the method cannot assign an array's reference to the original array variable (located outside the method). |
Declaring multiple methods with the same name but with different parameter lists is known as overloading a method. The compiler selects the appropriate method by choosing a method whose name, number of parameters, and parameter data types match a method call's name, number of arguments, and argument data types. Examine the code below:
void print ()
{
System.out.println ("Default");
}
void print (String arg)
{
System.out.println (arg);
}
The above example demonstrates two overloaded print() methods. Specifying print() results in a call to the top method, whereas specifying print ("Hello"); results in a call to the bottom method.
A method cannot be overloaded by only changing return data types. The compiler reports an error if it encounters two method declarations in the same class with the same names and parameter lists:
int sum (int x, int y)
{
return x + y;
}
long sum (int x, int y)
{
return x + y;
}
The above example demonstrates two nonoverloaded sum() methods. The compiler will report an error because declaring a method that is identical to an existing method except for
a different return data type does not give the compiler enough information to tell those methods apart. How can the compiler
distinguish between the above example's sum() methods when confronted with sum (3, 2)?
A constructor is a special method called by the creation operator to initialize an object. Place your own custom initialization code in the constructor. Because constructors are methods, they can be declared with parameter lists and access specifiers. However, a constructor must be given the same name as the class in which it is declared. Furthermore, a constructor must not be declared with a return data type.
Refer to Listing 2 in Part 1, which presents a CODemo2 class along with the following line of code: CODemo2 obj1 = new CODemo2 ();. I noted that CODemo2() is a constructor. However, there was no CODemo2() constructor declared in CODemo2. What's going on? Basically, when you declare a class and do not specify a constructor, the compiler creates a default constructor
with an empty parameter list. Take a look at this example:
class X
{
}
X looks empty, but it's not. In reality, the compiler sees X with a default no-argument constructor, as demonstrated by the following example:
class X
{
X ()
{
}
}
The compiler only creates a default no-argument constructor when no other constructors are explicitly declared in the class. If you declare any constructor in the class, the compiler will not generate a default no-argument constructor. See the code below:
class X
{
private int i;
X (int i)
{
this.i = i;
}
}
class UseX
{
public static void main (String [] args)
{
X x1 = new X (2);
X x2 = new X (); // Compiler error.
}
}
In the preceding example, an attempt to call the default no-argument constructor results in a compiler error because there is no default no-argument constructor.
Constructors initialize instance fields to explicit values. The compiler inserts initialization code that explicitly initializes instance fields into the constructor prior to any developer-provided code. For proof, check out the following example:
class X
{
int i = 5;
X ()
{
System.out.println (i);
}
}
class UseX
{
public static void main (String [] args)
{
X x = new X ();
}
}
If you run UseX, you'll discover 5 as that program's output. Obviously, the compiler must have initialized i prior to the System.out.println (i); method call in X().
You can declare multiple constructors in a class. Multiple constructors make it possible to perform different kinds of initialization. To see an example, check out Listing 4:
Listing 4. Geometry.java
// Geometry.java
class Circle
{
private int x, y, r;
Circle ()
{
// System.out.println ("Before");
this (0, 0, 1);
System.out.println ("After");
}
Circle (int x, int y, int r)
{
this.x = x;
this.y = y;
this.r = r;
}
int getX () { return x; }
int getY () { return y; }
int getRadius () { return r; }
}
class Geometry
{
public static void main (String [] args)
{
Circle c1 = new Circle ();
System.out.println ("Center X = " + c1.getX ());
System.out.println ("Center Y = " + c1.getY ());
System.out.println ("Center Radius = " + c1.getRadius ());
Circle c2 = new Circle (5, 6, 7);
System.out.println ("Center X = " + c2.getX ());
System.out.println ("Center Y = " + c2.getY ());
System.out.println ("Center Radius = " + c2.getRadius ());
}
}
Geometry.java declares a pair of classes: Circle and Geometry. Class Circle declares a pair of constructors. The first constructor takes no arguments and contains the following line of code: this (0, 0, 1);, which calls the second three-argument constructor. (Java requires use of keyword this in place of a constructor name.)
A constructor call, via this(), must be the first developer-provided code in a constructor method. Otherwise, the compiler reports an error. (That is the
reason for turning System.out.println ("Before"); into a comment.) Because a constructor call must be the first line of code, you cannot specify two constructor calls in a
constructor. For example, you cannot state this (0, 0, 1); this (60, 30, 30);. If you try, the compiler reports an error.
If you study the source code to Java's class library, you'll come across class declarations consisting of only a single constructor
declared private. In addition, you might encounter a method called getInstance() that allows only a single object from that class to be created. These types of classes are singletons.
A Singleton is a design pattern -- frequently occurring code specification -- in which only a single object can ever be created from a class. The reason for the restriction is the cost and the probability of error associated with the creation of multiple objects from that class. For example, suppose you declare a class that mimics a network connection. You only need a single object to communicate with the network connection because establishing the connection is time-intensive and subject to error (the connection might be down). The following example declares a singleton class that provides network access:
public class NetworkConnection
{
private static NetworkConnection singleton;
private NetworkConnection ()
{
// The code in this constructor establishes the network connection.
// This code deals with situations in which the connection is down.
}
public static NetworkConnection getInstance ()
{
if (singleton == null)
singleton = new NetworkConnection ();
return singleton;
}
}
Because NetworkConnection declares a single private constructor, it is not possible to specify NetworkConnection nc = new NetworkConnection ();. Only one instance of NetworkConnection can be created, by calling getInstance. The following code demonstrates that:
NetworkConnection nc = NetworkConnection.getInstance ();
In this article, you learned how to declare fields and methods by using various access specifiers and modifiers. Furthermore, you learned how to distinguish between class and instance fields, and how to distinguish between class and instance methods. Finally, you learned about enumerated types and singletons.
The next article in this series introduces the second object-oriented principle -- inheritance.