The three words: final, finally, finalize in Java: sound similar, but each has a very different purpose. Most developers mix them up at first glance, but each of them covers a unique part of Java programming:
final: It locks things down. When we use final, it means that whatever we are working with cannot be changed.
finally: This one guarantees the execution of specific code regardless of what may happen. It’s just a promise that something is always going to be executed.
finalize(): This method is rather more of a goodbye call. It is invoked just before an object is cleaned up by Java’s garbage collector.
Let’s take a deep dive into each one, starting with the final keyword.
Detailed Explanation of the final Keyword: Restricting Classes, Methods, and Variables
When we declare something as final, we are saying that something can’t change. And that is so, irrespective of whether it is a class, a method, or a variable
Final Classes
A final class cannot be extended. Once we lock it down with final, nobody is going to come in and make a subclass.
This comes in handy when we do not want anyone to mess with the core structure.
This prevents developers from making a subclass that could modify the way things work within the class.
final class Vehicle {
private String type = "Car";
public String getType() {
return type;
}
}
// This will cause a compilation error
class SportsCar extends Vehicle {
public String getType() {
return "Sports Car";
}
}
Output:
Compilation error: Cannot inherit from final class Vehicle
Final Methods
A final method essentially means that nobody can override it in any subclass.
This locks up the behavior of the method and guarantees that its behavior won’t change no matter how many layers of inheritance exist.
By using final keyword on methods, we ensure that critical methods behave exactly as intended.
Example:
class MobilePhone {
public final void call() {
System.out.println("Calling from MobilePhone...");
}
}
class SmartPhone extends MobilePhone {
// This will throw a compilation error
public void call() {
System.out.println("Calling from SmartPhone...");
}
}
Output:
Compilation error: call() in SmartPhone cannot override call() in MobilePhone
Final Variables
With final variables, the story is simple—once they’re set, we can’t change them.
Let’s say we have a variable for a student’s roll number. Once it’s assigned, it stays the same.
Example:
class Student {
final int rollNumber;
public Student(int rollNumber) {
this.rollNumber = rollNumber;
}
public void displayRollNumber() {
System.out.println("Roll Number: " + rollNumber);
}
public static void main(String[] args) {
Student student1 = new Student(101);
student1.displayRollNumber();
}
}
Output:
Roll Number: 101
Get curriculum highlights, career paths, industry insights and accelerate your technology journey.
Download brochure
The Functionality of the finally Block in Java’s Exception Handling
This is all about the fact that something should be done regardless of whatever.
If we are throwing exceptions in Java, then we mostly use try and catch blocks. However, there is always that one question- what if we want to run some piece of code, no matter whether an exception occurs or not?
That’s where the finally block comes into play.
The code within the finally block is always executed, with or without an error.
Consider you opened a file, and worked with it, but now something got wrong in the middle.
You would want the file closed, even if there was an error.
That’s what finally is for.
It ensures that there are cleanup activities – which your application probably would like to do, such as closing resources – executed even in the presence of an error, which disrupts the usual execution of the program.
Example:
import java.util.Scanner;
public class DivisionExample {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a, b;
try {
System.out.print("Enter numerator: ");
a = sc.nextInt();
System.out.print("Enter denominator: ");
b = sc.nextInt();
int result = a / b;
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Error: Division by zero is not allowed.");
} finally {
sc.close(); // Always closes the scanner, whether an exception occurs or not
System.out.println("Scanner closed.");
}
}
}
Output:
Case 1 (Valid Input):
Enter numerator: 10
Enter denominator: 2
Result: 5
Scanner closed.
Case 2 (Division by Zero):
Enter numerator: 10
Enter denominator: 0
Error: Division by zero is not allowed.
Scanner closed.
Notice that in both cases, the finally block runs and closes the scanner, preventing resource leaks.
When Is finally Most Useful?
The finally block is a lifesaver when it comes to cleaning up resources:
Closing files
Closing database connections
Releasing memory or cleaning up objects
Without finally, we’d have to manually handle resource cleanup, which can easily get missed, especially in large applications.
Purpose and Limitations of finalize() Method in Java Object Life Cycle
What happens when the object in Java is no longer in use?
At some point, it must be cleaned up to free its memory for other processes.
This is where garbage collection comes in and with it the finalize() method.
While it gives us a chance to clean things up before the object is destroyed, there are some limitations we need to be aware of.
What Does finalize() Actually Do?
The finalize() method is called by the Garbage Collector right before an object is removed from memory.
It will let us do things like close files or release network connections still open.
Example:
class FileHandler {
public FileHandler() {
System.out.println("FileHandler object created");
}
@Override
protected void finalize() throws Throwable {
System.out.println("FileHandler object is being garbage collected");
}
}
public class Main {
public static void main(String[] args) {
FileHandler fh = new FileHandler();
fh = null; // Make the object eligible for garbage collection
System.gc(); // Request garbage collection
System.out.println("End of program");
}
}
Output:
FileHandler object created
End of program
FileHandler object is being garbage collected
In this case, when the FileHandler object is no longer referenced, the Garbage Collector calls finalize() before it removes the object.
The problem there, however, is that we have no control over when this happens.
Why finalize() Isn’t Reliable
The finalize() method looks like a convenient way to clean up objects.
But the timing isn’t predictable.
The Garbage Collector runs its own schedule. It may never call finalize() for an object soon after the object becomes eligible for collection.
Even worse, finalize() might never even get run at all. If the program terminates before garbage collection happens, then the method will never be invoked.
This unpredictability makes finalize() hazardous when you need to do resource cleanup.
A More Suitable Solution: Try-with-Resources
So, if finalize() isn’t really the solution for cleanup then what can we use?
There’s a better, more reliable way of resource cleanup in Java – try-with-resources. It automatically closes resources like files and streams once the try block is done.
Here’s an example of how it works:
Example:
import java.io.FileWriter;
import java.io.IOException;
public class FileWriteExample {
public static void main(String[] args) {
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello, World!");
} catch (IOException e) {
System.out.println("An error occurred: " + e.getMessage());
}
}
}
Output:
An error occurred: output.txt (Permission denied)
In this case, the FileWriter is automatically closed when the block finishes. We don’t have to rely on the unpredictable finalize() method.
Differences Between final, finally, finalize in Java: A Complete Comparison
Now that we’ve covered all three terms, let’s look at the key differences between final, finally, finalize in Java.
Here’s a simple table to break it down:
Aspect
final
finally
finalize()
Purpose
Prevents changes to classes, methods, variables
Ensures code always runs after try-catch
Called before an object is garbage collected
Used With
Classes, methods, variables
Exception handling
Objects (for cleanup)
Execution
During compilation
During runtime after exception handling
Called by Garbage Collector before object removal
Control Over Timing
Immediate (at compile-time)
Always runs after try-catch
Unpredictable timing
Alternatives
None
None
Try-with-resources for reliable cleanup
Best Practices for Developers When Using final, finally, and finalize in Java
Confusing final With Immutability
It is very tempting to believe that final makes the object immutable but nope it doesn’t.
It just prevents us from reassigning the variable. If the object itself has some mutable properties, those can still change.
Example:
final StringBuilder sb = new StringBuilder("Hello");
sb.append(", World!"); // This is allowed
// sb = new StringBuilder("New String"); // This will throw an error
In this case, we can still change the contents of sb, but we cannot reassign it.
Forgetting to Close Resources in the finally Block
Most developers forget to use finally for closing resources like files and database connections.
If left open, there will be major problems such as memory leaks or file locks. Make it a habit to close everything inside finally block or rather use try-with-resources.
Relying on finalize() for Critical Cleanup
We are not supposed to use finalize() for important cleanup, such as database connection. We do not know exactly when it actually runs; so, it could miss the cleanup call.
Always prefer try-with-resources or explicit close() calls for non-critical cleanup.
Overriding final Methods
Trying to override a method if it’s declared as final will give a compilation error.
This happens when developers are unaware of the fact that final locks down method behavior. Verify if a method is final before you try to override it.
Misusing finally with Return Statements
Often people think that the finally block gets skipped if a return statement is hit in the try or catch blocks, but that’s not true. The finally block will always run, even after a return statement.
Example:
public class ReturnExample {
public static void main(String[] args) {
System.out.println(testMethod());
}
static String testMethod() {
try {
return "Returning from try block";
} finally {
System.out.println("Finally block executed");
}
}
}
Output:
Finally block executed
Returning from try block
Real-World Use of final, finally, finalize in Java Programs: Unique Code Examples
You’ve probably wondered how final, finally, finalize in Java can be used in actual programs. The best way to understand them is by diving into some real-world code examples.
When we use final in real programs, it’s often to create constants or secure methods that can’t be changed by accident.
Take this final variable example:
class AppConfig {
public static final String APP_NAME = "BankApp";
public static final int MAX_USERS = 100;
public static void main(String[] args) {
System.out.println("App Name: " + APP_NAME);
System.out.println("Max Users: " + MAX_USERS);
}
}
Output:
App Name: BankApp
Max Users: 100
For methods, it’s a similar story. Once a method is marked final, it can’t be overridden.
class Payment {
public final void process() {
System.out.println("Processing payment...");
}
}
class CreditCardPayment extends Payment {
// This will throw a compilation error
// public void process() { System.out.println("Processing credit card payment..."); }
}
Using finally for Resource Cleanup
This is where we make sure important things—like closing files or database connections—always happen. This ensures we don’t leave any open resources hanging around.
Here’s an example using a file read operation:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class FileReadExample {
public static void main(String[] args) {
Scanner fileReader = null;
try {
File file = new File("data.txt");
fileReader = new Scanner(file);
while (fileReader.hasNextLine()) {
System.out.println(fileReader.nextLine());
}
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
} finally {
if (fileReader != null) {
fileReader.close(); // Ensure the scanner is always closed
System.out.println("Scanner closed.");
}
}
}
}
When to Use finalize() in Object Cleanup
While finalize() isn’t widely used anymore, there are still times when you might see it, especially in older codebases.
Here’s an example where finalize() is used to clean up before an object is garbage collected:
class DatabaseConnection {
public DatabaseConnection() {
System.out.println("Database connection opened.");
}
@Override
protected void finalize() throws Throwable {
System.out.println("Database connection closed.");
}
public static void main(String[] args) {
DatabaseConnection db = new DatabaseConnection();
db = null; // Object becomes eligible for garbage collection
System.gc(); // Request garbage collection
}
}
The Role of finally in Ensuring Resource Cleanup and How It Helps Avoid Leaks
The finally block will indeed execute whether the exception occurs or not. It ensures that resources are being closed, thus preventing potential memory leaks or file locks.
Imagine every time you open a resource, often it will not close correctly especially if something goes wrong with your code.
This is why we rely on finally to clean up the mess.
A Common Pitfall: Forgetting to Close Resources
Imagine that you are working with a database and that you forgot to close the connection after using it. That can cause resource exhaustion, slowing down your application or even crashing it.
By placing your clean-up code in the finally block, you guarantee it will run no matter what else happens.
Conclusion
Understanding final, finally, and finalize in Java is important for writing good programs. All serve a different purpose: final forbids classes, methods, or variables from being modified; finally ensures critical code always runs – useful if you have cleanup code that needs to execute; and finalize() cleans up objects on garbage collection, although its use is now considered generally unreliable.
The code goes stable if it’s implemented with final for security, finally for resource management, and avoiding finalize() for the use of try-with-resources. Acknowledging these differences eliminates many common errors in code and generally improves its quality, making it easier to control and less likely to throw issues.
If you want to enhance your skills and develop expertise in Java as well as other technologies, then the Certificate Program in Full Stack Development with Specialization for Web and Mobile at Hero Vired can be the perfect course for you. It helps you to be equipped with the most advanced tools and techniques needed for successful web and mobile development that would boost your career as a full-stack developer.
FAQs
Can a final variable be initialized later in the code?
No, a final variable must be initialized when it's declared or within the constructor, for example, variables.
Does the finally block run if there's a return statement in the try block?
Yes, the finally block always runs irrespective of a return statement.
Shall I use finalize() for cleaning resources?
No, instead you should use try-with-resources or explicitly close resources. finalize() is not predictable and must not be used in applications requiring strict certainty.
Can the final method in a subclass be overridden?
No, once declared as final, the method cannot be overridden
What happens if an exception occurs in finally block?
If an exception occurs in finally block then it propagates and can override the exception from try or catch block.
Hero Vired is a leading LearnTech company dedicated to offering cutting-edge programs in collaboration with top-tier global institutions. As part of the esteemed Hero Group, we are committed to revolutionizing the skill development landscape in India. Our programs, delivered by industry experts, are designed to empower professionals and students with the skills they need to thrive in today’s competitive job market.