JavaRush /Blog Java /Random-ES /Tipos primitivos en Java: no son tan primitivos
Viacheslav
Nivel 3

Tipos primitivos en Java: no son tan primitivos

Publicado en el grupo Random-ES

Introducción

Se puede considerar que el desarrollo de aplicaciones trabaja con algunos datos, o más bien, los almacena y procesa. Hoy me gustaría tocar el primer aspecto clave. ¿Cómo se almacenan los datos en Java? Aquí tenemos dos formatos posibles: tipos de datos primitivos y de referencia . Hablemos de los tipos primitivos y las posibilidades de trabajar con ellos (digan lo que digan, esta es la base de nuestro conocimiento de un lenguaje de programación). Los tipos de datos primitivos de Java son la base sobre la que descansa todo. No, no estoy exagerando en absoluto. Oracle tiene un tutorial separado dedicado a las primitivas: Tipos de datos primitivos Un poco de historia. Al principio no había nada. Pero el cero es aburrido. Y entonces apareció un poco . ¿Por qué lo llamaron así? Debe su nombre a la abreviatura “ binary digit ” (número binario). Es decir, sólo tiene dos significados. Y como era cero, es lógico que ahora sea 0 o 1. Y la vida se ha vuelto más divertida. Los pedazos comenzaron a reunirse en bandadas. Y estos rebaños empezaron a llamarse byte (byte). En el mundo moderno, byte = 2 elevado a la tercera potencia, es decir 8. Pero resulta que no siempre fue así. Existen muchas conjeturas, leyendas y rumores sobre el origen del nombre byte. Algunas personas piensan que se trata de codificaciones de esa época, mientras que otras piensan que era más rentable leer la información de esta manera. Un byte es la pieza de memoria direccionable más pequeña. Son los bytes los que tienen direcciones únicas en la memoria. Existe la leyenda de que ByTe es una abreviatura de Binary Term, una palabra de máquina. Palabra de máquina: en pocas palabras, es la cantidad de datos que el procesador puede procesar en una sola operación. Anteriormente, el tamaño de la palabra de la máquina era el mismo que el de la memoria direccionable más pequeña. En Java, las variables sólo pueden almacenar valores de bytes. Como dije anteriormente, existen dos tipos de variables en Java: Tipos primitivos en Java: No son tan primitivos - 1
  • Los tipos primitivos de Java almacenan directamente el valor de los bytes de datos (veremos los tipos de estos primitivos con más detalle a continuación);
  • un tipo de referencia, almacena los bytes de la dirección del objeto en Heap, es decir, a través de estas variables obtenemos acceso directamente al objeto en sí (una especie de control remoto para el objeto)

bytes de Java

Entonces, la historia nos dio un byte: la cantidad mínima de memoria que podemos usar. Y consta de 8 bits. El tipo de datos entero más pequeño en Java es el byte. Este es un tipo de 8 bits con signo. ¿Qué significa? Contemos. 2^8 es 256. ¿Pero qué pasa si queremos un número negativo? Y los desarrolladores de Java decidieron que el código binario "10000000" representaría -128, es decir, el bit más significativo (el bit más a la izquierda) indicaría si el número es negativo. El binario “0111 1111” es igual a 127. Es decir, 128 no se puede designar de ninguna manera, porque será -128. El cálculo completo se proporciona en esta respuesta: ¿ Por qué el rango de bytes es de -128 a 127 en Java? Para entender cómo se obtienen los números, debes mirar la imagen:
Tipos primitivos en Java: No son tan primitivos - 2
En consecuencia, para calcular el tamaño 2^(8-1) = 128. Esto significa que el límite mínimo (y tiene un signo menos) será -128. Y el máximo es 128 – 1 (resta cero). Es decir, el máximo será 127. De hecho, no trabajamos con tanta frecuencia con el tipo de byte en un “nivel alto”. Básicamente se trata del procesamiento de datos "en bruto". Por ejemplo, cuando se trabaja con transmisión de datos a través de una red, cuando los datos son un conjunto de 0 y 1 transmitidos a través de algún canal de comunicación. O al leer datos de archivos. También se pueden utilizar cuando se trabaja con cadenas y codificaciones. Código de ejemplo:
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);
}
Por cierto, no creas que usar el tipo byte reducirá el consumo de memoria. Byte se utiliza principalmente para reducir el consumo de memoria al almacenar datos en matrices (por ejemplo, almacenar datos recibidos a través de la red en algún búfer, que se implementará como una matriz de bytes). Pero al realizar operaciones con datos, el uso de bytes no cumplirá con sus expectativas. Esto se debe a la implementación de la Máquina Virtual Java (JVM). Dado que la mayoría de los sistemas son de 32 o 64 bits, el byte y el short durante los cálculos se convertirán a un int de 32 bits, del que hablaremos más adelante. Esto facilita los cálculos. Para obtener más detalles, consulte ¿La adición de bytes se convierte a int debido a las reglas del lenguaje Java o a JVM? . La respuesta también contiene enlaces a JLS (Especificación del lenguaje Java). Además, utilizar un byte en el lugar equivocado puede provocar momentos incómodos:
public static void main(String []args){
        for (byte i = 1; i <= 200; i++) {
            System.out.println(i);
        }
}
Habrá un bucle aquí. Debido a que el valor del contador alcanza el máximo (127), se producirá un desbordamiento y el valor pasará a ser -128. Y nunca saldremos del ciclo.

