Odpowiadałem na pytanie i wpadłem na scenariusz, którego nie potrafię wyjaśnić. Rozważ ten kod:
interface ConsumerOne<T> {
void accept(T a);
}
interface CustomIterable<T> extends Iterable<T> {
void forEach(ConsumerOne<? super T> c); //overload
}
class A {
private static CustomIterable<A> iterable;
private static List<A> aList;
public static void main(String[] args) {
iterable.forEach(a -> aList.add(a)); //ambiguous
iterable.forEach(aList::add); //ambiguous
iterable.forEach((A a) -> aList.add(a)); //OK
}
}
Nie rozumiem, dlaczego wyraźne wpisanie parametru lambda (A a) -> aList.add(a)
powoduje, że kod się kompiluje. Dodatkowo, dlaczego łączy się to z przeciążeniem Iterable
zamiast w tym CustomIterable
?
Czy jest jakieś wyjaśnienie tego lub link do odpowiedniej sekcji specyfikacji?
Uwaga: iterable.forEach((A a) -> aList.add(a));
kompiluje się tylko po CustomIterable<T>
rozszerzeniu Iterable<T>
(płaskie przeciążenie metod CustomIterable
powoduje niejednoznaczny błąd)
Uzyskanie tego na obu:
- wersja openjdk „13.0.2” 2020-01-14
Kompilator Eclipse - wersja Openjdk „1.8.0_232”
Kompilator Eclipse
Edycja : Powyższy kod nie kompiluje się przy budowaniu z maven, podczas gdy Eclipse pomyślnie kompiluje ostatni wiersz kodu.
Odpowiedzi:
TL; DR, to jest błąd kompilatora.
Nie ma reguły, która dawałaby pierwszeństwo konkretnej stosownej metodzie po jej odziedziczeniu lub metodzie domyślnej. Co ciekawe, kiedy zmieniam kod na
iterable.forEach((A a) -> aList.add(a));
stwierdzenie wywołuje błąd w Eclipse.Ponieważ żadna właściwość
forEach(Consumer<? super T) c)
metody zIterable<T>
interfejsu nie zmieniła się podczas deklarowania kolejnego przeciążenia, decyzja Eclipse o wybraniu tej metody nie może (konsekwentnie) opierać się na żadnej właściwości metody. Jest to wciąż jedyna metoda odziedziczona, wciąż jedynadefault
metoda, wciąż jedyna metoda JDK i tak dalej. Żadna z tych właściwości i tak nie powinna wpływać na wybór metody.Pamiętaj, że zmiana deklaracji na
powoduje również „dwuznaczny” błąd, więc liczba stosowanych przeciążonych metod również nie ma znaczenia, nawet jeśli są tylko dwaj kandydaci, nie ma ogólnej preferencji względem
default
metod.Jak do tej pory wydaje się, że problem pojawia się, gdy istnieją dwie odpowiednie metody oraz
default
metoda i relacja dziedziczenia, ale nie jest to właściwe miejsce na dalsze kopanie.Ale zrozumiałe jest, że konstrukcje z twojego przykładu mogą być obsługiwane przez inny kod implementacyjny w kompilatorze, jeden wykazuje błąd, a drugi nie.
a -> aList.add(a)
jest niejawnie wpisanym wyrażeniem lambda, którego nie można użyć do rozwiązania problemu przeciążenia. W przeciwieństwie,(A a) -> aList.add(a)
jest wyraźnie wpisany wyrażeniem lambda, którego można użyć do wybrania pasującej metody spośród przeciążonych metod, ale to nie pomaga tutaj (nie powinno tutaj pomóc), ponieważ wszystkie metody mają typy parametrów z dokładnie taką samą sygnaturą funkcjonalną .Jako kontrprzykład z
podpisy funkcjonalne różnią się, a użycie jawnie typu wyrażenia lambda może rzeczywiście pomóc w wyborze właściwej metody, podczas gdy niejawnie wpisane wyrażenie lambda nie pomaga, więc
forEach(s -> s.isEmpty())
powoduje błąd kompilatora. I wszystkie kompilatory Java się z tym zgadzają.Zauważ, że
aList::add
jest to niejednoznaczne odwołanie do metody, ponieważadd
metoda jest również przeciążona, więc nie może również pomóc w wyborze metody, ale odwołania do metod mogą być przetwarzane przez inny kod. Przełączenie na jednoznacznymaList::contains
lub zmieniaList
sięCollection
, abyadd
jednoznaczna, nie zmienił wynik w mojej instalacji Eclipse (kiedyś2019-06
).źródło
default
metodą, to tylko dodatkowy punkt. Moja odpowiedź pokazuje już przykład, w którym Eclipse nie daje pierwszeństwa domyślnej metodzie.default
zmienia wynik i od razu zakładasz, że znalazłeś przyczynę zaobserwowanego zachowania. Jesteś tak zbyt pewny tego, że błędnie nazywasz inne odpowiedzi, mimo że nie są nawet sprzeczne. Ponieważ projektujesz swoje własne zachowanie w stosunku do innych , nigdy nie twierdziłem, że powodem jest dziedziczenie . Udowodniłem, że tak nie jest. Wykazałem, że zachowanie jest niespójne, ponieważ Eclipse wybiera konkretną metodę w jednym scenariuszu, ale nie w innym, w którym występują trzy przeciążenia.default
drugąabstract
, z dwoma argumentami podobnymi do konsumentów i spróbuj. Zaćmienie słusznie mówi, że jest dwuznaczna, mimo że jedna jestdefault
metodą. Najwyraźniej dziedziczenie wciąż dotyczy tego błędu Eclipse, ale w przeciwieństwie do ciebie, nie oszaleję i nie nazywam innych odpowiedzi błędnie, tylko dlatego, że nie przeanalizowali błędu w całości. To nie nasza praca tutaj.Kompilator Eclipse poprawnie rozwiązuje tę
default
metodę , ponieważ jest to najbardziej specyficzna metoda zgodnie ze specyfikacją języka Java 15.12.2.5 :javac
(domyślnie używane przez Maven i IntelliJ) informuje, że wywołanie metody jest tutaj niejednoznaczne. Jednak zgodnie ze specyfikacją języka Java nie jest to niejednoznaczne, ponieważ jedna z dwóch metod jest tutaj najbardziej specyficzna.Wyrażenia lambda niejawnie wpisane są obsługiwane w Javie inaczej niż wyrażenia lambda typowo wpisane . W domniemanym typowaniu, w przeciwieństwie do jawnie wpisanych wyrażeń lambda, przechodzi się przez pierwszą fazę w celu identyfikacji metod ścisłego wywoływania (patrz specyfikacja języka Java jls-15.12.2.2 , pierwszy punkt). Dlatego wywołanie metody tutaj jest dwuznaczne dla niejawnie wpisanego wyrażeń lambda.
W twoim przypadku obejściem tego
javac
błędu jest określenie rodzaju interfejsu funkcjonalnego zamiast używania jawnie wpisanego wyrażenia lambda w następujący sposób:lub
Oto Twój przykład dodatkowo zminimalizowany do testowania:
źródło
default
metodę, gdy istnieją trzy metody kandydujące lub gdy obie metody są zadeklarowane w tym samym interfejsie.forEach(Consumer)
iforEach(Consumer2)
które nigdy nie kończą się tą samą metodą implementacji.Kod, w którym Eclipse implementuje JLS § 15.12.2.5, nie uznaje żadnej metody za bardziej szczegółową od drugiej, nawet w przypadku jawnie wpisanej lambda.
Idealnie więc Eclipse zatrzyma się tutaj i zgłosi dwuznaczność. Niestety, implementacja rozwiązania problemu przeciążenia zawiera nietuzinkowy kod oprócz implementacji JLS. Z mojego zrozumienia tego kodu (który pochodzi z czasu, gdy Java 5 była nowa) należy zachować, aby wypełnić pewne luki w JLS.
Złożyłem https://bugs.eclipse.org/562538 aby to śledzić.
Niezależnie od tego konkretnego błędu, mogę tylko zdecydowanie odradzać ten styl kodu. Przeciążenie jest dobre dla wielu niespodzianek w Javie, pomnożone przez wnioskowanie typu lambda, złożoność jest dość nieproporcjonalna do postrzeganego zysku.
źródło