Lec 07 - Immutability and Nested Classes
So far in this course, we have been focusing on three ways of dealing with software complexity:
by encapsulating and hiding the complexity behind abstraction barriers,
using a language with a strong type system and adhering to the subtyping substitution principle,
and applying the abstraction principles and reusing code written as functions, classes, and generics types.
This summarizes the Lec 01 - Lec 06 so perfectly!
Immutability
The fourth way of dealing with software complexity is to make our classes immutable.
An immutable class means that: the internal fields of the same instance from this immutable class cannot be changed outside its abstraction. (a.k.a If you do make a change, then likely you will get a new instance and it is not the same instance as before anymore).
Write Immutable Class
Basically, there are two steps we should follow to make a class immutable
Add final to class declaration to prevent inheritance
This is to prevent subclass of immutable class to override some methods to make the internal field of the immutable class mutable.
Make the class's internal fields immutable
This can be done by many ways:
For fields of primitive types, simply declare them as
final. For other fields, make sure they are of immutable type! (a.k.a we can then create other larger immutable classes by only using immutable classes as fields.)Have a class that prevents any and all kinds of sharing by copying all the parameters before assigning them to the fields and copying all return values. (See Another way to make internal fields immutable)
For example, the following are two good examples of the immutable Point and Circle class.
final class Point {
private final double x;
private final double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public Point moveTo(double x, double y) {
return new Point(x, y);
}
:
@Override
public String toString() {
return "(" + this.x + "," + this.y + ")";
}
}We start by making the fields final to signal our intention that we do not intend to assign another value to them. Now that the x and y cannot be re-assigned (a new value or even the same value), to move a point, we shouldn't re-assign to the fields x and y anymore. Instead, we return a new Point instance to prevent mutating the current instance.
Similarly, we can write our immutable class Circle.
Another way to make internal fields immutable
In fact, there is another way to make the internal fields of an immutable class immutable. That is,
to have a class that prevents any and all kinds of sharing by copying all the parameters before assigning them to the fields and copying all return values.
For example, we can rewrite our immutable Circle class as follows:
Here, the .clone() is used to make a copy of the value of internal fields.
Note that we assume that Point is immutable!
Takeaway
That is not to say that the
finalkeyword is not important. It helps accidental re-assignment and in some cases, that is sufficient especially if the fields are of primitive type.Once we have created one immutable class, we can then create other larger immutable classes by only using immutable classes as fields.
Advantages of Immutable Class
Ease of Understanding
Code written with immutable objects is easier to reason with and easier to understand because once we create an instance from an immutable class, its internal fields will never change unless we have explicitly re-assigned it.
Enabling Safe Sharing of Objects
This means we can now safely create a class field to represent an immutable property. For example, if our Point class is immutable, we can create a class field called ORIGIN to always represent the point (0, 0).
Enabling Safe Sharing of Internals
This advantage also utilizes the fact that the internal fields of an instance from an immutable class are immutable. See more from the lecture notes.
Enabling Safe Concurrent Execution
This won't be discussed for now. But imagine having code where we have to ensure its correctness regardless of how the execution interleaves! That's interesting right?
Nested Class
Definition: A nested class is a class defined within another containing class. For example,
Use: It usage is to encapsulate information within a containing class.
Property: A nested class is a field of the containing class and can access fields and methods of the containing class, including those declared as private.
A nested class can be classified into the following four types
Inner class
Static nested class
Local class
Anonymous class
Inner class
It is a non-static nested class, thus it can access all fields and methods of the containing class.
This means that when instantiating the inner class, you need an instance of the containing class and the position of new should be behind the . operator.
In the Inner class, we should be extremely careful with the use of this. For example, the following is wrong!
Qualified this
thisThis is to resolve the issue above. A qualified this reference is prefixed with the containing class name, to differentiate between the this of the inner class and the this of the containing class. In the example above, we can access x from A through the A.this reference.
To create an instance of the inner class, we can see the following code
Static nested class
It is a static nested class, thus it is associated with the containing class, NOT an instance.
This means when instantiating the static nested class, you don't need an instance of the containing class.
So, by default, it can only access static fields and static methods of the containing class. If you want to access the fields of the containing class, you can do so by using object reference.
Hiding nested class
The notes is very detailed. Please go back and refer to it if needed.
To put it simply, the nested class should be defined as private for the sake of not breaking the abstract barrier. This makes calling the nested class explicitly not allowed. However, calling a public method from the containing class that returns an instance of the private nested class is still allowed. For example,
Local class
It is a nested class declared within a function, just like a local variable. The local class has access to all the local variables from within the method it is declared, as well as the fields of its containing class.
Variable Capture
It is a behavior that the local class will capture the following variables
The local variables of the method where the local class comes from (including only the arguments that the lambda uses, see more in Diagnostic Quiz Q13)
The instance that invokes the method where the local class comes from. (See more in Rec 05)
Effectively final
finalEffectively final means that an implicitly final variable cannot be re-assigned after they are captured.
The use of this rule is because variable capture can sometimes be confusing, thus Java enforces a rule that only final or effectively final local variables can be captured. If the variables captured are neither final nor effectively final, then a compile error will be generated!
Anonymous Class
An anonymous class is one where you declare a local class and instantiate it in a single statement.
It has the following format: new X (arguments) { body }, where:
Xis a class that the anonymous class extends or an interface that the anonymous class implements.argumentsare the arguments that you want to pass into the constructor of the anonymous class.bodyis the body of the class as per normal, except that we cannot have a constructor for an anonymous class.
Like a local class, an anonymous class captures the variables of the enclosing scope as well — the same rules to variable access as local classes apply.
Last updated