corto

El límite de valores de bytes es bastante pequeño. Por lo tanto, para el siguiente tipo de datos decidimos duplicar el número de bits. Es decir, ahora no son 8 bits, sino 16. Es decir, 2 bytes. Los valores se pueden calcular de la misma forma. 2^(16-1) = 2^15 = 32768. Esto significa que el rango es de -32768 a 32767. Se utiliza muy raramente en casos especiales. Como nos dice la documentación del lenguaje Java: " puedes usar un short para ahorrar memoria en matrices grandes ".

En t

Entonces llegamos al tipo más utilizado. Ocupa 32 bits o 4 bytes. En general, seguimos duplicándonos. El rango de valores es de -2^31 a 2^31 – 1.

Valor int máximo

El valor máximo de int 2147483648 es 1, que no es nada pequeño. Como se indicó anteriormente, para optimizar los cálculos, porque Para las computadoras modernas, es más conveniente contar, teniendo en cuenta su capacidad de bits, los datos se pueden convertir implícitamente a int. He aquí un ejemplo sencillo:
byte a = 1;
byte b = 2;
byte result = a + b;
Qué código inofensivo, pero obtenemos el error: "error: tipos incompatibles: posible conversión con pérdida de int a byte". Tendrás que corregirlo a byte result = (byte)(a + b); Y un ejemplo más inofensivo. ¿Qué pasa si ejecutamos el siguiente código?
int value = 4;
System.out.println(8/value);
System.out.println(9/value);
System.out.println(10/value);
System.out.println(11/value);
Y sacaremos la conclusión.
2
2
2
2
*sonidos de pánico*
El hecho es que cuando se trabaja con valores int, el resto se descarta, dejando solo la parte entera (en tales casos es mejor usar double).

largo

Seguimos duplicando. Multiplicamos 32 por 2 y obtenemos 64 bits. Por tradición, esto es 4 * 2, es decir, 8 bytes. El rango de valores va desde -2^63 hasta 2^63 – 1. Más que suficiente. Este tipo le permite contar números grandes y grandes. Se utiliza a menudo cuando se trabaja con el tiempo. O, por ejemplo, en largas distancias. Para indicar que un número es largo, coloque el literal L – Largo después del número. Ejemplo:
long longValue = 4;
longValue = 1l; // Не ошибка, но плохо читается
longValue = 2L; // Идеально
Me gustaría adelantarme. A continuación, consideraremos el hecho de que existen envoltorios correspondientes para primitivas, que permiten trabajar con primitivas como objetos. Pero hay una característica interesante. A continuación se muestra un ejemplo: utilizando el mismo compilador en línea de Tutorialspoint, puede verificar el siguiente código:
public class HelloWorld {

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

    public static void printLong(long longValue) {
        System.out.println(longValue);
    }
}
Este código funciona sin errores, todo está bien. Pero tan pronto como el tipo en el método printLong se reemplaza de long a Long (es decir, el tipo ya no es primitivo, sino objeto), Java no tiene claro qué parámetro estamos pasando. Se empieza a suponer que se está transmitiendo un int y habrá un error. Por tanto, en el caso de un método, será necesario indicar explícitamente 4L. Muy a menudo, long se utiliza como identificación cuando se trabaja con bases de datos.

Java flotante y Java doble

Estos tipos se denominan tipos de punto flotante. Es decir, estos no son tipos de números enteros. El tipo flotante es de 32 bits (como int), y el doble se denomina tipo de doble precisión, por lo que es de 64 bits (multiplicado por 2, como nos gusta). Ejemplo:
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;
}
Y aquí hay un ejemplo de la diferencia de valores (debido a la precisión del tipo):
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 );
 }
Estos tipos primitivos se utilizan, por ejemplo, en matemáticas. Aquí está la prueba, una constante para calcular el número PI . Bueno, en general, puedes mirar la API de la clase de Matemáticas. Esto es lo que debería ser importante e interesante: incluso la documentación dice: “ Este tipo de datos nunca debe usarse para valores precisos, como la moneda. Para eso, necesitarás usar la clase java.math.BigDecimal. Numbers and Strings cubre BigDecimal y otras clases útiles proporcionadas por la plataforma Java. " Es decir, no es necesario calcular el dinero en flotación y el doble. Un ejemplo sobre precisión usando el ejemplo del trabajo en la NASA: Java BigDecimal, Cómo lidiar con cálculos de alta precisión Bueno, para sentirlo por ti mismo:
public static void main(String []args){
        float amount = 1.0000005F;
        float avalue = 0.0000004F;
        float result = amount - avalue;
        System.out.println(result);
}
Siga este ejemplo y luego agregue 0 antes de los números 5 y 4. Y verá todo el horror) Hay un informe interesante en ruso sobre flotar y duplicar sobre el tema: https://youtu.be/1RCn5ruN1fk Ejemplos de trabajo con BigDecimal se puede ver aquí: Hacer centavos con BigDecimal Por cierto, float y double pueden devolver más que solo un número. Por ejemplo, el siguiente ejemplo devolverá Infinity:
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        System.out.println(positive_infinity);
}
Y este devolverá 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);
}
Está claro lo del infinito. ¿Qué es NaN? Esto no es un número , lo que significa que el resultado no se puede calcular y no es un número. Aquí hay un ejemplo: queremos calcular la raíz cuadrada de -4. La raíz cuadrada de 4 es 2. Es decir, 2 se debe elevar al cuadrado y luego obtenemos 4. ¿Qué se debe elevar al cuadrado para obtener -4? No funcionará, porque... si hay un número positivo, permanecerá. Y si fue negativo, menos a menos dará un más. Es decir, no es computable.
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);
}
Aquí hay otra excelente descripción general sobre el tema de los números de punto flotante: ¿ Dónde está su punto?
Qué más leer:

booleano de Java

El siguiente tipo es booleano (tipo lógico). Sólo puede aceptar los valores verdadero o falso, que son palabras clave. Se utiliza en operaciones lógicas como bucles while y en bifurcaciones usando if, switch. ¿Qué cosas interesantes puedes encontrar aquí? Bueno, por ejemplo, teóricamente solo necesitamos 1 bit de información, 0 o 1, es decir, verdadero o falso. Pero en realidad, el booleano ocupará más memoria y esto dependerá de la implementación específica de JVM. Normalmente esto cuesta lo mismo que int. Otra opción es utilizar BitSet. Aquí hay una breve descripción del libro Java Fundamentals: BitSet

carácter Java

Ahora hemos llegado al último tipo primitivo. Entonces, los datos en char ocupan 16 bits y describen el carácter. Java usa codificación Unicode para char. El símbolo se puede configurar de acuerdo con dos tablas (puedes verlo aquí ):
  • tabla de caracteres Unicode
  • tabla de caracteres ASCII
Tipos primitivos en Java: No son tan primitivos - 3
Ejemplo en el estudio:
public static void main(String[] args) {
    char symbol = '\u0066'; // Unicode
    symbol = 102; // ASCII
    System.out.println(symbol);
}
Por cierto, char, al ser esencialmente un número, admite operaciones matemáticas como la suma. Y a veces esto puede tener consecuencias divertidas:
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");
        }
    }

}
Recomiendo encarecidamente consultar el IDE en línea desde tutorialspoint . Cuando vi este rompecabezas en una de las conferencias, me levantó el ánimo. Espero que también les guste el ejemplo) ACTUALIZADO: Esto fue en Joker 2017, informe: " Java Puzzlers NG S03 - ¡¿De dónde vienen todos ustedes?! "

Literales

Un literal es un valor especificado explícitamente. Usando literales, puede especificar valores en diferentes sistemas numéricos:
  • Sistema decimal: 10
  • Hexadecimal: 0x1F4, comienza con 0x
  • Sistema octal: 010, comienza desde cero.
  • Sistema binario (desde Java7): 0b101, comienza en 0b
Me detendría un poco más en el sistema octal, porque es curioso:
int costInDollars = 08;
Esta línea de código no se compilará:
error: integer number too large: 08
Parece una tontería. Ahora recordemos los sistemas binario y octal. No hay dos en el sistema binario, porque hay dos valores (a partir de 0). Y el sistema octal tiene 8 valores, empezando desde cero. Es decir, el valor 8 en sí no existe. Por tanto, se trata de un error que a primera vista parece absurdo. Y para recordar, aquí está la regla de “seguimiento” para traducir valores:
Tipos primitivos en Java: No son tan primitivos - 4

Clases contenedoras

Las primitivas en Java tienen sus propias clases contenedoras para que puedas trabajar con ellas como objetos. Es decir, para cada tipo primitivo existe un tipo de referencia correspondiente. Tipos primitivos en Java: No son tan primitivos - 5Las clases contenedoras son inmutables: esto significa que una vez que se crea un objeto, su estado (el valor del campo de valor) no se puede cambiar. Las clases contenedoras se declaran como finales: objetos, por así decirlo, de solo lectura. También me gustaría mencionar que no es posible heredar de estas clases. Java realiza automáticamente conversiones entre tipos primitivos y sus contenedores:
Integer x = 9;          // autoboxing
int n = new Integer(3); // unboxing
El proceso de convertir tipos primitivos en tipos de referencia (int->Integer) se llama autoboxing , y lo contrario se llama unboxing . Estas clases permiten guardar una primitiva dentro de un objeto, y el objeto en sí se comportará como un Objeto (bueno, como cualquier otro objeto). Con todo esto obtenemos una gran cantidad de métodos estáticos variados y útiles, como comparar números, convertir un símbolo a mayúsculas y minúsculas, determinar si un símbolo es una letra o un número, buscar el número mínimo, etc. El conjunto de funcionalidades proporcionado depende únicamente del propio contenedor. Un ejemplo de su propia implementación de un contenedor para int:
public class CustomerInt {

   private final int value;

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

   public int getValue() {
       return value;
   }
}
El paquete principal, java.lang, ya tiene implementaciones de las clases Boolean, Byte, Short, Character, Integer, Float, Long, Double, y no necesitamos crear nada propio, solo reutilizar el ya preparado. unos. Por ejemplo, estas clases nos dan la posibilidad de crear, digamos, una Lista , porque una Lista solo debe contener objetos, que las primitivas no lo son. Para convertir un valor de un tipo primitivo, existen métodos valueOf estáticos, por ejemplo, Integer.valueOf(4) devolverá un objeto de tipo Integer. Para la conversión inversa existen métodos intValue(), longValue(), etc. El compilador inserta llamadas a valueOf y *Value por sí solo, esta es la esencia del autoboxing y autounboxing. Cómo se ve realmente el ejemplo de empaquetado y desempaquetado automático presentado anteriormente:
Integer x = Integer.valueOf(9);
int n = new Integer(3).intValue();
Puede leer más sobre el empaquetado y desempaquetado automático en este artículo .

Elenco

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

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

Это то место в códigoе, где данная переменная будет выполнять свои функции и хранить в себе Cómoое-то significado. Когда же эта область закончится, переменная перестанет существовать и будет стерта из памяти и. Cómo уже можно догадаться, посмотреть o получить ее significado будет невозможно! Так что же это такое — область видимости? Tipos primitivos en Java: No son tan primitivos - 6Область определяется "блоком" — вообще всякой областью, замкнутой в фигурные скобки, выход за которые сулит удаление данных объявленных в ней. Или Cómo минимум — сокрытие их от других блоков, открытых вне текущего. В Java область видимости определяется двумя основными способами:
  • Классом.
  • Методом.
Как я и сказал, переменная не видна códigoу, если она определена за пределами блока, в котором она была инициализирована. Смотрим пример:
int x;
x = 6;
if (x >= 4) {
   int y = 3;
}
x = y;// переменная y здесь не видна!
И Cómo итог мы получим ошибку:

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

Заключение

Сегодня мы познакомoсь с восемью примитивными типами в Java. Эти типы можно разделить на четыре группы:
  • Целые числа: byte, short, int, long — представляют собой целые числа со знаком.
  • Числа с плавающей точкой — эта группа включает себе float и double — типы, которые хранят числа с точностью до определённого знака после запятой.
  • Булевы значения — boolean — хранят значения типа "истина/ложь".
  • Caracteres : este grupo incluye el tipo char.
Como mostró el texto anterior, las primitivas en Java no son tan primitivas y le permiten resolver muchos problemas de manera efectiva. Pero esto también introduce algunas características que debemos tener en cuenta si no queremos encontrarnos con comportamientos impredecibles en nuestro programa. Como dicen, hay que pagar por todo. Si queremos una primitiva con un rango "empinado" (amplio), algo así como largo, sacrificamos la asignación de una porción más grande de memoria y en la dirección opuesta. Al ahorrar memoria y usar bytes, obtenemos un rango limitado de -128 a 127.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION