Module 6, Part 1: Simple Classes and Objects#

In this Module we study the fundamentals of Java classes and objects.

When designing and writing programs, it is often necessary to model entities that can be described as groups of values, where each value has its its own (possibly distinct) type. In Java, we can represent such entities using classes and objects — and in this section we explore their basic concepts:

Note

In Java, classes are a very powerful and ubiquitous building block for programs. In this section we explore a “simple” usage of classes, that roughly correspond to what other programming languages would call a structure or record. Later in the course we will study more advanced notions and usages of classes.

Defining a Simple Class and Creating Objects#

Suppose we want to write a program that handles items in a shop, and each item must contain information about:

  • the item’s name (which we can model as value of type String)

  • the item’s net price (which we can model as a value of type double)

  • the item’s VAT (which we can model as a value of type double)

  • the item’s availability in the shop (which we can model as a value of type int)

In principle we could try representing and handling the information above by defining a bunch of variables and arrays (of different types) throughout our program. However, to make our program more readable, understandable, and easier to maintain, it makes much more sense to define a new type describing a shop item, so that every value of that type represents a complete shop item with all the required information.

To achieve this, in Java we can define a class describing a shop item: let us call such a class e.g. ShopItem. The class definition, in turn, can specify zero or more fields to represent the shop item’s information (i.e. the item’s name, net price, VAT, and availability).

After our new class ShopItem is defined, we can write programs that create and use objects of the class ShopItem (such objects are also called instances of the class shopItem). Intuitively, a class works like a blueprint for creating objects: see Fig. 18 below. More precisely:

  • the class ShopItem specifies the names and types of fields (e.g., the name of a shop item is a String, the VAT of a shop item is an integer…), and

  • each object of the class ShopItem actually “contains” a value for each one of those fields.

The ShopItem type is a structured data type, in the sense that a value (i.e., an object) of the type ShopItem “contains” other values (one per field).

This is shown in detail in Example 37 below.

Example 37 (A simple class representing a shop item)

The following class definition describes a shop item, with four fields (name, netPrice, vat, and availability). Note that, for each field, we specify the type followed by the field name: e.g. double vat means that the field called vat has type double. (Try to copy&paste the following class definition on the Java shell!)

class ShopItem {
    String name;
    double netPrice;
    double vat;
    int availability;
}

If you copy&paste the code above on the Java shell, you will see the output:

| created class ShopItem

This means that Java is now aware of the definition of the new class ShopItem, and we can define new objects belonging to that class. The intuition is that the definition of the class ShopItem works like a blueprint for creating new objects (or as a “cookie cutter” for creating new “cookies,” see Fig. 18 above): every object created as an instance of ShopItem will contain a field called name (with a value of type String), a field called netPrice (with a value of type double), etc.

For instance, we can try:

jshell> var item1 = new ShopItem();
item1 ==> ShopItem@52cc8049
|  created variable item1 : ShopItem

This way, we have created a new object of the class ShopItem (using new), and that object has been used to declare the variable item1: therefore, the variable item1 has type ShopItem, and we can use the variable item1 to access the newly-created object. (The line item1 ==> ShopItem@52cc8049 may be slightly different on your computer)

The fields of the newly-created object have a default value, that we can see by printing the fields’ values on screen. To access the fields of the object associated to the variable item1, we simply write item1 followed by a dot . and the field name. For example:

jshell> System.out.println("Name of 'item1': " + item1.name)
Name of 'item1': null

Note

Here, null means that no string has been specified for the field name of the newly-created object. We will discuss null values in (much) more detail later in the course.

jshell> System.out.println("Net price of 'item1': " + item1.netPrice)
Net price of 'item1': 0.0
jshell> System.out.println("VAT of 'item1': " + item1.vat)
VAT of 'item1': 0.0
jshell> System.out.println("Availability of 'item1': " + item1.availability)
Availability of 'item1': 0

We can change the values to the object fields by using assignments. For instance: (copy&paste the following code on the Java shell)

item1.name = "Freesbee";
item1.netPrice = 115;
item1.vat = 25 / 100.0;
item1.availability = 42;

And now, if we print the object fields again, we get:

jshell> System.out.println("Name of 'item1': " + item1.name)
Name of 'item1': Freesbee
jshell> System.out.println("Net price of 'item1': " + item1.netPrice)
Net price of 'item1': 115
jshell> System.out.println("VAT of 'item1': " + item1.vat)
VAT of 'item1': 0.25
jshell> System.out.println("Availability of 'item1': " + item1.availability)
Availability of 'item1': 42

Of course, we can create new objects of the class ShopItem, and each object will have its own fields with their own value, which we can read and modify. For example:

var item2 = new ShopItem();
item2.name = "Newspaper Politiken";
item2.netPrice = 25;
item2.vat = 0.0;
item2.availability = 12;

(Try printing the values of the fields of item2 on screen, and observe that they are different from the values in the fields of item1.)

Constructors#

Example 37 shows how to create a new object of our new class (in this case, ShopItem) — but it also shows the fields of newly-created objects contain default values that we may need to change afterwards, by assigning the desired values. It would be more convenient to directly create objects whose fields have the desired values. To do that, we can define one or more constructors, which take some arguments and use them to initialise the fields of the new object as we need. This is shown in Example 38 below.

Example 38 (Defining a constructor for the shop item objects)

Important

If you have tried Example 37 on the Java shell, you will need to reset the Java shell before trying the code snippets below. To reset the Java shell, write /reset and press .

Consider the following class definition for ShopItem — which is based on the definition in Example 37, plus some additional lines (highlighted):

 1class ShopItem {
 2    String name;
 3    double netPrice;
 4    double vat;
 5    int availability;
 6
 7    ShopItem(String name, double netPrice, double vat, int avail) {
 8        this.name = name;
 9        this.netPrice = netPrice;
10        this.vat = vat;
11        this.availability = avail;
12    }
13}

Lines 7–12 define a constructor that takes 4 arguments (name of type String, netPrice of type double, etc.).

Intuitively, the constructor works a bit like a static method, i.e. it is a sub-program that can be called by passing the required arguments. However, the constructor has two key differences:

  • the constructor above is called when we create a new object of the class ShopItem (using new) with the required 4 arguments; and

  • inside the constructor body, the newly-crated object can be accessed through a special predefined variable called this (which is not available inside a “normal” static method).

By defining the class ShopItem with the constructor above, we can create new objects of the class ShopItem in a more convenient way, as follows:

jshell> var item3 = new ShopItem("Plant pot", 61.5, 0.25, 23)
item3 ==> ShopItem@52cc8049
|  created variable item3 : ShopItem

When new ShopItem("Plant pot", 61.5, 0.25, 23) is executed, the ShopItem constructor above is called with the specified actual arguments, and the following happens:

  • in the body of the constructor, the special variable this automatically refers to the newly-created object;

  • on line 8 of the constructor, the field name of this object (i.e. this.name) is assigned the actual value of the constructor argument name (which is "Plant pot");

  • on line 9 of the constructor, the field netPrice of this object (i.e. this.netPrice) is assigned the actual value of the constructor argument netPrice (which is 61.5);

  • lines 10 and 11 of the constructor are similar: the fields this.vat and this.availability are assigned the actual values of the corresponding constructor arguments (which are 0.25 and 23, respectively).

Now, if we print the values of the fields of item3, we can see that they have the values specified when we created the object using new (which in turn called the constructor):

jshell> System.out.println("Name of 'item3': " + item3.name)
Name of 'item3': Plant pot
jshell> System.out.println("Net price of 'item3': " + item3.netPrice)
Net price of 'item3': 61.5
jshell> System.out.println("VAT of 'item3': " + item3.vat)
VAT of 'item3': 0.25
jshell> System.out.println("Availability of 'item3': " + item3.availability)
Availability of 'item3': 23

Example 39 (A program using the ShopItem class)

Let us consider this task: write a program that creates two shop items, prints them on screen, then increases their net price by 10%, and prints the items on screen again.

The program below is a possible solution to this task. Notice that it uses the ShopItem class introduced in Example 38 — but in order to avoid code duplication, it enriches the class ShopItem by defining two static methods (highlighted) with useful functionality:

  • description(item) creates and returns a String that shows the values of the various fields of the given item;

  • inflateNetPrice(item, percentage) modifies item by increasing its net price by the given percentage. NOTE: the return type of this static method is void, which means that calling inflateNetPrice(item, ...) does not produce any result value: the only purpose of that static method is to modify the given item. As a consequence, we cannot call inflateNetPrice(...) from inside an expression (if you try, you will get a type error, due to the return type void). Correspondingly, the body of the method inflateNetPrice(...) does not return any value.

 1class ShopPrices {
 2    public static void main(String[] args) {
 3        var item1 = new ShopItem("Cellphone", 2500, 0.25, 8);
 4        var item2 = new ShopItem("Chocolate bar", 25, 0.25, 37);
 5
 6        System.out.println("Item 1: " + ShopItem.description(item1));
 7        System.out.println("Item 2: " + ShopItem.description(item2));
 8
 9        System.out.print("Applying 10% inflation to net prices... ");
10        ShopItem.inflateNetPrice(item1, 10);
11        ShopItem.inflateNetPrice(item2, 10);
12        System.out.println("Done.");
13
14        System.out.println("Item 1: " + ShopItem.description(item1));
15        System.out.println("Item 2: " + ShopItem.description(item2));
16    }
17}
18
19class ShopItem {
20    String name;
21    double netPrice;
22    double vat;
23    int availability;
24
25    ShopItem(String name, double netPrice, double vat, int avail) {
26        this.name = name;
27        this.netPrice = netPrice;
28        this.vat = vat;
29        this.availability = avail;
30    }
31
32    static String description(ShopItem item) {
33        var str = item.name + " (price: " + item.netPrice + " DKK + "
34                    + (item.vat * 100) + "% VAT; "
35                    + "availability: " + item.availability + ")";
36        return str;
37    }
38
39    static void inflateNetPrice(ShopItem item, double percentage) {
40        item.netPrice = item.netPrice + (item.netPrice * (percentage / 100.0));
41
42        // This "return" is optional, since this method returns nothing (void)
43        return;
44    }
45}

If you copy&paste the program above in a Java file (called e.g. ShopPrices.java) and execute it, you will see the output:

Item 1: Cellphone (price: 2500.0 DKK + 25.0% VAT; availability: 8)
Item 2: Chocolate bar (price: 25.0 DKK + 25.0% VAT; availability: 37)
Applying 10% inflation to net prices... Done.
Item 1: Cellphone (price: 2750.0 DKK + 25.0% VAT; availability: 8)
Item 2: Chocolate bar (price: 27.5 DKK + 25.0% VAT; availability: 37)

Remark 16 (Passing objects as arguments to a method)

The program in Example 39 shows that if you call a method and give it an object as argument, then the method can modify that object. More specifically, you can observe that:

  • the main method of the program calls the static method ShopItem.inflateNetPrice(...) twice, by providing as arguments the ShopItem objects in variables item1 and item2;

  • the static method ShopItem.inflateNetPrice(item, ...) modifies the object field item.netPrice; and

  • after the method call returns, main prints the values of item1.netPrice and item2.netPrice (by calling ShopItem.description(...)) — and we can observe that the values of those fields have changed.

The general intuition here is that when a program calls a method by providing an object as argument, then the program “passes the control over the whole object” to the method — and the method might change the values of the object’s fields. This is what ShopItem.inflateNetPrice(...) does.

(In the next modules we will see more precisely why and how this happens.)

Non-Static Object Methods#

In Example 39 we have enriched the class ShopItem with some static methods that implement ShopItem-related functionality, making the code better organised and more reusable.

However, static methods are not the only tool to achieve this result: we can also implement non-static methods (a.k.a. object method, or simply methods).

From the caller perspective, the difference between a static method of class ShopItem and a non-static method of an object of the class ShopItem is that:

  • in Example 39 we call e.g.:

    ShopItem.description(item1)
    ShopItem.description(item2)
    

    i.e. we specify the class that contains the static method (ShopItem.description(...)) and we provide as argument the object that the static method uses for its computations (item1 and then item2);

  • instead, the corresponding calls to a similar non-static method would look like:

    item1.description()
    item2.description()
    

    which mean: “call the method description() of the object in the variable item1,” and then do the same with the object in item2. Notice that we do not need to pass the object as parameter (because we have already selected the object when calling its method).

To achieve this result, we only need to apply a few changes to static methods in Example 39 (that are shown in Example 40 below):

  1. we remove the static keyword from the method definition, and

  2. we remove the argument of type ShopItem on which the original static methods operates. Instead, inside the method body we can use the the special predefined variable this (the same special variable that we also used in the constructor).

Non-static methods can make our code even more compact — and later in the course we will see that non-static methods have other very useful features.

Example 40 (A program using the ShopItem class, version 2)

The program below is a variation of the one in Example 39 where the static methods description and inflateNetPrice are converted into non-static methods. The resulting differences with Example 39 are highlighted.

Notice that:

  • the non-static methods do not have the keyword static in their definition;

  • in the body of the methods, we use the special predefined variable this to access the object that we used to call the method. So, for example:

    • when the code calls item1.description(), then the variable this on lines 33–35 lets the method access the same object of the variable item1;

    • when the code calls item2.inflateNetPrice(10), then the variable this on line 40 lets the method access (and modify) the same object of the variable item2;

 1class ShopPrices {
 2    public static void main(String[] args) {
 3        var item1 = new ShopItem("Cellphone", 2500, 0.25, 8);
 4        var item2 = new ShopItem("Chocolate bar", 25, 0.25, 37);
 5
 6        System.out.println("Item 1: " + item1.description());
 7        System.out.println("Item 2: " + item2.description());
 8
 9        System.out.print("Applying 10% inflation to net prices... ");
10        item1.inflateNetPrice(10);
11        item2.inflateNetPrice(10);
12        System.out.println("Done.");
13
14        System.out.println("Item 1: " + item1.description());
15        System.out.println("Item 2: " + item2.description());
16    }
17}
18
19class ShopItem {
20    String name;
21    double netPrice;
22    double vat;
23    int availability;
24
25    ShopItem(String name, double netPrice, double vat, int avail) {
26        this.name = name;
27        this.netPrice = netPrice;
28        this.vat = vat;
29        this.availability = avail;
30    }
31
32    String description() {
33        var str = this.name + " (price: " + this.netPrice + " DKK + "
34                    + (this.vat * 100) + "% VAT; "
35                    + "availability: " + this.availability + ")";
36        return str;
37    }
38
39    void inflateNetPrice(double percentage) {
40        this.netPrice = this.netPrice + (this.netPrice * (percentage / 100.0));
41
42        // This "return" is optional, since this method returns nothing (void)
43        return;
44    }
45}

Concluding Remarks#

You should now have an understanding of two main topics:

  • How to define and use classes to group values of different types into a single object, and how to define a constructor to initialise a new object (using the special predefined variable this);

  • How to organise your code using (non-static) methods that can be called using an object — and how methods can use the special special predefined variable this to access the object used to call them, and to read and modify the values of that object’s fields.

With this knowledge, you should be able to understand each line of the programs in Example 34 and Example 40.

References and Further Readings#

You are invited to read the following sections of the reference book: (Note: they sometimes mention Java features that we will address later in the course)

  • Section 4.1 - “Classes and Objects Revisited”

  • Section 4.2 - “Anatomy of a Class”

  • Section 4.4 - “Anatomy of a Method”

  • Section 4.5 - “Constructors Revisited”

Exercises#

Note

These exercises are not part of the course assessment. They are provided to help you self-test your understanding.

Exercise 26 (Adding more methods to ShopItem)

Try to extend the class ShopItem in the Java program in Example 40 by adding the following methods:

  • double price()
    

    This method computes and returns the full price of this shop item, by adding the item’s VAT to the item’s net price. For example, consider the following program snippet:

    var headphones = new ShopItem("Headphones", 700, 0.25, 4);
    System.out.println("Price (including VAT): " + headphones.price());
    

    The program snippet above should output:

    Price (including VAT): 875.0
    

    NOTE: the method must not modify the values of the object’s fields.

  • boolean equals(ShopItem other)
    

    This method returns true if this shop item is equal to the other shop item given as argument. To be “equal,” the two objects this and other must have equal values in their corresponding fields. If the two objects are not equal, the method returns false.

    For example, consider the following program snippet:

    var item1 = new ShopItem("AAA batteries", 30, 0.25, 40);
    var item2 = new ShopItem("A".repeat(3) + " batteries", 30, 0.25, 40);
    
    if (item1.equals(item2)) {
        System.out.println("The items are equal");
    } else {
        System.out.println("The items are different");
    }
    

    The program snippet above should output:

    The items are equal
    

    NOTE: the method must not modify the values of the object fields. Also remember that, in order to check whether two String objects are equal, you should use the .equals(…) method.

Lab and Weekly Assessments#

During the lab you can try the exercises above or begin your work on the weekly assessments below.

Important

01 - Point#

Edit the file Point.java provided in the handout, and implement a class called Point representing a point on the Cartesian plane. The requirements are:

  • Objects of the class Point must have two fields called x and y, both having type double. Therefore, if p is an object of type Point, we can write e.g.:

    p.x = 1.0;
    p.y = 2.0;
    System.out.println("The point coordinates are: " + p.x + ", " + p.y);
    
  • The class Point must have the following static method:

    static void move(Point p, double dx, double dy)
    

    When called, the method modifies the Point object p by adding to its fields x and y the values dx and dy, respectively. (Remember that void means that the static method move(...) does not return any value.)

  • The class Point must have the following static method:

    static Point translate(Point p, double dx, double dy)
    

    When called, the method creates and returns a new Point object whose coordinates are obtained from the coordinates x and y of the Point object p, by adding the values of the arguments dx and dy, respectively. NOTE: the method must not modify the fields the object p.

The handout includes some Java files called Test01.java, Test02.java, etc.: they are test programs that use the class Point, and they might not compile or work correctly until you complete the implementation of class Point in the file Point.java. You should read those test programs and try to run them, but you must not modify them.

When you are done, submit the modified file Point.java on DTU Autolab.

Hint

  • You may want to read again how to split a Java program into separate files.

  • To check whether your solution works with one of the test programs, you will need to compile at least Point.java and that test program. For instance, if you want to try Test01.java, you will need to first run (on the console):

    javac Point.java Test01.java
    

    and then you can execute the main method contained in the class Test01, by running:

    java Test01
    

02 - Point 2#

Note

This assessment is a variation of 01 - Point, with two goals:

  1. adding a constructor to the class Point (as explained in Constructors), and

  2. turning the static methods of the class Point into non-static methods, (as explained in Non-Static Object Methods).

Therefore, you can solve this assessment by first solving 01 - Point, and then adapting its solution according to the requirements below.

Edit the file Point.java provided in the handout, and implement a class called Point representing a point on the Cartesian plane. The requirements are:

  • Objects of the class Point must have two fields called x and y, both having type double. Therefore, if p is an object of type Point, we can write e.g.:

    p.x = 1.0;
    p.y = 2.0;
    System.out.println("The point coordinates are: " + p.x + ", " + p.y);
    
  • The class must have the following constructor:

    Point(double x, double y)
    

    which allows us to create a new Point object by writing e.g.:

    var p = new Point(3.14, 42);
    
  • Point objects must have the following method:

    void move(double dx, double dy)
    

    When called, the method modifies this object by adding to its fields x and y the values dx and dy, respectively. (Remember that void means that the method move(...) does not return any value.)

  • Point objects must have the following method:

    Point translate(double dx, double dy)
    

    When called, the method creates and returns a new Point object whose coordinates are obtained from this object’s coordinates x and y by adding the values of the arguments dx and dy, respectively. NOTE: the method must not modify the fields of this object.

