Warum verwendet Java bei einigen Klassen keine Kapselung?

25

Meine Frage bezieht sich auf System.inund System.outKlassen (möglicherweise gibt es andere wie die in der Standardbibliothek). Warum das? Ist das nicht eine schlechte Praxis in OOP? Sollte es nicht so verwendet werden: System.getIn()und System.getOut()? Ich hatte immer diese Frage und hoffe, dass ich hier eine gute Antwort finden kann.

Clawdidr
quelle

Antworten:

36

Die Definition für die Felder inund outin der SystemKlasse lautet:

public final static PrintStream out;
public final static InputStream in;

Das sind Konstanten. Sie sind zufällig auch Objekte, aber sie sind Konstanten. Es ist sehr ähnlich wie in der Mathe-Klasse:

public static final double E = 2.7182818284590452354;
public static final double PI = 3.14159265358979323846;

Oder in der Booleschen Klasse:

public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);

Oder in der Farbklasse:

public final static Color white     = new Color(255, 255, 255);
public final static Color black     = new Color(0, 0, 0);
public final static Color red       = new Color(255, 0, 0);

Wenn auf eine öffentliche Konstante zugegriffen wird, die sich nicht ändert, hat die Verkapselung keinen wesentlichen Vorteil - konzeptionell oder leistungsbasiert. Es ist da. Es wird sich nicht ändern.

Es gibt keinen wirklichen Unterschied zwischen Color.whiteund System.out.


quelle
Nun, ich habe es nicht so gesehen, es sind konstante Objekte. Das ist eine ziemlich gute Erklärung.
Clawdidr
1
@Clawdidr im heutigen Java könnte man in Betracht ziehen, das enumzu verwenden, um sie zu halten ... obwohl enumes neu in Java 1.5 war (keine Option in den 1.0 Tagen).
Nur aus Neugier (selbst kein Java-Entwickler): Gibt es einen Unterschied zwischen final staticund static final? Wenn ja was ist das
Marjan Venema
1
@ MarjanVenema kein Unterschied, nur bevorzugte Reihenfolge der Modifikatoren. checkstyle modifier check zeigt die bevorzugte Reihenfolge an. Wer auch immer diese beiden Quelldateien in der jdk-Version geschrieben hat, ich habe mich anscheinend nicht auf die Reihenfolge geeinigt. Es ist nur eine Konvention.
:) und danke Michael, dass du dir die Zeit für die Antwort und den Link genommen hast.
Marjan Venema
5

Der wahre Grund dafür ist, dass dies ein altes Problem ist. Die System.in,out,errKonstanten waren Teil von Java 1.0 ... und wahrscheinlich viel weiter zurück. Als klar war, dass das Design Probleme hatte, war es zu spät, um es zu beheben. Das Beste, was sie tun konnten, war, die System.setIn,setOut,setErrMethoden in Java 1.1 hinzuzufügen und sich dann mit den Sprachspezifikationsproblemen 1 zu befassen .

Dies ähnelt der Frage, warum es eine statische System.arraycopyMethode gibt, deren Name die Java-Namenskonventionen verletzt.


Ob dies "schlechtes Design" ist oder nicht, glaube ich. Es gibt Situationen, in denen die derzeitige Nicht-OO-Behandlung ein ernstes Problem darstellt. (Überlegen Sie, wie Sie ein Java-Programm in einem anderen ausführen können, wenn die "Standard-E / A" -Stream-Anforderungen kollidieren. Überlegen Sie, ob ... Unit-Test-Code die Streams ändert.)

Ich kann mich jedoch auch auf das Argument beziehen, dass die derzeitige Vorgehensweise in vielen Fällen praktischer ist.


1 - Es ist interessant zu bemerken, dass die System.in,out,errVariablen in der JLS als "spezielle Semantik" besonders erwähnt werden. Das JLS sagt, dass, wenn Sie den Wert eines finalFeldes ändern , das Verhalten undefiniert ist ... außer im Fall dieser Felder.

Stephen C
quelle
2

