JavaRush /Java Blog /Random EN /Primitive types in Java: They're not that primitive
Viacheslav
Level 3

Primitive types in Java: They're not that primitive

Published in the Random EN group

Introduction

Application development can be considered as working with some data, or rather, storing and processing it. Today I would like to touch on the first key aspect. How is data stored in Java? Here we have two possible formats: reference and primitive data types. Let's talk about the types of primitive types and the possibilities of working with them (whatever one may say, this is the foundation of our knowledge of a programming language). Java primitive data types are the foundation on which everything rests. No, I'm not exaggerating at all. Oracle has a separate Tutorial dedicated to primitives: Primitive Data Types Primitive types in Java: They are not so primitive - 1 A little history. In the beginning there was zero. But zero is boring. And then bit appeared . Why was he called that? It was named so from the abbreviation “ bi nary digi t ” (binary number). That is, it only has two meanings. And since it was zero, it is logical that now it is either 0 or 1. And life has become more fun. The bits began to gather in flocks. And these flocks began to be called byte (byte). In the modern world, byte = 2 to the third power, i.e. 8. But it turns out that this was not always the case. There are many guesses, legends and rumors about where the name byte came from. Some people think that it’s all about the encodings of that time, while others think that it was more profitable to read information this way. A byte is the smallest addressable piece of memory. It is the bytes that have unique addresses in memory. There is a legend that ByTe is an abbreviation for Binary Term - a machine word. Machine word - simply put, this is the amount of data that the processor can process in one operation. Previously, the machine word size was the same as the smallest addressable memory. In Java, variables can only store byte values. As I said above, there are two types of variables in Java:
  • java primitive types directly store the value of data bytes (we will look at the types of these primitives in more detail below);
  • a reference type, stores the bytes of the object’s address in Heap, that is, through these variables we get access directly to the object itself (sort of a remote control for the object)

Java byte

So, history gave us a byte - the minimum amount of memory that we can use. And it consists of 8 bits. The smallest integer data type in java is byte. This is a signed 8-bit type. What does it mean? Let's count. 2^8 is 256. But what if we want a negative number? And the Java developers decided that the binary code "10000000" would represent -128, that is, the most significant bit (the leftmost bit) would indicate whether the number is negative. Binary “0111 1111” equals 127. That is, 128 cannot be designated in any way, because it will be -128. The full calculation is given in this answer: Why is the range of bytes -128 to 127 in Java? To understand how the numbers are obtained, you should look at the picture:
Primitive types in Java: They are not so primitive - 2
Accordingly, to calculate the size 2^(8-1) = 128. This means the minimum limit (and it has a minus) will be -128. And the maximum is 128 – 1 (subtract zero). That is, the maximum will be 127. In fact, we do not work with the byte type so often at a “high level”. Basically this is the processing of “raw” data. For example, when working with data transmission over a network, when the data is a set of 0s and 1s transmitted through some communication channel. Or when reading data from files. They can also be used when working with strings and encodings. Example 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);
}
By the way, don’t think that using the byte type will reduce memory consumption. Byte is mainly used to reduce memory consumption when storing data in arrays (for example, storing data received over the network in some buffer, which will be implemented as a byte array). But when performing operations on data, using byte will not meet your expectations. This is due to the implementation of the Java Virtual Machine (JVM). Since most systems are 32 or 64 bit, byte and short during calculations will be converted to a 32-bit int, which we will talk about later. This makes calculations easier. For more details, see Is addition of byte converts to int because of java language rules or because of jvm? . The answer also contains links to JLS (Java Language Specification). Additionally, using byte in the wrong place can lead to awkward moments:
public static void main(String []args){
        for (byte i = 1; i <= 200; i++) {
            System.out.println(i);
        }
}
There will be a loop here. Because the counter value reaches the maximum (127), an overflow will occur and the value will become -128. And we will never get out of the cycle.

short

The limit for byte values ​​is quite small. Therefore, for the next data type we decided to double the number of bits. That is, now it is not 8 bits, but 16. That is, 2 bytes. The values ​​can be calculated in the same way. 2^(16-1) = 2^15 = 32768. This means the range is from -32768 to 32767. It is used very rarely for any special cases. As the Java language documentation tells us: “ you can use a short to save memory in large arrays .”

int

