Java 101: Object-oriented language basics, Part 2: Fields and methods

Learn how to declare and access fields and methods in Java

Delve into fields and methods with this second article in the Java 101 "Object-oriented language basics" series. Deepen your understanding of fields, parameters, and local variables and learn to declare and access fields and methods.

Ever hear the expression, "I can't see the forest for the trees?" That expression refers to specific details that cloud your understanding of the big picture. And what could be cloudier than a detailed examination of fields and methods during an introduction to Java's classes and objects? That is the reason I chose to minimize fields and methods while maximizing the big picture -- classes and objects -- in Part 1 of this object-oriented language series. However, because you eventually have to step back from the forest and focus on the trees, this month's article examines fields and methods in great detail.

Java's variables can be divided into three categories: fields, parameters, and local variables. I'll present fields here and leave an exploration of parameters and local variables for a later section that discusses methods.

Declaring fields

A field is a variable declared in a class body that holds either part of an object's state (an instance field) or part of a class's state (a class field). Use the following Java syntax to declare a field in source code:

[ ( 'public' | 'private' | 'protected' ) ] 
  [ ( 'final' | 'volatile' ) ] 
    [ 'static' ] [ 'transient' ]
      data_type field_name [ '=' expression ] ';'

A field declaration specifies the field's name, data type, optional expression (which initializes the field), access specifiers, and modifiers. Choose identifiers for the data type and name that are not reserved words. Consider this example:

class Employee
{
   String name;    // The employee's name.
   double salary;  // The employee's salary.
   int jobID;      // The employee's job identification code (e.g., accountant, payroll clerk, manager, and so on).
}

The Employee class declares three fields: name, salary, and jobID. When you create an Employee object, name eventually holds a reference to a String object that contains the employee's name. The salary field will hold the employee's salary, and jobID will hold an integer that identifies the employee's job category.

Access specifiers

You can optionally declare a field with an access specifier keyword: public, private, or protected. The access specifier determines how accessible the field is to code in other classes. Access ranges from totally accessible to totally inaccessible.

