Search code examples
javaabstract-classoption-type

How to properly unwrap an optional in Java?


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..


Solution

  • 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.