1. Typecasting
Variables of primitive types (with the exception of the boolean
type) are used to store various types of numbers. Although the types of variables never changed, there is a place where you can convert from one type to another. And that place is assignment.
Variables of different types can be assigned to each other. When you do this the value of a variable of one type is converted to a value of another type and assigned to the second variable. In this regard, we can identify two kinds of type conversion: widening and narrowing.
Widening is like to moving a value from a small basket to a large one: this operation is seamless and painless. Narrowing happens when you move a value from a large basket to a small one: there may not be enough space, and you will have to throw something away.
Here are the types, sorted by basket size:
2. Widening type conversions
It's often necessary to assign a variable of one numeric type to a variable of another numeric type. How do you do that?
Java has 4 integer types:
Type | Size |
---|---|
byte |
1 byte |
short |
2 bytes |
int |
4 bytes |
long |
8 bytes |
Variable stored in smaller baskets can always be assigned to variables stored in larger baskets.
int
, short
and byte
variables can be easily assigned to long
variables. short
and byte
variables can be assigned to int
variables. And byte
variables can be assigned to short
variables.
Examples:
Code | Description |
---|---|
|
This code will compile just fine. |
Such a conversion, from a smaller to a larger type, is called a widening type conversion.
What about real numbers?
With them, everything is the same — size matters:
Type | Size |
---|---|
float |
4 bytes |
double |
8 bytes |
float
variables can be assigned to double
variables without any problems. But things are more interesting with the integer types.
You can assigned any integer variable to a float
variable. Even the long
type, which is 8 bytes long. And you can assign whatever you want — any integer variable or float
variable — to a double
variable:
Code | Note |
---|---|
|
|
Note that converting to a real type may result in loss of precision due to the lack of sufficient significant digits.
When converting from integers to floating-point numbers, the lower order parts of numbers may be discarded. But since fractional numbers are understood to store approximate values, such assignment operations are permitted.
3. Narrowing type conversions
What about the other possibilities? What if you need to assign a long
value to an int
variable?
Imagine a variable as a basket. We have baskets of various sizes: 1, 2, 4 and 8 bytes. It's not a problem to transfer apples from a smaller basket to a larger one. But when shifting from a larger basket to a smaller one, some of the apples may be lost.
This transformation — from a larger type to a smaller type — is called a narrowing type conversion. When performing an assignment operation like this, part of a number may simply not fit into the new variable and may therefore be discarded.
When narrowing a type, we must explicitly tell the compiler that we are not making a mistake, that we are deliberately discarding part of the number. The typecast operator is used for this. It is a type name in parentheses.
In such situations, the Java compiler requires the programmer to specify the typecast operator. In general, it looks like this:
(type) expression
Examples:
Code | Description |
---|---|
|
Each time the typecast operator must be indicated explicitly |
Here a
is equal to 1
, and perhaps the typecast operator seems like overkill. But what if a
were bigger?
Code | Description |
---|---|
|
|
One million fits perfectly into a long
and into an int
. But when assigning one million to a short
variable, the first two bytes are discarded, and only the last two bytes are retained. And when assigning to a byte
, the only thing that remains is the last byte.
How the numbers are arranged in memory:
Type | Binary notation | Decimal notation |
---|---|---|
int |
0b00000000000011110100001001000000 | 1000000 |
short |
0b0100001001000000 | 16.960 |
byte |
0b01000000 | 64 |
char
type
A char
, like a short
, occupies two bytes, but to convert one to another, you always need to use a typecast operator. The issue here is that the short
type is signed and can contain values from -32,768
to +32,767
, but the char
type is unsigned and can contain values from 0
to 65,535
.
Negative numbers cannot be stored in a char
, but they can be stored in a short
. And a short
cannot store numbers larger than 32,767
, but such numbers can be stored in a char
.
4. Type of an expression
What if variables of different types are used in the same expression? Logically, we understand that they first need to be converted to a common type. But which one?
To the larger one, of course.
Java always converts to the larger type. Roughly speaking, one of the type is first widened and only then is the operation performed using values of the same type.
If an int
and a long
are involved in an expression, the value of the int
will be converted to a long
and only then will the operation proceed:
Code | Description |
---|---|
|
a will be widened to a long and then the addition will occur. |
Floating-point numbers
If an integer and a floating-point number (float
or double
) are involved in an expression, the integer will be converted to a floating-point number (float
or double
), and only then will the operation be performed.
If the operation involves a float
and a double
, then the float
will be converted to a double
. Which is actually expected.
The byte
, short
, and char
types are always converted to int
when interacting with each other. There's a good reason why the int
type is considered the standard integer type.
If you multiply a byte
by a short
, you get an int
. If you multiply a byte
by a byte
, you get an int
. Even if you add a byte
and a byte
, you get an int
.
There are several reasons for this. Examples:
Code | Description |
---|---|
|
110 * 120 is 13,200 , which is slightly greater than the maximum value of the byte type: 127 |
|
110 + 120 is 230 , which is also slightly greater than the maximum value of the byte type: 127 |
In general, when multiplying an 8-bit (1 byte) number by an 8-bit (1 byte) number, we get a number that occupies 16-bits bits (2 bytes)
As a result, all operations with integer types that are smaller than int
are always immediately converted to int
s. And that means that if you want to store the result of the calculation in a variable of a type that is smaller than an int
, then you will always need to explicitly specify the typecast operator.
Examples:
Code | Description |
---|---|
|
The byte * byte expression will be an int |
|
The byte + byte expression will be an int |
|
The byte + int expression will be an int The literal one is an int .
|
5. An important nuance
The typecast operator has a fairly high priority.
That means that if an expression contains, for example, addition and a typecast operator, the typecast will be performed before the addition.
Example:
Code | Description |
---|---|
|
The typecast operator will only be applied to the a variable, which is already a byte . This code will not compile. |
|
This is the correct way. |
If you want to convert the entire expression to a specific type, and not just one component of the expression, then wrap the entire expression in parentheses and put the typecast operator in front.
GO TO FULL VERSION