user842225
user842225

Reputation: 5979

compiler complains unhandled IOException even though function signature throws it

I have a class, in which the static factory method uses computeIfAbsent() to return an instance:

public class TrafficMonitor {
   private static ConcurrentMap<String, TrafficMonitor> instances = new ConcurrentHashMap<>();
   private String busNumber;

   //private constructor could throw an IOException
   private TrafficMonitor(String busNumber) throws IOException {
       this.busNumber = busNumber;
       // it needs to be in constructor because it call a 3rd party API to instantiate an object which throws IOException
       doIoOperation();
   }

   // static factory method to return a instance of this class
   public static TrafficMonitor forBus(String busNumber) throws IOException {
       // Compiler error: Unhandled exception: java.io.IOException
       return instances.computeIfAbsent(busNumber, TrafficMonitor::new);
   }

}

The private constructor throws IOException, in static factory function forBus(String busNumber) even though I have throws IOException in signature, compiler still complains that unhandled exception: java.io.IOException. Why? How to get rid of it?

Another question: in this example, does TrafficMonitor::new automatically get the argument/parameter busNumber from the forBus(String busNumber) function or how does it create the instance with the private constructor which requires an argument String busNumber ?

Upvotes: 2

Views: 491

Answers (1)

JB Nizet
JB Nizet

Reputation: 691715

computeIfAbsent expects a Function as argument. The signature of Function's SAM is R apply(T t). As you see, a Function may not throw an IOException. But your constructor does throw an IOException. So TrafficMonitor::new is not a Function. So you can't pass that as argument to computeIfAbsent().

Clean way: don't do IO in a constructor:

public class TrafficMonitor {
    private static ConcurrentMap<String, TrafficMonitor> instances = new ConcurrentHashMap<>();
    private String busNumber;
    private boolean initialized;

    private TrafficMonitor(String busNumber) {
        this.busNumber = busNumber;
    }

    private synchronized void initialize() throws IOException {
        if (!initialized) {
            doIoOperation();
            initialized = true;
        }
    }

    public static TrafficMonitor forBus(String busNumber) throws IOException {
        TrafficMonitor result = instances.computeIfAbsent(busNumber, TrafficMonitor::new);
        result.initialize();
        return result;
   }
}

Less clean way: rethrow IOException as a runtime exception

private TrafficMonitor(String busNumber) {
    this.busNumber = busNumber;
    try { 
        doIoOperation();
    } catch(IOException e) {
        throw UnchedkIOException(e);
    }
}

or

public static TrafficMonitor forBus(String busNumber) {
    return instances.computeIfAbsent(busNumber, busNumber -> {
        try {
            return new TrafficMonitor(busNumber);
        } catch(IOException e) {
            throw UnchedkIOException(e);
        }
    });
}

Upvotes: 2

Related Questions