Classes
Goals
- Understand the purpose of classes.
- Learn how to create classes and chain constructors.
- Know the difference between static and instance variables of a class.
Concepts
- allocate
- Application Programming Interface (API)
- arguments
- class
- constant value
- constructor
- constructor chaining
- default constructor
- deallocate
- delegate
- design pattern
- encapsulation
- garbage collector
- instance
- instance variable
- instantiate
- no-argument constructor
- object-oriented
- parameter
- package access
- static initializer block
- static variable
Language
class
new
this
static
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.
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.
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.
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?
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.
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
.
//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.
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
.
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.
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.
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.
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.
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
- There is no reliable way to be notified before an object is destroyed, so don't even try.
finalize()
is not guaranteed to work for this purpose. - If a constructor parameter has the same name as an instance variable, don't forget to use
this
to distinguish between the two.
In the Real World
- Sometimes classes are declared for the sole purpose of collecting a series of constant values as
static final
variables. Such classes may never have a reason to be instantiated.
Think About It
- Am I writing the same code over and over in several constructors? Maybe I should chain the constructors.
- If I am using constructor chaining, does it make sense for a particular parameter to have a “default”? Or is this parameter one that should be required to be given before the class instance can be valid?
- Is there a value I am using multiple times? If the value represents the same thing semantically, I may want to store it in a constant for reuse, perhaps even between classes.
Self Evaluation
- What does it mean to say that a class “encapsulates” data?
- What is garbage collection?
- When is an object destroyed and its memory deallocated?
- What is the difference between parameters and arguments?
- How can you simulate “default arguments” in a Java constructor?
- How might you store useful values such as default constructor parameters in a way they can be reused?
Task
Improve the Booker application to replace the parallel arrays with a single array of Book
class instances.
- Store the array of books as a constant array of the
Booker
application class.- Store the books in a static variable.
- Use a static initializer block to initialize the
Book
instances.
- Use constructor chaining at least once.
- Use at least one constructor default value.
- Make sure a default value “makes sense” for a particular parameter.
- Not all parameters will have a “default” value, and will require the developer to supply some value.
- Do not use visibility modifiers, even if you know what these are.
- Do not use class instance methods, even if you know what these are.
- Modify your static method for printing a book to accept a
Book
instance as a parameter.
Create a bundle of the Booker repository and send it to your teacher.
See Also
- Classes (Oracle - The Java™ Tutorials)
- Creating Objects (Oracle - The Java™ Tutorials)
- Understanding Class Members (Oracle - The Java™ Tutorials)