Ancora sui Generici (problema più complesso)

di il
6 risposte

Ancora sui Generici (problema più complesso)

Ciao. pensavo di aver capito i generici, ma forse non è proprio cosi.
ho preso questo esempio da un libro che sto leggendo di un conosciuto autore:
l'esempio permette attraverso i generici di imporre delle regole e gestire tutto senza eccezioni, con classi come Erba, Erbivoro, Carnivoro , ma è più facile leggere il codice che descriverlo

public interface Cibo {
	String getCosa();
}
public interface Animale<C extends Cibo> {
	void mangia(C cibo);
}
public class Erba implements Cibo {
	public String getCosa() {
		return "erba";
	}
}
public class Erbivoro<E extends Erba> implements Cibo, Animale<E> {
	public void mangia(E cibo) {
		System.out.println("Sto mangiando " + cibo.getCosa());
	}
	public String getCosa() {
		return "Erbivoro";
	}
}
public class Carnivoro implements Animale<Erbivoro> {
	public void mangia(Erbivoro cibo) {
		System.out.println("Sto mangiando " + cibo.getCosa());
	}
}
public class Tester {
	public static void main(String args[]) {
		Erba e = new Erba();
		Erbivoro<Erba> mucca = new Erbivoro<>();
		Carnivoro tigre = new Carnivoro();

		System.out.print("Mucca: "); mucca.mangia(e);
		System.out.print("Tigre: "); tigre.mangia(mucca);

		/* 
		 * queste altre due istruzioni non compilano
		 * un oggetto mucca non può accettare un oggetto Animale come parametro
		 * per il vincolo del tipo generico <Erba>
		 * 
		 * una tigre non accetterà mai un oggetto di tipo erba per il vincolo 
		 * del tipo nell'implementazione dell'interfaccia Animale <Erbivoro>
		 */
		//System.out.print("Mucca: "); mucca.mangia(mucca);
		//System.out.print("Tigre: "); tigre.mangia(e);

		/*
		 * queste due di seguito però, anche se logicamente corrette, non compilano.
		 * bisogna modificare le classi e le interfacce
		 */
		//Carnivoro Leone = new Carnivoro();
		//System.out.print("Leone: "); leone.mangia(tigre);
	}
}
come da commenti che ho scritto nel codice, quale classe modifico e come, in modo che l'Erbivoro possa mangiare SOLO l'Erba, ma il Carnivoro può mangiare SIA l'Erbivoro CHE il Carnivoro?

Ho fatto decine di prove, ma non capisco dove sbaglio, mi restituisce sempre che a causa del vincolo su Erbivono, qualsiasi modifica faccia a Carnivoro, non viene accettata!

Capisco anche che può essere lunga, ma ringrazio in anticipo chi mi potrà aiutare e chi ha voglia.
Tagan.

6 Risposte

  • Re: Ancora sui Generici (problema più complesso)

    tagan ha scritto:


    in modo che l'Erbivoro possa mangiare SOLO l'Erba
    Precisiamo una cosa: un Erbivoro può tecnicamente/teoricamente mangiare qualunque cosa che è esattamente Erba o (se esistesse) qualcosa "sotto" Erba.
    Perché è Erbivoro<E extends Erba> quindi puoi avere un Erbivoro<Erba> ma se esistesse una classe:

    public class Cicoria extends Erba { ...... }

    allora puoi anche fare un Erbivoro<Cicoria> e sarebbe corretto, questo erbivoro mangia Cicoria (e non Erba più generalizzato).

    tagan ha scritto:


    ma il Carnivoro può mangiare SIA l'Erbivoro CHE il Carnivoro?
    Per come è ora, no, Carnivoro può mangiare solo un Erbivoro, perché Carnivoro implements Animale<Erbivoro> e Erbivoro è-un Cibo.

    Tra l'altro c'è una incompletezza: Erbivoro è una classe "generica" e quindi andrebbe usata parametrizzata. Ma qui:

    class Carnivoro implements Animale<Erbivoro> {
    public void mangia(Erbivoro cibo) {

    è usata SENZA parametrizzazione. Quindi comunque il codice è "dubbio".


    P.S. ho già visto queste classi altrove in passato e sono sicuro di aver già risposto su un contesto del genere. La mia idea è che codici/esempi di questo tipo servono solo per confondere di più le idee sui generics invece di chiarirle ....
  • Re: Ancora sui Generici (problema più complesso)

    andbin ha scritto:


    Precisiamo una cosa: un Erbivoro può tecnicamente/teoricamente mangiare qualunque cosa che è esattamente Erba o (se esistesse) qualcosa "sotto" Erba.
    Perché è Erbivoro<E extends Erba> quindi puoi avere un Erbivoro<Erba> ma se esistesse una classe:

    public class Cicoria extends Erba { ...... }

    allora puoi anche fare un Erbivoro<Cicoria> e sarebbe corretto, questo erbivoro mangia Cicoria (e non Erba più generalizzato).
    Esatto, mi sono espresso male. Intendevo dire Erba e sue sottoclassi.

    andbin ha scritto:


    Per come è ora, no, Carnivoro può mangiare solo un Erbivoro, perché Carnivoro implements Animale<Erbivoro> e Erbivoro è-un Cibo.

    Tra l'altro c'è una incompletezza: Erbivoro è una classe "generica" e quindi andrebbe usata parametrizzata. Ma qui:

    class Carnivoro implements Animale<Erbivoro> {
    public void mangia(Erbivoro cibo) {

    è usata SENZA parametrizzazione. Quindi comunque il codice è "dubbio".

    P.S. ho già visto queste classi altrove in passato e sono sicuro di aver già risposto su un contesto del genere. La mia idea è che codici/esempi di questo tipo servono solo per confondere di più le idee sui generics invece di chiarirle ....
    E come si usa in quel caso la parametrizzazione? <Erbivoro> cosi scritto, non è un parametro? o andava messo <T extends Erbivoro>
    ad esempio e ...void mangia(T cibo)
    
    class Carnivoro<T extends Erbivoro> implements Animale<T> {
    public void mangia(T cibo) {
    
    inoltre ho provato a implementare Cibo anche in Carnivoro, in modo "trasformare" un Carnivoro anche in Cibo, ma senza sucesso, a meno che non debba modificare anche l'interfaccia Animale.
  • Re: Ancora sui Generici (problema più complesso)

    tagan ha scritto:


    E come si usa in quel caso la parametrizzazione? <Erbivoro> cosi scritto, non è un parametro? o andava messo <T extends Erbivoro>
    ad esempio e ...void mangia(T cibo)
    
    class Carnivoro<T extends Erbivoro> implements Animale<T> {
    public void mangia(T cibo) {
    
    inoltre ho provato a implementare Cibo anche in Carnivoro, in modo "trasformare" un Carnivoro anche in Cibo, ma senza sucesso, a meno che non debba modificare anche l'interfaccia Animale.
    La questione è questa: Erbivoro mangia "qualcosa" e questo qualcosa è da parametrizzare (perché Erbivoro è una classe "generica"). Se Carnivoro mangia un Erbivoro .... allora bisogna trovare il modo per specificare COSA mangia l'erbivoro a sua volta mangiato dal Carnivoro!

    Ora, una possibilità è rendere Carnivoro anch'essa "generica" e rendere parametrizzabile il cibo MANGIATO dal Erbivoro (e solo Erbivoro).
    class Carnivoro<E extends Erba> implements Animale<Erbivoro<E>> {
    	public void mangia(Erbivoro<E> cibo) {
    allora puoi fare:
    Erbivoro<Cicoria> coniglio = new Erbivoro<>();
    
    Carnivoro<Cicoria> unCarnivoro = new Carnivoro<>();
    unCarnivoro.mangia(coniglio);   // OK !
    Ma è chiaro che quel unCarnivoro potrà mangiare SOLO erbivori che mangiano SOLO Cicoria (e non Erba più "sopra").


    Altra possibilità è rendere Carnivoro "generica" ma rendere parametrizzabile il cibo mangiato dal Carnivoro, senza specificare Erbivoro:
    class Carnivoro<C extends Cibo> implements Animale<C> {
    	public void mangia(C cibo) {
    e allora puoi fare:
    Erbivoro<Cicoria> coniglio = new Erbivoro<>();
    
    Carnivoro<Erbivoro<Cicoria>> unCarnivoro = new Carnivoro<>();
    unCarnivoro.mangia(coniglio);   // OK !

    Le due varianti sono entrambe CORRETTE tecnicamente ma hanno un senso/uso differente. Ti è chiaro?
  • Re: Ancora sui Generici (problema più complesso)

    andbin ha scritto:


    e allora puoi fare:
    Erbivoro<Cicoria> coniglio = new Erbivoro<>();
    
    Carnivoro<Erbivoro<Cicoria>> unCarnivoro = new Carnivoro<>();
    unCarnivoro.mangia(coniglio);   // OK !
    Le due varianti sono entrambe CORRETTE tecnicamente ma hanno un senso/uso differente. Ti è chiaro?
    questo codice non compila. mi da
    Exception in thread "main" java.lang.Error: Unresolved compilation problems:
    Bound mismatch: The type Erbivoro<Cicoria> is not a valid substitute for the bounded parameter <C extends Cibo> of the type Carnivoro<C>
    Cannot infer type arguments for Carnivoro<>

    ho modificato tutto in questo modo ma credo di aver fatto diversi errori anche se il programma sembra funzionare, perché ho warning sulla Classe Carnivoro come se non la parametrizzassi (ho racchiuso tutto in una unica classe):
    
    interface Cibo{}
    
    interface Animale<C extends Cibo> extends Cibo{
    	void mangia(C cibo);
    }
    
    class Erba implements Cibo{}
    
    class Erbivoro<E extends Erba> implements Animale<E>{
    	public void mangia(E erba) {
    		System.out.println("Erbivoro mangia Erba");
    	}
    }
    
    class Carnivoro<A extends Animale> implements Animale<A>{
    	public void mangia(A cibo) {
    		System.out.println("Carnivoro mangia Erbivoro e Carnivoro");
    	}
    }
    
    public class TesterGeneric {
    	public static void main(String[] args) {
    		Erba erba = new Erba();
    		Erbivoro<Erba> mucca = new Erbivoro<>();
    		Carnivoro<Erbivoro> tigre = new Carnivoro<>();
    		Carnivoro<Carnivoro> leone = new Carnivoro<>();
    		Carnivoro iena = new Carnivoro();
    		System.out.print("Mucca: "); mucca.mangia(erba);
    		System.out.print("Tigre: "); tigre.mangia(mucca);
    		System.out.print("Leone: "); leone.mangia(tigre);
    		System.out.print("Iena: "); iena.mangia(leone);
    
    		// QUESTA NON COMPILA. "iena" non mangia "erba" .....OK!
    		//System.out.print("Iena: "); iena.mangia(erba);
    	}
    }
    
    Ogni istanza di Carnivoro la posso scrivere come mi pare e questo non mi sembra corretto....!
    Warning: Carnivoro is a raw type. References to generic type Carnivoro<A> should be parameterized.

    credo che quel Animale<C extends Cibo> extends Cico .....non faccia bene!!!
    e neanche class Carnivoro<A extends Animale> implements Animale<A> che accetta e implementa un Animale!

    scrivere
    Carnivoro<Carnivoro<Erbivoro<Erba>>> leone = new Carnivoro<>();
    non da più warning, ma impone il vincolo che al metodo mangia, possa essere passato solo un erbivoro, quindi siamo al punto di partenza!
    questa non compila: leone.mangia(tigre);


    ma c'è soluzione?
    Boh!
    mi sa che passo ai thread....
    intanto scrivo all'autore......se mi risponde!
    Grazie.
  • Re: Ancora sui Generici (problema più complesso)

    tagan ha scritto:


    questo codice non compila. mi da
    Exception in thread "main" java.lang.Error: Unresolved compilation problems:
    Bound mismatch: The type Erbivoro<Cicoria> is not a valid substitute for the bounded parameter <C extends Cibo> of the type Carnivoro<C>
    Cannot infer type arguments for Carnivoro<>
    No, ti assicuro che è corretto e "compila" (l'ho scritto e compilato!).
    Chiaramente devi avere la classe Cicoria e la forma di Carnivoro deve essere la seconda che ho riportato.

    tagan ha scritto:


    ho modificato tutto in questo modo ma credo di aver fatto diversi errori anche se il programma sembra funzionare, perché ho warning sulla Classe Carnivoro come se non la parametrizzassi (ho racchiuso tutto in una unica classe):
    Infatti hai scritto:

    Carnivoro iena = new Carnivoro();

    Che NON è parametrizzata (né la variabile né la istanziazione).

    E questo è appunto un "raw type".

    tagan ha scritto:


    ma c'è soluzione?
    Boh!
    mi sa che passo ai thread....
    intanto scrivo all'autore......se mi risponde!
    Ascolta, mi sa che tutto questo scenario ti sta solo confondendo le idee sui generics (che invece sono più semplici).

    Ora: COSA vuoi che Carnivoro possa fare? Che: a) sia un Animale E b) possa mangiare un (qualunque) cibo (che comprende Erbivoro dato che è implements Cibo)?

    Allora molto banalmente:
    class Carnivoro implements Animale<Cibo> {
    	public void mangia(Cibo cibo) {
    E al mangia puoi passare QUALUNQUE oggetto purché discenda da Cibo. E' chiaro che non puoi passare un Carnivoro, perché non è un cibo ma in teoria potresti renderlo tale (similmente a Erbivoro).
  • Re: Ancora sui Generici (problema più complesso)

    Infatti questo esempio mi sta solo confondendo le idee, perché forse cosi riportato non c'è soluzione a quello che volevo. mi spisgo meglio e definitivamente:
    come è fatto il programma nel libro:
    - interfaccia Cibo
    - Intefaccia Animale<Parametrizzata con Cibo>
    - Classe Erba che implementa Cibo
    - Classe Erbivoro che accetta solo Erba
    - Classe Carnivoro che accetta solo Erbivoro, quindi:
    ......un Erbivoro mangia Erba
    ......un Carnivoro mangia Erbivoro MA NON Erba E NON Carnivoro.

    La mia modifica era, tentare di "sbloccare" il vincolo su Carnivoro in moco che sia possibile:
    un Carnivoro maniga Erbivoro E mangia Carnivoro MA NON mangia Erba

    il codice originale con tutti i suggerimento che hai dato (e eliminano tutti gli warning) e che nel libro non ci sono è questo:
    
    interface Cibo{}
    
    interface Animale<C extends Cibo>{
    	void mangia(C cibo);
    }
    
    class Erba implements Cibo{}
    
    class Erbivoro<E extends Erba> implements Cibo, Animale<E>{
    	public void mangia(E erba) {
    		System.out.println("Erbivoro mangia Erba");
    	}
    }
    
    class Carnivoro<E extends Erbivoro<Erba>> implements Animale<E>{
    	public void mangia(E cibo) {
    		System.out.println("Carnivoro mangia Erbivoro");
    	}
    }
    
    public class TesterGeneric {
    	public static void main(String[] args) {
    		Erba erba = new Erba();
    		Erbivoro<Erba> mucca = new Erbivoro<>();
    		Carnivoro<Erbivoro<Erba>> tigre = new Carnivoro<>();
    		System.out.print("Mucca: "); mucca.mangia(erba);
    		System.out.print("Tigre: "); tigre.mangia(mucca);
    
    		//aggiunto da me
    		Carnivoro<Erbivoro<Erba>> leone = new Carnivoro<>();
    		System.out.print("Leone: "); leone.mangia(tigre);
    	}
    }
    
    le ultime die righe le ho aggiunte io ma ovviamente non compilano per come è scritta la classe Carnivoro, classe che ho tentato di modificare invano.
    se eseguo, restituisce su leone.mangia(tigre);
    
    Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    	The method mangia(Erbivoro<Erba>) in the type Carnivoro<Erbivoro<Erba>> is not applicable for the arguments (Carnivoro<Erbivoro<Erba>>)
    
    Il problema è che (...penso..!) se modifico Carnivoro come
    
    class Carnivoro implements Animale<Cibo> {
    	public void mangia(Cibo cibo) {
    
    ad una tigre riesco a far mangiare anche l'erba.
    Volevo evitare questa cosa con i generici senza scrivere un test del tipo instanceof e implementare delle eccezioni.

    Grazie.
    Non so se ci sia soluzione o se è da riscrivere tutto.
    cmq ora mi sto esercitando sui thread e a breve chiederò un consiglio anche su un altro esercizio che sto facendo e ho trovato su una dispensa dell'università di pisa.

    Tagan
Devi accedere o registrarti per scrivere nel forum
6 risposte