Java exception (3) Several Puzzles about exceptions in Java Puzzles

Source: Internet
Author: User

Question 1: How can I print the following program? Copy the public class Indecisive {public static void main (String [] args) {System. out. println (demo-();} private static boolean demo-() {try {return true;} finally {return false ;}} copy the code running result: false result description: in a try-finally statement, the finally statement block is always executed when control leaves the try statement block. This is the case whether the try statement block ends normally or unexpectedly. When a statement or block throws an exception, or executes a break or continue for a closed statement, or executes a return statement in a method like this program, it will end unexpectedly. They are called unexpected termination because they prevent the program from executing the following statements in order. When both try and finally statement blocks end unexpectedly, the cause of unexpected termination in try statement blocks is discarded, the cause of the unexpected end of the try-finally statement block is the same as that of the finally statement block. In this program, the unexpected end caused by the return Statement in the try statement block will be discarded. The unexpected end of the try-finally statement is caused by the return in the finally statement block. In short, the program tries (try) (return) to return true, but the finally return (return) is false. Dropping the cause of unexpected termination is almost never the behavior you want, because the initial cause of unexpected termination may be more important to program behavior. For programs that execute break, continue, or return statements in the try statement block, it is particularly difficult to understand the behavior of programs that are blocked by the finally statement block. In short, each finally statement block should end normally unless an unchecked exception is thrown. Do not use a return, break, continue, or throw to Exit A finally statement block, and do not allow the propagation of a checked exception to a finally statement block. For the language designer, it may be required that the finally statement block should end normally without any exceptions that are not checked. For this purpose, the try-finally structure requires that the finally statement block can end normally. The return, break, or continue statement should be forbidden to pass control to a block other than the finally statement, any statement that can cause the checked exception to be propagated to a block other than the finally statement should also be banned. Puzzle 2: What do each of the following three programs print? Do not assume that all of them can be compiled. The first program copies the code import java. io. IOException; public class Arcane1 {public static void main (String [] args) {try {System. out. println ("Hello world");} catch (IOException e) {System. out. println ("I 've never seen println fail! ") ;}} Copy the code. The second program copies the code public class Arcane2 {public static void main (String [] args) {try {// If you have nothing nice to say, say nothing} catch (Exception e) {System. out. println ("This can't happen") ;}} copy code third program copy code interface Type1 {void f () throws CloneNotSupportedException;} interface Type2 {void f () throws InterruptedException;} interface Type3 extends Type1, Type2 {} public class Arcane 3 implements Type3 {public void f () {System. out. println ("Hello world");} public static void main (String [] args) {Type3 t3 = new Arcane3 (); t3.f ();}} copy the code running result: (01) the first program compilation error! Arcane1.java: 9: exception java. io. IOException is never thrown in body of corresponding try statement} catch (IOException e) {^ 1 error (02) the second program can be compiled and run normally. (03) The third program can be compiled and run properly. The output result is: Hello world: (01) Arcane1 shows a basic principle for exceptions to be checked. It seems that it can be compiled: The try clause executes I/O, and the catch clause captures the IOException. However, this program cannot be compiled, because the println method is not declared and will throw any checked exceptions, while IOException is a checked exception. The Language Specification describes: If a catch clause needs to catch a checked exception of Type E, and its corresponding try clause cannot throw a child type exception of Type E, this is a compilation error. (02) For the same reason, the second program, Arcane2, may not be compiled, but can. It can be compiled because its unique catch clause checks Exception. Although this is ambiguous, the catch clause that captures exceptions or Throwble is legal, regardless of the content of the try clause. Although Arcane2 is a legal program, the content of the catch clause will never be executed and the program will not print anything. (03) The third program, Arcane3, does not seem to be compiled. Method f declares in the Type1 interface that it should throw the CloneNotSupportedException of the checked exception, and declares in the Type2 interface that it should throw the InterruptedException of the checked exception. The Type3 interface inherits Type1 and Type2. Therefore, when calling method f on an object whose static type is Type3, it may throw these exceptions. A method must either capture all checked exceptions that can be thrown by its method body, or declare that it will throw these exceptions. The main method of Arcane3 calls method f on objects with the static type Type3, but it does not process CloneNotSupportedException and InterruptedExceptioin. So why can this program be compiled? The defect of the above analysis lies in the assumption that "Type3.f can throw exceptions declared on Type1.f and exceptions declared on Type2.f. This is incorrect because each interface limits the set of checked exceptions that can be thrown by method f. The set of checked exceptions that a method can throw is the intersection of all types of declarations it applies. Therefore, the f method on an object with the static type Type3 cannot throw any checked exception. Therefore, Arcane3 can be compiled without error and print Hello world. Puzzle 3: what will be printed by the program of the unpopular guests? Copy the public class UnwelcomeGuest {public static final long GUEST_USER_ID =-1; private static final long USER_ID; static {try {USER_ID = getUserIdFromEnvironment ();} catch (IdUnavailableException e) {USER_ID = GUEST_USER_ID; System. out. println ("Logging in as guest") ;}} private static long getUserIdFromEnvironment () throws IdUnavailableException {throw new IdUnavailableException ();} public Static void main (String [] args) {System. out. println ("User ID:" + USER_ID) ;}} class IdUnavailableException extends Exception {} copy the code execution result: UnwelcomeGuest. java: 10: variable USER_ID might already have been assigned USER_ID = GUEST_USER_ID; ^ 1 error result Description: The program looks intuitive. The call to getUserIdFromEnvironment throws an exception, so that the program assigns GUEST_USER_ID (-1L) to USER_ID and prints Loggin in as guest. Then run the main method to print the User ID:-1. The representation deceives us again and the program cannot be compiled. If you try to compile it, you will see an error message. Where is the problem? The USER_ID domain is an empty final (blank final), which is a final domain that has not been initialized in the Declaration. Obviously, an exception is thrown in the try statement block only when the value of USER_ID fails. Therefore, it is safe to assign a value in the catch statement block. No matter how you execute the static initialization statement block, only the USER_ID is assigned once, which is required by empty final. Why don't the compiler know this? It is very difficult to determine whether a program can assign values to an empty final more than once. In fact, this is impossible. This is equivalent to the classic downtime problem, which is generally considered impossible to solve. In order to be able to compile a compiler, the language standards adopt a conservative approach in this regard. In a program, an empty final field can be assigned a value only when it is explicitly unspecified. This term provides an accurate but conservative definition. Because it is conservative, the compiler must reject programs that prove secure. This puzzle shows such a program. Fortunately, you don't have to learn the terrible details of the assignment to write Java programs. It is usually clear that the assignment rules do not have any obstacles. If you write a program that might assign a value to an empty final value more than once, the compiler will help you point it out. In rare cases, just like this puzzle, you can write a secure program, but it does not meet the formal requirements of specifications. The complaints from the compiler are like writing an insecure program, and you must modify your program to satisfy it. The best way to solve this type of problem is to change the annoying domain from the empty final type to the ordinary final type, and replace the static initialization statement block with the initialization operation of a static domain. The best way to achieve this is to reconstruct the code in the static statement block as an assistant method: copy the code public class UnwelcomeGuest {public static final long GUEST_USER_ID =-1; private static final long USER_ID = getUserIdOrGuest (); private static long getUserIdOrGuest () {try {return getUserIdFromEnvironment ();} catch (IdUnavailableException e) {System. out. println ("Logging in as guest"); return GUEST_USER_ID ;}} private static long getUserIdFromEnvironment () Throws IdUnavailableException {throw new IdUnavailableException ();} public static void main (String [] args) {System. out. println ("User ID:" + USER_ID) ;}} class IdUnavailableException extends Exception {} this version of the copied code program is obviously correct, and is more readable than the original version, because it adds a descriptive name for the calculation of the Domain value, the original version only has one anonymous static initialization operation statement block. Apply this modification to the program and it can run as expected. In short, most programmers do not need to learn details about specific assignment rules. This rule is usually correct. If you have to refactor a program to eliminate errors caused by explicit assignment rules, you should consider adding a new method. This not only solves the problem of explicit assignment, but also improves the readability of the program. Puzzle 4: hello, goodbye! What will the following program print? Copy the public class HelloGoodbye {public static void main (String [] args) {try {System. out. println ("Hello world"); System. exit (0);} finally {System. out. println ("Goodbye world") ;}} copy the code running result: Hello world result Description: This program contains two println statements: one in the try statement block, the other is in the finally statement block. The try statement block executes its println statement and ends the execution ahead of schedule by calling System. exit. At this time, you may want the control to be transferred to the finally statement block. However, if you run the program, you will find that it will never say goodbye: It only prints Hello world. Does this violate the principles described in "Indecisive example? Whether the execution of try statement blocks ends normally or accidentally, finally statement blocks are executed. However, in this program, the try statement does not end its execution process. The System. exit method stops the current thread and all other threads that died on the spot. The appearance of the finally clause does not allow the thread to continue to execute. When System. exit is called, the virtual machine must perform two cleanup tasks before shutting down. First, it performs all the close hooks. These Hooks have been registered to the Runtime. addShutdownHook. This will be helpful for releasing resources outside of the VM. Disable the hook for actions that must occur before the VM exits. The following program version demonstrates this technology. It can print out Hello world and Goodbye world as expected: copy the code public class HelloGoodbye1 {public static void main (String [] args) {System. out. println ("Hello world"); Runtime. getRuntime (). addShutdownHook (new Thread () {public void run () {System. out. println ("Goodbye world") ;}}); System. exit (0) ;}} copy the code VM and execute it in System. the second cleanup task executed when exit is called is related to the Terminator. If System. runFinalizerOnExit or its devil twins Runtime. runFinalizersOnExit are called, the VM will call the Terminator on all objects that have not been terminated. These methods were outdated a long time ago, and the reason is also reasonable. For whatever reason, never call System. runFinalizersOnExit and Runtime. runFinalizersOnExit: they are one of the most dangerous methods in the Java class library [ThreadStop]. The result of calling these methods is that the terminator runs on objects that are being operated concurrently by other threads, leading to uncertain behavior or deadlock. In short, System. exit will immediately stop all program threads. It will not make the finally statement block called, but it will close the hook before stopping the VM. When the VM is shut down, use the close hook to terminate external resources. You can call System. halt to stop the VM without shutting down the hook, but this method is rarely used. Puzzle 5: What will the program below the unwilling constructor print? Copy the public class Reluctant {private Reluctant internalInstance = new Reluctant (); public Reluctant () throws Exception {throw new Exception ("I'm not coming out ");} public static void main (String [] args) {try {Reluctant B = new Reluctant (); System. out. println ("Surprise! ");} Catch (Exception ex) {System. out. println ("I told you so") ;}} copy the code running result: Exception in thread "main" java. lang. stackOverflowError at Reluctant. <init> (Reluctant. java: 3 )... result Description: The main method calls the Reluctant constructor and throws an exception. You may expect the catch clause to catch this exception and print the I told you so. By taking a closer look at this program, we will find that the Reluctant instance also contains the second internal instance, and its constructor will throw an exception. No matter which exception is thrown, it seems that the catch clause in main should capture it, so it is predicted that the program will print I told you should be a safe bet. But when you try to run it, you will find that it has not done such a thing at all: it throws the StackOverflowError exception, why? Like most programs that throw an StackOverflowError exception, this program also contains an infinite recursion. When you call a constructor, the initialization operation of instance variables will run [JLS 12.5] prior to the constructor's program body. In this puzzle, the initialization operation of the internalInstance variable recursively calls the constructor, And the constructor initializes its own internalInstance domain by calling the Reluctant constructor again, so it can be infinitely recursive. These recursive calls throw an StackOverflowError Exception before the constructor obtains the execution opportunity. Because StackOverflowError is a child type of Error rather than an Exception, the catch clause cannot capture it. It is not uncommon for an object to contain instances of the same type as its own. For example, Link List nodes, Tree nodes, and graph nodes all belong to this situation. You must initialize such instances with caution to avoid StackOverflowError exceptions. As for the nominal question of this puzzle: the constructor that declares that an exception will be thrown, You need to note that the constructor must declare all checked exceptions thrown by its instance initialization operation. Puzzle 6: the domain and streaming method copies a file to another file and is designed to close every stream it creates, even if it encounters an I/O error. Unfortunately, it is not always able to do this. Why not? How can you correct it? Copy the code static void copy (String src, String dest) throws IOException {InputStream in = null; OutputStream out = null; try {in = new FileInputStream (src ); out = new FileOutputStream (dest); byte [] buf = new byte [1024]; int n; while (n = in. read (buf)> 0) out. write (buf, 0, n);} finally {if (in! = Null) in. close (); if (out! = Null) out. close () ;}} copy the code to perform a Puzzle Analysis: This program looks comprehensive. Their watershed (in and out) is initialized to null, and new streams are immediately set to new values of these watershed once they are created. If the stream referenced by these fields is not empty, the finally statement block closes the stream. Even if the copy operation causes an IOException, the finally statement block is executed before the method returns. What's wrong? The problem lies in the finally statement block itself. The close method may also throw an IOException. If this happens when in. close is called, this exception will prevent out. close from being called, so that the output stream remains open. Note that this program violates the "indecisive" Suggestion: Calling close may cause the finally statement block to end unexpectedly. Unfortunately, the compiler cannot help you find this problem, because the exception thrown by the close method is of the same type as the exception thrown by read and write, and its peripheral method (copy) this exception is reported. The solution is to wrap every close in a nested try statement block. The finally statement block version below ensures that close is called on both streams: copy the code try {// as before} finally {if (in! = Null) {try {in. close ();} catch (IOException ex) {// There is nothing we can do if close fails} if (out! = Null) {try {out. close ();} catch (IOException ex) {// There is nothing we can do if close fails }}} copy the code in summary, when you call the close method in the finally statement block, you must use a nested try-catch statement to protect it against the spread of IOException. Generally, any checked exception that may be thrown in the finally statement block must be processed, rather than transmitted. Puzzle 7: What will happen if an exception is thrown out of the loop program? Copy the public class Loop {public static void main (String [] args) {int [] [] tests = {6, 5, 4, 3, 2, 1 }, {1, 2}, {1, 2, 3}, {1, 2, 3, 4}, {1}; int successCount = 0; try {int I = 0; while (true) {if (thirdElementIsThree (tests [I ++]) successCount ++ ;}} catch (ArrayIndexOutOfBoundsException e) {// No more tests to process} System. out. println (successCount);} private static boolean thir DElementIsThree (int [] a) {return. length >=3 & a [2] = 3 ;}} copy the code running result: 0 result Description: This program mainly describes two problems. 1st problems: exceptions should not be used to terminate the cycle! This program uses the thirdElementIsThree method to test each element in the tests array. The loop for Traversing this array is obviously a non-traditional loop: it does not terminate when the loop variable is equal to the length of the array, but ends when it tries to access an element not in the array. Although it is not traditional, this loop should work. If the parameter passed to thirdElementIsThree has three or more elements and the third element is 3, true is returned. For the five elements in the tests, two will return true, so it seems that the program should print 2. If you run it, you will find that it is printed at 0. Something is wrong. Are you sure you want to know? In fact, this program has made two mistakes. The first error is that the program uses a terrible loop usage, which relies on access to the array and throws an exception. This kind of usage is not only difficult to read, but also very slow. Do not use exceptions for loop control; use exceptions only for exception conditions. To correct this error, replace the entire try-finally statement block with the standard usage of the loop traversal array: for (int I = 0; I <test. length; I ++) if (thirdElementIsThree (tests [I]) successCount ++; if you are using 5.0 or an updated version, then you can replace the for (int [] test: tests) if (thirdElementIsThree (test) successCount ++; 2nd questions: it mainly compares the differences between "& operator" and "& operator. Note that the operator in the example is &, which performs "and" operations by bit.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.