-
Notifications
You must be signed in to change notification settings - Fork 68
/
chapter16-exception.jsh
186 lines (152 loc) · 6.37 KB
/
chapter16-exception.jsh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// To starts, run jshell --enable-preview which is a program able to interpret Java syntax
// then cut and paste the following lines to see how it works
// To exit jshell type /exit
// # Exception
// In Java, an exception is a mechanism to signal that the
// execution of a method can not be performed
// by example, trying to get a value of a list with an index equals to -1
String valueAt(List<String> list, int index) {
return list.get(index);
}
valueAt(List.of("hello"), -1);
// an exception has
// - a type that indicate the kind of error
// - an error message that explain in English the issue
// - a stacktrace which indicates where the exception was raised
// and the methods to reach that point
// In our example, `java.lang.IndexOutOfBoundsException` is the type,
// `Index: -1 Size: 1` is the message and
// ```
// at ImmutableCollections$AbstractImmutableList.outOfBounds (ImmutableCollections.java:201)
// at ImmutableCollections$List12.get (ImmutableCollections.java:418)
// at valueAt (#1:2)
// at (#2:1)
// ```
// is the stacktrace
// ## `throw`
// You can create (with `new`) and raise your own exception using the keyword `throw`
String valueAt(List<String> list, int index) {
if (index < 0 || index >= list.size()) {
throw new IllegalArgumentException("invalid index " + index);
}
return list.get(index);
}
valueAt(List.of("hello"), -1);
// The stacktrace is populated automatically when you create the exception
// not where you throw it so it's a good idea to create the exception
// not too far from where you throw it.
// In the following example, the stacktrace will say that the exception
// is created at `notTooFar (#5:2)`, on the second line, not at `notTooFar (#5:4)`.
void notTooFar() {
var exception = new RuntimeException("i'm created here");
// an empty line
throw exception;
}
notTooFar();
// ## Use existing exceptions
// While you can create your own exception (see below),
// usually we are re-using already existing exceptions.
// Exceptions commonly used in Java
// - NullPointerException if a reference is null
// - IllegalArgumentException if an argument of a method is not valid
// - IllegalStateException if the object state doesn't allow to proceed,
// by example if a file is closed, you can not read it
// - AssertionError if a code that should not be reached has been reached
// By example
enum State { OK, NOT_OK }
void testState(State state) {
switch(state) {
case OK -> System.out.println("Cool !");
case NOT_OK -> System.out.println("Not cool");
default -> { throw new AssertionError("Danger, Will Robinson"); }
}
}
// here the AssertionError can only be thrown if the code if testState()
// and the enum State disagree on set of possible values
// By example, if a new state is added
enum State { OK, NOT_OK, UNKNOWN }
testState(State.UNKNOWN);
// ## Recovering from an exception
// In Java, you can recover from an exception using a `try/catch` block.
URI uri;
try {
uri = new URI("http://i'm a malformed uri");
} catch(URISyntaxException e) {
// if the URI is malformed, used google by default
uri = new URI("http://www.google.fr");
}
System.out.println(uri);
// A common mistake is to write a `try/catch` in a method with an empty catch
// or a catch that log/print a message instead of actually recovering from the
// exception
// As a rule of thumb, if you can not write something meaningful in the catch
// block then you should not use a `try/catch`.
// ## Fighting with the compiler
// For the compiler, there are two kinds of exceptions that are handled differently
// - unchecked exception, you can throw them anywhere you want
// - checked exception, you can only throw them if
// - you are inside a method that declare to throws that exception (or a supertype)
// - you are inside a try/catch block on that exception (or a supertype)
// In Java, an exception that inherits from `RuntimeException` or `Error` are
// unchecked exceptions, all the others are checked exceptions
// so this code doesn't compile because `IOException` inherits from `Exception`
// and not `RuntimeException`.
/*
void hello() {
Files.delete(Path.of("I don't exist"));
}
*/
// A way to fix the issue is to use the keywords `throws` to ask the caller
// of the method to deal with the exception, again the caller will have,
// either by propagating it with a `throws` or recover from it with a `try/catch`.
void hello() throws IOException {
Files.delete(Path.of("I don't exist"));
}
// As a rule of thumb, 99% of the time you want to propagate the exception,
// and keep the number of `try/catch` as low as possible in your program,
// so prefer `throws` to `try/catch`.
// ### When you can not use `throws`, wrap the exception
// If a method has it's signature fixed because it overrides a method of an interface,
// then you can not use `throws`
// The following example doesn't compile because the method `run` of a `Runnable`
// doesn't declare to `throws` `IOException` so the only solution seems to be
// to use a `try/catch`.
/*
var aRunnable = new Runnable() {
public void run() {
Files.delete(Path.of("I don't exist"));
}
};
*/
// So here, we have to use a `try/catch` but we still want to propagate the exception.
// The trick is wrap the checked exception into an unchecked exception.
// This trick is so common that the Java API already comes with existing
// classes to wrap common checked exceptions. For `IOException`, the unchecked
// equivalent is `UncheckedIOException`.
var aRunnable = new Runnable() {
public void run() {
try {
Files.delete(Path.of("I don't exist"));
} catch(IOException e) {
// the way to recover, is to propagate it as an unchecked
throw new UncheckedIOException(e);
}
}
};
aRunnable.run();
// The exception `UndeclaredThrowableException` is used as the generic unchecked exception
// to wrap any checked exception which do not have an unchecked equivalent.
// ## Create your own Exception
// You can create your own exception by creating a class that inherits from `RuntimeException`
// You should provide at least two constructors, one with a message and one with a message
// and a cause.
public class MyException extends RuntimeException {
public MyException(String message) {
super(message);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
}
throw new MyException("This is my exception");
// But in general, don't ! Reuse existing commonly used exceptions.