Dlaczego „końcowe” jest niedozwolone w metodach interfejsu Java 8?

335

Jedną z najbardziej przydatnych funkcji Java 8 są nowe defaultmetody interfejsów. Istnieją zasadniczo dwa powody (mogą być inne), dlaczego zostały wprowadzone:

Z punktu widzenia projektanta API chciałbym móc korzystać z innych modyfikatorów metod interfejsu, np final. Przydałoby się to podczas dodawania metod wygody, zapobiegając „przypadkowym” zastąpieniom przy wdrażaniu klas:

interface Sender {

    // Convenience method to send an empty message
    default final void send() {
        send(null);
    }

    // Implementations should only implement this method
    void send(String message);
}

Powyższe jest już powszechną praktyką, gdyby Senderbyły klasą:

abstract class Sender {

    // Convenience method to send an empty message
    final void send() {
        send(null);
    }

    // Implementations should only implement this method
    abstract void send(String message);
}

Teraz defaulti finaloczywiście są oczywiście sprzeczne słowa kluczowe, ale samo domyślne słowo kluczowe nie byłoby ściśle wymagane , więc zakładam, że ta sprzeczność jest umyślna, aby odzwierciedlić subtelne różnice między „metodami klasy z ciałem” (tylko metody) a „interfejsem” metody z ciałem ” (metody domyślne), tj. różnice, których jeszcze nie rozumiałem.

W pewnym momencie obsługa modyfikatorów takich jak statici finalmetod interfejsu nie została jeszcze w pełni zbadana, powołując się na Briana Goetza :

Drugą częścią jest to, jak daleko posuniemy się do wspierania narzędzi do budowania klas w interfejsach, takich jak metody końcowe, metody prywatne, metody chronione, metody statyczne itp. Odpowiedź brzmi: jeszcze nie wiemy

Od tego czasu pod koniec 2011 roku staticdodano oczywiście obsługę metod w interfejsach. Najwyraźniej dodało to wiele wartości do samych bibliotek JDK, takich jak with Comparator.comparing().

Pytanie:

Jaki jest powód final(i także static final), że nigdy nie dotarł do interfejsów Java 8?

Lukas Eder
źródło
10
Przykro mi, że jestem mokrym kocem, ale jedynym sposobem na odpowiedź na pytanie wyrażone w tytule SO jest cytat Briana Goetza lub grupy ekspertów JSR. Rozumiem, że BG poprosił o publiczną dyskusję, ale to właśnie narusza warunki pisemnego zgłoszenia zastrzeżeń, ponieważ „opiera się ono głównie na opiniach”. Wydaje mi się, że obowiązki są tutaj uchylane. Zadaniem Grupy Ekspertów i szerszego procesu Java Community Process jest stymulowanie dyskusji i wymyślanie uzasadnień. Nie SO. Dlatego głosuję za zamknięciem, ponieważ „przede wszystkim opiera się na opiniach”.
Markiz Lorne
2
Jak wszyscy wiemy, finalzapobiega to zastąpieniu metody i widząc, jak MUSISZ przesłonić metody odziedziczone z interfejsów, nie rozumiem, dlaczego miałoby sens nadać jej ostateczność. Chyba że miałoby to oznaczać, że metoda jest ostateczna PO PRZECIWCIENIU jej raz .. W takim przypadku może być jakieś trudności? Jeśli nie rozumiem tego prawa, pozwól mi kmow. Wydaje się interesujące
Vince Emigh
22
@EJP: „Jeśli mogę to zrobić, to ty też możesz odpowiedzieć na własne pytanie, które w związku z tym nie wymagałoby zadawania pytań”. Odnosi się to do prawie wszystkich pytań na tym forum, prawda? Zawsze mogłem sam Google napisać jakiś temat przez 5 godzin, a następnie nauczyć się czegoś, czego wszyscy inni muszą nauczyć się w równie trudny sposób. Lub czekamy jeszcze kilka minut, aby ktoś udzielił jeszcze lepszej odpowiedzi, z której wszyscy w przyszłości (w tym 12 głosów pozytywnych i 8 gwiazdek) mogą czerpać zyski, ponieważ SO jest tak dobrze przywoływane w Google. Więc tak. Pytanie to może doskonale pasuje do Q & formie, jest.
Lukas Eder
5
@VinceEmigh „ ... widząc, jak MUSISZ przesłonić metody odziedziczone z interfejsów ... ” Nie jest to prawdą w Javie 8. Java 8 pozwala na implementację metod w interfejsach, co oznacza, że ​​nie musisz implementować ich w implementacji zajęcia Przydałoby finalsię w tym przypadku zapobieganie zastąpieniu przez klasę domyślną implementację metody interfejsu.
awksp
28
@EJP, którego nigdy nie znasz: Brian Goetz może odpowiedzieć !
assylias

Odpowiedzi:

419

To pytanie jest w pewnym stopniu związane z tym, z jakiego powodu „synchronizacja” jest niedozwolona w metodach interfejsu Java 8?

Kluczową rzeczą do zrozumienia w przypadku metod domyślnych jest to, że głównym celem projektowania jest ewolucja interfejsu , a nie „przekształcanie interfejsów w (mierne) cechy”. Chociaż oba te elementy nakładają się na siebie, a my staraliśmy się dostosować do tych drugich, gdzie nie przeszkadzało to w pierwszym, te pytania najlepiej zrozumieć, patrząc w tym świetle. (Uwaga też, że metody klasy będzie różna od metod interfejsu, bez względu na zamiar, z racji faktu, że metody interfejsu można mnożyć dziedziczone.)

Podstawową ideą metody domyślnej jest: jest to metoda interfejsu z domyślną implementacją, a klasa pochodna może zapewnić bardziej szczegółową implementację. A ponieważ ośrodek konstrukcja była ewolucja interfejsu, to był krytyczny cel projektu, że metody domyślne mógł być dodany do interfejsów po fakcie , w sposób zgodny źródłowej i binarnej kompatybilne.

Zbyt prosta odpowiedź na pytanie „dlaczego nie ostateczne domyślne metody” jest taka, że ​​wtedy ciało nie byłoby po prostu domyślną implementacją, byłoby jedyną implementacją. Chociaż jest to nieco zbyt prosta odpowiedź, daje nam wskazówkę, że pytanie zmierza już w wątpliwym kierunku.

Innym powodem, dla którego końcowe metody interfejsu są wątpliwe, jest to, że stwarzają one niemożliwe problemy dla implementatorów. Załóżmy na przykład, że masz:

interface A { 
    default void foo() { ... }
}

interface B { 
}

class C implements A, B { 
}

Tutaj wszystko jest dobrze; Cdziedziczy foo()po A. Teraz załóżmy, że Bzmieniono na foometodę z domyślną:

interface B { 
    default void foo() { ... }
}

Teraz, gdy przejdziemy do ponownej kompilacji C, kompilator powie nam, że nie wie, dla jakiego zachowania powinien dziedziczyć foo(), więc Cmusi go zastąpić (i może zdecydować o delegowaniu, A.super.foo()jeśli chce zachować to samo zachowanie). Ale co, jeśli Bzrobił swoje domyślne finali Anie jest pod kontrolą autora C? Teraz Cjest bezpowrotnie zepsuty; nie można go skompilować bez foo()przesłonięcia, ale nie można go przesłonić, foo()jeśli był końcowy w B.

To tylko jeden przykład, ale chodzi o to, że ostateczność metod jest tak naprawdę narzędziem, które ma sens w świecie klas z pojedynczym dziedzictwem (generalnie, które łączą stan z zachowaniem), niż z interfejsami, które jedynie przyczyniają się do zachowania i mogą być mnożone dziedziczny. Zbyt trudno jest uzasadnić, „jakie inne interfejsy mogłyby zostać zmieszane z ostatecznym implementatorem”, a zezwolenie na ostateczną metodę interfejsu prawdopodobnie spowodowałoby te problemy (i wybuchłyby nie na osobie, która napisała interfejs, ale na słaby użytkownik, który próbuje to zaimplementować).

Innym powodem, dla którego należy ich zabronić, jest to, że nie mieliby na myśli tego, co myślisz. Domyślna implementacja jest brana pod uwagę tylko wtedy, gdy klasa (lub jej nadklasy) nie zapewnia deklaracji (konkretnej lub abstrakcyjnej) metody. Gdyby domyślna metoda była ostateczna, ale nadklasa już ją zaimplementowała, domyślna byłaby zignorowana, co prawdopodobnie nie jest tym, czego oczekiwał autor domyślny, deklarując ją jako ostateczną. (To zachowanie dziedziczenia jest odzwierciedleniem centrum projektowania metod domyślnych - ewolucji interfejsu. Powinna istnieć możliwość dodania metody domyślnej (lub domyślnej implementacji do istniejącej metody interfejsu) do istniejących interfejsów, które już mają implementacje, bez zmiany zachowanie istniejących klas, które implementują interfejs,

Brian Goetz
źródło
88
Fantastycznie, że odpowiadasz na pytania dotyczące nowych funkcji językowych! Bardzo pomocne jest wyjaśnienie celu i szczegółów projektu, gdy zastanawiamy się, jak powinniśmy korzystać z nowych funkcji. Czy inne osoby zaangażowane w projekt przyczyniają się do SO - czy robisz to sam? Będę podążał za twoimi odpowiedziami pod tagiem java-8 - chciałbym wiedzieć, czy inni ludzie robią to samo, abym mógł ich śledzić.
Shorn
10
@Shorn Stuart Marks był aktywny w tagu java-8. Jeremy Manson pisał w przeszłości. Pamiętam też, że widziałem wiadomości od Joshua Blocha, ale nie mogę ich teraz znaleźć.
assylias
1
Gratulacje dla wprowadzenia domyślnych metod interfejsu jako znacznie bardziej eleganckiego sposobu osiągnięcia tego, co C # robi ze swoimi źle pomyślanymi i raczej źle wprowadzonymi metodami rozszerzenia. Jeśli chodzi o to pytanie, odpowiedź na temat niemożności rozwiązania konfliktu nazw rozwiązuje problem, ale resztą podanych powodów była nieprzekonująca filologia. (Jeśli chcę, aby metoda interfejsu była ostateczna, powinieneś założyć, że muszę mieć własne, cholernie dobre powody, dla których chciałbym zabronić komukolwiek udostępnienia dowolnej implementacji innej niż moja.)
Mike Nakis,
1
Mimo to można rozwiązać konflikt nazw w sposób podobny do tego w C #, dodając nazwę konkretnego interfejsu, który jest implementowany do deklaracji metody implementacji. Biorę to do siebie z tego, że domyślne metody interfejsu nie mogą być ostateczne w Javie, ponieważ wymagałoby to dodatkowych modyfikacji składni, co nie było dobre. (Być może zbyt ostry?)
Mike Nakis,
18
@Trying Klasy abstrakcyjne są nadal jedynym sposobem na wprowadzenie stanu lub wdrożenie podstawowych metod Object. Domyślne metody dotyczą czystego zachowania ; klasy abstrakcyjne służą do zachowania połączonego ze stanem.
Brian Goetz,
43

Na liście mailingowej lambda jest wiele dyskusji na ten temat . Jednym z tych, które wydają się zawierać wiele dyskusji na temat tego wszystkiego, jest: Widoczność metody zróżnicowanego interfejsu (była Final Defenders) .

W tej dyskusji Talden, autor oryginalnego pytania, zadaje pytanie bardzo podobne do twojego pytania:

Decyzja o upublicznieniu wszystkich członków interfejsu była rzeczywiście niefortunną decyzją. To, że każde użycie interfejsu w projekcie wewnętrznym ujawnia prywatne szczegóły implementacji, jest duże.

Jest to trudny do naprawienia bez dodawania do języka pewnych niejasnych lub łamliwych kompatybilności niuansów. Taka przerwa w kompatybilności i potencjalna subtelność byłyby nie do przyjęcia, więc musi istnieć rozwiązanie, które nie zepsuje istniejącego kodu.

Można ponownie wprowadzić słowo kluczowe „pakiet” jako specyfikator dostępu. Brak specyfikatora w interfejsie oznaczałby dostęp publiczny, a brak specyfikatora w klasie oznacza dostęp do pakietu. To, które specyfikatory mają sens w interfejsie, jest niejasne - szczególnie jeśli, aby zminimalizować obciążenie wiedzy programistów, musimy upewnić się, że specyfikatory dostępu oznaczają to samo w klasie i interfejsie, jeśli są obecne.

W przypadku braku domyślnych metod spekulowałbym, że specyfikator elementu w interfejsie musi być co najmniej tak samo widoczny jak sam interfejs (aby interfejs mógł faktycznie zostać wdrożony we wszystkich widocznych kontekstach) - przy użyciu domyślnych metod, które nie są tak pewny.

Czy była jakaś jasna informacja, czy jest to w ogóle możliwa dyskusja w terenie? Jeśli nie, czy powinno się to odbyć gdzie indziej.

Ostatecznie odpowiedź Briana Goetza brzmiała :

Tak, to już jest badane.

Pozwólcie jednak, że ustalę pewne realistyczne oczekiwania - funkcje języka / VM mają długi czas realizacji, nawet takie trywialne. Minął już czas na proponowanie nowych pomysłów na funkcje językowe dla Java SE 8.

Najprawdopodobniej nigdy nie został wdrożony, ponieważ nigdy nie był objęty zakresem. Nigdy nie zaproponowano jej na czas do rozważenia.

W kolejnej gorącej dyskusji na temat ostatecznych metod obrony na ten temat Brian powiedział ponownie :

I masz dokładnie to, czego sobie życzyłeś. Właśnie to dodaje ta funkcja - wielokrotne dziedziczenie zachowania. Oczywiście rozumiemy, że ludzie będą wykorzystywać je jako cechy. I ciężko pracowaliśmy, aby zapewnić, że oferowany przez nich model dziedziczenia jest prosty i wystarczająco czysty, aby ludzie mogli uzyskać dobre wyniki w różnych sytuacjach. Jednocześnie postanowiliśmy nie wypychać ich poza granice tego, co działa prosto i czysto, co prowadzi do reakcji „aw, nie posunąłeś się wystarczająco daleko” w niektórych przypadkach. Ale tak naprawdę większość tego wątku wydaje się narzekać, że szkło jest wypełnione w 98%. Wezmę 98% i dam radę!

To umacnia moją teorię, że po prostu nie była częścią zakresu ani części ich projektu. Udało im się zapewnić wystarczającą funkcjonalność, aby poradzić sobie z zagadnieniami ewolucji API.

Edwin Dalorzo
źródło
4
Widzę, że powinienem był zawrzeć starą nazwę „metody obrońcy” w mojej odysei Google'a dziś rano. +1 za wykopanie tego.
Marco13
1
Niezłe kopanie faktów historycznych. Twoje wnioski są zgodne z oficjalną odpowiedzią
Lukas Eder
Nie rozumiem, dlaczego miałoby to zerwać z kompatybilnością wsteczną. finał nie był wcześniej dozwolony. pozwalają teraz na prywatne, ale nie chronione. myeh ... prywatny nie może zaimplementować ... interfejs rozszerzający inny interfejs może implementować części interfejsu nadrzędnego, ale musi ujawniać zdolność przeciążania innym ...
mmm
17

Trudno będzie znaleźć i zidentyfikować „the” odpowiedź, dla przyczyn niezależnych wymienionych w komentarzach z @EJP: Istnieje około 2 (+/- 2) ludzi na świecie, którzy mogą dają jednoznacznej odpowiedzi na wszystko . Wątpliwości może być po prostu coś w rodzaju: „Wspieranie ostatecznych domyślnych metod nie wydawało się warte wysiłku polegającego na restrukturyzacji wewnętrznych mechanizmów rozstrzygania połączeń”. Jest to oczywiście spekulacja, ale jest ona poparta przynajmniej subtelnymi dowodami, takimi jak to Oświadczenie (jednej z dwóch osób) z listy mailingowej OpenJDK :

„Przypuszczam, że jeśli dozwolone byłyby metody„ ostateczne domyślne ”, mogą wymagać przepisania z wewnętrznego wywołania specjalnego na widoczny interfejs użytkownika.”

i trywialne fakty, takie jak ta, metoda po prostu nie jest uważana za (naprawdę) ostateczną metodę, gdy jest to defaultmetoda, jak obecnie zaimplementowana w metodzie Method :: is_final_method w OpenJDK.

Trudno znaleźć dalsze naprawdę „autorytatywne” informacje, nawet przy nadmiernej liczbie wyszukiwań internetowych i czytaniu dzienników zatwierdzeń. Pomyślałem, że może to być związane z potencjalnymi niejednoznacznościami podczas rozwiązywania wywołań metod interfejsu z invokeinterfaceinstrukcją i wywołaniami metod klasy, odpowiadającymi invokevirtualinstrukcji: W przypadku invokevirtualinstrukcji może istnieć proste wyszukiwanie vtable , ponieważ metoda musi być albo dziedziczona z nadklasy lub zaimplementowane bezpośrednio przez klasę. W przeciwieństwie do tego, invokeinterfacepołączenie musi zbadać odpowiednią stronę wywoływania, aby dowiedzieć się, do którego interfejsu to połączenie faktycznie się odnosi (wyjaśniono to bardziej szczegółowo na stronie InterfaceCalls na HotSpot Wiki). Jednak,finalmetody albo w ogóle nie są wstawiane do vtable , albo zastępują istniejące wpisy w vtable (patrz klassVtable.cpp. Linia 333 ), i podobnie, domyślne metody zastępują istniejące wpisy w vtable (patrz klassVtable.cpp, wiersz 202 ) . Tak więc faktyczny powód (a zatem odpowiedź) musi być ukryty głębiej w (raczej złożonym) mechanizmie rozstrzygania wywołań metody, ale być może te odniesienia będą mimo to uważane za pomocne, czy tylko dla innych, którym uda się uzyskać rzeczywistą odpowiedź z tego.

Marco13
źródło
Dzięki za ciekawy wgląd. Kawałek Johna Rose'a jest interesującym śladem. Nadal jednak nie zgadzam się z @EJP. Jako kontrprzykład sprawdź moją odpowiedź na bardzo interesujące, bardzo podobne pytanie, które zadał Peter Lawrey . Możliwe jest wykopanie faktów historycznych i zawsze cieszę się, że mogę je znaleźć tutaj na Stack Overflow (gdzie indziej?). Oczywiście twoja odpowiedź jest nadal spekulacyjna i nie jestem w 100% przekonany, że szczegóły implementacji JVM byłyby ostatecznym powodem (zamierzonej gry słów) zapisania JLS w taki czy inny sposób ...
Lukas Eder
@LukasEder Pewnie, tego rodzaju pytania interesujące i IMHO pasują do wzorca pytań i odpowiedzi. Myślę, że są tutaj dwie kluczowe kwestie, które spowodowały spór: po pierwsze, poprosiłeś o „powód”. W wielu przypadkach może to nie być oficjalnie udokumentowane. Np. W JLS nie ma żadnego „powodu”, dlaczego nie ma żadnych niepodpisanych int, ale patrz stackoverflow.com/questions/430346 ... ...
Marco13
... ... Po drugie, prosiłeś tylko o „autorytatywne cytaty”, co zmniejsza liczbę osób, które odważą się napisać odpowiedź z „kilkudziesięciu” do… „w przybliżeniu zero”. Poza tym nie jestem pewien, w jaki sposób przeplata się rozwój JVM i pisanie JLS, tj. W jakim stopniu rozwój wpływa na to, co jest zapisane w JLS, ale ... uniknę tutaj spekulacji; -)
Marco13
1
Nadal opieram swoją sprawę. Zobacz kto odpowiedział na moje inne pytanie :-) Będzie teraz zawsze być jasne, z odpowiedzią autorytatywny, tutaj na przepełnienie stosu , dlaczego zdecydowano się nie wspierać synchronizedna defaultmetodach.
Lukas Eder
3
@LukasEder Widzę, to samo tutaj. Kto mógł się tego spodziewać? Powody są dość przekonujące, szczególnie w przypadku tego finalpytania, i nieco upokarzające jest to, że nikt inny nie myślał o podobnych przykładach (a może niektórzy myśleli o tych przykładach, ale nie czuli się wystarczająco kompetentni, aby odpowiedzieć). Więc teraz (przepraszam, muszę to zrobić :) wypowiedziane jest ostatnie słowo.
Marco13
4

Nie sądzę, że konieczne jest określenie finalmetody interfejsu konwergencji. Mogę się zgodzić, że może to być pomocne, ale najwyraźniej koszty przeważają nad korzyściami.

Niezależnie od tego, co powinieneś zrobić, to napisać odpowiedni javadoc dla domyślnej metody, pokazując dokładnie, czym jest ta metoda i czego nie wolno robić. W ten sposób klasy implementujące interfejs „nie mogą” zmieniać implementacji, chociaż nie ma gwarancji.

Każdy może napisać Collectiontekst, który jest zgodny z interfejsem, a następnie robi rzeczy metodami absolutnie sprzecznymi z intuicją, nie ma innego sposobu, aby się przed tym uchronić, niż napisanie obszernych testów jednostkowych.

skiwi
źródło
2
Umowy Javadoc stanowią prawidłowe obejście konkretnego przykładu wymienionego w moim pytaniu, ale tak naprawdę nie chodzi o przypadek użycia metody interfejsu wygodnego. Pytanie dotyczy wiarygodnego powodu, dla finalktórego postanowiono nie zezwalać na interfacemetody Java 8 . Niewystarczający stosunek kosztów do korzyści jest dobrym kandydatem, ale jak dotąd to spekulacja.
Lukas Eder
0

Dodajemy defaultsłowo kluczowe do naszej metody, interfacegdy wiemy, że klasa rozszerzająca naszą implementację interfacemoże, ale nie musi override. Ale co, jeśli chcemy dodać metodę, której nie chcemy przesłonić żadną klasą implementacyjną? Cóż, dwie opcje były dla nas dostępne:

  1. Dodaj default finalmetodę.
  2. Dodaj staticmetodę.

Teraz Java mówi, że jeśli mamy classimplementację dwóch lub więcej interfacestakich, że mają one defaultmetodę o dokładnie takiej samej nazwie i podpisie, tj. Są duplikaty, wówczas musimy zapewnić implementację tej metody w naszej klasie. Teraz w przypadku default finalmetod nie możemy zapewnić implementacji i utknęliśmy. I dlatego finalsłowo kluczowe nie jest używane w interfejsach.

Ashutosh
źródło