instanceof Keyword in Java

In the world of Java programming, the ability to accurately determine the type of an object is essential for effective and reliable code. This is where the “instanceof” operator comes into play. The instanceof the operator serves as a powerful tool that allows developers to ascertain whether an object is an instance of a particular class or interface. This article delves into the fundamentals of the Java instanceof operator, its purpose, syntax, and practical applications.

Java instanceof Operator:

The instanceof operator is utilized to verify if an object belongs to a specific class or adheres to a certain interface. This enables you to confirm the object’s type before carrying out any actions specific to that type. The instanceof operator in Java provides a true or false outcome, indicating whether the object is indeed an instance of the specified type.

Here’s the general syntax of the instanceof operator:

Object instanceof Type

Where an object is the instance you want to check, and Type is the class or interface you want to compare against.

Example Code:

class Shape {
    public void draw() {
        System.out.println("Drawing a shape.");
    }
}
class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle.");
    }
}
class Square extends Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a square.");
    }
}
class DataFlair{
    public static void main(String[] args) {
        Shape shape1 = new Circle();
        Shape shape2 = new Square();
        // Using instanceof to check the type of shape1
        if (shape1 instanceof Circle) {
            System.out.println("shape1 is a Circle.");
        } else if (shape1 instanceof Square) {
            System.out.println("shape1 is a Square.");
        } else {
            System.out.println("shape1 is a different type of shape.");
        }
        // Using instanceof to check the type of shape2
        if (shape2 instanceof Circle) {
            System.out.println("shape2 is a Circle.");
        } else if (shape2 instanceof Square) {
            System.out.println("shape2 is a Square.");
        } else {
            System.out.println("shape2 is a different type of shape.");
        }
    }
}

Output:

shape1 is a Circle.
shape2 is a Square.

Explanation:

In this illustration, we establish a structure of geometric shapes, comprising a fundamental class named Shape and extending to two specialized variations: Circle and Square. We generate instances of these distinct classes and subsequently employ the instanceof operator to scrutinize their respective classifications.

instanceof with a variable that has a null value:

When using the instanceof operator with a variable that has a null value, Java will not throw an exception. Instead, it will evaluate to false for any type you check against. This is because null is not an instance of any class or interface, so the result of the instanceof operator will be false regardless of the type you’re checking.

Example Code:

class Vehicle {
    public void start() {
        System.out.println("Vehicle started.");
    }
}
class Car extends Vehicle {
    @Override
    public void start() {
        System.out.println("Car started.");
    }
}
class Bike extends Vehicle {
    @Override
    public void start() {
        System.out.println("Bike started.");
    }
}
class DataFlair{
    public static void main(String[] args) {
        Vehicle vehicle = null;
        if (vehicle instanceof Car) {
            System.out.println("vehicle is a Car.");
        } else if (vehicle instanceof Bike) {
            System.out.println("vehicle is a Bike.");
        } else if (vehicle instanceof Vehicle) {
            System.out.println("vehicle is a generic Vehicle.");
        } else {
            System.out.println("vehicle is null.");
        }
    }
}

Output:

the vehicle is null.

Explanation:

In this example, we have a class hierarchy with a base class Vehicle and two subclasses Car and Bike. We declare a vehicle variable and set it to null. Then, we use the instanceof the operator to check the type of vehicle.

Even though the vehicle variable is null, the instanceof checks will evaluate to false for all the class types, including the base class Vehicle, and it will only match the last case for null.

Parent reference is not an instanceof child:

If you have a parent object and you want to check if it’s an instance of a child class, the instanceof operator will return false. In Java, an instance of a parent class is not considered an instance of its child classes.

Example code:

class Parent {
    // Some methods and fields
}
class Child extends Parent {
    // Some methods and fields specific to Child
}
class DataFlair{
    public static void main(String[] args) {
        Parent parent = new Parent();
        Child child = new Child();
        // Check if parent is an instance of Child
        if (parent instanceof Child) {
            System.out.println("Parent is an instance of Child.");
        } else {
            System.out.println("Parent is not an instance of Child.");
        }
        // Check if child is an instance of Parent
        if (child instanceof Parent) {
            System.out.println("Child is an instance of Parent.");
        } else {
            System.out.println("Child is not an instance of Parent.");
        }
    }
}

Output:

The parent is not an instance of the Child.
The child is an instance of a Parent.

Explanation:

As you can see, the parent object is not considered an instance of the child class using the instanceof operator. However, the child object is considered an instance of the parent class.

This behaviour is consistent with the concept of class hierarchy and inheritance in Java, where a parent class is a more general type, and an instance of a child class is a more specialized type.

When a reference to a child class is used through a parent class reference, it is considered an instance of the child class:

If a parent class reference is referring to an actual instance of a child class, then the instanceof operator will correctly identify it as an instance of the child class. This is because the object being referred to is actually an instance of the child class.

Example Code:

class Parent {
    // Some methods and fields
}

class Child extends Parent {
    // Some methods and fields specific to Child
}

class DataFlair{
    public static void main(String[] args) {
        Parent parentReferenceToChild = new Child();

        // Check if parentReferenceToChild is an instance of Child
        if (parentReferenceToChild instanceof Child) {
            System.out.println("parentReferenceToChild is an instance of Child.");
        } else {
            System.out.println("parentReferenceToChild is not an instance of Child.");
        }
    }
}

Output:

parentReferenceToChild is an instance of Child.

Explanation:

When a parent class reference is pointing to an actual instance of a child class, the instanceof operator will correctly identify it as an instance of the child class. This behaviour is essential for polymorphism and dynamic method binding in Java.

Downcasting with instanceof operator:

Downcasting in Java involves casting an object of a more general type (a superclass or interface) to a more specific type (a subclass). The instanceof operator is often used before downcasting to ensure that the object is of the expected type to avoid potential ClassCastException at runtime.

Example Code:

class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound.");
    }
}
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks.");
    }
    public void fetch() {
        System.out.println("Dog fetches a ball.");
    }
}
class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Cat meows.");
    }
    public void scratch() {
        System.out.println("Cat scratches.");
    }
}
class DataFlair{
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();
        if (animal1 instanceof Dog) {
            Dog dog = (Dog) animal1; // Downcasting
            dog.makeSound();
            dog.fetch();
        } else if (animal1 instanceof Cat) {
            Cat cat = (Cat) animal1; // Downcasting
            cat.makeSound();
            cat.scratch();
        }
        if (animal2 instanceof Dog) {
            Dog dog = (Dog) animal2; // This block won't execute
            dog.makeSound();
            dog.fetch();
        } else if (animal2 instanceof Cat) {
            Cat cat = (Cat) animal2; // Downcasting
            cat.makeSound();
            cat.scratch();
        }
    }
}

Output:

Dog barks.
The dog fetches a ball.
Cat meows.
Cat scratches.

Explanation:

In this example, we have a class hierarchy with Animals as the base class and Dogs and Cats as subclasses. We create instances of Dog and Cat and then use the instanceof operator to perform downcasting safely based on the object’s actual type.

Downcasting without the use of instanceof Operator:

Downcasting without using instanceof requires having some form of guarantee about the actual type of the object you’re casting. This typically involves having some contextual information or a design pattern that ensures the type safety of the cast.

Example Code:

class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound.");
    }
}
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks.");
    }
    public void fetch() {
        System.out.println("Dog fetches a ball.");
    }
}
class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Cat meows.");
    }
    public void scratch() {
        System.out.println("Cat scratches.");
    }
}
class DataFlair{
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();
        // Downcasting through method call
        processAnimal(animal1);
        processAnimal(animal2);
    }
    public static void processAnimal(Animal animal) {
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.makeSound();
            dog.fetch();
        } else if (animal instanceof Cat) {
            Cat cat = (Cat) animal;
            cat.makeSound();
            cat.scratch();
        }
    }
}

Output:

Dog barks.
The dog fetches a ball.
Cat meows.
Cat scratches.

Explanation:

In this example, the downcasting is done inside the processAnimal method, which takes an Animal parameter. Within the method, we know the actual type of the object based on the method being called (make sound, fetch, or scratch), so we perform the appropriate downcasting and subsequent method calls.

This approach encapsulates the downcasting and behaviour specific to each subclass within the method, avoiding the need to use instanceof directly in the main part of the code.

instanceof in Interface:

You can use the instanceof operator with interfaces just like you do with classes. The instanceof operator allows you to check whether an object is an instance of a particular class or interface, including interfaces that the class may implement.

Example Code:

interface Vehicle {
    void start();
}
class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("Car started.");
    }
    public void drive() {
        System.out.println("Car is driving.");
    }
}
class Bike implements Vehicle {
    @Override
    public void start() {
        System.out.println("Bike started.");
    }
    public void ride() {
        System.out.println("Bike is being ridden.");
    }
}
class DataFlair{
    public static void main(String[] args) {
        Vehicle car = new Car();
        Vehicle bike = new Bike();

        performVehicleActions(car);
        performVehicleActions(bike);
    }
    public static void performVehicleActions(Vehicle vehicle) {
        vehicle.start();
        if (vehicle instanceof Car) {
            Car car = (Car) vehicle;
            car.drive();
        } else if (vehicle instanceof Bike) {
            Bike bike = (Bike) vehicle;
            bike.ride();
        }
    }
}

Output:

Car started.
The car is driving.
Bike started.
The bike is being ridden.

Explanation:

In this example, we have a Vehicle interface and two classes, Car and Bike, that implement the Vehicle interface. We create instances of these classes and pass them to a method called performVehicleActions, which calls the start method common to all vehicles and performs additional actions based on the specific type of vehicle using downcasting.

Application of instanceof operator:

The instanceof keywords in Java has several applications, mainly related to polymorphism, type checking, and safe casting.

Example:

class HotelRoom {
    int roomNumber;

    HotelRoom(int roomNumber) {
        this.roomNumber = roomNumber;
    }

    void displayInfo() {
        System.out.println("Room Number: " + roomNumber);
    }
}

class DeluxeRoom extends HotelRoom {
    int extraBedCost;

    DeluxeRoom(int roomNumber, int extraBedCost) {
        super(roomNumber);
        this.extraBedCost = extraBedCost;
    }

    @Override
    void displayInfo() {
        super.displayInfo();
        System.out.println("Room Type: Deluxe");
        System.out.println("Extra Bed Cost: $" + extraBedCost);
    }
}

class SuiteRoom extends HotelRoom {
    boolean hasJacuzzi;

    SuiteRoom(int roomNumber, boolean hasJacuzzi) {
        super(roomNumber);
        this.hasJacuzzi = hasJacuzzi;
    }

    @Override
    void displayInfo() {
        super.displayInfo();
        System.out.println("Room Type: Suite");
        System.out.println("Has Jacuzzi: " + (hasJacuzzi ? "Yes" : "No"));
    }
}

public class HotelExample {
    public static void main(String[] args) {
        HotelRoom room1 = new DeluxeRoom(101, 50);
        HotelRoom room2 = new SuiteRoom(202, true);

        displayRoomInfo(room1);
        displayRoomInfo(room2);
    }

    static void displayRoomInfo(HotelRoom room) {
        System.out.println("Room Information:");
        room.displayInfo();

        if (room instanceof DeluxeRoom) {
            DeluxeRoom deluxeRoom = (DeluxeRoom) room;
            System.out.println("Total Cost (including extra bed): $" + (deluxeRoom.extraBedCost + 100));
        } else if (room instanceof SuiteRoom) {
            SuiteRoom suiteRoom = (SuiteRoom) room;
            System.out.println("Total Cost: $" + (suiteRoom.hasJacuzzi ? 200 : 150));
        }
        
        System.out.println();
    }
}

Output:

Room Information:
Room Number: 101
Room Type: Deluxe
Extra Bed Cost: $50
Total Cost (including extra bed): $150

Room Information:
Room Number: 202
Room Type: Suite
Has Jacuzzi: Yes
Total Cost: $200

instanceof operator and generics:

Generics:

Generics allow you to design classes, interfaces, and methods that operate on various types while providing type safety at compile time. They enable you to write reusable and type-safe code without explicitly specifying the type in each usage.

Class Hierarchy:

In the provided example, we have a class hierarchy with three classes: Animal, Dog, and Cat. Dog and Cat are subclasses of Animal. Each class has a make-sound method that outputs a sound specific to the animal.

Generic Class Zoo<T>:

The Zoo class is a generic class that takes a type parameter T, which is constrained to extend the Animal class (T extends Animal). This ensures that only subclasses of Animal can be used as the type argument for Zoo.

The Zoo class has an instance variable resident of type T, representing the animal residing in the zoo. It also has a constructor to set the resident animal and a method performSound that invokes the makeSound method of the resident animal.

Using Generics:

In the main method, we create instances of Dog and Cat. Then, we instantiate Zoo objects for each animal type using the appropriate constructor.

Method animal sound:

The animalSound method takes a parameter of type Zoo<? Extends Animal>. This means it can accept any Zoo object parameterized with a subtype of Animal. The ? extends Animal wildcard ensures that the parameter is a Zoo containing an animal or its subtype.

instanceof Operator:

In the animal sound method, we use the instanceof operator to check if the parameter is indeed an instance of Zoo. This ensures that we are working with a valid Zoo object before attempting to invoke the performSound method.

Advantages:

The combination of the instanceof operator and generics provides type safety and flexibility. We can confidently call the performSound method on a Zoo object, knowing that the type parameter extends Animal and thus supports the makeSound operation.

Example Code:

class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

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

class Cat extends Animal {
    void makeSound() {
        System.out.println("Cat meows");
    }
}

class Zoo<T extends Animal> {
    private T resident;

    public Zoo(T resident) {
        this.resident = resident;
    }

    public void performSound() {
        resident.makeSound();
    }
}

public class InstanceOfGenericsExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();

        Zoo<Dog> dogZoo = new Zoo<>(dog);
        Zoo<Cat> catZoo = new Zoo<>(cat);

        animalSound(dogZoo);
        animalSound(catZoo);
    }

    static void animalSound(Zoo<? extends Animal> zoo) {
        if (zoo instanceof Zoo<?>) {
            zoo.performSound();
        }
    }
}

Output:

Dog barks
Cat meows

By leveraging generics and the instanceof operator, we achieve a clear separation of concerns and a higher degree of code reusability. This approach is particularly useful when dealing with a hierarchy of related classes where different operations need to be performed based on the specific subtype of an object.

Stream API – Filtering Types Using instanceof:

In the realm of Java programming, the Stream API emerges as a potent and succinct tool for handling data collections. A prevalent situation within the stream context involves sieving elements by their class type using the instanceof operator, subsequently followed by precise casting procedures. This systematic approach serves to bolster type security and preempt potential runtime glitches.

This article delves into the adept utilization of the Stream API for segregating elements by their types and seamlessly executing casts solely on the fitting elements.

Imagine a scenario where an array of objects contributes to a class hierarchy, and the aim is to execute targeted actions exclusively on objects of specific subtypes. To fulfill this objective, the Stream API proves invaluable in sieving objects based on their class using the instanceof operator, paving the way for judicious casting operations. This strategic application guarantees the fortification of type integrity and averts untimely runtime irregularities. Our exploration will delve into the pragmatic deployment of the Stream API, empowering efficient element filtration by type and subsequent casting focused solely on pertinent entities.

Example code:

import java.util.*;
import java.util.stream.*;

class Shape {
    void draw() {
        System.out.println("Drawing a shape");
    }
}

class Circle extends Shape {
    void draw() {
        System.out.println("Drawing a circle");
    }
}

class Rectangle extends Shape {
    void draw() {
        System.out.println("Drawing a rectangle");
    }
}

public class StreamInstanceOfExample {
    public static void main(String[] args) {
        List<Shape> shapes = Arrays.asList(
            new Circle(),
            new Rectangle(),
            new Circle(),
            new Rectangle(),
            new Circle()
        );

        List<Circle> circles = shapes.stream()
            .filter(shape -> shape instanceof Circle)
            .map(shape -> (Circle) shape)
            .collect(Collectors.toList());

        circles.forEach(Circle::draw);
    }
}

Output:

Drawing a circle
Drawing a circle
Drawing a circle

Benefits and Considerations:

Using the Stream API with the instanceof operator before casting offers several benefits:

Type safety: Filtering by type ensures that only compatible objects are cast, reducing the risk of ClassCastException at runtime.

Clean code: The stream operations provide a concise and expressive way to filter and process elements by type.

Readability: The code clearly communicates the intention of filtering and casting, making it easier for others to understand.

Conclusion

As you embark on your journey as a Java programmer, the instanceof operator beckons as a reliable ally. Its nuanced capabilities, harnessed through elegant syntax, grant you the means to optimize your code for clarity, stability, and resilience. By mastering the art of instanceof, you elevate your coding prowess and pave the way toward crafting Java applications that not only run but thrive in the dynamic realms of modern software development.