Quali sono i vantaggi della cancellazione dei tipi di Java?


98

Oggi ho letto un tweet che diceva:

È divertente quando gli utenti Java si lamentano della cancellazione del tipo, che è l'unica cosa che Java ha corretto, ignorando tutte le cose che ha sbagliato.

Quindi la mia domanda è:

Ci sono vantaggi dalla cancellazione del tipo di Java? Quali sono i vantaggi tecnici o di stile di programmazione che (eventualmente) offre oltre alla preferenza per le implementazioni JVM per la compatibilità con le versioni precedenti e le prestazioni di runtime?


6
Se non vuoi che la tua domanda venga chiusa come "basata sull'opinione", dovresti chiederla in modo diverso. (Come "quali sono i vantaggi tecnici della cancellazione del tipo"). Meglio ancora, chiedi al tweeter cosa sta realmente dicendo.
Stephen C

3
Rimuovere il "perché è una buona cosa". La "cosa buona" è una caratterizzazione palesemente soggettiva, e (ovviamente) solleva la questione che la cancellazione del tipo sia una cosa buona ... il che è controverso.
Stephen C

3
Dio ci aiuti se questo sito degenererà in domande su tutto ciò che è mai stato pubblicato su Twitter. Almeno cita una fonte attendibile per la tua domanda.
Marchese di Lorne

21
Perché la domanda deve provenire da una "fonte attendibile"? È solo sciocco.
vertti

4
Ebbene, la maggior parte dei problemi qui non provengono da NESSUNA fonte, eccetto il problema di chi ha interrogato con software / linguaggio / quant'altro specifico, e fortunatamente ci sono molti professionisti che "sprecano" il loro tempo aiutando queste persone.
vertti

Risposte:


90

La cancellazione del tipo è buona

Atteniamoci ai fatti

Molte delle risposte finora riguardano eccessivamente l'utente Twitter. È utile rimanere concentrati sui messaggi e non sul messenger. C'è un messaggio abbastanza coerente anche solo con gli estratti menzionati finora:

È divertente quando gli utenti Java si lamentano della cancellazione del tipo, che è l'unica cosa che Java ha corretto, ignorando tutte le cose che ha sbagliato.

