检查Java中的两个参数,要么都不为null要么都优雅地为null


161

我用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");
}

14
顺便提一句,请注意如何谨慎使用空格使代码更易于阅读-仅在当前代码中在运算符之间添加空格会大大增加IMO的可读性。
乔恩·斯基特

8
请定义“优雅”。
雷诺

您需要为SMTP身份验证凭据和信封发件人电子邮件地址提供一组单独的参数。该From电子邮件地址并不总是SMTP验证名称。
卡兹

3
它实际上无法进行更多优化,它是一行可读代码,并且过优化它不会获得任何好处。
diynevala

3
旁注:如果这是Shell脚本,密码是否不会保存在Shell历史记录中?
触摸我的身体

Answers:


331

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

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

if要是只有一个变量是空的条件将是真实的。

但我认为通常最好将两个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");
}

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


152
为什么在两个布尔上的Xor优于!=在两个布尔上的Xor?
埃里克·利珀特

1
哇,我没有意识到布尔值上的XOR与相同!=。Mindblown。在评论中,这是非常高的赞成票数。而且,为增加注释的质量,是的,我还认为针对不同的错误情况提供不同的错误消息会更好,因为用户将更好地了解如何更正错误。
justhalf

1
2个元素就可以了。如何使用> 2个元素执行此操作?
Anushree Acharjee '17年

如果任何元素> 0,我想显示一条错误消息。默认情况下,所有元素都设置为0。如果所有元素> 0,则它是有效的方案。这该怎么做?
Anushree Acharjee '17年

这将是一个不错的面试问题。我想知道有多少程序员知道这一点。但是我不同意它不够清晰,需要将其分成2个if子句-异常中的消息很好地记录了该消息(我编辑了问题,但直到接受后它才会出现)
亚当

286

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

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

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

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

18
您是我的SO英雄@Kaz之一,但没有人说“这或那,但两者都不一样” ^。:-)
大卫·布洛克

6
@DavidBullock:说到布尔值,没有人说“要么这个,要么那个,但不一样”,就像……“不一样”;XOR 超过布尔的“不等于”功能的另一个名称。
哈兹

5
@Kaz只是^说“嘿,我的操作数是布尔值”,而!=事实并非如此。(尽管不幸的是,这种影响由于需要检查“我的操作数可能是数字,并且此时我可能不是关系运算符”而减弱了,我认为这是一个缺点)。尽管我不同意,但您的英雄身份没有减退:-)
David Bullock

3
阅读问题的要求之后,您的解决方案,代码才有意义并易于阅读。但是,作为一个4年的开发人员(仍在学校),阅读此代码然后试图弄清楚什么是“业务需求”将是一件令人头疼的事情。从这段代码中,我不会立即理解“ from并且password必须都为null或都不为null”。也许这只是我和我的经验不足,但我喜欢@ stig-hemmer的答案中更具可读性的解决方案,即使它花费了另外三行代码。我想我只是不会马上得到 bool != bool -这不是直观的。
克里斯·西里菲斯

2
@DavidBullock ^运算符是按位运算符;它实际上并不意味着其操作数是布尔值。布尔xor运算符为xor
Brilliand

222

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

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

52
+1以获得更好的消息。这重要,没有人喜欢挥舞“出事了”的错误消息,因此没有人应该引起这种消息。但是,在实践中,人们应该更喜欢一个更具体的例外情况(尤其IllegalArgumentException是一个裸机而不是裸机RuntimeException
Marco13'1

6
@matt似乎您的批评不是与代码有关,而是与异常文本有关。但是我认为这个答案的优点在于if语句的结构,而不是错误消息的内容。这是最好的答案,因为它改进了功能。OP可以轻松地将自己喜欢的任何字符串替换为异常文本。
丹·亨德森

4
@matt的优点是可以区分两个无效状态中的哪个已发生,并自定义错误消息。因此,您可以说“您做了这件事”或“您做了那件事”,而不是说“您做错了这两件事之一”(然后根据需要继续提供解决步骤)。在两种情况下,分辨率可能都相同,但是说“您做错了其中一件事情”永远不是一个好主意。资料来源:在无法提供此功能的环境中,我回答了许多满足我错误文本中第一个条件的用户提出的问题。
丹·亨德森

4
@DanHenderson>说“您做错了其中一件事情”从来不是一个好主意。我不同意。密码/用户名,只说用户名和密码不匹配是比较安全的。
马特

2
从安全角度来看,@ DanHenderson 最好不要区分用户名是否在目录中,否则,攻击者可能会找到有效的用户名。但是,在这种情况下,混合null / not null始终是使用错误,并且显示更详细的错误消息不会泄漏比最初提供的用户更多的信息。
siegi '16

16

将该功能放入带有签名的2参数方法中:

void assertBothNullOrBothNotNull(Object a, Object b) throws RuntimeException

这样可以节省您感兴趣的实际方法的空间,并使其更具可读性。稍微冗长的方法名称没有什么问题,而非常简短的方法也没有什么问题。


14
没有节省空间((from == null) != (password == null)),这也很容易理解。没有有用的方法存在问题。
edc65 '16

3
if语句有一行,throw语句有第二行,右括号是第三行:全部替换为一行。如果给右括号加上新的一行,则可以节省多一行!
Traubenfuchs

10
在阅读代码时,您只有一种方法名称,您需要了解vs条件处理。
Traubenfuchs

1
肯定为+1,总是喜欢使用描述性方法和变量名称(使INTENTION清晰)抽象出简单的实现细节和运算符。逻辑包含错误。评论甚至更糟。方法名称显示意图并隔离逻辑,因此更容易查找和修复错误。该解决方案完全不需要读者知道或考虑任何语法或逻辑,它使我们可以专注于业务需求(这才是真正重要的)
sara 2016年

1
如果您想在此处有一些描述性异常消息,则会遇到不必要的麻烦。
Honza Brabec

11

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

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

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


6
不喜欢 from == null != password == null将所有内容保留在堆栈的同一帧上,但是Objects.isNull(Object)不必要地使用推和弹出两个帧。之所以存在Objects.isNull(Object),是因为“存在此方法以用作谓词”(即,在流中)。
大卫·布洛克

5
JIT通常会快速内联这样的简单方法,因此对性能的影响很可能是可以忽略的。我们的确可以争论Objects.isNull()-您可以根据需要编写自己的语言-但就可读性而言,我认为使用isNull()效果更好。此外,您还需要其他括号才能使简单表达式编译:from == null != (password == null)
Didier L

2
我同意JIT'ing(以及操作顺序...我很懒)。尽管如此,(val == null)还是提供很多与null进行比较的工具,我发现很难克服两个大的胖方法调用,即使该方法功能强大,可内联且功能完善,命名。不过那只是我。我最近决定我很温和。
大卫·布洛克

4
老实说,谁在乎单个堆栈框架?仅当您要处理(可能是无限的)递归,或者您有一个1000层级的应用程序且对象图具有撒哈拉沙漠的大小时,堆栈才成为问题。
萨拉

9

这是任意数量的空检查的通用解决方案

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) { .. }

2
好的方法,但是您的第一个“等效”评论是非常错误的(除了所有评论中存在的拼写错误)
Ben Voigt

@BenVoigt谢谢您的通知,现在已修复
Khaled.K,2016年

9

由于您想在没有发送者和密码的情况下做一些特殊的事情(使用默认设置),因此请先进行处理。
在那之后,您应该既有发送者又有密码来发送电子邮件。如果缺少任何一个,则引发异常。

// 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();
}

另一个好处是,如果您的默认值都为空,则也会检测到。


话虽如此,总的来说,我认为当您需要运算符时,应允许使用XOR。它语言的一部分,不仅是由于奥秘的编译器错误而起作用的技巧。
我曾经有一个牛-叫者,发现三元运算符太令人困惑了,无法使用...


1
投票的原因是我随机抽取了您的答案之一,但找不到承诺的xkcd链接!你太无耻了!
dhein

@Zaibis在我的辩护中,这只是业余爱好,不是全职工作。但我看看是否能找到一个...
SQB

8

我想提出另一种选择,那就是我实际上如何编写这段代码:

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

对我来说,这似乎是表达这种状况的最自然的方式,这使将来可能需要阅读的人容易理解。它还允许您提供更有用的诊断消息,并将不必要的密码视为不太严重的密码,并且可以轻松地修改这种情况的可能性。即,您可能会发现,将密码作为命令行参数不是最好的主意,并且如果该参数缺失,则可能希望允许从标准输入中读取密码。或者,您可能想静默地忽略多余的password参数。这样的更改不需要您重写整个过程。

除此之外,它仅执行最少数量的比较,因此它并不比更“优雅”的选择昂贵。尽管性能不太可能出现问题,因为启动新进程已经比额外的空检查昂贵得多。


我通过对话进行了评论,因此我可能会在后面加上“ //我们有一个null from”。该示例的简短性使得这一点确实微不足道。我喜欢逻辑的清晰性和减少测试的次数。
Nate

这可以很好地工作,但是嵌套if语句对我来说似乎不太清楚,更混乱。不过,这只是个人观点,因此我很高兴在此选择另一个答案
凯文·韦尔斯

就优雅而言,这可能是最不优雅的方法之一。
dramzy

7

我认为处理此问题的正确方法是考虑三种情况:既提供了“ 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语句可能是:

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"
);

2
该代码是错误的,不是因为它不起作用,而是因为它很难理解。调试别人编写的这种代码只是一场噩梦。
NO_NAME 2016年

2
@NO_NAME我不明白为什么很难理解。OP提供了三种情况:当同时提供了表单和密码时,没有提供两者时,以及混合情况应引发异常。您是指中间条件来检查它们是否都为null吗?
马特

2
我同意@NO_NAME的观点,即中间的情况需要太多的解析才能浏览。除非您解析顶行,否则您不会得到null与中间内容有关。在这种情况下,from和password的相等实际上只是不为null的副作用。
jimm101 '16

1
@ jimm101是的,我可以看到这是一个问题。对于更冗长的解决方案,性能收益将是最小的/无法检测到。我不确定这是否是问题所在。病态更新。
马特

2
@matt可能没有性能上的好处-在这种情况下,尚不清楚编译器将执行什么操作以及芯片将从内存中提取什么。大量的程序员周期可以花在优化上,而实际上并没有产生任何效果。
jimm101 '16

6

这是一种相对简单的方法,不涉及任何Xor或冗长的ifs。但是,它确实需要您稍微冗长一些,但是从好的方面来说,您可以使用我建议的自定义异常来获取更有意义的错误消息。

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);
  }
}

1
您不应使用异常代替控制流语句。除了影响性能外,更重要的是,它还会破坏您的思维流程,从而降低代码的可读性。

1
@anphu不,不。使用异常可以消除if-else子句,并使代码流程更加清晰,从而提高代码的可读性。 docs.oracle.com/javase/tutorial/essential/exceptions/…–
Arnab Datta

1
我认为您将真正与众不同的事情与日常发生的事情混淆了,可以将其视为正常执行的一部分。Java文档使用了一些异常的示例,例如在读取文件时内存不足。遇到无效的参数并不是例外。“不要将异常用于正常的控制流程。” - blogs.msdn.com/b/kcwalina/archive/2005/03/16/396787.aspx
安富

好吧,首先:如果缺少/正确的参数不是例外,那么为什么IllegalArgumentException甚至存在呢?第二:如果您真的认为无效参数不是例外,那么也不应对此进行验证。换句话说,只要尝试执行操作,然后在出现问题时抛出异常即可。这种方法很好,但是您正在谈论的性能损失是在这里引起的,而不是我建议的方法。抛出Java异常并不慢。这是需要时间才能恢复的stacktrace。
Arnab Datta 2016年

上下文是关键。在OP的上下文中(发送邮件应用程序),通常会忘记用户名和密码。在导弹制导算法中,空坐标参数应该引发异常。我没有说不验证参数。在OP的上下文中,我说过不要使用异常来驱动应用程序逻辑。展开stacktrace是我所谈论的perf hit。使用您的方法,最终您要么抓住了PasswordMissingException / FromEmailMissingException,即放松,或者更糟糕的是,让它不予处理。。
安富

6

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

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

对于检查此特定条件非常有效,但不能很好地概括两个变量。


1
看起来不错,但更难理解比^!=有两个布尔变量或两个if小号
COOLGUY

@coolguy我的猜测是,三元运算符比XOR运算符更常见(更不用说每次在未执行位运算的代码中看到XOR时都会出现“精神上的意外”),这种表达方式倾向于避免割伤并粘贴困扰双倍if的错误。
gbronner'1

不建议。这不会区分(a!= null && b == null)和(a == null && b!= null)。如果要使用三元运算符:a == null ? (b == null? "both null" : "a null while b is not") : (b ==null? "b null while a is not")
Arnab Datta 2016年

5

如我所见,您不必总是检查两个排他的password无效性,而仅当from不为null 时才检查是否为null。您可以忽略给定的password参数,如果from为null ,则使用您自己的默认值。

用伪写的必须是这样的:

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
}

4
唯一的问题是,当用户提供时password不提供时from,他们可能打算覆盖密码,但保留默认帐户。如果用户这样做,则该指令无效,应告知他们。该计划应该不会继续作为该输入是有效的,并继续前进,并尝试使用与用户没有指定密码的默认帐户。我对这样的节目发誓。
大卫·布洛克

4

我很惊讶,没有人提到制作的简单的解决方案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值。

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

这种方法的另一个优点是可读性更高,因为它提供了更多的语义信息。同样,您可能在其他地方同时要求输入名称和密码,因此定义附加类的成本将因多次使用而摊销。


它没有按预期方式工作,因为new Account(null, null)即使具有空名称和密码的帐户仍然有效,也会抛出NPE。
HieuHT

想法是,如果名称和密码为空,则传递空,无论在现实世界中您是否会说该帐户存在。
恢复莫妮卡

我从OP中得到的是 ,要么都是非null,要么都是null。我只是想知道如何创建名称和密码均为null的Account对象?
HieuHT

@HieuHT对不起,我的最后评论不清楚。如果名称和密码为null,则应使用null代替Account。您不会传递nullAccount构造函数。
恢复莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.