Methods

Goals

Concepts

Language

Javadoc

Library

Lesson

In the previous lesson on classes you learned that the main purpose of objects are to encapsulate data and functionality. In that lesson you learned how to include instance variables which live independently with each instance of the class, and how these instance variables are different from static variables which only have one instance, associated with the class itself. You also learned how to use a constructor to initialize instance variables each time a class is instantiated. The following example class encapsulates the x and y values of a two-dimensional geometric point.

com/example/Point.java
package com.example;

class Point
{

  int x;
  int y;

  Point(final int x, final int y)
  {
    this.x = x;
    this.y = y;
  }
}

Instance Methods

So far this description of classes has only covered the data portion of encapsulation. For encapsulating functionality, classes provide methods, which allow arguments and optionally return a value.

The methods you have learned about so far included the word static. If a method does not use the static keyword, it is called an instance method and can access the instance variables of the class as if those variables were the only ones that existed. Other instances of the class will have separate copies of those same instance variables. Instance methods know how to access only the instance variables of the instance on which they were invoked, because behind the scenes they use the equivalent of the this “variable” you learned about earlier.

The following example shows how a geometrical Point class might provide an instance method for moving the point's location, so that the program calling the method (the caller) does not have to make these calculations itself and worry about updating the point's internal variables. The method has encapsulated the functionality of moving the point.

Point class with move(…) instance method.
package com.example;

class Point
{

  int x;
  int y;

  Point(final int x, final int y)
  {
    this.x = x;
    this.y = y;
  }

  /**Moves the point by the given two deltas.
   * @param deltaX The positive or negative value for horizontal change.
   * @param deltaY The positive or negative value for vertical change.
   */
  void move(final int deltaX, final int deltaY)
  {
    this.x += deltaX;
    this.y += deltaY;
  }
}
final Point point = new Point(1, 1);
System.out.println("point x: " + point.x);  //prints "1"
System.out.println("point y: " + point.y);  //prints "1"

point.move(3, -5);

System.out.println("point x: " + point.x);  //prints "4"
System.out.println("point y: " + point.y);  //prints "-4"

An instance method can optionally provide a return value to the caller. Here for example is a method that calculates the slope between one point and another point.

Point class with calculateSlope(…) instance method.
package com.example;

class Point
{

  int x;
  int y;

  Point(final int x, final int y)
  {
    this.x = x;
    this.y = y;
  }

  /**Calculates and returns the slope of a line between this point
   * and the given coordinates.
   * @param x2 The other x coordinate.
   * @param y2 The other y coordinate.
   * @return The slope of the resulting line, or {@link Double#POSITIVE_INFINITY}
   *    if the resulting line would be vertical.
   */
  double calculateSlope(final int x2, final int y2)
  {
    final int deltaX = x2 - x;
    final int deltaY = y2 - y;
    if(deltaX == 0)
    {
      return Double.POSITIVE_INFINITY;
    }
    return (double)deltaY / (double)deltaX;
  }

  /**Calculates and returns the slope of a line between this point
   * and another one.
   * @param point2 The other point.
   * @return The slope of the resulting line, or {@link Double#POSITIVE_INFINITY}
   *    if the resulting line would be vertical.
   */
  double calculateSlope(final Point point2)
  {
    return calculateSlope(point2.x, point2.y);
  }
}
final Point point1 = new Point(1, 1);
System.out.println(point1.calculateSlope(-2, 3));  //prints "-0.66…"

final Point point2 = new Point(4, 5);
System.out.println(point1.calculateSlope(point2));  //prints "1.33…"

Class Methods

Just as a class can have instance variables and class variables, a class can also have instance methods and class methods. Also called “static methods”, these are the methods you created in previous lessons. Like class variables, class methods are declared by using the static keyword. Class methods most closely resemble the old-fashioned “procedures” and “functions” of non-object oriented languages, but in Java they are still grouped inside a class. To invoke a class method, you must use the name of the class followed by a dot . character and the name of the method, such as Foo.bar().

Class methods can provide general functionality that does not apply to any single instance of the class. They also provide a way to collect functionality related to instances of other classes. For example, think about the Point.calculateSlope() method above. Is it an expected behavior that a point would know how to calculate its slope compared with another point? Or is the concept of “slope” something that an individual point wouldn't know about, and instead some higher level entity might calculate instead? Design questions like this will be left to future lessons, but now as an example the calculateSlope() method is rewritten below as a class method of the Geometry class, which we'll use as a way to group together some static “utility methods”.

