21

I keep running into slight variations of a problem in Java and it's starting to get to me, and I can't really think of a proper way to get around it.

I have an object property that is final, but dynamic. That is, I want the value to be constant once assigned, but the value can be different each runtime. So I declare the class level variable at the beginning of the class - say private final FILE_NAME;. Then, in the constructor, I assign it a value - say FILE_NAME = buildFileName();

The problem begins when I have code in the buildFileName() method that throws an exception. So I try something like this in the constructor:

try{
   FILE_NAME = buildFileName();
}
catch(Exception e){
   ...
   System.exit(1);
}

Now I have an error - "The blank final field FILE_NAME may not have been initialized." This is where I start to get slightly annoyed at Java's strict compiler. I know that this won't be a problem because if it gets to the catch the program will exit... But the compiler doesn't know that and so doesn't allow this code. If I try to add a dummy assignment to the catch, I get - "The final field FILE_NAME may already have been assigned." I clearly can't assign a default value before the try-catch because I can only assign to it once.

Any ideas...?

froadie
  • 79,995
  • 75
  • 166
  • 235
  • You mean `private static final FILE_NAME;`? – Tom Hawtin - tackline May 05 '10 at 14:10
  • 2
    @Tom Hawtin - no. Why should it be static? – froadie May 05 '10 at 14:15
  • I agree with Ryan's answer (and voted up appropriately). One thing I'll say though is... do you really want to System.exit if you can't initialize? Perhaps the best idea is to just let the exception get thrown - either way if it's not handled you'll still exit, but if there is a handling mechanism in place, it can be taken care of properly. – corsiKa May 05 '10 at 14:29
  • 1
    @froadie: because `ALL_CAPS` indicates constants (a.k.a. `static final`): http://java.sun.com/docs/codeconv/html/CodeConventions.doc8.html#367 – Joachim Sauer Jun 16 '10 at 09:09
  • @Joachim Sauer - it is a constant, but it's an object-level constant... each object can have it's own file name, but once set it can't be changed. – froadie Jun 16 '10 at 19:11
  • 1
    @froadie: exactly. And at least the sun naming convention says that "variables declared class constants" should be named with `ALL_CAPS`. Other fields, even if `final`, should be `camelCase` with a lower case starting character. – Joachim Sauer Jun 17 '10 at 07:50

5 Answers5

20

How about

String tempName = null;
try{
   tempName = buildFileName();
}
catch(Exception e){
   ...
   System.exit(1);
}
FILE_NAME = tempName;
Ryan Elkins
  • 5,731
  • 13
  • 41
  • 67
  • thanks :) I've been facing this problem for a while, and this only just hit me... Should've been an automatic reaction to the problem as it seems to have been with you – froadie May 05 '10 at 13:58
  • Heh, yeah. I think you beat me to it by just a few seconds ;) – Ryan Elkins May 05 '10 at 13:59
7

Either

try {
   FILE_NAME = buildFileName();
} catch (Exception e){
   ...
   System.exit(1);
   throw new Error();
}

Or some prefer:

private static final String FILE_NAME = fileName();

private static String fileName() {
    try {
        return buildFileName();
    } catch (Exception e){
        ...
        System.exit(1);
        throw new Error();
    }
}

But calling System.exit in a static initialiser is probably a bad idea. It's going to mess your unit tests up.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • In the first example - why does throwing a new error make it pass the compiler? And why is it less ugly to add a line that will never be reached than it is to assign a temporary string a default value? – froadie May 05 '10 at 14:12
  • @froadie The details of the definite assignment rules are in the JLS. They are very tedious, but you should be able know what is going on by example. The compiler is required to detect that the variable is definitely assigned if an exception is not caught, and if an exception is caught the code leaves via an exception and is therefore not required to definitely assign the variable. (For `final` instance fields there are sneaky ways to look at the uninitialised variable before or after.) – Tom Hawtin - tackline May 05 '10 at 14:18
  • ok. but why is this a better method? the `throw new Error()` line will never be reached and is just there to placate the compiler. I don't see the advantage of this answer over the one I posted and Ryan Elkin's - let me know if you think there's something I'm missing – froadie May 05 '10 at 14:24
  • @froadie I would argue it is better because there is less noise (1 line vs 2) and the noise is restricted to the error handling code. – ILMTitan May 05 '10 at 15:00
