Czy to poprawna Java?

Czy to poprawna Java?

import java.util.Arrays;
import java.util.List;

class TestWillThatCompile {

    public static String f(List<String> list) {
        System.out.println("strings");
        return null;
    }

    public static Integer f(List<Integer> list) {
        System.out.println("numbers");
        return null;
    }

    public static void main(String[] args) {
        f(Arrays.asList("asdf"));
        f(Arrays.asList(123));
    }

}
  • Eclipse 3.5 mówi Tak
  • Eclipse 3.6 mówi Nie
  • Intellij 9 mówi Tak
  • Sun javac 1.6.0_20 mówi Tak
  • GCJ 4.4.3 mówi Tak
  • kompilator GWT mówi Tak
  • tłum przy moim poprzednie pytanie Stackoverflow mówi Nie

Moje rozumienie teorii Javy mówi Nie !

Byłoby ciekawie wiesz, co mówi o tym JLS.

Author: Community, 2010-06-24

10 answers

To zależy od tego, jak chcesz nazwać te metody. Jeśli chcesz wywołać te metody z innego kodu źródłowego Javy, to jest ona uważana za nieprawidłową z powodów przedstawionych w odpowiedzi Edwina . Jest to ograniczenie języka Java.

Jednak nie wszystkie klasy muszą być generowane z kodu źródłowego Javy (rozważ wszystkie języki, które używają JVM jako swojego środowiska wykonawczego: JRuby, Jython, itd...). na poziomie kodu bajtowego , JVM może disambiguate the two metody, ponieważ instrukcje kodu bajtowego określają typ , którego oczekują. Na przykład, tutaj jest klasa napisana w Jasmin , która może wywołać jedną z tych metod:

.class public CallAmbiguousMethod
.super java/lang/Object

.method public static main([Ljava/lang/String;)V
  .limit stack 3
  .limit locals 1

  ; Call the method that returns String
  aconst_null
  invokestatic   TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/String;

  ; Call the method that returns Integer
  aconst_null
  invokestatic   TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/Integer;

  return

.end method

Kompiluję go do pliku klasy używając następującego polecenia:

java -jar jasmin.jar CallAmbiguousMethod.j

I wywołaj go używając:

java CallAmbiguousMethod

Oto wyjście:

> java CallAmbiguousMethod
strings
numbers

Update

Simon napisał przykładowy program, który wywołuje te metody:

import java.util.Arrays;
import java.util.List;

class RealyCompilesAndRunsFine {

    public static String f(List<String> list) {
        return list.get(0);
    }

    public static Integer f(List<Integer> list) {
        return list.get(0);
    }

    public static void main(String[] args) {
        final String string = f(Arrays.asList("asdf"));
        final Integer integer = f(Arrays.asList(123));
        System.out.println(string);
        System.out.println(integer);
    }

}

Tutaj generowany jest bajt Javy:

>javap -c RealyCompilesAndRunsFine
Compiled from "RealyCompilesAndRunsFine.java"
class RealyCompilesAndRunsFine extends java.lang.Object{
RealyCompilesAndRunsFine();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."":()V
   4:   return

public static java.lang.String f(java.util.List);
  Code:
   0:   aload_0
   1:   iconst_0
   2:   invokeinterface #2,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   7:   checkcast       #3; //class java/lang/String
   10:  areturn

public static java.lang.Integer f(java.util.List);
  Code:
   0:   aload_0
   1:   iconst_0
   2:   invokeinterface #2,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   7:   checkcast       #4; //class java/lang/Integer
   10:  areturn

public static void main(java.lang.String[]);
  Code:
   0:   iconst_1
   1:   anewarray       #3; //class java/lang/String
   4:   dup
   5:   iconst_0
   6:   ldc     #5; //String asdf
   8:   aastore
   9:   invokestatic    #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   12:  invokestatic    #7; //Method f:(Ljava/util/List;)Ljava/lang/String;
   15:  astore_1
   16:  iconst_1
   17:  anewarray       #4; //class java/lang/Integer
   20:  dup
   21:  iconst_0
   22:  bipush  123
   24:  invokestatic    #8; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   27:  aastore
   28:  invokestatic    #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   31:  invokestatic    #9; //Method f:(Ljava/util/List;)Ljava/lang/Integer;
   34:  astore_2
   35:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;
   38:  aload_1
   39:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   42:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;
   45:  aload_2
   46:  invokevirtual   #12; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   49:  return

Okazuje się, że kompilator Sun generuje kod bajtowy niezbędny do rozróżnienia metod (patrz instrukcje 12 i 31 w ostatniej metodzie).

Aktualizacja #2

Specyfikacja języka Java sugeruje, że w rzeczywistości może to być poprawny kod źródłowy Javy. Na stronie 449 (§15.12 wyrażenia wywołania metody) widzimy to:

Jest możliwe, że żadna metoda nie jest najbardziej konkretna, ponieważ tam są dwa lub więcej metod, które są maksymalnie specyficzne. W tym przypadku:

  • jeśli wszystkie maksymalnie specyficzne metody mają równoważne (§8.4.2) podpisy, wtedy:
    • jeśli dokładnie jedna z maksymalnie specyficznych metod nie jest zadeklarowana jako abstrakcyjna, jest to najbardziej specyficzna metoda.
    • W przeciwnym razie, jeśli wszystkie metody maksymalnie specyficzne są zadeklarowane jako abstrakcyjne, i podpisy wszystkich maksymalnie specyficznych metod mają takie same kasowanie (§4.6), wtedy najbardziej konkretną metodę wybiera się arbitralnie spośród podzbiór maksymalnie specyficznych metod, które mają najbardziej specyficzne return type . Uważa się jednak, że najbardziej specyficzną metodą jest rzucanie zaznaczony wyjątek wtedy i tylko wtedy, gdy ten wyjątek lub jego usunięcie jest zadeklarowane w klauzule rzutów każdej z maksymalnie specyficznych metod.
  • inaczej mówimy, że wywołanie metody jest niejednoznaczne, a składnia występuje błąd.

O ile się nie mylę, to zachowanie powinno odnosić się tylko do metod deklarowanych jako abstrakcyjne...

Aktualizacja #3

Dzięki komentarzowi Ilmtitana:

@ Adam Paynter: Twój pogrubiony tekst robi nie ma znaczenia, bo to tylko przypadek gdy dwie metody są override-odpowiednik, który dan pokazał tak nie było. Tak więc, czynnikiem decydującym musi być, jeśli JLS bierze pod uwagę rodzaje generyczne, gdy określanie najbardziej szczegółowych metoda. – ILMTitan
 29
Author: Adam Paynter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:29:38

- - - edytowane w odpowiedzi na komentarze poniżej - - -

Ok, więc jest poprawna Java, ale nie powinna. Kluczem jest to, że tak naprawdę nie zależy od typu powrotu, ale od wymazanego parametru generycznego.

To nie działa na niestatycznej metodzie i jest wyraźnie zabronione na niestatycznej metodzie. Próba zrobienia tego w klasie nie powiodłaby się z powodu dodatkowych problemów, po pierwsze, typowa klasa nie jest ostateczna jak klasa {7]}.

To niekonsekwencja w skądinąd dość spójnym języku. TI zaryzykuje i powie, że to powinno być nielegalne, nawet jeśli technicznie jest dozwolone. To tak naprawdę nic nie dodaje do czytelności języka i dodaje niewiele do zdolności do rozwiązywania znaczących problemów. Jedynym znaczącym problemem, który wydaje się rozwiązać, jest to, czy jesteś na tyle zaznajomiony z językiem, aby wiedzieć, kiedy jest rdzeń Zasady wydają się być naruszane przez wewnętrzne niespójności języka w rozwiązywaniu wymazywania typów, generyków i wynikowych sygnatur metod.

Zdecydowanie należy unikać kodu, ponieważ trywialne jest rozwiązanie tego samego problemu na wiele bardziej znaczących sposobów, a jedyną korzyścią jest sprawdzenie, czy recenzent / extender zna zakurzony, brudny kącik specyfikacji języka.

- - - Original Post follows - - -

Chociaż Kompilatory mogły na to pozwolić, odpowiedź nadal brzmi nie.

Erasure zmieni zarówno listę , jak i listę w lista nierozpoznana. Oznacza to, że obie metody " f " będą miały ten sam podpis, ale różne typy zwrotów. Return type nie może być użyty do rozróżnienia metod, ponieważ nie powiedzie się, gdy powrócisz do wspólnego super-typu; jak:

Object o = f(Arrays.asList("asdf"));

Próbowałeś przechwycić zwrócone wartości do zmiennych? Być może kompilator zoptymalizował rzeczy w taki sposób, że nie nadepnął na właściwy kod błędu.

 13
Author: Edwin Buck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-24 16:27:06

Jedno pytanie, na które nie udzielono odpowiedzi brzmi: dlaczego wywołuje błąd kompilacji tylko w Eclipse 3.6?

Oto dlaczego: jest to funkcja .

W javac 7 rozważane są dwie metody duplikaty (lub błąd kolizji nazwy) niezależnie od ich typów zwrotów.

To zachowanie jest teraz bardziej spójne z javac 1.5, który podał nazwę błędy kolizji na metodach i ignorowane ich typy zwrotów. Tylko w 1.6 był dokonana zmiana obejmowała zwrot rodzaje podczas wykrywania zduplikowanych metod.

Postanowiliśmy wprowadzić tę zmianę na wszystkich poziomach zgodności (1.5, 1.6, 1.7) w wydaniu 3.6, więc użytkownicy nie będą zaskoczeni zmianą, jeśli kompilują swój kod używając javac 7.

 11
Author: Peter Schwarz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-25 11:01:24

Cóż, jeśli dobrze rozumiem punkt trzeci z pierwszej listy z sekcji 8.4.2 spec, to mówi, że Twoje metody f () mają ten sam podpis:

Http://java.sun.com/docs/books/jls/third_edition/html/classes.html#38649

To spec naprawdę odpowiada na to pytanie, a nie obserwowane zachowanie kompilatora X lub IDE X. Wszystko, co możemy powiedzieć patrząc na Narzędzia, to jak autor narzędzia zinterpretował spec.

Jeśli zastosujemy punkt trzeci, to get:

...
    public static String f(List<String> list) {
        System.out.println("strings");
        return null;
    }

    public static Integer f(List<String> list) {
        System.out.println("numbers");
        return null;
    }
...

I podpisy się zgadzają, więc jest kolizja i Kod nie powinien się kompilować.

 5
Author: Don Branson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-24 13:39:26

Jest ważny na podstawie specyfikacji.

Sygnatura metody m1 jest podpisy podpisu a metoda m2 jeżeli

  • m2 posiada ten sam podpis co m1 lub

  • Podpis m1 jest taki sam jak wymazanie podpisu m2.

Więc nie są to podzbiory siebie, bo wymazanie List<String> nie jest List<Integer> i vice versa.

Dwa podpisy metody m1 i m2 są override-odpowiednik iff m1 jest subsignature of m2 or m2 is a subsignature of m1.

Więc te dwa nie są równoważne (zwróć uwagę na iff ). A zasada przeciążenia jest następująca:

Jeśli dwie metody klasy (czy oba zgłoszone w tej samej klasie, lub zarówno dziedziczone przez klasę, jak i jedną deklarowane i dziedziczone) mają ta sama nazwa, ale podpisy które nie są override-odpowiednik, wtedy metoda mówi się, że nazwa jest przeciążona.

Dlatego te dwie metody są przeciążone i wszystko powinno działać.

 5
Author: Dave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-24 13:40:48

Działa również (tym razem z sun java 1.6.0_16)

import java.util.Arrays;
import java.util.List;

class RealyCompilesAndRunsFine {

    public static String f(List<String> list) {
        return list.get(0);
    }

    public static Integer f(List<Integer> list) {
        return list.get(0);
    }

    public static void main(String[] args) {
        final String string = f(Arrays.asList("asdf"));
        final Integer integer = f(Arrays.asList(123));
        System.out.println(string);
        System.out.println(integer);
    }

}
 1
Author: lacroix1547,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-24 13:10:51

Z tego co wiem ... plik klasy może zawierać obie metody, ponieważ deskryptor metody zawiera zarówno parametry, jak i typ zwracany. Jeśli typ zwracany byłby taki sam, to deskryptory byłyby takie same, a metody byłyby nie do odróżnienia po usunięciu typu (stąd też nie działa to z void). http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#7035

Teraz wywołanie metody z invoke_virtual wymaga metody deskryptor, więc możesz w rzeczywistości powiedzieć, którą z metod chcesz wywołać, więc wydaje się, że wszystkie te Kompilatory, które nadal mają Informacje ogólne, po prostu umieścić deskryptor dla metody, która pasuje do ogólnego typu parametru, więc wtedy jest zakodowany na twardo w bajtkodzie, która metoda do wywołania (co odróżnia się ich deskryptorami, lub dokładniej przez typ zwracania w tych deskryptorach), nawet jeśli parametr jest teraz listą, bez informacji ogólnych.

While I find ta praktyka jest trochę wątpliwa, muszę powiedzieć, że uważam to za fajne, że możesz to zrobić i myślę, że leki generyczne powinny być zaprojektowane tak, aby mogły działać w ten sposób w pierwszej kolejności (tak, Wiem, że spowodowałoby to problemy z kompatybilnością wsteczną).

 1
Author: Andrei Fierbinteanu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-24 13:29:31

Wnioskowanie typu Java (co się dzieje, gdy wywołujesz statyczne, ogólne metody, takie jak Array.asList) jest skomplikowana i nie jest dobrze określona w JLS. Ten artykuł z 2008 roku ma bardzo ciekawy opis niektórych problemów i jak można je naprawić:

Wnioskowanie typu Java jest zepsute: jak możemy to naprawić?

 1
Author: Nels Beckman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-24 14:16:48

Eclipse może wygenerować kod bajtowy z tego:

public class Bla {
private static BigDecimal abc(List<BigDecimal> l) {
    return l.iterator().next().multiply(new BigDecimal(123));
}

private static String abc(List<String> l) {
    return l.iterator().next().length() + "";
}

public static void main(String[] args) {
    System.out.println(abc(Arrays.asList("asdf")));
    System.out.println(abc(Arrays.<BigDecimal>asList(new BigDecimal(123))));
}
}

Wyjście:

4

15129

 0
Author: tkr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-24 13:27:04

Wygląda na to, że kompilator wybiera najbardziej specyficzną metodę opartą na generykach.

import java.util.Arrays;
import java.util.List;

class TestWillThatCompile {

public static Object f(List<?> list) {
    System.out.println("strings");
    return null;
}

public static Integer f(List<Integer> list) {
    System.out.println("numbers");
    return null;
}

public static void main(String[] args) {
    f(Arrays.asList("asdf"));
    f(Arrays.asList(123));
}

}

Wyjście:

strings
numbers
 0
Author: Denis Tulskiy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-25 13:53:13