In previous chapters we’ve shown examples illustrating how a class is declared. Let’s recap what we’ve covered so far and add some new concepts that are important when declaring classes.
Class declaration
As we saw in Chapter 1, we declare a class by using the class
keyword. In its full form a class declaration can take the following form:
[class modifiers] class ClassName [extends SuperClassName] [implements Interface1, Interface2, ...] {
// class body (includes class fields, methods, etc)
}
In syntax definitions, like the one above, everything in square brackets is optional. |
We’ll cover more class modifiers, the extends and implements keywords and the concept of interfaces in the remaining sections of this chapter.
|
For example, assume we have a class that represents a Light
in your house for an Internet of Things system:
public class Light {
// class body
}
Declaring fields
Fields are also known as member variables
and as mentioned in Chapter 1 they represent the state of our objects. Field declarations can take the following form:
[field modifiers] FieldType variableName [= fieldInitialValue];
For our Light
class example above, we can think of three states associated with it, a variable that specifies if the light is on/off, on
, a second variable that specifies the color
of the light (for example, "WHITE"
or "YELLOW"
), and a third variable that specifies the power
/brightness of the light (think of a dimmable light).
public class Light {
private boolean on;
private String color;
private int power = 0;
// ...
}
We’re making our fields private to follow best practices and only allow access to the state of the class from inside the class. This is known as Data Encapsulation.
|
There are other modifiers that we can apply to fields that we’ll cover in upcoming sections of this chapter. |
It’s important to note that these fields belong to the instances that we create from the Light
class, so each object we create will have its own separate set of values.
For example in this code:
Light light1 = new Light();
Light light2 = new Light();
we are creating 2 separate Light
objects, both of which have a value for on
, color
and power
independent from each other. For example, light1
could be turned on and light2
could be turned off, etc.
Declaring methods
Methods represent the behavior of our classes and as covered before are the way for classes in our application to interact with one another.
Method declarations take the following form:
[method modifiers] ReturnType methodName([MethodParameters]) [throws Exceptions] {
// method body
}
We’ll cover method modifiers later in this chapter. The throws keyword will be covered in a future chapter.
|
In our Light
class, we can add two very basic behaviors, turnOn
and turnOff
methods that will change the state of our on
field:
public class Light {
//...
public void turnOn() {
on = true;
}
public void turnOff() {
on = false;
}
//...
}
Method signature and method overloading
The signature of a method in Java is defined by the name of the method and the type of the parameters it receives.
Two methods in the same class can’t have the same signature, otherwise you’ll get a Duplicate method
compilation error as the compiler can’t differentiate them.
However, Java supports methods with the same name as long as their list of parameters is different (either in length, or in the types of the parameters it receives). This is known as Method overloading.
In our Light
class, we can assume that one of the requirements is to be able to turn the light on and at the same time set the color of the light, the desired power or both. We could use different method names, like turnOnWithColorAndPower
, but this becomes very verbose and we can express the same with 4 different turnOn
methods:
public class Light {
//...
// 1) method with no parameter
public void turnOn() {
on = true;
}
// 2) method with a String parameter
public void turnOn(String withColor) {
color = withColor;
on = true;
}
// 3) method with an int parameter
public void turnOn(int withPower) {
on = true;
power = withPower;
}
// 4) method with a String and an int parameter
public void turnOn(String withColor, int withPower) {
color = withColor;
power = withPower;
on = true;
}
//...
}
The code above is perfectly valid as even though the 4 methods have the same name, their list of parameters is different.
When we invoke the turnOn
method, Java will invoke the correct method depending on the parameters used, for example, if we do:
Light myLight = new Light();
myLight.turnOn(50);
Java will correctly invoke method #3 in our snippet above as it is the method that matches the parameter type being used. We are passing in 50
which is an int
literal value and method #3 expects an int
value as the input.
Our Light class is a very basic example. The Light class in this section has design failures that are included to introduce concepts in later sections and see how we can use them to improve the code.
|
The this
keyword and resolving ambiguity
When we are inside an instance method or in a constructor (covered in the next section), Java provides us with a reference that points to the current object called this
. With the this
reference we can access the instance fields and methods that belong to the object we’re operating on.
The 2 main reasons we’d use the this
keyword is 1) to clarify ambiguity and ensure we tell the Java compiler about what field/variable/method we want to operate on, and 2) if we need to pass a reference to the current object to a different method, for example, if we want to print the object in our Console we can do System.out.println(this)
.
To give an example, let’s take our turnOn(String withColor)
method and modify it slightly by changing the name of the parameter to be the same as the name of the field in the class (color
):
//...
private String color;
public void turnOn(String color) {
color = color;
on = true;
}
//...
This code is valid, as in, it will compile, however the compiler will give us a warning indicating that the color = color
assignment has no effect. The reason for this is that because both the method parameter (color
) and the class field (color
) have the same name, variable shadowing occurs and with the code as it is, it uses the same variable for the left-side and right-side of the assignment.
Java will use the color
variable in the closest scope/block of code, in the example above this means the method and hence Java resolves color
to be the local variable that comes in as the method argument and not the class field.
In short, we’re telling Java to assign to the color
parameter the value it already has, which isn’t really what we want to do. What we want to do is change the class color
field’s value to use the value of the color
input parameter.
Now, as mentioned above, we can use the this
keyword to resolve this type of cases. We can do the following:
public void turnOn(String color) {
this.color = color;
on = true;
}
By using this.color
Java knows that we’re referring to the color
field in the current object and that we want to change its value to the one provided in the color
variable that comes as the input parameter to the method.
In the same way we used this.color
to refer to a field, it can be used to invoke methods in the current object, for example, this.turnOn(20)
.
The use of the this
keyword isn’t mandatory, but if you have variables with the same name (like the color
case above), or methods with the same name where it can be ambiguous either for the Java compiler or for a person reading the code, then using the this
keyword is the solution.
It is common practice to use input parameters in a method that have the same name of the fields when the method is simply replacing the value, like in our turnOn(String color) example method above.
|
In future examples we’ll be using variable shadowing more often so keep in mind the difference between this.x and x when you have more than one variable x available.
|