Java [Generics] Abstrakte Methode - Übergabeparameter stets vom Typ der Klasse

T

Tersus

Gast
Guten Abend,
ich stehe gerade auf dem Schlauch. :rolleyes:

Ich suche soetwas hier:

Code:
public abstract class Oberklasse{

...
public abstract Oberklasse update(Oberklasse o);
...

}

Code:
public class Unterklasse extends Oberklasse{

...
public Unterklasse update(Unterklasse o){

}
...

}

Jeder, der eine Klasse von Oberklasse ableitet, soll gezwungen werden, die Methode "update" zu implementieren, jedoch muss sowohl als Rückgabetyp sowie als Übergabeparametertyp die überschreibene Klasse verwendet werden.
Andernfalls müsste in der "update" Methode jedesmal eine Typüberprüfung stattfinden.

Gibt es da eine Möglichkeit?

Grüße
 
Da man für solche Signaturen immer das "allgemeinste" Interface verwenden sollte (Oberklasse in deinem Beispiel), statt eine konkrete Implementierung (Unterklasse in deinem Beispiel), ist es nur korrekt, wenn sowohl Rückgabetyp als auch Parameter vom Typ Oberklasse sind. Die Frage ist eigentlich, warum du glaubst, dass du in update einen type check machen musst. Das gibt dein aktuelles Szenario aber nicht her, d.h. das Gedankenspiel musst du noch ein Stück weiter ausführen.

Ich vermute das worauf du hinaus willst, verlangt weitere Interfaces (per implements eingebunden), die dann z.B. in der update-Signatur verwendet werden würden, um das Vorhandensein bestimmter Methoden zu garantieren.
 
Zuletzt bearbeitet:
Möglichst ist es, ob es für Dein Design Sinn macht, kann ich nicht sagen:

Code:
public abstract class Oberklasse<T extends Oberklasse<T>>{
    public abstract T update(T o);     
}
 
Ich glaube, was er machen will, ist etwas, was in Java tatsächlich nicht geht, nämlich quasi sowas:

Code:
template<typename T>
void doSomething(const T& object) {
  ...
  T bla = someObject.update(object);
  ...
}

Aber etwas mehr Klarheit wäre tatsächlich super, vielleicht gibt es einen Workaround.
 
Das was du machen willst lässt sich mit Java realisieren. Auch wenn ich es persönlich aus style gründen für furchtbar halte.
Werde trotzdem die Lösung posten, vielleicht hilfts ja beim Generics Verstädnis

Code:
public abstract class Oberklasse<Type extends Oberklasse> {

    public abstract Type update(Type type);

}

Code:
public class Unterklasse extends Oberklasse<Unterklasse> {

    @Override
    public Unterklasse update(Unterklasse unterklasse) {
        return unterklasse;
    }

}
 
Guten Abend,
ich habe einen Fehler gemacht. Die update() muss in der Oberklasse nicht unbedingt abstract sein.

Code:
public class Oberklasse{

  private   long    ID;
  protected String  name;

  // Getter
  public String getName(){
    return this.name;
  }

  // Muss ueberschrieben werden
  protected Oberklasse update(Oberklasse O){

    this.name = O.getName();
    
    return this;

  }

}

public class Unterklasse extends Oberklasse{

  private String eigenschaft;

  // Getter
  
  public String getEigenschaft(){
    return this.eigenschaft;
  }

  @Override
  protected Unterklasse update(Unterklasse O){
    
    // Nur fuer Typ Unterklasse moeglich
    this.eigenschaft = O.getEigenschaft();
    
    // Nun noch Name aktualisieren
    return (Unterklasse) super.update(this);

  }

}

1. Gibt es eine Annotation, welche ich Oberklasse über die update() schreiben kann, die dafür sorgt, dass diese bereits implementierte Methode in Unterklassen überschrieben werden muss?

2. Jetzt wird in Unterklasse vllt. ersichtlich, warum ich hier in Update die Typen dieser Klasse verwenden möchte.


Grüße
 
Zuletzt bearbeitet von einem Moderator:
Mir ist irgendwie nicht ganz ersichtlich, warum du unbedingt das Objekt in der Update-Methode wieder returnen möchtest, zumal

Code:
return super.update(this);
das hier sowieso schon nicht hinhaut - wenn schon, dann
Code:
super.update();
return this;

Was mir zu deinem Problem noch einfallen würde (inklusive eventueller Syntax-Fehler, bin jetzt nicht ganz bewandert in Java):
Code:
interface <T> Updatable {
  T update(T object);
}

class Oberklasse implements Updatable<Oberklasse> {
  public Oberklasse update(Oberklasse object) {
    ...
    return this;
  }
}

class Unterklasse extends Oberklasse implements Updatable<Unterklasse> {
  public Unterklasse update(Unterklasse object) {
    ...
    return this;
  }
}

Erzwungen wird das dann allerdings erst, wenn du irgendwo mit
Code:
<T extends Updatable<T>>
arbeitest. Funktionieren sollte das allerdings, und zwar genau aus dem Grunde, warum das hier keinen Sinn ergibt:

Code:
@Override
protected Unterklasse update(Unterklasse O) {

Die Methode hat eine andere Signatur als die Update-Methode der Oberklasse, da wird nichts überschrieben. Das ist eine eigenständige Methode. Deswegen könntest du auch problemlos update von einem Unterklasse-Objekt mit einer Oberklasse-Referenz aufrufen, das wäre dann nur eben die Version der Oberklasse.

Ist das unerwünscht, bleibt wohl nur noch der Tipp von Funart (und ich will mal nichts gesagt haben von wegen "geht nicht"). Aber generell rieche ich hier irgendwie eher suboptimales Design.
 
Zuletzt bearbeitet:
@VikingGe
Keine schande, die wenigsten wissen was man alles mit Generics machen kann.

@Tersus
Das was du machen willst ergibt keinen Sinn, du willst eine default implementierung mit einer Pflicht diese zu überschreiben.
Ich rate mal was du meinen könntest.

Für mich riecht das nach einem teilweise default Verhalten + speziellen Verhalten je nach Typ. In der Regel verwendet man dazu eine public methode in der Oberklasse welche eine interne protected abstract Methode called.

z.B.:

Code:
public abstract class Oberklasse<Type extends Oberklasse> {

    public Type update(Type type) {
        updateInternal(type);
        // do something with type;
        return type;
    }

    protected abstract Type updateInternal(Type type);

}

Code:
public class Unterklasse extends Oberklasse<Unterklasse> {

    @Override
    protected Unterklasse updateInternal(Unterklasse unterklasse) {
        // do something with unterklasse
        return unterklasse;
    }

}
 
VikingGe schrieb:
... zumal
Code:
return super.update(this);
das hier sowieso schon nicht hinhaut ...
Doch, haut hin. Ich hatte nur einen Cast auf die Unterklasse vergessen.

@Funart
Die Lösung gefällt mir schon ausreichend gut.


Wenn ich die nächsten Tage Zeit habe, werde ich noch mal ausführlich erklären, worin der Sinn hinter all dem steckt.

Ich danke euch soweit erstmal für die Hilfe!
 
Doch, haut hin. Ich hatte nur einen Cast auf die Unterklasse vergessen.
Das ist aber schon irgendwie furchtbar unsauber, weil es
a) nicht nötig ist - das Objekt, das Oberklasse.update liefert kann ja nur this sein, sonst ist es höchstwahrscheinlich nicht vom Typen Unterklasse
b) einen impliziten Type Check beinhaltet. Von unten nach oben casten entspricht quasi dynamic_cast, und das kostet Laufzeit.

Wenn ich die nächsten Tage Zeit habe, werde ich noch mal ausführlich erklären, worin der Sinn hinter all dem steckt.
Bitte. Das interessiert mich dann doch. :D
 
VikingGe schrieb:
Bitte. Das interessiert mich dann doch. :D

Ich denke hiermit wird ersichtlicher, was ich möchte.

Selbsterklärende Oberklasse.
Code:
import java.io.Serializable;

public class DatenEinheit implements Serializable{
    
    /**
     * 
     */
    private static final long serialVersionUID = 1000000000000000000L;
    
    private     static  long    IDZaehler = 0;
    
    public      final   long    ID;
    public      final   long    ZEITSTEMPEL;
    
    private             String  mBezeichnung;
    
    public DatenEinheit(String bezeichnung) {
        
        this.ID             = IDZaehler++;
        this.ZEITSTEMPEL    = System.currentTimeMillis();
        this.mBezeichnung   = bezeichnung;
        
    }

    public String getBezeichnung() {
        return mBezeichnung;
    }

    public void setBezeichnung(String bezeichnung) {
        mBezeichnung = bezeichnung;
    }
    
    public void aktualisiere(DatenEinheit de){
        
        this.mBezeichnung = de.mBezeichnung;
        
    }    
    
}

Unterklasse durch Kommentare über den Methoden, auf die es ankommt, erklärt.
Code:
public class Termin extends DatenEinheit{
    
    /**
     * 
     */
    private static final long serialVersionUID = 1000000000000000001L;
    
    private String  mBeschreibung;
    private String  mTreffpunkt;
    private long    mZeitpunkt;
        
    public Termin(String name, String beschreibung, String treffpunkt, long zeitpunkt) {
        super(name);
        
        this.mBeschreibung  = beschreibung;
        this.mTreffpunkt    = treffpunkt;
        this.mZeitpunkt     = zeitpunkt;
        
    }

    public String getmBeschreibung() {
        return mBeschreibung;
    }

    public void setmBeschreibung(String mBeschreibung) {
        this.mBeschreibung = mBeschreibung;
    }

    public String getmTreffpunkt() {
        return mTreffpunkt;
    }

    public void setmTreffpunkt(String mTreffpunkt) {
        this.mTreffpunkt = mTreffpunkt;
    }

    public long getmZeitpunkt() {
        return mZeitpunkt;
    }

    public void setmZeitpunkt(long mZeitpunkt) {
        this.mZeitpunkt = mZeitpunkt;
    }

    /*
     * |------------|
     * | Variante 1 |
     * |------------|
     * 
     * Ich ueberschreibe die Methode der Superklasse, wie gewuenscht.
     * 
     * Problem A) ist jedoch, dass der Anwender hier faelschlicherweise
     * Uebergabeparameter vom Typ der Oberklasse verwenden kann, was
     * zu einer Ausnahme führen würde. Die Typsicherheit ist nicht
     * gewaerleistet.
     */
    
    @Override
    public void aktualisiere(DatenEinheit de) {

        try{    
            
            Termin _de = (Termin) de;
            this.mBeschreibung  = _de.mBeschreibung;
            this.mTreffpunkt    = _de.mTreffpunkt;
            this.mZeitpunkt     = _de.mZeitpunkt;
                
            
        }catch(ClassCastException cce){
        
            System.err.println("Uebergabeparameter muss vom Typ dieser Klasse sein!");
        
        }finally{
        
            super.aktualisiere(de);
        
        }
        
    }
    
    /*
     * |------------|
     * | Variante 2 | 
     * |------------|
     * 
     * Uebergabeparameter ist nun, wie gewünscht, vom Typ der Klasse selbst.
     * 
     * Problem A) ist jedoch, dass ich die gleichnamige Methode der Superklasse
     * aufgrund des anderen Uebergabeparametertyps nicht mehr ueberschreibe
     * und somit eine weitere Methode dieser Klasse hinzufuege.
     * 
     * Problem B) ist, dass der Anwender dieser Klasse muss also selbst die
     * "richtige" Methode, in diesem Fall diese hier, heraussuchen muss.
     */

    public void aktualisiere(Termin t) {
                
        this.mBeschreibung  = t.mBeschreibung;
        this.mTreffpunkt    = t.mTreffpunkt;
        this.mZeitpunkt     = t.mZeitpunkt;
        super.aktualisiere(t);
    }

}

Mit Generics ist mir hier auch nicht unbedingt geholfen, weil der Anwender der generischen Klassen auch einen falschen Typ angeben könnte.

Grüße
 
Ich versteh die Problemstellung noch immer nicht wirklich, wobei das vermutlich an am Gesamtkonstrukt liegt.
Das von mir zuletzt erwähnte sollte alle deine Probleme lösen.
Falls der Entwickler es nicht schafft den richtigen Typ als generisches Attribut anzugeben, hast du ganz andere Probleme.
Vor allem erzeugt es weniger Probleme als sich darauf zu verlassen, dass super.aktualisiere gecalled wird.

Eine ClassCastException fängt man auch nicht ab. Man checked einfach den Typ mittels instanceof.
 
Zurück
Oben