com/example/Geometry.java class with a utility class method.
package com.example;

class Geometry
{

  static final double UNDEFINED_SLOPE = Double.POSITIVE_INFINITY;

  /**Calculates and returns the slope of a line between two points.
   * @param point1 The first point.
   * @param point2 The other point.
   * @return The slope of the resulting line, or {@link #UNDEFINED_SLOPE}
   *    if the resulting line would be vertical.
   */
  static double slope(final Point point1, final Point point2)
  {
    final int deltaX = point2.x - point1.x;
    final int deltaY = point2.y - point1.y;
    if(deltaX == 0)
    {
      return UNDEFINED_SLOPE;
    }
    return (double)deltaY / (double)deltaX;
  }
}
final Point point1 = new Point(1, 1);
final Point point2 = new Point(4, 5);
System.out.println(Geometry.slope(point1, point2));  //prints "1.33…"

Varargs

You've already learned about varargs, and they can come in useful when working with your own custom classes. For example, here is a way to print a sequence of points, if you don't know how many points there are to print. Because this is not confined to a single point, we add it as a static utility method to the Point class itself. Remember that behind the scenes, Java will automatically create and pass an array, and the method can treat the argument as it would an array.

Passing multiple arguments using varargs.
package com.example;

class Point
{

  …

  /**Prints the coordinates of each point.
   * @param points The points to be printed.
   */
  static void print(final Point... points)   //NOTE: varargs... vs array[]
  {
    for(final Point point : points)
    {
      System.out.println("x: " + point.x + ", y: " + point.y);
    }
  }
}
final Point point1 = new Point(1, 1);
final Point point2 = new Point(4, 5);
Point.print(point1, point2);

Imports

If you wish to use a class from another package, such as those that come with the Java standard library, you can always use that class by typing its full name including package. For example there is a class named Random in the java.util package, which comes with the Java standard library. An instance of the class allows the caller to generate a stream of pseudorandom numbers by calling one or more of its methods.

The following snippet would print out three random numbers. Note how the Random class is identified by its full name java.util.Random.

final java.util.Random random = new java.util.Random();
for(int i = 0; i < 3; i++)
{
   System.out.println(random.nextInt());
}

Rather than entering the full name every time you access a class, you can can inform Java of which classes you intend to use at the beginning of your .java file. This is referred to as importing classes, and you do this with the import statement, naming the class that you would like to use.

Here is how you could use Random to generate some random points and print out their slope, using the method java.util.Random.nextInt() to generate random integers. The program uses the Point and Geometry classes defined above.

com/example/RandomPointPrinter.java
package com.example;

import java.util.Random;

/** Prints two random points and the slope between them. */
public class RandomPointPrinter
{

  public static main(String[] args)
  {
    final Random random = new Random();

    final Point point1 = new Point(random.nextInt(), random.nextInt());
    final Point point2 = new Point(random.nextInt(), random.nextInt());

    System.out.println("Points:");
    Point.print(point1, point2); //varargs

    final double slope = Geometry.slope(point1, point2); //not varargs
    if(slope == Geometry.UNDEFINED_SLOPE)
    {
      System.out.println("Slope: undefined");
    }
    else
    {
      System.out.println(String.format("Slope: %s:", slope));
    }
  }
}

Static Imports

A static import works a little differently than a class import. Rather than informing Java that you wish to use a class without needing to specify its package each time, the static import indicates that you want to use a static variable or static method in a class without needing to specify the class name each time. This can be used even for classes in the same package, as the static import is removing the need to indicate the class name, not the package name. A static import is similar to a class import, except that it uses import static and identifies the package, class, and member (variable or method) to be statically imported.

Take for example the static Geometry.slope(…) method we wrote above. Using static import we can rewrite our RandomPointPrinter so that we can simply say slope(…) instead of Geometry.slope(…). The same goes for the method Point.print(…) and the constant Geometry.UNDEFINED_SLOPE.

com/example/RandomPointPrinter.java with static imports.
package com.example;

import static com.example.Geometry.slope;
import static com.example.Geometry.UNDEFINED_SLOPE;
import static com.example.Point.print;

import java.util.Random;

/** Prints two random points and the slope between them. */
public class RandomPointPrinter
{

  public static main(String[] args)
  {
    final Random random = new Random();

    final Point point1 = new Point(random.nextInt(), random.nextInt());
    final Point point2 = new Point(random.nextInt(), random.nextInt());

    System.out.println("Points:");
    print(point1, point2);  //varargs

    final double slope = slope(point1, point2); //not varargs
    if(slope == UNDEFINED_SLOPE)
    {
      System.out.println(String.format("Slope: undefined"));
    }
    else
    {
      System.out.println(String.format("Slope: %s:", slope));
    }
  }
}

