Perché java.lang.Object non è astratto?

Possibile duplicato:
Java: motivazione della class Object non dichiarata astratta

Perché la class Object, che è la class base di ’em all in Java, non è astratta?

Ho avuto questa domanda per un tempo veramente molto lungo e qui viene chiesto solo per curiosità, tutto qui. Niente nel mio codice o nel codice di nessuno si sta rompendo perché non è astratto, ma mi chiedevo perché lo hanno reso concreto?

Perché qualcuno dovrebbe volere una “istanza” (e non la sua presenza alias Reference) di questa class Object? Un caso è un codice di sincronizzazione scadente che utilizza l’istanza di un object per il blocco (almeno l’ho usato in questo modo una volta .. il mio male).

Esiste un uso pratico di una “istanza” di una class Object? E in che modo la sua istanziazione si adatta a OOP? Cosa sarebbe successo se lo avessero contrassegnato come astratto (ovviamente dopo aver fornito le implementazioni ai suoi metodi)?

Senza i designer di java.lang.Object che ci dicono, dobbiamo basare le nostre risposte sull’opinione. Ci sono alcune domande che possono essere poste e che possono aiutare a chiarirlo.

Qualcuno dei metodi di Object trarrà vantaggio dall’essere astratto?

Si potrebbe sostenere che alcuni dei metodi potrebbero trarne vantaggio. Prendiamo ad esempio hashCode() ed equals() , probabilmente ci sarebbe stata molta meno frustrazione riguardo alle complessità di questi due se fossero stati entrambi resi astratti. Ciò richiederebbe agli sviluppatori di capire come dovrebbero essere implementati, rendendo più ovvio che dovrebbero essere coerenti (vedi Java efficace). Tuttavia, sono più dell’opinione che hashCode() , equals() e clone() appartengano a astrazioni opt-in separate (es. Interfacce). Gli altri metodi, wait() , notify() , finalize() , ecc. Sono sufficientemente complicati e / o sono nativi, quindi è meglio che siano già implementati e non traggano alcun beneficio dall’essere astratti.

Quindi immagino che la risposta sarebbe no, nessuno dei metodi di Object trarrebbe beneficio dall’essere astratto.

Sarebbe un vantaggio contrassegnare la class Object come astratta?

Supponendo che tutti i metodi siano implementati, l’unico effetto della marcatura dell’estratto di un object è che non può essere costruito (cioè il new Object() è un errore di compilazione). Questo avrebbe un vantaggio? Sono dell’opinione che il termine “object” sia esso stesso astratto (puoi trovare qualcosa intorno a te che può essere completamente descritto come “un object”?), Quindi si adatta al paradigma orientato agli oggetti. È comunque da un lato purista . Si potrebbe sostenere che costringere gli sviluppatori a scegliere un nome per qualsiasi sottoclass concreta, anche vuota, porterà a un codice che esprime meglio il loro intento. Penso che, per essere completamente corretto dal punto di vista del paradigma, Object dovrebbe essere contrassegnato come abstract , ma quando si riduce ad esso, non c’è alcun reale beneficio, è una questione di preferenza progettuale (pragmatismo vs purezza).

La pratica dell’uso di un object Object per la sincronizzazione è una ragione sufficiente per renderla concreta?

Molte delle altre risposte parlano della costruzione di un object semplice da utilizzare nell’operazione synchronized() . Anche se questa potrebbe essere stata una pratica comune e accettata, non credo che sarebbe una buona ragione per evitare che l’object sia astratto se i progettisti volessero che fosse. Altre risposte hanno menzionato come dovremmo dichiarare una singola sottoclass vuota di Object ogni volta che volevamo sincronizzarci su un certo object, ma questo non regge: una sottoclass vuota potrebbe essere stata fornita nell’SDK ( java.lang.Lock o qualsiasi altra cosa), che potrebbe essere costruita ogni volta che volevamo sincronizzarci. Fare questo avrebbe il vantaggio di creare una dichiarazione d’intenti più forte.

Ci sono altri fattori che potrebbero essere stati influenzati negativamente dalla creazione dell’estratto dell’object?