So we got to the most frequently used type. It takes up 32 bits, or 4 bytes. In general, we continue to double. The range of values ​​is from -2^31 to 2^31 – 1.

Maximum int value

The maximum value of int 2147483648 is 1, which is not small at all. As stated above, to optimize calculations, because It is more convenient for modern computers, taking into account their bit capacity, to count; data can be implicitly converted to int. Here's a simple example:
byte a = 1;
byte b = 2;
byte result = a + b;
Such harmless code, but we get the error: “error: incompatible types: possible lossy conversion from int to byte.” You'll have to correct it to byte result = (byte)(a + b); And one more harmless example. What happens if we run the following code?
int value = 4;
System.out.println(8/value);
System.out.println(9/value);
System.out.println(10/value);
System.out.println(11/value);
And we will get the conclusion
2
2
2
2
*sounds of panic*
The fact is that when working with int values, the remainder is discarded, leaving only the whole part (in such cases it is better to use double).

long

We continue to double. We multiply 32 by 2 and get 64 bits. By tradition, this is 4 * 2, that is, 8 bytes. The range of values ​​is from -2^63 to 2^63 – 1. More than enough. This type allows you to count large, large numbers. Often used when working with time. Or over long distances, for example. To indicate that a number is long, place the literal L – Long after the number. Example:
long longValue = 4;
longValue = 1l; // Не ошибка, но плохо читается
longValue = 2L; // Идеально
I would like to get ahead of myself. Next, we will consider the fact that there are corresponding wrappers for primitives, which make it possible to work with primitives as objects. But there is an interesting feature. Here is an example: Using the same Tutorialspoint online compiler, you can check the following code:
public class HelloWorld {

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

    public static void printLong(long longValue) {
        System.out.println(longValue);
    }
}
This code works without errors, everything is fine. But as soon as the type in the printLong method is replaced from long to Long (i.e. the type becomes not primitive, but object), it becomes unclear to Java what parameter we are passing. It begins to assume that an int is being transmitted and there will be an error. Therefore, in the case of a method, it will be necessary to explicitly indicate 4L. Very often long is used as an ID when working with databases.

Java float and Java double

These types are called floating point types. That is, these are not integer types. The float type is 32 bits (like int), and double is called a double precision type, so it is 64 bits (multiply by 2, just like we like). Example:
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;
}
And here is an example of the difference in values ​​(due to type precision):
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 );
 }
These primitive types are used in mathematics, for example. Here is the proof, a constant for calculating the number PI . Well, in general, you can look at the API of the Math class. Here's what else should be important and interesting: even the documentation says: “ This data type should never be used for precise values, such as currency. For that, you will need to use the java.math.BigDecimal class instead.Numbers and Strings covers BigDecimal and other useful classes provided by the Java platform. " That is, money in float and double does not need to be calculated. An example about accuracy using the example of work at NASA: Java BigDecimal, Dealing with high precision calculations Well, to feel it for yourself:
public static void main(String []args){
        float amount = 1.0000005F;
        float avalue = 0.0000004F;
        float result = amount - avalue;
        System.out.println(result);
}
Follow this example, and then add 0 before the numbers 5 and 4. And you will see all the horror) There is an interesting report in Russian about float and double on the topic: https://youtu.be/1RCn5ruN1fk Examples of working with BigDecimal can be seen here: Make cents with BigDecimal By the way, float and double can return more than just a number. For example, the example below will return Infinity:
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        System.out.println(positive_infinity);
}
And this one will return 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);
}
It’s clear about infinity. What is NaN? This is Not a number , meaning the result cannot be calculated and is not a number. Here's an example: We want to calculate the square root of -4. The square root of 4 is 2. That is, 2 must be squared and then we get 4. What must be squared to get -4? It won't work, because... if there is a positive number, then it will remain. And if it was negative, then minus by minus will give a plus. That is, it is not 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);
}
Here's another great overview on the topic of floating point numbers: Where is your point?
What else to read:

Java boolean

The next type is Boolean (logical type). It can only accept the values ​​true or false, which are keywords. Used in logical operations such as while loops, and in branching using if, switch. What interesting things can you find out here? Well, for example, theoretically, we only need 1 bit of information, 0 or 1, that is, true or false. But in reality, Boolean will take up more memory and this will depend on the specific JVM implementation. Typically this costs the same as int. Another option is to use BitSet. Here's a short description from the Java Fundamentals book: BitSet

