`

Does Java need Checked Exceptions?

阅读更多

Although C++ introduced the exception specification, Java is the only mainstream language that enforces the use of specifications with "Checked Exceptions." In this discussion I will examine the motivation and outcome of this experiment, and look at alternative (and possibly more useful) ways to manage exceptions. The goal of this discussion is to explore the ideas -- in particular, what are your experiences with exceptions? Would it be more beneficial to you to use un-checked exceptions?
Note: You can find an associated essay
here, and a related article here.

I began learning about exceptions when they were introduced into the C++ committee, and it's been a long learning curve. One of the first justifications for exceptions was that they would allow programmers to write less error checking code because this code could be delayed until a more appropriate point in the program, rather than having to put tests at the point of every function call -- which no one was doing anyway. In fact, I think it was the poor error-handling model that C brought in that was the major motivation for exception handling in C++, because what we really needed was a unified and consistent way of reporting errors (unfortunately, because C++ is backwards-compatible with C, exception handling in C++ is simply an additional error handling model).

Checked exceptions seem like a really good idea at first. But it's all based on our unchallenged assumption that static type checking detects your problems and is always best. Java is the first language (that I know of) that uses checked exceptions and is thus the first experiment. However, the kind of code you must write around these things and the common phenomenon of "swallowed" exceptions begins to suggest there's a problem. In Python, exceptions are unchecked, and you can catch them and do something if you want, but you aren't forced. And it seems to work just fine.

I think it's a compile-time vs. run-time checking issue. We have gotten so used to thinking that the only correct way, only safe and reliable way, to do things is at compile time, that we automatically discount any solutions that rely on run-time as unreliable. That thinking came from C++, but I note that Java actually does a fair number of things at runtime, which we accept merely because they can't be done at compile time. But despite that we still hold this idea that if it can be done at compile time, then that's the only proper time to do it (I am also referring here to weak typing in Python). I know this seems like a less precise and provable way of thinking, but if you start having experiences that seem to disprove the common way of thinking then you start questioning it.

I began having discussions about this last Summer, and which I've started hearing from other people about -- that checked exceptions were a mistake. They're not in Python or C#, or C++. In fact, the only language I know of where they exist is in Java, and I'll bet it was because people saw unchecked exception specifications in C++ and thought that was a mistake (I know I did, for the longest time). At this point, I feel like checked exceptions are (1) an untried experiment when they were put into Java (unless you know of some other language where it is implemented ... Ada, perhaps?) (2) a failure because so many people end up swallowing the exceptions in their code.

In the Python/C# approach, the exception is thrown, and if you want to you can write code to catch it, but if you don't you aren't forced to write a bunch of extra code and be tempted to swallow the exception.

I currently plan to rewrite the Exceptions chapter (and the rest of the book) for the 3rd edition to change the way exceptions are handled.

The way I (now) see exceptions is something like this:

1) The great value of exceptions is the unification of error reporting: a standard mechanism by which to report errors, rather than the potpourri of ignorable approaches that we had in C (and thus, C++, which only adds exceptions to the mix, and doesn't make it the exclusive approach). The big advantage Java has over C++ is that exceptions are the only way to report errors.

2) "Ignorable" in the previous paragraph is the other issue. The theory is that if the compiler forces the programmer to either handle the exception or pass it on in an exception specification, then the programmer's attention will always be brought back to the possibility of errors and they will thus properly take care of them. I think the problem is that this is an untested assumption we're making as language designers that falls into the field of psychology. My theory is that when someone is trying to do something and you are constantly prodding them with annoyances, they will use the quickest device available to make those annoyances go away so they can get their thing done, perhaps assuming they'll go back and take out the device later. I discovered I had done this in the first edition of Thinking in Java:

 

...
} catch (SomeKindOfException e) {}

 
And then more or less forgot it until the rewrite. How many people thought this was a good example and followed it? I began seeing the same kind of code, and realized people were stubbing out exceptions and then they were disappearing. The overhead of checked exceptions was having the opposite effect of what was intended, something that can happen when you experiment (and I now believe that checked exceptions were an experiment based on what someone thought was a good idea, and which I believed was a good idea until recently).

When I started using Python, all the exceptions appeared, none were accidentally "disappeared." If you want to catch an exception, you can, but you aren't forced to write reams of code all the time just to be passing the exceptions around. They go up to where you want to catch them, or they go all the way out if you forget (and thus they remind you) but they don't vanish, which is the worst of all possible cases. I now believe that checked exceptions encourage people to make them vanish. Plus they make much less readable code.

In the end, I think we must realize the experimental nature of checked exceptions and look at them carefully before assuming that everything about exceptions in Java is good. I believe that having a single mechanism for handling errors is excellent, and I believe that using a separate channel (the exception handling mechanism) for moving the exceptions around is good. But I do remember one of the early arguments for exception handling in C++ was that it would allow the programmer to separate the sections of code where you just wanted to get work done from the sections where you handled errors, and it seems to me that checked exceptions do not do this; instead, they tend to intrude (a lot) into your "normal working code" and thus are a step backwards. My experience with Python exceptions supports this, and unless I get turned around on this issue I intend to put a lot more RuntimeExceptions into my Java code.

One thing has become very clear to me, especially because of Python: the more random rules you pile onto the programmer, rules that have nothing to do with solving the problem at hand, the slower the programmer can produce. And this does not appear to be a linear factor, but an exponential one.

I've gotten a report or two where people were saying that checked exceptions were such a problem (getting swallowed) in production code that they wanted to change the situation. It may be that the majority of programmers out there are what you might classify as beginners. I've seen this again and again in seminars -- people with years of programming experience who don't understand some basic things.

Maybe it's a time thing. I started struggling with the idea of exceptions when they were introduced at the C++ committee, and after this much time it suddenly hit me. But I also suspect it comes from using a language that has exceptions, but not checked exceptions. I think it's the best of both worlds -- if I want to catch the exception, I can, but I'm not tempted to swallow it just to avoid writing reams of code. If I don't' want to write around the exceptions, I ignore them, and if one comes up it gets reported to me during debugging, and I can decide how to handle it then. I still deal with the exception, but I'm not forced to write a bunch of code about exceptions all the time. ExceptionAdapter

Here's a tool that I developed with the help of Heinz Kabutz. It converts any checked exception into a RuntimeException while preserving all the information from the checked exception. 

 

import java.io.*;
class ExceptionAdapter extends RuntimeException {
  private final String stackTrace;
  public Exception originalException;
  public ExceptionAdapter(Exception e) {
    super(e.toString());
    originalException = e;
    StringWriter sw = new StringWriter();
    e.printStackTrace(new PrintWriter(sw));
    stackTrace = sw.toString();
  }
  public void printStackTrace() { 
    printStackTrace(System.err);
  }
  public void printStackTrace(java.io.PrintStream s) { 
    synchronized(s) {
      s.print(getClass().getName() + ": ");
      s.print(stackTrace);
    }
  }
  public void printStackTrace(java.io.PrintWriter s) { 
    synchronized(s) {
      s.print(getClass().getName() + ": ");
      s.print(stackTrace);
    }
  }
  public void rethrow() { throw originalException; }
} 

 
 

  

The original exception is stored in originalException, so you can always recover it. In addition, its stack trace information is extracted into the stackTrace string, which will then be printed using the usual printStackTrace() if the exception gets all the way out to the console. However, you can also put a catch clause at a higher level in your program to catch an ExceptionAdapter and look for particular types of exceptions, like this:

 

catch(ExceptionAdapter ea) {
  try {
    ea.rethrow();
  } catch(IllegalArgumentException e) {
    // ...
  } catch(FileNotFoundException e) {
    // ...
  }
  // etc.
}

 

 

 

 

 Here, you're still able to catch the specific type of exception but you're not forced to put in all the exception specifications and try-catch clauses everywhere between the origin of the exception and the place that it's caught. An even more importantly, no one writing code is tempted to swallow the exception and thus erase it. If you forget to catch some exception, it will show up at the top level. If you want to catch exceptions somewhere in between, you can.

Or, since originalException is public, you can also use RTTI to look for particular types of exceptions.

Here's some test code, just to make sure it works (not the way I suggest using it, however):

public class ExceptionAdapterTest {
  public static void main(String[] args) {
    try {
      try {
        throw new java.io.FileNotFoundException("Bla");
      } catch(Exception ex) {
        ex.printStackTrace();
        throw new ExceptionAdapter(ex);
      }   
    } catch(RuntimeException e) {
      e.printStackTrace();
    }
    System.out.println("That's all!");
  }
}

 

 

By using this tool you can get the benefits of the unchecked exception approach (less code, cleaner code) without losing the core of the information about the exception.

If you were writing code where you wanted to throw a particular type of checked exception, you could use (or modify, if it isn't already possible) the ExceptionAdapter like this:

 

 

if(futzedUp)
    throw new ExceptionAdapter(new CloneNotSupportedException());

 

 

This means you can easily use all the exceptions in their original role, but with unchecked-style coding.

 

Kevlin Henney writes:

I must admit that I've come to similar conclusions myself based on experience in Java and C++, and readings in other languages. My only caveat on it is that I have found exception specifications, in the style of CORBA, to be useful where errors are propagated across significant boundaries, ie machine boundaries, and where such interfaces need to be more explicit. However, although apparently similar to Java's EH mechanism, there is no concept of compile-time checking.

To clarify a couple of points in your article, Ada does not have any form of exception specification, and so does not have a checked/unchecked model. The roots of exception specifications go back to CLU and further. The key paper for this is "Exception Handling in CLU" by Barbara Liskov and Alan Snyder (IEEE Transactions on Software Engineering, Vol SE-5, No 6, Nov 1979). Unfortunately I cannot find a copy of this paper online, and have only a paper copy. There is a "History of CLU" paper here.

Unfortunately, it does not provide much detail on the exception handling mechanism. What is worth noting is that information-rich exceptions are seen as an extension of the procedural paradigm (and are therefore not necessarily an object-related concept), that CLU supported the equivalent of throw specs in its procedure signatures, that there were no compile-time checks of procedure body against the signature (a conscious design decision to avoid overwhelming programmers with irrelevant detail :->), and that unlisted exceptions automatically translated to a special failure exception.

In this you can see the origins of C++'s mechanisms, and the screws that were tightened -- interestingly rejecting the original rationale for not having compile-time checks -- in Java. The CLU mechanism has been influential elsewhere, perhaps the closest offspring being in Modula-3 - - the language report is available here

Interestingly, Modula-3 also chooses to have a throw spec that is runtime rather than compile-time checked.

Kevlin Henney
http://www.curbralan.com


 

Here's an interesting comment by one of the C# language designers: (Full comment). Note in particular:

Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result -- decreased productivity and little or no increase in code quality.

The rest of the note goes on to argue this claim. The reason I find this particularly compelling is that it does agree that checked exceptions seem to be helpful for small projects, which is generally the space where we argue the point. However, when projects get large (actually, I've noticed it when they are anything except small), checked exceptions get ungainly and seem to cause problems. I would therefore suggest that the reason checked exceptions seem so compellingly "right" at first is that they have been presented and argued in the realm of small examples.

分享到:
评论

相关推荐

    详解Java中Checked Exception与Runtime Exception 的区别

    Java 中 Checked Exception 与 Runtime Exception 的区别 Java 中的异常处理机制是 Java 语言的一个重要特色,它允许程序产生例外状况。在学习 Java 时,我们需要了解不同种类的异常的区别。Java 提供了两种异常...

    java 面试常见问题整理

    Checked Exception 和 Unchecked Exception 有什么区别? Throwable 类常用方法有哪些? try-catch-finally 如何使用? finally 中的代码一定会执行吗? 如何使用 try-with-resources 代替try-catch-finally? I/O ...

    Java-CustomExceptions:Java 自定义异常

    Java异常分为两种类型:未检查异常(Unchecked Exceptions)和检查异常(Checked Exceptions)。自定义异常如果继承自`RuntimeException`或其子类,将成为未检查异常,否则默认为检查异常。未检查异常在编译时不强制...

    10万字208道Java经典面试题总结(附答案)_Java攻城狮-CSDN博客_java经典面试题及答案.pdf

    Java中的异常类型包括CheckedException和RuntimeException。 3. 如何处理Java异常?可以使用try-catch语句来处理Java异常。 五、Java多线程编程 1. 什么是Java多线程?Java多线程是指在一个程序中运行多个线程。 2...

    实习生java面试题集及详细答案.pdf

    异常可以分为checked异常和unchecked异常两种,checked异常需要在编译时检查,而unchecked异常则不需要。 2. 什么是Java中的try-catch语句?try-catch语句的作用是什么? Java中的try-catch语句是一种异常处理机制...

    通过实例了解java checked和unchecked异常

    通过实例了解 Java checked 和 unchecked 异常 Java 异常分为两种类型:checked 异常和 unchecked 异常。checked 异常是可以在执行过程中恢复的,例如无效的用户输入、文件不存在、网络或者数据库链接错误等。这些...

    Exceptions in Java and Eiffel:Two Extremes in Exception Design and Application

    Java中的异常分为两大类:检查异常(checked exceptions)和运行时异常(runtime exceptions)。检查异常是指编译器强制要求程序员必须处理的异常类型,通常用于表示程序中可能出现的正常但无法预料的情况;而运行时...

    java english interview

    * Java 定义了两种类型的异常:checked exceptions 和 unchecked exceptions。 * Checked exceptions:继承自 Exception 类的异常,客户端代码必须处理这些异常,可以在 catch 子句中捕获或使用 throws 子句将其传递...

    JAVA语言规范 每个JAVA程序员都该读的文档

    它也提到了受检查异常(Checked Exceptions)和未检查异常(Unchecked Exceptions)的区别。 内存管理和垃圾回收也是Java的一大特色。规范中详细阐述了对象的生命周期,包括创建、引用、垃圾收集和内存泄漏的预防。...

    JAVA思维导图9张!

    3. **异常处理**:这部分可能会讲解如何使用try-catch-finally语句来捕获和处理异常,以及不同类型的异常类,如检查异常(Checked Exceptions)和运行时异常(Unchecked Exceptions)。 4. **集合框架**:Java集合...

    Java精华(免费版)

    所有的checked exception是从java.lang.Exception类衍生出来的,而runtime exception则是从java.lang.RuntimeException或java.lang.Error类衍生出来的。   它们的不同之处表现在两方面:机制上和逻辑上。   一...

    Java多线程编程实战指南-核心篇

    而检查异常(Checked Exceptions)如果在线程中抛出,需在该线程或其祖先线程中捕获,否则会导致线程中断。 最后,书中还将涵盖Java内存模型(JMM)和volatile关键字。JMM定义了线程如何访问共享变量的规则,保证了...

    Java的内置异常-Java教程共1页.pdf.zip

    Java将异常分为两大类:检查性异常(Checked Exceptions)和运行时异常(Unchecked Exceptions)。检查性异常是那些在编译期间需要处理的异常,如IOException,而运行时异常则是在程序运行期间抛出的,例如...

    详解Java中的checked异常和unchecked异常区别

    Java中的checked异常和unchecked异常区别详解 Java中的checked异常和unchecked异常是Java语言中两种不同的异常类型,它们之间的区别是很多开发者容易混淆的。下面,我们将详细介绍checked异常和unchecked异常的概念...

    Java面试宝典2013最新版

    12. Java异常处理机制,包括运行时异常(RuntimeException)和一般异常(checked exceptions),以及关键字:throws、throw、try、catch、finally的使用。 13. Java中的多线程实现方法,包括创建线程的不同方式,...

    java学习 java学习

    学习如何使用try-catch-finally结构来处理异常,以及了解不同类型的异常,如检查异常(Checked Exceptions)和运行时异常(Unchecked Exceptions),对于编写健壮的代码至关重要。 Java的GUI编程可以使用Swing或...

    java常用API.pdf

    异常分为受检异常(checked exceptions)和非受检异常(unchecked exceptions)。Java使用try-catch-finally语句块来捕获和处理异常。 JDBC(Java Database Connectivity)是Java中用于连接和操作数据库的标准接口...

    java api 中文版

    6. **异常处理**:Java强调异常处理,API文档列出了所有可能抛出的异常,包括运行时异常(RuntimeExceptions)和检查异常(Checked Exceptions)。例如,`IOException`是常见的I/O操作异常。 7. **多线程编程**:...

    java-api的html版

    3. **异常信息**:方法可能会抛出异常,API文档会列出可能抛出的检查异常(checked exceptions)和运行时异常(runtime exceptions)。这有助于开发者在调用方法时做好异常处理。 4. **示例代码**:许多API文档中...

Global site tag (gtag.js) - Google Analytics