Newsgroup: it.comp.java
Titolo: ottimizzare ciclo in java

sono alle prime armi con java, ma non con la programmazione, ho un dubbio
per cui chiedo a voi che siete più esperti di me...
data una lista di oggetti, in questo caso una lista di stringhe,
supponiamo che io debba ciclare all'interno della lista per stamparne a
video gli elementi, lasciando perdere i generics che non sono il motivo
della domanda, il codice sarebbe questo:
List list=new ArrayList();
list.add("pippo");
list.add("pluto");
list.add("paperino");
for(Iterator itr=list.iterator();itr.hasNext()) {
      	String str=(String)itr.next();
       	System.out.println(str);
}
perchè l'oggetto str viene dichiarato e assegnato ad ogni iterazione del
ciclo for? non è meglio dichiaralo esternamente al ciclo in modo che ad
ogni iterazione avvenga solo l'assegnazione dell'oggetto? in tutti gli
esempi che ho visto finora, sto studiando java in vari libri, ho visto
fare come nell'esempio che vi ho riportato... Vengo dal C e questo modo di
iterare nei cicli mi da l'idea di poca ottimizzazione... magari c'è un
motivo valido, chi me lo spiega?  
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 }
List list=new ArrayList();
list.add("pippo");
list.add("pluto");
list.add("paperino");
Iterator it = list.iterator();
while (it.hasNext()) {
        String str=(String)itr.next();
        System.out.println(str);
}
-- 
Davide Consonni

Davide Consonni wrote:
ehm errata:
      String str=(String)itr.next(); diventa it quindi:
List list=new ArrayList();
list.add("pippo");
list.add("pluto");
list.add("paperino");
Iterator it = list.iterator();
while (it.hasNext()) {
        String str=(String)it.next();
        System.out.println(str);
}
poi adesso si usano i generics, cosi eviti anche il cast esplicito in
stringa
-- 
Davide Consonni

 fare come nell'esempio che vi ho riportato...
Perché (ahimč) i sistemi con garbage collection sollevano il programmatore
da attenzioni particolari, quali la parsimonia delle risorse. Ovviamente,
č meglio riutilizzare gli oggetti, che non crearne tutte le volte di nuovi.


 ehm errata:



forse non mi sono spiegato bene... che il ciclo sia un while piuttosto che
un for o che si usino i generics o meno, la domanda resta: perchč ad ogni
iterazione devo ridichiarare l'oggetto str? non č piů ottimizzato
dichiararlo all'esterno del ciclo? perchč sta pratica č diffusa in tutti i
testi che sto leggendo? non č piů corretto fare cosě?
List list=new ArrayList();
list.add("pippo");
list.add("pluto");
list.add("paperino");
Iterator it = list.iterator();
String str;
while (it.hasNext()) {
        str=(String)it.next();
        System.out.println(str);


 }
non credo ci sia differenza, visto che le stringhe in java sono immutabili
quindi ad ogni iterazione str conterrą cmq una nuova istanza.
del resto se ne occupa il gc.
-- 
Davide Consonni

 }
Giusto, correttissimo, ma se hai migliaia, anzi no... di più, migliaia
di migliaia di elementi nella tua list allora <<inizia>> a diventare
un po' pesante perchè....
...[ segue citazione da 
http://www.mondoinformatico.info/ottimizzare-il-codice-in-java_post-312.html
]
<< Siccome gli oggetti String sono immutabili, questo fa si che ogni
volta si crei un nuovo oggetto e si lasci l’altro per il << Garbage
Collector. Questo implica un notevole spreco di tempo, che si può
evitare utilizzando StringBuffer, poiché << questo permette la
modifica degli oggetti rappresentati.
Quindi, per 3, 100 o 1000 elementi nella tua list non ti preoccupare
se fai uso del tipo String; invece se hai molti più elementi allora ti
conviene usare il tipo StringBuffer

 iterare nei cicli mi da l'idea di poca ottimizzazione... 
Anche nel C è consigliabile dichiarare le variabili il piu' tardi possibile.
-- 
Andrea Francia
http://www.andreafrancia.it/

 č meglio riutilizzare gli oggetti, che non crearne tutte le volte di nuovi.
In questo caso non si tratta di riutilizzare gli oggetti ma dei 
variabili che memorizzano il riferimento all'oggetto.


 
-- 
Andrea Francia
http://www.andreafrancia.it/

 conviene usare il tipo StringBuffer
grazie ora è più chiaro, comunque al posto di String poteva esserci
qualsiasi altro oggetto, in tal caso mi sembra di aver capito che è meglio
dichiarare l'oggetto all'esterno del ciclo



Lo StringBuffer non c'entra niente, qui non si sta modificando delle 
stringhe.
Poi piuttosto che usare lo StringBuffer conviene usare lo StringBuilder 
che non ha l'overhead dei metodi synchronized.


 
Assolutamente no, vedi il mio post.
Non stai dichiarando l'oggetto. Ma una variabile da 32 bit che contiene 
un riferimento all'oggetto.



E' ovvio che è la creazione degli oggetti ad "ammazzare
l'applicazione".
Per le le stringhe, però, java ha una gestione a "pool", quindi le
classiche considerazioni in questo caso non possono essere fatte. In
realtà, le stringhe le hai già create in maniera letterale ("pippo",
"pluto"...).
Se il tuo obiettivo è stampare la stringa, puoi mettere fuori dal
ciclo la dichiarazione dell'oggetto stringa e ridefinirlo ad ogni
iterazione.
String str = null;
while (it.hasNext()) {
        str=(String)itr.next();
        System.out.println(str);
}
Questo però può essere una trappola in varie situazioni.
Ad esempio, se devi aggiungere l'oggetto ad una lista, devi dichiarare
un nuovo oggetto ogni volta altrimenti otterrai una lista di elementi
che puntano allo stesso oggetto.
In genere, ci sono varie tecniche di ottimizzazione che evitano la
creazione di nuovi oggetti per migliorare le performance in tempo e
spazio. Alcune tecniche consistono in veri e propri refactoring.
In generale, cmq, meglio ricordare la legge dell'ottimizzazione:
"Fa funzionare il codice e alla fine, se hai tempo, forse, ottimizza".
Nel senso che, a meno che non sia un requisito molto stringente, non
bisogna progettare e sviluppare un software con in testa il pallino
dell'ottimizzazione ma intervenire soltanto alla fine, se il profiling
non è in linea con il requisito, non funzionale, di performance.
Di certo, bisogna evitare comuni strafalcioni in fase di coding.

 un riferimento all'oggetto.
Non è così. Se è vero che le stringhe sono immutabili, allora stai
istanziando comunque e sempre un nuovo oggetto. Piuttosto, mi viene da
chiedermi se anche spostando fuori dal ciclo la dichiarazione della
variabile, le cose cambino.
La risposta è presumibilmente no, vista proprio l'immutabilità.


 Non sono d'accordo, vedi il mio post.
Ti ho risposto.


 
Intendevo l'altro post, quello delle 11.52 in risposta al primo 
messaggio dell'OP.


 
-- 
Andrea Francia
http://www.andreafrancia.it/

 d'accordo.
no alla riga due hai aggiunto un reference ad un'oggetto
-- 
Davide Consonni

 
Allora perché nel tuo post hai detto la cosa seguente?
Davide Consonni wrote:
 > quindi ad ogni iterazione str conterrà cmq una nuova istanza.
 > del resto se ne occupa il gc.


  > del resto se ne occupa il gc.
perchè sei tu fai 
1: String a;
2: a = "uno";
3: a = "due";
hai creato due istanze di stringa.
poi entra in gioco il gc a pulire quello che deve pulire. quando arrivi alla
riga 3 la stringa "uno" è eliggibile per il gc
-- 
Davide Consonni


Pero' in
List list=new ArrayList();
list.add("pippo");
list.add("pluto");
list.add("paperino");
for(Iterator itr=list.iterator();itr.hasNext()) {
       	String str=(String)itr.next();
        	System.out.println(str);
}
Il GC non entra in gioco nel for.
Perché non vengono create nuove istanze (l'istanza restituita da 
itr.next() è comunque la stessa che c'e' dentro la lista) e alla fine 
del corpo del ciclo cmq le istanze rimangono referenziate almeno dalla 
lista.
O no?


 O no?
in questo caso tu hai già le istanze delle stringhe nella lista e nel for
riassegni di vola in volta il reference alla stringa alla variabile str ma
qui non stai creando nuove istanze
infatti se esegui questo codice:
        List list = new ArrayList();
        list.add("pippo");
        list.add("pluto");
        list.add("paperino");
        for (Iterator itr = list.iterator(); itr.hasNext();) {
            Object obj = itr.next();
            System.out.println(obj.hashCode());
            String str = (String) obj;
            System.out.println(str.hashCode());
            System.out.println(str.equals(obj));            
        }
otterrai questo risultato:
106673622
106673622
true
106767924
106767924
true
1118014174
1118014174
true
in questo caso stiamo parlando solo di assegnazione di references. non si
sta creando nessuna nuova stringa.
-- 
Davide Consonni


Ok siamo d'accordo.


 
A dirla proprio tutta non è esattamente vero. Di solito i literal String 
vengono "riciclati":
String a, b;
a = "uno";
a = "due";
b = "uno";
if (a == b) System.out.println("Sono la stessa istanza");
Quindi alla seconda assegnazione di "uno" non corrisponde la creazione 
di un oggetto String...ma probabilmente neanche alla prima, dato che 
durante la vita del programma quel literal potrebbe già essere stato 
usato e quindi l'oggetto corrispondente essere già stato costruito.
-- 
Lucio Benfante
JUG Padova           
http://www.parancoe.org
 ...have a look at it!
www.jugpadova.it
 
http://www.jugevents.org

Davide Consonni wrote:
aggiungo che cambiando il codice in:
        List list = new ArrayList();
        String s = null;
        list.add("pippo");
        list.add("pluto");
        list.add("paperino");
        for (Iterator itr = list.iterator(); itr.hasNext();) {
            Object obj = itr.next();
            System.out.println(obj.hashCode());
            s = (String) obj;
            System.out.println(s.hashCode());
            System.out.println(s.equals(obj));            
        }
