JavaRush /Blogue Java /Random-PT /Tipos primitivos em Java: eles não são tão primitivos
Viacheslav
Nível 3

Tipos primitivos em Java: eles não são tão primitivos

Publicado no grupo Random-PT

Introdução

O desenvolvimento de aplicativos pode ser considerado como trabalhar com alguns dados, ou melhor, armazená-los e processá-los. Hoje gostaria de abordar o primeiro aspecto fundamental. Como os dados são armazenados em Java? Aqui temos dois formatos possíveis: tipos de dados de referência e primitivos . Vamos falar sobre os tipos de tipos primitivos e as possibilidades de trabalhar com eles (digamos o que se diga, esta é a base do nosso conhecimento de uma linguagem de programação). Os tipos de dados primitivos Java são a base sobre a qual tudo se baseia. Não, não estou exagerando nada. A Oracle tem um tutorial separado dedicado aos primitivos: Tipos de dados primitivos Tipos primitivos em Java: eles não são tão primitivos - 1 Um pouco de história. No começo era zero. Mas zero é chato. E então um pouco apareceu . Por que ele foi chamado assim? Foi nomeado assim a partir da abreviatura “ binary digit ” (número binário). Ou seja, tem apenas dois significados. E como era zero, é lógico que agora seja 0 ou 1. E a vida ficou mais divertida. Os pedaços começaram a se reunir em bandos. E esses rebanhos passaram a ser chamados de byte (byte). No mundo moderno, byte = 2 elevado à terceira potência, ou seja, 8. Mas acontece que nem sempre foi assim. Existem muitos palpites, lendas e rumores sobre a origem do nome byte. Algumas pessoas pensam que tudo se resume às codificações da época, enquanto outras pensam que era mais lucrativo ler as informações desta forma. Um byte é o menor pedaço de memória endereçável. São os bytes que possuem endereços exclusivos na memória. Existe uma lenda de que ByTe é uma abreviatura de Binary Term - uma palavra de máquina. Palavra de máquina - simplificando, esta é a quantidade de dados que o processador pode processar em uma operação. Anteriormente, o tamanho da palavra de máquina era igual ao da menor memória endereçável. Em Java, as variáveis ​​só podem armazenar valores de bytes. Como eu disse acima, existem dois tipos de variáveis ​​em Java:
  • os tipos primitivos java armazenam diretamente o valor dos bytes de dados (veremos os tipos desses primitivos com mais detalhes a seguir);
  • um tipo de referência, armazena os bytes do endereço do objeto no Heap, ou seja, através dessas variáveis ​​conseguimos acesso direto ao próprio objeto (espécie de controle remoto para o objeto)

Byte Java

Então, a história nos deu um byte – a quantidade mínima de memória que podemos usar. E consiste em 8 bits. O menor tipo de dados inteiro em java é byte. Este é um tipo assinado de 8 bits. O que isso significa? Vamos contar. 2^8 é 256. Mas e se quisermos um número negativo? E os desenvolvedores Java decidiram que o código binário “10000000” representaria -128, ou seja, o bit mais significativo (o bit mais à esquerda) indicaria se o número é negativo. O binário “0111 1111” é igual a 127. Ou seja, 128 não pode ser designado de forma alguma, pois será -128. O cálculo completo é fornecido nesta resposta: Por que o intervalo de bytes é de -128 a 127 em Java? Para entender como os números são obtidos, você deve olhar a imagem:
Tipos primitivos em Java: eles não são tão primitivos – 2
Assim, para calcular o tamanho 2^(8-1) = 128. Isso significa que o limite mínimo (e tem um sinal de menos) será -128. E o máximo é 128 – 1 (subtraia zero). Ou seja, o máximo será 127. Na verdade, não trabalhamos com tanta frequência com o tipo byte em “alto nível”. Trata-se principalmente do processamento de dados “brutos”. Por exemplo, ao trabalhar com transmissão de dados em rede, quando os dados são um conjunto de 0s e 1s transmitidos por algum tipo de canal de comunicação. Ou ao ler dados de arquivos. Eles também podem ser usados ​​ao trabalhar com strings e codificações. Código de exemplo:
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);
}
A propósito, não pense que usar o tipo byte reduzirá o consumo de memória. Byte é usado principalmente para reduzir o consumo de memória ao armazenar dados em arrays (por exemplo, armazenar dados recebidos pela rede em algum buffer, que será implementado como um array de bytes). Mas ao realizar operações em dados, o uso de byte não atenderá às suas expectativas. Isto se deve à implementação da Java Virtual Machine (JVM). Como a maioria dos sistemas são de 32 ou 64 bits, o byte e o short durante os cálculos serão convertidos em um int de 32 bits, sobre o qual falaremos mais tarde. Isso facilita os cálculos. Para obter mais detalhes, consulte A adição de bytes é convertida em int por causa das regras da linguagem java ou por causa do jvm? . A resposta também contém links para JLS (Java Language Specification). Além disso, usar byte no lugar errado pode levar a momentos estranhos:
public static void main(String []args){
        for (byte i = 1; i <= 200; i++) {
            System.out.println(i);
        }
}
Haverá um loop aqui. Como o valor do contador atinge o máximo (127), ocorrerá um estouro e o valor se tornará -128. E nunca sairemos do ciclo.

curto

O limite para valores de bytes é bastante pequeno. Portanto, para o próximo tipo de dados decidimos dobrar o número de bits. Ou seja, agora não são 8 bits, mas 16. Ou seja, 2 bytes. Os valores podem ser calculados da mesma forma. 2^(16-1) = 2^15 = 32768. Isso significa que o intervalo é de -32768 a 32767. É usado muito raramente para casos especiais. Como nos diz a documentação da linguagem Java: “ você pode usar um short para economizar memória em arrays grandes ”.

interno

Então chegamos ao tipo usado com mais frequência. Ocupa 32 bits ou 4 bytes. Em geral, continuamos a duplicar. A faixa de valores é de -2^31 a 2^31 – 1.

Valor interno máximo

O valor máximo de int 2147483648 é 1, o que não é nada pequeno. Como dito acima, para otimizar os cálculos, porque É mais conveniente para os computadores modernos, tendo em conta a sua capacidade de bits, contar; os dados podem ser convertidos implicitamente para int. Aqui está um exemplo simples:
byte a = 1;
byte b = 2;
byte result = a + b;
Código tão inofensivo, mas obtemos o erro: “erro: tipos incompatíveis: possível conversão com perdas de int para byte”. Você terá que corrigi-lo para byte result = (byte)(a + b); E mais um exemplo inofensivo. O que acontece se executarmos o código a seguir?
int value = 4;
System.out.println(8/value);
System.out.println(9/value);
System.out.println(10/value);
System.out.println(11/value);
E chegaremos à conclusão
2
2
2
2
*sons de pânico*
O fato é que ao trabalhar com valores int, o restante é descartado, restando apenas a parte inteira (nesses casos é melhor usar double).

longo

Continuamos a duplicar. Multiplicamos 32 por 2 e obtemos 64 bits. Por tradição, é 4 * 2, ou seja, 8 bytes. A faixa de valores é de -2^63 a 2^63 – 1. Mais que suficiente. Este tipo permite contar números muito grandes. Freqüentemente usado ao trabalhar com o tempo. Ou em longas distâncias, por exemplo. Para indicar que um número é longo, coloque o literal L – Long após o número. Exemplo:
long longValue = 4;
longValue = 1l; // Не ошибка, но плохо читается
longValue = 2L; // Идеально
Eu gostaria de me adiantar. A seguir, consideraremos o fato de que existem wrappers correspondentes para primitivas, que permitem trabalhar com primitivas como objetos. Mas há uma característica interessante. Aqui está um exemplo: Usando o mesmo compilador online Tutorialspoint, você pode verificar o seguinte 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 sem erros, está tudo bem. Mas assim que o tipo no método printLong é substituído de long para Long (ou seja, o tipo não se torna primitivo, mas objeto), fica claro para Java qual parâmetro estamos passando. Começa a assumir que um int está sendo transmitido e haverá um erro. Portanto, no caso de um método, será necessário indicar explicitamente 4L. Muitas vezes, long é usado como ID ao trabalhar com bancos de dados.

Java flutuante e Java duplo

Esses tipos são chamados de tipos de ponto flutuante. Ou seja, estes não são tipos inteiros. O tipo float tem 32 bits (como int) e double é chamado de tipo de precisão dupla, portanto tem 64 bits (multiplique por 2, como gostamos). Exemplo:
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;
}
E aqui está um exemplo da diferença de valores (devido à precisão do 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 );
 }
Esses tipos primitivos são usados ​​em matemática, por exemplo. Aqui está a prova, uma constante para calcular o número PI . Bem, em geral, você pode olhar a API da classe Math. Aqui está o que mais deve ser importante e interessante: até a documentação diz: “ Este tipo de dados nunca deve ser usado para valores precisos, como moeda. Para isso, você precisará usar a classe java.math.BigDecimal.Numbers and Strings cobre BigDecimal e outras classes úteis fornecidas pela plataforma Java. " Ou seja, o dinheiro em float e double não precisa ser calculado. Um exemplo sobre precisão usando o exemplo de trabalho na NASA: Java BigDecimal, Lidando com cálculos de alta precisão Bem, sinta por si mesmo:
public static void main(String []args){
        float amount = 1.0000005F;
        float avalue = 0.0000004F;
        float result = amount - avalue;
        System.out.println(result);
}
Siga este exemplo e adicione 0 antes dos números 5 e 4. E você verá todo o horror) Há um relatório interessante em russo sobre float e double sobre o assunto: https://youtu.be/1RCn5ruN1fk Exemplos de trabalho com BigDecimal pode ser visto aqui: Ganhe centavos com BigDecimal A propósito, float e double podem retornar mais do que apenas um número. Por exemplo, o exemplo abaixo retornará Infinity:
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        System.out.println(positive_infinity);
}
E este retornará 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);
}
É claro sobre o infinito. O que é NaN? Isto não é um número , o que significa que o resultado não pode ser calculado e não é um número. Aqui está um exemplo: Queremos calcular a raiz quadrada de -4. A raiz quadrada de 4 é 2. Ou seja, 2 deve ser elevado ao quadrado e então obtemos 4. O que deve ser elevado ao quadrado para obter -4? Não vai funcionar, porque... se houver um número positivo, ele permanecerá. E se fosse negativo, então menos por menos dará um sinal de mais. Ou seja, não é computável.
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);
}
Aqui está outra ótima visão geral sobre o tema dos números de ponto flutuante: Onde está o seu ponto?
O que mais ler:

Java booleano

O próximo tipo é Boolean (tipo lógico). Só pode aceitar os valores verdadeiro ou falso, que são palavras-chave. Usado em operações lógicas, como loops while, e em ramificações usando if, switch. Que coisas interessantes você pode descobrir aqui? Bom, por exemplo, teoricamente, precisamos apenas de 1 bit de informação, 0 ou 1, ou seja, verdadeiro ou falso. Mas, na realidade, o booleano ocupará mais memória e isso dependerá da implementação específica da JVM. Normalmente, isso custa o mesmo que int. Outra opção é usar BitSet. Aqui está uma breve descrição do livro Java Fundamentals: BitSet

Caractere Java

Agora chegamos ao último tipo primitivo. Portanto, os dados em char ocupam 16 bits e descrevem o caractere. Java usa codificação Unicode para char. O símbolo pode ser definido de acordo com duas tabelas (você pode vê-lo aqui ):
  • Tabela de caracteres Unicode
  • Tabela de caracteres ASCII
Tipos primitivos em Java: eles não são tão primitivos – 3
Exemplo no estúdio:
public static void main(String[] args) {
    char symbol = '\u0066'; // Unicode
    symbol = 102; // ASCII
    System.out.println(symbol);
}
A propósito, char, sendo essencialmente um número, suporta operações matemáticas como soma. E às vezes isso pode levar a consequências engraçadas:
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");
        }
    }

}
Eu recomendo fortemente verificar o IDE online em tutorialspoint . Quando vi esse quebra-cabeça em uma das conferências, meu ânimo melhorou. Espero que gostem do exemplo também) ATUALIZADO: Isso foi no Joker 2017, reportagem: " Java Puzzlers NG S03 - De onde vocês vêm?! "

Literais

Um literal é um valor especificado explicitamente. Usando literais, você pode especificar valores em diferentes sistemas numéricos:
  • Sistema decimal: 10
  • Hexadecimal: 0x1F4, começa com 0x
  • Sistema octal: 010, começa do zero.
  • Sistema binário (desde Java7): 0b101, começa em 0b
Eu entraria em mais detalhes sobre o sistema octal, porque é engraçado:
int costInDollars = 08;
Esta linha de código não será compilada:
error: integer number too large: 08
Parece um absurdo. Agora vamos lembrar dos sistemas binário e octal. Não existem dois no sistema binário, porque existem dois valores (começando em 0). E o sistema octal tem 8 valores, começando do zero. Ou seja, o valor 8 em si não existe. Portanto, trata-se de um erro que à primeira vista parece absurdo. E para lembrar, aqui está a regra de “acompanhamento” para traduzir valores:
Tipos primitivos em Java: eles não são tão primitivos – 4

Classes de wrapper

Primitivos em Java têm suas próprias classes wrapper para que você possa trabalhar com eles como objetos. Ou seja, para cada tipo primitivo existe um tipo de referência correspondente. Tipos primitivos em Java: eles não são tão primitivos – 5As classes wrapper são imutáveis: isso significa que, uma vez criado um objeto, seu estado – o valor do campo de valor – não pode ser alterado. As classes wrapper são declaradas como finais: objetos, por assim dizer, somente leitura. Gostaria também de mencionar que não é possível herdar dessas classes. Java faz conversões automaticamente entre tipos primitivos e seus wrappers:
Integer x = 9;          // autoboxing
int n = new Integer(3); // unboxing
O processo de conversão de tipos primitivos em tipos de referência (int->Integer) é chamado autoboxing e o inverso é chamado unboxing . Essas classes possibilitam armazenar um primitivo dentro de um objeto, e o próprio objeto se comportará como um Objeto (bem, como qualquer outro objeto). Com tudo isso, obtemos um grande número de métodos estáticos variados e úteis, como comparar números, converter um caractere em registro, determinar se um caractere é uma letra ou um número, procurar o número mínimo, etc. O conjunto de funcionalidades fornecido depende apenas do próprio wrapper. Um exemplo de sua própria implementação de wrapper para int:
public class CustomerInt {

   private final int value;

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

   public int getValue() {
       return value;
   }
}
O pacote principal, java.lang, já possui implementações das classes Boolean, Byte, Short, Character, Integer, Float, Long, Double, e não precisamos criar nada próprio, apenas reaproveitar o já pronto uns. Por exemplo, essas classes nos dão a capacidade de criar, digamos, uma Lista , porque uma Lista deve conter apenas objetos, o que os primitivos não contêm. Para converter um valor de um tipo primitivo, existem métodos valueOf estáticos, por exemplo, Integer.valueOf(4) retornará um objeto do tipo Integer. Para conversão reversa existem métodos intValue(), longValue(), etc. O compilador insere chamadas para valueOf e *Value por conta própria, esta é a essência do autoboxing e autounboxing. Qual é a aparência real do exemplo de autopacking e autounpacking apresentado acima:
Integer x = Integer.valueOf(9);
int n = new Integer(3).intValue();
Você pode ler mais sobre autopacking e autounpacking neste artigo .

Elenco

При работе с примитивами существует такое понятие 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 будет невозможно! Так что же это такое — область видимости? Tipos primitivos em Java: eles não são tão primitivos – 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 — хранят значения типа "истина/ложь".
  • Caracteres - este grupo inclui o tipo char.
Como o texto acima mostrou, as primitivas em Java não são tão primitivas e permitem resolver muitos problemas de forma eficaz. Mas isso também introduz alguns recursos que devemos ter em mente se não quisermos encontrar um comportamento imprevisível em nosso programa. Como se costuma dizer, você tem que pagar por tudo. Se quisermos uma primitiva com um alcance “íngreme” (amplo) - algo como longo - sacrificamos a alocação de um pedaço maior de memória e na direção oposta. Ao economizar memória e usar bytes, obtemos um intervalo limitado de -128 a 127.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION