📜 ⬆️ ⬇️

Exceptions in Java, Part II (checked / unchecked)

This is the second part of the article (the first part is try-catch-finally ), devoted to such a linguistic mechanism of Java as exceptions. It has an introductory character and is designed for novice developers or those who are just starting to learn a language.

I also teach Scala for Java Developers on the udemy.com online education platform (equivalent to Coursera / EdX).

1. magic checked / unchecked
2. Pessimistic mechanism
3. throws with unchecked (unckecked) exception
4. Multiple exceptions
5. Or catch, or throws
6. Behavior of the / JVM compiler
7. Overriding and throws
8. Inheritance
')

1. “Magic” checked / unchecked



The mechanism of the exceptional situation in Java is associated with two elements of "magic", i.e. behavior that is not reflected in the source code:
1. The “magic” of java.lang.Throwable - in throw, catch and throws can stand exclusively Throwable or its heirs ( we have already analyzed in the previous lecture ). This “right” to be in throw, catch and throws is not reflected in the source code.
2. All exceptions are divided into “checked” (checked) and “unchecked” (unchecked). This property is inherent in the “rhizome” (Throwable, Error, Exception, RuntimeException) and is inherited. Not visible in the source code of the exception class.

For further examples, just note that
- Throwable and Exception and all their heirs (except for the heirs of Error and RuntimeException) - checked
- Error and RuntimeException and all their heirs - unchecked

checked exception = a checked exception checked by the compiler.
What exactly the compiler checks we will discuss in this lecture.

Recall the exception hierarchy
Object | Throwable / \ Error Exception | RuntimeException 


Place the value of the checked / unchecked property
  Object | Throwable(CHECKED) / \ Error(UNCHECKED) Exception(CHECKED) | RuntimeException(UNCHECKED) 



2. Pessimistic mechanism



I call the connection between checked exceptions and throws “pessimistic,” we can “scare” polka about more than can actually happen, but not vice versa.

