Varsayılan yöntemle bir arayüz ne zaman başlatılır?


93

Cevaplamak için Java Dil Şartnamede arama yaparken bu soruyu , ben öğrendim o

Bir sınıf başlatılmadan önce, onun doğrudan üst sınıfı başlatılmalıdır, ancak sınıf tarafından uygulanan arabirimler başlatılmaz. Benzer şekilde, bir arayüzün süper arayüzleri, arayüz başlatılmadan önce başlatılmaz.

Kendi merakım için denedim ve beklendiği gibi arayüz InterfaceTypebaşlatılmadı.

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

Bu program yazdırır

implemented method

Ancak, arabirim bir defaultyöntem bildirirse , başlatma gerçekleşir. Şu şekilde InterfaceTypeverilen arayüzü düşünün

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public default void method() {
        System.out.println("default method");
    }
}

daha sonra yukarıdaki aynı program yazdırır

static initializer  
implemented method

Diğer bir deyişle, staticarayüz alanı başlatılır ( Ayrıntılı Başlatma Prosedüründe adım 9 ) ve staticbaşlatılmakta olan tipin başlatıcısı yürütülür. Bu, arayüzün başlatıldığı anlamına gelir.

JLS'de bunun olması gerektiğini gösteren hiçbir şey bulamadım. Beni yanlış anlamayın, uygulama sınıfının yöntem için bir uygulama sağlamaması durumunda bunun olması gerektiğini anlıyorum, ama ya sağlıyorsa? Bu durum Java Dil Spesifikasyonunda eksik mi, bir şey mi kaçırdım yoksa yanlış mı yorumluyorum?


4
Tahminim şu olurdu - bu tür arayüzler başlatma sırası açısından soyut sınıflar olarak kabul edildi. Bunu bir yorum olarak yazdım çünkü bunun doğru olup olmadığından emin değilim :)
Alexey Malev

JLS'nin 12.4 bölümünde yer almalıdır, ancak orada görünmemektedir. Eksik olduğunu söyleyebilirim.
Warren Çiğ

1
Boşver .... çoğu zaman anlamadıklarında veya bir açıklamaları olmadığında olumsuz oy verecekler :(. Bu genellikle SO'da olur.
NeverGiveUp161

interfaceJava'da herhangi bir somut yöntem tanımlamaması gerektiğini düşündüm . Bu yüzden InterfaceTypekodun derlenmiş olmasına şaşırdım .
MaxZoom

Yanıtlar:


85

Bu çok ilginç bir konu!

Görünüşe göre JLS bölüm 12.4.1 bunu kesin olarak ele almalı . Ancak Oracle JDK ve OpenJDK'nin (javac ve HotSpot) davranışı burada belirtilenden farklıdır. Özellikle, bu bölümdeki Örnek 12.4.1-3, arayüz başlatmayı kapsar. Örnek aşağıdaki gibidir:

interface I {
    int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
    int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
    int k = Test.out("k", 5);
}
class Test {
    public static void main(String[] args) {
        System.out.println(J.i);
        System.out.println(K.j);
    }
    static int out(String s, int i) {
        System.out.println(s + "=" + i);
        return i;
    }
}

Beklenen çıktı:

1
j=3
jj=4
3

ve gerçekten de beklenen çıktıyı alıyorum. Ancak, arayüze varsayılan bir yöntem eklenirse I,

interface I {
    int i = 1, ii = Test.out("ii", 2);
    default void method() { } // causes initialization!
}

çıktı şu şekilde değişir:

1
ii=2
j=3
jj=4
3

Bu, arayüzün Idaha önce olmadığı yerde başlatıldığını açıkça gösterir ! Varsayılan yöntemin varlığı, başlatmayı tetiklemek için yeterlidir. Varsayılan yöntemin çağrılması veya geçersiz kılınması veya hatta bahsedilmesi gerekmez, ayrıca soyut bir yöntemin varlığı da başlatmayı tetiklemez.

Benim spekülasyonum, HotSpot uygulamasının, invokevirtualçağrının kritik yoluna sınıf / arayüz başlatma denetimi eklemekten kaçınmak istediğidir . Java 8 ve varsayılan yöntemlerden önce, invokevirtualbir arabirimde kod çalıştırmayı asla sona erdiremezdi, bu nedenle bu ortaya çıkmadı. Bunun, yöntem tabloları gibi şeyleri başlatan sınıf / arayüz hazırlama aşamasının ( JLS 12.3.2 ) bir parçası olduğu düşünülebilir . Ama belki bu çok ileri gitti ve bunun yerine yanlışlıkla tam başlatma yaptı.

Ben ettik bu soruyu gündeme OpenJDK derleyici-dev posta listesinde. Bir meydana gelmiş Alex Buckley'den cevap o daha JVM yönelteceği soruları ve lamda uygulama ekipleri yükseltir ettiği (JLS editörü). Ayrıca, spesifikasyonda "T bir sınıftır ve T tarafından bildirilen statik bir yöntem çağrılır" ifadesinin T bir arabirim olması durumunda da geçerli olması gerektiğini belirten bir hata olduğunu not eder. Yani, burada hem özellik hem de HotSpot hataları olabilir.

Açıklama : OpenJDK üzerinde Oracle için çalışıyorum. İnsanlar bunun bana bu soruya ödülün eklenmesinde haksız bir avantaj sağladığını düşünüyorsa, bu konuda esnek olmaya hazırım.


6
Resmi kaynaklar istedim. Bundan daha resmi olduğunu sanmıyorum. Tüm gelişmeleri görmek için iki gün verin.
Sotirios Delimanolis

48
@StuartMarks " İnsanlar bunun bana haksız bir avantaj sağladığını düşünüyorlarsa " => sorulara yanıt almak için buradayız ve bu mükemmel bir cevap!
assylias

2
Bir yan not: JVM Spesifikasyonu, JLS'ye benzer bir açıklama içerir: docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5 Bu da güncellenmelidir .
Marco13

2
@assylias ve Sotirios, yorumlarınız için teşekkürler. Asylilerin yorumlarına yapılan 14 olumlu oyla birlikte (bu yazı itibariyle), herhangi bir potansiyel adaletsizlik hakkındaki endişelerimi hafifletti.
Stuart Marks

1
@SotiriosDelimanolis İlgili görünen birkaç hata var, JDK-8043275 ve JDK-8043190 ve bunlar 8u40'ta düzeltildi olarak işaretlenmiş. Ancak davranış aynı görünüyor. Bununla iç içe geçmiş bazı JVM Spec değişiklikleri de vardı, bu nedenle düzeltme, "eski başlatma sırasını geri yükleme" dışında bir şey olabilir.
Stuart Marks

13

Sabit InterfaceType.initolmayan değerle (yöntem çağrısı) başlatılan sabit alan hiçbir yerde kullanılmadığından arabirim başlatılmadı .

Derleme zamanında, sabit arabirim alanının hiçbir yerde kullanılmadığı ve arabirimin herhangi bir varsayılan yöntem içermediği (java-8'de) bilinir, bu nedenle arabirimi başlatmaya veya yüklemeye gerek yoktur.

Arayüz aşağıdaki durumlarda başlatılacaktır,

  • kodunuzda sabit alan kullanılır.
  • Arayüz varsayılan bir yöntem içerir (Java 8)

Varsayılan Yöntemler durumunda , uyguluyorsunuz InterfaceType. Yani, eğer InterfaceTypeherhangi bir varsayılan yöntem içerecekse, sınıfın uygulanmasında INHERITED (kullanılır) olacaktır . Ve Başlatma resmin içine girecek.

Ancak, (normal şekilde başlatılan) sabit arayüz alanına erişiyorsanız, arayüz başlatmaya gerek yoktur.

Aşağıdaki kodu düşünün.

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        System.out.println(InterfaceType.init);
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

Yukarıdaki durumda, alanı kullandığınız için Arayüz başlatılacak ve yüklenecektir InterfaceType.init.

Sorunuzda zaten verdiğiniz gibi, varsayılan yöntem örneğini vermiyorum.

Java dili spesifikasyonu ve örneği JLS 12.4.1'de verilmiştir (Örnek, varsayılan yöntemleri içermez.)


Varsayılan yöntemler için JLS bulamıyorum, iki olasılık olabilir

  • Java insanları, varsayılan yöntem durumunu düşünmeyi unuttu. (Şartname Dokümanı hatası.)
  • Varsayılan yöntemleri arayüzün sabit olmayan üyesi olarak adlandırırlar. (Ama yine de Specification Doc hatasından bahsetmedi.)

Varsayılan yöntem için bir referans arıyorum. Alan, sadece arayüzün başlatılıp başlatılmadığını göstermekti.
Sotirios Delimanolis

@SotiriosDelimanolis Varsayılan yöntem için cevabın nedeninden bahsetmiştim ... ancak maalesef varsayılan yöntem için henüz herhangi bir JLS bulunamadı.
Hata değil

Ne yazık ki, aradığım şey bu. Cevabınızın sadece soruda daha önce belirttiğim şeyleri tekrar ettiğini hissediyorum, yani. bir defaultyöntem içeriyorsa bir arabirim başlatılır ve arabirimi uygulayan bir sınıf başlatılır.
Sotirios Delimanolis

Java insanlarının varsayılan yöntemi düşünmeyi unuttuğunu düşünüyorum, Veya sadece varsayılan yöntemleri arayüzün sabit olmayan üyesi olarak adlandırıyorlar (varsayımım, hiçbir belgede bulamıyor).
Hata değil

1
@KishanSarsechaGajjar: Arayüzdeki sabit olmayan alanla ne demek istiyorsunuz? Arayüzdeki herhangi bir değişken / alan varsayılan olarak statiktir.
Lokesh

10

İnstanceKlass.cpp OpenJDK dosya başlatma yöntemi içeren InstanceKlass::initialize_impledilene karşılık Ayrıntılı Başlatma Prosedür benzer şekilde bulunan JLS olarak, Başlatma JVM Spec bölümü.

Kodda atıfta bulunulan JVM kitabında değil JLS'de bahsedilmeyen yeni bir adım içerir:

// refer to the JVM book page 47 for description of steps
...

if (this_oop->has_default_methods()) {
  // Step 7.5: initialize any interfaces which have default methods
  for (int i = 0; i < this_oop->local_interfaces()->length(); ++i) {
    Klass* iface = this_oop->local_interfaces()->at(i);
    InstanceKlass* ik = InstanceKlass::cast(iface);
    if (ik->has_default_methods() && ik->should_be_initialized()) {
      ik->initialize(THREAD);
    ....
    }
  }
}

Bu nedenle, bu başlatma, yeni bir Adım 7.5 olarak açıkça uygulanmıştır . Bu, bu uygulamanın bazı spesifikasyonları izlediğini gösterir, ancak web sitesindeki yazılı şartnamenin buna göre güncellenmediği görülmektedir.

DÜZENLEME: Referans olarak, ilgili adımın uygulamaya dahil edildiği taahhüt (Ekim 2012'den itibaren!): Http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

DÜZENLEME2: Tesadüfen, sonunda ilginç bir yan not içeren sıcak noktadaki varsayılan yöntemler hakkında bu Belgeyi buldum :

3.7 Çeşitli

Arabirimler artık bayt koduna sahip olduğundan, bunları bir uygulama sınıfı başlatıldığında başlatmalıyız.


1
Bunu araştırdığınız için teşekkürler. (+1) Yeni "7.5 adımı" yanlışlıkla spesifikasyondan çıkarılmış veya önerilmiş ve reddedilmiş ve uygulama onu kaldırmak için hiçbir zaman düzeltilmemiş olabilir.
Stuart Marks

1

Bir arayüz başlatmanın alt türlerin bağlı olduğu herhangi bir yan kanal yan etkisine neden olmaması gerektiğini, bu nedenle, bu bir hata olup olmadığını veya Java'nın onu hangi şekilde düzelttiği önemli olmamalı arayüzlerin sırayla başlatıldığı uygulama.

A durumunda, classalt sınıfların bağlı olduğu yan etkilere neden olabileceği kabul edilmektedir. Örneğin

class Foo{
    static{
        Bank.deposit($1000);
...

Herhangi bir alt sınıfı Foo, alt sınıf kodunun herhangi bir yerinde bankada 1000 $ görmeyi bekler. Bu nedenle, üst sınıf, alt sınıftan önce başlatılır.

Aynı şeyi üst yüzler için de yapmamız gerekmez mi? Ne yazık ki, süper arayüzlerin sırasının önemli olmaması gerekiyor, bu nedenle onları başlatacak iyi tanımlanmış bir sıra yok.

Bu yüzden arayüz ilklendirmelerinde bu tür yan etkiler oluşturmasak iyi olur. Sonuçta, interfacekolaylık sağlamak için kullandığımız bu özelliklere (statik alanlar / yöntemler) yönelik değildir.

Bu nedenle, bu prensibi izlersek, arayüzlerin hangi sırayla başlatılacağı bizi ilgilendirmez.

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.