Finally, this week, let's go through the meaning of the MODIFIER "final" in different contexts. As the name suggests, "final" means that we permanently fix some feature. However, it WORKS in slightly DIFFERENT WAYS IN DIFFERENT CONTEXTS. 1 In the context of a 'LOCAL VARIABLE', the modifier means that the value of the variable CANNOT CHANGE AFTER INITIALISATION. This is a way to ensure that the value of the variable does not change even by accident. public static void main(String[] args) { // The value of a is always 3 final int a = 3; // Ollie always points to the same student object final Student ollie = new Student("Oliver", "oliver@example.com", 123); // HOWEVER, the object CAN CHANGE IF it is NOT IMMUTABLE // i.e., if suitable public OPERATIONS are OFFERED TO the CLIENT oliver.setStudyCredits(159); } 2 2A In the context of an ATTRIBUTE, the operation is similar. The difference is that the value can either be given immediately at the time of definition… class Ball { // SET VALUE AT ATTRIBUTE LEVEL private final double diameter = 10.0; } 2B … or leave the value to be given in the CONSTRUCTORS. If the value is not given at the time of definition, it must be given in each constructor. After this, the value cannot change. class Ball { private final double diameter; public Ball(double diameter) { // SET VALUE IN THE CONSTRUCTOR this.diameter = diameter; } } 3 A PARAMETER can also be defined with the 'final' modifier. In this case, the value of the parameter variable cannot change. For example, objects passed as parameters could be equipped with a final modifier, so that the original reference is not accidentally lost within the method. public static void removeNegatives(final ArrayList list) { // Now the value of the parameter variable list cannot change, // i.e., it always points to the list that was passed // as a parameter. 4 In the context of METHODS and CLASSES, the final modifier is related to INHERITANCE. A method equipped with the modifier cannot be overridden in a derived class: class SecretAgent { private String name; private String code; public SecretAgent(String name, String code) { this.name = name; this.setCode(code); } // 'final' METHOD public final void setCode(String code) { // The class can be inherited, but this method cannot // be overridden in the derived class if (SecretAgent.codeOk(code)) { this.code = code; } else { this.code = "000"; } } public static boolean codeOk(String code) { if (code.length() != 3 || !code.startsWith("00")) { return false; } try { Integer.valueOf("" + code.charAt(2)); return true; } catch (NumberFormatException e) { return false; } } } A good example of this could be a setting method that is called in the constructor - if it is overridden in the derived class, the operation of the class can change significantly. If a class is equipped with the 'final' modifier, the 'final' CLASS CANNOT BE INHERITED at all by other classes. A typical example is the Java 'String' class, whose inheritance attempt leads to a compiler error message: eg1 // Now Ball cannot be inherited, an inheritance attempt gives an error final class Ball { private final double diameter; public Ball(double diameter) { this.diameter = diameter; } } class SoccerBall extends Ball { } // Compiler error! eg2 // String is declared as: public final class String { // ... } class MyString extends String { } // COMPILER ERROR! ======================================== The Last Piece on the Board At the end of chapter 6, let's implement a simple game. The game starts with an 'empty game board', where each player alternately places 1-3 of their own game pieces according to their own choice. The player who places the last piece on the board wins. A piece can only be placed in an empty square. There can be different amounts of pieces on the board at different rounds of the game.