Ar trebui să folosesc blocuri inițializatoare în Java? (Inginerie software, Java)

dirkk a intrebat.

Am dat recent peste o construcție Java pe care nu am mai văzut-o niciodată și mă întrebam dacă ar trebui să o folosesc. Se pare că se numește blocuri inițializatoare.

public class Test {
  public Test() { /* first constructor */ }
  public Test(String s) { /* second constructor */ }

  // Non-static initializer block - copied into every constructor:
  {
    doStuff();
  }
}

Blocul de cod va fi copiat în fiecare constructor, adică dacă aveți mai mulți constructori nu trebuie să rescrieți codul.

Cu toate acestea, văd trei dezavantaje principale în utilizarea acestei sintaxe:

  1. Este unul dintre foarte puținele cazuri din Java în care ordinea codului este importantă, deoarece puteți defini mai multe blocuri de cod și acestea vor fi executate în ordinea în care sunt scrise. Acest lucru mi se pare dăunător, deoarece simpla schimbare a ordinii blocurilor de cod va schimba de fapt codul.
  2. Nu văd cu adevărat niciun beneficiu în utilizarea acestei metode. În cele mai multe cazuri, constructorii se vor apela unul pe celălalt cu anumite valori predefinite. Chiar dacă nu este cazul, codul ar putea fi pur și simplu introdus într-o metodă privată și apelat de fiecare constructor.
  3. Acest lucru reduce lizibilitatea, deoarece ați putea pune blocul la sfârșitul clasei, iar constructorul este în mod normal la începutul clasei. Este destul de contra-intuitiv să te uiți la o parte complet diferită a unui fișier de cod dacă nu te aștepți ca acest lucru să fie necesar.

Dacă afirmațiile mele de mai sus sunt adevărate, de ce (și când) a fost introdusă această construcție lingvistică? Există cazuri de utilizare legitime?

Comentarii

  • Exemplul pe care l-ați postat nu include nimic care să semene cu un bloc inițializator. –  > Por Simon B.
  • @SimonBarker, uitați-vă din nou – { doStuff(); } de la nivelul clasei este un bloc inițializator. –  > Por amon.
  • @SimonBarker Blocul de cod care înconjoară doStuff() –  > Por dirkk.
  • ayp-sd.blogspot.de/2012/12/12/… –  > Por gnat.
  • „[S]ă nu cumva schimbarea ordinii blocurilor de cod va schimba de fapt codul.” Și cu ce se deosebește asta de schimbarea ordinii inițializatorilor de variabile sau a liniilor individuale de cod? Dacă nu există dependențe, atunci nu se produce niciun rău, iar dacă există dependențe, atunci plasarea acestora în altă ordine este la fel ca și ordonarea greșită a dependențelor pentru liniile individuale de cod. Doar pentru că Java vă permite să vă referiți la metode și clase înainte ca acestea să fie definite, nu înseamnă că codul dependent de ordine este rar în Java. –  > Por JAB.
5 răspunsuri
barjak

Există două cazuri în care folosesc blocuri de inițializator.

Primul este pentru inițializarea membrilor finali. În Java, puteți inițializa un membru final fie inline cu declarația, fie îl puteți inițializa în constructor. Într-o metodă, este interzisă atribuirea la un membru final.

Acest lucru este valabil:

final int val = 2;

Și acest lucru este valabil:

final int val;

MyClass() {
    val = 2;
}

Acesta nu este valabil:

final int val;

MyClass() {
    init();
}

void init() {
    val = 2;  // cannot assign to 'final' field in a method
}

Dacă aveți mai mulți constructori și dacă nu puteți inițializa un membru final inline (deoarece logica de inițializare este prea complexă) sau dacă constructorii nu se pot apela singuri, atunci puteți fie să copiați/lipiți codul de inițializare, fie să utilizați un bloc de inițializare.

final int val;
final int squareVal;

