Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Architekt

Community-Fossil

  • »Architekt« ist der Autor dieses Themas

Beiträge: 2 481

Wohnort: Hamburg

Beruf: Student

  • Private Nachricht senden

1

25.11.2017, 19:50

C++ / Java: Übersetzung eines generischen Visitors

Ich muss Code von C++ nach Java portieren und in Java war ich noch nie wirklich fit. Heute bin ich dabei dann auch direkt auf ein Problem gestoßen. Es handelt sich um einen generischen Visitor, dem man den Typ einer Klasse mitgibt um zu bestimmen, ob der übergebene Base-Typ auch der angegebene Sub-Typ ist. Konkret geht es darum, mithilfe von Templates eine bestimmte Methode zu überladen. Das ist in C++ kein Thema, in Java leider schon.

Hier einmal der C++ Teil den ich portieren soll:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>

class BundleVisitor;

class Bundle
{
public:
    virtual void accept(BundleVisitor&) = 0;
};

class AlphaBundle;
class BetaBundle;

class BundleVisitor
{
public:
    virtual void visit(AlphaBundle*) = 0;
    virtual void visit(BetaBundle*) = 0;
};

class AlphaBundle : public Bundle
{
public:
    void accept(BundleVisitor& v)
    {
        v.visit(this);
    }
};

class BetaBundle : public Bundle
{
public:
    void accept(BundleVisitor& v)
    {
        v.visit(this);
    }
};

class EmptyBundleVisitor : public BundleVisitor
{
public:
    virtual void visit(AlphaBundle*) { }
    virtual void visit(BetaBundle*) { }
};

template <typename T>
class BundleReveal : public EmptyBundleVisitor
{
private:
    T* _bundle;
    
public:
    void visit(T* bundle)
    {
        _bundle = bundle;
    }
    
    bool isValid()
    {
        return _bundle != nullptr;
    }
};

int main()
{
        // Demo
    Bundle* bundle = new AlphaBundle();
    BundleReveal<AlphaBundle> visitor;
    bundle->accept(visitor);
    
    std::cout << (visitor.isValid()) << std::endl;
    
    delete bundle;
}


In Java bekomme ich ein ähnliches Äquivalent mit Generics aber leider nicht hin:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
interface Bundle
{
    void accept(BundleVisitor v);
}

interface BundleVisitor
{
    void visit(AlphaBundle b);
    void visit(BetaBundle b);
}

class AlphaBundle implements Bundle
{
    public void accept(BundleVisitor v)
    {
        v.visit(this);
    }
}

class BetaBundle implements Bundle
{
    public void accept(BundleVisitor v)
    {
        v.visit(this);
    }
}

class EmptyBundleVisitor implements BundleVisitor
{
    public void visit(AlphaBundle b) { }
    public void visit(BetaBundle b) { }
}

class BundleReveal<T extends Bundle> extends EmptyBundleVisitor
{
    private T _bundle;
    
    public void visit(T bundle)
    {
        _bundle = bundle;
    }
    
    public boolean isValid()
    {
        return _bundle != null;
    }
}

public class MyClass
{
    public static void main(String args[])
    {
        Bundle bundle = new AlphaBundle();
        BundleReveal<AlphaBundle> visitor = new BundleReveal<>();
        bundle.accept(visitor);
        
        System.out.println(visitor.isValid());
    }
}


Scheinbar sind die Generics nicht für eine solche Covariance ausgelegt. Hat jemand eine Idee, wie ich das Konstrukt dennoch lösen kann? Notfalls würde ich es einfach mit instanceof und einen cast machen. Vermute mal, dass der C++ Code daher stammt, einen dynamic_cast zu vermeiden, da es ja instanceof in der Form nicht in C++ gibt/gab, aber kein Plan wirklich.
Der einfachste Weg eine Kopie zu entfernen ist sie zu löschen.
- Stephan Schmidt -

2

26.11.2017, 17:33

Es gibt ein paar Bibliotheken, die Dir den generischen Typ einer Klasse ermitteln, z.B. https://github.com/jhalterman/typetools

Durch Type-Erasure kann Dein Visitor nicht funktionieren. Java weiß nur, dass ein Objekt vom Typ Bundle reingekommen ist und löst Methodenaufrufe entsprechend aus. Generics führen nicht zu echten Implementierungen wie bei C++ Templates.

Ich finde das klassische Visitor-Pattern mit Methodenüberladung macht bei Java wenig Sinn, da es die Flexibilität sehr einschränkt. Ich würde an Deiner Stelle mit instanceof arbeiten. Ein Cast ist danach ja auch sicher. Die Methoden für spezielle Implementierungen benötigst Du dann gar nicht.

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
interface Bundle {
    default void accept(BundleVisitor v) {
        v.visit(this);
    }
}

interface BundleVisitor {
    void visit(Bundle b);
}

class AlphaBundle implements Bundle {
}

class BetaBundle implements Bundle {
}

class BundleReveal<T extends Bundle> implements BundleVisitor {

    private final Class<T> acceptedBundleType;

    private T bundle;

    public BundleReveal(Class<T> acceptedBundleType) {
        this.acceptedBundleType = acceptedBundleType;
    }

    @Override
    public void visit(Bundle b) {
        if (acceptedBundleType.isInstance(b)) {
            this.bundle = acceptedBundleType.cast(b);
        }
    }

    public boolean isValid() {
        return bundle != null;
    }
}

public class MyClass
{
  public static void main(String args[]) {
    Bundle bundle = new AlphaBundle();
    BundleReveal<AlphaBundle> visitor = new BundleReveal<>(AlphaBundle.class);
    bundle.accept(visitor);
    System.out.println(visitor.isValid());
  }
}

Edit: Habe noch ein bisschen aufgeräumt.

Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von »Chromanoid« (26.11.2017, 17:54)


Architekt

Community-Fossil

  • »Architekt« ist der Autor dieses Themas

Beiträge: 2 481

Wohnort: Hamburg

Beruf: Student

  • Private Nachricht senden

3

26.11.2017, 18:41

Nice! Das sieht brauchbar aus. Werd' dann aber nochmal fragen, ob ein direktes cast mit ggf. einem instanceof davor nicht ausreicht. Ein Cast wirft ja eine Exception wenn es nicht klappt soweit ich weiß, also sollte alles ok sein. Danke für deine Hilfe!
Der einfachste Weg eine Kopie zu entfernen ist sie zu löschen.
- Stephan Schmidt -

4

26.11.2017, 20:01

Man kann "instanceof T" nicht sinnvoll machen, da T ja im Grunde nur Bundle bedeutet (Type Erasure). Daher muss man mit dem Klassen-Objekt arbeiten. Das Casten nach T mittels "(T) b" hätte zwar geklappt, aber das bringt je nach IDE oft eine Warnung hervor, weil das isInstance nicht als Einschränkung des Typs erkannt wird. Der Cast mittels Class#cast wird hingegen nicht berücksichtigt (zurecht, spätestens wenn man das aufruft weiß man was man tut ^^). BTW wenn dich das interessiert, such mal nach Reified Generics. Das ist genau, was ich hier "emuliert" habe. Der generische Typ des BundleReveal-Visitors wurde explizit gemacht.

Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von »Chromanoid« (26.11.2017, 20:13)


Architekt

Community-Fossil

  • »Architekt« ist der Autor dieses Themas

Beiträge: 2 481

Wohnort: Hamburg

Beruf: Student

  • Private Nachricht senden

5

27.11.2017, 20:08

Werde ich mal machen vielen Dank. Mir waren diese default-Methoden auch noch nicht bekannt. Ähnelt schon recht stark einer abstrakten Klasse.
Der einfachste Weg eine Kopie zu entfernen ist sie zu löschen.
- Stephan Schmidt -

6

27.11.2017, 20:40

Dann solltest Du Dir auch Lambdas und Streams anschauen. Das ist syntaktisch schon ziemlich anders als Java < 8...

Werbeanzeige