user2609605
user2609605

Reputation: 628

java final field may already be assigned: compiler bug?

I have the following class:

public class FileId {

    final long length;
    final String hash;

    FileId(File file) {
        assert !file.isDirectory();
        this.length = file.length();
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] dataBytes = new byte[1024];

                int nread = 0;
                while ((nread = fis.read(dataBytes)) != -1) {
                    md.update(dataBytes, 0, nread);
                }
                this.hash = md.toString();
            } catch (IOException e) {
                // TBD: add warning 
                this.hash = "";
            }
        } catch (NoSuchAlgorithmException nsae) {
            // TBD: emit warning 
            this.hash = "";
        }
    }
}

The last two assignments of hash yield a compiler error

variable hash might already have been assigned

How can this be?
For example, if the first assignment is done, then no exceptions are thrown so the other assignments do not occur.

Where is my mistake?

Upvotes: 3

Views: 113

Answers (2)

Mehmet Atakan Serin
Mehmet Atakan Serin

Reputation: 13

I think you are right. But Java compiler is not that smart. I think design motivation is to avoid situations like this:

class FileId {

    final long length;
    final String hash;

    FileId(File file) {
        assert !file.isDirectory();
        this.length = file.length();
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] dataBytes = new byte[1024];

                int nread = 0;
                while ((nread = fis.read(dataBytes)) != -1) {
                    md.update(dataBytes, 0, nread);
                }
                this.hash = md.toString();
                throw new NoSuchAlgorithmException("I will make hash to be assigned twice");
            } catch (IOException e) {
                // TBD: add warning
                this.hash = "";
            }
        } catch (NoSuchAlgorithmException nsae) {
            // TBD: emit warning
            this.hash = "";
        }
    }
}

Upvotes: 0

shmosel
shmosel

Reputation: 50756

You're not wrong, but you're applying a deeper analysis than the compiler does. try and catch are not exclusive blocks by their nature, so the compiler wants to confirm that there was no assignment at all in the try. In the words of the JLS:

  • V is definitely unassigned before a catch block iff all of the following are true:
    • V is definitely unassigned after the try block.

Since you have an assignment in the try block, your variable does not meet the requirement that final fields be definitely unassigned before assignment.

Practically speaking, there's a simple workaround. Create a non-final local variable and assign it to your field at the end of the constructor. You can even default it to the fallback so there's no need to overwrite it in the catch block.

FileId(File file) {
    String hash = "";
    try {
        ...
        hash = md.toString();
    } catch (...) {
        // warning
    }
    this.hash = hash;
}

Upvotes: 13

Related Questions