We can not throw, but do not warn
 public class App { public static void main(String[] args) { throw new Exception(); //    } } >> COMPILATION ERROR: unhandled exception: java.lang.Exception 


We can not throw, but warn about the "less"
 import java.io.IOException; public class App { public static void main(String[] args) throws IOException { throw new Exception(); //    } } >> COMPILATION ERROR: unhandled exception: java.lang.Exception 


We can warn you exactly what we are throwing.
 public class App { public static void main(String[] args) throws Exception { //   Exception throw new Exception(); //   Exception } } 


We can warn of more than we throw
 public class App { public static void main(String[] args) throws Throwable { //  "" Throwable throw new Exception(); //    Exception } } 


We can even warn about what is not there at all.
 public class App { public static void main(String[] args) throws Exception { //  //     } } 


Even if you warn about what is not - everyone must be afraid
 public class App { public static void main(String[] args) { f(); //    } public static void f() throws Exception { } } >> COMPILATION ERROR: unhandled exception: java.lang.Exception 


Although they (frightened) may scare the rest even more
 public class App { //    Throwable public static void main(String[] args) throws Throwable { f(); } //    - Exception public static void f() throws Exception { } } 


What is the purpose of "pessimism"? Everything is quite simple.
You are in the mode of protyping "sketched", say, a class utility for downloading from the Internet
 public class InternetDownloader { public static byte[] (String url) throws IOException { return "<html><body>Nothing! It's stub!</body></html>".getBytes(); } } 

and would like to “force” users of your class to ALWAYS handle a possible IOException exception, although you do NOT GENERATE such an exception from the dummy implementation. But in the future - are going.


3. throws with unchecked (unckecked) exception


Calling a method that “scares” unchecked with the exception does not impose any duties on us.
 public class App { public static void main(String[] args) { f(); } public static void f() throws RuntimeException { } } 

This construct serves the purpose of “pointing out” to a code reader programmer that your method may throw some unchecked exception.

Example (java.lang.NumberFormatException is an unchecked exception):
 package java.lang; public final class Integer extends Number implements Comparable<Integer> { ... /** * ... * * @param sa {@code String} containing the {@code int} * representation to be parsed * @return the integer value represented by the argument in decimal. * @exception NumberFormatException if the string does not contain a * parsable integer. */ public static int parseInt(String s) throws NumberFormatException { return parseInt(s,10); } ... } 

Integer.parseInt () may throw unchecked NumberFormatException on an unsuitable argument (int k = Integer.parseInt ("123abc")), this reflected
- in method signature: throws NumberFormatException
- in the documentation (javadoc): @ exception
but it does not oblige us to anything.


4. Multiple exceptions


Consider a situation with a code that can throw checked exceptions of different types.
Further consider that EOFException and FileNotFoundException are descendants of IOException.

We can specify exactly what we throw away.
 import java.io.EOFException; import java.io.FileNotFoundException; public class App { //    public static void main(String[] args) throws EOFException, FileNotFoundException { if (System.currentTimeMillis() % 2 == 0) { throw new EOFException(); } else { throw new FileNotFoundException(); } } } 

or so
 import java.io.EOFException; import java.io.FileNotFoundException; public class App { //    public static void main(String[] args) throws EOFException, FileNotFoundException { f0(); f1(); } public static void f0() throws EOFException {...} public static void f1() throws FileNotFoundException {...} } 


And we can "scare" stronger (the ancestor of both exceptions)
 import java.io.EOFException; import java.io.FileNotFoundException; import java.io.IOException; public class App { //    public static void main(String[] args) throws IOException { if (System.currentTimeMillis() % 2 == 0) { throw new EOFException(); } else { throw new FileNotFoundException(); } } } 

or so
 import java.io.EOFException; import java.io.FileNotFoundException; public class App { //    public static void main(String[] args) throws IOException { f0(); f1(); } public static void f0() throws EOFException {...} public static void f1() throws FileNotFoundException {...} } 


You can do it like this: EOFException and FileNotFoundException “summarize to” IOException, and InterruptedException “skip intact” (InterruptedException is NOT a descendant of IOException)
 import java.io.EOFException; import java.io.FileNotFoundException; public class App { public static void main(String[] args) throws IOException, InterruptedException { f0(); f1(); f2(); } public static void f0() throws EOFException {...} public static void f1() throws FileNotFoundException {...} public static void f2() throws InterruptedException {...} } 



5. Or catch, or throws


Don't scare you intercept
So
 public class App { public static void main(String[] args) { try { throw new Exception(); } catch (Exception e) { // ... } } } 


or so (set catch by ancestor and accurately intercept)
 public class App { public static void main(String[] args) { try { throw new Exception(); } catch (Throwable e) { // ... } } } 


But if only a descendant is intercepted, it will not work.
 public class App { public static void main(String[] args) { try { throw new Throwable(); } catch (Exception e) { // ... } } } >> COMPILATION ERROR: unhandled exception: java.lang.Throwable 


Of course, intercepting "brother" is not suitable either.
 public class App { public static void main(String[] args) { try { throw new Exception(); } catch (Error e) { // ... } } } >> COMPILATION ERROR: unhandled exception: java.lang.Exception 


If you have intercepted a part, then you can not frighten
 import java.io.EOFException; import java.io.FileNotFoundException; public class App { // EOFException  catch-,    public static void main(String[] args) throws FileNotFoundException { try { if (System.currentTimeMillis() % 2 == 0) { throw new EOFException(); } else { throw new FileNotFoundException(); } } catch (EOFException e) { // ... } } } 



6. Behavior of the / JVM compiler


It is necessary to understand that
- checking for cheched exceptions occurs at the time of compilation (compile-time checking)
- catching exceptions (catch) occurs at the time of execution (runtime checking)
 public class App { //  Exception public static void main(String[] args) throws Exception { Throwable t = new Exception(); //    Exception throw t; //     } } >> COMPILATION ERROR: unhandled exception: java.lang.Throwable 


Complete analogy with
 public class App { public static void main(String[] args) throws Exception { Object ref = "Hello!"; // ref    char c = ref.charAt(0); //     } } >> COMPILATION ERROR: Cannot resolve method 'charAt(int)' 

Although LINK ref INDICATES to an object of type java.lang.String (which has a charAt (int) method), but TYPE OF LINKS is java.lang.Object (a reference of type java.lang.Object to an object of type java.lang.String). The compiler focuses on the “left type” (the type of the link, not the type of the referenced type) and does not pass such code.

Although In this SITUATION, the compiler could make out that t refers to Exception and ref to String, but this is no longer possible with separate compilations. Imagine we COULD compile SEPARATELY such a class, package it in a jar and distribute
 //  !   ! public class App { public static void f0(Throwable t) throws Exception { throw t; } public static void f1(Object ref){ char c = ref.charAt(0); } } 

And someone takes this class, adds it to the classpath and calls App.f0 (new Throwable ()) or App.f1 (new Integer (42)). In this case, the JVM would be faced with a situation where it requires you to throw a checked exception that the compiler did not track (in the case of f0) or call a method that does not exist (in the case of f1)!

Java is a language with static typing (i.e., tracking of the correctness of using types (presence of used fields, presence of called methods, checking for checked exceptions, ...) is performed by the compiler), prohibits such behavior. In some languages ​​(languages ​​with dynamic typing - tracking the correctness of using types is performed by the runtime (it is allowed, for example, in JavaScript).

The compiler will not skip this code, although the main method will NOT throw out exceptions.
 public class App { //  Exception public static void main(String[] args) throws Exception { try { Throwable t = new Exception(); //    Exception throw t; //     } catch (Exception e) { System.out.println("!"); } } } >> COMPILATION ERROR: unhandled exception: java.lang.Throwable 


 public class App { //   Throwable public static void main(String[] args) throws Throwable { try { Throwable t = new Exception(); //    Exception throw t; } catch (Exception e) { //    Exception System.out.println("!"); } } } >> ! 



7. Overriding and throws


When overriding a child's list of exceptions, it is not obliged to coincide with that of an ancestor.
But it should be "not stronger" than the ancestor list:
 import java.io.FileNotFoundException; import java.io.IOException; public class Parent { //   IOException  InterruptedException public void f() throws IOException, InterruptedException {} } class Child extends Parent { //      IOException @Override public void f() throws FileNotFoundException {} } 


However, here we tried to "extend the type" of thrown exceptions.
 import java.io.IOException; public class Parent { public void f() throws IOException, InterruptedException {} } class ChildB extends Parent { @Override public void f() throws Exception {} } >> COMPILATION ERROR: overridden method does not throw 'java.lang.Exception' 


Why can I narrow the type, but not expand?
Consider the following situation:
 public class Parent { //   Exception public void f() throws Exception {} } 


 //     Java, , ,   public class Child extends Parent { //   Exception  Throwable public void f() throws Throwable {} } 


 public class Demo { public static void test(Parent ref) { //   , Parent.f()  Exception     catch try { ref.f(); } catch(Exception e) {} } } 


 public class App { public static void main(String[] args) { //   , Demo.test  Parent      - Child Demo.test(new Child()); } } 


Take a close look at this example - if a descendant could extend the type of ancestor being thrown, then those places that “wait” for the ancestor, and get a copy of the “extended” descendant could uncontrollably throw checked exceptions


8. Inheritance


Recall the exception hierarchy with the checked / unchecked property flags set
  Object | Throwable(CHECKED) / \ Error(UNCHECKED) Exception(CHECKED) | RuntimeException(UNCHECKED) 

The logic of the location of the property is NOT CONNECTED WITH THE INHERITANCE. We will consider this logic later (in the following articles).
However, the checked / unchecked property of user-defined exception classes is built EXCLUSIVELY based on inheritance.
The rule is extremely simple:
1. If the exception from the list is Throwable, Error, Exception, RuntimeException - then you simply need to remember your property.
2. If you are not from the list, then your property is equal to the property of the ancestor. To break inheritance here it is impossible.

If we spawn descendants A, B, C, D, E, F, G, H, I, J, K, L which are inherited as follows from the “rhizome” (Throwable, Error, Exception, RuntimeException), then the value of their property is checked / unchecked can be seen on the diagram
  Object | Throwable(CHECKED) / | \ Error(UNCHECKED) | Exception(CHECKED) | | | | | A(UNC) D(UNC) | F(C) RuntimeException(UNCHECKED) / \ | / \ | | B(UNC) C(UNC) | G(C) H(C) I(UNC) J(UNC) E(C) / \ K(UNC) L(UNC) 


Contacts



I do Java online training ( programming courses ) and publish some of the training materials as part of the reworking of the Java Core course . You can see videos of lectures in the audience on the youtube channel , perhaps the video of the channel is better organized in this article .
My teaching method is that I
  1. showing different uses
  2. I build a complex sequence of examples for each option
  3. explain the logic of moving authors (as far as possible)
  4. I give a large number of tests (50-100) comprehensively testing understanding and demonstrate various combinations
  5. I give laboratory for independent work

This article follows points # 1 (various options) and # 2 (a sequence of examples for each option).

skype: GolovachCourses
email: GolovachCourses@gmail.com

Source: https://habr.com/ru/post/225585/


All Articles