[Java] Esercizio generici

di il
15 risposte

[Java] Esercizio generici

Progettare una classe generica i cui oggetti rappresentino una collezione di coppie (x, y) senza ripetizioni. La classe dispone dei seguenti
metodi:
- aggiungi: data in input una coppia (x, y), la aggiunge alla collezione.
- elimina: data in input una coppia (x, y), la elimina se è presente.
- getCoppiePerX: data la coordinata x, fornisce l'insieme delle coppie aventi la coordinata x specificata;
- toString: restituisce una rappresentazione a stringa della collezione di coppie analoga a quella del file fornito in ingresso, in cui i valori x delle coppie sono ordinati secondo l'ordinamento naturale del tipo di x.

Inizialmente avevo pensato di creare una classe Coppia generica, ossia:

=== COPPIA ====

public class Coppia<T,S> {
	
	private T x;
	private S y;
	
	public Coppia(T x, S y){
		this.x=x;
		this.y=y;
	}

	public T getX(){return x;}
	public S getY(){return y;}
}


Poi creare una classe MultiCoppia che contiene una Collection di elementi di tipo Coppia generici.
Però così facendo mi da un problema di compilazione.

public class MultiCoppia <T,S> {

	private Collection<Coppia<T,S>> l;
	
	public MultiCoppia(Collection<Coppia<T,S>> l){
		this.l = l;
	}
	
	public void aggiungi(Coppia<T,S> c){
		if(c!=null) l.add(c);
	}
	
	public void elimina(Coppia<T,S> c){
		l.remove(c);
	}
	
	public Set<Coppia<T,S>> getCoppiePerX(T x){
		
		return l.stream().filter(xx -> xx.getX().equals(x)).collect(Collectors.toSet());
		
	}
	
	public String toString(){ 
		String s="";
		
		for(Coppia c : l){
			s+=c.getX()+","+c.getY();
			s+="\n";
		}
		
		return s;
	}

Non pensavo fosse tanto difficile lavorare con i generici, sono molto delicati.

Qualche consiglio su come strutturare il problema?

Ciao

15 Risposte

  • Re: [Java] Esercizio generici

    Paolovox ha scritto:


    Però così facendo mi da un problema di compilazione.
    In MultiCoppia nel toString() hai usato Coppia come "raw type", ovvero come tipo non parametrizzato.

    Quindi, invece di:

