Understanding Final, Finally, Finalize in Java: A Detailed Comparison

Updated on October 24, 2024

Article Outline

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.

 

Also Read: Exception Handling in Java

 

Example:

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
*Image
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.

 

Also Read: Difference Between Throw and Throws in Java

Why Use the Finally Block?

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.

 

Also Read:  Types of Exceptions in Java

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.

 

Also Read: Java Interview Questions and Answers

Using final for Constants and Secure Methods

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 } }

Output:

Database connection opened. Database connection closed.

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
No, a final variable must be initialized when it's declared or within the constructor, for example, variables.
Yes, the finally block always runs irrespective of a return statement.
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.
No, once declared as final, the method cannot be overridden
If an exception occurs in finally block then it propagates and can override the exception from try or catch block.

Updated on October 24, 2024

Link
left dot patternright dot pattern

Programs tailored for your success

Popular

Management

Data Science

Finance

Technology

Future Tech

Upskill with expert articles

View all
Hero Vired logo
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.
Blogs
Reviews
Events
In the News
About Us
Contact us
Learning Hub
18003093939     ·     hello@herovired.com     ·    Whatsapp
Privacy policy and Terms of use

|

Sitemap

© 2024 Hero Vired. All rights reserved