Reputation: 2281
I've encountered a situation that looked more or less like this while refactoring code; rewritten here for the sake of the example:
public class UrlProbe {
private final OkHttpClient http;
private final String url;
private final Function<Response, Object> transform;
private Object cachedValue;
public UrlProbe(OkHttpClient http, String url) {
this(http, url, this::debuggingStringTransform);
}
public UrlProbe(OkHttpClient http, String url, Function<Response, Object> transform) {
this.http = http;
this.url = url;
this.transform = transform;
}
private Object debuggingStringTransform(Response response) {
String newValue = response.body().toString();
System.out.println("Debugging new value from url " + url + ": " + newValue);
return newValue;
}
public synchronized Object probe() {
if (cachedValue != null) {
return cachedValue;
}
try (Response response = http.newCall(new Request.Builder().url(url).get().build()).execute()) {
cachedValue = transform.apply(response);
return cachedValue;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
This code will not compile because we cannot reference this before supertype constructor has been called
:
public UrlProbe(OkHttpClient http, String url) {
this(http, url, this::debuggingStringTransform);
}
The following will not compile either:
public UrlProbe(OkHttpClient http, String url) {
this(http, url, response -> debuggingStringTransform(response));
}
The only way I've found around this is to explicitly use a non-chaining constructor:
public UrlProbe(OkHttpClient http, String url) {
this.http = http;
this.url = url;
this.transform = this::debuggingStringTransform;
}
While it makes sense to restrict the use of this
in constructors chaining arguments, I find it surprising in this particular context as there doesn't appear to be any kind of evaluation of the object being constructed caused by the use of this
when it comes to method references and the contents of a lambda expression.
Is there a rationale behind this restriction other than it is because JLS §8.8.7.1 says so?
Upvotes: 2
Views: 224
Reputation: 10628
Allowing referencing this scope that early would break code that looks like this
public class UrlProbe {
final String url;
final String param2;
public UrlProbe(String url) {
this(url, this::debuggingStringTransform);
}
public UrlProbe(String url, Function<String, String> transform) {
this(url, transform.apply("")); // <-- What should happen when url is referenced here?
}
public UrlProbe(String url, String param2) {
this.url = url;
this.param2 = param2;
}
private String debuggingStringTransform(String response) {
System.out.println("Debugging new value from url " + url + ": " + response);
return response;
}
}
That's at least one way of violating the rules.
Upvotes: 2
Reputation: 106450
IntelliJ shows this on the tooltip for why this code is "bad":
Cannot reference 'this' before supertype constructor has been called
This makes sense. You're in the middle of constructing your object and the method reference as defined only exists after the class is instantiated. It can't realistically exist before it's fully instantiated, since from a semantic standpoint, you can't actually do anything with it until it's "ready".
If you wanted to get around this, you could change that function to a static one, since there's no state required for it:
public UrlProbe(OkHttpClient http, String url) {
this(http, url, UrlProbe::debuggingStringTransform);
}
private static Object debuggingStringTransform(Response response) {
String newValue = response.body().toString();
System.out.println("Debugging new value from url " + url + ": " + newValue);
return newValue;
}
...although admittedly it looks weird to see a private static
method.
Alternatively, have this function exist elsewhere in the same package, like in a static class at the bottom of this one:
public UrlProbe(OkHttpClient http, String url) {
this(http, url, Functions::debuggingStringTransform);
}
static class Functions {
static Object debuggingStringTransform(Response response) {
String newValue = response.body().toString();
System.out.println("Debugging new value from url " + url + ": " + newValue);
return newValue;
}
}
Upvotes: 1