Ich stelle eine recht einfache Frage, bin aber etwas verwirrt.
Angenommen, ich habe eine Klasse Parent
:
public class Parent {
int name;
}
Und noch eine Klasse Child
:
public class Child extends Parent{
int salary;
}
Und schließlich meine Main.java-Klasse
public class Main {
public static void main(String[] args)
{
Parent parent = new Child();
parent.name= "abcd";
}
}
Wenn ich ein untergeordnetes Objekt wie mache
Child child = new Child():
Dann child
kann das Objekt auf beide name and salary
Variablen zugreifen .
Meine Frage ist:
Parent parent = new Child();
Ermöglicht den Zugriff nur auf name
Variablen der übergeordneten Klasse. Also, was ist die genaue Verwendung dieser Linie?
Parent parent = new Child();
Und auch wenn dynamischer Polymorphismus verwendet wird, warum ist die Variable der untergeordneten Klasse danach nicht mehr zugänglich?
Parent parent = new Child();
name
richtige Typ?Antworten:
Zunächst eine Klarstellung der Terminologie: Wir weisen
Child
einer Variablen vom Typ ein Objekt zuParent
.Parent
ist eine Referenz auf ein Objekt, das zufällig ein Subtyp vonParent
a istChild
.Es ist nur in einem komplizierteren Beispiel nützlich. Stellen Sie sich vor, Sie fügen
getEmployeeDetails
der Klasse Parent Folgendes hinzu:public String getEmployeeDetails() { return "Name: " + name; }
Wir könnten diese Methode überschreiben,
Child
um weitere Details bereitzustellen:@Override public String getEmployeeDetails() { return "Name: " + name + " Salary: " + salary; }
Jetzt können Sie eine Codezeile schreiben, die alle verfügbaren Details abruft, unabhängig davon, ob es sich bei dem Objekt um ein
Parent
oder um Folgendes handeltChild
:Der folgende Code:
Parent parent = new Parent(); parent.name = 1; Child child = new Child(); child.name = 2; child.salary = 2000; Parent[] employees = new Parent[] { parent, child }; for (Parent employee : employees) { employee.getEmployeeDetails(); }
Wird zur Ausgabe führen:
Name: 1 Name: 2 Salary: 2000
Wir haben a
Child
alsParent
. Es hatte ein spezielles Verhalten, das für dieChild
Klasse einzigartig war , aber als wirgetEmployeeDetails()
anriefen, konnten wir den Unterschied ignorieren und uns darauf konzentrieren, wieParent
undChild
ähnlich sind. Dies wird als Subtyp-Polymorphismus bezeichnet .In Ihrer aktualisierten Frage wird gefragt, warum
Child.salary
nicht zugänglich ist, wenn dasChild
Objekt in einerParent
Referenz gespeichert ist . Die Antwort ist der Schnittpunkt von "Polymorphismus" und "statischer Typisierung". Da Java zur Kompilierungszeit statisch typisiert ist, erhalten Sie bestimmte Garantien vom Compiler, müssen jedoch im Austausch Regeln befolgen, da der Code sonst nicht kompiliert wird. Hier besteht die relevante Garantie darin, dass jede Instanz eines Subtyps (z. B.Child
) als Instanz seines Supertyps (zParent
. B. ) verwendet werden kann. Beispielsweise wird Ihnen garantiert, dass beim Zugriffemployee.getEmployeeDetails
oderemployee.name
beim Definieren der Methode oder des Felds für jedes Nicht-Null-Objekt, das einer Variablenemployee
vom Typ zugewiesen werden könnteParent
. Um diese Garantie zu gewährleisten, berücksichtigt der Compiler nur diesen statischen Typ (im Grunde den Typ der Variablenreferenz).Parent
), wenn er entscheidet, auf was Sie zugreifen können. Sie können also nicht auf Mitglieder zugreifen, die für den Laufzeittyp des Objekts definiert sindChild
.Wenn Sie wirklich wollen , eine verwenden ,
Child
alsParent
dies ist eine einfache Einschränkung zu leben und Ihr Code für verwendbar seinParent
und alle Subtypen. Wenn dies nicht akzeptabel ist, geben Sie den Typ der Referenz anChild
.quelle
Wenn Sie Ihr Programm kompilieren, erhält die Referenzvariable der Basisklasse Speicher und der Compiler überprüft alle Methoden in dieser Klasse. Daher werden alle Methoden der Basisklasse überprüft, nicht jedoch die Methoden der untergeordneten Klasse. Jetzt können zur Laufzeit, wenn das Objekt erstellt wird, nur überprüfte Methoden ausgeführt werden. Falls eine Methode in der untergeordneten Klasse überschrieben wird, wird diese Funktion ausgeführt. Andere Funktionen der untergeordneten Klasse werden nicht ausgeführt, da der Compiler sie zum Zeitpunkt der Kompilierung nicht erkannt hat.
quelle
Sie können über eine gemeinsame übergeordnete Schnittstelle auf alle Unterklassen zugreifen. Dies ist vorteilhaft für die Ausführung gemeinsamer Operationen, die für alle Unterklassen verfügbar sind. Ein besseres Beispiel wird benötigt:
public class Shape { private int x, y; public void draw(); } public class Rectangle extends Shape { public void draw(); public void doRectangleAction(); }
Nun, wenn Sie haben:
List<Shape> myShapes = new ArrayList<Shape>();
Sie können jedes Element in der Liste als Form referenzieren. Sie müssen sich keine Sorgen machen, ob es sich um ein Rechteck oder einen anderen Typ handelt, wie z. B. Kreis. Sie können sie alle gleich behandeln; Sie können alle zeichnen. Sie können doRectangleAction nicht aufrufen, da Sie nicht wissen, ob die Form wirklich ein Rechteck ist.
Dies ist ein Handel zwischen der generischen Behandlung von Objekten und der spezifischen Behandlung von Objekten.
Wirklich, ich denke, Sie müssen mehr über OOP lesen. Ein gutes Buch sollte helfen: http://www.amazon.com/Design-Patterns-Explained-Perspective-Object-Oriented/dp/0201715945
quelle
Wenn Sie einer Unterklasse einen übergeordneten Typ zuweisen, bedeutet dies, dass Sie damit einverstanden sind, die allgemeinen Funktionen der übergeordneten Klasse zu verwenden.
Es gibt Ihnen die Freiheit, von verschiedenen Unterklassenimplementierungen zu abstrahieren. Infolgedessen werden Sie mit den übergeordneten Funktionen eingeschränkt.
Diese Art der Zuweisung wird jedoch als Upcasting bezeichnet.
Parent parent = new Child();
Das Gegenteil ist Niedergeschlagenheit.
Wenn Sie also eine Instanz von erstellen
Child
und diese an ein Downcast senden, könnenParent
Sie dieses Typattribut verwendenname
. Wenn Sie eine Instanz von erstellenParent
, können Sie dasselbe wie im vorherigen Fall tun, aber nicht verwenden,salary
da das Attribut in der Instanz nicht vorhanden istParent
. Kehren Sie zum vorherigen Fall zurück, dersalary
nur bei Downcasting verwendet werden kannChild
.Es gibt detailliertere Erklärungen
quelle
Parent
, können Sie verwenden,Parent
wenn Sie zuweisen (wenn Sie können)Child
, können Sie verwendenChild
.Es ist einfach.
Parent parent = new Child();
In diesem Fall ist der Typ des Objekts
Parent
. AmeiseParent
hat nur eine Eigenschaft. Es istname
.Child child = new Child();
Und in diesem Fall ist der Typ des Objekts
Child
. AmeiseChild
hat zwei Eigenschaften. Sie sindname
undsalary
.Tatsache ist, dass das nicht endgültige Feld nicht sofort bei der Deklaration initialisiert werden muss. Normalerweise geschieht dies zur Laufzeit, da Sie häufig nicht genau wissen, welche Implementierung Sie genau benötigen. Stellen Sie sich zum Beispiel vor, Sie haben eine Klassenhierarchie mit der Klasse
Transport
an der Spitze. Und drei Unterklassen:Car
,Helicopter
undBoat
. Und es gibt noch eine andere Klasse,Tour
die ein Feld hatTransport
. Das ist:class Tour { Transport transport; }
Solange ein Benutzer keine Reise gebucht und keine bestimmte Transportart ausgewählt hat, können Sie dieses Feld nicht initialisieren. Es ist zuerst.
Zweitens wird angenommen, dass alle diese Klassen eine Methode haben müssen,
go()
jedoch mit einer anderen Implementierung. Sie können standardmäßig eine Basisimplementierung in der Oberklasse definierenTransport
und eindeutige Implementierungen in jeder Unterklasse besitzen. Mit dieser Initialisierung könnenTransport tran; tran = new Car();
Sie die Methode aufrufentran.go()
und Ergebnisse abrufen , ohne sich um eine bestimmte Implementierung kümmern zu müssen. Es wird eine überschriebene Methode aus einer bestimmten Unterklasse aufgerufen.Darüber hinaus können Sie die Instanz der Unterklasse überall dort verwenden, wo die Instanz der Oberklasse verwendet wird. Zum Beispiel möchten Sie die Möglichkeit bieten, Ihren Transport zu mieten. Wenn Sie nicht Polymorphismus verwenden, haben Sie eine Menge von Methoden für jeden Fall schreiben:
rentCar(Car car)
,rentBoat(Boat boat)
und so weiter. Gleichzeitig können Sie durch Polymorphismus eine universelle Methode erstellenrent(Transport transport)
. Sie können darin ein Objekt jeder Unterklasse von übergebenTransport
. Wenn sich Ihre Logik im Laufe der Zeit erhöht und Sie eine weitere Klasse in der Hierarchie erstellen müssen? Wenn Sie Polymorphismus verwenden, müssen Sie nichts ändern. Erweitern Sie einfach die KlasseTransport
und übergeben Sie Ihre neue Klasse an die Methode:public class Airplane extends Transport { //implementation }
und
rent(new Airplane())
. Undnew Airplane().go()
im zweiten Fall.quelle
Diese Situation tritt auf, wenn Sie mehrere Implementierungen haben. Lassen Sie mich erklären. Angenommen, Sie haben mehrere Sortieralgorithmen und möchten zur Laufzeit den zu implementierenden auswählen oder jemand anderem die Möglichkeit geben, seine Implementierung hinzuzufügen. Um dieses Problem zu lösen, erstellen Sie normalerweise eine abstrakte Klasse (Parent) und haben eine andere Implementierung (Child). Wenn Sie schreiben:
Child c = new Child();
Sie binden Ihre Implementierung an die Child-Klasse und können sie nicht mehr ändern. Andernfalls, wenn Sie verwenden:
Parent p = new Child();
Solange Child Parent erweitert, können Sie es in Zukunft ändern, ohne den Code zu ändern.
Dasselbe kann mit Schnittstellen gemacht werden: Parent ist keine Klasse mehr, sondern eine Java-Schnittstelle.
Im Allgemeinen können Sie diesen Ansatz in DAO-Mustern verwenden, in denen Sie mehrere DB-abhängige Implementierungen wünschen. Sie können einen Blick auf FactoryPatter oder AbstractFactory Pattern werfen. Hoffe das kann dir helfen.
quelle
Angenommen, Sie möchten ein Array von Instanzen der Parent-Klasse und eine Reihe von Child-Klassen Child1, Child2, Child3, die Parent erweitern. Es gibt Situationen, in denen Sie nur an der allgemeineren Implementierung der übergeordneten Klasse interessiert sind und sich nicht für spezifischere Dinge interessieren, die von untergeordneten Klassen eingeführt werden.
quelle
Ich denke, alle obigen Erklärungen sind etwas zu technisch für Leute, die mit objektorientierter Programmierung (OOP) noch nicht vertraut sind. Vor Jahren habe ich eine Weile gebraucht, um mich damit zu beschäftigen (als Jr Java Developer), und ich habe wirklich nicht verstanden, warum wir eine übergeordnete Klasse oder eine Schnittstelle verwenden, um die tatsächliche Klasse, die wir tatsächlich aufrufen, unter der Decke zu verbergen.
Der unmittelbare Grund dafür ist, die Komplexität zu verbergen, damit sich der Anrufer nicht oft ändern muss (in Laienbegriffen gehackt und aufgebockt werden). Dies ist sehr sinnvoll, insbesondere wenn Sie vermeiden möchten, dass Fehler entstehen. Und je mehr Sie Code ändern, desto wahrscheinlicher ist es, dass sich einige davon auf Sie einschleichen. Wenn Sie jedoch nur Code erweitern, ist es weitaus weniger wahrscheinlich, dass Sie Fehler haben, da Sie sich jeweils auf eine Sache konzentrieren und sich Ihr alter Code nicht oder nur geringfügig ändert. Stellen Sie sich vor, Sie haben eine einfache Anwendung, mit der die Mitarbeiter der Ärzteschaft Profile erstellen können. Nehmen wir zur Vereinfachung an, wir haben nur Allgemeinmediziner, Chirurgen und Krankenschwestern (in Wirklichkeit gibt es natürlich viel spezifischere Berufe). Für jeden Beruf, Sie möchten einige allgemeine und einige spezifische Informationen nur für diesen Fachmann speichern. Beispielsweise kann ein Chirurg allgemeine Felder wie Vorname, Nachname, Jahre Erfahrung als allgemeine Felder haben, aber auch bestimmte Felder, z. B. Spezialisierungen, die in einer Listeninstanzvariablen gespeichert sind, wie Liste mit Inhalten, die "Knochenchirurgie", "Augenchirurgie" usw. ähnlich sind. Eine Krankenschwester hätte nichts davon, könnte aber Listenverfahren haben, mit denen sie vertraut ist. GeneralPractioners hätte ihre eigenen Besonderheiten. Als Ergebnis, wie Sie ein Profil von bestimmten speichern. Sie möchten jedoch nicht, dass Ihre ProfileManager-Klasse über diese Unterschiede informiert wird, da sie sich im Laufe der Zeit zwangsläufig ändern und erhöhen, wenn Ihre Anwendung ihre Funktionalität auf weitere medizinische Berufe erweitert, z. B. Physiotherapeut, Kardiologe, Onkologe usw. Sie möchten lediglich, dass Ihr ProfileManger save () sagt, unabhängig davon, wessen Profil er speichert. Daher ist es üblich, diese hinter und Schnittstelle und abstrakte Klasse oder eine Elternklasse zu verbergen (wenn Sie die Erstellung eines allgemeinen medizinischen Mitarbeiters zulassen möchten). In diesem Fall wählen wir eine übergeordnete Klasse aus und nennen sie MedicalEmployee. Unter dem Deckmantel kann es auf jede der oben genannten spezifischen Klassen verweisen, die es erweitern. Wenn der ProfileManager myMedicalEmployee.save () aufruft, wird die save () -Methode polymorph (viele-strukturell) in den richtigen Klassentyp aufgelöst, der ursprünglich zum Erstellen des Profils verwendet wurde, z. B. Nurse, und ruft darin die save () -Methode auf Klasse. oder eine Elternklasse (wenn Sie die Erstellung eines allgemeinen medizinischen Mitarbeiters zulassen möchten). In diesem Fall wählen wir eine übergeordnete Klasse aus und nennen sie MedicalEmployee. Unter dem Deckmantel kann es auf jede der oben genannten spezifischen Klassen verweisen, die es erweitern. Wenn der ProfileManager myMedicalEmployee.save () aufruft, wird die save () -Methode polymorph (viele-strukturell) in den richtigen Klassentyp aufgelöst, der ursprünglich zum Erstellen des Profils verwendet wurde, z. B. Nurse, und ruft die save () -Methode auf Klasse. oder eine Elternklasse (wenn Sie die Erstellung eines allgemeinen medizinischen Mitarbeiters zulassen möchten). In diesem Fall wählen wir eine übergeordnete Klasse aus und nennen sie MedicalEmployee. Unter dem Deckmantel kann es auf jede der oben genannten spezifischen Klassen verweisen, die es erweitern. Wenn der ProfileManager myMedicalEmployee.save () aufruft, wird die save () -Methode polymorph (viele-strukturell) in den richtigen Klassentyp aufgelöst, der ursprünglich zum Erstellen des Profils verwendet wurde, z. B. Nurse, und ruft darin die save () -Methode auf Klasse.
In vielen Fällen wissen Sie nicht genau, welche Implementierung Sie zur Laufzeit benötigen. Aus dem obigen Beispiel wissen Sie nicht, ob ein Generalpraktiker, ein Chirurg oder eine Krankenschwester ein Profil erstellen würde. Sie wissen jedoch, dass Sie dieses Profil nach Abschluss speichern müssen, egal was passiert. MedicalEmployee.profile () macht genau das. Es wird von jedem bestimmten Typ von MedicalEmployee repliziert (überschrieben) - GeneralPractitioner, Surgeon, Nurse,
Das Ergebnis von (1) und (2) oben ist, dass Sie jetzt neue medizinische Berufe hinzufügen, save () in jeder neuen Klasse implementieren und dadurch die save () -Methode in MedicalEmployee überschreiben können und ProfileManager bei nicht ändern müssen alles.
quelle
Ich weiß, dass dies ein sehr alter Thread ist, aber ich bin einmal auf denselben Zweifel gestoßen.
Das Konzept von
Parent parent = new Child();
hat also etwas mit frühem und spätem Binden in Java zu tun.Die Bindung von privaten, statischen und endgültigen Methoden erfolgt beim Kompilieren, da sie nicht überschrieben werden können und die normalen Methodenaufrufe und überladenen Methoden Beispiele für eine frühe Bindung sind.
Betrachten Sie das Beispiel:
class Vehicle { int value = 100; void start() { System.out.println("Vehicle Started"); } static void stop() { System.out.println("Vehicle Stopped"); } } class Car extends Vehicle { int value = 1000; @Override void start() { System.out.println("Car Started"); } static void stop() { System.out.println("Car Stopped"); } public static void main(String args[]) { // Car extends Vehicle Vehicle vehicle = new Car(); System.out.println(vehicle.value); vehicle.start(); vehicle.stop(); } }
Ausgabe: 100
Auto gestartet
Fahrzeug angehalten
Dies geschieht, weil
stop()
es sich um eine statische Methode handelt, die nicht überschrieben werden kann. Die Bindung vonstop()
erfolgt also zur Kompilierungszeit undstart()
ist nicht statisch. Sie wird in der untergeordneten Klasse überschrieben. Die Informationen über den Objekttyp sind also nur zur Laufzeit verfügbar (späte Bindung), und daher wird diestart()
Methode der Fahrzeugklasse aufgerufen.Auch in diesem Code
vehicle.value
gibt uns das100
als Ausgabe an, da die Initialisierung von Variablen nicht zu spät gebunden wird. Das Überschreiben von Methoden ist eine der Möglichkeiten, mit denen Java den Laufzeitpolymorphismus unterstützt .Ich hoffe, dies beantwortet, wo
Parent parent = new Child();
es wichtig ist und warum Sie mit der obigen Referenz nicht auf die untergeordnete Klassenvariable zugreifen konnten.quelle
Sie deklarieren parent als Parent, sodass Java nur Methoden und Attribute der Parent-Klasse bereitstellt.
Child child = new Child();
sollte arbeiten. Oder
Parent child = new Child(); ((Child)child).salary = 1;
quelle
Parent parent = new Child();
es nützlich / sinnvoll ist.