Basic Java Questions and Answers (Part 1) - Type-Casting

I’ve compiled a list of questions and answers about fundamental topics in Java that I believe every Java developer should be aware of. This is part 1, covering the concepts of type-casting, upcasting and downcasting.

What is type-casting?

it refers to the process of converting from one data type to another.

What are the conditions for type-casting?

To convert between data types:

  • the data types must be compatible. We can convert an integer to a float or vice versa, but we can't convert between an integer and a String. A compile error will be thrown in this case.

  • The data type that we convert into must be larger than the original data type. Otherwise, Java will complain. However, this is still possible with a workaround, which we will discuss in the next section.

What are the categories of type-casting?

There are two types of type-casting:

  • Widening conversion (implicit casting): If we cast a smaller data type to a larger one, Java compiler will automatically perform the casting. For example, if we want to convert from an integer to a double, we only need to assign the integer value to a double because integers are smaller (32 bits) than doubles (64 bits), so there is no risk of data loss after conversion. Here's a code snippet to demonstrate this:

      double number = 10;      //output = 10.0, which is a double
    

    We declared a double variable and assigned to it an integer value of 10. Since an integer is smaller than a double, the double will easily have more than enough room to store that value.

  • Narrowing conversion (explicit casting): If we cast a larger data type to a smaller one, Java compiler will complain and will not perform the conversion. For instance, attempting to cast a double to an integer will trigger a compile error. To solve this, we can explicitly cast the value to the smaller data type using the casting operator ():

      int number = (int) 10.50;  //output = 10, which is an integer
    

    Contrary to the previous example, the double value of 10.50 is assigned to an integer. Without an explicit operator, a portion of data would be lost since it's larger than what an integer can hold. So we use the casting operator (int) next to the double value so that it gets converted and stored as an integer.

Type-casting with primitive types vs reference types

type-casting not only works works on primitive types, but on reference types as well, although in slightly different ways due to the inherent difference between primitive and reference type variables.

What is Upcasting?

Upcasting is a process of implicitly casting a subclass to a superclass and is closely related to inheritance. Before we further discuss this, let's look an example:

public class Vehicle {
    private String name;

    public Vehicle() {}
}

public class Car extends Vehicle {
    private String model;

    public Car() {
        super();
    }

    public String getModel() {
        return model;
    }
}

As you can see, there are two classes, Vehicle and Car. The Car class extends or inherits Vehicle. In this scenario, when we create an object of type Car, we can either assign it to a reference of type Car class like so:

Car vehicle = new Car();

Or assign it to a reference of type Vehicle, which is the superclass:

Vehicle vehicle = new Car();

This is an example of upcasting where the compiler implicitly casts the subclass to the superclass.

Using upcasting, we can access all methods defined in the superclass from the subclass but we're restricted from calling the methods in the sublcass itself. If we attempt to call a method in the subclass based on the last example, the compiler will complain:

Vehicle vehicle = new Car();
// vehicle.getModel(); Cannot resolve method 'getModel' in 'Vehicle'

What is Downcasting?

Downcasting is the reverse of upcasting. It’s a process of converting from the superclass to the subclass.

Let’s modify the previous example:

// Upcasting
Vehicle vehicle = new Car();

// Downcasting
if(vehicle instanceof Vehicle) {
    Car car = (Car) vehicle;
    car.driveCar();
}

We use instanceof to check if the vehicle reference is an instance of Vehicle class before explicitly downcasting using the (Car) operator. This is a way to prevent ClassCastException at runtime.

Downcasting is particularly useful in cases where we have an instance of a superclass but we need to use the fields and methods of a subclass. Since we can only access superclass fields and methods in this scenario, downcasting is an approach to solve this problem.

There you have it. This is part one of the series of Java questions and answers. I hope what we've discussed so far was helpful. Stay tuned for part two.