How to properly unwrap an optional in Java?

Tags: , ,



I am learning the basics of Java and I am exploring Optionals and abstract classes so I came across the following issue,

I have this code

import java.util.Optional;

public abstract class Animal {
    abstract void makeSound();
    
    public static void main(String[] args) {
        System.out.println("Start");
        
        Dog dog = new Dog();
        Cat cat = new Cat();
        Horse horse = new Horse();
        
        
        Animal[] animals = {dog, cat, horse};
        
        for (Animal animal : animals) {
            
            Optional<Dog> _dog = animal instanceof Dog ? Optional.of((Dog) animal) : null;
            Optional<Cat> _cat = animal instanceof Cat ? Optional.of((Cat) animal) : null;
            Optional<Horse> _horse = animal instanceof Horse ? Optional.of((Horse) animal) : null;
            
            if (_dog.isPresent()) {
                System.out.println("it is a Dog");
            } else {
                System.out.println("it is NOT a Dog");
            }
                        
            animal.makeSound();
        }
    
    }
}

class Horse extends Animal {
    String speed = "Fast";
    @Override
    void makeSound() {
        System.out.println("Neighing...");
    }
}

class Dog extends Animal {
    String color = "Brown";
    @Override
    void makeSound() {
        System.out.println("Barking...");
    }
}

class Cat extends Animal {
    Integer lives = 9;
    @Override
    void makeSound() {
        System.out.println("Mewoing......");
    }
}

I was expecting to see the prints on the console “It is a Dog” followed by 2 “It is not a Dog” Since I’m using the method .isPresent() on optionals,

But I got 1 print and then a NullPointerException:

That’s what I got printed:

Start
it is a Dog
Barking...
Exception in thread "main" java.lang.NullPointerException
    at com.example.helloworldeclipse.Animal.main(Animal.java:24)

Shouldn’t isPresent be safe? is there a better way to cast abstract classes to subclasses in situations similar like this?

I don’t get why it’s not working.. What am I doing wrong here?

Thank you in advance for all the answers..

Answer

The problem is that you are assigning null to an Optional reference when the instanceof check fails.

You have at least two options:

  1. Use Optional.empty() instead of null

    Optional<Dog> dog = animal instanceof Dog ? Optional.of((Dog) animal) : Optional.empty();
    
  2. Use Optional’s filter and map methods:

    Optional<Dog> dog = Optional.of(animal)
        .filter(Dog.class::isInstance)
        .map(Dog.class::cast);
    

    The predicate Dog.class::isInstance checks if the given instance, which is the value within the Optional, is an instance of Dog. This is equivalent to instanceof. Then Dog.class::cast casts the given object to a Dog instance.


Note: If animal itself could be null, then you should use Optional::ofNullable instead of Optional::of.



Source: stackoverflow