quindi con la stringa esterna al ciclo ottieni sempre 
106673622
106673622
true
106767924
106767924
true
1118014174
1118014174
true
e anche qui non si stanno allocando nuove stringhe, ma si riassegna
semplicemente il reference a "s".
-- 
Davide Consonni

 
si ok, qui però stai parlando di caching delle stringhe che è
un'ottimizzazione della jvm. qui però non sono sicuro che *da specifiche*
java ti garantisca sempre lo stesso reference.
-- 
Davide Consonni

 un'ottimizzazione della jvm. 
Questa è un ottimizzazione del compilatore Java -> bytecode, non della JVM.


 JVM.
ma č una roba dipendente dall'implementazione o č da specifica ?
-- 
Davide Consonni

 
No, non č piů corretto, nč piů performante, dato che il compilatore 
riuscirebbe a fare la stessa cosa, e anzi forse meglio.
Quindi la tua ottimizzazione del codice non produce risultati 
differenti, ma anzi probabilmente rende piů difficoltose le 
ottimizzazioni che il compilatore potrebbe fare.
Giusto per trasformare le chiacchiere in fatti, diamo un'occhiata al 
bytecode generato nei due casi.
Compilando la seguente classe:
public class LoopOptimization {
     public void notOptimizedLoop(List list) {
         Iterator it = list.iterator();
         while (it.hasNext()) {
             String str = (String) it.next();
             System.out.println(str);
         }
     }
     public void optimizedLoop(List list) {
         Iterator it = list.iterator();
         String str = null;
         while (it.hasNext()) {
             str = (String) it.next();
             System.out.println(str);
         }
     }
}
Il bytecode generato č il seguente:
public void notOptimizedLoop(java.util.List);
   Code:
    0:	aload_1
    1:	invokeinterface	#2,  1; //InterfaceMethod 
java/util/List.iterator:()Ljava/util/Iterator;
    6:	astore_2
    7:	aload_2
    8:	invokeinterface	#3,  1; //InterfaceMethod 
java/util/Iterator.hasNext:()Z
    13:	ifeq	36
    16:	aload_2
    17:	invokeinterface	#4,  1; //InterfaceMethod 
java/util/Iterator.next:()Ljava/lang/Object;
    22:	checkcast	#5; //class java/lang/String
    25:	astore_3
    26:	getstatic	#6; //Field java/lang/System.out:Ljava/io/PrintStream;
    29:	aload_3
    30:	invokevirtual	#7; //Method 
java/io/PrintStream.println:(Ljava/lang/String;)V
    33:	goto	7
    36:	return
public void optimizedLoop(java.util.List);
   Code:
    0:	aload_1
    1:	invokeinterface	#2,  1; //InterfaceMethod 
java/util/List.iterator:()Ljava/util/Iterator;
    6:	astore_2
    7:	aconst_null
    8:	astore_3
    9:	aload_2
    10:	invokeinterface	#3,  1; //InterfaceMethod 
java/util/Iterator.hasNext:()Z
    15:	ifeq	38
    18:	aload_2
    19:	invokeinterface	#4,  1; //InterfaceMethod 
java/util/Iterator.next:()Ljava/lang/Object;
    24:	checkcast	#5; //class java/lang/String
    27:	astore_3
    28:	getstatic	#6; //Field java/lang/System.out:Ljava/io/PrintStream;
    31:	aload_3
    32:	invokevirtual	#7; //Method 
java/io/PrintStream.println:(Ljava/lang/String;)V
    35:	goto	9
    38:	return
Come si vede (a parte l'inizializzazione a null fuori dal ciclo) il 
bytecode all'interno del ciclo č esattamente lo stesso.
Ciao
   Lucio


 
Boh, io non lo so, so solo che se quando sta compilando trova delle 
stringhe uguali aggiunge ne scrive solo una nel file .class.


 
A corollario di tutto ciò aggiungo che al momento dell'esecuzione ci si 
mette pure il JIT che potrebbe effettuare ulteriori ottimizzazioni 
durante l'esecuzione del bytecode.


 
Basta leggersi le specifiche, no? :)
# Literal strings within the same class (§8) in the same package (§7) 
represent references to the same String object (§4.3.1).
# Literal strings within different classes in the same package represent 
references to the same String object.
# Literal strings within different classes in different packages 
likewise represent references to the same String object.
http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.5


 Alla riga 2 sto istanziando un nuovo oggetto? Se č cosě non sono d'accordo.
Non si tratta di "essere d'accordo". Se non č cosě, dimostramelo :-)
Molto probabilmente hai ragione tu e sto prendendo un abbaglio, anche
visto quello che dice Davide. 
Casi come quello che riporti, comunque, non possono essere presi ad
esempio, perché in molti linguaggi (non so se Java si comporti alla stessa
maniera, ma in Delphi ad esempio č cosě), esiste il concetto di
copy-on-write: i due riferimenti all'inizio puntano alla stessa zona di
memoria e quando uno dei due viene modificato, viene effettuata la copia
del valore originale.
Il concetto č che, se b ha lo stesso valore (come riferimento) di a, nel
momento in cui modifico il contenuto di a che succede a b? dovrei trovar
modificato anche il contenuto di b, il che significa che la semantica č
errata...


 errata...
