Classes

Goals

Concepts

Language

Preview

package com.example;

class FooBar
{

  public static final String FOO_BAR = "foobar";

  final int foo;
  final int bar;

  FooBar(final int foo, final int bar)
  {
    this.foo = foo;
    this.bar = bar;
  }
}

Lesson

Objects

One of the central features of Java is that it is an object-oriented language. In Java, setting aside the primitive types such as int and double, all variables are references to objects. You don't care where in heap memory these objects are stored; you just care when they are created and how long they “live”.

Objects help to design a program by grouping together data (variables) and functionality (methods). This grouping of things together is called encapsulation.

Creating New Objects: new

All objects are associated with a class, which is like a template for creating objects. To create an object, you'll use the keyword new along, followed by the name of the class of the object to create, which has a pair of parentheses attached. The object this creates is what we call an instance of the class. When you create a class instance, Java will allocate or set aside memory for it. You can create as many instances of a class as you wish, until you run out of memory or other resources. As the examples below illustrate, you can think of creating an instance of a class is like baking a new cookie, if you have a Cookie class. You'll learn how to create a Cookie class later on in this lesson.

Creating an instance of a Cookie class.
final Cookie cookie = new Cookie();

All instances of a class are not equal; they can encapsulate different values, which is what makes objects useful. When you create an instance of a class, many times you can pass values to the class. For example, Java has an error class IllegalArgumentException, which you can use to indicate an error related to a value you receive from a different area of the program. You'll learn how to use exceptions in another lesson, but the important thing to note here is that when you create an instance of this class, you may specify a message that explains the error. This value will be stored only with that one instance of IllegalArgumentException, not with other instances of the class.

Specifying a message when creating a new instance of IllegalArgumentException.
final IllegalArgumentException = new IllegalArgumentException("This algorithm does not support negative numbers.");

Destroying Objects

Most of the time (although not always) you will assign a reference to the newly created object to some variable so that you can use it later. You can afterwards assign the object reference to more than one variable, as you learned in a previous lesson.

You also learned that you never have to explicitly tell Java to destroy an object. The JRE has a garbage collector which will automatically destroy an object (and deallocate its memory, making it available for other objects) when there are no more references to that object. Remember that reference variables, like all other variables, disappear when they go out of scope. If a reference variable goes out of scope and it was the only variable references some object, that object will eventually be garbage-collected and destroyed.

Object references, scope, and garbage collection.
public static main(String[] args)
{

  final Cookie cookie1 = new Cookie();

  { //new scope

    //make a second reference to the first Cookie instance
    final Cookie cookie2 = cookie1;

    //make a second Cookie instance
    final Cookie cookie3 = new Cookie();

  }  //cookie2 and cookie3 go out of scope

  //cookie1 is still in scope; the first Cookie instance cannot yet be garbage-collected
  //cookie3 is out of scope; the second Cookie instance will eventually be garbage-collected
}

Classes

To define your own custom class, you'll use the class keyword, as you did in your “Hello World” program. Usually a class is defined in a separate file with the same name as the class, with a .java extension. The directory of the .java file must reflect the package indicated inside the file.

com/example/Cookie.java
package com.example;

/** A class representing a cookie to bake.*/
class Cookie
{
}

The class above does nothing; it does not encapsulate any data. It merely serves as a template for creating instances of Cookie. In itself this may not appear very useful, but it is significant that you can now have several instances of objects that are different, yet still share a commonality in that they are instances of the same class with a particular name. How might you use a class that holds no data and defines no functionality?

Examples of creating instances of a class.
final Cookie cookie1a = new Cookie();
final Cookie cookie1b = cookie1a;
final Cookie cookie2 = new Cookie();

System.out.println(cookie1a == cookie1b);   //prints "true"; two references to the same instance
System.out.println(cookie1a == cookie2);    //prints "false"; different instances

Instance Variables

Once way an object can encapsulate data is by having its own variables. When a class declares variables in the body of the class, the variable storage is replicated each time you create an instance of the class. That is, each class instance will hold variables independently from each other.

Declaring instance variables.
package com.example;

class FortuneCookie
{
  int luckyNumber = 5;
  String message;  //initialization optional
}

To directly access an object's instance variable, use the . character between the name of the variable referencing the instance and the name of the object's instance variable, such as myCookie.luckyNumber.

Accessing instance variables.
//in the example class above, the luckyNumber variable is initialized to 5
final FortuneCookie fortuneCookie1a = new FortuneCookie();
final FortuneCookie fortuneCookie1b = fortuneCookie1a;
final FortuneCookie fortuneCookie2 = new FortuneCookie();

System.out.println(fortuneCookie1a.luckyNumber);   //prints "5"

fortuneCookie1a.luckyNumber = 9;
fortuneCookie2.luckyNumber = 20;
System.out.println(fortuneCookie1a.luckyNumber);   //prints "9"
System.out.println(fortuneCookie1b.luckyNumber);   //prints "9"
System.out.println(fortuneCookie2.luckyNumber);    //prints "20"

FortuneCookie.luckyNumber = 30; //ERROR! instance variables require an instance of the class

Constructors

If you'd like to pass information to the object at the moment it is created or “constructed”, you'll need to define a constructor in the class. The constructor looks similar to a method, but does not indicate a return type—not even void. A constructor always has the same name as the class. Constructors are most often used for initializing instance variables of the new object, but can perform almost any function they wish. The JVM automatically calls a class' constructor when new indicates that a new class instance should be created. You can declare an instance variable as final without initializing it, as long as it is initialized in a constructor.

Like methods constructors allow a list of parameters. A constructor with no parameters is usually called no-argument constructor, because you do not have to pass any arguments when creating the object using that constructor. The JVM calls the appropriate constructor automatically based upon the arguments you pass when you instantiate the class using new. Consider a class used to encapsulate the two-dimension coordinates in a single geometric point.

Class constructors.
package com.example;

/**
 * Encapsulates the X and Y coordinates
 * of a two-dimensional geometric point.
 */
class Point
{
  int x;
  final int y; //instance variables can be final

  /** No-argument constructor. */
  Point()
  {
    x = 2; //x and y are in the parent scope
    x = 3; //non-final variables can be changed at any time
    y = 4;
    y = 5; //ERROR! cannot change final variable once initialized
  }

  /**
   * Value setting constructor.
   * @param initialX The initial horizontal coordinate.
   * @param initialY The initial vertical coordinate.
   */
  Point(final int initialX, final int initialY)
  {
    x = initialX;
    y = initialY;
    System.out.println((double)y / x); //normal code can go in the constructor
  }
}

Instance variables can be references to objects, and you can initialize them using constructors as well. The FortuneCookie class could itself encapsulate both a String and a Point.

Class constructors with objects.
package com.example;

class FortuneCookie
{
  final int luckyNumber;
  final String message;
  final Point goodPoint;

  /**
   * Constructor
   * @param someLuckyNumber The value for initializing the lucky number.
   * @param someMessage The value for initializing the message.
   * @param somePoint The value for initializing the point.
   */
  FortuneCookie(final int someLuckyNumber, final String someMessage, final Point somePoint)
  {
    luckyNumber = someLuckyNumber;
    message = someMessage;
    goodPoint = somePoint;
  }
}
final FortuneCookie goodCookie = new FortuneCookie(3, "Congratulations", new Point(3, 7));

The class may have several constructors to choose from, and you may find that you have very similar code that is repeated in several constructors in order to initialize the class. In order to minimize duplication, you can use constructor chaining, in which one constructor delegates to another constructor by passing control to it. Because it is one constructor rather than the JVM invoking another constructor, this does not result in the creation of multiple instances; it is merely a way to cut down on repetition and consolidate common functionality. To invoke another constructor, use the keyword this along with the appropriate arguments in parentheses that match the parameters of the constructor being chained.

Constructor chaining.
package com.example;

class Point
{
  final int x;
  final int y;
  final double originSlope;  //calculated whichever constructor is called

  /**
   * No-argument constructor.
   * Uses the default values 2 and 3 for x and y, respectively.
   * This is only an example; normally a point would not have such default values.
   */
  Point()
  {
    this(2, 3); //invoke the other constructor with default values
  }

  /**
   * Value setting constructor.
   * @param initialX The initial horizontal coordinate.
   * @param initialY The initial vertical coordinate.
   */
  Point(final int initialX, final int initialY)
  {
    x = initialX;
    y = initialY;
    originSlope = (double)y / x; //we didn't have to duplicate this code
  }
}

You can continue chaining constructors several times in turn if you want.

this

The keyword this can also act like a variable that, when used inside the class, refers to the class instance itself. Remember that code outside a class can access an instance variable by using the form fortuneCooke.luckyNumber. Inside the FortuneCookie class, this can be used in the same way. A constructor in the examples above, for instance, could use either luckyNumber or this.luckyNumber to access the instance variable of the same class. Most of the time the use of this is optional and only makes for verbose code, although it might be useful in some cases to clarify what the program is doing.

Most often the this reference is used when a local-scope variable is hiding an instance variable. If a local variable has the same name as a class' instance variable, normally the local variable “wins” and “hides” the instance variable. The keyword this, which holds a reference to the current instance, can be used to “get around” the local variable and access the one at the class level.

this and local variable hiding instance variable.
package com.example;