If you do not declare a field with an access specifier keyword, Java assigns the field a default access level, which makes the field accessible within its class and to all classes within the same package. (Think of packages as libraries of classes -- a concept I'll explore in a future article.) Any class not declared in the same package as the class that contains the field cannot access the field. Take a look at the following example:

class MyClass
{
   int fred;
}

Only code contained in MyClass and other classes declared in the same package as MyClass can access fred.

If you declare a field private, only code contained in its class can access the field. That field becomes inaccessible to every other class in every other package. Examine the code below:

class Employee
{
   private double salary;
}

Only code contained in Employee can access salary.

If you declare a field public, code contained in its class and all other packages' classes can access the field. Refer to the code below:

public class Employee
{
   public String name;
}

Code contained in Employee and all other packages' classes can access name. (Employee must also be declared public before code in other packages can access name.)

Declaring every field in a given class public defeats the concept of information hiding. Suppose you create a Body class to model the human body and Eye, Heart, and Lung classes to model an eye, the heart, and a lung. The Body class declares Eye, Heart, and Lung reference fields, as demonstrated by the following example:

public class Body
{
   public Eye leftEye, rightEye;
   private Heart heart;
   private Lung leftLung, rightLung;
}

The leftEye and rightEye field declarations are public because a body's eyes are visible to an observer. However, the heart, leftLung, and rightLung declarations are private because the organs they represent are hidden inside the body. Suppose heart, leftLung, and rightLung were declared public. Wouldn't that be the equivalent to having a body with its heart and lungs exposed?

Finally, a field declared protected resembles a field with the default access level. The only difference between the two access specifiers is that subclasses in any package can access the protected field. The following example demonstrates that:

public class Employee
{
   protected String name;
}

Only code contained in Employee, other classes declared in the same package as Employee, and all Employee's subclasses (declared in any package) can access name.

Modifiers

You can optionally declare a field with a modifier keyword: final or volatile and/or static and/or transient.

If you declare a field final, the compiler ensures that the field is initialized and subsequently treats the field as a constant -- a read-only variable. The compiler can now perform internal optimizations on a program's byte codes because it knows the constant will not change. Consider the example below:

class Employee
{
   final int ACCOUNTANT = 1;
   final int PAYROLL_CLERK = 2;
   final int MANAGER = 3;
   int jobID = ACCOUNTANT;
}

The example above declares three final int fields: ACCOUNTANT, PAYROLL_CLERK, and MANAGER.

Note: It is customary to use all capital letters and separate multiple words with underscores when declaring constants. That helps distinguish constants from read/write variables when analyzing source code.

If you declare a field volatile, multiple threads can access the field, and certain compiler optimizations are prevented so that the field is accessed appropriately. (You'll learn about volatile fields when I discuss threads in a future article.)

If you declare a field static, all objects share one copy of the field. When you assign a new value to that field, all objects can see the new value. If static is not specified, the field is known as an instance field, and each object receives its own copy.

Finally, the value of a field declared transient will not be saved during object serialization. (I explore the topics of transient fields and object serialization in a future article.)

Instance fields

An instance field is a field declared without the static keyword modifier. Instance fields are associated with objects -- not classes. When modified by an object's code, only the associated class instance -- the object -- sees the change. An instance field is created when an object is created and destroyed when its object is destroyed.

The following example demonstrates an instance field:

class SomeClass1
{
   int i = 5;
   void print ()
   {
      System.out.println (i);
   }
   public static void main (String [] args)
   {
      SomeClass1 sc1 = new SomeClass1 ();
      System.out.println (sc1.i);
   }
}

SomeClass1 declares an instance field named i and demonstrates two common ways to access that instance field -- from an instance method or from a class method. Both methods are in the same class as the instance field.

To access an instance field from an instance method in the same class, you only specify the field's name. To access an instance field from another class's instance method, you must have an object reference variable that contains the address of an object created from the class that declares the instance field you want to access. Prefix the object reference variable -- along with the dot operator -- to the instance field's name. (You'll explore instance methods later in this article.)

To access an instance field from a class method in the same class, create an object from the class, assign its reference to an object reference variable, and prefix that variable to the instance field's name. To access an instance field from another class's class method, complete the same steps as when you accessed that field from a class method in the same class. (I'll present class methods later in this article.)

When the JVM creates an object, it allocates memory for each instance field and subsequently zeroes the field's memory, which establishes an instance field's default value. The way you interpret a default value depends on the field's data type. Interpret a reference field's default value as null, a numeric field's default value as 0 or 0.0, a Boolean field's default value as false, and a character field's default value as \u0000.

Class fields

A class field is a field declared with the static keyword modifier. Class fields are associated with classes -- not objects. When modified by a class's code, the class (as well as any created objects) sees the change. A class field is created when a class is loaded and destroyed if and when a class is unloaded. (I believe some JVMs unload classes whereas other JVMs do not.)

The example below illustrates a class field:

class SomeClass2
{
   static int i = 5;
   void print ()
   {
      System.out.println (i);
   }
   public static void main (String [] args)
   {
      System.out.println (i);
   }
}

SomeClass2 declares a class field i and demonstrates two common ways to access i -- from an instance or class method (both methods are in the same class as the class field).

To access a class field from an instance method in the same class, you only specify the field's name. To access a class field from another class's instance method, prefix the class field with the name of the class in which the class field is declared. For example, specify SomeClass2.i to access i from an instance method in another class -- which is in the same package as SomeClass2, because SomeClass2 isn't declared public.

To access a class field from a class method in the same class, you only specify the field's name. To access a class field from another class's class method, follow the same procedure as you did when accessing the class field from an instance method in another class.

Once a class loads, the JVM allocates memory for each class field and establishes a class field's default value. Interpret a class field's default value just as you would interpret an instance field's default value.

Class fields are the closest things to global variables in Java. Check out the example below:

class Global
{
   static String name;
}
class UseGlobal
{
   public static void main (String [] args)
   {
      Global.name = "UseGlobal";
      System.out.println (Global.name);
   }
}

The above example declares a pair of classes in a single source file: Global and UseGlobal. If you compile and then run the application, the JVM loads UseGlobal and then begins executing the main() method's byte codes. After seeing Global.name, the JVM searches for, loads, and then verifies the Global class. Once Global is verified, the JVM allocates memory for name and initializes that memory to null. Behind the scenes, the JVM creates a String object and initializes that object to all characters between the pair of double quote characters -- UseGlobal. That reference assigns to name. Then, the program accesses Global.name and retrieves the String object's reference, which subsequently passes to System.out.println(). Finally, the contents of the String object appear on the standard output device.

Because neither Global nor UseGlobal are explicitly marked public, you can choose any name for the source file. Compiling that source file results in a pair of class files: Global.class and UseGlobal.class. Because UseGlobal contains the main() method, use that class to run the program. Type java UseGlobal to run the program at the command line.

If you type java Global instead, you would receive the following error message:

Exception in thread "main" java.lang.NoSuchMethodError: main

The word main at the end of the error message indicates that java couldn't find a main() method in class Global.

Constants

A constant is a read-only variable; once the JVM initializes that variable, the variable's value cannot change.

Declare constants with the final keyword. Just as there are two kinds of fields, instance and class, constants come in two flavors -- instance and class. For efficiency, create class constants, or final static fields. Consider this example:

class Constants
{
   final int FIRST = 1;
   final static int SECOND = 2;
   public static void main (String [] args)
   {
      int iteration = SECOND;
      if (iteration == FIRST) // Compiler error.
          System.out.println ("first iteration");
      else
      if (iteration == SECOND)
          System.out.println ("second iteration");
   }
}

The above example's Constants class declares a pair of constants -- FIRST and SECOND. FIRST is an instance constant because the JVM creates a separate copy of FIRST for each Constants object. In contrast, because the JVM creates a single copy of SECOND after loading Constants, SECOND is a class constant.

Note: A compiler error occurs when you attempt to directly access FIRST in main(). Constant FIRST does not exist until an object is created, but then FIRST is only accessible to that object -- not the class.

Use constants for the following reasons:

  1. In source code, it is easier and less error-prone to change a constant's initial value than to replace all occurrences of a hard-coded magic number.
  2. Constants facilitate reading and understanding source code. For example, the constant NUM_ARRAY_ELEMENTS is more meaningful than the number 12.

Enumerated types

Suppose you write a Java program, Zoo1, that models a zoo of circus animals:

Listing 1. Zoo1.java

// Zoo1.java
class CircusAnimal
{
   final static int TIGER = 1;
   final static int LION = 2;
   final static int ELEPHANT = 3;
   final static int MONKEY = 4;
}
class Zoo1
{
   private int animal;
   public static void main (String [] args)
   {
      Zoo1 z1 = new Zoo1 ();
      z1.animal = CircusAnimal.TIGER;
      // Some time later ...
      if (z1.animal == CircusAnimal.TIGER)
          System.out.println ("This circus animal is a tiger!");
      else
      if (z1.animal == CircusAnimal.MONKEY)
          System.out.println ("This circus animal is a monkey!");
      else
          System.out.println ("Don't know what this circus animal is!");
   }
}

Zoo1.java declares a pair of classes: CircusAnimal and Zoo1. Class CircusAnimal declares some convenient integer constants that represent various circus animals, whereas Zoo1 is the starting class -- its main() method runs the Zoo1 application.

The main() method creates a Zoo1 object, assigns that object's reference to z1, and initializes the object's animal instance field to CircusAnimal.TIGER. Even though animal is private, method main() has access to that variable -- through the z1 object reference variable -- because main() is part of the same class in which animal is declared. Later, main() checks animal's current value and outputs an appropriate message based on its findings.

Zoo1 features a problem: animal's int data type keyword. Suppose we assign z1.animal to 978324 -- which is legal because both animal and 978324 have the integer data type. The resulting assignment would be meaningless. What animal does 978324 represent? Using an integer as animal's data type makes it possible to create irrational code, which often leads to bugs. A solution to that problem is to give animal a more appropriate data type and restrict the set of values that can assign to animal. That is the rationale behind using enumerated data types.

An enumerated data type is a reference data type with a restricted set of values. Each value is an object created from the enumerated data type, as shown below:

Listing 2. Zoo2.java

// Zoo2.java
class CircusAnimal
{
   static final CircusAnimal TIGER = new CircusAnimal ("Tiger");
   static final CircusAnimal LION = new CircusAnimal ("Lion");
   static final CircusAnimal ELEPHANT = new CircusAnimal ("Elephant");
   static final CircusAnimal MONKEY = new CircusAnimal ("Monkey");
   private String animalName;
   private CircusAnimal (String name)
   {
      animalName = name;
   }
   public String toString ()
   {
      return animalName;
   }
}
class Zoo2
{
   private CircusAnimal animal;
   public static void main (String [] args)
   {
      Zoo2 z2 = new Zoo2 ();
      z2.animal = CircusAnimal.TIGER;
      // Some time later ...
      if (z2.animal == CircusAnimal.TIGER)
          System.out.println ("This circus animal is a tiger!");
      else
      if (z2.animal == CircusAnimal.MONKEY)
          System.out.println ("This circus animal is a monkey!");
      else
          System.out.println ("Don't know what this circus animal is!");
   }
}

The Zoo2 class closely resembles Listing 1's Zoo1 class, with the major difference being animal's data type. Instead of int, animal now has the CircusAnimal data type -- only references to objects created from CircusAnimal can be assigned to animal.

The CircusAnimal class declares four CircusAnimal constants: TIGER, LION, ELEPHANT, and MONKEY. Each constant initializes to a CircusAnimal object.

A special CircusAnimal constructor, which takes a single String argument, is declared. The string passed to the constructor is saved in a private animalName field. (I discuss constructors later in this article.)

The constructor is declared private to prevent the creation of CircusAnimal objects beyond those four objects declared as constants. You don't want someone to create a CircusAnimal object equivalent to the meaningless 978324. Only the four CircusAnimal constants are valid for the Zoo2 program.

Why is a toString() method declared in CircusAnimal? That method returns the value of the String object, whose reference is assigned to animalName. Suppose you call System.out.println (z2.animal) after initializing z2.animal to CircusAnimal.TIGER. The result: Tiger will print, which is the value passed to CircusAnimal's constructor when the TIGER constant was created. System.out.println() acquires Tiger behind the scenes by calling the toString() method. (I'll have more to say about toString() in a later article.)

Now that you know how to declare and access fields, you need to learn how to declare and access methods.

Declaring methods

Java uses the term method to refer to named bodies of code that are associated with classes. Whereas fields hold either an object's state or class values, methods describe the behaviors of either an object or a class. Furthermore, Java's methods are completely declared inside classes. To declare a method in source code, use the following Java syntax:

[ ( 'public' | 'private' | 'protected' ) ] 
  ( [ 'abstract' ] | [ 'final' ] [ 'static' ] [ 'native' ] [ 'synchronized' ] )
    return_data_type method_name '(' [ parameter_list  ] ')'
    compound_statement

A method declaration consists of a method signature followed by a compound statement. The method signature specifies the method's name, return data type, parameter list, access specifiers, modifiers, and the types of exceptions the method can throw. (I discuss throwing exceptions in a future article.) The compound statement is a block (group) of statements that executes when the method is called by code executing in another method. Code that calls a method is known as the method's caller. See "Non-Object-Oriented Language Basics, Part 3" for the syntax of a compound statement.

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:

  • One of the primitive data type keywords boolean, byte, char, double, float, int, long, or short
  • A reference data type identifier

If 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.)