Ottengo enormi vantaggi (ad es. Parametricità) e costo nullo (il presunto costo è un limite dell'immaginazione).

new T è un programma rotto. È isomorfico all'affermazione "tutte le proposizioni sono vere". Non sono grande in questo.

Un obiettivo: programmi ragionevoli

Questi tweet riflettono una prospettiva che non è interessata a sapere se possiamo far fare qualcosa alla macchina , ma più se possiamo ragionare che la macchina farà qualcosa che vogliamo realmente. Un buon ragionamento è una prova. Le dimostrazioni possono essere specificate in notazione formale o qualcosa di meno formale. Indipendentemente dal linguaggio delle specifiche, devono essere chiari e rigorosi. Le specifiche informali non sono impossibili da strutturare correttamente, ma sono spesso difettose nella programmazione pratica. Finiamo con rimedi come test automatizzati ed esplorativi per compensare i problemi che abbiamo con il ragionamento informale. Questo non vuol dire che il test sia intrinsecamente una cattiva idea, ma il citato utente di Twitter suggerisce che esiste un modo molto migliore.

Quindi il nostro obiettivo è avere programmi corretti su cui ragionare in modo chiaro e rigoroso in un modo che corrisponda a come la macchina eseguirà effettivamente il programma. Questo, però, non è l'unico obiettivo. Vogliamo anche che la nostra logica abbia un certo grado di espressività. Ad esempio, c'è solo così tanto che possiamo esprimere con la logica proposizionale. È bello avere una quantificazione universale (∀) ed esistenziale (∃) da qualcosa come la logica del primo ordine.

Utilizzo di sistemi di tipo per il ragionamento

Questi obiettivi possono essere affrontati molto bene dai sistemi di tipo. Ciò è particolarmente chiaro a causa della corrispondenza Curry-Howard . Questa corrispondenza è spesso espressa con la seguente analogia: i tipi stanno ai programmi come i teoremi stanno alle dimostrazioni.

Questa corrispondenza è piuttosto profonda. Possiamo prendere espressioni logiche e tradurle attraverso la corrispondenza ai tipi. Quindi, se abbiamo un programma con lo stesso tipo di firma che compila, abbiamo dimostrato che l'espressione logica è universalmente vera (una tautologia). Questo perché la corrispondenza è bidirezionale. La trasformazione tra i mondi tipo / programma e teorema / dimostrazione è meccanica e in molti casi può essere automatizzata.

Curry-Howard si adatta bene a ciò che vorremmo fare con le specifiche per un programma.

I sistemi di tipi sono utili in Java?

Anche con una comprensione di Curry-Howard, alcune persone trovano facile ignorare il valore di un sistema di tipi, quando

  1. è estremamente difficile lavorare con
  2. corrisponde (attraverso Curry-Howard) a una logica con espressività limitata
  3. è rotto (che arriva alla caratterizzazione dei sistemi come "deboli" o "forti").

Per quanto riguarda il primo punto, forse gli IDE rendono il sistema di tipi di Java abbastanza facile da lavorare (questo è altamente soggettivo).

Per quanto riguarda il secondo punto, Java sembra quasi corrispondere a una logica del primo ordine. I generici danno l'uso del sistema di tipi equivalente alla quantificazione universale. Sfortunatamente, i caratteri jolly ci danno solo una piccola frazione di quantificazione esistenziale. Ma la quantificazione universale è un buon inizio. È bello poter dire che funzioni per il List<A>lavoro universalmente per tutti gli elenchi possibili perché A è completamente libero da vincoli. Questo porta a ciò di cui parla l'utente Twitter rispetto alla "parametricità".

Un articolo spesso citato sulla parametricità è i Teoremi di Philip Wadler gratuitamente! . La cosa interessante di questo articolo è che dalla sola firma del tipo, possiamo provare alcune invarianti molto interessanti. Se dovessimo scrivere test automatizzati per questi invarianti, sprecheremmo molto tempo. Ad esempio, per List<A>, dalla sola firma del tipo perflatten

<A> List<A> flatten(List<List<A>> nestedLists);

possiamo ragionarlo

flatten(nestedList.map(l -> l.map(any_function)))
     flatten(nestList).map(any_function)

Questo è un semplice esempio, e probabilmente puoi ragionarci in modo informale, ma è ancora più bello quando otteniamo tali prove formalmente gratuitamente dal sistema di tipi e controllate dal compilatore.

La mancata cancellazione può portare ad abusi

Dal punto di vista dell'implementazione del linguaggio, i generici di Java (che corrispondono ai tipi universali) giocano molto pesantemente sulla parametricità utilizzata per ottenere prove su ciò che fanno i nostri programmi. Questo arriva al terzo problema menzionato. Tutti questi guadagni di prova e correttezza richiedono un sistema di tipo suono implementato senza difetti. Java ha sicuramente alcune caratteristiche del linguaggio che ci permettono di mandare in frantumi il nostro ragionamento. Questi includono ma non sono limitati a:

  • effetti collaterali con un sistema esterno
  • riflessione

I farmaci generici non cancellati sono per molti versi legati alla riflessione. Senza cancellazione ci sono informazioni di runtime che vengono trasportate con l'implementazione che possiamo usare per progettare i nostri algoritmi. Ciò significa che staticamente, quando ragioniamo sui programmi, non abbiamo il quadro completo. La riflessione minaccia gravemente la correttezza di tutte le prove su cui ragioniamo staticamente. Non è un caso che la riflessione porti anche a una serie di difetti complicati.

Quindi quali sono i modi in cui i farmaci generici non cancellati potrebbero essere "utili?" Consideriamo l'utilizzo menzionato nel tweet:

<T> T broken { return new T(); }

Cosa succede se T non ha un costruttore senza argomenti? In alcune lingue ciò che ottieni è nullo. O forse salti il ​​valore nullo e vai direttamente a sollevare un'eccezione (a cui comunque i valori nulli sembrano portare). Poiché il nostro linguaggio è completo di Turing, è impossibile ragionare su quali chiamate brokencoinvolgeranno tipi "sicuri" con costruttori senza argomenti e quali no. Abbiamo perso la certezza che il nostro programma funzioni universalmente.

Cancellare significa che abbiamo ragionato (quindi cancelliamo)

Quindi, se vogliamo ragionare sui nostri programmi, consigliamo vivamente di non utilizzare caratteristiche linguistiche che minacciano fortemente il nostro ragionamento. Una volta fatto ciò, perché non eliminare i tipi in fase di esecuzione? Non sono necessari. Possiamo ottenere un po 'di efficienza e semplicità con la soddisfazione che nessun cast fallirà o che i metodi potrebbero mancare durante l'invocazione.

La cancellazione incoraggia il ragionamento.


7
Nel tempo che ho impiegato per mettere insieme questa risposta, altre persone hanno risposto con risposte simili. Ma avendo scritto così tanto, ho deciso di postarlo piuttosto che scartarlo.
Sukant Hajra

32
Con tutto il rispetto, la cancellazione del tipo era un mezzo tentativo di portare una funzionalità a Java senza interrompere la compatibilità con le versioni precedenti. Non è un punto di forza. Se la preoccupazione principale era che si potesse provare a creare un'istanza di un oggetto senza un costruttore senza parametri, la risposta giusta è consentire un vincolo di "tipi con costruttori senza parametri", per non paralizzare una caratteristica del linguaggio.
Basic

5
Di base, con rispetto, non stai facendo un argomento coeso. Stai semplicemente affermando che la cancellazione è "paralizzante" con un completo disprezzo per il valore delle prove in fase di compilazione come strumento convincente per ragionare sui programmi. Inoltre, questi vantaggi sono completamente ortogonali al percorso tortuoso e alla motivazione che Java ha preso per l'implementazione dei generici. Inoltre, questi vantaggi valgono per le lingue con un'implementazione della cancellazione molto più illuminata.
Sukant Hajra

44
L'argomento presentato qui non è contro la cancellazione, è contro la riflessione (e il controllo del tipo a runtime in generale) - fondamentalmente afferma che la riflessione è negativa, quindi il fatto che la cancellazione non funzioni bene con esse è positivo. È un punto valido di per sé (anche se molte persone non sarebbero d'accordo); ma non ha molto senso nel contesto, perché Java ha già controlli di riflessione / runtime, e non lo hanno fatto e non andranno via. Quindi avere un costrutto che non gioca con quella parte esistente e non deprecata del linguaggio è un limite, non importa come lo guardi.
Pavel Minaev