Java char

Now we have reached the last primitive type. So, the data in char takes up 16 bits and describes the character. Java uses Unicode encoding for char. The symbol can be set in accordance with two tables (you can see it here ):
  • Unicode character table
  • ASCII character table
Primitive types in Java: They are not so primitive - 3
Example in the studio:
public static void main(String[] args) {
    char symbol = '\u0066'; // Unicode
    symbol = 102; // ASCII
    System.out.println(symbol);
}
By the way, char, being essentially a number, supports mathematical operations such as sum. And sometimes this can lead to funny consequences:
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");
        }
    }

}
I highly recommend checking out the online IDE from tutorialspoint . When I saw this puzzle at one of the conferences, it lifted my spirits. I hope you like the example too) UPDATED: This was at Joker 2017, report: " Java Puzzlers NG S03 - Where are you all coming from?! "

Literals

A literal is an explicitly specified value. Using literals, you can specify values ​​in different number systems:
  • Decimal system: 10
  • Hexadecimal system: 0x1F4, starts with 0x
  • Octal system: 010, starts from zero.
  • Binary system (since Java7): 0b101, starts at 0b
I would dwell a little more on the octal system, because it’s funny:
int costInDollars = 08;
This line of code will not compile:
error: integer number too large: 08
It seems like nonsense. Now let's remember about the binary and octal systems. There are no twos in the binary system, because there are two values ​​(starting from 0). And the octal system has 8 values, starting from zero. That is, the value 8 itself does not exist. Therefore, this is an error that at first glance seems absurd. And to remember, here’s the “follow-up” rule for translating values:
Primitive types in Java: They are not so primitive - 4

Wrapper classes

Primitives in Java have their own wrapper classes so that you can work with them as objects. That is, for each primitive type there is a corresponding reference type. Primitive types in Java: They are not so primitive - 5Wrapper classes are immutable: this means that once an object is created, its state—the value of the value field—cannot be changed. Wrapper classes are declared as final: objects, so to speak, read-only. I would also like to mention that it is not possible to inherit from these classes. Java automatically makes conversions between primitive types and their wrappers:
Integer x = 9;          // autoboxing
int n = new Integer(3); // unboxing
The process of converting primitive types to reference types (int->Integer) is called autoboxing , and the reverse is called unboxing . These classes make it possible to save a primitive inside an object, and the object itself will behave like an Object (well, like any other object). With all this, we get a large number of varied, useful static methods, such as comparing numbers, converting a symbol to case, determining whether a symbol is a letter or a number, searching for the minimum number, etc. The provided set of functionality depends only on the wrapper itself. An example of your own implementation of a wrapper for int:
public class CustomerInt {

   private final int value;

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

   public int getValue() {
       return value;
   }
}
The main package, java.lang, already has implementations of the classes Boolean, Byte, Short, Character, Integer, Float, Long, Double, and we don’t need to create anything of our own, but just reuse the ready-made ones. For example, such classes give us the ability to create, say, a List , because a List should only contain objects, which primitives are not. To convert a value of a primitive type, there are static valueOf methods, for example, Integer.valueOf(4) will return an object of type Integer. For reverse conversion there are methods intValue(), longValue(), etc. The compiler inserts calls to valueOf and *Value on its own, this is the essence of autoboxing and autounboxing. What the example of autopacking and autounpacking presented above actually looks like:
Integer x = Integer.valueOf(9);
int n = new Integer(3).intValue();
You can read more about autopacking and autounpacking in this article .

Cast

При работе с примитивами существует такое понятие 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 будет невозможно! Так что же это такое — область видимости? Primitive types in Java: They are not so primitive - 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 — хранят значения типа "истина/ложь".
  • Characters - this group includes the char type.
As the text above showed, the primitives in Java are not so primitive and allow you to solve many problems effectively. But this also introduces some features that we should keep in mind if we do not want to encounter unpredictable behavior in our program. As they say, you have to pay for everything. If we want a primitive with a “steep” (wide) range - something like long - we sacrifice the allocation of a larger piece of memory and in the opposite direction. By saving memory and using bytes, we get a limited range from -128 to 127.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION