Classes and Objects
In this chapter, we will see how to divide our programs across multiple files, as well as the basics of object-oriented programming.
In this chapter, we will see how to divide our programs across multiple files, as well as the basics of object-oriented programming.
We can write some fairly complicated programs in a single main method, and we can do just about anything using a bunch of static methods in a single file. However, there is a lot more to programming than just getting it to work – we also want to be able to easily reuse pieces of our code, and for our programs to be easy for other people to read. This requires us to think about good design for our programs instead of just functionality.
Computer programmers almost always work in teams, which means it is vital that all team members can use and understand code written by others in the group. If you think about how a big program (like a popular computer game) would look if it was written in a single file…it would be a nightmare to read. Programs like that tend to have millions of lines of code – it would be very difficult to ever find what you were after.
It is much easier to read code that is divided into many files and many methods by functionality. That way, you could go directly to the section of code you were interested in without having to wade through everything else. When each method solves a small piece of the problem, and each file holds a group of methods that do related things, it’s very easy to figure out what’s going on.
Creating programs with multiple classes also gives us the option to create new specialized data types. Later in this chapter, we will see how to turn these classes into data types, complete with their own fields (pieces of relevant information) and methods to operate on those fields. For example, we will be able to create a Rectangle type that store the width and height for the rectangle, and that has area and perimeter methods that operate on the width and height.
We have already seen how to write several methods within one class. Now, we will learn how to create several classes with different methods. This section will not discuss how to create objects (instances of classes) yet – for now, we’re just dividing methods into different files as an organization trick.
Suppose we want to create a lot of methods that perform mathematical operations (average,
round, max, min, etc.). It would be nice to be able to reuse these methods in other projects, so
we will want to divide them into a separate class. (Actually, there is a Math class in the Java
libraries that contains these methods, but we will create our own.)
Creating a separate class with static methods is exactly like the classes we’ve written in the past
– the only difference is that the separate class will not have a main method. Here is our
MathOps class:
//stored in the file MathOps.java
public class MathOps
{
//assumes arr has at least one element
public static int max(int[] arr)
{
int m = arr[0];
for (int i = 1; i < arr.length; i++)
{
if (arr[i] > m) m = arr[i];
}
return m;
}
//assumes arr has at least one element
public static int min(int[] arr)
{
int m = arr[0];
for (int i = 1; i < arr.length; i++)
{
if (arr[i] < m) m = arr[i];
}
return m;
}
public static int round(double num)
{
if ((int)(num+0.5) > (int) num)
{
//num must have decimal of .5 or higher
//round up
return (int)(num+0.5);
}
else {
//round down
return (int)num;
}
}
public static double avg(int[] arr)
{
int sum = 0;
for (int i = 0; i < arr.length; i++)
{
sum += arr[i];
}
//need to cast to a double to avoid integer division
return sum / (double) arr.length;
}
}Now, suppose we want to create a separate class with a main method that uses the MathOps
class. When we call static methods from another class, we will use the format:
ClassName.methodName(params);Here, ClassName is the name of the class that contains the method (in this case, MathOps),
and methodName is the name of the method we want to call. Here is a program that asks the
user for 10 numbers, and then prints the maximum, minimum, and average of those numbers:
//stored in Compute.java
import java.util.*;
public class Compute
{
public static void main(String[] args)
{
Scanner s = new Scanner(System.in);
int[] nums = int[10];
for (int i = 0; i < 10; i++)
{
System.out.print("Enter a number: ");
nums[i] = Integer.parseInt(s.nextLine());
}
System.out.printf("Maximum: %d%n", MathOps.max(nums));
System.out.printf("Minimum: %d%n", MathOps.min(nums));
System.out.printf("Average: %d%n", MathOps.avg(nums));
}
}Compiling works differently when we have multiple files, because we need to be sure to compile
all the source code files instead of just the one with the main method. Here’s how:
javac *.javaThis compiles ALL files with the .java extension – all your source code files. You could then
run the program with:
java Computewhich runs your program as usual. Note that you will run your program with the name of the file
that contains the main method.
In this section, we will learn to make classes into our own specialized data types – complete with data fields and methods that operate on the data.
In the previous section, we looked at a specific kind of class – classes that have only static
methods. However, very few classes in Java are written with entirely static methods. In this
section, we will explore a more generic definition of a class. These generic classes in Java are
made up of three parts:
Here is the format of a general class (note that our classes with static methods also fit this syntax
– they are just a specific kind of class):
public class Name
{
//declare fields
//constructor(s)
//methods
}For now, each class should be stored in a separate file. The name of the file should match the
name of the class (including capitalization), plus the “.java” extension. For example, the class
above should be stored in the file Name.java.
The three parts of a class (fields, constructor, methods) should be preceded by a visibility modifier. This specifies where that field, constructor, or method can be seen. There are three visibility modifiers:
public – visible anywhere the class is visibleprivate – visible only within the classprotected – discussed in the “Inheritance” sectionAgain, fields are special variables that can be used throughout the class. They are defined at the beginning of the class, using the following format:
visibility type name;For example:
private int size;Like typical variables, fields can also be initialized on the same line as their declaration. For example:
private int size = 0;However, initialization of fields in usually done in the constructor.
The constructor is a special section that initializes the fields in a class. It must have the same name as the class. It can either take initial values for the fields as parameters, or it can assign them default values. Here’s the format of a constructor:
public Name(params)
{
//initialize fields
}Here, Name is the name of the class, and params are possible parameters to the constructor.
Here is a sample class with a constructor:
public class Dog
{
//fields
private String name;
public String breed;
//constructor
public Dog()
{
name = "Fido";
breed = "Mutt";
}
}When the constructor is called, the name and breed are set to the default values “Fido” and “Mutt”. We could also
accept initial values for the name and breed as parameters to the constructor:
public Dog(String n, String b)
{
name = n;
breed = b;
}We will look more at methods parameters in the next section. For now, remember that parameters must have a specified type and a name – just like in static methods.
Think of Java classes as a more complicated variable type. For example, we wrote a Dog class,
so now we will be able to create variables of type Dog. These variables are similar to ordinary variables,
but they each have their own methods and fields. These types of variables are called
objects, whereas ints and doubles are primitive types.
Declaring an object variable has the sam format as declaring a primitive type variable:
type name;Recall our Dog class from the previous section:
public class Dog
{
//fields
private String name;
public String breed;
//constructor
public Dog()
{
name = "Fido";
breed = "Mutt";
}
}We can declare a variable of type Dog like this:
Dog d1;Initializing an object variable works a little differently than initializing a primitive type variable. We must call the object’s constructor to set of values for its fields. Here is the format of initializing (also called instantiating) an object:
name = new ClassName(params);Here, name is the name of the object variable, ClassName is the name of the class whose tytpe we are using, and params are the
parameters passed to the constructor. Here’s how we would create a new Dog object:
d1 = new Dog();This calls the no-parameter Dog constructor, and initializes the Dog’s name to “Fido” and its
breed to “Mutt”. Supppose instead the Dog class had this version of the constructor (with parameters for the
name and breed):
public Dog(String n, String b)
{
name = n;
breed = b;
}Here’s how we could create a Dog object now:
Dog d2 = new Dog("Rover", "Labrador");Keep in mind that although the d1 and d2 variables are both of type Dog, and both have values
for the name and breed, they are completely separate in memory. Dog is just a template that
allows us to create variables.
Once we have created an object, we can access any of its public members. In the Dog class, the
breed variable is public, so we can access it. Here’s the format for accessing public fields:
objectName.variableNameFor example, here’s how we would print the breed of both dogs:
System.out.println(d1.breed);
System.out.println(d2.breed);This would print:
Mutt
LabradorNon-static methods in Java are fairly similar to the static methods we already know about. The difference is that non-static methods are associated with an object of a given class type, not with the class itself. This means that we can call a method for each of our object variables, and that the method will use that object’s fields in its calculations. (This is not always true, but we’ll pretend for now.) Here’s the format of a method:
visibility returnType name(args)
{
//code
//possible return statement
}Let’s discuss each part separately. The visibility can be either public or private,
depending on whether we want to call the method outside this class. (It can later be
protected as well.) The returnType specifies what type of value will be returned. If the
method doesn’t return anything, its return type is void. name is just the name of the method,
which you will use when calling it. args are optional arguments (parameters) for the
method.
If a method has a non-void return type, it must have a return statement. This looks like:
return value/variable;where that value or variable has the same type as returnType.
Here’s a very simple method that just prints “Hello, World!” to the screen:
public void greeting()
{
System.out.println("Hello, World!");
}Method arguments (parameters) are just values passed to the method. Each method can have zero or many arguments. Here’s what the argument list should look like:
type1 name1, type2 name2, ...If a method has no arguments, you still need to include the () after the method name (like the
greeting method above). Here’s an example of a method that takes two ints, and prints their
product to the screen:
public void printProduct(int num1, int num2)
{
int product = num1*num2;
System.out.printf("The product is %d%n", product);
}So far, we’ve only looked at methods that have a void return type – that don’t return anything.
Here’s a method that returns something:
public int product (int num1, int num2)
{
int product = num1*num2;
return product;
}This method has an int return type, and so it includes a return statement. The variable
returned (product) also has type int.
Now that we can write methods, we need to be able to call them. This will be different depending on whether we’re calling a method that’s in the same class as us, or in a different class. Here’s how we call a method in the same class as us:
methodName(args)Here, methodName is the name of the method we’re calling, and args are the values of the
parameters we’re passing. The parameters must have the same type as the corresponding
arguments in the methods definition. Here’s an example of calling the printProduct method
(from the same class):
int val1 = 5;
int val2 = 7;
printProduct(val1, val2);The VALUES of val1 and val2 (5 and 7) are passed to printProduct, and stored in the
arguments num1 and num2.
When the method we’re calling returns a value, we need to store that return value when we call
it. Here’s an example of calling the product method (from the same class):
int val1 = 5;
int val2 = 7;
int result = product(val1, val2);Now the product of val1 and val2 (35) is stored in the result variable.
Notice that this syntax for calling methods is so far similar to what we’ve done to call static methods.
Calling non-static methods from a different class works similarly, expect we first need an object of that class type. Let’s look at an example class:
public class Rectangle
{
private int length;
private int width;
public Rectangle(int l, int w)
{
length = l;
width = w;
}
public int area()
{
return length*width;
}
public int perimeter()
{
return length*2 + width*2;
}
}Before we can call any of the methods in Rectangle, we need to create a new Rectangle
object. Let’s create two – a 4x6 rectangle and a 3x5 rectangle:
Rectangle r1 = new Rectangle(4, 6);
Rectangle r2 = new Rectangle(3, 5);Now, here’s the format for calling a non-static method in a different class:
objectName.methodName(args);So, to call the area method on the 4x6 rectangle, we’d do:
r1.area();However, the area method returns a value, so we probably want to store or do something with a
result. We’ll store the area of the 4x6 rectangle, and print the perimeter of the 3x5 rectangle:
int result = r1.area();
System.out.printf("The perimeter is %d%n", r2.perimeter());Method overloading is when you have two or more versions of the same method, and each version has a different argument list. For example, suppose you wanted to define a method that computed the max of two numbers. You might want to do this for both ints and doubles. Here’s how:
public int max(int num1, int num2)
{
if (num1 > num2) return num1;
else return num2;
}
public double max(double num1, double num2)
{
if (num1 > num2) return num1;
else return num2;
}Notice that the two versions of max have the same name, but different argument lists. They also
have different return types, but overloaded methods do not have to work this way. The compiler
can figure out which one you want to call based on what types you pass. For example:
int max1 = max(4, 7)would call the first version of max since the arguments are both ints. On the other hand:
double max2 = max(5.4, 8.76);would call the second version since the arguments are both doubles.
Constructors can also be overloaded. This is done by creating two constructors (both with the name of the class) with different argument lists.
In this section, we will discuss several keywords in Java that relate to classes, objects, and methods.
static keywordClass variables and class methods are defined with the static keyword. We have written
static methods in the past without talking about how they are different from non-static methods.
Variables and methods in a class that are not static are instance variables and instance methods,
which means there is a different version of each method and variable for each object instance of
the class.
Static variables and methods are called class variables and class methods, which means there is only one version of them for all different object instances of the class. Static methods and variables can also be accessed with the class name (as we’ve done before) without having to create an object instance. Regular variables and methods cannot be accessed in this way.
Here’s how to declare a static variable:
visibility static type name;And here’s how to declare a static method:
visibility static returnType name(args);Here’s a sample class with static variables and methods:
public class Circle
{
public static double pi = 3.14159;
public static double area(double radius)
{
return pi*radius*radius;
}
}Now, because pi and area are static, we access them using the class name (Circle). All of
the following are valid:
System.out.println(Circle.pi); //prints 3.14159
double area = Circle.area(2); //area = 12.566
Circle c1 = new Circle();
Circle c2 = new Circle();
System.out.println(c1.pi); //prints 3.14159
double area = c1.area(2); //area = 12.566
Circle.pi = 3.14; //Changes pi for all Circle objectsStatic methods cannot refer to any fields. They can only refer to class variables (static variables), method arguments, and local variables.
final keywordThe keyword final in front of a variable denotes that the variable is a constant. Any
variable declared with “final” cannot be modified once it is initialized. For example, the pi
variable in the Circle class could be constant – pi should really always be the same value.
Here’s how we would change the class:
public class Circle {
public static final double PI = 3.14159;
public static double area(double radius)
{
return PI*radius*radius;
}
}Notice that constants are traditionally given names in all capital letters. Also, if I try to change
the value of PI, such as:
Circle.PI = 3.14;I will get a compiler error.
null keywordWhen we declare any variable without assigning it a value, that variable has no value by default. This means if we tried to do something like this:
int val;
val++;We would get a compiler error, because we can’t add one to a variable with no value. Similarly, if we did this:
Rectangle r;
int a = r.area();We would also get a compiler error, because we are trying to use r without initializing it to be a new object.
Sometimes you may want to initialize a class type variable, even if you are not ready to create a
new object yet. To do this, you can set the variable to the special value null:
Rectangle r = null;The null value is a valid value for all non-primitive variables (all variables that have a class
type). Primitives like ints, chars, doubles, and booleans cannot be set to null.
this keywordThe keyword this refers to “this object instance”. We can use it inside a class to refer to
fields and methods in this class. The keyword is primarily used to distinguish
fields from local variables. For example:
public class Person
{
private String name;
private int age;
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
}Inside the Person constructor, this.name refers to the field called name,
while just name refers to the constructor parameter called name. In general, you can say:
this.nameto refer to a method or field (called name) inside THIS class. However, you cannot use the
this keyword with static methods or variables, since they don’t depend on a particular object
instance.
This section will show how to use objects in combinations with other programming constructs – as elements in an arrya, as parameters to methods, and as fields in other classes.
We have already seen how to create arrays of things like ints and doubles, but we can also create arrays of objects, where each element has a class type. The format for declaring an array of objects is just like declaring any other array:
type[] name;But here, type should be the name of a class. We can also create space for the array just like
we’ve done before:
name = new type[size];Elements in arrays are initialized to the default value of that type, so elements in an array of
objects are automatically initialized to null.
To see an example, recall the Rectangle class from earlier in the chapter. Suppose that we
want to create an array of 10 rectangles whose values are inputted by the user. Then, we want to
print the area and perimeter of each rectangle. Here’s how:
//Declare the array and allocate space
Rectangle[] rectArray = new Rectangle[10];
Scanner s = new Scanner(System.in);
//Get information about each rectangle
for (int i = 0; i < rectArray.length; i++)
{
System.out.print("Enter the length: ");
int length = s.nextInt();
System.out.print("Enter the width: ");
int width = s.nextInt();
//Create a new Rectangle object with the
//correct dimensions, and store it in the array
rectArray[i] = new Rectangle(length, width);
}
//Loop to print the area and perimeter of each rectangle
for (int i = 0; i < rectArray.length; i++)
{
System.out.printf("Rectangle %d: area %d, perimeter %d%n", i, rectArray[i].area(), rectArray[i].perim());
}Notice that we’re treating each object in the array exactly like we would any other object, but instead of having to create a separate variable to refer to each object, we can store them all in an array.
We can pass objects to methods just like any other type of element. If we pass an object to a
method, the type for that parameter is the type of the object (the name of the class). Suppose we
want to write a printRectangle method that takes a Rectangle object as a parameter and
then prints its area and perimeter. Here’s how the method would look:
public void printRectangle(Rectangle r)
{
//Now we can treat r just like any other Rectangle object
System.out.printf("Area: %d%n", r.area());
System.out.printf("Perimeter: %d%n", r.perim());
}Now, suppose we’re in the same class as the printRectangle method. Then we could do:
Rectangle rect = new Rectangle(3, 4);
printRectangle(rect);Notice that passing objects to methods works exactly like passing other types to methods – the only difference is that the parameter type is now the name of a class.
We can also make objects be fields in another class – again, this works just like other
types of fields, but the type for the field will be the name of a class. For
example, suppose we have the following Person class:
public class Person
{
public String name;
public int age;
public Person(String n, int a)
{
name = n;
age = a;
}
}Now, suppose we want to write a Child class that holds a child’s name, grade, school, and
parent information. Each parent will be stored as a Person field. Here is the
Child class:
public class Child
{
private String name;
private int grade;
private String school;
private Person mother;
private Person father;
//n: name, g: grade, s: school
//mn: Mom’s name, ma: Mom’s age
//fn: Dad’s name, fa: Dad’s age
public Child(String n, int g, String s, String mn,
int ma, String fn, in fa)
{
name = n;
grade = g;
school = s;
//Initialize fields to be new objects
//with corresponding names and ages
mother = new Person(mn, ma);
father = new Person(fn, fa);
}
}Now, suppose we are outside both the Person and Child classes and we want to create a
Child object with the following information:
Here’s what we would do:
Child c = new Child("Fred", 5, "Bluemont Elementary", "Donna", 34, "Frank", 35);There are two ways to call a method – calling by reference and calling by value. While these two approaches can sometimes look the same, passing an array or an object to a method is very different from passing a primitive variable like an int. If you modify an object or array that is a method argument, it will modify the original variable. However, if you modify a primitive method argument, the original variable remains unchanged.
When you pass objects and arrays to methods, you are calling the method by reference. This means that if you change a value in the array, or change a property of the object, then the original variable that you passed to the method will also change.
Consider the following method:
public void setZero(int[] nums) {
for (int i = 0; i < nums.length; i++) {
nums[i] = 0;
}
}Now, consider this call to setZero:
int[] vals = {1, 2, 3, 4};
setZero(vals);When we return from setZero, every element in the vals array will be 0. This is because if
an array is changed by a method, the original array (vals) is also changed.
Passing objects is also a type of call by reference. Consider the following class:
public class Person {
private String name;
private int age;
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
public void incAge()
{
age++;
}
}And consider this outside method:
public void changePerson(Person p)
{
p.incAge();
}Now we create a Person object and call the changePerson method:
Person pers = new Person("Amy", 26);
changePerson(p);When we return from the changePerson call, pers now has age 27 (since changing the
object in the method changed the original object).
However, suppose changePerson looked like this instead:
public void changePerson(Person p)
{
p = null;
}If I now created a Person object and called changePerson:
Person pers = new Person("Amy", 26);
changePerson(p);Then pers would NOT have the value null after the method call. This is rather confusing
(and makes more sense in C++, which uses pointers), but p and pers are two different variables
that reference the same object in memory. If I change that object’s age (like in the first version
of changePerson), then the age changes for BOTH variables. However, if I set p to null, it
just makes p reference null instead of the Person object. pers still references the Person
object, so it remains unchanged.
When you pass primitive variables (like ints) to a method, whatever changes you make inside the method will not affect the original variable. For example:
public void inc(int x)
{
x++;
}
//assume this code is somewhere else in the same class
int num = 4;
inc(num);Even though the inc method adds one to x, it does not affect the value of num. This is because
instead of passing the num variable, only the value is passed. This value (4) is stored in the
method argument (x). When I increment x, it does not change num because they are two
separate variables.
Just remember that if you pass an int, double, or char and change it inside a method – the change won’t stick. If you change an object or array, the change will stick.