JavaRush /Blog Java /Random-FR /Types primitifs en Java : ils ne sont pas si primitifs
Viacheslav
Niveau 3

Types primitifs en Java : ils ne sont pas si primitifs

Publié dans le groupe Random-FR

Introduction

Le développement d’applications peut être considéré comme le travail avec certaines données, ou plutôt comme leur stockage et leur traitement. Aujourd’hui, je voudrais aborder le premier aspect clé. Comment les données sont-elles stockées en Java ? Ici, nous avons deux formats possibles : les types de données référence et primitifs . Parlons des types de types primitifs et des possibilités de travailler avec eux (quoi qu'on en dise, c'est le fondement de notre connaissance d'un langage de programmation). Les types de données primitifs Java constituent la base sur laquelle tout repose. Non, je n'exagère pas du tout. Oracle propose un didacticiel distinct dédié aux primitives : Types de données primitifs Types primitifs en Java : ils ne sont pas si primitifs - 1 Un peu d'histoire. Au début, il y en avait zéro. Mais zéro, c'est ennuyeux. Et puis un peu est apparu . Pourquoi s'appelait-il ainsi ? Il a été nommé ainsi à partir de l'abréviation « chiffre binaire » (nombre binaire). Autrement dit, cela n’a que deux significations. Et comme c'était zéro, il est logique que ce soit maintenant soit 0, soit 1. Et la vie est devenue plus amusante. Les morceaux ont commencé à se rassembler en troupeaux. Et ces troupeaux ont commencé à être appelés octet (octet). Dans le monde moderne, octet = 2 à la puissance trois, c'est-à-dire 8. Mais il s’avère que cela n’a pas toujours été le cas. Il existe de nombreuses suppositions, légendes et rumeurs sur l'origine du nom octet. Certains pensent que tout dépend des encodages de l’époque, tandis que d’autres pensent qu’il était plus rentable de lire les informations de cette façon. Un octet est le plus petit morceau de mémoire adressable. Ce sont les octets qui ont des adresses uniques en mémoire. Il existe une légende selon laquelle ByTe est une abréviation de Binary Term - un mot machine. Mot machine - en termes simples, il s'agit de la quantité de données que le processeur peut traiter en une seule opération. Auparavant, la taille du mot machine était la même que celle de la plus petite mémoire adressable. En Java, les variables ne peuvent stocker que des valeurs en octets. Comme je l'ai dit plus haut, il existe deux types de variables en Java :
  • les types primitifs Java stockent directement la valeur des octets de données (nous examinerons les types de ces primitives plus en détail ci-dessous) ;
  • un type référence, stocke les octets de l'adresse de l'objet dans Heap, c'est-à-dire que grâce à ces variables, nous accédons directement à l'objet lui-même (une sorte de contrôle à distance pour l'objet)

Octet Java

Ainsi, l'histoire nous a donné un octet - la quantité minimale de mémoire que nous pouvons utiliser. Et il se compose de 8 bits. Le plus petit type de données entier en Java est l'octet. Il s'agit d'un type signé 8 bits. Qu'est-ce que ça veut dire? Comptons. 2 ^ 8 vaut 256. Mais que se passe-t-il si nous voulons un nombre négatif ? Et les développeurs Java ont décidé que le code binaire "10000000" représenterait -128, c'est-à-dire que le bit le plus significatif (le bit le plus à gauche) indiquerait si le nombre est négatif. Le binaire « 0111 1111 » est égal à 127. Autrement dit, 128 ne peut en aucun cas être désigné, car ce sera -128. Le calcul complet est donné dans cette réponse : Pourquoi la plage d'octets est-elle comprise entre -128 et 127 en Java ? Pour comprendre comment les nombres sont obtenus, vous devez regarder l'image :
Types primitifs en Java : ils ne sont pas si primitifs - 2
Par conséquent, pour calculer la taille 2^(8-1) = 128. Cela signifie que la limite minimale (et elle a un moins) sera de -128. Et le maximum est 128 – 1 (soustraire zéro). Autrement dit, le maximum sera de 127. En fait, nous ne travaillons pas si souvent avec le type d'octet à un « niveau élevé ». Il s’agit essentiellement du traitement de données « brutes ». Par exemple, lorsque vous travaillez avec la transmission de données sur un réseau, lorsque les données sont un ensemble de 0 et de 1 transmis via un canal de communication. Ou lors de la lecture de données à partir de fichiers. Ils peuvent également être utilisés lorsque vous travaillez avec des chaînes et des encodages. Exemple de code :
public static void main(String []args){
        byte value = 2;
        byte shortByteValue = 0b10; // 2
        System.out.println(shortByteValue);
        // Начиная с JDK7 мы можем разделять литералы подчёркиваниями
        byte minByteValue = (byte) 0B1000_0000; // -128
        byte maxByteValue = (byte) 0b0111_1111; // 127
        byte minusByteValue = (byte) 0b1111_1111; // -128 + 127
        System.out.println(minusByteValue);
        System.out.println(minByteValue + " to " + maxByteValue);
}
Soit dit en passant, ne pensez pas que l’utilisation du type byte réduira la consommation de mémoire. L'octet est principalement utilisé pour réduire la consommation de mémoire lors du stockage de données dans des tableaux (par exemple, stocker les données reçues sur le réseau dans un tampon, qui sera implémenté sous forme de tableau d'octets). Mais lors de l'exécution d'opérations sur des données, l'utilisation d'octets ne répondra pas à vos attentes. Cela est dû à l'implémentation de la machine virtuelle Java (JVM). Étant donné que la plupart des systèmes sont en 32 ou 64 bits, les octets et les courts-circuits lors des calculs seront convertis en un entier de 32 bits, dont nous parlerons plus tard. Cela facilite les calculs. Pour plus de détails, voir L'ajout d'octets est-il converti en int à cause des règles du langage Java ou à cause de JVM ? . La réponse contient également des liens vers JLS (Java Language Spécification). De plus, utiliser un octet au mauvais endroit peut entraîner des moments gênants :
public static void main(String []args){
        for (byte i = 1; i <= 200; i++) {
            System.out.println(i);
        }
}
Il y aura une boucle ici. La valeur du compteur atteignant le maximum (127), un débordement se produira et la valeur deviendra -128. Et nous ne sortirons jamais du cycle.

court

La limite pour les valeurs d'octets est assez petite. Par conséquent, pour le type de données suivant, nous avons décidé de doubler le nombre de bits. Autrement dit, ce n'est plus 8 bits, mais 16. C'est-à-dire 2 octets. Les valeurs peuvent être calculées de la même manière. 2^(16-1) = 2^15 = 32768. Cela signifie que la plage va de -32768 à 32767. Elle est très rarement utilisée pour des cas particuliers. Comme nous le dit la documentation du langage Java : « vous pouvez utiliser un short pour économiser de la mémoire dans de grands tableaux ».

int

Nous sommes donc arrivés au type le plus fréquemment utilisé. Cela prend 32 bits, soit 4 octets. En général, nous continuons à doubler. La plage de valeurs va de -2^31 à 2^31 – 1.

Valeur entière maximale

La valeur maximale de int 2147483648 est 1, ce qui n'est pas du tout petit. Comme indiqué ci-dessus, pour optimiser les calculs, car Il est plus pratique pour les ordinateurs modernes, compte tenu de leur capacité en bits, de compter ; les données peuvent être implicitement converties en int. Voici un exemple simple :
byte a = 1;
byte b = 2;
byte result = a + b;
Un tel code inoffensif, mais nous obtenons l'erreur : « erreur : types incompatibles : conversion possible avec perte d'int en octet ». Vous devrez le corriger en byte result = (byte)(a + b); Et encore un exemple inoffensif. Que se passe-t-il si nous exécutons le code suivant ?
int value = 4;
System.out.println(8/value);
System.out.println(9/value);
System.out.println(10/value);
System.out.println(11/value);
Et nous obtiendrons la conclusion
2
2
2
2
*bruits de panique*
Le fait est que lorsque vous travaillez avec des valeurs int, le reste est supprimé, ne laissant que la partie entière (dans de tels cas, il est préférable d'utiliser double).

long

Nous continuons à doubler. On multiplie 32 par 2 et on obtient 64 bits. Par tradition, il s'agit de 4 * 2, soit 8 octets. La plage de valeurs va de -2^63 à 2^63 – 1. Plus que suffisant. Ce type vous permet de compter de très grands nombres. Souvent utilisé lorsque l’on travaille avec le temps. Ou sur de longues distances, par exemple. Pour indiquer qu'un nombre est long, placez le littéral L – Long après le nombre. Exemple:
long longValue = 4;
longValue = 1l; // Не ошибка, но плохо читается
longValue = 2L; // Идеально
J'aimerais prendre de l'avance. Ensuite, nous considérerons le fait qu'il existe des wrappers correspondants pour les primitives, qui permettent de travailler avec des primitives en tant qu'objets. Mais il y a une fonctionnalité intéressante. Voici un exemple : En utilisant le même compilateur en ligne Tutorialspoint, vous pouvez vérifier le code suivant :
public class HelloWorld {

     public static void main(String []args) {
        printLong(4);
     }

    public static void printLong(long longValue) {
        System.out.println(longValue);
    }
}
Ce code fonctionne sans erreur, tout va bien. Mais dès que le type dans la méthode printLong est remplacé de long par Long (c'est-à-dire que le type ne devient pas primitif, mais objet), Java ne sait plus quel paramètre nous transmettons. Il commence à supposer qu'un int est en cours de transmission et qu'il y aura une erreur. Ainsi, dans le cas d’une méthode, il faudra indiquer explicitement 4L. Très souvent, le code long est utilisé comme identifiant lorsque l'on travaille avec des bases de données.

Java flottant et Java double

Ces types sont appelés types à virgule flottante. Autrement dit, ce ne sont pas des types entiers. Le type float est de 32 bits (comme int), et double est appelé type double précision, il est donc de 64 bits (multiplié par 2, comme nous aimons). Exemple:
public static void main(String []args){
        // float floatValue = 2.3; lossy conversion from double to float
        float floatValue = 2.3F;
        floatValue = 2.3f;
        double doubleValue = 2.3;
        System.out.println(floatValue);
        double cinema = 7D;
}
Et voici un exemple de la différence de valeurs (due à la précision du type) :
public static void main(String []args){
        float piValue = (float)Math.PI;
        double piValueExt = Math.PI;
        System.out.println("Float value: " + piValue );
        System.out.println("Double value: " + piValueExt );
 }
Ces types primitifs sont utilisés en mathématiques par exemple. En voici la preuve, une constante pour calculer le nombre PI . Eh bien, en général, vous pouvez consulter l'API de la classe Math. Voici ce qui devrait être important et intéressant : même la documentation dit : « Ce type de données ne doit jamais être utilisé pour des valeurs précises, telles que la devise. Pour cela, vous devrez plutôt utiliser la classe java.math.BigDecimal. Numbers and Strings couvre BigDecimal et d'autres classes utiles fournies par la plate-forme Java. " C'est-à-dire que l'argent flottant et double n'a pas besoin d'être calculé. Un exemple sur la précision en utilisant l'exemple de travail à la NASA : Java BigDecimal, Gérer des calculs de haute précision Eh bien, pour le ressentir par vous-même :
public static void main(String []args){
        float amount = 1.0000005F;
        float avalue = 0.0000004F;
        float result = amount - avalue;
        System.out.println(result);
}
Suivez cet exemple, puis ajoutez 0 avant les chiffres 5 et 4. Et vous verrez toute l'horreur) Il existe un rapport intéressant en russe sur float et double sur le sujet : https://youtu.be/1RCn5ruN1fk Exemples de travail avec BigDecimal peut être vu ici : Gagner des centimes avec BigDecimal À propos, float et double peuvent renvoyer plus qu'un simple nombre. Par exemple, l'exemple ci-dessous renverra Infinity :
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        System.out.println(positive_infinity);
}
Et celui-ci renverra NAN :
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        double negative_infinity = -15.0 / 0;
        System.out.println(positive_infinity + negative_infinity);
}
C’est clair pour l’infini. Qu’est-ce que NaN ? Ce n'est pas un nombre , ce qui signifie que le résultat ne peut pas être calculé et n'est pas un nombre. Voici un exemple : Nous voulons calculer la racine carrée de -4. La racine carrée de 4 est 2. Autrement dit, 2 doit être mis au carré et nous obtenons alors 4. Que faut-il mettre au carré pour obtenir -4 ? Ça ne marchera pas, parce que... s'il y a un nombre positif, alors il restera. Et si c'était négatif, alors moins par moins donnera un plus. Autrement dit, ce n'est pas calculable.
public static void main(String []args){
        double sqrt = Math.sqrt(-4);
        System.out.println(sqrt + 1);
        if (Double.isNaN(sqrt)) {
           System.out.println("So sad");
        }
        System.out.println(Double.NaN == sqrt);
}
Voici un autre excellent aperçu sur le sujet des nombres à virgule flottante : où en êtes-vous ?
Que lire d'autre :

Java booléen

Le type suivant est booléen (type logique). Il ne peut accepter que les valeurs vraies ou fausses, qui sont des mots-clés. Utilisé dans les opérations logiques telles que les boucles while et dans les branchements à l'aide de if, switch. Quelles choses intéressantes pouvez-vous découvrir ici ? Eh bien, par exemple, en théorie, nous n'avons besoin que d'un seul bit d'information, 0 ou 1, c'est-à-dire vrai ou faux. Mais en réalité, Boolean prendra plus de mémoire et cela dépendra de l’implémentation spécifique de la JVM. Généralement, cela coûte le même prix que int. Une autre option consiste à utiliser BitSet. Voici une brève description tirée du livre Java Fundamentals : BitSet

Caractère Java

Nous avons maintenant atteint le dernier type primitif. Ainsi, les données en char occupent 16 bits et décrivent le caractère. Java utilise le codage Unicode pour les caractères. Le symbole peut être défini selon deux tableaux (vous pouvez le voir ici ) :
  • Table de caractères Unicode
  • Table de caractères ASCII
Types primitifs en Java : ils ne sont pas si primitifs - 3
Exemple en studio :
public static void main(String[] args) {
    char symbol = '\u0066'; // Unicode
    symbol = 102; // ASCII
    System.out.println(symbol);
}
À propos, char, étant essentiellement un nombre, prend en charge les opérations mathématiques telles que la somme. Et parfois cela peut entraîner de drôles de conséquences :
public class HelloWorld{

    public static void main(String []args){
        String costForPrint = "5$";
        System.out.println("Цена только для вас " +
        + costForPrint.charAt(0) + getCurrencyName(costForPrint.charAt(1)));
    }

    public static String getCurrencyName(char symbol) {
        if (symbol == '$') {
            return " долларов";
        } else {
            throw new UnsupportedOperationException("Not implemented yet");
        }
    }

}
Je recommande fortement de consulter l' IDE en ligne à partir de tutorielspoint . Quand j’ai vu ce puzzle lors d’une des conférences, cela m’a remonté le moral. J'espère que vous aimez aussi l'exemple) MISE À JOUR : C'était au Joker 2017, rapport : " Java Puzzlers NG S03 - D'où venez-vous tous ?! "

Littéraux

Un littéral est une valeur explicitement spécifiée. À l'aide de littéraux, vous pouvez spécifier des valeurs dans différents systèmes numériques :
  • Système décimal : 10
  • Système hexadécimal : 0x1F4, commence par 0x
  • Système octal : 010, commence à zéro.
  • Système binaire (depuis Java7) : 0b101, commence à 0b
Je m'attarderais un peu plus sur le système octal, parce que c'est drôle :
int costInDollars = 08;
Cette ligne de code ne compilera pas :
error: integer number too large: 08
Cela semble absurde. Rappelons maintenant les systèmes binaire et octal. Il n'y en a pas deux dans le système binaire, car il y a deux valeurs (à partir de 0). Et le système octal a 8 valeurs, en partant de zéro. Autrement dit, la valeur 8 elle-même n'existe pas. Il s’agit donc d’une erreur qui, à première vue, semble absurde. Et pour rappel, voici la règle de « suivi » pour traduire les valeurs :
Types primitifs en Java : ils ne sont pas si primitifs - 4

Cours d'emballage

Les primitives en Java ont leurs propres classes wrapper afin que vous puissiez les utiliser en tant qu'objets. Autrement dit, à chaque type primitif correspond un type de référence. Types primitifs en Java : ils ne sont pas si primitifs - 5Les classes Wrapper sont immuables : cela signifie qu'une fois qu'un objet est créé, son état (la valeur du champ de valeur) ne peut pas être modifié. Les classes Wrapper sont déclarées comme finales : des objets, pour ainsi dire, en lecture seule. Je voudrais également mentionner qu'il n'est pas possible d'hériter de ces classes. Java effectue automatiquement des conversions entre les types primitifs et leurs wrappers :
Integer x = 9;          // autoboxing
int n = new Integer(3); // unboxing
Le processus de conversion des types primitifs en types référence (int->Integer) est appelé autoboxing , et l'inverse est appelé unboxing . Ces classes permettent de sauvegarder une primitive à l'intérieur d'un objet, et l'objet lui-même se comportera comme un Objet (enfin, comme n'importe quel autre objet). Avec tout cela, nous obtenons un grand nombre de méthodes statiques variées et utiles, comme comparer des nombres, convertir un symbole en casse, déterminer si un symbole est une lettre ou un chiffre, rechercher le nombre minimum, etc. L'ensemble des fonctionnalités fournies dépend uniquement du wrapper lui-même. Un exemple de votre propre implémentation d'un wrapper pour int :
public class CustomerInt {

   private final int value;

   public CustomerInt(int value) {
       this.value = value;
   }

   public int getValue() {
       return value;
   }
}
Le package principal, java.lang, a déjà des implémentations des classes Boolean, Byte, Short, Character, Integer, Float, Long, Double, et nous n'avons pas besoin de créer quoi que ce soit de nous-mêmes, mais simplement de réutiliser le tout fait. ceux. Par exemple, de telles classes nous donnent la possibilité de créer, disons, une liste , car une liste ne doit contenir que des objets, ce que ne sont pas les primitives. Pour convertir une valeur d'un type primitif, il existe des méthodes statiques valueOf, par exemple, Integer.valueOf(4) renverra un objet de type Integer. Pour la conversion inverse, il existe des méthodes intValue(), longValue(), etc. Le compilateur insère lui-même des appels à valueOf et *Value, c'est l'essence de l'autoboxing et de l'autounboxing. À quoi ressemble réellement l’exemple d’autopacking et d’autounpacking présenté ci-dessus :
Integer x = Integer.valueOf(9);
int n = new Integer(3).intValue();
Vous pouvez en savoir plus sur l'emballage automatique et le déballage automatique dans cet article .

Casting

При работе с примитивами существует такое понятие How приведение типов, одно из не очень приятных свойств C++, тем не менее приведение типов сохранено и в языке Java. Иногда мы сталкиваемся с такими ситуациями, когда нам нужно совершать взаимодействия с данными разных типов. И очень хорошо, что в некоторых ситуациях это возможно. В случае с ссылочными переменными, там свои особенности, связанные с полиморфизмом и наследованием, но сегодня мы рассматриваем простые типы и соответственно приведение простых типов. Существует преобразование с расширением и преобразование сужающее. Всё на самом деле просто. Если тип данных становится больше (допустим, был int, а стал long), то тип становится шире (из 32 бит становится 64). И в этом случае мы не рискуем потерять данные, т.к. если влезло в int, то в long влезет тем более, поэтому данное приведение мы не замечаем, так How оно осуществляется автоматически. А вот в обратную сторону преобразование требует явного указания от нас, данное приведение типа называется — сужение. Так сказать, чтобы мы сами сказали: «Да, я даю себе отчёт в этом. В случае чего — виноват сам».
public static void main(String []args){
   int intValue = 128;
   byte value = (byte)intValue;
   System.out.println(value);
}
Whatбы потом в таком случае не говорor что «Ваша Джава плохая», когда получат внезапно -128 instead of 128 ) Мы ведь помним, что в byteе 127 верхнее meaning и всё что находилось выше него соответственно можно потерять. Когда мы явно превратor наш int в byte, то произошло переполнение и meaning стало -128.

Область видимости

Это то место в codeе, где данная переменная будет выполнять свои функции и хранить в себе Howое-то meaning. Когда же эта область закончится, переменная перестанет существовать и будет стерта из памяти и. How уже можно догадаться, посмотреть or получить ее meaning будет невозможно! Так что же это такое — область видимости? Types primitifs en Java : ils ne sont pas si primitifs - 6Область определяется "блоком" — вообще всякой областью, замкнутой в фигурные скобки, выход за которые сулит удаление данных объявленных в ней. Или How минимум — сокрытие их от других блоков, открытых вне текущего. В Java область видимости определяется двумя основными способами:
  • Классом.
  • Методом.
Как я и сказал, переменная не видна codeу, если она определена за пределами блока, в котором она была инициализирована. Смотрим пример:
int x;
x = 6;
if (x >= 4) {
   int y = 3;
}
x = y;// переменная y здесь не видна!
И How итог мы получим ошибку:

Error:(10, 21) java: cannot find symbol
  symbol:   variable y
  location: class com.javaRush.test.type.Main
Области видимости могут быть вложенными (если мы объявor переменную в первом, внешнем блоке, то во внутреннем она будет видна).

Заключение

Сегодня мы познакомorсь с восемью примитивными типами в Java. Эти типы можно разделить на четыре группы:
  • Целые числа: byte, short, int, long — представляют собой целые числа со знаком.
  • Числа с плавающей точкой — эта группа включает себе float и double — типы, которые хранят числа с точностью до определённого знака после запятой.
  • Булевы значения — boolean — хранят значения типа "истина/ложь".
  • Caractères - ce groupe inclut le type char.
Comme le montre le texte ci-dessus, les primitives en Java ne sont pas si primitives et vous permettent de résoudre efficacement de nombreux problèmes. Mais cela introduit également certaines fonctionnalités que nous devons garder à l'esprit si nous ne voulons pas rencontrer de comportement imprévisible dans notre programme. Comme on dit, il faut tout payer. Si nous voulons une primitive avec une plage « raide » (large) - quelque chose comme longue - nous sacrifions l'allocation d'une plus grande partie de la mémoire et dans la direction opposée. En économisant de la mémoire et en utilisant des octets, nous obtenons une plage limitée de -128 à 127.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION