انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-009-L05

تحويل الأنواع والتحويل القسري (casting)

  • الدرس 5 من 7
  • 12 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

So far each box held a value of exactly its own type. Real programs mix them all the time: a whole-number quantity times a decimal price, a total divided among a number of people, a big count squeezed into a smaller box. When types meet, Java converts values from one type to another. Some conversions are safe and happen automatically. Others can destroy information, and Java makes you ask for them explicitly with a cast.

Keep the box picture. Moving a value from a small box to a bigger one always fits, like pouring a cup into a bucket. Moving from a big box to a smaller one may not fit, and something spills. This lesson teaches you to predict the spill, including the surprising case where a whole number runs past its limit and wraps around.

Why this matters: conversion bugs are among the most common silent bugs in real code. They do not crash; they produce a plausible but wrong number, such as an average of 2 instead of 2.5, or a negative count of views. Spotting them is a core skill.

This lesson builds on Lessons 1 to 4, especially the sizes and ranges of the primitive types in Lesson 2.

Learning Objectives

By the end of this lesson you will be able to:

  • Explain widening conversions (small box to bigger box) and why they happen automatically.
  • Explain narrowing conversions (big box to smaller box), and write a cast.
  • Predict that casting a decimal to an int cuts off the fraction (it does not round).
  • Predict the result of integer division and fix it when decimals are wanted.
  • Explain integer overflow and predict the wrapped result, and avoid it with long.
  • Read the compiler message possible lossy conversion.

Written Explanation

Widening: small box to bigger box

A widening conversion goes from a type to one that can hold at least the same range of values. The compiler performs it automatically, with no extra syntax:

text
byte -> short -> int -> long -> float -> double       char -> int
java
int whole = 7;
double wide = whole;    // 7 becomes 7.0, automatically

Widening between integer types is exact. Widening an int to a double is also exact. The Language Specification warns about one subtle case: converting an int or long to float, or a long to double, can lose some of the least significant digits, because a float has only about seven significant decimal digits of precision. So "widening" means "bigger range", not always "perfect copy". Example 3 shows it.

Narrowing: big box to smaller box

A narrowing conversion goes the other way, for example double to int, long to int, or int to byte. Java does not do these automatically, because information may be lost. If you try, the compiler stops you:

text
error: incompatible types: possible lossy conversion from double to int

If you really mean it, you write a cast: the target type in parentheses before the value.

java
double pi = 3.99;
int cut = (int) pi;     // 3

The cast says "I know this may lose information, and I accept that". Two rules of what happens:

  • Decimal to whole number: the fraction is cut off, not rounded. (int) 3.99 is 3, and (int) -3.99 is -3: the result moves toward zero. If you want rounding, the standard method Math.round(x) rounds to the nearest whole number (it returns a long).
  • Whole number to a smaller whole type: only the low bits are kept. The value can change completely, including its sign. For example (byte) 130 is -126.

Integer division

When both operands of / are integers, the result is an integer: the fraction is dropped. So 7 / 2 is 3, not 3.5. The remainder operator % gives what is left over: 7 % 2 is 1.

If either operand is a double, the other is widened and the division is a decimal one: 7 / 2.0 is 3.5.

Where you put the cast matters:

ExpressionResultWhy
7 / 23integer division
7 / 2.03.5one side is double
(double) 7 / 23.5the cast applies to 7 first, then decimal division
(double) (7 / 2)3.0the integer division happens first, the cast comes too late

A frequent beginner bug is double share = total / people; with two ints. The division is done in integers, then the result is widened to double. The decimals were already lost.

Integer overflow

Every whole-number type has a maximum. What happens when you go past it? Java does not raise an error. The result wraps around: it keeps only what fits, as if an odometer rolled past its largest number.

java
int max = Integer.MAX_VALUE;   // 2147483647
System.out.println(max + 1);   // -2147483648

The Language Specification says integer operators never signal overflow; results wrap, keeping the low-order bits. Adding 1 to the largest int gives the smallest. This is why overflow is a silent bug: the program continues with a wrong, often negative, number.

Overflow also hides in multiplication. 1_000_000 * 1_000_000 is a trillion, which is far outside int. Because both operands are int, Java does the whole multiplication as int and wraps. Assigning the result to a long variable afterwards does not help, because the damage is done before the assignment. The fix is to make one operand long first:

java
long ok = (long) a * a;    // a is widened to long, so the multiplication is done as long

When arithmetic mixes types, Java first widens both operands to a common type (int with long gives long; anything with double gives double), then calculates in that type.

Seeing overflow instead of hiding it

If wrapping would be a bug, the standard library provides Math.addExact(a, b) and similar methods that throw an ArithmeticException (message integer overflow) instead of wrapping. Using them is a later-module topic; it is enough to know they exist.

Examples

Example 1: widening, casting, and rounding (simple)

java
public class Casts {
    public static void main(String[] args) {
        int whole = 7;
        double wide = whole;
        System.out.println(wide);
        double pi = 3.99;
        int cut = (int) pi;
        System.out.println(cut);
        System.out.println((int) -3.99);
        System.out.println(Math.round(3.99));
        System.out.println(Math.round(2.5));
    }
}

Output (JDK 21):

text
7.0
3
-3
4
3

Widening gave 7.0 automatically. The cast cut 3.99 to 3, and -3.99 to -3 (toward zero, not down). Math.round really rounds: 3.99 becomes 4, and 2.5 becomes 3.

Example 2: division that loses its decimals (practical)

Four friends share a bill of 10:

java
public class Share {
    public static void main(String[] args) {
        int total = 10, people = 4;
        double share = total / people;
        System.out.println(share);
        System.out.println((double) total / people);
        System.out.println(7 / 2);
        System.out.println(7 / 2.0);
        System.out.println((double) (7 / 2));
    }
}

Output:

text
2.0
2.5
3
3.5
3.0

The first line is the trap: the variable is a double, yet the answer is 2.0, because total / people was an integer division that gave 2 before the widening. Casting one operand first (second line) gives the correct 2.5. The last line shows a cast that is too late.

Example 3: overflow, narrowing, and a lossy widening (realistic)

java
public class Overflow {
    public static void main(String[] args) {
        int max = Integer.MAX_VALUE;
        System.out.println(max + 1);
        System.out.println(Integer.MIN_VALUE - 1);
        int a = 1_000_000;
        System.out.println(a * a);
        long ok = (long) a * a;
        System.out.println(ok);
        long wrong = a * a;
        System.out.println(wrong);
        long big = 3_000_000_000L;
        int shrunk = (int) big;
        System.out.println(shrunk);
        System.out.println((byte) 130);
        System.out.println((int) 1e20);
        int i = 1_234_567_890;
        float f = i;
        System.out.println((int) f);
    }
}

Output:

text
-2147483648
2147483647
-727379968
1000000000000
-727379968
-1294967296
-126
2147483647
1234567936

Line by line: the maximum plus one wraps to the minimum, and the minimum minus one wraps to the maximum. a * a wraps to -727379968, the same value the Language Specification itself uses to illustrate overflow. The long version with a cast before the multiplication gives the true trillion; the "wrong" version assigns to a long but still wraps, because the multiplication was done in int. Squeezing three billion into an int gives -1294967296. (byte) 130 is -126. (int) 1e20 does not wrap: converting a too-large decimal to an int clamps to the maximum, 2147483647. The last line shows widening int to float losing digits: 1234567890 came back as 1234567936.

Backend Insight

  • Averages, percentages, and ratios. Computing an average as sum / count with two integers silently truncates. A dashboard that shows 2 instead of 2.5, or a conversion rate of 0, is usually this bug. Make one operand decimal before dividing.
  • Counters and ids that overflow. A counter of views or a running total kept in an int can wrap to a negative number after about 2.1 billion. Use long for anything that can grow without a clear limit.
  • Mixed units. Multiplying 24 * 60 * 60 * 1000 * 365 (milliseconds in a year) in int arithmetic overflows. Written as 24L * 60 * 60 * 1000 * 365, the first L makes the whole chain long and gives the correct 31536000000.
  • Boundaries between systems. Data from a database or a request often arrives as a larger type than your variable. A cast makes the narrowing visible in code, so reviewers can ask "can this value exceed the range?"

Defensive habit: when narrowing, check that the value fits first, or use an exact-arithmetic helper such as Math.addExact when wrapping would be a bug.

Common Mistakes

Mistake 1: Expecting a decimal from integer division

int average = 7 / 2; is 3. If you want 3.5, at least one operand must be a double, and the variable must be a double too.

Mistake 2: Casting too late

(double) (a / b) converts the already-truncated integer. Cast an operand: (double) a / b.

Mistake 3: Expecting a cast to round

(int) 9.99 is 9. The cast discards the fraction. Use Math.round if you want the nearest whole number.

Mistake 4: Trying to assign a wider value to a narrower variable without a cast

java
double d = 9.5;
int i = d;

The compiler (JDK 21) says incompatible types: possible lossy conversion from double to int. The same message appears for long to int, double to float, and int * 1.5 stored back into an int. Either change the variable to the wider type, or write a cast knowing the cost.

Mistake 5: Assuming small types stay small in arithmetic

java
byte b = 10;
b = b + 1;     // error: possible lossy conversion from int to byte

Arithmetic on byte, short, and char is done in int, so the result is an int and must be cast back. This is one reason beginners should prefer int. Similarly char c = 'A'; c = c + 1; does not compile without (char).

Mistake 6: Believing overflow will warn you

Overflow does not warn or crash. A program can print -2147483648 and carry on. Whenever numbers can get large, ask whether int is big enough.

Exercises

Try each exercise yourself before opening the solutions. Compile and run them if you have a JDK.

Level A: Foundation

A1. Which conversion needs a cast: int to long, or long to int? Why?

أظهر الحل A1

A1. long to int needs a cast, because the long may hold values the int cannot. int to long is a widening conversion and is automatic.

A2. What do 9 / 4, 9 / 4.0, and 9 % 4 evaluate to?

أظهر الحل A2

A2. 2, 2.25, and 1.

A3. What is (int) 5.9, and what is (int) -5.9?

أظهر الحل A3

A3. 5 and -5: the fraction is cut off toward zero.

Level B: Application

B1. A student has int points = 17; and int tests = 4;. Write the line that computes average as a double, correct to the decimal part, and give its value.

أظهر الحل B1

B1. double average = (double) points / tests; gives 4.25. Writing points / tests first would give 4.

B2. Predict the output:

java
byte small = (byte) 127;
small = (byte) (small + 1);
System.out.println(small);
أظهر الحل B2

B2. 127 + 1 is 128, which does not fit a byte. Keeping the low 8 bits gives -128, so it prints -128. The wrap-around is the same idea as with int, applied to a smaller box.

B3. Explain why long seconds = 60 * 60 * 24 * 365 * 100; is risky, and rewrite it safely. (A hundred years of seconds is about 3.15 billion.)

أظهر الحل B3

B3. All the literals are int, so the multiplication runs in int arithmetic and the result (about 3.15 billion) exceeds the int maximum before it is stored in the long. Fix: make one operand long first, long seconds = 60L * 60 * 24 * 365 * 100;. It gives 3153600000.

Level C: Challenge

C1. A program reads int widthPx = 1920; and int heightPx = 1080; and wants the aspect ratio as a decimal (about 1.78). The programmer writes double ratio = widthPx / heightPx; and gets 1.0. Explain exactly where the information is lost and give two correct fixes.

أظهر الحل C1

C1. Both operands are int, so 1920 / 1080 is an integer division: it gives 1. That 1 is then widened to 1.0. The decimals were lost before the assignment. Fixes: (double) widthPx / heightPx, or widthPx / (double) heightPx, or change one variable to double. Either gives about 1.78.

C2. int a = 2_000_000_000; int b = 2_000_000_000; long sum = a + b; prints a negative number. Explain why, predict the value, and write two fixes.

أظهر الحل C2

C2. Both operands are int, so a + b is computed as an int. The true sum is 4,000,000,000, which is more than Integer.MAX_VALUE, so it wraps to 4,000,000,000 - 4,294,967,296 = -294,967,296. Assigning to a long afterwards cannot recover the lost value. Fixes: long sum = (long) a + b;, or declare a and b as long.

Mini Challenge

Write a program Checks that prints, with a short label each: (1) the average of the three numbers 7, 8, and 10 as a double; (2) the number of seconds in 30 days, using a long; (3) Integer.MAX_VALUE + 1 with a one-line comment saying why the result is negative; (4) the result of narrowing 2.99 to an int.

Check your work: the average is about 8.33 and not 8; the seconds figure is 2592000; the overflow comment mentions that the value wraps around; and the cast result is 2, not 3.

Summary & Next Lesson

Key takeaways

  • Widening (small to big, for example int to double) is automatic; narrowing (big to small) needs a cast and may lose information.
  • A cast from decimal to whole cuts off the fraction; it does not round. Math.round rounds.
  • / on two integers is integer division: 7 / 2 is 3. Make one operand double to get 3.5, and do it before dividing.
  • Integer overflow wraps around silently: Integer.MAX_VALUE + 1 is -2147483648.
  • Use long where values can get large, and make an operand long before the arithmetic.

Next lesson

You can now predict what happens when types mix. The next lesson, "Local type inference with var", shows how to let the compiler work out a local variable's type from its value, and when that helps or hurts readability.

Self-check

  1. Is double d = 5; allowed without a cast? Why?
  2. What is (int) 2.99?
  3. What is 5 / 2, and how do you get 2.5?
  4. What does Integer.MAX_VALUE + 1 evaluate to?
  5. Why does storing a * a in a long not fix an overflow in a * a when a is an int?
أظهر الإجابات
  1. Yes: int to double is a widening conversion, which is automatic.
  2. 2.
  3. 2. Use 5 / 2.0 or (double) 5 / 2.
  4. -2147483648: the value wraps around.
  5. The multiplication is performed in int arithmetic before the result is stored, so it has already wrapped. Make one operand long first.

References

  • Oracle, The Java Language Specification, Java SE 21, Chapter 5: Conversions and Contexts. It lists the widening conversions and notes that int to float and long to float/double can lose precision, says narrowing from a floating-point type to an integer type truncates toward zero and clamps values that are too large (for example (int) 1e20f is the int maximum), and says narrowing between integer types keeps only the low-order bits (so (byte) 255 is -1).
  • Oracle, The Java Language Specification, Java SE 21, section 4.2.2: Integer Operations. It says integer operators never signal overflow and that results wrap, and it uses 1000000 * 1000000 as an example producing -727379968.
  • Oracle, The Java Tutorials: Primitive Data Types. It gives the sizes and ranges of the integer types.
  • Every output above, including ArithmeticException: integer overflow from Math.addExact, was produced by running the code with JDK 21, and the exercise answers were checked the same way. Math.round behavior was observed in the run, not read from its documentation.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.