Java: come posso rendere il tipo di ritorno, di un metodo ereditato in una sottoclasse, lo stesso della sottoclasse?

4

Sono piuttosto inesperto in Java, e sto avendo un problema nel formare una sottoclasse di una classe che ho creato. La classe che ho creato, chiamata Vector2D , contiene metodi, come add(Vector2D addend) , che accetta un oggetto Vector2D come argomento e restituisce un oggetto Vector2D . Questo metodo in particolare è progettato per aggiungere due vettori e restituire la somma.

Quindi, ho iniziato a codificare una sottoclasse, chiamata Position2D , che intendo avere tutte le funzionalità della classe Vector2D , ad eccezione delle funzioni aggiuntive e l'utilizzo di Vector2D nelle classi preesistenti, sia come argomento, tipo restituito, ecc., sostituito con Position2D , in modo che la funzione add(Position2D addend) aggiunga due Position2D di vettori insieme e restituisca la somma come Position2D .

Il problema sorge quando, quando Position2D eredita da Vector2D , tali funzioni continuano a funzionare in termini di oggetti Vector2D , sputando un errore non appena viene passato un oggetto Position2D .

Il codice pertinente per la classe Vector2D è il seguente:

public class Vector2D{

    //Field Variables
    private double x; //The vector's x-coordinate
    private double y; //The vector's y-coordinate

    //Constructors
    public Vector2D(){
        //Constructs empty vector.
        setX(0);
        setY(0);
    }

    //Methods
    public void setX(double newX){
        //This method is not problematic when inherited for the subclass.
        x = newX;
    }

    public void setY(double newY){
        //This method is not problematic too.
        y = newY;
    }

    public Vector2D add(Vector2D addend){
        //Adds two vectors and returns the sum.
        //This method, however, does pose a problem.
        Vector2D sum = new Vector2D();
        sum.x = x + addend.x;
        sum.y = y + addend.y;
        return sum;
    }

}

L'unico work-around che posso attualmente pensare a cosa potrebbe essere il codice in add(Position2D addend) in Position2D è:

public Position2D add(Position2D addend){
    //Adds two position vectors and returns the sum.
    Position2D sum = new Position2D();
    sum.x = x + addend.x;
    sum.y = y + addend.y;
    return sum;
}

Ad esempio, ripeti il metodo da Vector2D e modificalo manualmente. Questo, naturalmente, non è efficiente e fa perdere tempo, specialmente quando lo stesso deve essere fatto per metodi simili, e più ancora ogni volta che intendo creare più sottoclassi, ad es. %codice%. Pertanto, sto cercando un mezzo molto più pulito ed efficiente per farlo.

    
posta SprocketsAreNotGears 27.08.2014 - 21:31
fonte

2 risposte

3

Ricorda che l'ereditarietà definisce un tipo di relazione "è un". Il codice

class Position2D extends Vector2D

dice al compilatore che ogni Position2D è un Vector2D, ma non viceversa.

Guarda i seguenti esempi assumendo che tu abbia già fatto la dichiarazione di cui sopra:

// obviously okay, type of instance matches reference type
Vector2D vecVecRef = new Vector2D();
Position2D posPosRef = new Position2D();
// this is okay because a Position2D is a Vector2D
Vector2D posVecRef = new Position2D();
// The compiler will not automatically convert a Vector2D to a Position2D
Position2D vecPosRef = new Vector2D(); //Compiler error!

Una variabile Position2D non può mai riferirsi a un oggetto Vector2D. Se provi a tipizzarlo, il compilatore dirà "bene ... mi fido di te", ma poi la JVM si arrabbierà quando scoprirà che stai cercando di assegnare un Vector2D a un Position2D. Alza un ClassCastException al runtime.

// compiles fine but raises ClassCastException in runtime
Position2D vecPosRef = (Position2D) new Vector2D();

Questo perché solo Class può lanciare una sottoclasse in una superclasse e non viceversa. Quindi, in pratica, non puoi trasmettere Vector2D a Position2D e non puoi assegnarlo senza eseguire il cast.

La soluzione più semplice a questo problema è avere un costruttore definito nella sottoclasse che rende un oggetto Position2D fuori da un determinato oggetto Vector2D.

class Position2D extends Vector2D {
    Position2D() {
        // default stuff
    }

    Position2D(Vector2D v) {
        // you currently don't have the getX and getY methods
        // so define them in your superclass
        setX(v.getX());
        setY(v.getY());
    }
}

Con questo semplice e conveniente costruttore, puoi usare un codice come questo:

public class Inheritance {
    public static void main(String[] args) {
        Position2D pos1 = new Position2D();
        Position2D pos2 = new Position2D();
        pos1.setX(3);
        pos1.setY(4);
        pos2.setX(5);
        pos2.setY(6);
        Position2D pos3 = new Position2D(pos1.add(pos2)); // using constructor
        System.out.println(pos3.getX()); // this prints 8.0
    }
}

Come puoi vedere, in questo modo è molto più estensibile della riscrittura di tutti i metodi della sottoclasse.

    
risposta data 27.08.2014 - 22:24
fonte
3

Puoi utilizzare il principio di inversione di dipendenza e il schema del metodo factory .

Il tipo di ritorno dovrebbe essere un supertipo di Vector2D e Position2D .

Chiamiamo IVector2D (un'interfaccia).

La soluzione sarebbe:

public interface IVector2D {

    public void setX(double newX);  
    public void setY(double newY);  
    public double getX();   
    public double getY();       
    public IVector2D add(IVector2D addend);
    public IVector2D getInstance(); // this helps decouple the instantiation

}

An then

public class Vector2D implements IVector2D {

    private double x; //The vector's x-coordinate
    private double y; //The vector's y-coordinate

    public Vector2D(){ setX(0); setY(0); }

    public IVector2D getInstance(){ return new Vector2D(); }

    @Override
    public void setX(double newX) { x = newX; }

    @Override
    public void setY(double newY) { y = newY; }

    @Override
    public double getX() { return this.x; }

    @Override
    public double getY() { return this.y; }

    @Override
    public IVector2D add(IVector2D addend) {
        IVector2D sum = getInstance();
        sum.setX(this.getX()+addend.getX());
        sum.setY(this.getX()+addend.getY());
        return sum;
    }
}

Quando estendi, sostituisci getInstance() e implementa un costruttore:

public class Position2D extends Vector2D {      
    public Position2D(){ super(); }     
    @Override
    public IVector2D getInstance(){ return new Position2D(); }
}

Nota che stai effettivamente restituendo un oggetto di tipo Position2D , come volevi, all'interno di un riferimento di tipo Ivector2D , perché stai sovrascrivendo getInstance() e add() chiamerà il getInstance() di Position2D . Quell'astrazione consentiva che il metodo add() funzionasse perfettamente.

Il programma di test dovrebbe essere:

public static void main(String[] args) {
    IVector2D a = new Position2D();
    IVector2D b = new Position2D();
    a.setX(1.2d);
    a.setY(2.3d);
    b.setX(0.33d);
    b.setY(9.0d);
    IVector2D c = a.add(b);
    System.out.println(c.getX());
}

Ouput:

1.53
    
risposta data 27.08.2014 - 23:10
fonte

Leggi altre domande sui tag