java方法可选参数
在Java类中设计方法时,某些参数对于其执行而言可能是可选的。 无论是在DTO,胖模型域对象还是简单的无状态服务类中,可选方法参数都是常见的。
从本文中, 您将学习如何在Java中处理可选参数 。 我们将专注于常规方法,具有可选字段的类构造函数,并快速查看所讨论主题的不良做法。 我们将停下来看看Java 8 Optional,并评估它是否符合我们的需求。
让我们开始吧。
1.可选方法参数
您可以通过几种不同的方法来处理Java可选参数。 我将引导您从最简单到更复杂。
@Nullable注解
为什么不传递零值呢? 这是一个简单的解决方案,不需要任何额外的工作。 您没有任何需要作为方法参数之一的对象吗? 没问题。 只需传递null即可,编译器很高兴。
这里的问题是可读性。 调用方法的程序员如何知道他是否可以安全地传递null? 对于哪些参数可以接受空值,哪些是必需的?
为了清楚说明null是有效输入,可以使用@Nullable批注。
User createUser(String name, @Nullable Email email) {
// ...
}
您不同意此方法声明是不言自明的吗?
尽管简单,但是null传递方法的问题在于它很容易失控。 团队可能会Swift开始过度使用它,并使代码库难以维护,并带有大量的空检查条件 。
不过,还有其他选择。
可选清单
代替null,我们有时可以创建类的空表示。 考虑一下Java集合。 如果方法接受列表或映射,则永远不要使用null作为输入。
空集合总是比空集合好,因为在大多数情况下,不需要任何特殊处理。
您可能想知道为什么要浪费内存来创建空集合。 毕竟,null不会花费您任何费用。
您的怀疑是有道理的。 幸运的是,有一个简单的解决方案。
每当需要空的代表时,就不应创建集合的新实例。 在代码库中重复使用同一实例。
你知道吗? Java已经具有您可以使用的所有集合的空实例。 您可以在Collections实用程序类中找到它们 。
User createUser(String name, List<Rights> rights) {
// ...
}
import java.util.Collections;
// ...
create("bob", Collections.emptyList());
空对象模式
空集合的概念也适用于其他类。 空集合只是具有零元素的常规集合。 同样,您可以考虑应用程序中的其他对象。
Null对象是表示缺失值的类的特殊实例。 如果某些方法希望将对象作为参数,则始终可以传递Null对象表示形式,而不必担心它将在运行时引起意外的异常。
您可以通过两种方式实现Null对象模式。
对于简单值对象,将预定义值分配给属性的默认实例就足够了。 通常,您将此Null对象公开为常量,以便可以多次重用。 例如:
public class User {
public static final User EMPTY = new User("", Collections.emptyList());
private final String name;
private final List<Rights> rights;
public User(String name, List<Rights> rights) {
Objects.requireNonNull(name);
Objects.requireNonNull(rights);
this.name = name;
this.rights = rights;
}
// ...
}
如果您的Null对象还需要模仿通过方法公开的某些行为,则简单实例可能无法工作。 在这种情况下,您应该扩展类并覆盖此类方法。
这是扩展前一个示例的示例:
public class AnonymousUser extends User {
public static final AnonymousUser INSTANCE = new AnonymousUser();
private AnonymousUser() {
super("", Collections.emptyList());
}
@Override
public void changeName(String newName) {
throw new AuthenticationException("Only authenticated user can change the name");
}
}
专用的Null对象类使您可以将多个角落案例放在一个地方,这使维护更加轻松。
方法重载
如果您设计的方法带有可选参数,则可以公开该方法的重载版本。 每种方法应仅接受必需的参数。
使用这种方法,您不必期望调用方将为可选参数提供默认值。 您可以通过自己的内部重载方法传递默认值。 换句话说,您对方法的调用者隐藏了可选参数的默认值。
User createUser(String name) {
this.createUser(name, Email.EMPTY);
}
User createUser(String name, Email email) {
Objects.requireNonNull(name);
Objects.requireNonNull(rights);
// ...
}
重载的方法可以互相调用,但这不是强制性的。 如果更方便,则可以独立实现每种方法。 但是,通常您会验证所有参数,并将逻辑放在参数列表最长的方法中。
值得一提的是,方法重载已在标准Java库中广泛使用。 当您学习如何设计API时,请向经验丰富的人学习。
参数对象模式
大多数开发人员都同意,当方法参数列表过长时,将变得难以阅读。 通常,您可以使用Parameter Object pattern处理问题。 参数对象是一个命名的容器类,它对所有方法参数进行分组。
是否解决了可选方法参数的问题?
不,不是。
它只是将问题移至参数对象的构造函数。
让我们看看如何用…解决这个更普遍的问题。
2.可选的构造函数参数
从带有可选参数的问题的角度来看,简单的构造函数与常规成员方法没有什么不同。 您可以成功使用我们已经在构造函数中讨论过的所有技术。
但是,当构造函数参数列表越来越长并且其中许多是可选参数时,应用构造函数重载似乎很麻烦。
如果您同意,则应签出Builder模式。
建造者模式
让我们考虑一个具有多个可选字段的类:
class ProgrammerProfile {
// required field
private final String name;
// optional fields
private final String blogUrl;
private final String twitterHandler;
private final String githubUrl;
public ProgrammerProfile(String name) {
Objects.requireNonNull(name);
this.name = name;
// other fields assignment...
}
// getters
}
如果创建了一个构造函数来覆盖所有可能的带有可选参数的组合,那么最终将得到不胜枚举的清单。
如何避免多个构造函数? 使用构建器类。
您通常将构建器实现为它应该构建的类的内部类。 这样,两个类都可以访问其私有成员。
看一下前面示例中的类的构建器:
class ProgrammerProfile {
// fields, getters, ...
private ProgrammerProfile(Builder builder) {
Objects.requireNonNull(builder.name);
name = builder.name;
blogUrl = builder.blogUrl;
twitterHandler = builder.twitterHandler;
githubUrl = builder.githubUrl;
}
public static Builder newBuilder() {
return new Builder();
}
static final class Builder {
private String name;
private String blogUrl;
private String twitterHandler;
private String githubUrl;
private Builder() {}
public Builder withName(String val) {
name = val;
return this;
}
public Builder withBlogUrl(String val) {
blogUrl = val;
return this;
}
public Builder withTwitterHandler(String val) {
twitterHandler = val;
return this;
}
public Builder withGithubUrl(String val) {
githubUrl = val;
return this;
}
public ProgrammerProfile build() {
return new ProgrammerProfile(this);
}
}
}
代替公共构造函数,我们只为内部构建器类公开一个静态工厂方法。 专用构造函数(构建器在build()方法中调用)使用构建器实例来分配所有字段并验证是否存在所有必需的值。
一想起来,这是一种非常简单的技术。
仅设置选定的可选参数的该构建器的客户机代码可能如下所示:
ProgrammerProfile.newBuilder()
.withName("Daniel")
.withBlogUrl("www.dolszewski.com/blog/")
.build();
使用构建器,您可以使用对象的可选参数创建所有可能的组合。
编译时安全的类生成器
不幸的是,仅通过查看上一段落中构建器的方法,您就无法真正分辨出哪些参数是可选的,哪些是必需的。 而且,在不知不觉中您可能会偶然忽略所需的参数。
查看以下错误使用的构建器示例 :
ProgrammerProfile.newBuilder()
.withBlogUrl("www.dolszewski.com/blog/")
.withTwitterHandler("daolszewski")
.build();
编译器不会报告任何错误。 您将仅在运行时意识到缺少必需参数的问题。
那么,您如何解决这个问题?
您需要稍微修改构建器工厂方法,以便仅可以使用必需参数来调用它,而仅对可选参数使用左构建器方法。
这就是您需要更改的所有内容:
class ProgrammerProfile {
// ...
public static Builder newBuilder(String name) {
return new Builder(name);
}
public static final class Builder {
private final String name;
// ...
private Builder(String name) {
this.name = name;
}
// ...
}
}
生成器类的生成
您可能会认为构建器需要大量代码。
不用担心
您不必自己键入所有代码。 所有流行的Java IDE都具有允许生成类生成器的插件。 IntelliJ用户可以检查InnerBuilder插件 ,而Eclipse爱好者可以看看Spart Builder Generator 。 您也可以在官方存储库中找到替代插件。
如果您使用项目Lombok ,它还可以简化与类构建器的合作。 如果需要开始的地方,可以查看有关Lombok建筑商的简短介绍 。
3. Java可选参数反模式
在浏览网络以寻找使用Java可选参数的方法时,除了我们已经介绍的内容之外,您还可以找到一些其他建议。 让我解释一下为什么您应该将它们视为错误的方法。
地图
从技术上讲,方法的输入是一组键值对。 在Java中,我们有一个符合此描述的标准内置数据结构-Map。
编译不会阻止您将HashMap <String,Object>用作所有可选方法参数的容器,但是您的常识应该如此。
尽管您可以在HashMap中放入任何内容,但这是错误的想法。 这种方法难以理解,难以理解,并将Swift成为您维护的噩梦。
还是不服气?
您绝对应该考虑将您的职业转到JavaScript开发人员。 在公司哭泣要容易得多。
Java变量
需要明确的是,使用Java varargs绝对没有错。 如果您不知道您的方法将被调用多少个参数,那么varargs非常适合。
但是将varargs用作单个值的容器(可能存在或不存在)是一种滥用。 这样的声明允许使用比预期更多的可选值来调用方法。 我们讨论了用于处理单个可选参数的更具描述性的方法。
为什么不将Optional作为方法参数?
最后,最有争议的方法-Java 8 Optional作为方法输入。 我已经写了一篇关于可选用例的文章 ,其中还介绍了方法参数。 让我扩展您在那里可以找到的内容。
内存使用情况
创建Optional类的实例时,必须为其分配内存。 尽管使用Optional.empty()访问的空可选实例是可重用的单例(就像我们已经讨论过的空集合一样),但非空实例将占用操作内存。
如果将此方法与其他方法进行比较,仅出于调用将立即解开对象的方法的目的而使用Optional工厂方法包装对象就没有意义。
但是,如今,垃圾收集器可以很好地处理短寿命的物品。 内存分配没什么大不了的。 我们还有其他缺点吗?
铭记读者
代码的可读性如何?
createProfile("Daniel", Optional.of("www.dolszewski.com/blog/"),
Optional.of("daolszewski"), Optional.of("https://github.com/danielolszewski"));
也许这只是个人喜好问题,但对于许多开发人员而言,多个Optional工厂调用会分散您的注意力。 阅读器代码中的噪音。 但同样,这只是一个口味问题。 让我们找到更令人信服的东西。
Java语言设计师的意见
Oracle公司Java语言架构师Brian Goetz 曾表示 ,将Optional添加到标准库中时要考虑方法的结果,而不是方法的输入。
但是软件开发人员是叛逆者,不喜欢听当局的话。 这种说法似乎也很微弱。 我们必须更深入。
Optional是否解决了可选参数问题?
如果您有这样的方法声明:
doSomethingWith(Optional<Object> parameter);
您应该期望多少输入?
该参数可以是包装值或空的可选实例。 答案是2,对吗?
错误。
真正的答案是3,因为您还可以将null用作参数。 当您不知道谁将成为您的API的调用者时,您应该对输入具有有限的信任。
在这种情况下,在处理参数之前,应检查Optional是否不等于null,然后检查该值是否存在。 非常复杂,您不同意吗?
我敢肯定我还没有穷尽这个话题。 由于这是Java编程的潜在圣战之一,因此您应该形成自己的见解。 如果要向Optional的参数列表中添加某些内容作为方法参数,请在注释中分享您的想法。 非常受欢迎。
结论
让我们回顾一下我们学到的东西。 Java无法为方法参数设置默认值。 该语言为我们提供了许多其他可选参数来处理可选参数。
这些替代方法包括@Nullable批注,空对象,方法重载和参数对象模式。 我们还熟悉具有多个可选字段的对象的类构建器。 最后,我们回顾了常见的不良做法和潜在的滥用行为。
如果您觉得这篇文章有帮助,请与您的关注者分享。 我也很想知道您的想法,欢迎提出所有意见。
翻译自: https://www.javacodegeeks.com/2018/11/java-optional-parameters.html
java方法可选参数