Verfasste Forenbeiträge

Ansicht von 19 Beiträgen - 1 bis 19 (von insgesamt 19)
  • Autor
    Beiträge
  • als Antwort auf: Polymorphie mit Gerenics #3525

    Beispiel (kompiliert nicht):

    class A<T> {
        T t;
        void f(T x) { x.m(); }
    }
    class X { void m() {} }
    class Y extends X {}

    Beispielobjekte:

    X g = new X(); Y h = new Y();

     

    Was gibt es zu beachten?

    1. Eine generische Klasse gibt es nicht ohne eingesetzten Typ. Es gibt z. B. Objekte der Klassen A<X> und A<Y> und A<String>, es gibt aber keine Objekte der Klasse A, weil es die “allgemeine” Klasse A nicht gibt. Auch A<T> gibt es nicht, weil T keine Klasse ist. Der generische Typ T könnte zusätzlich eingeschränkt sein, z. B. class A<T extends X>, dann wäre A<String> nicht mehr möglich, weil für T dann mindestens X (oder spezieller, also Y geht auch) eingesetzt werdem muss.
       
    2. Der generische Typ gibt nicht die Vererbungshierarchie vor. Während die Zuweisung von einem Y an eine Variable vom Typ X möglich wäre (d. h. X var = new Y(); geht), da Y spezieller ist als X, gilt das nicht für die Klasse A<...>. Weder die Zuweisung von A<Y> an A<X> noch umgekehrt wäre möglich (d. h. A<X> var = new A<Y>(); ist ein Compiler-Fehler), weil die Klassen A<Y> und A<X> überhaupt nichts miteinander zu tun haben.
       
    3. Generische Typen sind nur Platzhalter. Angenommen es existiert eine Variable A<Y> m, dann gibt es dafür die Methode void f(Y x) (siehe Beispiel oben). Der Aufruf m.f(h) wäre möglich, m.f(g) wäre nicht möglich. Für eine Variable A<X> n wären beide Aufrufe möglich. Für eine Variable A<String> o wäre keiner der beide Aufrufe möglich.
       
    4. Type Erasure: Generische Typen werden nicht als Platzhalter übersetzt, sondern wird beim Kompilieren der allgemeinste Typ verwendet. Vielleicht fragst du dich, warum das Beispiel eigentlich gar nicht kompiliert. Der Grund dafür ist Type Erasure. Der Compiler übersetzt “A” nicht in allen möglichen Formen (A<X>, A<Y>, A<Integer>, …), weil es dafür ja unendlich viele Möglichkeiten gäbe. Obwohl es die Klasse in unendlich vielen verschiedenen Ausführungen gibt, wird sie nur einmal übersetzt – und zwar als A<Object>, d. h. T wird durch Object ersetzt. Angenommen wir hätte ein A<X> a = new A<X>(); und rufen nun die Methode f mit unserem X-Objekt von oben auf, also a.f(g), dann wird in der f-Methode ja x.m() aufgerufen (wobei x = g gilt). Die Methode m() gibt es aber aus statischer Sicht nicht… Warum? Der statische Typ von x ist innerhalb von der “allgemeinen” Klasse A leider Object (eben wegen Type Erasure). Wegen Zeile 2 kompiliert das Programm also nicht. Der sog. raw type ist deshalbObject, weil T nicht eingeschränkt ist. Wäre die Definition stattdessen A<T extends X>, so würde T durch X statt Objekt erased werden und alles würde wie erwartet laufen.
       
      Auch bei Attributtypen werden entsprechend ersetzt. Betrachten wir wieder A<X> a = new A<X>();, dann ist der statische Typ von a.t hier X und der Aufruf a.t.m() wäre möglich. Benutzen wir das Attribut aber innerhalb von A, dann ist der Typ von t wieder Object… Während a.t.m() also funktioniert, wäre der Aufruf t.m() innerhalb von A nicht möglich (aus dem gleichen Grund weshalb auch x.m() nicht möglich ist).
       
      Type Erasure kann noch komplizierter werden, wenn es bspw. um Überschreibung geht (siehe Altklausur WS 18/19 Wdh.). Nähere Erklärungen dazu mache ich hier nicht, weil das zu kompliziert zu schreiben ist. Man lernt es außerdem besser, indem selbst Beispiele in der IDE macht. Type Erasure haben wir bspw. bei Aufruf 8 in meiner GenericPoly Aufgabe – schau dir das mal an + Erklärung dazu.

     

    als Antwort auf: KontrollFlussDiagramm Altklausur 18-19 #3516

    Genau. Bei break verlässt man das switch. Ansonsten (wenn auch nicht continue oder return kommt) geht es mit dem nächsten Case weiter. Hintergrund ist eigentlich, dass man so mehrere Cases zusammenfassen kann, vgl. Kapitel 5.2 “x == 2 || x == 3“.

    als Antwort auf: KontrollFlussDiagramm Altklausur 18-19 #3513

    Im case 2 wird a += 30 ausgeführt. Nachdem wir kein break haben geht geht es danach in den case 3 und es wird break l1 ausgeführt.

    Diesen Fall gibt es in genau dieser Form übrigens auch mehrmals in den Skript-Aufgaben (siehe Teilaufgabe i) und l)). Du solltest überprüfen, ob du das dort richtig gemacht hast.

    als Antwort auf: Poly Hard, Altklausur 17/18 WDH #3491

    Hier das vollständige Objektdiagramm zur Polymorphie-Aufgabe aus der Wiederholungsklausur 2017/18. Außerdem habe ich den ersten Aufruf nach dem gezeigten Schema gelöst. Attribute kann man dafür im Objektdiagramm ablesen – man muss nur wissen auf welchem Objekt man gerade operiert (also worauf man die Methode aufgerufen hat, wie bei this). Der zweite Aufruf ist ziemlich ähnlich zum ersten. Ich habe leider keine Zeit, diese Aufgabe detaillierter zu erklären, aber ich hoffe, dir hilft das weiter.

    Grafische Lösung zur Polymorphie-Aufgabe WS1718Wdh

    Falls das Bild nicht oder unscharf angezeigt werden sollte, findest du es hier in voller Auflösung.

    als Antwort auf: Zusatzaufgabe: Polymorphie mit Attributen #3440

    Wenn du b.a.a.foo() hättest, dann benötigst du den statischen und dynamischen Typ von b.a.a, um die Methode foo() aufzurufen. Dazu gehen wir von links nach rechts: b ist statisch B und dynamisch B, also suchen wir in unserem B-Objekt erstmal ein Attribut a, um b.a zu bestimmen. Dieses Attribut a muss aus B (oder einer Oberklasse von B) kommen, welches wir hier finden (das ist das einzige a in dem B-Objekt). b.a ist somit statisch B, dynamisch C (das obere der beiden Cs). Wir suchen also in dem oberen C-Objekt ein Attribut a, um b.a.a zu bestimmen. Dieses Attribut a muss aus B (oder einer Oberklasse von B) kommen, welches wir hier finden – das ist das Attribut aA. Das aC sehen wir nicht, da der statische Typ B ist. Der statische Typ von b.a.a ist somit B, der dynamische C (das untere der beiden).

    als Antwort auf: Generische Klassen Aufgabe 73 Aufrufe 8,17,21 #3439
    • h) X<A> h = new X<C>(); kompiliert nicht, weil der Typ der Variable (X<A>) nichts mit dem Typ des Objekts (X<C>) zu tun hat, d. h. weder erbt X<A> von X<C> noch umgekehrt. Die Klassen haben genauso wenig miteinander zu tun wie String und Stack – gar nichts. Dass die generischen Typen in einer Vererbungshierarchie stehen ist schön, tut aber nichts zur Sache – X<A> erbt von Object, X<C> erbt von Object.
    • q) class Q<T> extends X<T> {}  kompiliert nicht, weil der Typparameter von Q (also T) nicht eingeschränkt ist, d. h. über T ist lediglich bekannt, dass es mindestens Object sein muss. Die Oberklasse X verlangt aber einen generischen Typ, der mindestens A ist (<T extends A>).
    • u) class U<K extends A> extends X<D> {}; definiert zunächst eine Unterklasse von X<D>, d. h. bspw. es gibt die Klasse U<A>, die von X<D> erbt; es gibt die Klasse U<B>, die von X<D> erbt; es gibt die Klasse U<C>, die von X<D> erbt; und es gibt die Klasse U<D>, die von X<D> erbt. Dass hier der Typparameter nicht durchgegeben wird ist okay – muss man ja nicht.
      Das Problem liegt bei X<B> u = new U<B>();, denn U<B> ist keine Unterklasse von X<B>, also kompiliert es nicht. U<B> erbt von X<D>, nicht von X<B>.
    als Antwort auf: Aufgabe 85 #3438

    Du hast es genau richtig erklärt. Bei den anderen Teilaufgaben ist es wie bei g). Z. B. bei k) wird versucht, eine Variable vom Typ I an eine Variable vom Typ C zuzuweisen. Der Compiler sieht nur diese statischen Typen und überprüft nicht, was tatsächlich (dynamisch) gespeichert wird. Eine Variable vom Typ I könnte zwar ein C (oder D) speichern (wie es hier der Fall ist), aber theoretisch (aus Compiler-Sicht) auch ein Objekt jedes anderen Typs der mindestens I ist (also jede Klasse, die I implementiert). Es könnte ja neben C noch eine Klasse F geben, die I implementiert. Die Zuweisung funktioniert daher immer nur dann, wenn der (statische) Typ der rechten Seite der Zuweisung mindestens so speziell ist wie der Typ der Variable, an die zugewiesen wird (linke Seite). Also kannst du an eine Variable vom Typ C nur C oder D zuweisen. An eine Variable vom Typ I kannst du C, D oder I zuweisen. Die rechte Seite muss also den gleichen Typ haben oder einen Typ, der in der Vererbungshierarchie darunter steht (aber nicht darüber)!

    als Antwort auf: Crashkurs-Aufgaben zu Polymorphie #3421

    Der Aufruf 8 kommt ja irgendwann zu e.g(f). Der statische Typ von e ist hier jedoch nicht F sondern E. Grund dafür ist Type Erasure, weil der Aufruf innerhalb von C<?> stattfindet. Der Compiler weiß nicht, dass wir hier fix in C<F> sind (wir könnten irgendwann ja auch mal in C<D> sein). Der statische Typ muss für einen bestimmten Kontext aber immer fix sein. Deshalb wird für e der allgemeinste mögliche Typ genommen und das ist E (wegen C<R extends E>). Dann suchen wir g(F) in E, finden dort g(E) und überschreiben es anschließend in F.

    Das Attribut bestimmten wir immer über den statischen Typ, also den Typ der Variable, von der wir auf ein Attribut zugreifen bzw. von der Klasse, in der wir uns befinden. Bei d.e ist der statische Typ von d bspw. D (dynamisch F), also wollen wir im F-Objekt die Variable e. Dort gibt es zwei, weil F das e Attribut aus D und A erbt. Wir wählen das aus D geerbte e, weil der statische Typ von d D ist, also besteht nur Zugriff auf die Variablen aus D und Oberklassen von D.

    als Antwort auf: Zusatzaufgabe: Polymorphie mit Attributen #3395

    Ich habe jetzt zusätzlich zu den Lösungen noch Diagramme hinzugefügt. Unter den Pfeilen steht außerdem, wegen welchem Statement der jeweilige Pfeil eingezeichnet werden musste. Die Statements sind farbig hinterlegt, sodass du erkennen kannst, durch welches Statement welches Objekt erzeugt wurde. Für ausführlichere Erklärungen habe ich momentan leider zu wenig Zeit, sonst wären die Aufgaben auch im Skript.

    als Antwort auf: Wieso kompiliert dieser Code nicht? #3371

    Das liegt daran, dass generische Typen nur in einem objektorientierten Kontext benutzt werden können. Erzeugt man beispielsweise ein Foo<String> Objekt, so wäre klar, dass die Methode m einen String erwartet und auch einen String zurückgibt. Das wäre richtig, wenn die Methode nicht static wäre. Statische Methoden müssen aber nicht auf einem Objekt aufgerufen werden. Man ruft sie i. d. R. über den Klassennamen auf, also hier mittels Foo.m(...). Nun fehlt der Typparameter. Man könnte argumentieren, dass man den Typparameter ja angeben könnte, beispielsweise mittels Foo<String>.m(...). Das ist richtig, trotzdem funktioniert es bei statischen Methoden nicht. Warum genau das nicht funktioniert kann ich dir nicht sagen – eventuell hat man sich einfach dafür entschieden (weil meiner Meinung nach könnte es theoretisch auch möglich sein).

    Für dich heißt das, dass du dir einfach merkst, dass man generische Typen nicht in einem statischen Kontext benutzen kann. Möchte man das, so benötigt man eine generische Methode. Man müsste hier also public static <R> R m(R r) schreiben. Das <R> führt den neuen Typparameter R ein (könnte auch anders heißen, hat nichts mit dem R aus Foo<R> zu tun, dieses R wäre dann überflüssig).

    als Antwort auf: Aufgabe 87 – Aufruf 13 #3366

    Weil es sich um eine private Methode handelt und private Methoden nur innerhalb der Klasse selbst sichtbar sind. Du kannst diese Methode also nur dann benutzen, wenn der Aufruf in der Klasse F selbst stattfindet. Das ist hier nicht der Fall, da alle Aufrufe außerhalb stehen (also nicht in class F { ... }).

    als Antwort auf: Aufgabe 108 #3342

    Dieser Schritt wird gemacht, um das Zeichen (z. B. '4') in eine Ziffer (z. B. 4) umzuwandeln.

    Hintergrund:

    int c = '4';
    System.out.println(c);

    Dieser Code gibt den Wert 52 aus, weil 52 der ASCII-Wert des Zeichens ‘4’ ist und wir das Zeichen in einer int-Variable speichern. Genauso sieht das auch bei der chars()-Methode aus. Diese gibt dir keinen CharStream (diese Klasse existiert nicht) sondern einen IntStream. Um aus dem Zeichen ‘4’ (welches im Stream als 52 gepseichert ist) nun die “richtige” Ziffer 4 zu machen, müssen wir das Zeichen ‘0’ subtrahieren.  Dazu muss man die ASCII-Werte nicht kennen – es genügt zu wissen, dass die Entfernung zwischen ‘4’ und ‘0’ genau 4 beträgt. Das kannst du aber auch an der ASCII-Tabelle sehen. ‘4’ hat den Wert 52, ‘0’ hat den Wert 48, 52-48 ergibt 4. Genauso wäre das bei Buchstaben. Um aus einem Keinbuchstaben einen Großbuchstaben zu machen schreibt man c-'a'+'A', d. h. man subtrahiert erst das kleine a, um den Offset (Entfernung des Zeichens zum kleinen a) zu kennen (bspw. 3, falls c ein 'd' ist) und addiert diesen Offset dann zum großen A (z. B. 'A'+3 == 'D').

    als Antwort auf: Crashkurs-Aufgaben zu Polymorphie #3247

    Es geht um den Aufruf a.f(this): a ist dynamisch nicht C sondern A!

    • thisist statisch C, weil wir in der Klasse C sind, und dynamisch ebenfalls C, weil beim vorherigen Aufruf  (c.f((C)c)) ein C-Objekt vor dem Punkt stand, d. h. wir operieren auf einem C-Objekt.
    • Nachdem wir also auf einem C-Objekt operieren, handelt es sich bei a um das Attribut a des C-Objekts, das ursprünglich durch A a = new A() innerhalb des A-Kontruktors mit new C(this) erzeugt wurde . Dort wird this (also während der Erzeugung von A das A-Objekt selbst) übergeben. Das C-Objekt speichert sich genau dieses A-Objekt in seinem Attribut a ab (mittels this.a = a), d. h. das hier verwendete a hat den dynamischen Typ A (und zwar genau das A-Objekt, das auch in der main-Methode gespeichert wird).

    Daher suchen wir in A eine Methode f(C), finden statisch f(B) und führen diese Methode auch aus.

    als Antwort auf: Aufgabe 18d #3234

    Nein, t[i+1] – t[i] könnte negativ sein. Dann würdest du nie in den Fall “> schwank” kommen. Der Teil if (schwank <0) schwank = -schwank; sollte also irgendwie vor dem Vergleich mit schwank passieren.

    Außerdem musst du max vor der Schleife definieren, sonst kannst du es am Ende nicht zurückgeben (bei dir ist das nur lokal in der Schleife definiert).

    max speichert bei dir auch nicht den richtigen Wert, weil du es einfach immer überschreibst, d. h. die Variable speichert am Ende immer den letzten Tag (bei dem es noch zwei Werte gibt). Du darfst max nur dann überschreiben, wenn du auch schwank überschreibst.

    als Antwort auf: Crashkurs-Aufgaben zu Polymorphie #3189

    Zusatzaufgabe: Polymorphie mit parametrisierten Klassen (Generics)

    Die Java-Dateien der Polymorphie-Aufgaben aus dem Crashkurs findest du hier.
    Die unkommentierte Lösung zu dieser Aufgabe findest du hier, ein Objektdiagramm hier.

    public class GenericPoly {
       static class A<T extends B> {
          protected T e;
          public void set(T elem) { e = elem; }
          void f(B b) { System.out.println("A.f(B)"); }
          void f(C<E> c) { System.out.println("A.f(C)"); e.f(e); c.f(e); }
          void g(F f) { System.out.println("A.g(F)"); }
       }
       static class B extends A<B> {
          public B() { set(this); }
          void f(C<E> c) { System.out.println("B.f(C)"); }
          void f(F f) { System.out.println("B.f(F)"); this.g(f); }
       }
       static class C<R extends E> extends B {
          public R e;
          public C(R r) { this.e = r; }
          void f(B b) { System.out.println("C.f(B)"); }
          void f(A<C<E>> a) { System.out.println("C.f(A)"); a.f(this); }
          void g(F f) { System.out.println("C.g(F)"); e.g(f); }
       }
       static class D extends A<C<E>> implements E {
          protected A<B> e;
          public D() { e = new C<D>(this); }
          public void f(D d) { System.out.println("D.f(D)"); e.f(super.e); }
          public void g(F f) { System.out.println("D.g(F)"); }
          public void g(E e) { System.out.println("D.g(E)"); }
       }
       interface E {
          void g(E e);
       }
       static class F extends D {
          void f(A<C<F>> a) { System.out.println("F.f(A)"); this.f(a.e); }
          public void f(D d) { System.out.println("F.f(D)"); super.f(this); }
       }
       public static void main(String[] args) {
          A<C<F>> a = new A<>();
          C<F> c = new C<>(new F());
          a.set(c);
          D d = c.e;
          d.f(c);       // Aufruf 1
          c.f(a.e);     // Aufruf 2
          d.f(a);       // Aufruf 3
          d.f(d);       // Aufruf 4
          c.f(d);       // Aufruf 5
          d.e.f(c);     // Aufruf 6
          ((F)d).f(a);  // Aufruf 7
          c.f((F)d);    // Aufruf 8
          a.f(((A<C<E>>)d).e);  // Aufruf 9
       }
    }
    

    als Antwort auf: Crashkurs-Aufgaben zu Polymorphie #3188

    Zusatzaufgabe: Polymorphie mit Attributen

    Die Java-Dateien der Polymorphie-Aufgaben aus dem Crashkurs findest du hier.
    Die unkommentierte Lösung zu dieser Aufgabe findest du hier, ein Objektdiagramm hier.

    public class AttributPoly {
       static class A {
          protected B a;
          public A() { a = new C(this); }
          public A(B b) { set(b); }
          public void set(B b) { a = b; }
          void f(B b) { System.out.println("A.f(B)"); a.f(this); }
       }
       static class B extends A {
          public B(B b) { super(b); }
          public B() { }
          void f(A a) { System.out.println("B.f(A)"); this.a.f(a); }
          void f(B b) { System.out.println("B.f(B)"); }
       }
       static class C extends B {
          private A a;
          public C(A a) { super(null); this.a = a; }
          void f(A a) { System.out.println("C.f(A)"); }
          void f(B b) { System.out.println("C.f(B)"); a.f(this); }
          void f(C c) { System.out.println("C.f(C)"); }
       }
       public static void main(String[] args) {
          B b = new B(); A a = new A();
          B c = a.a;
          c.set(b);
          b.a.set(c);
          b.f(a);         // Aufruf 1
          c.f((C)c);      // Aufruf 2
          a.f(a);         // Aufruf 3
          b.a.f(c);       // Aufruf 4
          a.f(b);         // Aufruf 5
          ((C)c).a.f(c);  // Aufruf 6
          ((C) a.a.a.a).a.a.a.f(a);  // Aufruf 7
       }
    }
    

    als Antwort auf: Aufgabe 69 #3072

    Perfekt 🙂

    als Antwort auf: Aufgabe 69 #3053

    Wenn der Compiler beim Cast zwischen Book und Medium meckert ist das ein Indiz dafür, dass die Klassen in keiner Vererbungsrelation stehen. Book sollte von Medium erben – fehlt diese Ergänzung bei dir vielleicht?

    als Antwort auf: Willkommen zum Crashkurs! #2764

    Ich bin bemüht, so schnell wie möglich auf deine Frage zu antworten, bitte aber um Verständnis, wenn es länger dauert (insb. bei langen Fragen).

Ansicht von 19 Beiträgen - 1 bis 19 (von insgesamt 19)