È valido Java?

È valido Java?

import java.util.Arrays; import java.util.List; class TestWillThatCompile { public static String f(List list) { System.out.println("strings"); return null; } public static Integer f(List list) { System.out.println("numbers"); return null; } public static void main(String[] args) { f(Arrays.asList("asdf")); f(Arrays.asList(123)); } } 
  • Eclipse 3.5 dice
  • Eclipse 3.6 dice no
  • Intellij 9 dice di
  • Sun javac 1.6.0_20 dice
  • GCJ 4.4.3 dice
  • Il compilatore GWT dice di
  • La folla alla mia precedente domanda StackOverflow dice no

La mia comprensione della teoria java dice no !

Sarebbe interessante sapere cosa sta dicendo il JLS a riguardo.

Dipende da come desideri chiamare questi metodi. Se si desidera chiamare questi metodi da un altro codice sorgente Java , allora è considerato non valido per le ragioni illustrate nella risposta di Edwin . Questa è una limitazione del linguaggio Java.

Tuttavia, non tutte le classi devono essere generate dal codice sorgente Java (considerare tutte le lingue che utilizzano la JVM come runtime: JRuby, Jython, ecc.). A livello di bytecode , la JVM può disambiguare i due metodi perché le istruzioni bytecode specificano il tipo di ritorno che si aspettano. Ad esempio, ecco una class scritta in Jasmin che può chiamare uno di questi metodi:

 .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 

Lo compilo in un file di class usando il seguente comando:

 java -jar jasmin.jar CallAmbiguousMethod.j

E chiamalo usando:

 java CallAmbiguousMethod

Ecco, l’output è:

 > java CallAmbiguousMethod
 stringhe
 numeri

Aggiornare

Simon ha pubblicato un programma di esempio che chiama questi metodi:

 import java.util.Arrays; import java.util.List; class RealyCompilesAndRunsFine { public static String f(List list) { return list.get(0); } public static Integer f(List 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); } } 

Ecco il bytecode Java generato:

 > javap -c RealyCompilesAndRunsFine
 Compilato da "RealyCompilesAndRunsFine.java"
 class RealyCompilesAndRunsFine estende java.lang.Object {
 RealyCompilesAndRunsFine ();
   Codice:
    0: aload_0
    1: invokespecial # 1;  // Metodo java / lang / Object. "" :() V
    4: ritorno

 public static java.lang.String f (java.util.List);
   Codice:
    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);
   Codice:
    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 []);
   Codice:
    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;  // Metodo java / util / Arrays.asList: ([Ljava / lang / Object;) Ljava / util / List;
    12: invokestatic # 7;  // Metodo 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;  // Metodo java / lang / Integer.valueOf: (I) Ljava / lang / Integer;
    27: aastore
    28: invokestatic # 6;  // Metodo java / util / Arrays.asList: ([Ljava / lang / Object;) Ljava / util / List;
    31: invokestatic # 9;  // Metodo 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;  // Metodo 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;  // Metodo java / io / PrintStream.println: (Ljava / lang / Object;) V
    49: ritorno

Risulta che il compilatore Sun sta generando il bytecode necessario per disambiguare i metodi (vedere le istruzioni 12 e 31 nell’ultimo metodo).

Aggiornamento n. 2

La specifica del linguaggio Java suggerisce che questo potrebbe, in effetti, essere valido codice sorgente Java. Nella pagina 449 (§15.12 Method Invocation Expressions) vediamo questo:

È ansible che nessun metodo sia il più specifico, perché ci sono due o più metodi che sono massimamente specifici. In questo caso:

  • Se tutti i metodi massimamente specifici hanno firme override-equivalenti (§8.4.2), allora:
    • Se esattamente uno dei metodi massimamente specifici non è dichiarato astratto, è il metodo più specifico.
    • Altrimenti, se tutti i metodi massimamente specifici sono dichiarati astratti e le firme di tutti i metodi massimamente specifici hanno la stessa cancellazione (§4.6), allora il metodo più specifico è scelto arbitrariamente tra il sottoinsieme dei metodi massimamente specifici che hanno il tipo di ritorno più specifico . Tuttavia, si considera che il metodo più specifico lanci un’eccezione controllata se e solo se tale eccezione o la sua cancellazione sono dichiarate nelle clausole di tiro di ciascuno dei metodi massimamente specifici.
  • Altrimenti, diciamo che l’invocazione del metodo è ambigua e si verifica un errore di compilazione.

