Java'da başlatıcı blokları kullanmalı mıyım?


16

Son zamanlarda daha önce hiç görmediğim bir Java yapısına rastladım ve kullanmam gerekip gerekmediğini merak ediyordum. Başlatıcı blokları olarak adlandırılıyor gibi görünüyor .

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

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

Kod bloğu her kurucuya kopyalanır, yani birden fazla kurucunuz varsa kodu yeniden yazmanız gerekmez.

Ancak, bu sözdizimini kullanarak üç ana dezavantaj görüyorum:

  1. Birden fazla kod bloğu tanımlayabileceğiniz ve yazıldıkları sırayla yürütülecekleri için, Java'da kodunuzun sırasının önemli olduğu çok az durumdan biridir. Kod bloklarının sırasını değiştirmek aslında kodu değiştireceğinden bu bana zarar veriyor.
  2. Bunu kullanarak hiçbir fayda görmüyorum. Çoğu durumda, yapıcılar birbirlerini önceden tanımlanmış bazı değerlerle çağırırlar. Durum böyle olmasa bile, kod sadece özel bir yönteme konabilir ve her kurucudan çağrılabilir.
  3. Bloğu sınıfın sonuna koyabileceğiniz ve yapıcı normalde sınıfın başında olduğu için okunabilirliği azaltır. Gerekli olmasını beklemiyorsanız, bir kod dosyasının tamamen farklı bir bölümüne bakmak oldukça sezgiseldir.

Yukarıdaki ifadelerim doğruysa, bu dil yapısı neden (ve ne zaman) tanıtıldı? Herhangi bir meşru kullanım durumu var mı?


3
Gönderdiğiniz örnek, bir başlatıcı bloğuna benzeyen hiçbir şey içermiyor.
Simon B

6
@SimonBarker tekrar bakın - { doStuff(); }sınıf düzeyinde bir başlatıcı bloğu.
amon

@SimonBarker Etraftaki kod bloğudoStuff()
Monica'yı eski durumuna döndür - dirkk


2
"[S] kod bloklarının sırasını değiştirmek aslında kodu değiştireceğini ima eder." Değişken başlatıcıların veya tek tek kod satırlarının sırasını değiştirmekten ne farkı var? Bağımlılık yoksa, hiçbir zarar oluşmaz ve bağımlılıklar varsa, bağımlılıkları sıra dışı bırakmak, tek tek kod satırları için kötüye kullanılan bağımlılıklarla aynıdır. Java'nın yöntemlere ve sınıflara tanımlanmadan önce başvurmanıza izin vermesi, Java'da sıraya bağlı kodun nadir olduğu anlamına gelmez.
JAB

Yanıtlar:


20

Başlatıcı bloklarını kullandığım iki durum var.

Birincisi final üyelerini başlatmak içindir. Java'da, son üyeyi bildirimle satır içi olarak başlatabilir veya kurucuda başlatabilirsiniz. Bir yöntemde, nihai bir üyeye atanması yasaktır.

Bu geçerlidir:

final int val = 2;

Bu da geçerlidir:

final int val;

MyClass() {
    val = 2;
}

Bu geçersiz:

final int val;

MyClass() {
    init();
}

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

Birden fazla kurucunuz varsa ve bir son üye satır içi başlatamazsanız (başlatma mantığı çok karmaşık olduğu için) veya yapıcılar kendilerini çağıramazsa, başlatma kodunu kopyalayabilir / yapıştırabilirsiniz veya kullanabilirsiniz bir başlatıcı bloğu.

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

Başlatıcı blokları için sahip olduğum diğer kullanım durumu küçük yardımcı veri yapıları oluşturmak içindir. Bir üye beyan ederim ve kendi başlatıcı bloğundaki açıklamalarından hemen sonra değerleri koyarım.

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

Geçersiz olan yöntem çağrısı değil. İnit yönteminin içindeki kod geçersizdir. Yalnızca yapıcılar ve initalizer blokları bir son üye değişkenine atayabilir, bu nedenle init içindeki atama derlenmez.
barjak

Dördüncü kod bloğunuz derlenmiyor. Initalizer blokları tüm kuruculardan önce çalışır , bu nedenle squareVal = val * valbaşlatılmamış değerlere erişmekten şikayet eder. Başlatıcı blokları, yapıcıya iletilen argümanlara bağlı olamaz. Bu tür bir problem için gördüğüm genel çözüm, karmaşık mantıkla tek bir "temel" kurucu tanımlamak ve bu bağlamda diğer tüm kurucuları tanımlamaktır. Aslında örnek başlatıcıların çoğu kullanımı bu kalıpla değiştirilebilir.
Malnormalulo

11

