@@ -32,13 +32,28 @@ Scala 的解决方案是使用类似 `Option`/`Some`/`None` 类的结构。
32
32
想象一下,您想编写一个方法,可以轻松地将字符串转换为整数值,并且您想要一种优雅的方法来处理异常,这个是异常是该方法获取类似“Hello”而不是“1”的字符串时引发的。
33
33
对这种方法的初步猜测可能如下所示:
34
34
35
+ {% tabs fp-java-try class=tabs-scala-version %}
36
+ {% tab 'Scala 2' %}
37
+ ``` scala
38
+ def makeInt (s : String ): Int =
39
+ try {
40
+ Integer .parseInt(s.trim)
41
+ } catch {
42
+ case e : Exception => 0
43
+ }
44
+ ```
45
+ {% endtab %}
46
+
47
+ {% tab 'Scala 3' %}
35
48
``` scala
36
49
def makeInt (s : String ): Int =
37
50
try
38
51
Integer .parseInt(s.trim)
39
52
catch
40
53
case e : Exception => 0
41
54
```
55
+ {% endtab %}
56
+ {% endtabs %}
42
57
43
58
如果转换成功,则此方法返回正确的 ` Int ` 值,但如果失败,则该方法返回 ` 0 ` 。
44
59
出于某些目的,这可能是可以的,但它并不准确。
@@ -57,23 +72,42 @@ Scala 中这个问题的一个常见解决方案是使用三个类,称为 `Opt
57
72
58
73
这是 ` makeInt ` 的修订版:
59
74
75
+ {% tabs fp--try-option class=tabs-scala-version %}
76
+ {% tab 'Scala 2' %}
77
+ ``` scala
78
+ def makeInt (s : String ): Option [Int ] =
79
+ try {
80
+ Some (Integer .parseInt(s.trim))
81
+ } catch {
82
+ case e : Exception => None
83
+ }
84
+ ```
85
+ {% endtab %}
86
+
87
+ {% tab 'Scala 3' %}
60
88
``` scala
61
89
def makeInt (s : String ): Option [Int ] =
62
90
try
63
91
Some (Integer .parseInt(s.trim))
64
92
catch
65
93
case e : Exception => None
66
94
```
95
+ {% endtab %}
96
+ {% endtabs %}
67
97
68
98
这段代码可以理解为,“当给定的字符串转换为整数时,返回包裹在 ` Some ` 中的 ` Int ` ,例如 ` Some(1) ` 。
69
99
当字符串无法转换为整数时,会抛出并捕获异常,并且该方法返回一个 ` None ` 值。”
70
100
71
101
这些示例展示了 ` makeInt ` 的工作原理:
72
102
103
+ {% tabs fp-try-option-example %}
104
+ {% tab 'Scala 2 and 3' %}
73
105
``` scala
74
106
val a = makeInt(" 1" ) // Some(1)
75
107
val b = makeInt(" one" ) // None
76
108
```
109
+ {% endtab %}
110
+ {% endtabs %}
77
111
78
112
如图所示,字符串` "1" ` 产生一个 ` Some(1) ` ,而字符串 ` "one" ` 产生一个 ` None ` 。
79
113
这是错误处理的 ` Option ` 方法的本质。
@@ -99,11 +133,24 @@ val b = makeInt("one") // None
99
133
100
134
一种可能的解决方案是使用 ` match ` 表达式:
101
135
136
+ {% tabs fp-option-match class=tabs-scala-version %}
137
+ {% tab 'Scala 2' %}
138
+ ``` scala
139
+ makeInt(x) match {
140
+ case Some (i) => println(i)
141
+ case None => println(" That didn’t work." )
142
+ }
143
+ ```
144
+ {% endtab %}
145
+
146
+ {% tab 'Scala 3' %}
102
147
``` scala
103
148
makeInt(x) match
104
149
case Some (i) => println(i)
105
150
case None => println(" That didn’t work." )
106
151
```
152
+ {% endtab %}
153
+ {% endtabs %}
107
154
108
155
在本例中,如果 ` x ` 可以转换为 ` Int ` ,则计算第一个 ` case ` 子句右侧的表达式;如果 ` x ` 不能转换为 ` Int ` ,则计算第二个 ` case ` 子句右侧的表达式。
109
156
@@ -113,6 +160,20 @@ makeInt(x) match
113
160
例如,假设您要将三个字符串转换为整数值,然后将它们相加。
114
161
这就是你使用 ` for ` 表达式和 ` makeInt ` 的方法:
115
162
163
+ {% tabs fp-for-comprehension class=tabs-scala-version %}
164
+ {% tab 'Scala 2' %}
165
+ ``` scala
166
+ val y = for {
167
+ a <- makeInt(stringA)
168
+ b <- makeInt(stringB)
169
+ c <- makeInt(stringC)
170
+ } yield {
171
+ a + b + c
172
+ }
173
+ ```
174
+ {% endtab %}
175
+
176
+ {% tab 'Scala 3' %}
116
177
``` scala
117
178
val y = for
118
179
a <- makeInt(stringA)
@@ -121,6 +182,8 @@ val y = for
121
182
yield
122
183
a + b + c
123
184
```
185
+ {% endtab %}
186
+ {% endtabs %}
124
187
125
188
在该表达式运行后,` y ` 将是以下两种情况之一:
126
189
@@ -129,6 +192,24 @@ yield
129
192
130
193
你可以自己测试一下:
131
194
195
+ {% tabs fp-for-comprehension-evaluation class=tabs-scala-version %}
196
+ {% tab 'Scala 2' %}
197
+ ``` scala
198
+ val stringA = " 1"
199
+ val stringB = " 2"
200
+ val stringC = " 3"
201
+
202
+ val y = for {
203
+ a <- makeInt(stringA)
204
+ b <- makeInt(stringB)
205
+ c <- makeInt(stringC)
206
+ } yield {
207
+ a + b + c
208
+ }
209
+ ```
210
+ {% endtab %}
211
+
212
+ {% tab 'Scala 3' %}
132
213
``` scala
133
214
val stringA = " 1"
134
215
val stringB = " 2"
@@ -141,15 +222,21 @@ val y = for
141
222
yield
142
223
a + b + c
143
224
```
225
+ {% endtab %}
226
+ {% endtabs %}
144
227
145
228
使用该样本数据,变量 ` y ` 的值将是 ` Some(6) ` 。
146
229
147
230
要查看失败案例,请将这些字符串中的任何一个更改为不会转换为整数的字符串。
148
231
当你这样做时,你会看到 ` y ` 是 ` None ` :
149
232
233
+ {% tabs fp-for-comprehension-failure-result %}
234
+ {% tab 'Scala 2 and 3' %}
150
235
``` scala
151
236
y: Option [Int ] = None
152
237
```
238
+ {% endtab %}
239
+ {% endtabs %}
153
240
154
241
## 将 Option 视为容器
155
242
@@ -171,10 +258,14 @@ They have many of the methods you’d expect from a collection class, including
171
258
172
259
This raises an interesting question: What will these two values print, if anything?
173
260
261
+ {% tabs fp-option-methods-evaluation %}
262
+ {% tab 'Scala 2 and 3' %}
174
263
``` scala
175
264
makeInt(" 1" ).foreach(println)
176
265
makeInt(" x" ).foreach(println)
177
266
```
267
+ {% endtab %}
268
+ {% endtabs %}
178
269
179
270
Answer: The first example prints the number ` 1 ` , and the second example doesn’t print anything.
180
271
The first example prints ` 1 ` because:
@@ -196,12 +287,30 @@ Somewhere in Scala’s history, someone noted that the first example (the `Some`
196
287
* But* despite having two different possible outcomes, the great thing with ` Option ` is that there’s really just one path: The code you write to handle the ` Some ` and ` None ` possibilities is the same in both cases.
197
288
The ` foreach ` examples look like this:
198
289
290
+ {% tabs fp-another-option-method-example %}
291
+ {% tab 'Scala 2 and 3' %}
199
292
``` scala
200
293
makeInt(aString).foreach(println)
201
294
```
295
+ {% endtab %}
296
+ {% endtabs %}
202
297
203
298
And the ` for ` expression looks like this:
204
299
300
+ {% tabs fp-another-for-comprehension-example class=tabs-scala-version %}
301
+ {% tab 'Scala 2' %}
302
+ ``` scala
303
+ val y = for {
304
+ a <- makeInt(stringA)
305
+ b <- makeInt(stringB)
306
+ c <- makeInt(stringC)
307
+ } yield {
308
+ a + b + c
309
+ }
310
+ ```
311
+ {% endtab %}
312
+
313
+ {% tab 'Scala 3' %}
205
314
``` scala
206
315
val y = for
207
316
a <- makeInt(stringA)
@@ -210,16 +319,31 @@ val y = for
210
319
yield
211
320
a + b + c
212
321
```
322
+ {% endtab %}
323
+ {% endtabs %}
213
324
214
325
With exceptions you have to worry about handling branching logic, but because ` makeInt ` returns a value, you only have to write one piece of code to handle both the Happy and Unhappy Paths, and that simplifies your code.
215
326
216
327
Indeed, the only time you have to think about whether the ` Option ` is a ` Some ` or a ` None ` is when you handle the result value, such as in a ` match ` expression:
217
328
329
+ {% tabs fp-option-match-handle class=tabs-scala-version %}
330
+ {% tab 'Scala 2' %}
331
+ ``` scala
332
+ makeInt(x) match {
333
+ case Some (i) => println(i)
334
+ case None => println(" That didn't work." )
335
+ }
336
+ ```
337
+ {% endtab %}
338
+
339
+ {% tab 'Scala 3' %}
218
340
``` scala
219
341
makeInt(x) match
220
342
case Some (i) => println(i)
221
343
case None => println(" That didn't work." )
222
344
```
345
+ {% endtab %}
346
+ {% endtabs %}
223
347
224
348
> There are several other ways to handle ` Option ` values.
225
349
> See the reference documentation for more details.
@@ -229,6 +353,8 @@ makeInt(x) match
229
353
230
354
回到 ` null ` 值,` null ` 值可以悄悄地潜入你的代码的地方是这样的类:
231
355
356
+ {% tabs fp=case-class-nulls %}
357
+ {% tab 'Scala 2 and 3' %}
232
358
``` scala
233
359
class Address (
234
360
var street1 : String ,
@@ -238,10 +364,26 @@ class Address(
238
364
var zip : String
239
365
)
240
366
```
367
+ {% endtab %}
368
+ {% endtabs %}
241
369
242
370
虽然地球上的每个地址都有一个 ` street1 ` 值,但 ` street2 ` 值是可选的。
243
371
因此,` street2 ` 字段可以被分配一个 ` null ` 值:
244
372
373
+ {% tabs fp-case-class-nulls-example class=tabs-scala-version %}
374
+ {% tab 'Scala 2' %}
375
+ ``` scala
376
+ val santa = new Address (
377
+ " 1 Main Street" ,
378
+ null , // <-- D’oh! A null value!
379
+ " North Pole" ,
380
+ " Alaska" ,
381
+ " 99705"
382
+ )
383
+ ```
384
+ {% endtab %}
385
+
386
+ {% tab 'Scala 3' %}
245
387
``` scala
246
388
val santa = Address (
247
389
" 1 Main Street" ,
@@ -251,10 +393,14 @@ val santa = Address(
251
393
" 99705"
252
394
)
253
395
```
396
+ {% endtab %}
397
+ {% endtabs %}
254
398
255
399
从历史上看,开发人员在这种情况下使用了空白字符串和空值,这两种方法都是使用技巧来解决基础性的问题,这个问题是:` street2 ` 是一个* 可选* 字段。
256
400
在 Scala 和其他现代语言中,正确的解决方案是预先声明 ` street2 ` 是可选的:
257
401
402
+ {% tabs fp-case-class-with-options %}
403
+ {% tab 'Scala 2 and 3' %}
258
404
``` scala
259
405
class Address (
260
406
var street1 : String ,
@@ -264,9 +410,25 @@ class Address(
264
410
var zip : String
265
411
)
266
412
```
413
+ {% endtab %}
414
+ {% endtabs %}
267
415
268
416
现在开发人员可以编写更准确的代码,如下所示:
269
417
418
+ {% tabs fp-case-class-with-options-example-none class=tabs-scala-version %}
419
+ {% tab 'Scala 2' %}
420
+ ``` scala
421
+ val santa = new Address (
422
+ " 1 Main Street" ,
423
+ None , // 'street2' has no value
424
+ " North Pole" ,
425
+ " Alaska" ,
426
+ " 99705"
427
+ )
428
+ ```
429
+ {% endtab %}
430
+
431
+ {% tab 'Scala 3' %}
270
432
``` scala
271
433
val santa = Address (
272
434
" 1 Main Street" ,
@@ -276,9 +438,25 @@ val santa = Address(
276
438
" 99705"
277
439
)
278
440
```
441
+ {% endtab %}
442
+ {% endtabs %}
279
443
280
444
或这个:
281
445
446
+ {% tabs fp-case-class-with-options-example-some class=tabs-scala-version %}
447
+ {% tab 'Scala 2' %}
448
+ ``` scala
449
+ val santa = new Address (
450
+ " 123 Main Street" ,
451
+ Some (" Apt. 2B" ),
452
+ " Talkeetna" ,
453
+ " Alaska" ,
454
+ " 99676"
455
+ )
456
+ ```
457
+ {% endtab %}
458
+
459
+ {% tab 'Scala 3' %}
282
460
``` scala
283
461
val santa = Address (
284
462
" 123 Main Street" ,
@@ -288,6 +466,8 @@ val santa = Address(
288
466
" 99676"
289
467
)
290
468
```
469
+ {% endtab %}
470
+ {% endtabs %}
291
471
292
472
## ` Option ` 不是唯一的解决方案
293
473
0 commit comments