Perché Java non è OOP "puro"? [duplicare]

0

Java è progettato in un approccio OO, e in qualche modo persino i programmatori 'costringono' i programmatori all'interno del paradigma OO (che può essere considerato buono o cattivo, una questione di opinione).

Tuttavia, mentre quasi tutto in Java è una classe o un oggetto, i tipi di dati primitivi ( int , double , ecc.) non lo sono.

Anche se non vedo alcun vantaggio, ci sono degli svantaggi. Ad esempio, quando un metodo vuole assumere come parametro "qualsiasi valore", in genere dichiara il parametro di tipo Object , il supertipo di tutte le classi in Java. Tuttavia se il programmatore vuole inserire un int nel metodo - un valore perfettamente valido - deve "boxare" il valore in un'istanza della classe Integer , creata appositamente per questi tipi di bisogni (fino a come ho capito).

La mia domanda è: perché non sono primitive nelle classi Java, come tutto il resto?

    
posta Aviv Cohn 18.05.2014 - 00:06
fonte

4 risposte

8

Ci sono dei compromessi in ogni scelta di design.

Quando hai un linguaggio OO "puro" (vedi se puoi convincere qualcuno a concordare su quel che è) , quando Inoltre, piuttosto che avere il sistema ottenere due numeri e aggiungerli, si sta invece invocando dapprima il metodo add sull'oggetto e recuperandone uno nuovo (rendendo possibile la creazione di oggetti numerici mutabili per tempi difficili).

Un esempio di questo sarebbe la classe BigInteger . Invece di int fourtyTwo = 6 * 7; hai BigInteger fourtyTwo = new BigInteger(6).multiply(new BigInteger(7)); Hai creato un totale di tre oggetti e fatto alcuni calcoli in background. Anche questo è un po 'un casino, ma questa è solo la semantica - puoi nasconderlo con gli operatori, ma è ancora che fa tutto il lavoro dietro di esso.

Da C2 È orientato agli oggetti Java il motivo diventa chiaro:

Lastly, it is a hybrid to provide performance gains. Even though Smalltalk has an advanced virtual machine, its inability (when I was using it) to provide a machine-correlated bytecode for integer math placed a significant performance impact on its entire environment. Being a hybrid, Java cannot be called a true Object-Oriented language. But then, why does it matter? ;-) Use the right tool for the job and life will be happy!

Puoi vedere ciò che viene menzionato in alcuni documenti per SmallTalk:

Thus, math is not a special case in Smalltalk; it is done, exactly like everything else, by creating objects, and sending them messages. This may seem odd to the Smalltalk novice, but this regularity turns out to be quite a boon: once you've mastered just a few paradigms, all of the language “falls into place”. Before you go on to the next chapter, make sure you try math involving * (multiplication), - (subtraction), and / (division) also. These examples should get you started:

Mentre sì, gli uomini sanno che alcune cose possono essere più eleganti nel design del linguaggio, ma significa anche che sarà più lento. Da talk su SmallTalk e Objective C :

What makes things slow?

  • Small integer arithmetic
  • Boxing
  • Dynamic message lookup
  • Memory management operations

Un altro esempio di questa performance può essere visto su Language Benchmarks Game per Ruby vs Java - dove Ruby implementa la" pura "matematica stile OO - i messaggi vengono inviati all'oggetto da aggiungere anziché lavorare con le primitive. Nota che in molti casi Ruby è uno o due ordini di grandezza più lento di Java. Questi benchmark pesano tutti sulla matematica e, guardando il codice Java, usano tutti i primitivi.

Ci sono cose che puoi fare per accelerare, ma è ancora meno che ottimale. L'inizio di Java era afflitto da problemi di prestazioni. Fare matematica molto più lentamente sarebbe stato terribile.

Osserviamo un'architettura Java ad alte prestazioni: LMAX . È stato tirato fuori da ogni immaginazione, in alcuni casi, riscrivendo alcune cose di base.

It took a bit more cleverness to go up another order of magnitude. There are several things that the LMAX team found helpful to get there. One was to write custom implementations of the java collections that were designed to be cache-friendly and careful with garbage. An example of this is using primitive java longs as hashmap keys with a specially written array backed Map implementation (LongToObjectHashMap). In general they've found that choice of data structures often makes a big difference, Most programmers just grab whatever List they used last time rather than thinking which implementation is the right one for this context.

Scrittura di una HashMap che potrebbe funzionare su <long, Object> anziché su% co_de consentita per alcuni significativi guadagni in termini di prestazioni non dovendo fare l'uguaglianza basata su oggetti per due oggetti (potrebbe solo usare <Long, Object> ) ed evitare un numero di problemi con garbage collection (guarda il tuo heap un po 'di tempo e conta quanti oggetti == sono seduti quando usi pesantemente Long s).

Infine, considera che l'hotspot optimizer di Java compilerà il codice su nativo quando necessario. Il codice prodotto da HashMap è molto più semplice e più veloce del corrispondente int fourtyTwo = 6 * 7

