Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
das eigentliche Anlegen läuft in beiden Fällen genau gleich ab, da gibt es keinen Unterschied. Die Varianten unterscheiden sich darin, was du nach dem Anlegen mit dem Objekt machen kannst.
Angenommen, deine MeineObjektKlasse hat eine Methode namens m. Dann kannst du diese in Variante 2 benutzen, weil der Compiler weiß, dass meineObjektKlasse vom Typ MeineObjektKlasse ist, in der diese Methode vorhanden ist.
In Variante 1 reduzierst du das Objekt auf den Typ Object. Der Compiler "vergisst" dann, welchen Typ deine Variable o tatsächlich hat. Du kannst dann nur noch auf die Methoden zugreifen, die in Object vorhanden sind - m ist nicht dabei.
Das ganze wird sinnvoll, sobald man sich mit Vererbung und Polymorphie befasst. Möchtest du das erklärt bekommen oder ist das sowieso für später geplant?
erst einmal vielen Dank für die ausführliche Antwort. Wenn ich hierfür einen Stern vergeben könnte, würde ichs tun
Für die Frage, ob ich weitere Erklärungen bräuchte kann ich nur sagen, dass ich "eigentlich" schon durch das Thema OOP durch bin, mir leider aber trotzdem "kleine" Details fehlen, die im Buch nicht wirklich erklärt werden.
Wenn Du natürlich Lust hast, auch dieses kurz zu erläutern, wäre ich Dir sehr Dankbar
Wenn du mit OOP durch bist, kennst du sicher den Begriff der Vererbung? Man kann eine Klasse von einer anderen ableiten und dabei z.B. Methoden hinzufügen, die in der Oberklasse nicht vorhanden waren. Nehmen wir als Beispiel ein Zeichenprogramm, mit dem man verschiedenen geometrische Figuren zeichnen kann, meinetwegen Kreise und Quadrate. Wenn du jetzt ganz viele Objekte davon hast, möchtest du ja nicht für jedes eine eigene Variable anlegen, sondern die zum Beispiel in einer Liste speichern. Das Problem ist nur: Wenn du deine Liste vom Typ "Kreis" sein lässt, kann sie nur Kreise speichern, wenn sie vom Typ "Quadrat" ist, nur Quadrate..
In Java gibt es drei Auswege:
- du könntest die Liste vom Typ "Object" sein lassen, denn wie du oben entdeckt hast, sind ja alle Klassen implizit auch "Objects". Deshalb können sie auch in so eine Liste getan werden. Das funktioniert, hat aber Nachteile. Beim Auslesen sind es nämlich immernoch "Objects", du müsstest sie dann manuell in Kreis oder Quadrat umwandeln. Insbesondere kann der Compiler nicht schon beim Kompilieren prüfen, ob das alles mit rechten Dingen zugeht, was du ihm da zumutest.
- du könntest eine gemeinsame Oberklasse für beide einführen, zum Beispiel "Zeichending". Die Klassen "Kreis" und "Quadrat" werden dann von "Zeichending" abgeleitet. Wenn du jetzt eine Liste vom Typ "Zeichending" anlegst, kannst du da Kreise und Quadrate reinlegen und der Compiler kann trotzdem prüfen, dass alles seine Richtigkeit hat! Bei "Object" könntest du nämlich auch Strings reinlegen und spätestens beim Zeichnen reißen die dann
Java:
List<Zeichending> meineDinge = new ArrayList<>();
Zeichending meinKreis = new Kreis(); // funktioniert, da Kreis extends Zeichending
meineDinge.add(meinKreis); // funktioniert deshalb auch
Man benötigt diese Technik also, wenn man mehrere Typen mit gleicher Basisklasse in eine Sammlung oder so legen will. Man kann dann nicht mehr auf ihre speziellen Methoden zugreifen, wohl aber auf die, die auch in der Basisklasse vorhanden sind.
- die dritte Variante sind Interfaces. Eigentlich genau wie bei der vorigen Variante, nur dass Zeichending dann eben ein Interface und keine Klasse ist. Wozu dieser Unterschied? Nun, ein Interface selbst schreibt nur vor, welche Methoden seine Jünger einhalten müssen, aber es kann keinen Code dafür bieten. Das Interface für Zeichending könnte zum Beispiel so aussehen:
Java:
public interface Zeichending {
void draw();
}
Das würde dafür sorgen, dass Kreis und Quadrat auf jeden Fall eine Methode "draw" haben müssen, damit sie sich Zeichending nennen dürfen. Ich habe in das Beispiel bei Variante zwei den beliebtesten Anwendungsfall für sowas reingeschummelt, nämlich bei der List:
Java:
List<Zeichending> meineDinge = new ArrayList<>();
meineDinge ist vom Typ "List", rechts wurde aber der Typ "ArrayList" benutzt? Warum habe ich das so gemacht? Nun, List beschreibt schon alle Methoden, die man von einer Liste erwartet, also Methoden zum Hinzufügen und Herausnehmen. Es gibt in Java aber mehrere Varianten von Listen, zum Beispiel ArrayList (Einfügen langsam, Rausnehmen schnell) oder LinkedList (Einfügen schnell, Rausnehmen langsam). Man kann je nach Zweck aussuchen, welche man gerade braucht. Aber falls man merkt, dass man doch eine falsche Wahl getroffen hat, kann man im obigen Beispiel jetzt an *einer* zentralen Stelle ändern, welche List-Variante man möchte. Wenn ich links auch ArrayList geschrieben hätte, würde sich das durch's ganze Programm ziehen und ich müsste an vielen Stellen was ändern.
Die Regel bei sowas lautet also: Wenn ich nur das Interface brauche, speicher ich mir die Variable auch als Typ des Interfaces. Nur, wenn ich auch speziellere Methoden brauche, speichere ich die Variable vom Typ des Objekts.
So, das war jetzt ziemlich viel.. aber du kannst gern weitere Fragen stellen, falls noch was unklar ist oder unverständlich war
Habe mir deinen Beitrag jetzt gefühlte 10 mal durchgelesen und habe wirklich nicht viel davon verstanden, was du mir hier versuchst zu erklären -.- ...
Vielleicht liegt es daran, dass ich das Prinzip der OOP noch nicht ganz verstanden habe oder die Klasse Liste einfach noch nicht kenne.
Aber wenn wir gerade dabei sind, lerne ich Java nur für die Android Programmierung und bin mir nicht sicher, ob ich sowas wissen muss, bis jetzt konnte ich dafür nämlich noch keinen Gebrauch finden.
Was ich allerdings weiß ist, dass ich den Code von Doodle Jump mir angesehen habe und feststellen musste, dass ich nicht einmal 1/8 davon verstehe, weil mir grundlegende Dinge fehlen. Dazu muss ich sagen, dass ich erst 4 Monate am lernen bin und schon meine erste App im Appstore habe.
Folgende Stelle finde ich im Doodle Jump Code:
Java:
static {
}
Da weiß ich z.B nicht, was das sein soll, eine Methode sieht anders aus. In dieser Static "Dinx" wird die eigene Klasse wohl aufgerufen und mit desiredAssertionStatus() überprüft.
Vielleicht bin ich auch einfach noch nicht so weit, um dieses verstehen zu können aber einige Dinge sind wirklich verwirrend.
Im AndroidPit sagen sie mir, ich sollte NIE eine Klasse mit static Methoden erstellen. Im Doodle Jump Code finde ich allerdings nur Static Methoden.
Ich denke ich werde einen Kurs besuchen, denn alleine scheint es unmöglich zu sein!
Ich Danke Dir trotzdem vielmals und bin natürlich für weitere Hilfe trotzdem Dankbar
Das ist so etwas wie ein Initialisierungsblock, nur eben statisch. Wenn die Klasse das erste mal aufgerufen wird, werden alle solcher Blöcke ausgeführt. Ähnlich wie das hier:
[c]public static int[] zahlen = new int[] {1, 2, 3, 4};[/c]
Sobald man die Klasse verwendet, wird das Array initialisiert und befüllt. Kann man auch so schreiben:
Java:
public static int[] zahlen;
static {
zahlen = new int[] {1, 2, 3, 4};
}
Das selbe gilt für Blöcke ohne das static, nur werden sie nach dem Konstruktor ausgeführt, und nicht, wenn man die Klasse das erste mal benutzt.
Hallo Ikaron,
danke für die ausführlichen Erklärungen.
Darf ich noch eine Frage dazu stellen?
Frage 1:
zur Variante 2 deiner Erklärung (also eigene Vererbung):
z.B. Auto auto = new Opel();
Opel erbt dabei von Auto
du sagtest:
jetz kann ich auf diesem Objekt nur die Methoden aufrufen die auch in der Klasse Auto sind.
heisst das, dass er auch wirklich die implementierung der Klasse Auto nimmt, oder dass er die neue Implementierung der Klasse Opel nimmt, falls die Methode neu definiert wurde.
Frage 2:
Kann dieses Problem komplett durch Interfaces gelöst werden?
das heisst kann ich wenn ich bei Progammlauf dynamisch noch nicht weiss welches Objekt kommt, immer die Methode über das Interface aufrufen?
zu Frage 1: wenn die Methode in Opel die gleiche Signatur wie die Methode in Auto hat, wird die Methode in Opel genommen, deshalb ist das ganze so toll Unter der Signatur einer Methode versteht man deren Namen zusammen mit Typen und Reihenfolge der Parameter sowie den Rückgabetyp. Kurz: Wenn die "Kopfzeile" der Methde die gleiche ist, dann wird die genommen, zu der das Objekt tatsächlich gehört, also so, wie sie mit new angelegt wurde.
Beispiel:
Java:
In Auto:
public void fahre() {
System.out.println("Ich fahre und bin nichts Besonderes");
In Opel:
public void fahre() {
System.out.println("Seht her: Ich fahre und bin dabei noch ein Opel!");
}
Wenn du nun eine Liste hast, in der Autos, Opels und vielleicht noch andere Unterklassen von Auto vorkommen, kannst du in einer Schleife sowas machen:
Java:
for(Auto auto : autos) {
auto.fahre();
}
Dann nimmt jede Klasse genau ihre Implementierung, d.h. genau die, die aus Sicht der Vererbung am "dichtesten" am dem tatsächlichen Typ ist. Wenn du eine Hierarchie Auto -> Opel -> Agila hast und auf einem Agila-Objekt die Methode "fahren" aufrufst, wird zuerst geschaut, ob in Agila die Methode vorhanden ist. Wenn Nein, wird in Opel geschaut und so geht es weiter nach oben, bis die Methode irgendwann vorhanden ist.
Zu Frage 2:
Richtig. Wenn du eine Methode programmierst, für die es nur wichtig ist, dass etwas fahren kann, könntest du ein Interface anlegen, in der nur die Methode "fahren" vorhanden ist - nennen wir es "Fahrbar". Als Parametertyp kannst du dann "Fahrbar" verwenden:
Java:
public void fahre(Fahrbar fahrbaresDing) {
fahrbaresDing.fahre();
}
Das tolle ist jetzt, dass du ohne weiteres ein Agila-Objekt übergeben kannst, wenn Auto "Fahrbar" implementiert. Der Vorteil ist, dass du später auch Fahrräder oder sonstwas mit diesem Interface ausstatten kannst und deine Methoden trotzdem noch funktionieren. Hättest du "Auto" als Parametertyp verwendet, wäre das nicht so elegant gewesen.
Ergo: Interfaces benutzen, um zu markieren, dass eine Klasse irgendwas Besonderes kann. Vererbung benutzen, wenn man sich irgendwie von einer Oberklasse unterscheiden möchte, aber trotzdem Gemeinsamkeiten vorhanden sind.
Der Name reicht noch nicht. Wenn in Auto eine Methode "void fahre()" ist, kann die nicht von Opel mit "void fahre(String wohin)" überschrieben werden, ebenso wenig mit "int fahre()". Übereinstimmen müssen:
* Name
* Anzahl, Reihenfolge und Typen der Parameter
* Rückgabedatentyp
Wobei bei den letzten beiden Punkten keine exakte Übereinstimmung nötig ist - wenn eine andere Methode "Auto" zurückgibt, darf sie auch mit einer Methode überschrieben werden, die "Opel" zurückgibt, weil "Opel" eben auch als "Auto" gewertet werden kann. Das ist aber recht fortgeschritten und man benötigt es eher seltne.
Nochmal kurz zu dem static Block..
Habe ich das jetzt richtig verstanden, dass der nach dem Konstruktor aufgerufen wird? Wieso steckt man den Inhalt nicht einfach in den Konstruktor? Ich denke, weil sich eine Klasse nicht selbst aufrufen kann oder?
So wie ich das jetzt aus den Erklärungen verstanden habe:
Der Konstruktor wird nur aufgerufen wenn du ein neues Objekt dieser Klasse erzeugst.
Dann werden die Initialisierungsvorgänge im Konstruktor aufgerufen...
warum du den static block nicht einfach in den Konstruktor tust?
na weil der für die statische Klasse ist. also nicht bei Objekterzeugung sondern bei statischer Benutzung dieser.
z.B. Auto auto = new Auto(); -> Konstruktor
System.print(); -> statische Benutzung der Klasse
also beim Aufruf statischer Methoden auf dem Klassennamen;
Meines Wissens nach wird der static initializer Block vom System (JVM) geladen sobald die Klasse in irgend einer Art benutzt wird. (Ist wohl auch nicht so ganz richtig aber zum Verständnis soll's wohl in Ordnung gehen) Und zwar nur einmal. In diesen Blöcken kann nur auf static Felder und Methoden direkt zugegriffen werden.
Dann gibt es noch "normale" initializer Blocks. Die werden beim Erzeugen einer Instanz ausgeführt. Jedes Mal. Und zwar direkt nach dem super() Aufruf bevor der restliche Code des Konstruktors ausgeführt wird. (Nicht vergessen: Auch wenn super() nicht vom Programmierer aufgerufen wird, wird er dennoch automatisch aufgerufen). In diesen Blöcken kann wie beim static initializer auf static Felder und Methoden zugegriffen werden, aber auch auf Instanz Felder und Methoden.
Mehrere Initializer Blöcke (gilt für beide Arten) werden in Reihe von oben nach unten abgearbeitet.
Kleines Beispiel:
Java:
public class Act
{
static {
System.out.println("static init");
printStatic();
}
static {
System.out.println("nochmal static init");
}
{
System.out.println("Nach super()");
printStatic();
print();
}
{
System.out.println("zweiter init block");
}
public Act()
{
super();
System.out.println("Constructor()");
}
public void print()
{
System.out.println("Ich darf auf Instanzmethoden etc zugreifen.");
}
public static void printStatic()
{
System.out.println("Ich darf auf Klassenmethoden zugreifen");
}
public static void main(String[] args) throws SecurityException, NoSuchMethodException
{
Act act = new Act();
Act act2 = new Act();
}
}
Ausgabe:
Code:
static init
Ich darf auf Klassenmethoden zugreifen
nochmal static init
Nach super()
Ich darf auf Klassenmethoden zugreifen
Ich darf auf Instanzmethoden etc zugreifen.
zweiter init block
Constructor()
Nach super()
Ich darf auf Klassenmethoden zugreifen
Ich darf auf Instanzmethoden etc zugreifen.
zweiter init block
Constructor()
So habe ich es mir durch experimentieren und recherchieren im Netz zusammengereimt. Auch ich bitte hier um Verbesserung/Berichtigung falls irgendwas nicht korrekt ist.