Skip to main content

Java Keywords (Part XXIV): native

Java keyword list abstract continue for new switch assert default goto * package synchronized boolean do if private this break double implements protected throw byte else import public throws case enum instanceof return transient catch extends int short try char final interface static void class finally long strictfp volatile const * float native super while Keyword marked with an asterisk (*) are keywords that, although valid, are not used by programmers. This is the last chapter of the Java Keyword series. This is probably the keyword I have used the least. In my 20 year career as a software developer, I have used this keyword once, and that was to make some addition to legacy code. The keyword native is a method modifier . Basically, it is a keyword that can only be applied to methods. According to the Java Language Specification (JLS), A method that is native is implemented i...

Exception Handling: File CRUD Operations Example


Introduction to Exception Handling

This is a supplement to my previous blog Creating Custom Exception Classes in Java. I recommend you read that one first and then read this one.

Java provides many Exception (and Error) Handling classes. Both of these categories implement the Throwable interface. Although it is not imperative that you develop your own custom exception classes, it is often a good idea to do so; particularly when dealing with large systems. The main reason to create custom exception classes is to quickly identify which custom class is being affected or causing these custom exceptions. For example, referencing a null object and trying to use it will result in a NullPointerException. However, in your code, there could be literally hundreds or even thousands of statements which could result in such exception.

Designing your Custom Exception Class

Suppose that you are designing a class that will serve as a message receiver. For simplicity, let’s call it MessageReceiver.java. This message receiver class is designed to receive a message and process it. The UML below illustrates this basic concept. This is the basic design pattern that you should follow to implement custom exception classes.
  1. Create a Custom Exception class named some custom class ONLY IF THE JAVA PROVIDED CLASSES ARE NOT EXPLICIT ENOUGH FOR YOUR CASE. No sense in reinventing the wheel. Only create those classes that you need.
  2. Your custom class must extend the Java Exception class; which is the parent class of all exception classes.
  3. Add at least four constructors as illustrated in the UML below. No additional methods are needed.



Suppose that the received message (which is an object) is null. Attempting to access this null object could result in some NullPointerException that is too general to effectively troubleshoot. However, you can design your custom exception class to add enough detail to this exception so that exception messages are more helpful. Just the fact that you will see on the program’s stack trace that a MessageReceiverException is being thrown is more helpful than the general NullPointerException in this case. You can further expand the user-friendliness of your custom exception class by generating your own set of messages.


package demo;

public interface IMessageReceiver
{
    public void onMessage(Message message) throws MessageReceiverException;
}



package demo;

public class Message
{
    public String extractMessage() {
        return "Hello!";
    }
}



public class MessageReceiver implements IMessageReceiver {

    public void onMessage(Message message) {
        processMessage(message);
    }

    private void processMessage(Message message) {
        System.out.println("Message: " + message.extractMessage());
    }

    public static void main(String[] args){
        Message message = null;
        IMessageReceiver receiver = new MessageReceiver();
        receiver.onMessage(message);
    }
}
The stack trace if this program is executed will look something like this:
run:
Exception in thread "main" java.lang.NullPointerException
at demo.MessageReceiver.processMessage(MessageReceiver.java:19)
at demo.MessageReceiver.onMessage(MessageReceiver.java:15)
at demo.MessageReceiver.main(MessageReceiver.java:25)
Java Result: 1


By looking at the main method used to test this class, you can quickly see how this execution will result in a NullPointerException that is not handled by the message receiver. This exception is not very informative. In a large system it would be very hard to find the root cause of such generic exception messages. If we create a custom Message Receiver exception class following the procedure already outlined in this document, we could make the following modifications to the code to make it more specific:


public class MessageReceiver2 implements IMessageReceiver {

    public void onMessage(Message message) throws MessageReceiverException{
        try {
            processMessage(message);
        } catch (MessageReceiverException ex) {
            System.out.println("ERROR: " + ex.getClass() + " caught.");
            System.out.println("CAUSE: " + ex.getMessage());
        }
    }

    private void processMessage(Message message) throws MessageReceiverException {
        try {
            System.out.println("Message: " + message.extractMessage());
        } catch (NullPointerException e) {
            throw new MessageReceiverException("Message object was null");
        }
    }

    public static void main(String[] args){
        Message message = null;
        IMessageReceiver receiver = new MessageReceiver2();
        receiver.onMessage(message);
    }
}
The console output of this implementation is much more user friendly:


run:
ERROR: class demo.MessageReceiverException caught.
CAUSE: Message object was null

The key to making this user friendly is how the original exception is handled. In this case, the process message method attempted to extract the message contents from an empty object which resulted in a null pointer exception. Instead of replicating this same (non user-friendly) exception, it actually transformed this exception into our custom Message Receiver Exception with a custom message that stated that the object being manipulated was null. In turn, the on message method caught this exception and displayed a well-constructed, user-friendly message to the console that should simplify determining what went wrong during execution. These print statements could have been logged in a file as well or instead.

File CRUD Operations Example


package demo;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This is to demonstrate CRUD (Create, Read, Update, Delete) on a File object
 * @author Hector Fontanez
 */
public class FileManipulationDemo {

    public static File create (String path) throws IOException {
        FileWriter writer = null;
        File file = new File(path); //Created in memory
        try {
            writer = new FileWriter(file); //This is when the file is committed to file system
            writer.write("Welcome to Java!");
            writer.write("\nHello World!");
            writer.close(); //Closes the stream (FileWriter instance cannot be used anymore)
        } catch (IOException ex) {
            Logger.getLogger(FileManipulationDemo.class.getName()).log(Level.SEVERE, null, ex);
            throw ex;
        }
        return file;
    }

