Hi! As you have progressed through CodeGym, you've encountered primitive types many times.
Here is a short list of what we know about them:
- They are not objects and represent a value stored in memory
- 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
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:
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.
Widening in Java
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.
Narrowing in Java
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:
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
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:
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.
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.
GO TO FULL VERSION