5

For everyone who is talking about the fact that the object is in an "unitialized state", please refer to the answer to this question which shows that an object reference can be passed around, dereferenced, have methods invoked from it, and have fields accessed before a constructor terminates and all fields have been assigned (including final fields).

So here's the use case:

public class Entity {

    private final String name;

    public Entity() {
        this(toString()); //Nope, Chuck Testa
    }

    public Entity(String name) {
        this.name = name;
    }
}

The compiler error is:

Cannot refer to an instance method while explicitly invoking a constructor.

Note that toString() has not been overriden and is the default call from Object.

I'm certainly interested in the philosophical/technical reasons behind this, so if anyone can explain that, that would be an awesome bonus. But I'm looking for a way to call toString() from that default constructor as it refers down to the more specific one with more arguments. The actual use case is a bit more complicated and ends up referring all the way down to a constructor with four arguments, but that shouldn't really matter.

I know I could do something like this...

private static final String TO_STRING_CONSTRUCTOR_ARGUMENT = "aflhsdlkfjlkswf";

public Entity() {
    this(TO_STRING_CONSTRUCTOR_ARGUMENT);
}

public Entity(String name) {
    this.name = name == TO_STRING_CONSTRUCTOR_ARGUMENT ? toString() : name;
}

... but it seems like a pretty inelegant solution.

So, any way to pull it off? Or any recommended best practices to deal with this situation?

Community
  • 1
  • 1
asteri
  • 11,402
  • 13
  • 60
  • 84
  • 1
    you want to get information about the object before the object is created? – Ross Drew Feb 03 '14 at 12:35
  • 3
    And what does your `toString` look like? If it refers to any instance variables, they are uninitialized at that point. If not, then you have an effectively static method, so make one and call it from both `toString` and `this()`. – Marko Topolnik Feb 03 '14 at 12:35
  • @MarkoTopolnik IT's the default `toString()` of `Object`. – asteri Feb 03 '14 at 12:36
  • @RossDrew Actually, the space for the object is already allocated regardless of where the constructor is in its run, so the `Entity@hexaddress` should already be populated. – asteri Feb 03 '14 at 12:37
  • 3
    It's not hex address... it's the result of invoking `hashCode()` in hex notation. As far as `Object#toString()` is concerned, it could be anything. – Marko Topolnik Feb 03 '14 at 12:38
  • @MarkoTopolnik Fair enough. Thanks for the clarification. – asteri Feb 03 '14 at 12:39
  • But anyhow, `toString` can be overridden in a subclass so it doesn't matter whether your particular class overrides it or not. If you expect the JLS to allow the exceptional case where you call `toString` from `this()`, and your class is final, and it doesn't override either `toString` or `hashCode`... it's an unreasonable expectation, to say the least. – Marko Topolnik Feb 03 '14 at 12:42
  • @MarkoTopolnik Yeah, good point. Although you'll see in [the answer to this question](http://stackoverflow.com/a/20619254/1435657) that access to `final` or other instance variables is possible before they're initialized anyhow. – asteri Feb 03 '14 at 12:44
  • Without looking at that link of yours, you are talking about leaking `this` from the constructor? It's a known loophole, stemming from the limited range of checks a static code analyzer can do, especially in the face of polymorphism. – Marko Topolnik Feb 03 '14 at 12:47

5 Answers5

2

I would prefer not to pass this around until the object is created. Instead I would do this:

public class Entity {

    private final String name;

    public Entity() {
        this(null); // or whatever
    }

    public Entity(String name) {
        this.name = name;
    }

    public String getName() { 
        return name != null ? name : Objects.hashCode(this);
    }
}

If you can live without the final name, you can use an initializer block:

public class Entity {

    private String name;

    {name = this.toString();}

    public Entity() {
    }

    public Entity(String name) {
        this.name = name;
    }
}

this is only available after all calls to this() or super() are done. The initializer runs first after the constructors call to super() and is allowed to access this.