Che sia errato o no non č il caso di discuterne. Sta di fatto che Java 
fa cosě. Per esempio:
class Pippo {
   int value;
}
Pippo a = new Pippo();
a.value = 6;
Pippo b = a;
System.out.println(b.value); // stampa 6
a.value = 7;
System.out.println(b.value); // stampa 7


 errata...
Infatti, è così.
Bisogna starci attenti :)

 System.out.println(b.value); // stampa 7
..questo è normale. Stai parlando di istanze di Pippo, non di stringhe.
Il concetto del copy-on-write vale SOLO per le stringhe nei linguaggi che
ho citato, non per i reference in generale (anche in Delphi, data la
classe Pippo, vale il discorso che fai, ma nel caso delle stringhe le cose
cambiano).


 cambiano).
Ah ok, adesso ho imparato una cosa su Delphi.
Quindi in Java le stringhe sono immutable, mentre in Delphi sono 
copyonwrite.
Due modi un po' diversi per ottenere di non sprecare memoria per le 
stringhe duplicate e non sprecare tempo a copiarle se non serve.
Invece mi sembra che il Pascal si copiava tutti i 255 caratteri della 
stringa.


 motivo valido, chi me lo spiega?  
in c è chiaro, non lo puoi fare.
il motivo per cui spesso si fa in quel modo è per comodità, leggibilità 
e manutenibilità del codice. come regola generale è buona norma definire 
le variabili nello scope più "stretto" possibile all'interno del quale 
sono usate.
-- 
 Fino all'anno scorso avevo un solo difetto. Ero presuntuoso.
http://softvalley.blogspot.com
http://www.flickr.com/photos/marko_po/

         str=(String)itr.next();
non fa nessuna allocazione e non certo perchè si tratta di stringhe. 
anche se fossero sarchiaponi sarebbe la stessa cosa. in quel caso 
avviene semplicemente una assegnazione di reference.


 in c è chiaro, non lo puoi fare.
Come no? In C puoi mettere le variabili subito dopo l'apertura dello scope.
I C (e non sto dicendo C++) è perfettamente legale scrivere questo
for(i=0; i< 10; i ++) {
    int a = i*2;
    printf("%d", a);
}
E pure questo:
int a = 3;
printf("%d\n", a);
{
    int b = 8;
    printf("%d\n", b);
}
Non puoi fare questo:
int a = 3;
printf("%d\n", a);
int b = 8;	<--- errore di compilazione


stringa.
In realtà Delphi supporta due modalità di gestione delle stringhe,
quella compatibile col vecchio Turbo Pascal e quella più simile alle
ASCIIZ del C. In realtà queste ultime, in Delphi, non sono
propriamente delle ASCIIZ, ma la lunghezza è comunque conservata in 4
byte a monte, oltre a informazioni per il reference counting (Delphi
implementa un banale meccanismo di garbage collection per le
stringhe).

 errata...
non puoi modificare il contenuto di a visto che String e' immutable. puoi 
solo far puntare il reference ad un'altra stringa, ma non puoi mutare 
l'oggetto.
Tommy

 attenti", più qualcosa si è perso nel design.
Scusa, non capisco cosa ci sarebbe da stare attenti in Java. In Java hai la 
garanzia che l'oggetto String non possa cambiare state, e' immutable. Questo 
lo rende thread safe, lo rende sicuro, e rende tutto semplice (ma 
inefficiente e quindi ecco perche' esiste StringBuilder).
Magari non ho capito cosa intendi.
Tommy

 nuovi.
dichiarandolo fuori o dentro il ciclo non cambi affatto il numero di oggetti 
che vengono creati sulla heap.
Tommy

 String str=(String)itr.next();
Dichiaravi un reference che puntava ad una delle stringhe precedentemente 
pre-allocate.
Tommy

 è meglio riutilizzare gli oggetti, che non crearne tutte le volte di nuovi.
ma nel caso di specie non c'è allocazione supplementare di nuovi 
oggetti. le istanze le recupera da una lista.


 ciclo e' solo un reference che probabilmente viene "optimised-out" cmq.
Per cofermare che si dovrebbero lasciare queste piccolezze al bytecode 
compiler e hotspot ho digitato il codice originale dell'OP in Netbeans (ho 
dovuto aggiungere un ; nel for loop visto che mancava).
Ho compilato in bytecode il main che conteneva il codice ed ho prodotto un 
class file. Poi ho decompilato il .class in un .java (compilazion seguita da 
decompilazione). Questo e' il risultato della decompilazione:
ArrayList arraylist = new ArrayList();
arraylist.add("pippo");
arraylist.add("pluto");
arraylist.add("paperino");
String s;
for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); 
System.out.println(s))
     s = (String)iterator.next();
come vedete il reference e' gia' fuori dal loop, e questo solo durante il 
primo passaggio, hotspot poi potra' fare di meglio.
Non mi sembra caso di scrivere codice come sopra, il seguente codice 
compilato e decompilato produce esattamente lo stesso codice qui sopra, ma 
e' molto piu' leggibile:
List<String> list = new ArrayList<String>();
list.add("pippo");
list.add("pluto");
list.add("paperino");
for(final String str : list)
{
     System.out.println(str);
}
Compilato e decompilato produce esattamente lo stesso codice riportato 
sopra. Solo che
for(final String str : list)
{
     System.out.println(str);
}
e' piu' leggibile di:
String s;
for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); 
System.out.println(s))
     s = (String)iterator.next();