Access Level Modifiers

Package (Default) Access

Until this point we have been placing our custom classes such as Point and Geometry in the same package com.example as the program using those classes. This works because, by default, Java allows what is called package-level access to other classes and their members.

The access level (also called visibility, because it determines what other classes can "see" as far as the compiler is concerned) can be changed at two different places in a program:

The class level modifier sets the limit on visibility; regardless of a member's visibility setting, it can never be more visible than that of the class.

Public Access

But you have seen that we are also able to access classes outside our program package, such as java.lang.Double and java.util.Random. This is because the classes in those packages use an access level modifier to specify public access, both at the class level and at the method level. In fact you've seen public access at work in main(…) method of the “Hello World” program in the first lesson.

com/example/HelloWorld.java
package com.example;

public class HelloWorld
{
  public static void main(String[] args)
  {
    System.out.println("Hello, World!");
  }
}

Notice that the class is declared public so that it can be used by the world at large. If a class is declared as public, classes from other packages can access that class, but they still cannot call its methods unless they too are declared as public.

The remaining access levels apply only members (variables and methods of a class, both instance members and static members).

Protected Access

Protected access prevents members from being accessed outside their package, except by subclasses (in whatever package they reside). This access uses the keyword protected instead of public. You will learn more about subclasses in a future lesson, and we will explore protected-level access in more detail then.

Private Access

Using the private access level prevents members from being access by other classes, even those within the same package! A class would usually mark a method private if that method performed some temporary calculation that should not be available to the world at large.

Data Hiding

The program design concept of data hiding goes hand-in-hand with the object-oriented concept of encapsulation. A class not only encapsulates or bundles together related data and functionality, it can also hide that data and make it available only on a need-to-know basis, requiring access to go through the proper channel (e.g. through public methods). This makes the program safer by preventing inadvertent modification of an object's variables, or by ensuring that certain “side effects” always take place when values are changed.

One example might be a bank account. For security of the account we would not want all parts of the program to be able to manipulate the long value holding the account balance. We would want to account to only be accessed via methods such as deposit(long amount), which would ensure that "side effects" (such as sending an email confirmation) were always performed.

com/example/BankAccount.java
package com.example;

public class BankAccount
{

  private long balance; //private instance variable

  /** Default constructor with a zero balance. */
  public BankAccount()
  {
    this(0);
  }

  /** Initial amount constructor.
   * @param initialBalance The initial balance of the account, in cents.
   */
  public BankAccount(final long initialBalance)
  {
    balance = initialBalance;
  }

  /** Deposits money to the account.
   * <p>This method emails the current balance to the customer when finished.</p>
   * @param amount The amount, in cents to deposit.
   */
  public void deposit(final long amount)
  {
    balance += amount;
    emailCurrentBalance();
  }

  /** Emails the current balance to the customer. */
  private void emailCurrentBalance()
  {
    System.out.println(String.format("Emailing current balance %s...", balance));
    //TODO implement email functionality
  }
}

JavaBeans

With access modifiers classes can do a good job of hiding information. If they want to expose information, they can provide accessor methods. There is sometimes a need for other classes to discover which information a class is exposing. For example, when assembling components in a user interface tools need to know that a particular UI component allows its color to be changed.

Other languages have added language features that allow the developer to define “properties” that hide the actual storage yet force access to go through property methods. Java did not add property support to the language; instead it created a set of conventions: the JavaBeans component model. To create a “JavaBean” a developer follows the following pattern:

Here is how we could rewrite the Point class as a JavaBean:

com.example.Point as a JavaBean
package com.example;

class Point
{

  private int x;

  public int getX() {
    return x;
  }

  public void setX(final int x) {
    this.x = x;
  }

  private int y;

  public int getY() {
    return y;
  }

  public void setY(final int y) {
    this.y = y;
  }

  public Point() {
    this(0, 0);
  }

  public Point(final int x, final int y)
  {
    setX(x);
    setY(y);
  }
}

Now other classes would be able to see that the Point class has x and y properties because of its getter and setter methods. Tools would be able to create points and allow the x and y properties to be modified in a user interface builder, for example.

Review

Gotchas

In the Real World

Think About It

Self Evaluation

Task

Update your Book class to use methods and data hiding.

See Also

References