Ci sono diverse aree, separate da un punto di vista del design puro, che possono aver influenzato la scelta. Sfortunatamente, non ne so abbastanza su di loro per ampliarli. Tuttavia, non mi sorprenderebbe se qualcuno di questi avesse un impatto sulla decisione:

  • Prestazione
  • Sicurezza
  • Semplicità di implementazione della JVM

Potrebbero esserci altri motivi?

È stato detto che potrebbe essere in relazione alla riflessione. Tuttavia, la riflessione è stata introdotta dopo la progettazione di Object. Quindi, se influisce sulla riflessione o no è discutibile – non è la ragione. Lo stesso per i generici.

C’è anche il punto indimenticabile che java.lang.Object è stato progettato dagli umani: potrebbero aver fatto un errore, potrebbero non aver considerato la domanda. Non c’è linguaggio senza difetti, e questo potrebbe essere uno di questi, ma se lo è, non è certo un grosso problema. E penso di poter dire con sicurezza, senza alcuna ambizione, che è molto improbabile essere coinvolto nella progettazione di una parte fondamentale di una tecnologia così ampiamente utilizzata, specialmente quella che dura da 15 (?) Anni e che continua a essere forte, quindi questo non dovrebbe essere considerato una critica

Detto questo, l’avrei reso astratto ;-p

Sommario
Fondamentalmente, per quanto vedo, la risposta ad entrambe le domande “Perché java.lang.Object è concreto?” o (se fosse così) “Perché è java.lang.Object abstract?” è … “Perché no?”.

Le semplici istanze di java.lang.Object vengono in genere utilizzate negli scenari di blocco / sincronizzazione e questa è prassi accettata.

Inoltre – quale sarebbe la ragione per essere astratto? Perché non è completamente funzionale a sé stante come istanza? Potrebbe davvero fare con alcuni membri astratti? Non pensarlo. Quindi l’argomento per renderlo astratto in primo luogo è inesistente. Quindi non lo è.

Prendi la classica gerarchia degli animali, dove hai una class astratta Animal , il ragionamento per rendere astratta la class Animal è perché un’istanza di Animale è effettivamente un ‘invalido’ – per mancanza di una parola migliore – animale (anche se tutti i suoi metodi fornire un’implementazione di base). Con Object , semplicemente non è il caso. Non c’è un caso travolgente per renderlo astratto in primo luogo.

Posso pensare a diversi casi in cui le istanze di Object sono utili:

  • Blocco e sincronizzazione, come menzionate voi e altri commentatori. Probabilmente è un odore di codice, ma ho visto istanze di oggetti usate in questo modo tutto il tempo.
  • Come oggetti nulli , poiché equals restituirà sempre false, tranne che sull’istanza stessa.
  • Nel codice di test, specialmente quando si testano le classi di raccolta. A volte è più facile riempire una raccolta o un array con oggetti fittizi piuttosto che con valori null .
  • Come istanza di base per le classi anonime. Per esempio:
    Object o = new Object() {...code here...}

Da tutto quello che ho letto, sembra che Object non abbia bisogno di essere concreto , e in realtà avrebbe dovuto essere astratto.

Non solo non c’è bisogno che sia concreto, ma dopo qualche lettura in più sono convinto che Object non essere astratto è in conflitto con il modello di ereditarietà di base – non dovremmo permettere sottoclassi astratte di una class concreta, poiché le sottoclassi dovrebbero solo aggiungere funzionalità.
Chiaramente questo non è il caso in Java, dove abbiamo sottoclassi astratte di Object .

Penso che probabilmente avrebbe dovuto essere dichiarato astratto, ma una volta che è stato fatto e rilasciato è molto difficile da annullare senza causare molto dolore – vedi Java Language Spec 13.4.1:

“Se una class che non era astratta viene modificata per essere dichiarata astratta, allora i binari preesistenti che tentano di creare nuove istanze di quella class genereranno un InstantiationError al momento del collegamento o (se si utilizza un metodo riflettente) un’IstantiationException in fase di esecuzione pertanto un tale cambiamento non è raccomandato per le classi ampiamente distribuite. ”