A meno che non mi sbagli, questo comportamento dovrebbe applicarsi solo ai metodi dichiarati come astratti, sebbene …

Aggiornamento n. 3

Grazie al commento di ILMTitan:

@Adam Paynter: il tuo testo in grassetto non ha importanza, perché è solo un caso in cui due metodi sono equivalenti alla sovrascrittura, cosa che Dan ha mostrato non era il caso. Pertanto, il fattore determinante deve essere se il JLS prende in considerazione i tipi generici quando determina il metodo più specifico. – ILMTitan

— Modificato in risposta ai commenti qui sotto —

Ok, quindi è valido Java, ma non dovrebbe essere. La chiave è che non si basa sul tipo di ritorno, ma sul parametro Generics cancellato.

Questo non funzionerebbe con un metodo non statico ed è esplicitamente vietato su un metodo non statico. Tentare di farlo in una class fallirebbe a causa di problemi extra, il primo è che una class tipica non è definitiva come la class Class .

È un’incongruenza in un linguaggio altrimenti piuttosto coerente. TI uscirò su un ramo e dirò che dovrebbe essere illegale, anche se tecnicamente permesso. In realtà non aggiunge nulla alla leggibilità del linguaggio, e aggiunge poco alla capacità di risolvere problemi significativi. L’unico problema significativo che sembra risolvere è se si è abbastanza familiari con il linguaggio da sapere quando i principi fondamentali sembrano essere violati dalle incoerenze interne del linguaggio nella risoluzione della cancellazione dei tipi, dei generici e delle firme dei metodi risultanti.

Sicuramente codice da evitare, poiché è banale risolvere lo stesso problema in un numero qualsiasi di modi più significativi, e l’unico vantaggio è vedere se il revisore / estensore conosce un angolo sporco e polveroso delle specifiche del linguaggio.

— Post originale segue —

Mentre i compilatori potrebbero averlo permesso, la risposta è ancora no.

La cancellazione trasformsrà sia la lista che la lista in una lista non decorata. Ciò significa che entrambi i metodi “f” avranno la stessa firma ma tipi di ritorno diversi. Il tipo di restituzione non può essere utilizzato per differenziare i metodi, perché così facendo si fallisce quando si ritorna in un super-tipo comune; piace:

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

Hai provato a catturare i valori restituiti in variabili? Forse il compilatore ha ottimizzato le cose in modo tale che non stia calpestando il giusto codice di errore.

Una persona a cui non è stata data una risposta è: perché fa scattare solo un errore di compilazione in Eclipse 3.6?

Ecco perché: è una funzionalità .

In javac 7, due metodi sono considerati duplicati (o un errore di conflitto di nomi) indipendentemente dal loro tipo di ritorno.

Questo comportamento è ora più coerente con javac 1.5, che ha segnalato il nome di errori di scontro sui metodi e ignorato i loro tipi di ritorno. Solo nella versione 1.6 è stata apportata una modifica che includeva i tipi di ritorno quando si rilevavano metodi duplicati.

Abbiamo deciso di apportare questa modifica a tutti i livelli di conformità (1.5, 1.6, 1.7) nella versione 3.6 in modo che gli utenti non siano sorpresi dal cambiamento se compilano il codice utilizzando javac 7.

Bene, se capisco correttamente il punto 3 di bullet point nel primo elenco della sezione 8.4.2 delle specifiche, i tuoi metodi f () hanno la stessa firma:

http://java.sun.com/docs/books/jls/third_edition/html/classs.html#38649

È la specifica che risponde veramente a questa domanda, e non il comportamento osservato del compilatore X o IDE X. Tutto quello che possiamo dire osservando gli strumenti è come l’autore dello strumento ha interpretato le specifiche.

Se applichiamo il terzo punto, otteniamo:

 ...
     public static String f (Elenco  elenco) {
         System.out.println ( "stringhe");
         return null;
     }

     intero statico pubblico f (elenco  elenco) {
         System.out.println ( "numeri");
         return null;
     }
 ...

e le firme corrispondono, quindi c’è una collisione e il codice non dovrebbe essere compilato.

È valido in base alle specifiche .

La firma di un metodo m1 è una sottosegnalazione della firma di un metodo m2 se entrambi

  • m2 ha la stessa firma di m1 , o

  • la firma di m1 è la stessa della cancellazione della firma di m2 .

Quindi queste non sono le secondarie l’una dell’altra perché la cancellazione di List non è List e viceversa.

Le due firme dei metodi m1 e m2 sono equivalenti a override se m1 è una sottocalibrazione di m2 o m2 è una sottosegnalazione di m1 .

Quindi questi due non sono override-equivalenti (si noti l’ iff ). E la regola per il sovraccarico è:

Se due metodi di una class (sia dichiarati nella stessa class, sia entrambi ereditati da una class, o uno dichiarato e uno ereditato) hanno lo stesso nome ma le firme che non sono equivalenti alla sovrascrittura, allora si dice che il nome del metodo sia sovraccarico.

Pertanto, questi due metodi sono sovraccarichi e tutto dovrebbe funzionare.

Anche lavorando (con sun java 1.6.0_16 questa volta) lo è

 import java.util.Arrays; import java.util.List; class RealyCompilesAndRunsFine { public static String f(List list) { return list.get(0); } public static Integer f(List 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); } } 

Da quello che posso dire il file .class può contenere entrambi i metodi poiché il descrittore del metodo detiene i parametri, oltre al tipo restituito. Se il tipo restituito fosse lo stesso, i descrittori sarebbero uguali ei metodi sarebbero indistinguibili dopo la cancellazione del tipo (quindi non funziona neanche con il vuoto). http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#7035

Ora, chiamare il metodo con invoke_virtual richiede il descrittore del metodo, quindi in effetti puoi dire quale dei metodi vuoi chiamare, quindi sembra che tutti quei compilatori, che hanno ancora le informazioni generiche, semplicemente mettano il descrittore per il metodo che corrisponde al tipo generico del parametro, quindi è codificato nel bytecode quale metodo chiamare (come distinto dai descrittori, o più esattamente dal tipo restituito in quei descrittori), anche se il parametro è ora un elenco, senza generico informazione.

Mentre trovo questa pratica un po ‘discutibile, devo dire che trovo fantastico che tu possa fare questo, e pensare che i generici dovrebbero essere stati progettati per essere in grado di lavorare in questo modo in primo luogo (sì, lo so che sarebbe creare problemi con la retrocompatibilità).

L’inferenza di tipo Java (cosa succede quando chiami metodi generici e statici come Array.asList) è complicata e non ben specificata in JLS. Questo documento del 2008 ha una descrizione molto interessante di alcuni dei problemi e di come potrebbe essere risolto:

Inferenza di tipo Java è interrotta: come possiamo risolvere il problema?

Eclipse può produrre codice byte fuori da questo:

 public class Bla { private static BigDecimal abc(List l) { return l.iterator().next().multiply(new BigDecimal(123)); } private static String abc(List l) { return l.iterator().next().length() + ""; } public static void main(String[] args) { System.out.println(abc(Arrays.asList("asdf"))); System.out.println(abc(Arrays.asList(new BigDecimal(123)))); } } 

Produzione:

4

15129

Sembra che il compilatore scelga il metodo più specifico basato sui generici.

 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 list) { System.out.println("numbers"); return null; } public static void main(String[] args) { f(Arrays.asList("asdf")); f(Arrays.asList(123)); } 

}

Produzione:

 strings numbers