atamanroman
  • 11,607
  • 7
  • 57
  • 81
  • This also won't compile. You are attempting to assign a value to `name` after it has already been assigned. – asteri Feb 03 '14 at 12:54
  • 1
    Yes of course, didn't get that final there. Does not change why you can't call `this` when passing args to a super constructor. (`final` removed) – atamanroman Feb 03 '14 at 12:55
  • Removing the `final` sort of makes the problem trivial, since I can then assign the value anywhere I like, including after the constructors have returned. So this doesn't really help. Thank you, though. – asteri Feb 03 '14 at 13:03
  • BTW. This is strange, because uninitialized `this` variable escapes in such case. – Mikhail Feb 03 '14 at 13:06
  • @Mikhail Haha, that's what I've been trying to tell everyone. The reference exists regardless of whether a constructor has terminated (or even been run). Actually, his example shows that the `toString()` call is perfectly possible before a constructor executes, so it should be no problem. – asteri Feb 03 '14 at 13:10
  • @JeffGohlke no, field initializers are slotted in by the compiler between the chaining `super` call and the rest of the constructor body. The `super` still comes first. This is why when the superclass constructor calls a method that you override, your method sees its own class's instance fields with their default values, not the ones that the initializers would fill in. – Ian Roberts Feb 03 '14 at 13:13
  • @IanRoberts [Not according to the JLS](http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.9.4). Fields are initialized to their default values before any constructor runs. – asteri Feb 03 '14 at 13:15
  • @IanRoberts Ah, gotcha. I misunderstood what you meant by "initializers". Right, makes sense. But I don't see how that is an argument against what I said. The reference still exists, no? Just because his block hasn't run doesn't mean the reference and fields aren't there. So I don't get the relevance, I suppose. – asteri Feb 03 '14 at 13:17
  • I understand what you are trying to solve and that it is an interesting question to ask. My answer boils down to _don't use the super/this arguments this way because it is not allowed_. An elegant fix was if you create a property which does `return name != null ? name : this.toString();`. That's my anwer to *Or any recommended best practices to deal with this situation?* – atamanroman Feb 03 '14 at 13:31
1

As for the reasons why that is a compiler error, please see section 8.8.7 of the JLS. The reasons why this was made a compiler error are not clear, but consider that the constructor chain has to be the first thing executed when new'ing an Object and look at the order of evaluation here:

public Entity() {
        this(toString()); 
}

toString() is evaluated first before the even the super constructor is invoked. In general this leaves open all kinds of possibilities for uninitialized state.

As a personal preference, I would suggest that everything an object needs to have in order to create valid state should be available within its constructor. If you have no way of providing valid state in a default constructor without invoking other methods defined in the object hierarchy, then get rid of the default constructor and put the onus on the users of your class to supply a valid String to your other constructor.

If you are ultimately just trying invoke the other constructor with the value of toString(), then I would suggest the following instead:

    public Entity() {
        name = toString();
    }

which accomplishes the same goal you set out to achieve and properly initializes name.

whaley
  • 16,075
  • 10
  • 57
  • 68
  • Your example at the end would make perfect sense for the code above, but unfortunately the real-life code chains downward to a constructor of four arguments (least arguments chaining to most arguments). But you make good points in your post, thank you. – asteri Feb 03 '14 at 12:58
  • telescoping constructors tend to be more trouble than they are worth. chapter 2 of Effective Java shows a couple of different and better ways to handle this - static factory methods and builders. – whaley Feb 03 '14 at 17:19
1

As explained in the JLS this is not allowed before the instance is initialized.

However, there are ways to handle your scenario in a consistent manner.

As I see your case, you want to signify either a generated value (toString()) or a user provided value, which can be null.

Given this constraints, using TO_STRING_CONSTRUCTOR_ARGUMENT is failing for at least one specific use case, however obscure it may be.

Essentially you will need to replace the String with an Optional similar to what exists in Google Guava and will be included in Java 8, and seen in many other languages.

Having a StringOptional/StringHolder or whatever you choose, similar to this:

public class StringOptional {
  private String value;
  private boolean set = false;
  public StringOptional() {}
  public StringOptional(String value) {
    this.value = value;
    this.set = true;
  }

  public boolean isSet() { return set; }
  public String getValue() { return value; }
}

Then you can call constructors with the knowledge of the inferred path.

public class Entity {
   public Entity() { 
     this(New StringOptional());
   }

   public Entity(String s) {
     this(new StringOptional(s));
   }

   private Entity(StringOptional optional) {
     super(optional);
   }
}

And store this for subsquent need:

if (optional.isSet() ? optional.getValue() : toString();

This is how I usually would handle a maybe-null scenario, hope it augments as an answer.

asteri
  • 11,402
  • 13
  • 60
  • 84
Niels Bech Nielsen
  • 4,777
  • 1
  • 21
  • 44
  • I'm afraid I don't really understand your answer. Is it because it's dependent on Guava? I don't understand what the `super(optional)` call would invoke. There is no constructor in `Object` that I know of that accepts a `StringOptional` object... ? – asteri Feb 03 '14 at 13:21
  • The super call was just if you wanted to send it further around in the construction chain. The optional object is either set (with a value of null or string value) or not set. You will always be able to see which case invoked it by using the test. – Niels Bech Nielsen Feb 03 '14 at 13:23
  • And no, not dependent on Guava, they just have a similar generic Optional class. – Niels Bech Nielsen Feb 03 '14 at 13:25
  • Interesting proposition. It's at least worth considering. Thanks for taking the time to write it up! – asteri Feb 03 '14 at 13:29
0

You can use a static method factory in your class Entity, and put the constructor private:

public class Entity {

    private String name;

    private Entity() {
    }

    public Entity(String name) {
        this.name = name;
    }

    public static Entity createEntity() {
        Entity result = new Entity();
        result.name = result.toString();
        return result;
    }
}
Happy
  • 1,815
  • 2
  • 18
  • 33
  • 2
    This won't compile. You're attempting to assign a `final` field after the construction of the object. Plus the constructor doesn't assign any value to the field, which it must. – asteri Feb 03 '14 at 12:48
  • You are right I missed that. Well, make the field non final ;). – Happy Feb 03 '14 at 13:32
0

You cannot 'use' an instance that has not been created yet. By calling a second constructor you are postponing the creation, you cannot use it before the call or in the action of calling.

aalku
  • 2,860
  • 2
  • 23
  • 44
  • Nope. Space is allocated for the instance before any constructor is run. You can pass around a `this` reference to other methods within the constructor and do all sorts of things with it before your constructor terminates, including access instance variables. – asteri Feb 03 '14 at 12:45
  • 4
    @JeffGohlke yes, you can access methods and fields within a constructor, but _only_ after the chaining constructor call has returned (whether this is an explicit `this(...)` or `super(...)` or the implicit `super()` that is added by default if you don't chain explicitly). – Ian Roberts Feb 03 '14 at 12:46
  • @IanRoberts True. Nice point. So even if the *entire* construction hasn't completed, the chained one is at least guaranteed to. – asteri Feb 03 '14 at 12:47
  • @IanRoberts Thank you, you expressed it better than me. – aalku Feb 03 '14 at 12:47
  • 2
    @JeffGohlke yes. And this nicely explains why you can't pass `toString()` to `this(...)` - the `toString()` call would have to happen before the `this()` one. – Ian Roberts Feb 03 '14 at 12:48
  • @JeffGohlke I had a similar problem trying to dynamically choose what upper constructor to use. `if (x==0) { this("") } else { super() };` You cannot do that, you need to use a static factory method that chooses the constructor to use and every constructor may call a single chained constructor. – aalku Feb 03 '14 at 12:50
  • @IanRoberts I see what you're saying, and that makes sense as far as Java rules, but I don't quite understand *why* that's such a big problem. If I get to my second constructor and then invoke `toString()`, the result of the call is the same (assuming I *could* even call it from the first, less specific one). – asteri Feb 03 '14 at 12:52