class Point
{
  int x = 5;
  int y = 7;

  /** No-argument constructor. */
  Point()
  {
    int x; //local variable "hides" the instance variable

    x = 2;

    System.out.println(x);    //prints "2"

    System.out.println(this.x);   //prints "5"
  }
}

One place you'll see local variables hiding instance variables is during initialization, when local variables are passed as parameters to a constructor. It is not uncommon to have initialization parameters as the same name as instance variables. If so, you'll need to use this to distinguish between the two variables.

this in instance variable initialization.
package com.example;

class Point
{

  final int x;
  final int y;

  Point(final int x, final int y)
  {
    this.x = x;  //need "this" to distinguish between variables
    this.y = y;
  }
}

Class Variables

As you have seen, the instance variables of a class only exist after a class has been instantiated, and then they exist as a separate copy for each object instance. A class variable, on the other hand, exists as soon as the class itself is created by the JVM, and is shared among all object instances. Indeed a class variable exists even if no instances of the class has been created. A class variable is declared using the static keyword.

Instance variables and class variables.
package com.example;

class FooBar
{
  final int foo = 5;  //instance variable
  static int bar = 10;  //class variable

  /** No-argument constructor. */
  FooBar()
  {
    System.out.println(bar);
    bar += 20;
  }

}
package com.example;

class MyApp
{
  public static main(final String[] args)
  {
    System.out.println(FooBar.foo);   //ERROR! cannot access instance variable through a class
    System.out.println(FooBar.bar);   //prints "10"

    final FooBar fooBar1 = new FooBar(); //prints "10"
    System.out.println(fooBar1.foo);  //prints "5"
    System.out.println(fooBar1.bar);  //prints "30"; WARNING! shouldn't go through instance variable
    System.out.println(FooBar.bar);   //prints "30"

    final FooBar fooBar2 = new FooBar(); //prints "30"
    System.out.println(fooBar2.foo);  //prints "5"
    System.out.println(FooBar.bar);   //prints "50"

    FooBar.bar = 100;

    final FooBar fooBar3 = new FooBar(); //prints "100"
    System.out.println(FooBar.bar);   //prints "120"
  }
}

An extremely common use of class variables or “static variables” is that, when they are combined with final, they effectively make a constant value (sometimes referred to as just a “constant”) which can be accessed without creating an instance of a class. Constants can also be access before the class instance is completely initialized, such as in the middle of a constructor. For example, a constant value can define the default value for a constructor so that several constructors can use it through constructor chaining.

Static variables used for constructor default values.
package com.example;

/**
 * A point with default values of 2, 3.
 */
class TwoThreePoint
{

  static final int DEFAULT_X = 2;
  static final int DEFAULT_Y = 3;

  final int x;
  final int y;

  TwoThreePoint()
  {
    this(DEFAULT_X);
  }

  TwoThreePoint(final int x)
  {
    this(x, DEFAULT_Y);
  }

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

Some class variables require complicated initialization, making it difficult or impossible to set the variable at the same time you declare it. One should not use a constructor to initialize static variables, as a constructor will be called each time the class is instantiates—and indeed may never be called. Instead Java provides for a static initializer block, which might be considered analogous to a “class constructor”. A static initializer block is the keyword static following by a set of braces { and } in the body of the class, and is executed when the JVM loads the class for the first time before it is accessed.

Static initializer block.
package com.example;

class MathStuff
{

  static final int EIGHT_TO_POWER_OF_FIVE;

  static  //static initializer block
  {
    //not the best way to find a power; for example only
    int temp = 1;
    for(int i = 0; i < 5; i++)
    {
      temp *= 8;
    }
    EIGHT_TO_POWER_OF_FIVE = temp;
  }

}

Review

Summary

The following example class encapsulates the x and y values of a two-dimensional geometric point. You can use an array of Point instances to keep track of multiple points.

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

class Point
{

  static final int ORIGIN_X = 0;
  static final int ORIGIN_Y = 0;

  final int x;
  final int y;

  Point(final int x, final int y)
  {
    this.x = x;
    this.y = y;
  }
}
final Point[] points = new Point[2] {
    new Point(3, 4),
    new Point(5, 6)
  };

System.out.println("point 1 x: " + points[0].x);  //prints "3"
System.out.println("point 1 y: " + points[0].y);  //prints "4"

System.out.println("point 2 x: " + points[1].x);  //prints "5"
System.out.println("point 2 y: " + points[1].y);  //prints "6"

Gotchas

In the Real World

Think About It

Self Evaluation

Task

Improve the Booker application to replace the parallel arrays with a single array of Book class instances.

Create a bundle of the Booker repository and send it to your teacher.

See Also

References