原文:Java Coding Problems
协议:CC BY-NC-SA 4.0
贡献者:飞龙
本文来自【ApacheCN Java 谷歌翻译用谷歌翻译。
本章包括 18 参与对象、不变性和switch
表达式问题。本章从处理开始null
引用的几个问题。继续处理相关检查索引,equals()
和hashCode()
以及不变性(例如,编写不变性和从不变性传输/返回可变对象)。本章的最后一部分讨论了克隆对象和 JDK12switch
表达式。本章结束时,您将掌握对象和不变性的基本知识。此外,你将知道如何处理新的switch
表达式。在任何地方 Java 在开发人员的武库中,这些都是有价值的、不可选的知识。
问题
使用以下问题来测试你的对象、不变性和switch
表达式编程能力。在转向解决方案和下载示例程序之前,我强烈建议您尝试每个问题:
- 使用代码检查
null
函数引用:编写程序,引用给定的函数和命令代码null
检查。 - 检查
null
引用并抛出定制NullPointerException
错误:编写程序并执行给定的参考null
检查并抛出带有定制信息的定制信息NullPointerException
。 - 检查
null
引用并抛出指定的异常(例如,IllegalArgumentException
:编写一个程序,对给定的引用执行null
检查并抛出指定异常。 - 检查
null
引用并返回非null
默认引用:编写程序,对给定引用执行null
检查,如果是非null
,返回;否则返回非null
默认引用。 - 检查从 0 索引在长度范围内:编写程序,检查给定索引是否在 0(含)在给定长度(不含)之间。如果给定索引超过 0 在给定长度范围内,抛出
IndexOutOfBoundsException
。 - 检查从 0 长度范围内的子范围:编写程序,检查给定的子范围从开始到结束,是否在 0 在给定长度范围内。若给定子范围不在范围内,则抛出
IndexOutOfBoundsException
。 - 解释
equals()
和hashCode()
并举例说明equals()
和hashCode()
方法在 Java 如何工作。 - 不可变对象概述:解释和举例说明什么 Java 不可变变对象。
- 不可变字符串:解释
String
类别不可变的原因。 - 编写不可变类:编写表示不可变类的程序。
- 将可变对象传递给不可变类或从不可变类返回:编写程序,将可变对象传递给不可变类或从不可变类返回。
- 通过构建器模式编写不可变类:编写构建器模式实现的程序。. 避免不可变对象中的坏数据:编写防止不可变对象中的坏数据坏数据的程序。
- 克隆对象:编写程序,演示浅层和深层克隆技术。
- 覆盖
toString()
:解释并举例说明覆盖toString()
的实践。 switch
表达式:简要概述 JDK12 中的switch
表达式。- 多个
case
标签:写一段代码,使用多个代码case
标签举例说明 JDK12switch
。 - 句子块:编写代码作为例子 JDK12
switch
,其中的case
标签指向花括号块。
以下部分介绍了上述每个问题的解决方案。记住,通常没有正确的方法来解决特定的问题。此外,请记住,这里显示的解释只包括解决问题所需的最有趣和最重要的细节。下载示例解决方案,查看更多详细信息,并尝试程序。
40 检查函数和命令代码中的空引
检查函数样式或命令代码null
引用是减少名称的常用推荐技术NullPointerException
异常发生。该检查广泛应用于方法参数,以确保引用不会导致传输NullPointerException
或意外行为。
例如,将List<Integer>
至少需要两种传递方法null
检查。首先,该方法应确保列表引用本身不是null
。其次,根据列表的使用方法,应确保列表不包括在内null
对象:
List<Integer> numbers = Arrays.asList(1, 2, null, 4, null, 16, 7, null);
该列表将传递给以下方法:
public static List<Integer> evenIntegers(List<Integer> integers) {
if (integers == null) {
return Collections.EMPTY_LIST; } List<Integer> evens = new ArrayList<>(); for (Integer nr: integers) {
if (nr != null && nr % 2 == 0) {
evens.add(nr); } } return evens; }
注意,前面的代码使用依赖于==
和!=
运算符(integers==null
、nr !=null
的经典检查。从 JDK8 开始,java.util.Objects
类包含两个方法,它们基于这两个操作符包装null
检查:object == null
包装在Objects.isNull()
中,object != null
包装在Objects.nonNull()
中。
基于这些方法,前面的代码可以重写如下:
public static List<Integer> evenIntegers(List<Integer> integers) {
if (Objects.isNull(integers)) {
return Collections.EMPTY_LIST;
}
List<Integer> evens = new ArrayList<>();
for (Integer nr: integers) {
if (Objects.nonNull(nr) && nr % 2 == 0) {
evens.add(nr);
}
}
return evens;
}
现在,代码在某种程度上更具表现力,但这并不是这两种方法的主要用法。实际上,这两个方法是为了另一个目的(符合 API 注解)而添加的——在 Java8 函数式代码中用作谓词。在函数式代码中,null
检查可以如下例所示完成:
public static int sumIntegers(List<Integer> integers) {
if (integers == null) {
throw new IllegalArgumentException("List cannot be null");
}
return integers.stream()
.filter(i -> i != null)
.mapToInt(Integer::intValue).sum();
}
public static boolean integersContainsNulls(List<Integer> integers) {
if (integers == null) {
return false;
}
return integers.stream()
.anyMatch(i -> i == null);
}
很明显,i -> i != null
和i -> i == null
的表达方式与周围的代码不一样。让我们用Objects.nonNull()
和Objects.isNull()
替换这些代码片段:
public static int sumIntegers(List<Integer> integers) {
if (integers == null) {
throw new IllegalArgumentException("List cannot be null");
}
return integers.stream()
.filter(Objects::nonNull)
.mapToInt(Integer::intValue).sum();
}
public static boolean integersContainsNulls(List<Integer> integers) {
if (integers == null) {
return false;
}
return integers.stream()
.anyMatch(Objects::isNull);
}
或者,我们也可以使用Objects.nonNull()
和Objects.isNull()
方法作为参数:
public static int sumIntegers(List<Integer> integers) {
if (Objects.isNull(integers)) {
throw new IllegalArgumentException("List cannot be null");
}
return integers.stream()
.filter(Objects::nonNull)
.mapToInt(Integer::intValue).sum();
}
public static boolean integersContainsNulls(List<Integer> integers) {
if (Objects.isNull(integers)) {
return false;
}
return integers.stream()
.anyMatch(Objects::isNull);
}
令人惊叹的!因此,作为结论,无论何时需要进行null
检查,函数式代码都应该依赖于这两种方法,而在命令式代码中,这是一种偏好。
41 检查空引用并引发自定义的NullPointerException
检查null
引用并用定制消息抛出NullPointerException
可以使用以下代码完成(此代码执行这四次,在构造器中执行两次,在assignDriver()
方法中执行两次):
public class Car {
private final String name;
private final Color color;
public Car(String name, Color color) {
if (name == null) {
throw new NullPointerException("Car name cannot be null");
}
if (color == null) {
throw new NullPointerException("Car color cannot be null");
}
this.name = name;
this.color = color;
}
public void assignDriver(String license, Point location) {
if (license == null) {
throw new NullPointerException("License cannot be null");
}
if (location == null) {
throw new NullPointerException("Location cannot be null");
}
}
}
因此,这段代码通过结合==
操作符和NullPointerException
类的手动实例化来解决这个问题。从 JDK7 开始,这种代码组合隐藏在一个名为Objects.requireNonNull()
的static
方法中。通过这种方法,前面的代码可以用表达的方式重写:
public class Car {
private final String name;
private final Color color;
public Car(String name, Color color) {
this.name = Objects.requireNonNull(name, "Car name cannot be
null");
this.color = Objects.requireNonNull(color, "Car color cannot be
null");
}
public void assignDriver(String license, Point location) {
Objects.requireNonNull(license, "License cannot be null");
Objects.requireNonNull(location, "Location cannot be null");
}
}
因此,如果指定的引用是null
,那么Objects.requireNonNull()
将抛出一个包含所提供消息的NullPointerException
。否则,它将返回选中的引用。
在构造器中,当提供的引用是null
时,有一种典型的抛出NullPointerException
的方法。但在方法上(例如,assignDriver()
),这是一个有争议的方法。一些开发人员更喜欢返回一个无害的结果或者抛出IllegalArgumentException
。下一个问题,检查空引用并抛出指定的异常(例如,IllegalArgumentException
),解决了IllegalArgumentException
方法。
在 JDK7 中,有两个Objects.requireNonNull()
方法,一个是以前使用的,另一个是抛出带有默认消息的NullPointerException
,如下例所示:
this.name = Objects.requireNonNull(name);
从 JDK8 开始,还有一个Objects.requireNonNull()
。这个将NullPointerException
的自定义消息封装在Supplier
中。这意味着消息创建被推迟,直到给定的引用是null
(这意味着使用+
操作符连接消息的各个部分不再是一个问题)。
举个例子:
this.name = Objects.requireNonNull(name, ()
-> "Car name cannot be null ... Consider one from " + carsList);
如果此引用不是null
,则不创建消息。
42 检查空引用并引发指定的异常
当然,一种解决方案需要直接依赖于==
操作符,如下所示:
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
因为没有requireNonNullElseThrow()
方法,所以这个问题不能用java.util.Objects
的方法来解决。抛出IllegalArgumentException
或其他指定的异常可能需要一组方法,如下面的屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ure4cICg-1657077359609)(img/fcd54997-7ebf-46d7-8eea-c80bb23be82a.png)]
让我们关注一下requireNonNullElseThrowIAE()
方法。这两个方法抛出IllegalArgumentException
,其中一个自定义消息被指定为String
或Supplier
(在null
被求值为true
之前避免创建):
public static <T> T requireNonNullElseThrowIAE(
T obj, String message) {
if (obj == null) {
throw new IllegalArgumentException(message);
}
return obj;
}
public static <T> T requireNonNullElseThrowIAE(T obj,
Supplier<String> messageSupplier) {
if (obj == null) {
throw new IllegalArgumentException(messageSupplier == null
? null : messageSupplier.get());
}
return obj;
}
所以,投掷IllegalArgumentException
可以通过这两种方法来完成。但这还不够。例如,代码可能需要抛出IllegalStateException
、UnsupportedOperationException
等。对于这种情况,最好采用以下方法:
public static <T, X extends Throwable> T requireNonNullElseThrow(
T obj, X exception) throws X {
if (obj == null) {
throw exception;
}
return obj;
}
public static <T, X extends Throwable> T requireNotNullElseThrow(
T obj, Supplier<<? extends X> exceptionSupplier) throws X {
if (obj != null) {
return obj;
} else {
throw exceptionSupplier.get();
}
}
考虑将这些方法添加到名为MyObjects
的助手类中。如以下示例所示调用这些方法:
public Car(String name, Color color) {
this.name = MyObjects.requireNonNullElseThrow(name,
new UnsupportedOperationException("Name cannot be set as null"));
this.color = MyObjects.requireNotNullElseThrow(color, () ->
new UnsupportedOperationException("Color cannot be set as null"));
}
此外,我们也可以通过这些例子来丰富MyObjects
中的其他异常。
43 检查空引用并返回非空默认引用
通过if
-else
(或三元运算符)可以很容易地提供该问题的解决方案,如以下示例所示(作为变体,name
和color
可以声明为非final
,并在声明时用默认值初始化):
public class Car {
private final String name;
private final Color color;
public Car(String name, Color color) {
if (name == null) {
this.name = "No name";
} else {
this.name = name;
}
if (color == null) {
this.color = new Color(0, 0, 0);
} else {
this.color = color;
}
}
}
但是,从 JDK9 开始,前面的代码可以通过Objects
类的两个方法简化。这些方法是requireNonNullElse()
和requireNonNullElseGet()
。它们都有两个参数,一个是检查空值的引用,另一个是在检查的引用为null
时返回的非null
默认引用:
public class Car {
private final String name;
private final Color color;
public Car(String name, Color color) {
this.name = Objects.requireNonNullElse(name, "No name");
this.color = Objects.requireNonNullElseGet(color,
() -> new Color(0, 0, 0));
}
}
在前面的示例中,这些方法在构造器中使用,但也可以在方法中使用。
44 检查从 0 到长度范围内的索引
首先,让我们用一个简单的场景来突出这个问题。此场景可能在以下简单类中实现:
public class Function {
private final int x;
public Function(int x) {
this.x = x;
}
public int xMinusY(int y) {
return x - y;
}
public static int oneMinusY(int y) {
return 1 - y;
}
}
注意,前面的代码片段没有对x
和y
进行任何范围限制。现在,让我们施加以下范围(这在数学函数中非常常见):
x
必须介于 0(含)和 11(不含)之间,所以x
属于[0, 11)
。- 在
xMinusY()
方法中,y
必须在 0(含)x
(不含)之间,所以y
属于[0, x)
。 - 在
oneMinusY()
方法中,y
必须介于 0(包含)和 16(排除)之间,所以y
属于[0, 16)
。
这些范围可以通过if
语句在代码中施加,如下所示:
public class Function {
private static final int X_UPPER_BOUND = 11;
private static final int Y_UPPER_BOUND = 16;
private final int x;
public Function(int x) {
if (x < 0 || x >= X_UPPER_BOUND) {
throw new IndexOutOfBoundsException("...");
}
this.x = x;
}
public int xMinusY(int y) {
if (y < 0 || y >= x) {
throw new IndexOutOfBoundsException("...");
}
return x - y;
}
public static int oneMinusY(int y) {
if (y < 0 || y >= Y_UPPER_BOUND) {
throw new IndexOutOfBoundsException("...");
}
return 1 - y;
}
}
考虑用更有意义的异常替换IndexOutOfBoundsException
(例如,扩展IndexOutOfBoundsException
并创建一个类型为RangeOutOfBoundsException
的自定义异常)。
从 JDK9 开始,可以重写代码以使用Objects.checkIndex()
方法。此方法验证给定索引是否在 0 到长度的范围内,并返回该范围内的给定索引或抛出IndexOutOfBoundsException
:
public class Function {
private static final int X_UPPER_BOUND = 11;
private static final int Y_UPPER_BOUND = 16;
private final int x;
public Function(int x) {
this.x = Objects.checkIndex(x, X_UPPER_BOUND);
}
public int xMinusY(int y) {
Objects.checkIndex(y, x);
return x - y;
}
public static int oneMinusY(int y) {
Objects.checkIndex(y, Y_UPPER_BOUND);
return 1 - y;