    public static void read(File file) throws IOException {
        String line = null;
        FileReader fr = new FileReader(file);
        BufferedReader br = new BufferedReader(fr);

        while((line = br.readLine()) != null) {
            System.out.println(line);
        }
        //Reclaim resources whenever you can
        fr.close();
        br.close();
    }

    public static void update(File file) throws IOException {
        FileWriter writer = null;
        try {
            writer = new FileWriter(file,true); //The second argument is the APPEND flag
            writer.write("\nThis should update the file");
            writer.flush();//Contents is saved but stream is not closed
            writer.close();//Contents is saved AND stream is closed
        } catch (IOException ex) {
            Logger.getLogger(FileManipulationDemo.class.getName()).log(Level.SEVERE, null, ex);
            throw ex;
        }
    }

    public static void delete(File file) {
        file.delete();
    }

    public static void main(String[] args) {

        try {
            File file = create("C:/Users/Hector/Desktop/myfile.txt");
            read(file);
            update(file);
            read(file); // Should show the update
            delete(file);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(FileManipulationDemo.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(FileManipulationDemo.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Conclusion

This example demonstrates basic file operations (create, read, update, and delete) and makes use of exception handling for efficient handling of unexpected situations. It also illustrates another good point about exception handling: When handling different types of exceptions, pay attention to the hierarchy. You can see that the portion of the code dealing with reading files could result in two types of exceptions: IOException or FileNotFoundException. These two exception are related in the sense that FileNotFoundException is a subclass of IOException. Therefore, it should be listed in the catch block before IOException. Otherwise, IOException will handle any FileNotFoundException that may occur and will make the FileNotFoundException block unreachable; resulting in a compile error.

There may be cases when it doesn't make sense to catch all of the exception of a particular type because the same action to be taken might apply to all cases. For instance, a FileNotFoundException might be handled the same way as the more generic IOException; like in this example. Since the same action is taking place, it is OK to remove the most specific FileNotFoundException and leaving the most general IOException to handle all cases. Keep in mind that this is not going to be the case all the time. There might be situations that might require a different type of action. For example, a file not existing is a completely different case than a file existing but it is locked by another user. It is very possible that the latter could be handled by invoking some method that would allow access to a copy of the original file for reading purposes. On the other hand, a file that doesn't exist could be handled by simply creating a blank file to replace it. So, in those cases, the bodies of these catch blocks would be totally different.

For the sake of rapid development, you may decide to handle everything generically at first and, as the system matures and you gain more knowledge of the system and the requirements get refined, you may decide to add more specific exception handling to your code. Obviously, the preferred solution is to add as much refinement as possible based on the existing requirements. If there is something my experience have taught me is that sometimes it is very difficult to add such refinements to the system once it is considered complete; especially if no bugs have been reported.

Lastly, and almost a direct contradiction to the point just made, you do not want to start by making everything to catch the Exception class. In my opinion, that is extremely poor programming (lazy programming) that could lead to more headaches than it is worth. Even in the initial stages of software design, you should have sufficient insight into what is being build to make better decisions with regards to exception handling.

Comments

Popular posts from this blog

Implementing Interfaces with Java Records

If you have not read my article on Java records and do not know about this topic, please read my blog titled " Customizing Java Records " first and then come back to this one. Now that you know how to customize Java records, implementing an interface using Java records should be very easy to understand. If you don't know about interfaces in Java, you can read more on my article about interfaces. The recipe for implementing an interface is simply an expansion of what you learned in my previous blog on how to customize a Java record. Following our Rectangle example, let's create an interface with the same two methods we used before. public interface Shape { double area(); double perimeter(); } Now, let's further customize the previous example by doing two things: Add implements Shape at the end of the record declaration (after the record constructor), and Add @Override to the existing methods to ensure these methods com...

Customizing Java Records

If you have not read my article on Java records and do not know about this topic, please read my blog titled " Java Keywords Addendum: The Java Record " first and then come back to this one. What is a customization of a record? A customization of a record is simply the addition of code inside the body of the class. Before proceeding further, let's recap important aspects of a Java Record: Java records are immutable Because of item 1 above, you cannot add new fields unless defined in the record constructor Java records already override: Object#equals(Object) and Object#hashCode() , and then override Object#toString() You could redefine overridden methods as part of your customization if you would like. For example, if you want a fancier implementation of the Object#toString() method, you could do so. Let's look at our first customization example. Using the example from my previous blog, public record Student(...

Object-Oriented Programming Basics: What is in a Class?

EDITORIAL NOTE : This article was published briefly back in 2016 and quickly set back to draft because I wasn't happy with its contents. It is a shame that it was taking me three years to revisit this topic and work on a new and improved version. At least, I'm hoping it will be to the liking you the reader. Keep in mind that the opening paragraph will still read as if I just wrote it for my (former) students at Texas Wesleyan. I started working on lecture on the topic of Object-Oriented (OO) Programming by gathering some material, old and new, when I realized this might be good and simple post for my second attempt at blogging. To be completely honest, in the 8 hours I spent collecting information and preparing material for this week's lecture, I realized I still made some of the mistakes I am about to blog about. I am actually hoping I can write a series of postings regarding Object-Oriented Programming (OOP). But to do so, I must start from the very beginning. ...