Genel olarak, statik olmayan başlatıcı blokları kullanmayın (ve statik olanlardan da kaçının).

Kafa karıştırıcı Sözdizimi

Bu soruya bakıldığında 3 cevap var, ancak bu sözdizimiyle 4 kişiyi kandırdınız. Onlardan biriydim ve 16 yıldır Java yazıyorum! Açıkça, sözdizimi potansiyel olarak hataya açıktır! Ondan uzak dururdum.

Teleskop Yapıcılar

Gerçekten basit şeyler için, bu karışıklığı önlemek için "teleskop" yapıcılarını kullanabilirsiniz:

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

Oluşturucu Deseni

Her yapıcı veya diğer karmaşık başlatma işlemlerinin sonunda doStuff () yöntemine ihtiyacınız varsa, belki de bir oluşturucu deseni en iyisi olacaktır. Josh Bloch , inşaatçıların iyi bir fikir olmasının birkaç nedenini listeler. Inşaatçılar yazmak için biraz zaman alır, ama düzgün yazılmış, kullanmak için bir zevk.

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

Statik Başlatıcı Döngüler

Ben kullanmayı kullanılan statik ilklendiriciler çok, ama bazen 2 sınıflar sınıf tamamen yüklenecek edemeden bloklar çağrılan başlatıcısı birbirlerinin statik bağlıydı döngüler koştu. Bu bir "sınıf yüklenemedi" veya benzer şekilde belirsiz bir hata mesajı üretti. Sorunun ne olduğunu anlamak için dosyaları kaynak kontrolünde bilinen en son çalışan sürümle karşılaştırmak zorunda kaldım. Hiç eğlenceli değil.

Tembel Başlatma

Statik başlatıcılar, çalışırken ve çok kafa karıştırıcı olmadıklarında performans nedenlerinden dolayı iyi olabilirler. Ama genel olarak, bugünlerde statik başlatıcılara tembel başlatmayı tercih ediyorum . Ne yaptıkları açık, onlarla henüz bir sınıf yükleme hatasıyla karşılaşmadım ve başlatıcı bloklarından daha fazla başlatma durumunda çalışıyorlar.

Veri Tanımı

Veri yapıları oluşturmak için statik başlatma yerine (diğer cevaplardaki örneklerle karşılaştır), şimdi kullanıyorum Paguro'nun değişmez veri tanımı yardımcı işlevlerini kullanıyorum :

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

Java'nın başlangıcında, bazı şeyleri yapmanın tek yolu başlatıcı bloklarıydı, ancak şimdi kafa karıştırıcı, hata eğilimli ve çoğu durumda daha iyi alternatiflerle değiştirildi (yukarıda detaylandırılmıştır). Onları eski kodda görmeniz durumunda başlatıcı blokları hakkında bilmek ilginç, ya da bir teste girdiler, ancak kod incelemesi yapıyordum ve yeni bir kodda birini gördüysem, neden hiçbirinin Yukarıdaki alternatifler, kodunuzu beğenmeden önce uygundur.


3

final( Barjak'ın cevabına bakınız ) olarak bildirilen bir örnek değişkeninin başlatılmasına ek olarak, staticbaşlatma bloğundan da bahsedeceğim .

Bunları bir çeşit "statik yapıcı" olarak kullanabilirsiniz.

Bu şekilde, sınıfa ilk kez başvurulduğunda statik bir değişken üzerinde karmaşık başlatma işlemleri yapabilirsiniz.

İşte barjak'ın ilhamından bir örnek:

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

1

Statik olmayan başlatıcı blokları söz konusu olduğunda, çıplak işlevleri anonim sınıflarda varsayılan bir kurucu olarak hareket etmektir. Temelde var olma tek hakları budur.


0

1, 2, 3 ifadelerine tamamen katılıyorum. Bu nedenlerle asla blok başlatıcıları kullanmıyorum ve neden Java'da olduğunu bilmiyorum.

Ancak, bir durumda statik blok başlatıcısı kullanmak zorundayım: zaman yapıcı denetlenen bir istisna atabilir bir statik alan somutlaştırmak zorunda.

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

Ama bunun yerine yapmanız gerekenler:

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

Ben (o da işaretine engeller çok çirkin bu deyim bulmak contextolarak final) ama bu tek yol bu tür alanları başlatmak için Java tarafından desteklenmemektedir.


Bence context = null;catch bloğunuza koyarsanız, bağlamı nihai olarak ilan edebileceğinizi düşünüyorum.
16:32, GlenPeterson

@GlenPeterson Denedim ama derlemiyor:The final field context may already have been assigned
benekli

ayy! Bahse girerim, statik blok içinde yerel bir değişken static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
tanıtırsanız bağlamınızı sonlandırabilirsiniz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.