Hi, today we will learn the topic of the Java Type Casting. You can study the material either in video format with a CodeGym mentor or in a more detailed text version with me below.

What is Type Casting in Java?

A data type is a predefined set of values that specify the type of values that can be stored in it along with the operation that can be carried out on them.
The Java Type Casting is a process by which one data type is converted to another.
It can be both implicit and explicit. Implicit typecasting also known as automatic typecasting is done by the compiler. Explicit typecasting is done manually by the programmer in the code.

Why is Type Casting Required?

Java has different primitive data types which require different spaces in memory. This can cause compatibility issues while assigning a value of one data type to another. If the datatypes are already compatible, typecasting is done automatically by the compiler. Thus, typecasting solves a major compatibility problem when dealing with different data types in our program.

Types of Java Type Casting

There are two types of type casting in Java.
  1. Widening Type Casting — Also known as Implicit or Automatic Type Casting
  2. Narrowing Type Casting — Also known as Explicit or Manual Type Casting

Widening Type Casting

Widening typecasting, as the name suggests, refers to the widening of a smaller data type to a larger data type. We perform this typecasting when we want to convert a small type to a large type. The data types must be compatible with each other. There is no implicit conversion from numeric to char or boolean type. In Java, char and boolean types are non-compatible.
byte -> short -> char -> int -> long -> float -> double
This type of casting is done automatically by the compiler without any loss of information. It does not require any external trigger by the programmer.

Example


//Automatic type conversion
public class WideningExample {
   public static void main(String[] args) {

       int i = 100;
       System.out.println("int value: " + i);

       // int to long type
       long l = i;
       System.out.println("int to long value: " + l);

       // long to float type
       float f = l;
       System.out.println("int to float value: " + f);


       byte b = 1;
       System.out.println("byte value: " + b);

       // byte to int type
       i = b;
       System.out.println("byte to int value: " + i);

       char c = 'a';
       System.out.println("char value: " + c);

       // char to int type
       i = c;

       // prints the ASCII value of the given character
       // ASCII value of 'a' = 97
       System.out.println("char to int value: " + i);
   }
}

Output

int value: 100 int to long value: 100 int to float value: 100.0 byte value: 1 byte to int value: 1 char value: a char to int value: 97

Explanation

In the code above, we have shown widening typecasting which is done by the compiler automatically. First of all, we assigned values to an int, byte, and char. We then assigned the int values to a long and float, both of which are larger than int. We also assigned the byte and char values to int. Both byte and char are smaller data types than int, thus, these conversions were implicit.

Narrowing Type Casting

Narrowing typecasting, as the name suggests, refers to the narrowing of a larger data type to a smaller data type. We perform this typecasting when we want to convert a large type to a small type.
double -> float -> long -> int -> char -> short -> byte
For this type of casting, we override the Java’s default conversion by specifying our own conversion. To achieve this, we write the variable or value that needs to be typecasted preceded by the target data type in parentheses ‘()’. However, this type of casting may result in a possible loss of precision.

Example


//Manual Type Conversion
public class NarrowingExample {
   public static void main(String[] arg) {

       // double data type
       double d = 97.04;
       // Print statements
       System.out.println("double value: " + d);

       // Narrowing type casting from double to long
       // implicitly writing the target data type in () followed by initial data type
       long l = (long) d;

       // fractional part lost - loss of precision
       System.out.println("long value: " + l);

       // Narrowing type casting from double to int
       // implicitly writing the target data type in () followed by initial data type
       int i = (int) l;

       // fractional part lost - loss of precision
       System.out.println("int value: " + i);

       // Narrowing type casting from double to int
       // implicitly writing the target data type in () followed by initial data type
       char c = (char) i;

       // displaying character corresponding to the ASCII value of 100
       System.out.println("char value: " + c);
   }
}

Output

double value: 97.04 long value: 97 int value: 97 char value: a

Explanation

Narrowing typecasting needs to be done explicitly by the programmer using a standard syntax. In the program above, we have started with a double value which is larger than the long and int data types. We typecasted this double to long and int by using parentheses with the desired target data types. We have also manually typecasted ‘int’ to ‘char’. Java Type Casting - 1

Whether you’re working with primitive types or objects, understanding how to convert one type to another is essential. Let's dive into explicit upcasting and downcasting and see how they work in action.

What is Explicit Upcasting?

Upcasting in Java refers to converting a subclass object into a superclass type. This happens automatically when assigning a subclass object to a superclass reference, but it can also be done explicitly. Upcasting allows you to treat objects in a generalized way, making your code more flexible and reusable.

Here’s an example of explicit upcasting:

class Animal {
            void makeSound() {
                System.out.println("Some generic animal sound");
            }
        }

        class Dog extends Animal {
            void makeSound() {
                System.out.println("Bark!");
            }
        }

        public class UpcastingExample {
            public static void main(String[] args) {
                Dog dog = new Dog();
                Animal animal = (Animal) dog; // Explicit upcasting
                animal.makeSound(); // Calls Dog's overridden method
            }
        }

Even though animal now refers to a Dog object, it still calls the overridden makeSound() method from the Dog class. This is a great example of polymorphism in action!

What About Explicit Downcasting?

Downcasting is the opposite of upcasting. It converts a superclass reference back into a subclass reference. Unlike upcasting, downcasting is not automatic—it must be done explicitly. But be careful! If the object being cast is not actually an instance of the subclass, Java will throw a ClassCastException at runtime.

Let’s see downcasting in action:

class Animal {
            void makeSound() {
                System.out.println("Some generic animal sound");
            }
        }

        class Dog extends Animal {
            void makeSound() {
                System.out.println("Bark!");
            }
            void wagTail() {
                System.out.println("Tail wagging!");
            }
        }

        public class DowncastingExample {
            public static void main(String[] args) {
                Animal animal = new Dog(); // Upcasting (Implicit)
                
                if (animal instanceof Dog) { // Always check before downcasting!
                    Dog dog = (Dog) animal; // Explicit downcasting
                    dog.makeSound();
                    dog.wagTail(); // Now we can call Dog-specific methods
                } else {
                    System.out.println("Downcasting not possible!");
                }
            }
        }

The instanceof operator ensures safe downcasting by verifying that the object is actually an instance of the subclass before attempting the cast. Without this check, attempting to cast a generic Animal to a Dog could result in a runtime error.

Why Do Upcasting and Downcasting Matter?

Upcasting makes your code more flexible by allowing you to treat specific objects as more general types. This is useful when designing reusable APIs. Downcasting, on the other hand, lets you regain access to subclass-specific behavior when needed. But always use downcasting carefully—incorrect usage can lead to exceptions.

Type Casting Between Numeric Types and Strings

In Java, converting between numeric types and strings is a frequent requirement, especially when working with user input, data processing, or displaying results. Let's explore examples of both scenarios:

1. Converting Numeric Types to Strings

To convert a numeric value to a string, Java provides simple methods like String.valueOf() and concatenation with an empty string.


public class NumericToStringExample {
    public static void main(String[] args) {
        int number = 123;
        // Using String.valueOf()
        String str1 = String.valueOf(number);
        System.out.println("String representation (method 1): " + str1);

        // Using concatenation
        String str2 = number + "";
        System.out.println("String representation (method 2): " + str2);
    }
}

2. Converting Strings to Numeric Types

To convert a string to a numeric type, Java offers parsing methods such as Integer.parseInt() and Double.parseDouble().


public class StringToNumericExample {
    public static void main(String[] args) {
        String str = "456";
        // Convert to int
        int intValue = Integer.parseInt(str);
        System.out.println("Integer value: " + intValue);

        // Convert to double
        double doubleValue = Double.parseDouble(str);
        System.out.println("Double value: " + doubleValue);
    }
}

Note: Ensure that the string being converted is a valid numeric representation; otherwise, it will throw a NumberFormatException.

Real-life Example: Type Casting in a Practical Application

Type casting often plays a critical role in real-world applications, such as in e-commerce systems where price calculations and formatting require seamless conversion between numeric and string types.

Example: Calculating Total Price


import java.util.Scanner;

public class PriceCalculator {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Input price as string
        System.out.print("Enter the price of the item: ");
        String priceStr = scanner.nextLine();

        // Input quantity as string
        System.out.print("Enter the quantity: ");
        String quantityStr = scanner.nextLine();

        // Convert strings to numeric types
        double price = Double.parseDouble(priceStr);
        int quantity = Integer.parseInt(quantityStr);

        // Calculate total
        double totalPrice = price * quantity;

        // Convert total price to string for display
        String totalPriceStr = String.format("%.2f", totalPrice);

        System.out.println("Total Price: $" + totalPriceStr);
    }
}

In this example, we use type casting to convert user input (strings) into numeric types for calculation. After computing the total price, we format it back into a string for display purposes.

Conclusion

By the end of this post, we hope you have got yourself familiarized with the Java Type Casting in detail. You have learned the two types of typecasting in java. You have also learned how to manually cast the non-compatible data types using Narrowing typecasting. You can try other combinations of data types with different values on your own to understand it in more depth. Keep practising for a deeper command of the concept. Till then, keep growing and keep shining!