tags:

views:

142

answers:

8
class Foo {
    final String s;

    final int x = s.length();

    Foo(String s) {
        this.s = s;
    }
}

The above code will not compile, because the assignment to x happens before the assignment to s. I could place the assignment to x inside the constructor, but for readability in a real case with 100 fields, I'd rather not.

I have come up with a workaround, which I dislike. Can you find a better one?

abstract class Hack {
    final String s;

    Hack(String s) {
        this.s = s;
    }
}

class Foo extends Hack {
    final int x = s.length();

    Foo(String s) {
        super(s);
    }
}
+4  A: 

As it is for readability:

class Foo {    
  final String s;    
  final int sizeOfS;
  Foo(String s) {        
    this.s = s;    
    sizeOfS = s.length();
  }
}

Readability comes in the first place from appropriate variable naming!

NomeN
And besides that from not having 100 fields! Sounds like a severe violation of the Single Responsibility Principle or a too low level of abstraction.
jens
I cannot go back in time and prevent this class from being written.
Ricky Clarkson
A: 

Other than comment:

interface Foo {
}

public static Foo newFoo(final String _s) {
    return new Foo() {
        private final String s = _s;
        private final int x = _s.length();
   };
}
Tom Hawtin - tackline
+1  A: 

I think you can not. As Java basic tutorial stated, if you have some more complex logic than constants for initalization then you have to write the code into the constructor (or static initialization block).

PeterMmm
I showed that I can, via my workaround. I was not asking whether what I can already do is possible, but whether there is a better way.
Ricky Clarkson
A: 

I would have a setS(String s) method that correctly sets the x int. Use that is your constructor. If you don't want s or x to be publicly settable, make the setS method private/protected/default.

class Foo {
    int x;
    String s;
    private setS(String s) { this.s=s;this.x=s.length();}

    Foo(String s) {
            setS(s);
    }
}

Another solution (which seems to more closely match what you are trying to do) is to eliminate the x int. Provide:

int getX() { return s.length(); }

instead of an x member.

If x is really a surrogate for a far more complex Object try:

class Foo {
    private Object complex;
    private String s;
    Foo(String s){ this.s = s;}
    Object getComplex() {
        if (complex == null) {
            *complex=new Complex(s);
        }
        return complex;
    }
}

*May need to add synchronized or double checked locking here for thread safety.

KitsuneYMG
You might want to reread the question...
NomeN
I can't then make the fields final.
Ricky Clarkson
Your lazy initialisation is exactly what I am trying to get rid of.
Ricky Clarkson
+1  A: 

Consider the builder pattern:

 public interface Foo {}

 public class FooBuilder  {

       private final String s;

       public FooBuilder(String s) {
           this.s = s;
       }

       public Foo build() {
           return new FooImpl();
       }

       private class FooImpl implements Foo {
           final int x = FooBuilder.this.s.length();
       }
  }

There are probably a couple of not-so-nice twists here, in that the implementation has to move to an inner class. Although the interface could be an abstract class instead and accomplish the same purpose.

Yishai
A: 

Since in your original example x is functionally dependent on s does it make sense to make a separate class that aggregates the two? Then the parent has only one assignment. You still need an assignment to s and computing the other value in <init> but that seems to be much more readable when there are not 100 fields.

Hemal Pandya
A: 

Note that your example will always fail: It tries to get the length of s before it is initialized, causing a NullPointerException.

What's wrong with just doing this?

final String s;
final int x;

Foo(String s) {
  this.s = s;
  this.x = s.length();
}
Jorn
My first example fails to compile. Without final it would compile and you would be correct. My workaround works.
Ricky Clarkson
I already expressed that I don't want to write a 100-line constructor if I can help it.
Ricky Clarkson
I think you're trying to fix a class that's defective by design (or lack thereof). Consider refactoring it (I know, it's not a option) or starting from scratch. Or just live with your defective class.
Jorn
I am refactoring it. Perhaps I should ask another question on how to refactor that particular class.
Ricky Clarkson
A: 

Since x always depend on s you have 3 options.

1) initializing x is a side-effect of initializing s

class Foo {
    final String s;
    final int x;

    private void setS(String s) {
        this.s = s;
        this.x = s.length();
    }

    Foo(String s) {
        this.setS(s);
    }
}

2) calculate x from s

class Foo {
    final String s;

    private int getX() {
        return s.length();
    }

    Foo(String s) {
        this.setS(s);
    }
}

2a) calculate x from s but cache x

class Foo {
    final String s;
    private int cached_x = null;

    int getX() {
        if (x == null) {
            cached_x = s.lenth();
        return cached_x;
    }

    Foo(String s) {
        this.s = s;
    }
}

If these options are to complex, it is perfectly OK to initialize your variables inside the constructor. That's its purpose anyway:

class Foo {
    final String s;
    final int x;

    Foo(String s) {
        this.s = s;
        this.x = this.s.length();
    }
}
wierob