Warum ist die Instanzerstellung so, wie sie ist?

17

Ich habe C # in den letzten sechs Monaten oder so gelernt und beschäftige mich jetzt mit Java. Meine Frage bezieht sich auf die Instanzerstellung (in beiden Sprachen) und mehr auf: Ich frage mich, warum sie das so gemacht haben. Nehmen Sie dieses Beispiel

Person Bob = new Person();

Gibt es einen Grund, warum das Objekt zweimal angegeben wird? Würde es jemals eine geben something_else Bob = new Person()?

Es würde scheinen, wenn ich mich an die Konvention halten würde, wäre es eher so:

int XIsAnInt;
Person BobIsAPerson;

Oder vielleicht einer von diesen:

Person() Bob;
new Person Bob;
new Person() Bob;
Bob = new Person();

Ich denke, ich bin neugierig, ob es eine bessere Antwort gibt als "so wird es gemacht".

Jason Wohlgemuth
quelle
26
Was ist, wenn Person ein Subtyp ist LivingThing? Du könntest schreiben LivingThing lt = new Person(). Suchen Sie nach Vererbung und Schnittstellen.
Xlecoustillier
2
Person Bobdeklariert eine Variable vom Typ "reference to Person", die aufgerufen wird Bob. new Person()Erstellt ein PersonObjekt. Referenzen, Variablen und Objekte sind drei verschiedene Dinge!
user253751
5
Ärgern Sie sich über die Entlassung? Warum dann nicht schreiben var bob = new Person();?
200_success
4
Person Bob();ist in C ++ möglich und bedeutet fast dasselbe wiePerson Bob = Person();
user60561
3
@ user60561 no, deklariert eine Funktion, die keine Argumente verwendet und Person zurückgibt.
Nikolai

Antworten:

52

Wäre da jemals eine etwas_else Bob = neue Person ()?

Ja, wegen der Vererbung. Wenn:

public class StackExchangeMember : Person {}

Dann:

Person bob = new StackExchangeMember();
Person sam = new Person();

Bob ist auch eine Person und will nicht anders behandelt werden als jeder andere.

Außerdem könnten wir Bob mit Superkräften ausstatten:

public interface IModerator { }
public class StackOverFlowModerator : StackExchangeMember, IModerator {}

IModerator bob = new StackOverFlowModerator();

Und so wird er es nicht ertragen, anders behandelt zu werden als jeder andere Moderator. Und er schleicht gerne im Forum herum, um alle in der Reihe zu halten, während er inkognito ist:

StackExchangeMember bob = new StackOverFlowModerator();

Wenn er dann ein schlechtes erstes Poster findet, wirft er seinen Umhang der Unsichtbarkeit ab und stürzt sich.

((StackOverFlowModerator) bob).Smite(sam);

Und danach kann er alles Unschuldige und so weiter tun:

((Person) bob).ImNotMeanIWasJustInstantiatedThatWay();
Radarbob
quelle
20
Dies wäre viel klarer, wenn Sie Ihre Objektnamen in Kleinbuchstaben schreiben würden.
Leichtigkeit Rennen mit Monica
38
Gutes Beispiel für die Spezifikation; schlechtes Beispiel für Vererbung. Versuchen Sie anderen Lesern nicht, Benutzerrollen mithilfe der Vererbung zu lösen.
Aaronaught
8
@Aaronaught ist richtig. Erstellen Sie keine separaten Klassen für verschiedene Arten von Personen. Verwenden Sie ein Bitfeld enum.
Cole Johnson
1
@Aaronaught Es ist alles sehr gut zu sagen, was nicht zu tun ist, aber es ist nicht sehr hilfreich, ohne zu sagen, was die Leute stattdessen tun sollen.
Pharap,
5
@Pharap: Ich habe genau das in mehreren anderen Fragen getan . Die einfache Antwort lautet, dass Benutzer (Authentifizierung / Identität) und Sicherheitsrichtlinien (Autorisierung / Berechtigungen) als separate Anliegen behandelt werden sollten und die Standardmodelle für Sicherheitsrichtlinien entweder rollenbasiert oder anspruchsbasiert sind. Die Vererbung ist nützlicher, um das Objekt zu beschreiben, das die Authentifizierung tatsächlich durchführt, z. B. eine LDAP-Implementierung und eine SQL-Implementierung.
Aaronaught
34

Lassen Sie uns Ihre erste Codezeile nehmen und untersuchen.

Person Bob = new Person();

Die erste Personist eine Typspezifikation. In C # können wir darauf verzichten, indem wir einfach sagen

var Bob = new Person();

und der Compiler ableiten , die Art des Variablen Bob vom Konstruktoraufruf Person().

Aber vielleicht möchten Sie so etwas schreiben:

IPerson Bob = new Person();

Wenn Sie nicht den gesamten API-Vertrag von Person erfüllen, sondern nur den von der Schnittstelle angegebenen Vertrag IPerson.

Robert Harvey
quelle
2
+1: Ich werde das IPersonBeispiel in meinem Code ausführen, um sicherzustellen, dass ich nicht versehentlich private Methoden verwende, wenn ich Code schreibe, der in eine andere IPersonImplementierung kopiert / eingefügt werden soll.
Cort Ammon - Reinstate Monica
@ CortAmmon Ich denke, Sie haben einen Tippfehler, klar, Sie meinten "Arbeit mit Polymorphismus", anstatt diesen Code zu kopieren / einzufügen: D
Benjamin Gruenbaum
@BenjaminGruenbaum sicher, für eine Definition von Polymorphismus ;-)
Cort Ammon - Reinstate Monica
@CortAmmon wie würden Sie versehentlich eine private Methode aufrufen ? Sicher meintest du internal?
Cole Johnson
@ColeJohnson so oder so. Ich schrieb das Denken an einen bestimmten Fall, auf den ich stoße, wo privatees Sinn macht: Factory-Methoden, die Teil der Klasse sind, die instanziiert wird. In meiner Situation sollte der Zugang zu privaten Werten die Ausnahme sein, nicht die Norm. Ich arbeite an Code, der mich noch lange überleben wird. Wenn ich das so mache, ist es nicht nur unwahrscheinlich, dass ich private Methoden selbst verwende, sondern wenn der nächste Entwickler diese ein paar Dutzend Stellen kopiert / einfügt und der Entwickler sie danach kopiert, verringert sich die Wahrscheinlichkeit, dass jemand eine Gelegenheit dazu sieht Verwenden Sie private Methoden als "normales" Verhalten.
Cort Ammon - Reinstate Monica
21
  1. Diese Syntax ist so ziemlich ein Erbe von C ++, das im Übrigen beides enthält:

    Person Bob;

    und

    Person *bob = new Bob();

    Der erste, um ein Objekt innerhalb des aktuellen Bereichs zu erstellen, der zweite, um einen Zeiger auf ein dynamisches Objekt zu erstellen.

  2. Das kannst du auf jeden Fall haben something_else Bob = new Person()

    IEnumerable<int> nums = new List<int>(){1,2,3,4}

    Sie machen hier zwei verschiedene Dinge, geben den Typ der lokalen Variablen an numsund sagen, Sie möchten ein neues Objekt vom Typ 'Liste' erstellen und dort ablegen.

  3. C # stimmt mit Ihnen überein, da der Typ der Variablen die meiste Zeit mit dem übereinstimmt, was Sie in die Variable eingegeben haben:

    var nums = new List<int>();
  4. In einigen Sprachen tun Sie Ihr Bestes, um die Angabe der Variablentypen wie in F # zu vermeiden :

    let list123 = [ 1; 2; 3 ]
AK_
quelle
5
Es ist wahrscheinlich genauer zu sagen, dass Ihr zweites Beispiel einen Zeiger auf ein neues Bob-Objekt erstellt. Die Art und Weise, wie Dinge gespeichert werden, ist technisch ein Implementierungsdetail.
Robert Harvey
4
"Der erste, der ein lokales Objekt auf dem Stapel erstellt, der zweite, der ein Objekt auf dem Heap erstellt." Oh man, nicht schon wieder diese Fehlinformation.
Leichtigkeit Rennen mit Monica
@LightnessRacesinOrbit besser?
AK_
1
@AK_ Während "dynamisch" so ziemlich "Heap" bedeutet (wenn Heap das von der Plattform verwendete Speichermodell ist), unterscheidet sich "automatisch" stark von "Stapel". Wenn Sie dies tun new std::pair<int, char>(), haben die Mitglieder firstund seconddas Paar eine automatische Speicherdauer, aber sie werden wahrscheinlich auf dem Heap zugeordnet (als Mitglieder des pairObjekts für die dynamische Speicherdauer ).
Setzen Sie Monica
1
@AK_: Ja, diese Namen bedeuten genau das, was sie bedeuten, wohingegen dieser "Stapel" vs. "Haufen" -Nonsen nicht stimmt . Das ist der springende Punkt. Dass Sie sie als verwirrend empfinden, verstärkt die Notwendigkeit, dass wir sie unterrichten, damit Sie sich nicht auf ungenaue / falsche Terminologie verlassen, nur weil sie Ihnen vertraut ist!
Leichtigkeit Rennen mit Monica
3

Es gibt einen großen Unterschied zwischen int xund Person bob. Ein intist ein intist ein intund es muss immer ein sein intund kann nie etwas anderes als ein sein int. Auch wenn Sie das intbeim Deklarieren ( int x;) nicht initialisieren , wird es dennoch intauf den Standardwert gesetzt.

Wenn Sie jedoch deklarieren Person bob, gibt es eine große Flexibilität, worauf sich der Name bobzu einem bestimmten Zeitpunkt tatsächlich beziehen kann. Es könnte sich auf eine beziehen Person, oder es könnte sich auf eine andere Klasse beziehen, zB Programmerabgeleitet von Person; es könnte sogar sein null, sich überhaupt auf kein Objekt zu beziehen.

Beispielsweise:

  Person bob   = null;
  Person carol = new Person();
  Person ted   = new Programmer();
  Person alice = personFactory.functionThatReturnsSomeKindOfPersonOrNull();

Die Sprachdesigner hätten sicherlich eine alternative Syntax entwickeln können, die das Gleiche bewirkt hätte wie Person carol = new Person()bei weniger Symbolen, aber sie hätten es dennoch zulassen müssen Person carol = new Person() (oder eine seltsame Regel festlegen müssen, die dieses bestimmte der vier obigen Beispiele als illegal erklärt). Es ging ihnen mehr darum, die Sprache "einfach" zu halten, als extrem präzisen Code zu schreiben. Das mag ihre Entscheidung beeinflusst haben, die kürzere alternative Syntax nicht bereitzustellen, aber es war auf jeden Fall nicht notwendig und sie haben es nicht bereitgestellt.

David K
quelle
1

Die beiden Deklarationen können unterschiedlich sein, sind jedoch häufig gleich. Ein allgemein empfohlenes Muster in Java sieht folgendermaßen aus:

List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();

Diese Variablen listundmap werden mithilfe der Schnittstellen deklariert, Listund Mapwährend der Code bestimmte Implementierungen instanziiert. Auf diese Weise hängt der Rest des Codes nur von den Schnittstellen ab, und es ist einfach, verschiedene Implementierungsklassen für die Instanziierung auszuwählen TreeMap, da der Rest des Codes nicht von einem Teil der HashMapAPI abhängen kann , der sich außerhalb der MapSchnittstelle befindet.

Ein weiteres Beispiel, bei dem sich die beiden Typen unterscheiden, ist eine Factory-Methode, die eine bestimmte Unterklasse zum Instanziieren auswählt und sie dann als Basistyp zurückgibt, sodass der Aufrufer die Implementierungsdetails nicht kennen muss, z.

Typinferenz kann die Quellcode-Redundanz beheben. ZB in Java

List<String> listOne = Collections.emptyList();

wird dank der Typinferenz und der Deklaration die richtige Art von Liste erstellen

static <T> List<T> emptyList(); 

In einigen Sprachen geht die Typinferenz weiter, z. B. in C ++

auto p = new Person();
Jerry101
quelle
BTW die Java - Sprache setzt eine starke Konvention für kleingeschrieben Bezeichnernamen verwenden, wie bob, nicht Bob. Dies vermeidet viele Mehrdeutigkeiten, zB package.Class vs. Class.variable.
Jerry101
... und deswegen hast du clazz.
ein
Nein, clazzwird verwendet, da classes sich um ein Schlüsselwort handelt, sodass es nicht als Bezeichner verwendet werden kann.
Jerry101
... was kein Problem wäre, wenn die Namenskonventionen nicht so wären, wie sie sind. Classist eine vollkommen gültige Kennung.
CHAO
... wie es ist cLaSs, cLASSund cLASs.
el.pescado
1

Mit den Worten des Laien:

  • Das Trennen der Deklaration von der Instanziierung hilft zu entkoppeln, wer die Objekte verwendet und wer sie erstellt
  • Wenn Sie dies tun, wird der Polyporphismus aktiviert, da, solange der instanziierte Typ ein Subtyp des Deklarationstyps ist, der gesamte Code, der die Variable verwendet, funktioniert
  • In stark typisierten Sprachen müssen Sie eine Variable deklarieren, die ihren Typ angibt, indem var = new Process()Sie einfach nicht zuerst die Variable deklarieren.
Tulains Córdova
quelle
0

Es geht auch um die Kontrolle darüber, was passiert. Wenn die Deklaration eines Objekts / einer Variablen automatisch einen Konstruktor aufruft, z. B. if

Person somePerson;

war automatisch das gleiche wie

Person somePerson = new Person(blah, blah..);

Dann könnten Sie (zum Beispiel) niemals statische Factory-Methoden verwenden, um Objekte zu instanziieren, anstatt Standardkonstruktoren zu verwenden. Das heißt, es gibt Zeiten, in denen Sie keinen Konstruktor für eine neue Objektinstanz aufrufen möchten.

Dieses Beispiel ist in erklärt Joshua Bloch ‚s Effective Java (Punkt 1 ironisch genug!)

David Scholefield
quelle
In Bezug auf Bücher stammten sowohl mein C # -Buch als auch mein Java-Buch von Joyce Farrell. Genau das hat der Kurs festgelegt. Ich habe auch beide mit verschiedenen Youtube-Videos auf C # und Java ergänzt.
Jason Wohlgemuth
Ich habe nicht kritisiert, nur einen Hinweis gegeben, woher das kommt :)
David Scholefield