Hi! You're already familiar with the word "modifier". At a minimum, you've encountered access modifiers (public, private) and the static modifier.
Today we'll discuss a special modifier called final.
You could say the final modifier "cements" parts of our program where constant, unambiguous, unchanging behaviors are needed.
There are three places in your programs that you can use it: classes, methods, and variables.
Let's run through them in order.
If the final modifier is used in a class declaration, it means the class can't be inherited.
In previous lessons, we used a simple inheritance example: we had an
Nothing changed. And it shouldn't have. Like we said earlier, Strings are immutable. So, what's with all the methods in the
![Fixed values in Java: final, constants, and immutable - 2](https://cdn.codegym.cc/images/article/6c57a5a9-d609-454a-819e-22a811604d42/1024.jpeg)
Animal
parent class and two child classes: Cat
and Dog
public class Animal {
}
public class Cat extends Animal {
// Fields and methods of the Cat class
}
public class Dog extends Animal {
// Fields and methods of the Dog class
}
However, if we use the final modifier on the Animal
class, the Cat
and Dog
classes cannot inherit it.
public final class Animal {
}
public class Cat extends Animal {
// Error! Cannot inherit from final Animal
}
The compiler immediately generates an error. In Java, many final classes are already implemented. Among those you use frequently, String
is the most well-known.
Moreover, if a class is declared as final, all of the class's methods also become final.
What does that mean?
If a method is declared using the final modifier, you can't override that method.
For example, here we have an Animal
class that declares a speak()
method.
But, dogs and cats definitely "speak" in different ways. So, we'll declare speak() methods in both the Cat
and Dog
classes, but we'll implement them differently.
public class Animal {
public void speak() {
System.out.println("Hello!");
}
}
public class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow!");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Woof!");
}
}
We made the Cat
and Dog
classes override the method declared in the parent class. Now, an animal will speak differently, depending on what type of object it is:
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
cat.speak();
dog.speak();
}
}
Output:
Meow!
Woof!
However, if we declare the Animal
class's speak()
method as final, then we can't override it in other classes:
public class Animal {
public final void speak() {
System.out.println("Hello!");
}
}
public class Cat extends Animal {
@Override
public void speak() {// Error! A final method can't be overridden!
System.out.println("Meow!");
}
}
And our objects will be forced to use the speak()
method as defined in the parent class:
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
cat.speak();
dog.speak();
}
Output:
Hello!
Hello!
Now, regarding final variables. They are also known as constants.
First (and most importantly), the initial value assigned to a constant value can't be changed. It is assigned once and for all.
public class Main {
private static final int CONSTANT_EXAMPLE = 333;
public static void main(String[] args) {
CONSTANT_EXAMPLE = 999;// Error! You can't assign a new value to a final variable!
}
}
A constant doesn't need to be initialized immediately. That can be done later. But, the value initially assigned to it will stay the same forever.
public static void main(String[] args) {
final int CONSTANT_EXAMPLE;
CONSTANT_EXAMPLE = 999;// This is allowed
}
Second, note our variable's name. Java has a different naming convention for constants. It isn't the usual camelCase notation. If it had been an ordinary variable, we would have called it constantExample. But, the names of constants are written in all caps, with underscores between words (if there is more than one word), e.g. "CONSTANT_EXAMPLE".
Why do we need constants? They are very useful if, for example, there is a fixed value you regularly use in a program.
For example, you've decided to make history and write the game "The Witcher 4" by yourself. The game will obviously regularly use the protagonist's name: "Geralt of Rivia".
This string (and other heroes' names) is best declared as a constant: its value will be stored in one place, and you definitely won't make a typo when entering it a million times.
public class TheWitcher4 {
private static final String GERALT_NAME = "Geralt of Rivia";
private static final String YENNEFER_NAME = "Yennefer of Wengerberg";
private static final String TRISS_NAME = "Triss Merigold";
public static void main(String[] args) {
System.out.println("The Witcher 4");
System.out.println("It's already the fourth Witcher game, but " + GERALT_NAME + " still can't decide who" +
" he likes more: " + YENNEFER_NAME + " or " + TRISS_NAME);
System.out.println("But, if you've never played The Witcher before, we'll start from the beginning.");
System.out.println("The protagonist's name is " + GERALT_NAME);
System.out.println(GERALT_NAME + " is a witcher, a monster hunter");
}
}
Output:
The Witcher 4
It's already the fourth Witcher game, but Geralt of Rivia still can't decide who he likes more: Yennefer of Wengerberg or Triss Merigold
But, if you've never played The Witcher before, we'll start from the beginning.
The protagonist's name is Geralt of Rivia
Geralt of Rivia is a witcher, a monster hunter
We've declared the heroes' names as constants. Now we definitely won't make a typo, and there's no need to write them out by hand each time. Another plus: if we ever need to change the variable's value across the entire program, you can do it in one place, rather than manually modifying it across the entire code base. :)
Immutable types
As you've worked with Java, you've probably already gotten used to the idea that programmers have almost complete control over the state of all objects. If you want to create aCat
object, you can. If you want to rename it, you can. If you want to change its age or something else, you can.
But Java has several data types that have a special property. They are immutable.
If a class is immutable, then the state of its objects can't be changed.
Want some examples?
It might surprise you, but the most well-known immutable class is String!
So, we really can't change a String's value?
Well, let's try it:
public static void main(String[] args) {
String str1 = "I love Java";
String str2 = str1;// Both reference variables point to the same string.
System.out.println(str2);
str1 = "I love Python";// but changing str1 has no impact on str2
System.out.println(str2);// str2 continues to point to the "I love Java" string, but str1 now points to a different object
}
Output:
I love Java
I love Java
After we wrote
str1 = "I love Python";
the "I love Java"
string object didn't change or go anywhere. It still happily exists and has the exact same text as before.
The code
str1 = "I love Python";
simply created another object, which str1 now points to. But, we can't seem to have any effect on the "I love Java" string object.
Okay, let's try something else! The String
class is full of methods, and some of them appear to change the object's state!
For example, there's a replace()
method. Let's change the word "Java" to "Python" in our string!
public static void main(String[] args) {
String str1 = "I love Java";
String str2 = str1;// Both reference variables point to the same string.
System.out.println(str2);
str1.replace("Java", "Python");// We try to change the state of str1 by swapping the word "Java" with "Python"
System.out.println(str2);
}
Output:
I love Java
I love Java
It didn't work again! Maybe the replace method doesn't work?
Let's try something else. For example, substring()
. It returns a substring based on character indices passed as arguments. Let's cut off the first 10 characters of our string:
public static void main(String[] args) {
String str1 = "I love Java";
String str2 = str1;// Both reference variables point to the same string.
System.out.println(str2);
str1.substring(10);// Truncate the original String
System.out.println(str2);
}
Output:
I love Java
I love Java
![Fixed values in Java: final, constants, and immutable - 3](https://cdn.codegym.cc/images/article/76f63ce7-29a3-4141-94c5-c4320c93f21d/1024.jpeg)
String
class? After all, they can truncate strings, change characters, and more. What's the point if nothing happens?
They actually can do these things! But, they return a new String every time.
It's pointless to write
str1.replace("Java", "Python");
because you can't change the original object.
But, if you write the method's result to a new reference variable, you'll see the difference immediately!
public static void main(String[] args) {
String str1 = "I love Java";
String str2 = str1;// Both reference variables point to the same string.
System.out.println(str2);
String str1AfterReplacement = str1.replace("Java", "Python");
System.out.println(str2);
System.out.println(str1AfterReplacement);
}
All String
methods work this way. Nothing can be done to the "I love Java"
object.
You can just create a new object and write: "<new object> = the result of manipulating the "I love Java" object "
.
What other types are immutable?
Some that you'll definitely need to remember right away are all the wrapper classes for the primitive types.
Integer
, Byte
, Character
, Short
, Boolean
, Long
, Double
, Float
: all of these classes create immutable
objects (we'll talk about them in upcoming lessons). This includes classes used to create large numbers, such as BigInteger
and BigDecimal
.
We recently covered exceptions and touched on the stack trace.
Well, guess what, java.lang.StackTraceElement objects are also immutable. This makes sense: if someone could change our stack's data, it would make the whole thing pointless. Imagine someone going through the stack trace and changing an OutOfMemoryError to a FileNotFoundException. And then you use that stack to find the cause of the error. But the program doesn't even use files. :) So, they made these objects immutable, just in case.
Okay, so it more or less make sense for StackTraceElement.
But, why would anyone need to make Strings immutable? Why would changing their values be a problem? It would probably even be more convenient. :/
There are several reasons for this.
First, it saves memory. Immutable strings can be placed in the string pool, allowing strings to be reused instead of creating new ones.
Second, for security. For example, usernames and passwords are Strings in almost every program. Making possible to change them could result in authorization problems.
There are other reasons, but our study of Java hasn't covered them yet, so we'll come back to them later.More reading: |
---|