Io adoro ottimizzare il codice, e' uno dei miei compiti preferiti, ma non si 
deve mai perdere tempo a cercare di fare ottimizzazioni inutili.
In generale si dovrebbe cercare di scrivere codice elegante, leggibile e 
robusto. Bisognerebbe evitare grosse atrocita' che sprecano prestazioni, 
vero, ma evitare di pensare alle piccolezze di cui si puo' occupare il 
compilatore (come un reference che va sullo stack). Poi si dovrebbe usare un 
profiler per ottimizzare atrocita' che non si sono viste, oppure colli di 
bottiglia non previsti, etc...
Un esempio di atrocita':
List list = new ArrayList();
list.add("pippo");
list.add("pluto");
list.add("paperino");
for(final Object o : list.toArray())
{
     System.out.println(new String(o.toString()));
}
Qui, creiamo un array che non serve! Inoltre poi creiamo una nuova stringa 
ad ogni iterazione. Decompilato e compilato viene:
ArrayList arraylist = new ArrayList();
arraylist.add("pippo");
arraylist.add("pluto");
arraylist.add("paperino");
Object aobj[] = arraylist.toArray();
int i = aobj.length;
for(int j = 0; j < i; j++)
{
    Object obj = aobj[j];
    System.out.println(new String(obj.toString()));
}
Come si vede in questo caso il compilatore bytecode non ha fatto miracoli, 
abbiamo sempre la creazione di un array e ad ogni iterazione la creazione di 
una nuova stringa. Questo e' un esempio di una atrocita' che va evitata 
subito! Mettere un reference all'interno di un ciclo non e' una atrocita', 
anzi rende solo il codice piu' leggibile ed il compilatore e' in grado di 
ottimizzare come vuole....
Tommy

 Per cofermare che si dovrebbero lasciare queste piccolezze al bytecode ...
Ottimo post.
Concordo in pieno con le conclusioni.
Giusto per curiosità, che cosa hai usato per decompilare?
-- 
Andrea Francia
http://andreafrancia.blogspot.com/2008/05/schermare-le-remoteexception.html

 Giusto per curiosità, che cosa hai usato per decompilare?
Jad: 
http://www.kpdus.com/jad.html
ma ce ne sono anche altri altrettanto o piu' validi.
Tommy

 Scusa, non capisco cosa ci sarebbe da stare attenti in Java. 
Mi riferisco alla risposta "bisogna starci attenti" che ho ricevuto.



Scusami, l'autore della risposta a cui ti riferisci sono io.
Era un modo "simpatico" per dire che un linguaggio, per essere
utilizzato al meglio, deve essere studiato per bene nei suoi concetti
base.
Un concetto basilare in Java è che le variabili sono tutte di tipo
reference (e vengono passate ai metodi come copia).
Se modifico un oggetto accedendo a questo tramite una sua variabile
reference, ogni altro accesso allo stesso oggetto (attraverso
qualsiasi altro reference) vedrà l'oggetto modificato.

 avviene semplicemente una assegnazione di reference.
Sì, infatti.
Nella mia risposta avevo deviato la discussione verso altre
considerazioni:)

 
E in definitiva a cosa dovremmo stare attenti,  che non si capisce?

 wrote
[quote]
Scusami, l'autore della risposta a cui ti riferisci sono io.
Era un modo "simpatico" per dire che un linguaggio, per essere
utilizzato al meglio, deve essere studiato per bene nei suoi concetti
base.
Un concetto basilare in Java è che le variabili sono tutte di tipo
reference (e vengono passate ai metodi come copia).
Se modifico un oggetto accedendo a questo tramite una sua variabile
reference, ogni altro accesso allo stesso oggetto (attraverso
qualsiasi altro reference) vedrà l'oggetto modificato.
[/quote]
verissimo ed e' per questo che in Java si tende a creare oggetti immutable, 
cosi' questo rischio non accade.
Poi concordo, non piacciono nemmeno a me tante cose di Java. Per tante cose 
Java e' comodo, veloce, facile, per altre, beh, rimpiango molto il C++.
Tommy


Sì, ci sono alcune cose di Java che, per favorire praticità o
compatibilità all'indietro, vanno a discapito di altre.
Non conosco benissimo C++ ma di sicuro la sua potenza ancora non è
stata raggiunta.
Ad esempio, l'introduzione dei Generics (template c++) sono molto
lontani dall'essere utili per un design riutilizzabile.

 lontani dall'essere utili per un design riutilizzabile.
A me sembrano molto utili per fare design riutilizzabili invece.


 hanno applicato il principio di "salvare il programmatore da se stesso".
vedi ereditarietà multipla :D
-- 
Davide Consonni


Non ti parlo per esperienza diretta. Ma mettiamo tu voglia usare il
polimorfismo e l'overriding di metodi che restituiscono una lista
di ?, da specializzare classe per classe, in modo da restituire quindi
una lista di Oggetto1 o Oggetto2 etc....
Per il poco tempo a disposizione non ho approfondito più di tanto...ma
penso non sia possibile. Mentre qualche ex-guru di C++ mi ha detto che
con i template, quelli veri del c++, si può.
Pensandoci, deve essere per forza così, visto che i generics, dopo la
compilazione, non esistono più e servono, per adesso, semplicemente a
fare controlli a tempo di compilazione (il che è già una cosa di per
sé molto utile). La limitata potenza dei generics è data dal fatto che
i progettisti java hanno preferito tenere la compatibilità
all'indietro con il codice pre-generics.
Poi, se qualcuno ha da condividere storie generics di successo,
sarebbe opportuno che le condividesse qui.

 sarebbe opportuno che le condividesse qui.
cos'è una storia generics di successo ?
-- 
Davide Consonni


:)
Metti che in una classe astratta si voglia definire un metodo astratto
che restituisca una lista di generics. Quando si va a ridefinire il
metodo nelle varie classi concrete, vorresti specializzare il metodo a
ritornare una lista di oggetti specifici.
Mi sembra che non sia possibile.......

 Mi sembra che non sia possibile.......
no, i generics non sono covariant.
-- 
Davide Consonni


IMHO I generics sono troppo comodi.



E' possibile. Quando ho tempo faccio una prova e posto.


 E' possibile. Quando ho tempo faccio una prova e posto.
Ok.
Aspettiamo con ansia:)


import java.util.Collections;
import java.util.List;
class CanUomo {}
class Rutto extends CanUomo {}
abstract class CanUomoList {
     abstract List<? extends CanUomo> getList();
}
class RuttoList extends CanUomoList{
     @Override
     List<Rutto> getList() {
         return Collections.emptyList();
     }
}
E' questo che intendevate?


 vedi ereditarietà multipla :D
Si, questo e' un esempio che fanno in molti, ma la decisione di Java e' 
forse troppo estrema dall'altra parte... Ovvero un caso abbastanza utile di 
ereditarieta' multipla e' quella in cui erediti anche implementazioni di 
metodi (ma non variabili e oggetti).
Tommy


Anche se non è la stessa cosa puoi sempre ottenere lo stesso effetto con 
la composizione.
-- 
Andrea Francia
http://andreafrancia.blogspot.com/2008/06/macross-frontier.html

 Mi sembra che non sia possibile.......
In C++ i templates definiscono un vero e proprio "tipo", in Java non fanno 
affatto questo ma inseriscono un cast.
Ovvero, in C++:
std::list<Person>
e' una lista di persone.
in Java:
List<Person>
e' una lista di oggetti (Object) che in alcuni casi otterra' un cast 
automatico a Person, questo perche' in Java Object sta alla base di tutti 
gli oggetti, quindi e' come avere "Person extends Object".
L'altra cosa da notare e' che se poi hai:
std::list<Animal>
in C++ ora hai di fatto due tipi diversi: std::list<Animal> and 
std::list<Person>. In Java hai sempre ed un solo tipo di lista List<Object> 
che a volte fara' un cast a Person ed a volte a Animal.
In C++ anche senza essere un guru, puoi sicuramente fare quello che dici, e' 
banale.
Tommy

 }
il punto e' che getList ritorna una lista EmptyList... non un List<Rutto> o 
null.
hai da solo dimostrato quanto siano invece limitati i generics... ora cambio 
il tuo codice in:
class RuttoList extends CanUomoList{
     @Override
     List<Rutto> getList() {
         final List list = new ArrayList();
         list.add("stringa");
         list.add(1);
         list.add(new CanUomo());
         list.add(true);
         return list;
     }
}
e tutto funziona!
Sto ritornando una lista che contiene una stringa, un intero, un CanUomo ed 
un booleano attraverso il metodo getList che ha una signature List<Rutto>. 
Nota che nessun oggetto sulla lista e' un Rutto!!!
Se ottengo la lista cosi' non ho problemi:
final List myList1 = ruttoList.getList();
ora posso iterare i mei oggetti di vari tipi...
for(final Object o : myList1)
{
    System.out.println(o);
}
se invece faccio:
final List<Rutto> myList2 = ruttoList.getList();
for(final Rutto o : myList2)
{
    System.out.println(o);
}
compila tutto bene, ma ottengo un class cast exception a runtime ( un 
pochino tardi, no?!) (*)
A me questo non piace... a mio avviso il compilatore non dovrebbe accettare:
List<Rutto> getList() {
   final List list = new ArrayList();
...
   return list;
}
perche' non e' sicuro.
e poi non dovrebbe accettare:
final List myList1 = ruttoList.getList();
ma dovrebbe volere per forza:
final List<Rutto> myList2 = ruttoList.getList();
In C++ il compilatore e' molto piu' severo e cmq l'implementazione di 
templates non si basa su casts ma su creazione di veri e propri nuovi tipi.
(*) = ottieni una class cast exception in quanto questo in realta' e' tutto 
cio' che e' un generic, un cast!
Tommy

 gli oggetti, quindi e' come avere "Person extends Object".
Non č esattamente cosě.
Se hai
	List<String> stringhe = null;
         List<Object> oggetti = null;
Sono due tipi diversi.
Tant'e vero che sia
	oggetti = stringhe; // non compila
	stringhe = oggetti; // non compila
Invece si ha che
	List<String> estende List
	List<Oggetti> estende List
