In Java gibt es primitive Typen für byte
, short
, int
und long
und die gleiche Sache für float
und double
. Warum muss eine Person festlegen, wie viele Bytes für einen primitiven Wert verwendet werden sollen? Konnte die Größe nicht einfach dynamisch bestimmt werden, abhängig davon, wie groß die übergebene Zahl war?
Ich kann mir zwei Gründe vorstellen:
- Das dynamische Einstellen der Datengröße würde bedeuten, dass sich die Daten auch dynamisch ändern müssen. Dies kann möglicherweise zu Leistungsproblemen führen.
- Vielleicht möchte der Programmierer nicht, dass jemand eine größere Zahl als eine bestimmte Größe verwenden kann, und dies ermöglicht es ihm, diese zu begrenzen.
Ich denke immer noch, dass es eine Menge zu gewinnen gab, wenn man einfach eine Single int
und einen float
Typ verwendete. Gab es einen bestimmten Grund, warum Java sich entschied, diesen Weg nicht zu gehen?
java
language-design
data-types
numbers
yitzih
quelle
quelle
Antworten:
Wie so viele Aspekte des Sprachdesigns kommt es zu einem Kompromiss zwischen Eleganz und Leistung (ganz zu schweigen von einem historischen Einfluss früherer Sprachen).
Alternativen
Es ist sicherlich möglich (und recht einfach), eine Programmiersprache zu erstellen, die nur einen einzigen Typ natürlicher Zahlen enthält
nat
. Fast alle Programmiersprachen, die für das akademische Studium verwendet werden (z. B. PCF, System F), haben diesen Typ mit einer einzigen Zahl, der, wie Sie vermutet haben, die elegantere Lösung ist. In der Praxis geht es beim Sprachdesign jedoch nicht nur um Eleganz. Wir müssen auch die Leistung berücksichtigen (inwieweit die Leistung berücksichtigt wird, hängt von der beabsichtigten Anwendung der Sprache ab). Die Aufführung umfasst sowohl zeitliche als auch räumliche Einschränkungen.Raumbeschränkungen
Wenn Sie den Programmierer die Anzahl der Bytes im Voraus auswählen lassen, können Sie Speicherplatz in Programmen mit eingeschränktem Speicherplatz sparen. Wenn alle Ihre Zahlen unter 256 liegen, können Sie 8-mal so viele
byte
s wielong
s verwenden oder den gespeicherten Speicher für komplexere Objekte verwenden. Der Java-Standardanwendungsentwickler muss sich nicht um diese Einschränkungen kümmern, sie treten jedoch auf.Effizienz
Auch wenn wir den Speicherplatz ignorieren, werden wir immer noch von der CPU eingeschränkt, die nur Befehle enthält, die mit einer festen Anzahl von Bytes arbeiten (8 Bytes bei einer 64-Bit-Architektur). Dies bedeutet, dass selbst die Bereitstellung eines einzelnen 8-Byte-
long
Typs die Implementierung der Sprache erheblich einfacher machen würde als die Verwendung eines unbegrenzten natürlichen Zahlentyps, da arithmetische Operationen direkt auf eine einzelne zugrunde liegende CPU-Anweisung abgebildet werden können. Wenn Sie dem Programmierer erlauben, beliebig große Zahlen zu verwenden, muss eine einzelne Rechenoperation einer Folge komplexer Maschinenbefehle zugeordnet werden, die das Programm verlangsamen würden. Dies ist Punkt (1), den Sie angesprochen haben.Gleitkommatypen
Die bisherige Diskussion betraf nur ganze Zahlen. Gleitkommatypen sind ein komplexes Biest mit äußerst subtiler Semantik und Kantenfällen. So, obwohl wir leicht ersetzen könnten
int
,long
,short
undbyte
mit einer einzigennat
Art ist es nicht klar , was die Art des Gleitkommazahlen selbst ist . Offensichtlich sind es keine reellen Zahlen, da in einer Programmiersprache keine reellen Zahlen existieren können. Sie sind auch keine ganz rationalen Zahlen (obwohl es einfach ist, einen rationalen Typ zu erstellen, falls gewünscht). Grundsätzlich entschied sich IEEE für eine Art Annäherung an reelle Zahlen, und alle Sprachen (und Programmierer) sind seitdem mit ihnen festgefahren.Endlich:
Dies ist kein triftiger Grund. Erstens kann ich mir keine Situationen vorstellen, in denen Typen auf natürliche Weise numerische Grenzen kodieren könnten, ganz zu schweigen von den astronomisch geringen Chancen, dass die Grenzen, die der Programmierer erzwingen möchte, genau der Größe eines der primitiven Typen entsprechen.
quelle
type my_type = int (7, 2343)
.Der Grund ist sehr einfach: Effizienz . In mehrfacher Hinsicht.
Native Datentypen: Je genauer die Datentypen einer Sprache mit den zugrunde liegenden Datentypen der Hardware übereinstimmen, desto effizienter wird die Sprache. (Nicht in dem Sinne, dass Ihre Programme unbedingt effizient sein müssen, sondern in dem Sinne, dass Sie, wenn Sie wirklich wissen, was Sie tun, Code schreiben können, der so effizient ausgeführt wird, wie es die Hardware kann.) Die angebotenen Datentypen von Java entsprechen Bytes, Wörtern, Doppelwörtern und Vierfachwörtern der beliebtesten Hardware da draußen. Das ist der effizienteste Weg.
Unberechtigter Overhead auf 32-Bit-Systemen: Wenn entschieden worden wäre, alles auf eine 64-Bit-Länge mit fester Größe abzubilden, hätte dies einen enormen Nachteil für 32-Bit-Architekturen zur Folge, die erheblich mehr Taktzyklen benötigen, um ein 64-Bit -System auszuführen. Bit-Operation als eine 32-Bit-Operation.
Verschwendung von Arbeitsspeicher: Es gibt eine Menge Hardware, die in Bezug auf die Ausrichtung des Arbeitsspeichers nicht allzu wählerisch ist (die Intel x86- und x64-Architekturen sind Beispiele dafür), sodass ein Array von 100 Byte auf dieser Hardware nur 100 Byte Arbeitsspeicher belegen kann. Wenn Sie jedoch kein Byte mehr haben und stattdessen ein langes verwenden müssen, belegt dasselbe Array eine Größenordnung mehr Speicher. Und Byte-Arrays sind sehr verbreitet.
Berechnen von Zahlengrößen: Ihre Vorstellung, die Größe einer Ganzzahl dynamisch zu bestimmen, je nachdem, wie groß die übergebene Zahl war, ist zu simpel. Es gibt keinen einzigen Punkt, an dem eine Zahl "übergeben" werden könnte. Die Berechnung, wie groß eine Zahl sein muss, muss zur Laufzeit für jede einzelne Operation durchgeführt werden, die möglicherweise ein Ergebnis einer größeren Größe erfordert: Jedes Mal, wenn Sie eine Zahl inkrementieren, jedes Mal, wenn Sie zwei Zahlen hinzufügen, jedes Mal, wenn Sie zwei multiplizieren Zahlen usw.
Operationen mit Zahlen unterschiedlicher Größe: In der Folge würde es alle Operationen erschweren, Zahlen potenziell unterschiedlicher Größe im Speicher zu haben: Auch um zwei Zahlen einfach zu vergleichen, müsste die Laufzeit zunächst prüfen, ob beide zu vergleichenden Zahlen gleich sind Größe, und wenn nicht, passen Sie die Größe der kleineren an die Größe der größeren an.
Operationen, die bestimmte Operandengrößen erfordern: Für bestimmte bitweise Operationen muss die Ganzzahl eine bestimmte Größe haben. Diese Operationen müssten emuliert werden, wenn sie keine vorgegebene spezifische Größe hätten.
Overhead des Polymorphismus: Das Ändern der Größe einer Zahl zur Laufzeit bedeutet im Wesentlichen, dass sie polymorph sein muss. Dies bedeutet wiederum, dass es sich nicht um ein Grundelement mit fester Größe handeln kann, das dem Stapel zugewiesen wurde, sondern um ein Objekt, das dem Heap zugewiesen wurde. Das ist schrecklich ineffizient. (Lesen Sie noch einmal # 1 oben.)
quelle
Um zu vermeiden, dass die Punkte, die in anderen Antworten besprochen wurden, wiederholt werden, werde ich stattdessen versuchen, mehrere Perspektiven zu skizzieren.
Aus Sicht der Sprachgestaltung
Historische Gründe
Dies wird bereits in dem Wikipedia-Artikel über die Geschichte von Java und in der Antwort von Marco13 kurz besprochen .
Ich möchte darauf hinweisen, dass:
Effizienzgründe
Wann ist Effizienz wichtig?
Speichereffizienz (im Speicher oder auf der Festplatte)
Effizienz der Ausführung (innerhalb der CPU oder zwischen CPU und Speicher)
Die Notwendigkeit, dass Programmiersprachen eine Abstraktion für kleine ganze Zahlen bieten, auch wenn sie auf bestimmte Kontexte beschränkt sind
Interoperabilität
char
Array der Größe 256 akzeptiert. (Beispiel.)BitConverter
), um das Packen und Entpacken von schmalen Ganzzahlen in Bit- und Byte-Streams zu erleichtern.String-Behandlung
Dateiformatbehandlung
Wünschbarkeit, Softwarequalität und Verantwortung des Programmierers
Stellen Sie sich das folgende Szenario vor.
Häufig muss für diesen Zweck Software entwickelt werden, mit der sich viele Größenordnungen sicher skalieren lassen, wobei die Komplexität zunimmt. Es kommt nicht automatisch, auch wenn das Problem des Überlaufs von Ganzzahlen beseitigt ist. Dies schließt sich zu einem Kreis, der die Perspektive des Sprachdesigns beantwortet: Oft ist Software, die sich weigert, eine Arbeit auszuführen, wenn ein unbeabsichtigter ganzzahliger Überlauf auftritt (durch Auslösen eines Fehlers oder einer Ausnahme), besser als Software, die automatisch astronomisch große Operationen ausführt.
Dies bedeutet die Perspektive des OP,
das ist nicht richtig. Es sollte dem Programmierer gestattet sein und manchmal erforderlich sein, die maximale Größe anzugeben, die ein ganzzahliger Wert in kritischen Teilen der Software annehmen kann. Wie die Antwort von gardenhead zeigt, sind die natürlichen Grenzen primitiver Typen für diesen Zweck nicht nützlich. Die Sprache muss Programmierern die Möglichkeit geben, Größenangaben zu machen und solche Grenzwerte durchzusetzen.
quelle
Es kommt alles von Hardware.
Ein Byte ist die kleinste adressierbare Speichereinheit auf den meisten Hardwarekomponenten.
Jeder Typ, den Sie gerade erwähnt haben, besteht aus mehreren Bytes.
Ein Byte besteht aus 8 Bits. Damit können Sie 8 Boolesche Werte ausdrücken, aber Sie können nicht immer nur einen nachschlagen. Sie adressieren 1, Sie adressieren alle 8.
Früher war es so einfach, aber dann sind wir von einem 8-Bit-Bus zu einem 16-, 32- und jetzt 64-Bit-Bus übergegangen.
Das heißt, solange wir noch auf Byte-Ebene adressieren können, können wir kein einzelnes Byte mehr aus dem Speicher abrufen, ohne die benachbarten Bytes abzurufen.
Angesichts dieser Hardware haben sich die Sprachentwickler dafür entschieden, Typen auszuwählen, die es uns ermöglichen, Typen auszuwählen, die zur Hardware passen.
Sie können behaupten, dass ein solches Detail entfernt werden kann und sollte, insbesondere in einer Sprache, die auf jeder Hardware ausgeführt werden soll. Dies hätte versteckte Leistungsprobleme, aber Sie könnten Recht haben. Das ist einfach nicht so passiert.
Java versucht dies tatsächlich. Bytes werden automatisch zu Ints hochgestuft. Eine Tatsache, die Sie verrückt macht, wenn Sie zum ersten Mal versuchen, ernsthafte Änderungen daran vorzunehmen.
Warum hat es nicht gut funktioniert?
Das große Verkaufsargument von Java war damals, dass man sich mit einem bekannten guten C-Algorithmus hinsetzen, es in Java tippen und mit kleinen Optimierungen würde es funktionieren. Und C ist sehr nah an der Hardware.
Das beizubehalten und die Größe von integralen Typen zu abstrahieren, funktionierte einfach nicht zusammen.
Also könnten sie haben. Sie haben es einfach nicht getan.
Das ist gültiges Denken. Dafür gibt es Methoden. Die Klemmfunktion für einen. Eine Sprache könnte so weit gehen, beliebige Grenzen in ihre Typen zu setzen. Und wenn diese Grenzen zur Kompilierungszeit bekannt sind, können Sie die Speicherung dieser Zahlen optimieren.
Java ist einfach nicht diese Sprache.
quelle
Wahrscheinlich ist ein wichtiger Grund, warum diese Typen in Java existieren, einfach und bedenklich nicht technisch:
C und C ++ hatten auch diese Typen!
Obwohl es schwierig ist, einen Beweis dafür zu liefern, gibt es zumindest einige starke Beweise: Die Oak Language Specification (Version 0.2) enthält die folgende Passage:
Die Frage könnte also lauten:
Warum wurden Short, Int und Long in C erfunden?
Ich bin mir nicht sicher, ob die Antwort auf die Brieffrage im Kontext der hier gestellten Frage zufriedenstellend ist. In Kombination mit den anderen Antworten wird jedoch möglicherweise klar, dass es von Vorteil sein kann, diese Typen zu haben (unabhängig davon, ob ihre Existenz in Java nur ein Erbe von C / C ++ ist).
Die wichtigsten Gründe, die mir einfallen, sind:
Ein Byte ist die kleinste adressierbare Speichereinheit (wie CandiedOrange bereits erwähnt). A
byte
ist der elementare Datenbaustein, der aus einer Datei oder über das Netzwerk gelesen werden kann. Einige explizite Darstellungen sollten vorhanden sein (und es gibt sie in den meisten Sprachen, auch wenn sie manchmal in Verkleidung erscheinen).In der Praxis ist es zwar sinnvoll, alle Felder und lokalen Variablen mit einem einzigen Typ darzustellen und diesen Typ aufzurufen
int
. Zu Stackoverflow gibt es eine verwandte Frage: Warum verwendet die Java-API int anstelle von short oder byte? . Wie ich dort in meiner Antwort erwähnt habe, ist eine Rechtfertigung für die Verwendung der kleineren Typen (byte
undshort
), dass Sie Arrays dieser Typen erstellen können: Java hat eine Darstellung von Arrays, die immer noch ziemlich "hardwarenah" ist. Im Gegensatz zu anderen Sprachen (und im Gegensatz zu Arrays von Objekten wie einemInteger[n]
Array) ist einint[n]
Array keine Sammlung von Referenzen, bei denen die Werte über den gesamten Heap verteilt sind. Stattdessen es wird in der Praxis ein fortlaufender Block vonn*4
Bytes - Ein Speicherblock mit bekannter Größe und Datenlayout. Wenn Sie die Wahl haben, 1000 Bytes in einer Sammlung von Objekten mit beliebig großen Ganzzahlen zu speichern, oder in einem Objektbyte[1000]
(das 1000 Bytes benötigt), kann letzteres tatsächlich Speicherplatz sparen. (Einige andere Vorteile sind möglicherweise subtiler und werden nur dann offensichtlich, wenn Java mit nativen Bibliotheken verbunden wird.)In Bezug auf die Punkte, die Sie speziell gefragt haben:
Es wäre wahrscheinlich möglich, die Größe von Variablen dynamisch festzulegen, wenn man in Betracht ziehen würde, eine völlig neue Programmiersprache von Grund auf neu zu entwerfen. Ich bin kein Experte für Compilerkonstruktionen, denke aber, dass es schwierig ist, Sammlungen mit sich dynamisch ändernden Typen sinnvoll zu verwalten - insbesondere, wenn Sie eine stark typisierte Sprache haben. Es käme also wahrscheinlich darauf an, dass alle Zahlen in einem "generischen Datentyp mit willkürlicher Genauigkeit" gespeichert würden, was sich sicherlich auf die Leistung auswirken würde. Natürlich gibt es Programmiersprachen, die stark typisiert sind und / oder Nummerntypen mit willkürlicher Größe anbieten, aber ich glaube nicht, dass es eine echte Allzweck-Programmiersprache gibt, die diesen Weg gegangen ist.
Randnotizen:
Sie haben sich vielleicht über den
unsigned
Modifikator gewundert, der in der Oak-Spezifikation erwähnt wurde. In der Tat enthält es auch eine Bemerkung: "unsigned
ist noch nicht implementiert; es könnte niemals sein." . Und sie hatten recht.Sie wundern sich nicht nur, warum C / C ++ überhaupt diese verschiedenen Integer-Typen hat, sondern Sie wundern sich auch, warum sie sie so schrecklich durcheinandergebracht haben, dass Sie nie wissen, wie viele Bits eine
int
hat. Die Begründungen hierfür beziehen sich normalerweise auf die Leistung und können an anderer Stelle nachgeschlagen werden.quelle
Es zeigt sicherlich, dass Sie noch nicht über Leistung und Architekturen unterrichtet wurden.
Wenn Sie die Bedeutung der Datengröße ignorieren, wirkt sich dies immer auf die Leistung aus. Sie müssen so viele Ressourcen wie nötig verwenden, aber nicht mehr, immer!
Das ist der Unterschied zwischen einem Programm oder System, das wirklich einfache Dinge tut und unglaublich ineffizient ist, viele Ressourcen erfordert und die Verwendung dieses Systems wirklich kostspielig macht. oder ein System, das viel leistet, aber schneller läuft als andere und das sehr billig zu betreiben ist.
quelle
Dafür gibt es ein paar gute Gründe
(1) Während die Speicherung von einer Byte-Variablen gegenüber einer Länge unbedeutend ist, ist die Speicherung von Millionen in einem Array sehr wichtig.
(2) "Hardware native" Arithmetik auf der Grundlage bestimmter ganzzahliger Größen kann sehr viel effizienter sein, und für einige Algorithmen auf einigen Plattformen kann dies wichtig sein.
quelle