Di volta in volta è necessario un object semplice che non ha uno stato proprio. Sebbene tali oggetti sembrino inutili a prima vista, hanno comunque utilità poiché ognuno ha un’identity framework diversa. Tnis è utile in diversi scenari, il più importante dei quali è il blocco: si desidera coordinare due thread. In Java lo fai usando un object che verrà usato come un lucchetto. L’object non ha bisogno di nessuno stato è sufficiente la sua semplice esistenza per diventare una serratura:

 class MyThread extends Thread { private Object lock; public MyThread(Object l) { lock = l; } public void run() { doSomething(); synchronized(lock) { doSomethingElse(); } } } Object lock = new Object(); new MyThread(lock).start(); new MyThread(lock).start(); 

In questo esempio abbiamo usato un lock per impedire che i due thread doSomethingElse() contemporaneamente doSomethingElse()

Se Object fosse astratto e avessimo bisogno di un lock, dovremmo suddividerlo in sottoclass senza aggiungere alcun metodo o campo solo in modo da poter istanziare il lock.

Venendo a pensarci, ecco una doppia domanda alla tua: Supponiamo che gli oggetti fossero astratti, definirà qualche metodo astratto? Credo che la risposta sia No. In tali circostanze non ha molto valore definire la class come astratta.

Non capisco perché la maggior parte sembra credere che fare una class pienamente funzionale, che implementa tutti i suoi metodi in un uso astratto a pieno titolo sarebbe una buona idea.
Preferirei chiedere perché renderlo astratto? Fa qualcosa che non dovrebbe? manca qualche funzionalità che dovrebbe avere? Entrambe le domande possono essere risolte con no, è una class pienamente funzionante a sé stante, rendendola astratta porta solo a persone che implementano classi vuote.

 public class UseableObject extends AbstractObject{} 

UseableObject eredita da Object astratto e sorprende che possa essere implementato, non aggiunge alcuna funzionalità e la sua unica ragione di esistere è consentire l’accesso ai metodi esposti da Object.
Inoltre, non sono d’accordo con l’uso della sincronizzazione “scadente”. L’utilizzo di oggetti privati ​​per sincronizzare l’accesso è più sicuro rispetto all’utilizzo di sincronizzazione (questo) e più sicuro e più facile da usare rispetto alle classi di blocco da java util concurrent.

Mi sembra che ci sia una semplice questione di praticità qui. Fare un abstract di class toglie la capacità del programmatore di fare qualcosa, cioè di istanziarlo. Non c’è nulla che tu possa fare con una class astratta che non puoi fare con una lezione concreta. (Bene, puoi dichiarare funzioni astratte in esso, ma in questo caso non abbiamo bisogno di avere funzioni astratte.) Quindi rendendolo concreto, lo rendi più flessibile.

Ovviamente se ci fosse un danno attivo che è stato fatto rendendolo concreto, quella “flessibilità” sarebbe un inconveniente. Ma non riesco a pensare a nessun danno attivo fatto rendendo Object instantiable. (È “istantaneo” una parola? Qualunque.) Potremmo discutere se un dato uso che qualcuno ha fatto di un’istanza Object grezza sia una buona idea. Ma anche se tu potessi convincermi che ogni uso che io abbia mai visto di un’istanza Object grezza era una ctriggers idea, ciò non proverebbe ancora che non ci potrebbero essere buoni usi là fuori. Quindi se non danneggia nulla, e potrebbe essere d’aiuto, anche se non riusciamo a pensare a un modo che potrebbe effettivamente aiutare al momento, perché proibirlo?

Sospetto che i progettisti non sapessero in che modo le persone potrebbero usare un Oggetto in futuro, e quindi non volevano limitare i programmatori costringendoli a creare una class aggiuntiva dove non fosse necessario, ad esempio per cose come i mutex, le chiavi eccetera.

Penso che tutte le risposte fin qui dimentichino com’era con Java 1.0. In Java 1.0, non si poteva creare una class anonima, quindi se si voleva semplicemente un object per qualche scopo (sincronizzazione o segnaposto nullo) dovresti dichiarare una class per quello scopo, e quindi un intero gruppo di codice avrebbe queste classi extra per questo scopo. Molto più semplice per consentire solo l’istanziazione diretta dell’object.

Certo, se stavi progettando Java oggi potresti dire che tutti dovrebbero fare:

  Object NULL_OBJECT = new Object(){}; 

Ma quella non era un’opzione in 1.0.

Significa anche che può essere istanziato in un array. Nei pre-1.5 giorni, questo ti permetterebbe di avere strutture dati generiche. Questo potrebbe ancora essere vero su alcune piattaforms (sto pensando a J2ME, ma non ne sono sicuro)

Ragioni per le quali l’object deve essere concreto.

  1. riflessione
    vedi Object.getClass ()

  2. uso generico (pre Java 5)

  3. Confronto / uscita
    vedere Object.toString (), Object.equals (), Object.hashCode (), ecc.

  4. sincronizzazione
    vedere Object.wait (), Object.notify (), ecc.

Anche se un paio di aree sono state sostituite / deprecate, c’era ancora bisogno di una class genitore concreta per fornire queste funzionalità a ogni class Java.

La class Object viene utilizzata in reflection in modo che il codice possa chiamare metodi su istanze di tipo indeterminato, ad esempio ‘Object.class.getDeclaredMethods ()’. Se Object dovesse essere astratto, il codice che voleva partecipare avrebbe dovuto implementare tutti i metodi astratti prima che il codice client potesse usare la riflessione su di essi.

Secondo Sun, una class astratta è una class che è dichiarata astratta – può includere o meno metodi astratti. Le classi astratte non possono essere istanziate, ma possono essere sottoclassate. Questo significa anche che non puoi chiamare metodi o accedere a campi pubblici di una class astratta.

Esempio di una class radice astratta:

 abstract public class AbstractBaseClass { public Class clazz; public AbstractBaseClass(Class clazz) { super(); this.clazz = clazz; } } 

Un figlio della nostra AbstractBaseClass:

 public class ReflectedClass extends AbstractBaseClass { public ReflectedClass() { super(this); } public static void main(String[] args) { ReflectedClass me = new ReflectedClass(); } } 

Questo non verrà compilato perché non è valido fare riferimento a “this” in un costruttore a meno che non chiami un altro costruttore nella stessa class. Posso farlo compilare se lo cambio in:

  public ReflectedClass() { super(ReflectedClass.class); } 

ma funziona solo perché ReflectedClass ha un genitore (“Oggetto”) che è 1) concreto e 2) ha un campo per memorizzare il tipo per i suoi figli.

Un esempio più tipico della riflessione sarebbe in una funzione membro non statico:

 public void foo() { Class localClass = AbstractBaseClass.clazz; } 

Questo fallisce a meno che tu non modifichi il campo “clazz” per essere statico. Per il campo class di Object questo non funzionerebbe perché dovrebbe essere specifico dell’istanza. Non avrebbe senso che Object avesse un campo di class statico.

Ora, ho provato il seguente cambiamento e funziona, ma è un po ‘fuorviante. Richiede ancora che la class base sia estesa per funzionare.

 public void genericPrint(AbstractBaseClass c) { Class localClass = c.clazz; System.out.println("Class is: " + localClass); } public static void main(String[] args) { ReflectedClass me = new ReflectedClass(); ReflectedClass meTwo = new ReflectedClass(); me.genericPrint(meTwo); } 

I generici di Pre-Java5 (come con gli array) sarebbero stati impossibili

 Object[] array = new Object[100]; array[0] = me; array[1] = meTwo; 

Le istanze devono essere costruite per fungere da segnaposto fino a quando non vengono ricevuti gli oggetti reali.

Sospetto che la risposta breve sia che le classi di raccolta hanno perso informazioni sui tipi nei giorni precedenti ai generici di Java. Se una raccolta non è generica, deve restituire un object concreto (ed essere downcast in fase di esecuzione su qualunque tipo fosse precedentemente).

Dal momento che rendere una class concreta in una class astratta romperebbe la compatibilità binaria (come noto come upthread), la class Object concreta è stata mantenuta. Vorrei sottolineare che in nessun caso è stato creato per il solo scopo di sonicizzazione; le classi fittizie funzionano altrettanto bene.

Il difetto di progettazione non include i farmaci generici dall’inizio. Molte critiche di design sono rivolte a quella decisione e alle sue conseguenze. [oh e la regola di sottotitolazione dell’array.]

Non è astratto perché ogni volta che creiamo una nuova class estende la class Object, se fosse astratta devi implementare tutti i metodi della class Object che è overhead … Ci sono già dei metodi implementati in quella class …