4

On second thought, I think I just came up with a solution! - use an intermediate variable.

String fileName = null;
try{
   fileName = buildFileName();
}
catch(Exception e){
   ...
   System.exit(1);
}
FILE_NAME = fileName;

Don't know why it took me so long to think of this...

froadie
  • 79,995
  • 75
  • 166
  • 235
  • That's ugly - you don't want the `= ""`. – Tom Hawtin - tackline May 05 '10 at 14:05
  • @Tom Hawtin - true. It should probably be initialized to null just from an ideal design perspective. But practically, it could be initialized to just about anything, as it's either going to be immediately replaced or the program will crash. But I'll change it just to be correct – froadie May 05 '10 at 14:08
  • 1
    No, not `null` either. IT SHOULD NOT BE ASSIGNED. – Tom Hawtin - tackline May 05 '10 at 14:09
  • 1
    @Tom Hawtin: You have to initialize `fileName` to something. If you don't you would get `The local variable fileName may not have been initialized` compile error. `null` is definitely a good choice. – Alexander Pogrebnyak May 05 '10 at 14:17
  • 1
    @Alexander Pogrebnyak No you don't. Not if you definitely do not read the variable again. – Tom Hawtin - tackline May 05 '10 at 14:20
  • @Tom: You WILL get a compiler error if you just change it to 'String fileName;'. That variable has to be initialized to SOMETHING for this example to compile. It sounds like you're talking about a different solution rather than just not initializing the temp variable. – Ryan Elkins May 05 '10 at 14:51
  • 1
    @Tom Hawtin: Have to initialize variable to NULL for the same token your `fileName()` function has to `throw Error( )` after `System.exit( 1)`. For compiler `System.exit` is just another function and it needs a special terminator in the block. Granted, you could put `fileName = null;` after `System.exit` instead of initializing it at the declaration line. BTW, I do think that your solution is cleaner. – Alexander Pogrebnyak May 05 '10 at 15:16
1

I would personally just throw an Error -- if your error flow is properly designed, the System.exit() should be redundant. Your program presumably doesn't plough on into the wilderness if an Error is thrown...?

Neil Coffey
  • 21,615
  • 7
  • 62
  • 83
0

Along the same lines as the OP's issue, I had to be able to find a way to assign values to final fields to be read in from a .properties file on the filesystem, so the values couldn't be known by my app until that happened. Using a generalized method call to assign the value after reading the content of the .properties file into a Properties object on app startup was a Hail Mary pass that thankfully worked out. It also limits the no. of times the file has to be read to once per the app's getting loaded into the memory simply by the code checking to see if the Properties object is or is not currently null. But of course, once assigned, the final field's value cannot be altered except by altering its "final" status via manuipulating the field's modifying definition at runtime (as discussed in some other places here on SO, such as https://stackoverflow.com/a/3301720/1216686 - sneaky, but I love it!). Code example, with typical runtime error checking such as for NPEs omitted for brevity:

import java.util.Properties;

public class MyConstants {

  private static Properties props; // declared, not initialized,
                                   // so it can still be set to
                                   // an object reference.

  public static String MY_STRING = getProperty("prop1name", "defaultval1");
  public static int MY_INT = Integer.parseInt(getProperty("prop2name", "1"));
  // more fields...

  private static String getProperty(String name, String dflt) {
   if ( props == null ) {
     readProperties();
   }
   return props.getProperty(name, dflt);
  } 

  private static void readProperties() {
     props = new Properties(); // Use your fave way to read
                      // props from the file system; a permutation
                      // of Properties.load(...) worked for me.
  } 

  // Testing...
  public static void main(String[] args) {
      System.out.println(MY_STRING);
      System.out.println(MY_INT);
  }

}

This lets you externalize properties to be read into the app and still mark the fields used to hold their values as "final". It also allows you to guarantee a returned value for the final field value since getProperty() in the Properties class allows the method's calling code to pass in a default value to use in case the property's key-value pair wasn't found in the external .properties file.

Community
  • 1
  • 1
Matt Campbell
  • 1,967
  • 1
  • 22
  • 34