Access specifiers

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().

Modifiers

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.

Instance methods

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.)

Class methods

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).

Overloading methods

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)?

Constructors

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 ();

Review

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.

Jeff Friesen has been involved with computers for the past 20 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he wrote his own Java book for beginners -- Java 2 By Example (QUE, 2000) -- and helped write a second Java book, Special Edition Using Java 2 Platform (QUE, 2001). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.

Learn more about this topic

  • For a glossary specific to this article, homework, and more, see the Java 101 study guide that accompanies this article
    http://www.javaworld.com/javaworld/jw-05-2001/jw-0504-java101guide.html
  • The Java Language Specification, Second Edition, James Gosling, Bill Joy, Guy Steele, Gilad Bracha (Sun Microsystems, 2000)
    http://www.javasoft.com/docs/books/jls/second_edition/html/j.title.doc.html
  • The Java Tutorial
    http://www.javasoft.com/tutorial/
  • Sun's official Java language definition
    http://java.sun.com/docs/overviews/java/java-overview-1.html
  • Learn more about singletons in Joshua Fox's article "When is a Singleton not a Singleton," (JavaWorld, January 12, 2001)
    http://www.javaworld.com/jw-01-2001/jw-0112-singleton.html
  • Find out how the compiler handles constructors in "Constructor Help," Tony Sintes (JavaWorld, November 22, 2000)
    http://www.javaworld.com/javaqa/2000-11/04-qa-1122-constructor.html
  • Read "Understanding Constructors," Robert Nielsen (JavaWorld, October 13, 2000) to learn more about constructors
    http://www.javaworld.com/jw-10-2000/jw-1013-constructors.html
  • Need more help with Java? Please visit the Java Beginner discussion
    http://www.itworld.com/jump/jw-0504-java101/forums.itworld.com/webx?230@@.ee6b804
  • Sign up for the JavaWorld This Week free weekly email newsletter and keep up with what's new at JavaWorld
    http://www.idg.net/jw-subscribe
  • Check out past Java 101 articles
    http://www.javaworld.com/javaworld/topicalindex/jw-ti-java101.html
  • For more articles geared toward the Java beginner, browse the Intro Level section of JavaWorld's Topical Index
    http://www.javaworld.com/javaworld/topicalindex/jw-ti-introlevel.html
  • Java experts answer your toughest Java questions in JavaWorld's Java Q&A column
    http://www.javaworld.com/javaworld/javaqa/javaqa-index.html
  • For Tips 'N' Tricks see
    http://www.javaworld.com/javaworld/javatips/jw-javatips.index.html
  • Join the discussion
    Be the first to comment on this article. Our Commenting Policies
    See more