Ich glaube, dass das Out-Objekt unveränderlich ist, was es irgendwie sicher macht (dies ist fraglich), in einem öffentlichen statischen Endfeld gehalten zu werden.

Viele der Klassen im JDK respektieren nicht die besten objektorientierten Gestaltungsprinzipien. Ein Grund dafür ist die Tatsache, dass sie vor fast 20 Jahren geschrieben wurden, als sich die Objektorientierung erst als Mainstream-Paradigma herauskristallisierte und viele Programmierer sie einfach nicht so kannten, wie sie jetzt sind. Ein sehr gutes Beispiel für ein schlechtes API-Design ist das Date & Time API, dessen Änderung 19 Jahre gedauert hat ...

m3th0dman
quelle
3
Ich würde die Aussage noch stärker machen, eine öffentliche Endvariable (statisch oder nicht) ist genau so "sicher" wie ein Getter und ich würde die Unveränderlichkeit einem Setter / Getter-Paar vorziehen. Setter sind fast immer eine schlechte Idee (Unveränderlich ist gut - und wenn es nicht unveränderlich sein kann, verdient die Methode "Sets" wahrscheinlich auch ein wenig Geschäftslogik). Wenn Sie einen Getter benötigen, können Sie ihn auch öffentlich machen final, aber beides ist vorzuziehen - frage keine Klasse nach ihren Daten, frage eine Klasse, etwas mit ihren Daten zu tun.
Bill K
@BillK: Ja, auch als Demeter-Gesetz bekannt (obwohl es kein Gesetz, sondern eine Richtlinie ist).
Sleske
2

Diese Antwort ist großartig und wahr.

Ich wollte hinzufügen, dass in einigen Fällen Kompromisse aus Gründen der Benutzerfreundlichkeit gemacht wurden.

Objekte vom Typ String können ohne neues Ereignis instanziiert werden, wenn String kein Grundelement ist:

String s = "Hello";

Ein String, der kein Primitiv ist, sollte wie folgt instanziiert werden:

String s = new String("Hello");          // this also works 

Der Compiler lässt jedoch die kürzere, weniger OO-Option zu, da String die bei weitem am häufigsten verwendete Klasse in der API ist.

Arrays können auch auf Nicht-OO-Weise initialisiert werden:

int i[] = {1,2,3};

Seltsamerweise ist ein Objekt entweder eine Instanz einer Klasse oder ein Array . Das heißt, Arrays sind ein völlig separater Klassentyp.

Arrays haben ein lengthöffentliches Feld, das keine Konstante ist. Außerdem gibt es keine Dokumentation zu den Klassen-Arrays, von denen eine Instanz ist. (Nicht zu verwechseln mit Arrays class oder java.reflect.Array).

int a = myArray.length;    // not length()
Tulains Córdova
quelle
6
Das sind verschiedene Dinge - erstellt new String("Hello")immer ein neues String-Objekt. While String s = "Hello";verwendet das internierte Objekt. Vergleichen Sie: "Hello" == "Hello"kann wahr sein, während new String("Hello") == new String("Hello")immer falsch ist. In der ersten Phase findet eine Magie zur Optimierung der Kompilierungszeit statt, die mit nicht möglich ist new String("Hello"). Siehe en.wikipedia.org/wiki/String_interning
@MichaelT Der Vollständigkeit halber habe ich den Arrays eine zusätzliche Verrücktheit hinzugefügt.
Tulains Córdova
2
@Tarik Ich habe Einwände gegen die "Zeichenfolge, die nicht primitiv ist, sollte folgendermaßen instanziiert werden: String s = new String("Hello");", die falsch ist. Die kürzere Option ( String s = "Hello";) ist mehr korrekt , weil die Zeichenfolge Internierung.
2
Mit Stringgibt es keine "korrektere" Option. Beide sind richtig. Obwohl, wie MichaelT sagt, die kürzere wegen String-Internierung bevorzugt wird.
Andres F.
1
@AndresF. Ich habe "weniger korrekt" für "weniger OO" geändert.
Tulains Córdova