Si tratta di trade offs, e questo è stato uno dei punti in cui la performance ha conquistato la "pura" eleganza.

    
risposta data 18.05.2014 - 01:07
fonte
3

Citando una mia risposta in un'altra domanda .

In alcuni casi i progettisti di linguaggi fanno compromessi per l'usabilità.

I numeri sono usati troppo spesso. Dovendo istanziarli tutto il tempo renderebbe la lingua troppo prolissa.

Ci sono alcuni altri esempi di eccezioni speciali fatte per motivi di usabilità:

Gli oggetti di tipo String possono essere istanziati senza nuovo, evento quando String non è una primitiva:

String s = "Hello";

String essendo un non-primitivo, dovrebbe essere instanciated in questo modo:

String s = new String("Hello");          // this also works 

Ma il compilatore consente l'opzione OO più breve, meno, perché la stringa è di gran lunga la classe più utilizzata nell'API.

Anche gli array possono essere inizializzati in modo non OO:

int i[] = {1,2,3};

Stranamente, un oggetto è un'istanza di una classe o di un array. Gli array di significato sono un tipo di classe completamente separato.

Gli array hanno un campo pubblico di lunghezza che non è una costante. Inoltre non c'è documentazione sugli array di classe sono un'istanza di. (non confondere con la classe Arrays o java.reflect.Array).

int a = myArray.length;    // not length()
    
risposta data 18.05.2014 - 00:21
fonte
1

Un principio fondamentale di OOP è che i valori (non solo le variabili) hanno tipi, che è un requisito per la spedizione dinamica. Ogni oggetto è contrassegnato con un tipo. Questo tag è di solito un puntatore a una struttura che rappresenta una classe. In Java, gli oggetti hanno un ulteriore overhead per facilitare la sincronizzazione, ma è necessario essere consapevoli che ogni oggetto deve almeno trasportare un puntatore (4 o 8 byte) in giro insieme ai dati effettivi. Questo da solo raddoppia o quadruplica le dimensioni di un primitivo come int ! Avere primitive che non sono oggetti reali è quindi molto desiderabile dal punto di vista delle prestazioni.

Avere primitive non è un problema - C # è molto simile a questo riguardo, tranne per il fatto che la vera autoboxing rende esplicite classi companion come Integer non necessario.

In Java esiste anche una connessione tra primitivi e generici. A causa della cronologia di Java, i generici sono stati implementati usando la cancellazione dei tipi: al momento dell'esecuzione, non vi è alcuna differenza tra List<Foo> e List<Integer> , poiché i tipi generici vengono controllati solo durante la compilazione. In realtà, tali raccolte generiche contengono solo Object s. Generics può anche essere implementato in modo efficiente usando la specializzazione (che è come funzionano i template in C ++). Poiché ora possiamo capire se abbiamo un List<Foo> o List<int> , possiamo usare lo storage compatto per i tipi primitivi (o in linea di principio altre classi sigillate / finali). Java non può farlo, quindi abbiamo bisogno di oggetti pieni quando si memorizzano le primitive nelle raccolte - qui, un Integer wrapper.

Tali problemi con i generici non erano all'orizzonte quando Java era stato rilasciato per la prima volta. A poco a poco, è stato aggiunto un numero sempre maggiore di autoboxing per rendere questa divisione meno visibile.

    
risposta data 18.05.2014 - 00:56
fonte
1

Penso che l'unica ragione sia che è complicato per lo scrittore di compilatori.

Voglio dire, potresti avere un sistema in cui ogni tipo primitivo è stato usato nella lingua come se fosse un qualsiasi oggetto con tutta l'"eleganza" e la convenienza che ciò comporta. Quindi una chiamata a string s = i.ToString(); diventerebbe una chiamata alla funzione statica equivalente string s = ToString(i); come un esempio semplicistico.

Tuttavia, dovresti dire quali tipi erano primitivi e quali no; dovresti anche proibire di ereditare da questi (o potresti entrare in ogni tipo di disastro); e anche allora alcune persone creerebbero i loro tipi pseudo-primitivi e si aspettano che si esibiscano con la velocità dei primitivi.

Ma penso che alcuni aspetti degli oggetti si dimostrino troppo difficili da implementare in questo modo per le primitive: la garbage collection richiede che tutti gli oggetti siano creati nell'heap e referenziati e che le interfacce previste dalle chiamate al metodo non necessariamente funziona se non ci fosse una vera interfaccia da chiamare (cioè ogni oggetto ha bisogno di un vtable che i nostri primitivi-come-oggetti non avrebbero)

Quindi la boxe è una soluzione alternativa che è stata aggiunta. Penso che il modo in C ++ sia migliore - non richiede la boxe e puoi mischiare facilmente le primitive e gli oggetti. Java ha optato per un'astrazione di livello superiore e questo modo di lavorare diversamente con i primitivi è solo un altro esempio del mondo reale che influisce sul nostro desiderio di perfezione.

    
risposta data 18.05.2014 - 01:04
fonte

Leggi altre domande sui tag