Reputation: 628
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
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
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