View Full Version : Java 2 SE 5: Considerazioni personali.
Salve a tutti. Oggi stavo pensando a delle questioni che riguardano il linguaggio di programmazione Java e in particolare ad un'aggiunta che e' stata fatta nell'ultima revisione 5.0 del linguaggio nelle specifiche J2SE: i metodi varargs.
Prima di entrare nel merito del mio ragionamento, illustrero' brevemente il concetto di polimorfismo in Java e come esso viene implementato, perche' e' proprio da questo concetto che parte il mio ragionamento.
Come in altri linguaggi Object Oriented, uno dei concetti utilizzati per implementare il polimorfismo, e' l'overload delle funzioni, cioe' la possibilita' di dichiarare piu' metodi di classe (funzioni membro per chi conosce C++) con lo stesso nome ma con parametri differenti. Una cosa che bisogna capire dell'overload dei metodi in Java e' come esso venga implementato.
Quando viene chiamato un metodo sovraccaricato, il compilatore Java cerca una corrispondenza fra gli argomenti usati per chiamare il metodo e i parametri dichiarati nei metodi. Bisogna notare che la corrispondenza non deve essere necessariamente esatta.
Java puo' far affidamento, nella risoluzione di un metodo sovraccaricato, alla sua conversione automatica dei tipi.
Per rendere chiaro questo concetto, vi scrivo questo piccolo spezzone di codice:
class Sovraccarico {
void test()
{
System.out.println("Nessun parametro.");
}
void test(int a, int b)
{
System.out.println("Due parametri: " + a + " " + b);
}
void test(double a)
{
System.out.println("Un solo parametro: " + a);
}
}
class TestSovraccarico {
public static void main(String[] args)
{
Sovraccarico objTest = new Sovraccarico();
int j = 10;
// Chiamata senza parametri.
objTest.test();
// Chiamata con due parametri interi.
objTest.test(23, 54);
// Chiamata con un parametro intero.
objTest.test(j);
// Chiamata con un parametro double.
objTest.test(23.465);
}
}
Come si puo' notare, le prime due chiamate e l'ultima chiamata di objTest.test() vengono chiamate perche' c'e' una corrispondenza esatta come numero di parametri e come tipo fra i metodi sovraccaricati definiti nella classe Sovraccarico.
La terza chiamata, invece, non ha un riscontro come tipo ma se eseguiamo il programma, vediamo che il metodo test() che viene chiamato e' quello che ha come parametro quello double. Il tipo inter j, quindi, viene promosso a double e la risoluzione di overload verificata correttamente e eseguita senza problemi.
In Java 2 SE 5.0, (JDK 1.5) e' stata fatta un'interessante aggiunta. I metodi varargs, che consentono di avere una sintassi dedicata per quei metodi che devono utilizzare un numero variabile di argomenti. Prima del JDK 1.5, Java non aveva tale possibilita' e bisognava ricorrere a degli arcani artifici, come ad esempio passare gli argomenti per mezzo di un array. Io ho sempre considerato Java un buon linguaggio che a volte si perde in un bicchier d'acqua. Questa nuova aggiunta, secondo me, non smentisce cio' che penso.
La sintassi per i metodi varargs e' la seguente:
tipo metodo(tipo ... var);
Questa sintassi consente di avere dei metodi con parametri variabili, che vengono interpretati come un normale array che contiene i parametri. Tutti dello stesso tipo. Per esempio il metodo:
void test(int ... i);
consente di considerare `i' come un array `i[]' che contiene i vari parametri e il numero dei parametri passato come argomento di tale metodo puo' essere determinato facilmente con il campo length dell'oggetto array:
i.length
Con questa sintassi, come si vede, i vari parametri passati come argomento
del metodo devono essere necessariamente tutti dello stesso tipo. Cosa succede se si vuole dichiarare un metodo che usa parametri variabili ma che non siano tutti dello stesso tipo?
Bisogna usare l'overloading dei metodi. Cioe' dichiarare tanti metodi sovraccaricati tante quante sono le permutazioni di argomenti che vogliamo utilizzare. E qui' casca l'asino.
Supponiamo di dichiarare due metodi in overload con le seguenti dichiarazioni:
void test(int ... a);
void test(int a, int ... b);
Se nel codice usassi la chiamata test(1) cosa succederebbe? Entrambi i metodi precedentemente dichiarati possono essere validi candidati ad essere eseguiti. Quindi sarebbe meglio non utilizzare i metodi overloaded
ma utilizzare proprio metodi con nomi differenti per evitare ambiguita' di questo genere.
Java quindi consente la scrittura di codice ambiguo? Non e' forse questo uno dei principali capisaldi di Java, cioe' la robustezza del codice?
Mi viene in mente, a questo punto, il linguaggio C. Tanto criticato, dichiarato obsoleto, eppure, rimane ancora forte dei suoi concetti e metodi ultra testati e ampiamente accettati. Il tipo va_list e le macro va_start() e va_end() per implemetare chiamate con un numero variabile di argomenti...
effettivamente la tua riflessione è interessante, anche se poi il compilatore ti segnala questa ambiguità se si verifica, cmq, a parte questa ottima segnalazione di mjordan, penso che java 5.0 sia un'ottimo linguaggio, per gli scopi per i quali è stato studiato.
franksisca
20-06-2005, 20:47
Guarda, questa notizia è davvero molto interessante.Comunque, ho da farti una domanda.Come ci sei arrivato? o meglio, come te ne sei accorto?
Non ho ancora provato, ma se questo problema avviene anche per i Costruttori, cosa può succedere?
lombardp
20-06-2005, 20:57
Sicuro che venga compilato?
Data l'ambiguità il compilatore non dovrebbe dare errore? (Sarebbe il comportamento più corretto)
l'ho provato, se c'è una chiamata al metodo/i in questione che genera l'ambiguità, il compilatore si incavola
franksisca
20-06-2005, 21:17
Allora credo che il problema non sussiste più.Se non compila, allora non funziona?
Che ne pensate?
Come non compila? :confused:
A me compila perfettamente e non genera neanche uno straccio di warning :confused:
prova.java:19: reference to test is ambiguous, both method test(int,int...) in prova and method test(int...) in prova match
int r=p.test (1,2);
^
1 error
Procedura completata con codice di uscita 1
prova.java:19: reference to test is ambiguous, both method test(int,int...) in prova and method test(int...) in prova match
int r=p.test (1,2);
^
1 error
Procedura completata con codice di uscita 1
Due domande:
1) Puoi postare il codice?
2) L'output di javac -version (a me restituisce javac 1.5.0_02)
public class prova {
public static void test (int a, int ... b) {
int r=a;
for (int i=0; i<b.length; ++i)
r=b[i];
}
public static void test (int ... b) {
int r=0;
for (int i=0; i<b.length; ++i)
r=b[i];
}
public static void main (String [] args) {
p.test (1,2);
}
}
javac 1.5.0
prova.java:19: reference to test is ambiguous, both method test(int,int...) in prova and method test(int...) in prova match
int r=p.test (1,2);
^
1 error
Procedura completata con codice di uscita 1
Due domande:
1) Puoi postare il codice?
2) L'output di javac -version (a me restituisce javac 1.5.0_02)
Comunque hai evidenziato un altro aspetto, se penso che hai scritto nel codice quello che credo, poi vi dico :eek:
Questo codice dovrebbe compilare perfettamente e non emettere warning:
class Sovraccarico {
void test()
{
System.out.println("Nessun parametro.");
}
void test(int a, int b)
{
System.out.println("Due parametri: " + a + " " + b);
}
void test(double a)
{
System.out.println("Un solo parametro: " + a);
}
void test(double a, double ... b)
{
System.out.println("TADA");
}
}
class TestSovraccarico {
public static void main(String[] args)
{
Sovraccarico objTest = new Sovraccarico();
int j = 10;
double k = 13.45;
objTest.test();
objTest.test(23, 54);
objTest.test(j);
objTest.test(23.465);
objTest.test(k);
}
}
Ecco, come temevo :eek:
Praticamente nel tuo codice con due metodi overloaded e 2 dichiarazioni varargs, il compilatore emette un errore come dovrebbe essere.
Con due metodi overloaded e un solo metodo varargs, il compilatore non riconosce l'ambiguita' e non si degna neanche di generare un warning :eek:
Cos'e' questo, un'ambiguita' del costrutto o un bug del compilatore?
scusa, nel tuo codice non vedo dove sia l'ambiguità, se passo due interi, chiama test (int, int);, se passi un double, chiama test (double);, se passi più double (o più interi, ecc), chiama test (double, double ...)
scusa, nel tuo codice non vedo dove sia l'ambiguità, se passo due interi, chiama test (int, int);, se passi un double, chiama test (double);, se passi più double (o più interi, ecc), chiama test (double, double ...)
Qui:
void test(double a)
{
System.out.println("Un solo parametro: " + a);
}
void test(double a, double ... b)
{
System.out.println("TADA");
}
Se passi un solo double, anche il secondo metodo potrebbe essere un candidato. Difatti avere un parametro definito ed uno varargs definisce (come comportamento) che puoi passare anche solo un valore come parametro, quindi rientra in entrambi le tipologie. Questo lo puoi verificare eliminando il primo metodo che ti ho postato qui (quello con un solo parametro double) e lasciando quello cl parametro double e il parametro varargs. L'overload viene risoluto, utilizzando anche un solo parametro :eek:
lombardp
20-06-2005, 23:12
Qui:
void test(double a)
{
System.out.println("Un solo parametro: " + a);
}
void test(double a, double ... b)
{
System.out.println("TADA");
}
Se passi un solo double, anche il secondo metodo potrebbe essere un candidato. Difatti avere un parametro definito ed uno varargs definisce (come comportamento) che puoi passare anche solo un valore come parametro, quindi rientra in entrambi le tipologie. Questo lo puoi verificare eliminando il primo metodo che ti ho postato qui (quello con un solo parametro double) e lasciando quello cl parametro double e il parametro varargs. L'overload viene risoluto, utilizzando anche un solo parametro :eek:
Probabilmente chi ha progettato il compilatore ha stabilito che, in quel caso, l'ambiguità può essere risolta molto semplicemente: si sceglie la funzione con il numero esatto di parametri. Un po' come dire che le funzioni con numero esatto di parametri hanno la "priorità" rispetto a quelle con varargs, nella risoluzione delle ambiguità... è una scelta abbastanza logica.
se vogliamo prendere la questione rigorosamente, allora teoricamente ci potrebbe essere un'ambiguità, ma è evidente che il metodo chiamato a runtime è quello che più si avvicina alla lista di parametri passati, io, se devo essere sincero, non ci vedo nessun problema, anche se la tua riflessione è stata molto interessante, poi ho sicuramente meno esperienza di te, quindi potrebbe realmente esserci il problema, io, ripeto, vedo un comportamento molto chiaro e ovvio.
lombardp
20-06-2005, 23:18
Allora credo che il problema non sussiste più.Se non compila, allora non funziona?
Che ne pensate?
Sembra proprio che il problema non sussista, almeno non nei termini iniziali. L'aspetto evidenziato da Mjordan è effettivamente "delicato", ma il compilatore dovrebbe riuscire a segnalare tutte le ambiguità che non riesce a risolvere. Per quelle che riesce a risovere... bisogna ricordarsi che le priorità che utilizza.
Il fatto e' che a me ha causato un grosso problema, nel senso che avevo fatto una prototipazione (alla cazzo di cane, lo ammetto) dei metodi overloaded che mi sarebbero potuti servire. Non ho rimosso poi quello a singolo parametro ma avevo realizzato che poteva servirmi una versione a piu' parametri.
Abbiamo appurato che il compilatore fa la scelta giusta (perche come dice giustamente cisc, in caso di ambiguita' bisogna guardare le precedenze) pero', nella mia testa, c'era il metodo varargs che doveva essere eseguito e questo mi ha dato un paio d'ore di grattacapo perche il tutto era dentro una classe piu grossa con delle classi locali, ecc. ecc.)
In ogni caso, al di la della precedenza, sarebbe stato piu' giusto emettere almeno un warning, perche' l'ambiguita logica e' palese. Entrambi i metodi sovraccaricati potrebbero essere candidati possibili). In C, per esempio, il compilatore emette un warning quando fai una conversione di tipo automatica senza un casting esplicito.
La revisione del 1999 del C standard, ha addirittura eliminato la regola dell'int implicito (cioe' che quando a una funzione non specifichi il tipo di ritorno, viene considerato automaticamente come int). Quindi da un linguaggio come Java mi aspettavo, sinceramente, maggiore "loquacita'".
In generale e' considerato "male" avere regole implicite in un linguaggio.
Questo modo di operare del compilatore puo' dar vita a dei bug veramente oscuri. Quindi mi aspettavo di piu' da questo costrutto, soprattutto perche' Java ha sempre prediletto la "robustezza del codice" e l'impossibilita' di commettere errori concettuali abbastanza comuni. :) In sostanza, quello che voglio dire io, e' che per me quello che era corretto in quel momento nella mia testa era il metodo scartato. Quindi io e il compilatore concettualmente la pensavavamo diversamente in quel momento. Una situazione tipica da warning per un comune linguaggio, da errore abituato a come opera Java. Anche perche' l'ambiguita' segnalata da cisc effettivamente viene troncata in toto, anche se e' piu' forte. Comunque, ho imparato qualcosa di nuovo, se non altro spero di avervi dato uno spunto di riflessione in piu', visto che ame mi se n'e' andata la giornata appresso a sta cagata :sofico: Quando uno e' convinto che il codice che ha scritto e' giusto purtroppo ecco i risultati... :p
Che poi, scusate, una piccola provocazione. Ma sto costrutto varargs a parte l'introduzione sintattica, praticamente non ha introdotto niente per semplifucare la gestione dei parametri variabili. :D
Prima si gestivano comunque con un array passato per argomento, ora passo un intero che viene interpretato comunque come un array... Si, certo, all'occhio e' una sintassi piu' leggibile, ma come versatilita' di programmazione in questo caso rimane identico a prima :sofico:
Ahhhh... Ma forse questa e' carne per Java 1.6 :sofico:
franksisca
21-06-2005, 00:59
Adesso io vi faccio una domanda:
public Studente(int matricola, int eta, int esami){}
public Studente(int matricola, int eta){}
public Studente(int matricola){}
....
public static void main(String dati[]){
Studente studente=new Studente(25);
....
....
....
}
Che cosa :mbe: SUCCEDE :mbe: ?
Che c'e' di strano? :confused:
Viene richiamato il costruttore oveloaded
public Studente(int matricola){}
Non capisco cosa vuoi evidenziare.
In ogni caso, al di la della precedenza, sarebbe stato piu' giusto emettere almeno un warning, perche' l'ambiguita logica e' palese. Entrambi i metodi sovraccaricati potrebbero essere candidati possibili). In C, per esempio, il compilatore emette un warning quando fai una conversione di tipo automatica senza un casting esplicito.
E' tanto che non compilo C, ma sei sicuro che avvenga sempre? Io ricordo che accade solo se il casting implicito portasse a possibile perdita di informazione. Seguendo invece il tuo ragionamento il compilatore dovrebbe emettere un warning per il seguente codice, non ambiguo a mio parere:
public static void print(int i) { System.out.println("int " + i); }
public static void print(double d) { System.out.println("double " + d); }
public static void main(String... args) { print(3); }
In generale e' considerato "male" avere regole implicite in un linguaggio.
Dipende...
In sostanza, quello che voglio dire io, e' che per me quello che era corretto in quel momento nella mia testa era il metodo scartato. Quindi io e il compilatore concettualmente la pensavavamo diversamente in quel momento.
Il compilatore non può preoccuparsi di cosa pensa il programmatore, al limite può sforzarsi di fargli scrivere del codice corretto e non ambiguo.
Comunque, ho imparato qualcosa di nuovo,
...
se n'e' andata la giornata appresso a sta cagata :sofico: Quando uno e' convinto che il codice che ha scritto e' giusto purtroppo ecco i risultati... :p
Quando non hai dubbi è il momento di andarteli a cercare... Spesso mi ritrovo a controllare con dei semplici test se le mie verità coincidono con la realtà...
E' tanto che non compilo C, ma sei sicuro che avvenga sempre? Io ricordo che accade solo se il casting implicito portasse a possibile perdita di informazione. Seguendo invece il tuo ragionamento il compilatore dovrebbe emettere un warning per il seguente codice, non ambiguo a mio parere:
public static void print(int i) { System.out.println("int " + i); }
public static void print(double d) { System.out.println("double " + d); }
public static void main(String... args) { print(3); }
Bhe', i warning del compilatore C dipendono appunto dal compilatore e dai flag che controllano la paranoia dei warning, non c'e' un comportamento predefinito. GCC 4.0.0 rompe le balle anche quando due tipi differiscono in segno. Per quanto riguarda il mio discorso, esso si riferiva ai metodi sovraccaricati varargs. Il tuo codice in questo caso non e' ambiguo proprio perche' passare "3" come argomento e' diverso che passare "3.0", quindi l'ambiguita' non sussiste. L'ambiguita' che evidenziavo io era sul numero di parametri passato per argomento, non nella risoluzione dei tipi.
Il compilatore non può preoccuparsi di cosa pensa il programmatore, al limite può sforzarsi di fargli scrivere del codice corretto e non ambiguo.
Quindi sostituirsi al programmatore :D Il compilatore non e' soltanto uno strumento per generare codice. Un buon compilatore e' il primo strumento di testing. I metri di qualita' di un compilatore sono anche l'utilita' dei messaggi che e' in grado di generare. Tanto piu' e' loquace (purche' coerente) tanto piu' aiuta nella stesura del codice. L'esempio che ho dato io prima e' un codice "corretto in sintassi" ma "scorretto in logica" perche' presenta piu' di un caso valido. Siccome non mi vengono possibili circostanze in cui avere due metodi varargs sovraccaricati in quel modo possa risultare utile (e comunque realizzabili in modo diverso e sicuramente piu' chiaro), per come la penso io dovrebbe generare almeno un warning, anche in vista del fatto che il codice presentato da cisc addirittura abortisce con un errore. Le differenze sono molto sottili, ma presenti. Se stavamo parlando di C o C++ probabilmente la questione non sarebbe sorta. Ma Java nelle sue specifiche sottolinea la parola "robusto" nella descrizione del linguaggio. Chiaramente un programmatore non puo' essere sempre in grado di garantire la robustezza di quello che scrive e qui il compilatore e' un valido aiuto. :)
Voglio provare a sollevare la questione sul JCP, voglio vedere cosa mi dicono :)
GCC 4.0.0 rompe le balle anche quando due tipi differiscono in segno.
intendi signed/unsigned? Non ci sarebbe possibile perdita di informazione?
Per quanto riguarda il mio discorso, esso si riferiva ai metodi sovraccaricati varargs. Il tuo codice in questo caso non e' ambiguo proprio perche' passare "3" come argomento e' diverso che passare "3.0", quindi l'ambiguita' non sussiste. L'ambiguita' che evidenziavo io era sul numero di parametri passato per argomento, non nella risoluzione dei tipi.
Intendevo fare un paragone proprio a livello logico ma non ho reso l'idea.
Ti sei chiesto cosa succede se dichiari un metodo m(int... interi) e lo invochi come m()? Succede che il compilatore effettua la chiamata come m(x) dove x è un'istanza di int[], e non NULL come si potrebbe anche pensare. Quindi nel caso da te citato, è logico che a fronte della chiamata m(1) il compilatore scelga di invocare il metodo m(int i) piuttosto che m(int i, int... x) che comporterebbe la creazione ad hoc di un'istanza di int[].
Il compilatore non e' soltanto uno strumento per generare codice. Un buon compilatore e' il primo strumento di testing. I metri di qualita' di un compilatore sono anche l'utilita' dei messaggi che e' in grado di generare. Tanto piu' e' loquace (purche' coerente) tanto piu' aiuta nella stesura del codice.
Sono d'accordo, anche se ci sono altre correnti di pensiero che preferiscono affibbiare l'onere della correttezza logica del codice ad appositi framework di test, rendendo più 'accomodante' il compilatore...
L'esempio che ho dato io prima e' un codice "corretto in sintassi" ma "scorretto in logica" perche' presenta piu' di un caso valido.
E' qui che non ti capisco... Per me è alla stessa strega del casting implicito se necessario e sicuro. Con la tua logica anche m(int i) e m(double d) potrebbero rendere ambigua la chiamata m(3).
per come la penso io dovrebbe generare almeno un warning, anche in vista del fatto che il codice presentato da cisc addirittura abortisce con un errore.
Il codice di cisc da un errore in fase di compilazione, non di esecuzione. Quindi direi che il compilatore ti avvisa eccome!
Voglio provare a sollevare la questione sul JCP, voglio vedere cosa mi dicono :)
Facci sapere ;)
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.