2017-09-27 1 views
2

Ich habe eine Situation festgestellt, die beim Refactoring-Code mehr oder weniger so aussah; hier aus Gründen des Beispiels neu geschrieben:Java-Konstruktor-Verkettung mit Methodenreferenz

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); 
     } 
    } 
} 

Dieser Code wird nicht kompiliert, weil wir cannot reference this before supertype constructor has been called:

public UrlProbe(OkHttpClient http, String url) { 
    this(http, url, this::debuggingStringTransform); 
} 

Im Folgenden wird auch nicht kompilieren:

public UrlProbe(OkHttpClient http, String url) { 
    this(http, url, response -> debuggingStringTransform(response)); 
} 

Der einzige Weg, ich Habe herausgefunden, dass dies explizit einen nicht verketteten Konstruktor verwendet:

public UrlProbe(OkHttpClient http, String url) { 
    this.http = http; 
    this.url = url; 
    this.transform = this::debuggingStringTransform; 
} 

Während es sinnvoll ist, die Verwendung von this in Konstruktoren Verkettung von Argumenten zu beschränken, finde ich es in diesem speziellen Kontext überraschend, da es keine Art von Bewertung des Objekts zu sein scheint, verursacht durch die Verwendung von this wenn es um Methodenreferenzen und den Inhalt eines Lambda-Ausdrucks geht.

Gibt es eine andere Begründung als JLS §8.8.7.1?

+0

Sie können auch Methode 'debuggingStringTransform' statisch machen und statische Methodenreferenz verwenden –

+0

@VladBochenin Die Situation, die ich bei der Arbeit hatte, war ein wenig komplexer als das, aber in meinem Beispiel' debuggingStringTransform' habe ich darauf geachtet, eine Referenz aufzunehmen zu dem Instanzfeld "url", so dass es nicht statisch sein kann – Hay

+0

Es ist, weil Sie nicht garantieren können, dass debugginStringTransform nicht aufgerufen wird, bevor dieser oder super aufgerufen wird. Was passiert, wenn der nächste Konstruktor die Funktion aufruft, bevor Super ausgeführt wird? – jontro

Antwort

2

erlauben diesen Bereich verweist, die früh Code brechen würde, der so aussieht

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; 
    } 
} 

Das ist mindestens eine Möglichkeit, die Regeln zu verletzen.

+0

Das ist interessant, es ist mir absolut nicht in den Sinn gekommen, dass der aufgerufene Konstruktor die Funktion aufrufen könnte, die als Parameter übergeben wurde (und sie möglicherweise an einen anderen Konstruktor kettet). – Hay

1

IntelliJ zeigt dies auf dem Tooltip dafür, warum dieser Code ist "schlecht":

enter image description here

Kann nicht Referenz 'this' vor Supertyp Konstruktor hat

Dies macht Sinn genannt . Sie sind dabei, Ihr Objekt zu konstruieren, und die Methodenreferenz wie definiert existiert nur nach Die Klasse wird instanziiert. Es kann nicht realistisch existieren vor es ist vollständig instanziiert, da von einem semantischen Standpunkt, Sie können eigentlich nichts damit zu tun, bis es "bereit" ist.

Wenn Sie dies umgehen wollte, Sie konnte Änderung, die in eine statische Funktion, da kein Staat dafür erforderlich gibt es:

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; 
} 

... obwohl zugegebenermaßen sieht es seltsam zu siehe eine private static Methode.

Alternativ haben diese Funktion anderswo im selben Paket vorhanden sind, wie in einer statischen Klasse am unteren Ende dieses:

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; 
    } 
} 
Verwandte Themen