Java8: java.lang.Object'ten bir yöntem için varsayılan bir yöntem tanımlamak neden yasaklanmıştır.


130

Varsayılan yöntemler, Java araç kutumuzda güzel ve yeni bir araçtır. Ancak, yöntemin bir defaultsürümünü tanımlayan bir arayüz yazmaya çalıştım toString. Java yöntemleri ilan etmesinden bu yana bu, yasak olduğunu söylüyor java.lang.Objectolmayabilir defaulted. Durum neden böyle?

"Temel sınıf her zaman kazanır" kuralı olduğunu biliyorum, bu nedenle varsayılan olarak (pun;), defaultbir Objectyöntemin herhangi bir uygulamasının üzerine Objectzaten yöntem tarafından yazılacaktır . Ancak, Objectteknik özellikteki yöntemler için bir istisna olmaması için hiçbir neden göremiyorum . Özellikle toStringbunun için varsayılan bir uygulamaya sahip olmak çok faydalı olabilir.

Öyleyse, Java tasarımcılarının defaultyöntemleri geçersiz kılan yöntemlere izin vermemeye karar vermelerinin nedeni Objectnedir?


1
Şimdi kendim hakkında çok iyi hissediyorum, bunu 100 kez yükseltiyorum ve böylece altın bir rozet. iyi soru!
Eugene

Yanıtlar:


186

Bu, siz kazmaya başlayana ve bunun aslında kötü bir fikir olduğunu anlayana kadar "açıkça iyi bir fikir" gibi görünen dil tasarım sorunlarından bir diğeri.

Bu postanın konu hakkında (ve diğer konularda da) çok şey var. Bizi mevcut tasarıma getirmek için bir araya gelen birkaç tasarım gücü vardı:

  • Kalıtım modelini basit tutma arzusu;
  • Açık örneklerin ötesine baktığınızda (örneğin, AbstractListbir arayüze dönüştüğünüzde), equals / hashCode / toString'in kalıtımsal olarak alınmasının tekli mirasa ve duruma güçlü bir şekilde bağlı olduğunu ve arayüzlerin çok sayıda miras alınmış ve durumsuz olduğunu fark edersiniz;
  • Potansiyel olarak bazı şaşırtıcı davranışların kapısını açtı.

"Basit tutma" hedefine zaten dokundunuz; kalıtım ve çatışma çözme kuralları çok basit olacak şekilde tasarlanmıştır (sınıflar arabirimleri kazanır, türetilmiş arabirimler süper arabirimleri kazanır ve diğer tüm çatışmalar uygulayıcı sınıf tarafından çözülür.) Elbette bu kurallar bir istisna yapmak için değiştirilebilir, ancak Sanırım o dizgiyi çekmeye başladığınızda, artan karmaşıklığın düşündüğünüz kadar küçük olmadığını göreceksiniz.

Elbette, daha fazla karmaşıklığı haklı çıkaracak bir dereceye kadar fayda var, ancak bu durumda orada değil. Burada bahsettiğimiz yöntemler eşittir, hashCode ve toString'dir. Bu yöntemlerin tümü özünde nesne durumu ile ilgilidir ve o sınıf için eşitliğin ne anlama geldiğini belirlemek için en iyi konumda olan arabirim değil, duruma sahip olan sınıftır (özellikle eşitlik sözleşmesi oldukça güçlü olduğu için; bkz.Etkili Bazı şaşırtıcı sonuçlar için Java); arayüz yazarları çok uzak.

AbstractListÖrneği çıkarmak kolaydır ; Bu AbstractListdavranıştan kurtulup Listarayüze koyabilirsek çok güzel olurdu . Ancak bu bariz örneğin ötesine geçtiğinizde, bulunabilecek pek çok iyi örnek yoktur. Kökte, AbstractListtek kalıtım için tasarlanmıştır. Ancak arayüzler çoklu kalıtım için tasarlanmalıdır.

Dahası, bu dersi yazdığınızı hayal edin:

class Foo implements com.libraryA.Bar, com.libraryB.Moo { 
    // Implementation of Foo, that does NOT override equals
}

FooSupertypes de yazar görünüyor, eşittir hiçbir uygulanmasını görür ve bunu elde etmek için referans eşitliği sonlandırıyor yapması gereken tüm devralır, eşit; Object. Daha sonra, gelecek hafta, Bar için kütüphane bakıcısı "yardımcı bir şekilde" varsayılan bir equalsuygulama ekler . Posta ile gönder Şimdi, Foobaşka bir bakım etki alanındaki bir arabirim tarafından anlamsallığı bozuldu ve ortak bir yöntem için bir varsayılan ekleyerek "yardımcı" oldu.

Varsayılanların varsayılan olması beklenir. Hiçbirinin olmadığı bir arabirime (hiyerarşinin herhangi bir yerinde) bir varsayılan eklemek, somut uygulama sınıflarının anlamlarını etkilememelidir. Ancak varsayılanlar Nesne yöntemlerini "geçersiz kılabilir", bu doğru olmaz.

Dolayısıyla, zararsız bir özellik gibi görünse de, aslında oldukça zararlıdır: Küçük artımlı ifadeler için çok fazla karmaşıklık ekler ve iyi niyetli, zararsız görünen değişikliklerin ayrı derlenmiş arabirimlere zarar vermesini çok kolaylaştırır. sınıfları uygulamanın amaçlanan semantiği.


13
Bunu açıklamak için zaman ayırmanıza ve dikkate alınan tüm faktörlere minnettarım. Bunun hashCodeve için tehlikeli olacağını kabul ediyorum equals, ancak bunun için çok yararlı olacağını düşünüyorum toString. Örneğin, bazı Displayablearayüz tanımlayabilir String display()yöntem ve tanımlamak mümkün Demirbaş bir ton tasarruf olacağını default String toString() { return display(); }içinde Displayableyerine her bir gerektiren, Displayableuygulamaya toString()bir veya genişletmek DisplayableToStringtemel sınıf.
Brandon

8
@Brandon toString () 'i miras almaya izin vermenin , equals () ve hashCode () için olduğu gibi tehlikeli olmayacağı konusunda haklısınız . Öte yandan, şimdi özellik daha da düzensiz olacaktır - ve bu tek yöntem uğruna, miras kuralları konusunda hala aynı ek karmaşıklığa maruz kalırsınız ... .
Brian Goetz

5
@gexicide toString()Yalnızca arayüz yöntemlerine dayanıyorsa, arayüze benzer bir şey ekleyebilir ve arayüz uygulamasını çağırmak için her alt sınıfta default String toStringImpl()geçersiz kılabilirsiniz toString()- biraz çirkin, ancak işe yarıyor ve hiç yoktan iyidir. :) Bunu yapmanın başka bir yolu Objects.hash(), Arrays.deepEquals()ve gibi bir şey yapmaktır Arrays.deepToString(). @ BrianGoetz'in cevabı için + 1'lendi!
Siu Ching Pong - Asuka Kenji -

3
Bir lambda'nın toString () varsayılan davranışı gerçekten iğrençtir. Lambda fabrikasının çok basit ve hızlı olacak şekilde tasarlandığını biliyorum, ancak alay konusu bir sınıf adı vermek gerçekten yardımcı olmuyor. default toString()İşlevsel bir arabirimde bir geçersiz kılma olması , en azından işlevin imzasını ve uygulayıcının ebeveyn sınıfını tükürmek gibi bir şey yapmamıza izin verir. Daha da iyisi, eğer bazı yinelemeli toString stratejileri getirebilseydik, lambda'nın gerçekten iyi bir tanımını elde etmek için kapanıştan geçebilir ve böylece lambda öğrenme eğrisini büyük ölçüde iyileştirebiliriz.
Groostav

Herhangi bir sınıfta, alt sınıfta, örnek üyesinde toString'de yapılan herhangi bir değişiklik, bir sınıfın sınıflarını veya kullanıcılarını uygulama üzerinde bu etkiye sahip olabilir. Ayrıca, varsayılan yöntemlerden herhangi birinde yapılacak herhangi bir değişiklik de muhtemelen tüm uygulama sınıflarını etkileyecektir. Bir arayüzün davranışını değiştiren birine gelince, toString, hashCode ile bu kadar özel olan nedir? Bir sınıf başka bir sınıfı genişletirse, onlar da değişebilir. Veya delegasyon modelini kullanıyorlarsa. Java 8 arayüzlerini kullanan birinin bunu yükselterek yapması gerekecektir. Alt sınıfta bastırılabilecek bir uyarı / hata sağlanmış olabilir.
mmm

30

java.lang.ObjectVarsayılan yöntemler hiçbir zaman "erişilebilir" olmayacağından, içindeki yöntemler için arabirimlerde varsayılan yöntemlerin tanımlanması yasaktır .

Arayüzü uygulayan sınıflarda varsayılan arabirim yöntemlerinin üzerine yazılabilir ve yöntemin sınıf uygulaması, yöntem bir üst sınıfta uygulanmış olsa bile arabirim uygulamasından daha yüksek önceliğe sahiptir. Tüm sınıflar buradan miras aldığından java.lang.Object, içindeki yöntemler java.lang.Objectarabirimdeki varsayılan yönteme göre önceliğe sahip olur ve bunun yerine çağrılır.

Oracle'dan Brian Goetz, bu posta listesi gönderisinde tasarım kararı hakkında birkaç ayrıntı daha veriyor .


3

Java dili yazarlarının kafasını göremiyorum, bu yüzden sadece tahmin edebiliriz. Ama ben birçok sebep görüyorum ve bu konuda kesinlikle katılıyorum.

Varsayılan yöntemlerin kullanılmasının ana nedeni, eski uygulamaların geriye dönük uyumluluğunu bozmadan arabirimlere yeni yöntemler ekleyebilmektir. Varsayılan yöntemler, uygulama sınıflarının her birinde bunları tanımlamaya gerek kalmadan "uygunluk" yöntemlerini sağlamak için de kullanılabilir.

Bunların hiçbiri Diziye ve diğer Object yöntemlerine uygulanmaz. Basitçe ifade etmek gerekirse, varsayılan yöntemler, başka bir tanımın olmadığı durumlarda varsayılan davranışı sağlamak için tasarlanmıştır . Diğer mevcut uygulamalarla "rekabet edecek" uygulamalar sağlamamak.

"Temel sınıf her zaman kazanır" kuralının da sağlam nedenleri vardır. Sınıfların gerçek uygulamaları tanımladığı , arayüzlerin ise biraz daha zayıf olan varsayılan uygulamaları tanımladığı varsayılır .

Ayrıca, genel kurallara HERHANGİ bir istisna getirmek, gereksiz karmaşıklığa neden olur ve başka sorular ortaya çıkarır. Nesne, diğerleri gibi (aşağı yukarı) bir sınıftır, öyleyse neden farklı davranışları olsun?

Sonuçta, önerdiğiniz çözüm muhtemelen artılardan daha fazla dezavantaj getirecektir.


Benimki yazarken gexicide'ın cevabının ikinci paragrafını fark etmedim. Sorunu daha ayrıntılı olarak açıklayan bir bağlantı içerir.
Marwin

1

Muhakeme çok basittir, çünkü Object tüm Java sınıfları için temel sınıftır. Dolayısıyla, bazı arabirimlerde varsayılan yöntem olarak tanımlanan Object metodumuz olsa bile, bu işe yaramaz çünkü Object'in metodu her zaman kullanılacaktır. Bu nedenle, karışıklığı önlemek için, Object sınıfı yöntemlerini geçersiz kılan varsayılan yöntemlere sahip olamayız.


1

Çok bilgiçlik bir cevap vermek için, sadece bir tanımlamak yasaktır defaultbir yöntemi kamu dan yöntemle java.lang.Object. Bu soruyu yanıtlamak için üç şekilde kategorize edilebilecek, dikkate alınması gereken 11 yöntem vardır.

  1. Altı Objectyöntemlerle olamaz defaultçünkü onlar yöntemleri finalve hiç geçersiz kılınan olamaz: getClass(), notify(), notifyAll(), wait(), wait(long), ve wait(long, int).
  2. Üç Objectyöntemlerine sahip olamaz default: Brian Goetz tarafından yukarıda açıklanan nedenlerden ötürü yöntemleri equals(Object), hashCode()ve toString().
  3. İki Objectyöntemlerle olabilir var defaultböyle varsayılan değeri en az sorgulanabilir olsa yöntemleri: clone()ve finalize().

    public class Main {
        public static void main(String... args) {
            new FOO().clone();
            new FOO().finalize();
        }
    
        interface ClonerFinalizer {
            default Object clone() {System.out.println("default clone"); return this;}
            default void finalize() {System.out.println("default finalize");}
        }
    
        static class FOO implements ClonerFinalizer {
            @Override
            public Object clone() {
                return ClonerFinalizer.super.clone();
            }
            @Override
            public void finalize() {
                ClonerFinalizer.super.finalize();
            }
        }
    }

.Amaç ne? Hala NEDEN kısmına cevap vermediniz - "Peki, Java tasarımcılarının varsayılan yöntemlerin Object'ten yöntemleri geçersiz kılmasına izin vermemeye karar vermelerinin nedeni nedir?"
pro_cheats
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.