Come notato da @FrustratedWithFormsDesigner, la compatibilità all'indietro era l'unico grosso ostacolo. E la risoluzione che scelsero fu di mantenere la compatibilità con le versioni precedenti usando tipo di cancellazione , cioè rimuovendo tutte le informazioni di tipo generico durante la compilazione. Ciò significa che List<String>
e List<Integer>
diventeranno lo stesso tipo in fase di esecuzione (e lo stesso di un pre-Java5 List
). E ha molte conseguenze negative, inclusa la maggior parte di ciò che hai elencato sopra.
Aggiornamento
I vincoli impostati da questo sono molto più stringenti di quanto si possa pensare ingenuamente:
What we require is that the same client code works with both the legacy and generic versions of a library. This means that the supplier and clients of a library can make completely independent choices about when to move from legacy to generic code. This is a much stronger requirement than backward compatibility; it is called migration compatibility or platform compatibility.
Java implements generics via erasure, which ensures that legacy and generic versions usually generate identical class files, save for some auxiliary information about types. It is possible to replace a legacy class file by a generic class file without changing, or even recompiling, any client code; this is called binary compatibility.
( Generics e raccolte Java , capitolo 5)
La compatibilità binaria non è sempre automaticamente garantita; la sezione 8.4 dello stesso libro descrive alcuni casi in cui la compatibilità binaria può rompersi durante la generalizzazione delle interfacce legacy anche se sono state seguite tutte le regole generali.
Un altro aspetto della retrocompatibilità è l'aderenza a un sistema di tipi in cui non tutto è un oggetto. Quindi in Java i tipi primitivi sono fuori portata per i generici proprio come per le collezioni, e si ha boxing / unboxing per complicare la vita (mentre in Scala tutto è un oggetto).
Una terza eredità in Java è costituita da array nativi che non si adattano semplicemente ai generici:
Gli array - sono sempre tipi modificabili, mentre i tipi generici no;
Gli array - possono memorizzare tipi primitivi mentre le raccolte (generiche) non possono;
- il sottotipo dell'array è covariante, mentre il sottotipo generico è invariante;
- di conseguenza, gli array sono meno sicuri del tipo dei generici e si può sostenere che dovrebbero essere dichiarati obsoleti a favore delle raccolte generiche e non utilizzati nel nuovo codice.
Tutto ciò significa molte più regole da rispettare, quindi casi angusti e compromessi da fare, limitando la potenza e la semplicità dei generici Java.
Diverse decisioni di questo tipo possono essere viste a posteriori come errate, ma non c'è più modo di tornare indietro, quindi nessuna possibilità di risolverle senza compromettere la compatibilità con le versioni precedenti, che - giustamente o meno - è diventata una sorta di santa mucca in Java anni. Immagino che la storia dei generici di Java abbia probabilmente portato molta esperienza e spunti di riflessione per i suoi stessi designer, e queste esperienze sono state sicuramente riutilizzate durante la progettazione di Scala.