Infatti puoi fare
	List list = oggetti; //compila
	list = stringhe;  //compila
Quindi in Java sono due tipi diversi come in C++.
In piu' hanno un supertipo comune.
Il fatto della type erasure avviene dopo la compilazione.
E' vero che a runtime non ci sono differenze il tipo di 'stringhe' e il 
tipo 'oggetti' ma durante la compilazione sono due tipi diversi.
Semplicemente la tipipizzazione dei parametri non č supportata a 
runtime. Si vocifera che questa cosa potrebbe essere introdotta nel Java 
  7 o successivi.
Non sono mai (ancora?) stato persuaso da chi pensa che i Generics Java 
siano fatti peggio dei template C++.


 la composizione.
con piu' codice si... per ogni metodo devi scrivere un metodo che chiama il 
metodo dell'oggetto che tieni.
Se cambi il numero dei metodi, o il nome, o i parametri. etc.. dell'oggetto. 
hai piu' manutenzione da fare, per ogni classe che usa quell'oggetto in 
questo modo dei cambiare codice. La composizione e' per certi versi piu' 
potente (puoi cambiare il comportamento anche a runtime), ma in alcuni casi, 
quando questo non e' richiesto, comporta solo piu' codice e piu' 
manutenzione.
Tommy

 perche' non e' sicuro.
Lo accetta per compatibilità con i sorgenti vecchi. Pero' ti mette un 
warning.
Se preferisci un compilatore che ti obbliga a riscrivere i sorgenti 
piuttosto che guardare i warning va bene.



Vero.


 siano fatti peggio dei template C++.
Il problema dei templates in C++ sono essenzialmetne due:
- sono complessi da usare (non le cose basi)
- usano piu' spazio e richiedono piu' tempo per compilare.
Tommy

 piuttosto che guardare i warning va bene.
Sicuramente si, preferisco di gran lunga gli errori. Il fatto che accetti 
queste cose per compatibilita' e' una attenuante, non una assoluzione!
Tommy

 wrote
Tornando al tuo esempio... o quasi...
final List<Rutto> myList1 = ruttoList.getList();
myList1.toArray(new Rutto[0]);
quel new Rutto[0] e' necessario perche nonostante myList1 sia un List<Rutto> 
il compilatore a meno che tu non passi il tipo Rutto[] a toArray() non sa 
affatto che tipo creare!!! Tu passi il tpo e lui poi fa un bel .getClass per 
sapere che tipo myList1 dovrebbe tenere!!!
Questo rende ovvio il concetto che myList1 e' un semplice ArrayList (nel mio 
caso).
se non passi new Rutto[0] non puo' ritornare un Rutto[] ma solo un Object[] 
in quanto si e' dimanticato il tipo!
In C++ non e' necessario passare "new Rutto[0]", toArray sarebbe in grado di 
creare un array del tipo giusto facendo (javizzando) un "new T[size]" dove T 
realmente sa che tipo e' e se era un List<Rutto>, allora T sara' un Rutto.
Tommy

 queste cose per compatibilita' e' una attenuante, non una assoluzione!
Chi ha montagne di codice scritto per la 1.4 non credo che sia d'accordo.
D'altra parte anche tu non hai tutti i torti, avrebbero potuto fare che 
se compili -source 1.5 ti obbliga a specificare sempre il tipo generico, 
mentre con -source 1.4 non ti permette di usare i generics.
Pero' io preferisco la soluzione di mezzo, quella dove ti mettono i warning.


 Non sono affatto due tipi diversi.
Non siamo d'accordo.
Secondo me i tipi sono diversi perché me i tipi sono quelli definiti dal 
linguaggio e dal compilatore.
Per te sono uguali perché per te i tipi che contano sono quelli presenti 
a runtime.


 sapere che tipo myList1 dovrebbe tenere!!!
Questi tipi esistono solo a compile-time e la reflection non te li dice 
a causa della type erasure.
Concordo che questo è un limite dei Generics.


  - public static final List EMPTY_LIST;
si, ma quel T non vuole dire nulla, vuole solo dire "un tipo", non e' Rutto 
(ma piuttosto Object). Di fatto EMPTY_LIST e quello che emptyList() 
restituisce sono la stessa cosa... tanto e' vero che emptyList() non e' 
altro che:
public static final <T> List<T> emptyList() {
 return (List<T>) EMPTY_LIST;
}
Quindi mi puo' anche dare un warning se uso EMPTY_LIST direttamente ma a me 
questo pare tutto molto "sporco".
Io concordo con quello che dici, non pensare il contrario, non so se capisci 
cosa sto cercando di dire, probabilmente tu la vedi una discussione 
filosofica...
Tommy

 runtime.
