Hi! As you have progressed through CodeGym, you've encountered primitive types many times.
Widening and narrowing of primitive types - 1
Here is a short list of what we know about them:
  1. They are not objects and represent a value stored in memory
  2. There are several kinds
    • Whole numbers: byte, short, int, long
    • Floating-point (fractional) numbers: float and double
    • Logical values: boolean
    • Symbolic values (for representing letters and numerals): char
  3. Each type has its own range of values:

Primitive type Size in memory Value range
byte 8 bits -128 to 127
short 16 bits -32768 to 32767
char 16 bits 0 to 65536
int 32 bits -2147483648 to 2147483647
long 64 bits -9223372036854775808 to 9223372036854775807
float 32 bits (2 to the power of -149) to ((2 - (2 to the power of -23)) * 2 to the power of 127)
double 64 bits (-2 to the power of 63) to ((2 to the power of 63) - 1)
boolean 8 (when used in arrays), 32 (if not used in arrays) true or false
But in addition to having different values, they also differ in how much space they occupy in memory. An int takes more than a byte. And a long is bigger than a short. The amount of memory occupied by primitives can be compared to Russian nesting dolls: Widening and narrowing of primitive types - 2 Each nesting doll has space available inside. The larger the nesting doll, the more space there is. A large nesting doll (long) will easy accommodate a smaller int. It easily fits and you don't need to do anything else. In Java, when working with primitives, this is called implicit conversion. Or put differently, it's called widening. Here's a simple example of a widening conversion:
public class Main {

   public static void main(String[] args) {

       int bigNumber = 10000000;

       byte littleNumber = 16;

       bigNumber = littleNumber;
       System.out.println(bigNumber);
   }
}
Here we assign a byte value to an int variable. The assignment succeeds without any problems: the value stored in a byte takes up less memory than what an int can accommodate. The little nesting doll (byte value) easily fits inside the big nesting doll (int variable). It's a different matter if you try to do the opposite, i.e. to put a large value into a variable whose range can't accommodate such a big data type. With real nesting dolls, the number simply wouldn't fit. With Java, it can, but with nuances. Let's try putting an int into a short variable:
public static void main(String[] args) {

   int bigNumber = 10000000;

   short littleNumber = 1000;

   littleNumber = bigNumber;// Error!
   System.out.println(bigNumber);
}
Error! The compiler understands that you're trying to do something abnormal by shoving a large nesting doll (int) inside a small one (short). In this case, the compilation error is a warning from the compiler: "Hey, are you absolutely certain that you want to do this?" If you're certain, then you tell the compiler: "Everything is okay. I know what I'm doing!" This process is called explicit type conversion, or narrowing. To perform a narrowing conversion, you need to explicitly indicate the type that you want to convert your value to. In other words, you need to answer the compiler's question: "Well, which of these little nesting dolls do you want to put this big nesting doll into?" In our case, it looks like this:
public static void main(String[] args) {

   int bigNumber = 10000000;

   short littleNumber = 1000;

   littleNumber = (short) bigNumber;
   System.out.println(littleNumber);
}
We explicitly indicate that we want to put an int into a short variable and that we'll take the responsibility. Seeing that a narrower type has been explicitly indicated, the compiler performs the conversion. What's the result? Console output: -27008 That was a little unexpected. Why exactly did we get that? In fact, it's all very simple. Originally, the value was 10000000 It was stored in an int variable, which occupies 32 bits. This is its binary representation:
Widening and narrowing of primitive types - 3
We write this value into a short variable, which can only store 16 bits! Accordingly, only the first 16 bits of our number will be moved there. The rest will be discarded. As a result, the short variable receives the following value
Widening and narrowing of primitive types - 4
which in decimal form is equal to -27008 That is why the compiler asks you to "confirm" by indicating an explicit narrowing conversion to a specific type. First, this shows that you are taking responsibility for the result. And second, it tells the compiler how much space to allocate when converting happens. After all, in the last example, if we assigned an int value to a byte variable rather than a short, then we would only have 8 bits at our disposal, not 16, and the result would be different. Fractional types (float and double) have their own process for narrowing conversions. If you try casting a factional number to an integer type, the fractional part will be discarded.
public static void main(String[] args) {

   double d = 2.7;

   long x = (int) d;
   System.out.println(x);
}
Console output: 2

char

You already know that char is used to display individual characters.
public static void main(String[] args) {

   char c = '!';
   char z = 'z';
   char i = '8';

}
But this data type has several features that are important to understand. Let's look again at the table of value ranges:
Primitive type Size in memory Value range
byte 8 bits -128 to 127
short 16 bits -32768 to 32767
char 16 bits 0 to 65536
int 32 bits -2147483648 to 2147483647
long 64 bits -9223372036854775808 to 9223372036854775807
float 32 bits (2 to the power of -149) to ((2 - (2 to the power of -23)) * 2 to the power of 127)
double 64 bits (-2 to the power of 63) to ((2 to the power of 63) - 1)
boolean 8 (when used in arrays), 32 (if not used in arrays) true or false
The range 0 to 65536 is indicated for the char type. But what does that mean? After all, a char doesn't just represent numbers, but also letters, punctuation marks… The thing is that in Java char values are stored in Unicode format. We already encountered Unicode in one of the previous lessons. You probably remember that Unicode is a character encoding standard that includes the symbols of almost all the written languages of the world. In other words, it's a list of special codes that represent nearly every character in any language. The entire Unicode table is very large, and, of course, there's no need to learn it by heart. Here's a small part of it: Widening and narrowing of primitive types - 5 The main thing is to understand how chars are stored, and to remember that if you know the code for a particular character, you can always produce that character in your program. Let's try with some random number:
public static void main(String[] args) {

   int x = 32816;

   char c = (char) x ;
   System.out.println(c);
}
Console output: This is the format used to store chars in Java. Each symbol corresponds to a number: a 16-bit (two-byte) numeric code. In Unicode, 32816 corresponds to the Chinese character . Make note of the following point. In this example, we used an int variable. It occupies 32 bits in memory, while a char occupies 16. Here we chose an int, because our number (32816) won't fit in a short. Although the size of a char (just like a short) is 16 bits, there are no negative numbers in the char range, so the "positive" part of the char range is twice as large (65536 instead of 32767 for the short type). We can use an int as long as our code stays below 65536. But if you create an int value greater than 65536, then it will occupy more than 16 bits. And this will result in a narrowing conversion
char c = (char) x;
the extra bits will be discarded (as discussed above) and the result will be quite unexpected.

Special features of adding chars and integers

Let's look at an unusual example:
public class Main {

   public static void main(String[] args) {

      char c = '1';

      int i = 1;

       System.out.println(i + c);
   }
}
Console output: 50 O_О How does that make sense? 1+1. Where did the 50 come from?! You already know that char values are stored in memory as numbers in the range from 0 to 65536, and that these numbers are a Unicode representation of a character. Widening and narrowing of primitive types - 6 When we add a char and some whole-number type, the char is converted to the corresponding Unicode number. In our code, when we added 1 and '1', the symbol '1' was converted to its own code, which is 49 (you can verify this in the table above). Therefore, the result is 50. Let's once again take our old friend as an example, and try adding it to some number.
public static void main(String[] args) {

   char c = '耰';
   int x = 200;

   System.out.println(c + x);
}
Console output: 33016 We already discovered that corresponds to 32816. And when we add this number and 200, we get our result: 33016. :) As you can see, the algorithm here is quite simple, but you shouldn't forget it.