【问题标题】:compiler complains unhandled IOException even though function signature throws it即使函数签名抛出它,编译器也会抱怨未处理的 IOException
【发布时间】:2020-02-10 10:50:44
【问题描述】:

我有一个类,其中静态工厂方法使用computeIfAbsent()返回一个实例:

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);
   }

}

私有构造函数在静态工厂函数forBus(String busNumber) 中抛出IOException,即使我在签名中有throws IOException,编译器仍然会抱怨unhandled exception: java.io.IOException。为什么?如何摆脱它?

另一个问题:在这个例子中,TrafficMonitor::new 是否会自动从forBus(String busNumber) 函数中获取参数/参数busNumber,或者它如何使用需要参数String busNumber 的私有构造函数创建实例?

【问题讨论】:

  • computeIfAbsent 需要一个函数作为参数。 Function 的 SAM 的签名是R apply(T t)。如您所见,函数可能不会抛出 IOException。但是您的构造函数确实会引发 IOException。所以 TrafficMonitor::new 不是一个函数。所以你不能把它作为参数传递给 computeIfAbsent()。
  • 也许你是对的,我对双冒号不熟悉,如何摆脱它?
  • 摆脱它的干净方法是避免在构造函数中执行 IO 操作。构造函数不应该这样做。它应该创建一个对象。不太干净的方法是传递一个调用构造函数、捕获 IOException 并将其作为运行时异常重新抛出的 lambda。
  • A constructor isn't supposed to do that,好吧,没有这样的规则,我认为这取决于。就我而言,这样做是有意义的,因为我的类是第 3 方 API 的包装器,它在实例化期间抛出 IOException。话虽如此,如果您能为您的答案提供代码示例,那就太好了。

标签: java


【解决方案1】:

computeIfAbsent 需要 Function 作为参数。 Function 的 SAM 的签名是R apply(T t)。如您所见,Function 可能不会抛出IOException。但是你的构造函数确实抛出了一个IOException。所以TrafficMonitor::new 不是函数。所以你不能把它作为参数传递给computeIfAbsent()

干净的方式:不要在构造函数中做 IO:

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;
   }
}

不太干净的方法:将 IOException 作为运行时异常重新抛出

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

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

【讨论】:

  • SAM 在您的回答中代表什么?
  • 单一抽象方法。这是接口必须满足的条件才能成为功能接口,即能够用作 lambda 或方法引用的类型。
  • 我喜欢干净的方式,但它违反了我试图实现的逻辑,如果你仔细看看我的代码forBus(String busNumber)TrafficMonitor 的实例是相同的相同的busNumber。这就是busNumber 的“单例”。 doIoOperation() 应该只在创建TrafficMonitor 的新实例时执行。您能否修改您的干净解决方案代码以满足此要求?
  • 哦,是的,你有一个布尔标志来控制初始化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 2011-01-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多