Forse e' una discussione filosofica anche questa, hai ragione... e' che non 
mi convincono del tutto...
final List<Rutto> myList1 = ruttoList.getList();
myList1.toArray(new Rutto[0]);
lascia stare il runtime, concentriamoci sul compile time, Java potrebbe 
accettare anche:
final List<Rutto> myList1 = ruttoList.getList();
myList1.toArray();
e, in compile time, capire che myList1 e' un List<Rutto> e quindi passare in 
automatico un new Rutto[0] nel bytecode che produce (putroppo qualche cosa 
deve passare visto che quando at runtime deve eseguire toArray() a quel 
punto davvero non sa il tipo per via del type erasure).
(l'esempio invece ritorna un Object[])
Quindi, quello che voglio dire che e' at runtime non sono due tipi diversi, 
su questo concordiamo. E... compile time, beh, non mi convince, il 
compilatore genera spesso solo warnings e non errori, e' troppo permissimo e 
non fa cose banali (per motivi validi (compatibilita' etc..), non discuto, 
ma il risultato non m convince del tutto).
Tommy

 ma il risultato non m convince del tutto).
Ok.
Comunque chissà se qualcuno ha colto la citazione Rutto e CanUomo?


 Comunque chissà se qualcuno ha colto la citazione Rutto e CanUomo?
Io come al solito no... o usato google ora e suppongo sia da Balle Spaziali, 
ma... azzz sara' un secolo che non vedo quel film... :-)
Tommy

 cosa...
Allora non ho capito.


 versione 1.5.
Questo l'ho trovato interessante:
"There is a common misconception that type erasure was nessecary for 
backwards compatability in the sense that old code could run on a new JVM 
and new code could call old code. However, this was entirely possible 
without type erasure (i.e. with reified generics), as has been demonstrated 
with the addition of generics to the very similar C# programming language."
Ti posto anche cio' che seguiva in quanto comunque interessante:
...
Type erasure was only nessecary because of a requirement for a temporary 
migration compatability; a requirement that the collection classes be the 
same classes, so that code which did not insulate itself through use of 
interfaces (thus breaking best practices) could co-exist in the same JVM 
with new generic style code without the use of wrapping techniques.
Type erasure has several drawbacks:
  a.. Typecasting overhead when inserting and removing from a collection and 
when calling any other method of a generic type which takes a parameter of a 
parameterized type.
  b.. Inability to use primitive types as type parameters. Because all types 
must be of reference type and must be "erased" to their upper bounds, it was 
not possible to allow the use of primitive types. Instead Java 5.0 features 
autoboxing which "boxes" primitive type on insertion into collections, thus 
incurring extra overhead and making the code more opaque.
  c.. What looks like separate classes at development time is really a 
single class at runtime. Thus static (class-level) members are shared among 
all classes realized from the generic class.
  d.. Inability to create new instances or new arrays of types defines 
through a type parameter.
  e.. Disparity with arrays which means that in general the developer should 
never let a method return an array of a parametric type.
  f.. The type erasure process may produce clashing method signatures.
  g.. Inability to implement different realizations of the same generic 
interface.
  h.. Inability to use generic exceptions
tratto da wikipedia.
Invece tratto da: 
http://www.artima.com/intv/genericsP.html
For example, with Java generics, you don't actually get any of the execution 
efficiency that I talked about, because when you compile a generic class in 
Java, the compiler takes away the type parameter and substitutes Object 
everywhere. So the compiled image for List<T> is like a List where you use 
the type Object everywhere. Of course, if you now try to make a List<int>, 
you get boxing of all the ints. So there's a bunch of overhead there.  [ 
perche' int non deriva da Object... quindi non puo' essere messo in una list 
e quindi deve fare boxing a Integer].
Furthermore, to keep the VM happy, the compiler actually has to insert all 
of the type casts you didn't write.
...
And really all they're doing in their implementation is automatically 
inserting those type casts for you. So you get the syntactic sugar, or some 
of it at least, but you don't get any of the execution efficiency. So that's 
issue number one I have with Java's solution.
Issue number two, and I think this is probably an even bigger issue, is that 
because Java's generics implementation relies on erasure of the type 
parameter, when you get to runtime, you don't actually have a faithful 
representation of what you had at compile time.
Tommy

 with new generic style code without the use of wrapping techniques.
Non ho capito bene questa parte.
Comunque tutto molto interessante.


 tratto da wikipedia.
La pagina è 
http://en.wikipedia.org/wiki/Criticism_of_Java


>>>>> IMHO I generics sono troppo comodi.
che siano troppo comodi concordo in pieno, ma sarebbero potuti essere molto 
piu' comodi.
Poi l'articolo parlava di boxing... ovvero il fatto che ArrayList tiene 
Object in realta' rende impossibile fare ArrayList<int> in quanto int 
essendo un primitivo non deriva da Object e quindi non si puo'. Questo rende 
necessario quindi fare ArrayList<Integer> che probabilmente comporta tutta 
una serie di conversioni da int a Integer e poi da Integer a int.. tutto non 
necessario nella maggior parte dei casi.
Poi non fraintendere, uso i generics tutti i giorni e li trovo utile come li 
trovi utili tu, ma penso solo potrebbero essere molto meglio.
Tommy

 essendo un primitivo non deriva da Object e quindi non si puo'. 
Anche i generics come tipo primitivo, come i reified generics sono tra 
le cose che la comunità Java chiede. Chissà se e quando saranno aggiunti?
Sui vari siti che parlano di Java 7 se ne parla. Pero' non sono siti 
ufficiali Sun quindi contengono un po' quello che gli utenti vorrebbero 
piuttosto quello che ci sarà.
Per esempio:
http://tech.puredanger.com/java7#reified
