sfondo
Esistono tre posizioni in cui è possibile visualizzare Final in Java:
Rendere finale una classe impedisce qualsiasi sottoclasse della classe. La finalizzazione di un metodo impedisce alle sottoclassi del metodo di sovrascriverlo. La realizzazione di un campo finale ne impedisce la successiva modifica.
fraintendimenti
Ci sono ottimizzazioni che si verificano intorno ai metodi e ai campi finali.
Un metodo finale semplifica l'ottimizzazione di HotSpot tramite inline. Tuttavia, HotSpot lo fa anche se il metodo non è definitivo in quanto funziona supponendo che non sia stato ignorato fino a prova contraria. Maggiori informazioni al riguardo su SO
Un'ultima variabile può essere ottimizzata in modo aggressivo e si può leggere di più nella sezione JLS 17.5.3 .
Tuttavia, con questa comprensione si dovrebbe essere consapevoli del fatto che nessuna di queste ottimizzazioni riguarda la conclusione di una classe . Non si ottiene alcun miglioramento delle prestazioni rendendo finale una classe.
L'aspetto finale di una classe non ha nulla a che fare con l'immutabilità. Si può avere una classe immutabile (come BigInteger ) che non è final o una classe che è mutabile e final (come StringBuilder ). La decisione se una classe debba essere definitiva è una questione di design.
Progetto finale
Le stringhe sono uno dei tipi di dati più utilizzati. Si trovano come chiavi per le mappe, memorizzano nomi utente e password, sono ciò che leggi da una tastiera o un campo in una pagina web. Le stringhe sono ovunque .
Mappe
La prima cosa da considerare di ciò che accadrebbe se si potesse sottoclassare String è rendersi conto che qualcuno potrebbe costruire una classe String mutabile che sembrerebbe altrimenti essere una String. Ciò rovinerebbe Maps ovunque.
Considera questo ipotetico codice:
Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");
t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);
one.prepend("z");
Questo è un problema con l'utilizzo di una chiave modificabile in generale con una mappa, ma la cosa che sto cercando di ottenere è che all'improvviso un certo numero di cose sull'interruzione della mappa. La voce non è più nel posto giusto nella mappa. In una HashMap, il valore di hash è (avrebbe dovuto) essere modificato e quindi non è più nella voce corretta. Nella TreeMap, l'albero ora è rotto perché uno dei nodi è dalla parte sbagliata.
Poiché l'uso di una stringa per queste chiavi è così comune, questo comportamento dovrebbe essere prevenuto rendendo la stringa definitiva.
Potresti essere interessato a leggere Perché String è immutabile in Java? per ulteriori informazioni sulla natura immutabile delle stringhe.
Stringhe nefaste
Esistono diverse opzioni nefaste per le stringhe. Considera se ho creato una stringa che ha sempre restituito true quando è stato chiamato uguale ... e l'ha passato in un controllo password? O hai fatto in modo che i compiti a MyString inviassero una copia della stringa a qualche indirizzo email?
Queste sono possibilità reali quando hai la possibilità di sottoclassare String.
Java.lang Ottimizzazioni delle stringhe
Mentre prima ho detto che final non rende String più veloce. Tuttavia, la classe String (e altre classi in java.lang) fanno un uso frequente della protezione a livello di pacchetto di campi e metodi per consentire ad altre java.langclassi di essere in grado di armeggiare con gli interni anziché passare continuamente attraverso l'API pubblica per String. Funzioni come getChars senza controllo dell'intervallo o lastIndexOf utilizzato da StringBuffer o il costruttore che condivide l'array sottostante (si noti che è una cosa Java 6 che è stata modificata a causa di problemi di memoria).
Se qualcuno creasse una sottoclasse di String, non sarebbe in grado di condividere tali ottimizzazioni (a meno che non ne facesse parte java.lang, ma si tratta di un pacchetto sigillato ).
È più difficile progettare qualcosa per l'estensione
Progettare qualcosa da estendere è difficile . Significa che devi esporre parti dei tuoi interni affinché qualcos'altro possa essere modificato.
Una stringa estendibile non avrebbe potuto avere una perdita di memoria riparare la . Quelle parti di esso avrebbero dovuto essere esposte a sottoclassi e la modifica di quel codice avrebbe comportato la rottura delle sottoclassi.
Java è orgoglioso della retrocompatibilità e aprendo le classi core all'estensione, si perde parte di quella capacità di sistemare le cose pur mantenendo la calcolabilità con sottoclassi di terze parti.
Checkstyle ha una regola che impone (che mi frustra davvero quando scrivo codice interno) chiamato "DesignForExtension" che impone che ogni classe sia:
- Astratto
- Finale
- Implementazione vuota
Il razionale per cui è:
Questo stile di progettazione dell'API protegge le superclassi dall'interruzione delle sottoclassi. Il rovescio della medaglia è che le sottoclassi sono limitate nella loro flessibilità, in particolare non possono impedire l'esecuzione di codice nella superclasse, ma ciò significa anche che le sottoclassi non possono corrompere lo stato della superclasse dimenticando di chiamare il metodo super.
Consentire l'estensione delle classi di implementazione significa che le sottoclassi possono eventualmente corrompere lo stato della classe su cui si basa e far sì che varie garanzie fornite dalla superclasse non siano valide. Per qualcosa di così complesso come String, è quasi una certezza che cambiando parte di esso si romperà qualcosa.
Hurbis sviluppatore
Fa parte dell'essere uno sviluppatore. Considera la probabilità che ogni sviluppatore crei la propria sottoclasse String con la propria raccolta di programmi di utilità all'interno. Ma ora queste sottoclassi non possono essere assegnate liberamente l'una all'altra.
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
In questo modo porta alla follia. Casting per String tutto il luogo e il controllo per vedere se la stringa è un'istanza della vostra classe String o, se non, facendo una nuova stringa in base a quello e ... basta, no. Non farlo.
Sono sicuro che è possibile scrivere una buona classe String ... ma lascio la scrittura più implementazioni di stringa a quei matti che scrivono C ++ e avere a che fare con std::stringe char*e qualcosa da boost e sString , e tutto il resto .
Magia della stringa Java
Ci sono molte cose magiche che Java fa con le stringhe. Ciò semplifica la gestione di un programmatore ma introduce alcune incongruenze nella lingua. Consentire le sottoclassi su String richiederebbe un pensiero molto significativo su come gestire queste cose magiche:
Letterali per archi (JLS 3.10.5 )
Avere codice che consente di fare:
String foo = "foo";
Questo non deve essere confuso con la boxe dei tipi numerici come Integer. Non puoi farlo 1.toString(), ma puoi farlo "foo".concat(bar).
L' +operatore (JLS 15.18.1 )
Nessun altro tipo di riferimento in Java consente di utilizzare un operatore su di esso. La stringa è speciale. L'operatore di concatenazione di stringhe funziona anche a livello di compilatore in modo che "foo" + "bar"diventi "foobar"quando viene compilato anziché in fase di esecuzione.
Conversione di stringhe (JLS 5.1.11 )
Tutti gli oggetti possono essere convertiti in stringhe semplicemente utilizzandoli in un contesto String.
String Interning ( JavaDoc )
La classe String ha accesso al pool di stringhe che gli consentono di avere rappresentazioni canoniche dell'oggetto che viene popolato al tipo di compilazione con i letterali String.
Consentire una sottoclasse di String significherebbe che questi bit con String che semplificano la programmazione diventerebbero molto difficili o impossibili da eseguire quando sono possibili altri tipi di String.