10
La tua risposta è semplicemente fuori tema. Si immette una spiegazione prolissa (sebbene interessante nei suoi termini) del perché la cancellazione del tipo come concetto astratto è generalmente utile nella progettazione di sistemi di tipi, ma l'argomento è il vantaggio di una caratteristica specifica di Java Generics etichettata come "cancellazione del tipo ", che assomiglia solo superficialmente al concetto che descrivi, non riuscendo completamente a fornire invarianti interessanti e introducendo vincoli irragionevoli.
Marko Topolnik

40

I tipi sono un costrutto utilizzato per scrivere programmi in un modo che consente al compilatore di verificare la correttezza di un programma. Un tipo è una proposizione su un valore: il compilatore verifica che questa proposizione sia vera.

Durante l'esecuzione di un programma, non dovrebbero essere necessarie informazioni sul tipo - questo è già stato verificato dal compilatore. Il compilatore dovrebbe essere libero di scartare queste informazioni per eseguire l'ottimizzazione del codice: farlo funzionare più velocemente, generare un binario più piccolo, ecc. La cancellazione dei parametri di tipo facilita questo.

Java interrompe la tipizzazione statica consentendo di interrogare le informazioni sul tipo in fase di esecuzione - reflection, instanceof, ecc. Ciò consente di costruire programmi che non possono essere verificati staticamente - ignorano il sistema di tipi. Inoltre perde opportunità per l'ottimizzazione statica.

Il fatto che i parametri di tipo vengano cancellati impedisce la costruzione di alcune istanze di questi programmi non corretti, tuttavia, programmi più errati non sarebbero consentiti se venissero cancellate più informazioni sul tipo e le funzionalità di reflection e instanceof fossero rimosse.

La cancellazione è importante per mantenere la proprietà di "parametricità" di un tipo di dati. Supponiamo che io abbia un tipo "List" parametrizzato sul tipo di componente T. cioè List <T>. Quel tipo è una proposizione che questo tipo di List funziona in modo identico per qualsiasi tipo T. Il fatto che T sia un parametro di tipo astratto e illimitato significa che non sappiamo nulla di questo tipo, quindi ci è impedito di fare qualcosa di speciale per casi speciali di T.

ad esempio diciamo che ho una lista xs = asList ("3"). Aggiungo un elemento: xs.add ("q"). Finisco con ["3", "q"]. Poiché questo è parametrico, posso assumere che List xs = asList (7); xs.add (8) finisce con [7,8] So dal tipo che non fa una cosa per String e una cosa per Int.

Inoltre, so che la funzione List.add non può inventare i valori di T dal nulla. So che se il mio asList ("3") ha un "7" aggiunto, le uniche risposte possibili sarebbero costruite dai valori "3" e "7". Non è possibile aggiungere un "2" o una "z" all'elenco perché la funzione non sarebbe in grado di costruirlo. Nessuno di questi altri valori sarebbe sensato da aggiungere e la parametricità impedisce la costruzione di questi programmi errati.

Fondamentalmente, la cancellazione impedisce ad alcuni mezzi di violare la parametricità, eliminando così la possibilità di programmi errati, che è l'obiettivo della digitazione statica.


15

(Anche se ho già scritto una risposta qui, rivisitando questa domanda due anni dopo mi rendo conto che esiste un altro modo completamente diverso di rispondere, quindi lascio intatta la risposta precedente e aggiungo questa.)


È altamente discutibile se il processo eseguito su Java Generics meriti il ​​nome di "cancellazione del tipo". Poiché i tipi generici non vengono cancellati ma sostituiti con le loro controparti grezze, una scelta migliore sembra essere la "mutilazione di tipo".

La caratteristica per eccellenza della cancellazione del tipo nel suo senso comunemente inteso è costringere il runtime a rimanere entro i confini del sistema di tipi statici rendendolo "cieco" alla struttura dei dati a cui accede. Questo dà piena potenza al compilatore e gli consente di dimostrare teoremi basati solo su tipi statici. Aiuta anche il programmatore vincolando i gradi di libertà del codice, dando più potere al ragionamento semplice.

La cancellazione del tipo di Java non ottiene questo risultato: paralizza il compilatore, come in questo esempio:

void doStuff(List<Integer> collection) { 
}

void doStuff(List<String> collection) // ERROR: a method cannot have 
                   // overloads which only differ in type parameters

(Le due dichiarazioni precedenti collassano nella stessa firma del metodo dopo la cancellazione.)

Il rovescio della medaglia, il runtime può ancora ispezionare il tipo di un oggetto e il motivo a riguardo, ma poiché la sua comprensione del vero tipo è paralizzata dalla cancellazione, le violazioni del tipo statico sono banali da ottenere e difficili da prevenire.

Per rendere le cose ancora più complicate, le firme di tipo originale e cancellata coesistono e vengono considerate in parallelo durante la compilazione. Questo perché l'intero processo non riguarda la rimozione delle informazioni sul tipo dal runtime, ma la modifica di un sistema di tipi generico in un sistema di tipi grezzi legacy per mantenere la compatibilità con le versioni precedenti. Questa gemma è un classico esempio:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

(Il ridondante extends Objectdoveva essere aggiunto per preservare la compatibilità con le versioni precedenti della firma cancellata.)

Ora, con questo in mente, rivisitiamo la citazione:

È divertente quando gli utenti Java si lamentano della cancellazione del tipo, che è l'unica cosa che Java ha capito bene

Che cosa ha funzionato esattamente Java? È la parola stessa, indipendentemente dal significato? Per contrasto, dai un'occhiata al inttipo umile : nessun controllo del tipo a runtime viene mai eseguito, o addirittura possibile, e l'esecuzione è sempre perfettamente indipendente dai tipi. Ecco come appare la cancellazione del tipo se eseguita correttamente: non sai nemmeno che c'è.


10

L'unica cosa che non vedo qui considerata è che il polimorfismo di runtime di OOP dipende fondamentalmente dalla reificazione dei tipi in fase di runtime. Quando un linguaggio la cui spina dorsale è tenuta in posizione da tipi rifiutati introduce un'estensione importante al suo sistema di tipi e lo basa sulla cancellazione del tipo, la dissonanza cognitiva è il risultato inevitabile. Questo è esattamente quello che è successo alla comunità Java; è per questo che la cancellazione del tipo ha attirato così tante controversie e, in definitiva, perché ci sono piani per annullarla in una futura versione di Java . Trovare qualcosa di divertente in quella lamentela degli utenti Java tradisce o un onesto fraintendimento dello spirito di Java, o uno scherzo consapevolmente deprecante.

L'affermazione "la cancellazione è l'unica cosa che Java ha corretto" implica l'affermazione che "tutti i linguaggi basati sull'invio dinamico contro il tipo di runtime dell'argomento funzione sono fondamentalmente difettosi". Sebbene certamente una rivendicazione legittima di per sé, e che può anche essere considerata come una valida critica a tutti i linguaggi OOP incluso Java, non può porsi come un punto cardine da cui valutare e criticare le funzionalità nel contesto di Java , dove il polimorfismo del runtime è assiomatico.

In sintesi, mentre si può validamente affermare che "la cancellazione del tipo è la strada da percorrere nella progettazione del linguaggio", le posizioni che supportano la cancellazione del tipo all'interno di Java sono mal riposte semplicemente perché è troppo, troppo tardi per quello ed era già stato così anche al momento storico quando Oak fu abbracciato da Sun e ribattezzato Java.



Quanto al fatto che la tipizzazione statica stessa sia la giusta direzione nella progettazione dei linguaggi di programmazione, questo si inserisce in un contesto filosofico molto più ampio di ciò che pensiamo costituisca l'attività della programmazione . Una scuola di pensiero, chiaramente derivante dalla tradizione classica della matematica, vede i programmi come istanze di un concetto matematico o di un altro (proposizioni, funzioni, ecc.), Ma esiste una classe di approcci completamente diversa, che vede la programmazione come un modo per parlare con la macchina e spiegare cosa vogliamo da essa. Da questo punto di vista il programma è un'entità dinamica, in crescita organica, un drammatico opposto dell'edificio costruito con cura di un programma tipizzato staticamente.

Sembrerebbe naturale considerare i linguaggi dinamici come un passo in quella direzione: la coerenza del programma emerge dal basso verso l'alto, senza costrutti a priori che lo impongano dall'alto verso il basso. Questo paradigma può essere visto come un passo verso la modellazione del processo mediante il quale noi, gli esseri umani, diventiamo ciò che siamo attraverso lo sviluppo e l'apprendimento.


Grazie per quello. È il tipo esatto di comprensione più profonda delle filosofie coinvolte che mi aspettavo di vedere per questa domanda.
Daniel Langdon

L'invio dinamico non ha alcuna dipendenza dai tipi reificati. Spesso si basa su ciò che la letteratura di programmazione chiama "tag", ma anche in questo caso non è necessario utilizzare tag. Se crei un'interfaccia (per un oggetto) e crei istanze anonime di quell'interfaccia, stai eseguendo l'invio dinamico senza alcun bisogno di tipi reificati.
Sukant Hajra

@sukanthajra Questa è solo una rottura di capelli. Il tag è le informazioni di runtime necessarie per risolvere il tipo di comportamento che presenta l'istanza. È fuori dalla portata del sistema di tipi statici, e questa è l'essenza del concetto in discussione.
Marko Topolnik

Esiste un tipo molto statico progettato esattamente per questo ragionamento chiamato "tipo somma". Si chiama anche "tipo variante" (nell'ottimo libro di Benjamin Pierce sui tipi e sui linguaggi di programmazione). Quindi non si tratta affatto di spaccare i capelli. Inoltre, puoi utilizzare un catamorfismo / piega / visitatore per un approccio completamente senza tag.
Sukant Hajra

Grazie per la lezione di teoria, ma non vedo la rilevanza. Il nostro argomento è il sistema di tipi di Java.
Marko Topolnik

9

Un post successivo dello stesso utente nella stessa conversazione:

new T è un programma rotto. È isomorfico all'affermazione "tutte le proposizioni sono vere". Non sono grande in questo.

(Questo era in risposta a un'affermazione di un altro utente, vale a dire che "sembra che in alcune situazioni la 'nuova T' sarebbe meglio", l'idea è che new T()è impossibile a causa della cancellazione del tipo. (Questo è discutibile, anche se Tfosse disponibile su runtime, potrebbe essere una classe astratta o un'interfaccia, oppure potrebbe essere Void, o potrebbe mancare di un costruttore no-arg, o il suo costruttore no-arg potrebbe essere privato (ad esempio, perché dovrebbe essere una classe singleton), o il suo Il costruttore no-arg potrebbe specificare un'eccezione controllata che il metodo generico non cattura o specifica, ma questa era la premessa. Indipendentemente da ciò, è vero che senza cancellazione potresti almeno scrivere T.class.newInstance(), che gestisce quei problemi.))

Questa visione, che i tipi sono isomorfi alle proposizioni, suggerisce che l'utente ha un background nella teoria dei tipi formale. (S) molto probabilmente non gli piacciono i "tipi dinamici" o i "tipi runtime" e preferirebbe un Java senza downcast instanceofe riflessioni e così via. (Pensa a un linguaggio come Standard ML, che ha un sistema di tipi (statico) molto ricco e la cui semantica dinamica non dipende da alcuna informazione di tipo.)

E 'vale la pena tenere in mente, tra l'altro, che l'utente sia traina: while (s) preferisce probabilmente sinceramente (staticamente) lingue scritte, (s) che è , non sinceramente cercando di persuadere gli altri di quella vista. Piuttosto, lo scopo principale del tweet originale era quello di deridere coloro che non sono d'accordo, e dopo che alcuni di questi dissidenti sono intervenuti, l'utente ha pubblicato tweet di follow-up come "il motivo per cui java ha la cancellazione del tipo è che Wadler e altri sanno cosa stanno facendo, a differenza degli utenti di java ". Sfortunatamente, questo rende difficile scoprire cosa sta realmente pensando; ma fortunatamente, probabilmente significa anche che non è molto importante farlo. Le persone con una reale profondità delle loro opinioni generalmente non ricorrono a troll che sono abbastanza privi di contenuti.


2
Non si compila in Java perché ha la cancellazione del tipo
Mark Rotteveel

1
@MarkRotteveel: Grazie; Ho aggiunto un paragrafo per spiegarlo (e commentarlo).
ruakh

1
A giudicare dalla combinazione di voti positivi e negativi, questa è la risposta più controversa che abbia mai pubblicato. Sono stranamente orgoglioso.
ruakh

6

Una cosa buona è che non c'era bisogno di cambiare JVM quando sono stati introdotti i generici. Java implementa i generici solo a livello di compilatore.


1
Cambiamenti di JVM rispetto a cosa? I modelli non avrebbero nemmeno richiesto modifiche JVM.
Marchese di Lorne

5

Il motivo per cui la cancellazione del tipo è una buona cosa è che le cose che rende impossibili sono dannose. Impedire l'ispezione degli argomenti di tipo in fase di esecuzione semplifica la comprensione e il ragionamento sui programmi.

Un'osservazione che ho trovato in qualche modo controintuitiva è che quando le firme delle funzioni sono più generiche, diventano più facili da capire. Questo perché il numero di possibili implementazioni è ridotto. Considera un metodo con questa firma, che in qualche modo sappiamo non ha effetti collaterali:

public List<Integer> XXX(final List<Integer> l);

Quali sono le possibili implementazioni di questa funzione? Moltissimi. Puoi dire molto poco su ciò che fa questa funzione. Potrebbe invertire l'elenco di input. Potrebbe essere l'accoppiamento di int, sommandoli e restituendo un elenco della metà delle dimensioni. Ci sono molte altre possibilità che potrebbero essere immaginate. Ora considera:

public <T> List<T> XXX(final List<T> l);

Quante sono le implementazioni di questa funzione? Poiché l'implementazione non può conoscere il tipo di elementi, è ora possibile escludere un numero enorme di implementazioni: gli elementi non possono essere combinati, aggiunti all'elenco o filtrati, et al. Siamo limitati a cose come: identità (nessuna modifica all'elenco), eliminazione di elementi o inversione dell'elenco. Questa funzione è più facile da ragionare basandosi solo sulla sua firma.

Tranne che ... in Java puoi sempre imbrogliare il sistema dei tipi. Poiché l'implementazione di quel metodo generico può usare cose come instanceofcontrolli e / o cast su tipi arbitrari, il nostro ragionamento basato sulla firma del tipo può essere facilmente reso inutile. La funzione potrebbe ispezionare il tipo degli elementi e fare un numero qualsiasi di cose in base al risultato. Se questi hack di runtime sono consentiti, le firme dei metodi parametrizzati diventano molto meno utili per noi.

Se Java non avesse la cancellazione del tipo (ovvero, gli argomenti del tipo sono stati reificati in fase di esecuzione), ciò consentirebbe semplicemente più imbrogli di questo tipo che compromettono il ragionamento. Nell'esempio precedente, l'implementazione può violare le aspettative impostate dalla firma del tipo solo se l'elenco ha almeno un elemento; ma se Tfosse reificato, potrebbe farlo anche se l'elenco fosse vuoto. I tipi reificati aumenterebbero semplicemente le (già moltissime) possibilità di ostacolare la nostra comprensione del codice.

La cancellazione del tipo rende la lingua meno "potente". Ma alcune forme di "potere" sono effettivamente dannose.


1
Sembra che tu stia sostenendo che i generici sono troppo complessi perché gli sviluppatori Java possano "comprenderli e ragionarli", o che non ci si possa fidare di non spararsi ai piedi se ne hanno la possibilità. Quest'ultimo sembra essere in sincronia con l'ethos di Java (come nessun tipo non firmato, ecc.) Ma sembra un po 'condiscendente per gli sviluppatori
Basic

1
@Basic Devo essermi espresso molto male, perché non è quello che intendevo affatto. Il problema non è che i generici siano complessi, è che cose come il casting e instanceofimpediscono la nostra capacità di ragionare su ciò che il codice fa in base ai tipi. Se Java dovesse reificare gli argomenti di tipo, peggiorerebbe il problema. La cancellazione dei tipi in fase di esecuzione ha l'effetto di rendere più utile il sistema dei tipi.
Lachlan

@lachlan aveva perfettamente senso per me, e non penso che una normale lettura di esso avrebbe suscitato alcun insulto per gli sviluppatori Java.
Rob Grant,

3

Questa non è una risposta diretta (OP ha chiesto "quali sono i vantaggi", io rispondo "quali sono i contro")

Rispetto al sistema di tipo C #, la cancellazione del tipo Java è una vera seccatura per due ragioni

Non puoi implementare due volte un'interfaccia

In C # è possibile implementare entrambi IEnumerable<T1>e in IEnumerable<T2>modo sicuro, soprattutto se i due tipi non condividono un antenato comune (ovvero il loro antenato è Object ).

Esempio pratico: in Spring Framework, non è possibile implementare ApplicationListener<? extends ApplicationEvent>più volte. Se hai bisogno di comportamenti diversi in base a Tte devi testareinstanceof

Non puoi fare una nuova T ()

(e hai bisogno di un riferimento a Class per farlo)

Come altri hanno commentato, l'equivalente di new T()può essere fatto solo tramite la reflection, solo invocando un'istanza di Class<T>, assicurandosi dei parametri richiesti dal costruttore. C # ti consente di fare new T() solo se ti limiti Ta un costruttore senza parametri. Se Tnon rispetta tale vincolo, viene generato un errore di compilazione .

In Java, sarai spesso costretto a scrivere metodi che assomigliano al seguente

public <T> T create(....params, Class<T> classOfT)
{

    ... whatever you do
    ... you will end up
    T = classOfT.newInstance();


    ... or more advanced reflection
    Constructor<T> parameterizedConstructorThatYouKnowAbout = classOfT.getConstructor(...,...);
}

Gli svantaggi nel codice sopra sono:

  • Class.newInstance funziona solo con un costruttore senza parametri. Se nessuno disponibile, ReflectiveOperationExceptionviene lanciato in fase di esecuzione
  • Il costruttore riflesso non evidenzia i problemi in fase di compilazione come sopra. Se effettui il refactoring, degli argomenti di scambio, lo saprai solo in fase di esecuzione

Se fossi l'autore di C #, avrei introdotto la possibilità di specificare uno o più vincoli del costruttore che sono facili da verificare in fase di compilazione (quindi posso richiedere ad esempio un costruttore con string,stringparametri). Ma l'ultima è la speculazione


2

Un punto aggiuntivo che nessuna delle altre risposte sembra aver considerato: se hai davvero bisogno di generici con la digitazione in fase di esecuzione , puoi implementarlo tu stesso in questo modo:

public class GenericClass<T>
{
     private Class<T> targetClass;
     public GenericClass(Class<T> targetClass)
     {
          this.targetClass = targetClass;
     }

Questa classe è quindi in grado di fare tutte le cose che sarebbero ottenibili di default se Java non usasse la cancellazione: può allocare nuovi Ts (supponendo che Tabbia un costruttore che corrisponde al modello che si aspetta di usare), o array di Ts, può verifica dinamicamente in fase di esecuzione se un particolare oggetto è un Te cambia il comportamento a seconda di quello, e così via.

Per esempio:

     public T newT () { 
         try {
             return targetClass.newInstance(); 
         } catch(/* I forget which exceptions can be thrown here */) { ... }
     }

     private T value;
     /** @throws ClassCastException if object is not a T */
     public void setValueFromObject (Object object) {
         value = targetClass.cast(object);
     }
}

Qualcuno vuole spiegare il motivo del voto negativo? Questa è, per quanto posso vedere, una spiegazione perfettamente valida del perché il presunto svantaggio del sistema di cancellazione del tipo di Java non è in realtà un vero limite. Allora, qual'è il problema?
Jules

Sto votando. Non che io supporti il ​​controllo del tipo in fase di esecuzione, ma questo trucco può essere utile nelle miniere di sale.
techtangents

3
Java è un linguaggio delle miniere di sale, il che lo rende una necessità assoluta data la mancanza di informazioni sul tipo di runtime. Si spera che la cancellazione del tipo venga annullata in una versione futura di Java, rendendo i generici una funzionalità molto più utile. Attualmente è
opinione comune

Bene, certo ... Finché il tuo TargetType non usa i generici stessi. Ma questo sembra piuttosto limitante.
Basic

Funziona anche con tipi generici. L'unico vincolo è che se vuoi creare un'istanza hai bisogno di un costruttore con i giusti tipi di argomenti, ma ogni altro linguaggio di cui sono a conoscenza ha lo stesso vincolo, quindi non vedo il limite.
Jules

0

evita c ++ - come il codice gonfio perché lo stesso codice viene utilizzato per più tipi; tuttavia, la cancellazione del tipo richiede l'invio virtuale, mentre l'approccio c ++ - code-bloat può fare generici inviati non virtualmente


3
La maggior parte di questi invii virtuali vengono convertiti in quelli statici in runtime, quindi non è così male come potrebbe sembrare.
Piotr Kołaczkowski

1
molto vero, la disponibilità di un compilatore in fase di esecuzione consente a java di fare molte cose interessanti (e ottenere prestazioni elevate) rispetto a c ++.
negromante

wow, fammi scrivere da qualche parte java raggiunge prestazioni elevate rispetto a cpp ..
jungle_mole

1
@jungle_mole sì, grazie. poche persone si rendono conto del vantaggio di avere un compilatore disponibile in fase di esecuzione per compilare nuovamente il codice dopo la profilazione. (o che la garbage collection sia big-oh (non-garbage) piuttosto che l'ingenua e errata convinzione che la garbage collection sia big-oh (garbage), (o che mentre la garbage collection è un piccolo sforzo, compensa consentendo il costo zero allocazione)). È un mondo triste in cui le persone adottano ciecamente ciò che crede la loro comunità e smettono di ragionare dai primi principi.
negromante

0

La maggior parte delle risposte riguarda più la filosofia di programmazione che i dettagli tecnici effettivi.

E sebbene questa domanda abbia più di 5 anni, la domanda rimane ancora: perché la cancellazione del tipo è desiderabile da un punto di vista tecnico? Alla fine, la risposta è piuttosto semplice (a un livello superiore): https://en.wikipedia.org/wiki/Type_erasure

I modelli C ++ non esistono in fase di esecuzione. Il compilatore emette una versione completamente ottimizzata per ogni chiamata, il che significa che l'esecuzione non dipende dalle informazioni sul tipo. Ma come fa un JIT a gestire diverse versioni della stessa funzione? Non sarebbe meglio avere una sola funzione? Non vorrei che JIT dovesse ottimizzare tutte le diverse versioni di esso. Bene, ma allora per quanto riguarda la protezione dai tipi? Immagino che debba uscire dalla finestra.

Ma aspetta un secondo: come fa .NET? Riflessione! In questo modo devono ottimizzare solo una funzione e ottenere anche informazioni sul tipo di runtime. Ed è per questo che i generici .NET erano più lenti (anche se sono migliorati molto). Non sto sostenendo che non sia conveniente! Ma è costoso e non dovrebbe essere usato quando non è assolutamente necessario (non è considerato costoso nei linguaggi tipizzati dinamicamente perché il compilatore / interprete si basa comunque sulla riflessione).

In questo modo la programmazione generica con cancellazione del tipo è vicina allo zero overhead (sono ancora necessari alcuni controlli / cast di runtime): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.