The handout includes some Java files called Test01.java, Test02.java, etc.: they are test programs that use the class Point, and they might not compile or work correctly until you complete the implementation of class Point in the file Point.java. You should read those test programs and try to run them, but you must not modify them.

When you are done, submit the modified file Point.java on DTU Autolab.

Hint

The hints given for 01 - Point are also useful for this assessment.

03 - Cars#

Edit the file Car.java provided in the handout, and implement a class called Car representing a car. The requirements are:

  • Objects of the class Car must have four fields called respectively:

    • brand (e.g. "FIAT")

    • model (e.g. "Topolino")

    • numberPlate (e.g. "EZ 13623")

    • color (e.g. "blue")

  • The Car class class must have the following static method:

    static String description(Car car)
    

    When called, the method returns a string with information about the car object given as argument. For instance, calling Car.description(c) (using a car object c with the field values listed above) must return a string of the following form:

    FIAT Topolino, blue [EZ 13623]
    
  • The Car class must have the following static method:

    static boolean equals(Car car, Car other)
    

    When called, the method returns true if the brand, model, numberPlate, and color fields of the car given as argument are equal to the corresponding fields of the other argument; otherwise, the method returns false.

  • The Car class must have the following static method:

    static boolean isAlike(Car car, Car other)
    

    When called, the method returns true if the brand, model, and color fields of the car given as argument object are equal to the corresponding fields of the other argument; otherwise, the method returns false.

The handout includes some Java files called Test01.java, Test02.java, etc.: they are test programs that use the class Car, and they might not compile or work correctly until you complete the implementation of class Car in the file Car.java. You should read those test programs and try to run them, but you must not modify them.

When you are done, submit the modified file Car.java on DTU Autolab.

Hint

The hints given for 01 - Point are also useful for this assessment.

04 - Cars 2#

Note

This assessment is a variation of 03 - Cars, with two goals:

  1. adding a constructor to the class Car (as explained in Constructors), and

  2. turning the static methods of the class Car into non-static methods, (as explained in Non-Static Object Methods).

Therefore, you can solve this assessment by first solving 03 - Cars, and then adapting its solution according to the requirements below.

Edit the file Car.java provided in the handout, and implement a class called Car representing a car. The requirements are:

  • Objects of the class Car must have four fields called respectively:

    • brand (e.g. "Ford")

    • model (e.g. "Fiesta")

    • numberPlate (e.g. "AF 54539")

    • color (e.g. "red")

  • The class must have a constructor that takes the car’s brand, model, number plate, and colour, and can be called e.g. as:

    var c = new Car("FIAT", "Topolino", "EZ 13623", "blue");
    
  • Car objects must have the following method:

    String description()
    

    When called, the method returns a string with the car information. For instance, calling c.description() (using c from the previous point) must return a string of the following form:

    FIAT Topolino, blue [EZ 13623]
    
  • Car objects must have the following method:

    boolean equals(Car other)
    

    When called, the method returns true if the brand, model, numberPlate, and color fields of this object are equal to the corresponding fields of the other object; otherwise, the method returns false.

  • Car objects must have the following method:

    boolean isAlike(Car other)
    

    When called, the method returns true if the brand, model, and color fields of this object are equal to the corresponding fields of the other object; otherwise, the method returns false.

The handout includes some Java files called Test01.java, Test02.java, etc.: they are test programs that use the class Car, and they might not compile or work correctly until you complete the implementation of class Car in the file Car.java. You should read those test programs and try to run them, but you must not modify them.

When you are done, submit the modified file Car.java on DTU Autolab.

Hint

The hints given for 01 - Point are also useful for this assessment.