MyClass(int v, String s) {
    this.val = v;
    this.s = s;
}

MyClass(Point p, long id) {
    this.val = p.x;
    this.id = id;
}

{
    squareVal = val * val;
}

Celălalt caz de utilizare pe care îl am pentru blocurile de inițializare este pentru a construi mici structuri de date ajutătoare. Declar un membru și introduc valori în el imediat după declarațiile sale în propriul bloc inițializator.

private Map<String, String> days = new HashMap<String, String>();
{
    days.put("mon", "monday");
    days.put("tue", "tuesday");
    days.put("wed", "wednesday");
    days.put("thu", "thursday");
    days.put("fri", "friday");
    days.put("sat", "saturday");
    days.put("sun", "sunday");
}

Comentarii

  • Nu apelul la metodă este invalid. Este codul din interiorul metodei init care este invalid. Numai constructorii și blocurile de inițializare pot atribui o variabilă membră finală, astfel încât atribuirea din init nu va fi compilată. –  > Por barjak.
  • Al patrulea bloc de cod nu se compilează. Blocurile de initalizator rulează înainte de toți constructorii, prin urmare squareVal = val * val se va plânge de accesarea valorilor neinițializate. Blocurile de inițializare nu pot depinde de niciunul dintre argumentele transmise constructorului. Soluția obișnuită pe care am văzut-o pentru acest tip de problemă este de a defini un singur constructor „de bază” cu o logică complexă și de a defini toți ceilalți constructori în funcție de acesta. Majoritatea utilizărilor inițializatorilor de instanță, de fapt, pot fi înlocuite cu acest model. –  > Por Malnormalulo.
GlenPeterson

În general, nu folosiți blocuri de inițializatori non-statici (și poate evitați-le și pe cele statice).

Sintaxa confuză

Dacă ne uităm la această întrebare, există 3 răspunsuri, totuși ați păcălit 4 persoane cu această sintaxă. Eu am fost unul dintre ei și scriu Java de 16 ani! În mod clar, sintaxa este potențial generatoare de erori! Eu aș sta departe de ea.

Constructori telescopici

Pentru lucruri foarte simple, puteți folosi constructori „telescopici” pentru a evita această confuzie:

public class Test {
    private String something;

    // Default constructor does some things
    public Test() { doStuff(); }

    // Other constructors call the default constructor
    public Test(String s) {
        this(); // Call default constructor
        something = s;
    }
}

Modelul constructorului

Dacă aveți nevoie de doStuff() la sfârșitul fiecărui constructor sau de o altă inițializare sofisticată, poate că un model de constructor ar fi cel mai bun. Josh Bloch enumeră mai multe motive pentru care constructorii sunt o idee bună. Constructorii necesită puțin timp pentru a fi scriși, dar, dacă sunt scriși corect, sunt o plăcere să fie utilizați.

public class Test {
    // Value can be final (immutable)
    private final String something;

    // Private constructor.
    private Test(String s) { something = s; }

    // Static method to get a builder
    public static Builder builder() { return new Builder(); }

    // builder class accumulates values until a valid Test object can be created. 
    private static class Builder {
        private String tempSomething;
        public Builder something(String s) {
            tempSomething = s;
            return this;
        }
        // This is our factory method for a Test class.
        public Test build() {
            Test t = new Test(tempSomething);
            // Here we do your extra initialization after the
            // Test class has been created.
            doStuff();
            // Return a valid, potentially immutable Test object.
            return t;
        }
    }
}

// Now you can call:
Test t = Test.builder()
             .setString("Utini!")
             .build();

Bucle de inițializare statică

Obișnuiam să folosesc static dar, ocazional, am întâlnit bucle în care două clase depindeau de apelarea blocurilor de inițializare statică ale fiecăreia dintre ele înainte ca clasa să fie încărcată complet. Acest lucru producea un mesaj de eroare „failed to load class” sau un mesaj de eroare la fel de vag. A trebuit să compar fișierele cu ultima versiune de lucru cunoscută în controlul sursei pentru a afla care era problema. Deloc amuzant.

Inițializare leneșă

Poate că inițializatoarele statice sunt bune din motive de performanță atunci când funcționează și nu sunt prea confuze. Dar, în general, prefer inițializarea leneșă în locul inițializatorilor statici în aceste zile. Este clar ce fac, nu am dat încă peste un bug de încărcare a clasei cu ele și funcționează în mai multe situații de inițializare decât blocurile de inițializare.

Definiția datelor

În loc de inițializare statică pentru construirea structurilor de date, (comparați cu exemplele din celelalte răspunsuri), acum folosesc funcțiile ajutătoare de definire a datelor imuabile ale Paguro:

private ImMap<String,String> days =
        map(tup("mon", "monday"),
            tup("tue", "tuesday"),
            tup("wed", "wednesday"),
            tup("thu", "thursday"),
            tup("fri", "friday"),
            tup("sat", "saturday"),
            tup("sun", "sunday"));

Conculsion

La începutul Java, blocurile de inițializare erau singura modalitate de a face anumite lucruri, dar acum sunt confuze, predispuse la erori și, în majoritatea cazurilor, au fost înlocuite cu alternative mai bune (detaliate mai sus). Este interesant să știți despre blocurile inițializatoare în cazul în care le vedeți în codul vechi sau dacă apar la un test, dar dacă aș face o revizuire a codului și aș vedea unul în codul nou, v-aș cere să justificați de ce niciuna dintre alternativele de mai sus nu este potrivită înainte de a vă aproba codul.

C.Șampanie

În plus față de inițializarea unei variabile de instanță care este declarată ca fiind final (a se vedea răspunsul lui barjak), aș menționa și static bloc de inițializare.

Le puteți folosi ca un fel de „contructor static”.

În acest fel puteți face inițializări complexe pe o variabilă statică a prima dată când clasa este menționată.

Iată un exemplu inspirat de cel al lui barjak:

public class dayHelper(){
    private static Map<String, String> days = new HashMap<String, String>();
    static {
        days.put("mon", "monday");
        days.put("tue", "tuesday");
        days.put("wed", "wednesday");
        days.put("thu", "thursday");
        days.put("fri", "friday");
        days.put("sat", "saturday");
        days.put("sun", "sunday");
    }
    public static String getLongName(String shortName){
         return days.get(shortName);
    }
}

Nico

În ceea ce privește blocurile inițializatoare non-statice, funcția lor simplă este de a acționa ca un constructor implicit în clasele anonime. Acesta este practic singurul lor drept de a exista.

A fost reperat

Sunt total de acord cu afirmațiile 1, 2, 3. Nici eu nu folosesc niciodată blocuri inițializatoare din aceste motive și nu știu de ce există în Java.

Cu toate acestea, sunt nevoit să folosesc static block initializer într-un singur caz: când trebuie să instanțez un câmp static al cărui constructor poate arunca o excepție verificată.

private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile

Dar, în schimb, trebuie să faceți:

private static JAXBContext context;
static {
    try
    {
        context = JAXBContext.newInstance(Foo.class);
    }
    catch (JAXBException e)
    {
        //seriously...
    }
}

Acest idiom mi se pare foarte urât (de asemenea, te împiedică să marchezi context ca final), dar aceasta este singura modalitate suportată de Java pentru a inițializa astfel de câmpuri.

Comentarii

  • Cred că dacă setați context = null; în blocul catch, s-ar putea să puteți declara contextul ca fiind final. –  > Por GlenPeterson.
  • @GlenPeterson Am încercat, dar nu se compilează: The final field context may already have been assigned –  > Por Spotted.
  • oops! Pun pariu că puteți face contextul final dacă introduceți o variabilă locală în interiorul blocului static: static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; } –  > Por GlenPeterson.

Tags: