在 Java 中检查两个参数,要么都不为空,要么都为空

我使用 Spring boot 开发了一个用于发送电子邮件的 shell 项目,例如。

sendmail -from foo@bar.com -password  foobar -subject "hello world"  -to aaa@bbb.com

如果缺少 frompassword参数,则使用默认的发送方和密码,例如 noreply@bar.com123456

因此,如果用户传递 from参数,他们也必须传递 password参数,反之亦然。也就是说,要么两者都是非空的,要么两者都是空的。

我怎样才能优雅地检查这个?

现在我的方法是

if ((from != null && password == null) || (from == null && password != null)) {
throw new RuntimeException("from and password either both exist or both not exist");
}
42967 次浏览

好吧,听起来像是你在试图检查两者的“无效”条件是否相同。你可以用:

if ((from == null) != (password == null))
{
...
}

或者使用辅助变量使其更加明确:

boolean gotFrom = from != null;
boolean gotPassword = password != null;
if (gotFrom != gotPassword)
{
...
}

有一种使用 ^(XOR)运算符的方法:

if (from == null ^ password == null) {
// Use RuntimeException if you need to
throw new IllegalArgumentException("message");
}

如果只有一个变量为 null,则 if条件为 true。

但是我认为通常最好使用两个 if条件和不同的异常消息。你不能用一个条件来定义哪里出错了。

if ((from == null) && (password != null)) {
throw new IllegalArgumentException("If from is null, password must be null");
}
if ((from != null) && (password == null)) {
throw new IllegalArgumentException("If from is not null, password must not be null");
}

它更具可读性,更容易理解,而且只需要一点额外的输入。

正如我所看到的您的意图,没有必要总是检查两个排他的空性,但是检查 password是否为空,当且仅当 from不为空。如果 from为空,则可以忽略给定的 password参数并使用自己的默认值。

伪语言必须是这样写的:

if (from == null) { // form is null, ignore given password here
// use your own defaults
} else if (password == null) { // form is given but password is not
// throw exception
} else { // both arguments are given
// use given arguments
}

将这个功能放在一个带签名的2参数方法中:

void assertBothNullOrBothNotNull(Object a, Object b) throws RuntimeException

这样可以在您感兴趣的实际方法中节省空间,并使其更具可读性。稍微冗长一点的方法名没有错,非常短的方法也没有错。

就我个人而言,我更喜欢可读而不是优雅。

if (from != null && password == null) {
throw new RuntimeException("-from given without -password");
}
if (from == null && password != null) {
throw new RuntimeException("-password given without -from");
}

我认为处理这个问题的正确方法是考虑以下三种情况: 同时提供了“ from”和“ password”,两者都没有提供,两者混合使用。

if(from != null && password != null){
//use the provided values
} else if(from == null && password == null){
//both values are null use the default values
} else{
//throw an exception because the input is not correct.
}

听起来好像原来的问题想要在输入不正确的情况下中断流程,但是之后他们将不得不重复一些逻辑。也许一个很好的投掷语句可能是:

throw new IllegalArgumentException("form of " + form +
" cannot be used with a "
+ (password==null?"null":"not null") +
" password. Either provide a value for both, or no value for both"
);

我想提出另一个替代方案,也就是我实际上如何编写这段代码:

if( from != null )
{
if( password == null )
error( "password required for " + from );
}
else
{
if( password != null )
warn( "the given password will not be used" );
}

对我来说,这似乎是最自然的方式来表达这种情况,这使得它很容易理解的人谁可能会读到它的未来。它还允许您提供更有帮助的诊断消息,并将不必要的密码视为不那么严重,而且它使得修改这种情况很可能发生的密码变得容易。也就是说,您可能会发现,将密码作为命令行参数并不是最好的办法,并且可能希望允许在缺少参数的情况下从标准输入中可选地读取密码。或者您可能希望静默地忽略多余的密码参数。像这样的改变不需要你重写整个事情。

除此之外,它只执行最小数量的比较,所以它并不比更多的 “优雅”替代品更昂贵。虽然在这里性能不太可能成为问题,因为启动一个新进程已经比额外的 null 检查昂贵得多。

Java8解决方案是使用 Objects.isNull(Object),假设有静态导入:

if (isNull(from) != isNull(password)) {
throw ...;
}

对于 Java < 8(或者如果您不喜欢使用 Objects.isNull()) ,您可以很容易地编写自己的 isNull()方法。

下面是针对任意数量的空检查的通用解决方案

public static int nulls(Object... objs)
{
int n = 0;
for(Object obj : objs) if(obj == null) n++;
return n;
}


public static void main (String[] args) throws java.lang.Exception
{
String a = null;
String b = "";
String c = "Test";


System.out.println (" "+nulls(a,b,c));
}

用途

// equivalent to (a==null & !(b==null|c==null) | .. | c==null & !(a==null|b==null))
if (nulls(a,b,c) == 1) { .. }


// equivalent to (a==null | b==null | c==null)
if (nulls(a,b,c) >= 1) { .. }


// equivalent to (a!=null | b!=null | c!=null)
if (nulls(a,b,c) < 3) { .. }


// equivalent to (a==null & b==null & c==null)
if (nulls(a,b,c) == 3) { .. }


// equivalent to (a!=null & b!=null & c!=null)
if (nulls(a,b,c) == 0) { .. }

这里有一个相对简单的方法,不涉及任何 Xor og 冗长的 if。但是,它确实要求您稍微更详细一些,但是从好的方面来看,您可以使用我建议的自定义 Exception 来获得更有意义的错误消息。

private void validatePasswordExists(Parameters params) {
if (!params.hasKey("password")){
throw new PasswordMissingException("Password missing");
}
}


private void validateFromExists(Parameters params) {
if (!params.hasKey("from")){
throw new FromEmailMissingException("From-email missing");
}
}


private void validateParams(Parameters params) {


if (params.hasKey("from") || params.hasKey("password")){
validateFromExists(params);
validatePasswordExists(params);
}
}

由于您希望在没有发件人和密码时执行特殊操作(使用默认值) ,所以首先处理这个问题。
之后,您应该同时拥有发送者和密码来发送电子邮件; 如果其中一个缺失,则抛出异常。

// use defaults if neither is provided
if ((from == null) && (password == null)) {
from = DEFAULT_SENDER;
password = DEFAULT_PASSWORD;
}


// we should have a sender and a password now
if (from == null) {
throw new MissingSenderException();
}
if (password == null) {
throw new MissingPasswordException();
}

一个额外的好处是,如果您的任何一个默认值为 null,也将被检测到。


尽管如此,一般来说我认为当 XOR 是您需要的操作符时,应该允许使用它。它是 语言的一部分,而不仅仅是一些因为神秘的编译器错误而起作用的技巧。
我曾经遇到过一个牧牛人,他发现三元操作符太令人困惑而不能使用..。

似乎没有人提到过 三元运算符三元运算符:

if (a==null? b!=null:b==null)

对于检查这个特定的条件很有效,但是不能很好地推广超过两个变量。

令我惊讶的是,没有人提到一个简单的解决方案,即创建一个类的 frompassword字段,并传递一个对该类实例的引用:

class Account {
final String name, password;
Account(String name, String password) {
this.name = Objects.requireNonNull(name, "name");
this.password = Objects.requireNonNull(password, "password");
}
}


// the code that requires an account
Account from;
// do stuff

这里的 from可以是 null 或非 null,如果它是非 null 的,它的两个字段都有非 null 值。

这种方法的一个优点是,在最初获得帐户时触发一个字段而不是另一个字段为空的错误,而不是在使用帐户的代码运行时触发。在执行使用该帐户的代码时,数据不可能无效。

这种方法的另一个优点是更易读,因为它提供了更多的语义信息。另外,您可能需要在其他地方将名称和密码放在一起,因此定义另一个类的成本将分摊到多个用法中。

if ((from == null) == (password == null)){ //if both are true or both are false


}

(来源: Intellij IDEA)