    for(Coppia c : l) {

    metti

    for(Coppia<T,S> c : l) {

    Paolovox ha scritto:


    Non pensavo fosse tanto difficile lavorare con i generici, sono molto delicati.
    Mah ... a dire il vero, in questo esercizio l'uso dei generics è davvero super-basilare. Cioè solo due classi "generiche" che dichiarano delle type variable (T e S) e il relativo uso di queste in variabili/metodi.

    Ma sui generics c'è molto .. molto di più. Se non li hai ancora visti, aspetta di vedere i bounds, wildcard, wildcard capture, la erasure e i suoi effetti e altro.

    Diciamo che se hai già capito perché un List<String> NON è-un (e non è assegnabile a) List<Object>, allora hai già capito molto, diciamo il "grosso" dei generics che è l'aspetto cruciale della implementazione dei generics.
  • Re: [Java] Esercizio generici

    Ti ringrazio da adesso ho cominciato a leggere la guida ufficiale https://docs.oracle.com/javase/tutorial/java/generics.

    Comunque il problema sorge quando faccio aggiungi(...), dato che la mia collezione MultiCoppia così implementata deve per forza contenere un insieme di coppie omogenee. Non capisco come crearla mista a partire da qualsiasi coppia.

    Buona giornata e grazie per la risposta.
  • Re: [Java] Esercizio generici

    Paolovox ha scritto:


    Comunque il problema sorge quando faccio aggiungi(...), dato che la mia collezione MultiCoppia così implementata deve per forza contenere un insieme di coppie omogenee. Non capisco come crearla mista a partire da qualsiasi coppia.
    Allora vedi prima la parte concettuale "perché un List<String> NON è-un (e non è assegnabile a) List<Object>"

    E sappi anche che se hai un Coppia<Number,Object>, esso può contenere es. un Integer e String, oppure un Long e un Date. Insomma la parametrizzazione è Number,Object ma per gli oggetti realmente passati vale la ereditarietà.
  • Re: [Java] Esercizio generici

    Ci sono sulla prima parte concettuale.
    Se abbiamo due classi A e B, e B deriva da A, allora è concettualmente sbagliato dire che una collezione di B deriva dalla collezioni di elementi di A. Qundi non esiste nessuna relazione di ereditarietà tra le due collezioni e l'assegnazione di una lista di String ad una variabile di tipo List<Object> genera un errore a tempo di compilazione e viceversa.

    Creo quindi una classe generica con tipi di parametri generici, nel mio caso ho due tipi di parametri generici, usati sia per la classeCoppia che per la classe MultiCoppia. Quindi nel momento in cui istanzio l'oggetto MultiCoppia, gli inferisco esplicitamente i due tipi di argomenti per i tipi di parametri <T,S> esempio <Integer, String>. Da qui in poi i tipi di argomenti vengono inferiti automaticamente anche alla classe Coppia, e quindi avrò una collezione di coppie omogenee Integer, String, e qualsiasi altra coppia io voglia inserirci mi da errore a tempo di compilazione.

    Però a tempo di esecuzione scatta il meccanismo di type erasure, e quindi i tipi generici non sono più disponibili, ma diventano o Object o la prima classe presente in un vincolo del tipo.

    Quando parte l'esecuzione dal main e trova ad esempio MultiCoppia<Integer,String> mc = new MultiCoppia<>(Arrays.asList()); ,
    comunque dopo il type erasure sarà MultiCoppia<Object,Object> ? Quindi è utile a prevenire errori a tempo di compilazione?

    Sto cercando di fare chiarezza per applicare questo nuovo stile di programmazione e capirne l'utilità.
  • Re: [Java] Esercizio generici

    Così è possibile creare una collezione multiCoppia eterogenea, ma inferendogli il tipo Object mi sembra una presa per i fondelli come soluzione. E potrei tra l'altro con lo stesso tipo generico, assegnargli qualsiasi tipo.
    
    public class MultiCoppiaV2<T> {
    
    	private Set<Coppia> s = new HashSet<>();
    	
    	private class Coppia{
    		
    		private T x,y;
    		
    		public Coppia(T x, T y){this.x = x; this.y= y;}
    		
    		public T getX(){return x;}
    		public T getY(){return y;}
    		
    	}
    	
    	public void aggiungi(T x, T y){
    		s.add(new Coppia(x,y));
    	}
    	
    	public String toString(){
    		String ss="";
    		for(Coppia c : s){
    			ss+=c.getX()+","+c.getY()+"\n";
    		}
    		return ss;
    	}
    	
    	public static void main(String[] args) {
    		
    		MultiCoppiaV2<Object> mv = new MultiCoppiaV2<>();
    		mv.aggiungi(1,12);
    		mv.aggiungi("ciao", 42);
    		mv.aggiungi('a', new Date());
    		System.out.println(mv.toString());
    		
    	}
    
    Aggiornato con due tipi generici e non solo uno e gli inferisco il tipo di argomento nel main, perdendo l'eterogeneità della collezione.
    
    public class MultiCoppiaV2<T,S> {
    
    	private Set<Coppia> s = new HashSet<>();
    	
    	private class Coppia{
    		
    		private T x;
    		private S y;
    		
    		public Coppia(T x, S y){this.x = x; this.y= y;}
    		
    		public T getX(){return x;}
    		public S getY(){return y;}
    		
    	}
    	
    	public void aggiungi(T x, S y){
    		s.add(new Coppia(x,y));
    	}
    	
    	public String toString(){
    		String ss="";
    		for(Coppia c : s){
    			ss+=c.getX()+","+c.getY()+"\n";
    		}
    		return ss;
    	}
    	
    	public static void main(String[] args) {
    		
    		MultiCoppiaV2<Integer, Integer> mv = new MultiCoppiaV2<>();
    		mv.aggiungi(1,12);
    
  • Re: [Java] Esercizio generici

    Paolovox ha scritto:


    Ci sono sulla prima parte concettuale.
    Se abbiamo due classi A e B, e B deriva da A, allora è concettualmente sbagliato dire che una collezione di B deriva dalla collezioni di elementi di A. Qundi non esiste nessuna relazione di ereditarietà tra le due collezioni e l'assegnazione di una lista di String ad una variabile di tipo List<Object> genera un errore a tempo di compilazione e viceversa.
    Ok, partiamo da questo. Il punto è capire bene il "perché". E per farlo in genere è utile vedere la questione contrapposta agli array.

    Un String[] è-un Object[]
    String[] strs = new String[5];
    Object[] objs = strs;     // OK
    Mentre un List<String> NON è-un List<Object>
    List<String> strs = new ArrayList<String>();
    List<Object> objs = strs;     // NOOOOOO !
  • Re: [Java] Esercizio generici

    Un String[] è-un Object[]

    - Il primo assegnamento è fattibile perchè:
    String deriva ed eredita tutto da Object e nel momento in cui facciamo puntare objs nell'heap all'oggetto dove punta strs, viene effettuato un cast implicito. Da qui in poi a tutti gli oggetti contenuti nell'array di stringhe, verrà limitata la propria interfaccia ai soli metodi che possiede la classe Object.
    In memoria comunque la natura dell'array sarà di tipo String, infatti ricastando estendiamo di nuovo l'interfaccia dei metodi da poter utilizzare.
    
    	String[] str = new String[]{"pippo","pluto"};
    	Object[] obj = str;
    		
    		
    		for(int i=0; i<obj.length ; i++){
    			System.out.println(((String)obj[i]).length());
    		}
    
    - Il secondo non è fattibile perchè a tempo di esecuzione non sappiamo il tipo di argomento inferito al tipo generico, dato che dopo il meccanismo di type erasure, saranno tutti Object o la classe del vincolo. E poi anche il discorso concettuale di prima, ovvero che non esiste nessuna relazione di ereditarietà tra due collezioni G<A> e G<B>, nonostante A e B siano uniti da una relazione di ereditarietà.
    In quest'ultimo caso sarebbe anche possibile, ma è impredicibile.

    Mi ci sto avvicinando??
  • Re: [Java] Esercizio generici

    Paolovox ha scritto:


    - Il secondo non è fattibile perchè a tempo di esecuzione non sappiamo il tipo di argomento inferito al tipo generico, dato che dopo il meccanismo di type erasure, saranno tutti Object o la classe del vincolo.
    Sì, il punto è questo. Per gli array è diverso, perché a runtime un oggetto array mantiene la informazione "i miei elementi sono di tipo X" (si dice che il tipo è reified, "reificato").
    String[] strs = new String[5];
    Object[] objs = strs;          // OK
    objs[0] = new Integer(123);    // a runtime: ArrayStoreException
    Anche se tu "vedi" l'array come Object[] non puoi inserire qualcosa di errato per l'array realmente istanziato. Il tipo dell'array è controllato quando assegni qualcosa! Quindi il problema è già controllato a monte.


    Mentre per i generics, che sono implementati per erasure, è diverso.
    List<String> strs = new ArrayList<String>();
    List<Object> objs = strs;     // SE questo fosse reso possibile .....
    objs.add(new Integer(123));   // allora questo sarebbe lecito ma .....
    ... poi magari in un altro punto completamente diverso dell'applicazione farai es.

    String s = strs.get(0);

    e ti beccheresti un ClassCastException, perché il compilatore mette un cast nascosto come se fosse String s = (String) strs.get(0);
    E ovviamente il Integer inserito non può essere un String.

    Questo sarebbe molto ... MOLTO più grave. Ed in sostanza è questo il motivo per cui i generics sono "invarianti" (mentre gli array sono "covarianti").
  • Re: [Java] Esercizio generici

    Wow grazie a te mi si è aperta una nuova finestra sul mondo di Java e ho acquisito un minimo di consapevolezza in più.

    Un'ulteriore domanda che mi sto ponendo è:
    volendo ottenere una collezione eterogenea, è lecito utilizzare direttamente il tipo raw e quindi inferire al tipo generico direttamente Object come in questo caso? O semplicemente non utilizzare proprio un approccio generico ma direttamente coppie (Object, Object) ?

    === MultiCoppie Eterogenea ===
    
    MultiCoppiaV2 mv = new MultiCoppiaV2<>();	//MultiCoppiaV2<Object,Object> 
    		mv.aggiungi(1,12);
    		mv.aggiungi("ciao", 42);
    		mv.aggiungi("ciao", 42);
    		mv.aggiungi('a', new Date());
    		System.out.println(mv.toString());
    
    Adesso dato che inserisco le mie coppie in un Set, la coppia ("ciao",42) viene inserita due volta. Come faccio a generare un hashCode univoco per oggetti con le stesse proprietà, non conoscendo molto sulla natura dell'oggetto?
    
    private class Coppia{
    		
    		private T x;
    		private S y;
    		
    		public Coppia(T x, S y){this.x = x; this.y= y;}
    		
    		public T getX(){return x;}
    		public S getY(){return y;}
    		
    	}
    
  • Re: [Java] Esercizio generici

    Paolovox ha scritto:


    volendo ottenere una collezione eterogenea, è lecito utilizzare direttamente il tipo raw e quindi inferire al tipo generico direttamente Object come in questo caso? O semplicemente non utilizzare proprio un approccio generico ma direttamente coppie (Object, Object) ?
    Se hai una classe "generica", è bene non usare il raw type, perché vai incontro a warning di "unchecked". Quindi meglio:

    MultiCoppiaV2<Object,Object> mv = new MultiCoppiaV2<>();

    Paolovox ha scritto:


    Adesso dato che inserisco le mie coppie in un Set, la coppia ("ciao",42) viene inserita due volta. Come faccio a generare un hashCode univoco per oggetti con le stesse proprietà, non conoscendo molto sulla natura dell'oggetto?
    Per HashSet è necessario ridefinire hashCode() e equals(). Vanno sempre ridefiniti entrambi in modo appropriato, altrimenti quasi sicuramente "rompi" il contratto che deve esistere tra i due metodi.

    Non hai bisogno di "sapere" cosa sono gli oggetti. Sicuramente avranno un equals e hashCode. Una cosa a cui prestare attenzione: equals riceve un Object e il parametro deve restare tale. Ma a te serve vederlo come Coppia ... solo che Coppia è una classe "generica". In questo caso è utile usare il wildcard, ovvero Coppia<?,?> perché alla fine non ti interessa nulla della parametrizzazione.
    Se nel equals facessi

    Coppia<T,S> c = (Coppia<T,S>) obj;

    avresti un unchecked cast. Ecco perché è meglio il wildcard. Chiaramente poi i due x/y li potrai solo vedere come Object ma è sufficiente per usare equals()
  • Re: [Java] Esercizio generici

    Grazie mille per i chiarimenti. Solo che ho ancora qualche problema e in quest'implementazione ancora mi inserisce due volte la coppia ("ciao",42).
    
    public class MultiCoppiaV2<T,S> {
    
    	private Set<Coppia<T,S>> s = new HashSet<>();
    	
    	private class Coppia<T,S>{
    		
    		private T x;
    		private S y;
    		
    		public Coppia(T x, S y){this.x = x; this.y= y;}
    		
    		public T getX(){return x;}
    		public S getY(){return y;}
    		
    		
    		@Override
    		public boolean equals(Object obj){
    			if ( this == obj ) return true;
    			if ( obj.getClass() ==  Coppia.class) return false;
    			
    			Coppia<?,?> c = (Coppia<?,?>)obj;
    			
    			return (c.getX().equals(this.x) && c.getY().equals(this.y)) ? true : false;	
    		}
    		
    		@Override
    		public int hashCode(){
    			return (x.hashCode()+23)*(y.hashCode()+42)/3 ;
    		}
    		
    	}
    	
    	public void aggiungi(T x, S y){
    		s.add(new Coppia<T,S>(x,y));
    	}
    	
    	public String toString(){
    		String ss="";
    		for(Coppia<T,S> c : s){
    			ss+=c.getX()+","+c.getY()+"\n";
    		}
    		return ss;
    	}
    	
    	public static void main(String[] args) {
    		
    		MultiCoppiaV2<Object, Object> mv = new MultiCoppiaV2<>();
    		mv.aggiungi(1,12);
    		mv.aggiungi("ciao", 42);
    		mv.aggiungi("ciao", 42);
    		
    		mv.aggiungi('a', new Date());
    		System.out.println(mv.toString());
    		
    	}
    
    
    Ciao e buona giornata.
  • Re: [Java] Esercizio generici

    Paolovox ha scritto:


    Grazie mille per i chiarimenti. Solo che ho ancora qualche problema e in quest'implementazione ancora mi inserisce due volte la coppia ("ciao",42).
    		@Override
    		public boolean equals(Object obj){
    			if ( this == obj ) return true;
    			if ( obj.getClass() ==  Coppia.class) return false;
    			
    			Coppia<?,?> c = (Coppia<?,?>)obj;
    			
    			return (c.getX().equals(this.x) && c.getY().equals(this.y)) ? true : false;	
    		}
    		
    		@Override
    		public int hashCode(){
    			return (x.hashCode()+23)*(y.hashCode()+42)/3 ;
    		}
    
    In equals

    if ( obj.getClass() == Coppia.class) return false;

    è nel senso sbagliato (nel tuo caso è sempre false!). Questo è il punto principale errato. Poi al fondo si potrebbe evitare l'operatore condizionale ?: ma questo è il meno.

    Il hashCode è di per sé tecnicamente corretto. Ma in genere non si fanno divisioni o cose più strane. Basta qualcosa tipo:

    return x.hashCode() * 31 + y.hashCode();

    per dare un "peso" diverso al hashCode di x.
  • Re: [Java] Esercizio generici

    Si giusto

    Così potrebbe andare e sembra funzionante:
    
    @Override
    		public boolean equals(Object obj){
    			if ( this == obj) return true;
    			if ( obj == null || obj.getClass() !=  Coppia.class) return false;
    			
    			Coppia<?,?> c = (Coppia<?,?>)obj;
    			
    			return (c.getX().equals(this.x) && c.getY().equals(this.y));
    		}
    		
    		@Override
    		public int hashCode(){
    			return (x.hashCode()+23)*(y.hashCode()+42);
    		}
    
  • Re: [Java] Esercizio generici

    Paolovox ha scritto:


    Così potrebbe andare e sembra funzionante
    Sì, è tecnicamente corretto.

    Comunque ribadisco che per il hashCode generalmente non si cercano cose "strane", numeri a caso o moltiplicazioni tra i valori. Può bastare:

    x.hashCode() * 31 + y.hashCode();
Devi accedere o registrarti per scrivere nel forum
15 risposte